fix(assets): surface chain-native assets in search and fix fiat precision - #12543
Conversation
…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>
|
Warning Review limit reached
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 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 configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (7)
📝 WalkthroughWalkthroughThis PR updates CoinGecko mappings, locale-aware price formatting, portfolio valuation, asset search ranking, degraded account handling, and related asset display logic. ChangesAsset data and CoinGecko mappings
Price formatting
Portfolio, search, and degraded accounts
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
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (4)
src/components/Amount/Amount.tsx (1)
100-111: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd explicit return types to the formatter components.
FiatBase,Fiat, andPriceomit declared return types. DeclareReact.ReactElementfor 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 winAdd explicit return types to the test helpers.
Add return types to
setup,balance, andprice. The new helper functions currently rely on inferred return types.Run
pnpm run lint --fixandpnpm run type-checkafter 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 winAdd a fixture for a Sei ERC-20 platform entry.
The expected map now covers native
sei-networkandethena-usde, but the shown parser input does not exercise the new Sei token branch. Add a coin withplatforms[CoingeckoAssetPlatform.Sei]and assert thateip155: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 winUse descriptive names for the CoinGecko asset index.
accis an unclear abbreviation.remappedOutputdoes not describe the key and value structure. Rename them to names such asassetIdsByCoinGeckoIdandassetIdsForCoinGeckoId.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
⛔ Files ignored due to path filters (31)
packages/caip/src/adapters/coincap/generated/eip155_1/adapter.jsonis excluded by!**/generated/**packages/caip/src/adapters/coincap/generated/eip155_10/adapter.jsonis excluded by!**/generated/**packages/caip/src/adapters/coincap/generated/eip155_137/adapter.jsonis excluded by!**/generated/**packages/caip/src/adapters/coincap/generated/eip155_42161/adapter.jsonis excluded by!**/generated/**packages/caip/src/adapters/coincap/generated/eip155_43114/adapter.jsonis excluded by!**/generated/**packages/caip/src/adapters/coincap/generated/eip155_56/adapter.jsonis excluded by!**/generated/**packages/caip/src/adapters/coincap/generated/eip155_8453/adapter.jsonis excluded by!**/generated/**packages/caip/src/adapters/coincap/generated/solana_5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/adapter.jsonis excluded by!**/generated/**packages/caip/src/adapters/coingecko/generated/eip155_1/adapter.jsonis excluded by!**/generated/**packages/caip/src/adapters/coingecko/generated/eip155_1329/adapter.jsonis excluded by!**/generated/**packages/caip/src/adapters/coingecko/generated/eip155_137/adapter.jsonis excluded by!**/generated/**packages/caip/src/adapters/coingecko/generated/eip155_4663/adapter.jsonis excluded by!**/generated/**packages/caip/src/adapters/coingecko/generated/eip155_5000/adapter.jsonis excluded by!**/generated/**packages/caip/src/adapters/coingecko/generated/eip155_5064014/adapter.jsonis excluded by!**/generated/**packages/caip/src/adapters/coingecko/generated/eip155_56/adapter.jsonis excluded by!**/generated/**packages/caip/src/adapters/coingecko/generated/eip155_747474/adapter.jsonis excluded by!**/generated/**packages/caip/src/adapters/coingecko/generated/eip155_8453/adapter.jsonis excluded by!**/generated/**packages/caip/src/adapters/coingecko/generated/eip155_999/adapter.jsonis excluded by!**/generated/**packages/caip/src/adapters/coingecko/generated/index.tsis excluded by!**/generated/**packages/caip/src/adapters/coingecko/generated/near_mainnet/adapter.jsonis excluded by!**/generated/**packages/caip/src/adapters/coingecko/generated/solana_5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/adapter.jsonis excluded by!**/generated/**public/generated/asset-manifest.jsonis excluded by!**/generated/**public/generated/asset-manifest.json.bris excluded by!**/generated/**public/generated/asset-manifest.json.gzis excluded by!**/*.gz,!**/generated/**public/generated/generatedAssetData.jsonis excluded by!**/generated/**public/generated/generatedAssetData.json.bris excluded by!**/generated/**public/generated/generatedAssetData.json.gzis excluded by!**/*.gz,!**/generated/**public/generated/relatedAssetIndex.jsonis excluded by!**/generated/**public/generated/relatedAssetIndex.json.bris excluded by!**/generated/**public/generated/relatedAssetIndex.json.gzis excluded by!**/*.gz,!**/generated/**src/state/slices/portfolioSlice/__snapshots__/portfolioSlice.test.ts.snapis excluded by!**/*.snap
📒 Files selected for processing (43)
packages/caip/src/adapters/coingecko/index.test.tspackages/caip/src/adapters/coingecko/utils.test.tspackages/caip/src/adapters/coingecko/utils.tspackages/chain-adapters/src/evm/EvmBaseAdapter.tspackages/chain-adapters/src/types.tsscripts/generateAssetData/blacklist.jsonscripts/generateAssetData/color-map.jsonscripts/generateAssetData/ethereal/index.tsscripts/generateAssetData/generateRelatedAssetIndex/generateRelatedAssetIndex.tsscripts/generateAssetData/utils/index.tssrc/components/Amount/Amount.tsxsrc/components/AssetHeader/AssetMarketData.tsxsrc/components/AssetSearch/components/AssetRow.tsxsrc/components/AssetSearch/components/GroupedAssetRow.tsxsrc/components/AssetSearch/components/MarketRow.tsxsrc/components/Graph/PrimaryChart/PrimaryChart.tsxsrc/components/Layout/Header/DegradedStateBanner.tsxsrc/components/Layout/Header/Header.tsxsrc/components/MarketTableVirtualized/PriceCell.tsxsrc/components/MultiHopTrade/components/TradeInput/components/HighlightedTokensPriceCell.tsxsrc/components/MultiHopTrade/components/TradeInput/components/TopAssetCard.tsxsrc/features/agenticChat/components/shared/AssetListItem.tsxsrc/features/agenticChat/components/tools/GetAssetsUI.tsxsrc/hooks/useLocaleFormatter/useLocaleFormatter.currency.test.tsxsrc/hooks/useLocaleFormatter/useLocaleFormatter.test.tsxsrc/hooks/useLocaleFormatter/useLocaleFormatter.tssrc/lib/assetSearch/utils.tssrc/lib/market-service/coingecko/coingecko.test.tssrc/pages/Buy/TopAssets.tsxsrc/pages/Dashboard/components/AccountList/AccountTable.tsxsrc/pages/Dashboard/components/AccountList/GroupedAccounts.tsxsrc/pages/Explore/components/AssetCard.tsxsrc/pages/Fox/components/FoxTokenHeader.tsxsrc/pages/Markets/components/AssetCard.tsxsrc/pages/Markets/components/CardWithSparkline.tsxsrc/pages/ThorChainLP/components/PoolInfo.tsxsrc/state/migrations/index.tssrc/state/slices/common-selectors.tssrc/state/slices/portfolioSlice/portfolioSlice.test.tssrc/state/slices/portfolioSlice/portfolioSlice.tssrc/state/slices/portfolioSlice/portfolioSliceCommon.tssrc/state/slices/portfolioSlice/selectors.tssrc/state/slices/portfolioSlice/utils/index.ts
💤 Files with no reviewable changes (1)
- scripts/generateAssetData/ethereal/index.ts
…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>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/lib/assetSearch/utils.ts (2)
18-28: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd regression tests for the new ranking thresholds.
Test
searchAssetswith indexes 99, 100, 1,999, and 2,000. Verify theTOP_SYMBOL_*,PRIMARY_SYMBOL_*, and no-symbol-bonus paths for exact and prefix matches.The supplied
src/lib/assetSearch/utils.test.tscases 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 winUse enums for the new TypeScript constants.
TOP_MARKET_CAP_INDEXand the new score values useconstdeclarations. 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
📒 Files selected for processing (1)
src/lib/assetSearch/utils.ts
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>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/state/slices/common-selectors.ts (1)
49-50: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse enums for the new search constants.
Replace
UNHELD_RESULT_SLOTS,MIN_ASSET_ID_SEARCH_LENGTH, andMATCHwith descriptive enum members. KeepMATCHnumeric because the filter and comparator rely on numeric ordering.Run
pnpm run lint --fixandpnpm 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
📒 Files selected for processing (5)
src/components/Amount/Amount.tsxsrc/hooks/useLocaleFormatter/useLocaleFormatter.tssrc/lib/assetSearch/utils.tssrc/pages/Dashboard/components/AccountList/AccountTable.tsxsrc/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
…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>
Description
Fixes SS-5721, and the class of bugs it uncovered.
Root cause of the reported issue.
getAssetIdsSortedByMarketCapremapped 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 bychainIdkept 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:
dog→ Dog (Bitcoin) (market-cap rank 847) above Dogecoin (rank 53)s→ Sphynx Cat dust above Sushib→ USDC first, becausebis a hex digit and matched 17,526 of 24,509 results viaassetIdOrdering is now explicit, and applies to the searched and unsearched lists alike:
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:
bitcoinmust find BTC rather than a token whose symbol is literallyBITCOIN.Two matching bugs fell out of the same review.
assetIdmatching now only considers the reference — chain names live in the CAIP prefix, sostarknetwas hitting every Starknet asset instead of STRK — and requires six characters, since partial address search was never meant to fire onb.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.Fiatrounds to the currency's minor units, read fromIntl.resolvedOptions()rather than assumed to be cents — JPY/KRW lose their phantom decimals, KWD/BHD keep their thirdAmount.Pricescales digits to magnitude, for the ~19 sites rendering a quoted price rather than an amount someone holds<threshold derives from the displayed precision instead of a hardcoded0.000001, so a positive value never reads as a flat$0.00Pricing consistency
A single
getUserCurrencyPriceresolver 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-undefinedprice 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.
deriveEvmAccountIdsAndMetadatachecks each derived address witheth_getCodeto spot a WalletConnect smart account. With a chain's nodes unreachable that probe rejects, which rejects the whole derivation — andderiveAccountIdsAndMetadatasettles it, logs the reason, and carries on with an empty result:Discovery then reads the empty result as "no accounts on this chain" and exits its loop normally, so
getAccountis never called,isDegradedis 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 tofalsewhen it can't run.A successful fallback looked healthy.
EvmBaseAdapter.getAccountfalls back to a direct RPC call returningtokens: []. 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
upsertPortfoliodeep merges and an emptyassetIdsarray never clears a populated one — so a failure after a successful load left the account reading as healthy.getAccountis 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:
getAccountfails → discovery catch, bannerA 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
adapter.jsonexisted and was correct, butgenerated/index.tsnever imported it, so USDe had no priceethereum(it is ETH, not akatanatoken)Issue (if applicable)
closes #12534
Linear: SS-5721
Risk
Medium. No transaction construction, signing, or contract interaction is touched. The risk is display and state breadth, not correctness of on-chain actions.
Amount.Fiatis 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 formatterAmount.*(which formats) or does BN arithmetic (which benefits)packages/chain-adaptersgains one optionalAccountfield, defaulted false at the portfolio boundaryReviewers 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-checkandpnpm run lintclean. 765 state/lib tests and 296 search/formatter tests passing. The only failures in the wider suite arehdwallet-integration/keepkey.test.ts(needs a physical device) andpublic-api/integration.test.ts(needs a running server), both failing independently of this branch.New coverage in
useLocaleFormatter.currency.test.tsxfor minor units, the<threshold per currency, andFiatvsPrice. ExistinguseLocaleFormatter.test.tsxexpectations updated — those changes are intentional (6-digit ceiling; JPY/LBP losing phantom decimals; BHD gaining its third).Search strings, expected top results:
tonstarknetdogbfoxrunebitcoinBITCOIN0xa0b80xa0With a funded wallet:
s→ held by balance, Sushi above any dustd→ held dust by balance, then Dai and other unheld by market caplimit − 5held, so five slots remain for unheldOperations
This is user-facing and not flagged. Suggested regression sweep in a preview environment:
$0.00$0.00158), unchanged from productiondev-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 wayScreenshots (if applicable)