stacker.news/worker/autowithdraw.js

127 lines
3.4 KiB
JavaScript

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 { walletLightningAddress: { 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 })
}