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 { createHmac } from '@/api/resolvers/wallet'
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 * as ITEM_CREATE from './itemCreate'
@ -264,42 +264,51 @@ async function performDirectAction (actionType, args, incomingContext) {
throw new NonInvoiceablePeerError()
}
let invoiceObject
try {
await assertBelowMaxPendingDirectPayments(userId, 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,
description,
expiry: INVOICE_EXPIRE_SECS
}, { models, lnd })
} catch (e) {
console.error('failed to create outside invoice', e)
throw new NonInvoiceablePeerError()
}
}, { models, lnd })) {
let hash
try {
hash = parsePaymentRequest({ request: invoice }).id
} catch (e) {
console.error('failed to parse invoice', e)
logger?.error('failed to parse invoice: ' + e.message, { bolt11: invoice })
continue
}
const { invoice, wallet } = invoiceObject
const hash = parsePaymentRequest({ request: invoice }).id
const payment = await models.directPayment.create({
data: {
comment,
lud18Data,
desc: noteStr,
bolt11: invoice,
msats: cost,
hash,
walletId: wallet.id,
receiverId: userId
try {
return {
invoice: await models.directPayment.create({
data: {
comment,
lud18Data,
desc: noteStr,
bolt11: invoice,
msats: cost,
hash,
walletId: wallet.id,
receiverId: userId
}
}),
paymentMethod: 'DIRECT'
}
} catch (e) {
console.error('failed to create direct payment', e)
logger?.error('failed to create direct payment: ' + e.message, { bolt11: invoice })
}
}
})
return {
invoice: payment,
paymentMethod: 'DIRECT'
} catch (e) {
console.error('failed to create user invoice', e)
}
throw new NonInvoiceablePeerError()
}
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
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
const wallets = await getInvoiceableWallets(userId, {
paymentAttempt,
@ -72,44 +72,42 @@ export async function createInvoice (userId, { msats, description, descriptionHa
}
}
return { invoice, wallet, logger }
yield { invoice, wallet, logger }
} catch (err) {
console.error('failed to create user invoice:', err)
logger.error(err.message, { status: true })
}
}
throw new Error('no wallet to receive available')
}
export async function createWrappedInvoice (userId,
{ msats, feePercent, description, descriptionHash, expiry = 360 },
{ paymentAttempt, predecessorId, models, me, lnd }) {
let logger, bolt11
try {
const { invoice, wallet } = await createInvoice(userId, {
// this is the amount the stacker will receive, the other (feePercent)% is our fee
msats: toPositiveBigInt(msats) * (100n - feePercent) / 100n,
description,
descriptionHash,
expiry
}, { paymentAttempt, predecessorId, models })
logger = walletLogger({ wallet, models })
bolt11 = invoice
const { invoice: wrappedInvoice, maxFee } =
await wrapInvoice({ bolt11, feePercent }, { msats, description, descriptionHash }, { me, lnd })
return {
invoice,
wrappedInvoice: wrappedInvoice.request,
wallet,
maxFee
// loop over all receiver wallet invoices until we successfully wrapped one
for await (const { invoice, logger, wallet } of createUserInvoice(userId, {
// this is the amount the stacker will receive, the other (feePercent)% is our fee
msats: toPositiveBigInt(msats) * (100n - feePercent) / 100n,
description,
descriptionHash,
expiry
}, { paymentAttempt, predecessorId, models })) {
let bolt11
try {
bolt11 = invoice
const { invoice: wrappedInvoice, maxFee } = await wrapInvoice({ bolt11, feePercent }, { msats, description, descriptionHash }, { me, lnd })
return {
invoice,
wrappedInvoice: wrappedInvoice.request,
wallet,
maxFee
}
} catch (e) {
console.error('failed to wrap invoice:', e)
logger?.error('failed to wrap invoice: ' + e.message, { bolt11 })
}
} catch (e) {
logger?.error('invalid invoice: ' + e.message, { bolt11 })
throw e
}
throw new Error('no wallet to receive available')
}
export async function getInvoiceableWallets (userId, { paymentAttempt, predecessorId, models }) {

View File

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