fix(wallet): send amount_msat when paying an amountless invoice #162

Merged
padreug merged 1 commit from fix/wallet-amountless-invoice into dev 2026-09-22 21:06:01 +00:00

View file

@ -2,6 +2,7 @@ import { ref, computed } from 'vue'
import { BaseService } from '@/core/base/BaseService' import { BaseService } from '@/core/base/BaseService'
import { injectService, SERVICE_TOKENS } from '@/core/di-container' import { injectService, SERVICE_TOKENS } from '@/core/di-container'
import { config } from '@/lib/config' import { config } from '@/lib/config'
import { decode as decodeBolt11 } from 'light-bolt11-decoder'
export interface PayLink { export interface PayLink {
id: string id: string
@ -255,6 +256,19 @@ export default class WalletService extends BaseService {
out: true, out: true,
bolt11: lower bolt11: lower
} }
// An amountless invoice carries no amount of its own, so the payer
// chooses one. LNbits rejects the payment outright ("Amount required
// for amountless invoices") unless `amount_msat` is supplied, so the
// caller's amount has to be forwarded explicitly. For an invoice that
// does carry an amount we deliberately send nothing: the invoice's own
// amount is authoritative and must not be overridden by the caller.
if (this.getInvoiceAmountMsat(lower) === 0) {
if (!request.amount || request.amount <= 0) {
throw new Error('This invoice has no amount. Enter an amount to send.')
}
body.amount_msat = Math.round(request.amount * 1000)
}
} else if (dest.includes('@') || lower.startsWith('lnurl')) { } else if (dest.includes('@') || lower.startsWith('lnurl')) {
// Lightning address or LNURL // Lightning address or LNURL
endpoint = `${config.api.baseUrl}/api/v1/payments/lnurl` endpoint = `${config.api.baseUrl}/api/v1/payments/lnurl`
@ -300,6 +314,26 @@ export default class WalletService extends BaseService {
} }
} }
/**
* Amount of a BOLT11 invoice in millisats, or `null` if it cannot be decoded.
*
* Returns `0` for an amountless invoice, which is a meaningful value here
* and not an error — hence `null` rather than `0` for the failure case.
*/
private getInvoiceAmountMsat(bolt11: string): number | null {
try {
const decoded = decodeBolt11(bolt11)
const section = decoded.sections.find((s: any) => s.name === 'amount')
if (!section || section.value === undefined || section.value === null) return 0
return Number(section.value)
} catch (error) {
// Let the server be the authority on whether the invoice is payable;
// a local decode failure should not block the attempt.
console.warn('WalletService: could not decode invoice amount', error)
return null
}
}
/** /**
* Load existing pay links * Load existing pay links
*/ */