From 7cd13b795124f6ffef1ca5b103e9ee10f744ff81 Mon Sep 17 00:00:00 2001 From: alexander-sei Date: Sat, 27 Jun 2026 20:04:29 +0200 Subject: [PATCH] feat(docs): add Agent Skills registry and Templates gallery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the P1 #4 (Agent Skills registry) and P1 #5 (Templates gallery) gaps from the Sei-vs-Solana docs review. Skills (P1 #4): - 6 Foundation skills under .mintlify/skills//SKILL.md (sei-contracts, sei-frontend, sei-precompiles, sei-nodes, sei-payments, sei-security), installable via `npx skills add docs.sei.io` and served at /.well-known/skills/. - Registry page at /ai/skills with a filterable grid (snippets/skills-registry.jsx) and one-command install. - .gitignore: un-ignore .mintlify/skills/ so the skills deploy (the rest of .mintlify/ stays ignored as a local cache). Templates (P1 #5): - Gallery at /evm/templates listing the real @sei-js/create-sei entries only (Next.js + wagmi template, precompiles extension) — no fabricated demos or screenshots. Nav + redirects (docs.json): add ai/skills and evm/templates; redirect /skills -> /ai/skills and /templates -> /evm/templates. SSTORE gas is cited as 72,000 (same on both networks, governance Proposal #109), consistent with /evm/differences-with-ethereum. Co-Authored-By: Claude Opus 4.8 --- .gitignore | 5 +- .mintlify/skills/sei-contracts/SKILL.md | 254 +++++++++++++++++++ .mintlify/skills/sei-frontend/SKILL.md | 203 +++++++++++++++ .mintlify/skills/sei-nodes/SKILL.md | 242 ++++++++++++++++++ .mintlify/skills/sei-payments/SKILL.md | 240 ++++++++++++++++++ .mintlify/skills/sei-precompiles/SKILL.md | 271 ++++++++++++++++++++ .mintlify/skills/sei-security/SKILL.md | 229 +++++++++++++++++ ai/skills.mdx | 70 ++++++ docs.json | 12 + evm/templates.mdx | 96 +++++++ snippets/skills-registry.jsx | 292 ++++++++++++++++++++++ 11 files changed, 1913 insertions(+), 1 deletion(-) create mode 100644 .mintlify/skills/sei-contracts/SKILL.md create mode 100644 .mintlify/skills/sei-frontend/SKILL.md create mode 100644 .mintlify/skills/sei-nodes/SKILL.md create mode 100644 .mintlify/skills/sei-payments/SKILL.md create mode 100644 .mintlify/skills/sei-precompiles/SKILL.md create mode 100644 .mintlify/skills/sei-security/SKILL.md create mode 100644 ai/skills.mdx create mode 100644 evm/templates.mdx create mode 100644 snippets/skills-registry.jsx diff --git a/.gitignore b/.gitignore index ab946a3..ef6266d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ .DS_Store node_modules/ -.mintlify/ +# Mintlify uses .mintlify/ as a local build cache, but .mintlify/skills/ holds +# the hosted agent skills (SKILL.md) that must ship — keep that subtree tracked. +.mintlify/* +!.mintlify/skills/ .next/ .vercel/ dist/ diff --git a/.mintlify/skills/sei-contracts/SKILL.md b/.mintlify/skills/sei-contracts/SKILL.md new file mode 100644 index 0000000..1c05518 --- /dev/null +++ b/.mintlify/skills/sei-contracts/SKILL.md @@ -0,0 +1,254 @@ +--- +name: sei-contracts +description: > + Use when "deploy a smart contract on Sei", "set up Foundry for Sei", "set up Hardhat for Sei", "verify a contract on Seiscan", "write a Solidity contract for Sei", "make my Sei contract upgradeable", "optimize my contract for Sei parallel execution", "what is the Sei gas model", "use ERC-4337 account abstraction on Sei", "why does my contract behave differently on Sei than Ethereum", or "deploy a token on Sei". Covers EVM smart-contract development on Sei: Foundry/Hardhat setup, the Sei gas model, OCC parallel-execution-aware design, Seiscan verification via Sourcify, upgradeability, and account abstraction. +license: MIT +compatibility: Requires Node.js 18+; Foundry or Hardhat; solc 0.8.x +metadata: + author: Sei + version: 1.0.0 + intended-host: docs.sei.io + domain: contracts +--- + +# Sei contracts + +This skill makes an agent fluent in EVM smart-contract development on Sei: scaffolding projects with Foundry or Hardhat, pointing them at the right RPC endpoints and chain IDs, writing Solidity that respects the Sei gas model and the optimistic-concurrency (OCC) parallel scheduler, deploying with fast-finality semantics, verifying on Seiscan through Sourcify, and shipping upgradeable contracts and ERC-4337 account abstraction. Sei is EVM-compatible, so standard Solidity, OpenZeppelin, Foundry, and Hardhat all work — this skill focuses on the deltas from mainnet Ethereum that trip people up. + +## Critical facts + +- **Chain IDs:** mainnet `pacific-1` = `1329` (`0x531`); testnet `atlantic-2` = `1328` (`0x530`). Deploy and verify against testnet first. +- **EVM RPC:** mainnet `https://evm-rpc.sei-apis.com`; testnet `https://evm-rpc-testnet.sei-apis.com`. Gas is paid in `usei` (1 SEI = 10^18 wei, 18 decimals on the EVM side). +- **~400ms blocks with fast finality:** use `tx.wait(1)` — never `tx.wait(12)`. There are **no `safe` or `finalized` block tags** on Sei; use `latest`. +- **No EIP-1559 base-fee burn:** all transaction fees go to validators. Prefer **legacy `gasPrice`**. `maxFeePerGas`/`maxPriorityFeePerGas` are accepted but there is no priority-fee market. +- **`block.coinbase` returns the global fee collector**, not the block proposer — do not use it to identify the validator that produced a block. +- **`block.prevrandao` is NOT a safe randomness source** on Sei. Use an external VRF (Pyth Entropy/VRF or Chainlink VRF). +- **Parallel execution (OCC):** Sei executes non-conflicting transactions in parallel. Transactions that write the same storage key conflict and get re-executed serially. Partition state by user/asset/id; avoid hot global counters. +- **Storage write (SSTORE) gas is 72,000** — far above Ethereum's 20,000, and the **same on mainnet and testnet** (set by governance [Proposal #109](https://www.mintscan.io/sei/proposals/109)). It is a **governance-adjustable** on-chain parameter, so don't assume it is fixed forever: read the live value at https://docs.sei.io/evm/differences-with-ethereum#sstore-gas-cost and estimate per-transaction with `eth_estimateGas`. (A `forge --gas-report --fork-url` report uses revm's standard EVM schedule and shows ~22k, not Sei's cost.) The same governance-adjustable caveat applies to the **minimum gas price** and **block gas limit**. +- **Dual-address accounts:** every key has a `sei1...` (Cosmos) address and a `0x...` (EVM) address. Cross-VM value transfers require address association via the `Addr` precompile. See https://docs.sei.io/learn/accounts. +- **CosmWasm is deprecated for new development** per SIP-3 (proposal 99) — target Sei EVM. +- **Verification is via Seiscan, backed by Sourcify** — no Etherscan API key required. + +## Default stack + +- **Toolchain:** Foundry for contract-heavy work (fast tests, fuzzing, fork testing); Hardhat for JS/TS-heavy teams that want Ignition and the OpenZeppelin Upgrades plugin. +- **Solidity:** `solc` 0.8.x (the docs use `0.8.28`) with the optimizer enabled (`runs = 200`). +- **Libraries:** OpenZeppelin Contracts v5 (`@openzeppelin/contracts`), plus `@openzeppelin/contracts-upgradeable` for proxies. +- **Precompile ABIs + viem chain configs:** import from `@sei-js/precompiles` (exports the precompile addresses/ABIs and `sei` / `seiTestnet` viem chains) — do not hardcode. +- **Scaffold a dApp:** `npx @sei-js/create-sei app --name `. +- **AI tooling:** `claude mcp add sei-mcp-server npx @sei-js/mcp-server`. +- **Networks:** default to testnet (`atlantic-2`, 1328); only target mainnet (1329) on explicit confirmation. + +## Foundry setup + +`foundry.toml` — pin the compiler, enable the optimizer, and register both Sei RPCs: + +```toml +[profile.default] +src = "src" +out = "out" +libs = ["lib"] +solc_version = "0.8.28" +optimizer = true +optimizer_runs = 200 + +[rpc_endpoints] +sei_testnet = "https://evm-rpc-testnet.sei-apis.com" +sei_mainnet = "https://evm-rpc.sei-apis.com" +``` + +Deploy with a Forge script, then verify on Sourcify (testnet shown): + +```bash +# Deploy (private key from env; never commit it) +forge script script/DeployCounter.s.sol \ + --rpc-url $SEI_TESTNET_RPC --private-key $PRIVATE_KEY --broadcast + +# Verify on Seiscan via Sourcify — no API key needed +forge verify-contract \ + --verifier sourcify \ + --chain-id 1328 \ + \ + src/Counter.sol:Counter +``` + +Profile your contract's gas with Foundry, but get Sei's real storage-write cost from a live estimate — a `--fork-url` report forks state yet runs the standard EVM gas schedule, so it shows SSTORE at ~22k, not Sei's ~72k: + +```bash +forge test --gas-report --fork-url https://evm-rpc-testnet.sei-apis.com # relative profiling of your own logic +# For Sei's actual storage-write cost, estimate on-chain against a Sei RPC: +cast estimate "" --rpc-url https://evm-rpc.sei-apis.com +``` + +## Hardhat setup + +Hardhat 3 is ESM-first and loads plugins via an explicit `plugins` array. Each network needs `type: 'http'`: + +```typescript +import type { HardhatUserConfig } from 'hardhat/config'; +import { configVariable } from 'hardhat/config'; +import hardhatToolboxMochaEthers from '@nomicfoundation/hardhat-toolbox-mocha-ethers'; + +const config: HardhatUserConfig = { + plugins: [hardhatToolboxMochaEthers], + solidity: { + version: '0.8.28', + settings: { optimizer: { enabled: true, runs: 200 } } + }, + networks: { + seitestnet: { + type: 'http', + chainType: 'l1', + url: 'https://evm-rpc-testnet.sei-apis.com', + accounts: [configVariable('SEI_PRIVATE_KEY')], + chainId: 1328 + }, + seimainnet: { + type: 'http', + chainType: 'l1', + url: 'https://evm-rpc.sei-apis.com', + accounts: [configVariable('SEI_PRIVATE_KEY')], + chainId: 1329 + } + } +}; + +export default config; +``` + +Store the deploy key in Hardhat's encrypted keystore (never a plaintext file): `npx hardhat keystore set SEI_PRIVATE_KEY`. Deploy and verify: + +```bash +npx hardhat ignition deploy ignition/modules/deploy-sei-token.ts --network seitestnet +# Sourcify is enabled by default in hardhat-verify — no API key +npx hardhat verify sourcify --network seitestnet +``` + +## Design for parallel execution (OCC) + +Sei runs transactions optimistically in parallel and re-executes any pair that wrote the same storage key. Keeping write-sets disjoint is the single biggest throughput lever. **Partition state by user/asset/id; never bump a global counter on a hot path.** + +```solidity +// BAD — every swap writes the same slot, so all swaps serialize +contract DEX { + uint256 public totalVolume; + mapping(address => uint256) public balances; + + function swap(uint256 amount) external { + balances[msg.sender] -= amount; // per-user — fine + totalVolume += amount; // GLOBAL hot key — conflicts every tx + } +} + +// GOOD — drop the global write; reconstruct the aggregate off-chain from events +contract DEX { + mapping(address => uint256) public balances; + event Swap(address indexed user, uint256 amount); + + function swap(uint256 amount) external { + balances[msg.sender] -= amount; + emit Swap(msg.sender, amount); // indexer sums Swap events for total volume + } +} +``` + +Further OCC-aware rules: + +- **Prefer pull over push:** let users `withdraw()` their own balance (a single isolated key) instead of looping over recipients. +- **Avoid unbounded loops** that write storage — page work across multiple transactions. +- **Per-user reentrancy state:** OpenZeppelin's single-slot `ReentrancyGuard` makes every guarded call conflict on one slot. Key the guard by `msg.sender` if concurrency matters. +- **If you must keep an on-chain aggregate, shard it** into N buckets (e.g. by `uint256(uint160(msg.sender)) & 0xFF`) so conflicts are rare. +- **Use precompiles** where available — they are optimized and cheaper than reimplementing logic in Solidity. See https://docs.sei.io/evm/precompiles/example-usage. + +Full playbook: https://docs.sei.io/evm/best-practices/optimizing-for-parallelization. + +## Gas-efficient Solidity on Sei + +Most Ethereum gas advice carries over. The Sei-specific priorities: + +- **Minimize storage writes.** SSTORE is 72,000 gas on Sei (vs Ethereum's 20,000) — batch computation in memory and commit a minimal set of writes. Estimate the real cost on-chain with `eth_estimateGas`; a `--fork-url` gas report uses the standard EVM schedule and understates it. +- **Use legacy `gasPrice`.** Do not rely on EIP-1559 priority-fee mechanics; there is no base-fee burn. Check the live minimum gas price at https://docs.sei.io before assuming a floor. +- **Respect the block gas limit.** A single transaction cannot exceed the (governance-adjustable) per-block gas limit — split long migration scripts into pageable batches. +- Standard wins: mark externally-called functions `external`, pack storage variables into shared slots, prefer `calldata` over `memory` for read-only array inputs, use `unchecked { ++i; }` where overflow is impossible, and use custom errors instead of revert strings. + +## Deploying with fast finality + +Sei reaches finality in roughly one block (~400ms), so wait for a single confirmation — never the Ethereum-style 12: + +```typescript +import { ethers } from 'ethers'; + +const provider = new ethers.JsonRpcProvider('https://evm-rpc-testnet.sei-apis.com'); +const signer = new ethers.Wallet(process.env.PRIVATE_KEY!, provider); + +const tx = await contract.increment(); +await tx.wait(1); // one confirmation is final on Sei — do NOT use wait(12) + +// Always read against 'latest' — Sei has no 'safe'/'finalized' tags +const head = await provider.getBlock('latest'); +``` + +## Upgradeability + +UUPS (ERC-1822) is the recommended default — a small proxy with upgrade logic in the implementation. Use `@openzeppelin/contracts-upgradeable`, an `initializer` instead of a constructor, and gate upgrades in `_authorizeUpgrade`: + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +contract MyToken is Initializable, ERC20Upgradeable, OwnableUpgradeable, UUPSUpgradeable { + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { _disableInitializers(); } + + function initialize(address owner) public initializer { + __ERC20_init("MyToken", "MTK"); + __Ownable_init(owner); + // OZ v5's UUPSUpgradeable is stateless — no __UUPSUpgradeable_init() call + } + + function _authorizeUpgrade(address) internal override onlyOwner {} +} +``` + +Sei-specific upgrade notes: + +- **The upgrade admin is a `0x...` EVM address.** `Ownable` will not accept a `sei1...` Cosmos address — if governance lives on the Cosmos side, authorize the associated EVM address or an EVM-side multisig (Safe). +- **Treat precompile addresses as `constant`** — they are fixed by Sei consensus and should never live in upgradeable storage. +- **Always check storage layout before upgrading** — only *append* new state variables. Use OpenZeppelin's `hardhat-upgrades` linter, or `forge inspect storageLayout` and diff manually for Foundry. Note: the OpenZeppelin `hardhat-upgrades` plugin targets Hardhat 2; on Hardhat 3 deploy an ERC1967 proxy directly and upgrade via UUPS `upgradeToAndCall`, validating layouts separately with `@openzeppelin/upgrades-core`. +- **Verify the new implementation, then mark the proxy.** Run `forge verify-contract ... --verifier sourcify --chain-id 1329`, then on Seiscan open the proxy address and confirm it as a proxy so reads route to the new ABI. + +## Account abstraction (ERC-4337) + +ERC-4337 works on Sei EVM. Sei's instant finality and gas model make it a good fit for gasless onboarding, ERC20-paymaster gas, batched user ops, and session keys. Use a bundler/paymaster provider (e.g. Pimlico or Particle Network) via the standard EntryPoint and a smart-account factory such as Safe, Kernel, SimpleAccount, or Biconomy. For consumer flows, the Sei Global Wallet wraps AA primitives so users never touch a seed phrase. Verify the live EntryPoint address, supported bundlers, and any token/paymaster addresses against https://docs.sei.io/evm/wallet-integrations/pimlico before deploying — do not hardcode them from memory. AA adds gas overhead per user op versus a direct EOA call, so skip it when a single signed call suffices. + +## Common pitfalls + +- **Waiting for 12 confirmations.** Sei is final in ~1 block; `tx.wait(12)` just stalls your dApp. Use `tx.wait(1)`. +- **Querying `safe` or `finalized` block tags.** They don't exist on Sei — use `latest`. +- **Using EIP-1559 fee fields and expecting a priority market.** There's no base-fee burn; set a legacy `gasPrice`. +- **Trusting `block.prevrandao` for randomness.** It is block-time-derived on Sei, not RANDAO output — use an external VRF. +- **Reading the proposer from `block.coinbase`.** It returns the global fee collector address. +- **Hot global counters.** A single `totalX += amount;` on every call serializes all callers under OCC — aggregate off-chain via events or shard the slot. +- **Assuming Ethereum's 20,000-gas SSTORE.** Storage writes cost 72,000 gas on Sei (governance-adjustable, same on mainnet and testnet) — estimate on-chain with `eth_estimateGas`; a `--gas-report --fork-url` report applies the standard schedule and understates it, so storage-heavy designs surprise you in production. +- **Mixing address formats.** A contract expecting a `0x...` address will not accept a `sei1...` address; cross-VM transfers need the accounts associated first. +- **Single-transaction mega-migrations.** A loop that fits in a 60M-gas Ethereum block can exceed Sei's lower, governance-set block gas limit — paginate. +- **Reaching for CosmWasm for a new project.** It's deprecated for new development per SIP-3 — build on Sei EVM. + +## Key docs + +| Topic | Link | +| --- | --- | +| EVM overview | https://docs.sei.io/evm/evm-general | +| Foundry on Sei | https://docs.sei.io/evm/evm-foundry | +| Hardhat on Sei | https://docs.sei.io/evm/evm-hardhat | +| Verify contracts (Sourcify/Seiscan) | https://docs.sei.io/evm/evm-verify-contracts | +| Optimizing for parallelization | https://docs.sei.io/evm/best-practices/optimizing-for-parallelization | +| Parallelization engine | https://docs.sei.io/learn/parallelization-engine | +| Precompiles (addresses + examples) | https://docs.sei.io/evm/precompiles/example-usage | +| Accounts & dual addresses | https://docs.sei.io/learn/accounts | +| Account abstraction (Pimlico AA / paymasters) | https://docs.sei.io/evm/wallet-integrations/pimlico | diff --git a/.mintlify/skills/sei-frontend/SKILL.md b/.mintlify/skills/sei-frontend/SKILL.md new file mode 100644 index 0000000..c7be4a4 --- /dev/null +++ b/.mintlify/skills/sei-frontend/SKILL.md @@ -0,0 +1,203 @@ +--- +name: sei-frontend +description: > + Use when "build a Sei dApp frontend", "connect a wallet to Sei", "set up wagmi or viem for Sei", "configure the Sei chain in wagmi", "use Sei Global Wallet for social login", "add MetaMask or Compass to my Sei app with EIP-6963", "RainbowKit/ConnectKit with Sei", "show both sei1 and 0x addresses", "why is my Sei transaction stuck waiting for confirmations", "what gas price should the frontend send on Sei". Covers building Sei EVM dApp frontends: wagmi v2 + viem chain config, wallet integration, dual-address UX, legacy gas, and 400ms-finality UI patterns. +license: MIT +compatibility: Requires Node.js 18+; React 18+; wagmi v2 + viem +metadata: + author: Sei + version: 1.0.0 + intended-host: docs.sei.io + domain: frontend +--- + +# Sei frontend + +This skill makes the agent good at wiring a React dApp frontend to Sei EVM: configuring wagmi v2 + viem for the `sei` and `seiTestnet` chains, connecting wallets (Sei Global Wallet, MetaMask, Compass) through EIP-6963 and connect-modal libraries, presenting the dual `sei1…` / `0x…` address model to users, sending transactions with the correct legacy gas fields, and building UI that takes advantage of Sei's ~400ms finality instead of fighting it. + +## Critical facts + +- **Chain IDs.** Mainnet `pacific-1` is EVM chain `1329` (`0x531`); testnet `atlantic-2` is EVM chain `1328` (`0x530`). Default to testnet in development and promote to mainnet only when the user explicitly asks. +- **RPC endpoints.** EVM mainnet `https://evm-rpc.sei-apis.com`, EVM testnet `https://evm-rpc-testnet.sei-apis.com`. Cosmos RPC is `https://rpc.sei-apis.com` / `https://rpc-testnet.sei-apis.com` (only needed for Cosmos-side queries). +- **Chain config comes from `wagmi/chains` / `viem/chains`.** Import the `sei` and `seiTestnet` chain objects from `wagmi/chains` (or `viem/chains`) — this is what the Sei frontend docs use. `@sei-js/precompiles` separately exports the precompile constants you need for dual-address resolution (`ADDRESS_PRECOMPILE_ADDRESS`, `ADDRESS_PRECOMPILE_ABI`); it also ships viem chain configs as a secondary option, but lead with `wagmi/chains` and stay consistent across the app. +- **Use legacy `gasPrice`, never EIP-1559 fields.** Sei has no EIP-1559 base-fee burn — set `gasPrice`, not `maxFeePerGas` / `maxPriorityFeePerGas`. The minimum gas price is a governance-adjustable parameter; do not hardcode a forever-number. Let the wallet/RPC estimate when possible, and check the live value at https://docs.sei.io. +- **~400ms blocks with fast finality.** Wait for a single confirmation (`tx.wait(1)` in ethers, default `useWaitForTransactionReceipt` in wagmi). Never spin on 12 confirmations or "safe"/"finalized" tags — Sei has no `safe`/`finalized` block tags; use `latest`. +- **Every account is dual-address.** One key yields a Cosmos `sei1…` (bech32) address and an EVM `0x…` (hex) address, both derived from the same public key. Until the two are **associated** on-chain they behave as separate accounts with separate balances. Resolve either side through the Addr precompile at `0x0000000000000000000000000000000000001004` (`getSeiAddr` / `getEvmAddr`). See https://docs.sei.io/learn/accounts. +- **EIP-6963 is the wallet-discovery standard.** Wallets announce themselves via events instead of fighting over `window.ethereum`; wagmi's `injected()` connector discovers all of them automatically (Sei Global Wallet, MetaMask, Compass, …). See the supported list at https://docs.sei.io/learn/wallets. +- **Target the EVM for new dApps.** CosmWasm is deprecated for new development per SIP-3 (proposal 99); build frontends against EVM contracts. + +## Default stack + +| Layer | Default | Use instead when | +|---|---|---| +| Library | wagmi v2 + viem (React) | Non-React or Node script → ethers v6 | +| Consumer wallet | Sei Global Wallet (`@sei-js/sei-global-wallet`) — social login, no extension | Existing-wallet users → MetaMask / Compass via EIP-6963 | +| Connect modal | RainbowKit or ConnectKit | Bare-bones → wagmi `useConnect` directly | +| Chain config | `sei` / `seiTestnet` from `wagmi/chains` (or `viem/chains`) | A chain not exported → viem `defineChain` | +| Data layer | TanStack Query (wagmi default) | Already on Redux/Zustand → integrate manually | + +For the full supported-wallet list (and hardware wallets like Ledger, which are reached through a host wallet or WalletConnect rather than direct EIP-6963 discovery), see https://docs.sei.io/learn/wallets. Scaffold a fresh dApp with `npx @sei-js/create-sei app --name `. + +## wagmi v2 setup + +```ts +// wagmi.ts +import { http, createConfig } from 'wagmi'; +import { sei, seiTestnet } from 'wagmi/chains'; +import { injected } from 'wagmi/connectors'; + +export const config = createConfig({ + chains: [sei, seiTestnet], + connectors: [injected()], // discovers all EIP-6963 wallets automatically + transports: { + [sei.id]: http('https://evm-rpc.sei-apis.com'), + [seiTestnet.id]: http('https://evm-rpc-testnet.sei-apis.com') + } +}); +``` + +```tsx +// main.tsx — wrap the app once +import { WagmiProvider } from 'wagmi'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { config } from './wagmi'; + +const queryClient = new QueryClient(); + +export function Providers({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} +``` + +## Reading and writing contracts + +Pin `chainId` on every write so a wallet connected to the wrong network can't silently submit to it. Let the wallet and RPC estimate the legacy `gasPrice` — don't bake a constant into the UI. + +```tsx +import { useAccount, useReadContract, useWriteContract, useWaitForTransactionReceipt } from 'wagmi'; +import { parseUnits } from 'viem'; +import { sei } from 'wagmi/chains'; + +function Transfer({ token, to, amount }: { token: `0x${string}`; to: `0x${string}`; amount: string }) { + const { address } = useAccount(); + const { data: balance } = useReadContract({ + address: token, + abi: ERC20_ABI, + functionName: 'balanceOf', + args: address ? [address] : undefined, + query: { enabled: !!address } + }); + + const { writeContract, data: hash, isPending } = useWriteContract(); + // ~400ms blocks: one confirmation is final — do NOT wait for 12. + const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ hash }); + + const send = () => + writeContract({ + address: token, + abi: ERC20_ABI, + functionName: 'transfer', + args: [to, parseUnits(amount, 18)], + chainId: sei.id // pin chain to prevent cross-network mistakes + // Sei uses legacy gas — never maxFeePerGas. Omit gasPrice so the wallet/RPC + // estimates it. If you must override, read the live minimum from docs.sei.io; + // do not hardcode a magic value (it is governance-adjustable). + }); + + return ( + + ); +} +``` + +## Wallet connection (EIP-6963) + +`injected()` already enumerates every announced wallet, so a connect menu is just a map over discovered connectors — no per-wallet special-casing. + +```tsx +import { useConnect } from 'wagmi'; + +export function ConnectMenu() { + const { connectors, connect } = useConnect(); + return ( +
    + {connectors.map((c) => ( +
  • + +
  • + ))} +
+ ); +} +``` + +### Sei Global Wallet (embedded, social login) + +For consumer apps default to Sei Global Wallet — Google/X/Telegram/email login, no extension install, self-custodial, and EIP-6963 compatible so wagmi's `injected()` picks it up. A single side-effect import registers it for discovery: + +```ts +// At the top of your app entry (App.tsx / layout.tsx) +import '@sei-js/sei-global-wallet/eip6963'; +``` + +It then appears in the connect menu alongside MetaMask and other EIP-6963 wallets. For a polished modal, RainbowKit's `getDefaultConfig({ appName, projectId, chains: [sei, seiTestnet] })` or ConnectKit both work; pass a WalletConnect `projectId` from cloud.walletconnect.com. + +## Dual-address UX (`0x…` ↔ `sei1…`) + +Show the EVM `0x…` address as the primary identifier and surface the Cosmos `sei1…` counterpart when the user needs it (staking, IBC, Cosmos-native assets). Resolve and detect association through the Addr precompile; the call reverts when the address has never been associated — render that as "not linked," not an error. + +```tsx +import { useReadContract } from 'wagmi'; +import { ADDRESS_PRECOMPILE_ADDRESS, ADDRESS_PRECOMPILE_ABI } from '@sei-js/precompiles'; + +function DualAddress({ evm }: { evm: `0x${string}` }) { + const { data: seiAddr, isError } = useReadContract({ + address: ADDRESS_PRECOMPILE_ADDRESS, // 0x0000000000000000000000000000000000001004 + abi: ADDRESS_PRECOMPILE_ABI, + functionName: 'getSeiAddr', + args: [evm] + }); + + const linked = !isError && !!seiAddr; + return ( +
+ EVM: {evm} + Cosmos: {linked ? (seiAddr as string) : '(not linked)'} + {!linked && Broadcast any transaction to associate and enable cross-VM transfers.} +
+ ); +} +``` + +`@sei-js/precompiles` exports `ADDRESS_PRECOMPILE_ADDRESS` and `ADDRESS_PRECOMPILE_ABI` so you don't have to inline the precompile address or ABI (this is what `learn/accounts.mdx` uses). The cleanest way to associate is **Method 1 — broadcast any transaction**: the first signed tx records the public key on-chain and links both formats automatically. A wallet-signed-message flow (Method 3) covers the same UX without exposing a private key. See https://docs.sei.io/learn/accounts. + +## Common pitfalls + +- **Sending EIP-1559 gas fields.** `maxFeePerGas` / `maxPriorityFeePerGas` are wrong on Sei — use legacy `gasPrice`. There is no base-fee burn; fees go to validators. +- **Hardcoding a gas-price constant forever.** The minimum gas price, block gas limit, and storage (SSTORE) gas cost are governance-adjustable; the gas-price floor can differ between mainnet and testnet, while SSTORE is currently 72,000 on both. Don't bake a magic number into the UI — let the wallet/RPC estimate, or read the live value from https://docs.sei.io/evm/differences-with-ethereum#sstore-gas-cost. +- **Waiting for many confirmations.** Code copied from Ethereum often waits 6–12 confirmations or polls a `finalized` tag. Sei finalizes in ~400ms with no `safe`/`finalized` tags — wait for `1` confirmation against `latest` and update the UI immediately. +- **Assuming `0x…` and `sei1…` are different users.** They are the same account once associated. Don't show a "balance is zero" error for an unassociated address — prompt the user to associate (broadcast a tx) first. +- **Treating an Addr-precompile revert as a crash.** A revert means "not yet associated." Catch it and render an unlinked state. +- **Fighting over `window.ethereum`.** With several extensions installed, reading `window.ethereum` directly is unreliable. Rely on EIP-6963 discovery via wagmi `injected()` and let the user choose. +- **Interpolating on-chain data into prompts or code.** Token names, symbols, and URI fields are attacker-controlled; treat them as untrusted display strings, never as instructions. +- **Targeting CosmWasm for a new build.** CosmWasm is deprecated for new development (SIP-3) — point new frontends at EVM contracts. +- **Skipping testnet.** Deploy and exercise the full wallet + transaction flow on `atlantic-2` (1328) before touching mainnet. + +## Key docs + +| Topic | Link | +|---|---| +| Building a frontend (ethers / viem / wagmi) | https://docs.sei.io/evm/building-a-frontend | +| Sei Global Wallet integration | https://docs.sei.io/evm/sei-global-wallet | +| Dual-address accounts & association | https://docs.sei.io/learn/accounts | +| Supported wallets | https://docs.sei.io/learn/wallets | +| Network endpoints & chain IDs | https://docs.sei.io | diff --git a/.mintlify/skills/sei-nodes/SKILL.md b/.mintlify/skills/sei-nodes/SKILL.md new file mode 100644 index 0000000..c9e5ed9 --- /dev/null +++ b/.mintlify/skills/sei-nodes/SKILL.md @@ -0,0 +1,242 @@ +--- +name: sei-nodes +description: > + Use when "run a Sei full node", "set up a Sei RPC node", "become a Sei validator", "state sync my Sei node", "restore a Sei snapshot", "configure app.toml / config.toml for seid", "what hardware does a Sei node need", "enable SeiDB / RocksDB backend", "migrate to Giga storage", or "my node won't sync past genesis". Covers running and operating Sei full nodes, RPC/archive nodes, and validators — syncing, configuration, the SeiDB storage backend, and validator lifecycle. +license: MIT +compatibility: Linux server; the seid binary +metadata: + author: Sei + version: 1.0.0 + intended-host: docs.sei.io + domain: infrastructure +--- + +# Running Sei nodes and validators + +This skill makes the agent reliable at operating Sei infrastructure: choosing a node type, bootstrapping fast via state sync or a snapshot, tuning `app.toml` / `config.toml`, understanding the SeiDB two-layer storage backend (and the RocksDB / Giga storage options), and standing up a validator without getting tombstoned. It targets a Linux server running the `seid` binary. + +Sei is a Cosmos SDK chain with an integrated EVM execution layer. Nodes run consensus (Tendermint/CometBFT-style) plus the Sei application, and serve both Cosmos RPC and Sei EVM RPC from the same process. + +## Critical facts + +- **Networks**: mainnet is `pacific-1` (EVM chain ID 1329 / 0x531); testnet is `atlantic-2` (EVM chain ID 1328 / 0x530). Block time is ~400ms with fast finality. +- **Genesis is automatic**. `seid init` writes the correct `genesis.json` for known networks (mainnet and testnets) — do **not** hand-download a genesis file. +- **Never start from genesis on a live network.** Doing so panics with `panic: recovered: runtime error: integer divide by zero`. You must bootstrap via [state sync](https://docs.sei.io/node/statesync) or a [snapshot](https://docs.sei.io/node/snapshot). +- **Init mode binds ports.** Default `--mode full` binds RPC and P2P to all interfaces (`0.0.0.0`). For validators (and seed nodes) use `--mode validator` / `--mode seed` so RPC and P2P listen on localhost only. +- **SeiDB is the storage backend**, not the legacy IAVL store. It has two layers: State Commit (SC, hot state on memiavl, computes the app hash) and State Store (SS, historical key/values for queries). Legacy IAVL (`sc-enable = false`) is deprecated and slated for removal — run SeiDB. +- **SS is required for any node that serves RPC** (`ss-enable = true`). Validators that serve no queries can disable it to save disk. +- **Minimum gas price is governance-set and chain-enforced.** mainnet enforces a chain-wide floor (set `minimum-gas-prices` at or above it, e.g. `0.02usei`); `0usei` is only valid for local/private dev. The exact floor, block gas limit, and SSTORE/storage gas cost are governance-adjustable — confirm the live values at [docs.sei.io](https://docs.sei.io) rather than hardcoding them. +- **Double-signing = permanent tombstoning.** Never run the same `priv_validator_key.json` on two machines at once. Always preserve `priv_validator_state.json` across any resync. +- **`occ-enabled = true`** turns on Optimistic Concurrency Control for parallel transaction execution — keep it on. +- **Go 1.24.x** is required to build `seid` v6.3+. Official Docker images: `ghcr.io/sei-protocol/sei` (linux/amd64 and linux/arm64). + +## Node types + +| Type | Purpose | Key config | +|---|---|---| +| Full / RPC | Serve Cosmos + EVM RPC, relay txs | `ss-enable = true`; bootstrap via state sync or snapshot | +| Archive | Full history for deep queries / tracing | `ss-keep-recent = 0` (keep everything); disable `[statesync]`; sync from a long-history snapshot or genesis | +| Seed | P2P peer discovery only | `seid init --mode seed` | +| Validator | Sign blocks, secure the network | `seid init --mode validator`; protect the consensus key | + +Recommended hardware (from the docs): 16 cores, 256 GB DDR5 RAM, 2 TB NVMe SSD (high IOPS), 2 Gbps low-latency network. + +## Install and initialize + +```bash +# Build from source (pick the recommended tag from the Network Versions table on docs.sei.io) +git clone https://github.com/sei-protocol/sei-chain.git +cd sei-chain +git checkout +make install +seid version + +# Initialize. Default mode is full; use --mode validator for a validator. +seid init --chain-id pacific-1 +# genesis.json is written automatically — do NOT download one. + +# Set persistent peers (grab a current list from docs.sei.io/node) +PEERS="" +sed -i 's/persistent-peers = .*/persistent-peers = "'$PEERS'"/' ~/.sei/config/config.toml +``` + +## State sync (fastest bootstrap) + +State sync fetches a recent app-state snapshot from peers instead of replaying history (days → minutes). It only runs if the node has no local state (`LastBlockHeight = 0`); the resulting node has a truncated block history starting at the snapshot height. + +```bash +#!/bin/bash +# mainnet RPC, e.g. https://rpc.sei-apis.com:443 or https://sei-rpc.polkachu.com:443 +STATE_SYNC_RPC="https://rpc.sei-apis.com:443" +# mainnet pacific-1 state-sync peers (set as persistent-peers): +STATE_SYNC_PEER="3be6b24cf86a5938cce7d48f44fb6598465a9924@p2p.state-sync-0.pacific-1.seinetwork.io:26656,b21279d7092fde2e41770832a1cacc7d0051e9dc@p2p.state-sync-1.pacific-1.seinetwork.io:26656,616c05e9ba24acc89c0de630b5e3adbedaebb478@p2p.state-sync-2.pacific-1.seinetwork.io:26656" + +# Existing node only: back up validator key + state, then reset. +mkdir -p $HOME/key_backup +cp $HOME/.sei/config/priv_validator_key.json $HOME/key_backup/ 2>/dev/null +cp $HOME/.sei/data/priv_validator_state.json $HOME/key_backup/ 2>/dev/null +seid tendermint unsafe-reset-all --home $HOME/.sei +rm -rf $HOME/.sei/data/* $HOME/.sei/wasm +# Restore validator state AFTER the reset/clear, BEFORE starting seid. +cp $HOME/key_backup/priv_validator_state.json $HOME/.sei/data/priv_validator_state.json 2>/dev/null + +# Round the trust height down (e.g. to the nearest 100,000) so it lands safely below +# the latest snapshot height and avoids backward light-client verification. +LATEST_HEIGHT=$(curl -s $STATE_SYNC_RPC/block | jq -r .block.header.height) +BLOCK_HEIGHT=$(( (LATEST_HEIGHT / 100000) * 100000 )) +TRUST_HASH=$(curl -s "$STATE_SYNC_RPC/block?height=$BLOCK_HEIGHT" | jq -r .block_id.hash) + +sed -i.bak -E "s|^(enable[[:space:]]+=[[:space:]]+).*$|\1true| ; \ +s|^(rpc-servers[[:space:]]+=[[:space:]]+).*$|\1\"$STATE_SYNC_RPC,$STATE_SYNC_RPC\"| ; \ +s|^(trust-height[[:space:]]+=[[:space:]]+).*$|\1$BLOCK_HEIGHT| ; \ +s|^(trust-hash[[:space:]]+=[[:space:]]+).*$|\1\"$TRUST_HASH\"|" $HOME/.sei/config/config.toml +sed -i.bak -e "s|^persistent-peers *=.*|persistent-peers = \"$STATE_SYNC_PEER\"|" $HOME/.sei/config/config.toml + +sudo systemctl start seid +``` + +State sync can be flaky — if it stalls, just retry (4-5 attempts is normal). RocksDB-configured RPC nodes **must** bootstrap via state sync, not from existing data. Testnet (atlantic-2) uses `https://rpc-testnet.sei-apis.com:443` with its own peer set — see [docs.sei.io/node/statesync](https://docs.sei.io/node/statesync). + +## Snapshot restore (alternative bootstrap) + +Snapshots are a compressed copy of `data/` (+ `wasm/`) you extract straight into `$HOME/.sei`. Providers: Polkachu, Imperator, Stakeme, kjnodes. + +```bash +sudo systemctl stop seid +# Existing node only: preserve validator state, reset, clear data + wasm +cp $HOME/.sei/data/priv_validator_state.json $HOME/priv_validator_state.json +seid tendermint unsafe-reset-all --home $HOME/.sei +rm -rf $HOME/.sei/data $HOME/.sei/wasm + +SNAPSHOT_URL="" +curl -L $SNAPSHOT_URL | lz4 -c -d | tar -x -C $HOME/.sei # adjust per provider (.tar.gz, --strip-components, etc.) + +cp $HOME/priv_validator_state.json $HOME/.sei/data/priv_validator_state.json # restore AFTER extract +sudo systemctl start seid +``` + +The `wasm/` folder is part of the snapshot and is required — the node will not sync without it. After restore, ensure `sc-enable = true` and `ss-enable = true` in `app.toml`. + +## Essential app.toml tuning + +These are the knobs operators actually touch (defaults are sensible; the full reference is on docs.sei.io). + +```toml +# Spam floor — set at or above the mainnet-enforced minimum. +minimum-gas-prices = "0.02usei" + +# Parallel execution — keep on. +occ-enabled = true + +[state-commit] +sc-enable = true # SeiDB hot layer (app hash). Disabling = deprecated IAVL. +sc-async-commit-buffer = 100 # larger = faster catch-up; 0 = synchronous +sc-keep-recent = 1 # set 1 if serving IBC light-client / relayer proofs + +[state-store] +ss-enable = true # REQUIRED for any RPC-serving node +ss-backend = "pebbledb" # or "rocksdb" (see below) +ss-keep-recent = 100000 # ~28h of pacific-1 history; 0 = keep everything (archive) + +[receipt-store] +rs-backend = "pebbledb" # EVM receipts; pruned with min-retain-blocks +``` + +For an **archive node**: set `ss-keep-recent = 0`, disable `[statesync]` (`enable = false`), and source a long-history snapshot or sync from genesis. + +## SeiDB, RocksDB, and Giga storage + +- **SeiDB layers**: SC = memiavl Merkle tree for Cosmos modules (and the app hash); EVM state can optionally route through **FlatKV** (an EVM-tuned PebbleDB) but defaults to memiavl-only. SS = historical raw KV for queries. +- **RocksDB SS backend** (optional): native MVCC + column families make iteration-heavy work (`debug_trace*`, large archive queries) up to ~10-30× faster than PebbleDB as history grows. Build once, then set the backend: + + ```bash + make build-rocksdb # one-time; needs build deps (cmake, libzstd-dev, liburing-dev, etc.) + make install-rocksdb # seid version then includes the "rocksdbBackend" build tag + ``` + + ```toml + # ~/.sei/config/app.toml + ss-backend = "rocksdb" + ``` + + RocksDB RPC nodes must state-sync on first start; archive nodes currently must sync from genesis (a PebbleDB→RocksDB migration is in progress). +- **Giga Storage** (optional, RPC nodes only today): repartitions SeiDB so EVM state lives in its own SC/SS databases, freeing non-EVM modules from EVM write amplification. It requires a **fresh state sync** — flipping the EVM SS modes on a node with existing data fails startup safety checks. Follow the [Giga SS Store Migration Guide](https://docs.sei.io/node/giga-storage-migration); the resulting shape is `sc-write-mode = "dual_write"`, `sc-read-mode = "split_read"`, `sc-enable-lattice-hash = true`, plus split EVM SS modes. +- **Giga Executor** is a *separate* feature (`[giga_executor] enabled`) that swaps the EVM interpreter to an evmone-based engine for throughput. Don't conflate it with Giga Storage. + +## Run as a service + +```bash +sudo tee /etc/systemd/system/seid.service > /dev/null << EOF +[Unit] +Description=Sei Node +After=network-online.target +[Service] +User=$USER +ExecStart=$(which seid) start +Restart=always +RestartSec=3 +LimitNOFILE=65535 +[Install] +WantedBy=multi-user.target +EOF + +sudo systemctl daemon-reload && sudo systemctl enable --now seid + +# Monitor +seid status # sync status (catching_up should be false) +journalctl -fu seid -o cat # live logs +``` + +## Becoming a validator + +```bash +# 1. Init in validator mode (RPC/P2P bind to localhost) +seid init --chain-id pacific-1 --mode validator + +# 2. Fully sync first (state sync / snapshot), then create the validator +seid keys add operator +seid tx staking create-validator \ + --amount=1000000usei \ + --pubkey=$(seid tendermint show-validator) \ + --moniker="choose_moniker" \ + --chain-id=pacific-1 \ + --commission-rate="0.10" \ + --commission-max-rate="0.20" \ + --commission-max-change-rate="0.01" \ + --min-self-delegation="1" \ + --gas="auto" --gas-adjustment="1.5" --gas-prices="0.02usei" \ + --from=operator + +# Verify signing +seid query slashing signing-info $(seid tendermint show-validator) +``` + +Use `atlantic-2` for testnet. Protect the consensus key (`priv_validator_key.json`) with offline backups or an HSM; consider a sentry-node architecture (`pex = false` on the validator, private peer IDs on the sentries) to shield it from DDoS. + +## Common pitfalls + +- **Starting from genesis on a live network** → `integer divide by zero` panic. Always state-sync or snapshot. +- **Hand-downloading a genesis file.** `seid init` already wrote the right one for known networks; overwriting it causes mismatches. +- **Forgetting `wasm/` in a snapshot restore** — the node silently fails to sync. The folder is bundled in the snapshot; restore it too. +- **Skipping the `priv_validator_state.json` restore after a reset/resync** — `seid tendermint unsafe-reset-all` plus `rm -rf data/*` wipes it, so always copy your backup back to `$HOME/.sei/data/priv_validator_state.json` before starting. On a snapshot restore, copy it back *after* extraction since the extract overwrites it. +- **Running validator keys on two machines** → permanent tombstoning. The same applies after any resync: verify the old instance is fully offline. +- **Validator with RPC/P2P on `0.0.0.0`** — happens when you forget `--mode validator`. Re-init or rebind to localhost and use sentries. +- **Hardcoding gas / SSTORE numbers.** Minimum gas price and block gas limit are governance-adjustable (and the gas-price floor differs between mainnet and testnet); SSTORE/storage gas is governance-adjustable too, currently 72,000 — the same on both networks. Read live values from [docs.sei.io](https://docs.sei.io); do not assume Ethereum's 20,000 SSTORE. +- **Stale binary after a snapshot/state-sync** → `AppHash` errors. Use the `seid` version that matches the snapshot/network height. +- **Disabling SS on an RPC node** — historical queries break. `ss-enable = true` is mandatory wherever you serve RPC. +- **Treating SeiDB like Ethereum geth.** This is a Cosmos+EVM node; storage, pruning, and the app hash live in SeiDB (memiavl + PebbleDB/RocksDB), not a single geth-style DB. +- **Pruning interval too small** collides with snapshot creation; too large delays pruning and risks missed blocks. Leave `ss-prune-interval` near its default (600s) unless you have a reason. + +## Key docs + +| Topic | Link | +|---|---| +| Node operations home (hardware, install steps) | https://docs.sei.io/node | +| Node operations guide (config reference, SeiDB, maintenance) | https://docs.sei.io/node/node-operators | +| State sync | https://docs.sei.io/node/statesync | +| Snapshot sync | https://docs.sei.io/node/snapshot | +| Validator operations guide | https://docs.sei.io/node/validators | +| RocksDB backend | https://docs.sei.io/node/rocksdb-backend | +| Giga SS Store migration | https://docs.sei.io/node/giga-storage-migration | +| Advanced config & monitoring | https://docs.sei.io/node/advanced-config-monitoring | +| Technical reference (versions, genesis, peers) | https://docs.sei.io/node/technical-reference | diff --git a/.mintlify/skills/sei-payments/SKILL.md b/.mintlify/skills/sei-payments/SKILL.md new file mode 100644 index 0000000..48518ff --- /dev/null +++ b/.mintlify/skills/sei-payments/SKILL.md @@ -0,0 +1,240 @@ +--- +name: sei-payments +description: > + Use when "accept USDC on Sei", "send USDC payment", "USDC contract address on Sei", "charge per API request", "HTTP 402 micropayments", "x402 on Sei", "monetize my API with crypto", "pay-per-call agent payments", "add a paywall to my endpoint", "stablecoin transfer on Sei". Covers accepting and sending payments on Sei with USDC (ERC-20) and x402 HTTP-native micropayments. +license: MIT +compatibility: Node.js 18+; viem or ethers +metadata: + author: Sei + version: 1.0.0 + intended-host: docs.sei.io + domain: payments +--- + +# Sei payments + +This skill makes an agent good at moving and accepting digital dollars on Sei: transferring USDC as a standard ERC-20 token, and gating HTTP endpoints behind per-request payments with the x402 protocol so APIs, agents, and content can charge in stablecoins. USDC is the unit of account for both flows — x402 settles in USDC on Sei. + +## Critical facts + +- **USDC is a standard ERC-20 on Sei EVM.** Transfer it with `transfer(to, amount)`, read balances with `balanceOf(account)`. No special precompile or bridge call is needed for plain transfers. +- **USDC has 6 decimals** (not 18). `1 USDC = 1_000_000` base units. Always convert with `parseUnits(value, 6)` / `formatUnits(value, 6)` — using 18 will overpay by 10^12x. +- **USDC token addresses** (verify on Seiscan before sending real value): + - Mainnet (pacific-1, chain id 1329): `0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392` + - Testnet (atlantic-2, chain id 1328): `0x4fCF1784B31630811181f670Aea7A7bEF803eaED` +- **Get testnet USDC** from the [Circle Faucet](https://faucet.circle.com), or bridge real USDC cross-chain with [Circle CCTP v2](https://github.com/circlefin/circle-cctp-crosschain-transfer). You still need a little native SEI to pay transaction fees. +- **Sei runs ~400ms blocks with fast finality, which makes micropayments practical.** A payment confirms in roughly a block — confirm with one confirmation (`tx.wait(1)` or one block of polling), never `tx.wait(12)`. There are no `safe`/`finalized` block tags on Sei; query `latest`. +- **Use legacy `gasPrice`** for payment transactions. Sei has no EIP-1559 base-fee burn — set a single `gasPrice` rather than `maxFeePerGas`/`maxPriorityFeePerGas` (all fees go to validators). The minimum gas price is governance-adjustable; check the live value at https://docs.sei.io. +- **x402 uses HTTP 402 ("Payment Required").** The server answers an unpaid request with `402` plus a JSON payment challenge; the client pays on-chain, then retries with proof in an `X-Payment` header (base64-encoded JSON). The challenge `x402Version` is `1` and the scheme is `exact`. +- **EVM tooling works as-is.** Use viem or ethers with the Sei EVM RPCs — mainnet `https://evm-rpc.sei-apis.com`, testnet `https://evm-rpc-testnet.sei-apis.com`. + +## Default stack + +- **Language/runtime:** Node.js 18+ with `"type": "module"` (ES module imports), TypeScript optional. +- **Chain library:** `viem` (used throughout the Sei docs examples) or `ethers`. +- **x402 packages (`@sei-js`):** pick by role — + - Client (paying): [`@sei-js/x402-fetch`](https://www.npmjs.com/package/@sei-js/x402-fetch) (fetch wrapper) or [`@sei-js/x402-axios`](https://www.npmjs.com/package/@sei-js/x402-axios) (axios interceptors). + - Server (charging): [`@sei-js/x402-express`](https://www.npmjs.com/package/@sei-js/x402-express), [`@sei-js/x402-hono`](https://www.npmjs.com/package/@sei-js/x402-hono), or [`@sei-js/x402-next`](https://www.npmjs.com/package/@sei-js/x402-next). + - Core protocol: [`@sei-js/x402`](https://www.npmjs.com/package/@sei-js/x402). +- **Settlement asset:** USDC (6 decimals). Quote prices in whole USDC, convert to base units at the edge. +- **Secrets:** keep `PRIVATE_KEY` in `.env`; never commit it. + +## Send / accept USDC (viem) + +Minimal ERC-20 flow — check balance, then transfer. Network is selected by env (`SEI_NETWORK=testnet|mainnet`, defaulting to testnet). This is plain ESM JavaScript (`index.js`), so it avoids TypeScript-only syntax — run it directly with `node index.js`. + +```js +import 'dotenv/config'; +import { createPublicClient, createWalletClient, http, formatUnits, parseUnits } from 'viem'; +import { privateKeyToAccount } from 'viem/accounts'; + +const seiTestnet = { + id: 1328, + name: 'Sei Atlantic-2 Testnet', + network: 'sei-atlantic-2', + nativeCurrency: { name: 'Sei', symbol: 'SEI', decimals: 18 }, + rpcUrls: { default: { http: ['https://evm-rpc-testnet.sei-apis.com'] } }, + blockExplorers: { default: { url: 'https://testnet.seiscan.io' } }, + testnet: true, +}; +const seiMainnet = { + id: 1329, + name: 'Sei Mainnet (pacific-1)', + network: 'sei-pacific-1', + nativeCurrency: { name: 'Sei', symbol: 'SEI', decimals: 18 }, + rpcUrls: { default: { http: ['https://evm-rpc.sei-apis.com'] } }, + blockExplorers: { default: { url: 'https://seiscan.io' } }, + testnet: false, +}; + +const NETWORK = (process.env.SEI_NETWORK || 'testnet').toLowerCase(); +const chain = NETWORK === 'mainnet' ? seiMainnet : seiTestnet; + +// USDC: 6 decimals. Verify addresses on Seiscan before mainnet use. +const USDC_ADDRESS = + NETWORK === 'mainnet' + ? '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392' + : '0x4fCF1784B31630811181f670Aea7A7bEF803eaED'; +const USDC_DECIMALS = 6; +const USDC_ABI = [ + { name: 'balanceOf', type: 'function', stateMutability: 'view', + inputs: [{ name: 'account', type: 'address' }], outputs: [{ name: '', type: 'uint256' }] }, + { name: 'transfer', type: 'function', stateMutability: 'nonpayable', + inputs: [{ name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' }], + outputs: [{ name: '', type: 'bool' }] }, +]; + +// Validate inputs (matches the style of evm/usdc-on-sei.mdx). +const PRIVATE_KEY_RAW = process.env.PRIVATE_KEY; +const RECIPIENT = process.env.RECIPIENT_ADDRESS; +if (!PRIVATE_KEY_RAW) { + console.error('Error: set PRIVATE_KEY in your .env file'); + process.exit(1); +} +if (!RECIPIENT || !/^0x[a-fA-F0-9]{40}$/.test(RECIPIENT)) { + console.error('Error: set a valid RECIPIENT_ADDRESS in your .env file'); + process.exit(1); +} +const PRIVATE_KEY = PRIVATE_KEY_RAW.startsWith('0x') ? PRIVATE_KEY_RAW : `0x${PRIVATE_KEY_RAW}`; + +const account = privateKeyToAccount(PRIVATE_KEY); +const publicClient = createPublicClient({ chain, transport: http() }); +const walletClient = createWalletClient({ account, chain, transport: http() }); + +const balance = await publicClient.readContract({ + address: USDC_ADDRESS, abi: USDC_ABI, functionName: 'balanceOf', args: [account.address], +}); +console.log('USDC balance:', formatUnits(balance, USDC_DECIMALS)); + +const amount = parseUnits('10', USDC_DECIMALS); // 10 USDC +if (balance < amount) throw new Error('Insufficient USDC balance'); + +const hash = await walletClient.writeContract({ + address: USDC_ADDRESS, abi: USDC_ABI, functionName: 'transfer', args: [RECIPIENT, amount], +}); +await publicClient.waitForTransactionReceipt({ hash }); // one confirmation is enough on Sei +console.log('Sent. Explorer:', `${chain.blockExplorers.default.url}/tx/${hash}`); +``` + +```shell +npm init -y +npm install viem dotenv +# package.json: add "type": "module" for ESM import syntax +# .env: PRIVATE_KEY=0x... RECIPIENT_ADDRESS=0x... SEI_NETWORK=testnet +node index.js # testnet (default) +SEI_NETWORK=mainnet node index.js # mainnet +``` + +## Charge per request with x402 + +The x402 flow has five steps: (1) client requests a protected resource; (2) server returns `402` with a payment challenge; (3) client pays on-chain (a USDC transfer to `payTo`); (4) client retries with a base64 `X-Payment` proof; (5) server verifies the payment on-chain and serves the resource. + +The challenge advertised on a `402` response (one entry per accepted payment option): + +```json +{ + "x402Version": 1, + "accepts": [ + { + "scheme": "exact", + "network": "sei-testnet", + "maxAmountRequired": "1000", + "resource": "/api/weather", + "description": "Get current weather data", + "mimeType": "application/json", + "payTo": "0x9dC2aA0038830c052253161B1EE49B9dD449bD66", + "maxTimeoutSeconds": 300, + "asset": "0x4fCF1784B31630811181f670Aea7A7bEF803eaED", + "extra": { "name": "USDC", "version": "2", "reference": "sei-1234567890-abc123" } + } + ] +} +``` + +`maxAmountRequired` is in USDC base units — `"1000"` is `0.001` USDC (`parseUnits('0.001', 6)`). The `reference` is a unique per-challenge nonce used to prevent replay. Note that `extra.version` is the USDC contract's **EIP-712 domain version** (used for permit/signed transfers of the token) — it is the token's domain version, not the x402 protocol version. The protocol version is the top-level `x402Version` (`1`); do not conflate the two. + +Server side, in a Next.js route handler — return `402` until a valid proof arrives: + +```typescript +import { NextRequest, NextResponse } from 'next/server'; + +export async function GET(req: NextRequest) { + const paymentHeader = req.headers.get('x-payment'); + + if (!paymentHeader) { + // No payment yet: advertise requirements. + return NextResponse.json(generatePaymentChallenge(), { status: 402 }); + } + + const verification = await verifyPayment(paymentHeader); + if (!verification.isValid) { + const challenge = generatePaymentChallenge(); + (challenge as any).error = verification.reason ?? 'Payment verification failed'; + return NextResponse.json(challenge, { status: 402 }); + } + + // Paid: serve the resource. + return NextResponse.json({ location: 'Sei Network', temperature: '99°F', payment: verification }); +} +``` + +Verification checks the challenge fields and confirms the on-chain transaction succeeded: + +```typescript +async function verifyPayment(paymentHeader: string) { + const data = JSON.parse(Buffer.from(paymentHeader, 'base64').toString()); + const { x402Version, scheme, network, payload } = data; + + if (x402Version !== 1 || scheme !== 'exact' || network !== 'sei-testnet') { + return { isValid: false, reason: 'Invalid payment format or network' }; + } + + const receipt = await publicClient.getTransactionReceipt({ + hash: payload.txHash as `0x${string}`, + }); + // Also confirm recipient, amount, and that the reference has not been used before. + return { isValid: receipt?.status === 'success', txHash: payload.txHash }; +} +``` + +Client side, the proof is base64-encoded JSON sent back in `X-Payment`: + +```typescript +const paymentProof = { + x402Version: 1, + scheme: 'exact', + network: 'sei-testnet', + payload: { txHash, amount: parseUnits('0.001', 6).toString(), from: senderAddress }, +}; + +const res = await fetch(`${baseUrl}/api/weather`, { + headers: { 'X-Payment': Buffer.from(JSON.stringify(paymentProof)).toString('base64') }, +}); +``` + +For production, prefer the `@sei-js/x402-*` middleware (Express/Hono/Next) and client wrappers over hand-rolling challenge/verify logic. See the [sei-x402 repo](https://github.com/sei-protocol/sei-x402) and its quickstarts for sellers and buyers. + +## Common pitfalls + +- **Treating USDC as 18 decimals.** USDC is 6 decimals on Sei. `parseUnits('10', 6)` not `parseEther('10')`. A single wrong constant multiplies the amount by 10^12. +- **Waiting for 12 confirmations.** Sei runs ~400ms blocks with fast finality. Use a single confirmation (`waitForTransactionReceipt` / `tx.wait(1)`); waiting 12 blocks adds pointless latency that defeats the point of micropayments. +- **Querying `safe` or `finalized` block tags.** Those tags do not exist on Sei. Read state at `latest`. +- **Sending EIP-1559 fee fields.** Use legacy `gasPrice`; there is no base-fee burn on Sei (all fees go to validators). The minimum gas price is governance-adjustable — link to https://docs.sei.io rather than hardcoding a number. +- **Mixing TypeScript syntax into a `.js` file.** Plain `node index.js` cannot parse `as const`, the `!` non-null assertion, or `as` casts. Either keep the script as valid ESM JavaScript (as above) or rename it `index.ts` and run it with a TS runner like `npx tsx index.ts`. +- **Forgetting native SEI for fees.** A USDC transfer still costs transaction fees paid in native SEI. A wallet with USDC but zero SEI cannot pay. +- **Trusting `txHash` alone in x402.** Verify the receipt status, the recipient (`payTo`), the exact amount, AND that the `reference` nonce has not been seen before — otherwise the same valid payment can be replayed against your endpoint. Cache verified payments to prevent double-spend. +- **Confusing the two version fields in x402.** `x402Version` is the protocol version (`1`); `extra.version` is the USDC contract's EIP-712 domain version (`"2"`). They are unrelated. +- **Using testnet addresses on mainnet (or vice versa).** The USDC address differs per network; the wrong one points at a different/nonexistent token. Re-verify against Seiscan before moving real value. +- **Skipping address association assumptions.** Plain ERC-20 USDC transfers between `0x...` addresses need no association. Only if a flow crosses into Cosmos-side modules do the user's `sei1...` and `0x...` addresses need linking — see https://docs.sei.io/learn/accounts. +- **Assuming USDC is bridgeable for free.** To get USDC onto Sei from another chain, use Circle CCTP v2 (or the Circle Faucet on testnet); do not invent a bridge contract. + +## Key docs + +| Topic | Link | +| --- | --- | +| USDC on Sei (addresses, transfer guide) | https://docs.sei.io/evm/usdc-on-sei | +| x402 protocol on Sei | https://docs.sei.io/ai/x402 | +| sei-x402 repo (packages, quickstarts) | https://github.com/sei-protocol/sei-x402 | +| Accounts & dual-address association | https://docs.sei.io/learn/accounts | +| Circle developer docs / USDC | https://developers.circle.com | +| Circle testnet faucet | https://faucet.circle.com | diff --git a/.mintlify/skills/sei-precompiles/SKILL.md b/.mintlify/skills/sei-precompiles/SKILL.md new file mode 100644 index 0000000..7a28cef --- /dev/null +++ b/.mintlify/skills/sei-precompiles/SKILL.md @@ -0,0 +1,271 @@ +--- +name: sei-precompiles +description: > + Use when "call the Sei staking precompile", "delegate SEI from a contract", "vote on a Sei governance proposal in Solidity", "claim staking rewards via distribution precompile", "read a native denom balance with the bank precompile", "parse JSON on-chain on Sei", "verify a passkey/WebAuthn P256 signature on Sei", "associate my sei1 and 0x addresses", "look up an ERC20 pointer for a CW20/native token", "@sei-js/precompiles addresses and ABIs". Covers calling Sei's native precompiles (Bank, Staking, Governance, Distribution, Oracle, JSON, P256, Addr, IBC, Pointer/PointerView) from Solidity and viem/ethers. +license: MIT +compatibility: Requires @sei-js/precompiles; Solidity 0.8.x or viem/wagmi +metadata: + author: Sei + version: 1.0.0 + intended-host: docs.sei.io + domain: precompiles +--- + +# Sei precompiles + +This skill makes the agent precise at calling Sei's native precompiles — fixed-address contracts deployed by the protocol that expose Cosmos-layer functionality (staking, governance, distribution, bank, address association, IBC, cross-VM pointers) plus JSON parsing and P-256 verification to the EVM. Precompiles behave like ordinary contracts from Solidity/viem/ethers, but they execute privileged native logic. Use `@sei-js/precompiles` for the addresses, ABIs, and viem chain configs. + +## Critical facts + +- **Addresses are fixed** (40-hex, left-padded). Bank `0x...1001` · JSON `0x...1003` · Addr `0x...1004` · Staking `0x...1005` · Governance `0x...1006` · Distribution `0x...1007` · Oracle `0x...1008` · IBC `0x...1009` · PointerView `0x...100A` · Pointer `0x...100B` · P256 `0x...1011`. Get them from `@sei-js/precompiles` rather than hardcoding. +- **Precompiles only exist on Sei.** A plain local EVM (Hardhat node, `forge test` without a fork) has nothing at these addresses, so calls revert. Test against a fork: Foundry `--fork-url https://evm-rpc.sei-apis.com`, or Hardhat `forking`. +- **Staking decimal asymmetry (the #1 footgun).** `delegate()` reads `msg.value` in 18-decimal wei; `undelegate()` / `redelegate()` take the amount in 6-decimal usei (`1 SEI = 1_000_000 usei`). `delegate()`'s `msg.value` is truncated to 6 decimals, so send a multiple of `1e12` wei. `createValidator()` also takes `msg.value` in wei. Match each signature exactly. +- **Mixed precision in reads.** Staking `delegation().balance.amount` is 6-decimal usei while `.delegation.shares` is 18-decimal (`.delegation.decimals` always returns `18`, referring to shares). Distribution `rewards()` returns 18-decimal `DecCoins`; the amounts actually withdrawn (and in events) are 6-decimal usei — a `1e12` gap when reconciling. +- **The native Oracle precompile (`0x...1008`) is deprecated** and will be shut off soon. For new price feeds use a third-party oracle (Chainlink, Pyth, Redstone, API3) — see https://docs.sei.io/evm/oracles. Do not build new code on the Oracle precompile. +- **Governance voting power = staked SEI only.** Liquid (unstaked) SEI gives zero voting power. Mainnet proposal deposit is 3,500 SEI (7,000 expedited); deposits are burned if a proposal gets >33.4% NoWithVeto. Vote options: `1`=Yes, `2`=Abstain, `3`=No, `4`=NoWithVeto. `voteWeighted` weights are decimal strings that must sum to exactly `"1.0"`. +- **Dual-address accounts.** Every key has a `sei1...` (bech32) and a `0x...` (EVM) address. They are linked only after *association*; the Addr precompile (`0x...1004`) queries/creates the mapping, and association happens automatically on the account's first on-chain transaction. Validator addresses use the `seivaloper1...` prefix. +- **Fast finality + legacy fees.** Block time is ~400ms with fast/instant finality — use `tx.wait(1)`, never `tx.wait(12)`. There are no `safe`/`finalized` tags; query `latest`. Send transactions with a legacy `gasPrice` (Sei has no EIP-1559 base-fee burn). +- **Storage gas differs from Ethereum.** Writing storage (SSTORE) costs 72,000 gas on Sei — far above Ethereum's 20,000, and the same on mainnet and testnet (set by governance [Proposal #109](https://www.mintscan.io/sei/proposals/109)). It is a governance-adjustable on-chain parameter, so don't hardcode it forever. Block gas limit and minimum gas price are likewise governance-adjustable. Check the live values at https://docs.sei.io/evm/differences-with-ethereum#sstore-gas-cost. + +## Setup + +```bash +npm install @sei-js/precompiles viem ethers +``` + +```ts +// Addresses + ABIs + viem chain configs all come from one package. +import { + BANK_PRECOMPILE_ADDRESS, BANK_PRECOMPILE_ABI, + STAKING_PRECOMPILE_ADDRESS, STAKING_PRECOMPILE_ABI, + GOVERNANCE_PRECOMPILE_ADDRESS, GOVERNANCE_PRECOMPILE_ABI, + DISTRIBUTION_PRECOMPILE_ADDRESS, DISTRIBUTION_PRECOMPILE_ABI, + JSON_PRECOMPILE_ADDRESS, JSON_PRECOMPILE_ABI, + ADDRESS_PRECOMPILE_ADDRESS, ADDRESS_PRECOMPILE_ABI, +} from '@sei-js/precompiles'; +import { sei, seiTestnet } from '@sei-js/precompiles'; // viem chain configs +``` + +## Default stack + +- **TypeScript:** viem or ethers v6 + `@sei-js/precompiles` for addresses/ABIs. Use the exported `sei` (pacific-1, chainId 1329) / `seiTestnet` (atlantic-2, chainId 1328) chain configs. +- **Solidity:** declare a minimal `interface` for the precompile you call and cast the fixed address to it (shown below). Solidity 0.8.x. +- **Testing:** Foundry with `--fork-url`, or Hardhat with network forking, so the precompile addresses are populated. +- **Scaffold:** `npx @sei-js/create-sei app --name `. + +## Reading state (Bank, viem) + +`eth_getBalance` only returns the EVM-side SEI balance. Use the Bank precompile to read any native denom (including factory and IBC tokens) for any address. + +```ts +import { createPublicClient, http } from 'viem'; +import { sei } from '@sei-js/precompiles'; +import { BANK_PRECOMPILE_ADDRESS, BANK_PRECOMPILE_ABI } from '@sei-js/precompiles'; + +const client = createPublicClient({ chain: sei, transport: http('https://evm-rpc.sei-apis.com') }); + +const usei = await client.readContract({ + address: BANK_PRECOMPILE_ADDRESS, + abi: BANK_PRECOMPILE_ABI, + functionName: 'balance', + args: ['0xYourAddress', 'usei'], +}); + +const all = await client.readContract({ + address: BANK_PRECOMPILE_ADDRESS, + abi: BANK_PRECOMPILE_ABI, + functionName: 'all_balances', + args: ['0xYourAddress'], +}); // -> [{ denom: 'usei', amount: 1000000n }, ...] +``` + +## Staking + Distribution (ethers v6) + +Mind the decimals: `delegate` → wei (18), `undelegate`/`redelegate` → usei (6), reads → usei (6) for balances. + +```ts +import { ethers } from 'ethers'; +import { + STAKING_PRECOMPILE_ADDRESS, STAKING_PRECOMPILE_ABI, + DISTRIBUTION_PRECOMPILE_ADDRESS, DISTRIBUTION_PRECOMPILE_ABI, +} from '@sei-js/precompiles'; + +const provider = new ethers.JsonRpcProvider('https://evm-rpc.sei-apis.com'); +const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider); +const staking = new ethers.Contract(STAKING_PRECOMPILE_ADDRESS, STAKING_PRECOMPILE_ABI, wallet); +const distribution = new ethers.Contract(DISTRIBUTION_PRECOMPILE_ADDRESS, DISTRIBUTION_PRECOMPILE_ABI, wallet); +const validator = 'seivaloper1...'; + +// Delegate 10 SEI — msg.value in wei (18 decimals). Format to 6 dp first so it is a multiple of 1e12. +const delegateTx = await staking.delegate(validator, { value: ethers.parseUnits('10', 18) }); +await delegateTx.wait(1); + +// Undelegate 5 SEI — amount in usei (6 decimals), NOT wei. Subject to a 21-day unbonding period. +const undelegateTx = await staking.undelegate(validator, ethers.parseUnits('5', 6)); +await undelegateTx.wait(1); + +// Read a delegation: balance.amount is 6-decimal usei; delegation.shares is 18-decimal. +const d = await staking.delegation(wallet.address, validator); +console.log('Staked:', ethers.formatUnits(d.balance.amount, 6), 'SEI'); + +// Claim rewards. rewards() query is 18-decimal DecCoins; the withdrawn amount is 6-decimal usei. +const claimTx = await distribution.withdrawDelegationRewards(validator); +await claimTx.wait(1); +``` + +## Governance vote (viem) + +```ts +import { createWalletClient, custom } from 'viem'; +import { sei } from '@sei-js/precompiles'; +import { GOVERNANCE_PRECOMPILE_ADDRESS, GOVERNANCE_PRECOMPILE_ABI } from '@sei-js/precompiles'; + +const walletClient = createWalletClient({ chain: sei, transport: custom(window.ethereum) }); +const [account] = await walletClient.getAddresses(); + +// Vote Yes (1) on proposal 99. Requires staked SEI for voting power. +const hash = await walletClient.writeContract({ + account, + address: GOVERNANCE_PRECOMPILE_ADDRESS, + abi: GOVERNANCE_PRECOMPILE_ABI, + functionName: 'vote', + args: [99n, 1], +}); +``` + +## Solidity: stake from a contract + +Declare a minimal interface and cast the fixed address. This pattern works for any precompile. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IStaking { + function delegate(string memory validator) external payable returns (bool); +} +interface IDistribution { + function withdrawDelegationRewards(string memory validator) external returns (bool); +} + +contract StakeHelper { + address constant STAKING = 0x0000000000000000000000000000000000001005; + address constant DISTRIBUTION = 0x0000000000000000000000000000000000001007; + + // msg.value is the delegation amount in wei (18 decimals). + // Delegation is recorded under THIS contract's address, not the caller's — + // track per-user shares yourself if you need attribution. + function stake(string calldata validator) external payable { + require(msg.value > 0, "no value"); + require(IStaking(STAKING).delegate{value: msg.value}(validator), "delegate failed"); + } + + function harvest(string calldata validator) external { + require(IDistribution(DISTRIBUTION).withdrawDelegationRewards(validator), "claim failed"); + } +} +``` + +## P256 verification (Solidity) + +Sei's P256 precompile implements RIP-7212. Its real interface is `verify(bytes input) view returns (bytes)` — the 160-byte input is `hash ‖ r ‖ s ‖ pubX ‖ pubY`. On an invalid signature it returns **empty** data, which makes a high-level typed call revert when decoding `bytes`. Always use a low-level `staticcall` and treat empty output as "invalid". + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IP256Verify { + function verify(bytes calldata input) external view returns (bytes memory); +} + +contract PasskeyAuth { + address constant P256 = 0x0000000000000000000000000000000000001011; + + function verifySig(bytes32 hash, bytes32 r, bytes32 s, bytes32 x, bytes32 y) + external view returns (bool) + { + bytes memory input = abi.encodePacked(hash, r, s, x, y); // 160 bytes + (bool ok, bytes memory out) = P256.staticcall( + abi.encodeWithSelector(IP256Verify.verify.selector, input) + ); + return ok && out.length > 0; // empty return data == invalid signature + } +} +``` + +## JSON parsing on-chain + +The JSON precompile parses payloads far cheaper than hand-rolled Solidity. `extractAsUint256` handles integers only (no decimals/booleans/negatives — encode decimals as scaled integers). It does **not** support dot-notation for nested keys; extract the parent object as bytes, then parse it. + +```ts +import { ethers } from 'ethers'; +import { JSON_PRECOMPILE_ADDRESS, JSON_PRECOMPILE_ABI } from '@sei-js/precompiles'; + +const json = new ethers.Contract(JSON_PRECOMPILE_ADDRESS, JSON_PRECOMPILE_ABI, provider); +const input = ethers.toUtf8Bytes(JSON.stringify({ price: 100, symbol: 'SEI' })); + +const price = await json.extractAsUint256(input, 'price'); // 100n +const symbol = ethers.toUtf8String(await json.extractAsBytes(input, 'symbol')); // "SEI" +``` + +## Address association (Addr precompile) + +Query the link between a key's `sei1...` and `0x...` addresses. Association is automatic on first transaction and **permanent**; manual association via `associate(v,r,s,customMessage)` or `associatePubKey(pubKeyHex)` is for the cases where it hasn't happened yet. + +```ts +import { ethers } from 'ethers'; +import { ADDRESS_PRECOMPILE_ADDRESS, ADDRESS_PRECOMPILE_ABI } from '@sei-js/precompiles'; + +const addr = new ethers.Contract(ADDRESS_PRECOMPILE_ADDRESS, ADDRESS_PRECOMPILE_ABI, provider); +const seiAddr = await addr.getSeiAddr('0xYourAddress'); // "sei1..." or reverts if unassociated +const evmAddr = await addr.getEvmAddr('sei1...'); // "0x..." +``` + +## Cross-VM: Pointer / PointerView + +Pointers are automatically deployed contracts that proxy a token from one VM in the other (e.g. a native/CW20 denom as a standard ERC20, a CW721 as an ERC721). Resolve an existing pointer with PointerView (`0x...100A`) — `getNativePointer(denom)`, `getCW20Pointer(addr)`, `getCW721Pointer(addr)`. The return value exposes the pointer address as `.addr` and an `exists` boolean; **always check `exists`** before using the address, since not every token has a deployed pointer. Once you have the address, treat it as a plain ERC20/ERC721. + +```ts +import { POINTERVIEW_PRECOMPILE_ADDRESS, POINTERVIEW_PRECOMPILE_ABI } from '@sei-js/precompiles'; + +const pointerView = new ethers.Contract(POINTERVIEW_PRECOMPILE_ADDRESS, POINTERVIEW_PRECOMPILE_ABI, provider); + +const result = await pointerView.getNativePointer('usei'); +if (result.exists) { + console.log('ERC20 pointer for usei:', result.addr); + // result.addr is now usable as a standard ERC20 contract. +} +``` + +Deploying a *new* pointer (the registration side, Pointer precompile `0x...100B`) and the cross-VM/CosmWasm interop story are out of scope here — CosmWasm is deprecated for new development per SIP-3 (proposal 99), so new projects should target EVM-only. For pointer registration details and the full cross-VM model, see https://docs.sei.io/learn/pointers. + +## Common pitfalls + +- **Treating `undelegate`/`redelegate` amounts as wei.** They are 6-decimal usei. Passing `parseEther('5')` undelegates 5,000,000,000,000 SEI worth of units — it will fail or behave wildly. Only `delegate`/`createValidator` use 18-decimal `msg.value`. +- **Reconciling `rewards()` against withdrawn amounts directly.** The query is 18-decimal DecCoins; withdrawals are 6-decimal usei. Divide the query by `1e18` and withdrawals by `1e6` before comparing. +- **Assuming `getNativePointer`/`getCW20Pointer` returns a bare address or a 3-tuple.** It returns a struct — read `.addr` and gate on `.exists`. Do not destructure a positional `version`/3-element tuple; no such field is documented. +- **Testing precompiles on a non-forked local node.** Nothing is deployed at the precompile addresses off-Sei → reverts. Fork mainnet/testnet RPC in your test runner. +- **Building on the native Oracle precompile.** It is deprecated and will be turned off. Use Chainlink/Pyth/Redstone/API3 instead. +- **Calling P256 `verify` through a typed high-level contract call.** Invalid signatures return empty data and the decode reverts. Use `staticcall` and check `out.length > 0`. +- **`extractAsUint256` on non-integers** (decimals, booleans, negatives) reverts. Scale decimals to integers; encode booleans as 0/1. +- **Expecting voting power from liquid SEI.** Only staked/delegated SEI votes; if you don't vote, your validator's vote is inherited. +- **`voteWeighted` weights not summing to exactly `"1.0"`** → the transaction is rejected by the gov module. +- **Using `tx.wait(12)` or `finalized`/`safe` block tags.** Sei finalizes fast — `tx.wait(1)` and `latest` are correct. Use legacy `gasPrice`, not EIP-1559 `maxFeePerGas`/priority fees. +- **Hardcoding a single SSTORE/storage gas number.** It is currently 72,000 gas (the same on mainnet and testnet) but is governance-adjustable. Estimate gas dynamically (`estimateGas`) and link to the live value rather than baking in a constant. +- **Assuming a `sei1` and `0x` address are already linked.** They share a key but are only mapped after association; sending native tokens to an unassociated counterpart can be unreachable from the other VM. + +## Key docs + +| Topic | Link | +| --- | --- | +| Precompile example usage (Bank/Staking/Gov/Distribution/JSON) | https://docs.sei.io/evm/precompiles/example-usage | +| Staking precompile (decimals, validators, unbonding) | https://docs.sei.io/evm/precompiles/staking | +| Distribution precompile (rewards, commission) | https://docs.sei.io/evm/precompiles/distribution | +| Governance precompile (vote, deposit, proposals) | https://docs.sei.io/evm/precompiles/governance | +| JSON precompile | https://docs.sei.io/evm/precompiles/json | +| P256 precompile (RIP-7212 / passkeys) | https://docs.sei.io/evm/precompiles/p256-precompile | +| Address precompile (association) | https://docs.sei.io/evm/precompiles/cosmwasm-precompiles/addr | +| Bank precompile | https://docs.sei.io/evm/precompiles/cosmwasm-precompiles/bank | +| Oracle precompile (deprecated) + third-party oracles | https://docs.sei.io/evm/oracles | +| Pointer contracts / cross-VM | https://docs.sei.io/learn/pointers | +| Accounts & address association | https://docs.sei.io/learn/accounts | diff --git a/.mintlify/skills/sei-security/SKILL.md b/.mintlify/skills/sei-security/SKILL.md new file mode 100644 index 0000000..d4fee80 --- /dev/null +++ b/.mintlify/skills/sei-security/SKILL.md @@ -0,0 +1,229 @@ +--- +name: sei-security +description: > + Use when "is this safe to deploy on Sei", "how do I get randomness on Sei", + "block.prevrandao isn't random", "simulate before sending a transaction", + "verify address association before transfer", "secure a Sei smart contract", + "my AI agent is about to write on-chain", "pin the chainId before signing", + "sanitize on-chain data before the LLM", "should I auto-retry a failed tx". + Security patterns for Sei smart contracts and on-chain agents: testnet-first + deployment, simulate-before-write, safe randomness, cross-VM address + verification, and AI-agent safety. +license: MIT +compatibility: General; applies to Solidity contracts and TypeScript agents +metadata: + author: Sei + version: 1.0.0 + intended-host: docs.sei.io + domain: security +--- + +# Sei security + +This skill makes an assistant cautious and correct when writing Solidity contracts or TypeScript agents that move value on Sei. It encodes the Sei-specific traps that generic Ethereum security advice misses — predictable `PREVRANDAO`, the dual-address account model, governance-tuned gas, sub-second finality — plus the simulate-before-write and AI-agent guardrails that prevent an autonomous agent from signing something it shouldn't. + +The guiding rule: **on Sei, never trust an EVM assumption you haven't verified against the live network.** Default to testnet, simulate every state change, pin the chainId, and treat all on-chain data as untrusted input. + +## Critical facts + +- **`block.prevrandao` is NOT random on Sei.** It returns a deterministic value derived from block time and is predictable by validators. Use Pyth Entropy/VRF or Chainlink VRF for any value-bearing randomness. +- **`block.coinbase` is the global fee collector, not the block proposer.** Do not use it for MEV detection, tip routing, or proposer logic. +- **Dual-address accounts.** Every key controls a `sei1...` (Cosmos) address and a `0x...` (EVM) address. Cross-VM value transfers require the two to be **associated** via the Addr precompile (`0x0000000000000000000000000000000000001004`). An unassociated `0x...` can be derived that does not yet map to the `sei1...` a user expects — verify association before relying on the mapping. See https://docs.sei.io/learn/accounts. +- **Sub-second finality (~400ms block time).** Use `tx.wait(1)` — one confirmation is final. Never wait 12 blocks, and there are **no `safe` or `finalized` block tags** on Sei; query `latest`. +- **Use legacy `gasPrice`.** Sei has no EIP-1559 base-fee burn (all fees go to validators), so EIP-1559 priority-fee mechanics don't apply. Passing `maxFeePerGas`/`maxPriorityFeePerGas` can trigger fee errors. +- **Storage (`SSTORE`) gas is 72,000 — far above Ethereum's 20,000, and the same on mainnet and testnet.** It was set by governance [Proposal #109](https://www.mintscan.io/sei/proposals/109) and is an on-chain parameter that can change again via governance, so don't hardcode a single eternal figure; check the live value at https://docs.sei.io/evm/differences-with-ethereum#sstore-gas-cost. Minimum gas price and block gas limit are likewise governance-adjustable. +- **CosmWasm is deprecated for new development** per SIP-3 (proposal 99). Target the Sei EVM. +- **Contract verification uses Sourcify** (no Etherscan API key): `forge verify-contract --verifier sourcify`. Verify immediately after deploy so reviewers can read your source. + +## Deploy testnet-first, simulate-before-write + +Every state-changing transaction should be simulated with `eth_call` (or `estimateGas`) before it is signed and broadcast. Simulation reverts with the same reason the real write would, so you catch failures for free. + +```typescript +import { createPublicClient, createWalletClient, http } from 'viem'; +import { privateKeyToAccount } from 'viem/accounts'; +import { seiTestnet } from '@sei-js/precompiles'; // also exports `sei` (mainnet) + +const SEI_MAINNET = 1329; // pacific-1 +const SEI_TESTNET = 1328; // atlantic-2 + +const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); +const publicClient = createPublicClient({ chain: seiTestnet, transport: http() }); +const wallet = createWalletClient({ account, chain: seiTestnet, transport: http() }); + +async function safeWrite(params: Parameters[0]) { + // 1. Verify we are on the network we think we are — fail fast on a mismatch. + const chainId = await publicClient.getChainId(); + if (chainId !== SEI_TESTNET && chainId !== SEI_MAINNET) { + throw new Error(`Refusing to write: unknown chainId ${chainId}`); + } + + // 2. Simulate first. Reverts here exactly as the real tx would. + const { request } = await publicClient.simulateContract(params); + + // 3. Only after a clean simulation do we sign and broadcast. + const hash = await wallet.writeContract(request); + + // 4. One confirmation is final on Sei. Do NOT poll for 12 blocks. + const receipt = await publicClient.waitForTransactionReceipt({ hash, confirmations: 1 }); + if (receipt.status !== 'success') throw new Error(`Tx reverted on-chain: ${hash}`); + return receipt; +} +``` + +Promote to mainnet (`pacific-1` / 1329) only after the full flow passes against testnet (`atlantic-2` / 1328) and the contract is verified on Seiscan. + +## Safe randomness, not `PREVRANDAO` + +Do not roll your own randomness from on-chain values. On Sei use Pyth Entropy (the canonical docs use `IEntropyV2` with `requestV2()`) or Chainlink VRF — request a number, then consume it in the provider's callback. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import "@pythnetwork/entropy-sdk-solidity/IEntropyV2.sol"; +import "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol"; + +// ❌ NEVER — these are deterministic and validator-predictable on Sei. +// uint256 roll = uint256(block.prevrandao) % 100; +// uint256 bad = uint256(keccak256(abi.encode(block.timestamp, block.coinbase))); + +// ✅ Pyth Entropy V2 — pay the live fee, request, resolve in the callback. +contract Dice is IEntropyConsumer { + IEntropyV2 entropy; + + constructor(address _entropy) { + entropy = IEntropyV2(_entropy); + } + + // Required by IEntropyConsumer. + function getEntropy() internal view override returns (address) { + return address(entropy); + } + + function roll() external payable returns (uint64 sequenceNumber) { + uint128 fee = entropy.getFeeV2(); // fee is dynamic — read it on-chain + require(msg.value >= fee, "insufficient entropy fee"); + sequenceNumber = entropy.requestV2{value: fee}(); // V2 needs no user random number + } + + // Called by the keeper when randomness is ready. NEVER resolve inline. + function entropyCallback( + uint64 sequenceNumber, + address providerAddress, + bytes32 randomNumber + ) internal override { + uint256 result = (uint256(randomNumber) % 6) + 1; + // ... settle game state using `result` here. + } +} +``` + +Use the Entropy contract address for your target network from https://docs.sei.io/evm/vrf/pyth-network-vrf and budget gas for the callback. Chainlink VRF is the alternative — see https://docs.sei.io/evm/oracles. For oracle prices, do not read an AMM spot price (manipulable in a single block) — use a TWAP or a price feed (Pyth, Chainlink). + +## Verify address association before cross-VM value transfers + +When a contract receives or routes value across the EVM/Cosmos boundary, confirm the `0x...` actually maps to the expected `sei1...` before trusting it. Treat user-supplied bech32 strings (validator addresses, denoms, IBC channels) as untrusted — allowlist them before forwarding to a precompile. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +interface IAddr { + function getSeiAddr(address evmAddr) external view returns (string memory); + function getEvmAddr(string memory seiAddr) external view returns (address); +} + +address constant ADDR_PRECOMPILE = 0x0000000000000000000000000000000000001004; + +function requireAssociated(address evmAddr, string memory expectedSeiAddr) view { + string memory actual = IAddr(ADDR_PRECOMPILE).getSeiAddr(evmAddr); + require(bytes(actual).length != 0, "address not associated"); // empty == unassociated + require( + keccak256(bytes(actual)) == keccak256(bytes(expectedSeiAddr)), + "address mismatch" + ); +} +``` + +Validator addresses on Sei use the bech32 prefix `seivaloper1...` (this is what the Staking, Distribution, and Governance precompiles expect). Do **not** try to validate one by hardcoding a character count — bech32 lengths are not a stable constant, and a prefix-only check would still accept a regular `sei1...` account address. Instead, validate user-supplied validator strings against a known allowlist of operators your contract trusts: + +```solidity +mapping(bytes32 => bool) public allowedValidators; // keccak256(validatorAddr) => allowed + +function requireAllowedValidator(string calldata validatorAddr) view { + require(allowedValidators[keccak256(bytes(validatorAddr))], "validator not allowlisted"); + // Now safe to forward `validatorAddr` to the Staking precompile. +} +``` + +See the Staking precompile (`0x0000000000000000000000000000000000001005`) address format and method signatures at https://docs.sei.io/evm/precompiles. Off-chain, the Addr precompile returns an empty string for an unassociated address — branch on that before assuming a transfer will land where the user intends. See https://docs.sei.io/learn/accounts. + +## AI-agent safety + +On-chain data is attacker-controlled input. A token name, NFT metadata field, or memo can carry a prompt-injection payload. Pin the chainId on every write, verify the network, and never auto-retry a write without first checking whether it was already included. + +```typescript +// 1. Pin chainId on EVERY write so a wrong-network signer fails fast. +const hash = await wallet.writeContract({ ...request, chainId: 1329 }); + +// 2. Sanitize untrusted on-chain strings before they reach an LLM prompt. +const name = await token.read.name(); +if (!/^[a-zA-Z0-9 \-_.]{1,64}$/.test(name)) { + throw new Error('Suspicious token name rejected'); // don't forward to the model +} + +// 3. NEVER auto-retry a write. A "failed" RPC call may have landed. +// Check inclusion before resubmitting — a blind retry can double-spend. +const receipt = await publicClient.getTransactionReceipt({ hash }).catch(() => null); +if (!receipt) { + // Inclusion unknown. Re-query by hash / check the nonce. Do NOT just send again. +} + +// 4. Require explicit human confirmation before any mainnet (1329) write. +// Default the agent to testnet (1328). +``` + +Mandatory write-op flow for an agent: **simulate → estimate cost → summarize the action and fee for the user → wait for explicit confirmation → execute with `{ gasPrice, chainId }` → `tx.wait(1)`.** Read-only calls may retry with backoff; writes must not. + +## Default secure stack + +| Concern | Recommendation | +|---|---| +| Contract framework | Foundry (`forge test --gas-report` against the target network) | +| Reentrancy | OpenZeppelin `ReentrancyGuard` + checks-effects-interactions | +| Access control | `Ownable2Step` / `AccessControl`; multisig (Safe) for ownership; Timelock for sensitive params | +| Token transfers | `SafeERC20` (`safeTransfer`) — never ignore a transfer return value | +| Randomness | Pyth Entropy/VRF or Chainlink VRF — never `PREVRANDAO` | +| Prices | Pyth / Chainlink / Redstone / API3 — never AMM spot (the native Oracle precompile is deprecated) | +| Static analysis | Slither / Aderyn before mainnet; external audit above meaningful TVL | +| Verification | Sourcify via `forge verify-contract --verifier sourcify` | +| Chain config | `@sei-js/precompiles` (`sei`, `seiTestnet`, precompile ABIs) | + +## Common pitfalls + +- **Using `PREVRANDAO` (or `blockhash`/`timestamp`/`coinbase`) for randomness.** All deterministic on Sei. Validators can predict the outcome. +- **Trusting `block.coinbase` as the proposer.** It is the fee collector; proposer logic built on it is wrong. +- **Sending EIP-1559 fee fields.** `max fee per gas less than block base fee` / `transaction underpriced` errors usually mean you should pass legacy `gasPrice` instead. Minimum gas price is governance-set — check docs, not a hardcoded constant. +- **Waiting for 12 confirmations or polling `safe`/`finalized` tags.** Finality is one block (~400ms); those tags don't exist on Sei. Use `latest` and `tx.wait(1)`. +- **Transferring across VMs without checking association.** An unassociated `0x...` may not map to the `sei1...` a user assumes — verify via the Addr precompile first. +- **Mixing wei and usei in Staking precompile calls.** `delegate` is `payable` (value in **wei**, 18 decimals, e.g. `parseEther`); `undelegate` takes an amount in **usei** (6 decimals, 1 SEI = 1,000,000 usei). Confirm the unit for any other Staking precompile method against https://docs.sei.io/evm/precompiles — passing the wrong unit silently sends ~1e12× too much or too little. +- **Hardcoding `SSTORE` / min-gas / block-gas-limit numbers.** All governance-adjustable. Read the live value from docs.sei.io and budget storage-write gas with on-chain `eth_estimateGas` — a `forge --gas-report --fork-url` report uses the standard EVM schedule and understates Sei's SSTORE (~22k vs ~72k). +- **Auto-retrying failed writes in an agent.** A failed-looking RPC response may have been included. Check inclusion by hash before resubmitting, or risk a double action. +- **Forwarding raw on-chain strings into an LLM prompt.** Token names, memos, and metadata are untrusted; sanitize against an allowlist regex first. +- **Skipping verification.** Always run the simulate-before-write flow and verify source on Seiscan; for revert reasons use `cast` tracing (see Key docs). + +## Key docs + +| Topic | URL | +|---|---| +| Accounts & address association (cross-VM) | https://docs.sei.io/learn/accounts | +| EVM differences vs Ethereum (gas, randomness, coinbase) | https://docs.sei.io/evm/differences-with-ethereum | +| Debugging contracts (simulate, trace, revert reasons) | https://docs.sei.io/evm/debugging-contracts | +| Best practices | https://docs.sei.io/evm/best-practices/optimizing-for-parallelization | +| Pyth Entropy / VRF (randomness) | https://docs.sei.io/evm/vrf/pyth-network-vrf | +| Oracles (Pyth, Chainlink, Redstone, API3) | https://docs.sei.io/evm/oracles | +| Precompiles (Addr, Staking, Oracle) | https://docs.sei.io/evm/precompiles | +| Contract verification (Sourcify) | https://docs.sei.io/evm/evm-verify-contracts | +| Networks & chain IDs | https://docs.sei.io/evm/networks | +| AI tooling & MCP server | https://docs.sei.io/ai/mcp-server | diff --git a/ai/skills.mdx b/ai/skills.mdx new file mode 100644 index 0000000..2955d13 --- /dev/null +++ b/ai/skills.mdx @@ -0,0 +1,70 @@ +--- +title: 'Skills Registry' +sidebarTitle: 'Skills Registry' +description: 'Install Sei Foundation agent skills into your AI coding assistant with one command. Each skill teaches your assistant a focused slice of Sei — contracts, frontend, precompiles, nodes, payments, or security.' +keywords: ['sei skills', 'agent skills', 'skill.md', 'npx skills add', 'claude code', 'cursor', 'windsurf', 'ai coding assistant'] +--- + +import { SkillsRegistry } from '/snippets/skills-registry.jsx'; + +Sei Foundation publishes a set of **agent skills** — focused, installable knowledge packs that make your AI coding assistant Sei-aware. They're hosted directly on this docs site and follow the open [`skill.md`](https://www.mintlify.com/blog/skill-md) standard, so any compatible assistant (Claude Code, Cursor, Windsurf, and others) can install them. + +## Install + +Install the complete Foundation set with one command — the CLI detects your installed assistants and lets you pick which to install into: + +```bash +npx skills add docs.sei.io +``` + + + This fetches every skill served at `docs.sei.io/.well-known/skills/` and installs the ones you select. Skills stay current on every docs deploy — no manual updates. + + +## Foundation skills + +Filter by domain, then copy the install command from any card. Installing the set gives your assistant all of them; install just the ones you need to keep your assistant's context lean. + + + +## How skills work + +Each Foundation skill is a single `SKILL.md` file with YAML frontmatter describing **when** an assistant should reach for it and a focused playbook of Sei-specific facts, code, and pitfalls. Because they're served from this site, they're also discoverable by autonomous agents: + + + + Agents can enumerate every skill at `docs.sei.io/.well-known/skills/` and fetch any `SKILL.md` directly — no install step required. + + + Skills are versioned in [`sei-protocol/sei-docs`](https://github.com/sei-protocol/sei-docs) under `.mintlify/skills/` and redeploy with the docs. + + + +## Foundation vs. the full sei-skill + +The skills above are **focused** — one domain each, ideal when you want to keep your assistant's context tight. If you'd rather load a single comprehensive knowledge base covering every domain at once, use the full [**sei-skill**](/ai/sei-skill) instead. + +| Use a Foundation skill when… | Use the full [sei-skill](/ai/sei-skill) when… | +|---|---| +| You work mostly in one area (e.g. contracts only) | You want all-domain coverage in one install | +| You want to keep assistant context lean | You're starting a new Sei project from scratch | +| You're composing your own skill set | You want the editor-resident Claude Code skill | + +## Community skills + +The skills registry is open. Ecosystem teams — DEXs, lending protocols, oracles, infra providers — can publish their own skills so developers building on top of them get accurate, integration-specific guidance from their AI assistant. + + + Host a `SKILL.md` in your own docs (any Mintlify site serves `/.well-known/skills/` automatically) or contribute one to `sei-protocol/sei-docs`. Community skills will be listed here as they ship. + + +## Next steps + + + + The full multi-domain knowledge base, with per-assistant install instructions and example prompts. + + + Give your assistant live on-chain access — read balances, send transactions, and query network state. + + diff --git a/docs.json b/docs.json index e1bc9ea..c0c7175 100644 --- a/docs.json +++ b/docs.json @@ -207,6 +207,7 @@ "pages": [ "evm/sei-js/index", "evm/sei-js/create-sei", + "evm/templates", "evm/sei-js/ledger", "evm/sei-js/registry" ] @@ -352,6 +353,7 @@ "group": "AI", "pages": [ "ai/index", + "ai/skills", { "group": "Agent Skills", "pages": [ @@ -1655,6 +1657,16 @@ "source": "/llms/skill.md", "destination": "/skill.md", "permanent": true + }, + { + "source": "/skills", + "destination": "/ai/skills", + "permanent": true + }, + { + "source": "/templates", + "destination": "/evm/templates", + "permanent": true } ], "interaction": { diff --git a/evm/templates.mdx b/evm/templates.mdx new file mode 100644 index 0000000..81dd28e --- /dev/null +++ b/evm/templates.mdx @@ -0,0 +1,96 @@ +--- +title: 'Templates' +sidebarTitle: 'Templates' +description: 'Production-ready Sei dApp templates you can scaffold in one command with @sei-js/create-sei — wallet connections, contract interactions, and TypeScript wired up out of the box.' +keywords: ['sei templates', 'create-sei', 'scaffold', 'starter', 'nextjs', 'wagmi', 'viem', 'precompiles', 'dapp template'] +--- + +Start a new Sei dApp from a working, production-ready template instead of a blank folder. The [`@sei-js/create-sei`](/evm/sei-js/create-sei) CLI scaffolds a fully wired project — wallet connections, contract interactions, TypeScript, and styling — in seconds. + +## Scaffold in one command + + + +```bash npm +npx @sei-js/create-sei app --name my-sei-app +``` + +```bash pnpm +pnpm create @sei-js/sei app --name my-sei-app +``` + + + +Then install and run: + +```bash +cd my-sei-app +npm install +npm run dev +``` + +## Templates + + + + The default starter — a production-ready **Next.js 14** app with type-safe wallet connections and contract reads/writes. + + **Stack:** Next.js 14 · wagmi v2 · viem · TanStack Query · Tailwind CSS · Mantine UI · Biome · TypeScript + + **Includes:** MetaMask / WalletConnect / Coinbase Wallet support, organized components/hooks/utilities, and Sei network config. + + ```bash + npx @sei-js/create-sei app --name my-sei-app + ``` + + + + The default template plus working examples that query Sei's native [precompiles](/evm/precompiles/example-usage) — Bank, Staking, and Governance — directly from the frontend. + + **Adds:** native token supply, staking info, and governance proposal reads via `@sei-js/precompiles`. + + ```bash + npx @sei-js/create-sei app --name my-app --extension precompiles + ``` + + + + + See every available extension with `npx @sei-js/create-sei list-extensions`. Full CLI options and the interactive setup flow are documented on the [Scaffold Sei](/evm/sei-js/create-sei) page. + + +## What every template gives you + + + + Pre-configured wallet connections and React hooks — connect, read balances, and send transactions without boilerplate. + + + Mainnet (`pacific-1`, 1329) and testnet (`atlantic-2`, 1328) wired up, with contract-interaction examples. + + + End-to-end TypeScript with wagmi + viem, so contract calls and ABIs are typed. + + + Tailwind CSS, Mantine UI, Biome formatting, and Git initialized — ready to build on. + + + + + **Prerequisites:** Node.js v18 or higher (`node --version` to check). + + +## More templates are coming + +This gallery grows as new starters ship. Building a template the ecosystem should know about — a DeFi starter, an NFT mint, an x402-payments app, or an AI agent? Contribute it to [`sei-protocol/sei-js`](https://github.com/sei-protocol/sei-js/tree/main/packages/create-sei) and it can be listed here. + +## Next steps + + + + Full `create-sei` options, extensions, and troubleshooting. + + + Wire wagmi + viem and a wallet into a Sei dApp from first principles. + + diff --git a/snippets/skills-registry.jsx b/snippets/skills-registry.jsx new file mode 100644 index 0000000..7a8df16 --- /dev/null +++ b/snippets/skills-registry.jsx @@ -0,0 +1,292 @@ +export const SkillsRegistry = () => { + // --- Foundation skills hosted on docs.sei.io (.mintlify/skills//SKILL.md). + // All install together via `npx skills add docs.sei.io`. Keep this list in + // sync with the .mintlify/skills/ directory. --- + const SKILLS = [ + { + id: 'sei-contracts', + title: 'Smart Contracts', + domain: 'Contracts', + href: '/evm/evm-general', + desc: 'Foundry and Hardhat setup, the Sei gas model, OCC-aware contract design, and verifying on Seiscan via Sourcify.' + }, + { + id: 'sei-frontend', + title: 'Frontend', + domain: 'Frontend', + href: '/evm/building-a-frontend', + desc: 'wagmi + viem chain config, Sei Global Wallet, dual-address UX, and fast-finality patterns for 400ms blocks.' + }, + { + id: 'sei-precompiles', + title: 'Precompiles', + domain: 'Precompiles', + href: '/evm/precompiles/example-usage', + desc: 'Call Sei native precompiles — Bank, Staking, Governance, Oracle, and more — from Solidity and viem.' + }, + { + id: 'sei-nodes', + title: 'Nodes & Validators', + domain: 'Infrastructure', + href: '/node', + desc: 'Run full nodes and validators: state sync, snapshots, monitoring, and the SeiDB storage backend.' + }, + { + id: 'sei-payments', + title: 'Payments', + domain: 'Payments', + href: '/ai/x402', + desc: 'Accept and send payments on Sei with USDC and x402 HTTP-native micropayments.' + }, + { + id: 'sei-security', + title: 'Security', + domain: 'Security', + href: '/evm/debugging-contracts', + desc: 'Simulate-before-write, safe randomness, address-association checks, and AI-agent safety guardrails.' + } + ]; + + const INSTALL_CMD = 'npx skills add docs.sei.io'; + const FILTERS = ['All', 'Contracts', 'Frontend', 'Precompiles', 'Infrastructure', 'Payments', 'Security']; + + // --- Dark mode detection (Mintlify toggles a `dark` class on ) --- + const [isDark, setIsDark] = useState(false); + useEffect(() => { + const el = document.documentElement; + const update = () => setIsDark(el.classList.contains('dark')); + update(); + const obs = new MutationObserver(update); + obs.observe(el, { attributes: true, attributeFilter: ['class'] }); + return () => obs.disconnect(); + }, []); + + const [filter, setFilter] = useState('All'); + const [filterHover, setFilterHover] = useState(null); + + // --- Per-domain inline SVG icons (stateless; no images, so re-creation on + // render is harmless). currentColor inherits the maroon brand tint. --- + const Icon = ({ domain, size = 22 }) => { + const common = { + xmlns: 'http://www.w3.org/2000/svg', + width: size, + height: size, + viewBox: '0 0 24 24', + fill: 'none', + stroke: 'currentColor', + strokeWidth: 1.8, + strokeLinecap: 'round', + strokeLinejoin: 'round', + 'aria-hidden': true + }; + if (domain === 'Contracts') + return ( + + + + + ); + if (domain === 'Frontend') + return ( + + + + + ); + if (domain === 'Precompiles') + return ( + + + + + ); + if (domain === 'Infrastructure') + return ( + + + + + + ); + if (domain === 'Payments') + return ( + + + + + ); + if (domain === 'Security') + return ( + + + + + ); + return ( + + + + ); + }; + + const CopyIcon = ({ size = 14 }) => ( + + ); + + const CheckIcon = ({ size = 14 }) => ( + + ); + + const ArrowRightIcon = ({ size = 14, style }) => ( + + ); + + // --- Skill card. Created once via lazy initializer so its identity is stable + // across parent re-renders (theme toggle / filter change), preserving the + // card's own hover + copy state. `isDark` arrives as a prop. See + // mintlify-jsx-snippet-rules. --- + const [SkillCard] = useState(() => ({ skill, isDark }) => { + const [hover, setHover] = useState(false); + const [copied, setCopied] = useState(false); + const [copyHover, setCopyHover] = useState(false); + const [linkHover, setLinkHover] = useState(false); + + const copy = async () => { + try { + await navigator.clipboard.writeText(INSTALL_CMD); + setCopied(true); + setTimeout(() => setCopied(false), 1600); + } catch (e) { + /* clipboard unavailable — no-op */ + } + }; + + const accent = isDark ? 'var(--sei-maroon-25)' : 'var(--sei-maroon-100)'; + const linkColor = isDark ? (linkHover ? 'var(--sei-cream)' : 'var(--sei-maroon-25)') : 'var(--sei-maroon-100)'; + + return ( +
setHover(true)} + onMouseLeave={() => setHover(false)} + className='flex flex-col h-full p-5 transition-all duration-200' + style={{ + backgroundColor: hover ? 'rgba(128,128,128,0.10)' : 'rgba(128,128,128,0.05)', + border: '1px solid rgba(128,128,128,0.20)', + borderRadius: '12px', + transform: hover ? 'translateY(-2px)' : 'none' + }}> +
+ + + +
+

+ {skill.title} +

+ + {skill.id} + +
+
+ +

+ {skill.desc} +

+ + + + +
+ ); + }); + + const visible = filter === 'All' ? SKILLS : SKILLS.filter((s) => s.domain === filter); + + return ( +
+ {/* --- Filter pills --- */} +
+ {FILTERS.map((f) => { + const active = filter === f; + const hovered = filterHover === f; + return ( + + ); + })} +
+ + {/* --- Grid --- */} +
+ {visible.map((skill) => ( + + ))} +
+ + {visible.length === 0 &&
No skills in this category yet.
} +
+ ); +};