Skip to content

feat(swap): Soroban tokens in the swap picker (C3) - #3073

Open
piyalbasu wants to merge 31 commits into
feat/swap-backend-txfrom
feat/swap-soroban-tokens
Open

piyalbasu wants to merge 31 commits into
feat/swap-backend-txfrom
feat/swap-soroban-tokens

Conversation

@piyalbasu

@piyalbasu piyalbasu commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

What

The swap token picker now offers Soroban contract tokens (SEP-41) alongside classic assets. You can find them through search, Popular tokens, a pasted contract ID (looked up through the backend, so tokens stellar.expert doesn't index still resolve), or your balances, and swap them through LI.FI on mainnet. The Swap button on a Soroban token's details screen now works too.

  • Decimals: each token's decimals are respected end to end. The amount you see is the amount sold, and the minimum received is scaled to the bought token's own decimals. For a token you don't hold, decimals are read through the backend when it is picked; if that fails, the pick is refused, as on mobile.
  • Row subtitle: a Soroban-only token shows a shortened contract ID (CBI7…IHRV), so a lookalike still looks different.
  • Trustline: a Soroban destination never triggers the classic trustline or reserve logic. The one exception is a SAC held by its contract ID, which still needs its classic trustline.
  • Off mainnet: Soroban tokens you don't hold are hidden, since LI.FI only serves mainnet.

Draft, for diff review. Stacked on C2 (feat/swap-backend-tx, which is stacked on C1, feat/swap-backend-quotes): C2 is #3075, C1 is #3074. Needs stellar/freighter-backend-v2#178 live first.

Why

Swap routing spec, client item C3: stellar/wallet-eng-monorepo#131.

Implementation details (for agents)

What changed

  • Picker (useSwapTokenLookup, buildSwapSections, trendingAssets): Soroban records are no longer filtered out. A contract token's identity is its contract ID (swapIdentityKey) for dedupe, hidden filtering and the same-token reset; a held token picked under another code is picked as the balance. A pasted contract ID goes to the backend token lookup, not stellar.expert. Rows carry the shortened contract ID as their subtitle. A record with no code is named by its contract ID.
  • Decimals: read at pick for an unheld contract token (getDecimalsForAsset) and stored with the selection; a failed read refuses the pick and goes to Sentry. The build uses the held or stored decimals and reads only for a destination that never went through the picker.
  • isToken follows the sell-side token (the review reads it against the source): the direction toggle sets it, the destination pick no longer does.
  • Send cap: validateSwapAmount takes isClassic, so the classic TX_SEND_MAX cap applies to classic sources only.
  • Trustline: for a destination that is a held contract balance and that isSacContract identifies as a SAC, the trustline is checked against its classic canonical.
  • Icons: the picker reads icons through the existing pipeline and never writes to the shared icon cache.
  • Asset detail: the Swap button shows for everything except liquidity-pool shares.
  • Contract IDs: one contract-ID check (isContractId) replaces three.

Verification

  • Each fix has a test that was run against the code before the fix and failed. Mutations were applied as exact edits, undone, and caught.
  • Includes a build test where both sides have decimals other than 7 (6 → 18).
  • jest for extension and @shared: 295 suites, 2758 tests pass. tsc and eslint are clean.
  • /code-review at high, then medium on each fix round.

Known follow-ups

  • Send has the same classic cap and 7-decimal limit for contract tokens. That's existing behaviour.
  • A swap into a SAC held by its contract ID pays twice the inclusion fee, the same as the existing trustline case through LI.FI.
  • freighter-backend's /token-details answers 400 for every failure, so a lookup outage reads as "not a token" and a pasted ID shows "No tokens match" instead of the unavailable notice (both platforms). Needs 404 for not-a-token and 5xx for upstream failures.
  • The picker source analytics value is "popular" here and "trending" on mobile.

Screenshots

pr3073-soroban-picker-quote

Mainnet, e2e build with stubbed backend responses. Left: pasting a contract ID lists the Soroban token with its shortened contract ID. Right: an XLM → Soroban token quote through a LI.FI route, at the token's 18 decimals. The mobile PR (stellar/freighter-mobile#1048) shows the same flow against a real backend, through review.

🤖 Generated with Claude Code

piyalbasu and others added 16 commits November 9, 2026 12:14
A contract token can have up to 18 decimals (the backend reads
decimals() on chain); the swap flow assumed 7 everywhere.

- decodeSwapTx accepts contract tokens (a contract token is its own
  contract ID) and reads the amount sold at the sold token's decimals.
  A classic path payment for a contract token is refused.
- getMinOut rounds to the bought token's decimals: the backend parses
  minOut at that precision and rejects extra digits.
- getSwapTokenDecimals: 7 for classic; a contract token's from the held
  balance, else from the picked row. The build refuses when a contract
  destination's decimals are unknown (only reachable via a hand-typed
  destination_asset URL for a token neither held nor picked).
- The sell input, its validation and the build use the source's
  decimals from balances, no longer via isToken, which a later
  destination pick overwrites.
- Custom networks: a contract token has no route; Horizon isn't asked.
- The "custom tokens not supported" error is gone.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Spec §6: the trustline, its 0.5 XLM reserve and the reserve sheet are
classic-only. A non-held contract destination counted as needing a
trustline (two-op fee, reserve preflight, Max less 0.5 XLM). A held
contract source was offered as the reserve sheet's sell token, whose
receive quote is classic-only; it still has to cover the fee in XLM.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Spec §6 / C3: the picker keeps its sources (held tokens, stellar.expert
search and trending, asset lists) and stops filtering out Soroban
contract tokens.

- Held contract tokens appear in "Your tokens" (both pickers), with
  their balance at their decimals.
- Search and trending keep contract records, carrying stellar.expert's
  decimals onto the picked token, so the build knows them.
- Contract tokens are bulk-scanned with Blockaid as CODE-CONTRACT, as
  the Add token flow scans them.
- A contract token needs no trustline. isContract now means a Soroban-
  only token (contract issuer); a SAC is listed in its classic form.
- The "Soroban tokens aren't supported" empty state, hadSorobanMatches
  and the picker's now-unused isDestination prop are gone; e2e test 5
  now expects the contract token's row.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Spec §6 row subtitle: classic tokens keep the issuer's home domain; a
Soroban-only token shows its contract ID shortened (CBI7…IHRV), even if
it reports a domain, so a lookalike still looks different. Held rows
keep the balance subtitle they share with the account home.

The row menu's "View on stellar.expert" opens a contract token's
contract page; /asset/CODE-C… is not a stellar.expert page.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…hed on pick

Spec §6: icons come from the user's asset lists, then the issuer's
stellar.toml; the first hit is cached so the token looks the same
across the wallet. Discover rows showed stellar.expert's toml image even
when a list (or the wallet's icon cache) had another.

Rows now take the wallet's cached icon, else the first asset list's,
else the toml image stellar.expert returned with the record (its copy
of the issuer's stellar.toml, so no extra Horizon + toml round trip per
row). The picked token's icon is written to the Redux and background
icon caches, which the amount screen, review and account home read.
Rows are not cached at search time, so searches don't fill the cache
with tokens nobody picked.

findTokenListIcon is getIconFromTokenLists' lookup without its cache
write, so the picker can run it on every row.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
User-approved decision: a contract token the account doesn't hold gets
its decimals from getDecimalsForAsset (the existing on-chain read via
/token-details), not from the stellar.expert row it was picked from and
never a silent 7. The read runs at the start of the build; if it fails,
the build stops before any quote or /swap/tx call, shows "Couldn't read
this token's details", and reports to Sentry.

getSwapTokenDecimals is gone; getHeldTokenDecimals only reads a held
balance, and getDecimalsForAsset is the one resolver. The sell input
still uses 7 for an unheld source, which has no balance to swap.

New tests: on-chain decimals reach /swap/tx's minOut; a 6 -> 18 decimal
build scales the amount sold and minOut to each side.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
swapQuote's contractIssuer (StrKey.isValidContract) and decodeSwapTx's
own StrKey check duplicated isContractId; both now use it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
TX_SEND_MAX (922337203685.4775807) is the int64 stroop limit of a
classic amount; a contract token's i128 balance has no such cap, and at
18 decimals the cap is not even the right number. Send's validator has
the same cap and 7-decimal check for tokens; left alone here.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
User-approved decision: the picker shows icons through its lookup (the
wallet's icon cache, then asset lists, then the toml image) but never
writes the shared icon cache. Drops the pick-time write from 98dbc66;
the lookup and the shared icon pipeline are unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
User-approved decision: only mainnet has a source (LI.FI) that routes
contract tokens, so on testnet and other networks search and Popular
show no contract token the account doesn't hold. Held ones stay in Your
tokens. buildSwapSections now reads the networkDetails it was passed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
User-approved decision: the asset details screen's Swap button now shows
for contract tokens too (not for liquidity-pool shares), opening Swap
with the token as the source, now that the swap flow handles them.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
A SAC added by contract ID without a trustline is held Soroban-shaped
(injectLocalTokenBalances), named after its classic asset. Picked as the
destination it counted as a contract token, so no reserve preflight and
no 0.5 XLM held back from Max before review.

Checked against freighter-backend-v2 #178 (lifi.go toClassic/BuildTx):
the backend detects a SAC destination from its contract instance and
lets LI.FI's trust(account) through; decodeSwapTx reads it as
addsTrustline, so the build and the review's trustline banner already
worked. Only the pre-review reserve check was missing.

An unheld SAC returned by stellar.expert as a bare contract record would
still be missed; stellar.expert returns SACs in classic form.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
"Swap for 0.5 XLM" swaps the best classic balance into XLM, but left
isToken as a Soroban source had set it, so the review showed empty
Soroban fee rows and asked RPC whether the classic source's SAC supports
muxed addresses. Both sides are classic after it.

The direction toggle also writes isToken false when the new source is a
held Soroban token; the swap review reads Soroban fees from the build's
resource fee, so that one shows nothing wrong and is left as is.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
stellar.expert can return a SEP-41 contract with no code metadata; the
picker showed it as a blank row and carried an empty code to the amount
card and review. Its code falls back to the shortened contract ID, as
its subtitle already shows. The Add token flow's mapStellarExpertRecord
has the same empty fallback; outside swap, left as is.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@github-actions

github-actions Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

PR Preview build is ready: https://github.com/stellar/freighter/releases/tag/untagged-b9a990eb32cef1474b1e
Backend: sandbox (piyalbasu). SDF collaborators only — install instructions in the release description.

…n-tokens

master's #3065 routes asset search and Popular tokens through backend
v2 from the service worker. Resolutions:
- trendingAssets.ts: master's getStellarExpertAssets fetch, keeping this
  branch's contract-token mapping (truncateString, isContractId).
- trendingAssets.test.ts: master's respondWithRecords message mock,
  widened to code/decimals; this branch's two contract-token tests
  moved onto it from the fetch spy.
- swap.test.ts: the contract-address search test stubs
  ASSET_SEARCH_ROUTE on the context with this branch's TKN record; the
  page-level stellar.expert route no longer intercepts anything.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
piyalbasu and others added 5 commits November 9, 2026 19:36
Brings in the merge of 6f50255 (quote loading state; Review gated until
the quote for the current inputs lands) into feat/swap-backend-tx.

Conflicts: none textual.

Semantic fix: getSwapDerivedData now takes publicKey and swapQuoteKey (to
tell whether the stored quote matches the inputs). The "getSwapDerivedData
with a contract token sold" tests in SwapAmount.sorobanTokens.test.tsx call
it directly, so they now pass a key matching their inputs (the quote has
landed); their assertions are unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
# Conflicts:
#	extension/src/popup/components/swap/SwapAmount/index.tsx
# Conflicts:
#	extension/src/popup/components/swap/SwapAmount/__tests__/SwapAmount.liveQuote.test.tsx
#	extension/src/popup/components/swap/SwapAmount/hooks/__tests__/useSimulateSwapData.test.ts
#	extension/src/popup/components/swap/SwapAmount/hooks/useSimulateSwapData.tsx
#	extension/src/popup/helpers/swapQuote.ts
@piyalbasu
piyalbasu marked this pull request as ready for review November 11, 2026 22:38
A held contract token is keyed by its on-chain symbol (SYM:C…) and a
search or Popular row for the same contract by stellar.expert's code
(ABC:C…), so every identity check saw two tokens: the row listed as new
next to the held balance, a hidden token resurfaced in search, picking
the held contract as destination did not reset the source, and the
direction toggle treated the held destination as non-held. Mobile keys
Soroban tokens on the contract ID (canonicalId); the extension now does
the same through swapIdentityKey at each comparison, leaving stored
canonicals and the wire format untouched.

Sites changed (gate 1 grep of canonical-vs-canonical comparisons under
src/popup/components/swap and src/popup/views/Swap): buildSwapSections
held set, verified set, Popular filter and search dedupe;
omitHiddenFromSections; SwapPickerSections hiddenAssets; withRowIcons
cached-icon lookup; getSwapDerivedData.destinationIsNonHeld; the Swap
view's two same-token resets. Not changed: tokenPrices / icons maps
read by canonical (storage keys, not identity), getHeldTokenDecimals
and findAssetBalance (already by contract).

Search dedupe now excludes every held identity from the discover
sections, not only the held rows matching the term, so a held contract
never lists as new under a code the account does not use for it.

Found by /code-review on #3073.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
piyalbasu and others added 2 commits November 11, 2026 19:44
The pick committed the token at once and the build read its decimals
later, so a user could type an amount and see a live quote before
"Couldn't read this token's details" on Review, the receive card sat at
7 decimals meanwhile, and swap.destination_selected counted picks that
never became quotable. Mobile reads at pick and refuses the pick; the
extension now does the same (Piyal's call, 2026-11-11).

The destination picker reads getDecimalsForAsset (the backend's
token-details on public networks) before applying an unheld contract
pick, stores the result on the selection, and refuses the pick with an
error notice when the read fails (reported to Sentry, no metric). A
repeat tap while a read is pending is ignored, and every pick
supersedes a pending read so a late result cannot apply. The picked row
no longer carries stellar.expert's toml-sourced decimals. The build
uses the stored decimals and keeps the build-time read only for a
destination that never went through the picker (defaulted or
deep-linked), which still fails closed.

The receive card's cryptoDecimals was dead: AmountCard reads it only in
the input onChange, which the read-only card stubs. The prop is now
optional (default 7) and dropped from that card.

Found by /code-review and the drift review on #3073.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
The picker sent every search term to stellar.expert, so a contract
token it does not index, or any term during a stellar.expert outage,
came back "No tokens match" and could not be picked, though quoting and
building would handle it. Mobile reads a pasted contract ID through
token-details (handleContractLookup); the extension now does the same
(Piyal's call, 2026-11-11), reusing the Add-token lookup's pieces: the
backend's token details, isSacContractExecutable for a SAC, which is
listed as its classic asset so the trustline logic applies, and the
native row for XLM's own contract.

getTokenDetails gains throwOnFailure, as mobile's did, so a failed read
can be told apart from "not a token": a 400 is an answer (null), any
other failure throws into the lookup's existing catch, which serves the
cached result or the held-only fallback with its "discovery unavailable"
notice, the same path a failed stellar.expert search takes. Other
callers keep the old swallow-and-null behaviour.

Found by /code-review and the drift review on #3073.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Active moderate and critical findings remain in the live-quote preflight, Soroban asset handling, picker identity/display logic, and multi-hop route normalization.

5 open issues
What changed in this PR

Adds Soroban SEP-41 tokens to the swap picker and transaction flow alongside classic assets, with contract-token discovery, decimal-aware quoting and transaction handling, trustline behavior, icons, and asset-detail integration.

Changes:

  • Supports Soroban tokens in balances, search, Popular tokens, and pasted contract-ID flows with mainnet filtering and contract-ID subtitles.
  • Propagates token-specific decimals through validation, quoting, transaction construction, review decoding, and minimum-received calculations.
  • Updates trustline handling, Stellar Expert links, localization, icon lookup, unit tests, and swap E2E coverage.
File Description
extension/​src/​popup/​views/​__tests__/​Swap.telemetry.test.tsx Updates swap telemetry fixtures for the revised token lookup result.
extension/​src/​popup/​views/​__tests__/​Swap.selectionType.test.tsx Updates selection fixtures for the revised swap picker result shape.
extension/​src/​popup/​locales/​pt/​translation.json Adds Portuguese token-detail error text and removes obsolete Soroban swap copy.
extension/​src/​popup/​locales/​en/​translation.json Adds English token-detail error text and removes obsolete Soroban swap copy.
extension/​src/​popup/​locales/​__tests__/​translationParity.test.ts Updates the expected swap translation parity keys.
extension/​src/​popup/​helpers/​trendingAssets.ts Preserves Soroban contract-token records and metadata from trending assets.
extension/​src/​popup/​helpers/​swapQuote.ts Converts contract assets for backend routing and exposes held-token decimals.
extension/​src/​popup/​helpers/​decodeSwapTx.ts Decodes classic and Soroban swaps using token-specific decimal handling.
extension/​src/​popup/​helpers/​__tests__/​trendingAssets.test.ts Tests trending Soroban records, decimals, and fallback naming.
extension/​src/​popup/​helpers/​__tests__/​swapQuote.test.ts Tests contract-token routing and held-token decimal lookup.
extension/​src/​popup/​helpers/​__tests__/​decodeSwapTx.test.ts Tests Soroban transaction decoding and decimal handling.
extension/​src/​popup/​ducks/​transactionSubmission.ts Makes destination-token decimals optional for unresolved contract tokens.
extension/​src/​popup/​components/​swap/​SwapAsset/​SwapTokenMenu/​index.tsx Links contract tokens to their corresponding Stellar Expert pages.
extension/​src/​popup/​components/​swap/​SwapAsset/​SwapTokenMenu/​__tests__/​SwapTokenMenu.test.tsx Tests classic and contract-token Stellar Expert links.
extension/​src/​popup/​components/​swap/​SwapAsset/​SwapPickerSections/​index.tsx Displays contract IDs and token metadata in picker sections.
extension/​src/​popup/​components/​swap/​SwapAsset/​SwapPickerSections/​__tests__/​SwapPickerSections.test.tsx Tests contract-ID subtitles and updated picker empty states.
extension/​src/​popup/​components/​swap/​SwapAsset/​index.tsx Removes obsolete Soroban unsupported-state wiring from the asset picker.
extension/​src/​popup/​components/​swap/​SwapAsset/​hooks/​useSwapTokenLookup.ts Includes Soroban balances and discovery records, applies network filtering, and resolves icons.
extension/​src/​popup/​components/​swap/​SwapAsset/​hooks/​__tests__/​useSwapTokenLookup.test.ts Tests held, popular, searched, and off-mainnet Soroban token behavior.
extension/​src/​popup/​components/​swap/​SwapAsset/​hooks/​__tests__/​useSwapTokenLookup.soroban.test.tsx Tests Soroban search results, decimals, icons, and Blockaid integration.
extension/​src/​popup/​components/​swap/​SwapAsset/​hooks/​__tests__/​useSwapTokenLookup.abort.test.tsx Updates lookup cache fixtures with icon state.
extension/​src/​popup/​components/​swap/​SwapAsset/​__tests__/​SwapAsset.test.tsx Updates asset-picker fixtures for the revised Soroban lookup behavior.
extension/​src/​popup/​components/​swap/​SwapAmount/​index.tsx Applies token decimals and SAC-specific trustline logic to swap amounts.
extension/​src/​popup/​components/​swap/​SwapAmount/​hooks/​useSimulateSwapData.tsx Resolves missing decimals, scales minimum output, and reports decimal lookup failures.
extension/​src/​popup/​components/​swap/​SwapAmount/​hooks/​__tests__/​useSimulateSwapData.test.ts Tests decimal-aware minimum-output calculations and error mapping.
extension/​src/​popup/​components/​swap/​SwapAmount/​hooks/​__tests__/​useSimulateSwapData.quote.test.tsx Tests Soroban quote building, decimal scaling, and decimal-read failures.
extension/​src/​popup/​components/​swap/​SwapAmount/​hooks/​__tests__/​useSimulateSwapData.i18n.test.tsx Updates simulation fixtures with token decimal metadata.
extension/​src/​popup/​components/​swap/​SwapAmount/​helpers/​swapAmountValidation.ts Validates precision per token and applies send caps only to classic sources.
extension/​src/​popup/​components/​swap/​SwapAmount/​helpers/​getSwapDerivedData.ts Updates reserve and fee derivation for Soroban token sources.
extension/​src/​popup/​components/​swap/​SwapAmount/​helpers/​__tests__/​swapAmountHelpers.test.ts Tests token-specific precision and classic-versus-contract amount limits.
extension/​src/​popup/​components/​swap/​SwapAmount/​__tests__/​SwapAmount.sorobanTokens.test.tsx Tests Soroban decimal propagation, trustlines, reserve behavior, and fees.
extension/​src/​popup/​components/​swap/​SwapAmount/​__tests__/​SwapAmount.reserveQuote.test.tsx Tests swap-state reset behavior when replacing a Soroban source.
extension/​src/​popup/​components/​swap/​SwapAmount/​__tests__/​SwapAmount.liveQuote.test.tsx Tests live quote behavior for Soroban destinations.
extension/​src/​popup/​components/​InternalTransaction/​ReviewTransaction/​index.tsx Allows unresolved destination-token decimals in review metadata.
extension/​src/​popup/​components/​account/​AssetDetail/​index.tsx Enables Swap for non-liquidity-pool Soroban assets.
extension/​src/​popup/​components/​__tests__/​AssetDetail.test.tsx Updates asset-detail expectations for the Soroban Swap button.
extension/​e2e-tests/​swap.test.ts Replaces the unsupported-token scenario with Soroban contract-token discovery coverage.
@shared/​api/​types/​types.ts Adds optional decimals to shared trending-asset records.
@shared/​api/​helpers/​getIconFromTokenList.ts Adds side-effect-free token-list icon lookup for swap picker rows.
@shared/​api/​helpers/​__tests__/​getIconFromTokenList.test.ts Tests token-list icon matching and priority behavior.

Review effort: Lite

piyalbasu and others added 6 commits November 11, 2026 19:50
edab953 excluded every held identity from the search results. A held
contract searched by stellar.expert's code (not its balance's symbol)
then vanished: the held row did not match the term, and the search row
was dropped as held. Mobile dedupes search rows only against the held
rows the term matched and flags the rest held by contract ID; the
extension now does the same. Popular still excludes every held token,
as mobile's popularTokens does.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
The toggle cleared isToken unconditionally. With C3 a held contract
token can sit on either side, and the review reads isToken against
srcAsset (getContractIdFromTransactionData) for its contract memo and
muxed-address checks, so toggling a held contract token into the sell
slot left the review treating it as classic. isToken now follows the
token that lands in the sell slot; an empty slot (a non-held
destination that could not move) reads as classic.

Every saveIsToken in the swap flow, and which side it derives from:
- SwapAmount reserve prefill: false; the new source is a classic
  non-XLM token (correct).
- SwapAmount direction toggle: now the new source (this commit).
- Swap view source pick: the picked source's isContract (correct).
- Swap view destination pick: the picked DESTINATION's isContract,
  which is the other side from what the review reads; left as is here.
- Swap view deep-link source: false only when the source param is XLM;
  a classic or contract source param leaves the previous value; left
  as is here.

Copilot thread on #3073.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
handleDestinationPick decided "held" from the Your tokens rows, which in
search mode are only the rows the term matched. A held SYM:C1 found
through its ABC:C1 search row (listed as held since 44e8fee) then got
a pick-time decimals read and was stored as ABC:C1. The pick now looks
the row's contract up against every held balance, the set Popular
filters against, and a held match is picked as the balance: its
canonical, symbol and decimals, no read. Mobile's picker takes the same
rule.

Medium review on the C3 fix round.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
isToken describes the source: the review reads it against srcAsset for
its contract memo and muxed-address checks (see 0912ca0). The
destination pick set it from the destination, so a contract source with
a classic destination picked was reviewed as classic, and a contract
destination flipped it on for a classic source. The pick no longer
writes it; when it clears the source (same-token reset) it clears
isToken with it.

Medium review on the C3 fix round.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
getTokenDetails parsed the body before checking for the 400 that means
"not a token", so the backend's plain-text 400 ("Soroban has been
disabled on pubnet") threw a SyntaxError, which throwOnFailure turns
into a failed lookup. The status is now checked first, matching
mobile's axios path, which never parses a 400's body.

Medium review on the C3 fix round.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The read took the public key from the picker's account data, which can
still be loading when Popular rows are already on screen; a tap then
sent an empty key and the backend answered 400. It now reads the active
account's key from Redux (publicKeySelector), as the rest of the swap
flow does.

Medium review on the C3 fix round.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
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