125 lines
3.8 KiB
JavaScript
125 lines
3.8 KiB
JavaScript
// import server side wallets
|
|
import * as lnd from 'wallets/lnd/server'
|
|
import * as cln from 'wallets/cln/server'
|
|
import * as lnAddr from 'wallets/lightning-address/server'
|
|
import * as lnbits from 'wallets/lnbits/server'
|
|
import * as nwc from 'wallets/nwc/server'
|
|
import * as phoenixd from 'wallets/phoenixd/server'
|
|
|
|
// we import only the metadata of client side wallets
|
|
import * as blink from 'wallets/blink'
|
|
import * as lnc from 'wallets/lnc'
|
|
import * as webln from 'wallets/webln'
|
|
|
|
import { addWalletLog } from '@/api/resolvers/wallet'
|
|
import walletDefs from 'wallets/server'
|
|
import { parsePaymentRequest } from 'ln-service'
|
|
import { toPositiveNumber } from '@/lib/validate'
|
|
import { PAID_ACTION_TERMINAL_STATES } from '@/lib/constants'
|
|
import { withTimeout } from '@/lib/time'
|
|
|
|
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 }, { models }) {
|
|
// get the wallets in order of priority
|
|
const wallets = await models.wallet.findMany({
|
|
where: { userId, enabled: true, canReceive: true },
|
|
include: {
|
|
user: true
|
|
},
|
|
orderBy: [
|
|
{ priority: 'asc' },
|
|
// use id as tie breaker (older wallet first)
|
|
{ id: 'asc' }
|
|
]
|
|
})
|
|
|
|
msats = toPositiveNumber(msats)
|
|
|
|
for (const wallet of wallets) {
|
|
const w = walletDefs.find(w => w.walletType === wallet.type)
|
|
try {
|
|
const { walletType, walletField, createInvoice } = w
|
|
|
|
const walletFull = await models.wallet.findFirst({
|
|
where: {
|
|
userId,
|
|
type: walletType
|
|
},
|
|
include: {
|
|
[walletField]: true
|
|
}
|
|
})
|
|
|
|
if (!walletFull || !walletFull[walletField]) {
|
|
throw new Error(`no ${walletType} wallet found`)
|
|
}
|
|
|
|
// check for pending withdrawals
|
|
const pendingWithdrawals = await models.withdrawl.count({
|
|
where: {
|
|
walletId: walletFull.id,
|
|
status: null
|
|
}
|
|
})
|
|
|
|
// and pending forwards
|
|
const pendingForwards = await models.invoiceForward.count({
|
|
where: {
|
|
walletId: walletFull.id,
|
|
invoice: {
|
|
actionState: {
|
|
notIn: PAID_ACTION_TERMINAL_STATES
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
console.log('pending invoices', pendingWithdrawals + pendingForwards)
|
|
if (pendingWithdrawals + pendingForwards >= MAX_PENDING_INVOICES_PER_WALLET) {
|
|
throw new Error('wallet has too many pending invoices')
|
|
}
|
|
|
|
const invoice = await withTimeout(
|
|
createInvoice({
|
|
msats,
|
|
description: wallet.user.hideInvoiceDesc ? undefined : description,
|
|
descriptionHash,
|
|
expiry
|
|
}, walletFull[walletField]), 10_000)
|
|
|
|
const bolt11 = await parsePaymentRequest({ request: invoice })
|
|
if (BigInt(bolt11.mtokens) !== BigInt(msats)) {
|
|
if (BigInt(bolt11.mtokens) > BigInt(msats)) {
|
|
throw new Error(`invoice is for an amount greater than requested ${bolt11.mtokens} > ${msats}`)
|
|
}
|
|
if (BigInt(bolt11.mtokens) === 0n) {
|
|
throw new Error('invoice is for 0 msats')
|
|
}
|
|
if (BigInt(msats) - BigInt(bolt11.mtokens) >= 1000n) {
|
|
throw new Error(`invoice has a different satoshi amount ${bolt11.mtokens} !== ${msats}`)
|
|
}
|
|
|
|
await addWalletLog({
|
|
wallet,
|
|
level: 'INFO',
|
|
message: `wallet does not support msats so we floored ${msats} msats to nearest sat ${BigInt(bolt11.mtokens)} msats`
|
|
}, { models })
|
|
}
|
|
|
|
return { invoice, wallet }
|
|
} catch (error) {
|
|
console.error(error)
|
|
await addWalletLog({
|
|
wallet,
|
|
level: 'ERROR',
|
|
message: `creating invoice for ${description ?? ''} failed: ` + error
|
|
}, { models })
|
|
}
|
|
}
|
|
|
|
throw new Error('no wallet available')
|
|
}
|