Fix receiver fallback on caller error (#1907)

* Rename to createUserInvoice

* Fix no receiver fallback on wrap, direct or autowithdrawal error

* Fix missing error logs for direct payments
This commit is contained in:
ekzyis 2025-02-15 03:01:14 +01:00 committed by GitHub
parent 0032e064b2
commit 5e85147578
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 76 additions and 65 deletions

View File

@ -3,7 +3,7 @@ import { datePivot } from '@/lib/time'
import { PAID_ACTION_PAYMENT_METHODS, USER_ID } from '@/lib/constants' import { PAID_ACTION_PAYMENT_METHODS, USER_ID } from '@/lib/constants'
import { createHmac } from '@/api/resolvers/wallet' import { createHmac } from '@/api/resolvers/wallet'
import { Prisma } from '@prisma/client' import { Prisma } from '@prisma/client'
import { createWrappedInvoice, createInvoice as createUserInvoice } from '@/wallets/server' import { createWrappedInvoice, createUserInvoice } from '@/wallets/server'
import { assertBelowMaxPendingInvoices, assertBelowMaxPendingDirectPayments } from './lib/assert' import { assertBelowMaxPendingInvoices, assertBelowMaxPendingDirectPayments } from './lib/assert'
import * as ITEM_CREATE from './itemCreate' import * as ITEM_CREATE from './itemCreate'
@ -264,26 +264,28 @@ async function performDirectAction (actionType, args, incomingContext) {
throw new NonInvoiceablePeerError() throw new NonInvoiceablePeerError()
} }
let invoiceObject
try { try {
await assertBelowMaxPendingDirectPayments(userId, incomingContext) await assertBelowMaxPendingDirectPayments(userId, incomingContext)
const description = actionDescription ?? await paidActions[actionType].describe(args, incomingContext) const description = actionDescription ?? await paidActions[actionType].describe(args, incomingContext)
invoiceObject = await createUserInvoice(userId, {
for await (const { invoice, logger, wallet } of createUserInvoice(userId, {
msats: cost, msats: cost,
description, description,
expiry: INVOICE_EXPIRE_SECS expiry: INVOICE_EXPIRE_SECS
}, { models, lnd }) }, { models, lnd })) {
let hash
try {
hash = parsePaymentRequest({ request: invoice }).id
} catch (e) { } catch (e) {
console.error('failed to create outside invoice', e) console.error('failed to parse invoice', e)
throw new NonInvoiceablePeerError() logger?.error('failed to parse invoice: ' + e.message, { bolt11: invoice })
continue
} }
const { invoice, wallet } = invoiceObject try {
const hash = parsePaymentRequest({ request: invoice }).id return {
invoice: await models.directPayment.create({
const payment = await models.directPayment.create({
data: { data: {
comment, comment,
lud18Data, lud18Data,
@ -294,12 +296,19 @@ async function performDirectAction (actionType, args, incomingContext) {
walletId: wallet.id, walletId: wallet.id,
receiverId: userId receiverId: userId
} }
}) }),
return {
invoice: payment,
paymentMethod: 'DIRECT' paymentMethod: 'DIRECT'
} }
} catch (e) {
console.error('failed to create direct payment', e)
logger?.error('failed to create direct payment: ' + e.message, { bolt11: invoice })
}
}
} catch (e) {
console.error('failed to create user invoice', e)
}
throw new NonInvoiceablePeerError()
} }
export async function retryPaidAction (actionType, args, incomingContext) { export async function retryPaidAction (actionType, args, incomingContext) {

View File

@ -24,7 +24,7 @@ export default [lnd, cln, lnAddr, lnbits, nwc, phoenixd, blink, lnc, webln]
const MAX_PENDING_INVOICES_PER_WALLET = 25 const MAX_PENDING_INVOICES_PER_WALLET = 25
export async function createInvoice (userId, { msats, description, descriptionHash, expiry = 360 }, { paymentAttempt, predecessorId, models }) { export async function * createUserInvoice (userId, { msats, description, descriptionHash, expiry = 360 }, { paymentAttempt, predecessorId, models }) {
// get the wallets in order of priority // get the wallets in order of priority
const wallets = await getInvoiceableWallets(userId, { const wallets = await getInvoiceableWallets(userId, {
paymentAttempt, paymentAttempt,
@ -72,34 +72,29 @@ export async function createInvoice (userId, { msats, description, descriptionHa
} }
} }
return { invoice, wallet, logger } yield { invoice, wallet, logger }
} catch (err) { } catch (err) {
console.error('failed to create user invoice:', err)
logger.error(err.message, { status: true }) logger.error(err.message, { status: true })
} }
} }
throw new Error('no wallet to receive available')
} }
export async function createWrappedInvoice (userId, export async function createWrappedInvoice (userId,
{ msats, feePercent, description, descriptionHash, expiry = 360 }, { msats, feePercent, description, descriptionHash, expiry = 360 },
{ paymentAttempt, predecessorId, models, me, lnd }) { { paymentAttempt, predecessorId, models, me, lnd }) {
let logger, bolt11 // loop over all receiver wallet invoices until we successfully wrapped one
try { for await (const { invoice, logger, wallet } of createUserInvoice(userId, {
const { invoice, wallet } = await createInvoice(userId, {
// this is the amount the stacker will receive, the other (feePercent)% is our fee // this is the amount the stacker will receive, the other (feePercent)% is our fee
msats: toPositiveBigInt(msats) * (100n - feePercent) / 100n, msats: toPositiveBigInt(msats) * (100n - feePercent) / 100n,
description, description,
descriptionHash, descriptionHash,
expiry expiry
}, { paymentAttempt, predecessorId, models }) }, { paymentAttempt, predecessorId, models })) {
let bolt11
logger = walletLogger({ wallet, models }) try {
bolt11 = invoice bolt11 = invoice
const { invoice: wrappedInvoice, maxFee } = await wrapInvoice({ bolt11, feePercent }, { msats, description, descriptionHash }, { me, lnd })
const { invoice: wrappedInvoice, maxFee } =
await wrapInvoice({ bolt11, feePercent }, { msats, description, descriptionHash }, { me, lnd })
return { return {
invoice, invoice,
wrappedInvoice: wrappedInvoice.request, wrappedInvoice: wrappedInvoice.request,
@ -107,9 +102,12 @@ export async function createWrappedInvoice (userId,
maxFee maxFee
} }
} catch (e) { } catch (e) {
logger?.error('invalid invoice: ' + e.message, { bolt11 }) console.error('failed to wrap invoice:', e)
throw e logger?.error('failed to wrap invoice: ' + e.message, { bolt11 })
} }
}
throw new Error('no wallet to receive available')
} }
export async function getInvoiceableWallets (userId, { paymentAttempt, predecessorId, models }) { export async function getInvoiceableWallets (userId, { paymentAttempt, predecessorId, models }) {

View File

@ -1,6 +1,6 @@
import { msatsSatsFloor, msatsToSats, satsToMsats } from '@/lib/format' import { msatsSatsFloor, msatsToSats, satsToMsats } from '@/lib/format'
import { createWithdrawal } from '@/api/resolvers/wallet' import { createWithdrawal } from '@/api/resolvers/wallet'
import { createInvoice } from '@/wallets/server' import { createUserInvoice } from '@/wallets/server'
export async function autoWithdraw ({ data: { id }, models, lnd }) { export async function autoWithdraw ({ data: { id }, models, lnd }) {
const user = await models.user.findUnique({ where: { id } }) const user = await models.user.findUnique({ where: { id } })
@ -42,14 +42,18 @@ export async function autoWithdraw ({ data: { id }, models, lnd }) {
if (pendingOrFailed.exists) return if (pendingOrFailed.exists) return
const { invoice, wallet, logger } = await createInvoice(id, { msats, description: 'SN: autowithdrawal', expiry: 360 }, { models }) for await (const { invoice, wallet, logger } of createUserInvoice(id, {
msats,
description: 'SN: autowithdrawal',
expiry: 360
}, { models })) {
try { try {
return await createWithdrawal(null, return await createWithdrawal(null,
{ invoice, maxFee: msatsToSats(maxFeeMsats) }, { invoice, maxFee: msatsToSats(maxFeeMsats) },
{ me: { id }, models, lnd, wallet, logger }) { me: { id }, models, lnd, wallet, logger })
} catch (err) { } catch (err) {
logger.error(`incoming payment failed: ${err}`, { bolt11: invoice }) console.error('failed to create autowithdrawal:', err)
throw err logger?.error(`incoming payment failed: ${err}`, { bolt11: invoice })
}
} }
} }