Skip to content

feat(config): chain registry for multi-EVM support#8

Merged
fielding merged 3 commits into
mainfrom
feat/chain-registry
Apr 26, 2026
Merged

feat(config): chain registry for multi-EVM support#8
fielding merged 3 commits into
mainfrom
feat/chain-registry

Conversation

@fielding

Copy link
Copy Markdown
Owner

Summary

First PR in the multi-EVM rollout (epic RG-7ce12d). Introduces a Record<chainId, ChainConfig> registry at packages/app/src/config/chains.ts seeded with Base (8453) and Base Sepolia (84532), and rewrites config/contracts.ts as a thin compatibility shim that reads every existing singleton export from getChainConfig(DEFAULT_CHAIN_ID).

Follow-up PRs (in order):

  • RG-9430aa + RG-42e939 — switch /create and /vaults to useChainId() + getChainConfig(), thread usdcDecimals through 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.xyz with 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 across create/vaults without ambiguity about what data changed.

Design notes

  • usdcDecimals is in the ChainConfig shape 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.
  • Env overrides (NEXT_PUBLIC_SABLIER_LOCKUP, NEXT_PUBLIC_USDC_ADDRESS, NEXT_PUBLIC_TREASURY_ADDRESS) still work, but only for the DEFAULT_CHAIN_ID (whichever chain the current deployment is targeting). Preserves today's testnet staging flexibility.
  • BROKER_FEE stays global (0.5% everywhere). Zero-address treasury still disables the fee, matching Sablier's broker-field semantics.
  • PRESETS stays 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 — clean
  • NODE_ENV=production pnpm --filter app build — clean, 9 routes emit
  • Manual smoke: load ripguard.xyz locally against Base mainnet, verify /, /create (connect wallet), /vaults, OG image, share card all render identically to pre-PR
  • Vercel preview deploy — walk the same pages against the preview URL before merging

🤖 Generated with Claude Code

fielding and others added 2 commits April 24, 2026 10:11
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>
@vercel

vercel Bot commented Apr 24, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
ripguard Ready Ready Preview, Comment Apr 25, 2026 2:07am
ripguard-testnet Ready Ready Preview, Comment Apr 25, 2026 2:07am

@fielding fielding left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

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 treasury to the registry address sanity test. sablierLockup and usdc are 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_IDS in 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_FEE shim 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.ts

All 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>
@fielding

Copy link
Copy Markdown
Owner Author

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.

PR Branch What it does
→ 1 #8 feat/chain-registry registry foundation (you are here)
2 #11 feat/page-refactor switch pages to useChainId, thread USDC decimals
3 #12 feat/wagmi-multichain registry-driven wagmi config + wrong-chain guard
4 #13 feat/expand-chains add 6 new chains (Eth/Arb/Op/Poly/Avax/BNB) + BNB disclosure
5 #14 feat/landing-chain-chips landing chain chip row
6 #15 feat/balances-script `pnpm balances` CLI for treasury reads

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 fielding left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Re-reviewed after the latest update. The two test nits from my earlier pass are fixed:

  • treasury is covered by the registry address sanity test.
  • SUPPORTED_CHAIN_IDS is 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.

@fielding fielding merged commit 32a1411 into main Apr 26, 2026
3 checks passed
@fielding fielding deleted the feat/chain-registry branch April 26, 2026 08:06
fielding added a commit that referenced this pull request Apr 26, 2026
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>
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.

1 participant