import { authenticatedLndGrpc, createInvoice } from 'ln-service' import { msatsToSats, satsToMsats } from '../lib/format' import { datePivot } from '../lib/time' import { createWithdrawal, sendToLnAddr } from '../api/resolvers/wallet' export async function autoWithdraw ({ data: { id }, models, lnd }) { const user = await models.user.findUnique({ where: { id } }) if (user.autoWithdrawThreshold === null || user.autoWithdrawMaxFeePercent === null) return const threshold = satsToMsats(user.autoWithdrawThreshold) const excess = Number(user.msats - threshold) // excess must be greater than 10% of threshold if (excess < Number(threshold) * 0.1) return const maxFee = msatsToSats(Math.ceil(excess * (user.autoWithdrawMaxFeePercent / 100.0))) const amount = msatsToSats(excess) - maxFee // must be >= 1 sat if (amount < 1) return // check that // 1. the user doesn't have an autowithdraw pending // 2. we have not already attempted to autowithdraw this fee recently const [pendingOrFailed] = await models.$queryRaw` SELECT EXISTS( SELECT * FROM "Withdrawl" WHERE "userId" = ${id} AND "autoWithdraw" AND (status IS NULL OR ( status <> 'CONFIRMED' AND now() < created_at + interval '1 hour' AND "msatsFeePaying" >= ${satsToMsats(maxFee)} )) )` if (pendingOrFailed.exists) return // get the wallets in order of priority const wallets = await models.wallet.findMany({ where: { userId: user.id }, orderBy: { priority: 'desc' } }) for (const wallet of wallets) { try { if (wallet.type === 'LND') { await autowithdrawLND( { amount, maxFee }, { models, me: user, lnd }) } else if (wallet.type === 'LIGHTNING_ADDRESS') { await autowithdrawLNAddr( { amount, maxFee }, { models, me: user, lnd }) } return } catch (error) { console.error(error) } } // none of the wallets worked } async function autowithdrawLNAddr ( { amount, maxFee }, { me, models, lnd, headers, autoWithdraw = false }) { if (!me) { throw new Error('me not specified') } const wallet = await models.wallet.findFirst({ where: { userId: me.id, type: 'LIGHTNING_ADDRESS' }, include: { walletLightningAddress: true } }) if (!wallet || !wallet.walletLightningAddress) { throw new Error('no lightning address wallet found') } const { walletLND: { address } } = wallet return await sendToLnAddr(null, { addr: address, amount, maxFee }, { me, models, lnd, autoWithdraw: true }) } async function autowithdrawLND ({ amount, maxFee }, { me, models, lnd }) { if (!me) { throw new Error('me not specified') } const wallet = await models.wallet.findFirst({ where: { userId: me.id, type: 'LND' }, include: { walletLND: true } }) if (!wallet || !wallet.walletLND) { throw new Error('no lightning address wallet found') } const { walletLND: { cert, macaroon, socket } } = wallet const { lnd: lndOut } = await authenticatedLndGrpc({ cert, macaroon, socket }) const invoice = await createInvoice({ description: me.hideInvoiceDesc ? undefined : 'autowithdraw to LND from SN', lnd: lndOut, tokens: amount, expires_at: datePivot(new Date(), { seconds: 360 }) }) return await createWithdrawal(null, { invoice: invoice.request, maxFee }, { me, models, lnd, autoWithdraw: true }) }