Repository navigation
fix: enforce max send fee drains confirmed amount - #1147
coreyphillips wants to merge 2 commits into
Conversation
[Critical impact] The PR appears safe to merge; no new blocking issue was found. SummaryKeeps max on-chain sends tied to the recipient and selected fee speed.
No new actionable issues were found. No previous review threads were supplied. Tests were inspected but not run. Diagram%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Amount, speed, or selected coins change] --> B[Estimate maximum for recipient and speed]
B --> C[Update amount and max flag]
C --> D[Refresh fees]
D --> E[Enable confirmation swipe]
E --> F[Sync wallet before sending]
F --> G{Confirmed amount still equals maximum?}
G -->|Yes, and marked max| H[Prepare drain]
G -->|No| I[Prepare exact-amount send]
H --> J[Retain receipt and broadcast]
I --> J
Reviews (2) · Last reviewed commit: "chore: merge master into spar/issue-1144" · Reviewed by Greptile |
|
Review by codex (round 1): The approach is sound, but the actual drain can still use a different fee-rate snapshot than the new max validation. Existing test results show 1,919 tests passing, including both affected suites. A fresh targeted run was blocked by the sandbox denying the Gradle cache lock.
|
|
Applied the review's blocking fix: sendOnchain now snapshots the send state once and passes the same speed and feeRates to both estimateMaxSendOnchain (the drain guard) and sendOnChain, so the guard and the transaction can no longer be built from different fee tables. Updated the send-flow test to use a non-null FeeRates and assert both calls receive it. Targeted unit tests and detekt pass; committed as e00b553, not pushed.
|
|
Machine state for resuming this PR. Safe to ignore. {
"version": 1,
"round": 1,
"next_actor": "claude",
"status": "pending",
"ledger": {},
"filed": []
} |
|
Review by claude (round 2): The PR mirrors the iOS approach: recompute the max sendable amount at confirm time with the selected speed/fee rates and fall back to an exact-amount send when the cached max no longer matches. Direction is right, plumbing
|
|
Review by claude (round 2): The PR stops the wallet from draining at a fee speed the confirmed amount didn't account for by recomputing the max at the selected speed/rates and only setting isMaxAmount when it still matches exactly, and it now threads state.feeRates into sendOnChain so the check and the send use the same rates. The repo helper is clean and the new unit tests pass (verified: ./gradlew :app:testDevDebugUnitTest --tests '*AppViewModelSendFlowTest' --tests '*LightningRepoTest' -> BUILD SUCCESSFUL). The problem is the fallback: when the recomputed max is lower than the confirmed amount, the code sends the stale exact amount, which cannot fund itself, so the send fails instead of sending the wrong amount. That converts the issue's exact scenario (max send, then pick Fast) from 'sends less than displayed' into 'send always errors', and the address asymmetry makes it fire even without a speed change. The guard direction is right; the fallback needs rethinking (recompute/refresh the displayed amount, or drain when the recomputed max is <= the confirmed amount).
|
jvsena42
left a comment
There was a problem hiding this comment.
Reviewed the drain/max-send change and tested it on device (dev build, regtest, funded wallet). One of the findings is a reproduced regression — details inline.
|
@coreyphillips can you look into this (addressing comments), looks quite important on 1st sight, would like to merge and ship it in upcoming 2.5.0 🙏🏻 |
|
@jvsena42 thanks for the device repro, both blockers were real. Pushed 96d17bd. Rather than gating the drain on equality with the cached max, the send flow now recomputes the max for the actual recipient at the selected speed (
Logs now use @ovitrif should be good for 2.5.0 once re-checked. QA notes: max send to a P2TR recipient at default speed should drain, and switching speed on confirm should update the displayed amount. Covered in |
|
@coreyphillips can you please add PR description details matching our usual format? Ensuring sensitive framing, ofc 🙏🏻 While at it please also update with latest master to resolve conflicts flagged by GitHub. |
|
@ovitrif merged latest master, description updated, ready for re-review. |
ovitrif
left a comment
There was a problem hiding this comment.
Pay is not sequenced with refreshMaxSendOnchain, so switching a max send to Fast can still drain at the new fee while the confirm amount is the old one.
The hardware early-return in that helper also has no send-flow test that would fail if the guard disappeared.
|
@ovitrif both addressed in bdf6963.
Three new tests in |
jvsena42
left a comment
There was a problem hiding this comment.
Reviewed the max-send math end to end. The arithmetic itself is right: estimateMaxSendOnchain works off spendableOnchainBalanceSats (anchor reserve already excluded) minus calculateSendAllFee(retainReserves = true) at the selected speed, wrapped in USat, and the drain uses the same FeeRates snapshot. No reserve stranding, no dust change, no underflow.
The problem is the classification rule, not the math. amount >= max reclassifies any near-max exact-amount send as a drain and rewrites the amount. Verified by running the regression test below against bdf6963: expected:<99800> but was:<99500>, with the other 195 tests in AppViewModelSendFlowTest still passing.
Two of these are replies on the existing threads where the same ground was already covered — they are there because the shipped fix leaves something specific still open, not to re-litigate.
|
Moved to |
|
making this draft until it receives updates (descoped from 2.5.0, cc @coreyphillips) |
|
Potential issue on e2e run: multi_address_1 max send pays 399580 with isMaxAmount=false; have 400000 sats, need 400580 multi_address_-_Multi_address_multi_address_1_-_Receive_to_each_address_type_and_send_max_combined-2026-09-04T01-26-18-344Z.mp4Logs: attempt-3.zip |
|
Not signed off: the last round changed nothing, so the branch is the one that was already reviewed. Raised and refuted:
Noted, not blocking:
|
Regtest APKDownload bitkit-dev-debug universal APK (expires in 30 days). |
talosmachina
left a comment
There was a problem hiding this comment.
Recomputes the on-chain max for the actual recipient at the selected speed on every on-chain refresh, carries an explicit isMaxAmount flag into the send, rechecks it in LightningRepo.sendOnChain after the pre-send sync, and disables the swipe while an on-chain refresh is loading. Reviewed at f2259185, full tier.
No new findings.
Still open from earlier rounds, not re-posted: @jvsena42's thread on amount >= max in refreshMaxSendOnchain. It is unchanged at this head: any amount at or above the speed max is still lowered to the max and flagged as a drain, with no indication on the confirm screen.
Merge confidence: 3/5. That open thread is the main reason, plus the e2e shards (including multi_address_1, the case @piotr-iohk reported) were still pending when I reviewed.
What I checked
- Build skipped: static review. Bitkit Android PR branches are not built on this reviewer's machine, so nothing was compiled or run. Detekt and lint are green in CI.
- Drain decision:
sendOnchainnow readsSendUiState.isMaxAmount, notamount == maxSendOnchainSats. That flag is cleared byonAmountChangeand by the amount reset. It is set only byrefreshMaxSendOnchain, and only when there is no payment request and no hardware wallet. The repo recheck afterensureSyncedBeforeSenduses the samesatsPerVBytethe send uses. It only downgrades to an exact-amount send and never upgrades one, so a deposit landing after confirm is no longer swept. - Ordering: the earlier
join()on the refresh job was removed in16e658c7. The swipe is now gated ononchainFeeUi.isLoadingfor on-chain sends, and the repo recheck stands behind it. This covers the swipe-during-refresh case from the earlier threads. - Stale writes:
divergedFromchecks amount, method, address, speed and wallet, and the generation check sits in both update paths. - Ruled out: the fee-greater-than-spendable case. It saturates to 0,
takeIf { it > 0uL }maps that to no estimate, and no estimate now givesisMaxAmount = false, not the cached max. - Not settled: when the repo recheck disagrees after the sync, the send goes out as an exact send of
spendable - sendAllFee. Whether that can pay the fee of a multi-input exact send is whatmulti_address_1exercises. Read that shard's result before merging.
Reviewer model: claude-opus-5-5
Reasoning effort: Not exposed (not present in the run metadata available to this reviewer)
|
Changed
Answered
Not changing
Filed separately
Needs your decision
|
|
The open item from my review at
I have nothing to add on the KDoc question. This is a static read of the diff since my review. I did not build it, and it is not a new review. |
jvsena42
left a comment
There was a problem hiding this comment.
Code scan of the delta since bdf69630 (16e658c7, 8961672f, 55648ef3, df3cffc0). One MEDIUM: the drain recheck from my earlier thread narrows the deposit-sweep window but does not close it. Two LOWs. Device gate not run, since a MEDIUM is open.
Checked and clean:
- Only the Max button sets
isMaxAmount, so BIP21, LNURL and Paykit amounts can't become a drain. - The estimate and the send resolve the same fee rate from the same state.
- The fallback exact-amount send never delivers more than the confirmed amount.
divergedFromrejects late refresh writes.
LOW, outside the diff (AppViewModel.kt:4504, switchToLightning): switchToLightning replaces amount with fixedContactLightningAmount without going through onAmountChange, so the flag stays set. Switching back to Savings runs refreshMaxSendOnchain, which overwrites the invoice amount with the on-chain max and flags a drain. The user did tap Max earlier, and the new figure shows before the swipe is enabled, so no unconfirmed amount is sent. But the send no longer follows the invoice. Suggested fix: clear isMaxAmount here when the amount is replaced.
|
Changed
|
jvsena42
left a comment
There was a problem hiding this comment.
Approving at dcb69993. All three findings from my last review are fixed:
- Non-transfer drains now
check(prepared.receipt.amountSats == sats). switchToLightningclearsisMaxAmountwhen the amount changes.- The max write recomputes
isAmountInputValid.
Device gate on a dev build (regtest, 3 UTXOs, 100,000 sats):
- Max at Normal: confirm showed 99,754 with fee 246. The broadcast had one output of 99,754, and the on-chain balance was 0 afterwards.
- Max follows speed: at Fast it was 99,263 (fee 737); switching to Slow rewrote it to 99,754 (log:
Updating max amount '99263' to '99754' at speed 'slow'). - Max with manual coin selection: 69,326 at Fast became 69,681 at Normal. It sent exactly 69,681 and left a balance of 0.
- Normal 10,000 send: sent exactly 10,000 with 39,859 change. Changing speed kept the amount.
- The new drain check never failed during these runs.
- Not reachable in the UI: the non-max "above the max is marked invalid" path. The number pad refuses digits past the max.
Out of scope (pre-existing, not from this PR):
- From a Max at Normal, the speed picker greys out Fast and caps Custom at 1 sat/vB, because
SendFeeViewModel.getFeeLimit()caps the fee at balance minus amount. - For a Max, the confirm-screen fee is estimated as a regular send with change. So
calculateTotalFeecan fall back to 1,000 sats and logCoin selection failedorInsufficient fundsbefore the drain, even though the broadcast is exact.
CI green apart from one pending e2e suite.
jvsena42
left a comment
There was a problem hiding this comment.
Re-approving at 2a87d21d. The new commit only adds a LightningRepoTest case for a max on-chain send whose fee exceeds the balance.
There was a problem hiding this comment.
Verdict: 💬 Comment — one MEDIUM funding-selector regression remains.
Findings:
- MEDIUM — Clear Max intent when the funding selector restores an invoice amount (
AppViewModel.kt:7238). For a unified invoice with a fixed Lightning amount, choose Savings → Max → Confirm, then use the funding selector to choose Spending and return to Savings.onPaymentMethodSwitchrestores the invoice amount but preservesisMaxAmount, so this branch replaces it with the whole Savings max again. A focused regression throughPaymentMethodSelectreproduces 10,000 → 99,000 sats. The new reset inswitchToLightningcovers a different entry point. Clear the flag when the funding conversion changes the amount and cover the selector path. The replacement is displayed before payment; this does not claim an unconfirmed amount is broadcast.
QA for 2a87d21:
Max sends at the faster fee speed ✅ Confirm and broadcast both used 99,635 sats, leaving Savings at zero.
PR case 1a, regtest build from this head. The cached Max was 99,890; Confirm recomputed it to 99,635 at Fast. Decoding the transaction confirmed one 99,635-sat Taproot output and a 365-sat fee.
Fast was selected with a smaller amount before returning to Amount and tapping Max, because the existing fee-picker cap prevents selecting Fast from a Normal Max. The exact Normal-Max → Fast picker sequence remains constrained by that pre-existing behavior, also noted in the earlier device report.
software-max-fast-2x.2x.mp4
Fast Max: amount entry, confirmation, send and zero Savings balance.
Max follows the slower fee speed ✅ Switching Fast to Slow raised Max from 99,635 to 99,878 sats, and exactly 99,878 was sent.
PR case 1b. Decoding the transaction confirmed one 99,878-sat output and a 122-sat fee; Savings was zero afterwards. This follows the corrected Max-intent behavior: Slow refreshes the amount, rather than retaining the Fast amount as the description still says.
software-max-slow-2x.2x.mp4
Fast Max changes to Slow, then sends the refreshed amount.
Max sends to a Taproot recipient ✅ Default-speed Confirm and broadcast both used 199,878 sats, leaving Savings at zero.
PR case 2. A 200,000-sat wallet initially offered the cached 199,890-sat Max. Confirm adjusted it for the Taproot recipient. Independent transaction decoding confirmed a single 199,878-sat Taproot output and a 122-sat fee.
taproot-max-replay-2x.2x.mp4
Taproot Max at Normal, exact sent amount and zero Savings balance.
Exact amount survives fee speed changes ✅ The existing device report verified an unchanged 10,000-sat send and exact delivery.
PR case 3: relied on the device report at dcb69993. Runtime code is unchanged between that head and 2a87d21; the only subsequent change adds a repository test. This case was not repeated.
Hardware Max follows the hardware balance ✅ Switching Fast to Slow changed hardware Max from 798,259 to 799,047 sats.
PR case 4, deterministic Trezor on regtest. The hardware balance was 799,441 sats, distinct from the software wallet balance. Fast deducted 1,182 sats; Slow deducted 394. This checks confirmation and fee refresh; no hardware transaction was signed or sent.
hardware-max-fast-to-slow-2x.2x.mp4
Hardware funding source remains selected as Max follows the new fee speed.
Coin selection refreshes Max and fees ✅ The existing device report verified refreshed selected-coin amounts and exact delivery.
PR case 5: relied on the device report at dcb69993: manual selection changed Max from 69,326 at Fast to 69,681 at Normal, then sent exactly 69,681. Runtime code is unchanged through 2a87d21, so this case was not repeated.
Fixed invoice amount clears Max through the funding selector ❌ A focused unit regression reproduced 10,000 sats becoming 99,000 on returning to Savings.
Additional regression case 6, using the production PaymentMethodSelect handlers with mocked balances and invoice data. The assertion fails with expected:<10000> but was:<99000>. All original cases in AppViewModelSendFlowTest and LightningRepoTest passed. This selector sequence was not driven on a device.
Built with assembleDevDebug and ran the two affected unit-test classes. Full review covered 44bf50e..2a87d21, including all changed production and test files, callers, refresh cancellation, fee snapshots and prepared-receipt checks. Controlled deposit races, estimator failures and dust boundaries were covered by unit tests rather than device fault injection. Existing resolved threads were checked and left closed.
No dedicated iOS twin is linked. The matching iOS safeguard comes from synonymdev/bitkit-ios#540 [codex] Prevent dust change from triggering send-all: explicit Max intent and the fresh selected-rate equality check match; Android additionally refreshes the displayed Max here.
Reviewed by gpt-6.1-sol-high
| return | ||
| } | ||
|
|
||
| if (state.isMaxAmount) { |
There was a problem hiding this comment.
MEDIUM: Clear Max intent when the funding selector restores an invoice amount
With a fixed-amount unified Lightning/on-chain invoice, select Savings and tap Max, then use the confirmation funding selector to switch to Spending and back to Savings. onPaymentMethodSwitch replaces amount with the fixed Lightning invoice amount but leaves isMaxAmount set, so this branch replaces that exact amount with the entire savings max again. A focused PaymentMethodSelect regression at this head reproduces 10000 sats becoming 99000 sats on the return to Savings. The new reset in switchToLightning does not cover this selector path. Clear Max intent whenever the funding conversion replaces the amount, and cover PaymentMethodSelect in the test. The rewritten amount is visible before payment; this is not a claim that an unconfirmed amount is broadcast.
There was a problem hiding this comment.
Confirmed against the code. onPaymentMethodSwitch (AppViewModel.kt:4304-4316) replaces amount with the converted or fixed invoice amount but never touches isMaxAmount. Going Savings Max → Spending sets amount to the fixed invoice amount from paymentAmountForSource, and returning to Savings calls selectFundingSource → refreshOnchainSendIfNeeded → refreshMaxSendOnchain. That sees isMaxAmount still true (line 7238) and rewrites the amount to the full max. This is a regression from the PR's Max refresh, and the fix is the same isMaxAmount = it.isMaxAmount && newAmount == it.amount guard already used in onAmountChange and switchToLightning, plus a test. Not changed here: @ovi-reviewer cannot write to this repository, and checkin_trust is "write".
There was a problem hiding this comment.
Rechecked at 76f7b30: this remains unchanged. A focused regression through the production PaymentMethodSelect handlers restores the fixed 10,000-sat invoice amount on Spending, then gets 99,000 sats on returning to Savings (expected:<10000> but was:<99000>). onPaymentMethodSwitch still replaces amount without clearing isMaxAmount. This confirms the existing finding; the newer c606021 head is not covered by this review.
There was a problem hiding this comment.
Confirmed at HEAD: onPaymentMethodSwitch (AppViewModel.kt:4494-4506) replaces amount with the converted or fixed amount but keeps isMaxAmount, so returning to Savings sends refreshMaxSendOnchain (line 7928) down the Max branch and swaps the fixed amount for the full max. The fix is the same isMaxAmount = it.isMaxAmount && amount == it.amount guard already used in onAmountChange and switchToLightning, plus a regression test. Not changed here: @ovi-reviewer cannot write to this repository, and checkin_trust is "write".
|
Answered
|
|
@coreyphillips this conflicts with |
|
Answered
|
2a87d21 to
76f7b30
Compare
There was a problem hiding this comment.
Verdict: 💬 Comment — two funding-selector defects and a test-helper regression remain at 76f7b30.
The PR moved to c606021 after this run; these results are for 76f7b30.
Findings:
- MEDIUM — Preserve fresh validation after selecting Savings (
AppViewModel.kt:7938). The selector awaitsrefreshMaxSendOnchain, then overwrites its result withvalidateAmount, which checks the cached default-speed maximum. A valid Max above that cache becomes invalid and disables payment. Conversely, an exact amount above the recipient's selected-speed maximum becomes valid again. FocusedPaymentMethodSelectregressions reproduce both: cached 100,000 / live Max 100,100 ends invalid; exact 99,800 / live Max 99,500 ends valid. Keep the fresh recipient/speed validation when the selector completes. - MEDIUM — Fixed invoice amounts still retain Max intent (existing thread,
AppViewModel.kt:7928). The fixed-invoice selector regression still reproduces 10,000 → 99,000 sats on returning from Spending to Savings.onPaymentMethodSwitchreplaces the amount without clearingisMaxAmount; the refresh then replaces the invoice amount with Max. This remains the existing finding, with a reply in its thread. - LOW — Match the new mining-fee argument in send verification (
AppViewModelSendFlowTest.kt:18986).verifyOnchainSendomits a matcher forapprovedMiningFeeSats. Its callers fail withInvalidUseOfMatchersException: 18 matchers expected, 17 recorded. AddingapprovedMiningFeeSats = anyOrNull()locally made those cases pass. The correction was removed after verification.
QA for 76f7b30:
Tested on Android 15, regtest, with a build from this head. All manual cases were repeated because the rebase changed native dependencies and surrounding funding paths.
Max sends at the faster fee speed ✅ Confirm and broadcast both used 99,635 sats, leaving Savings at zero.
Case 1a. A 100,000-sat wallet offered cached Max 99,890; Confirm refreshed it to 99,635 at Fast. Independent transaction decoding confirmed one 99,635-sat Taproot output and a 365-sat fee.
Fast was selected with a smaller amount before returning to Amount and tapping Max. The exact Normal-Max → Fast picker sequence remains constrained by the pre-existing fee cap documented in the earlier device report.
max-fast-2x.2x.mp4
Fast Max, confirmation, payment and zero Savings balance.
Max follows the slower fee speed ✅ Fast → Slow raised Max from 99,635 to 99,878 sats, and exactly 99,878 was sent.
Case 1b. Decoding confirmed one 99,878-sat output and a 122-sat fee; Savings became zero. This follows the corrected Max-intent expectation: Slow refreshes the amount, rather than retaining the Fast amount as the description still says.
max-slow-2x.2x.mp4
Fast Max changes to Slow and sends the refreshed amount.
Max sends to a Taproot recipient ✅ Normal-speed Confirm and broadcast both used 99,878 sats, leaving Savings at zero.
Case 2. Cached Max 99,890 was adjusted for the Taproot recipient. Transaction decoding confirmed a single 99,878-sat Taproot output and a 122-sat fee.
taproot-max-2x.2x.mp4
Taproot Max at Normal, exact delivery and zero Savings balance.
Exact amount survives fee speed changes ✅ Normal → Fast kept 10,000 sats, and the transaction paid exactly 10,000.
Case 3. Transaction decoding confirmed the 10,000-sat recipient output, 89,542-sat change and a 458-sat fee. The app log confirmed isMaxAmount=false. An incoming test deposit briefly covered the success screen; dismissing it revealed the correct payment result.
exact-amount-2x.2x.mp4
Exact amount through the speed change and successful payment.
Hardware Max follows the hardware balance ✅ Fast → Slow changed hardware Max from 499,430 to 499,810 sats.
Case 4. The selected Trezor held 500,000 sats, distinct from the software wallet. Fast deducted 570 sats; Slow deducted 190. The hardware source stayed selected. This case checked confirmation and fee refresh; no hardware transaction was signed or sent.
hardware-max-2x.2x.mp4
Hardware funding source and Max follow the fee-speed change.
Coin selection refreshes amount and fees ✅ Selecting only the 20,000-sat output refreshed the fee and paid exactly 10,000 sats.
Case 5. Deselected the separate 89,542-sat output before Continue. Confirm retained 10,000 sats and used a 153-sat fee. Transaction decoding and the app's selected-input log confirmed that only the chosen 20,000-sat output was spent, with 9,847 sats change.
coin-selection-2x.2x.mp4
Manual input selection, refreshed confirmation and exact payment.
Check: fixed invoice amount clears Max through the funding selector ❌ The focused regression still gets 99,000 sats instead of the fixed 10,000.
Additional case 6. Executed the production PaymentMethodSelect handlers with mocked balances and a fixed unified invoice. Savings Max → Spending restored 10,000; returning to Savings replaced it with 99,000. The final assertion failed with expected:<10000> but was:<99000>. This selector sequence was not driven on a device in this round.
Check: funding selection preserves recipient and speed validation ❌ The focused regressions reproduce both an invalid affordable Max and a valid unaffordable exact amount.
Additional case 7. Executed PaymentMethodSelect(Savings) with controlled balances and fee estimates. With cached Max 100,000, a fresh Max of 100,100 was retained but marked invalid. An exact 99,800 against fresh Max 99,500 was incorrectly marked valid. Both assertions failed. These boundary states were checked through unit regressions, without device fault injection.
Check: existing send regression tests ❌ The affected suites expose the missing matcher in the new verification helper.
Built with assembleDevDebug and ran AppViewModelSendFlowTest and LightningRepoTest. The repository cases passed; send-flow callers of verifyOnchainSend failed on the matcher error above. A temporary matcher correction made the affected cases pass. The reviewed APK used unchanged production code, and all temporary test edits were removed. Full repository tests and lint were not run.
The earlier reviewed head is no longer an ancestor after the rebase. Coverage therefore includes the full current patch f7ad821..76f7b30, its reached callers, and comparison with the previous review. Deposit races, estimator failure and dust boundaries were checked by unit tests rather than device fault injection. The PR moved to c606021 during the hardware case; that newer head is not covered here.
No dedicated iOS twin is linked. The related safeguard in synonymdev/bitkit-ios#540 [codex] Prevent dust change from triggering send-all matches explicit Max intent and fresh-maximum checks; this Android PR also refreshes the displayed Max.
Reviewed by gpt-6.1-sol-high
| _sendUiState.update { | ||
| if (generation != sendStateGeneration || it.divergedFrom(state)) return@update it | ||
| // validateAmount caps at the cached max, which a max at a cheaper speed can exceed | ||
| it.copy(amount = max, isAmountInputValid = max > Defaults.dustLimit.toULong()) |
There was a problem hiding this comment.
MEDIUM: Preserve fresh validation after selecting Savings
Selecting Savings awaits this refresh, but onPaymentMethodSwitch then overwrites isAmountInputValid with validateAmount, which uses the cached default-speed maximum. An affordable selected-speed Max above that cache is immediately invalidated, disabling Swipe To Pay. Conversely, an exact amount above the live maximum but below the cache is re-enabled after this helper rejects it.
Focused PaymentMethodSelect regressions at 76f7b30 reproduce both: cached 100,000 / live Max 100,100 ends invalid; exact 99,800 / live Max 99,500 ends valid. Preserve the fresh recipient/speed validation when the selector completes.
There was a problem hiding this comment.
Confirmed at HEAD: selectFundingSource(Savings) joins the refresh (line 4644), then onPaymentMethodSwitch (lines 4509-4519) resets isAmountInputValid with validateAmount, which checks the cached default-speed max (line 5106). A Slow Max above the cache ends invalid and an exact amount over the live max ends valid. Running the cached validation before the Savings refresh lets the fresh check win, and keeps the cached result when the estimate fails. Not changed here: @ovi-reviewer cannot write to this repository, and checkin_trust is "write".
| transferContext = anyOrNull(), | ||
| payerIdentity = anyOrNull(), | ||
| paymentDeadlineAt = anyOrNull(), | ||
| contactPublicKey = anyOrNull(), |
There was a problem hiding this comment.
LOW: Match the new mining-fee argument in send verification
The rebased sendOnChain signature includes approvedMiningFeeSats, but this new verification helper supplies matchers only for the preceding arguments. Mockito receives the default null as a raw argument, so its callers fail with InvalidUseOfMatchersException: 18 matchers expected, 17 recorded.
Adding approvedMiningFeeSats = anyOrNull() locally made the affected cases pass. Please add that matcher here; the temporary correction used for verification was removed.
|
Answered
|
Fixes #1144
Closes #1765
This PR keeps max on-chain sends in step with the fee speed and recipient they are sent at, so the amount shown on the confirm screen is the amount that goes out.
Description
Preview
N/A, the only visible change is the confirm amount updating when the fee speed changes.
QA Notes
Manual Tests
bcrt1p...) at the default speed: send completes with no on-chain balance left behind.regression:Send → Amount below max → Confirm → change speed: amount is unchanged and exactly that amount is delivered.regression:Hardware wallet funding source → Amount → tap Max → Confirm → change speed: amount follows the hardware wallet's available balance.regression:Send → Amount → Coin Selection → Continue → Confirm: fee and amount still refresh.Automated Checks
AppViewModelSendFlowTest.kt.LightningRepoTest.kt.just compile,just test,just lint.