Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .changeset/migrate-monorepo-to-bun.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ Migrate the monorepo to Bun and publish ESM-only packages.

**Breaking:** all packages drop CommonJS (`require`) entry points and flatten `dist/`. Consumers must use ESM `import`. `@sei-js/precompiles` now ships an `exports` map with working `./ethers`, `./viem`, and `./precompiles` subpaths. `@sei-js/sei-global-wallet` keeps the `./solana` entrypoint.

Install, build, and test with Bun (`bun install`, `bun run build`, `bun run test`). `@sei-js/mcp-server` now requires Node.js 20 or newer. The MCP server and precompiles development toolchain pin Viem to `2.37.2`, the last compatible release that includes `seiDevnet`. Generated apps target the new `@sei-js/precompiles` major. Packages continue to publish to npm via Changesets.
Install, build, and test with Bun (`bun install`, `bun run build`, `bun run test`). `@sei-js/mcp-server` now requires Node.js 20 or newer. The MCP server and precompiles development toolchain use Viem 2.55.16. Generated apps target the new `@sei-js/precompiles` major. Packages continue to publish to npm via Changesets.
5 changes: 5 additions & 0 deletions .changeset/restore-sei-chain-exports.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sei-js/precompiles': major
---

Export Viem's canonical Sei mainnet and testnet definitions from the package root and `viem` entrypoint. Viem 2.55.16 or newer is now required.
6 changes: 6 additions & 0 deletions .changeset/retire-arctic-network.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@sei-js/mcp-server': major
'@sei-js/registry': major
---

Remove Sei devnet support. The MCP server no longer accepts `sei-devnet`, chain ID `713715`, or `DEVNET_RPC_URL`. Registry exports now contain only `pacific-1` and `atlantic-2`, and `CHAIN_IDS.devnet` has been removed.
4 changes: 2 additions & 2 deletions REVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ invariants are load-bearing and are enforced in code rather than by convention:
Beyond those, scrutinise anything that widens what a caller controls: contract
ABIs reach `JSON.parse` from tool arguments in `src/core/tools.ts`, addresses
and call arguments arrive unvalidated from the model, and RPC endpoints are
overridable through `MAINNET_RPC_URL` / `TESTNET_RPC_URL` / `DEVNET_RPC_URL` in
`src/core/chains.ts`. Private keys are read from the environment in
overridable through `MAINNET_RPC_URL` / `TESTNET_RPC_URL` in `src/core/chains.ts`.
Private keys are read from the environment in
`src/core/config.ts` and must never reach a log line, an error message, or a
tool response.

Expand Down
58 changes: 7 additions & 51 deletions bun.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/create-sei/templates/next-template/.env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Chain configuration
# Options: mainnet, testnet, devnet
# Options: mainnet, testnet
# Defaults to mainnet if not set; this example overrides it to testnet
NEXT_PUBLIC_CHAIN=testnet

Expand Down
4 changes: 2 additions & 2 deletions packages/create-sei/templates/next-template/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ In addition, this app uses [RainbowKit](https://www.rainbowkit.com/docs/wallet-b
To change the list of supported wallets, as well as connection configurations you can modify the `src/app/components/Web3Provider.tsx` component.

### Changing the Chain
This application connects to the arctic-1 devnet by default, using chain configurations uploaded to viem.
This application connects to Sei mainnet by default.

To change the chain the app connects to, edit the `selectedChain` variable in the `chain.ts` file in `src/app/constants`
Set `NEXT_PUBLIC_CHAIN=testnet` in `.env.local` to use Sei testnet. The chain selection logic is in `src/components/providers/providers.tsx`.

## Learn More

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,7 @@ export default function DevelopmentPage() {
<Code block>
{`# .env.local
NEXT_PUBLIC_CHAIN=mainnet # Default: Sei Pacific-1
# NEXT_PUBLIC_CHAIN=testnet # Sei Atlantic-2
# NEXT_PUBLIC_CHAIN=devnet # Sei Devnet`}
# NEXT_PUBLIC_CHAIN=testnet # Sei Atlantic-2`}
</Code>
</Paper>
<Text size="sm" c="gray.6">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,8 @@ import { type ReactNode, useMemo } from "react";
import { createConfig, http, WagmiProvider } from "wagmi";

import "@rainbow-me/rainbowkit/styles.css";
import { defineChain } from "viem";
import { sei, seiTestnet } from "viem/chains";

const seiDevnet = defineChain({
id: 713715,
name: "Sei Devnet",
nativeCurrency: { name: "Sei", symbol: "SEI", decimals: 18 },
rpcUrls: {
default: {
http: ["https://evm-rpc-arctic-1.sei-apis.com"],
},
},
blockExplorers: {
default: {
name: "Seitrace",
url: "https://seitrace.com",
},
},
testnet: true,
});

const queryClient = new QueryClient();

const connectors = connectorsForWallets(
Expand All @@ -56,8 +37,6 @@ export default function Providers({ children }: ProvidersProps) {
switch (chainName.toLowerCase()) {
case "testnet":
return seiTestnet;
case "devnet":
return seiDevnet;
default:
return sei;
}
Expand All @@ -73,7 +52,6 @@ export default function Providers({ children }: ProvidersProps) {
transports: {
[sei.id]: http(),
[seiTestnet.id]: http(),
[seiDevnet.id]: http(),
},
ssr: true,
}),
Expand Down
1 change: 0 additions & 1 deletion packages/mcp-server/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,3 @@ SERVER_PATH=/mcp
# Optional, only needed if you want to use a custom RPC URL
MAINNET_RPC_URL=your_mainnet_rpc_url_here
TESTNET_RPC_URL=your_testnet_rpc_url_here
DEVNET_RPC_URL=your_devnet_rpc_url_here
2 changes: 1 addition & 1 deletion packages/mcp-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"cors": "^2.8.5",
"dotenv": "^16.5.0",
"express": "^4.21.2",
"viem": "2.37.2",
"viem": "2.55.16",
"zod": "^3.24.2"
},
"keywords": [
Expand Down
11 changes: 4 additions & 7 deletions packages/mcp-server/src/core/chains.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Chain } from 'viem';
import { sei, seiDevnet, seiTestnet } from 'viem/chains';
import { sei, seiTestnet } from 'viem/chains';

// Default configuration values
export const DEFAULT_NETWORK = 'sei';
Expand All @@ -9,22 +9,19 @@ export const DEFAULT_CHAIN_ID = 1329;
// Map chain IDs to chains
export const chainMap: Record<number, Chain> = {
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.)

};

// Map network names to chain IDs for easier reference
export const networkNameMap: Record<string, number> = {
sei: 1329,
'sei-testnet': 1328,
'sei-devnet': 713_715
'sei-testnet': 1328
};

// Map chain IDs to RPC URLs
export const rpcUrlMap: Record<number, string> = {
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.

};

/**
Expand Down
22 changes: 5 additions & 17 deletions packages/mcp-server/src/core/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@ function registerReadOnlyPrompts(server: McpServer) {
'Explore information about a specific block',
{
blockNumber: z.string().optional().describe('Block number to explore. If not provided, latest block will be used.'),
network: z
.string()
.optional()
.describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet' etc.) or chain ID. Supports all Sei networks. Defaults to Sei mainnet.")
network: z.string().optional().describe("Network name ('sei' or 'sei-testnet') or chain ID. Defaults to Sei mainnet.")
},
({ blockNumber, network = DEFAULT_NETWORK }) => ({
messages: [
Expand All @@ -54,10 +51,7 @@ function registerReadOnlyPrompts(server: McpServer) {
'Analyze a specific transaction',
{
txHash: z.string().describe('Transaction hash to analyze'),
network: z
.string()
.optional()
.describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet' etc.) or chain ID. Supports all Sei networks. Defaults to Sei mainnet.")
network: z.string().optional().describe("Network name ('sei' or 'sei-testnet') or chain ID. Defaults to Sei mainnet.")
},
({ txHash, network = DEFAULT_NETWORK }) => ({
messages: [
Expand All @@ -78,10 +72,7 @@ function registerReadOnlyPrompts(server: McpServer) {
'Analyze an EVM address',
{
address: z.string().describe('Sei 0x address to analyze'),
network: z
.string()
.optional()
.describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet' etc.) or chain ID. Supports all Sei networks. Defaults to Sei mainnet.")
network: z.string().optional().describe("Network name ('sei' or 'sei-testnet') or chain ID. Defaults to Sei mainnet.")
},
({ address, network = DEFAULT_NETWORK }) => ({
messages: [
Expand Down Expand Up @@ -145,7 +136,7 @@ function registerReadOnlyPrompts(server: McpServer) {
'compare_networks',
'Compare Sei networks',
{
networkList: z.string().describe("Comma-separated list of networks to compare (e.g., 'sei,sei-testnet,sei-devnet')")
networkList: z.string().describe("Comma-separated list of networks to compare (for example, 'sei,sei-testnet')")
},
({ networkList }) => {
const networks = networkList.split(',').map((n) => n.trim());
Expand All @@ -171,10 +162,7 @@ function registerReadOnlyPrompts(server: McpServer) {
tokenAddress: z.string().describe('Token contract address to analyze'),
tokenType: z.string().optional().describe('Type of token to analyze (erc20, erc721/nft, or auto-detect). Defaults to auto.'),
tokenId: z.string().optional().describe('Token ID (required for NFT analysis)'),
network: z
.string()
.optional()
.describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet', etc.) or chain ID. Supports all Sei networks. Defaults to Sei mainnet.")
network: z.string().optional().describe("Network name ('sei' or 'sei-testnet') or chain ID. Defaults to Sei mainnet.")
},
({ tokenAddress, tokenType = 'auto', tokenId, network = DEFAULT_NETWORK }) => {
let promptText = '';
Expand Down
Loading
Loading