Skip to content

fix: guard advanced transfer against unaffordable capacity - #1180

Open
jvsena42 wants to merge 12 commits into
masterfrom
fix/advanced-transfer-affordability
Open

fix: guard advanced transfer against unaffordable capacity#1180
jvsena42 wants to merge 12 commits into
masterfrom
fix/advanced-transfer-affordability

Conversation

@jvsena42

@jvsena42 jvsena42 commented Aug 25, 2026

Copy link
Copy Markdown
Member

This PR:

  1. Offers a receiving capacity on the advanced transfer screen that the wallet can actually pay for
  2. Re-reads the funding budget before placing an order, instead of trusting the one sized on screen entry
  3. Discards fee quotes that arrive for a capacity the user has already moved away from

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 — 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 produced an order costing more than the user had. The order was created regardless, and the failure only surfaced on the confirm screen, which throws while preparing the funding plan and leaves the swipe control stuck loading.

The max is now settled on an affordable capacity before it is offered, so Max lands on something that can be ordered rather than something that fails later. Finding that ceiling does not need a long search: the LSP fee is close to linear in the capacity, so pricing the min and the max brackets the answer and re-pricing through the rate those two quotes imply lands on it in a round or two. Every capacity offered has been quoted and found affordable. The screen shows a loading state while this settles, so the amount buttons cannot act on a range still being worked out.

The affordability check in the view model stays as a backstop for what moves after that, and both it and the confirm check now resolve the budget at the moment of the decision rather than reading the copy cached on screen entry. A hardware transfer keeps its cached budget, since those funds sit on the device and a fresh on-chain read would reject every one of them.

Separately, the fee quote behind the liquidity fee display was racing itself: each amount change started an uncancelled request, so a slow quote could land after a faster one and leave the display pinned to an amount the user had already left.

If the node will not report a balance at all, or the LSP will not give a quote, 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. Those cases are logged separately so support logs show why a check was skipped.

Preview

Screen_recording_20260826_133538.mp4

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 settling correctly does nothing.

Manual Tests

  • 1. Transfer → Spending → Max → Continue → Advanced: the amount buttons and Continue are briefly disabled while the max settles.
  • 2. Same screen → tap Max → Continue: advances to Spending Confirm and the order is created.
  • 3. Same screen → type a capacity above the settled max: the receiving capacity maximum toast shows the settled value.
  • 4a. regression: Same screen → tap Default: Continue advances and the order is created.
    • 4b. regression: Same screen → tap Min: Continue advances normally.
  • 6. regression: Spending Confirm → Advanced → Default returns to the default order and confirm still swipes through.

Automated Checks

  • Unit tests added in TransferViewModelTest.kt: settling the max on an affordable capacity, leaving an already-affordable max untouched, and holding the loading state while it settles.
  • Unit tests added in TransferViewModelTest.kt: rejecting an unaffordable capacity, accepting an affordable one, and proceeding when no budget was sized or the fee quote fails.
  • Unit tests added in TransferViewModelTest.kt: rejecting an order once the balance drains after the limits were sized, both on confirm and on the advanced screen, and funding a hardware transfer from the device balance rather than empty on-chain savings.
  • Unit test added in TransferViewModelTest.kt: discarding a slower fee quote for a capacity the user has already left.
  • Each rejection and staleness case was confirmed to fail with its fix removed, so none are vacuous.
  • Local just compile, just test, and just lint all pass, with only pre-existing detekt findings in untouched files.

@jvsena42 jvsena42 self-assigned this Aug 25, 2026
@jvsena42
jvsena42 force-pushed the fix/advanced-transfer-affordability branch 2 times, most recently from 542700f to a006370 Compare August 25, 2026 16:08
@jvsena42
jvsena42 marked this pull request as ready for review August 26, 2026 12:49
@greptile-apps

greptile-apps Bot commented Aug 26, 2026

Copy link
Copy Markdown

Greptile Summary

The PR makes advanced transfer capacity affordability-aware, refreshes the selected funding source before order creation, and prevents cancelled fee quotes from updating the displayed estimate.

  • Settles the advanced receiving-capacity maximum against live LSP fee quotes and the selected wallet budget.
  • Re-checks software-wallet or hardware-wallet funding budgets before creating normal and advanced orders.
  • Cancels superseded receiving-capacity quote jobs and adds regression coverage for affordability, balance changes, hardware funding, loading state, and quote ordering.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt Adds affordability settling, live software and hardware budget refreshes, and cancellation of superseded receiving-fee quote jobs without leaving a blocking failure.
app/src/main/java/to/bitkit/ui/screens/transfer/SpendingAdvancedScreen.kt Integrates the settled range and affordability state while disabling amount controls during initialization.
app/src/test/java/to/bitkit/viewmodels/TransferViewModelTest.kt Adds regression coverage for affordable capacity limits, drained funding sources, hardware-wallet selection, loading state, and stale quote ordering.
app/src/main/res/values/strings.xml Adds localized messaging for advanced-transfer affordability rejection.
changelog.d/next/1180.fixed.md Documents that advanced transfers now expose a maximum receiving capacity the selected balance can fund.

Sequence Diagram

sequenceDiagram
    participant User
    participant UI as Advanced transfer UI
    participant VM as TransferViewModel
    participant Wallet as Selected funding source
    participant LSP as Blocktank LSP
    User->>UI: Open advanced transfer
    UI->>VM: updateAdvancedTransferValues(order)
    VM->>Wallet: Load current funding budget
    VM->>LSP: Quote min/max receiving capacity
    LSP-->>VM: Capacity-dependent fees
    VM-->>UI: Affordable capacity range
    User->>UI: Select capacity and continue
    UI->>VM: onSpendingAdvancedContinue(capacity)
    VM->>Wallet: Refresh selected funding budget
    VM->>LSP: Re-quote selected capacity
    alt Affordable
        VM->>LSP: Create order
        VM-->>UI: Navigate to confirmation
    else Unaffordable
        VM-->>UI: Show balance error
    end
Loading

Reviews (4): Last reviewed commit: "fix: re-read device budget before placin..." | Re-trigger Greptile

Comment thread app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt
@jvsena42
jvsena42 marked this pull request as draft August 26, 2026 12:58
@jvsena42
jvsena42 marked this pull request as ready for review August 26, 2026 14:13
@jvsena42
jvsena42 marked this pull request as draft August 26, 2026 14:34
@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 marked this pull request as ready for review August 26, 2026 16:18
Base automatically changed from fix/899-max-transfer-to-spending to master August 26, 2026 16:20
Comment thread app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt Outdated
@jvsena42
jvsena42 marked this pull request as draft August 26, 2026 16:25
@jvsena42
jvsena42 force-pushed the fix/advanced-transfer-affordability branch from 5d171aa to 5a0288f Compare August 26, 2026 16:49
@jvsena42
jvsena42 marked this pull request as ready for review August 26, 2026 16:50
@ovitrif
ovitrif requested review from ovitrif and piotr-iohk August 26, 2026 19:06

@ovitrif ovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants