You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
UnifiedPayment::send applies the caller's amount inconsistently for fixed-amount instructions #1201
At main c32d3dd53638bce3d30522ffb9d52ad674f4578f, UnifiedPayment::send handles a caller-supplied amount inconsistently for PaymentInstructions::FixedAmount. The FixedAmount arm only rejects an amount below the instruction's amount. A larger amount then reaches the BOLT 12 branch, which calls send_using_amount whenever an amount is present and so pays the caller's larger value, whereas the BOLT 11 branch ignores the caller's amount and pays the invoice amount. The same URI therefore pays different amounts depending on which method succeeds, and an integration that always forwards a user-entered or default amount can overpay a fixed-amount offer without noticing.
Decide on one behavior for fixed-amount instructions and apply it across methods: either use send_using_amount only for ConfigurableAmount and call Bolt12Payment::send for fixed offers, or reject any caller amount that differs from the fixed amount up front with Error::InvalidAmount. Extend unified_send_receive_bip21_uri to send a fixed-amount URI with a larger amount and assert either an InvalidAmount error or that the receiver gets exactly the fixed amount, for both the BOLT 12 and the BOLT 11 fallback paths. The behavior was verified by reading; no PoC was run during triage.
Reproduced on 9386a1c: a 100k sat BIP 21 URI paid with amount_msat = 200_000_000 sends 200k sats over BOLT 12 but 100k over the BOLT 11 fallback.
I'd pay the resolved amount on every path, so BOLT 12 uses resolved.ln_payment_amount() like on-chain already uses onchain_payment_amount(). A larger caller amount is then ignored everywhere, and a smaller one is still rejected up front by the check from #607. Rejecting any differing amount would work too, but it'd start failing apps that pass a default amount today. Happy to go that way if you'd rather.
Two other things I noticed:
An HRN resolving to a fixed-amount offer with no caller amount passes 0 to send_using_amount_inner, which fails. Using the resolved amount fixes this too.
Amountless BOLT 11 invoices can't be paid through UnifiedPayment::send at all. set_amount only sets ln_amt, so send rejects the invoice as zero-amount and we return PaymentSendingFailed (reproduced). Same PR, or separate?
Activity
Ansh-699 commentedon Oct 27, 2026
I'd like to take this one.
Reproduced on 9386a1c: a 100k sat BIP 21 URI paid with
amount_msat = 200_000_000sends 200k sats over BOLT 12 but 100k over the BOLT 11 fallback.I'd pay the resolved amount on every path, so BOLT 12 uses
resolved.ln_payment_amount()like on-chain already usesonchain_payment_amount(). A larger caller amount is then ignored everywhere, and a smaller one is still rejected up front by the check from #607. Rejecting any differing amount would work too, but it'd start failing apps that pass a default amount today. Happy to go that way if you'd rather.Two other things I noticed:
send_using_amount_inner, which fails. Using the resolved amount fixes this too.UnifiedPayment::sendat all.set_amountonly setsln_amt, sosendrejects the invoice as zero-amount and we returnPaymentSendingFailed(reproduced). Same PR, or separate?