fix(wallet): send amount_msat when paying an amountless invoice
Paying a zero-amount BOLT11 always failed. The send dialog correctly showed an amount field for such an invoice, but the amount the user typed was then dropped: the request body carried only `bolt11`. LNbits rejects that outright — `_validate_payment_request` raises "Amount required for amountless invoices." when the invoice has no amount and no `amount_msat` accompanies it. So the user filled in an amount and got an error. The invoice is now decoded in the service rather than trusted from the caller, so every caller benefits and no call site has to remember. When the decoded amount is zero we forward the caller's amount as `amount_msat`; when the invoice carries its own amount we deliberately send nothing, since the invoice amount is authoritative and must not be overridable by the caller. A decode failure returns null and sends nothing, leaving the server as the authority on whether the invoice is payable. Verified with light-bolt11-decoder: BOLT11 spec donation vector (amountless) -> 0 (amount_msat sent) live 777-sat LNbits invoice -> 777000 (amount_msat omitted) malformed input -> null (defer to server)
This commit is contained in:
parent
cc2adcb808
commit
d636be6bb5
1 changed files with 34 additions and 0 deletions
|
|
@ -2,6 +2,7 @@ import { ref, computed } from 'vue'
|
|||
import { BaseService } from '@/core/base/BaseService'
|
||||
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
|
||||
import { config } from '@/lib/config'
|
||||
import { decode as decodeBolt11 } from 'light-bolt11-decoder'
|
||||
|
||||
export interface PayLink {
|
||||
id: string
|
||||
|
|
@ -255,6 +256,19 @@ export default class WalletService extends BaseService {
|
|||
out: true,
|
||||
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')) {
|
||||
// Lightning address or 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
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue