fix(receive): handle additional receive liquidity edge cases - #1222
fix(receive): handle additional receive liquidity edge cases#1222pwltr wants to merge 11 commits into
Conversation
53e0156 to
314445d
Compare
Greptile SummaryThis PR centralizes receive-liquidity decisions and updates invoice creation, CJIT limits, receive navigation, session reset behavior, and receive-detail presentation.
Confidence Score: 4/5The PR should not merge until the CJIT maximum is derived from current Blocktank options rather than potentially stale cached limits. A previously populated Blocktank info object bypasses refresh during maximum calculation, so the receive flow can enforce an outdated channel-size limit and repeat it after a server rejection. Files Needing Attention: app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt
|
| Filename | Overview |
|---|---|
| app/src/main/java/to/bitkit/models/ReceiveLiquidityDecision.kt | Adds a pure, tested decision model for ordinary Lightning invoices and source-aware additional-liquidity routing. |
| app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt | Adds channel-size validation and maximum-CJIT calculation, but derives the maximum from potentially stale cached Blocktank options. |
| app/src/main/java/to/bitkit/repositories/WalletRepo.kt | Gates invoice generation and channel-event refreshes on ready-channel inbound capacity. |
| app/src/main/java/to/bitkit/ui/screens/wallets/receive/EditInvoiceVM.kt | Coordinates source-specific liquidity decisions and Blocktank limit lookup for edited receive amounts. |
| app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveAmountScreen.kt | Adds maximum CJIT validation, input clamping, and dedicated maximum-exceeded feedback. |
| app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveQrScreen.kt | Updates receive-tab availability, fallback selection, and details layout according to Lightning receive capability. |
| app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveSheet.kt | Tracks the edit source and routes initial versus additional CJIT flows to their corresponding confirmation screens. |
| app/src/main/java/to/bitkit/ui/ContentView.kt | Keys each receive-sheet instance so remembered navigation and CJIT state reset on close and reopen. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Edit receive amount] --> B{Selected source}
B -->|Savings or Auto| C[Update shared receive invoice]
B -->|Spending| D{Amount exceeds inbound liquidity?}
D -->|No| C
D -->|Yes| E{Geo-blocked?}
E -->|Yes| F[Geo-block screen]
E -->|No| G{Within current CJIT limits?}
G -->|No| H[CJIT amount entry]
G -->|Yes| I[Create additional CJIT]
I --> J[Additional-liquidity confirmation]
H --> J
Reviews (1): Last reviewed commit: "fix(receive): handle additional receive ..." | Re-trigger Greptile
ovitrif
left a comment
There was a problem hiding this comment.
QA Notes
Pixel_8_Pro emulator (sdk_gphone16k_arm64), regtest via network Electrum/Blocktank.
- Savings edit above inbound stayed on the on-chain QR and did not route to CJIT.
- Auto edit above inbound fell back to Savings/on-chain QR.
- Spending edit below the CJIT minimum routed to CJIT amount entry.
- Spending edit with a valid additional-CJIT amount opened confirmation.
- Spending edit above the real CJIT maximum stayed on amount entry and clamped to max.
- Receive sheet close/reopen started fresh on Auto.
- Auto details showed both on-chain and Lightning copy cards.
Approve.
|
There are conflicts. |
ovitrif
left a comment
There was a problem hiding this comment.
Added some nits after manual review for covering potential edge cases and defaulting to existing patterns throughout the code.
Been db checking them with AI on the side.
539d014 to
d9c603a
Compare
|
All review comments addressed |
|
QA Android, regtest. Existing channel: inbound 766,164, spending 3,000. Invoice at 766,164 is fine. Savings edit above inbound stayed on-chain. Auto edit above inbound fell back to Savings QR. Neither entered extra CJIT. Spending 766,165 opens extra-CJIT amount (MINIMUM 3,000). Continue at 3,000 fails. UI toast is the raw exception, not the new max copy:
Same Blocktank node-cap rejection as synonymdev/bitkit-ios#711. Client max handling only covers per-channel Please don’t offer extra CJIT (or map this error) when the node is already at Blocktank’s capacity limit. Don’t toast Recording: Screen.Recording.2026-09-04.at.15.37.47.movLogs: |
jvsena42
left a comment
There was a problem hiding this comment.
Diffed against the iOS port (#711). ReceiveLiquidityDecision and its 12 tests are a 1:1 match, and the routing, CJIT max search and session reset are equivalent. Android is the better of the two in three places (refreshes Blocktank info before the max check and tests it, hides Auto while a CJIT invoice is shown, routes edit-flow failures to the amount screen).
The two items worth attention are the tab effects, where this PR changed behaviour that the iOS side did not. Three of these are replies on existing threads.
Separately, and not filed as a finding since it is a bitkit-core question rather than a defect in this PR: get_default_lsp_balance returns max_channel_size_sat above threshold 2 (~EUR 495), so client + lsp > max for every amount above that and the "real max" this PR surfaces is effectively capped there regardless of Blocktank's maxChannelSizeSat. calc_default_lsp_balance uses max - client in the same band. Worth confirming that cap is intended.
ovitrif
left a comment
There was a problem hiding this comment.
Two things regress behaviour that master had, both in code the rebase brought in, so I'd like them fixed before this goes in.
The bigger one is BlocktankRepo.freshMaxChannelSizeSat(). It calls refreshInfo().getOrThrow(), and refreshInfo() wraps the whole cache-then-server sequence in a single runCatching, so a failing info(refresh = true) request returns Result.failure even after the cached value was already written into _blocktankState. Since createCjit() now calls it before the LSP request, one flaky Blocktank /info call aborts CJIT creation entirely. On master, createCjit had no dependency on /info at all. The earlier thread on this was marked fixed, but getOrThrow() is still there on this head.
The second is the AUTO auto-switch effect in ReceiveQrScreen. It lost the initialTab == null guard that master had. A LaunchedEffect body runs on first composition, so it now scrolls to ReceiveTab.AUTO and sets selectedTab = ReceiveTab.AUTO whenever Auto is visible, undoing the effect above it that applied the requested tab. ReceiveSheet passes initialTab = ReceiveTab.TREZOR whenever a hardware wallet is attached or the user is coming back from a hardware invoice edit, so hardware receive lands on Auto instead of Trezor.
The rest are non-blocking. ./gradlew detekt reports two violations on BlocktankRepo.kt on this head (ComplexCondition at line 478 and ImportOrdering), which is why the code-scanning alert is still open; the error-classification substring is broader than intended; and there are a few small rule and cleanup items noted inline.
|
HW receive broke after rebasing onto master (Trezor tab). e2e Home Receive, paired Trezor, Spending 0 / no LN:
New Lightning-unavailable fallback does E2E helper reads the default-tab QR, taps Trezor, and waits for a different address. This branch already opened on Trezor, so the addresses match and it times out. Home Receive should stay Savings (Auto if LN works). Trezor only when opening from the HW wallet screen. Recording (master): Screen.Recording.2026-09-07.at.12.41.16-master.movRecording (this branch): Screen.Recording.2026-09-07.at.12.44.47-1222.mov |
Addressed. Home Receive no longer defaults to Trezor just because a single hardware wallet is paired; it defaults to Auto when Lightning is available, otherwise Savings. Trezor is now only used as the initial tab when opening from the hardware wallet receive flow, or when returning from an edit that actually originated on the Trezor tab. I also fixed the Savings fallback to scroll to the actual Savings index instead of hardcoding |
jvsena42
left a comment
There was a problem hiding this comment.
Traced the funds-critical parts and they hold up: createCjit still sends invoiceSat = amountSats with channelSizeSat = amountSats + lspBalance, the Confirm screen's receiveAmountSats is the same action.amountSats used for the invoice, the binary search in maxCjitAmountSats() is over a monotone predicate and is overflow-safe, getInvoiceForTab is called with the per-item tab, and the hardware address is only produced under ReceiveTab.TREZOR. I found no way to show a destination belonging to the wrong tab and no sat/msat or off-by-one error in the limits.
One regression and one nit inline.
Regression test — stale channel cache in
|
Fixes #1226
Description
Ports the receive liquidity/CJIT behavior from bitkit-ios synonymdev/bitkit-ios#711.
ReceiveLiquidityDecisionhelper for Lightning invoice vs additional CJIT routing.Preview
Screen.Recording.2026-09-03.at.12.35.19.mov
QA Notes
Manual checks:
Automated checks:
ReceiveLiquidityDecisionTest.ktReceiveInvoiceUtilsTest.ktEditInvoiceVMTest.kt