fix(tokens): disable Convert on Dollars when nothing else is held - #1679
Merged
Merged
Conversation
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
changed the base branch from
fix/convert-destination-fallback
to
code/cash
October 5, 2026 17:36
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 becauseProceedWithConversionreturned early without resetting loading.Change
TokenCoordinatorkeeps 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.TokenInfoViewModel.State.canConvertreuses the existingfundableBalanceMintsset, which is built with the samehasDisplayableValuefilter as the default-destination fallback. The set is empty untiltokenBalanceshydrates, so Convert starts disabled while balances load.canConvertis true.OnConvertalso checkscanConvert, so nothing else can open the flow with no destination.ProceedWithConversionclears 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 onlaunchpadMetadata, so the button doesn't either.Tests:
TokenInfoViewModelConvertTestcovers loading, Dollars-only, a zero-balance other holding, another holding with a balance (opens Convert), and a non-Dollars screen.SwapViewModelErrorTestcovers the fallback (largest other holding, never an empty one) and the loading reset.