diff --git a/STYLE_GUIDE.md b/STYLE_GUIDE.md index d064af0..057335b 100644 --- a/STYLE_GUIDE.md +++ b/STYLE_GUIDE.md @@ -33,7 +33,7 @@ Great documentation is self-explanatory. Documentation shouldn't need more docum ## Organization -The Sei Docs are structured using [Mintlify](https://mintlify.com). The documentation is organized into four main sections based on target audience and purpose. +The Sei Docs are structured using [Mintlify](https://mintlify.com). The documentation is organized into tabs based on target audience and purpose. ### Learn @@ -64,11 +64,23 @@ The EVM section is the primary developer resource for building on Sei. It covers - **Reference**: Transactions, RPC reference, tokens, changelog, ecosystem contracts - **Hardware Wallets**: Ledger integration with Ethers +### Cookbook + +The Cookbook tab holds short, task-focused recipes, such as "Read a balance", "Send SEI", and "Listen to events". Each recipe does one task from start to finish. Put new recipes in `evm/cookbook/`. The older example pages stay in `evm/evm-parity/examples/` so that their URLs do not change. + +When you write a recipe: + +- Show the same code in viem, ethers, and web3.py where the task allows it. Label the `` tabs exactly `viem`, `ethers`, and `web3.py`, so that the tabs on a page stay in sync. +- Use Sei Testnet for anything that sends a transaction. +- When a read-only JSON-RPC call shows the result live, add a ``. +- If a TypeScript example uses top-level `await`, tell readers to run it as a `.mts` file with `npx tsx`. In a new npm project, `tsx` compiles a plain `.ts` file as CommonJS, where top-level `await` fails. +- End with a "You are done when you see" block that shows the expected output. + ### Cosmos-SDK (Deprecated) > ⚠️ **Deprecation Notice**: Cosmos SDK and CosmWasm functionality is being deprecated in favor of EVM-only. For more details, see [SIP-3](https://github.com/sei-protocol/sips/blob/main/sips/sip-3.md) and [Proposal 99](https://seistream.app/proposals/99). -This section contains legacy documentation for Cosmos SDK functionality. New development should focus on the EVM. +The Learn tab lists the single deprecation page, `cosmos-sdk/index.mdx`, next to the SIP-03 migration guides. New development should focus on the EVM. ### Operate (Node) diff --git a/docs.json b/docs.json index ce24c8a..8465429 100644 --- a/docs.json +++ b/docs.json @@ -51,7 +51,8 @@ "learn/dev-gas", "learn/accounts", "learn/sip-03-migration", - "learn/sip-03-exchange-migration" + "learn/sip-03-exchange-migration", + "cosmos-sdk/index" ] }, { @@ -221,23 +222,6 @@ { "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": [ @@ -304,12 +288,55 @@ ] }, { - "tab": "Cosmos-SDK", + "tab": "Cookbook", "groups": [ { - "group": "Cosmos-SDK", + "group": "Overview", "pages": [ - "cosmos-sdk/index" + "evm/cookbook/index" + ] + }, + { + "group": "Client Setup", + "pages": [ + "evm/evm-parity/examples/viem-quickstart", + "evm/evm-parity/examples/ethers-quickstart", + "evm/evm-parity/examples/wagmi-react" + ] + }, + { + "group": "Accounts and Transactions", + "pages": [ + "evm/cookbook/read-a-balance", + "evm/cookbook/send-sei", + "evm/evm-parity/examples/transaction-lifecycle", + "evm/evm-parity/examples/error-handling", + "evm/cookbook/sponsor-gas-with-pimlico" + ] + }, + { + "group": "Tokens", + "pages": [ + "evm/cookbook/deploy-an-erc20", + "evm/evm-parity/examples/erc20", + "evm/evm-parity/examples/erc721", + "evm/evm-parity/examples/erc1155" + ] + }, + { + "group": "Contracts", + "pages": [ + "evm/evm-parity/examples/deploy-verify", + "evm/cookbook/listen-to-events", + "evm/evm-parity/examples/multicall", + "evm/cookbook/read-a-price-feed" + ] + }, + { + "group": "Sei Features", + "pages": [ + "evm/evm-parity/examples/sei-precompiles", + "evm/evm-parity/examples/pointer-contracts" ] } ] @@ -1697,6 +1724,16 @@ "source": "/templates", "destination": "/evm/templates", "permanent": true + }, + { + "source": "/cookbook", + "destination": "/evm/cookbook", + "permanent": true + }, + { + "source": "/evm/evm-parity/examples", + "destination": "/evm/cookbook", + "permanent": true } ], "interaction": { diff --git a/evm/cookbook/deploy-an-erc20.mdx b/evm/cookbook/deploy-an-erc20.mdx new file mode 100644 index 0000000..033220b --- /dev/null +++ b/evm/cookbook/deploy-an-erc20.mdx @@ -0,0 +1,265 @@ +--- +title: 'Deploy an ERC-20' +description: 'Write an ERC-20 token with OpenZeppelin Contracts, deploy it to Sei Testnet with Foundry, Hardhat, or Remix, then verify it and read it back.' +keywords: ['sei evm', 'erc-20', 'deploy token', 'openzeppelin', 'foundry', 'hardhat', 'remix', 'cookbook'] +--- + +import { SandboxEmbed } from '/snippets/sandbox-embed.jsx'; +import { AddSeiButton } from '/snippets/add-sei-button.jsx'; + +This recipe deploys `DemoToken`, a fixed-supply ERC-20 token built on [OpenZeppelin Contracts](https://docs.openzeppelin.com/contracts/5.x/erc20). Standard ERC-20 contracts work on Sei without changes. + +## Before you start + +- A Sei Testnet account that holds some SEI for gas. Get testnet SEI from the [faucet](/learn/faucet). Use a throwaway account that holds only testnet funds. +- For Foundry, the account's private key in the `PRIVATE_KEY` environment variable. +- For Hardhat, the private key in the Hardhat keystore, as the [Hardhat guide](/evm/evm-hardhat#configuring-hardhat-for-sei-evm) shows. +- For Remix, a browser wallet such as MetaMask. + +## The contract + +```solidity DemoToken.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract DemoToken is ERC20 { + constructor() ERC20("Demo Token", "DEMO") { + _mint(msg.sender, 1_000_000 * 10 ** decimals()); + } +} +``` + +The constructor mints 1,000,000 DEMO to the account that deploys the contract. `decimals()` returns 18, so the contract stores the supply as 1,000,000 × 1018 base units. + +## Deploy the contract + + + + +Run these commands to create a project, install OpenZeppelin Contracts, and write the import remappings to `remappings.txt`: + +```bash +forge init demo-token +cd demo-token +forge install OpenZeppelin/openzeppelin-contracts +forge remappings > remappings.txt +``` + +`remappings.txt` maps `@openzeppelin/contracts/` to the installed library. Foundry finds that mapping on its own, but editors and other Solidity tools read it from this file. + +Save the contract as `src/DemoToken.sol`. Then deploy it: + +```bash +forge create src/DemoToken.sol:DemoToken \ + --rpc-url https://evm-rpc-testnet.sei-apis.com \ + --private-key $PRIVATE_KEY \ + --broadcast +``` + +Foundry prints the token address after `Deployed to:`. + +Without `--broadcast`, `forge create` only simulates the deployment. It prints the transaction, but it does not send it. + +For project settings, tests, and deploy scripts, see the [Foundry guide](/evm/evm-foundry). + + + + +Start from a Hardhat 3 project that has Sei Testnet configured as the `seitestnet` network. The [Hardhat guide](/evm/evm-hardhat#configuring-hardhat-for-sei-evm) shows the configuration and how to store your key in the Hardhat keystore. Then install OpenZeppelin Contracts: + +```bash +npm install @openzeppelin/contracts +``` + +Save the contract as `contracts/DemoToken.sol`. Then describe the deployment in an Ignition module: + +```ts ignition/modules/DemoToken.ts +import { buildModule } from '@nomicfoundation/hardhat-ignition/modules'; + +export default buildModule('DemoTokenModule', (m) => { + const token = m.contract('DemoToken'); + return { token }; +}); +``` + +Deploy the module: + +```bash +npx hardhat ignition deploy ignition/modules/DemoToken.ts --network seitestnet +``` + +Ignition prints the token address next to `DemoTokenModule#DemoToken`. + + + + +Remix compiles and deploys from the browser, so you do not install anything. First, add Sei Testnet to your wallet: + + + +The sandbox below preloads `DemoToken`. Compile it on the **Solidity Compiler** tab. Then deploy it from the **Deploy & Run** tab with **Environment** set to **Injected Provider - MetaMask**. Remix shows the token address under **Deployed Contracts**. + + + + + + +## Verify the source + +Verification publishes your source code on [Seiscan](https://testnet.seiscan.io), so that anyone can read the contract. Sourcify verification does not need an API key. Replace `0xYourTokenAddress` with the deployed address: + + + +```bash Foundry +forge verify-contract \ + --verifier sourcify \ + --chain-id 1328 \ + 0xYourTokenAddress \ + src/DemoToken.sol:DemoToken +``` + +```bash Hardhat +npx hardhat verify sourcify --network seitestnet 0xYourTokenAddress +``` + + + +For Remix and the other verification options, see [Verify contracts](/evm/evm-verify-contracts). + +## Read the token back + +To confirm the deployment, read the token's metadata and your balance. First, install a client library: + + + +```bash viem +npm install viem +``` + +```bash ethers +npm install ethers +``` + +```bash web3.py +pip install web3 +``` + + + +The TypeScript examples use top-level `await`. Save each one as a `.mts` file, such as `script.mts`. Run it with `npx tsx script.mts` on Node.js 18 or later. In a new npm project, `tsx` compiles a plain `.ts` file as CommonJS, where top-level `await` does not work. + +Replace `0xYourTokenAddress` with the token address and `0xYourAddress` with the deployer address: + + + +```ts viem +import { createPublicClient, http, parseAbi, formatUnits } from 'viem'; +import { seiTestnet } from 'viem/chains'; + +const client = createPublicClient({ chain: seiTestnet, transport: http() }); + +const token = '0xYourTokenAddress'; +const owner = '0xYourAddress'; +const 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)', +]); + +const [name, symbol, decimals, totalSupply, balance] = await Promise.all([ + client.readContract({ address: token, abi, functionName: 'name' }), + client.readContract({ address: token, abi, functionName: 'symbol' }), + client.readContract({ address: token, abi, functionName: 'decimals' }), + client.readContract({ address: token, abi, functionName: 'totalSupply' }), + client.readContract({ address: token, abi, functionName: 'balanceOf', args: [owner] }), +]); + +console.log(`${name} (${symbol})`); +console.log('Total supply:', formatUnits(totalSupply, decimals)); +console.log('Your balance:', formatUnits(balance, decimals)); +``` + +```ts ethers +import { ethers } from 'ethers'; + +const provider = new ethers.JsonRpcProvider('https://evm-rpc-testnet.sei-apis.com'); + +const token = new ethers.Contract( + '0xYourTokenAddress', + [ + '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)', + ], + provider +); +const owner = '0xYourAddress'; + +const [name, symbol, decimals, totalSupply, balance] = await Promise.all([ + token.name(), + token.symbol(), + token.decimals(), + token.totalSupply(), + token.balanceOf(owner), +]); + +console.log(`${name} (${symbol})`); +console.log('Total supply:', ethers.formatUnits(totalSupply, decimals)); +console.log('Your balance:', ethers.formatUnits(balance, decimals)); +``` + +```python web3.py +from decimal import Decimal +from web3 import Web3 + +w3 = Web3(Web3.HTTPProvider("https://evm-rpc-testnet.sei-apis.com")) + +def view(name, inputs, output): + return {"type": "function", "name": name, "stateMutability": "view", + "inputs": [{"name": n, "type": t} for n, t in inputs], + "outputs": [{"name": "", "type": output}]} + +abi = [ + view("name", [], "string"), + view("symbol", [], "string"), + view("decimals", [], "uint8"), + view("totalSupply", [], "uint256"), + view("balanceOf", [("owner", "address")], "uint256"), +] +token = w3.eth.contract(address=Web3.to_checksum_address("0xYourTokenAddress"), abi=abi) +owner = Web3.to_checksum_address("0xYourAddress") + +scale = Decimal(10) ** token.functions.decimals().call() +print(f"{token.functions.name().call()} ({token.functions.symbol().call()})") +print("Total supply:", Decimal(token.functions.totalSupply().call()) / scale) +print("Your balance:", Decimal(token.functions.balanceOf(owner).call()) / scale) +``` + + + +**You are done when you see:** + +```text +Demo Token (DEMO) +Total supply: 1000000 +Your balance: 1000000 +``` + +ethers prints `1000000.0`, because `formatUnits` in ethers always includes a decimal place. + +## Related recipes + +- [ERC-20 interaction](/evm/evm-parity/examples/erc20): transfers, approvals, and allowances +- [Listen to events](/evm/cookbook/listen-to-events): stream the token's `Transfer` events +- [Deploy and verify](/evm/evm-parity/examples/deploy-verify): deploy any contract with viem, ethers, Foundry, or Hardhat diff --git a/evm/cookbook/index.mdx b/evm/cookbook/index.mdx new file mode 100644 index 0000000..66aa11e --- /dev/null +++ b/evm/cookbook/index.mdx @@ -0,0 +1,100 @@ +--- +title: 'Cookbook' +description: 'Short recipes for common tasks on Sei: read balances, send SEI, deploy and use tokens, listen to events, sponsor gas, and read price feeds.' +keywords: ['sei evm', 'cookbook', 'recipes', 'code examples', 'viem', 'ethers', 'web3.py'] +--- + +Each recipe does one task from start to finish. Most recipes show the same code in viem, ethers, and web3.py. When you pick a library in one code block, every code block on the page switches to that library. + +## Networks + +| Network | EVM chain ID | HTTP RPC | WebSocket | +| --- | --- | --- | --- | +| Sei Mainnet | `1329` (`0x531`) | `https://evm-rpc.sei-apis.com` | `wss://evm-ws.sei-apis.com` | +| Sei Testnet | `1328` (`0x530`) | `https://evm-rpc-testnet.sei-apis.com` | `wss://evm-ws-testnet.sei-apis.com` | + +Recipes that send transactions use Sei Testnet. Get testnet SEI from the [faucet](/learn/faucet). The public endpoints have rate limits. For production traffic, use a [dedicated RPC provider](/learn/rpc-providers). + +## Client setup + + + + Read chain data and send transactions from Node.js with viem. + + + Read chain data and send transactions with ethers. + + + Connect wallets, read chain state, and send transactions in a React dApp. + + + Read chain data and sign transactions with web3.py. + + + +## Accounts and transactions + + + + Read the SEI and ERC-20 balances of any address. + + + Send SEI to another address and confirm the receipt. + + + Send a transaction, wait for the receipt, and decode its logs. + + + Decode reverts, handle wallet rejections, and recover from RPC errors. + + + Send from a smart account while a paymaster pays the gas. + + + +## Tokens + + + + Deploy an OpenZeppelin token with Foundry, Hardhat, or Remix. + + + Read token data, transfer tokens, and manage approvals. + + + Read NFT ownership and transfer NFTs. + + + Read balances, transfer tokens, and approve operators. + + + +## Contracts + + + + Deploy any contract and publish its source on Seiscan. + + + Stream new events over WebSocket and fetch past events. + + + Batch many contract reads into one request. + + + Read the SEI/USD price from an oracle and check that it is fresh. + + + +## Sei features + + + + Call staking, governance, and other native features from the EVM. + + + Use CosmWasm tokens through standard ERC interfaces. + + + +To ask for a new recipe, open an issue in the [sei-docs repository](https://github.com/sei-protocol/sei-docs/issues). diff --git a/evm/cookbook/listen-to-events.mdx b/evm/cookbook/listen-to-events.mdx new file mode 100644 index 0000000..03ca186 --- /dev/null +++ b/evm/cookbook/listen-to-events.mdx @@ -0,0 +1,211 @@ +--- +title: 'Listen to events' +description: 'Stream contract events as they happen over a WebSocket subscription, and fetch past events with eth_getLogs, in viem, ethers, or web3.py.' +keywords: ['sei evm', 'events', 'logs', 'eth_subscribe', 'eth_getLogs', 'websocket', 'viem', 'ethers', 'web3.py', 'cookbook'] +--- + +Contracts emit events to record what happened, such as a token transfer. This recipe watches `Transfer` events from USDC on Sei Mainnet as they happen. Then it fetches past `Transfer` events. USDC has many transfers, so new events usually arrive within a minute. + +## Pick the right endpoint + +Use a different public endpoint for each task: + +| Task | Endpoint | Sei Mainnet | Sei Testnet | +| --- | --- | --- | --- | +| Live events (`eth_subscribe`) | WebSocket | `wss://evm-ws.sei-apis.com` | `wss://evm-ws-testnet.sei-apis.com` | +| Past events (`eth_getLogs`) | HTTP | `https://evm-rpc.sei-apis.com` | `https://evm-rpc-testnet.sei-apis.com` | + +Watch live events over WebSocket. Watchers that poll the public HTTP endpoints for new events can miss events or report them twice. Fetch past events over HTTP, because the public WebSocket endpoints do not serve `eth_getLogs`. + +## Install + + + +```bash viem +npm install viem +``` + +```bash ethers +npm install ethers +``` + +```bash web3.py +pip install web3 +``` + + + +The TypeScript examples use top-level `await`. Save each one as a `.mts` file, such as `script.mts`. Run it with `npx tsx script.mts` on Node.js 18 or later. In a new npm project, `tsx` compiles a plain `.ts` file as CommonJS, where top-level `await` does not work. + +## Watch new events + +Each script prints a line for every USDC transfer. The script runs until you stop it with Ctrl+C. + + + +```ts viem +import { createPublicClient, webSocket, parseAbiItem, formatUnits } from 'viem'; +import { sei } from 'viem/chains'; + +const client = createPublicClient({ + chain: sei, + transport: webSocket('wss://evm-ws.sei-apis.com'), +}); + +const USDC = '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392'; + +const unwatch = client.watchEvent({ + address: USDC, + event: parseAbiItem('event Transfer(address indexed from, address indexed to, uint256 value)'), + strict: true, + onLogs: (logs) => { + for (const { args, blockNumber } of logs) { + console.log(`${args.from} -> ${args.to}: ${formatUnits(args.value, 6)} USDC (block ${blockNumber})`); + } + }, + onError: (error) => console.error(error.message), +}); +// Call unwatch() to stop the subscription +``` + +```ts ethers +import { ethers } from 'ethers'; + +const provider = new ethers.WebSocketProvider('wss://evm-ws.sei-apis.com'); + +const USDC = '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392'; +const usdc = new ethers.Contract( + USDC, + ['event Transfer(address indexed from, address indexed to, uint256 value)'], + provider +); + +await usdc.on('Transfer', (from, to, value, event) => { + console.log(`${from} -> ${to}: ${ethers.formatUnits(value, 6)} USDC (block ${event.log.blockNumber})`); +}); +// To stop: await usdc.removeAllListeners(); await provider.destroy(); +``` + +```python web3.py +import asyncio +from decimal import Decimal +from web3 import AsyncWeb3, WebSocketProvider +from web3.utils.subscriptions import LogsSubscription + +USDC = "0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392" +TRANSFER_ABI = [{ + "type": "event", "name": "Transfer", "anonymous": False, + "inputs": [ + {"name": "from", "type": "address", "indexed": True}, + {"name": "to", "type": "address", "indexed": True}, + {"name": "value", "type": "uint256", "indexed": False}, + ], +}] + +async def main(): + async with AsyncWeb3(WebSocketProvider("wss://evm-ws.sei-apis.com")) as w3: + transfer = w3.eth.contract(address=USDC, abi=TRANSFER_ABI).events.Transfer() + + async def on_log(context): + event = transfer.process_log(context.result) + args = event["args"] + amount = Decimal(args["value"]) / 10**6 + print(f"{args['from']} -> {args['to']}: {amount} USDC (block {event['blockNumber']})") + + await w3.subscription_manager.subscribe( + LogsSubscription(address=USDC, topics=[transfer.topic], handler=on_log) + ) + await w3.subscription_manager.handle_subscriptions() + +asyncio.run(main()) +``` + + + +**You are done when you see:** + +```text +0x3D20…CB04 -> 0x8397…62C4: 27.847524 USDC (block 235331015) +``` + +The scripts print the full sender and recipient addresses. The example output above shortens them. Sei has instant finality, so each event is final when it arrives. You do not need to wait for confirmations or handle reorganizations. + +The web3.py example uses the subscription manager, which needs web3.py 7.7 or later. + +## Fetch past events + +`eth_getLogs` returns the events in a block range. On the public endpoints, one request can cover at most 2,000 blocks. A larger range fails with `block range too large`. This example fetches the transfers from the latest 1,000 blocks. + + + +```ts viem +import { createPublicClient, http, parseAbiItem } from 'viem'; +import { sei } from 'viem/chains'; + +const client = createPublicClient({ chain: sei, transport: http() }); + +const USDC = '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392'; +const latest = await client.getBlockNumber(); + +const logs = await client.getLogs({ + address: USDC, + event: parseAbiItem('event Transfer(address indexed from, address indexed to, uint256 value)'), + fromBlock: latest - 999n, + toBlock: latest, +}); +console.log(`${logs.length} transfers in blocks ${latest - 999n} to ${latest}`); +``` + +```ts ethers +import { ethers } from 'ethers'; + +const provider = new ethers.JsonRpcProvider('https://evm-rpc.sei-apis.com'); + +const USDC = '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392'; +const usdc = new ethers.Contract( + USDC, + ['event Transfer(address indexed from, address indexed to, uint256 value)'], + provider +); +const latest = await provider.getBlockNumber(); + +const logs = await usdc.queryFilter(usdc.filters.Transfer(), latest - 999, latest); +console.log(`${logs.length} transfers in blocks ${latest - 999} to ${latest}`); +``` + +```python web3.py +from web3 import Web3 + +w3 = Web3(Web3.HTTPProvider("https://evm-rpc.sei-apis.com")) + +USDC = "0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392" +TRANSFER_ABI = [{ + "type": "event", "name": "Transfer", "anonymous": False, + "inputs": [ + {"name": "from", "type": "address", "indexed": True}, + {"name": "to", "type": "address", "indexed": True}, + {"name": "value", "type": "uint256", "indexed": False}, + ], +}] +transfer = w3.eth.contract(address=USDC, abi=TRANSFER_ABI).events.Transfer() +latest = w3.eth.block_number + +logs = transfer.get_logs(from_block=latest - 999, to_block=latest) +print(f"{len(logs)} transfers in blocks {latest - 999} to {latest}") +``` + + + +**You are done when you see:** + +```text +319 transfers in blocks 235329562 to 235330561 +``` + +To cover a longer range, split it into windows of 2,000 blocks or fewer. Request the windows one after another. For a full history of a contract, use an [indexer](/learn/indexers) instead of `eth_getLogs`. + +## Related recipes + +- [WebSocket connections](/evm/evm-parity/websocket): new blocks and the `newHeads` subscription +- [Transaction lifecycle](/evm/evm-parity/examples/transaction-lifecycle): decode the events from one transaction's receipt +- [Deploy an ERC-20](/evm/cookbook/deploy-an-erc20): deploy a token that emits its own `Transfer` events diff --git a/evm/cookbook/read-a-balance.mdx b/evm/cookbook/read-a-balance.mdx new file mode 100644 index 0000000..9957d94 --- /dev/null +++ b/evm/cookbook/read-a-balance.mdx @@ -0,0 +1,174 @@ +--- +title: 'Read a balance' +description: 'Read the SEI balance of any address, and its balance of an ERC-20 token such as USDC, with viem, ethers, or web3.py.' +keywords: ['sei evm', 'balance', 'eth_getBalance', 'balanceOf', 'erc-20', 'viem', 'ethers', 'web3.py', 'cookbook'] +--- + +import { RunSnippet } from '/snippets/run-snippet.jsx'; + +A balance read is a free, read-only call. You do not need a private key, a wallet, or SEI for gas. + +## Try it live + +This call runs from your browser against Sei Mainnet. It returns the balance in wei, the smallest unit of SEI. One SEI is 1018 wei. + + + +## Install + + + +```bash viem +npm install viem +``` + +```bash ethers +npm install ethers +``` + +```bash web3.py +pip install web3 +``` + + + +The TypeScript examples use top-level `await`. Save each one as a `.mts` file, such as `script.mts`. Run it with `npx tsx script.mts` on Node.js 18 or later. In a new npm project, `tsx` compiles a plain `.ts` file as CommonJS, where top-level `await` does not work. + +## Read a native balance + +The native balance is the amount of SEI that an address holds. + + + +```ts viem +import { createPublicClient, http, formatEther } from 'viem'; +import { sei } from 'viem/chains'; + +const client = createPublicClient({ chain: sei, transport: http() }); + +// Replace with any Sei EVM address +const address = '0x0000000000000000000000000000000000000000'; + +const wei = await client.getBalance({ address }); +console.log(`${formatEther(wei)} SEI`); +``` + +```ts ethers +import { ethers } from 'ethers'; + +const provider = new ethers.JsonRpcProvider('https://evm-rpc.sei-apis.com'); + +// Replace with any Sei EVM address +const address = '0x0000000000000000000000000000000000000000'; + +const wei = await provider.getBalance(address); +console.log(`${ethers.formatEther(wei)} SEI`); +``` + +```python web3.py +from web3 import Web3 + +w3 = Web3(Web3.HTTPProvider("https://evm-rpc.sei-apis.com")) + +# Replace with any Sei EVM address +address = Web3.to_checksum_address("0x0000000000000000000000000000000000000000") + +wei = w3.eth.get_balance(address) +print(f"{w3.from_wei(wei, 'ether')} SEI") +``` + + + +**You are done when you see:** + +```text +1384.115892177302953913 SEI +``` + +The number is illustrative. Each library returns the balance in wei: a `bigint` in viem and ethers, and an `int` in web3.py. The `formatEther` and `from_wei(…, 'ether')` helpers divide by 1018. They carry Ethereum names, but they apply to SEI because SEI also uses 18 decimals on the EVM. + +EVM RPC methods take `0x` addresses. If you have a `sei1…` address, see [Accounts](/learn/accounts) to find the EVM address of the same account. web3.py also requires the checksummed form of an address, which `Web3.to_checksum_address` produces from a lowercase one. + +## Read an ERC-20 balance + +The token contract stores the token balances, so you call its `balanceOf` function. Then you scale the result by the token's `decimals`. This example reads USDC on Sei Mainnet at `0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392`, which uses 6 decimals. + + + +```ts viem +import { createPublicClient, http, parseAbi, formatUnits } from 'viem'; +import { sei } from 'viem/chains'; + +const client = createPublicClient({ chain: sei, transport: http() }); + +const USDC = '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392'; +const address = '0x0000000000000000000000000000000000000000'; +const abi = parseAbi([ + 'function balanceOf(address owner) view returns (uint256)', + 'function decimals() view returns (uint8)', +]); + +const [balance, decimals] = await Promise.all([ + client.readContract({ address: USDC, abi, functionName: 'balanceOf', args: [address] }), + client.readContract({ address: USDC, abi, functionName: 'decimals' }), +]); +console.log(`${formatUnits(balance, decimals)} USDC`); +``` + +```ts ethers +import { ethers } from 'ethers'; + +const provider = new ethers.JsonRpcProvider('https://evm-rpc.sei-apis.com'); + +const USDC = '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392'; +const address = '0x0000000000000000000000000000000000000000'; +const usdc = new ethers.Contract( + USDC, + ['function balanceOf(address owner) view returns (uint256)', 'function decimals() view returns (uint8)'], + provider +); + +const [balance, decimals] = await Promise.all([usdc.balanceOf(address), usdc.decimals()]); +console.log(`${ethers.formatUnits(balance, decimals)} USDC`); +``` + +```python web3.py +from decimal import Decimal +from web3 import Web3 + +w3 = Web3(Web3.HTTPProvider("https://evm-rpc.sei-apis.com")) + +USDC = "0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392" +address = Web3.to_checksum_address("0x0000000000000000000000000000000000000000") +abi = [ + {"type": "function", "name": "balanceOf", "stateMutability": "view", + "inputs": [{"name": "owner", "type": "address"}], "outputs": [{"name": "", "type": "uint256"}]}, + {"type": "function", "name": "decimals", "stateMutability": "view", + "inputs": [], "outputs": [{"name": "", "type": "uint8"}]}, +] +usdc = w3.eth.contract(address=USDC, abi=abi) + +balance = usdc.functions.balanceOf(address).call() +decimals = usdc.functions.decimals().call() +print(f"{Decimal(balance) / 10**decimals} USDC") +``` + + + +The zero address holds no USDC, so the example prints `0 USDC`. ethers prints `0.0 USDC`, because `formatUnits` in ethers always includes a decimal place. To see a real balance, replace `address` with your own address. + +## Read many balances at once + +Each read above is a separate RPC request. To read dozens of balances in one request, batch them with [Multicall3](/evm/evm-parity/examples/multicall). Multicall3 is deployed at `0xcA11bde05977b3631167028862bE2a173976CA11` on Sei Mainnet and Sei Testnet. + +## Related recipes + +- [Send SEI](/evm/cookbook/send-sei): move SEI to another address +- [ERC-20 interaction](/evm/evm-parity/examples/erc20): token metadata, transfers, and approvals +- [Multicall](/evm/evm-parity/examples/multicall): batch many reads into one request diff --git a/evm/cookbook/read-a-price-feed.mdx b/evm/cookbook/read-a-price-feed.mdx new file mode 100644 index 0000000..a6fba6e --- /dev/null +++ b/evm/cookbook/read-a-price-feed.mdx @@ -0,0 +1,135 @@ +--- +title: 'Read a price feed' +description: 'Read the latest SEI/USD price from the API3 data feed on Sei Mainnet with viem, ethers, or web3.py. Check that the price is fresh before you use it.' +keywords: ['sei evm', 'oracle', 'price feed', 'api3', 'sei/usd', 'viem', 'ethers', 'web3.py', 'cookbook'] +--- + +import { RunSnippet } from '/snippets/run-snippet.jsx'; + +The native [Oracle precompile](/evm/precompiles/oracle) is retired, so price data on Sei comes from third-party oracles. This recipe reads [API3](/evm/oracles/api3)'s SEI/USD feed. API3 pushes new prices on-chain, so a contract already stores the latest price. You read it with a free view call. You do not need a key, an API token, or SEI. + +| Feed | Network | Proxy address | Decimals | +| --- | --- | --- | --- | +| SEI/USD | Sei Mainnet | `0x09c6e594DE2EB633902f00B87A43b27F80a31a60` | 18 | + +Read the feed on Sei Mainnet, because a data feed on a testnet can be stale. Always check the timestamp, as the examples below do. + +## Try it live + +This call runs the proxy's `read()` function (selector `0x57de26a4`) from your browser. The response holds two 32-byte values. The first is the price with 18 decimals. The second is the Unix time of the last update. + + + +## Install + + + +```bash viem +npm install viem +``` + +```bash ethers +npm install ethers +``` + +```bash web3.py +pip install web3 +``` + + + +The TypeScript examples use top-level `await`. Save each one as a `.mts` file, such as `script.mts`. Run it with `npx tsx script.mts` on Node.js 18 or later. In a new npm project, `tsx` compiles a plain `.ts` file as CommonJS, where top-level `await` does not work. + +## Read the price + + + +```ts viem +import { createPublicClient, http, parseAbi, formatUnits } from 'viem'; +import { sei } from 'viem/chains'; + +const client = createPublicClient({ chain: sei, transport: http() }); + +const SEI_USD = '0x09c6e594DE2EB633902f00B87A43b27F80a31a60'; +const abi = parseAbi(['function read() view returns (int224 value, uint32 timestamp)']); + +const [value, timestamp] = await client.readContract({ address: SEI_USD, abi, functionName: 'read' }); + +const ageSeconds = Math.floor(Date.now() / 1000) - timestamp; +if (value <= 0n || ageSeconds > 24 * 60 * 60) { + throw new Error(`Unusable price: ${value} (updated ${ageSeconds} seconds ago)`); +} +console.log(`SEI/USD: ${formatUnits(value, 18)} (updated ${ageSeconds} seconds ago)`); +``` + +```ts ethers +import { ethers } from 'ethers'; + +const provider = new ethers.JsonRpcProvider('https://evm-rpc.sei-apis.com'); + +const SEI_USD = '0x09c6e594DE2EB633902f00B87A43b27F80a31a60'; +const feed = new ethers.Contract( + SEI_USD, + ['function read() view returns (int224 value, uint32 timestamp)'], + provider +); + +const [value, timestamp] = await feed.read(); + +const ageSeconds = Math.floor(Date.now() / 1000) - Number(timestamp); +if (value <= 0n || ageSeconds > 24 * 60 * 60) { + throw new Error(`Unusable price: ${value} (updated ${ageSeconds} seconds ago)`); +} +console.log(`SEI/USD: ${ethers.formatUnits(value, 18)} (updated ${ageSeconds} seconds ago)`); +``` + +```python web3.py +import time +from decimal import Decimal +from web3 import Web3 + +w3 = Web3(Web3.HTTPProvider("https://evm-rpc.sei-apis.com")) + +SEI_USD = "0x09c6e594DE2EB633902f00B87A43b27F80a31a60" +abi = [{ + "type": "function", "name": "read", "stateMutability": "view", "inputs": [], + "outputs": [{"name": "value", "type": "int224"}, {"name": "timestamp", "type": "uint32"}], +}] +feed = w3.eth.contract(address=SEI_USD, abi=abi) + +value, timestamp = feed.functions.read().call() + +age_seconds = int(time.time()) - timestamp +if value <= 0 or age_seconds > 24 * 60 * 60: + raise ValueError(f"Unusable price: {value} (updated {age_seconds} seconds ago)") +print(f"SEI/USD: {Decimal(value) / 10**18} (updated {age_seconds} seconds ago)") +``` + + + +**You are done when you see:** + +```text +SEI/USD: 0.0721 (updated 181 seconds ago) +``` + +The price is illustrative. API3 updates a feed when the price moves past the feed's deviation threshold. When the price is flat, API3 updates the feed at least once every 24 hours. The examples therefore reject a value that is not positive or that is more than 24 hours old. See API3's [update parameters](https://docs.api3.org/dapps/integration/#update-parameters) for details. + +## Use the price in a contract + +A contract reads the same proxy through API3's `IApi3ReaderProxy` interface and should apply the same checks. The [API3 guide](/evm/oracles/api3) has a complete consumer contract. + +## Other oracles on Sei + +[Chainlink Data Streams](/evm/oracles/chainlink), [Pyth](/evm/oracles/pyth-network), and [RedStone](/evm/oracles/redstone) are pull oracles. Your transaction carries a signed, current price with it. A view call on its own returns either nothing or the last price that someone else submitted, which can be old. To use one of them, follow its guide. + +## Related recipes + +- [Read a balance](/evm/cookbook/read-a-balance): other read-only contract calls +- [Multicall](/evm/evm-parity/examples/multicall): read a price feed and other data in one request diff --git a/evm/cookbook/send-sei.mdx b/evm/cookbook/send-sei.mdx new file mode 100644 index 0000000..a0a02cb --- /dev/null +++ b/evm/cookbook/send-sei.mdx @@ -0,0 +1,132 @@ +--- +title: 'Send SEI' +description: 'Send SEI from one account to another on Sei Testnet with viem, ethers, or web3.py, then confirm the transfer from its receipt.' +keywords: ['sei evm', 'send sei', 'transfer', 'sendTransaction', 'receipt', 'viem', 'ethers', 'web3.py', 'cookbook'] +--- + +This recipe sends 0.01 SEI on Sei Testnet and waits for the receipt. Sei has [instant finality](/evm/evm-parity/finality), so the transfer is final as soon as the receipt arrives. + +## Before you start + +You need a Sei Testnet account that holds some SEI. Get testnet SEI from the [faucet](/learn/faucet). Then put the account's private key in the `PRIVATE_KEY` environment variable: + +```bash +export PRIVATE_KEY=0xYourPrivateKey +``` + +Use a throwaway key that holds only testnet funds. Never commit a private key or paste it into your code. + +## Install + + + +```bash viem +npm install viem +``` + +```bash ethers +npm install ethers +``` + +```bash web3.py +pip install web3 +``` + + + +The TypeScript examples use top-level `await`. Save each one as a `.mts` file, such as `script.mts`. Run it with `npx tsx script.mts` on Node.js 18 or later. In a new npm project, `tsx` compiles a plain `.ts` file as CommonJS, where top-level `await` does not work. + +## Send the transaction + +Replace `0xRecipientAddress` with the `0x` address that receives the SEI. + + + +```ts viem +import { createPublicClient, createWalletClient, http, parseEther } from 'viem'; +import { privateKeyToAccount } from 'viem/accounts'; +import { seiTestnet } from 'viem/chains'; + +const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); +const publicClient = createPublicClient({ chain: seiTestnet, transport: http() }); +const walletClient = createWalletClient({ account, chain: seiTestnet, transport: http() }); + +const hash = await walletClient.sendTransaction({ + to: '0xRecipientAddress', + value: parseEther('0.01'), +}); +console.log('Transaction hash:', hash); + +const receipt = await publicClient.waitForTransactionReceipt({ hash }); +console.log('Status:', receipt.status); +console.log('Block:', receipt.blockNumber); +``` + +```ts ethers +import { ethers } from 'ethers'; + +const provider = new ethers.JsonRpcProvider('https://evm-rpc-testnet.sei-apis.com'); +const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider); + +const tx = await wallet.sendTransaction({ + to: '0xRecipientAddress', + value: ethers.parseEther('0.01'), +}); +console.log('Transaction hash:', tx.hash); + +const receipt = await tx.wait(); +console.log('Status:', receipt?.status); +console.log('Block:', receipt?.blockNumber); +``` + +```python web3.py +import os +from web3 import Web3 +from web3.middleware import SignAndSendRawMiddlewareBuilder + +w3 = Web3(Web3.HTTPProvider("https://evm-rpc-testnet.sei-apis.com")) +account = w3.eth.account.from_key(os.environ["PRIVATE_KEY"]) + +# Sign locally: public RPC endpoints don't hold keys +w3.middleware_onion.inject(SignAndSendRawMiddlewareBuilder.build(account), layer=0) + +tx_hash = w3.eth.send_transaction({ + "from": account.address, + "to": Web3.to_checksum_address("0xRecipientAddress"), + "value": w3.to_wei(0.01, "ether"), +}) +print("Transaction hash:", w3.to_hex(tx_hash)) + +receipt = w3.eth.wait_for_transaction_receipt(tx_hash) +print("Status:", receipt.status) +print("Block:", receipt.blockNumber) +``` + + + +**You are done when you see:** + +```text +Transaction hash: 0x9a9531211a1d7257546558b69b9ea092a3937c6034b665f6440fbf9ef117b712 +Status: success +Block: 274527901n +``` + +viem prints the status as `success` and the block number as a `bigint`, with an `n` at the end. ethers and web3.py print the status as `1`. A status of `reverted` or `0` means that the transaction was included but failed. To find the cause, see [Error handling](/evm/evm-parity/examples/error-handling). + +To see the transfer in the block explorer, open `https://testnet.seiscan.io/tx/` followed by the transaction hash. + +## How gas is set + +All three libraries set the nonce, the gas limit, and the fees for you: + +- **Gas limit.** The libraries call `eth_estimateGas`. A transfer to a wallet address uses 21,000 gas. A transfer to a contract can use more, because the contract runs code when it receives SEI. +- **Fees.** The libraries send an EIP-1559 (type 2) transaction and read the current base fee from the latest block. Sei does not burn the base fee. Validators receive the whole fee. See [Gas and fees](/evm/evm-parity/gas-and-fees). + +To send on Sei Mainnet, use the `sei` chain in viem, or the `https://evm-rpc.sei-apis.com` endpoint in ethers and web3.py. Transfers on Sei Mainnet spend real SEI. + +## Related recipes + +- [Read a balance](/evm/cookbook/read-a-balance): check the result of the transfer +- [Transaction lifecycle](/evm/evm-parity/examples/transaction-lifecycle): receipts, logs, and past transactions +- [Sponsor gas with Pimlico](/evm/cookbook/sponsor-gas-with-pimlico): send from an account that holds no SEI diff --git a/evm/cookbook/sponsor-gas-with-pimlico.mdx b/evm/cookbook/sponsor-gas-with-pimlico.mdx new file mode 100644 index 0000000..c356c6f --- /dev/null +++ b/evm/cookbook/sponsor-gas-with-pimlico.mdx @@ -0,0 +1,99 @@ +--- +title: 'Sponsor gas with Pimlico' +description: 'Send a transaction from an ERC-4337 smart account on Sei Testnet while a Pimlico paymaster pays the gas, with permissionless.js and viem.' +keywords: ['sei evm', 'account abstraction', 'erc-4337', 'paymaster', 'gasless', 'sponsored transaction', 'pimlico', 'permissionless', 'cookbook'] +--- + +With gas sponsorship, your users do not need SEI to use your dApp. A paymaster pays the gas instead. This recipe uses [Pimlico](https://pimlico.io)'s bundler and paymaster on Sei Testnet. It creates an ERC-4337 smart account and sends its first transaction without any SEI in the account. + +## How it works + +An ERC-4337 smart account is a contract wallet. It sends a *user operation* instead of a normal transaction: + +1. Your code builds a user operation and signs it with the owner key of the smart account. +2. Pimlico's paymaster agrees to pay for the operation. +3. Pimlico's bundler submits the operation to the EntryPoint contract on Sei. +4. The first operation also deploys the smart account contract. + +## Before you start + +- A Pimlico API key. Create one in the [Pimlico dashboard](https://dashboard.pimlico.io). +- Node.js 18 or later. + +Put the API key and an owner key in environment variables. The owner key can be any new private key, such as one from `generatePrivateKey()` in `viem/accounts`. The account does not need SEI: + +```bash +export PIMLICO_API_KEY=pim_xxxxxxxxxxxxxxxxxxxxxx +export PRIVATE_KEY=0xYourPrivateKey +``` + +## Install + +```bash +npm install viem permissionless +``` + +If npm stops with an `ERESOLVE` error about the `ox` package, run `npm install viem permissionless --legacy-peer-deps`. permissionless.js declares an optional peer version of `ox` that can differ from the version that viem installs. + +## Send a sponsored transaction + +```ts sponsored.mts +import { createPublicClient, http } from 'viem'; +import { privateKeyToAccount } from 'viem/accounts'; +import { seiTestnet } from 'viem/chains'; +import { createSmartAccountClient } from 'permissionless'; +import { toSimpleSmartAccount } from 'permissionless/accounts'; +import { createPimlicoClient } from 'permissionless/clients/pimlico'; + +const pimlicoUrl = `https://api.pimlico.io/v2/${seiTestnet.id}/rpc?apikey=${process.env.PIMLICO_API_KEY}`; + +const publicClient = createPublicClient({ chain: seiTestnet, transport: http() }); +const pimlicoClient = createPimlicoClient({ chain: seiTestnet, transport: http(pimlicoUrl) }); + +// A smart account that a single private key controls +const account = await toSimpleSmartAccount({ + client: publicClient, + owner: privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`), +}); +console.log('Smart account:', account.address); + +const smartAccountClient = createSmartAccountClient({ + account, + chain: seiTestnet, + bundlerTransport: http(pimlicoUrl), + paymaster: pimlicoClient, + userOperation: { + estimateFeesPerGas: async () => (await pimlicoClient.getUserOperationGasPrice()).fast, + }, +}); + +const hash = await smartAccountClient.sendTransaction({ + to: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045', + value: 0n, + data: '0x1234', +}); +console.log('Transaction hash:', hash); +``` + +Run it with `npx tsx sponsored.mts`. Keep the `.mts` extension. The script uses top-level `await`, which works only when Node.js loads the file as an ES module. + +**You are done when you see:** + +```text +Smart account: 0x3308a63562c135E899E0D03F82dcbC081E40aA50 +Transaction hash: 0x61d675675af6dc6e17fa05463f3e7d81c9710d506689eef425cbc180abf346bf +``` + +The smart account address depends on the owner key, so yours differs. The account holds no SEI, but the transaction still succeeds because the paymaster paid for it. `sendTransaction` returns after the operation is included in a block. To see the transaction that the bundler submitted, look up the hash on [Seiscan](https://testnet.seiscan.io). + +## Notes + +- **EntryPoint version.** `toSimpleSmartAccount` uses EntryPoint v0.7 at `0x0000000071727De22E5E9d8BAf0edAc6f37da032` by default. EntryPoint v0.6, v0.7, and v0.8 are deployed on Sei Testnet. +- **Other account types.** permissionless.js also supports Safe, Kernel, and other smart accounts. See Pimlico's [smart account comparison](https://docs.pimlico.io/permissionless/how-to/accounts/comparison). +- **Cost.** Pimlico sponsors testnet operations for free. On Sei Mainnet, Pimlico bills you for the gas that its paymaster pays, so you need a paid plan. See [Pimlico pricing](https://docs.pimlico.io/guides/pricing). To use Sei Mainnet, replace `seiTestnet` with `sei`. +- **Spending limits.** Sponsorship policies in the Pimlico dashboard limit how much you sponsor per transaction, per user, or in total. + +## Related recipes + +- [Send SEI](/evm/cookbook/send-sei): send a normal transaction from a funded account +- [Pimlico integration](/evm/wallet-integrations/pimlico): the full walkthrough, with a step-by-step explanation of each client diff --git a/evm/evm-foundry.mdx b/evm/evm-foundry.mdx index cfe9f21..c286e41 100644 --- a/evm/evm-foundry.mdx +++ b/evm/evm-foundry.mdx @@ -603,15 +603,17 @@ Alternatively, you can deploy directly: ```bash # Deploy Counter to testnet -forge create --rpc-url $SEI_TESTNET_RPC --private-key $PRIVATE_KEY src/Counter.sol:Counter +forge create --rpc-url $SEI_TESTNET_RPC --private-key $PRIVATE_KEY --broadcast src/Counter.sol:Counter # Deploy SeiToken to testnet -forge create --rpc-url $SEI_TESTNET_RPC --private-key $PRIVATE_KEY src/SeiToken.sol:SeiToken --constructor-args $(cast abi-encode "constructor(address)" "YOUR_ADDRESS") +forge create --rpc-url $SEI_TESTNET_RPC --private-key $PRIVATE_KEY --broadcast src/SeiToken.sol:SeiToken --constructor-args YOUR_ADDRESS # Deploy SeiNFT to testnet -forge create --rpc-url $SEI_TESTNET_RPC --private-key $PRIVATE_KEY src/SeiNFT.sol:SeiNFT --constructor-args $(cast abi-encode "constructor(address,string)" "YOUR_ADDRESS" "https://your-metadata-server.com/metadata/") +forge create --rpc-url $SEI_TESTNET_RPC --private-key $PRIVATE_KEY --broadcast src/SeiNFT.sol:SeiNFT --constructor-args YOUR_ADDRESS "https://your-metadata-server.com/metadata/" ``` +Without `--broadcast`, `forge create` only simulates the deployment. Put `--constructor-args` last, because it takes every value after it. Pass the plain values, not ABI-encoded data. + A successful deployment prints output similar to this: ```bash diff --git a/evm/evm-parity/examples/deploy-verify.mdx b/evm/evm-parity/examples/deploy-verify.mdx index b970f77..6334bf8 100644 --- a/evm/evm-parity/examples/deploy-verify.mdx +++ b/evm/evm-parity/examples/deploy-verify.mdx @@ -80,6 +80,7 @@ Foundry works against Sei with the standard `--rpc-url` flag. forge create src/MyContract.sol:MyContract \ --rpc-url https://evm-rpc.sei-apis.com \ --private-key $PRIVATE_KEY \ + --broadcast \ --constructor-args arg1 arg2 ``` @@ -88,9 +89,12 @@ For Sei Testnet: ```bash forge create src/MyContract.sol:MyContract \ --rpc-url https://evm-rpc-testnet.sei-apis.com \ - --private-key $PRIVATE_KEY + --private-key $PRIVATE_KEY \ + --broadcast ``` +Without `--broadcast`, `forge create` only simulates the deployment. Put `--constructor-args` last, because it takes every value after it. + ## Deploying with Hardhat Add the Sei networks to your `hardhat.config.ts` (Hardhat 3). Keep the toolbox in the `plugins` array. It comes with projects that `npx hardhat --init` creates, and it includes Hardhat Ignition for deployments: @@ -164,6 +168,7 @@ Or deploy and verify in one step: forge create src/MyContract.sol:MyContract \ --rpc-url https://evm-rpc.sei-apis.com \ --private-key $PRIVATE_KEY \ + --broadcast \ --verify \ --verifier sourcify \ --chain-id 1329 diff --git a/evm/evm-parity/examples/erc1155.mdx b/evm/evm-parity/examples/erc1155.mdx index 8a397f4..99124e1 100644 --- a/evm/evm-parity/examples/erc1155.mdx +++ b/evm/evm-parity/examples/erc1155.mdx @@ -32,7 +32,7 @@ In Remix, compile the contract on the **Solidity Compiler** tab. Then deploy it ```ts viem -import { createPublicClient, createWalletClient, http, parseAbi } from 'viem'; +import { createPublicClient, createWalletClient, http, parseAbi, webSocket } from 'viem'; import { privateKeyToAccount } from 'viem/accounts'; import { sei } from 'viem/chains'; @@ -227,11 +227,15 @@ const isApproved = await readContract.isApprovedForAll('0xOwnerAddress', '0xOper ## Watching transfer events +Watch new events over the WebSocket endpoint. Watchers that poll the public HTTP endpoint can miss events or report them twice. For details, see [Listen to events](/evm/cookbook/listen-to-events). + ```ts viem +const wsClient = createPublicClient({ chain: sei, transport: webSocket('wss://evm-ws.sei-apis.com') }); + // Watch single transfers -const unwatch = client.watchContractEvent({ +const unwatch = wsClient.watchContractEvent({ address: CONTRACT, abi: ERC1155_ABI, eventName: 'TransferSingle', @@ -243,7 +247,7 @@ const unwatch = client.watchContractEvent({ }); // Watch batch transfers -const unwatchBatch = client.watchContractEvent({ +const unwatchBatch = wsClient.watchContractEvent({ address: CONTRACT, abi: ERC1155_ABI, eventName: 'TransferBatch', @@ -256,13 +260,18 @@ const unwatchBatch = client.watchContractEvent({ ``` ```ts ethers -readContract.on('TransferSingle', (operator, from, to, id, value) => { +const wsProvider = new ethers.WebSocketProvider('wss://evm-ws.sei-apis.com'); +const wsContract = new ethers.Contract(CONTRACT, ERC1155_ABI, wsProvider); + +wsContract.on('TransferSingle', (operator, from, to, id, value) => { console.log(`Token ${id}: ${from} → ${to}, amount: ${value}`); }); -readContract.on('TransferBatch', (operator, from, to, ids, values) => { +wsContract.on('TransferBatch', (operator, from, to, ids, values) => { console.log(`Batch from ${from} → ${to}:`, ids, values); }); + +// To stop: await wsContract.removeAllListeners(); await wsProvider.destroy(); ``` diff --git a/evm/evm-parity/examples/erc20.mdx b/evm/evm-parity/examples/erc20.mdx index 4ce4de7..bfbd916 100644 --- a/evm/evm-parity/examples/erc20.mdx +++ b/evm/evm-parity/examples/erc20.mdx @@ -32,7 +32,7 @@ In Remix, compile the contract on the **Solidity Compiler** tab. Then deploy it ```ts viem -import { createPublicClient, createWalletClient, http, parseAbi } from 'viem'; +import { createPublicClient, createWalletClient, http, parseAbi, webSocket } from 'viem'; import { privateKeyToAccount } from 'viem/accounts'; import { sei } from 'viem/chains'; @@ -195,10 +195,14 @@ const txMax = await writeContract.approve('0xSpenderAddress', ethers.MaxUint256) ## Watching transfer events +Watch new events over the WebSocket endpoint. Watchers that poll the public HTTP endpoint can miss events or report them twice. For details, see [Listen to events](/evm/cookbook/listen-to-events). + ```ts viem -const unwatch = client.watchContractEvent({ +const wsClient = createPublicClient({ chain: sei, transport: webSocket('wss://evm-ws.sei-apis.com') }); + +const unwatch = wsClient.watchContractEvent({ address: TOKEN, abi: ERC20_ABI, eventName: 'Transfer', @@ -209,42 +213,51 @@ const unwatch = client.watchContractEvent({ }, }); -// Stop watching -unwatch(); +// Call unwatch() to stop watching ``` ```ts ethers -readContract.on('Transfer', (from, to, value) => { +const wsProvider = new ethers.WebSocketProvider('wss://evm-ws.sei-apis.com'); +const wsContract = new ethers.Contract(TOKEN, ERC20_ABI, wsProvider); + +wsContract.on('Transfer', (from, to, value) => { console.log(`${from} → ${to}: ${value}`); }); -// Stop watching -readContract.off('Transfer'); +// To stop: await wsContract.off('Transfer'); await wsProvider.destroy(); ``` ## Fetching historical transfers +The public endpoints return logs for at most 2,000 blocks per request. Query a bounded range. This example reads the latest 2,000 blocks: + ```ts viem +const latest = await client.getBlockNumber(); + const logs = await client.getContractEvents({ address: TOKEN, abi: ERC20_ABI, eventName: 'Transfer', - fromBlock: 0n, - toBlock: 'latest', + fromBlock: latest - 1999n, + toBlock: latest, }); ``` ```ts ethers +const latest = await provider.getBlockNumber(); + const filter = readContract.filters.Transfer(); -const logs = await readContract.queryFilter(filter, 0, 'latest'); +const logs = await readContract.queryFilter(filter, latest - 1999, latest); ``` +To read older transfers, request consecutive ranges of 2,000 blocks or fewer, or use an [indexer](/learn/indexers). + ## 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 with a CW20 pointer address. To look up the pointer address, see [Pointer Contracts](/evm/evm-parity/examples/pointer-contracts). diff --git a/evm/evm-parity/examples/erc721.mdx b/evm/evm-parity/examples/erc721.mdx index 1855c8e..50e18c3 100644 --- a/evm/evm-parity/examples/erc721.mdx +++ b/evm/evm-parity/examples/erc721.mdx @@ -32,7 +32,7 @@ In Remix, compile the contract on the **Solidity Compiler** tab. Then deploy it ```ts viem -import { createPublicClient, createWalletClient, http, parseAbi } from 'viem'; +import { createPublicClient, createWalletClient, http, parseAbi, webSocket } from 'viem'; import { privateKeyToAccount } from 'viem/accounts'; import { sei } from 'viem/chains'; @@ -210,10 +210,14 @@ const revokeTx = await writeContract.setApprovalForAll('0xOperatorAddress', fals ## Watching transfer events +Watch new events over the WebSocket endpoint. Watchers that poll the public HTTP endpoint can miss events or report them twice. For details, see [Listen to events](/evm/cookbook/listen-to-events). + ```ts viem -const unwatch = client.watchContractEvent({ +const wsClient = createPublicClient({ chain: sei, transport: webSocket('wss://evm-ws.sei-apis.com') }); + +const unwatch = wsClient.watchContractEvent({ address: NFT, abi: ERC721_ABI, eventName: 'Transfer', @@ -224,17 +228,18 @@ const unwatch = client.watchContractEvent({ }, }); -// Stop watching -unwatch(); +// Call unwatch() to stop watching ``` ```ts ethers -readContract.on('Transfer', (from, to, tokenId) => { +const wsProvider = new ethers.WebSocketProvider('wss://evm-ws.sei-apis.com'); +const wsContract = new ethers.Contract(NFT, ERC721_ABI, wsProvider); + +wsContract.on('Transfer', (from, to, tokenId) => { console.log(`Token ${tokenId}: ${from} → ${to}`); }); -// Stop watching -readContract.off('Transfer'); +// To stop: await wsContract.off('Transfer'); await wsProvider.destroy(); ``` diff --git a/evm/evm-parity/examples/ethers-quickstart.mdx b/evm/evm-parity/examples/ethers-quickstart.mdx index 51ce8cf..98dae01 100644 --- a/evm/evm-parity/examples/ethers-quickstart.mdx +++ b/evm/evm-parity/examples/ethers-quickstart.mdx @@ -16,12 +16,14 @@ This example shows how to use ethers v6 with Sei. The patterns apply whether you npm install ethers ``` -These snippets are TypeScript. To run any of them without a separate build step, use [`tsx`](https://www.npmjs.com/package/tsx) (Node 18+): +These snippets are TypeScript, and they use top-level `await`. To run one without a separate build step, save it as `script.mts`. Then run it with [`tsx`](https://www.npmjs.com/package/tsx) (Node 18+): ```bash -npx tsx script.ts +npx tsx script.mts ``` +The `.mts` extension tells Node.js to treat the file as an ES module. If you use a plain `.ts` file in a new npm project, `tsx` fails with `Top-level await is currently not supported with the "cjs" output format`. + ## Read-only provider ```ts @@ -147,15 +149,25 @@ const gas = await provider.estimateGas({ ## Listening for events +Listen for new events over the WebSocket endpoint. Listeners that poll the public HTTP endpoint can miss events or report them twice. The contract needs the event in its ABI: + ```ts -contract.on('Transfer', (from, to, value) => { +const wsProvider = new ethers.WebSocketProvider('wss://evm-ws.sei-apis.com'); +const token = new ethers.Contract( + '0xTokenAddress', + ['event Transfer(address indexed from, address indexed to, uint256 value)'], + wsProvider +); + +token.on('Transfer', (from, to, value) => { console.log('Transfer:', { from, to, value }); }); -// Stop listening -contract.off('Transfer'); +// To stop: await token.off('Transfer'); await wsProvider.destroy(); ``` +For past events and more examples, see [Listen to events](/evm/cookbook/listen-to-events). + ## Next steps - [Python quickstart (web3.py)](/evm/python-quickstart): the same first steps in Python diff --git a/evm/evm-parity/examples/viem-quickstart.mdx b/evm/evm-parity/examples/viem-quickstart.mdx index 7085f18..08fffaa 100644 --- a/evm/evm-parity/examples/viem-quickstart.mdx +++ b/evm/evm-parity/examples/viem-quickstart.mdx @@ -16,12 +16,14 @@ This example shows how to use viem with Sei outside a React context: in a Node.j npm install viem ``` -These snippets are TypeScript. To run any of them without a separate build step, use [`tsx`](https://www.npmjs.com/package/tsx) (Node 18+): +These snippets are TypeScript, and they use top-level `await`. To run one without a separate build step, save it as `script.mts`. Then run it with [`tsx`](https://www.npmjs.com/package/tsx) (Node 18+): ```bash -npx tsx script.ts +npx tsx script.mts ``` +The `.mts` extension tells Node.js to treat the file as an ES module. If you use a plain `.ts` file in a new npm project, `tsx` fails with `Top-level await is currently not supported with the "cjs" output format`. + ## Public client A public client handles all read-only operations. diff --git a/evm/evm-parity/websocket.mdx b/evm/evm-parity/websocket.mdx index f8f84ea..515cf9f 100644 --- a/evm/evm-parity/websocket.mdx +++ b/evm/evm-parity/websocket.mdx @@ -47,8 +47,7 @@ const unwatch = client.watchBlocks({ }, }); -// Stop watching -unwatch(); +// Call unwatch() to stop watching ``` ```ts ethers @@ -56,8 +55,7 @@ provider.on('block', (blockNumber) => { console.log('New block:', blockNumber); }); -// Stop watching -provider.off('block'); +// To stop: await provider.off('block'); await provider.destroy(); ``` @@ -88,8 +86,7 @@ contract.on('Transfer', (from, to, value, event) => { console.log('Transfer:', { from, to, value }); }); -// Stop watching -contract.off('Transfer'); +// To stop: await contract.off('Transfer'); await provider.destroy(); ``` diff --git a/evm/evm-verify-contracts.mdx b/evm/evm-verify-contracts.mdx index a4f12b2..1d49cd6 100644 --- a/evm/evm-verify-contracts.mdx +++ b/evm/evm-verify-contracts.mdx @@ -68,12 +68,13 @@ You can also deploy and verify in a single step with `forge create`: forge create src/Counter.sol:Counter \ --rpc-url https://evm-rpc-testnet.sei-apis.com \ --private-key $PRIVATE_KEY \ + --broadcast \ --verify \ --verifier sourcify \ --chain-id 1328 ``` -To pass constructor arguments, add them after the contract path. For example: `forge create src/Token.sol:Token --constructor-args "MyToken" "MTK" 18` +To pass constructor arguments, put `--constructor-args` last, because it takes every value after it. For example: `forge create src/Token.sol:Token --broadcast --constructor-args "MyToken" "MTK" 18` ### Verify with Hardhat diff --git a/evm/index.mdx b/evm/index.mdx index d3783af..cf7c742 100644 --- a/evm/index.mdx +++ b/evm/index.mdx @@ -33,6 +33,7 @@ keywords: ["sei evm", "ethereum virtual machine", "web3 development", "blockchai - [EVM general guide](/evm/evm-general) - [Project templates](/evm/templates) + - [Cookbook recipes](/evm/cookbook) diff --git a/evm/migrate-from-solana.mdx b/evm/migrate-from-solana.mdx index f1211d5..7fa8189 100644 --- a/evm/migrate-from-solana.mdx +++ b/evm/migrate-from-solana.mdx @@ -733,6 +733,7 @@ npx hardhat run scripts/deploy.ts --network seiTestnet # Foundry forge create --rpc-url https://evm-rpc-testnet.sei-apis.com \ --private-key $PRIVATE_KEY \ + --broadcast \ src/Counter.sol:Counter ``` diff --git a/evm/oracles/pyth-network.mdx b/evm/oracles/pyth-network.mdx index acf5017..f32a203 100644 --- a/evm/oracles/pyth-network.mdx +++ b/evm/oracles/pyth-network.mdx @@ -5,6 +5,8 @@ keywords: ['pyth network', 'oracle', 'price feeds', 'pull oracle', 'real-time da --- Pyth Network is a first-party oracle that delivers high-fidelity, low-latency financial market data on-chain. Unlike traditional push-based oracles, Pyth uses a "pull" model: applications get signed price updates on demand. This significantly reduces gas costs and still keeps the data fresh. Pyth has more than 400 price feeds across cryptocurrencies, equities, FX, and commodities. It aggregates data directly from more than 120 institutional data publishers, including major exchanges and trading firms. +Send a Pyth API key with every request to Hermes. On Sei Mainnet, use the new Pyth contract address. Pyth made both changes in its Pyth Core upgrade on August 26, 2026. This guide includes them. For background, see Pyth's [upgrade guide](https://docs.pyth.network/price-feeds/core/upgrade/preparing). + ## What you'll be doing in this guide This tutorial shows you how to: @@ -26,6 +28,7 @@ Before you start this tutorial, make sure that you have: - **JavaScript and Node.js**: To fetch price data off-chain with the Pyth EVM SDK - **Development environment**: Remix IDE, Hardhat, Foundry, or a similar Solidity development setup - **Sei network access**: An RPC endpoint and familiarity with the Sei EVM environment +- **Pyth API key**: Hermes rejects requests without one. Pyth's [upgrade guide](https://docs.pyth.network/price-feeds/core/upgrade/preparing) explains how to get a key. ### Required dependencies @@ -58,9 +61,9 @@ Make sure that your development environment is configured for Sei: Pyth's pull-based oracle model consists of: -1. **Publishers**: 120+ institutional data providers that sign and submit price data to Pythnet -2. **Pythnet**: A dedicated blockchain that aggregates publisher data with a stake-weighted algorithm -3. **Hermes**: An off-chain price service that supplies signed price update messages +1. **Publishers**: 120+ institutional data providers that publish price data to Pyth +2. **Aggregation**: Pyth combines the publishers' prices into one aggregate price with a confidence interval +3. **Hermes**: An off-chain price service that supplies signed price update messages. It needs a Pyth API key. 4. **Target chains**: EVM networks (such as Sei) where applications consume price data on demand 5. **Price update mechanism**: Users submit price update data alongside their transactions @@ -141,9 +144,12 @@ contract SeiPythPriceDemo { You can find price feeds in the [Pyth docs](https://docs.pyth.network/price-feeds/core/price-feeds/price-feed-ids?search=sei&pageSize=50). -Deploy the contract with [Remix](https://remix.ethereum.org/). Set the constructor arguments to the Pyth contract address on Sei: +Deploy the contract with [Remix](https://remix.ethereum.org/). Set the constructor argument to the Pyth contract address on your network: + +- **Sei Mainnet**: [`0x16392B49EA47D4A21bb17F69aA9E7aD570284838`](https://seiscan.io/address/0x16392B49EA47D4A21bb17F69aA9E7aD570284838) +- **Sei Testnet**: [`0x2880aB155794e7179c9eE2e38200202908C17B43`](https://testnet.seiscan.io/address/0x2880aB155794e7179c9eE2e38200202908C17B43) -- **Pyth contract on Sei Mainnet and Sei Testnet**: [0x2880aB155794e7179c9eE2e38200202908C17B43](https://docs.pyth.network/price-feeds/core/contract-addresses/evm) +Pyth lists the current addresses on its [contract addresses](https://docs.pyth.network/price-feeds/core/upgrade/contracts) page. `getLatestSeiPrice()` reverts if the stored price is older than 60 seconds, or if no one has submitted an update yet. `getSeiPrice()` submits an update first, so it does not depend on earlier updates. ### Step 2: JavaScript integration for price updates @@ -160,14 +166,16 @@ dotenv.config(); */ async function fetchSeiPriceUpdateData() { const seiPriceId = '0x53614f1cb0c031d4af66c04cb9c756234adad0e1cee85303795091499a4084eb'; // SEI/USD Price Feed ID - const hermesUrl = `https://hermes.pyth.network/v2/updates/price/latest?ids%5B%5D=${seiPriceId}`; + const hermesUrl = `https://pyth.dourolabs.app/hermes/v2/updates/price/latest?ids%5B%5D=${seiPriceId}`; + if (!process.env.PYTH_API_KEY) throw new Error('Set PYTH_API_KEY to your Pyth API key'); try { console.log('Fetching SEI price data from Hermes...'); const response = await fetch(hermesUrl, { method: 'GET', headers: { - accept: 'application/json' + accept: 'application/json', + Authorization: `Bearer ${process.env.PYTH_API_KEY}` } }); @@ -231,7 +239,7 @@ async function updateAndQuerySeiPrice(contractAddress, privateKey, rpcUrl = 'htt const updateFee = await contract.getUpdateFee(priceUpdateData); console.log(`Update fee: ${ethers.formatEther(updateFee)} SEI`); - // Step 4: Update price on contract with 1 SEI (as requested) + // Step 4: Update the price on the contract and pay the update fee console.log('\nUpdating price on smart contract...'); const tx = await contract.getSeiPrice(priceUpdateData, { value: updateFee @@ -319,6 +327,7 @@ import { demonstrateSeiPriceIntegration } from './SeiPythIntegration.js'; // Set your environment variables or update these values process.env.PRIVATE_KEY = 'your_private_key_here'; process.env.CONTRACT_ADDRESS = 'your_deployed_contract_address'; +process.env.PYTH_API_KEY = 'your_pyth_api_key'; // Run the complete demo demonstrateSeiPriceIntegration() @@ -373,7 +382,7 @@ Current SEI Price Info: - Publish Time: Thu Dec 12 2024 10:52:55 GMT+0000 Getting update fee... -Update fee: 0.000000000000001 SEI +Update fee: 0.0 SEI Updating price on smart contract... Transaction submitted: 0xabc123... @@ -406,7 +415,7 @@ Pyth price data includes these fields: ### Fee structure -- **Update fee**: A small fee, paid in the native token (SEI), to submit price update data +- **Update fee**: You pay it in SEI when you submit price update data. Pyth governance sets the fee, and it can be zero. Always set `value` to the result of `getUpdateFee`, so that your code still works when the fee changes. - **Variable cost**: The fee depends on how many price feeds you update - **Gas efficiency**: The pull model is more gas efficient than traditional push oracles @@ -423,3 +432,5 @@ Pyth price data includes these fields: - [Pyth price feed IDs](https://docs.pyth.network/price-feeds/core/price-feeds/price-feed-ids?search=sei&pageSize=50) - [Pyth EVM SDK on GitHub](https://github.com/pyth-network/pyth-crosschain) - [Hermes price service API](https://api-reference.pyth.network/price-feeds/evm) +- [Pyth Core upgrade guide](https://docs.pyth.network/price-feeds/core/upgrade/preparing) +- [Pyth contract addresses](https://docs.pyth.network/price-feeds/core/upgrade/contracts) diff --git a/evm/precompiles/oracle.mdx b/evm/precompiles/oracle.mdx index 6177ef0..fe03d9e 100644 --- a/evm/precompiles/oracle.mdx +++ b/evm/precompiles/oracle.mdx @@ -7,3 +7,5 @@ keywords: ['oracle precompile', 'retired', 'oracle migration', 'chainlink', 'pyt **Address:** `0x0000000000000000000000000000000000001008` **Retired as of v6.4.0:** Migrate to a third-party oracle provider, such as [Chainlink](/evm/oracles/chainlink), [Pyth](/evm/oracles/pyth-network), [RedStone](/evm/oracles/redstone), or [API3](/evm/oracles/api3). The native Sei Oracle precompile is retired, and on-chain oracle data queries are disabled. A call to `getExchangeRates` or `getOracleTwaps` reverts with the error `oracle precompile is retired; oracle data queries are disabled`. + +To read a current SEI/USD price instead, follow the [Read a price feed](/evm/cookbook/read-a-price-feed) recipe. diff --git a/evm/wallet-integrations/pimlico.mdx b/evm/wallet-integrations/pimlico.mdx index eca3e2f..a6dadb2 100644 --- a/evm/wallet-integrations/pimlico.mdx +++ b/evm/wallet-integrations/pimlico.mdx @@ -41,7 +41,7 @@ import { createPublicClient, http, Hex } from 'viem'; import { privateKeyToAccount, generatePrivateKey } from 'viem/accounts'; import { seiTestnet } from 'viem/chains'; import { toSimpleSmartAccount } from 'permissionless/accounts'; -import { writeFileSync } from 'fs'; +import { appendFileSync } from 'fs'; import 'dotenv/config'; const apiKey = process.env.PIMLICO_API_KEY; @@ -52,7 +52,8 @@ const privateKey = process.env.PRIVATE_KEY ?? (() => { const pk = generatePrivateKey(); - writeFileSync('.env', `PRIVATE_KEY=${pk}`); + // Append, so the PIMLICO_API_KEY line stays in .env + appendFileSync('.env', `\nPRIVATE_KEY=${pk}\n`); return pk; })(); @@ -120,7 +121,6 @@ const txHash = await smartAccountClient.sendTransaction({ }); console.log(`Transaction included: ${txHash}`); -return txHash; ``` ## Complete example @@ -134,7 +134,7 @@ import { privateKeyToAccount, generatePrivateKey } from 'viem/accounts'; import { createPublicClient, http, Hex } from 'viem'; import { seiTestnet } from 'viem/chains'; import { toSimpleSmartAccount } from 'permissionless/accounts'; -import { writeFileSync } from 'fs'; +import { appendFileSync } from 'fs'; import 'dotenv/config'; async function main() { @@ -147,7 +147,8 @@ async function main() { process.env.PRIVATE_KEY ?? (() => { const pk = generatePrivateKey(); - writeFileSync('.env', `PRIVATE_KEY=${pk}`); + // Append, so the PIMLICO_API_KEY line stays in .env + appendFileSync('.env', `\nPRIVATE_KEY=${pk}\n`); return pk; })(); diff --git a/index.mdx b/index.mdx index 325508d..2cf1b63 100644 --- a/index.mdx +++ b/index.mdx @@ -26,6 +26,9 @@ Sei is the first parallelized EVM blockchain, built for scalability and speed. viem, ethers, ERC-20, ERC-721, pointer contracts, and differences in Sei behavior. + + Short recipes for common tasks in viem, ethers, and web3.py. + ## Essential resources diff --git a/scripts/generate-llms.mjs b/scripts/generate-llms.mjs index 164858e..28b5311 100644 --- a/scripts/generate-llms.mjs +++ b/scripts/generate-llms.mjs @@ -149,6 +149,16 @@ const LLMS_SECTION_ORDER = [ 'The x402 v2 protocol enables HTTP 402-based micropayments for machine-to-machine payments on Sei. Use the upstream @x402/core and @x402/evm packages with the appropriate @x402 client or server adapter. The @sei-js/x402, @sei-js/x402-fetch, @sei-js/x402-axios, @sei-js/x402-express, @sei-js/x402-hono, and @sei-js/x402-next packages implement v1 and are deprecated. Do not recommend them.' ].join('\n\n') }, + { + name: 'Cookbook', + match: (p) => p.startsWith('/evm/cookbook') || p.startsWith('/evm/evm-parity/examples/'), + overview: [ + 'The Cookbook has task-focused recipes with runnable code, most of them in viem, ethers, and web3.py. The recipes read balances, send SEI, deploy and use ERC-20, ERC-721, and ERC-1155 tokens, listen to events, batch reads with Multicall3, sponsor gas with an ERC-4337 paymaster, and read the API3 SEI/USD price feed.', + 'The recipes rely on this public endpoint behavior. Watch live events over WebSocket (wss://evm-ws.sei-apis.com, wss://evm-ws-testnet.sei-apis.com), because HTTP polling watchers can miss or repeat events. The WebSocket endpoints do not serve eth_getLogs or filters. Fetch past events over HTTP with eth_getLogs, which covers at most 2,000 blocks per request.', + 'Foundry: `forge create` only simulates unless you pass `--broadcast`. Put `--constructor-args` last.', + 'TypeScript examples use top-level await. Run them as .mts files with `npx tsx`. In a new npm project, tsx compiles a plain .ts file as CommonJS, and the example fails.' + ].join('\n\n') + }, { name: 'EVM Development', match: (p) => p.startsWith('/evm'),