Skip to content

Commit aeaec89

Browse files
alexander-seiclaude
andcommitted
feat(evm-examples): add runnable code & sandbox embeds
Close the "static vs interactive" gap with Solana's docs (roadmap P0.2) by making the EVM example pages runnable. - RunSnippet: click-to-run read-only JSON-RPC straight against Sei's public RPC (CORS-open, no proxy), with hex->decimal decoding. Wired into the viem and ethers quickstarts as "Try It Live". - SandboxEmbed: click-to-load iframe wrapper for external sandboxes. Tier 2 (CodeSandbox) on the viem/ethers quickstarts for editable, anonymously-runnable viem/ethers TypeScript; Tier 3 (Remix) on deploy-verify/erc20/erc721 to compile + deploy Solidity to Sei testnet, paired with AddSeiButton. Surfaces use theme-agnostic translucent styles (Mintlify does not apply dark:bg-* to custom-snippet divs). No npm imports; all declarations live inside the component per Mintlify's runtime rules. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 0c121ba commit aeaec89

7 files changed

Lines changed: 485 additions & 0 deletions

File tree

‎evm/evm-parity/examples/deploy-verify.mdx‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,30 @@ title: 'Deploy and Verify'
33
description: 'Deploying and verifying smart contracts on Sei with viem, ethers, Foundry, and Hardhat'
44
---
55

6+
import { SandboxEmbed } from '/snippets/sandbox-embed.jsx';
7+
import { AddSeiButton } from '/snippets/add-sei-button.jsx';
8+
69
Sei is EVM-compatible — standard deployment tooling works without modification. This page covers deploying a contract and verifying its source on the Sei block explorer.
710

811
For a deeper look at verification options (Remix, Sourcify UI, batch verification), see the [Verify Contracts](/evm/evm-verify-contracts) page.
912

13+
## Deploy from your browser (Remix)
14+
15+
No local toolchain required — compile and deploy a contract to Sei testnet straight from the browser. The Remix sandbox below preloads a minimal `Counter.sol`.
16+
17+
First, add Sei testnet to your wallet:
18+
19+
<AddSeiButton network="testnet" label="Add Sei testnet" />
20+
21+
Then in Remix: compile under the **Solidity Compiler** tab, open **Deploy & Run**, set **Environment** to **Injected Provider — MetaMask** (or **External HTTP Provider** pointed at `https://evm-rpc-testnet.sei-apis.com`), and click **Deploy**. Sei has instant finality, so the deployment confirms in well under a second.
22+
23+
<SandboxEmbed
24+
kind="remix"
25+
src="https://remix.ethereum.org/?#activate=solidity,fileManager&code=Ly8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IE1JVApwcmFnbWEgc29saWRpdHkgXjAuOC4yNDsKCi8vLyBAdGl0bGUgQ291bnRlciDigJQgbWluaW1hbCBjb250cmFjdCB0byBjb21waWxlICYgZGVwbG95IHRvIFNlaSB0ZXN0bmV0Lgpjb250cmFjdCBDb3VudGVyIHsKICAgIHVpbnQyNTYgcHVibGljIGNvdW50OwogICAgZnVuY3Rpb24gaW5jcmVtZW50KCkgZXh0ZXJuYWwgeyBjb3VudCsrOyB9CiAgICBmdW5jdGlvbiBkZWNyZW1lbnQoKSBleHRlcm5hbCB7IGNvdW50LS07IH0KfQ"
26+
title="Counter.sol · deploy to Sei testnet"
27+
description="Remix IDE with a minimal Counter contract preloaded. Compile in-browser, then deploy to Sei testnet."
28+
/>
29+
1030
## Deploying with viem or ethers
1131

1232
<CodeGroup>

‎evm/evm-parity/examples/erc20.mdx‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,30 @@ title: 'ERC-20 Interaction'
33
description: 'Reading and writing ERC-20 tokens on Sei with viem and ethers'
44
---
55

6+
import { SandboxEmbed } from '/snippets/sandbox-embed.jsx';
7+
import { AddSeiButton } from '/snippets/add-sei-button.jsx';
8+
69
# ERC-20 Interaction
710

811
Standard ERC-20 contracts work on Sei without modification. This page covers the common read and write operations using viem and ethers.
912

13+
## Try it: deploy an ERC-20
14+
15+
Compile and deploy a real ERC-20 to Sei testnet from the browser — the Remix sandbox preloads an OpenZeppelin-based `DemoToken`. Then point the read/write patterns below at your deployed address.
16+
17+
First, add Sei testnet to your wallet:
18+
19+
<AddSeiButton network="testnet" label="Add Sei testnet" />
20+
21+
In Remix, compile under **Solidity Compiler**, then **Deploy & Run** with **Environment** set to **Injected Provider — MetaMask** (or **External HTTP Provider** at `https://evm-rpc-testnet.sei-apis.com`).
22+
23+
<SandboxEmbed
24+
kind="remix"
25+
src="https://remix.ethereum.org/?#activate=solidity,fileManager&code=Ly8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IE1JVApwcmFnbWEgc29saWRpdHkgXjAuOC4yNDsKCmltcG9ydCAiQG9wZW56ZXBwZWxpbi9jb250cmFjdHMvdG9rZW4vRVJDMjAvRVJDMjAuc29sIjsKCmNvbnRyYWN0IERlbW9Ub2tlbiBpcyBFUkMyMCB7CiAgICBjb25zdHJ1Y3RvcigpIEVSQzIwKCJEZW1vIFRva2VuIiwgIkRFTU8iKSB7CiAgICAgICAgX21pbnQobXNnLnNlbmRlciwgMV8wMDBfMDAwICogMTAgKiogZGVjaW1hbHMoKSk7CiAgICB9Cn0"
26+
title="DemoToken (ERC-20) · deploy to Sei testnet"
27+
description="Remix IDE with an OpenZeppelin ERC-20 preloaded. Compile in-browser and deploy to Sei testnet."
28+
/>
29+
1030
## Setup
1131

1232
<CodeGroup>

‎evm/evm-parity/examples/erc721.mdx‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,30 @@ title: 'ERC-721 Interaction'
33
description: 'Reading and writing ERC-721 NFTs on Sei with viem and ethers'
44
---
55

6+
import { SandboxEmbed } from '/snippets/sandbox-embed.jsx';
7+
import { AddSeiButton } from '/snippets/add-sei-button.jsx';
8+
69
# ERC-721 Interaction
710

811
Standard ERC-721 contracts work on Sei without modification. This page covers reading token ownership, transferring NFTs, and managing approvals.
912

13+
## Try it: deploy an ERC-721
14+
15+
Compile and deploy a real ERC-721 to Sei testnet from the browser — the Remix sandbox preloads an OpenZeppelin-based `DemoNFT` with a public `mint()`. Then point the patterns below at your deployed address.
16+
17+
First, add Sei testnet to your wallet:
18+
19+
<AddSeiButton network="testnet" label="Add Sei testnet" />
20+
21+
In Remix, compile under **Solidity Compiler**, then **Deploy & Run** with **Environment** set to **Injected Provider — MetaMask** (or **External HTTP Provider** at `https://evm-rpc-testnet.sei-apis.com`).
22+
23+
<SandboxEmbed
24+
kind="remix"
25+
src="https://remix.ethereum.org/?#activate=solidity,fileManager&code=Ly8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IE1JVApwcmFnbWEgc29saWRpdHkgXjAuOC4yNDsKCmltcG9ydCAiQG9wZW56ZXBwZWxpbi9jb250cmFjdHMvdG9rZW4vRVJDNzIxL0VSQzcyMS5zb2wiOwoKY29udHJhY3QgRGVtb05GVCBpcyBFUkM3MjEgewogICAgdWludDI1NiBwdWJsaWMgbmV4dElkOwogICAgY29uc3RydWN0b3IoKSBFUkM3MjEoIkRlbW8gTkZUIiwgIkRORlQiKSB7fQogICAgZnVuY3Rpb24gbWludCgpIGV4dGVybmFsIHsgX3NhZmVNaW50KG1zZy5zZW5kZXIsIG5leHRJZCsrKTsgfQp9"
26+
title="DemoNFT (ERC-721) · deploy to Sei testnet"
27+
description="Remix IDE with an OpenZeppelin ERC-721 preloaded. Compile in-browser and deploy to Sei testnet."
28+
/>
29+
1030
## Setup
1131

1232
<CodeGroup>

‎evm/evm-parity/examples/ethers-quickstart.mdx‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ title: 'ethers v6 Quickstart'
33
description: 'Using ethers v6 with Sei in a Node.js script or browser context'
44
---
55

6+
import { RunSnippet } from '/snippets/run-snippet.jsx';
7+
import { SandboxEmbed } from '/snippets/sandbox-embed.jsx';
8+
69
# ethers v6 Quickstart
710

811
This example covers ethers v6 with Sei. The patterns apply whether you are writing a Node.js script, a CLI tool, or a browser app.
@@ -55,6 +58,31 @@ Nonce: 7
5558

5659
The exact numbers are illustrative — your block number and balance will differ. `getBlockNumber()` and `getTransactionCount()` return a `number`, while `getBalance()` returns a `bigint` in wei (format it with `ethers.formatEther()`).
5760

61+
## Try It Live
62+
63+
Run these read-only calls against Sei mainnet right now — no install, no private key. They're plain JSON-RPC, the same reads the script above performs.
64+
65+
<RunSnippet method="eth_blockNumber" network="mainnet" title="eth_blockNumber" />
66+
67+
<RunSnippet
68+
method="eth_getBalance"
69+
params={["0x0000000000000000000000000000000000000000", "latest"]}
70+
network="mainnet"
71+
title="eth_getBalance"
72+
description="Balance in wei of the example address at the latest block — swap in any address."
73+
/>
74+
75+
## Edit and run
76+
77+
Edit and re-run the full ethers code from this page live in the browser — a real ethers v6 `JsonRpcProvider` reading Sei testnet:
78+
79+
<SandboxEmbed
80+
kind="codesandbox"
81+
src="https://codesandbox.io/embed/qs82lw?view=split&hidenavigation=1&theme=dark"
82+
title="ethers · read Sei testnet"
83+
description="A real ethers v6 provider reading Sei testnet (atlantic-2). Edit index.js and the preview re-runs."
84+
/>
85+
5886
## Browser Provider
5987

6088
In a browser context, connect to the user's injected wallet:

‎evm/evm-parity/examples/viem-quickstart.mdx‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ title: 'viem Quickstart'
33
description: 'Using viem with Sei in a Node.js script, CLI tool, or backend service'
44
---
55

6+
import { RunSnippet } from '/snippets/run-snippet.jsx';
7+
import { SandboxEmbed } from '/snippets/sandbox-embed.jsx';
8+
69
# viem Quickstart
710

811
This example shows how to use viem with Sei outside of a React context — in a Node.js script, CLI tool, or backend service. For React apps, see [wagmi](https://wagmi.sh) and the [frontend guide](/evm/building-a-frontend).
@@ -72,6 +75,33 @@ Nonce: 7
7275

7376
The exact numbers are illustrative — your block number and balance will differ. Note the trailing `n`: `getBlockNumber()` and `getBalance()` return `bigint`, while `getTransactionCount()` returns a `number`.
7477

78+
## Try It Live
79+
80+
Run these read-only calls against Sei mainnet right now — no install, no private key. They're the same reads the script above performs, issued straight from your browser.
81+
82+
<RunSnippet method="eth_blockNumber" network="mainnet" title="eth_blockNumber" description="Latest committed block on pacific-1. Returns a hex quantity — the widget decodes it to decimal below the response." />
83+
84+
<RunSnippet method="eth_chainId" network="mainnet" title="eth_chainId" description="Sei mainnet EVM chain ID. 0x531 = 1329." />
85+
86+
<RunSnippet
87+
method="eth_getBalance"
88+
params={["0x0000000000000000000000000000000000000000", "latest"]}
89+
network="mainnet"
90+
title="eth_getBalance"
91+
description="Balance in wei of the example address at the latest block — swap in any address."
92+
/>
93+
94+
## Edit and run
95+
96+
The buttons above issue raw JSON-RPC. To run the full viem code from this page — imports, a public client, several reads — edit and re-run it live in the sandbox below. No install, no wallet, no key:
97+
98+
<SandboxEmbed
99+
kind="codesandbox"
100+
src="https://codesandbox.io/embed/cgtx45?view=split&hidenavigation=1&theme=dark"
101+
title="viem · read Sei testnet"
102+
description="A real viem public client reading Sei testnet (atlantic-2). Edit index.js and the preview re-runs."
103+
/>
104+
75105
## Wallet Client
76106

77107
A wallet client handles signing and broadcasting transactions.

‎snippets/run-snippet.jsx‎

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
// RunSnippet — a click-to-load "Try it" button for read-only EVM JSON-RPC calls.
2+
//
3+
// Sei's public EVM RPC endpoints return `access-control-allow-origin: *`, so a
4+
// read-only call can be issued straight from the browser with a plain `fetch`
5+
// POST — no SDK, no proxy. Mintlify's React runtime cannot import npm packages,
6+
// so this component uses only browser globals (fetch, BigInt).
7+
//
8+
// Theming note: Mintlify applies `dark:text-*` utilities (class strategy) but
9+
// NOT `dark:bg-*` / `dark:border-*` on custom-snippet container <div>s — verified
10+
// empirically. So surfaces and hairlines use theme-agnostic translucent inline
11+
// styles (rgba grey), which render correctly over both light and dark backdrops,
12+
// while text keeps the working `dark:text-*` classes.
13+
//
14+
// Usage in MDX:
15+
// import { RunSnippet } from '/snippets/run-snippet.jsx';
16+
//
17+
// <RunSnippet method="eth_blockNumber" />
18+
// <RunSnippet
19+
// method="eth_getBalance"
20+
// params={["0x0000000000000000000000000000000000000000", "latest"]}
21+
// network="testnet"
22+
// description="Read the SEI balance of an address at the latest block."
23+
// />
24+
export const RunSnippet = (props) => {
25+
const {
26+
method = 'eth_blockNumber',
27+
params = [],
28+
network = 'testnet',
29+
endpoint,
30+
label,
31+
title,
32+
description,
33+
decode = 'auto'
34+
} = props || {};
35+
36+
// --- config (kept inside the component: module-level decls crash in Mintlify) ---
37+
const ENDPOINTS = {
38+
testnet: 'https://evm-rpc-testnet.sei-apis.com',
39+
mainnet: 'https://evm-rpc.sei-apis.com'
40+
};
41+
const rpcUrl = endpoint || ENDPOINTS[network] || ENDPOINTS.testnet;
42+
const networkLabel = network === 'mainnet' ? 'pacific-1 · mainnet' : network === 'testnet' ? 'atlantic-2 · testnet' : network;
43+
44+
const requestBody = { jsonrpc: '2.0', id: 1, method, params };
45+
const requestJson = JSON.stringify(requestBody, null, 2);
46+
47+
// --- state ---
48+
const [phase, setPhase] = useState('idle'); // idle | loading | success | error
49+
const [result, setResult] = useState(null);
50+
const [errorMsg, setErrorMsg] = useState(null);
51+
const [elapsed, setElapsed] = useState(null);
52+
const [copied, setCopied] = useState(false);
53+
const [btnHover, setBtnHover] = useState(false);
54+
55+
// --- helpers (nested) ---
56+
const groupThousands = (s) => s.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
57+
58+
const hexToDecimal = (value) => {
59+
if (typeof value !== 'string' || !/^0x[0-9a-fA-F]+$/.test(value)) return null;
60+
try {
61+
return groupThousands(BigInt(value).toString(10));
62+
} catch (e) {
63+
return null;
64+
}
65+
};
66+
67+
const run = async () => {
68+
setPhase('loading');
69+
setErrorMsg(null);
70+
setResult(null);
71+
setElapsed(null);
72+
const startedAt = typeof performance !== 'undefined' ? performance.now() : null;
73+
try {
74+
const response = await fetch(rpcUrl, {
75+
method: 'POST',
76+
headers: { 'Content-Type': 'application/json' },
77+
body: JSON.stringify(requestBody)
78+
});
79+
const data = await response.json();
80+
if (startedAt != null && typeof performance !== 'undefined') {
81+
setElapsed(Math.round(performance.now() - startedAt));
82+
}
83+
if (data && data.error) {
84+
setErrorMsg(data.error.message || 'RPC returned an error');
85+
setPhase('error');
86+
return;
87+
}
88+
setResult(data ? data.result : undefined);
89+
setPhase('success');
90+
} catch (err) {
91+
setErrorMsg(err && err.message ? err.message : 'Request failed');
92+
setPhase('error');
93+
}
94+
};
95+
96+
const resultString = result === undefined ? 'undefined' : JSON.stringify(result, null, 2);
97+
const decoded = decode !== 'off' && typeof result === 'string' ? hexToDecimal(result) : null;
98+
99+
const copyResult = () => {
100+
if (typeof navigator !== 'undefined' && navigator.clipboard) {
101+
navigator.clipboard.writeText(resultString);
102+
setCopied(true);
103+
setTimeout(() => setCopied(false), 2000);
104+
}
105+
};
106+
107+
// --- icons (nested) ---
108+
const SpinnerIcon = () => (
109+
<svg className='animate-spin h-4 w-4' xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24'>
110+
<circle className='opacity-25' cx='12' cy='12' r='10' stroke='currentColor' strokeWidth='4' />
111+
<path className='opacity-75' fill='currentColor' d='M4 12a8 8 0 0 1 8-8v4a4 4 0 0 0-4 4H4z' />
112+
</svg>
113+
);
114+
115+
const PlayIcon = () => (
116+
<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='currentColor'>
117+
<path d='M8 5v14l11-7z' />
118+
</svg>
119+
);
120+
121+
// --- theme-agnostic surfaces (see theming note above) ---
122+
const HAIRLINE = 'rgba(128, 128, 128, 0.25)';
123+
const surfaceStyle = { backgroundColor: 'rgba(128, 128, 128, 0.08)' };
124+
const monoStyle = { fontFamily: 'var(--sei-font-mono)' };
125+
const codeStyle = { backgroundColor: 'rgba(128, 128, 128, 0.05)', fontFamily: 'var(--sei-font-mono)' };
126+
127+
const cardClass = 'not-prose w-full rounded-lg border overflow-hidden my-4';
128+
const headerClass = 'flex items-center justify-between gap-3 px-4 py-2.5 border-b';
129+
const labelClass = 'text-xs uppercase tracking-wide text-neutral-500 dark:text-neutral-500';
130+
const preClass = 'm-0 px-4 py-3 text-sm overflow-x-auto text-neutral-700 dark:text-neutral-300';
131+
132+
const buttonStyle = {
133+
backgroundColor: btnHover ? 'var(--sei-maroon-200)' : 'var(--sei-maroon-100)',
134+
color: '#ffffff',
135+
fontFamily: 'var(--sei-font-mono)',
136+
textTransform: 'uppercase',
137+
letterSpacing: '0.04em',
138+
fontSize: '10px',
139+
opacity: phase === 'loading' ? 0.7 : 1,
140+
cursor: phase === 'loading' ? 'default' : 'pointer'
141+
};
142+
143+
return (
144+
<div className={cardClass} style={{ borderColor: HAIRLINE }}>
145+
<div className={headerClass} style={{ ...surfaceStyle, borderBottomColor: HAIRLINE }}>
146+
<div className='flex flex-col min-w-0'>
147+
<span className='text-sm font-medium text-neutral-900 dark:text-white truncate' style={monoStyle}>
148+
{title || method}
149+
</span>
150+
<span className='text-xs text-neutral-500 dark:text-neutral-500'>{networkLabel}</span>
151+
</div>
152+
<button
153+
type='button'
154+
onClick={run}
155+
disabled={phase === 'loading'}
156+
onMouseEnter={() => setBtnHover(true)}
157+
onMouseLeave={() => setBtnHover(false)}
158+
className='inline-flex items-center gap-1.5 px-3 py-1.5 shrink-0 transition-colors'
159+
style={buttonStyle}>
160+
{phase === 'loading' ? <SpinnerIcon /> : <PlayIcon />}
161+
{phase === 'loading' ? 'Running…' : label || 'Run'}
162+
</button>
163+
</div>
164+
165+
{description ? (
166+
<div className='px-4 pt-3 text-sm text-neutral-600 dark:text-neutral-400'>{description}</div>
167+
) : null}
168+
169+
<div className='px-4 pt-3 pb-1'>
170+
<span className={labelClass}>Request</span>
171+
</div>
172+
<pre className={preClass} style={codeStyle}>
173+
{requestJson}
174+
</pre>
175+
176+
{phase === 'success' ? (
177+
<div className='border-t' style={{ borderTopColor: HAIRLINE }}>
178+
<div className='flex items-center justify-between px-4 pt-3 pb-1'>
179+
<span className={labelClass}>Response{elapsed != null ? ` · ${elapsed} ms` : ''}</span>
180+
<button
181+
type='button'
182+
onClick={copyResult}
183+
className='text-xs text-neutral-500 hover:text-neutral-800 dark:text-neutral-400 dark:hover:text-neutral-200 transition-colors'>
184+
{copied ? 'Copied' : 'Copy'}
185+
</button>
186+
</div>
187+
<pre className={preClass} style={codeStyle}>
188+
{resultString}
189+
</pre>
190+
{decoded ? (
191+
<div className='px-4 pb-3 text-xs text-neutral-500 dark:text-neutral-500' style={monoStyle}>
192+
= {decoded} (decimal)
193+
</div>
194+
) : null}
195+
</div>
196+
) : null}
197+
198+
{phase === 'error' ? (
199+
<div className='border-t' style={{ borderTopColor: HAIRLINE }}>
200+
<div className='px-4 pt-3 pb-1'>
201+
<span className={labelClass}>Error</span>
202+
</div>
203+
<pre className={`${preClass} text-red-600 dark:text-red-400`} style={codeStyle}>
204+
{errorMsg}
205+
</pre>
206+
</div>
207+
) : null}
208+
</div>
209+
);
210+
};

0 commit comments

Comments
 (0)