What happened?
While testing public Paykit/contact payments, I tried to send 5,000 sats on-chain to a contact. The transaction sent the whole on-chain balance instead.
This does not look Paykit-specific. Logs show the send flow still requested 5,000 sats, but the iOS on-chain send path passed isMaxAmount: true because selected UTXOs would have left dust change. That caused LDK sendAllToAddress to run and drain the full wallet balance.
Observed transaction:
5a1b6a9df66368acc1a14a93f24b34d266a763c5adfdfb44f89b9041041c36a5
Relevant log findings:
- Balance before send:
onchain=72394, lightning=23354.
- UTXO selection target:
5000 sats.
- Selected UTXOs:
2810 + 2640 = 5450 sats.
- Fee estimate for selected UTXOs:
181 sats.
- Expected change would be
269 sats, below dust.
- Final send call:
Sending 5000 sats ... (isMaxAmount: true).
- LDK created tx by
sending available on-chain funds retaining a reserve of 0sats.
- Wallet event detected amount:
-72394 sats.
- Balance changed:
onchain 72394 -> 0.
Expected behavior
Sending 5,000 sats should not drain the whole wallet balance.
If selected UTXOs would leave dust change, the app should either:
- select different UTXOs,
- absorb only the selected-input dust into fee/recipient amount with clear UI,
- show an error and ask the user to adjust amount/coin selection,
- or otherwise avoid calling a full-wallet send-all path unless the user explicitly selected max amount.
Steps to Reproduce
- Use an iOS wallet with multiple on-chain UTXOs.
- Start an on-chain send to a contact or normal address.
- Enter an amount that causes selected UTXOs to leave dust change. In the observed case:
- amount:
5,000 sats
- selected UTXOs total:
5,450 sats
- estimated fee:
181 sats
- dust change:
269 sats
- Confirm/send the transaction.
- Observe that the transaction drains the full on-chain balance instead of sending only the requested amount.
Logs / Screenshots / Recordings
Attachment prepared:
bitkit_logs_2026-05-05_12-15-22.zip
Key txid:
5a1b6a9df66368acc1a14a93f24b34d266a763c5adfdfb44f89b9041041c36a5
Bitkit Version
2.2.0
Observed on bitkit-ios branch codex-paykit-public-endpoints-pr527 at revision c58922938b3d.
Device / OS
iPhone 17 Simulator.
Reproducibility
Rarely (once or twice)
Observed once during Paykit contact-payment testing, but the log path suggests it should be reproducible whenever selected on-chain UTXOs leave dust change and shouldUseSendAll becomes true for a non-max send.
Additional context
Likely source:
SendConfirmationView.swift computes useMaxAmount = wallet.isMaxAmountSend || shouldUseSendAll.
shouldUseSendAll can become true when selected UTXOs would leave dust change.
WalletViewModel.send(... isMaxAmount: true) passes this to LightningService.send.
LightningService.send(... isMaxAmount: true) calls sendAllToAddress, which sends all available on-chain funds rather than only the selected UTXOs.
This was discovered while paying an on-chain public Paykit/contact endpoint, but the failure mode appears to be generic iOS on-chain dust/send-all handling rather than Paykit-specific.
Similar issues check
What happened?
While testing public Paykit/contact payments, I tried to send
5,000 satson-chain to a contact. The transaction sent the whole on-chain balance instead.This does not look Paykit-specific. Logs show the send flow still requested
5,000 sats, but the iOS on-chain send path passedisMaxAmount: truebecause selected UTXOs would have left dust change. That caused LDKsendAllToAddressto run and drain the full wallet balance.Observed transaction:
5a1b6a9df66368acc1a14a93f24b34d266a763c5adfdfb44f89b9041041c36a5Relevant log findings:
onchain=72394,lightning=23354.5000 sats.2810 + 2640 = 5450 sats.181 sats.269 sats, below dust.Sending 5000 sats ... (isMaxAmount: true).sending available on-chain funds retaining a reserve of 0sats.-72394 sats.onchain 72394 -> 0.Expected behavior
Sending
5,000 satsshould not drain the whole wallet balance.If selected UTXOs would leave dust change, the app should either:
Steps to Reproduce
5,000 sats5,450 sats181 sats269 satsLogs / Screenshots / Recordings
Attachment prepared:
bitkit_logs_2026-05-05_12-15-22.zip
Key txid:
5a1b6a9df66368acc1a14a93f24b34d266a763c5adfdfb44f89b9041041c36a5Bitkit Version
2.2.0Observed on
bitkit-iosbranchcodex-paykit-public-endpoints-pr527at revisionc58922938b3d.Device / OS
iPhone 17 Simulator.
Reproducibility
Rarely (once or twice)
Observed once during Paykit contact-payment testing, but the log path suggests it should be reproducible whenever selected on-chain UTXOs leave dust change and
shouldUseSendAllbecomes true for a non-max send.Additional context
Likely source:
SendConfirmationView.swiftcomputesuseMaxAmount = wallet.isMaxAmountSend || shouldUseSendAll.shouldUseSendAllcan become true when selected UTXOs would leave dust change.WalletViewModel.send(... isMaxAmount: true)passes this toLightningService.send.LightningService.send(... isMaxAmount: true)callssendAllToAddress, which sends all available on-chain funds rather than only the selected UTXOs.This was discovered while paying an on-chain public Paykit/contact endpoint, but the failure mode appears to be generic iOS on-chain dust/send-all handling rather than Paykit-specific.
Similar issues check
bitkit-ios#200— Coin selection failed when sending near max amount from the wallet — OPENCoin selection failed when sending near max amount from the wallet #200
Related dust/near-max behavior, but not an exact duplicate. This issue is worse: the send succeeds and drains the whole on-chain balance.
bitkit-android#815— android: send near-max leaves unspendable dust change — OPENandroid: send near-max leaves unspendable dust change bitkit-android#815
Related cross-platform dust-change handling context, but not an iOS duplicate.