Skip to content

fix(assets): surface chain-native assets in search and fix fiat precision - #12543

Merged
kaladinlight merged 11 commits into
developfrom
fix/asset-search-and-price-display
Aug 10, 2026
Merged

fix(assets): surface chain-native assets in search and fix fiat precision#12543
kaladinlight merged 11 commits into
developfrom
fix/asset-search-and-price-display

Conversation

@kaladinlight

@kaladinlight kaladinlight commented Aug 10, 2026

Copy link
Copy Markdown
Member

Description

Fixes SS-5721, and the class of bugs it uncovered.

Root cause of the reported issue. getAssetIdsSortedByMarketCap remapped coingecko data keyed on (coingeckoId, chainId). A single chain can hold more than one asset for the same coingecko id — Toncoin is both the TON native asset and a jetton on TON — so keying by chainId kept only the last of them and dropped the chain-native asset from the market-cap-ordered list that search is built on.

Fixing that exposed two larger problems, each of which had been masked rather than handled.

1. Search ordering was expressed as relevance scores

Relevance decided both what matched and how results ranked, so every ordering question had to be encoded as a number. That made short queries behave badly in ways nothing caught:

  • dogDog (Bitcoin) (market-cap rank 847) above Dogecoin (rank 53)
  • sSphynx Cat dust above Sushi
  • bUSDC first, because b is a hex digit and matched 17,526 of 24,509 results via assetId
  • empty search → Bitcoin above Ethereum regardless of balance

Ordering is now explicit, and applies to the searched and unsearched lists alike:

const [held, unheld] = partition(candidates, asset => balanceOf(asset).gt(0))
held.sort(byBalanceDesc, thenMarketCapRank)
unheld.sort(byStrongMatchFirst, thenMarketCapRank)
return held.slice(0, limit - UNHELD_RESULT_SLOTS).concat(unheld).slice(0, limit)

Balance is summed across the family, so a wallet holding Sushi anywhere reads as holding Sushi even though the primary is the Arbitrum variant. Five result slots are reserved for unheld assets, so a large portfolio can never crowd out the thing being searched for.

That let matching shrink to what its name suggests — a flat tier table, with the market-cap gate, the symbol-length rule and the rank plumbing all removed. What stayed is the primary preference, which is load-bearing: bitcoin must find BTC rather than a token whose symbol is literally BITCOIN.

Two matching bugs fell out of the same review. assetId matching now only considers the reference — chain names live in the CAIP prefix, so starknet was hitting every Starknet asset instead of STRK — and requires six characters, since partial address search was never meant to fire on b.

2. Fiat values were truncated to cents in state

A holding worth $0.0016 was persisted as the string "0.00", making a priced sub-cent asset indistinguishable from an unpriced one. It failed every .gt(0) check downstream and rendered as nothing at all.

Values are now kept at full precision in state and formatted at the view:

  • Amount.Fiat rounds to the currency's minor units, read from Intl.resolvedOptions() rather than assumed to be cents — JPY/KRW lose their phantom decimals, KWD/BHD keep their third
  • New Amount.Price scales digits to magnitude, for the ~19 sites rendering a quoted price rather than an amount someone holds
  • The < threshold derives from the displayed precision instead of a hardcoded 0.000001, so a positive value never reads as a flat $0.00

Pricing consistency

A single getUserCurrencyPrice resolver replaced three divergent implementations. Market data is keyed on a family's primary, so a variant with no listing of its own is valued at its primary's price rather than dropping to zero. Also fixes a latent NaN, where a possibly-undefined price poisoned a whole chain total.

Degraded state

Three separate reasons a chain could fail without the banner ever appearing.

A probe could delete the chain. deriveEvmAccountIdsAndMetadata checks each derived address with eth_getCode to spot a WalletConnect smart account. With a chain's nodes unreachable that probe rejects, which rejects the whole derivation — and deriveAccountIdsAndMetadata settles it, logs the reason, and carries on with an empty result:

if (isRejected(result)) console.error(result.reason)   // the namespace simply vanishes

Discovery then reads the empty result as "no accounts on this chain" and exits its loop normally, so getAccount is never called, isDegraded is never set, and the account is never enabled. Every layer behaved correctly on the input it received; the information was destroyed at the top. The probe is an optimisation for one wallet type, so it now defaults to false when it can't run.

A successful fallback looked healthy. EvmBaseAdapter.getAccount falls back to a direct RPC call returning tokens: []. The account then looks completely fine while every token balance is missing — this is why Optimism USDC vanished with no warning. Those accounts are now flagged.

A refetch failure was invisible. The hard-failure path sets the same flag, because upsertPortfolio deep merges and an empty assetIds array never clears a populated one — so a failure after a successful load left the account reading as healthy. getAccount is also bounded at 60s, so an unresponsive node errors rather than loading forever.

Verified end to end against both failure modes, with and without persisted state:

blocked result
unchained only fallback succeeds, account healthy-looking with no tokens → flagged, banner
unchained + all public RPCs derivation survives, getAccount fails → discovery catch, banner
either, after clearing site data banner via discovery; the chain is absent rather than stale, since accounts can't be enumerated

A wallet that skips auto-discovery (Ledger, GridPlus, Trezor) reports nothing here, which is correct — failing to derive an account is a different thing from being unable to fetch one that we know exists.

Portfolio rows

Only families held on more than one chain are expandable; previously non-expandable rows rendered an empty drawer and swallowed navigation. A single-holding family renders as that holding rather than its primary, which may sit on a chain the wallet holds nothing on.

Asset data

  • Registered the Ethereal coingecko adapter — its adapter.json existed and was correct, but generated/index.ts never imported it, so USDe had no price
  • Katana's native asset mapped to ethereum (it is ETH, not a katana token)
  • Added the Sei erc20 platform; blacklisted the Celo erc20 twin of the native asset, which was double-counting portfolio value
  • Related-asset generation uses the retrying axios instance

Issue (if applicable)

closes #12534

Linear: SS-5721

Risk

What protocols, transaction types, wallets or contract interactions might be affected by this PR?

Medium. No transaction construction, signing, or contract interaction is touched. The risk is display and state breadth, not correctness of on-chain actions.

  • Amount.Fiat is used at ~228 sites. Values ≥ $1 are provably unchanged; the exposure is sub-$1 amounts, which previously arrived pre-truncated and so never exercised the graduated formatter
  • Portfolio valuation selectors no longer round in state. Every consumer either renders through Amount.* (which formats) or does BN arithmetic (which benefits)
  • packages/chain-adapters gains one optional Account field, defaulted false at the portfolio boundary
  • Migration bumped to 354 to pick up regenerated asset data

Reviewers should know: the ordering change means a held partial match deliberately precedes an unheld exact match. That is the intended behaviour, not an oversight — the reserved tail is what keeps the exact hit reachable.

Testing

Engineering

pnpm run type-check and pnpm run lint clean. 765 state/lib tests and 296 search/formatter tests passing. The only failures in the wider suite are hdwallet-integration/keepkey.test.ts (needs a physical device) and public-api/integration.test.ts (needs a running server), both failing independently of this branch.

New coverage in useLocaleFormatter.currency.test.tsx for minor units, the < threshold per currency, and Fiat vs Price. Existing useLocaleFormatter.test.tsx expectations updated — those changes are intentional (6-digit ceiling; JPY/LBP losing phantom decimals; BHD gaining its third).

Search strings, expected top results:

query expect previously
ton TON · Toncoin missing entirely — the reported bug
starknet STRK · Starknet Spiko Amundi Overnight Swap Fund
dog DOGE · Dogecoin Dog (Bitcoin), rank 847
b BTC, BNB, BCH USDC — matched via hex in the assetId
fox FOX ViFoxCoin
rune RUNE · THORChain a spam asset named "Rune"
bitcoin BTC a token whose symbol is BITCOIN
0xa0b8 USDC partial address search still works
0xa0 nothing by address below the six-character floor

With a funded wallet:

  • empty search → largest holding first, not Bitcoin
  • s → held by balance, Sushi above any dust
  • d → held dust by balance, then Dai and other unheld by market cap
  • any query → at most limit − 5 held, so five slots remain for unheld

Operations

  • 🏁 My feature is behind a flag and doesn't require operations testing (yet)

This is user-facing and not flagged. Suggested regression sweep in a preview environment:

  1. Portfolio total — should match production. It may tick up slightly, as held variants with no market data of their own are now valued at their family's price instead of zero
  2. Celo — should appear once, not twice; total drops by the previously double-counted amount
  3. My Crypto ordering — real value above dust; dust ordered among itself rather than tied at $0.00
  4. Currency switching — JPY, KRW, KWD, EUR; confirm no phantom or missing decimals
  5. Market/asset pages — prices keep sub-cent precision ($0.00158), unchanged from production
  6. Trade input & confirm — network fee and preview amounts render sensibly, including on cheap L2s
  7. Degraded banner — to force it, block a chain's unchained host in devtools (dev-api.<chain>.shapeshift.com). Blocking that alone exercises the silent-fallback case; blocking its public RPCs too exercises the hard-failure case. The warning icon should appear in the header either way

Screenshots (if applicable)

…sion

Searching "Ton" returned neither the native TON asset nor TON-chain assets
with a balance, because the market-cap ordering remap keyed on (coingeckoId,
chainId). A chain can hold more than one asset for the same coingecko id -
Toncoin is both the TON native asset and a jetton on TON - so keying by chainId
kept only the last of them and dropped the native asset entirely.

Fixing that exposed a second class of bug: portfolio balances were truncated to
cents in state, so a priced sub-cent holding was indistinguishable from an
unpriced one and rendered as nothing at all. Values are now kept at full
precision in state and formatted at the view, with Amount.Fiat rounding to the
currency's minor units and a new Amount.Price scaling digits to magnitude.

- search: rank primary symbol-exact above name-exact, widen the market-cap
  gate to the top 2000, and promote held assets ahead of relevance matches
- state: single getUserCurrencyPrice resolver so a variant with no listing of
  its own is valued at its family primary's price rather than dropping to zero
- format: minor units read from the currency (JPY has none, KWD has three)
  rather than assuming cents; the "<" threshold follows the displayed precision
- degraded: EVM adapters silently fall back to a direct RPC call that returns
  no token balances - flag those accounts so the banner reflects reality, and
  set the flag on hard failures too, since upsertPortfolio deep merges and an
  empty assetIds array never clears a populated one
- portfolio: only expand families held on more than one chain, and render a
  single-holding family as that holding rather than its unheld primary
- assets: register the Ethereal coingecko adapter (its adapter.json existed but
  was never imported, so USDe had no price), map Katana's native asset to
  ethereum, add the Sei erc20 platform, blacklist the Celo erc20 twin

closes #12534

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kaladinlight
kaladinlight requested a review from a team as a code owner August 10, 2026 19:18
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@kaladinlight, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 12 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ec093bf3-8033-4eea-aad6-06765ba78ef7

📥 Commits

Reviewing files that changed from the base of the PR and between 175ec8c and e4e8b2b.

📒 Files selected for processing (7)
  • src/components/Amount/Amount.tsx
  • src/components/Layout/Header/DegradedStateBanner.tsx
  • src/lib/account/evm.ts
  • src/lib/assetSearch/utils.ts
  • src/pages/Dashboard/components/AccountList/AccountTable.tsx
  • src/pages/Dashboard/components/AccountList/GroupedAccounts.tsx
  • src/state/slices/portfolioSlice/selectors.ts
📝 Walkthrough

Walkthrough

This PR updates CoinGecko mappings, locale-aware price formatting, portfolio valuation, asset search ranking, degraded account handling, and related asset display logic.

Changes

Asset data and CoinGecko mappings

Layer / File(s) Summary
CoinGecko chain and asset mappings
packages/caip/src/adapters/coingecko/*
CoinGecko parsing now supports Sei tokens, Sei and Ethereal native assets, and Ethereum-backed Katana assets.
Asset generation and market-cap indexing
scripts/generateAssetData/*, src/lib/market-service/coingecko/coingecko.test.ts
Asset generation updates blacklist, color, relationship, retry, and multi-asset market-cap handling.

Price formatting

Layer / File(s) Summary
Locale-aware fiat and price formatting
src/components/Amount/Amount.tsx, src/hooks/useLocaleFormatter/*
Fiat formatting uses currency minor units. Price formatting uses magnitude-based precision with a six-digit limit.
Market price display integration
src/components/*, src/features/agenticChat/*, src/pages/*
Market price displays now use Amount.Price across charts, search, trading, dashboard, pages, and chat.

Portfolio, search, and degraded accounts

Layer / File(s) Summary
Degraded account state propagation
packages/chain-adapters/src/*, src/state/slices/portfolioSlice/*, src/components/Layout/Header/*
RPC and timeout fallbacks mark incomplete accounts as degraded. Selectors and the header banner expose degraded account IDs.
Related-asset valuation and search ranking
src/state/slices/common-selectors.ts, src/state/slices/portfolioSlice/selectors.ts, src/lib/assetSearch/utils.ts
Related assets receive price fallbacks and family-level valuation. Search prioritizes held assets and ordered match tiers.
Held asset family display
src/pages/Dashboard/components/AccountList/*, src/state/migrations/index.ts, src/state/slices/portfolioSlice/portfolioSlice.test.ts
Account displays exclude spam and zero-balance assets and expand only families with multiple held assets. Migration and selector expectations are updated.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AccountAdapter
  participant PortfolioSlice
  participant PortfolioSelectors
  participant Dashboard
  AccountAdapter->>PortfolioSlice: return account data or fallback
  PortfolioSlice->>PortfolioSelectors: store account and isDegraded state
  PortfolioSelectors->>Dashboard: provide degraded IDs and valued asset families
  Dashboard->>Dashboard: filter held, non-spam assets
Loading

Possibly related PRs

Suggested reviewers: 0xapotheosis

Poem

I’m a rabbit with mappings to chart,
Sei and Ethereal now play their part.
Prices find precision, small or wide,
Held assets hop to the front of the stride.
Degraded accounts raise a sign,
Related balances align.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes substantial unrelated changes for fiat formatting, degraded accounts, portfolio pricing, and asset metadata beyond issue #12534. Split unrelated work into separate pull requests or link issues that define the additional fiat, portfolio, account, and asset-metadata objectives.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The search changes promote held assets and improve symbol, name, and asset-ID matching for issue #12534.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the pull request’s primary changes to asset search and fiat precision.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/asset-search-and-price-display

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 6

🧹 Nitpick comments (4)
src/components/Amount/Amount.tsx (1)

100-111: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add explicit return types to the formatter components.

FiatBase, Fiat, and Price omit declared return types. Declare React.ReactElement for each component.

As per coding guidelines, “ALWAYS use explicit types for function parameters and return values in TypeScript.”

Also applies to: 165-172

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/Amount/Amount.tsx` around lines 100 - 111, Declare an explicit
React.ReactElement return type for the FiatBase, Fiat, and Price formatter
components, updating each component signature while preserving their existing
parameters and rendering behavior.

Source: Coding guidelines

src/hooks/useLocaleFormatter/useLocaleFormatter.currency.test.tsx (1)

16-30: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add explicit return types to the test helpers.

Add return types to setup, balance, and price. The new helper functions currently rely on inferred return types.

Run pnpm run lint --fix and pnpm run type-check after the change.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useLocaleFormatter/useLocaleFormatter.currency.test.tsx` around
lines 16 - 30, Explicitly annotate the return types of the setup, balance, and
price test helpers, preserving their existing hook result and formatter-function
behavior. Run pnpm run lint --fix and pnpm run type-check to verify the changes.

Source: Coding guidelines

packages/caip/src/adapters/coingecko/utils.test.ts (1)

246-251: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a fixture for a Sei ERC-20 platform entry.

The expected map now covers native sei-network and ethena-usde, but the shown parser input does not exercise the new Sei token branch. Add a coin with platforms[CoingeckoAssetPlatform.Sei] and assert that eip155:1329/erc20:<address> maps to its CoinGecko ID.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/caip/src/adapters/coingecko/utils.test.ts` around lines 246 - 251,
Add a test fixture in the parser input using a coin whose platforms include
CoingeckoAssetPlatform.Sei, then extend the expected map to assert its
eip155:1329/erc20:<address> key resolves to that coin’s CoinGecko ID. Keep the
existing native Sei and Ethena entries unchanged.
scripts/generateAssetData/utils/index.ts (1)

53-64: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use descriptive names for the CoinGecko asset index.

acc is an unclear abbreviation. remappedOutput does not describe the key and value structure. Rename them to names such as assetIdsByCoinGeckoId and assetIdsForCoinGeckoId.

As per coding guidelines, “Avoid abbreviations in names unless they are widely understood” and “Avoid non-descriptive variable names.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/generateAssetData/utils/index.ts` around lines 53 - 64, Rename the
reduce accumulator from acc to assetIdsByCoinGeckoId and remappedOutput to a
descriptive name reflecting its CoinGecko-ID-to-asset-IDs mapping. Within the
reducer, rename the per-entry asset ID collection reference to
assetIdsForCoinGeckoId and update all references consistently.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/Layout/Header/Header.tsx`:
- Line 222: Move the DegradedStateBanner rendering out of Header’s desktop-only
return path so degraded state is visible on mobile. Prefer rendering it from
MobileNavBar while preserving the existing isDegradedState or degradedChainIds
condition.

In `@src/hooks/useLocaleFormatter/useLocaleFormatter.currency.test.tsx`:
- Around line 61-65: Update the KWD assertions in the “KWD keeps its third
decimal” test to expect truncation after three fractional digits rather than
rounding, while preserving the existing currency formatting and
non-breaking-space expectations.

In `@src/hooks/useLocaleFormatter/useLocaleFormatter.ts`:
- Around line 168-183: In numberToFiat, separate the currency-derived minimum
fraction digits from the resolved maximum calculation, then clamp the
formatter’s minimumFractionDigits to Math.min(currencyMinimumFractionDigits,
maximumFractionDigits). Compute maximumFractionDigits without referencing the
later-clamped minimum, preserving the existing option override and precision
limits.

In `@src/lib/market-service/coingecko/coingecko.test.ts`:
- Around line 224-228: Update the assertions in the asset ID test to compare
matching value shapes: compare each scalar asset ID with its corresponding key,
or normalize both sides into equivalent arrays. Preserve the existing btcKey and
ethKeys ordering derived from Object.keys(result).

In `@src/state/slices/common-selectors.ts`:
- Around line 639-644: Declare explicit return types for the new callbacks and
memoized results: set hasBalance in src/state/slices/common-selectors.ts lines
639-644 to boolean; type the memoized account-ID result in
src/components/Layout/Header/DegradedStateBanner.tsx lines 52-55; type the
predicate and expandable-set results in
src/pages/Dashboard/components/AccountList/AccountTable.tsx lines 90-108; and
type the held-row collection result in
src/pages/Dashboard/components/AccountList/GroupedAccounts.tsx lines 99-105,
using the existing domain types.

In `@src/state/slices/portfolioSlice/selectors.ts`:
- Line 440: Update the account-balance selector’s valuation logic to use
getUserCurrencyPrice(assetId, assets, marketData) instead of directly reading
marketData[assetId]?.price, while preserving the existing fiatBalance mapping
and formatting behavior.

---

Nitpick comments:
In `@packages/caip/src/adapters/coingecko/utils.test.ts`:
- Around line 246-251: Add a test fixture in the parser input using a coin whose
platforms include CoingeckoAssetPlatform.Sei, then extend the expected map to
assert its eip155:1329/erc20:<address> key resolves to that coin’s CoinGecko ID.
Keep the existing native Sei and Ethena entries unchanged.

In `@scripts/generateAssetData/utils/index.ts`:
- Around line 53-64: Rename the reduce accumulator from acc to
assetIdsByCoinGeckoId and remappedOutput to a descriptive name reflecting its
CoinGecko-ID-to-asset-IDs mapping. Within the reducer, rename the per-entry
asset ID collection reference to assetIdsForCoinGeckoId and update all
references consistently.

In `@src/components/Amount/Amount.tsx`:
- Around line 100-111: Declare an explicit React.ReactElement return type for
the FiatBase, Fiat, and Price formatter components, updating each component
signature while preserving their existing parameters and rendering behavior.

In `@src/hooks/useLocaleFormatter/useLocaleFormatter.currency.test.tsx`:
- Around line 16-30: Explicitly annotate the return types of the setup, balance,
and price test helpers, preserving their existing hook result and
formatter-function behavior. Run pnpm run lint --fix and pnpm run type-check to
verify the changes.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 69a51eac-66ff-4f99-a2e2-19c837621efb

📥 Commits

Reviewing files that changed from the base of the PR and between f016182 and 32010b4.

⛔ Files ignored due to path filters (31)
  • packages/caip/src/adapters/coincap/generated/eip155_1/adapter.json is excluded by !**/generated/**
  • packages/caip/src/adapters/coincap/generated/eip155_10/adapter.json is excluded by !**/generated/**
  • packages/caip/src/adapters/coincap/generated/eip155_137/adapter.json is excluded by !**/generated/**
  • packages/caip/src/adapters/coincap/generated/eip155_42161/adapter.json is excluded by !**/generated/**
  • packages/caip/src/adapters/coincap/generated/eip155_43114/adapter.json is excluded by !**/generated/**
  • packages/caip/src/adapters/coincap/generated/eip155_56/adapter.json is excluded by !**/generated/**
  • packages/caip/src/adapters/coincap/generated/eip155_8453/adapter.json is excluded by !**/generated/**
  • packages/caip/src/adapters/coincap/generated/solana_5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/adapter.json is excluded by !**/generated/**
  • packages/caip/src/adapters/coingecko/generated/eip155_1/adapter.json is excluded by !**/generated/**
  • packages/caip/src/adapters/coingecko/generated/eip155_1329/adapter.json is excluded by !**/generated/**
  • packages/caip/src/adapters/coingecko/generated/eip155_137/adapter.json is excluded by !**/generated/**
  • packages/caip/src/adapters/coingecko/generated/eip155_4663/adapter.json is excluded by !**/generated/**
  • packages/caip/src/adapters/coingecko/generated/eip155_5000/adapter.json is excluded by !**/generated/**
  • packages/caip/src/adapters/coingecko/generated/eip155_5064014/adapter.json is excluded by !**/generated/**
  • packages/caip/src/adapters/coingecko/generated/eip155_56/adapter.json is excluded by !**/generated/**
  • packages/caip/src/adapters/coingecko/generated/eip155_747474/adapter.json is excluded by !**/generated/**
  • packages/caip/src/adapters/coingecko/generated/eip155_8453/adapter.json is excluded by !**/generated/**
  • packages/caip/src/adapters/coingecko/generated/eip155_999/adapter.json is excluded by !**/generated/**
  • packages/caip/src/adapters/coingecko/generated/index.ts is excluded by !**/generated/**
  • packages/caip/src/adapters/coingecko/generated/near_mainnet/adapter.json is excluded by !**/generated/**
  • packages/caip/src/adapters/coingecko/generated/solana_5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/adapter.json is excluded by !**/generated/**
  • public/generated/asset-manifest.json is excluded by !**/generated/**
  • public/generated/asset-manifest.json.br is excluded by !**/generated/**
  • public/generated/asset-manifest.json.gz is excluded by !**/*.gz, !**/generated/**
  • public/generated/generatedAssetData.json is excluded by !**/generated/**
  • public/generated/generatedAssetData.json.br is excluded by !**/generated/**
  • public/generated/generatedAssetData.json.gz is excluded by !**/*.gz, !**/generated/**
  • public/generated/relatedAssetIndex.json is excluded by !**/generated/**
  • public/generated/relatedAssetIndex.json.br is excluded by !**/generated/**
  • public/generated/relatedAssetIndex.json.gz is excluded by !**/*.gz, !**/generated/**
  • src/state/slices/portfolioSlice/__snapshots__/portfolioSlice.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (43)
  • packages/caip/src/adapters/coingecko/index.test.ts
  • packages/caip/src/adapters/coingecko/utils.test.ts
  • packages/caip/src/adapters/coingecko/utils.ts
  • packages/chain-adapters/src/evm/EvmBaseAdapter.ts
  • packages/chain-adapters/src/types.ts
  • scripts/generateAssetData/blacklist.json
  • scripts/generateAssetData/color-map.json
  • scripts/generateAssetData/ethereal/index.ts
  • scripts/generateAssetData/generateRelatedAssetIndex/generateRelatedAssetIndex.ts
  • scripts/generateAssetData/utils/index.ts
  • src/components/Amount/Amount.tsx
  • src/components/AssetHeader/AssetMarketData.tsx
  • src/components/AssetSearch/components/AssetRow.tsx
  • src/components/AssetSearch/components/GroupedAssetRow.tsx
  • src/components/AssetSearch/components/MarketRow.tsx
  • src/components/Graph/PrimaryChart/PrimaryChart.tsx
  • src/components/Layout/Header/DegradedStateBanner.tsx
  • src/components/Layout/Header/Header.tsx
  • src/components/MarketTableVirtualized/PriceCell.tsx
  • src/components/MultiHopTrade/components/TradeInput/components/HighlightedTokensPriceCell.tsx
  • src/components/MultiHopTrade/components/TradeInput/components/TopAssetCard.tsx
  • src/features/agenticChat/components/shared/AssetListItem.tsx
  • src/features/agenticChat/components/tools/GetAssetsUI.tsx
  • src/hooks/useLocaleFormatter/useLocaleFormatter.currency.test.tsx
  • src/hooks/useLocaleFormatter/useLocaleFormatter.test.tsx
  • src/hooks/useLocaleFormatter/useLocaleFormatter.ts
  • src/lib/assetSearch/utils.ts
  • src/lib/market-service/coingecko/coingecko.test.ts
  • src/pages/Buy/TopAssets.tsx
  • src/pages/Dashboard/components/AccountList/AccountTable.tsx
  • src/pages/Dashboard/components/AccountList/GroupedAccounts.tsx
  • src/pages/Explore/components/AssetCard.tsx
  • src/pages/Fox/components/FoxTokenHeader.tsx
  • src/pages/Markets/components/AssetCard.tsx
  • src/pages/Markets/components/CardWithSparkline.tsx
  • src/pages/ThorChainLP/components/PoolInfo.tsx
  • src/state/migrations/index.ts
  • src/state/slices/common-selectors.ts
  • src/state/slices/portfolioSlice/portfolioSlice.test.ts
  • src/state/slices/portfolioSlice/portfolioSlice.ts
  • src/state/slices/portfolioSlice/portfolioSliceCommon.ts
  • src/state/slices/portfolioSlice/selectors.ts
  • src/state/slices/portfolioSlice/utils/index.ts
💤 Files with no reviewable changes (1)
  • scripts/generateAssetData/ethereal/index.ts

Comment thread src/components/Layout/Header/Header.tsx Outdated
Comment thread src/hooks/useLocaleFormatter/useLocaleFormatter.currency.test.tsx
Comment thread src/hooks/useLocaleFormatter/useLocaleFormatter.ts
Comment thread src/lib/market-service/coingecko/coingecko.test.ts
Comment thread src/state/slices/common-selectors.ts Outdated
Comment thread src/state/slices/portfolioSlice/selectors.ts
kaladinlight and others added 2 commits August 10, 2026 13:29
…act match

Searching "dog" returned Dog (Bitcoin) at market-cap index 847 above Dogecoin
at index 53, because an exact symbol match scored better than a prefix one
regardless of how far apart the two assets sit.

Widening the market-cap gate to 2000 is what exposed this - it is also what
fixes "rune" (THORChain over a spam asset literally named Rune) and "wif", so
reverting it trades one bug for two. Instead, assets inside the top of the
market-cap ordering now score their symbol matches above a distant exact match.

Measured over the top 500 primaries, this moves 8 queries - every one of them
a one or two character query where the dominant coin is the better answer:
"b" now finds Bitcoin rather than BUILDon, "t" finds Tron rather than
Threshold Network, "u" finds Tether rather than United Stables.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tion

scoreAsset inferred an asset's standing from its index in the candidate list.
That list is selectAssetsSortedByMarketCapUserCurrencyBalanceCryptoPrecisionAndName,
which orders by balance before market cap - so for a connected wallet every
held asset landed at the front and read as a top-100 coin. Searching "d" put
eight dust holdings above Dai, each earning the strongest symbol bonus in the
table on the strength of being owned rather than being significant.

searchAssets now takes an explicit market cap rank per assetId, and global
search supplies one built from the market-cap-ordered primary list. Assets
absent from it score as having no standing rather than inheriting whatever
their neighbour's position implied.

The other six callers pass differently-ordered lists and keep the previous
positional behaviour, which is no worse than before but is the same latent
trap - worth revisiting once there is a shared rank source.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/lib/assetSearch/utils.ts (2)

18-28: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add regression tests for the new ranking thresholds.

Test searchAssets with indexes 99, 100, 1,999, and 2,000. Verify the TOP_SYMBOL_*, PRIMARY_SYMBOL_*, and no-symbol-bonus paths for exact and prefix matches.

The supplied src/lib/assetSearch/utils.test.ts cases do not cover these threshold boundaries.

Also applies to: 88-106

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/assetSearch/utils.ts` around lines 18 - 28, Add regression coverage
in the asset-search tests for searchAssets at market-cap indexes 99, 100, 1,999,
and 2,000, asserting exact and prefix matches across TOP_SYMBOL_EXACT,
TOP_SYMBOL_PREFIX, PRIMARY_SYMBOL_EXACT, PRIMARY_NAME_EXACT, and the
no-symbol-bonus path. Ensure the assertions verify ranking behavior on both
sides of each threshold while leaving the scoring implementation unchanged.

22-26: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use enums for the new TypeScript constants.

TOP_MARKET_CAP_INDEX and the new score values use const declarations. Convert them to descriptive numeric enums, or document an approved exception for this score table.

As per coding guidelines: “ALWAYS use enums for constants in TypeScript.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/assetSearch/utils.ts` around lines 22 - 26, Replace the
TOP_MARKET_CAP_INDEX and SCORE constant declarations with descriptive numeric
enums, preserving their current numeric values and references. If the score
table cannot use enums, document the approved exception alongside it.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/assetSearch/utils.ts`:
- Around line 94-106: Update every caller of searchAssets, especially the worker
and fallback paths using
selectAssetsSortedByMarketCapUserCurrencyBalanceCryptoPrecisionAndName, to pass
marketCapRankByAssetId. Ensure scoring in searchAssets derives TOP_SYMBOL_* and
related market-cap classification from the rank map rather than the
balance-first input index; alternatively provide a market-cap-ordered list.

---

Nitpick comments:
In `@src/lib/assetSearch/utils.ts`:
- Around line 18-28: Add regression coverage in the asset-search tests for
searchAssets at market-cap indexes 99, 100, 1,999, and 2,000, asserting exact
and prefix matches across TOP_SYMBOL_EXACT, TOP_SYMBOL_PREFIX,
PRIMARY_SYMBOL_EXACT, PRIMARY_NAME_EXACT, and the no-symbol-bonus path. Ensure
the assertions verify ranking behavior on both sides of each threshold while
leaving the scoring implementation unchanged.
- Around line 22-26: Replace the TOP_MARKET_CAP_INDEX and SCORE constant
declarations with descriptive numeric enums, preserving their current numeric
values and references. If the score table cannot use enums, document the
approved exception alongside it.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 60b06d65-6a3b-4a5a-81a9-440e02006313

📥 Commits

Reviewing files that changed from the base of the PR and between 32010b4 and d0226dc.

📒 Files selected for processing (1)
  • src/lib/assetSearch/utils.ts

Comment thread src/lib/assetSearch/utils.ts Outdated
kaladinlight and others added 2 commits August 10, 2026 13:45
Searching "s" put Sphynx Cat above Sushi. Both score PRIMARY_NAME_PREFIX, and
the tie fell through to "preserve original order (which should be by market
cap)" - but for global search that order is balance-first, so a held dust token
edged out an established one. Sushi compounds it: its primary is the Arbitrum
variant, so a wallet holding Sushi anywhere else leaves the primary sorting as
though it had no balance at all.

Sphynx Cat is not in the generated asset data - it is upserted at runtime from
account data - which is also why the market cap gate could not filter it. The
name-based primary bonuses have no market cap gate, so an unranked token still
earns PRIMARY_NAME_PREFIX; ranking now decides between equals.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Relevance decided both what matched and how results ranked, which meant every
ordering question had to be expressed as a score. Ordering is now explicit:
what you hold, largest first, then everything else by market cap, with the tail
of the window reserved for unheld assets so a portfolio cannot crowd out what
was searched for. The same ordering applies to the unsearched list, which
previously short circuited to market cap alone and so showed Bitcoin above
Ethereum regardless of balance.

That leaves matching to do only what its name suggests. The score table loses
the market cap gate, the symbol length rule and the rank map plumbing - all of
which existed to solve ranking - and keeps the primary preference, which is
load bearing: searching "bitcoin" must find BTC rather than a token whose
symbol is literally BITCOIN.

Also fixes an assetId match that had been masked by the old ordering. Addresses
are hex, so "b" matched virtually every EVM asset via assetId - 17,526 of the
24,509 hits for that query. Those sorted last and never surfaced inside a ten
result window; ordering by balance floated them to the top, which is why USDC
led the results for "b". Partial address search now needs six characters.

From review:
- clamp minimumFractionDigits to the resolved maximum, since Intl throws when a
  caller asks for fewer digits than the currency's minor units - reachable now
  that the minimum comes from the currency rather than a hardcoded two
- revert the Header degraded state gate, which was a no-op: Header returns null
  below md, so the banner was already desktop only. Mobile remains uncovered
- explicit return types on the new callbacks and formatter components

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/state/slices/common-selectors.ts (1)

49-50: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use enums for the new search constants.

Replace UNHELD_RESULT_SLOTS, MIN_ASSET_ID_SEARCH_LENGTH, and MATCH with descriptive enum members. Keep MATCH numeric because the filter and comparator rely on numeric ordering.

Run pnpm run lint --fix and pnpm run type-check.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/state/slices/common-selectors.ts` around lines 49 - 50, Replace the
search constants UNHELD_RESULT_SLOTS and MIN_ASSET_ID_SEARCH_LENGTH in
src/state/slices/common-selectors.ts (lines 49-50) and MATCH in
src/lib/assetSearch/utils.ts (lines 12-35) with descriptive enum members,
preserving MATCH as a numeric value for filter and comparator ordering. Run pnpm
run lint --fix and pnpm run type-check.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/state/slices/common-selectors.ts`:
- Around line 627-648: Update orderResults and its caller in searchAssets to
carry each asset’s match tier, sorting results by match tier before held status,
family balance, and market-cap rank. Preserve the existing limit and unheld-slot
behavior, and add a regression test proving an exact symbol match precedes a
higher-balance partial match.

---

Nitpick comments:
In `@src/state/slices/common-selectors.ts`:
- Around line 49-50: Replace the search constants UNHELD_RESULT_SLOTS and
MIN_ASSET_ID_SEARCH_LENGTH in src/state/slices/common-selectors.ts (lines 49-50)
and MATCH in src/lib/assetSearch/utils.ts (lines 12-35) with descriptive enum
members, preserving MATCH as a numeric value for filter and comparator ordering.
Run pnpm run lint --fix and pnpm run type-check.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c12c92e6-8e15-49ee-b2b5-658ef1a6bba9

📥 Commits

Reviewing files that changed from the base of the PR and between d0226dc and 67a6b8b.

📒 Files selected for processing (5)
  • src/components/Amount/Amount.tsx
  • src/hooks/useLocaleFormatter/useLocaleFormatter.ts
  • src/lib/assetSearch/utils.ts
  • src/pages/Dashboard/components/AccountList/AccountTable.tsx
  • src/state/slices/common-selectors.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/pages/Dashboard/components/AccountList/AccountTable.tsx
  • src/components/Amount/Amount.tsx
  • src/hooks/useLocaleFormatter/useLocaleFormatter.ts

Comment thread src/state/slices/common-selectors.ts
kaladinlight and others added 6 commits August 10, 2026 15:16
…ng hits

Two ordering problems that only pure market cap could produce.

"starknet" returned Spiko Amundi Overnight Swap Fund before STRK: chain names
live in the CAIP prefix, so matching the whole assetId meant the query hit every
asset on Starknet. Partial address search only ever wanted the reference.

"fox" returned ViFoxCoin before FOX, which is correct by market cap - ViFoxCoin
is larger - but not by intent. Unheld results now place assets the query names
above ones it merely turns up inside, market cap deciding within each group.
Held assets are unaffected, and "dog" still leads with Dogecoin.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e resolver

Three valuation sites still read marketData[assetId] directly, so a held variant
with no listing of its own was valued at zero: the per-account balance map, the
balance threshold filter, and the synthetic row for an unheld family member.
The first undercounts account-filtered balances and can reorder accounts.

Reported by coderabbit on the per-account map; the other two are the same shape.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…hain

Blocking every Optimism RPC removed the chain from the portfolio with no
degraded state and no error, because the failure never reached anything that
could report it.

deriveEvmAccountIdsAndMetadata probes each derived address with eth_getCode to
spot a WalletConnect smart account. With the nodes unreachable that probe
rejects, which rejects the whole derivation; deriveAccountIdsAndMetadata settles
it, logs the reason and carries on with an empty result. Discovery then reads
that as "no accounts on this chain" and exits its loop normally, so getAccount
is never called, isDegraded is never set, and the account is never enabled -
leaving nothing downstream that knows the chain failed.

The probe is an optimisation for a single wallet type, so it now defaults to
false when it cannot run. Discovery reaches getAccount again, and a chain that
is genuinely unreachable fails where something is watching.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Per coderabbit and the repo's explicit-types guideline.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kaladinlight
kaladinlight merged commit 9cba0b8 into develop Aug 10, 2026
4 checks passed
@kaladinlight
kaladinlight deleted the fix/asset-search-and-price-display branch August 10, 2026 22:11
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.

Issue searching some assets in general search

1 participant