Skip to content

refactor: CHAIN_REGISTRY — one source of truth for every FE chain fact#2401

Merged
Hugo0 merged 3 commits into
feat/solana-tron-withdrawalsfrom
refactor/chain-registry
Jul 13, 2026
Merged

refactor: CHAIN_REGISTRY — one source of truth for every FE chain fact#2401
Hugo0 merged 3 commits into
feat/solana-tron-withdrawalsfrom
refactor/chain-registry

Conversation

@Hugo0

@Hugo0 Hugo0 commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Summary

The elegance follow-up from the final adversarial review (into #2398 per Hugo): one chain's facts were spread across seven hand-maintained maps (CHAIN_LOGOS, SUPPORTED_EVM_CHAINS, EVM_CHAIN_ID_TO_RHINO_NAME, RHINO_WITHDRAW_SUPPORTED_TOKENS_BY_CHAIN, EVM_DEPOSIT_TOKEN_EXCEPTIONS, CHAIN_ROLLOUT_FLAGS, NON_EVM_WITHDRAW_CHAINS) — the drift between them caused the SCROLL rot and the frozen-SDA incident. All seven still export from their old paths (zero consumer churn), but each is now derived from a single CHAIN_REGISTRY entry per chain.

Adding/launching a chain is now one edit in one file — id, Rhino name, family, deposit/withdraw token sets, logo, rollout flag, all in one entry with the verification requirements documented at the top.

Behavior-proven

src/constants/__tests__/chainRegistry.test.ts asserts every derived map equals the literal it replaced, plus registry invariants (no duplicate ids; routable ⇒ Rhino name; deposit ⇒ displayable; non-EVM withdraw ⇒ synthetic record). Two deliberate, documented supersets: Kaia gains a chainId→name mapping (deposit chain; inert), rollout flags gain display-name alias keys (inert).

🔍 Discovery for a product decision (NOT changed here)

BASE has never been in the curated withdraw gate — users cannot withdraw to Base, apparently since the June-2026 curation. Rhino fully supports it. Enabling = one withdraw: line on the registry entry (+ verification + optional rollout flag). Deliberately left as-is so the refactor stays behavior-neutral.

Out of scope

chain-details.json / token-details.json (generic EVM metadata: explorers, full token lists) and BE CHAINS_CONFIG (already a single map).

QA

typecheck 0 · 1,831 tests green (7 new equality/invariant tests) · next build

One chain's facts were spread across seven hand-maintained maps; the
drift between them caused the SCROLL rot and the frozen-SDA incident.
Every map still exports from its old path, but is now DERIVED from a
single registry entry per chain — adding or launching a chain is one
edit in one file.

Behavior-proven, not just claimed: __tests__/chainRegistry.test.ts
asserts each derived map equals the literal it replaced. Two deliberate
deltas, both documented in the test: Kaia gains a chainId→Rhino-name
mapping (it's a deposit chain; harmless superset), and rollout-flag
keying gains display-name aliases (inert superset). One discovery
preserved as-is: BASE has never been in the curated withdraw gate —
looks like a June-2026 curation oversight, flagged for a product
decision rather than smuggled in via refactor.

Registry invariants are tested too: no duplicate ids, routable chains
must have a Rhino name, deposit chains must be displayable, non-EVM
withdraw destinations must carry their synthetic selector record.
@vercel

vercel Bot commented Jul 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview, Comment Jul 13, 2026 6:36am

Request Review

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The PR introduces a centralized chain registry for Rhino metadata, derives existing support and rollout mappings from it, migrates consumers to the new exports, and adds tests for mapping equivalence, registry invariants, and non-EVM withdrawal isolation.

Changes

Chain registry centralization

Layer / File(s) Summary
Registry data model and derived views
src/constants/chainRegistry.consts.ts
Defines immutable EVM and non-EVM chain metadata, rollout mappings, synthetic withdrawal records, and non-EVM chain detection.
Derived Rhino support surfaces
src/constants/rhino.consts.ts, src/components/Global/TokenSelector/TokenSelector.consts.ts
Derives logos, supported chains, deposit exceptions, Rhino name mappings, and withdrawal token support from CHAIN_REGISTRY.
Consumer migration and registry validation
src/context/tokenSelector.context.tsx, src/features/payments/shared/hooks/useCrossChainTransfer.ts, src/hooks/useChainRollout.ts, src/lib/validation/addressFamily.ts, src/utils/featureFlag.utils.ts, src/constants/__tests__/*
Updates imports to the centralized exports and adds tests covering mappings, supported surfaces, registry invariants, and non-EVM identifier leakage.

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

Possibly related PRs

Suggested labels: enhancement

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: centralizing frontend chain facts into CHAIN_REGISTRY as the source of truth.
Description check ✅ Passed The description is directly aligned with the refactor and explains the registry consolidation, tests, and preserved behavior.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

…ons into the registry

Base: missing from the curated withdraw gate since June — an
oversight, not a decision (Hugo). Verified live before enabling:
ARB→BASE quotes OK for ETH/USDC/USDT + outflow SDA create OK
(2026-07-13). Rollout-flagged (chain-rollout-base, created ON).

Full clean per review: chainRollout.consts and nonEvmWithdraw.consts
existed only to hold single derived constants — their exports now
live in chainRegistry.consts itself and the files are gone. No
re-export shims remain; rhino.consts/TokenSelector.consts keep their
derived chain maps because they co-locate with family-level constants
and a dozen consumers, but every value traces to one registry entry.
…raw gates

Turns the 'gated only by discipline' caveat into an enforced invariant:
NON_EVM_WITHDRAW_CHAINS (solana/tron) must be disjoint from
TOKEN_SELECTOR_SUPPORTED_NETWORK_IDS (send/claim/pay gate) and from
supportedPeanutChains (URL parse/validation source). Fails at test time
if a future change lets a base58-address chain into an EVM-only flow.

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

🧹 Nitpick comments (2)
src/constants/chainRegistry.consts.ts (1)

294-296: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Preserve deep immutability of the exported registry.

readonly currently protects only the array; each ChainRegistryEntry remains mutable. A consumer can mutate an entry after CHAIN_ROLLOUT_FLAGS and NON_EVM_WITHDRAW_CHAINS have been computed, leaving the registry and derived views inconsistent.

Proposed fix
-export const CHAIN_REGISTRY: readonly ChainRegistryEntry[] = CHAIN_REGISTRY_LITERAL
+export const CHAIN_REGISTRY: ReadonlyArray<Readonly<ChainRegistryEntry>> = CHAIN_REGISTRY_LITERAL
🤖 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/constants/chainRegistry.consts.ts` around lines 294 - 296, Update the
exported CHAIN_REGISTRY type and its underlying CHAIN_REGISTRY_LITERAL
definition to enforce deep immutability, including readonly entry properties and
nested values. Ensure consumers cannot mutate any ChainRegistryEntry or nested
registry data after CHAIN_ROLLOUT_FLAGS and NON_EVM_WITHDRAW_CHAINS are derived.
src/constants/__tests__/chainRegistry.test.ts (1)

154-168: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Add an invariant catching id/aliasId collisions.

The uniqueness check only covers entry.id, but EVM_CHAIN_ID_TO_RHINO_NAME in rhino.consts.ts (Line 111) flat-maps [c.id, ...(c.aliasIds ?? [])] into one keyed object. If an aliasId on one entry ever collides with another entry's canonical id (or another entry's alias), Object.fromEntries silently keeps only the last write — no test would fail. Extending the invariant to assert uniqueness across id + all aliasIds combined would catch this class of regression before it reaches the Rhino name resolver.

♻️ Suggested invariant addition
     it('registry invariants', () => {
         const ids = CHAIN_REGISTRY.map((c) => c.id)
         expect(new Set(ids).size).toBe(ids.length) // no duplicate ids
+        const allKeys = CHAIN_REGISTRY.flatMap((c) => [c.id, ...(c.aliasIds ?? [])])
+        expect(new Set(allKeys).size).toBe(allKeys.length) // no id/alias collisions
         for (const entry of CHAIN_REGISTRY) {
🤖 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/constants/__tests__/chainRegistry.test.ts` around lines 154 - 168, Extend
the registry invariants test around CHAIN_REGISTRY to collect each entry’s
canonical id together with all values in aliasIds, then assert the combined list
has no duplicates. Preserve the existing canonical-id uniqueness check and
ensure collisions between ids and aliases, or between aliases, fail the test.
🤖 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.

Nitpick comments:
In `@src/constants/__tests__/chainRegistry.test.ts`:
- Around line 154-168: Extend the registry invariants test around CHAIN_REGISTRY
to collect each entry’s canonical id together with all values in aliasIds, then
assert the combined list has no duplicates. Preserve the existing canonical-id
uniqueness check and ensure collisions between ids and aliases, or between
aliases, fail the test.

In `@src/constants/chainRegistry.consts.ts`:
- Around line 294-296: Update the exported CHAIN_REGISTRY type and its
underlying CHAIN_REGISTRY_LITERAL definition to enforce deep immutability,
including readonly entry properties and nested values. Ensure consumers cannot
mutate any ChainRegistryEntry or nested registry data after CHAIN_ROLLOUT_FLAGS
and NON_EVM_WITHDRAW_CHAINS are derived.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 018f10ce-28c9-4794-a55b-e81e9c1ec96e

📥 Commits

Reviewing files that changed from the base of the PR and between ea63b6c and 89e0621.

📒 Files selected for processing (12)
  • src/components/Global/TokenSelector/TokenSelector.consts.ts
  • src/constants/__tests__/chainRegistry.test.ts
  • src/constants/__tests__/nonEvmLeak.test.ts
  • src/constants/chainRegistry.consts.ts
  • src/constants/chainRollout.consts.ts
  • src/constants/nonEvmWithdraw.consts.ts
  • src/constants/rhino.consts.ts
  • src/context/tokenSelector.context.tsx
  • src/features/payments/shared/hooks/useCrossChainTransfer.ts
  • src/hooks/useChainRollout.ts
  • src/lib/validation/addressFamily.ts
  • src/utils/featureFlag.utils.ts
💤 Files with no reviewable changes (2)
  • src/constants/chainRollout.consts.ts
  • src/constants/nonEvmWithdraw.consts.ts

@Hugo0
Hugo0 merged commit bd5af5f into feat/solana-tron-withdrawals Jul 13, 2026
10 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant