Fix autowithdrawal logs (#1073)
* Also log autowithdrawal routing errors * Only log autowithdrawal success in worker * Use WalletType for WalletLog.wallet * Fix autowithdrawal success message * Infer walletName from walletType in upsertWallet
This commit is contained in:
parent
c19c9124ec
commit
e30dfbae57
|
@ -437,12 +437,11 @@ export default {
|
||||||
data.macaroon = ensureB64(data.macaroon)
|
data.macaroon = ensureB64(data.macaroon)
|
||||||
data.cert = ensureB64(data.cert)
|
data.cert = ensureB64(data.cert)
|
||||||
|
|
||||||
const wallet = 'walletLND'
|
const walletType = 'LND'
|
||||||
return await upsertWallet(
|
return await upsertWallet(
|
||||||
{
|
{
|
||||||
schema: LNDAutowithdrawSchema,
|
schema: LNDAutowithdrawSchema,
|
||||||
walletName: wallet,
|
walletType,
|
||||||
walletType: 'LND',
|
|
||||||
testConnect: async ({ cert, macaroon, socket }) => {
|
testConnect: async ({ cert, macaroon, socket }) => {
|
||||||
try {
|
try {
|
||||||
const { lnd } = await authenticatedLndGrpc({
|
const { lnd } = await authenticatedLndGrpc({
|
||||||
|
@ -457,12 +456,12 @@ export default {
|
||||||
expires_at: new Date()
|
expires_at: new Date()
|
||||||
})
|
})
|
||||||
// we wrap both calls in one try/catch since connection attempts happen on RPC calls
|
// we wrap both calls in one try/catch since connection attempts happen on RPC calls
|
||||||
await addWalletLog({ wallet, level: 'SUCCESS', message: 'connected to LND' }, { me, models })
|
await addWalletLog({ wallet: walletType, level: 'SUCCESS', message: 'connected to LND' }, { me, models })
|
||||||
return inv
|
return inv
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// LND errors are in this shape: [code, type, { err: { code, details, metadata } }]
|
// LND errors are in this shape: [code, type, { err: { code, details, metadata } }]
|
||||||
const details = err[2]?.err?.details || err.message || err.toString?.()
|
const details = err[2]?.err?.details || err.message || err.toString?.()
|
||||||
await addWalletLog({ wallet, level: 'ERROR', message: `could not connect to LND: ${details}` }, { me, models })
|
await addWalletLog({ wallet: walletType, level: 'ERROR', message: `could not connect to LND: ${details}` }, { me, models })
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -472,12 +471,11 @@ export default {
|
||||||
upsertWalletCLN: async (parent, { settings, ...data }, { me, models }) => {
|
upsertWalletCLN: async (parent, { settings, ...data }, { me, models }) => {
|
||||||
data.cert = ensureB64(data.cert)
|
data.cert = ensureB64(data.cert)
|
||||||
|
|
||||||
const wallet = 'walletCLN'
|
const walletType = 'CLN'
|
||||||
return await upsertWallet(
|
return await upsertWallet(
|
||||||
{
|
{
|
||||||
schema: CLNAutowithdrawSchema,
|
schema: CLNAutowithdrawSchema,
|
||||||
walletName: wallet,
|
walletType,
|
||||||
walletType: 'CLN',
|
|
||||||
testConnect: async ({ socket, rune, cert }) => {
|
testConnect: async ({ socket, rune, cert }) => {
|
||||||
try {
|
try {
|
||||||
const inv = await createInvoiceCLN({
|
const inv = await createInvoiceCLN({
|
||||||
|
@ -488,11 +486,11 @@ export default {
|
||||||
msats: 'any',
|
msats: 'any',
|
||||||
expiry: 0
|
expiry: 0
|
||||||
})
|
})
|
||||||
await addWalletLog({ wallet, level: 'SUCCESS', message: 'connected to CLN' }, { me, models })
|
await addWalletLog({ wallet: walletType, level: 'SUCCESS', message: 'connected to CLN' }, { me, models })
|
||||||
return inv
|
return inv
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const details = err.details || err.message || err.toString?.()
|
const details = err.details || err.message || err.toString?.()
|
||||||
await addWalletLog({ wallet, level: 'ERROR', message: `could not connect to CLN: ${details}` }, { me, models })
|
await addWalletLog({ wallet: walletType, level: 'ERROR', message: `could not connect to CLN: ${details}` }, { me, models })
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -500,15 +498,14 @@ export default {
|
||||||
{ settings, data }, { me, models })
|
{ settings, data }, { me, models })
|
||||||
},
|
},
|
||||||
upsertWalletLNAddr: async (parent, { settings, ...data }, { me, models }) => {
|
upsertWalletLNAddr: async (parent, { settings, ...data }, { me, models }) => {
|
||||||
const wallet = 'walletLightningAddress'
|
const walletType = 'LIGHTNING_ADDRESS'
|
||||||
return await upsertWallet(
|
return await upsertWallet(
|
||||||
{
|
{
|
||||||
schema: lnAddrAutowithdrawSchema,
|
schema: lnAddrAutowithdrawSchema,
|
||||||
walletName: wallet,
|
walletType,
|
||||||
walletType: 'LIGHTNING_ADDRESS',
|
|
||||||
testConnect: async ({ address }) => {
|
testConnect: async ({ address }) => {
|
||||||
const options = await lnAddrOptions(address)
|
const options = await lnAddrOptions(address)
|
||||||
await addWalletLog({ wallet, level: 'SUCCESS', message: 'fetched payment details' }, { me, models })
|
await addWalletLog({ wallet: walletType, level: 'SUCCESS', message: 'fetched payment details' }, { me, models })
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -524,19 +521,9 @@ export default {
|
||||||
throw new GraphQLError('wallet not found', { extensions: { code: 'BAD_INPUT' } })
|
throw new GraphQLError('wallet not found', { extensions: { code: 'BAD_INPUT' } })
|
||||||
}
|
}
|
||||||
|
|
||||||
// determine wallet name for logging
|
|
||||||
let walletName = ''
|
|
||||||
if (wallet.type === 'LND') {
|
|
||||||
walletName = 'walletLND'
|
|
||||||
} else if (wallet.type === 'CLN') {
|
|
||||||
walletName = 'walletCLN'
|
|
||||||
} else if (wallet.type === 'LIGHTNING_ADDRESS') {
|
|
||||||
walletName = 'walletLightningAddress'
|
|
||||||
}
|
|
||||||
|
|
||||||
await models.$transaction([
|
await models.$transaction([
|
||||||
models.wallet.delete({ where: { userId: me.id, id: Number(id) } }),
|
models.wallet.delete({ where: { userId: me.id, id: Number(id) } }),
|
||||||
models.walletLog.create({ data: { userId: me.id, wallet: walletName, level: 'SUCCESS', message: 'wallet deleted' } })
|
models.walletLog.create({ data: { userId: me.id, wallet: wallet.type, level: 'SUCCESS', message: 'wallet deleted' } })
|
||||||
])
|
])
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
@ -580,7 +567,7 @@ export const addWalletLog = async ({ wallet, level, message }, { me, models }) =
|
||||||
}
|
}
|
||||||
|
|
||||||
async function upsertWallet (
|
async function upsertWallet (
|
||||||
{ schema, walletName, walletType, testConnect }, { settings, data }, { me, models }) {
|
{ schema, walletType, testConnect }, { settings, data }, { me, models }) {
|
||||||
if (!me) {
|
if (!me) {
|
||||||
throw new GraphQLError('you must be logged in', { extensions: { code: 'UNAUTHENTICATED' } })
|
throw new GraphQLError('you must be logged in', { extensions: { code: 'UNAUTHENTICATED' } })
|
||||||
}
|
}
|
||||||
|
@ -593,7 +580,7 @@ async function upsertWallet (
|
||||||
await testConnect(data)
|
await testConnect(data)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
await addWalletLog({ wallet: walletName, level: 'ERROR', message: 'failed to attach wallet' }, { me, models })
|
await addWalletLog({ wallet: walletType, level: 'ERROR', message: 'failed to attach wallet' }, { me, models })
|
||||||
throw new GraphQLError('failed to connect to wallet', { extensions: { code: 'BAD_INPUT' } })
|
throw new GraphQLError('failed to connect to wallet', { extensions: { code: 'BAD_INPUT' } })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -623,6 +610,9 @@ async function upsertWallet (
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const walletName = walletType === 'LND'
|
||||||
|
? 'walletLND'
|
||||||
|
: walletType === 'CLN' ? 'walletCLN' : 'walletLightningAddress'
|
||||||
if (id) {
|
if (id) {
|
||||||
txs.push(
|
txs.push(
|
||||||
models.wallet.update({
|
models.wallet.update({
|
||||||
|
@ -637,7 +627,7 @@ async function upsertWallet (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
models.walletLog.create({ data: { userId: me.id, wallet: walletName, level: 'SUCCESS', message: 'wallet updated' } })
|
models.walletLog.create({ data: { userId: me.id, wallet: walletType, level: 'SUCCESS', message: 'wallet updated' } })
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
txs.push(
|
txs.push(
|
||||||
|
@ -651,7 +641,7 @@ async function upsertWallet (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
models.walletLog.create({ data: { userId: me.id, wallet: walletName, level: 'SUCCESS', message: 'wallet created' } })
|
models.walletLog.create({ data: { userId: me.id, wallet: walletType, level: 'SUCCESS', message: 'wallet created' } })
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -659,7 +649,7 @@ async function upsertWallet (
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createWithdrawal (parent, { invoice, maxFee }, { me, models, lnd, headers, autoWithdraw = false }) {
|
export async function createWithdrawal (parent, { invoice, maxFee }, { me, models, lnd, headers, walletId = null }) {
|
||||||
assertApiKeyNotPermitted({ me })
|
assertApiKeyNotPermitted({ me })
|
||||||
await ssValidate(withdrawlSchema, { invoice, maxFee })
|
await ssValidate(withdrawlSchema, { invoice, maxFee })
|
||||||
await assertGofacYourself({ models, headers })
|
await assertGofacYourself({ models, headers })
|
||||||
|
@ -698,10 +688,11 @@ export async function createWithdrawal (parent, { invoice, maxFee }, { me, model
|
||||||
|
|
||||||
const user = await models.user.findUnique({ where: { id: me.id } })
|
const user = await models.user.findUnique({ where: { id: me.id } })
|
||||||
|
|
||||||
|
const autoWithdraw = !!walletId
|
||||||
// create withdrawl transactionally (id, bolt11, amount, fee)
|
// create withdrawl transactionally (id, bolt11, amount, fee)
|
||||||
const [withdrawl] = await serialize(
|
const [withdrawl] = await serialize(
|
||||||
models.$queryRaw`SELECT * FROM create_withdrawl(${decoded.id}, ${invoice},
|
models.$queryRaw`SELECT * FROM create_withdrawl(${decoded.id}, ${invoice},
|
||||||
${Number(decoded.mtokens)}, ${msatsFee}, ${user.name}, ${autoWithdraw})`,
|
${Number(decoded.mtokens)}, ${msatsFee}, ${user.name}, ${autoWithdraw}, ${walletId}::INTEGER)`,
|
||||||
{ models }
|
{ models }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -158,9 +158,17 @@ const initIndexedDB = async (storeName) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const renameWallet = (wallet) => {
|
const renameWallet = (wallet) => {
|
||||||
if (wallet === 'walletLightningAddress') return 'lnAddr'
|
switch (wallet) {
|
||||||
if (wallet === 'walletLND') return 'lnd'
|
case 'walletLightningAddress':
|
||||||
if (wallet === 'walletCLN') return 'cln'
|
case 'LIGHTNING_ADDRESS':
|
||||||
|
return 'lnAddr'
|
||||||
|
case 'walletLND':
|
||||||
|
case 'LND':
|
||||||
|
return 'lnd'
|
||||||
|
case 'walletCLN':
|
||||||
|
case 'CLN':
|
||||||
|
return 'cln'
|
||||||
|
}
|
||||||
return wallet
|
return wallet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Withdrawl" ADD COLUMN "walletId" INTEGER;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Withdrawl" ADD CONSTRAINT "Withdrawl_walletId_fkey" FOREIGN KEY ("walletId") REFERENCES "Wallet"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION create_withdrawl(lnd_id TEXT, invoice TEXT, msats_amount BIGINT, msats_max_fee BIGINT, username TEXT, auto_withdraw BOOLEAN, wallet_id INTEGER)
|
||||||
|
RETURNS "Withdrawl"
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
user_id INTEGER;
|
||||||
|
user_msats BIGINT;
|
||||||
|
withdrawl "Withdrawl";
|
||||||
|
BEGIN
|
||||||
|
PERFORM ASSERT_SERIALIZED();
|
||||||
|
|
||||||
|
SELECT msats, id INTO user_msats, user_id FROM users WHERE name = username;
|
||||||
|
IF (msats_amount + msats_max_fee) > user_msats THEN
|
||||||
|
RAISE EXCEPTION 'SN_INSUFFICIENT_FUNDS';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM "Withdrawl" WHERE hash = lnd_id AND status IS NULL) THEN
|
||||||
|
RAISE EXCEPTION 'SN_PENDING_WITHDRAWL_EXISTS';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM "Withdrawl" WHERE hash = lnd_id AND status = 'CONFIRMED') THEN
|
||||||
|
RAISE EXCEPTION 'SN_CONFIRMED_WITHDRAWL_EXISTS';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
INSERT INTO "Withdrawl" (hash, bolt11, "msatsPaying", "msatsFeePaying", "userId", "autoWithdraw", "walletId", created_at, updated_at)
|
||||||
|
VALUES (lnd_id, invoice, msats_amount, msats_max_fee, user_id, auto_withdraw, wallet_id, now_utc(), now_utc()) RETURNING * INTO withdrawl;
|
||||||
|
|
||||||
|
UPDATE users SET msats = msats - msats_amount - msats_max_fee WHERE id = user_id;
|
||||||
|
|
||||||
|
RETURN withdrawl;
|
||||||
|
END;
|
||||||
|
$$;
|
|
@ -156,6 +156,7 @@ model Wallet {
|
||||||
walletLightningAddress WalletLightningAddress?
|
walletLightningAddress WalletLightningAddress?
|
||||||
walletLND WalletLND?
|
walletLND WalletLND?
|
||||||
walletCLN WalletCLN?
|
walletCLN WalletCLN?
|
||||||
|
withdrawals Withdrawl[]
|
||||||
|
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
}
|
}
|
||||||
|
@ -694,7 +695,9 @@ model Withdrawl {
|
||||||
msatsFeePaid BigInt?
|
msatsFeePaid BigInt?
|
||||||
status WithdrawlStatus?
|
status WithdrawlStatus?
|
||||||
autoWithdraw Boolean @default(false)
|
autoWithdraw Boolean @default(false)
|
||||||
|
walletId Int?
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
wallet Wallet? @relation(fields: [walletId], references: [id], onDelete: SetNull)
|
||||||
|
|
||||||
@@index([createdAt], map: "Withdrawl.created_at_index")
|
@@index([createdAt], map: "Withdrawl.created_at_index")
|
||||||
@@index([userId], map: "Withdrawl.userId_index")
|
@@index([userId], map: "Withdrawl.userId_index")
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { authenticatedLndGrpc, createInvoice } from 'ln-service'
|
import { authenticatedLndGrpc, createInvoice } from 'ln-service'
|
||||||
import { msatsToSats, numWithUnits, satsToMsats } from '@/lib/format'
|
import { msatsToSats, satsToMsats } from '@/lib/format'
|
||||||
import { datePivot } from '@/lib/time'
|
import { datePivot } from '@/lib/time'
|
||||||
import { createWithdrawal, sendToLnAddr, addWalletLog } from '@/api/resolvers/wallet'
|
import { createWithdrawal, sendToLnAddr, addWalletLog } from '@/api/resolvers/wallet'
|
||||||
import { createInvoice as createInvoiceCLN } from '@/lib/cln'
|
import { createInvoice as createInvoiceCLN } from '@/lib/cln'
|
||||||
|
@ -46,34 +46,18 @@ export async function autoWithdraw ({ data: { id }, models, lnd }) {
|
||||||
|
|
||||||
for (const wallet of wallets) {
|
for (const wallet of wallets) {
|
||||||
try {
|
try {
|
||||||
const message = `autowithdrawal of ${numWithUnits(amount, { abbreviate: false, unitSingular: 'sat', unitPlural: 'sats' })}`
|
|
||||||
if (wallet.type === 'LND') {
|
if (wallet.type === 'LND') {
|
||||||
await autowithdrawLND(
|
await autowithdrawLND(
|
||||||
{ amount, maxFee },
|
{ amount, maxFee },
|
||||||
{ models, me: user, lnd })
|
{ models, me: user, lnd })
|
||||||
await addWalletLog({
|
|
||||||
wallet: 'walletLND',
|
|
||||||
level: 'SUCCESS',
|
|
||||||
message
|
|
||||||
}, { me: user, models })
|
|
||||||
} else if (wallet.type === 'CLN') {
|
} else if (wallet.type === 'CLN') {
|
||||||
await autowithdrawCLN(
|
await autowithdrawCLN(
|
||||||
{ amount, maxFee },
|
{ amount, maxFee },
|
||||||
{ models, me: user, lnd })
|
{ models, me: user, lnd })
|
||||||
await addWalletLog({
|
|
||||||
wallet: 'walletCLN',
|
|
||||||
level: 'SUCCESS',
|
|
||||||
message
|
|
||||||
}, { me: user, models })
|
|
||||||
} else if (wallet.type === 'LIGHTNING_ADDRESS') {
|
} else if (wallet.type === 'LIGHTNING_ADDRESS') {
|
||||||
await autowithdrawLNAddr(
|
await autowithdrawLNAddr(
|
||||||
{ amount, maxFee },
|
{ amount, maxFee },
|
||||||
{ models, me: user, lnd })
|
{ models, me: user, lnd })
|
||||||
await addWalletLog({
|
|
||||||
wallet: 'walletLightningAddress',
|
|
||||||
level: 'SUCCESS',
|
|
||||||
message
|
|
||||||
}, { me: user, models })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -82,9 +66,7 @@ export async function autoWithdraw ({ data: { id }, models, lnd }) {
|
||||||
// LND errors are in this shape: [code, type, { err: { code, details, metadata } }]
|
// LND errors are in this shape: [code, type, { err: { code, details, metadata } }]
|
||||||
const details = error[2]?.err?.details || error.message || error.toString?.()
|
const details = error[2]?.err?.details || error.message || error.toString?.()
|
||||||
await addWalletLog({
|
await addWalletLog({
|
||||||
wallet: wallet.type === 'LND'
|
wallet: wallet.type,
|
||||||
? 'walletLND'
|
|
||||||
: wallet.type === 'CLN' ? 'walletCLN' : 'walletLightningAddress',
|
|
||||||
level: 'ERROR',
|
level: 'ERROR',
|
||||||
message: 'autowithdrawal failed: ' + details
|
message: 'autowithdrawal failed: ' + details
|
||||||
}, { me: user, models })
|
}, { me: user, models })
|
||||||
|
@ -116,7 +98,7 @@ async function autowithdrawLNAddr (
|
||||||
}
|
}
|
||||||
|
|
||||||
const { walletLightningAddress: { address } } = wallet
|
const { walletLightningAddress: { address } } = wallet
|
||||||
return await sendToLnAddr(null, { addr: address, amount, maxFee }, { me, models, lnd, autoWithdraw: true })
|
return await sendToLnAddr(null, { addr: address, amount, maxFee }, { me, models, lnd, walletId: wallet.id })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function autowithdrawLND ({ amount, maxFee }, { me, models, lnd }) {
|
async function autowithdrawLND ({ amount, maxFee }, { me, models, lnd }) {
|
||||||
|
@ -152,7 +134,7 @@ async function autowithdrawLND ({ amount, maxFee }, { me, models, lnd }) {
|
||||||
expires_at: datePivot(new Date(), { seconds: 360 })
|
expires_at: datePivot(new Date(), { seconds: 360 })
|
||||||
})
|
})
|
||||||
|
|
||||||
return await createWithdrawal(null, { invoice: invoice.request, maxFee }, { me, models, lnd, autoWithdraw: true })
|
return await createWithdrawal(null, { invoice: invoice.request, maxFee }, { me, models, lnd, walletId: wallet.id })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function autowithdrawCLN ({ amount, maxFee }, { me, models, lnd }) {
|
async function autowithdrawCLN ({ amount, maxFee }, { me, models, lnd }) {
|
||||||
|
@ -185,5 +167,5 @@ async function autowithdrawCLN ({ amount, maxFee }, { me, models, lnd }) {
|
||||||
expiry: 360
|
expiry: 360
|
||||||
})
|
})
|
||||||
|
|
||||||
return await createWithdrawal(null, { invoice: inv.bolt11, maxFee }, { me, models, lnd, autoWithdraw: true })
|
return await createWithdrawal(null, { invoice: inv.bolt11, maxFee }, { me, models, lnd, walletId: wallet.id })
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ import { notifyDeposit, notifyWithdrawal } from '@/lib/webPush'
|
||||||
import { INVOICE_RETENTION_DAYS } from '@/lib/constants'
|
import { INVOICE_RETENTION_DAYS } from '@/lib/constants'
|
||||||
import { datePivot, sleep } from '@/lib/time.js'
|
import { datePivot, sleep } from '@/lib/time.js'
|
||||||
import retry from 'async-retry'
|
import retry from 'async-retry'
|
||||||
|
import { addWalletLog } from '@/api/resolvers/wallet'
|
||||||
|
import { msatsToSats, numWithUnits } from '@/lib/format'
|
||||||
|
|
||||||
export async function subscribeToWallet (args) {
|
export async function subscribeToWallet (args) {
|
||||||
await subscribeToDeposits(args)
|
await subscribeToDeposits(args)
|
||||||
|
@ -205,7 +207,7 @@ async function subscribeToWithdrawals (args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkWithdrawal ({ data: { hash }, boss, models, lnd }) {
|
async function checkWithdrawal ({ data: { hash }, boss, models, lnd }) {
|
||||||
const dbWdrwl = await models.withdrawl.findFirst({ where: { hash, status: null } })
|
const dbWdrwl = await models.withdrawl.findFirst({ where: { hash, status: null }, include: { wallet: true } })
|
||||||
if (!dbWdrwl) {
|
if (!dbWdrwl) {
|
||||||
// [WARNING] LND paid an invoice that wasn't created via the SN GraphQL API.
|
// [WARNING] LND paid an invoice that wasn't created via the SN GraphQL API.
|
||||||
// >>> an adversary might be draining our funds right now <<<
|
// >>> an adversary might be draining our funds right now <<<
|
||||||
|
@ -237,16 +239,25 @@ async function checkWithdrawal ({ data: { hash }, boss, models, lnd }) {
|
||||||
if (code === 0) {
|
if (code === 0) {
|
||||||
notifyWithdrawal(dbWdrwl.userId, wdrwl)
|
notifyWithdrawal(dbWdrwl.userId, wdrwl)
|
||||||
}
|
}
|
||||||
|
if (dbWdrwl.wallet) {
|
||||||
|
// this was an autowithdrawal
|
||||||
|
const message = `autowithdrawal of ${numWithUnits(msatsToSats(paid), { abbreviate: false })} with ${numWithUnits(msatsToSats(fee), { abbreviate: false })} as fee`
|
||||||
|
await addWalletLog({ wallet: dbWdrwl.wallet.type, level: 'SUCCESS', message }, { models, me: { id: dbWdrwl.userId } })
|
||||||
|
}
|
||||||
} else if (wdrwl?.is_failed || notFound) {
|
} else if (wdrwl?.is_failed || notFound) {
|
||||||
let status = 'UNKNOWN_FAILURE'
|
let status = 'UNKNOWN_FAILURE'; let message = 'unknown failure'
|
||||||
if (wdrwl?.failed.is_insufficient_balance) {
|
if (wdrwl?.failed.is_insufficient_balance) {
|
||||||
status = 'INSUFFICIENT_BALANCE'
|
status = 'INSUFFICIENT_BALANCE'
|
||||||
|
message = "you didn't have enough sats"
|
||||||
} else if (wdrwl?.failed.is_invalid_payment) {
|
} else if (wdrwl?.failed.is_invalid_payment) {
|
||||||
status = 'INVALID_PAYMENT'
|
status = 'INVALID_PAYMENT'
|
||||||
|
message = 'invalid payment'
|
||||||
} else if (wdrwl?.failed.is_pathfinding_timeout) {
|
} else if (wdrwl?.failed.is_pathfinding_timeout) {
|
||||||
status = 'PATHFINDING_TIMEOUT'
|
status = 'PATHFINDING_TIMEOUT'
|
||||||
|
message = 'no route found'
|
||||||
} else if (wdrwl?.failed.is_route_not_found) {
|
} else if (wdrwl?.failed.is_route_not_found) {
|
||||||
status = 'ROUTE_NOT_FOUND'
|
status = 'ROUTE_NOT_FOUND'
|
||||||
|
message = 'no route found'
|
||||||
}
|
}
|
||||||
|
|
||||||
await serialize(
|
await serialize(
|
||||||
|
@ -254,6 +265,15 @@ async function checkWithdrawal ({ data: { hash }, boss, models, lnd }) {
|
||||||
SELECT reverse_withdrawl(${dbWdrwl.id}::INTEGER, ${status}::"WithdrawlStatus")`,
|
SELECT reverse_withdrawl(${dbWdrwl.id}::INTEGER, ${status}::"WithdrawlStatus")`,
|
||||||
{ models }
|
{ models }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (dbWdrwl.wallet) {
|
||||||
|
// add error into log for autowithdrawal
|
||||||
|
addWalletLog({
|
||||||
|
wallet: dbWdrwl.wallet.type,
|
||||||
|
level: 'ERROR',
|
||||||
|
message: 'autowithdrawal failed: ' + message
|
||||||
|
}, { models, me: { id: dbWdrwl.userId } })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue