feat(config): chain registry for multi-EVM support#8
Conversation
Carries forward the task setup from the 2026-04-24 planning session plus a newly captured changelog task. Marks RG-bd4d82 as in_progress since the chain registry work is now being landed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces Record<chainId, ChainConfig> registry in config/chains.ts seeded with Base (8453) and Base Sepolia (84532). Every per-chain value — Sablier Lockup address, USDC address, USDC decimals, treasury, explorer URL, stream start block, log chunk size — lives in the registry now, preparing for the 7-chain rollout (Ethereum, Arbitrum, Optimism, Polygon, Avalanche, BNB to follow in later PRs). usdcDecimals is part of the shape from day one so the page refactor (RG-9430aa) can thread it through the approve/lock flow without another schema churn — BNB's 18-decimal USDC is the sharp edge. config/contracts.ts keeps every existing singleton export but now reads them from getChainConfig(DEFAULT_CHAIN_ID). Behavior on Base mainnet and Base Sepolia is bit-identical to the pre-refactor state; call sites are unchanged. The page-level switch to useChainId() + getChainConfig() and the deletion of these shims lands in RG-9430aa. Refs: RG-bd4d82, RG-42e939 (partial — data shape only) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
fielding
left a comment
There was a problem hiding this comment.
Looks good to me. No blockers.
I checked the registry and compatibility shim path, and it preserves the current Base/Base Sepolia singleton behavior. Keeping call sites unchanged in this PR makes sense, especially with prod already live.
A few small follow-ups, none blocking:
-
Add
treasuryto the registry address sanity test.sablierLockupandusdcare checked today, and treasury matters because it decides whether broker fees work or are disabled. The zero address still passes the same address regex. -
Avoid mutating
SUPPORTED_CHAIN_IDSin the test:expect([...SUPPORTED_CHAIN_IDS].sort()).toEqual(Object.keys(CHAINS).map(Number).sort());
-
In the call-site refactor, make the broker fee derive from the active chain treasury. The current default-chain
BROKER_FEEshim is fine for this PR, but the next step should avoid passing a zero treasury with a non-zero fee to Sablier.
Validation I ran locally:
pnpm --filter app test -- src/config/chains.test.ts src/config/contracts.test.ts
pnpm --filter app exec tsc --noEmit
NODE_ENV=production pnpm --filter app build
NEXT_PUBLIC_CHAIN=base-sepolia pnpm --filter app test -- src/config/chains.test.ts src/config/contracts.test.tsAll passed.
Addresses review feedback on #8: - Add treasury to the address-validity sweep so a malformed treasury (or one accidentally swapped with a non-address value) gets caught alongside sablierLockup and usdc. - Spread SUPPORTED_CHAIN_IDS before sorting so the test doesn't mutate the exported array — the in-place sort would have leaked module state across test runs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reviewer orientation — multi-EVM stack [1/6]This PR is the bottom of a 6-PR stack that ships full multi-EVM support. Each PR builds on the prior; review in chronological order.
Start here. When done, move to #11. Merge order is bottom → top after smoke testing. Smoke test checklist lives at `.handoff/SMOKE_TEST_MULTI_EVM.md` (in the user's local notes; not committed). Test against #15's Vercel preview since it sits on top of the full stack. |
fielding
left a comment
There was a problem hiding this comment.
Re-reviewed after the latest update. The two test nits from my earlier pass are fixed:
treasuryis covered by the registry address sanity test.SUPPORTED_CHAIN_IDSis copied before sorting, so the exported array is not mutated.
No new findings on this layer. The registry foundation and compatibility shim still look behavior-preserving for Base and Base Sepolia.
Replaces the default-chain BROKER_FEE singleton with BROKER_FEE_RATE and two helpers — brokerFeeForTreasury(treasury) and brokerFeePctString(fee) — so each call site can compute the broker fee from its active chain's treasury rather than the deployment's default. Addresses the third review note on PR #8. parseAmountParam now takes an optional decimals cap (default 6) so BNB Chain's 18-decimal USDC isn't truncated by the URL-param sanity regex. The default keeps every existing call site unchanged. The chain-specific singleton exports (SABLIER_LOCKUP, USDC_ADDRESS, TREASURY, EXPLORER_URL, STREAM_START_BLOCK, LOG_CHUNK_SIZE) are removed in this commit; the next commit switches every consumer over to getChainConfig(useChainId()). Refs: RG-9430aa, RG-42e939 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
First PR in the multi-EVM rollout (epic
RG-7ce12d). Introduces aRecord<chainId, ChainConfig>registry atpackages/app/src/config/chains.tsseeded with Base (8453) and Base Sepolia (84532), and rewritesconfig/contracts.tsas a thin compatibility shim that reads every existing singleton export fromgetChainConfig(DEFAULT_CHAIN_ID).Follow-up PRs (in order):
RG-9430aa+RG-42e939— switch/createand/vaultstouseChainId()+getChainConfig(), threadusdcDecimalsthrough the approve/lock flow, delete the singletons.RG-b53952— add the other 6 target chains to the wagmi config, wrong-chain guard on/create.RG-213ce7— landing chain chip row.RG-644a80— per-chain treasury Safes, smoke tests (Arbitrum first for cheap gas, BNB for 18-decimal USDC validation).Why a compatibility shim instead of refactoring call sites in this PR
Production is live on
ripguard.xyzwith real users and real broker-fee revenue. Splitting the registry introduction from the call-site refactor means this PR is provably behavior-preserving on Base mainnet — every singleton import resolves to the same address, block number, and chunk size as before. The next PR can then be read purely as a find-and-replace acrosscreate/vaultswithout ambiguity about what data changed.Design notes
usdcDecimalsis in theChainConfigshape from day one even though no consumer reads it yet. BNB's USDC being 18 decimals (vs 6 on every other target chain) is the single most likely regression source during the rollout; putting the field in the registry now avoids a second schema churn when the page refactor lands.NEXT_PUBLIC_SABLIER_LOCKUP,NEXT_PUBLIC_USDC_ADDRESS,NEXT_PUBLIC_TREASURY_ADDRESS) still work, but only for theDEFAULT_CHAIN_ID(whichever chain the current deployment is targeting). Preserves today's testnet staging flexibility.BROKER_FEEstays global (0.5% everywhere). Zero-address treasury still disables the fee, matching Sablier's broker-field semantics.PRESETSstays global. Schedules aren't chain-scoped.Test plan
pnpm --filter app test— 63 tests pass (was 56; 7 new registry tests)pnpm --filter app exec tsc --noEmit— cleanNODE_ENV=production pnpm --filter app build— clean, 9 routes emitripguard.xyzlocally against Base mainnet, verify/,/create(connect wallet),/vaults, OG image, share card all render identically to pre-PR🤖 Generated with Claude Code