diff --git a/docs.json b/docs.json index 6d97b6a..ecb3b07 100644 --- a/docs.json +++ b/docs.json @@ -109,7 +109,21 @@ "groups": [ { "group": "Overview", - "pages": ["evm/index"] + "pages": [ + "evm/index", + { + "group": "EVM Compatibility", + "pages": [ + "evm/evm-parity/evm-compatibility", + "evm/evm-parity/finality", + "evm/evm-parity/gas-and-fees", + "evm/evm-parity/transaction-types", + "evm/evm-parity/state-proofs", + "evm/evm-parity/websocket", + "evm/evm-parity/signing" + ] + } + ] }, { "group": "Essentials", @@ -190,7 +204,8 @@ "pages": [ "evm/sei-js/index", "evm/sei-js/create-sei", - "evm/sei-js/ledger" + "evm/sei-js/ledger", + "evm/sei-js/registry" ] }, { @@ -200,6 +215,23 @@ { "group": "Ecosystem Tutorials", "pages": [ + { + "group": "sei-js Examples", + "pages": [ + "evm/evm-parity/examples/viem-quickstart", + "evm/evm-parity/examples/ethers-quickstart", + "evm/evm-parity/examples/wagmi-react", + "evm/evm-parity/examples/erc20", + "evm/evm-parity/examples/erc721", + "evm/evm-parity/examples/erc1155", + "evm/evm-parity/examples/multicall", + "evm/evm-parity/examples/pointer-contracts", + "evm/evm-parity/examples/sei-precompiles", + "evm/evm-parity/examples/deploy-verify", + "evm/evm-parity/examples/transaction-lifecycle", + "evm/evm-parity/examples/error-handling" + ] + }, { "group": "Indexers", "pages": [ diff --git a/evm/evm-parity/evm-compatibility.mdx b/evm/evm-parity/evm-compatibility.mdx new file mode 100644 index 0000000..32efc25 --- /dev/null +++ b/evm/evm-parity/evm-compatibility.mdx @@ -0,0 +1,138 @@ +--- +title: 'EVM Compatibility' +description: 'EVM feature support on Sei — what works, what differs from Ethereum, and what is not available' +icon: 'table' +--- + +# EVM Compatibility + +This page documents Sei's EVM feature support. Each row shows whether a capability works as on standard Ethereum, behaves differently in a documented way, or is not available on Sei. + +Standard EVM tooling — viem, wagmi, ethers, Foundry, Hardhat — works on Sei for all rows marked **Supported**. Where Sei differs from Ethereum, the difference is noted so you can account for it in your application. + +## Status Legend + +| Status | Meaning | +| --- | --- | +| Supported | Works on Sei EVM with standard Ethereum behavior. | +| Supported — differences | Available on Sei, but intentionally differs from Ethereum. See the notes. | +| Sei extension | A Sei-specific capability with no standard Ethereum equivalent. | +| Not supported | Not available on Sei EVM. | + +## Transactions + +| Feature | Sei status | Notes | +| --- | --- | --- | +| Legacy transactions (type 0) | Supported — differences | Minimum gas price is set by Sei governance parameters, not a fixed floor. [See Gas and Fees.](/evm/evm-parity/gas-and-fees) | +| EIP-2930 access list transactions (type 1) | Supported | | +| EIP-1559 fee market transactions (type 2) | Supported — differences | No base-fee burning. Fees go to validators rather than being burned. [See Gas and Fees.](/evm/evm-parity/gas-and-fees) | +| EIP-4844 blob transactions (type 3) | Not supported | Sei runs Pectra without blob transactions. [See Transaction Types.](/evm/evm-parity/transaction-types) | +| EIP-7702 set-code transactions (type 4) | Supported | | + +## Account and State + +| Feature | Sei status | Notes | +| --- | --- | --- | +| `eth_getBalance` | Supported | | +| `eth_getTransactionCount` (nonce) | Supported | | +| `eth_getCode` | Supported | | +| `eth_getStorageAt` | Supported — differences | SSTORE cost is governance-adjustable; do not hard-code gas assumptions. [See Gas and Fees.](/evm/evm-parity/gas-and-fees) | +| `eth_getProof` | Supported — differences | Returns IAVL proof data rather than Ethereum Merkle Patricia Trie proofs. Proof verification logic must account for this. [See State Proofs.](/evm/evm-parity/state-proofs) | + +## Blocks and Finality + +| Feature | Sei status | Notes | +| --- | --- | --- | +| `eth_getBlockByHash`, `eth_getBlockByNumber` | Supported | | +| `eth_blockNumber` | Supported | | +| Finality tags (`latest`, `safe`, `finalized`) | Supported — differences | Sei has instant finality. All three tags refer to the same commitment level. [See Finality.](/evm/evm-parity/finality) | +| Pending block state | Supported — differences | Ethereum-style pending state visibility is not guaranteed. Do not rely on pending transaction ordering. [See Finality.](/evm/evm-parity/finality) | + +## Logs, Filters, and Subscriptions + +| Feature | Sei status | Notes | +| --- | --- | --- | +| `eth_getLogs` | Supported | | +| Filter lifecycle (`eth_newFilter`, `eth_getFilterChanges`, `eth_getFilterLogs`, `eth_uninstallFilter`) | Supported | | +| WebSocket subscriptions (`eth_subscribe`) | Supported | [See WebSocket Connections.](/evm/evm-parity/websocket) | + +## Execution and Simulation + +| Feature | Sei status | Notes | +| --- | --- | --- | +| `eth_call` | Supported | | +| `eth_estimateGas` | Supported | | +| `debug_traceTransaction` | Supported | | +| Block-level tracing | Supported | | + +## Signing + +| Feature | Sei status | Notes | +| --- | --- | --- | +| EIP-155 replay protection | Supported | | +| Personal sign (`personal_sign`, `eth_sign`) | Supported | Depends on the connected wallet exposing the method. [See Signing.](/evm/evm-parity/signing) | +| EIP-712 typed data signing (`eth_signTypedData_v4`) | Supported | Depends on the connected wallet exposing the method. [See Signing.](/evm/evm-parity/signing) | +| EIP-1271 contract signature validation | Supported | [See Signing.](/evm/evm-parity/signing) | + +## Token Standards + +| Feature | Sei status | Notes | +| --- | --- | --- | +| ERC-20 | Supported | | +| ERC-721 | Supported | | +| ERC-1155 | Supported | | + +## Account Abstraction + +| Feature | Sei status | Notes | +| --- | --- | --- | +| ERC-4337 | Supported | Requires a compatible bundler. Sei EVM is ERC-4337 compatible. | + +## Name Services + +| Feature | Sei status | Notes | +| --- | --- | --- | +| ENS | Not supported | Sei does not run Ethereum mainnet ENS. Application-level name services can be deployed as contracts. | + +## Sei Extensions + +These capabilities are Sei-specific and have no standard Ethereum equivalent. They are exposed through `@sei-js` packages layered on top of standard EVM tooling. + +| Feature | Notes | +| --- | --- | +| Sei precompiles (staking, governance, distribution, oracle, P256, JSON, CosmWasm bridge) | EVM contracts at deterministic addresses. ABIs and contract addresses are exported from `@sei-js/precompiles` for use with any standard EVM library. | +| Pointer contracts (CW20 ↔ ERC-20, CW721 ↔ ERC-721) | Bridge between CosmWasm and EVM token standards. Standard ERC interfaces work against pointer contracts. | +| Native address association (EVM ↔ Cosmos address) | Links an EVM address and a Cosmos address for the same account. Required before some Sei-native flows. | +| TokenFactory native token creation | Create native Sei tokens that are usable across EVM and CosmWasm without a wrapper contract. | + +## Unsupported RPC Methods + +The following JSON-RPC methods are either not available on Sei or will return errors: + +### Blob / EIP-4844 + +| Method | Reason | +| --- | --- | +| `eth_blobBaseFee` | Sei runs Pectra without blob transaction support. This method does not exist on Sei. | +| `engine_getBlobsV1` | Blob data availability endpoints are not supported. | + +### Pending State + +| Method | Behavior on Sei | +| --- | --- | +| `eth_getBlockByNumber("pending")` | Returns `null` or the latest committed block. Sei does not maintain an Ethereum-style pending block. | +| `eth_getTransactionCount(address, "pending")` | Returns the same value as `"latest"`. Pending nonce does not differ from confirmed nonce on Sei. | +| `eth_newPendingTransactionFilter` | The filter can be created but will not reliably emit pending transactions. Use `eth_newBlockFilter` or WebSocket block subscriptions instead. | +| `eth_subscribe("newPendingTransactions")` | Supported at the RPC level but Sei does not guarantee Ethereum-style pending transaction visibility. Treat results as best-effort. | + +### Proof Verification + +| Method | Behavior on Sei | +| --- | --- | +| `eth_getProof` | Returns IAVL tree proofs, not Ethereum Merkle Patricia Trie proofs. The method itself works, but the proof format is incompatible with standard Ethereum MPT verifiers. [See State Proofs.](/evm/evm-parity/state-proofs) | + +### Deprecated / Removed Opcodes + +| Method | Reason | +| --- | --- | +| `eth_accounts` | Returns an empty array on Sei RPC nodes. Use a wallet library (`eth_requestAccounts`) to get accounts from the connected user's wallet instead. | diff --git a/evm/evm-parity/examples/deploy-verify.mdx b/evm/evm-parity/examples/deploy-verify.mdx new file mode 100644 index 0000000..5577713 --- /dev/null +++ b/evm/evm-parity/examples/deploy-verify.mdx @@ -0,0 +1,177 @@ +--- +title: 'Deploy and Verify' +description: 'Deploying and verifying smart contracts on Sei with viem, ethers, Foundry, and Hardhat' +icon: 'rocket' +--- + +# Deploy and Verify + +Sei is EVM-compatible — standard deployment tooling works without modification. This page covers deploying a contract and verifying its source on the Sei block explorer. + +For a deeper look at verification options (Remix, Sourcify UI, batch verification), see the [Verify Contracts](/evm/evm-verify-contracts) page. + +## Deploying with viem or ethers + + + +```ts viem +import { createPublicClient, createWalletClient, http } from 'viem'; +import { privateKeyToAccount } from 'viem/accounts'; +import { sei } from '@sei-js/precompiles/viem'; + +const account = privateKeyToAccount('0xYourPrivateKey'); +const publicClient = createPublicClient({ chain: sei, transport: http() }); +const walletClient = createWalletClient({ account, chain: sei, transport: http() }); + +const hash = await walletClient.deployContract({ + abi: CONTRACT_ABI, + bytecode: '0x608060...', + args: [/* constructor args */], +}); + +const { contractAddress } = await publicClient.waitForTransactionReceipt({ hash }); +console.log('Deployed at:', contractAddress); +``` + +```ts ethers +import { ethers } from 'ethers'; + +const provider = new ethers.JsonRpcProvider('https://evm-rpc.sei-apis.com'); +const wallet = new ethers.Wallet('0xYourPrivateKey', provider); + +const factory = new ethers.ContractFactory(CONTRACT_ABI, '0x608060...', wallet); +const contract = await factory.deploy(/* constructor args */); + +await contract.waitForDeployment(); +console.log('Deployed at:', await contract.getAddress()); +``` + + + +## Deploying with Foundry + +Foundry works against Sei with the standard `--rpc-url` flag. + +```bash +forge create src/MyContract.sol:MyContract \ + --rpc-url https://evm-rpc.sei-apis.com \ + --private-key $PRIVATE_KEY \ + --constructor-args arg1 arg2 +``` + +For testnet: + +```bash +forge create src/MyContract.sol:MyContract \ + --rpc-url https://evm-rpc-testnet.sei-apis.com \ + --private-key $PRIVATE_KEY +``` + +## Deploying with Hardhat + +Add Sei networks to your `hardhat.config.ts`: + +```ts +import { HardhatUserConfig } from 'hardhat/config'; + +const config: HardhatUserConfig = { + networks: { + sei: { + url: 'https://evm-rpc.sei-apis.com', + chainId: 1329, + accounts: [process.env.PRIVATE_KEY!], + }, + seiTestnet: { + url: 'https://evm-rpc-testnet.sei-apis.com', + chainId: 1328, + accounts: [process.env.PRIVATE_KEY!], + }, + }, +}; + +export default config; +``` + +Then deploy: + +```bash +npx hardhat run scripts/deploy.ts --network sei +``` + +## Verifying Contracts + +Sei's block explorer is [Seiscan](https://seiscan.io). The recommended verification method is [Sourcify](https://sourcify.dev/) — no API key required. + +### Foundry + +Verify after deployment: + +```bash +forge verify-contract \ + --verifier sourcify \ + --chain-id 1329 \ + 0xDeployedContractAddress \ + src/MyContract.sol:MyContract +``` + +Or deploy and verify in one step: + +```bash +forge create src/MyContract.sol:MyContract \ + --rpc-url https://evm-rpc.sei-apis.com \ + --private-key $PRIVATE_KEY \ + --verify \ + --verifier sourcify \ + --chain-id 1329 +``` + +For testnet, use `--chain-id 1328` and `--rpc-url https://evm-rpc-testnet.sei-apis.com`. + +### Hardhat + +Install the verify plugin: + +```bash +npm install --save-dev @nomicfoundation/hardhat-verify +``` + +Add it to `hardhat.config.ts` — no additional Sourcify config block is needed: + +```ts +import '@nomicfoundation/hardhat-verify'; + +const config: HardhatUserConfig = { + networks: { + sei: { + url: 'https://evm-rpc.sei-apis.com', + chainId: 1329, + accounts: [process.env.PRIVATE_KEY!], + }, + seiTestnet: { + url: 'https://evm-rpc-testnet.sei-apis.com', + chainId: 1328, + accounts: [process.env.PRIVATE_KEY!], + }, + }, +}; +``` + +Verify a deployed contract: + +```bash +npx hardhat verify sourcify --network sei 0xDeployedContractAddress +``` + +If your contract has constructor arguments: + +```bash +npx hardhat verify sourcify --network sei 0xDeployedContractAddress "arg1" "arg2" +``` + +## Network Reference + +| Network | Chain ID | RPC | Explorer | +| --- | --- | --- | --- | +| Mainnet | 1329 | `https://evm-rpc.sei-apis.com` | [seiscan.io](https://seiscan.io) | +| Testnet | 1328 | `https://evm-rpc-testnet.sei-apis.com` | [testnet.seiscan.io](https://testnet.seiscan.io) | +| Devnet | 713715 | `https://evm-rpc-arctic-1.sei-apis.com` | — | diff --git a/evm/evm-parity/examples/erc1155.mdx b/evm/evm-parity/examples/erc1155.mdx new file mode 100644 index 0000000..fe59f91 --- /dev/null +++ b/evm/evm-parity/examples/erc1155.mdx @@ -0,0 +1,249 @@ +--- +title: 'ERC-1155 Interaction' +description: 'Reading and writing ERC-1155 multi-token contracts on Sei with viem and ethers' +icon: 'grid-2' +--- + +# ERC-1155 Interaction + +ERC-1155 is the multi-token standard — a single contract can hold both fungible tokens (like gold coins in a game) and non-fungible tokens (like unique items), with efficient batch operations built in. Standard ERC-1155 contracts work on Sei without modification. + +## Setup + + + +```ts viem +import { createPublicClient, createWalletClient, http, parseAbi } from 'viem'; +import { privateKeyToAccount } from 'viem/accounts'; +import { sei } from '@sei-js/precompiles/viem'; + +const client = createPublicClient({ chain: sei, transport: http() }); +const account = privateKeyToAccount('0xYourPrivateKey'); +const walletClient = createWalletClient({ account, chain: sei, transport: http() }); + +const ERC1155_ABI = parseAbi([ + 'function balanceOf(address account, uint256 id) view returns (uint256)', + 'function balanceOfBatch(address[] accounts, uint256[] ids) view returns (uint256[])', + 'function isApprovedForAll(address account, address operator) view returns (bool)', + 'function setApprovalForAll(address operator, bool approved)', + 'function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes data)', + 'function safeBatchTransferFrom(address from, address to, uint256[] ids, uint256[] amounts, bytes data)', + 'function uri(uint256 id) view returns (string)', + 'event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value)', + 'event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values)', +]); + +const CONTRACT = '0xContractAddress'; +``` + +```ts ethers +import { ethers } from 'ethers'; + +const provider = new ethers.JsonRpcProvider('https://evm-rpc.sei-apis.com'); +const wallet = new ethers.Wallet('0xYourPrivateKey', provider); + +const ERC1155_ABI = [ + 'function balanceOf(address account, uint256 id) view returns (uint256)', + 'function balanceOfBatch(address[] accounts, uint256[] ids) view returns (uint256[])', + 'function isApprovedForAll(address account, address operator) view returns (bool)', + 'function setApprovalForAll(address operator, bool approved)', + 'function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes data)', + 'function safeBatchTransferFrom(address from, address to, uint256[] ids, uint256[] amounts, bytes data)', + 'function uri(uint256 id) view returns (string)', + 'event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value)', + 'event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values)', +]; + +const CONTRACT = '0xContractAddress'; +const readContract = new ethers.Contract(CONTRACT, ERC1155_ABI, provider); +const writeContract = new ethers.Contract(CONTRACT, ERC1155_ABI, wallet); +``` + + + +## Reading a Single Balance + + + +```ts viem +const balance = await client.readContract({ + address: CONTRACT, + abi: ERC1155_ABI, + functionName: 'balanceOf', + args: ['0xOwnerAddress', 1n], // token ID 1 +}); +``` + +```ts ethers +const balance = await readContract.balanceOf('0xOwnerAddress', 1n); +``` + + + +## Batch Balance Query + +`balanceOfBatch` retrieves multiple owner/ID pairs in one call — the most efficient way to load a user's inventory: + + + +```ts viem +const owners = ['0xOwner', '0xOwner', '0xOwner'] as const; +const ids = [1n, 2n, 3n]; + +const balances = await client.readContract({ + address: CONTRACT, + abi: ERC1155_ABI, + functionName: 'balanceOfBatch', + args: [owners, ids], +}); +// balances[0] = balance of token 1, balances[1] = token 2, etc. +``` + +```ts ethers +const owners = ['0xOwner', '0xOwner', '0xOwner']; +const ids = [1n, 2n, 3n]; + +const balances = await readContract.balanceOfBatch(owners, ids); +``` + + + +## Reading Token Metadata URI + +```ts viem +const uri = await client.readContract({ + address: CONTRACT, + abi: ERC1155_ABI, + functionName: 'uri', + args: [1n], +}); +// URI often contains {id} — replace it with the hex token ID per ERC-1155 spec +const resolvedUri = uri.replace('{id}', (1n).toString(16).padStart(64, '0')); +``` + +## Transferring a Single Token + + + +```ts viem +const hash = await walletClient.writeContract({ + address: CONTRACT, + abi: ERC1155_ABI, + functionName: 'safeTransferFrom', + args: [account.address, '0xRecipient', 1n, 5n, '0x'], // transfer 5 of token ID 1 +}); + +const receipt = await client.waitForTransactionReceipt({ hash }); +``` + +```ts ethers +const tx = await writeContract.safeTransferFrom(wallet.address, '0xRecipient', 1n, 5n, '0x'); +const receipt = await tx.wait(); +``` + + + +## Batch Transfer + +Send multiple token IDs in a single transaction: + + + +```ts viem +const hash = await walletClient.writeContract({ + address: CONTRACT, + abi: ERC1155_ABI, + functionName: 'safeBatchTransferFrom', + args: [ + account.address, + '0xRecipient', + [1n, 2n, 3n], // token IDs + [5n, 10n, 1n], // amounts + '0x', + ], +}); +``` + +```ts ethers +const tx = await writeContract.safeBatchTransferFrom( + wallet.address, + '0xRecipient', + [1n, 2n, 3n], + [5n, 10n, 1n], + '0x', +); +``` + + + +## Operator Approval + + + +```ts viem +// Grant an operator permission to transfer all tokens +const hash = await walletClient.writeContract({ + address: CONTRACT, + abi: ERC1155_ABI, + functionName: 'setApprovalForAll', + args: ['0xOperatorAddress', true], +}); + +// Check approval status +const isApproved = await client.readContract({ + address: CONTRACT, + abi: ERC1155_ABI, + functionName: 'isApprovedForAll', + args: ['0xOwnerAddress', '0xOperatorAddress'], +}); +``` + +```ts ethers +const tx = await writeContract.setApprovalForAll('0xOperatorAddress', true); + +const isApproved = await readContract.isApprovedForAll('0xOwnerAddress', '0xOperatorAddress'); +``` + + + +## Watching Transfer Events + + + +```ts viem +// Watch single transfers +const unwatch = client.watchContractEvent({ + address: CONTRACT, + abi: ERC1155_ABI, + eventName: 'TransferSingle', + onLogs: (logs) => { + logs.forEach(({ args }) => { + console.log(`Token ${args.id}: ${args.from} → ${args.to}, amount: ${args.value}`); + }); + }, +}); + +// Watch batch transfers +const unwatchBatch = client.watchContractEvent({ + address: CONTRACT, + abi: ERC1155_ABI, + eventName: 'TransferBatch', + onLogs: (logs) => { + logs.forEach(({ args }) => { + console.log(`Batch from ${args.from} → ${args.to}:`, args.ids, args.values); + }); + }, +}); +``` + +```ts ethers +readContract.on('TransferSingle', (operator, from, to, id, value) => { + console.log(`Token ${id}: ${from} → ${to}, amount: ${value}`); +}); + +readContract.on('TransferBatch', (operator, from, to, ids, values) => { + console.log(`Batch from ${from} → ${to}:`, ids, values); +}); +``` + + diff --git a/evm/evm-parity/examples/erc20.mdx b/evm/evm-parity/examples/erc20.mdx new file mode 100644 index 0000000..c4e8de1 --- /dev/null +++ b/evm/evm-parity/examples/erc20.mdx @@ -0,0 +1,231 @@ +--- +title: 'ERC-20 Interaction' +description: 'Reading and writing ERC-20 tokens on Sei with viem and ethers' +icon: 'coins' +--- + +# ERC-20 Interaction + +Standard ERC-20 contracts work on Sei without modification. This page covers the common read and write operations using viem and ethers. + +## Setup + + + +```ts viem +import { createPublicClient, createWalletClient, http, parseAbi } from 'viem'; +import { privateKeyToAccount } from 'viem/accounts'; +import { sei } from '@sei-js/precompiles/viem'; + +const client = createPublicClient({ chain: sei, transport: http() }); +const account = privateKeyToAccount('0xYourPrivateKey'); +const walletClient = createWalletClient({ account, chain: sei, transport: http() }); + +const ERC20_ABI = parseAbi([ + 'function name() view returns (string)', + 'function symbol() view returns (string)', + 'function decimals() view returns (uint8)', + 'function totalSupply() view returns (uint256)', + 'function balanceOf(address owner) view returns (uint256)', + 'function allowance(address owner, address spender) view returns (uint256)', + 'function transfer(address to, uint256 amount) returns (bool)', + 'function approve(address spender, uint256 amount) returns (bool)', + 'function transferFrom(address from, address to, uint256 amount) returns (bool)', + 'event Transfer(address indexed from, address indexed to, uint256 value)', + 'event Approval(address indexed owner, address indexed spender, uint256 value)', +]); + +const TOKEN = '0xTokenAddress'; +``` + +```ts ethers +import { ethers } from 'ethers'; + +const provider = new ethers.JsonRpcProvider('https://evm-rpc.sei-apis.com'); +const wallet = new ethers.Wallet('0xYourPrivateKey', provider); + +const ERC20_ABI = [ + 'function name() view returns (string)', + 'function symbol() view returns (string)', + 'function decimals() view returns (uint8)', + 'function totalSupply() view returns (uint256)', + 'function balanceOf(address owner) view returns (uint256)', + 'function allowance(address owner, address spender) view returns (uint256)', + 'function transfer(address to, uint256 amount) returns (bool)', + 'function approve(address spender, uint256 amount) returns (bool)', + 'function transferFrom(address from, address to, uint256 amount) returns (bool)', + 'event Transfer(address indexed from, address indexed to, uint256 value)', + 'event Approval(address indexed owner, address indexed spender, uint256 value)', +]; + +const TOKEN = '0xTokenAddress'; +const readContract = new ethers.Contract(TOKEN, ERC20_ABI, provider); +const writeContract = new ethers.Contract(TOKEN, ERC20_ABI, wallet); +``` + + + +## Reading Token Metadata + + + +```ts viem +const [name, symbol, decimals, totalSupply] = await Promise.all([ + client.readContract({ address: TOKEN, abi: ERC20_ABI, functionName: 'name' }), + client.readContract({ address: TOKEN, abi: ERC20_ABI, functionName: 'symbol' }), + client.readContract({ address: TOKEN, abi: ERC20_ABI, functionName: 'decimals' }), + client.readContract({ address: TOKEN, abi: ERC20_ABI, functionName: 'totalSupply' }), +]); +``` + +```ts ethers +const [name, symbol, decimals, totalSupply] = await Promise.all([ + readContract.name(), + readContract.symbol(), + readContract.decimals(), + readContract.totalSupply(), +]); +``` + + + +## Reading Balances and Allowances + + + +```ts viem +const balance = await client.readContract({ + address: TOKEN, + abi: ERC20_ABI, + functionName: 'balanceOf', + args: ['0xOwnerAddress'], +}); + +const allowance = await client.readContract({ + address: TOKEN, + abi: ERC20_ABI, + functionName: 'allowance', + args: ['0xOwnerAddress', '0xSpenderAddress'], +}); +``` + +```ts ethers +const balance = await readContract.balanceOf('0xOwnerAddress'); +const allowance = await readContract.allowance('0xOwnerAddress', '0xSpenderAddress'); +``` + + + +## Transferring Tokens + + + +```ts viem +import { parseUnits } from 'viem'; + +const hash = await walletClient.writeContract({ + address: TOKEN, + abi: ERC20_ABI, + functionName: 'transfer', + args: ['0xRecipient', parseUnits('10', 18)], +}); + +const receipt = await client.waitForTransactionReceipt({ hash }); +``` + +```ts ethers +const tx = await writeContract.transfer('0xRecipient', ethers.parseUnits('10', 18)); +const receipt = await tx.wait(); +``` + + + +## Approving a Spender + + + +```ts viem +import { parseUnits, maxUint256 } from 'viem'; + +// Approve a specific amount +const hash = await walletClient.writeContract({ + address: TOKEN, + abi: ERC20_ABI, + functionName: 'approve', + args: ['0xSpenderAddress', parseUnits('100', 18)], +}); + +// Or approve max (unlimited) +const hashMax = await walletClient.writeContract({ + address: TOKEN, + abi: ERC20_ABI, + functionName: 'approve', + args: ['0xSpenderAddress', maxUint256], +}); +``` + +```ts ethers +// Approve a specific amount +const tx = await writeContract.approve('0xSpenderAddress', ethers.parseUnits('100', 18)); + +// Or approve max (unlimited) +const txMax = await writeContract.approve('0xSpenderAddress', ethers.MaxUint256); +``` + + + +## Watching Transfer Events + + + +```ts viem +const unwatch = client.watchContractEvent({ + address: TOKEN, + abi: ERC20_ABI, + eventName: 'Transfer', + onLogs: (logs) => { + logs.forEach(({ args }) => { + console.log(`${args.from} → ${args.to}: ${args.value}`); + }); + }, +}); + +// Stop watching +unwatch(); +``` + +```ts ethers +readContract.on('Transfer', (from, to, value) => { + console.log(`${from} → ${to}: ${value}`); +}); + +// Stop watching +readContract.off('Transfer'); +``` + + + +## Fetching Historical Transfers + + + +```ts viem +const logs = await client.getContractEvents({ + address: TOKEN, + abi: ERC20_ABI, + eventName: 'Transfer', + fromBlock: 0n, + toBlock: 'latest', +}); +``` + +```ts ethers +const filter = readContract.filters.Transfer(); +const logs = await readContract.queryFilter(filter, 0, 'latest'); +``` + + + +## CosmWasm Token Compatibility + +CW20 tokens on Sei have ERC-20 pointer contracts that expose the standard ERC-20 interface. You can use all of the patterns above against a CW20 pointer address. See [Pointer Contracts](/evm/evm-parity/examples/pointer-contracts) for how to look up the pointer address. diff --git a/evm/evm-parity/examples/erc721.mdx b/evm/evm-parity/examples/erc721.mdx new file mode 100644 index 0000000..6d88e96 --- /dev/null +++ b/evm/evm-parity/examples/erc721.mdx @@ -0,0 +1,225 @@ +--- +title: 'ERC-721 Interaction' +description: 'Reading and writing ERC-721 NFTs on Sei with viem and ethers' +icon: 'image' +--- + +# ERC-721 Interaction + +Standard ERC-721 contracts work on Sei without modification. This page covers reading token ownership, transferring NFTs, and managing approvals. + +## Setup + + + +```ts viem +import { createPublicClient, createWalletClient, http, parseAbi } from 'viem'; +import { privateKeyToAccount } from 'viem/accounts'; +import { sei } from '@sei-js/precompiles/viem'; + +const client = createPublicClient({ chain: sei, transport: http() }); +const account = privateKeyToAccount('0xYourPrivateKey'); +const walletClient = createWalletClient({ account, chain: sei, transport: http() }); + +const ERC721_ABI = parseAbi([ + 'function name() view returns (string)', + 'function symbol() view returns (string)', + 'function totalSupply() view returns (uint256)', + 'function balanceOf(address owner) view returns (uint256)', + 'function ownerOf(uint256 tokenId) view returns (address)', + 'function tokenURI(uint256 tokenId) view returns (string)', + 'function getApproved(uint256 tokenId) view returns (address)', + 'function isApprovedForAll(address owner, address operator) view returns (bool)', + 'function approve(address to, uint256 tokenId)', + 'function setApprovalForAll(address operator, bool approved)', + 'function transferFrom(address from, address to, uint256 tokenId)', + 'function safeTransferFrom(address from, address to, uint256 tokenId)', + 'event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)', + 'event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId)', +]); + +const NFT = '0xNFTContractAddress'; +``` + +```ts ethers +import { ethers } from 'ethers'; + +const provider = new ethers.JsonRpcProvider('https://evm-rpc.sei-apis.com'); +const wallet = new ethers.Wallet('0xYourPrivateKey', provider); + +const ERC721_ABI = [ + 'function name() view returns (string)', + 'function symbol() view returns (string)', + 'function totalSupply() view returns (uint256)', + 'function balanceOf(address owner) view returns (uint256)', + 'function ownerOf(uint256 tokenId) view returns (address)', + 'function tokenURI(uint256 tokenId) view returns (string)', + 'function getApproved(uint256 tokenId) view returns (address)', + 'function isApprovedForAll(address owner, address operator) view returns (bool)', + 'function approve(address to, uint256 tokenId)', + 'function setApprovalForAll(address operator, bool approved)', + 'function transferFrom(address from, address to, uint256 tokenId)', + 'function safeTransferFrom(address from, address to, uint256 tokenId)', + 'event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)', + 'event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId)', +]; + +const NFT = '0xNFTContractAddress'; +const readContract = new ethers.Contract(NFT, ERC721_ABI, provider); +const writeContract = new ethers.Contract(NFT, ERC721_ABI, wallet); +``` + + + +## Reading Token Data + + + +```ts viem +const tokenId = 1n; + +const [owner, uri, approved] = await Promise.all([ + client.readContract({ address: NFT, abi: ERC721_ABI, functionName: 'ownerOf', args: [tokenId] }), + client.readContract({ address: NFT, abi: ERC721_ABI, functionName: 'tokenURI', args: [tokenId] }), + client.readContract({ address: NFT, abi: ERC721_ABI, functionName: 'getApproved', args: [tokenId] }), +]); +``` + +```ts ethers +const tokenId = 1n; + +const [owner, uri, approved] = await Promise.all([ + readContract.ownerOf(tokenId), + readContract.tokenURI(tokenId), + readContract.getApproved(tokenId), +]); +``` + + + +## Checking Ownership Count + + + +```ts viem +const balance = await client.readContract({ + address: NFT, + abi: ERC721_ABI, + functionName: 'balanceOf', + args: ['0xOwnerAddress'], +}); +``` + +```ts ethers +const balance = await readContract.balanceOf('0xOwnerAddress'); +``` + + + +## Transferring an NFT + + + +```ts viem +const hash = await walletClient.writeContract({ + address: NFT, + abi: ERC721_ABI, + functionName: 'safeTransferFrom', + args: [account.address, '0xRecipient', 1n], +}); + +const receipt = await client.waitForTransactionReceipt({ hash }); +``` + +```ts ethers +const tx = await writeContract.safeTransferFrom(wallet.address, '0xRecipient', 1n); +const receipt = await tx.wait(); +``` + + + +## Approving a Single Token + + + +```ts viem +const hash = await walletClient.writeContract({ + address: NFT, + abi: ERC721_ABI, + functionName: 'approve', + args: ['0xOperatorAddress', 1n], +}); +``` + +```ts ethers +const tx = await writeContract.approve('0xOperatorAddress', 1n); +``` + + + +## Approving All Tokens (Operator) + + + +```ts viem +// Grant operator approval +const hash = await walletClient.writeContract({ + address: NFT, + abi: ERC721_ABI, + functionName: 'setApprovalForAll', + args: ['0xOperatorAddress', true], +}); + +// Revoke +const revokeHash = await walletClient.writeContract({ + address: NFT, + abi: ERC721_ABI, + functionName: 'setApprovalForAll', + args: ['0xOperatorAddress', false], +}); +``` + +```ts ethers +// Grant operator approval +const tx = await writeContract.setApprovalForAll('0xOperatorAddress', true); + +// Revoke +const revokeTx = await writeContract.setApprovalForAll('0xOperatorAddress', false); +``` + + + +## Watching Transfer Events + + + +```ts viem +const unwatch = client.watchContractEvent({ + address: NFT, + abi: ERC721_ABI, + eventName: 'Transfer', + onLogs: (logs) => { + logs.forEach(({ args }) => { + console.log(`Token ${args.tokenId}: ${args.from} → ${args.to}`); + }); + }, +}); + +// Stop watching +unwatch(); +``` + +```ts ethers +readContract.on('Transfer', (from, to, tokenId) => { + console.log(`Token ${tokenId}: ${from} → ${to}`); +}); + +// Stop watching +readContract.off('Transfer'); +``` + + + +## CosmWasm NFT Compatibility + +CW721 tokens on Sei have ERC-721 pointer contracts that expose the standard ERC-721 interface. You can use all of the patterns above against a CW721 pointer address. See [Pointer Contracts](/evm/evm-parity/examples/pointer-contracts) for how to look up the pointer address. diff --git a/evm/evm-parity/examples/error-handling.mdx b/evm/evm-parity/examples/error-handling.mdx new file mode 100644 index 0000000..5f5ffde --- /dev/null +++ b/evm/evm-parity/examples/error-handling.mdx @@ -0,0 +1,192 @@ +--- +title: 'Error Handling' +description: 'Decode contract reverts, handle wallet rejections, and recover from RPC errors in Sei apps' +icon: 'triangle-exclamation' +--- + +# Error Handling + +Most transaction failures fall into three categories: contract reverts, wallet rejections, and RPC errors. This page shows how to decode each type and respond correctly. + +## Contract Reverts + +When a transaction reverts, the error contains the revert reason if the contract provides one. + + + +```ts viem +import { ContractFunctionRevertedError, BaseError } from 'viem'; + +try { + await client.simulateContract({ + address: TOKEN, + abi: ERC20_ABI, + functionName: 'transfer', + args: ['0xRecipient', 1_000_000n], + account, + }); +} catch (err) { + if (err instanceof BaseError) { + const revert = err.walk((e) => e instanceof ContractFunctionRevertedError); + if (revert instanceof ContractFunctionRevertedError) { + console.error('Revert reason:', revert.data?.errorName); + // e.g. 'ERC20InsufficientBalance' + } + } +} +``` + +```ts ethers +import { ethers } from 'ethers'; + +try { + await writeContract.transfer('0xRecipient', 1_000_000n); +} catch (err: any) { + if (err.code === 'CALL_EXCEPTION') { + // Decode using the contract's ABI + const decoded = writeContract.interface.parseError(err.data); + if (decoded) { + console.error('Revert:', decoded.name, decoded.args); + } else { + console.error('Unknown revert data:', err.data); + } + } +} +``` + + + +## Simulating Before Sending + +Always simulate write calls before submitting them. Simulation runs the call against current chain state and surfaces reverts before you spend gas: + +```ts viem +import { ContractFunctionRevertedError, BaseError } from 'viem'; + +async function safeWrite(params: Parameters[0]) { + // 1. Simulate — reverts here cost no gas + const { request } = await client.simulateContract(params); + // 2. Execute only if simulation passes + return walletClient.writeContract(request); +} +``` + +## Wallet Rejections + +Users can reject signature and transaction requests. These rejections have a predictable error code: + + + +```ts viem +import { UserRejectedRequestError } from 'viem'; + +try { + await walletClient.sendTransaction({ to: '0xRecipient', value: 1n }); +} catch (err) { + if (err instanceof UserRejectedRequestError) { + // User cancelled — don't show an error, just reset UI state + console.log('User rejected the request'); + } +} +``` + +```ts ethers +try { + await signer.sendTransaction({ to: '0xRecipient', value: 1n }); +} catch (err: any) { + if (err.code === 4001 || err.code === 'ACTION_REJECTED') { + console.log('User rejected the request'); + } +} +``` + + + +## Insufficient Funds + + + +```ts viem +import { InsufficientFundsError } from 'viem'; + +try { + await walletClient.sendTransaction({ to: '0xRecipient', value: parseEther('9999') }); +} catch (err) { + if (err instanceof InsufficientFundsError) { + console.error('Not enough SEI for this transaction'); + } +} +``` + +```ts ethers +try { + await wallet.sendTransaction({ to: '0xRecipient', value: ethers.parseEther('9999') }); +} catch (err: any) { + if (err.code === 'INSUFFICIENT_FUNDS') { + console.error('Not enough SEI for this transaction'); + } +} +``` + + + +## Classifying Any Error + +A utility to handle the most common cases in one place: + +```ts +import { + BaseError, + ContractFunctionRevertedError, + UserRejectedRequestError, + InsufficientFundsError, +} from 'viem'; + +function classifyError(err: unknown): string { + if (err instanceof UserRejectedRequestError) return 'rejected'; + if (err instanceof InsufficientFundsError) return 'insufficient_funds'; + + if (err instanceof BaseError) { + const revert = err.walk((e) => e instanceof ContractFunctionRevertedError); + if (revert instanceof ContractFunctionRevertedError) { + return `revert:${revert.data?.errorName ?? 'unknown'}`; + } + } + + return 'unknown'; +} +``` + +## RPC Errors and Retries + +Transient RPC failures (network timeouts, rate limits) can be retried. Don't retry on contract reverts or user rejections — those are deterministic. + +```ts +async function withRetry(fn: () => Promise, maxAttempts = 3): Promise { + let lastError: unknown; + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + try { + return await fn(); + } catch (err: any) { + // Don't retry user rejections or contract reverts + if ( + err instanceof UserRejectedRequestError || + err?.code === 4001 || + err?.code === 'CALL_EXCEPTION' + ) { + throw err; + } + lastError = err; + if (attempt < maxAttempts) { + await new Promise((r) => setTimeout(r, 500 * attempt)); // backoff + } + } + } + throw lastError; +} + +// Usage +const balance = await withRetry(() => + client.readContract({ address: TOKEN, abi: ERC20_ABI, functionName: 'balanceOf', args: [owner] }) +); +``` diff --git a/evm/evm-parity/examples/ethers-quickstart.mdx b/evm/evm-parity/examples/ethers-quickstart.mdx new file mode 100644 index 0000000..b52cc5f --- /dev/null +++ b/evm/evm-parity/examples/ethers-quickstart.mdx @@ -0,0 +1,113 @@ +--- +title: 'ethers v6 Quickstart' +description: 'Using ethers v6 with Sei in a Node.js script or browser context' +icon: 'bolt' +--- + +# ethers v6 Quickstart + +This example covers ethers v6 with Sei. The patterns apply whether you are writing a Node.js script, a CLI tool, or a browser app. + +## Install + +```bash +npm install ethers +``` + +## Read-Only Provider + +```ts +import { ethers } from 'ethers'; + +const provider = new ethers.JsonRpcProvider('https://evm-rpc.sei-apis.com'); +``` + +## Reading Chain Data + +```ts +const blockNumber = await provider.getBlockNumber(); + +const block = await provider.getBlock('latest'); + +const balance = await provider.getBalance('0xYourAddress'); + +const nonce = await provider.getTransactionCount('0xYourAddress'); +``` + +## Browser Provider + +In a browser context, connect to the user's injected wallet: + +```ts +const provider = new ethers.BrowserProvider(window.ethereum); +const signer = await provider.getSigner(); +const address = await signer.getAddress(); +``` + +## Wallet from Private Key + +For scripts and backend services: + +```ts +const wallet = new ethers.Wallet('0xYourPrivateKey', provider); +``` + +## Sending a Transaction + +```ts +const tx = await wallet.sendTransaction({ + to: '0xRecipient', + value: ethers.parseEther('1'), +}); + +const receipt = await tx.wait(); +// receipt is final immediately — Sei has instant finality +``` + +## Reading a Contract + +```ts +const abi = ['function balanceOf(address owner) view returns (uint256)']; +const contract = new ethers.Contract('0xTokenAddress', abi, provider); + +const balance = await contract.balanceOf('0xYourAddress'); +``` + +## Writing to a Contract + +Pass a signer (wallet or browser signer) to the contract constructor for write operations: + +```ts +const contract = new ethers.Contract('0xTokenAddress', abi, wallet); + +const tx = await contract.transfer('0xRecipient', 1_000_000n); +const receipt = await tx.wait(); +``` + +## Estimating Gas + +```ts +const gas = await provider.estimateGas({ + from: wallet.address, + to: '0xContractAddress', + data: '0xCalldata', +}); +``` + +## Listening for Events + +```ts +contract.on('Transfer', (from, to, value) => { + console.log('Transfer:', { from, to, value }); +}); + +// Stop listening +contract.off('Transfer'); +``` + +## Next Steps + +- [ERC-20 interaction](/evm/evm-parity/examples/erc20) — full token read/write examples +- [ERC-721 interaction](/evm/evm-parity/examples/erc721) — NFT ownership, transfers, and approvals +- [Pointer contracts](/evm/evm-parity/examples/pointer-contracts) — interact with CosmWasm tokens via ERC interfaces +- [WebSocket connections](/evm/evm-parity/websocket) — real-time block and event subscriptions diff --git a/evm/evm-parity/examples/multicall.mdx b/evm/evm-parity/examples/multicall.mdx new file mode 100644 index 0000000..0b66b63 --- /dev/null +++ b/evm/evm-parity/examples/multicall.mdx @@ -0,0 +1,156 @@ +--- +title: 'Multicall' +description: 'Batch multiple contract reads into a single RPC call using Multicall3 on Sei' +icon: 'layer-group' +--- + +# Multicall + +Multicall3 lets you batch multiple read calls into a single RPC round-trip, dramatically reducing latency for dashboards, portfolio pages, and any view that needs data from multiple contracts at once. + +Multicall3 is deployed at `0xcA11bde05977b3631167028862bE2a173976CA11` on Sei mainnet and testnet — the same address as Ethereum. + +## Batching ERC-20 Reads with viem + +viem's `multicall` action wraps Multicall3 automatically: + +```ts +import { createPublicClient, http, parseAbi } from 'viem'; +import { sei } from '@sei-js/precompiles/viem'; + +const client = createPublicClient({ chain: sei, transport: http() }); + +const ERC20_ABI = parseAbi([ + 'function balanceOf(address owner) view returns (uint256)', + 'function symbol() view returns (string)', + 'function decimals() view returns (uint8)', +]); + +const tokens = [ + '0xTokenA', + '0xTokenB', + '0xTokenC', +] as const; + +const owner = '0xYourAddress'; + +// Fetch symbol, decimals, and balance for every token in one call +const results = await client.multicall({ + contracts: tokens.flatMap((address) => [ + { address, abi: ERC20_ABI, functionName: 'symbol' }, + { address, abi: ERC20_ABI, functionName: 'decimals' }, + { address, abi: ERC20_ABI, functionName: 'balanceOf', args: [owner] }, + ]), +}); + +// Results are returned in the same order as the input contracts array +// Each result has { status: 'success' | 'failure', result, error } +tokens.forEach((address, i) => { + const symbol = results[i * 3]; + const decimals = results[i * 3 + 1]; + const balance = results[i * 3 + 2]; + + if (symbol.status === 'success' && balance.status === 'success') { + console.log(`${address}: ${balance.result} (${symbol.result})`); + } +}); +``` + +## Allowing Individual Call Failures + +By default, a single failed call causes the entire batch to revert. Use `allowFailure: true` (the default in viem) to get partial results instead: + +```ts +const results = await client.multicall({ + contracts: [ + { address: '0xTokenA', abi: ERC20_ABI, functionName: 'balanceOf', args: [owner] }, + { address: '0xMaybeInvalid', abi: ERC20_ABI, functionName: 'balanceOf', args: [owner] }, + ], + allowFailure: true, // default — individual failures don't abort the batch +}); + +results.forEach((result) => { + if (result.status === 'success') { + console.log('Balance:', result.result); + } else { + console.warn('Call failed:', result.error); + } +}); +``` + +## Batching with ethers + +ethers doesn't have a built-in multicall action, but you can call the Multicall3 contract directly: + +```ts +import { ethers } from 'ethers'; + +const provider = new ethers.JsonRpcProvider('https://evm-rpc.sei-apis.com'); + +const MULTICALL3_ADDRESS = '0xcA11bde05977b3631167028862bE2a173976CA11'; +const MULTICALL3_ABI = [ + 'function aggregate3(tuple(address target, bool allowFailure, bytes callData)[] calls) view returns (tuple(bool success, bytes returnData)[] returnData)', +]; + +const multicall = new ethers.Contract(MULTICALL3_ADDRESS, MULTICALL3_ABI, provider); + +const ERC20_INTERFACE = new ethers.Interface([ + 'function balanceOf(address owner) view returns (uint256)', +]); + +const owner = '0xYourAddress'; +const tokens = ['0xTokenA', '0xTokenB', '0xTokenC']; + +const calls = tokens.map((target) => ({ + target, + allowFailure: true, + callData: ERC20_INTERFACE.encodeFunctionData('balanceOf', [owner]), +})); + +const results = await multicall.aggregate3(calls); + +results.forEach(({ success, returnData }: { success: boolean; returnData: string }, i: number) => { + if (success) { + const [balance] = ERC20_INTERFACE.decodeFunctionResult('balanceOf', returnData); + console.log(`${tokens[i]}: ${balance}`); + } +}); +``` + +## Wagmi + +In React apps, `useReadContracts` handles batching automatically: + +```tsx +import { useReadContracts } from 'wagmi'; +import { parseAbi } from 'viem'; + +const ERC20_ABI = parseAbi([ + 'function balanceOf(address owner) view returns (uint256)', + 'function symbol() view returns (string)', +]); + +const TOKEN_A = '0xTokenA' as const; +const TOKEN_B = '0xTokenB' as const; +const owner = '0xYourAddress' as const; + +export function MultiTokenBalances() { + const { data } = useReadContracts({ + contracts: [ + { address: TOKEN_A, abi: ERC20_ABI, functionName: 'symbol' }, + { address: TOKEN_A, abi: ERC20_ABI, functionName: 'balanceOf', args: [owner] }, + { address: TOKEN_B, abi: ERC20_ABI, functionName: 'symbol' }, + { address: TOKEN_B, abi: ERC20_ABI, functionName: 'balanceOf', args: [owner] }, + ], + }); + + if (!data) return null; + + return ( +
    +
  • {data[0].result}: {data[1].result?.toString()}
  • +
  • {data[2].result}: {data[3].result?.toString()}
  • +
+ ); +} +``` diff --git a/evm/evm-parity/examples/pointer-contracts.mdx b/evm/evm-parity/examples/pointer-contracts.mdx new file mode 100644 index 0000000..fae0342 --- /dev/null +++ b/evm/evm-parity/examples/pointer-contracts.mdx @@ -0,0 +1,157 @@ +--- +title: 'Pointer Contracts' +description: 'Looking up and interacting with pointer contracts that bridge CosmWasm and EVM tokens on Sei' +icon: 'arrow-right-arrow-left' +--- + +# Pointer Contracts + +Sei runs two token execution environments side by side — EVM and CosmWasm. Pointer contracts are automatically deployed EVM contracts that proxy a CosmWasm token, and vice versa. + +| Token | Pointer type | EVM interface | +| --- | --- | --- | +| CW20 (CosmWasm fungible token) | ERC-20 pointer | Standard ERC-20 | +| CW721 (CosmWasm NFT) | ERC-721 pointer | Standard ERC-721 | +| Native Sei token (bank module) | ERC-20 pointer | Standard ERC-20 | + +Once you have the pointer address, you interact with it using the standard ERC-20 or ERC-721 interface — no Sei-specific code needed. + +For background on how pointer contracts work, see the [Pointers overview](/learn/pointers). + +## Looking Up a Pointer Address + +The pointerview precompile resolves pointer addresses by CosmWasm contract address or native denom. Its ABI and address are exported from `@sei-js/precompiles`. + + + +```ts viem +import { createPublicClient, http } from 'viem'; +import { sei } from '@sei-js/precompiles/viem'; +import { + POINTERVIEW_PRECOMPILE_ABI, + POINTERVIEW_PRECOMPILE_ADDRESS, +} from '@sei-js/precompiles'; + +const client = createPublicClient({ chain: sei, transport: http() }); + +// CW20 → ERC-20 pointer +const erc20Pointer = await client.readContract({ + address: POINTERVIEW_PRECOMPILE_ADDRESS, + abi: POINTERVIEW_PRECOMPILE_ABI, + functionName: 'getCW20Pointer', + args: ['sei1...cw20ContractAddress'], +}); + +// CW721 → ERC-721 pointer +const erc721Pointer = await client.readContract({ + address: POINTERVIEW_PRECOMPILE_ADDRESS, + abi: POINTERVIEW_PRECOMPILE_ABI, + functionName: 'getCW721Pointer', + args: ['sei1...cw721ContractAddress'], +}); + +// Native denom → ERC-20 pointer +const nativePointer = await client.readContract({ + address: POINTERVIEW_PRECOMPILE_ADDRESS, + abi: POINTERVIEW_PRECOMPILE_ABI, + functionName: 'getNativePointer', + args: ['usei'], +}); +``` + +```ts ethers +import { ethers } from 'ethers'; +import { + POINTERVIEW_PRECOMPILE_ABI, + POINTERVIEW_PRECOMPILE_ADDRESS, +} from '@sei-js/precompiles'; + +const provider = new ethers.JsonRpcProvider('https://evm-rpc.sei-apis.com'); + +const pointerview = new ethers.Contract( + POINTERVIEW_PRECOMPILE_ADDRESS, + POINTERVIEW_PRECOMPILE_ABI, + provider, +); + +// CW20 → ERC-20 pointer +const erc20Pointer = await pointerview.getCW20Pointer('sei1...cw20ContractAddress'); + +// CW721 → ERC-721 pointer +const erc721Pointer = await pointerview.getCW721Pointer('sei1...cw721ContractAddress'); + +// Native denom → ERC-20 pointer +const nativePointer = await pointerview.getNativePointer('usei'); +``` + + + +The return value includes the pointer address and an `exists` boolean. Check `exists` before interacting with the pointer — not all CosmWasm tokens have a deployed pointer. + +## Interacting with a CW20 Pointer as ERC-20 + +Once you have the pointer address, use it as a standard ERC-20 contract. + + + +```ts viem +import { parseAbi } from 'viem'; + +const ERC20_ABI = parseAbi([ + 'function balanceOf(address owner) view returns (uint256)', + 'function transfer(address to, uint256 amount) returns (bool)', +]); + +const balance = await client.readContract({ + address: erc20Pointer.addr, + abi: ERC20_ABI, + functionName: 'balanceOf', + args: ['0xYourAddress'], +}); +``` + +```ts ethers +const ERC20_ABI = [ + 'function balanceOf(address owner) view returns (uint256)', + 'function transfer(address to, uint256 amount) returns (bool)', +]; + +const token = new ethers.Contract(erc20Pointer.addr, ERC20_ABI, provider); +const balance = await token.balanceOf('0xYourAddress'); +``` + + + +## Interacting with a CW721 Pointer as ERC-721 + + + +```ts viem +import { parseAbi } from 'viem'; + +const ERC721_ABI = parseAbi([ + 'function ownerOf(uint256 tokenId) view returns (address)', + 'function tokenURI(uint256 tokenId) view returns (string)', +]); + +const owner = await client.readContract({ + address: erc721Pointer.addr, + abi: ERC721_ABI, + functionName: 'ownerOf', + args: [1n], +}); +``` + +```ts ethers +const ERC721_ABI = [ + 'function ownerOf(uint256 tokenId) view returns (address)', + 'function tokenURI(uint256 tokenId) view returns (string)', +]; + +const nft = new ethers.Contract(erc721Pointer.addr, ERC721_ABI, provider); +const owner = await nft.ownerOf(1n); +``` + + + +For full ERC-20 and ERC-721 examples including transfers, approvals, and events, see the [ERC-20](/evm/evm-parity/examples/erc20) and [ERC-721](/evm/evm-parity/examples/erc721) pages. diff --git a/evm/evm-parity/examples/sei-precompiles.mdx b/evm/evm-parity/examples/sei-precompiles.mdx new file mode 100644 index 0000000..217ec0d --- /dev/null +++ b/evm/evm-parity/examples/sei-precompiles.mdx @@ -0,0 +1,32 @@ +--- +title: 'Sei Precompiles' +description: 'Interact with native Sei chain features — staking, governance, native token balances, and more — from EVM applications' +icon: 'hexagon-nodes' +--- + +Sei exposes native chain functionality through precompiled contracts at deterministic EVM addresses. You can call them from any EVM library (viem, ethers, wagmi) using the ABIs exported from `@sei-js/precompiles` — no special SDK required. + +Available precompiles include: + +- **Bank** — query native denom balances (usei, factory tokens) +- **Staking** — delegate, undelegate, query delegations +- **Distribution** — claim staking rewards +- **Governance** — vote on active proposals +- **JSON** — parse JSON payloads within contracts +- **P256** — verify P-256 elliptic curve signatures + +For installation instructions, code examples (viem and ethers), common patterns, and a full working Node.js script, see the precompiles reference: + + + Usage examples for all precompiles with viem and ethers — Bank, Staking, Distribution, Governance, JSON, and P256. + + +For the full ABI reference and every available function on each precompile: + + + + + + + + diff --git a/evm/evm-parity/examples/transaction-lifecycle.mdx b/evm/evm-parity/examples/transaction-lifecycle.mdx new file mode 100644 index 0000000..dc8b383 --- /dev/null +++ b/evm/evm-parity/examples/transaction-lifecycle.mdx @@ -0,0 +1,180 @@ +--- +title: 'Transaction Lifecycle' +description: 'Send transactions, wait for receipts, and decode event logs on Sei' +icon: 'arrow-progress' +--- + +# Transaction Lifecycle + +This page covers the full round-trip of a transaction: building and sending it, waiting for the receipt, and reading the event logs it emitted. + +## Sending a Transaction + + + +```ts viem +import { createWalletClient, http, parseEther } from 'viem'; +import { privateKeyToAccount } from 'viem/accounts'; +import { sei } from '@sei-js/precompiles/viem'; + +const account = privateKeyToAccount('0xYourPrivateKey'); +const walletClient = createWalletClient({ account, chain: sei, transport: http() }); + +const hash = await walletClient.sendTransaction({ + to: '0xRecipient', + value: parseEther('1'), +}); +``` + +```ts ethers +import { ethers } from 'ethers'; + +const provider = new ethers.JsonRpcProvider('https://evm-rpc.sei-apis.com'); +const wallet = new ethers.Wallet('0xYourPrivateKey', provider); + +const tx = await wallet.sendTransaction({ + to: '0xRecipient', + value: ethers.parseEther('1'), +}); +``` + + + +## Waiting for the Receipt + + + +```ts viem +import { createPublicClient, http } from 'viem'; + +const client = createPublicClient({ chain: sei, transport: http() }); + +const receipt = await client.waitForTransactionReceipt({ hash }); + +console.log('Status:', receipt.status); // 'success' | 'reverted' +console.log('Block:', receipt.blockNumber); +console.log('Gas used:', receipt.gasUsed); +``` + +```ts ethers +const receipt = await tx.wait(); +// or wait for a specific number of confirmations: +// const receipt = await tx.wait(1); + +console.log('Status:', receipt.status); // 1 = success, 0 = reverted +console.log('Block:', receipt.blockNumber); +console.log('Gas used:', receipt.gasUsed); +``` + + + +Sei has instant finality — the receipt is final as soon as it arrives. One confirmation is sufficient. See [Finality](/evm/evm-parity/finality). + +## Decoding Event Logs from a Receipt + +After a contract interaction, decode the events the transaction emitted: + + + +```ts viem +import { parseAbi, decodeEventLog } from 'viem'; + +const ERC20_ABI = parseAbi([ + 'function transfer(address to, uint256 amount) returns (bool)', + 'event Transfer(address indexed from, address indexed to, uint256 value)', +]); + +const hash = await walletClient.writeContract({ + address: TOKEN, + abi: ERC20_ABI, + functionName: 'transfer', + args: ['0xRecipient', 1_000_000n], +}); + +const receipt = await client.waitForTransactionReceipt({ hash }); + +// viem returns typed logs automatically when you pass the ABI to getTransactionReceipt +const typedReceipt = await client.getTransactionReceipt({ hash }); + +// Or decode manually from raw logs +for (const log of receipt.logs) { + try { + const event = decodeEventLog({ abi: ERC20_ABI, ...log }); + console.log('Event:', event.eventName, event.args); + } catch { + // Log from a different contract or unknown ABI + } +} +``` + +```ts ethers +import { ethers } from 'ethers'; + +const ERC20_ABI = [ + 'function transfer(address to, uint256 amount) returns (bool)', + 'event Transfer(address indexed from, address indexed to, uint256 value)', +]; + +const contract = new ethers.Contract(TOKEN, ERC20_ABI, wallet); +const tx = await contract.transfer('0xRecipient', 1_000_000n); +const receipt = await tx.wait(); + +// ethers parses logs automatically when using a Contract instance +for (const log of receipt.logs) { + try { + const parsed = contract.interface.parseLog(log); + if (parsed) { + console.log('Event:', parsed.name, parsed.args); + // e.g. Event: Transfer [ '0xFrom', '0xTo', 1000000n ] + } + } catch { + // Log from a different contract + } +} +``` + + + +## Checking for Revert + +A receipt with `status: 'reverted'` (viem) or `status: 0` (ethers) means the transaction was included but the execution failed. The gas was still consumed. + +```ts viem +const receipt = await client.waitForTransactionReceipt({ hash }); + +if (receipt.status === 'reverted') { + // Transaction was mined but execution failed. + // Simulate the call to get the revert reason. + console.error('Transaction reverted at block', receipt.blockNumber); +} +``` + +## Fetching a Past Transaction + +```ts viem +const tx = await client.getTransaction({ hash: '0xTxHash' }); +console.log('From:', tx.from); +console.log('To:', tx.to); +console.log('Value:', tx.value); +console.log('Input data:', tx.input); +``` + +```ts ethers +const tx = await provider.getTransaction('0xTxHash'); +const receipt = await provider.getTransactionReceipt('0xTxHash'); +``` + +## Getting Logs for a Specific Transaction + +To fetch only the events emitted by a specific transaction (rather than decoding from the receipt): + +```ts viem +const logs = await client.getContractEvents({ + address: TOKEN, + abi: ERC20_ABI, + eventName: 'Transfer', + blockHash: receipt.blockHash, +}); + +const txLogs = logs.filter((log) => log.transactionHash === hash); +``` diff --git a/evm/evm-parity/examples/viem-quickstart.mdx b/evm/evm-parity/examples/viem-quickstart.mdx new file mode 100644 index 0000000..c9b9ec5 --- /dev/null +++ b/evm/evm-parity/examples/viem-quickstart.mdx @@ -0,0 +1,146 @@ +--- +title: 'viem Quickstart' +description: 'Using viem with Sei in a Node.js script, CLI tool, or backend service' +icon: 'bolt' +--- + +# viem Quickstart + +This example shows how to use viem with Sei outside of a React context — in a Node.js script, CLI tool, or backend service. For React apps, see [wagmi](https://wagmi.sh) and the [frontend guide](/evm/building-a-frontend). + +## Install + +```bash +npm install viem @sei-js/precompiles +``` + +## Public Client + +A public client handles all read-only operations. + +```ts +import { createPublicClient, http } from 'viem'; +import { sei } from '@sei-js/precompiles/viem'; + +const client = createPublicClient({ + chain: sei, + transport: http(), +}); +``` + +To use a custom RPC endpoint, pass the URL to `http()`: + +```ts +const client = createPublicClient({ + chain: sei, + transport: http('https://evm-rpc.sei-apis.com'), +}); +``` + +## Reading Chain Data + +```ts +const blockNumber = await client.getBlockNumber(); + +const block = await client.getBlock({ blockTag: 'latest' }); + +const balance = await client.getBalance({ address: '0xYourAddress' }); + +const nonce = await client.getTransactionCount({ address: '0xYourAddress' }); +``` + +## Wallet Client + +A wallet client handles signing and broadcasting transactions. + +```ts +import { createWalletClient, http } from 'viem'; +import { privateKeyToAccount } from 'viem/accounts'; +import { sei } from '@sei-js/precompiles/viem'; + +const account = privateKeyToAccount('0xYourPrivateKey'); + +const walletClient = createWalletClient({ + account, + chain: sei, + transport: http(), +}); +``` + +For browser use, replace `http()` with `custom(window.ethereum)` and call `walletClient.getAddresses()` to get the connected account. + +## Sending a Transaction + +```ts +import { parseEther } from 'viem'; + +const hash = await walletClient.sendTransaction({ + to: '0xRecipient', + value: parseEther('1'), +}); + +const receipt = await client.waitForTransactionReceipt({ hash }); +// receipt is final immediately — Sei has instant finality +``` + +## Reading a Contract + +```ts +import { parseAbi } from 'viem'; + +const abi = parseAbi(['function balanceOf(address owner) view returns (uint256)']); + +const balance = await client.readContract({ + address: '0xTokenAddress', + abi, + functionName: 'balanceOf', + args: ['0xYourAddress'], +}); +``` + +## Writing to a Contract + +```ts +const hash = await walletClient.writeContract({ + address: '0xTokenAddress', + abi, + functionName: 'transfer', + args: ['0xRecipient', 1_000_000n], +}); + +const receipt = await client.waitForTransactionReceipt({ hash }); +``` + +## Simulating Before Writing + +```ts +const { result } = await client.simulateContract({ + address: '0xTokenAddress', + abi, + functionName: 'transfer', + args: ['0xRecipient', 1_000_000n], + account, +}); + +// If simulation succeeds, execute it +const hash = await walletClient.writeContract(result); +``` + +## Estimating Gas + +Always use `estimateGas` rather than hard-coding values. SSTORE costs on Sei are governance-adjustable. + +```ts +const gas = await client.estimateGas({ + account, + to: '0xContractAddress', + data: '0xCalldata', +}); +``` + +## Next Steps + +- [ERC-20 interaction](/evm/evm-parity/examples/erc20) — full token read/write examples +- [ERC-721 interaction](/evm/evm-parity/examples/erc721) — NFT ownership, transfers, and approvals +- [Pointer contracts](/evm/evm-parity/examples/pointer-contracts) — interact with CosmWasm tokens via ERC interfaces +- [WebSocket connections](/evm/evm-parity/websocket) — real-time block and event subscriptions diff --git a/evm/evm-parity/examples/wagmi-react.mdx b/evm/evm-parity/examples/wagmi-react.mdx new file mode 100644 index 0000000..fbe8d01 --- /dev/null +++ b/evm/evm-parity/examples/wagmi-react.mdx @@ -0,0 +1,204 @@ +--- +title: 'Wagmi + React' +description: 'Connect wallets, read chain state, and write transactions in a React app on Sei using Wagmi' +icon: 'react' +--- + +# Wagmi + React + +[Wagmi](https://wagmi.sh) is the standard React library for EVM wallet connections and contract interactions. This page covers the essential patterns for a Sei frontend: configuration, wallet connect/disconnect, reading balances, and writing to contracts. + +For Node.js scripts and backend services, see the [viem Quickstart](/evm/evm-parity/examples/viem-quickstart) instead. + +## Install + +```bash +npm install wagmi viem @sei-js/precompiles @tanstack/react-query +``` + +## Configuration + +Configure Wagmi with the Sei chain and your preferred wallet connectors: + +```ts +// wagmi.config.ts +import { createConfig, http } from 'wagmi'; +import { injected, walletConnect } from 'wagmi/connectors'; +import { sei, seiTestnet } from '@sei-js/precompiles/viem'; + +export const config = createConfig({ + chains: [sei, seiTestnet], + connectors: [ + injected(), + walletConnect({ projectId: 'YOUR_WALLETCONNECT_PROJECT_ID' }), + ], + transports: { + [sei.id]: http(), + [seiTestnet.id]: http(), + }, +}); +``` + +## Provider Setup + +Wrap your app with `WagmiProvider` and `QueryClientProvider`: + +```tsx +// App.tsx +import { WagmiProvider } from 'wagmi'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { config } from './wagmi.config'; + +const queryClient = new QueryClient(); + +export function App() { + return ( + + + + + + ); +} +``` + +## Connecting a Wallet + +```tsx +import { useConnect, useDisconnect, useAccount } from 'wagmi'; + +export function WalletButton() { + const { connectors, connect } = useConnect(); + const { disconnect } = useDisconnect(); + const { address, isConnected } = useAccount(); + + if (isConnected) { + return ( +
+ {address} + +
+ ); + } + + return ( +
+ {connectors.map((connector) => ( + + ))} +
+ ); +} +``` + +## Reading the Native SEI Balance + +```tsx +import { useBalance, useAccount } from 'wagmi'; +import { formatEther } from 'viem'; + +export function SeiBalance() { + const { address } = useAccount(); + const { data: balance } = useBalance({ address }); + + if (!balance) return null; + + return ( +

Balance: {formatEther(balance.value)} SEI

+ ); +} +``` + +## Reading a Contract + +```tsx +import { useReadContract } from 'wagmi'; +import { parseAbi } from 'viem'; + +const ERC20_ABI = parseAbi([ + 'function balanceOf(address owner) view returns (uint256)', + 'function symbol() view returns (string)', +]); + +export function TokenBalance({ tokenAddress, owner }: { tokenAddress: `0x${string}`; owner: `0x${string}` }) { + const { data: balance } = useReadContract({ + address: tokenAddress, + abi: ERC20_ABI, + functionName: 'balanceOf', + args: [owner], + }); + + const { data: symbol } = useReadContract({ + address: tokenAddress, + abi: ERC20_ABI, + functionName: 'symbol', + }); + + if (balance === undefined) return null; + + return

{balance.toString()} {symbol}

; +} +``` + +## Writing to a Contract + +Use `useWriteContract` for transactions that mutate state, and `useWaitForTransactionReceipt` to track confirmation: + +```tsx +import { useWriteContract, useWaitForTransactionReceipt } from 'wagmi'; +import { parseAbi, parseUnits } from 'viem'; + +const ERC20_ABI = parseAbi([ + 'function transfer(address to, uint256 amount) returns (bool)', +]); + +export function TransferButton({ tokenAddress }: { tokenAddress: `0x${string}` }) { + const { writeContract, data: hash, isPending } = useWriteContract(); + const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ hash }); + + function handleTransfer() { + writeContract({ + address: tokenAddress, + abi: ERC20_ABI, + functionName: 'transfer', + args: ['0xRecipient', parseUnits('10', 18)], + }); + } + + return ( +
+ + {isSuccess &&

Transfer confirmed.

} +
+ ); +} +``` + +Sei has instant finality — `useWaitForTransactionReceipt` resolves as soon as the transaction is included in a block. A single confirmation is final. See [Finality](/evm/evm-parity/finality). + +## Watching the Connected Chain + +```tsx +import { useChainId, useSwitchChain } from 'wagmi'; +import { sei, seiTestnet } from '@sei-js/precompiles/viem'; + +export function NetworkSwitcher() { + const chainId = useChainId(); + const { switchChain } = useSwitchChain(); + + return ( +
+

Connected to chain {chainId}

+ {chainId !== sei.id && ( + + )} +
+ ); +} +``` diff --git a/evm/evm-parity/finality.mdx b/evm/evm-parity/finality.mdx new file mode 100644 index 0000000..3fe5406 --- /dev/null +++ b/evm/evm-parity/finality.mdx @@ -0,0 +1,85 @@ +--- +title: 'Finality and Block Tags' +description: 'How Sei instant finality affects block tag behavior and pending state' +icon: 'circle-check' +--- + +# Finality and Block Tags + +Sei uses Twin Turbo Consensus, which provides instant finality. Every committed block is final immediately — there is no period where a block could be reorganized away. + +This changes how block tags behave compared to Ethereum. + +## Block Tags + +On Ethereum, `latest`, `safe`, and `finalized` refer to different points in the chain: + +- `latest` — the most recent block, possibly not yet safe +- `safe` — a block unlikely to be reorganized +- `finalized` — a block that is permanently committed + +On Sei, all three tags resolve to the same block. There is no reorg risk at any point after a block is committed, so the distinction does not exist. + + + +```ts viem +import { createPublicClient, http } from 'viem'; +import { sei } from '@sei-js/precompiles/viem'; + +const client = createPublicClient({ chain: sei, transport: http() }); + +// All three return the same block on Sei +const latest = await client.getBlock({ blockTag: 'latest' }); +const safe = await client.getBlock({ blockTag: 'safe' }); +const finalized = await client.getBlock({ blockTag: 'finalized' }); +``` + +```ts ethers +import { ethers } from 'ethers'; + +const provider = new ethers.JsonRpcProvider('https://evm-rpc.sei-apis.com'); + +// All three return the same block on Sei +const latest = await provider.getBlock('latest'); +const safe = await provider.getBlock('safe'); +const finalized = await provider.getBlock('finalized'); +``` + + + +## Waiting for Confirmation + +Because finality is instant, you do not need to wait for multiple confirmations. `waitForTransactionReceipt` resolves as soon as the transaction is included in a block. + + + +```ts viem +const receipt = await client.waitForTransactionReceipt({ hash }); +// receipt is already final — no further confirmation needed +``` + +```ts ethers +const tx = await signer.sendTransaction({ /* ... */ }); +const receipt = await tx.wait(1); // 1 confirmation is final on Sei +``` + + + +## Pending State + +Sei does not expose Ethereum-style pending state. Do not rely on: + +- Reading pending transactions from the mempool +- `eth_getBlockByNumber` with `'pending'` +- Pending nonce differing from the confirmed nonce + +If your application polls pending transactions or depends on pending state visibility, replace that pattern with confirmed-block polling or WebSocket subscriptions on committed blocks. + +## Practical Impact + +| Pattern | On Ethereum | On Sei | +| --- | --- | --- | +| Wait for `finalized` tag | Waits ~13 minutes | Returns immediately | +| Check `safe` vs `latest` | Different blocks | Same block | +| Read pending mempool | Supported | Not reliable | +| Confirmation count | Meaningful | 1 is sufficient | diff --git a/evm/evm-parity/gas-and-fees.mdx b/evm/evm-parity/gas-and-fees.mdx new file mode 100644 index 0000000..bab1f93 --- /dev/null +++ b/evm/evm-parity/gas-and-fees.mdx @@ -0,0 +1,93 @@ +--- +title: 'Gas and Fees' +description: 'Sei gas price floor, EIP-1559 fee model differences, and SSTORE cost' +icon: 'gauge' +--- + +# Gas and Fees + +Sei supports both legacy and EIP-1559 transactions, but the fee model differs from Ethereum in three ways that affect how you estimate and set gas. + +## Legacy Gas Price Floor + +Legacy transactions (type 0) on Sei must meet a minimum gas price set by on-chain governance. This floor can change via governance proposals — do not hard-code a specific value. + +Use `eth_gasPrice` to read the current minimum: + + + +```ts viem +import { createPublicClient, http } from 'viem'; +import { sei } from '@sei-js/precompiles/viem'; + +const client = createPublicClient({ chain: sei, transport: http() }); +const gasPrice = await client.getGasPrice(); +``` + +```ts ethers +import { ethers } from 'ethers'; + +const provider = new ethers.JsonRpcProvider('https://evm-rpc.sei-apis.com'); +const feeData = await provider.getFeeData(); +const gasPrice = feeData.gasPrice; +``` + + + +## EIP-1559 Fee Model + +Sei supports EIP-1559 transactions (type 2), but does not burn the base fee. Fees go entirely to validators rather than being partially burned as on Ethereum. + +This does not affect how you construct or send transactions — `maxFeePerGas` and `maxPriorityFeePerGas` work as expected. It is relevant only if your application models token supply or displays fee-burn information to users. + + + +```ts viem +const fees = await client.estimateFeesPerGas(); +// fees.maxFeePerGas and fees.maxPriorityFeePerGas are usable as-is +``` + +```ts ethers +const feeData = await provider.getFeeData(); +// feeData.maxFeePerGas and feeData.maxPriorityFeePerGas are usable as-is +``` + + + +## SSTORE Cost + +The gas cost of `SSTORE` (writing to contract storage) is governance-adjustable on Sei. Do not hard-code storage write estimates in your application. + +Always use `eth_estimateGas` for any transaction that writes to storage: + + + +```ts viem +const gas = await client.estimateGas({ + account, + to: contractAddress, + data: encodedCalldata, +}); + +// Add a buffer for governance changes during high-activity periods +const gasWithBuffer = (gas * 120n) / 100n; // 20% buffer +``` + +```ts ethers +const gas = await provider.estimateGas({ + from: signer.address, + to: contractAddress, + data: encodedCalldata, +}); +``` + + + +## Summary + +| Behavior | Ethereum | Sei | +| --- | --- | --- | +| Legacy gas floor | Protocol minimum | Governance-set, can change | +| Base fee | Burned | Paid to validators | +| SSTORE cost | Fixed by EIP | Governance-adjustable | +| `estimateGas` | Always correct to use | Required — do not hard-code storage costs | diff --git a/evm/evm-parity/signing.mdx b/evm/evm-parity/signing.mdx new file mode 100644 index 0000000..33aba27 --- /dev/null +++ b/evm/evm-parity/signing.mdx @@ -0,0 +1,151 @@ +--- +title: 'Signing' +description: 'Wallet signing method support and EIP-712 typed data on Sei' +icon: 'pen' +--- + +# Signing + +Signing on Sei works through standard EVM libraries and wallet standards. The methods available depend on which wallet the user has connected. + +## Wallet Signing Support + +| Method | Standard | Sei Global Wallet | MetaMask | WalletConnect wallets | +| --- | --- | --- | --- | --- | +| `personal_sign` | EIP-191 | Supported | Supported | Wallet-dependent | +| `eth_signTypedData_v4` | EIP-712 | Supported | Supported | Wallet-dependent | +| `eth_sign` | Legacy | Not recommended | Supported | Wallet-dependent | +| Transaction signing | — | Supported | Supported | Supported | + +Do not assume all wallets support all methods. Always handle rejection gracefully. + +## Personal Sign + + + +```ts viem +import { createWalletClient, custom } from 'viem'; +import { sei } from '@sei-js/precompiles/viem'; + +const client = createWalletClient({ + chain: sei, + transport: custom(window.ethereum), +}); + +const [account] = await client.getAddresses(); + +const signature = await client.signMessage({ + account, + message: 'Hello Sei', +}); +``` + +```ts ethers +import { ethers } from 'ethers'; + +const provider = new ethers.BrowserProvider(window.ethereum); +const signer = await provider.getSigner(); + +const signature = await signer.signMessage('Hello Sei'); +``` + + + +## EIP-712 Typed Data + + + +```ts viem +const [account] = await client.getAddresses(); + +const signature = await client.signTypedData({ + account, + domain: { + name: 'My App', + version: '1', + chainId: 1329, // Sei mainnet + verifyingContract: '0xContractAddress', + }, + types: { + Order: [ + { name: 'maker', type: 'address' }, + { name: 'amount', type: 'uint256' }, + { name: 'expiry', type: 'uint256' }, + ], + }, + primaryType: 'Order', + message: { + maker: account, + amount: 1000000n, + expiry: BigInt(Math.floor(Date.now() / 1000) + 3600), + }, +}); +``` + +```ts ethers +import { ethers } from 'ethers'; + +const provider = new ethers.BrowserProvider(window.ethereum); +const signer = await provider.getSigner(); + +const domain = { + name: 'My App', + version: '1', + chainId: 1329, + verifyingContract: '0xContractAddress', +}; + +const types = { + Order: [ + { name: 'maker', type: 'address' }, + { name: 'amount', type: 'uint256' }, + { name: 'expiry', type: 'uint256' }, + ], +}; + +const value = { + maker: await signer.getAddress(), + amount: 1000000n, + expiry: BigInt(Math.floor(Date.now() / 1000) + 3600), +}; + +const signature = await signer.signTypedData(domain, types, value); +``` + + + +## Verifying Signatures + +```ts viem +import { verifyMessage, verifyTypedData } from 'viem'; + +// Verify personal sign +const valid = await verifyMessage({ + address: '0xSignerAddress', + message: 'Hello Sei', + signature, +}); + +// Verify EIP-712 +const validTyped = await verifyTypedData({ + address: '0xSignerAddress', + domain, + types, + primaryType: 'Order', + message: value, + signature, +}); +``` + +## EIP-1271 Contract Signatures + +Smart contract wallets on Sei can validate signatures via EIP-1271 (`isValidSignature`). Use viem's `verifyMessage` with a contract address — it automatically uses EIP-1271 when the address is a contract. + +```ts viem +const valid = await verifyMessage({ + address: '0xSmartContractWallet', + message: 'Hello Sei', + signature, +}); +// viem calls isValidSignature if the address is a contract +``` diff --git a/evm/evm-parity/state-proofs.mdx b/evm/evm-parity/state-proofs.mdx new file mode 100644 index 0000000..21acc8b --- /dev/null +++ b/evm/evm-parity/state-proofs.mdx @@ -0,0 +1,68 @@ +--- +title: 'State Proofs' +description: 'How eth_getProof differs on Sei due to IAVL tree storage' +icon: 'shield-halved' +--- + +# State Proofs + +Sei supports `eth_getProof` but returns a different proof format from Ethereum. If your application verifies proofs on-chain or off-chain, you need to account for this difference. + +## The Difference + +Ethereum stores state in a Merkle Patricia Trie (MPT) and `eth_getProof` returns MPT inclusion proofs. Sei stores state in an IAVL tree and returns IAVL proofs instead. + +The RPC method exists and responds correctly, but the proof data structure is not compatible with Ethereum MPT proof verifiers. + +## What This Affects + +Most applications do not call `eth_getProof` directly. It is primarily used by: + +- Light clients verifying state without trusting an RPC node +- Cross-chain bridges proving inclusion of state on Sei +- Applications verifying contract storage values trustlessly + +If you are doing standard contract reads, event queries, or transaction lookups, this difference does not affect you. + +## Calling eth_getProof + +The call works through standard libraries: + + + +```ts viem +import { createPublicClient, http } from 'viem'; +import { sei } from '@sei-js/precompiles/viem'; + +const client = createPublicClient({ chain: sei, transport: http() }); + +const proof = await client.getProof({ + address: '0xContractAddress', + storageKeys: ['0xStorageSlot'], +}); + +// proof.accountProof and proof.storageProof contain IAVL proof data, +// not Ethereum MPT proofs — do not pass to an MPT verifier +``` + +```ts ethers +import { ethers } from 'ethers'; + +const provider = new ethers.JsonRpcProvider('https://evm-rpc.sei-apis.com'); + +const proof = await provider.send('eth_getProof', [ + '0xContractAddress', + ['0xStorageSlot'], + 'latest', +]); + +// Same caveat — proof data is IAVL format +``` + + + +## Verifying Proofs + +To verify Sei state proofs, use an IAVL-compatible verifier. Standard Ethereum MPT verifier libraries (e.g. those used in Solidity or in Ethereum bridge contracts) will reject Sei proofs. + +Refer to the [Sei RPC reference](/evm/reference) for the exact proof schema returned. diff --git a/evm/evm-parity/transaction-types.mdx b/evm/evm-parity/transaction-types.mdx new file mode 100644 index 0000000..98c0383 --- /dev/null +++ b/evm/evm-parity/transaction-types.mdx @@ -0,0 +1,66 @@ +--- +title: 'Transaction Types' +description: 'Which Ethereum transaction types are supported on Sei' +icon: 'arrow-right-arrow-left' +--- + +# Transaction Types + +Sei supports most Ethereum transaction types. The one notable exception is blob transactions. + +## Supported Types + +| Type | EIP | Name | Sei support | +| --- | --- | --- | --- | +| 0 | — | Legacy | Supported — governance-set minimum gas price applies | +| 1 | EIP-2930 | Access list | Supported | +| 2 | EIP-1559 | Fee market | Supported — base fee is not burned | +| 4 | EIP-7702 | Set code | Supported | + +## Not Supported + +| Type | EIP | Name | Notes | +| --- | --- | --- | --- | +| 3 | EIP-4844 | Blob | Not supported — Sei runs Pectra without blob transactions | + +## Blob Transactions + +Sei runs the Pectra hardfork without blob transaction support. Attempting to send a type 3 transaction will be rejected at the RPC level. + +If you are porting code from Ethereum that uses blob transactions (e.g. rollup data availability), that path does not apply to Sei. + +## Sending Transactions + +Standard library defaults work correctly. viem, wagmi, and ethers all default to type 2 (EIP-1559) transactions on chains that support it, which Sei does. + + + +```ts viem +import { createWalletClient, http, parseEther } from 'viem'; +import { privateKeyToAccount } from 'viem/accounts'; +import { sei } from '@sei-js/precompiles/viem'; + +const account = privateKeyToAccount('0xYourPrivateKey'); +const client = createWalletClient({ account, chain: sei, transport: http() }); + +// Sends a type 2 transaction — correct default for Sei +const hash = await client.sendTransaction({ + to: '0xRecipient', + value: parseEther('1'), +}); +``` + +```ts ethers +import { ethers } from 'ethers'; + +const provider = new ethers.JsonRpcProvider('https://evm-rpc.sei-apis.com'); +const wallet = new ethers.Wallet('0xYourPrivateKey', provider); + +// ethers defaults to EIP-1559 where supported — correct for Sei +const tx = await wallet.sendTransaction({ + to: '0xRecipient', + value: ethers.parseEther('1'), +}); +``` + + diff --git a/evm/evm-parity/websocket.mdx b/evm/evm-parity/websocket.mdx new file mode 100644 index 0000000..e086811 --- /dev/null +++ b/evm/evm-parity/websocket.mdx @@ -0,0 +1,117 @@ +--- +title: 'WebSocket Connections' +description: 'Connecting to Sei via WebSocket for real-time block and event subscriptions' +icon: 'plug' +--- + +# WebSocket Connections + +Sei supports `eth_subscribe` over WebSocket. You can subscribe to new blocks, event logs, and pending transactions using standard library WebSocket transports. + +## Endpoints + +| Network | WebSocket endpoint | +| --- | --- | +| Mainnet | `wss://evm-ws.sei-apis.com` | +| Testnet | `wss://evm-ws-testnet.sei-apis.com` | +| Devnet | `wss://evm-ws-arctic-1.sei-apis.com` | + +## Connecting + + + +```ts viem +import { createPublicClient, webSocket } from 'viem'; +import { sei } from '@sei-js/precompiles/viem'; + +const client = createPublicClient({ + chain: sei, + transport: webSocket('wss://evm-ws.sei-apis.com'), +}); +``` + +```ts ethers +import { ethers } from 'ethers'; + +const provider = new ethers.WebSocketProvider('wss://evm-ws.sei-apis.com'); +``` + + + +## Watching New Blocks + + + +```ts viem +const unwatch = client.watchBlocks({ + onBlock: (block) => { + console.log('New block:', block.number); + }, +}); + +// Stop watching +unwatch(); +``` + +```ts ethers +provider.on('block', (blockNumber) => { + console.log('New block:', blockNumber); +}); + +// Stop watching +provider.off('block'); +``` + + + +## Watching Contract Events + + + +```ts viem +import { parseAbiItem } from 'viem'; + +const unwatch = client.watchEvent({ + address: '0xContractAddress', + event: parseAbiItem('event Transfer(address indexed from, address indexed to, uint256 value)'), + onLogs: (logs) => { + console.log('Transfer events:', logs); + }, +}); +``` + +```ts ethers +import { ethers } from 'ethers'; + +const ERC20_ABI = ['event Transfer(address indexed from, address indexed to, uint256 value)']; +const contract = new ethers.Contract('0xContractAddress', ERC20_ABI, provider); + +contract.on('Transfer', (from, to, value, event) => { + console.log('Transfer:', { from, to, value }); +}); + +// Stop watching +contract.off('Transfer'); +``` + + + +## Watching ERC-20 Transfers Across All Contracts + +```ts viem +import { parseAbiItem } from 'viem'; + +const unwatch = client.watchEvent({ + event: parseAbiItem('event Transfer(address indexed from, address indexed to, uint256 value)'), + onLogs: (logs) => { + logs.forEach((log) => { + console.log(`Transfer on ${log.address}:`, log.args); + }); + }, +}); +``` + +## Notes + +- Sei's instant finality means every block emitted over WebSocket is already final — no need to wait for additional confirmations before acting on an event. +- Pending transaction subscriptions (`newPendingTransactions`) are supported at the RPC level but Sei does not guarantee Ethereum-style pending state visibility. diff --git a/evm/precompiles/example-usage.mdx b/evm/precompiles/example-usage.mdx index 58322da..058d1ed 100644 --- a/evm/precompiles/example-usage.mdx +++ b/evm/precompiles/example-usage.mdx @@ -1,192 +1,347 @@ --- title: 'Precompile Example Usage' sidebarTitle: 'Example Usage' -description: "Learn how to interact with Sei's precompiles through ethers.js, covering all available precompiles with quick examples and common patterns for seamless integration between EVM and Cosmos functionality." -keywords: ['precompile usage', 'ethers.js', 'sei precompiles', 'evm integration', 'quick start guide'] +description: "Learn how to interact with Sei's precompiles through viem and ethers, covering all available precompiles with quick examples and common patterns for seamless integration between EVM and Sei-native functionality." +keywords: ['precompile usage', 'viem', 'ethers.js', 'sei precompiles', 'evm integration', 'quick start guide'] --- -Sei precompiles are special smart contracts deployed at fixed addresses that expose native Sei functionality to EVM applications. They can be used like any standard smart contract, enabling powerful cross-chain capabilities. + +Sei precompiles are special smart contracts deployed at fixed addresses that expose native Sei functionality to EVM applications. They can be called like any standard smart contract using viem, ethers, or wagmi. **Available Precompiles:** -- **Staking** (`0x1005`) - Delegation and staking operations -- **Governance** (`0x1006`) - Proposal submission and voting -- **JSON** (`0x1003`) - JSON data parsing -- **P256** (`0x1011`) - Verifying `P-256` elliptic curve signatures -- **Distribution** (`0x1007`) - Staking rewards and validator commissions - +| Precompile | Address | Description | +| --- | --- | --- | +| Bank | `0x1001` | Query native denom balances (usei, factory tokens) | +| JSON | `0x1003` | Parse JSON data within contracts | +| Staking | `0x1005` | Delegation and staking operations | +| Governance | `0x1006` | Proposal voting | +| Distribution | `0x1007` | Claim staking rewards | +| P256 | `0x1011` | Verify P-256 elliptic curve signatures | -## Quick Setup - -Install the required dependencies: +## Setup ```bash -npm install ethers @sei-js/evm +npm install viem ethers @sei-js/precompiles ``` -Set up your provider and signer: + + +```ts viem +import { createPublicClient, createWalletClient, http, custom } from 'viem'; +import { sei } from '@sei-js/precompiles/viem'; + +// Read-only +const client = createPublicClient({ chain: sei, transport: http() }); -```typescript +// Browser wallet (write) +const walletClient = createWalletClient({ chain: sei, transport: custom(window.ethereum) }); +const [account] = await walletClient.getAddresses(); +``` + +```ts ethers import { ethers } from 'ethers'; -// Browser environment with MetaMask +// Browser environment const provider = new ethers.BrowserProvider(window.ethereum); -await provider.send('eth_requestAccounts', []); const signer = await provider.getSigner(); -// Or Node.js environment -const provider = new ethers.JsonRpcProvider('https://evm-rpc-testnet.sei-apis.com'); -const signer = new ethers.Wallet('YOUR_PRIVATE_KEY', provider); +// Node.js / scripts +// const provider = new ethers.JsonRpcProvider('https://evm-rpc.sei-apis.com'); +// const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider); ``` -## Precompile Examples + -### Staking Precompile +## Bank Precompile -Delegate tokens and manage staking operations: +`eth_getBalance` only returns the EVM-side SEI balance. The Bank precompile lets you query any native denom — including factory tokens — from any address: -```typescript -import { STAKING_PRECOMPILE_ABI, STAKING_PRECOMPILE_ADDRESS } from '@sei-js/evm'; + -const staking = new ethers.Contract(STAKING_PRECOMPILE_ADDRESS, STAKING_PRECOMPILE_ABI, signer); +```ts viem +import { BANK_PRECOMPILE_ABI, BANK_PRECOMPILE_ADDRESS } from '@sei-js/precompiles'; + +// Query a specific denom balance +const balance = await client.readContract({ + address: BANK_PRECOMPILE_ADDRESS, + abi: BANK_PRECOMPILE_ABI, + functionName: 'balance', + args: ['0xYourAddress', 'usei'], +}); + +// Query all native balances for an address +const allBalances = await client.readContract({ + address: BANK_PRECOMPILE_ADDRESS, + abi: BANK_PRECOMPILE_ABI, + functionName: 'all_balances', + args: ['0xYourAddress'], +}); +// Returns: [{ denom: 'usei', amount: '1000000' }, ...] +``` + +```ts ethers +import { BANK_PRECOMPILE_ABI, BANK_PRECOMPILE_ADDRESS } from '@sei-js/precompiles'; + +const bank = new ethers.Contract(BANK_PRECOMPILE_ADDRESS, BANK_PRECOMPILE_ABI, provider); + +const balance = await bank.balance('0xYourAddress', 'usei'); +const allBalances = await bank.all_balances('0xYourAddress'); +``` + + + +## Staking Precompile + +Delegate, undelegate, and query staking state: -// Delegate 1 SEI -const validatorAddress = 'seivaloper1xyz...'; -const amountToDelegate = ethers.parseUnits('1', 18); + -const tx = await staking.delegate(validatorAddress, { value: amountToDelegate }); +```ts viem +import { parseEther } from 'viem'; +import { STAKING_PRECOMPILE_ABI, STAKING_PRECOMPILE_ADDRESS } from '@sei-js/precompiles'; + +// Delegate SEI to a validator +const delegateHash = await walletClient.writeContract({ + address: STAKING_PRECOMPILE_ADDRESS, + abi: STAKING_PRECOMPILE_ABI, + functionName: 'delegate', + args: ['seivaloper1...'], + value: parseEther('10'), +}); + +// Query delegation +const delegation = await client.readContract({ + address: STAKING_PRECOMPILE_ADDRESS, + abi: STAKING_PRECOMPILE_ABI, + functionName: 'delegation', + args: [account, 'seivaloper1...'], +}); + +// Undelegate +const undelegateHash = await walletClient.writeContract({ + address: STAKING_PRECOMPILE_ADDRESS, + abi: STAKING_PRECOMPILE_ABI, + functionName: 'undelegate', + args: ['seivaloper1...', parseEther('5')], +}); +``` + +```ts ethers +import { STAKING_PRECOMPILE_ABI, STAKING_PRECOMPILE_ADDRESS } from '@sei-js/precompiles'; + +const staking = new ethers.Contract(STAKING_PRECOMPILE_ADDRESS, STAKING_PRECOMPILE_ABI, signer); +const readStaking = new ethers.Contract(STAKING_PRECOMPILE_ADDRESS, STAKING_PRECOMPILE_ABI, provider); + +// Delegate 10 SEI +const tx = await staking.delegate('seivaloper1...', { value: ethers.parseEther('10') }); await tx.wait(); // Query delegation -const delegation = await staking.delegation(await signer.getAddress(), validatorAddress); -console.log('Delegated amount:', delegation.balance.amount.toString()); +const delegation = await readStaking.delegation(await signer.getAddress(), 'seivaloper1...'); +console.log('Delegated:', delegation.balance.amount.toString()); + +// Undelegate +const undelegateTx = await staking.undelegate('seivaloper1...', ethers.parseEther('5')); +await undelegateTx.wait(); ``` -### Governance Precompile + + +## Governance Precompile + +Vote on an active governance proposal: -Submit proposals and vote on governance: + + +```ts viem +import { GOVERNANCE_PRECOMPILE_ABI, GOVERNANCE_PRECOMPILE_ADDRESS } from '@sei-js/precompiles'; + +// Vote options: 1 = Yes, 2 = Abstain, 3 = No, 4 = NoWithVeto +const hash = await walletClient.writeContract({ + address: GOVERNANCE_PRECOMPILE_ADDRESS, + abi: GOVERNANCE_PRECOMPILE_ABI, + functionName: 'vote', + args: [99n, 1], +}); +``` -```typescript -import { GOVERNANCE_PRECOMPILE_ABI, GOVERNANCE_PRECOMPILE_ADDRESS } from '@sei-js/evm'; +```ts ethers +import { GOVERNANCE_PRECOMPILE_ABI, GOVERNANCE_PRECOMPILE_ADDRESS } from '@sei-js/precompiles'; const governance = new ethers.Contract(GOVERNANCE_PRECOMPILE_ADDRESS, GOVERNANCE_PRECOMPILE_ABI, signer); -// Vote on a proposal -const proposalId = 1; -const voteOption = 1; // 1=Yes, 2=Abstain, 3=No, 4=NoWithVeto +// 1 = Yes, 2 = Abstain, 3 = No, 4 = NoWithVeto +const tx = await governance.vote(99n, 1); +await tx.wait(); +``` + + + +## Distribution Precompile + +Claim staking rewards from a validator: + + + +```ts viem +import { DISTRIBUTION_PRECOMPILE_ABI, DISTRIBUTION_PRECOMPILE_ADDRESS } from '@sei-js/precompiles'; + +const hash = await walletClient.writeContract({ + address: DISTRIBUTION_PRECOMPILE_ADDRESS, + abi: DISTRIBUTION_PRECOMPILE_ABI, + functionName: 'withdrawDelegatorReward', + args: [account, 'seivaloper1...'], +}); +``` -const tx = await governance.vote(proposalId, voteOption); +```ts ethers +import { DISTRIBUTION_PRECOMPILE_ABI, DISTRIBUTION_PRECOMPILE_ADDRESS } from '@sei-js/precompiles'; + +const distribution = new ethers.Contract( + DISTRIBUTION_PRECOMPILE_ADDRESS, + DISTRIBUTION_PRECOMPILE_ABI, + signer, +); + +const tx = await distribution.withdrawDelegatorReward(await signer.getAddress(), 'seivaloper1...'); await tx.wait(); -console.log('Vote cast successfully'); ``` -### JSON Precompile + + +## JSON Precompile + +Parse JSON data within EVM contracts or dApps — useful for processing oracle responses, NFT metadata, and structured payloads: + + + +```ts viem +import { toHex, toBytes } from 'viem'; +import { JSON_PRECOMPILE_ABI, JSON_PRECOMPILE_ADDRESS } from '@sei-js/precompiles'; + +const data = { price: 100, symbol: 'SEI' }; +const inputBytes = toBytes(JSON.stringify(data)); + +// Extract a string value +const symbolBytes = await client.readContract({ + address: JSON_PRECOMPILE_ADDRESS, + abi: JSON_PRECOMPILE_ABI, + functionName: 'extractAsBytes', + args: [inputBytes, 'symbol'], +}); -Parse and query JSON data on-chain: +// Extract a numeric value +const price = await client.readContract({ + address: JSON_PRECOMPILE_ADDRESS, + abi: JSON_PRECOMPILE_ABI, + functionName: 'extractAsUint256', + args: [inputBytes, 'price'], +}); +``` -```typescript -import { JSON_PRECOMPILE_ABI, JSON_PRECOMPILE_ADDRESS } from '@sei-js/evm'; +```ts ethers +import { JSON_PRECOMPILE_ABI, JSON_PRECOMPILE_ADDRESS } from '@sei-js/precompiles'; -const jsonPrecompile = new ethers.Contract(JSON_PRECOMPILE_ADDRESS, JSON_PRECOMPILE_ABI, signer); +const jsonPrecompile = new ethers.Contract(JSON_PRECOMPILE_ADDRESS, JSON_PRECOMPILE_ABI, provider); -// Parse JSON data const data = { price: 100, symbol: 'SEI' }; const inputData = ethers.toUtf8Bytes(JSON.stringify(data)); -// Extract string value +// Extract a string value const symbolBytes = await jsonPrecompile.extractAsBytes(inputData, 'symbol'); const symbol = ethers.toUtf8String(symbolBytes); -// Extract numeric value +// Extract a numeric value const price = await jsonPrecompile.extractAsUint256(inputData, 'price'); + console.log(`${symbol}: ${price}`); ``` + + ## Common Patterns ### Error Handling -Always wrap precompile calls in try-catch blocks: +Wrap precompile calls in try/catch and check for specific error codes: -```typescript +```ts try { - const tx = await staking.delegate(validatorAddress, { value: amount }); + const tx = await staking.delegate('seivaloper1...', { value: ethers.parseEther('10') }); const receipt = await tx.wait(); console.log('Success:', receipt.hash); -} catch (error) { - if (error.code === 'INSUFFICIENT_FUNDS') { - console.error('Not enough SEI for delegation'); +} catch (err: any) { + if (err.code === 'INSUFFICIENT_FUNDS') { + console.error('Not enough SEI'); + } else if (err.code === 4001 || err.code === 'ACTION_REJECTED') { + console.log('User rejected'); } else { - console.error('Transaction failed:', error.message); + console.error('Transaction failed:', err.message); } } ``` -### Gas Optimization - -Set appropriate gas limits for different operations: +### Batch Read Operations -```typescript -// Simple operations (queries) -const delegation = await staking.delegation(address, validator, { - gasLimit: 100000 -}); +Fetch multiple precompile values in parallel: -// Complex operations (transactions) -const tx = await governance.submitProposal(proposalJSON, { - value: depositAmount, - gasLimit: 500000 -}); +```ts +const [delegation1, delegation2, rewards] = await Promise.all([ + readStaking.delegation(address, validator1), + readStaking.delegation(address, validator2), + distribution.delegationTotalRewards(address), +]); ``` -### Batch Operations - -Execute multiple precompile calls efficiently: +### Gas Limits -```typescript -// Batch read operations -const [delegation1, delegation2] = await Promise.all([staking.delegation(address, validator1), staking.delegation(address, validator2)]); +Precompile calls that write state need enough gas. Use `estimateGas` rather than hard-coding: -// Sequential write operations with error handling -const operations = [() => staking.delegate(validator1, { value: amount1 }), () => staking.delegate(validator2, { value: amount2 }), () => governance.vote(proposalId, voteOption)]; - -for (const operation of operations) { - try { - const tx = await operation(); - await tx.wait(); - } catch (error) { - console.error('Operation failed:', error); - } -} +```ts viem +const gas = await client.estimateGas({ + account, + address: STAKING_PRECOMPILE_ADDRESS, + abi: STAKING_PRECOMPILE_ABI, + functionName: 'delegate', + args: ['seivaloper1...'], + value: parseEther('10'), +}); ``` -## Complete Working Example +## Full Working Example -Here's a minimal example that demonstrates using multiple precompiles: +A Node.js script that delegates SEI and then casts a governance vote: -```javascript -// multi-precompile-demo.js -const { ethers } = require('ethers'); -const { STAKING_PRECOMPILE_ABI, STAKING_PRECOMPILE_ADDRESS, GOVERNANCE_PRECOMPILE_ABI, GOVERNANCE_PRECOMPILE_ADDRESS } = require('@sei-js/evm'); +```ts +import { ethers } from 'ethers'; +import { + STAKING_PRECOMPILE_ABI, STAKING_PRECOMPILE_ADDRESS, + GOVERNANCE_PRECOMPILE_ABI, GOVERNANCE_PRECOMPILE_ADDRESS, +} from '@sei-js/precompiles'; async function main() { - // Setup const provider = new ethers.JsonRpcProvider('https://evm-rpc.sei-apis.com'); - const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider); + const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider); - // Initialize precompiles const staking = new ethers.Contract(STAKING_PRECOMPILE_ADDRESS, STAKING_PRECOMPILE_ABI, wallet); const governance = new ethers.Contract(GOVERNANCE_PRECOMPILE_ADDRESS, GOVERNANCE_PRECOMPILE_ABI, wallet); - // Check delegation - const delegation = await staking.delegation(wallet.address, 'seivaloper1...'); + // Check current delegation + const readStaking = staking.connect(provider) as typeof staking; + const delegation = await readStaking.delegation(wallet.address, 'seivaloper1...'); console.log('Current delegation:', delegation.balance.amount.toString()); - // Vote on a proposal - const tx = await governance.vote(1, 1); - await tx.wait(); - console.log('Vote cast successfully'); + // Delegate 1 SEI + const delegateTx = await staking.delegate('seivaloper1...', { value: ethers.parseEther('1') }); + await delegateTx.wait(); + console.log('Delegated 1 SEI'); + + // Vote Yes on proposal 99 + const voteTx = await governance.vote(99n, 1); + await voteTx.wait(); + console.log('Vote cast'); } main().catch(console.error); @@ -195,30 +350,15 @@ main().catch(console.error); Run with: ```bash -PRIVATE_KEY=your_key node multi-precompile-demo.js +PRIVATE_KEY=your_key npx tsx script.ts ``` -## Best Practices - - -**Important Guidelines:** - -- Always validate inputs before calling precompiles -- Use appropriate decimal conversions -- Handle errors gracefully with specific error messages -- Set reasonable gas limits to avoid failed transactions -- Never expose private keys in code - - - ## Next Steps -For detailed documentation on each precompile: - -- [Staking Precompile Usage →](/evm/precompiles/staking) -- [Governance Precompile Usage →](/evm/precompiles/governance) -- [Distribution Precompile Usage →](/evm/precompiles/distribution) -- [P256 Precompile Usage →](/evm/precompiles/p256-precompile) -- [JSON Precompile Usage →](/evm/precompiles/json) +For full ABI reference and all available functions on each precompile: -**Need Help?** Join the [Sei Discord](https://discord.gg/sei) for community support or check the [GitHub repository](https://github.com/sei-protocol/sei-chain) for more examples. +- [Staking Precompile →](/evm/precompiles/staking) +- [Governance Precompile →](/evm/precompiles/governance) +- [Distribution Precompile →](/evm/precompiles/distribution) +- [JSON Precompile →](/evm/precompiles/json) +- [P256 Precompile →](/evm/precompiles/p256-precompile) diff --git a/evm/sei-js/index.mdx b/evm/sei-js/index.mdx index 038fd06..4d40bee 100644 --- a/evm/sei-js/index.mdx +++ b/evm/sei-js/index.mdx @@ -50,6 +50,14 @@ Secure transaction signing with Ledger hardware wallets. Provides TypeScript hel npm install @sei-js/ledger ``` +### [@sei-js/registry](/evm/sei-js/registry) + +Chain constants, RPC endpoints, token metadata, gas parameters, and wallet info — a typed reference for Sei network configuration. + +```bash +npm install @sei-js/registry +``` + ### [@sei-js/mcp-server](/evm/ai-tooling/mcp-server) Teach Claude, Cursor, Windsurf, or any LLM to interact with the Sei blockchain through the Model Context Protocol. @@ -71,6 +79,49 @@ npm run dev This creates a production-ready project with TypeScript, wallet connections, and Sei network integration out of the box. See the [Scaffold Sei](/evm/sei-js/create-sei) page for details on available templates and options. +## Examples + +End-to-end code examples using viem and ethers with Sei: + + + + Read chain data, send transactions, and interact with contracts in a Node.js script or backend. + + + The same patterns using ethers v6, including browser provider and event listening. + + + Connect wallets, read balances, and write contracts in a React app using Wagmi hooks. + + + Read metadata, check balances, transfer tokens, approve spenders, and query transfer history. + + + Check ownership, transfer NFTs, manage approvals, and watch transfer events. + + + Batch balance queries, single and batch transfers, operator approvals, and event watching. + + + Batch multiple contract reads into a single RPC call with Multicall3 and viem/ethers/wagmi. + + + Look up CW20/CW721/native pointer addresses and interact with CosmWasm tokens via ERC interfaces. + + + Native balances, staking, rewards, and governance voting from JavaScript. + + + Deploy contracts with viem, ethers, Foundry, or Hardhat and verify on Seiscan via Sourcify. + + + Send transactions, wait for receipts, and decode event logs. + + + Decode reverts, handle wallet rejections, and retry transient RPC failures. + + + ## Community & Support - [Discord](https://discord.gg/sei) — Join the Sei developer community diff --git a/evm/sei-js/registry.mdx b/evm/sei-js/registry.mdx new file mode 100644 index 0000000..db5e5c1 --- /dev/null +++ b/evm/sei-js/registry.mdx @@ -0,0 +1,159 @@ +--- +title: '@sei-js/registry' +sidebarTitle: '@sei-js/registry' +description: 'Chain constants, RPC endpoints, token metadata, and wallet info for Sei Network' +keywords: ['sei-js', 'registry', 'chain constants', 'rpc endpoints', 'token list', 'sei network'] +--- + +`@sei-js/registry` is a typed reference package for Sei chain metadata — RPC endpoints, token lists, gas parameters, wallet info, and more. It pulls from the official [sei-protocol/chain-registry](https://github.com/sei-protocol/chain-registry) and is kept in sync as a git submodule. + +## Install + +```bash +npm install @sei-js/registry +``` + +## CHAIN_IDS + +Canonical chain IDs for each Sei network: + +```ts +import { CHAIN_IDS } from '@sei-js/registry'; + +CHAIN_IDS.mainnet // 'pacific-1' +CHAIN_IDS.testnet // 'atlantic-2' +CHAIN_IDS.devnet // 'arctic-1' +``` + +Use these constants anywhere you reference a Sei network by chain ID to avoid hardcoding strings. + +## NETWORKS + +RPC, REST, gRPC, EVM RPC, WebSocket, and explorer endpoints for each network: + +```ts +import { NETWORKS } from '@sei-js/registry'; + +const mainnet = NETWORKS['pacific-1']; + +// Pick the first available EVM RPC endpoint +const evmRpc = mainnet.evm_rpc?.[0].url; + +// Pick the first available WebSocket endpoint +const evmWs = mainnet.evm_ws?.[0].url; + +// Pick a Cosmos REST endpoint +const rest = mainnet.rest[0].url; +``` + +Each entry has `provider` (name) and `url` fields. Multiple providers are listed per category — iterate to implement fallback logic: + +```ts +async function getWorkingRpc(network: 'pacific-1' | 'atlantic-2' | 'arctic-1') { + const endpoints = NETWORKS[network].evm_rpc ?? []; + for (const endpoint of endpoints) { + try { + const res = await fetch(endpoint.url, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ jsonrpc: '2.0', method: 'eth_blockNumber', params: [], id: 1 }), + }); + if (res.ok) return endpoint.url; + } catch {} + } + throw new Error(`No working EVM RPC found for ${network}`); +} +``` + +## TOKEN_LIST + +Token registry per network — name, symbol, base denom, decimal exponents, images, and CoinGecko IDs: + +```ts +import { TOKEN_LIST } from '@sei-js/registry'; + +// All tokens on mainnet +const tokens = TOKEN_LIST['pacific-1']; + +// Find SEI +const sei = tokens.find(t => t.symbol === 'SEI'); +// { base: 'usei', display: 'sei', denom_units: [{ denom: 'usei', exponent: 0 }, { denom: 'sei', exponent: 6 }] } + +// Convert a raw usei amount to display amount +function toDisplayAmount(usei: bigint, token: typeof sei): string { + const exp = token.denom_units.find(u => u.denom === token.display)?.exponent ?? 6; + return (Number(usei) / 10 ** exp).toString(); +} + +// Look up a token by its on-chain denom +function findByDenom(denom: string) { + return TOKEN_LIST['pacific-1'].find(t => t.base === denom); +} +``` + +Each token includes an `images` object with `png` and `svg` URLs suitable for display in wallet UIs or token pickers. + +## GAS_INFO + +Minimum gas price and module-specific adjustments per network: + +```ts +import { GAS_INFO } from '@sei-js/registry'; + +const { denom, min_gas_price } = GAS_INFO['pacific-1']; +// denom: 'usei', min_gas_price: 0.02 + +// Calculate the minimum fee for a given gas limit +function minFee(gasLimit: number): string { + const { min_gas_price, denom } = GAS_INFO['pacific-1']; + return `${Math.ceil(gasLimit * min_gas_price)}${denom}`; +} +``` + +The `min_gas_price` here is the Cosmos-side gas floor. For EVM transactions, always use `eth_gasPrice` or `eth_estimateGas` — the on-chain EVM gas floor is governed separately and can change. See [Gas and Fees](/evm/evm-parity/gas-and-fees). + +## CHAIN_INFO + +Basic chain metadata: daemon name, Bech32 prefix, HD path coin type, and supported wallets: + +```ts +import { CHAIN_INFO } from '@sei-js/registry'; + +CHAIN_INFO.bech32_prefix // 'sei' +CHAIN_INFO.slip44 // 118 (HD wallet coin type) +CHAIN_INFO.supported_wallets // ['fin', 'compass', 'leap', 'keplr'] + +// Validate a Cosmos-side Sei address format +function isSeiAddress(address: string): boolean { + return address.startsWith(CHAIN_INFO.bech32_prefix + '1'); +} + +// Derive the HD path for key generation +const path = `m/44'/${CHAIN_INFO.slip44}'/0'/0/0`; +``` + +## WALLETS + +Wallet metadata including icons, URLs, and EVM/native capability flags: + +```ts +import { WALLETS } from '@sei-js/registry'; + +// All wallets that support EVM +const evmWallets = WALLETS.filter(w => w.capabilities.includes('evm')); + +// Find a specific wallet for displaying its icon +const compass = WALLETS.find(w => w.identifier === 'compass'); +// { name: 'Compass Wallet', icon: 'https://...jpeg', url: 'https://compasswallet.io/', capabilities: ['native', 'evm'] } +``` + +Useful for building wallet selector UIs that show icons and filter by capability. + +## Network Reference + +| Constant | `pacific-1` (mainnet) | `atlantic-2` (testnet) | `arctic-1` (devnet) | +| --- | --- | --- | --- | +| Chain ID | `pacific-1` | `atlantic-2` | `arctic-1` | +| EVM Chain ID | 1329 | 1328 | 713715 | +| EVM RPC | `NETWORKS['pacific-1'].evm_rpc` | `NETWORKS['atlantic-2'].evm_rpc` | `NETWORKS['arctic-1'].evm_rpc` | +| EVM WS | `NETWORKS['pacific-1'].evm_ws` | `NETWORKS['atlantic-2'].evm_ws` | `NETWORKS['arctic-1'].evm_ws` | diff --git a/index.mdx b/index.mdx index 3b6b74e..67c87a1 100644 --- a/index.mdx +++ b/index.mdx @@ -18,7 +18,7 @@ The first parallelized EVM blockchain delivering unmatched scalability and speed ## Quick Start - + Deploy EVM contracts with parallelized execution. @@ -31,6 +31,9 @@ The first parallelized EVM blockchain delivering unmatched scalability and speed Verify and publish your contract source code. + + viem, ethers, ERC-20/721, pointer contracts, and Sei behavior differences. + ## Essential Resources