diff --git a/docs.json b/docs.json
index 63f1057..3327768 100644
--- a/docs.json
+++ b/docs.json
@@ -159,6 +159,7 @@
"evm/evm-general",
"evm/evm-hardhat",
"evm/evm-foundry",
+ "evm/python-quickstart",
"evm/evm-wizard",
"evm/solidity-resources",
{
diff --git a/evm/evm-hardhat.mdx b/evm/evm-hardhat.mdx
index 216f803..b82694a 100644
--- a/evm/evm-hardhat.mdx
+++ b/evm/evm-hardhat.mdx
@@ -43,51 +43,42 @@ Let's create a new project and set up Hardhat:
mkdir sei-hardhat-project
cd sei-hardhat-project
-# Initialize a new npm project
-npm init -y
-
-# Install Hardhat and necessary dependencies
-npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox @openzeppelin/contracts dotenv
-```
-
-After installation, initialize a new Hardhat project:
-
-```bash
+# Scaffold a Hardhat 3 project (ESM + TypeScript)
npx hardhat --init
```
-When prompted, select :
+When prompted, choose **Hardhat 3** and **a TypeScript project using Mocha and Ethers.js**, then follow the prompts. This generates an ESM project (`"type": "module"` in `package.json`) with `@nomicfoundation/hardhat-toolbox-mocha-ethers`, `ethers`, Hardhat Ignition, TypeScript, and a ready-to-use `tsconfig.json`.
-- Hardhat3
-- A TypeScript Hardhat project using Mocha and Ethers.js
-
-and then follow the instructions to set up your project.
-
-If you encounter an error because of mismatch of versioning, you can try the following command with `--legacy-peer-deps` flag:
+Then add the OpenZeppelin contract library:
```bash
-npm install --save-dev "@nomicfoundation/hardhat-toolbox-mocha-ethers@^3.0.2" "@nomicfoundation/hardhat-ethers@^4.0.4" "@nomicfoundation/hardhat-ignition@^3.0.7" "@types/chai@^4.2.0" "@types/chai-as-promised@^8.0.1" "@types/mocha@>=10.0.10" "@types/node@^22.8.5" "chai@^5.1.2" "ethers@^6.14.0" "forge-std@foundry-rs/forge-std#v1.9.4" "mocha@^11.0.0" "typescript@~5.8.0" --legacy-peer-deps
+npm install @openzeppelin/contracts
```
-Post that, it is recommended to install some hardhat based dependencies as well for supporting Hardhat3:
+
+Prefer a non-interactive setup (CI or scripted environments)? Do the same thing by hand — no prompts:
```bash
-npm i @nomicfoundation/hardhat-ethers-chai-matchers @nomicfoundation/hardhat-ignition-ethers @nomicfoundation/hardhat-keystore @nomicfoundation/hardhat-mocha @nomicfoundation/hardhat-network-helpers @nomicfoundation/hardhat-typechain @nomicfoundation/hardhat-verify
+npm init -y
+npm pkg set type=module
+npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox-mocha-ethers @openzeppelin/contracts typescript @types/node
```
+The `@nomicfoundation/hardhat-toolbox-mocha-ethers` bundle pulls in `ethers`, Ignition, Mocha/Chai, and the keystore, network-helpers, verify, and typechain plugins — so this single install covers everything in this guide. If you hit peer-dependency errors, re-run with `--legacy-peer-deps`.
+
+
## Configuring Hardhat for Sei EVM
Next, we'll need to configure Hardhat to work with the Sei EVM. Update your `hardhat.config.ts` file:
```typescript title="hardhat.config.ts"
-import { HardhatUserConfig } from 'hardhat/config';
-import '@nomicfoundation/hardhat-toolbox';
-import 'dotenv/config';
-
-// Load environment variables
-const PRIVATE_KEY = process.env.PRIVATE_KEY || '0x0000000000000000000000000000000000000000000000000000000000000000';
+import type { HardhatUserConfig } from 'hardhat/config';
+import { configVariable } from 'hardhat/config';
+import hardhatToolboxMochaEthers from '@nomicfoundation/hardhat-toolbox-mocha-ethers';
const config: HardhatUserConfig = {
+ // Hardhat 3 loads plugins from an explicit array — no side-effect `import` statements
+ plugins: [hardhatToolboxMochaEthers],
solidity: {
version: '0.8.28',
settings: {
@@ -98,42 +89,39 @@ const config: HardhatUserConfig = {
}
},
networks: {
- // Sei testnet configuration
+ // Sei testnet (atlantic-2)
seitestnet: {
+ type: 'http',
+ chainType: 'l1',
url: 'https://evm-rpc-testnet.sei-apis.com',
- accounts: [PRIVATE_KEY],
- chainId: 1328 // Sei testnet chain ID
+ accounts: [configVariable('SEI_PRIVATE_KEY')],
+ chainId: 1328
},
- // Sei mainnet configuration
+ // Sei mainnet (pacific-1)
seimainnet: {
+ type: 'http',
+ chainType: 'l1',
url: 'https://evm-rpc.sei-apis.com',
- accounts: [PRIVATE_KEY],
- chainId: 1329 // Sei mainnet chain ID
- },
- // Local development with Hardhat Network
- hardhat: {
- chainId: 31337
+ accounts: [configVariable('SEI_PRIVATE_KEY')],
+ chainId: 1329
}
- },
- paths: {
- sources: './contracts',
- tests: './test',
- cache: './cache',
- artifacts: './artifacts'
- },
- mocha: {
- timeout: 40000
}
};
+
+export default config;
```
-Create a `.env` file in your project root to store your private key:
+Hardhat 3 is ESM-first and requires `"type": "module"` in `package.json` (the scaffold sets this for you). Each network needs an explicit `type: 'http'`, and plugins load via the `plugins` array rather than the Hardhat 2 side-effect `import '@nomicfoundation/hardhat-toolbox'`. A local simulated network is provided automatically — no `hardhat`/`localhost` entry required.
+
+Hardhat 3 reads secrets through `configVariable(...)`, backed by an encrypted keystore — so your key is never stored in a plaintext file. Set it once:
```bash
-PRIVATE_KEY=your_private_key_here
+npx hardhat keystore set SEI_PRIVATE_KEY
```
-Add `.env` to your `.gitignore` file to prevent committing sensitive information such as your `PRIVATE_KEY` and potentially lose funds.
+You'll be prompted to paste the private key of the account you deploy from; Hardhat stores it encrypted on your machine. For CI, export a `SEI_PRIVATE_KEY` environment variable instead — `configVariable` falls back to it.
+
+Use a dedicated, throwaway deploy key funded with only what you need — never a personal wallet holding real funds.
## Using OpenZeppelin Contracts
@@ -179,7 +167,7 @@ contract SeiToken is ERC20, Ownable {
Now, create a deployment script in the `ignition/modules` directory called `deploy-sei-token.ts`:
-```typescript title="scripts/deploy-sei-token.ts"
+```typescript title="ignition/modules/deploy-sei-token.ts"
import { buildModule } from '@nomicfoundation/hardhat-ignition/modules';
export default buildModule('SeiTokenModule', (m) => {
@@ -296,37 +284,28 @@ contract SeiNFT is ERC721, ERC721Enumerable, ERC721URIStorage, ERC721Pausable, O
Create a deployment script `deploy-sei-nft.ts`:
```typescript title="scripts/deploy-sei-nft.ts"
-import { ethers } from 'hardhat';
+import { network } from 'hardhat';
-async function main() {
- const [deployer] = await ethers.getSigners();
- console.log('Deploying contracts with the account:', deployer.address);
+// Hardhat 3 exposes ethers through a network connection rather than a global import
+const { ethers } = await network.getOrCreate();
- // Base URI for your NFT metadata
- const baseURI = 'https://your-metadata-server.com/metadata/';
+const [deployer] = await ethers.getSigners();
+console.log('Deploying contracts with the account:', deployer.address);
- // Get the contract factory
- const SeiNFT = await ethers.getContractFactory('SeiNFT');
+// Base URI for your NFT metadata
+const baseURI = 'https://your-metadata-server.com/metadata/';
- // Deploy the contract
- const seiNFT = await SeiNFT.deploy(deployer.address, baseURI);
- await seiNFT.waitForDeployment();
+const SeiNFT = await ethers.getContractFactory('SeiNFT');
+const seiNFT = await SeiNFT.deploy(deployer.address, baseURI);
+await seiNFT.waitForDeployment();
- console.log('SeiNFT deployed to:', await seiNFT.getAddress());
-
- // Mint an example NFT
- console.log('Minting an example NFT...');
- const mintTx = await seiNFT.safeMint(deployer.address, '1.json');
- await mintTx.wait();
- console.log('NFT minted with ID: 1');
-}
+console.log('SeiNFT deployed to:', await seiNFT.getAddress());
-main()
- .then(() => process.exit(0))
- .catch((error) => {
- console.error(error);
- process.exit(1);
- });
+// Mint an example NFT
+console.log('Minting an example NFT...');
+const mintTx = await seiNFT.safeMint(deployer.address, '1.json');
+await mintTx.wait();
+console.log('NFT minted with ID: 1');
```
Deploy the NFT contract to the Sei testnet:
@@ -339,22 +318,17 @@ npx hardhat run scripts/deploy-sei-nft.ts --network seitestnet
Upgradeable contracts allow you to modify the contract's logic after deployment without changing the contract address, which is crucial for fixing bugs or adding new features. The UUPS (Universal Upgradeable Proxy Standard) pattern is a popular way to implement upgradeability.
-First, ensure you have installed the necessary upgrade plugins as mentioned in the [Using OpenZeppelin Contracts](#using-openzeppelin-contracts) section:
+For upgradeable contracts, also install the upgradeable variant of the OpenZeppelin library (the `@openzeppelin/contracts` package you installed earlier supplies the ERC1967 proxy):
```bash
-npm install @openzeppelin/contracts-upgradeable @openzeppelin/hardhat-upgrades
+npm install @openzeppelin/contracts-upgradeable
```
-And make sure your `hardhat.config.ts` includes the upgrades plugin:
-
-```typescript title="hardhat.config.ts"
-import { HardhatUserConfig } from 'hardhat/config';
-import '@nomicfoundation/hardhat-toolbox';
-import '@openzeppelin/hardhat-upgrades';
-import 'dotenv/config';
+
+OpenZeppelin's `@openzeppelin/hardhat-upgrades` plugin is built for Hardhat 2 and does not register with Hardhat 3's `plugins` array. So this guide deploys the proxy directly using the ERC1967 standard and upgrades through UUPS `upgradeToAndCall` — no extra plugin, fully Hardhat 3-native. The tradeoff: you don't get the plugin's automatic storage-layout safety checks, so make sure each new version only **appends** state variables. You can validate layouts separately with [`@openzeppelin/upgrades-core`](https://www.npmjs.com/package/@openzeppelin/upgrades-core).
+
-// ... rest of your config
-```
+No `hardhat.config.ts` changes are needed beyond the configuration shown earlier.
Now, let's create an upgradeable ERC20 token. Create `contracts/UpgradeableSeiToken.sol`:
@@ -376,7 +350,6 @@ contract UpgradeableSeiToken is Initializable, ERC20Upgradeable, OwnableUpgradea
function initialize(address initialOwner) initializer public {
__ERC20_init("Upgradeable Sei Token", "uSEI");
__Ownable_init(initialOwner);
- __UUPSUpgradeable_init();
// Mint 1 million tokens to the initializer (deployer)
_mint(initialOwner, 1000000 * 10 ** decimals());
@@ -401,36 +374,49 @@ contract UpgradeableSeiToken is Initializable, ERC20Upgradeable, OwnableUpgradea
}
```
-Note the use of `Initializable`, `initializer` modifier, `__ERC20_init`, `__Ownable_init`, `__UUPSUpgradeable_init`, and the `_authorizeUpgrade` function override. These are crucial for upgradeable contracts.
+Note the `Initializable` base, the `initializer` modifier, the `__ERC20_init` / `__Ownable_init` calls, and the `_authorizeUpgrade` override — these are what make the contract safe to run behind a proxy. (OpenZeppelin v5's `UUPSUpgradeable` is stateless, so there is no `__UUPSUpgradeable_init` to call.)
+
+Add a thin, named ERC1967 proxy so Hardhat emits an artifact you can deploy by name. Create `contracts/SeiTokenProxy.sol`:
+
+```solidity title="contracts/SeiTokenProxy.sol"
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.22;
+
+import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
+
+contract SeiTokenProxy is ERC1967Proxy {
+ constructor(address implementation, bytes memory _data)
+ ERC1967Proxy(implementation, _data)
+ {}
+}
+```
-Create a deployment script `scripts/deploy-upgradeable-token.ts`:
+Now create a deployment script `scripts/deploy-upgradeable-token.ts`. It deploys the implementation, then deploys the proxy with the `initialize` call encoded as constructor data so initialization happens atomically:
```typescript title="scripts/deploy-upgradeable-token.ts"
-import { ethers, upgrades } from 'hardhat';
+import { network } from 'hardhat';
-async function main() {
- const [deployer] = await ethers.getSigners();
- console.log('Deploying contracts with the account:', deployer.address);
+const { ethers } = await network.getOrCreate();
- const UpgradeableSeiToken = await ethers.getContractFactory('UpgradeableSeiToken');
+const [deployer] = await ethers.getSigners();
+console.log('Deploying contracts with the account:', deployer.address);
- // Deploy the upgradeable contract using the proxy pattern
- const upgradeableToken = await upgrades.deployProxy(UpgradeableSeiToken, [deployer.address], {
- initializer: 'initialize',
- kind: 'uups' // Specify UUPS kind
- });
- await upgradeableToken.waitForDeployment();
+// 1. Deploy the implementation contract
+const UpgradeableSeiToken = await ethers.getContractFactory('UpgradeableSeiToken');
+const implementation = await UpgradeableSeiToken.deploy();
+await implementation.waitForDeployment();
- console.log('UpgradeableSeiToken proxy deployed to:', await upgradeableToken.getAddress());
- console.log('Implementation contract address:', await upgrades.erc1967.getImplementationAddress(await upgradeableToken.getAddress()));
-}
+// 2. Deploy the ERC1967 proxy, running initialize(deployer) atomically
+const initData = UpgradeableSeiToken.interface.encodeFunctionData('initialize', [deployer.address]);
+const SeiTokenProxy = await ethers.getContractFactory('SeiTokenProxy');
+const proxy = await SeiTokenProxy.deploy(await implementation.getAddress(), initData);
+await proxy.waitForDeployment();
-main()
- .then(() => process.exit(0))
- .catch((error) => {
- console.error(error);
- process.exit(1);
- });
+// 3. Interact with the token through the proxy address from here on
+const token = await ethers.getContractAt('UpgradeableSeiToken', await proxy.getAddress());
+console.log('Proxy deployed to:', await token.getAddress());
+console.log('Implementation:', await implementation.getAddress());
+console.log('Version:', await token.version()); // V1
```
Deploy this to the testnet:
@@ -472,36 +458,28 @@ contract UpgradeableSeiTokenV2 is UpgradeableSeiToken {
Now, create an upgrade script `scripts/upgrade-token.ts`. **Replace `PROXY_ADDRESS` with the address printed when you deployed the proxy.**
```typescript title="scripts/upgrade-token.ts"
-import { ethers, upgrades } from 'hardhat';
+import { network } from 'hardhat';
-// !! REPLACE WITH YOUR PROXY ADDRESS !!
-const PROXY_ADDRESS = '0xYOUR_PROXY_CONTRACT_ADDRESS_HERE';
+const { ethers } = await network.getOrCreate();
-async function main() {
- const [deployer] = await ethers.getSigners();
- console.log('Upgrading contract with the account:', deployer.address);
-
- const UpgradeableSeiTokenV2 = await ethers.getContractFactory('UpgradeableSeiTokenV2');
+// !! REPLACE WITH YOUR PROXY ADDRESS from the deploy step !!
+const PROXY_ADDRESS = '0xYOUR_PROXY_CONTRACT_ADDRESS_HERE';
- console.log('Preparing upgrade...');
- const upgradeableTokenV2 = await upgrades.upgradeProxy(PROXY_ADDRESS, UpgradeableSeiTokenV2);
- await upgradeableTokenV2.waitForDeployment();
+const [deployer] = await ethers.getSigners();
+console.log('Upgrading contract with the account:', deployer.address);
- console.log('UpgradeableSeiToken upgraded successfully!');
- console.log('Proxy remains at:', await upgradeableTokenV2.getAddress()); // Should be the same as PROXY_ADDRESS
- console.log('New implementation contract address:', await upgrades.erc1967.getImplementationAddress(await upgradeableTokenV2.getAddress()));
+// 1. Deploy the new implementation
+const UpgradeableSeiTokenV2 = await ethers.getContractFactory('UpgradeableSeiTokenV2');
+const v2Implementation = await UpgradeableSeiTokenV2.deploy();
+await v2Implementation.waitForDeployment();
- // Optional: Verify the upgrade by calling the new version function
- const attached = UpgradeableSeiTokenV2.attach(await upgradeableTokenV2.getAddress());
- console.log('Contract version:', await attached.version());
-}
+// 2. Point the proxy at the new implementation (UUPS upgrades are owner-gated)
+const token = await ethers.getContractAt('UpgradeableSeiTokenV2', PROXY_ADDRESS);
+await (await token.upgradeToAndCall(await v2Implementation.getAddress(), '0x')).wait();
-main()
- .then(() => process.exit(0))
- .catch((error) => {
- console.error(error);
- process.exit(1);
- });
+console.log('Upgrade complete. Proxy remains at:', PROXY_ADDRESS);
+console.log('New implementation:', await v2Implementation.getAddress());
+console.log('Contract version:', await token.version()); // V2
```
Run the upgrade script:
@@ -521,22 +499,24 @@ Hardhat makes it easy to test your contracts before deploying them. Create a tes
```typescript title="test/sei-token-test.ts"
import { expect } from 'chai';
-import { ethers } from 'hardhat';
+import { network } from 'hardhat';
describe('SeiToken', function () {
- let SeiToken;
+ let ethers;
let seiToken;
let owner;
let addr1;
let addr2;
beforeEach(async function () {
+ // Hardhat 3 exposes ethers through a network connection
+ ({ ethers } = await network.getOrCreate());
+
// Get signers
[owner, addr1, addr2] = await ethers.getSigners();
// Deploy the token
- SeiToken = await ethers.getContractFactory('SeiToken');
- seiToken = await SeiToken.deploy(owner.address);
+ seiToken = await ethers.deployContract('SeiToken', [owner.address]);
});
describe('Deployment', function () {
diff --git a/evm/evm-parity/examples/deploy-verify.mdx b/evm/evm-parity/examples/deploy-verify.mdx
index cfa38ac..4b8e5d6 100644
--- a/evm/evm-parity/examples/deploy-verify.mdx
+++ b/evm/evm-parity/examples/deploy-verify.mdx
@@ -16,7 +16,7 @@ For a deeper look at verification options (Remix, Sourcify UI, batch verificatio
```ts viem
import { createPublicClient, createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
-import { sei } from '@sei-js/precompiles/viem';
+import { sei } from 'viem/chains';
const account = privateKeyToAccount('0xYourPrivateKey');
const publicClient = createPublicClient({ chain: sei, transport: http() });
diff --git a/evm/evm-parity/examples/erc1155.mdx b/evm/evm-parity/examples/erc1155.mdx
index 864c354..50e0fea 100644
--- a/evm/evm-parity/examples/erc1155.mdx
+++ b/evm/evm-parity/examples/erc1155.mdx
@@ -14,7 +14,7 @@ ERC-1155 is the multi-token standard — a single contract can hold both fungibl
```ts viem
import { createPublicClient, createWalletClient, http, parseAbi } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
-import { sei } from '@sei-js/precompiles/viem';
+import { sei } from 'viem/chains';
const client = createPublicClient({ chain: sei, transport: http() });
const account = privateKeyToAccount('0xYourPrivateKey');
diff --git a/evm/evm-parity/examples/erc20.mdx b/evm/evm-parity/examples/erc20.mdx
index 74d7ee5..051b205 100644
--- a/evm/evm-parity/examples/erc20.mdx
+++ b/evm/evm-parity/examples/erc20.mdx
@@ -14,7 +14,7 @@ Standard ERC-20 contracts work on Sei without modification. This page covers the
```ts viem
import { createPublicClient, createWalletClient, http, parseAbi } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
-import { sei } from '@sei-js/precompiles/viem';
+import { sei } from 'viem/chains';
const client = createPublicClient({ chain: sei, transport: http() });
const account = privateKeyToAccount('0xYourPrivateKey');
diff --git a/evm/evm-parity/examples/erc721.mdx b/evm/evm-parity/examples/erc721.mdx
index 12cc082..d1ed3b1 100644
--- a/evm/evm-parity/examples/erc721.mdx
+++ b/evm/evm-parity/examples/erc721.mdx
@@ -14,7 +14,7 @@ Standard ERC-721 contracts work on Sei without modification. This page covers re
```ts viem
import { createPublicClient, createWalletClient, http, parseAbi } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
-import { sei } from '@sei-js/precompiles/viem';
+import { sei } from 'viem/chains';
const client = createPublicClient({ chain: sei, transport: http() });
const account = privateKeyToAccount('0xYourPrivateKey');
diff --git a/evm/evm-parity/examples/ethers-quickstart.mdx b/evm/evm-parity/examples/ethers-quickstart.mdx
index c0e34a0..dac0ff4 100644
--- a/evm/evm-parity/examples/ethers-quickstart.mdx
+++ b/evm/evm-parity/examples/ethers-quickstart.mdx
@@ -13,6 +13,12 @@ This example covers ethers v6 with Sei. The patterns apply whether you are writi
npm install ethers
```
+These snippets are TypeScript. Run any of them with no separate build step using [`tsx`](https://www.npmjs.com/package/tsx) (Node 18+):
+
+```bash
+npx tsx script.ts
+```
+
## Read-Only Provider
```ts
@@ -26,13 +32,16 @@ const provider = new ethers.JsonRpcProvider('https://evm-rpc.sei-apis.com');
This is the first milestone — it needs no private key.
```ts
+// Any valid Sei EVM address — swap in your own
+const address = '0x0000000000000000000000000000000000000000';
+
const blockNumber = await provider.getBlockNumber(); // number
console.log('Block number:', blockNumber);
-const balance = await provider.getBalance('0xYourAddress'); // bigint, wei
+const balance = await provider.getBalance(address); // bigint, wei
console.log('Balance (SEI):', ethers.formatEther(balance));
-const nonce = await provider.getTransactionCount('0xYourAddress'); // number
+const nonce = await provider.getTransactionCount(address); // number
console.log('Nonce:', nonce);
```
@@ -119,36 +128,9 @@ contract.on('Transfer', (from, to, value) => {
contract.off('Transfer');
```
-## Using Python (web3.py)
-
-Sei is fully EVM-compatible, so any standard Ethereum client works — including Python's [web3.py](https://web3py.readthedocs.io).
-
-```bash
-pip install web3
-```
-
-```python
-from web3 import Web3
-
-w3 = Web3(Web3.HTTPProvider("https://evm-rpc.sei-apis.com"))
-
-print("Connected:", w3.is_connected())
-print("Latest block:", w3.eth.block_number)
-print("Chain ID:", w3.eth.chain_id) # 1329
-```
-
-**You're done when you see:**
-
-```text
-Connected: True
-Latest block: 148203117
-Chain ID: 1329
-```
-
-The block number is illustrative; the chain ID is always `1329` on mainnet.
-
## Next Steps
+- [Python quickstart (web3.py)](/evm/python-quickstart) — the same first steps in Python
- [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
diff --git a/evm/evm-parity/examples/multicall.mdx b/evm/evm-parity/examples/multicall.mdx
index 9e1554b..c01e82b 100644
--- a/evm/evm-parity/examples/multicall.mdx
+++ b/evm/evm-parity/examples/multicall.mdx
@@ -15,7 +15,7 @@ viem's `multicall` action wraps Multicall3 automatically:
```ts
import { createPublicClient, http, parseAbi } from 'viem';
-import { sei } from '@sei-js/precompiles/viem';
+import { sei } from 'viem/chains';
const client = createPublicClient({ chain: sei, transport: http() });
diff --git a/evm/evm-parity/examples/pointer-contracts.mdx b/evm/evm-parity/examples/pointer-contracts.mdx
index e05ed94..a44e324 100644
--- a/evm/evm-parity/examples/pointer-contracts.mdx
+++ b/evm/evm-parity/examples/pointer-contracts.mdx
@@ -25,7 +25,7 @@ The pointerview precompile resolves pointer addresses by CosmWasm contract addre
```ts viem
import { createPublicClient, http } from 'viem';
-import { sei } from '@sei-js/precompiles/viem';
+import { sei } from 'viem/chains';
import {
POINTERVIEW_PRECOMPILE_ABI,
POINTERVIEW_PRECOMPILE_ADDRESS,
diff --git a/evm/evm-parity/examples/transaction-lifecycle.mdx b/evm/evm-parity/examples/transaction-lifecycle.mdx
index afe7e65..b7618f4 100644
--- a/evm/evm-parity/examples/transaction-lifecycle.mdx
+++ b/evm/evm-parity/examples/transaction-lifecycle.mdx
@@ -14,7 +14,7 @@ This page covers the full round-trip of a transaction: building and sending it,
```ts viem
import { createWalletClient, http, parseEther } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
-import { sei } from '@sei-js/precompiles/viem';
+import { sei } from 'viem/chains';
const account = privateKeyToAccount('0xYourPrivateKey');
const walletClient = createWalletClient({ account, chain: sei, transport: http() });
diff --git a/evm/evm-parity/examples/viem-quickstart.mdx b/evm/evm-parity/examples/viem-quickstart.mdx
index 0a5bba0..7786a07 100644
--- a/evm/evm-parity/examples/viem-quickstart.mdx
+++ b/evm/evm-parity/examples/viem-quickstart.mdx
@@ -10,7 +10,13 @@ This example shows how to use viem with Sei outside of a React context — in a
## Install
```bash
-npm install viem @sei-js/precompiles
+npm install viem
+```
+
+These snippets are TypeScript. Run any of them with no separate build step using [`tsx`](https://www.npmjs.com/package/tsx) (Node 18+):
+
+```bash
+npx tsx script.ts
```
## Public Client
@@ -19,7 +25,7 @@ A public client handles all read-only operations.
```ts
import { createPublicClient, http } from 'viem';
-import { sei } from '@sei-js/precompiles/viem';
+import { sei } from 'viem/chains';
const client = createPublicClient({
chain: sei,
@@ -43,13 +49,16 @@ This is the first milestone — it needs no private key.
```ts
import { formatEther } from 'viem';
+// Any valid Sei EVM address — swap in your own
+const address = '0x0000000000000000000000000000000000000000';
+
const blockNumber = await client.getBlockNumber(); // bigint
console.log('Block number:', blockNumber);
-const balance = await client.getBalance({ address: '0xYourAddress' }); // bigint, wei
+const balance = await client.getBalance({ address }); // bigint, wei
console.log('Balance (SEI):', formatEther(balance));
-const nonce = await client.getTransactionCount({ address: '0xYourAddress' }); // number
+const nonce = await client.getTransactionCount({ address }); // number
console.log('Nonce:', nonce);
```
@@ -70,7 +79,7 @@ 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';
+import { sei } from 'viem/chains';
const account = privateKeyToAccount('0xYourPrivateKey');
@@ -154,36 +163,9 @@ const gas = await client.estimateGas({
});
```
-## Using Python (web3.py)
-
-Sei is fully EVM-compatible, so any standard Ethereum client works — including Python's [web3.py](https://web3py.readthedocs.io).
-
-```bash
-pip install web3
-```
-
-```python
-from web3 import Web3
-
-w3 = Web3(Web3.HTTPProvider("https://evm-rpc.sei-apis.com"))
-
-print("Connected:", w3.is_connected())
-print("Latest block:", w3.eth.block_number)
-print("Chain ID:", w3.eth.chain_id) # 1329
-```
-
-**You're done when you see:**
-
-```text
-Connected: True
-Latest block: 148203117
-Chain ID: 1329
-```
-
-The block number is illustrative; the chain ID is always `1329` on mainnet.
-
## Next Steps
+- [Python quickstart (web3.py)](/evm/python-quickstart) — the same first steps in Python
- [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
diff --git a/evm/evm-parity/examples/wagmi-react.mdx b/evm/evm-parity/examples/wagmi-react.mdx
index bf871d7..45aa0e1 100644
--- a/evm/evm-parity/examples/wagmi-react.mdx
+++ b/evm/evm-parity/examples/wagmi-react.mdx
@@ -23,7 +23,7 @@ Configure Wagmi with the Sei chain and your preferred wallet connectors:
// wagmi.config.ts
import { createConfig, http } from 'wagmi';
import { injected, walletConnect } from 'wagmi/connectors';
-import { sei, seiTestnet } from '@sei-js/precompiles/viem';
+import { sei, seiTestnet } from 'viem/chains';
export const config = createConfig({
chains: [sei, seiTestnet],
@@ -183,7 +183,7 @@ export function TransferButton({ tokenAddress }: { tokenAddress: `0x${string}` }
```tsx
import { useChainId, useSwitchChain } from 'wagmi';
-import { sei, seiTestnet } from '@sei-js/precompiles/viem';
+import { sei, seiTestnet } from 'viem/chains';
export function NetworkSwitcher() {
const chainId = useChainId();
diff --git a/evm/evm-parity/finality.mdx b/evm/evm-parity/finality.mdx
index a89e7fb..f4a4cc9 100644
--- a/evm/evm-parity/finality.mdx
+++ b/evm/evm-parity/finality.mdx
@@ -23,7 +23,7 @@ On Sei, all three tags resolve to the same block. There is no reorg risk at any
```ts viem
import { createPublicClient, http } from 'viem';
-import { sei } from '@sei-js/precompiles/viem';
+import { sei } from 'viem/chains';
const client = createPublicClient({ chain: sei, transport: http() });
diff --git a/evm/evm-parity/gas-and-fees.mdx b/evm/evm-parity/gas-and-fees.mdx
index d6015b2..3c37a9a 100644
--- a/evm/evm-parity/gas-and-fees.mdx
+++ b/evm/evm-parity/gas-and-fees.mdx
@@ -17,7 +17,7 @@ Use `eth_gasPrice` to read the current minimum:
```ts viem
import { createPublicClient, http } from 'viem';
-import { sei } from '@sei-js/precompiles/viem';
+import { sei } from 'viem/chains';
const client = createPublicClient({ chain: sei, transport: http() });
const gasPrice = await client.getGasPrice();
diff --git a/evm/evm-parity/signing.mdx b/evm/evm-parity/signing.mdx
index 433e8f0..414332a 100644
--- a/evm/evm-parity/signing.mdx
+++ b/evm/evm-parity/signing.mdx
@@ -24,7 +24,7 @@ Do not assume all wallets support all methods. Always handle rejection gracefull
```ts viem
import { createWalletClient, custom } from 'viem';
-import { sei } from '@sei-js/precompiles/viem';
+import { sei } from 'viem/chains';
const client = createWalletClient({
chain: sei,
diff --git a/evm/evm-parity/state-proofs.mdx b/evm/evm-parity/state-proofs.mdx
index 13c46fc..dbe256a 100644
--- a/evm/evm-parity/state-proofs.mdx
+++ b/evm/evm-parity/state-proofs.mdx
@@ -31,7 +31,7 @@ The call works through standard libraries:
```ts viem
import { createPublicClient, http } from 'viem';
-import { sei } from '@sei-js/precompiles/viem';
+import { sei } from 'viem/chains';
const client = createPublicClient({ chain: sei, transport: http() });
diff --git a/evm/evm-parity/transaction-types.mdx b/evm/evm-parity/transaction-types.mdx
index 6a95192..7f11981 100644
--- a/evm/evm-parity/transaction-types.mdx
+++ b/evm/evm-parity/transaction-types.mdx
@@ -37,7 +37,7 @@ Standard library defaults work correctly. viem, wagmi, and ethers all default to
```ts viem
import { createWalletClient, http, parseEther } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
-import { sei } from '@sei-js/precompiles/viem';
+import { sei } from 'viem/chains';
const account = privateKeyToAccount('0xYourPrivateKey');
const client = createWalletClient({ account, chain: sei, transport: http() });
diff --git a/evm/evm-parity/websocket.mdx b/evm/evm-parity/websocket.mdx
index e1850a5..a1315a6 100644
--- a/evm/evm-parity/websocket.mdx
+++ b/evm/evm-parity/websocket.mdx
@@ -21,7 +21,7 @@ Sei supports `eth_subscribe` over WebSocket. You can subscribe to new blocks, ev
```ts viem
import { createPublicClient, webSocket } from 'viem';
-import { sei } from '@sei-js/precompiles/viem';
+import { sei } from 'viem/chains';
const client = createPublicClient({
chain: sei,
diff --git a/evm/networks.mdx b/evm/networks.mdx
index fa2aa7e..f880fb9 100644
--- a/evm/networks.mdx
+++ b/evm/networks.mdx
@@ -11,3 +11,8 @@ keywords: ["sei evm", "network information", "chain ids", "rpc endpoints", "bloc
loading="lazy"
title="Sei EVM chain information"
/>
+
+The panel above is interactive. For plain-text endpoints you can copy directly — plus additional provider options — see:
+
+- [Chains & endpoints](/learn/dev-chains) — RPC URLs and chain IDs in plain text
+- [RPC providers](/learn/rpc-providers) — additional public and commercial RPC endpoints
diff --git a/evm/precompiles/example-usage.mdx b/evm/precompiles/example-usage.mdx
index 058d1ed..8acca96 100644
--- a/evm/precompiles/example-usage.mdx
+++ b/evm/precompiles/example-usage.mdx
@@ -30,7 +30,7 @@ npm install viem ethers @sei-js/precompiles
```ts viem
import { createPublicClient, createWalletClient, http, custom } from 'viem';
-import { sei } from '@sei-js/precompiles/viem';
+import { sei } from 'viem/chains';
// Read-only
const client = createPublicClient({ chain: sei, transport: http() });
diff --git a/evm/python-quickstart.mdx b/evm/python-quickstart.mdx
new file mode 100644
index 0000000..6bc06f0
--- /dev/null
+++ b/evm/python-quickstart.mdx
@@ -0,0 +1,109 @@
+---
+title: 'Python Quickstart (web3.py)'
+sidebarTitle: 'Python (web3.py)'
+description: 'Connect to Sei from Python using web3.py — read chain data, then sign and broadcast transactions against the Sei EVM JSON-RPC.'
+keywords: ['sei evm', 'python', 'web3.py', 'json-rpc', 'quickstart']
+---
+
+# Python Quickstart (web3.py)
+
+Sei is fully EVM-compatible, so any standard Ethereum client works — including Python's [web3.py](https://web3py.readthedocs.io). This guide connects to Sei from a Python script, reads chain data with no credentials, then signs and broadcasts a transaction.
+
+## Install
+
+We recommend an isolated virtual environment:
+
+```bash
+python3 -m venv venv
+source venv/bin/activate # Windows: venv\Scripts\activate
+pip install web3
+```
+
+## Reading Chain Data
+
+This is the first milestone — it needs no private key.
+
+```python
+from web3 import Web3
+
+w3 = Web3(Web3.HTTPProvider("https://evm-rpc.sei-apis.com"))
+
+# Any valid Sei EVM address — swap in your own
+address = "0x0000000000000000000000000000000000000000"
+
+print("Connected:", w3.is_connected())
+print("Chain ID:", w3.eth.chain_id) # 1329 on mainnet
+print("Latest block:", w3.eth.block_number)
+print("Balance (SEI):", w3.from_wei(w3.eth.get_balance(address), "ether"))
+print("Nonce:", w3.eth.get_transaction_count(address))
+```
+
+**You're done when you see:**
+
+```text
+Connected: True
+Chain ID: 1329
+Latest block: 148203117
+Balance (SEI): 12.5
+Nonce: 7
+```
+
+Values are illustrative — your block number, and the queried address's balance and nonce, will differ. The chain ID is always `1329` on mainnet (`1328` on the atlantic-2 testnet).
+
+## Sending a Transaction
+
+Next step — this requires a funded account. Get testnet SEI from the [faucet](/learn/faucet), then build, **sign locally**, and broadcast with `eth_sendRawTransaction`. Public RPC endpoints don't manage accounts, so you sign the transaction yourself rather than relying on node-managed accounts.
+
+```python
+import os
+from web3 import Web3
+
+# atlantic-2 testnet
+w3 = Web3(Web3.HTTPProvider("https://evm-rpc-testnet.sei-apis.com"))
+
+account = w3.eth.account.from_key(os.environ["SEI_PRIVATE_KEY"])
+
+tx = {
+ "to": "0xRecipient",
+ "value": w3.to_wei(0.001, "ether"),
+ "nonce": w3.eth.get_transaction_count(account.address),
+ "chainId": w3.eth.chain_id,
+ "type": 2, # EIP-1559
+ "maxPriorityFeePerGas": w3.to_wei(1, "gwei"),
+ "maxFeePerGas": w3.eth.gas_price * 2 + w3.to_wei(1, "gwei"),
+}
+tx["gas"] = w3.eth.estimate_gas(tx) # always estimate rather than hard-coding
+
+signed = w3.eth.account.sign_transaction(tx, account.key)
+tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
+
+receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
+print("Mined in block:", receipt.blockNumber) # final immediately — Sei has instant finality
+```
+
+Never hard-code or commit a private key. Load it from an environment variable or a secrets manager, and use a dedicated throwaway key funded with only what you need.
+
+`signed.raw_transaction` is the attribute name in web3.py v7+. On web3.py v6 it is `signed.rawTransaction`.
+
+## Reading a Contract
+
+```python
+abi = [{
+ "constant": True,
+ "inputs": [{"name": "owner", "type": "address"}],
+ "name": "balanceOf",
+ "outputs": [{"name": "", "type": "uint256"}],
+ "stateMutability": "view",
+ "type": "function",
+}]
+
+token = w3.eth.contract(address="0xTokenAddress", abi=abi)
+print(token.functions.balanceOf(address).call())
+```
+
+## Next Steps
+
+- [viem Quickstart](/evm/evm-parity/examples/viem-quickstart) — the TypeScript/Node.js equivalent
+- [ethers v6 Quickstart](/evm/evm-parity/examples/ethers-quickstart) — TypeScript with ethers
+- [Network information](/evm/networks) — RPC endpoints, chain IDs, and explorers
+- [Faucet](/learn/faucet) — get testnet SEI