fix: guard advanced transfer against unaffordable capacity - #1180
Draft
jvsena42 wants to merge 4 commits into
Draft
fix: guard advanced transfer against unaffordable capacity#1180jvsena42 wants to merge 4 commits into
jvsena42 wants to merge 4 commits into
Conversation
jvsena42
force-pushed
the
fix/advanced-transfer-affordability
branch
from
August 25, 2026 14:47
79d23d9 to
542700f
Compare
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jvsena42
force-pushed
the
fix/advanced-transfer-affordability
branch
from
August 25, 2026 16:08
542700f to
a006370
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR stops the advanced transfer screen from creating a channel order the wallet cannot pay for.
Stacked on #1179 — review that one first; this branch targets it, not master.
Description
The advanced screen lets the receiving capacity be chosen independently of the client balance, and the Min, Default and Max buttons set it straight from the LSP's liquidity options. Those bounds describe the LSP's channel size limits and existing channels — nothing in them knows what the wallet can afford. Since the LSP prices both sides of the channel, raising the receiving capacity raises the order fee, so tapping Max on a transfer already sized near the balance produces an order that costs more than the user has.
Nothing checked for it. The order was created regardless, and the failure only surfaced on the confirm screen, which throws while preparing the funding plan, toasts, and leaves the swipe control stuck in its loading state. The user ends up at a dead end with a live order already placed against the LSP.
Continue is now gated on the selected capacity fitting what the on-chain balance can fund, using the fee quote the screen already fetches for its liquidity fee display, so no extra requests are made while picking an amount. The check is repeated in the view model before the order is created, so a stale or racing UI state cannot wave one through.
Clamping the Max button to an affordable ceiling was considered and rejected: finding that ceiling needs either a long binary search of fee quotes or an assumption about how the fee scales with the receiving side, and #1179 exists because that kind of assumption was already wrong once.
When the budget cannot be determined
The funding budget is resolved at the moment of the decision rather than read from cached state, so a value that never loaded, or went stale while the screen sat open, cannot approve an unaffordable order. Both reads are local to the node, so this stays cheap. The cached copy backing the button is refreshed on the same path and is only an optimisation.
If the node will not report a balance at all, the user is deliberately not blocked and the confirm step stays the authority — blocking there would lock people out of the flow whenever the node is briefly unready, which is worse than the failure it prevents. That case and an unavailable fee quote are logged separately so support logs show why a check was skipped.
Preview
QA Notes
Needs a transfer sized at or near Max — at smaller amounts there is enough headroom that raising the receiving capacity never crosses the balance, and the guard correctly never engages.
Verified on a regtest emulator with a 300,000 sat balance: at Max client amount (298,819) the advanced Max capacity of 1,072,921 quoted a 1,743 sat liquidity fee and Continue stayed inert, while Default at 478,781 proceeded to confirm and created the order.
Manual Tests
regression:Same screen → tap Min: Continue advances normally.regression:Transfer a small amount (¼) → Advanced → tap Max: Continue advances, since the capacity is still affordable.regression:Spending Confirm → Advanced → Default button returns to the default order and confirm still swipes through.Automated Checks
TransferViewModelTest.kt: five cases covering budget reservation, rejecting an unaffordable capacity, accepting an affordable one, rejecting without a cached budget, and proceeding when the balance cannot be read.just compile,just test, andjust lintall pass, with only pre-existing detekt findings in untouched files.