Skip to content

fix(convert): auto-correct the entry to the affordable max instead of prompting - #1302

Merged
bmc08gt merged 1 commit into
code/cashfrom
fix/conversion-max-fee-handling
Aug 21, 2026
Merged

fix(convert): auto-correct the entry to the affordable max instead of prompting#1302
bmc08gt merged 1 commit into
code/cashfrom
fix/conversion-max-fee-handling

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Problem

Amount entry is capped at the raw balance, but the fee comes out of that same balance:

  • on top — the funding side is Dollars, which has no launchpad sale to skim, so the debit is entered × (1 + f)
  • grossed up — every other currency, where the pool's sell fee is taken out of the sale, so the debit is entered / (1 - f)

Either way, entering the maximum always overruns the balance by exactly the fee — and never by more.

Both flows handled that badly:

  • Get / Buy priced the number the user typed, landed on the receipt, and then popped an "Insufficient Balance After Fees" modal asking them to confirm dropping to the maximum. Accepting only corrected the receipt, so going back to amount entry still showed the old number.
  • Convert out of Dollars had no modal at all — converting the full balance hard-failed at submit with "Insufficient Funds".

Fix

Trim the entry itself to what the balance can actually fund, before anything is priced. The correction band is only ever the fee percentage (checkFundingAmount/checkBalanceLimit already block anything above the raw balance), so there is nothing meaningful to put to the user.

New pure helper entryAffordableAfterFee inverts both conventions:

fee shape debit affordable max
on top entered × (1 + f) balance / (1 + f)
grossed up entered / (1 - f) balance × (1 - f)

The result is floored to the currency's smallest unit via a new Fiat.flooredToSmallestUnit(). Rounding it up would put the debit straight back over the balance — $10.11 at 1% corrects to $10.00, not $10.01, because $10.01 plus its own fee is $10.1101.

The correction writes through a new AmountEntryDelegate.setAmount (a replace-setter — prefill types on top of the existing entry), so the amount screen and the receipt show the same number when the user navigates back.

Buy now derives its fee shape once through buyFeeFor(token), feeding both the correction and the receipt's exchangeFee, so the two can't drift apart. The dead modal branch and its three strings are removed.

Tests

16 new tests, written test-first:

  • FeeAffordableEntryTest (8) — entries with room left alone, zero-fee no-op, whole balance under each fee convention, the flooring case, idempotence, over-balance entries, and a JPY case confirming the correction is denominated in the balance's currency
  • LaunchpadSellFeeTest (4) — the two new inverses round-trip back to the balance
  • FiatTest (3) — flooring
  • AmountEntryDelegateTest (1) — setAmount replaces rather than appends

… prompting

Amount entry is capped at the raw balance, but the fee comes out of that same
balance — charged on top when the funding side is Dollars (no launchpad sale to
skim), grossed up out of the sale otherwise. Entering the maximum therefore
always overruns the balance by exactly the fee, and never by more.

Both flows handled that badly. A Get funded by a token the fee pushed over
balance priced the typed number and then popped an "Insufficient Balance After
Fees" modal asking the user to confirm a correction. Converting the whole
Dollars balance hard-failed at submit with "Insufficient Funds".

Trim the entry itself to what the balance can actually fund, before anything is
priced. The correction band is only ever the fee percentage, so there is nothing
to confirm. Writing through the delegate (rather than only the receipt, as the
modal did) keeps the amount screen agreeing with what the flow priced when the
user navigates back.

The maximum is floored to the currency's smallest unit — rounding it up would
put the debit straight back over the balance ($10.11 at 1% corrects to $10.00,
not $10.01). Buy now derives its fee shape once, feeding both the correction and
the receipt's fee, so the two can't disagree.
@github-actions github-actions Bot added area: network gRPC, connectivity, API, exchange rates area: tokens Token accounts, balances, token info type: fix Bug fix labels Aug 21, 2026
@bmc08gt
bmc08gt merged commit ff05a2f into code/cash Aug 21, 2026
3 checks passed
@bmc08gt
bmc08gt deleted the fix/conversion-max-fee-handling branch August 21, 2026 21:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: network gRPC, connectivity, API, exchange rates area: tokens Token accounts, balances, token info type: fix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant