Skip to content

fix(tokens): disable Convert on Dollars when nothing else is held - #1679

Merged
bmc08gt merged 2 commits into
code/cashfrom
fix/convert-disabled-without-destination
Oct 5, 2026
Merged

bmc08gt merged 2 commits into
code/cashfrom
fix/convert-disabled-without-destination

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

A Dollars-only user could open Convert on the Dollars screen into a flow with no destination. Bugsnag 6ac36f88991833fd09434aad: the Convert-from-Dollars fallback picked a stale zero-balance USDC row as the destination, and every confirm failed with "mint not found". Filtering that row out leaves the destination on Dollars, where tapping confirm on the receipt spins forever because ProceedWithConversion returned early without resetting loading.

Change

  • The fallback for a Convert opened from Dollars only picks held currencies with a displayable balance, the rule the destination picker applies. TokenCoordinator keeps zero-balance rows for accounts it no longer sees, and the server rejects a buy into one. This was fix(swap): don't default a Convert from Dollars to an empty holding #1677, folded in here.
  • The Convert tile on the Dollars screen is disabled, not hidden, unless the user holds another currency with a displayable balance. No hint text or toast.
  • TokenInfoViewModel.State.canConvert reuses the existing fundableBalanceMints set, which is built with the same hasDisplayableValue filter as the default-destination fallback. The set is empty until tokenBalances hydrates, so Convert starts disabled while balances load.
  • Other token screens are unchanged: Dollars is always a destination from them, so canConvert is true.
  • OnConvert also checks canConvert, so nothing else can open the flow with no destination.
  • ProceedWithConversion clears loading on every early return, as a backstop if a destination disappears between the button and the receipt.

The destination picker applies the same rule to the localized amount (nativeAmount.hasDisplayableValue), so the two can differ only on dust. Neither filters on launchpadMetadata, so the button doesn't either.

Tests: TokenInfoViewModelConvertTest covers loading, Dollars-only, a zero-balance other holding, another holding with a balance (opens Convert), and a non-Dollars screen. SwapViewModelErrorTest covers the fallback (largest other holding, never an empty one) and the loading reset.

Converting from Dollars opens with Dollars as the destination, and
SwapViewModel swaps in the largest other entry in tokenBalances. That list
can carry zero-balance rows, because TokenState.withAccounts only adds
holdings. Bugsnag 6ac36f88991833fd09434aad: a user holding only Dollars got a
leftover USDC row as the destination, and all six attempts failed with
INVALID_SWAP "mint not found".

Filter the fallback to held currencies with a displayable balance, which is
what the destination picker (SelectTokenViewModel) and iOS
(Session.balances(for:)) already do. With no candidate the destination stays
on Dollars.
A Dollars-only user could open Convert on the Dollars screen, but the flow
had no destination. With #1677 the destination stays on Dollars, and tapping
confirm on the receipt left the spinner on forever: ProceedWithConversion
returned early without resetting loading.

The Convert tile on the Dollars screen is now disabled unless the user holds
another currency with a displayable balance. TokenInfoViewModel.State.canConvert
reads fundableBalanceMints, which uses the same hasDisplayableValue filter as
the swap screen's default destination. The set is empty until balances
hydrate, so Convert starts disabled. Other token screens always have Dollars
as a destination and are unchanged. OnConvert checks canConvert too.

ProceedWithConversion now clears loading on every early return, so a
destination that disappears mid-flow can't strand the spinner.

Bugsnag 6ac36f88991833fd09434aad
@bmc08gt bmc08gt self-assigned this Oct 5, 2026
@github-actions github-actions Bot added type: fix Bug fix area: tokens Token accounts, balances, token info labels Oct 5, 2026
@bmc08gt
bmc08gt changed the base branch from fix/convert-destination-fallback to code/cash October 5, 2026 17:36
@bmc08gt
bmc08gt merged commit c597a6f into code/cash Oct 5, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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