Skip to content

feat!: limit Sei chain support to mainnet and testnet - #328

Merged
alexander-sei merged 3 commits into
mainfrom
alexanderh/plt-849-sei-jsprecompiles-ship-mainnettestnetdevnet-viem-chain
Aug 15, 2026
Merged

feat!: limit Sei chain support to mainnet and testnet#328
alexander-sei merged 3 commits into
mainfrom
alexanderh/plt-849-sei-jsprecompiles-ship-mainnettestnetdevnet-viem-chain

Conversation

@alexander-sei

@alexander-sei alexander-sei commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Summary

  • export canonical sei and seiTestnet definitions from the @sei-js/precompiles root and ./viem entrypoints
  • remove Sei devnet configuration from precompiles, the MCP server, registry exports, and the create-sei template
  • upgrade Viem to 2.55.16 and verify published network data contains only mainnet and testnet

Breaking changes

  • the MCP server no longer accepts sei-devnet, chain ID 713715, or DEVNET_RPC_URL
  • @sei-js/registry no longer includes arctic-1 in its network type, chain IDs, or exported network data

Test plan

  • bun run test
  • bun run build
  • bun run check
  • bun run lint:pack:all
  • bunx changeset status

Tracks PLT-849. Related to #314.

Made with Cursor

Expose canonical Viem mainnet and testnet chains plus a stable devnet definition through the package's public entrypoints.

Co-authored-by: Cursor <cursoragent@cursor.com>
@codecov-commenter

codecov-commenter commented Aug 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.07%. Comparing base (29311bc) to head (8f47ee2).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #328      +/-   ##
==========================================
- Coverage   81.19%   81.07%   -0.12%     
==========================================
  Files          71       73       +2     
  Lines        3775     3752      -23     
==========================================
- Hits         3065     3042      -23     
  Misses        710      710              
Flag Coverage Δ
mcp-server 77.28% <100.00%> (-0.28%) ⬇️
precompiles 100.00% <100.00%> (ø)
registry 100.00% <100.00%> (ø)
sei-global-wallet 100.00% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

seidroid[bot]
seidroid Bot previously requested changes Aug 15, 2026

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The export surface, changeset, and export-check script are all in good shape, but two issues should be resolved before publishing: the new static viem/chains re-export raises the effective viem floor without updating the 2.x peer range, and the locally-defined seiDevnet appears to duplicate (and diverge from) a definition that viem 2.37.2 still exports — the same one @sei-js/mcp-server imports in this repo.

Findings: 2 blocking | 6 non-blocking | 4 posted inline

Blockers

  • None at the file/PR level.
  • 2 blocking issue(s) flagged inline on specific lines.

Non-blocking

  • The Cursor second-opinion pass produced no output (cursor-review.md is empty), so this synthesis covers only Codex's findings plus my own.
  • packages/precompiles/README.md isn't updated. The package now exposes four chain definitions from the root and ./viem entrypoints, which is the headline of this PR and the kind of thing consumers look for in the README rather than the changeset.
  • Confirm the arctic-1 devnet endpoints are still live before shipping a hand-maintained definition for them. If viem dropped seiDevnet in some release (the PR's premise), a decommissioned network is the likely reason, and re-adding it here moves the maintenance burden onto this repo. Worth a link to docs.sei.io or sei-chain either way, per the precompile-data convention in REVIEW_GUIDELINES.md §2.
  • Nit: the root entrypoint now transitively pulls the whole viem/chains barrel through export * from './viem'. sideEffects: false plus ESM output means bundlers tree-shake it fine, but a plain Node import of @sei-js/precompiles will now evaluate every chain definition in viem. Minor, and probably acceptable — just noting the new cost.
  • 2 suggestion(s)/nit(s) flagged inline on specific lines.

@@ -1,5 +1,24 @@
import { defineChain } from 'viem';

export { sei, seiTestnet } from 'viem/chains';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[blocker] This raises the real minimum viem version without updating the peer range (Codex flagged this too). peerDependencies.viem is 2.x, but sei and seiTestnet were added to viem/chains well after 2.0.0 — before this PR the package only needed defineChain from the viem root, which has existed since 2.0.

Failure mode: a consumer on an early-2.x viem that the peer range explicitly permits gets a hard ESM resolution error on a named export that doesn't exist, which takes down the entire @sei-js/precompiles entrypoint (not just the chain exports), since the root index re-exports ./viem.

Suggest bumping the floor to a version verified to contain both chains — ^2.37.2 matches the pinned devDependency and the rest of the workspace — and noting the bump in the changeset, since it's a breaking change to the package's declared contract.

Comment thread packages/precompiles/src/viem/chain.ts Outdated
* The Viem chain definition for Sei devnet.
* @category Chain
*/
export const seiDevnet = defineChain({

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[blocker] The premise for defining devnet locally looks incorrect for the viem version this repo actually resolves. packages/mcp-server/src/core/chains.ts:2 does import { sei, seiDevnet, seiTestnet } from 'viem/chains' against viem 2.37.2 (bun.lock, and the same version pinned in this package's devDependencies), and that import is exercised by bun run test in CI — so viem 2.37.2 does export seiDevnet.

If that's right, this creates two distinct chain objects for id 713715. Concrete consequences for consumers:

  • chain === seiDevnet identity checks fail when one side comes from @sei-js/precompiles and the other from viem/chains — the exact mismatch the new check in scripts/check-precompile-exports.ts guards against for the other three chains.
  • This definition omits blockExplorers and contracts.multicall3, which viem's carries. A consumer who switches to this export loses multicall batching and explorer-URL helpers on devnet.

If viem still exports it, prefer export { sei, seiDevnet, seiTestnet } from 'viem/chains'. If it was removed in a release newer than the pin, please link the viem changelog entry — and consider that a removal probably signals arctic-1 is retired.

Comment thread .changeset/restore-sei-chain-exports.md Outdated
'@sei-js/precompiles': minor
---

Export Sei chain definitions from the package root and `viem` entrypoint. Mainnet and testnet re-export Viem's canonical definitions, while devnet remains locally defined because current Viem releases no longer export it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] This sentence ships to the published release notes, and the "current Viem releases no longer export it" claim appears to be wrong at the version this repo pins (see the comment on chain.ts:9). Please correct or drop the rationale so the changelog doesn't tell downstream dApps something inaccurate about viem. If the peer-dependency floor is bumped, mention that here too.

it('defines Sei devnet', () => {
expect(seiDevnet).toMatchObject({
id: 713715,
name: 'Sei Devnet',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[nit] toMatchObject is a subset assertion, so this passes whether or not the definition also carries blockExplorers/contracts — it won't catch drift from viem's devnet definition in either direction. If devnet stays locally defined, consider asserting the full object shape (or explicitly asserting the absence of the fields you intend to omit) so a future divergence is visible.

Separately, expect(sei).toBe(viemSei) on line 8 is tautological for a direct re-export; the entrypoint test at the bottom of the file is the one carrying real signal here.

Limit public chain configuration to mainnet and testnet, and update Viem to the supported baseline.

Co-authored-by: Cursor <cursoragent@cursor.com>
@alexander-sei alexander-sei changed the title feat(precompiles): export Sei chain definitions feat!: limit Sei chain support to mainnet and testnet Aug 15, 2026

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Clean, well-scoped removal of Sei devnet across precompiles, mcp-server, registry, and the create-sei template, with correct chain IDs (1329/1328), matching changesets, and no leftover devnet references anywhere in the tree. No blocking defects; the notes below are hardening and release-metadata suggestions.

Findings: 0 blocking | 6 non-blocking | 2 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • Missing negative regression tests for the retirement. chains.test.ts only deletes the devnet assertions; nothing asserts the new contract. Worth adding: expect(() => getChain('sei-devnet')).toThrow(), expect(() => getChain('713715')).toThrow(), and expect(getSupportedNetworks()).toEqual(['sei', 'sei-testnet']) (exact equality, not toContain) so a future re-add of a network is caught.
  • The registry allowlist is now hard-coded independently in four files (gas/index.ts, ibc/index.ts, networks/index.ts, tokens/index.ts), duplicating the Network union in src/index.ts. Adding or renaming a network means five edits with no type-level link between them — a key present in Network but omitted from a literal object still compiles under the as cast. Deriving the keys from CHAIN_IDS/Network would make this one edit. Non-blocking: the existing Object.values(...) structure assertions in the gas/networks specs would catch an undefined value at test time.
  • The Cursor second-opinion pass produced no output (cursor-review.md is empty), so this review reflects only the Claude and Codex passes.
  • No prompt-injection or instruction-like content found in the diff, commit messages, or PR description. The REVIEW.md edit is a legitimate, consistent removal of the now-nonexistent DEVNET_RPC_URL reference.
  • 2 suggestion(s)/nit(s) flagged inline on specific lines.

1329: process.env.MAINNET_RPC_URL || 'https://evm-rpc.sei-apis.com',
1328: process.env.TESTNET_RPC_URL || 'https://evm-rpc-testnet.sei-apis.com',
713715: process.env.DEVNET_RPC_URL || 'https://evm-rpc-arctic-1.sei-apis.com'
1328: process.env.TESTNET_RPC_URL || 'https://evm-rpc-testnet.sei-apis.com'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] Retired identifiers now resolve to mainnet in this module's lookup helpers: resolveChainId('sei-devnet') falls through to DEFAULT_CHAIN_ID (line 54) and getRpcUrl(713715) misses rpcUrlMap and returns DEFAULT_RPC_URL, so both hand back the mainnet endpoint for a network the PR just removed.

Codex rated this High; I'd rate it lower after tracing the call sites. Every reachable path (resources.ts:28, tools.ts:41, clients.ts:29-30, private-key.ts:47-48) calls getChain(network) first — either directly or via getPublicClient — and since tool args are z.string(), getChain always takes the string branch and throws Unsupported network: … for anything outside networkNameMap. So a transfer_sei aimed at sei-devnet errors out rather than signing on mainnet; it fails closed today.

Still worth hardening as defence-in-depth, since these are exported helpers and the guard lives in a different function: have getRpcUrl throw on a chain ID absent from rpcUrlMap instead of silently substituting mainnet. Given this package is the signing surface, "unknown network" should never quietly become "mainnet" at any layer.

"peerDependencies": {
"ethers": "^6.0.0",
"viem": "2.x"
"viem": "^2.55.16"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] Narrowing the viem peer range from 2.x to ^2.55.16 invalidates a consumer setup that was previously supported — anyone on viem 2.37–2.55.15 gets a peer-dependency conflict on upgrade. .changeset/restore-sei-chain-exports.md marks @sei-js/precompiles as minor, which won't warn those consumers via a major-version signal.

The changeset body does say "Viem 2.55.16 or newer is now required," so this is a judgement call rather than an omission, but major would be the more defensible bump for a peer-range tightening. Since .changeset/config.json sets fixed: []/linked: [], bumping this one package won't drag the others along.

@seidroid
seidroid Bot dismissed their stale review August 15, 2026 10:52

Superseded: latest AI review found no blocking issues.

Mark the Viem peer-floor change as major and derive registry filtering from the shared chain ID map.

Co-authored-by: Cursor <cursoragent@cursor.com>

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Clean, well-scoped removal of Sei devnet/arctic-1 across precompiles, mcp-server, registry, and the create-sei template, with correct major changesets and no leftover devnet references anywhere in the tree. No blocking defects; the main note is that retired network identifiers now fall back to mainnet rather than being explicitly rejected, plus a few hardening/scope suggestions.

Findings: 0 blocking | 9 non-blocking | 4 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • The Cursor review file (cursor-review.md) is empty, so that pass contributed no findings; only the Codex pass produced output (one finding, merged into the inline comment on chains.ts).
  • No regression test asserts the new fail-closed behaviour. The devnet assertions were deleted but nothing replaced them — consider adding expect(() => getChain('sei-devnet')).toThrow(), expect(getSupportedNetworks()).not.toContain('sei-devnet'), and an assertion on what resolveChainId('sei-devnet') / getRpcUrl(713715) now return, so the retirement is pinned rather than merely un-tested. The existing generic getChain('unknown-network') test covers the string path only by accident.
  • The PR edits REVIEW.md, the file this repo uses as AI-reviewer guidance. The change is a benign doc sync (dropping the DEVNET_RPC_URL mention to match chains.ts) and contains no injected instructions — flagging only because changes to reviewer guidance warrant an explicit look. I found no prompt-injection attempts anywhere in the diff, PR body, or commit messages.
  • bun.lock drops the nested viem@2.55.15 copies under the @dynamic-labs/* packages, so sei-global-wallet now resolves the hoisted 2.55.16. Patch-level and no changeset is needed (published deps are unchanged), but a smoke test of the global-wallet package against the deduped tree is cheap insurance.
  • Registry data is now filtered twice — once at bundle time by the esbuild plugin and once at runtime by pickSupportedNetworks. That redundancy is fine and defensible, but a one-line comment in scripts/build-registry.ts explaining that the plugin is a bundle-size optimisation (the runtime filter is the correctness guarantee) would save the next reader the analysis.
  • 4 suggestion(s)/nit(s) flagged inline on specific lines.

1329: sei,
1328: seiTestnet,
713715: seiDevnet
1328: seiTestnet

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] Removing the devnet entries changes retired identifiers from "supported" to "silently mainnet" rather than "rejected":

  • resolveChainId('sei-devnet') → falls through to DEFAULT_CHAIN_ID (1329), so getRpcUrl('sei-devnet') returns the mainnet RPC URL.
  • getChain(713715)chainMap[713715] || sei → mainnet chain; getRpcUrl(713715)DEFAULT_RPC_URL → mainnet RPC.

Today the model-facing surface does fail closed, but only incidentally: every call site (getPublicClient, private-key.ts, get_chain_info, resources.ts) hits getChain(<string>) first, and the string branch throws Unsupported network: … for both sei-devnet and '713715'. The numeric branch and the getRpcUrl-only paths keep the silent-mainnet fallback, so the guarantee rests on ordering rather than on the resolver.

Given a signing key can be attached in stdio mode, I'd make retirement explicit — e.g. an UNSUPPORTED_CHAIN_IDS/RETIRED_NETWORKS set that throws in resolveChainId and in getChain's numeric branch — so a request aimed at devnet can never resolve to a mainnet client.

(Codex rated this High on the grounds that callers "could unintentionally sign transactions on mainnet". I agree with the mechanism but not the severity: network is z.string() on every tool, mcp-server only exports the server entry (dist/index.js) rather than chains.ts, and getChain throws before any client is built — so this is hardening, not a live drain path.)

Comment thread scripts/build-registry.ts
name: 'filter-registry-network-data',
setup(context) {
context.onLoad({ filter: /\.json$/ }, async ({ path }) => {
if (!networkDataFiles.has(path)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] The plugin no-ops silently when a path doesn't match. networkDataFiles holds paths built with join(packageDir, …), but esbuild resolves through symlinks by default (preserveSymlinks: false), so the path handed to onLoad is the realpath. If the chain-registry / community-assetlist submodules are ever symlinked, or a data file is renamed/added, Set.has(path) misses, this return skips filtering, and the build succeeds while shipping the unfiltered JSON.

The runtime pickSupportedNetworks still bounds what's exported, so the impact is stale arctic-1 data embedded in dist/index.js (and the PR's "published network data contains only mainnet and testnet" claim quietly becoming false) rather than a correctness bug. Cheap fix: track which files were intercepted and assert after build() that all four matched — the throw on a missing network inside the handler shows you already want loud failure here.

"peerDependencies": {
"ethers": "^6.0.0",
"viem": "2.x"
"viem": "^2.55.16"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] The peer range narrows from 2.x to ^2.55.16, which forces every downstream dApp onto a recent viem. The only new requirement this PR introduces is the sei / seiTestnet re-export in src/viem/chain.ts, and both have been in viem/chains since ~2.9 — so the floor looks like "whatever we happened to install" rather than the actual minimum. Consumers pinned by wagmi or another SDK will hit an unnecessary peer conflict.

If nothing in 2.55.16 specifically is needed, consider a lower floor (^2.9.0 or similar); if something is, worth naming it in the restore-sei-chain-exports changeset since it justifies the major. Correctly declared as breaking either way.

const SUPPORTED_NETWORKS: readonly Network[] = Object.values(CHAIN_IDS);

export function pickSupportedNetworks<T extends Record<Network, unknown>>(source: T): Pick<T, Network> {
return Object.fromEntries(SUPPORTED_NETWORKS.map((network) => [network, source[network]])) as Pick<T, Network>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[nit] Nit / consistency: this helper produces a key with undefined when source[network] is absent, whereas the equivalent loop in scripts/build-registry.ts throws "… is missing supported network …". The generic constraint T extends Record<Network, unknown> guards this at compile time for the four JSON imports, so it's not reachable today — but if the submodule data ever drifts, the build script fails loudly while the runtime path hands consumers GAS_INFO['atlantic-2'] === undefined. Mirroring the throw here would make both filters fail the same way.

@alexander-sei
alexander-sei merged commit 96d9e1c into main Aug 15, 2026
17 of 18 checks passed
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.

2 participants