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,42 +264,51 @@ 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 })) {
} catch (e) { let hash
console.error('failed to create outside invoice', e) try {
throw new NonInvoiceablePeerError() 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 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, desc: noteStr,
desc: noteStr, bolt11: invoice,
bolt11: invoice, msats: cost,
msats: cost, hash,
hash, walletId: wallet.id,
walletId: wallet.id, receiverId: userId
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 })
}
} }
}) } catch (e) {
console.error('failed to create user invoice', e)
return {
invoice: payment,
paymentMethod: 'DIRECT'
} }
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,44 +72,42 @@ 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
try {
logger = walletLogger({ wallet, models }) bolt11 = invoice
bolt11 = invoice const { invoice: wrappedInvoice, maxFee } = await wrapInvoice({ bolt11, feePercent }, { msats, description, descriptionHash }, { me, lnd })
return {
const { invoice: wrappedInvoice, maxFee } = invoice,
await wrapInvoice({ bolt11, feePercent }, { msats, description, descriptionHash }, { me, lnd }) wrappedInvoice: wrappedInvoice.request,
wallet,
return { maxFee
invoice, }
wrappedInvoice: wrappedInvoice.request, } catch (e) {
wallet, console.error('failed to wrap invoice:', e)
maxFee 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 }) { 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,
try { description: 'SN: autowithdrawal',
return await createWithdrawal(null, expiry: 360
{ invoice, maxFee: msatsToSats(maxFeeMsats) }, }, { models })) {
{ me: { id }, models, lnd, wallet, logger }) try {
} catch (err) { return await createWithdrawal(null,
logger.error(`incoming payment failed: ${err}`, { bolt11: invoice }) { invoice, maxFee: msatsToSats(maxFeeMsats) },
throw err { me: { id }, models, lnd, wallet, logger })
} catch (err) {
console.error('failed to create autowithdrawal:', err)
logger?.error(`incoming payment failed: ${err}`, { bolt11: invoice })
}
} }
} }