# Agent Skills Source: https://l2docs.whitechain.io/build-with-ai/claude-skills Install the whitechain-dev Agent Skill, a testnet developer playbook for connecting, deploying, verifying, and inspecting contracts on Whitechain Sepolia, agnostic of which AI agent you run. [Agent Skills](https://agentskills.io) package Whitechain-specific instructions and reference files that an AI agent loads on demand, rather than relying on the model's general OP Stack knowledge. The `whitechain-dev` skill is a testnet developer playbook. It is not tied to one vendor's agent: any agent that supports the Agent Skills format, including Claude Code, can load it. It assumes you control your own signing key, for example a Foundry keystore or an environment variable, and covers network setup, contract deployment, verification, and chain inspection on Whitechain Sepolia. The skill does not cover token transfers, swaps, signing messages, or bridging. Support for those end-user wallet actions is planned separately. ## What it covers | Task category | Examples | | ------------- | ---------------------------------------------------------------- | | Connect | Wallet and network config, chain ID, RPC URL | | Investigate | Check addresses and balances, trace transactions, network status | | Contracts | Deploy, call, and verify contracts | | Faucet | Claim testnet WBT | | Nodes | Run a Whitechain node | ## Safety guardrails The skill never accepts a private key pasted in chat; it expects a Foundry encrypted keystore or an environment variable. Before any write operation, such as a contract call or deployment, it shows a summary and waits for explicit confirmation. It validates the chain ID before signing, to confirm the target is Whitechain Sepolia (`1874`), and validates shell inputs before building `forge`, `cast`, or `solc` commands. ## Install and use 1. Install the skill: ```bash theme={null} npx skills add whitechain-labs/skills --skill whitechain-dev ``` 2. Ask your agent to perform a Whitechain task in plain language, for example: "Deploy an ERC-20 token to Whitechain Sepolia and verify it." A compatible agent, such as Claude Code, loads the skill automatically when it recognizes the task. ## Related * [Feed Whitechain docs to AI tools](/build-with-ai/llms-txt) * [Deploy with Hardhat](/build/deploy/deploy-with-hardhat) # Build with AI Source: https://l2docs.whitechain.io/build-with-ai/index Resources for AI coding assistants and autonomous agents building on Whitechain: static docs files and installable Agent Skills, agnostic of which agent you run. Use this page as the starting point when you want an AI coding assistant or an autonomous agent to build on Whitechain. The skills and files below are agent-agnostic: they work with Claude Code, Cursor, Codex, or any other agent that can install a skill, not one vendor's tool specifically. ## Quick setup Pick the method that fits your tool. ### Static docs files Use these when you want to load context in a single fetch rather than maintaining a live connection. | File | What it contains | When to use it | | ----------------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------ | | [llms.txt](https://l2docs.whitechain.io/llms.txt) | Page index with titles and descriptions | Discovering which docs exist before going deeper | | [llms-full.txt](https://l2docs.whitechain.io/llms-full.txt) | Full content of every current-network page in one file | Giving an assistant broad context in one shot | Every page is also available as plain Markdown: append `.md` to any URL, for example `/learn/network/reference.md`. See [Feed Whitechain docs to AI tools](/build-with-ai/llms-txt). ### Skills Skills are installable, agent-agnostic workflows for Whitechain tasks: connecting to the network, deploying and verifying contracts, claiming testnet WBT, and running a node. They give an agent step-by-step procedural guidance instead of requiring it to piece the steps together from prose docs. ```bash theme={null} npx skills add whitechain-labs/skills --skill whitechain-dev ``` See [Agent Skills](/build-with-ai/claude-skills) for what the skill covers and its safety guardrails. ## Recommended starting points | What you're doing | Start here | | -------------------------------------------------- | -------------------------------------------------------- | | Installing the developer skill | [Agent Skills](/build-with-ai/claude-skills) | | Getting exact chain IDs and endpoints for a prompt | [Network reference](/learn/network/reference) | | Moving assets between Ethereum and Whitechain | [OP Stack canonical bridge](/build/bridge/bridge-assets) | | Running a node | [Run a node](/operate/run-a-node/overview) | ## Example prompts Copy one of these into your assistant to check that it has the right context: * "Check the WBT balance of 0xYourAddress on Whitechain Sepolia." * "Deploy a minimal ERC-20 to Whitechain Sepolia and verify it on Blockscout." * "Claim testnet WBT from the Whitechain Sepolia faucet for 0xYourAddress." * "Trace transaction 0xYourTxHash on Whitechain Sepolia and tell me whether it succeeded." # Feed Whitechain docs to AI tools Source: https://l2docs.whitechain.io/build-with-ai/llms-txt Whitechain docs publish llms.txt, llms-full.txt, and a markdown version of every page, so AI assistants can read accurate context directly instead of scraping rendered HTML. Whitechain docs publish machine-readable versions of every page alongside the rendered site. Point an AI assistant at these instead of pasting page text into a chat. ## What is available | Resource | URL | Contents | | ------------- | -------------------------------------------------------------------------------- | -------------------------------------------- | | llms.txt | [l2docs.whitechain.io/llms.txt](https://l2docs.whitechain.io/llms.txt) | Index of every page with a short description | | llms-full.txt | [l2docs.whitechain.io/llms-full.txt](https://l2docs.whitechain.io/llms-full.txt) | Full content of every page in one file | | Markdown page | any page URL with `.md` appended | Markdown source of that single page | For example, the network reference page at `/learn/network/reference` is also available as `/learn/network/reference.md`. llms.txt and llms-full.txt cover the current Whitechain L2 network. Pages under **Legacy**, which document the retired Whitechain L1 chain, are marked `noindex` and excluded from both files to keep them focused on the network you are building on. Legacy pages are still visible on the site and reachable by direct link. ## Use the page menu Every page includes a page-options menu that copies the page as markdown or opens it directly in ChatGPT, Claude, Perplexity, Cursor, or VS Code. Use it instead of copying rendered text by hand. ## Related * [Build with AI](/build-with-ai) * [Agent Skills](/build-with-ai/claude-skills) * [Deploy with Hardhat](/build/deploy/deploy-with-hardhat) # Gas and network tracker example Source: https://l2docs.whitechain.io/build/block-explorer/indexer-gas-tracker Track live gas tiers, coin price, fees, and network stats on Whitechain Sepolia with the Blockscout Etherscan-compatible RPC API and REST v2 stats, with copy-paste requests and real responses. All requests on this page target the Whitechain Sepolia explorer at `https://explorer.testnet.whitechain.io` (chain id 1874). The API is public, read-only, and needs no API key. This dashboard reads chain-wide metrics, so it takes no address input. Every request below has a **Run this request** button. It sends that exact request from your browser to Whitechain Sepolia and prints the live response, so you can compare it with the sample response above it. The requests are read-only GET calls with no key and no wallet connection. ## What you can build A network tracker reads two Blockscout API surfaces to show: * Slow, average, and fast gas tiers in Gwei. * Coin price in USD and BTC, and circulating supply. * Transaction fees for the most recent completed day. * Chain counters: total transactions, addresses, average block time, and a live chain head. ## API surfaces and base URLs | Surface | Base URL | Use it for | | ------------------------------ | -------------------------------------------------------------- | --------------------------------------------------- | | RPC API (Etherscan-compatible) | `https://explorer.testnet.whitechain.io/api?module=…&action=…` | Coin price, supply, daily fees, and the chain head. | | REST API v2 | `https://explorer.testnet.whitechain.io/api/v2` | The gas tiers and the aggregate network counters. | The Etherscan-compatible module set is `account`, `block`, `contract`, `logs`, `stats`, `token`, and `transaction`. It has no gas-oracle action, so the gas tiers come from REST v2 `/stats`, which is the canonical source for them. A client written against the Etherscan API works against the RPC surface unchanged. ## Endpoint map | Task | Interface | Request | | ----------------------------------- | --------- | -------------------------------------------------------- | | Coin price (USD, BTC) | RPC API | `GET /api?module=stats&action=coinprice` | | Circulating supply | RPC API | `GET /api?module=stats&action=coinsupply` | | Daily transaction fees | RPC API | `GET /api?module=stats&action=totalfees&date=YYYY-MM-DD` | | Chain head | RPC API | `GET /api?module=block&action=eth_block_number` | | Slow, average, fast gas tiers | REST API | `GET /api/v2/stats` (`gas_prices`) | | Transactions, addresses, block time | REST API | `GET /api/v2/stats` | ## Reading headline metrics with the RPC API Send GET requests to `/api` and select the call with `module` and `action`. Most actions return a `{ status, message, result }` envelope; `eth_block_number` returns a JSON-RPC envelope. Read `result` in both cases. ### Coin price ```bash Request theme={null} curl "https://explorer.testnet.whitechain.io/api?module=stats&action=coinprice" \ -H "accept: application/json" ``` ```json Response theme={null} { "status": "1", "message": "OK", "result": { "coin_usd": "57.38", "coin_btc": "0.00087178", "coin_usd_timestamp": "1784713251", "coin_btc_timestamp": "1784713251" } } ``` Read `coin_usd` and `coin_btc` for the price cards. The timestamps are Unix seconds marking when each price was set. ### Circulating supply ```bash Request theme={null} curl "https://explorer.testnet.whitechain.io/api?module=stats&action=coinsupply" \ -H "accept: application/json" ``` ```json Response theme={null} { "status": "1", "message": "OK", "result": "116244402085899461811944309200000000000000000000000000000000" } ``` `result` is the circulating supply in wei as a string. Divide by 10^18 for WBT, and use big-integer or decimal math because the value overflows a JavaScript number. ### Daily transaction fees ```bash Request theme={null} curl "https://explorer.testnet.whitechain.io/api?module=stats&action=totalfees&date=2026-07-21" \ -H "accept: application/json" ``` ```json Response theme={null} { "status": "1", "message": "OK", "result": "129648734562000000" } ``` `result` is the total fees paid that day, in wei. Here `129648734562000000` is about 0.1296 WBT. `totalfees` is aggregated per completed day, so the current day reads `0` until it closes. Query the previous completed day for a real value. ### Chain head Poll this on a short interval for a live block-height indicator. ```bash Request theme={null} curl "https://explorer.testnet.whitechain.io/api?module=block&action=eth_block_number" \ -H "accept: application/json" ``` ```json Response theme={null} { "jsonrpc": "2.0", "id": 1, "result": "0x2f915e" } ``` `result` is a hex quantity. `0x2f915e` is block `3117406`. Note this call returns a JSON-RPC envelope, not the `{ status, message, result }` shape the other RPC actions use. ## Reading gas tiers and counters with REST v2 One request to `/api/v2/stats` covers the gas tiers and the network counters. ```bash Request theme={null} curl "https://explorer.testnet.whitechain.io/api/v2/stats" \ -H "accept: application/json" ``` ```json Response (trimmed) theme={null} { "gas_prices": { "slow": 4.38, "average": 4.38, "fast": 4.38 }, "gas_prices_update_in": 15173, "average_block_time": 1000, "total_blocks": "3110938", "total_transactions": "3594744", "total_addresses": "69677", "transactions_today": "86581", "network_utilization_percentage": 0.1178324, "gas_used_today": "4098201458", "coin_price": "57.38" } ``` | Field | Meaning | How to use it | | --------------------------------------------------------- | ---------------------------------------- | --------------------------------------------------------------------------------------- | | `gas_prices.slow` / `.average` / `.fast` | Suggested gas prices in Gwei | Populate the three tier cards. Tiers can be equal on a quiet chain, as here (all 4.38). | | `gas_prices_update_in` | Milliseconds until the next tier refresh | Time your own refresh to match. | | `average_block_time` | Mean block time in milliseconds | Here 1000, so one block per second. | | `total_blocks` / `total_transactions` / `total_addresses` | Chain totals as strings | Parse before formatting. | | `network_utilization_percentage` | Recent utilization, 0 to 1 | Multiply by 100 for a percentage. | ## Errors An RPC action that fails returns `status` `"0"` with a `message` describing the problem, while `result` is empty; check `status` before trusting `result`. An empty result set (for example, a day with no fees) is not an error. REST v2 returns HTTP `422` with an `errors` array for a malformed request, where each entry carries a `title`, a `source.pointer` naming the bad field, and a `detail` string. ## Refresh cadence The metrics change at different rates, so refresh them on separate timers. Poll `eth_block_number` about once per second for a live head. Refetch the price, supply, fees, and `/api/v2/stats` together on a slower timer, for example every 15 seconds, which also lines up with `gas_prices_update_in`. ## Related * [Block explorer](/build/block-explorer/overview) * [Wallet indexing example](/build/block-explorer/indexer-wallet) * [Token dashboard indexing example](/build/block-explorer/indexer-token-dashboard) * [Network fees](/learn/network/network-fees) # Token dashboard indexing example Source: https://l2docs.whitechain.io/build/block-explorer/indexer-token-dashboard Show token metadata, holders, and a paginated transfer feed for any token on Whitechain Sepolia with the Blockscout GraphQL and REST v2 APIs, with copy-paste requests and real responses. All requests on this page target the Whitechain Sepolia explorer at `https://explorer.testnet.whitechain.io` (chain id 1874). The API is public, read-only, and needs no API key. The examples use the real testnet token `0x071c373d58A5290982a0E916D529a27849baE6e0` (USDW, an ERC-20 with 6 decimals). Replace it with the token contract address you want to read. The holder and transfer addresses in the sample responses, such as the example address `0xA439Ad519046CCd7056Ddf74fbaAc99d740Bdf09`, are wallets rather than contracts, so they carry no bytecode. Every request below has a **Run this request** button. It sends that exact request from your browser to Whitechain Sepolia and prints the live response, so you can compare it with the sample response above it. Each button carries its own input field, so editing the token address in one runner does not change the others. The requests are read-only GET and POST calls with no key and no wallet connection, and nothing you enter leaves your browser except the address in the request. ## What you can build A token dashboard takes one token contract address and reads two Blockscout API surfaces to show: * Metadata: name, symbol, decimals, total supply, and price when the explorer has one. * Two counters: current holders (non-zero balances) and lifetime transfers. * The top holders by balance. * A paginated transfer feed. ## API surfaces and base URLs | Surface | Base URL | Use it for | | ----------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | | GraphQL API | `https://explorer.testnet.whitechain.io/api/v1/graphql` | The transfer feed and per-address lookups. One POST returns exactly the fields you ask for, with cursor pagination. | | REST API v2 | `https://explorer.testnet.whitechain.io/api/v2` | Token metadata, the holders list, and the counters. | The split is deliberate. The Blockscout GraphQL schema has no `token` query, so metadata, holders, and counters come from REST v2. GraphQL owns the transfer feed, where field selection and cursor pagination help most. ## Endpoint map | Task | Interface | Request | | ------------------------------------- | ----------- | -------------------------------------------- | | Name, symbol, decimals, supply, price | REST API | `GET /api/v2/tokens/{hash}` | | Holder and transfer counts | REST API | `GET /api/v2/tokens/{hash}/counters` | | Top holders | REST API | `GET /api/v2/tokens/{hash}/holders` | | Transfer feed (paginated) | GraphQL API | `POST /api/v1/graphql` with `tokenTransfers` | | Single address lookup | GraphQL API | `POST /api/v1/graphql` with `address` | ## Reading metadata, holders, and counters with REST v2 Send GET requests with an `accept: application/json` header. Responses below are trimmed to the fields a dashboard reads. ### Token metadata ```bash Request theme={null} curl "https://explorer.testnet.whitechain.io/api/v2/tokens/0x071c373d58A5290982a0E916D529a27849baE6e0" \ -H "accept: application/json" ``` ```json Response (trimmed) theme={null} { "address_hash": "0x071c373d58A5290982a0E916D529a27849baE6e0", "name": "USDW", "symbol": "USDW", "decimals": "6", "type": "ERC-20", "total_supply": "10000000013605001000", "exchange_rate": null } ``` | Field | Meaning | How to use it | | --------------- | -------------------------------- | ------------------------------------------------------------ | | `decimals` | Fractional digits for this token | Divide every raw amount by 10^`decimals`. Here that is 10^6. | | `total_supply` | Raw total supply | Divide by 10^`decimals` for the display supply. | | `type` | Token standard | `ERC-20`, `ERC-721`, or `ERC-1155`. | | `exchange_rate` | USD price, or `null` | Show a price only when it is not `null`. | ### Counters ```bash Request theme={null} curl "https://explorer.testnet.whitechain.io/api/v2/tokens/0x071c373d58A5290982a0E916D529a27849baE6e0/counters" \ -H "accept: application/json" ``` ```json Response theme={null} { "token_holders_count": "5", "transfers_count": "31" } ``` Use `token_holders_count` for the holders card and `transfers_count` for the transfers card. Both are strings; parse them before formatting. ### Top holders ```bash Request theme={null} curl "https://explorer.testnet.whitechain.io/api/v2/tokens/0x071c373d58A5290982a0E916D529a27849baE6e0/holders" \ -H "accept: application/json" ``` ```json Response (trimmed to two items) theme={null} { "items": [ { "address": { "hash": "0xA439Ad519046CCd7056Ddf74fbaAc99d740Bdf09", "is_contract": false }, "value": "10000000000000000000" }, { "address": { "hash": "0x6e057133CFa4a9Ec70c77aaFe29751460FE16307", "name": "UniswapV3Pool", "is_contract": true }, "value": "2792871783" } ], "next_page_params": null } ``` Each item pairs an `address` with a raw `value`. Format `value` against the token `decimals` from the metadata call, and keep full precision so small balances stay visible. Show `address.name` (such as `UniswapV3Pool`) when present, otherwise the raw hash. ## Reading the transfer feed with GraphQL Send POST requests to `https://explorer.testnet.whitechain.io/api/v1/graphql` with a `content-type: application/json` body holding a `query` and its `variables`. You get back only the fields you request. ### The transfers query ```bash Request theme={null} curl "https://explorer.testnet.whitechain.io/api/v1/graphql" \ -H "content-type: application/json" \ -d '{ "query": "query Transfers($token: AddressHash!, $first: Int!, $after: String) { tokenTransfers(tokenContractAddressHash: $token, first: $first, after: $after) { edges { node { amount fromAddressHash toAddressHash transactionHash tokenIds } } pageInfo { hasNextPage endCursor } } }", "variables": { "token": "0x071c373d58A5290982a0E916D529a27849baE6e0", "first": 8, "after": null } }' ``` ```json Response (trimmed to one edge) theme={null} { "data": { "tokenTransfers": { "edges": [ { "node": { "amount": "207128221", "fromAddressHash": "0x6e057133CFa4a9Ec70c77aaFe29751460FE16307", "toAddressHash": "0xE637dc119ADAEb23e72f689183Cf6F60a52773Aa", "transactionHash": "0xb97ceb21417fe4a7d94162f99b3f2d8c5f7bc3b5ca54ac0b45fadb15c05c3b57", "tokenIds": null } } ], "pageInfo": { "hasNextPage": true, "endCursor": "WyIyNjE3MzMwIiwxXQ==" } } } } ``` | Field | Meaning | How to use it | | ----------------------------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------- | | `amount` | Raw amount moved | Divide by the token `decimals`. Here `207128221` is 207.128221 USDW. | | `fromAddressHash` / `toAddressHash` | Sender and recipient | Render both; a mint shows `from` as the zero address `0x0000000000000000000000000000000000000000`. | | `transactionHash` | The transaction the transfer belongs to | Link to `/tx/{hash}` on the explorer. | | `tokenIds` | NFT ids, or `null` | `null` for ERC-20. Set for ERC-721 and ERC-1155. | The GraphQL server caps operation complexity at 100, and `tokenTransfers` costs about 11 per item, so request 8 at a time (`first: 8`). Explore the full schema at [the GraphQL API docs](https://explorer.testnet.whitechain.io/api-docs?tab=graphql_api). ### Address lookup Use the `address` query when you need one address's native balance and whether it holds contract code, for example to annotate a holder row. ```bash Request theme={null} curl "https://explorer.testnet.whitechain.io/api/v1/graphql" \ -H "content-type: application/json" \ -d '{ "query": "query AddressLookup($hash: AddressHash!) { address(hash: $hash) { hash fetchedCoinBalance contractCode } }", "variables": { "hash": "0xA439Ad519046CCd7056Ddf74fbaAc99d740Bdf09" } }' ``` ```json Response theme={null} { "data": { "address": { "hash": "0xA439Ad519046CCd7056Ddf74fbaAc99d740Bdf09", "fetchedCoinBalance": "177504708847852442", "contractCode": null } } } ``` A `null` `contractCode` means the address is a wallet, not a contract. `fetchedCoinBalance` is the native balance in wei; divide by 10^18 for WBT. ## Pagination The transfer feed pages with an opaque cursor. Read `pageInfo.hasNextPage` and `pageInfo.endCursor` from a response, then send that `endCursor` back as the `after` variable to fetch the next page. Stop when `hasNextPage` is `false`. Do not build or parse the cursor yourself; treat it as opaque. ## Errors GraphQL always returns HTTP `200`. On failure the body has an `errors` array instead of (or alongside) `data`, so check for `errors` before reading `data`. An operation above the complexity cap returns such an error, which is why the page requests 8 transfers at a time. REST v2 returns HTTP `422` with an `errors` array for a malformed token address or parameter, where each entry carries a `title`, a `source.pointer` naming the bad field, and a `detail` string. ## Load sequence For a full dashboard, issue the three REST reads (metadata, counters, holders) and the first GraphQL transfers page in parallel; none depend on another. Then fetch further transfer pages on demand with the cursor. Refetch everything when the user enters a different token address. ## Related * [Block explorer](/build/block-explorer/overview) * [Wallet indexing example](/build/block-explorer/indexer-wallet) * [Gas and network tracker example](/build/block-explorer/indexer-gas-tracker) * [Blockscout API reference](https://explorer.testnet.whitechain.io/api-docs) # Wallet indexing example Source: https://l2docs.whitechain.io/build/block-explorer/indexer-wallet Read balances, token holdings, and transaction history for any address on Whitechain Sepolia with the Blockscout REST v2 API and ETH JSON-RPC, with copy-paste requests and real responses. All requests on this page target the Whitechain Sepolia explorer at `https://explorer.testnet.whitechain.io` (chain id 1874). The API is public, read-only, and needs no API key, so a wallet view runs fully client-side with no backend and no node of your own. Replace the example address `0xA439Ad519046CCd7056Ddf74fbaAc99d740Bdf09` with the address you want to read. Every request below has a **Run this request** button. It sends that exact request from your browser to Whitechain Sepolia and prints the live response, so you can compare it with the sample response above it. Each button carries its own address field, so editing the address in one runner does not change the others. The requests are read-only GET and POST calls with no key and no wallet connection, and nothing you enter leaves your browser except the address in the URL. ## What you can read A wallet view combines two Blockscout API surfaces to answer four questions about an address: * What is its balance? Both the indexed balance and the live node balance, in WBT. * What tokens does it hold? Every ERC-20, ERC-721, and ERC-1155 balance, with symbol and decimals. * What has it done? A paginated transaction history with method, direction, value, fee, and status. * Where is the chain now? The latest block height, for a live head indicator. ## API surfaces and base URLs | Surface | Base URL | Use it for | | ------------ | ---------------------------------------------------- | ------------------------------------------------------------------------------------- | | REST API v2 | `https://explorer.testnet.whitechain.io/api/v2` | The indexed view. Returns decoded JSON: address record, token balances, transactions. | | ETH JSON-RPC | `https://explorer.testnet.whitechain.io/api/eth-rpc` | The live node view. Standard Ethereum methods that return canonical chain state. | REST v2 answers "what does the indexer know", and it returns rich pre-decoded objects. JSON-RPC answers "what is true on the node right now", and it returns raw hex quantities. A wallet view reads both and shows them side by side. ## Endpoint map | Task | Interface | Request | | ------------------------ | ----------- | -------------------------------------------------- | | Address summary and type | REST API | `GET /api/v2/addresses/{hash}` | | Token holdings | REST API | `GET /api/v2/addresses/{hash}/token-balances` | | Transaction history | REST API | `GET /api/v2/addresses/{hash}/transactions` | | Live native balance | ETH RPC API | `POST /api/eth-rpc` with `eth_getBalance` | | Nonce (outbound count) | ETH RPC API | `POST /api/eth-rpc` with `eth_getTransactionCount` | | Chain head | ETH RPC API | `POST /api/eth-rpc` with `eth_blockNumber` | ## Reading with the REST API v2 Send GET requests with an `accept: application/json` header. The examples below use the real testnet address above; the responses are trimmed to the fields a wallet view reads. ### Address summary Use this to show the indexed balance and to label the address a wallet or a contract. ```bash Request theme={null} curl "https://explorer.testnet.whitechain.io/api/v2/addresses/0xA439Ad519046CCd7056Ddf74fbaAc99d740Bdf09" \ -H "accept: application/json" ``` ```json Response (trimmed) theme={null} { "hash": "0xA439Ad519046CCd7056Ddf74fbaAc99d740Bdf09", "coin_balance": "177504708847852442", "exchange_rate": "57.52", "is_contract": false, "is_verified": false, "ens_domain_name": null, "block_number_balance_updated_at": 3113184 } ``` | Field | Meaning | How to use it | | ----------------- | --------------------------------------- | --------------------------------------------------------------------- | | `coin_balance` | Indexed native balance, in wei | Divide by 10^18 for WBT. Here: 0.1775 WBT. | | `exchange_rate` | WBT price in USD at index time | Multiply by the WBT amount for a USD estimate. Here: about 10.21 USD. | | `is_contract` | Whether the address holds contract code | Label the address a contract or a wallet. | | `is_verified` | Whether a contract's source is verified | Show a verified badge for contracts. | | `ens_domain_name` | Primary name, or `null` | Show in place of the raw hash when present. | ### Token holdings Use this to list every fungible and non-fungible balance the address holds. ```bash Request theme={null} curl "https://explorer.testnet.whitechain.io/api/v2/addresses/0xA439Ad519046CCd7056Ddf74fbaAc99d740Bdf09/token-balances" \ -H "accept: application/json" ``` ```json Response (trimmed) theme={null} [ { "value": "10000000000000000000", "token_id": null, "token": { "address_hash": "0x071c373d58A5290982a0E916D529a27849baE6e0", "name": "USDW", "symbol": "USDW", "decimals": "6", "type": "ERC-20" } } ] ``` The response is a flat array, one entry per token. Format each `value` against its own `token.decimals` (they differ per token, so never assume 18). For `ERC-721` and `ERC-1155` entries, `token_id` is set and `type` names the standard. An empty array (`[]`) means the address holds no tokens. ### Transaction history Use this for the activity list. The response is newest first. ```bash Request theme={null} curl "https://explorer.testnet.whitechain.io/api/v2/addresses/0xA439Ad519046CCd7056Ddf74fbaAc99d740Bdf09/transactions" \ -H "accept: application/json" ``` ```json Response (trimmed to one item) theme={null} { "items": [ { "hash": "0xf3d44e187cf95cf227a253cf983c45b8ec76eba1c3177a79b46e31de9083c487", "timestamp": "2026-07-16T11:13:21.000000Z", "from": { "hash": "0xA439Ad519046CCd7056Ddf74fbaAc99d740Bdf09" }, "to": { "hash": "0xC880eF22c01184a3Db08F2c306684311C48cB495", "name": "Storage", "is_contract": true }, "value": "0", "fee": { "type": "actual", "value": "134555036000360" }, "method": "store", "result": "success", "block_number": 2604033, "confirmations": 512671, "transaction_types": ["contract_call"] } ], "next_page_params": null } ``` | Field | Meaning | How to use it | | -------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- | | `from.hash` / `to.hash` | Sender and recipient | Compare with the queried address to set direction: outbound if it matches `from`, inbound if it matches `to`. | | `to.name` | Contract name when the target is a known contract | Show "store on Storage" instead of a bare hash. | | `value` | Native WBT moved, in wei | Divide by 10^18. `0` for pure contract calls. | | `fee.value` | Fee paid, in wei (`fee.type` is `actual` or `maximum`) | Divide by 10^18 for the WBT fee. | | `method` | Decoded function name, or `null` | Show the action; `null` for a plain transfer or contract creation. | | `result` | `success` or an error string | Drive a status badge. | | `block_number` / `confirmations` | Inclusion height and depth | Link to the block; show confirmation count. | You can narrow the history with query parameters: | Parameter | Endpoint | Values | Effect | | --------- | --------------------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------- | | `filter` | `/api/v2/addresses/{hash}/transactions` | `to`, `from` | Keep only inbound or only outbound transactions for the address. | | `filter` | `/api/v2/transactions` (chain-wide) | `pending`, `validated` | List mempool or mined transactions across the chain. | | `type` | `/api/v2/transactions` | `token_transfer`, `contract_creation`, `contract_call` | Keep only that transaction category. | | `method` | `/api/v2/transactions` | e.g. `approve`, `transfer`, `multicall`, `mint`, `commit` | Keep only calls to that decoded method. | For example, outbound transactions only: ```bash Request theme={null} curl "https://explorer.testnet.whitechain.io/api/v2/addresses/0xA439Ad519046CCd7056Ddf74fbaAc99d740Bdf09/transactions?filter=from" \ -H "accept: application/json" ``` ## Reading with the ETH JSON-RPC API Send POST requests to `https://explorer.testnet.whitechain.io/api/eth-rpc` with a JSON-RPC body. Every method here returns a hex quantity in `result`; convert it to a number before display. ### Live native balance Compare this with the indexed `coin_balance` from the address summary. They match once the indexer catches up to the node. ```bash Request theme={null} curl "https://explorer.testnet.whitechain.io/api/eth-rpc" \ -H "content-type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"eth_getBalance","params":["0xA439Ad519046CCd7056Ddf74fbaAc99d740Bdf09","latest"]}' ``` ```json Response theme={null} { "jsonrpc": "2.0", "id": 1, "result": "0x2769f979cd5db9a" } ``` `0x2769f979cd5db9a` is `177504708847852442` wei, or about 0.1775 WBT. ### Nonce The nonce is the number of transactions the address has sent. Read it to show account activity or to build a raw transaction. ```bash Request theme={null} curl "https://explorer.testnet.whitechain.io/api/eth-rpc" \ -H "content-type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"eth_getTransactionCount","params":["0xA439Ad519046CCd7056Ddf74fbaAc99d740Bdf09","latest"]}' ``` ```json Response theme={null} { "jsonrpc": "2.0", "id": 1, "result": "0x1e" } ``` `0x1e` is `30`, so this address has sent 30 transactions. ### Chain head Poll this on a short interval for a live block-height indicator. ```bash Request theme={null} curl "https://explorer.testnet.whitechain.io/api/eth-rpc" \ -H "content-type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}' ``` ```json Response theme={null} { "jsonrpc": "2.0", "id": 1, "result": "0x2f80e0" } ``` `0x2f80e0` is block `3113184`. ## Pagination REST v2 list endpoints return `next_page_params`. When it is `null`, you have the last page. When it is an object, pass its fields back as query parameters on the same endpoint to get the next page. ```bash Request (next page of transactions) theme={null} curl "https://explorer.testnet.whitechain.io/api/v2/addresses/0xA439Ad519046CCd7056Ddf74fbaAc99d740Bdf09/transactions?block_number=2427207&index=1" \ -H "accept: application/json" ``` The exact keys inside `next_page_params` vary by endpoint (transactions page by `block_number` and `index`), so copy whatever keys the previous response returned rather than hardcoding them. ## Errors A malformed address or query parameter returns HTTP `422` with an `errors` array describing the bad input. Run any request above with a broken address to see it: ```json Response (HTTP 422) theme={null} { "errors": [ { "title": "Invalid value", "source": { "pointer": "/address_hash_param" }, "detail": "Invalid format. Expected ~r/^0x([A-Fa-f0-9]{40})$/" } ] } ``` A well-formed address the indexer has never seen is not an error: it returns HTTP `200` with `null` fields. JSON-RPC calls return HTTP `200` with an `error` object instead of `result` when the request is invalid, so check for `error` before reading `result`. Handle every path so a bad address shows a clear message rather than an empty view. ## Load sequence For a full wallet view, issue all six requests in parallel: the three REST reads (summary, token balances, transactions) and the three JSON-RPC reads (balance, nonce, chain head). None depend on another, so a single batch fills every card at once. Poll only `eth_blockNumber` afterward for the live head; refetch the rest when the user changes the address. ## Related * [Block explorer](/build/block-explorer/overview) * [Token dashboard indexing example](/build/block-explorer/indexer-token-dashboard) * [Gas and network tracker example](/build/block-explorer/indexer-gas-tracker) * [Blockscout API reference](https://explorer.testnet.whitechain.io/api-docs) # Overview Source: https://l2docs.whitechain.io/build/block-explorer/overview Inspect Whitechain transactions, addresses, and verified contracts with Blockscout, and read chain data through its API. Available on Testnet today. The Mainnet explorer is published at Mainnet launch. ## Blockscout Whitechain uses Blockscout as its block explorer. In a browser you can inspect transactions, addresses, blocks, and token transfers. For a verified contract you can read the source, call read methods, and submit write methods. | Network | Explorer | Availability | | ------------------ | ------------------------------------------------------------------------ | ------------- | | Whitechain Sepolia | [explorer.testnet.whitechain.io](https://explorer.testnet.whitechain.io) | Available now | | Whitechain Mainnet | Published at Mainnet launch | Pending | ## Reading chain data with the API Blockscout exposes an HTTP API that returns the same data the browser shows: balances, transactions, logs, token transfers, and verified contract details. It offers four interfaces, so you can use whichever matches your existing code. | Interface | Use it for | | ----------- | --------------------------------------------------------------------- | | REST API | JSON requests for addresses, transactions, blocks, tokens, and stats. | | ETH RPC API | Standard Ethereum JSON-RPC (`eth_` methods). | | RPC API | Etherscan-compatible endpoints (`module` and `action` parameters). | | GraphQL API | Single queries that return exactly the fields you request. | Full endpoints and parameters are in the API reference, by network. | Network | API reference | Availability | | ------------------ | ------------------------------------------------------------------------------------------ | ------------- | | Whitechain Sepolia | [explorer.testnet.whitechain.io/api-docs](https://explorer.testnet.whitechain.io/api-docs) | Available now | | Whitechain Mainnet | Published at Mainnet launch | Pending | The API answers targeted lookups: one address, one transaction, or recent logs. For large custom queries across the full chain history, a dedicated indexer is the right tool. Whitechain indexer support is documented as providers integrate. If a request fails, check whether the explorer and its API are running on the [status page](https://status.whitechain.io/). ## API access and rate limits The Blockscout API is public and read-only, and needs no API key. It is rate limited per IP to protect the service, so batch related lookups and cache results where you can. For sustained or high-volume querying across the full chain history, run your own indexer or Blockscout instance rather than the public API. ## Related * [Whitechain Sepolia](/learn/network/testnet) * [Deploy a contract](/learn/get-started/deploy-a-contract) * [Verify a proxy contract](/build/deploy/verify-proxy-contracts) * [Blockscout API reference](https://explorer.testnet.whitechain.io/api-docs) * [Service status](https://status.whitechain.io/) # OP Stack canonical bridge Source: https://l2docs.whitechain.io/build/bridge/bridge-assets Move assets between Ethereum and Whitechain with the OP Stack canonical bridge: deposits, withdrawals, contract addresses, and alternative bridges. Available on Testnet today. Mainnet contract addresses and final timing parameters are published at Mainnet launch. ## Overview Whitechain settles on Ethereum via the OP Stack canonical bridge. Assets and arbitrary messages move between L1 (Ethereum) and L2 (Whitechain) through Optimism-standard bridge contracts. Deposits arrive on L2 in about 2 minutes. Withdrawals back to L1 complete after a mandatory dispute-resolution period: minimum 7 days on mainnet, about 1 hour on testnet (see [Timing parameters](#timing-parameters)). ## Ethereum to Whitechain ### Deposits (L1 to L2) A deposit takes about 2 minutes. You initiate the deposit on L1 and the op-node automatically finalizes it on L2. No further action is required on L2 once the deposit is confirmed. | Asset | L1 method | Notes | | ---------------------- | -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ETH (native) | Not supported | Whitechain runs in Custom Gas Token (CGT) mode. `depositETH` reverts with `OptimismPortal_NotAllowedOnCGTMode()`. | | WBT (native gas token) | Not supported | There is no L1-to-L2 gas-token deposit path for native WBT, on testnet or mainnet. Use the [Portal bridge](/build/bridge/portal-bridge) to move WBT between Ethereum and Whitechain, or claim testnet WBT from the [faucet](/learn/get-started/get-testnet-wbt). | | ERC20 tokens | `approve`, then `L1StandardBridge.depositERC20` | The L2 token must already be deployed. See [For token issuers](#for-token-issuers). | | NFTs (ERC721) | `approve`, then `L1Erc721BridgeProxy.bridgeERC721` | Only L1-origin NFTs are supported. | | Arbitrary messages | `L1CrossDomainMessenger.sendMessage` | Has replay support if the L2 call runs out of gas. `OptimismPortal.depositTransaction` has no replay; do not call it directly. | Example `depositERC20` parameters: | Field | Example value | Note | | -------------- | ----------------------------------------- | ---------------------------- | | `_l1Token` | address of the L1 token | must have a paired L2 token | | `_l2Token` | address of the L2 `OptimismMintableERC20` | deployed via the factory | | `_amount` | `10000000` | 10 tokens at 6 decimals | | `_minGasLimit` | `200000` | minimum gas for L2 execution | | `_extraData` | `0x` | optional, empty by default | After \~2 minutes, verify the balance with `balanceOf` on the L2 token contract. ### Withdrawals (L2 to L1) A withdrawal takes about an hour on testnet. On mainnet it takes minimum 7 days. The wait for a qualifying output proposal adds to that minimum, and so does re-proving against a new game if the initial dispute game is successfully challenged (see [Timing parameters](#timing-parameters) for exact figures). The flow has six steps: 1. **Initiate on L2** call `L2StandardBridge.bridgeERC20` for ERC20 tokens, `L2ERC721Bridge.bridgeERC721` for L1-origin NFTs, or `L2CrossDomainMessenger.sendMessage` for arbitrary messages. Tokens are burned for L1-origin tokens and escrowed for L2-native tokens. L2-native NFTs cannot be withdrawn (see [When NOT to use the canonical bridge](#when-not-to-use-the-canonical-bridge)). The canonical bridge does not handle native WBT withdrawals; use the [Portal bridge](/build/bridge/portal-bridge) for that. 2. **Wait for an output proposal** the op-proposer publishes an output root on L1 via the `DisputeGameFactory` at a fixed interval (every 30 minutes on testnet). Your withdrawal can only be proven against a dispute game whose `l2BlockNumber` is greater than or equal to the L2 block of your withdrawal transaction. To find a suitable game and its `gameIndex`, monitor the create transactions on the `DisputeGameFactory` contract in the explorer. 3. **Prove the withdrawal on L1** call `OptimismPortal.proveWithdrawalTransaction` with the `gameIndex` and a Merkle proof from L2 storage. Tools such as viem OP Stack actions or `op-withdrawer` compute the proof for you (see [Programmatic bridging](#programmatic-bridging)). 4. **Resolve the dispute game** the game must resolve as `DEFENDER_WINS` before you can finalize. If it still has `IN_PROGRESS` status after its challenge clock expires, anyone can call `resolveClaim` on the game contract to resolve it. This step is permissionless and is typically handled by an off-chain service. If the game instead resolves as `CHALLENGER_WINS`, your proof is invalidated but your funds are not lost. Re-prove against a different, honest game; the delay countdown restarts from the new prove transaction. 5. **Wait for the dispute-resolution delays** two independent delays must elapse before finalization is allowed. `proofMaturityDelaySeconds` is the minimum time since your prove transaction. `disputeGameFinalityDelaySeconds` is the minimum time since the game resolved in favor of the defender. See [Timing parameters](#timing-parameters) for current values on each network. 6. **Finalize on L1** call `OptimismPortal.finalizeWithdrawalTransaction` to release the funds to L1. The dispute-resolution delay on mainnet (minimum 7 days) is enforced by the protocol and cannot be skipped. Plan treasury operations and time-sensitive flows accordingly. Testnet delays are intentionally shortened and do not reflect mainnet behavior. For urgent exits, see [When NOT to use the canonical bridge](#when-not-to-use-the-canonical-bridge). ### Timing parameters | Parameter | Testnet | Mainnet (planned) | | ------------------------------------ | ------------------------------------------------------------ | ---------------------------------------------- | | Deposit (L1 to L2) | \~2 minutes | \~2 minutes | | Proposer interval (new dispute game) | 30 minutes | TBD, confirmed once mainnet parameters are set | | `proofMaturityDelaySeconds` | 15 minutes | 7 days | | `disputeGameFinalityDelaySeconds` | 5 minutes | TBD, confirmed once mainnet parameters are set | | Full withdrawal cycle | \~1 hour typical, up to \~3 hours (observed; see note below) | minimum 7 days | The full testnet cycle is longer than the two delays combined (15 + 5 minutes) because of discrete service intervals. A withdrawal can only be proven once a dispute game covers its L2 block. If the latest game predates your transaction, you wait for the next one: an observed wait of up to \~45 minutes within a measured 64-minute cycle. In the worst case, when batcher and proposer intervals align unfavorably across stages, the full cycle reaches about 3 hours. Testnet delays are intentionally shortened to allow end-to-end testing. Do not assume testnet timings apply to mainnet. Mainnet values follow the OP Stack default consensus parameters (see [specs.optimism.io](https://specs.optimism.io), section Configurability, Consensus parameters). ### Programmatic bridging Common paths for interacting with the bridge contracts directly: | Tool | What it does | | --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [viem OP Stack actions](https://viem.sh/op-stack) | Recommended. The `viem/op-stack` module extends a client with L1 and L2 actions (`depositTransaction`, `proveWithdrawal`, `finalizeWithdrawal`, and the `buildProveWithdrawal` and `getWithdrawals` helpers) that build the proofs for you. See [Use viem with Whitechain](/build/dapps/use-viem). | | [op-withdrawer](https://github.com/base/op-enclave/tree/v0.0.1-rc6/op-withdrawer) | CLI that runs the prove and finalize steps for L2 to L1 withdrawals. It is a release-candidate build (`v0.0.1-rc6`) from `base/op-enclave`; treat it as pre-release and pin the version. | | Direct contract calls | Any Ethereum-compatible library (viem, ethers, web3.js) can call the bridge contracts. See [Contract addresses](#contract-addresses). | The `@eth-optimism/sdk` package is deprecated and is no longer maintained. Use viem OP Stack actions instead. Withdrawals still require the prove and finalize steps described above, regardless of which tool you use. No tool triggers them automatically. Proof generation additionally requires an archive-capable L2 RPC endpoint, since `eth_getProof` needs historical state access beyond the default pruning window. Without this step, funds remain locked in the L1 bridge escrow. ## Contract addresses Contract addresses below are for testnet. Mainnet addresses will be added once mainnet is live. ### Testnet L2 addresses below fall in the `0x4200...` predeploy range. They are identical across every OP Stack environment: devnet, testnet, and mainnet. L1 addresses are specific to this testnet deployment and will change if the deployment is ever redone. When verifying addresses, focus on the L1 side; the `0x4200...` L2 predeploys are fixed by the protocol and never change. | Contract | L1 | L2 | | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | | `L1StandardBridge` | [`0x0c50be539ab5d72d226038928f2eb25100899ded`](https://sepolia.etherscan.io/address/0x0c50be539ab5d72d226038928f2eb25100899ded) | n/a | | `OptimismPortal` | [`0xff9b597b0781457ae6aa7256ca5ed5839bf7d0c3`](https://sepolia.etherscan.io/address/0xff9b597b0781457ae6aa7256ca5ed5839bf7d0c3) | n/a | | `DisputeGameFactoryProxy` | [`0xfaa2faa8912c069c01abc169c33713c79027c833`](https://sepolia.etherscan.io/address/0xfaa2faa8912c069c01abc169c33713c79027c833) | n/a | | `L1CrossDomainMessengerProxy` | [`0x547967f45d19dc4ca0a056ba525afe99bda18a88`](https://sepolia.etherscan.io/address/0x547967f45d19dc4ca0a056ba525afe99bda18a88) | n/a | | `L1Erc721BridgeProxy` | [`0x1e419fc5575bbd6d352582e9d8073918d79f0c5b`](https://sepolia.etherscan.io/address/0x1e419fc5575bbd6d352582e9d8073918d79f0c5b) | n/a | | `SystemConfigProxy` | [`0x9328ea869949f33c57b7b680b6edb58769e2181c`](https://sepolia.etherscan.io/address/0x9328ea869949f33c57b7b680b6edb58769e2181c) | n/a | | `OptimismMintableERC20FactoryProxy` | [`0x5898f6c319842a379799d634c6ed2da960a42b9b`](https://sepolia.etherscan.io/address/0x5898f6c319842a379799d634c6ed2da960a42b9b) | n/a | | `L2StandardBridge` | n/a | `0x4200000000000000000000000000000000000010` | | `L2CrossDomainMessenger` | n/a | `0x4200000000000000000000000000000000000007` | | `OptimismMintableERC20Factory` | n/a | `0x4200000000000000000000000000000000000012` | | `L2ERC721Bridge` | n/a | `0x4200000000000000000000000000000000000014` | | `OptimismMintableERC721Factory` | n/a | `0x4200000000000000000000000000000000000017` | `OptimismPortal2` is the current implementation contract behind the `OptimismPortal` address above; use the name `OptimismPortal` when referring to it. ## When NOT to use the canonical bridge The canonical bridge is the safest and most trust-minimized path between L1 and L2, but it does not fit every use case. | Scenario | Why the canonical bridge does not work | Alternative | | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Need to exit to L1 quickly (mainnet) | The dispute-resolution period (minimum 7 days on mainnet) is mandatory and cannot be skipped | The [Portal bridge](/build/bridge/portal-bridge) for a faster, centralized transfer. On mainnet there is no fast canonical exit yet; plan for the full dispute period | | Bridging USDC | Whitechain already has a canonical USDC.e representation; a second wrapped pairing created via the factory would fragment liquidity | Use the [Portal bridge](/build/bridge/portal-bridge) for USDC.e | | Token has transfer fees or rebasing logic | The standard bridge does not account for balance changes during transfer | A custom bridge contract designed for the token | | Token has a blocklist or pausable transfer | Finalization may revert permanently, leaving funds stuck | A custom bridge contract | | L2-native NFTs going to L1 | The standard `L2ERC721Bridge` only supports L1-origin NFTs | A custom NFT bridge | | Very large amounts with time-sensitivity | Funds are locked for the entire dispute-resolution period | Stage the transfer in advance, or use a third-party bridge | ## For token issuers To make an L1 ERC20 bridgeable to Whitechain, deploy an `OptimismMintableERC20` on L2 that mirrors the L1 token. Use the `OptimismMintableERC20Factory.createOptimismMintableERC20WithDecimals` function with the L1 token address, the name and symbol you want on L2, and the decimals. Before creating a new pairing, confirm the token doesn't already have an official Whitechain representation (see [When NOT to use the canonical bridge](#when-not-to-use-the-canonical-bridge)). A second wrapped version of an already-represented token fragments liquidity. Example `createOptimismMintableERC20WithDecimals` parameters: | Field | Example value | | -------------- | -------------------------------- | | `_remoteToken` | address of the original L1 token | | `_name` | Example Token | | `_symbol` | EXT | | `_decimals` | 18 | The resulting L2 token grants mint and burn rights exclusively to the `L2StandardBridge`. Verify the pairing before sending real value: `L1StandardBridge.deposits(l1Token, l2Token)` must return a non-zero value, or query the factory to confirm the L2 contract was created. Tokens with transfer fees, rebasing logic, blocklists, or pausable transfers are **not compatible** with the standard bridge. Use a custom bridge solution for those cases. ## Disclaimer * Always test with a small amount before bridging large balances. * Verify every contract address against this page or the official Whitechain Explorer before submitting a transaction. * Withdrawals are non-reversible once finalized on L1. ## Related * [Quick end-to-end test on testnet](/build/bridge/bridge-e2e-testnet) * [Portal bridge](/build/bridge/portal-bridge) * [Faucet](/learn/get-started/get-testnet-wbt) * [Network reference](/learn/network/reference) * [Use viem with Whitechain](/build/dapps/use-viem) # Quick end-to-end test on testnet Source: https://l2docs.whitechain.io/build/bridge/bridge-e2e-testnet Deploy a test token, then run a full canonical bridge deposit and withdrawal on Whitechain Sepolia to verify the flow end to end. This tutorial is testnet-only. It uses public faucets and the shortened testnet dispute delays. The timings here do not apply to mainnet, where a withdrawal takes minimum 7 days. See [OP Stack canonical bridge](/build/bridge/bridge-assets) for the mainnet figures. This walkthrough runs a full canonical bridge deposit and withdrawal on Whitechain Sepolia and Ethereum Sepolia. It exists to verify the bridge hands-on: every step maps to a claim on the [OP Stack canonical bridge](/build/bridge/bridge-assets) page. Budget about 1 hour, up to about 3 hours in the worst case, most of it waiting for the withdrawal. ## Before you start You need MetaMask with two networks added and the same account funded on both. | Network | Chain ID | RPC | Explorer | | ------------------ | -------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | | Ethereum Sepolia | 11155111 | any public Sepolia RPC, for example `https://ethereum-sepolia-rpc.publicnode.com` | [sepolia.etherscan.io](https://sepolia.etherscan.io) | | Whitechain Sepolia | 1874 | `https://rpc.testnet.whitechain.io` | [explorer.testnet.whitechain.io](https://explorer.testnet.whitechain.io) | Fund the account on each side: * Sepolia ETH from a public faucet, for example the [Google Cloud Sepolia faucet](https://cloud.google.com/application/web3/faucet/ethereum/sepolia). * Testnet WBT from the [Whitechain faucet](/learn/get-started/get-testnet-wbt), to pay gas on Whitechain Sepolia. The same address is used on both networks. The contract addresses this tutorial calls are listed on the [canonical bridge page](/build/bridge/bridge-assets#contract-addresses); the ones you need are the `L1StandardBridge`, the `OptimismMintableERC20Factory` (`0x4200000000000000000000000000000000000012`), the `L2StandardBridge` (`0x4200000000000000000000000000000000000010`), the `OptimismPortal`, and the `DisputeGameFactoryProxy`. Use a dedicated testnet account, never a wallet that holds real funds. The withdrawal step loads a private key from a file. Never use a mainnet key, and never commit the key file. ## 1. Deploy a test ERC20 on Sepolia You need a token pair to move. Deploy a minimal ERC20 on Sepolia. Use an OpenZeppelin ERC20 with 6 decimals, a constructor that mints a starting balance, and an open `mint` method so you can refill later. ```solidity TestToken.sol theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.30; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract TestToken is ERC20 { constructor() ERC20("Test Token", "TST") { _mint(msg.sender, 1_000_000 * 10 ** decimals()); } function decimals() public pure override returns (uint8) { return 6; } function mint(address to, uint256 amount) external { _mint(to, amount); } } ``` Deploy it to Sepolia with whichever tool you prefer, then record the deployed L1 token address. In [Remix](https://remix.ethereum.org), deploy to the "Injected Provider" (MetaMask on Sepolia). Enable "Verify contract on Etherscan" in the deploy panel so the L1 token verifies automatically. Record the deployed L1 token address. In a [Hardhat](/build/deploy/deploy-with-hardhat) project, install the OpenZeppelin contracts, add the token at `contracts/TestToken.sol`, and add a `sepolia` network to `hardhat.config.ts`: ```bash Terminal theme={null} npm install @openzeppelin/contracts ``` ```ts hardhat.config.ts theme={null} networks: { sepolia: { type: "http", url: "https://ethereum-sepolia-rpc.publicnode.com", accounts: [process.env.PRIVATE_KEY!], }, }, ``` Deploy and verify: ```ts scripts/deploy-token.ts theme={null} import { network } from "hardhat"; const { viem } = await network.create("sepolia"); const token = await viem.deployContract("TestToken"); console.log("TestToken deployed to:", token.address); ``` ```bash Terminal theme={null} npx hardhat run scripts/deploy-token.ts --network sepolia npx hardhat verify --network sepolia ``` In a [Foundry](/build/deploy/deploy-with-foundry) project, install the OpenZeppelin contracts, add the token at `src/TestToken.sol`, then deploy with `forge create`: ```bash Terminal theme={null} forge install OpenZeppelin/openzeppelin-contracts echo '@openzeppelin/=lib/openzeppelin-contracts/' >> remappings.txt ``` Verification on Sepolia Etherscan needs a free API key in `ETHERSCAN_API_KEY`. ```bash Terminal theme={null} forge create src/TestToken.sol:TestToken \ --rpc-url https://ethereum-sepolia-rpc.publicnode.com \ --private-key $PRIVATE_KEY \ --broadcast \ --verify --verifier etherscan --etherscan-api-key $ETHERSCAN_API_KEY ``` The deployed L1 token address prints under `Deployed to`. ## 2. Create the L2 token pair A deposit only works once the L2 side of the pair exists. Create it with the factory on Whitechain Sepolia. 1. Open the `OptimismMintableERC20Factory` at `0x4200000000000000000000000000000000000012` in the [testnet explorer](https://explorer.testnet.whitechain.io/address/0x4200000000000000000000000000000000000012) and go to the write-as-proxy tab. 2. Call `createOptimismMintableERC20WithDecimals` with your L1 token address as `_remoteToken`, a name and symbol for L2, and `6` for `_decimals`. 3. Open the transaction and read the emitted `OptimismMintableERC20Created` (or `StandardL2TokenCreated`) event. The new L2 token address is in the log. 4. Verify the pairing on the new L2 token: `remoteToken()` returns your L1 token, and `bridge()` returns the `L2StandardBridge` (`0x4200000000000000000000000000000000000010`). Factory-created tokens show only raw bytecode in the explorer, so their read and write tabs are not available. To call the token from a UI, use the Remix "At Address" feature: paste the L2 token address against a minimal `OptimismMintableERC20` interface (with `balanceOf`, `remoteToken`, and `bridge`) and interact from there. ## 3. Deposit (L1 to L2) 1. On the L1 token in [Sepolia Etherscan](https://sepolia.etherscan.io), call `approve` with the `L1StandardBridge` as the spender and an amount at or above what you plan to deposit. 2. On the `L1StandardBridge`, call `depositERC20` through the write-as-proxy tab. | Field | Example value | Note | | -------------- | --------------------- | ----------------------- | | `_l1Token` | your L1 token address | the token you deployed | | `_l2Token` | your L2 token address | from step 2 | | `_amount` | `10000000` | 10 tokens at 6 decimals | | `_minGasLimit` | `200000` | gas for L2 execution | | `_extraData` | `0x` | empty | 3. After about 2 to 5 minutes, call `balanceOf` on the L2 token with your address to confirm the balance arrived. 4. Optional: check the L1 token balance of the `L1StandardBridge` to see the deposit held in escrow, which shows the lock-and-mint behavior. A bridged token does not appear in MetaMask automatically. Import the L2 token address manually (Import tokens in MetaMask on Whitechain Sepolia) to see the balance in the wallet. ## 4. Withdraw (L2 to L1) The withdrawal has an on-chain start you do by hand, then a prove and finalize sequence that cannot be done through the explorer. You run those two steps with a script. ### Initiate the withdrawal on L2 On the `L2StandardBridge` (`0x4200000000000000000000000000000000000010`) in the testnet explorer, call `bridgeERC20`: | Field | Value | | -------------- | --------------------------------- | | `_localToken` | your L2 token address | | `_remoteToken` | your L1 token address | | `_amount` | amount to withdraw, in base units | | `_minGasLimit` | `200000` | | `_extraData` | `0x` | Record the transaction hash. This is the L2 withdrawal transaction the script needs. ### Prove and finalize with viem Set up a small project and install viem. ```bash Terminal theme={null} npm install viem npm install --save-dev typescript @types/node tsx ``` Create `.env` with a dedicated testnet key and the L2 withdrawal hash from the previous step. The Whitechain Sepolia RPC `https://rpc.testnet.whitechain.io` is archive-capable, which the proof generation (`eth_getProof`) requires. ```bash .env theme={null} PRIVATE_KEY=0x_your_dedicated_testnet_key L2_WITHDRAWAL_TX=0x_your_l2_bridge_erc20_tx_hash ``` The script below uses viem OP Stack actions. Three details matter for Whitechain. First, the built-in `whitechainSepolia` chain does not carry the OP Stack contract addresses or a `sourceId`, so the script defines the L2 chain explicitly. Second, Whitechain runs fault proofs (dispute games), so the script uses `getWithdrawalStatus` to wait out the delays. The `waitToFinalize` action assumes the legacy `l2OutputOracle` model and does not apply here. Third, do not confuse the similarly named `whitechainTestnet` export in `viem/chains`, which is a different network (see the warning below). `viem/chains` also exports a chain literally named `whitechainTestnet`, but that is a *different* network (chain id `2625`, RPC `rpc-testnet.whitechain.io`). The network this guide uses, chain id `1874` with RPC `rpc.testnet.whitechain.io`, ships in viem as `whitechainSepolia`. Do not import `whitechainTestnet` here; use the explicit `defineChain` below instead. ```ts withdraw.ts theme={null} import { createPublicClient, createWalletClient, http, defineChain } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { sepolia } from 'viem/chains' import { publicActionsL1, publicActionsL2, walletActionsL1, getWithdrawals, } from 'viem/op-stack' const whitechainSepolia = defineChain({ id: 1874, name: 'Whitechain Sepolia', nativeCurrency: { name: 'WhiteBIT Token', symbol: 'WBT', decimals: 18 }, rpcUrls: { default: { http: ['https://rpc.testnet.whitechain.io'] } }, sourceId: 11155111, contracts: { portal: { 11155111: { address: '0xff9b597b0781457ae6aa7256ca5ed5839bf7d0c3' } }, disputeGameFactory: { 11155111: { address: '0xfaa2faa8912c069c01abc169c33713c79027c833' } }, l1StandardBridge: { 11155111: { address: '0x0c50be539ab5d72d226038928f2eb25100899ded' } }, }, }) const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`) const L1_RPC = 'https://ethereum-sepolia-rpc.publicnode.com' const publicL1 = createPublicClient({ chain: sepolia, transport: http(L1_RPC) }).extend(publicActionsL1()) const walletL1 = createWalletClient({ account, chain: sepolia, transport: http(L1_RPC) }).extend(walletActionsL1()) const publicL2 = createPublicClient({ chain: whitechainSepolia, transport: http() }).extend(publicActionsL2()) const L2_WITHDRAWAL_TX = process.env.L2_WITHDRAWAL_TX as `0x${string}` const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)) async function main() { const receipt = await publicL2.getTransactionReceipt({ hash: L2_WITHDRAWAL_TX }) const [withdrawal] = getWithdrawals(receipt) // Wait for a dispute game that covers the withdrawal's L2 block, then prove. const { output, withdrawal: w } = await publicL1.waitToProve({ receipt, targetChain: whitechainSepolia }) const proveArgs = await publicL2.buildProveWithdrawal({ output, withdrawal: w }) const proveHash = await walletL1.proveWithdrawal(proveArgs) const proveReceipt = await publicL1.waitForTransactionReceipt({ hash: proveHash }) if (proveReceipt.status !== 'success') throw new Error(`Prove tx reverted: ${proveHash}`) // Poll status until both delays elapse (fault-proof chain: not waitToFinalize). let status = await publicL1.getWithdrawalStatus({ receipt, targetChain: whitechainSepolia }) while (status !== 'ready-to-finalize' && status !== 'finalized') { await sleep(60_000) status = await publicL1.getWithdrawalStatus({ receipt, targetChain: whitechainSepolia }) } // Finalize on L1. if (status !== 'finalized') { const finalizeHash = await walletL1.finalizeWithdrawal({ targetChain: whitechainSepolia, withdrawal: w }) const finalizeReceipt = await publicL1.waitForTransactionReceipt({ hash: finalizeHash }) if (finalizeReceipt.status !== 'success') throw new Error(`Finalize tx reverted: ${finalizeHash}`) } console.log('withdrawal finalized', withdrawal.withdrawalHash) } main() ``` Run it: ```bash Terminal theme={null} npx tsx --env-file=.env withdraw.ts ``` `--env-file` loads the `.env` you created above. Without it `process.env.PRIVATE_KEY` is undefined and the script throws on the first line. The script leaves itself running through the wait. When it prints `withdrawal finalized`, confirm the L1 token balance on your address increased by the withdrawn amount, and that the `L1StandardBridge` escrow balance decreased by the same amount. ## Timing expectations Testnet timings observed during validation. Mainnet is far longer; see the [canonical bridge timing parameters](/build/bridge/bridge-assets#timing-parameters). | Phase | Expected time | | --------------------------------------------------------- | --------------------------------- | | Deposit L1 to L2 | 2 to 5 minutes | | Wait for a covering dispute game | up to about 45 minutes | | `proofMaturityDelaySeconds` after prove | 15 minutes | | `disputeGameFinalityDelaySeconds` after the game resolves | 5 minutes | | Full withdrawal cycle | about 1 hour, up to about 3 hours | The wait for a covering game can exceed one proposer interval, because the game must cover the exact L2 block of your withdrawal. If the latest game predates your transaction, you wait for the next one. ## What you have verified Completing this tutorial confirms the following claims on the canonical bridge page. | Step | Claim verified | | -------------------------------------------------- | ------------------------------------------------------------------------------ | | Deposit balance arrives in minutes | Deposits finalize on L2 in about 2 minutes | | Escrow balance rises on deposit, token mints on L2 | Lock-and-mint for L1-origin tokens | | Factory creates a working L2 pair | Token issuer flow via `OptimismMintableERC20Factory` | | Prove then wait then finalize | The withdrawal is a multi-step flow, not automatic | | Delays must elapse before finalize | `proofMaturityDelaySeconds` and `disputeGameFinalityDelaySeconds` are enforced | | L1 balance rises, escrow falls on finalize | Burn-and-release back to L1 | ## Related * [OP Stack canonical bridge](/build/bridge/bridge-assets) * [Use viem with Whitechain](/build/dapps/use-viem) * [Faucet](/learn/get-started/get-testnet-wbt) # Portal bridge Source: https://l2docs.whitechain.io/build/bridge/portal-bridge Move USDC, USDT, and WBT between Ethereum, Tron, and Whitechain in minutes with the Portal, Whitechain's centralized bridge. The Portal contracts and routes are deployed on Testnet (Ethereum Sepolia, Tron Nile, and Whitechain Sepolia). The user-facing app is still in development. Mainnet routes and addresses are published at Mainnet launch. ## Overview The Portal is a centralized bridge operated by Whitechain. It moves a fixed set of tokens between Ethereum, Tron, and Whitechain in minutes, and it is the only route that connects Tron to Whitechain. A Whitechain-operated relayer holds the bridge liquidity and signs each transfer, so you trade self-custody for speed. This is the opposite tradeoff from the [OP Stack canonical bridge](/build/bridge/bridge-assets), which is trust-minimized but takes minimum 7 days to withdraw to Ethereum on mainnet. Use the Portal when you need a fast transfer, a Tron route, or a token the canonical bridge does not support. Use the canonical bridge when you want a trustless path and can wait out the dispute period. ## Portal compared to the canonical bridge | Dimension | Portal | Canonical bridge | | ----------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------- | | Trust model | Centralized: a Whitechain relayer custodies funds in transit | Trustless: secured by OP Stack fault proofs | | Speed | Minutes both ways | \~2 minutes to deposit; minimum 7 days to withdraw on mainnet | | Networks | Ethereum, Tron, Whitechain | Ethereum, Whitechain | | Assets | Fixed list: USDC, USDT, WBT | Any compatible ERC-20, plus L1-origin NFTs | | Fees | WBT charged to cover Ethereum gas on Whitechain to Ethereum transfers; no bridging fee | Gas only | | Recipient | Any address | Connected wallet | For the trustless path and its withdrawal steps, see [OP Stack canonical bridge](/build/bridge/bridge-assets). ## Supported routes and assets The Portal connects Ethereum and Tron to Whitechain in both directions. There is no direct Ethereum to Tron route; to move between them, bridge through Whitechain in two steps. Tokens are a fixed list configured per route. A token deposited to Whitechain arrives as its bridged representation: USDC becomes USDC.e and USDT becomes USDT.e. WBT keeps its symbol and is the native gas token on Whitechain. | Route | Assets | Daily limit (testnet) | | ------------------------------- | ----------------------------- | -------------------------------------- | | Ethereum to and from Whitechain | USDC (arrives as USDC.e), WBT | USDC 10,000 per day; WBT 1,000 per day | | Tron to and from Whitechain | USDT (arrives as USDT.e), WBT | USDT 10,000 per day; WBT 1,000 per day | Daily limits are per token and apply to the current testnet deployment. Values may change, and mainnet limits are set at launch. ## How a transfer works 1. Submit the transfer on the source network and sign one transaction. You can set a recipient address different from your own. 2. Wait for the source network to confirm. Confirmation takes about 12 minutes on Ethereum and about 1 minute on Whitechain. 3. The relayer checks that the destination network holds enough liquidity for the token. If liquidity is short, the transfer is rejected and you can retry later. 4. The relayer releases the funds on the destination network. A Whitechain to Ethereum transfer is executed by the Portal, which pays the Ethereum gas for you. ## Transaction statuses The Portal tracks each transfer through three states. | Status | Meaning | | ----------- | ------------------------------------------------------------------------------- | | Pending | Submitted and waiting out a short security delay before the backend records it. | | In progress | Recorded and waiting for the required network confirmations. | | Completed | Confirmations passed and funds sent to the destination network. | ## Timing | Network | Confirmation time | | ---------- | ----------------- | | Ethereum | \~12 minutes | | Whitechain | \~1 minute | A transfer completes within minutes once the source network confirms. This is the reason to choose the Portal over the canonical bridge for a fast exit to Ethereum. ## Fees and limits The Portal charges no bridging fee today. You pay the source-network gas for your own transaction. For a Whitechain to Ethereum transfer, the Portal pays the Ethereum gas and recovers it by charging you the equivalent in WBT. It reads the current Ethereum gas price, prices a typical token transfer, and converts that cost to WBT using WhiteBIT market rates. Each token has a daily limit, listed in [Supported routes and assets](#supported-routes-and-assets). Transfers are also bounded by available liquidity on the destination network: a transfer larger than the pooled liquidity is rejected, and you can retry once liquidity is topped up. ## Trust model The Portal is centralized. A Whitechain-operated backend holds the bridge liquidity and signs every transfer with a dedicated key, and the on-chain Bridge contract only accepts transfers authorized by that key. This is what makes transfers fast, and it is the tradeoff against the canonical bridge: while funds are in transit, they are held by the Portal rather than by a trustless protocol. During maintenance the Portal can be paused. When it is paused, transfers stop until it resumes. ## Contract addresses Addresses below are for the current testnet deployment: Ethereum Sepolia, Tron Nile, and Whitechain Sepolia (chain ID 1874). Mainnet addresses are published at launch. Verify every address against this page before sending. ### Bridge and Mapper contracts The Bridge contract executes transfers. The Mapper defines which token pairs and directions are allowed. Both are proxy contracts. | Contract | Network | Address | | -------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | | Bridge | Ethereum Sepolia | [`0xFFf55679D856F8AA2F9CAe81faE03AB1959D988f`](https://sepolia.etherscan.io/address/0xFFf55679D856F8AA2F9CAe81faE03AB1959D988f) | | Mapper | Ethereum Sepolia | [`0x5a2AaeF4f2273F7034b3c1338F7AC3e74E19a266`](https://sepolia.etherscan.io/address/0x5a2AaeF4f2273F7034b3c1338F7AC3e74E19a266) | | Bridge | Whitechain Sepolia | [`0x8F4D5bC9379beF9c209C895a1d6e7c26F64fE14F`](https://explorer.testnet.whitechain.io/address/0x8F4D5bC9379beF9c209C895a1d6e7c26F64fE14F) | | Mapper | Whitechain Sepolia | [`0x8f3FF368cd820910cB1A62acee5548BC354599Bf`](https://explorer.testnet.whitechain.io/address/0x8f3FF368cd820910cB1A62acee5548BC354599Bf) | | Bridge | Tron Nile | [`TVAqK9AC3oCeCBLzJCheXfLjtGs1Hxjj75`](https://nile.tronscan.org/#/address/TVAqK9AC3oCeCBLzJCheXfLjtGs1Hxjj75) | | Mapper | Tron Nile | [`TD1YEQs8mooDV2KX5TWEAKyAP8xcCD81wd`](https://nile.tronscan.org/#/address/TD1YEQs8mooDV2KX5TWEAKyAP8xcCD81wd) | ### Token addresses | Token | Network | Address | | ------ | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | | USDC | Ethereum Sepolia | [`0x91B2E38C9B9acf8eF2B2C549BF7f86Ff5B0108fD`](https://sepolia.etherscan.io/address/0x91B2E38C9B9acf8eF2B2C549BF7f86Ff5B0108fD) | | WBT | Ethereum Sepolia | [`0x3c6bf6821d5cc721f6bf41490b0d2a6963d2b031`](https://sepolia.etherscan.io/address/0x3c6bf6821d5cc721f6bf41490b0d2a6963d2b031) | | USDT | Tron Nile | [`TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf`](https://nile.tronscan.org/#/address/TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf) | | WBT | Tron Nile | [`TGa8yeUzkCmuajSedYoPiA9bA8YBnvXvEL`](https://nile.tronscan.org/#/address/TGa8yeUzkCmuajSedYoPiA9bA8YBnvXvEL) | | WBT | Whitechain Sepolia | [`0x4200000000000000000000000000000000000006`](https://explorer.testnet.whitechain.io/address/0x4200000000000000000000000000000000000006) | | USDC.e | Whitechain Sepolia | [`0x5eb541ba8a2dd841af7864c3389162a23dda8401`](https://explorer.testnet.whitechain.io/address/0x5eb541ba8a2dd841af7864c3389162a23dda8401) | | USDT.e | Whitechain Sepolia | [`0xaa7962d073935D387e8d442DA1D7149C81388f46`](https://explorer.testnet.whitechain.io/address/0xaa7962d073935D387e8d442DA1D7149C81388f46) | ## When to use the Portal | Scenario | Use | | ------------------------------------------------------------- | ----------------------------------------------- | | Fast exit from Whitechain to Ethereum | Portal | | Move assets between Tron and Whitechain | Portal (the only route) | | Bridge a token on the Portal's fixed list | Portal | | Want a trustless, self-custody path | [Canonical bridge](/build/bridge/bridge-assets) | | Bridge an arbitrary ERC-20 that is not on the Portal list | [Canonical bridge](/build/bridge/bridge-assets) | | Large amount where trust-minimization matters more than speed | [Canonical bridge](/build/bridge/bridge-assets) | ## Disclaimer * Test with a small amount before bridging large balances. * Verify every contract address against this page or the official Whitechain Explorer before submitting a transaction. * The Portal is centralized: funds in transit are held by the operator, not by a trustless protocol. ## Related * [OP Stack canonical bridge](/build/bridge/bridge-assets) * [Faucet](/learn/get-started/get-testnet-wbt) * [Network reference](/learn/network/reference) # Connect a wallet with Reown AppKit Source: https://l2docs.whitechain.io/build/dapps/connect-wallet-with-reown Add Reown AppKit to an existing app on Whitechain Sepolia: which chain to import, the Turbopack build alias, the RPC override, and which AppKit features Whitechain does not support. Reown AppKit (formerly WalletConnect) gives a dapp a connect modal, a wallet session, and a wagmi config in one setup. This page covers what is specific to Whitechain: which chain to import, how to repoint the RPC, the build alias Next needs, and the AppKit features that do not work here. It assumes you are adding AppKit to an app you already have. To start from a working app instead, clone the [Next dapp template](/build/dapps/dapp-with-nextjs) or the [Vite dapp template](/build/dapps/dapp-with-vite); both ship this wiring, along with the providers and the connect button this page does not repeat. Core connect, sign, and transact work today on Whitechain Sepolia. AppKit features that depend on the Reown Blockchain API do not. See [What Reown does not support on Whitechain](#what-reown-does-not-support-on-whitechain). ## Whitechain networks in the package `@reown/appkit/networks` re-exports `viem/chains`, so Whitechain Sepolia is available from AppKit with no custom chain definition of your own: | Export | Chain ID | Network | Default RPC in the definition | | ------------------- | -------- | -------------------------------------------- | ----------------------------------- | | `whitechainSepolia` | `1874` | Whitechain Sepolia, the Layer 2 (L2) testnet | `https://rpc.testnet.whitechain.io` | ## Configure AppKit Install the packages: ```bash Terminal theme={null} npm install @reown/appkit @reown/appkit-adapter-wagmi wagmi viem @tanstack/react-query ``` Then get a project id from [dashboard.reown.com](https://dashboard.reown.com). The project id identifies your app to the WalletConnect relays, so it is public by design and belongs in a client-side variable such as `NEXT_PUBLIC_REOWN_PROJECT_ID`. Never put a server secret in one of those: everything in a client bundle ships to the browser. Import `whitechainSepolia`, hand it to the wagmi adapter, and call `createAppKit` once at module scope: ```ts src/lib/wagmi.ts theme={null} import { WagmiAdapter } from '@reown/appkit-adapter-wagmi'; import { whitechainSepolia } from '@reown/appkit/networks'; import { createAppKit } from '@reown/appkit/react'; const projectId = process.env.NEXT_PUBLIC_REOWN_PROJECT_ID ?? ''; const networks: [typeof whitechainSepolia] = [whitechainSepolia]; const adapter = new WagmiAdapter({ projectId, networks, ssr: true }); createAppKit({ adapters: [adapter], projectId, networks, features: { email: false, socials: false, analytics: false }, }); export const wagmiConfig = adapter.wagmiConfig; export { useAppKit } from '@reown/appkit/react'; ``` `createAppKit` takes a non-empty tuple rather than a plain array, so type the `networks` constant instead of letting TypeScript widen it. No type assertion is needed beyond that: a viem chain from `@reown/appkit/networks` satisfies AppKit's `AppKitNetwork` type directly. Set `ssr: true` only for a server-rendered app such as Next; drop it in a client-only single-page app. `features` turns off email and social login. Both route through Reown's embedded wallet, which does not support Whitechain, so the login completes and signing then fails. Leave them off unless you have confirmed otherwise for your users. Hand `wagmiConfig` to `WagmiProvider` and open the modal with `useAppKit`. Both are standard AppKit and wagmi, so follow the [Next dapp template](/build/dapps/dapp-with-nextjs#components) for the provider and panel code. One Whitechain-specific detail when you render a balance: `useBalance` in wagmi v3 returns `value`, `decimals`, and `symbol`. It has no `formatted` field, which wagmi v2 did have, so format it yourself with viem's `formatUnits(balance.value, balance.decimals)`. This configuration is compiled and run as a Next app against Whitechain Sepolia on the current releases: | Package | Version | | ----------------------------- | ------- | | `@reown/appkit` | 1.8.23 | | `@reown/appkit-adapter-wagmi` | 1.8.23 | | `wagmi` | 3.7.6 | | `viem` | 2.55.13 | | `@tanstack/react-query` | 5.101.4 | ## Build under Next and Turbopack wagmi's connector barrel pulls in wallet SDKs your app never uses. One of them declares optional peer dependencies of its own, which package managers skip by design. Turbopack resolves every import it can reach, so `next build` fails on packages nothing calls at runtime: ```text Build error theme={null} Module not found: Can't resolve '@x402/core/client' Module not found: Can't resolve '@x402/evm' ``` Installing them resolves the build: ```bash Terminal theme={null} npm install @x402/core @x402/evm @x402/extensions @x402/svm ``` They add about 28 MB to `node_modules` and nothing to the browser bundle, since no code path reaches them. The failing set tracks the connector packages rather than your code, so it changes when they do. Read the specifiers in your own build output rather than assuming this list. If a future version names packages this page does not, either install those or alias them to an empty module with Turbopack's `resolveAlias`, which is the workaround the wagmi maintainers point to in [wevm/wagmi#4906](https://github.com/wevm/wagmi/issues/4906). Vite and Rollup builds are unaffected. ## Point the app at a different RPC endpoint The `whitechainSepolia` definition already defaults to the official endpoint `https://rpc.testnet.whitechain.io`, so most apps need no override. Override it when you run your own node, a local development chain, or a private gateway. Set the transport on the wagmi adapter. Every read and write your app makes through wagmi hooks then goes to that endpoint: ```ts src/lib/wagmi.ts theme={null} import { http } from 'viem'; const adapter = new WagmiAdapter({ projectId, networks, transports: { [whitechainSepolia.id]: http('https://rpc.testnet.whitechain.io'), }, }); ``` AppKit also has a `customRpcUrls` option, which does not redirect wagmi's own calls. With `customRpcUrls` set and no transport, `useBalance`, `useReadContract`, and every other wagmi hook still reach the chain's default endpoint. Passing it to both the adapter and `createAppKit` does not change that. Use `transports` for anything your app reads or writes. The public endpoint is rate limited per client IP address. See [Rate limits on Whitechain endpoints](/learn/network/reference#rate-limits-on-whitechain-endpoints) before pointing a production app at it. ## What Reown does not support on Whitechain Reown's Blockchain API is a separate hosted service from the WalletConnect relays, and it is what powers AppKit's value-added features. Whitechain is not in its chain list: the list at `https://rpc.walletconnect.org/v1/supported-chains` contains neither `eip155:1874` nor `eip155:1875`. | AppKit feature | On Whitechain Sepolia | What to do instead | | ------------------------------------------------ | -------------------------------------- | --------------------------------------------------------------------------------------------------- | | Connect over WalletConnect, injected wallets | Works | Nothing. This is the setup above. | | Sign messages, send transactions, call contracts | Works | Nothing. wagmi and viem talk to the Whitechain RPC directly. | | Identity and ENS resolution | Not available | Resolve names yourself with a second viem client pointed at Ethereum mainnet. | | Embedded wallets (email and social login) | Login completes, transactions may fail | Set `features: { email: false, socials: false }` and ship external wallets only. | | Swaps | Not available on any testnet | Use a dapp on Whitechain. | | Transaction history in the account view | Not available on any testnet | Read it from the explorer API. See [Wallet indexing example](/build/block-explorer/indexer-wallet). | | On-Ramp | Not available | Send WBT from WhiteBIT, or claim test WBT from the [faucet](https://faucet.testnet.whitechain.io). | At L2 Mainnet the test-network limitation no longer applies, and Whitechain is working toward these features being available there. The remaining step is the chain being listed in Reown's Blockchain API. Whitechain publishes the Mainnet chain ID and endpoints in the [Network reference](/learn/network/reference) before launch, so check the supported-chain list against that chain ID once it is published. ## Related * [Next dapp template](/build/dapps/dapp-with-nextjs) * [Vite dapp template](/build/dapps/dapp-with-vite) * [Use viem with Whitechain](/build/dapps/use-viem) * [Wallets](/build/wallet/wallets) * [Network reference](/learn/network/reference) # Next dapp template Source: https://l2docs.whitechain.io/build/dapps/dapp-with-nextjs Clone the standalone Whitechain Next dapp starter, connect a wallet through Reown AppKit, and read and write the Storage contract on Whitechain Sepolia. The `whitechain-dapp-nextjs` starter is a Next frontend for Whitechain Sepolia with wallet connect and contract calls already wired. You clone it, add a Reown project id, and run. It runs on Next 16 with Turbopack, React 19, and Tailwind v4, with Reown AppKit over wagmi v3 and viem. It suits developers who want a working dapp without building the wallet layer by hand. For a static single-page app with no server, use the [Vite dapp template](/build/dapps/dapp-with-vite) instead. Both configured variables use the `NEXT_PUBLIC_` prefix, so they are baked into the browser bundle. Keep server secrets out of them. ## What you get * Wallet connect through Reown AppKit, with the connected account, network, and WBT balance. * A public read of the Storage contract that shows the current value with no wallet connected. * A write that stores a new value and refreshes the read when the transaction confirms. * A standalone codebase: every dependency is a public npm package, and the web3 layer lives in `src/lib`. ## Before you start * Node.js 20.18 or later and pnpm through Corepack. * A Reown project id from [dashboard.reown.com](https://dashboard.reown.com). * Optional: your own Storage contract. The template defaults to a public verified one, so you can skip this. To use your own, deploy with [Hardhat](/build/deploy/deploy-with-hardhat) or [Foundry](/build/deploy/deploy-with-foundry) and copy its address. * Test WBT for the write transaction, from the [faucet](/learn/get-started/get-testnet-wbt). ## 1. Get the template Clone the [templates repository](https://github.com/whitechain-labs/templates) and enter the dapp folder. ```bash Terminal theme={null} git clone https://github.com/whitechain-labs/templates.git cd templates/whitechain-dapp-nextjs ``` ## 2. Install dependencies Enable the pinned pnpm through Corepack, then install. Every dependency comes from public npm, so no registry token is needed. ```bash Terminal theme={null} corepack enable pnpm install ``` ## 3. Configure the environment Copy the example file and set the public values. ```bash Terminal theme={null} cp .env.example .env ``` | Variable | Value | | ------------------------------ | ------------------------------------------------------------------------------ | | `NEXT_PUBLIC_REOWN_PROJECT_ID` | Your Reown project id. | | `NEXT_PUBLIC_STORAGE_ADDRESS` | Optional. Defaults to a public verified Storage contract; set to use your own. | `NEXT_PUBLIC_STORAGE_ADDRESS` is optional. The template defaults to a public, verified Storage contract on Whitechain Sepolia: [`0xC880eF22c01184a3Db08F2c306684311C48cB495`](https://explorer.testnet.whitechain.io/address/0xC880eF22c01184a3Db08F2c306684311C48cB495). Set the variable only to point at your own deployment. ## 4. Run the app ```bash Terminal theme={null} pnpm dev ``` Open `http://localhost:3000`. The Storage card reads the current value over the public RPC right away. Select Connect wallet, approve the connection, and the account, network, and balance appear. For a production build, run `pnpm build` then `pnpm start`; the build uses `output: 'standalone'`, so you can deploy it as a Node server. ## 5. Read and write the contract `retrieve()` is a view call, so the stored value shows without a wallet. `store(uint256)` sends a transaction. With a wallet connected on Whitechain Sepolia, enter a number and confirm it in the wallet. The value updates once the transaction confirms. Open the transaction on the [explorer](https://explorer.testnet.whitechain.io) to check it. If the wallet is on another network, the panel shows a switch action. `store()` stays disabled until the wallet is on Whitechain Sepolia (chain 1874). ## How it looks Whitechain Next dapp with a wallet card and a Storage contract card The home page shows a title and two cards. The wallet card connects a wallet through Reown AppKit, then shows the account address and WBT balance. The Storage card reads the current value with no wallet, and shows a number input and Store button once a wallet is connected on the right network. ## Project layout | Path | What it holds | | ------------------------------ | ------------------------------------------------------------ | | `src/lib/wagmi.ts` | Chain, RPC, and the Reown AppKit and wagmi config. | | `src/lib/wallet.ts` | The `useWallet` hook: account, network, disconnect, switch. | | `src/lib/storage.ts` | The Storage ABI and address. | | `src/components/providers.tsx` | The client providers: wagmi and TanStack Query. | | `src/components/web3/` | The wallet and Storage panels. | | `src/app/` | App Router: layout, page, and `/api/healthz`. | | `src/empty-module.ts` | Empty module aliased for unused wallet SDKs under Turbopack. | ## Components The web3 layer lives in `src/lib` and the panels in `src/components/web3`. Each essential part is below, trimmed to the lines that matter. ### Chain and wallet config `src/lib/wagmi.ts` defines the chain with viem's OP Stack `chainConfig`, then wires Reown AppKit over wagmi. Change the RPC URL to use your own node. ```ts src/lib/wagmi.ts theme={null} export const whitechainTestnet: Chain = defineChain({ ...chainConfig, id: 1874, name: 'Whitechain Sepolia', testnet: true, nativeCurrency: { decimals: 18, name: 'WhiteBIT Coin', symbol: 'WBT' }, rpcUrls: { default: { http: ['https://rpc.testnet.whitechain.io'] } }, blockExplorers: { default: { name: 'Whitechain Explorer', url: 'https://explorer.testnet.whitechain.io' }, }, }); const adapter = new WagmiAdapter({ projectId, networks, ssr: true }); createAppKit({ adapters: [adapter], projectId, networks, enableCoinbase: false }); export const wagmiConfig = adapter.wagmiConfig; ``` ### Client providers `src/components/providers.tsx` is a client component that wraps the app in the wagmi and TanStack Query providers. The root layout renders it around `children`, so any client island below can call the web3 hooks. ```tsx src/components/providers.tsx theme={null} 'use client'; export function Providers({ children }: { children: ReactNode }) { const [queryClient] = useState( () => new QueryClient({ defaultOptions: { queries: { staleTime: 10_000, retry: 2 } } }), ); return ( {children} ); } ``` ### Wallet state hook `src/lib/wallet.ts` gives the panels one shape for account and network state. The connection itself is opened through the AppKit modal, not here. ```ts src/lib/wallet.ts theme={null} export function useWallet(): WalletState { const { address, chainId, isConnected, isConnecting, connector } = useConnection(); const { mutate: disconnect } = useDisconnect(); const { mutate: switchChain } = useSwitchChain(); return { address, chainId, isConnected, isConnecting, connector, disconnect, switchChain: (id: number) => switchChain({ chainId: id }), }; } ``` ### Wallet panel `src/components/web3/wallet-panel.tsx` opens the modal to connect, then reads the balance with wagmi. ```tsx src/components/web3/wallet-panel.tsx theme={null} const { open } = useAppKit(); const { address, isConnected, isConnecting, disconnect } = useWallet(); const { data: balance } = useBalance({ address, chainId: targetChainId, query: { enabled: Boolean(address) }, }); if (!isConnected) { return ; } // Connected: show the address, the formatted balance, and a Disconnect button. ``` ### Storage read `retrieve()` is a public view call, so it runs over the RPC with no wallet connected. ```tsx src/components/web3/storage-panel.tsx theme={null} const { data: storedValue, refetch } = useReadContract({ abi: storageAbi, address: storageAddress, functionName: 'retrieve', chainId: targetChainId, query: { enabled: Boolean(storageAddress) }, }); ``` ### Storage write `store(uint256)` is a write, gated on a connected wallet. The read refreshes once the transaction confirms. ```tsx src/components/web3/storage-panel.tsx theme={null} const { mutate: storeValue, data: txHash } = useWriteContract(); const receipt = useWaitForTransactionReceipt({ hash: txHash, chainId: targetChainId }); useEffect(() => { if (receipt.isSuccess) void refetch(); }, [receipt.isSuccess, refetch]); // On submit: storeValue({ abi: storageAbi, address: storageAddress, functionName: 'store', args: [BigInt(inputValue)], chainId: targetChainId, }); ``` ## Troubleshooting Set `NEXT_PUBLIC_STORAGE_ADDRESS` and switch the wallet to Whitechain Sepolia (chain 1874). Keep the `resolveAlias` shim in `next.config.mjs` (`src/empty-module.ts`) in sync with the wallet SDKs your connector set does not use. Use Node 20.18 or later. ## Related * [Templates repository on GitHub](https://github.com/whitechain-labs/templates) * [Vite dapp template](/build/dapps/dapp-with-vite) * [Connect to Whitechain Sepolia](/learn/get-started/connect-wallet) * [Use viem with Whitechain](/build/dapps/use-viem) # Vite dapp template Source: https://l2docs.whitechain.io/build/dapps/dapp-with-vite Clone the standalone Whitechain Vite dapp starter, connect a wallet through Reown AppKit, and read and write the Storage contract on Whitechain Sepolia. The `whitechain-dapp-vite` starter is a client-only single-page app for Whitechain Sepolia with wallet connect and contract calls already wired. You clone it, add a Reown project id, and run. It runs on React 19, Vite, TanStack Router, and Tailwind v4, with Reown AppKit over wagmi v3 and viem. It suits developers who want a static dapp without building the wallet layer by hand. For a server-rendered app that can hide secrets behind a backend, use the [Next dapp template](/build/dapps/dapp-with-nextjs) instead. This is a static SPA with no server, so it cannot hold secrets. Everything in the bundle ships to the browser. A Reown `projectId` is public by design, which is fine. Never put a privileged key in a `VITE_*` variable. ## What you get * Wallet connect through Reown AppKit, with the connected account, network, and WBT balance. * A public read of the Storage contract that shows the current value with no wallet connected. * A write that stores a new value and refreshes the read when the transaction confirms. * A standalone codebase: every dependency is a public npm package, and the web3 layer lives in `src/lib`. ## Before you start * Node.js 20.19 or later and pnpm through Corepack. * A Reown project id from [dashboard.reown.com](https://dashboard.reown.com). * Optional: your own Storage contract. The template defaults to a public verified one, so you can skip this. To use your own, deploy with [Hardhat](/build/deploy/deploy-with-hardhat) or [Foundry](/build/deploy/deploy-with-foundry) and copy its address. * Test WBT for the write transaction, from the [faucet](/learn/get-started/get-testnet-wbt). ## 1. Get the template Clone the [templates repository](https://github.com/whitechain-labs/templates) and enter the dapp folder. ```bash Terminal theme={null} git clone https://github.com/whitechain-labs/templates.git cd templates/whitechain-dapp-vite ``` ## 2. Install dependencies Enable the pinned pnpm through Corepack, then install. Every dependency comes from public npm, so no registry token is needed. ```bash Terminal theme={null} corepack enable pnpm install ``` ## 3. Configure the environment Copy the example file and set the public values. Vite exposes `VITE_*` variables to the browser bundle at build time. ```bash Terminal theme={null} cp .env.example .env ``` | Variable | Value | | ----------------------- | ------------------------------------------------------------------------------ | | `VITE_REOWN_PROJECT_ID` | Your Reown project id. | | `VITE_STORAGE_ADDRESS` | Optional. Defaults to a public verified Storage contract; set to use your own. | `VITE_STORAGE_ADDRESS` is optional. The template defaults to a public, verified Storage contract on Whitechain Sepolia: [`0xC880eF22c01184a3Db08F2c306684311C48cB495`](https://explorer.testnet.whitechain.io/address/0xC880eF22c01184a3Db08F2c306684311C48cB495). Set the variable only to point at your own deployment. ## 4. Run the app ```bash Terminal theme={null} pnpm dev ``` Open `http://localhost:5173`. The Storage card reads the current value over the public RPC right away. Select Connect wallet, approve the connection, and the account, network, and balance appear. `5173` is the Vite default. If it is already in use, Vite starts on the next free port and prints the URL. ## 5. Read and write the contract `retrieve()` is a view call, so the stored value shows without a wallet. `store(uint256)` sends a transaction. With a wallet connected on Whitechain Sepolia, enter a number and confirm it in the wallet. The value updates once the transaction confirms. Open the transaction on the [explorer](https://explorer.testnet.whitechain.io) to check it. If the wallet is on another network, the panel shows a switch action. `store()` stays disabled until the wallet is on Whitechain Sepolia (chain 1874). ## How it looks Whitechain Vite dapp with a wallet card and a Storage contract card The home page shows a title and two cards. The wallet card connects a wallet through Reown AppKit, then shows the account address and WBT balance. The Storage card reads the current value with no wallet, and shows a number input and Store button once a wallet is connected on the right network. ## Project layout | Path | What it holds | | ---------------------- | ------------------------------------------------------------------------ | | `src/lib/wagmi.ts` | Chain, RPC, and the Reown AppKit and wagmi config. | | `src/lib/wallet.ts` | The `useWallet` hook: account, network, disconnect, switch. | | `src/lib/storage.ts` | The Storage ABI and address. | | `src/lib/cn.ts` | The `cn` class-merge helper (clsx and tailwind-merge). | | `src/components/ui/` | Plain Tailwind primitives: Button, Card, Spinner. | | `src/components/web3/` | The wallet and Storage panels. | | `src/routes/` | File-based routes; the route tree generates into `src/routeTree.gen.ts`. | | `src/main.tsx` | App entry: wagmi and TanStack Query providers and the router. | ## Components The web3 layer lives in `src/lib` and the panels in `src/components/web3`. Each essential part is below, trimmed to the lines that matter. ### Chain and wallet config `src/lib/wagmi.ts` defines the chain with viem's OP Stack `chainConfig`, then wires Reown AppKit over wagmi. Change the RPC URL to use your own node; set `enableCoinbase: true` to add the Coinbase connector. ```ts src/lib/wagmi.ts theme={null} export const whitechainTestnet: Chain = defineChain({ ...chainConfig, id: 1874, name: 'Whitechain Sepolia', testnet: true, nativeCurrency: { decimals: 18, name: 'WhiteBIT Coin', symbol: 'WBT' }, rpcUrls: { default: { http: ['https://rpc.testnet.whitechain.io'] } }, blockExplorers: { default: { name: 'Whitechain Explorer', url: 'https://explorer.testnet.whitechain.io' }, }, }); const adapter = new WagmiAdapter({ projectId, networks, ssr: false }); createAppKit({ adapters: [adapter], projectId, networks, enableCoinbase: false }); export const wagmiConfig = adapter.wagmiConfig; ``` ### Wallet state hook `src/lib/wallet.ts` gives the panels one shape for account and network state. The connection itself is opened through the AppKit modal, not here. ```ts src/lib/wallet.ts theme={null} export function useWallet(): WalletState { const { address, chainId, isConnected, isConnecting, connector } = useConnection(); const { mutate: disconnect } = useDisconnect(); const { mutate: switchChain } = useSwitchChain(); return { address, chainId, isConnected, isConnecting, connector, disconnect, switchChain: (id: number) => switchChain({ chainId: id }), }; } ``` ### Wallet panel `src/components/web3/wallet-panel.tsx` opens the modal to connect, then reads the balance with wagmi. ```tsx src/components/web3/wallet-panel.tsx theme={null} const { open } = useAppKit(); const { address, isConnected, isConnecting, disconnect } = useWallet(); const { data: balance } = useBalance({ address, chainId: targetChainId, query: { enabled: Boolean(address) }, }); if (!isConnected) { return ; } // Connected: show the address, the formatted balance, and a Disconnect button. ``` ### Storage read `retrieve()` is a public view call, so it runs over the RPC with no wallet connected. ```tsx src/components/web3/storage-panel.tsx theme={null} const { data: storedValue, refetch } = useReadContract({ abi: storageAbi, address: storageAddress, functionName: 'retrieve', chainId: targetChainId, query: { enabled: Boolean(storageAddress) }, }); ``` ### Storage write `store(uint256)` is a write, gated on a connected wallet. The read refreshes once the transaction confirms. ```tsx src/components/web3/storage-panel.tsx theme={null} const { mutate: storeValue, data: txHash } = useWriteContract(); const receipt = useWaitForTransactionReceipt({ hash: txHash, chainId: targetChainId }); useEffect(() => { if (receipt.isSuccess) void refetch(); }, [receipt.isSuccess, refetch]); // On submit: storeValue({ abi: storageAbi, address: storageAddress, functionName: 'store', args: [BigInt(inputValue)], chainId: targetChainId, }); ``` ## Troubleshooting Run `pnpm dev` or `pnpm build` once. The TanStack Router plugin generates `src/routeTree.gen.ts`. Set `VITE_STORAGE_ADDRESS` and switch the wallet to Whitechain Sepolia (chain 1874). Use Node 20.19 or later. ## Related * [Templates repository on GitHub](https://github.com/whitechain-labs/templates) * [Next dapp template](/build/dapps/dapp-with-nextjs) * [Connect to Whitechain Sepolia](/learn/get-started/connect-wallet) * [Use viem with Whitechain](/build/dapps/use-viem) # Use viem with Whitechain Source: https://l2docs.whitechain.io/build/dapps/use-viem Set up a viem client for Whitechain Sepolia and run common operations: read a balance, send WBT, and call a contract. Whitechain Sepolia ships as `whitechainSepolia` in `viem/chains`. No custom chain definition is needed. This page shows how to install viem, create a client, and run the most common read and write operations. ## Install ```bash Terminal theme={null} npm install viem npm install --save-dev @types/node ``` The code snippets on this page use top-level `await` and `process.env`. Add `"type": "module"` to `package.json` and declare the `node` types in `tsconfig.json`: ```json package.json theme={null} { "type": "module" } ``` ```json tsconfig.json theme={null} { "compilerOptions": { "types": ["node"] } } ``` ## 1. Create a client Use `createPublicClient` for read operations and `createWalletClient` for transactions and contract writes. Read the private key from an environment variable and exclude `.env` from version control. ```ts client.ts theme={null} import { createPublicClient, createWalletClient, http } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { whitechainSepolia } from 'viem/chains' export const publicClient = createPublicClient({ chain: whitechainSepolia, transport: http(), }) const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`) export const walletClient = createWalletClient({ account, chain: whitechainSepolia, transport: http(), }) ``` `http()` with no argument uses the RPC endpoint built into `whitechainSepolia` (`https://rpc.testnet.whitechain.io`). Pass a URL string to `http('https://...')` to override it. ## 2. Read a balance `getBalance` returns the WBT balance of an address in wei. Use `formatEther` to convert it to a decimal string. ```ts theme={null} import { formatEther } from 'viem' import { publicClient, walletClient } from './client' const address = walletClient.account.address const balance = await publicClient.getBalance({ address }) console.log(`Address: ${address}`) console.log(`Balance: ${formatEther(balance)} WBT`) // e.g. "1.5" ``` ## 3. Send WBT `sendTransaction` sends WBT to a recipient address. Use `parseEther` to express the value in WBT rather than wei. ```ts theme={null} import { parseEther } from 'viem' import { walletClient } from './client' const hash = await walletClient.sendTransaction({ to: '0xRecipientAddress', value: parseEther('0.01'), }) console.log(hash) // transaction hash ``` ## 4. Call a contract Define the contract address and ABI once and share them across read and write operations. ```ts contract.ts theme={null} export const CONTRACT_ADDRESS = '0xYourContractAddress' as const export const storageAbi = [ { type: 'function', name: 'retrieve', inputs: [], outputs: [{ name: '', type: 'uint256' }], stateMutability: 'view', }, { type: 'function', name: 'store', inputs: [{ name: 'num', type: 'uint256' }], outputs: [], stateMutability: 'nonpayable', }, ] as const ``` Keep `as const` on the ABI. Without it, TypeScript cannot infer function names or argument types from the array. ### Read from a contract `readContract` calls a `view` or `pure` function. No transaction is sent and no gas is spent. ```ts theme={null} import { publicClient } from './client' import { CONTRACT_ADDRESS, storageAbi } from './contract' const value = await publicClient.readContract({ address: CONTRACT_ADDRESS, abi: storageAbi, functionName: 'retrieve', }) console.log(value) // bigint ``` ### Write to a contract `writeContract` sends a transaction that calls a state-changing function. ```ts theme={null} import { walletClient } from './client' import { CONTRACT_ADDRESS, storageAbi } from './contract' const hash = await walletClient.writeContract({ address: CONTRACT_ADDRESS, abi: storageAbi, functionName: 'store', args: [42n], }) console.log(hash) // transaction hash ``` ## Related * [Deploy with Hardhat](/build/deploy/deploy-with-hardhat) * [Deploy with Foundry](/build/deploy/deploy-with-foundry) * [Next dapp template](/build/dapps/dapp-with-nextjs) * [Network reference](/learn/network/reference) # Deploy with Foundry Source: https://l2docs.whitechain.io/build/deploy/deploy-with-foundry Deploy and verify a smart contract on Whitechain Sepolia using Foundry and forge from the command line. This page shows how to deploy and verify a contract on Whitechain Sepolia using Foundry. You pass the RPC URL and the explorer URL directly to `forge`, so `forge` needs no chain config file. It is for developers who use Foundry and work from the command line. ## Before you deploy * A funded testnet account. Claim test WBT from the [faucet](/learn/get-started/get-testnet-wbt). * The private key for that account. Use a throwaway key for testing. Never commit a private key, and never reuse a mainnet key for testing. Anyone with the key controls the funds. ## 1. Install Foundry Foundryup requires a Unix shell. macOS and Linux work directly. On Windows, run Foundry inside WSL. Install Foundry, then run `foundryup` to fetch `forge`, `cast`, and `anvil`. ```bash Terminal theme={null} curl -L https://foundry.paradigm.xyz | bash foundryup ``` If `foundryup` is not found after the first command, restart your terminal and run it again. PowerShell and Command Prompt cannot run Foundry. Install WSL first, then work from the Ubuntu terminal. Open PowerShell as administrator and install WSL with Ubuntu. Reboot if prompted, then set a Linux username and password. ```powershell PowerShell theme={null} wsl --install ``` Open the Ubuntu terminal and install the build tools Foundry needs. ```bash WSL terminal theme={null} sudo apt update && sudo apt install -y curl git build-essential ``` Install Foundry, then run `foundryup` to fetch `forge`, `cast`, and `anvil`. ```bash WSL terminal theme={null} curl -L https://foundry.paradigm.xyz | bash foundryup ``` Run every remaining step from this same Ubuntu terminal. If `foundryup` is not found, run `source ~/.bashrc` or restart the terminal. ## 2. Create a project Scaffold a project and move into it. ```bash Terminal theme={null} forge init whitechain-foundry cd whitechain-foundry ``` Pin the Solidity version in `foundry.toml` so the compiler matches the contract. Verification fails if the compiler version differs. ```toml foundry.toml theme={null} [profile.default] src = "src" out = "out" libs = ["lib"] solc = "0.8.30" ``` ## 3. Add the contract Create `src/Storage.sol`. You can delete the sample `Counter` files that `forge init` created. ```solidity src/Storage.sol theme={null} // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.30; contract Storage { uint256 number; function store(uint256 num) public { number = num; } function retrieve() public view returns (uint256) { return number; } } ``` ## 4. Set environment variables Create a `.env` file with the private key, then load it into the shell. ```bash .env theme={null} PRIVATE_KEY=your_private_key_here ``` ```bash Terminal theme={null} source .env ``` For a safer setup, import the key into an encrypted keystore with `cast wallet import`, then pass `--account ` instead of `--private-key`. ## 5. Add a deploy script Create `script/DeployStorage.s.sol`. ```solidity script/DeployStorage.s.sol theme={null} // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.30; import {Script} from "forge-std/Script.sol"; import {Storage} from "../src/Storage.sol"; contract DeployStorage is Script { function run() public { vm.startBroadcast(); new Storage(); vm.stopBroadcast(); } } ``` ## 6. Deploy the contract ```bash Terminal theme={null} forge script script/DeployStorage.s.sol \ --rpc-url https://rpc.testnet.whitechain.io/ \ --private-key $PRIVATE_KEY \ --broadcast ``` `forge script` only sends transactions when `--broadcast` is present. Without it, `forge` runs a simulation and deploys nothing. `forge` prints the deployed address under `Contract Address`. Copy it for verification. ## 7. Verify the contract Verify the deployed contract on the Whitechain Blockscout explorer. ```bash Terminal theme={null} forge verify-contract src/Storage.sol:Storage \ --rpc-url https://rpc.testnet.whitechain.io/ \ --verifier blockscout \ --verifier-url https://explorer.testnet.whitechain.io/api/ ``` The Blockscout verifier URL ends with `/api/`. A missing or wrong suffix makes the request fail. To verify during deployment instead, add `--verify --verifier blockscout --verifier-url https://explorer.testnet.whitechain.io/api/` to the `forge script` command in step 6. ## Verify the result Open the contract address on the [explorer](https://explorer.testnet.whitechain.io). A verified contract shows its source code and a verified marker. ## Troubleshooting The `--broadcast` flag is missing. Add `--broadcast` to send the transaction. The account holds no WBT. Claim test WBT from the [faucet](/learn/get-started/get-testnet-wbt). Do not send mainnet WBT to the testnet. The solc version differs from the pragma. Pin `solc = "0.8.30"` in `foundry.toml`, rebuild, and verify again. The `--verifier-url` is missing the `/api/` suffix. Use `https://explorer.testnet.whitechain.io/api/`. The explorer or RPC may be experiencing an outage. Check the [status page](https://status.whitechain.io/). ## Related * [Deploy a contract](/learn/get-started/deploy-a-contract) * [Connect to Whitechain Sepolia](/learn/get-started/connect-wallet) * [Deploy with Hardhat](/build/deploy/deploy-with-hardhat) * [Deploy with Remix](/build/deploy/deploy-with-remix) * [Verify a proxy contract](/build/deploy/verify-proxy-contracts) * [Faucet](/learn/get-started/get-testnet-wbt) * [Service status](https://status.whitechain.io/) # Deploy with Hardhat Source: https://l2docs.whitechain.io/build/deploy/deploy-with-hardhat Learn how to deploy and verify smart contracts on Whitechain Sepolia using the Hardhat development environment. This page shows how to deploy and verify a contract on Whitechain Sepolia using Hardhat. Whitechain Sepolia is a standard EVM network, so you add it by chain ID and RPC URL. Hardhat needs no Whitechain-specific plugin. It is for developers who work from the command line. Hardhat publishes its own Agent Skill for AI coding assistants. Install it with `npx skills add nomicfoundation/hardhat-skills` for AI-guided help with Hardhat 3 workflows and migrations. Pair it with the [whitechain-dev skill](/build-with-ai/claude-skills) for the Whitechain-specific steps below. ## Before you deploy * Node.js 22 or later and npm. Hardhat 3 requires Node.js 22 or newer. * A funded testnet account. Claim test WBT from the [faucet](/learn/get-started/get-testnet-wbt). * The private key for that account. Use a throwaway key for testing. ## 1. Create a project ```bash theme={null} npm install --save-dev hardhat npx hardhat --init ``` When prompted: | Prompt | Response | | ---------------------------------------------------------------------- | ----------------------------------------------------------------------- | | Which version of Hardhat? | Choose **Hardhat 3 (recommended for new projects)** | | Where would you like to initialize the project? | Press Enter for the current directory | | What type of project? | Choose **A TypeScript Hardhat project using Node Test Runner and Viem** | | Would you like to change `package.json` to turn your project into ESM? | Enter `y` | | Confirm dependency installation? | Press Enter | ## 2. Set environment variables ```bash theme={null} npm install --save-dev dotenv ``` Create a `.env` file: ```ini theme={null} PRIVATE_KEY=0x_your_private_key_here ``` The key must include the `0x` prefix. Hardhat rejects the network configuration without it. Add `.env` to `.gitignore` so you do not commit the key. Never commit a private key, and never reuse a mainnet key for testing. Anyone with the key controls the funds. ## 3. Configure the network Replace the contents of `hardhat.config.ts` with: ```typescript theme={null} import type { HardhatUserConfig } from "hardhat/config"; import hardhatToolboxViem from "@nomicfoundation/hardhat-toolbox-viem"; import "dotenv/config"; const config: HardhatUserConfig = { plugins: [hardhatToolboxViem], solidity: "0.8.30", networks: { whitechainTestnet: { type: "http", url: "https://rpc.testnet.whitechain.io", accounts: [process.env.PRIVATE_KEY!], }, }, verify: { sourcify: { enabled: true, }, }, }; export default config; ``` ## 4. Add a contract Create `contracts/Storage.sol`. ```solidity theme={null} // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.30; contract Storage { uint256 number; function store(uint256 num) public { number = num; } function retrieve() public view returns (uint256) { return number; } } ``` ## 5. Add a deploy script Create `scripts/deploy.ts`. ```typescript theme={null} import { network } from "hardhat"; const { viem } = await network.create("whitechainSepolia"); const storage = await viem.deployContract("Storage"); console.log("Storage deployed to:", storage.address); ``` `network.create("whitechainSepolia")` opens a connection to the network defined in `hardhat.config.ts`. Passing the name makes the script explicit; `network.create()` with no argument uses whatever `--network` selects. Do not use `network.connect()`, which is deprecated in Hardhat 3 and will be removed. Hardhat 3 uses ESM with top-level `await`, so no `main()` wrapper is needed. ## 6. Deploy the contract ```bash theme={null} npx hardhat run scripts/deploy.ts --network whitechainSepolia ``` The contract address prints to the console. Copy it for verification. ## 7. Verify the contract `Storage` takes no constructor arguments, so pass only the address. ```bash theme={null} npx hardhat verify --network whitechainSepolia ``` For a contract with constructor arguments, add them after the address. ## Verify the result Open the contract address on the [explorer](https://explorer.testnet.whitechain.io). The Contract tab shows the source code and a verified marker. The same submission also lands on [Sourcify](https://sourcify.dev), because both verifiers are enabled in the config above. ## Related * [Deploy a contract](/learn/get-started/deploy-a-contract) * [Verify a proxy contract](/build/deploy/verify-proxy-contracts) * [Agent Skills](/build-with-ai/claude-skills) * [Hardhat Skills](https://github.com/NomicFoundation/hardhat-skills) * [Connect to Whitechain Sepolia](/learn/get-started/connect-wallet) * [Deploy with Remix](/build/deploy/deploy-with-remix) * [Faucet](/learn/get-started/get-testnet-wbt) # Deploy with Remix Source: https://l2docs.whitechain.io/build/deploy/deploy-with-remix Learn how to deploy and verify smart contracts on Whitechain Sepolia using the Remix Online IDE. This page shows how to deploy and verify a contract on Whitechain Sepolia from the browser using Remix. It needs no local setup. It is for developers who want to deploy without installing a toolchain, or who are new to the network. ## Before you deploy * A wallet with Whitechain Sepolia added. See [Connect to Whitechain Sepolia](/learn/get-started/connect-wallet). * Test WBT for gas. Claim it from the [faucet](/learn/get-started/get-testnet-wbt). * The [Remix IDE](https://remix.ethereum.org). ## 1. Create the contract In Remix, create `contracts/1_Storage.sol` and paste this contract. ```solidity theme={null} // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.30; contract Storage { uint256 number; function store(uint256 num) public { number = num; } function retrieve() public view returns (uint256) { return number; } } ``` ## 2. Compile the contract 1. Open the **Solidity Compiler** tab. 2. Set the compiler to a version that matches the pragma. For this contract, use `0.8.30`. 3. Click **Compile 1\_Storage.sol**. A green check on the tab means the contract is compiled. ## 3. Connect your wallet 1. Open the **Deploy & Run Transactions** tab. 2. Under **Environment**, select **Browser Extension**, then your wallet. 3. Approve the connection in the wallet. 4. Confirm the wallet is on Whitechain Sepolia and the account shows a WBT balance. ## 4. Deploy the contract 1. Confirm the Deploy panel shows Whitechain Sepolia (`1874`). 2. Confirm the `Storage` contract shows **Compiled**. 3. Turn on **Verify Contract on Explorers** to verify during deployment. This is optional. You can verify later with steps 5 and 6. 4. Leave **Value** at `0` and **Gas limit** on auto. 5. Click **Deploy** and confirm the transaction in your wallet. After the transaction confirms, the contract appears under **Deployed Contracts**. Copy its address. ## 5. Configure verification If you verify manually, point Remix at the Whitechain explorer first. 1. Open the **Contract Verification** plugin. 2. Open **Settings**. 3. Under **Blockscout - Whitechain Sepolia**, set **Instance URL** to `https://explorer.testnet.whitechain.io`. 4. Click **Save**. ## 6. Verify the contract 1. Open the **Contract Verification** tab. 2. Set **Chain** to Whitechain Sepolia (`1874`). 3. Paste the deployed contract address. 4. Set **Contract Name** to `Storage - contracts/1_Storage.sol`. 5. Select **Blockscout**. 6. Click **Verify**. ## Verify the result Open the contract address on the [explorer](https://explorer.testnet.whitechain.io). A verified contract shows its source code and a verified marker. You can then call, retrieve, and store from the explorer or from Remix. ## Related * [Deploy a contract](/learn/get-started/deploy-a-contract) * [Connect to Whitechain Sepolia](/learn/get-started/connect-wallet) * [Deploy with Hardhat](/build/deploy/deploy-with-hardhat) * [Verify a proxy contract](/build/deploy/verify-proxy-contracts) * [Faucet](/learn/get-started/get-testnet-wbt) # Verify a proxy contract Source: https://l2docs.whitechain.io/build/deploy/verify-proxy-contracts Verify EIP-1967 and UUPS proxy contracts on Whitechain Sepolia with Blockscout: verify the implementation, verify the proxy, and link the two so the proxy tab exposes the implementation's functions. Available on Whitechain Sepolia today. The steps use the Blockscout explorer at `https://explorer.testnet.whitechain.io`. For a plain, non-proxy contract, see [Deploy with Hardhat](/build/deploy/deploy-with-hardhat) or [Deploy with Foundry](/build/deploy/deploy-with-foundry). ## How proxy verification works An upgradeable contract is two contracts: a proxy that holds the state and a fixed address, and an implementation (also called the logic contract) that holds the code. The proxy forwards every call to the implementation with `delegatecall`, so users always interact with the proxy address while the logic can be swapped. Proxy verification is therefore a two-part job. You verify the implementation so its source and ABI are known, and you verify the proxy so its own source is known. Blockscout links the two by reading the EIP-1967 implementation slot from the proxy's storage. Once both are verified and linked, the proxy address gains a `Read/Write proxy` tab that exposes the implementation's functions. Whitechain runs standard OP Stack Blockscout, which reads these slots automatically: | Pattern | Implementation pointer | Detected automatically | | -------------------------- | --------------------------------------------------------------------------------- | ---------------------- | | EIP-1967 transparent proxy | Storage slot `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc` | Yes | | UUPS proxy (ERC1967) | Same EIP-1967 slot | Yes | | EIP-1967 beacon proxy | Beacon slot `0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50` | Yes | Transparent and UUPS proxies share one slot, so Blockscout cannot tell them apart. It labels every EIP-1967 detection "Transparent Proxy", including UUPS. The label is cosmetic and does not affect the linkage or the proxy tab. ## Before you start * A proxy and its implementation deployed on Whitechain Sepolia. See [Deploy with Hardhat](/build/deploy/deploy-with-hardhat) or [Deploy with Foundry](/build/deploy/deploy-with-foundry). * Three values recorded at deploy time: the implementation address, the proxy address, and the ABI-encoded initializer calldata passed as the proxy's second constructor argument. You cannot verify the proxy without the calldata. * The OpenZeppelin contracts installed, because the proxy source comes from that library. Run `npm install @openzeppelin/contracts` for Hardhat, or `forge install OpenZeppelin/openzeppelin-contracts` for Foundry. * The Blockscout verifier configured for chain `1874`. Hardhat needs the `chainDescriptors` entry shown on the [Deploy with Hardhat](/build/deploy/deploy-with-hardhat) page. Foundry takes the explorer URL on the command line. ### Hardhat 3 needs the proxy named as a build root Hardhat 3 only emits artifacts for contracts declared in files under `contracts/`. `ERC1967Proxy` lives in the OpenZeppelin package, so importing it from a local file is not enough: a file that declares no contract of its own produces no artifact. Name it in `solidity.npmFilesToBuild` instead. ```typescript hardhat.config.ts theme={null} solidity: { version: "0.8.30", npmFilesToBuild: [ "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol", ], }, ``` Without this, `hardhat compile` succeeds but no `ERC1967Proxy` artifact exists, and both deployment and verification fail with an artifact-not-found error. ## 1. Verify the implementation contract Verify the implementation exactly like any standalone contract. It takes no constructor arguments in most upgradeable patterns, because upgradeable contracts use an `initialize` function instead of a constructor. ```bash Terminal theme={null} npx hardhat verify --network whitechainSepolia ``` ```bash Terminal theme={null} forge verify-contract src/MyContractV1.sol:MyContractV1 \ --rpc-url https://rpc.testnet.whitechain.io \ --verifier blockscout \ --verifier-url https://explorer.testnet.whitechain.io/api/ ``` Foundry's `--verifier-url` ends with `/api/`. Hardhat's `chainDescriptors` `apiUrl` ends with `/api` and no trailing slash. The two tools differ; each form is correct for its own tool. ## 2. Verify the proxy contract The proxy is `ERC1967Proxy`, `TransparentUpgradeableProxy`, or `BeaconProxy`, and it was deployed with two constructor arguments: the implementation address and the initializer calldata. Pass both when you verify. ```bash Terminal theme={null} npx hardhat verify --force --network whitechainSepolia \ ``` `--force` is required here. Hardhat checks whether an address is already verified before submitting, and that check uses Blockscout's legacy `getsourcecode` endpoint. For a detected proxy, that endpoint resolves through to the implementation and returns the implementation's name and source. Hardhat reads that as "the proxy is already verified", skips the submission, and exits successfully while the proxy's own source stays unverified. `--force` submits anyway. ```bash Terminal theme={null} forge verify-contract \ lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol:ERC1967Proxy \ --rpc-url https://rpc.testnet.whitechain.io \ --verifier blockscout \ --verifier-url https://explorer.testnet.whitechain.io/api/ \ --constructor-args $(cast abi-encode "constructor(address,bytes)" ) ``` Do not trust an "already verified" message on a proxy address. Confirm it against the API in step 3, which reports the proxy's own verification state rather than the implementation's. ## 3. Confirm the result Check the API rather than the page, because the page can look correct while the proxy itself is unverified. ```bash Terminal theme={null} curl -s https://explorer.testnet.whitechain.io/api/v2/smart-contracts/ ``` Confirm four fields: | Field | Expected value | | ------------------- | ---------------------------------------------------------------------------------- | | `is_fully_verified` | `true` | | `name` | the proxy contract name, for example `ERC1967Proxy`, not the implementation's name | | `proxy_type` | `eip1967` | | `implementations` | an array containing the implementation address and its contract name | Then open the proxy on the [explorer](https://explorer.testnet.whitechain.io). Under `Contract` there are three tabs: | Tab | What it shows | | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `Code` | The proxy's own verified source, with an exact-match marker and the decoded constructor arguments. | | `Read/Write contract` | The proxy's own functions. A single tab with an All, Read, Write filter. | | `Read/Write proxy` | The implementation's functions, called through the proxy. Shows a banner naming the detected pattern and a link to the implementation address. | The implementation address has only `Code` and `Read/Write contract`, and no proxy tab. That is correct: it is not a proxy. Open `Read/Write proxy` and check that the functions listed are the implementation's, not the proxy's. The proxy's own ABI has 8 entries and no state getters. A UUPS implementation adds its own functions alongside the inherited `upgradeToAndCall`, `proxiableUUID`, `owner`, `transferOwnership`, `renounceOwnership`, and `UPGRADE_INTERFACE_VERSION`. `/api/v2/smart-contracts/
/methods-read` and `methods-write` return 404 on this Blockscout build. To check the proxy state from a script, read `proxy_type` and `implementations` from `/api/v2/smart-contracts/
`. ## 4. Verify again after an upgrade An upgrade points the proxy at new code, so the verification you already did no longer covers it. Two things happen when you call `upgradeToAndCall`. The proxy stays verified. Its own source and constructor arguments do not change, so you never re-verify the proxy itself. Blockscout re-reads the EIP-1967 slot on each page load and follows the new implementation automatically. The new implementation is unverified. Until you verify it, the API returns `implementations` with a `name` of `null`, and the `Read/Write proxy` tab cannot show the new functions. Verify the new implementation exactly as in step 1, then confirm the linkage as in step 3. ```bash Terminal theme={null} npx hardhat verify --network whitechainSepolia ``` Every implementation you deploy stays on chain at its own address, verified independently. Only the address in the EIP-1967 slot is live. Earlier implementations remain verified but unreferenced, which is expected and needs no cleanup. ## Worked example on Whitechain Sepolia A UUPS pair on Whitechain Sepolia, verified with the steps above and then upgraded once, for comparison against your own output. | Item | Value | | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | Proxy | [`0x9890f99ab68065020ebe6ef6e64831451e4ad98d`](https://explorer.testnet.whitechain.io/address/0x9890f99ab68065020ebe6ef6e64831451e4ad98d) | | Live implementation (BoxV2) | [`0x8f4f72dd4421bc39af3b3ff145e0702f6ac95fb9`](https://explorer.testnet.whitechain.io/address/0x8f4f72dd4421bc39af3b3ff145e0702f6ac95fb9) | | EIP-1967 slot on the proxy | `0x0000000000000000000000008f4f72dd4421bc39af3b3ff145e0702f6ac95fb9`, which decodes to the live implementation address | | Proxy `proxy_type` | `eip1967` | | Proxy source path | `npm/@openzeppelin/contracts@5.6.1/proxy/ERC1967/ERC1967Proxy.sol` | | Compiler, every contract | `v0.8.28+commit.7893614a`, EVM `cancun`, optimizer disabled | | Upgrade cost | 38,548 gas for `upgradeToAndCall` | The proxy was verified once, before any upgrade, and is still verified now. Every contract reports `is_fully_verified: true` and `verified_via_sourcify: false`, so Blockscout verified them natively. Two earlier implementations remain on chain, verified and no longer referenced by the slot: BoxV1 [`0xc6d4397ba81e5f74e49d77f52b1d6c364a7a060b`](https://explorer.testnet.whitechain.io/address/0xc6d4397ba81e5f74e49d77f52b1d6c364a7a060b) and an earlier BoxV2 [`0x92b00d7db1aef4801f81167ffe58b2d35f0b9b4c`](https://explorer.testnet.whitechain.io/address/0x92b00d7db1aef4801f81167ffe58b2d35f0b9b4c). ## Troubleshooting Blockscout's legacy `getsourcecode` resolves through the proxy to the implementation, satisfying Hardhat's pre-check. Re-run with `--force`, then confirm `name` and `is_fully_verified` on the proxy through the API. Hardhat 3 emits artifacts only for contracts declared under `contracts/`. Add the proxy to `solidity.npmFilesToBuild`, as shown in "Before you start". The toolbox enables several verifiers. A different verifier in the same run failed, for example Etherscan without an API key. Read the per-verifier output. A Blockscout success is independent of the others. Both patterns use the same EIP-1967 slot, so Blockscout cannot distinguish them. The label is cosmetic and the linkage is correct. The solc version, EVM version, or optimizer setting differs from the deployed bytecode. Compare `artifacts/build-info` against the values the explorer reports, then verify again with the matching settings. The implementation is not verified, or the slot is non-standard. Verify the implementation first, then reload the proxy page. Blockscout re-reads the slot on load. The private key in `.env` has no `0x` prefix. Add the prefix, or normalize it in `hardhat.config.ts` before passing it to `accounts`. ## Related * [Deploy with Hardhat](/build/deploy/deploy-with-hardhat) * [Deploy with Foundry](/build/deploy/deploy-with-foundry) * [Block explorer overview](/build/block-explorer/overview) * [Connect to Whitechain Sepolia](/learn/get-started/connect-wallet) # For project partners Source: https://l2docs.whitechain.io/build/ecosystem/for-partners Why deploy on Whitechain: deep WhiteBIT integration, exchange-listing pathway, ecosystem co-marketing, and OP Stack technical foundations. Whitechain is more than an EVM-compatible Layer 2. It is the on-chain layer of the WhiteBIT ecosystem, with deep integration into a centralized exchange that serves more than 5 million active users. For project teams considering where to deploy, that integration is the difference. A generic L2 has none of it. Whitechain brings built-in distribution, liquidity, and a path to an exchange listing. This page is for BD leads, founders, and integration teams evaluating Whitechain as a deployment target. ## Distribution at the chain layer WhiteBIT is one of the largest centralized exchanges in Europe, with more than 5 million active users. Whitechain is the chain those users interact with when they hold or use WBT, which gives projects on Whitechain a measurable head start over chains where user acquisition has to be built from scratch. ## Direct exchange withdrawal to L2 After Mainnet launch, WhiteBIT users will withdraw WBT directly to Whitechain L2 from the exchange UI. There is no third-party bridge in the middle of that path. This means new users land on Whitechain with WBT in their wallet through the exchange flow they already use. ## Listing and co-marketing pathway Projects on Whitechain have a defined path to: * **WhiteBIT exchange listing.** Tokens that meet the listing criteria can be considered for the WhiteBIT order books, opening centralized-exchange liquidity to your users. * **Joint announcements.** Significant integrations and product launches on Whitechain are eligible for joint communication across WhiteBIT and Whitechain channels, including the Whitechain blog and social channels. The exact criteria, scope, and process are agreed per partnership. The path is not automatic; it is a structured engagement. ## Whitechain Foundation Grants The Whitechain Foundation runs a Grants Program with financial, marketing, and technical support for projects building on Whitechain. Grants are distributed via vesting rather than lump sums, which aligns funding with delivery milestones. Details on the [Whitechain Grants Program](https://whitechain.io/grants). ## Technical foundations The partnership value sits on top of a standard OP Stack technical floor: * EVM-compatible Layer 2 on the OP Stack, settling on Ethereum via blob data availability * 1-second block times with Flashblocks streamed about every 200 ms * Transaction fees of about 0.000105 WBT for a plain transfer at the 5 gwei base fee floor, well under one cent at \$50 per WBT, paid in WBT * Full EVM compatibility, deploy with Hardhat, Foundry, or any Ethereum toolchain * Canonical bridge to Ethereum for asset and message transfer For the network parameters and endpoints, see [Network reference](/learn/network/reference). ## First-party products on chain Whitechain ships with a small set of first-party products that signal the ecosystem is active from day one: * **Whitechain Portal.** First-party bridge for moving assets between Ethereum and Whitechain. * **WhiteSwap.** First-party DEX for swaps and liquidity on Whitechain. * **Block Explorer.** Production-grade Blockscout-based explorer. These products work as integration surfaces for partner projects: list pools on WhiteSwap, route bridge flows through Portal, surface contract activity on the explorer. ## Who is the typical partner Whitechain is most useful to projects that: * Want exposure to a large, existing crypto user base without rebuilding distribution * Need a low-cost, EVM-compatible L2 for high-volume application logic * Want a clear path to centralized-exchange listing for your on-chain product * Value being part of a focused ecosystem rather than one of many on a generic chain Common partner profiles include DEXs, lending protocols, NFT platforms, prediction markets, games, and consumer dapps with payment flows. ## How to engage 1. **Review the docs.** Start with [Introduction](/learn/general/what-is-whitechain) and [Network reference](/learn/network/reference) to confirm Whitechain fits your technical requirements. 2. **Apply for a grant** if your project qualifies. [Whitechain Grants Program](https://whitechain.io/grants). 3. **Reach out** via the [Discord community](https://discord.gg/eZwjxwNsU) for general partnership inquiries. 4. **Build and launch.** Deploy on Whitechain Sepolia (chain ID `1874`) and migrate to Mainnet at launch. ## Related * [Introduction to Whitechain](/learn/general/what-is-whitechain) * [Network reference](/learn/network/reference) * [Getting WBT](/learn/general/get-wbt) * [OP Stack canonical bridge](/build/bridge/bridge-assets) * [Whitechain Grants Program](https://whitechain.io/grants) # Wallets Source: https://l2docs.whitechain.io/build/wallet/wallets Connect MetaMask, Rabby, or Reown to Whitechain, and use Safe for multisig treasury and admin operations. Available on Testnet today. Mainnet support is published at Mainnet launch. Whitechain works with any standard EVM wallet. This page covers the wallets Whitechain recommends, for everyday signing and for multisig treasury and admin operations. ## MetaMask [MetaMask](https://metamask.io) is a browser-extension wallet and the most common way to connect to Whitechain. It injects a provider that dapps read through `window.ethereum`, the same interface wagmi's `injected` connector uses in the [dapp templates](/build/dapps/dapp-with-nextjs). Add the network with the steps in [Connect to Whitechain Sepolia](/learn/get-started/connect-wallet). MetaMask account menu with Networks highlighted MetaMask Manage networks modal with Add a custom network highlighted MetaMask Add a custom network form ## Rabby [Rabby](https://rabby.io) is a browser-extension wallet built for multi-chain use. It tracks every network you add. It switches to the one a dapp requests on its own, so you don't switch networks by hand before signing. It reads the same network parameters as MetaMask. Rabby wallet home screen with Add assets highlighted Rabby Receive assets screen with Add custom network highlighted Rabby Add Custom Network form ## Reown [Reown](https://reown.com) (formerly WalletConnect) provides the AppKit toolkit. It connects a dapp to a mobile or hardware wallet over WalletConnect instead of a browser extension. The connection opens with a QR code scan or a deep link. AppKit wraps wagmi, so a dapp keeps the same connection hooks regardless of which wallet connects. Reown AppKit connect wallet modal listing WalletConnect, MetaMask, Trust Wallet, and Rabby Wallet Reown AppKit sign-in options with email, Google, X, Discord, and GitHub ## Safe [Safe](https://safe.global) is the multisig wallet Whitechain recommends for treasury and admin operations. A Safe account sets a signer threshold, for example 2 of 3. A transaction only executes once enough signers approve it, so no single key can move funds or change an owned contract alone. Each signer connects with their own wallet, such as MetaMask or Rabby, to review and sign. The Safe interface holds a transaction in a pending queue until it collects enough signatures. Multisignature wallet diagram showing two signers approving a transaction before it reaches the receiver ## Related * [Connect to Whitechain Sepolia](/learn/get-started/connect-wallet) * [Next dapp template](/build/dapps/dapp-with-nextjs) # Frequently asked questions Source: https://l2docs.whitechain.io/learn/general/faq Common questions about Whitechain: what it is and who runs it, Mainnet status, WBT and gas fees, transaction finality, public RPC endpoints and rate limits, contract deployment, wallets, bridging, withdrawals, migration, and grants. Available on Testnet today (Whitechain Sepolia, chain ID `1874`). Mainnet is not yet live; its chain ID, endpoints, and launch date are published before Mainnet launch. ## General Whitechain is powered by WhiteBIT, one of the largest centralized exchanges in Europe, with more than 5 million active users. It is an EVM-compatible Layer 2 built on the OP Stack. It settles on Ethereum through blob data availability and uses WBT (WhiteBIT Coin) as the native gas token. WhiteBIT operates the sequencer at launch and is the canonical issuer of WBT. See [Introduction to Whitechain](/learn/general/what-is-whitechain). The L2 is live on Testnet today, as Whitechain Sepolia (chain ID `1874`). Mainnet is not yet live. Its chain ID, endpoints, and launch date are published before Mainnet launch. Build and test on Whitechain Sepolia now, then move to Mainnet at launch. See the [Network reference](/learn/network/reference). The OP Stack separates execution, data availability, and settlement into distinct layers. Every transaction's data is published and settled on Ethereum, so Whitechain inherits Ethereum's security and data-durability guarantees instead of carrying its own. The chain keeps full EVM compatibility, so any contract or toolchain that works on Ethereum works on Whitechain. The OP Stack also underlies Base, Optimism, and the wider Superchain, which gives Whitechain shared tooling and a path toward decentralized sequencing. Nothing is required from you. The legacy L1 is live and producing blocks today. At L2 Mainnet launch the full L1 state migrates into the L2 genesis automatically. Native WBT balances are preserved to the wei, and contracts keep their bytecode, storage, nonces, and addresses. ERC-20 and other token balances live in contract storage, so they migrate with it. Transaction history and pending mempool transactions are not carried over; only final state is. After the snapshot block the L1 answers read methods only, and it is decommissioned later, so export any historical data you need before then. See [Migration to L2](/learn/general/migration). Holders have no deadline, because holders have no task to complete. There is nothing to claim, swap, or unlock. Integrations have milestones to plan against: the L1 snapshot block, L2 Mainnet launch, the domain cut-over, the L1 read-only period, and L1 decommissioning. No date for any of them is announced yet. Whitechain publishes migration dates on [X](https://x.com/Whitechain_io), in the [Whitechain Telegram channel](https://t.me/Whitechain_io), and on the migration page. See [Deadlines](/learn/general/migration#deadlines). Your users' balances migrate on their own; your connection settings do not. Repoint to the L2 chain ID, RPC URL, and explorer, stop crediting L1 deposits at the snapshot block, and export any L1 history your systems need before decommissioning. Contracts need no redeployment. The hostnames also change. `rpc.whitechain.io` and `explorer.whitechain.io` serve the legacy L1 (chain ID `1875`) today and serve the L2 after the cut-over. The L1 explorer moves to `https://legacy-explorer.whitechain.io`, the L1 website moves to `https://legacy.whitechain.io`, and the legacy read-only RPC hostname is published in the [Network reference](/learn/network/reference) before Mainnet launch. Confirm which chain an endpoint serves with `eth_chainId` rather than by hostname. See [Endpoint and domain changes](/learn/general/migration#endpoint-and-domain-changes) and [Integrations and service operators](/learn/general/migration#integrations-and-service-operators). Blocks arrive every second, and Flashblock preconfirmations arrive about every 200 ms. Those are the sequencer's word only. Your transaction becomes safe when its batch is confirmed on Ethereum, up to about 30 minutes on Testnet, and finalized about 13 minutes after that. Only finalization is irreversible. See [Transaction finality](/learn/network/transaction-finality). WhiteBIT operates the single sequencer today, so block production depends on it. If the sequencer refuses your transaction, you can submit it through the `OptimismPortal` contract on Ethereum. The protocol must include it within the sequencing window, about 12 hours, or the chain is invalid. Transaction data is posted to Ethereum, so the sequencer cannot hide or alter a transaction. See [What keeps your funds safe](/learn/network/transaction-finality#what-keeps-your-funds-safe). For WBT held on the exchange, use WhiteBIT support. For integration, node, or contract questions, use the [Whitechain Discord](https://discord.gg/eZwjxwNsU). Migration dates are announced on [X](https://x.com/Whitechain_io) and in the [Whitechain Telegram channel](https://t.me/Whitechain_io). Support never asks for your seed phrase, private key, or a wallet signature to complete a migration step. See [Where to get help](/learn/general/migration#where-to-get-help). ## Fees and WBT You pay gas in WBT, the native gas token, configured as the OP Stack custom gas token with 18 decimals. Every fee on Whitechain is paid in WBT, regardless of network, so you need a WBT balance to send any transaction. WBT is the native asset of Whitechain and the token of the WhiteBIT ecosystem, so a single asset covers on-chain gas and exchange utility. See [Network fees](/learn/network/network-fees). A fee has two parts: the L2 execution fee (gas used times the gas price) and the L1 data fee for publishing your transaction to Ethereum. At the 5 gwei base-fee floor, a plain WBT transfer of 21,000 gas costs about 0.000105 WBT to execute, well under one cent at \$50 per WBT. Deploying a small contract costs about 0.0006 WBT, roughly three cents at that price. The L1 data fee is added on top and tracks Ethereum's gas price, so totals rise when Ethereum is busy. See [Network fees](/learn/network/network-fees). Buy WBT on [WhiteBIT](https://whitebit.com), through the Convert tool or a fiat deposit, then withdraw it to your Whitechain address. You can also bridge ERC-20 WBT from Ethereum mainnet. After Mainnet launch, WhiteBIT users withdraw WBT directly to Whitechain L2 from the exchange UI, with no third-party bridge in between. See [Getting WBT](/learn/general/get-wbt). Claim test WBT from the faucet at [faucet.testnet.whitechain.io](https://faucet.testnet.whitechain.io). Add Whitechain Sepolia to your wallet first, using chain ID `1874` and the RPC at `https://rpc.testnet.whitechain.io/`. Sign in with a GitHub account at least 30 days old. The standard drip is 0.5 WBT per 24-hour rolling window. Test WBT has no real value and cannot be bridged to mainnet. See [Faucet](/learn/get-started/get-testnet-wbt). The explorer may show only the L2 execution fee. The L1 data fee for publishing your transaction to Ethereum is charged on top of it, and the difference is that fee. Both parts are always charged. See [Network fees](/learn/network/network-fees). Yes. The network does the work up to the point of failure, so you pay for the gas used, and both fee parts are charged. Nothing is refunded. Set a sufficient gas limit so a contract call does not run out partway through. See [Network fees](/learn/network/network-fees). The L2 base fee follows EIP-1559 with elasticity 6 and denominator 250, so it moves by at most 2% per block and never drops below 5 gwei. The L1 data fee is the variable part: it tracks Ethereum's base fee and blob base fee, so your total rises when Ethereum is busy. See [Fee parameters](/learn/network/network-fees#fee-parameters). ## For developers Yes. Whitechain keeps full EVM compatibility, so any contract or toolchain that runs on Ethereum runs on Whitechain without modification. Deploy with Hardhat, Foundry, Remix, or any Ethereum library. See [Deploy a contract](/learn/get-started/deploy-a-contract). Whitechain Sepolia is live now; Mainnet parameters are published before Mainnet launch. | Network | Chain ID | RPC | Explorer | | ------------------ | ------------------------------------ | ------------------------------------ | ------------------------------------------------------------------------ | | Mainnet | TBD, published before Mainnet launch | TBD, published before Mainnet launch | TBD, published before Mainnet launch | | Whitechain Sepolia | `1874` | `https://rpc.testnet.whitechain.io` | [explorer.testnet.whitechain.io](https://explorer.testnet.whitechain.io) | Whitechain Sepolia also exposes a WebSocket endpoint at `wss://rpc.testnet.whitechain.io/ws`. See the [Network reference](/learn/network/reference). Whitechain Sepolia is a standard EVM network, so you add it by chain ID and RPC URL and need no Whitechain-specific plugin. Deploy with your usual toolchain, then verify the source on the [explorer](https://explorer.testnet.whitechain.io) or [Sourcify](https://sourcify.dev). The [Deploy with Hardhat](/build/deploy/deploy-with-hardhat) guide covers the full deploy-and-verify flow; [Remix](/build/deploy/deploy-with-remix) and [Foundry](/build/deploy/deploy-with-foundry) guides are also available. Use Whitechain Sepolia (chain ID `1874`), the public test environment for dapp deployment, integration testing, and claiming test WBT from the faucet. See [Connect to Whitechain Sepolia](/learn/get-started/connect-wallet). Any standard EVM wallet. Whitechain documents MetaMask, Rabby, and Reown (formerly WalletConnect) for everyday signing, and Safe for multisig treasury and admin operations. No Whitechain-specific plugin exists, because you add the network by chain ID and RPC URL. See [Wallets](/build/wallet/wallets). No. Use the Ethereum libraries you already use. viem includes OP Stack support, so it returns the L1 data fee and keeps working across network upgrades without edits to your code. See [Use viem with Whitechain](/build/dapps/use-viem). ## Public RPC and infrastructure Yes, per client IP address. You get 50 requests per second, plus a burst allowance of 500 requests above that. When you exceed it, the endpoint returns HTTP `429 Too Many Requests` with a `Retry-After` header. The limiter counts each HTTP request, so batching several calls into one request helps. See [Public RPC endpoints](/learn/network/reference#public-rpc-endpoints). | Property | Value | | -------------------------------- | -------------------------------------------------------- | | Sustained rate | 50 requests per second per client IP | | Burst allowance | Up to 500 requests above the sustained rate | | What the limit counts | Each HTTP request, not each JSON-RPC call inside a batch | | Status code when you are limited | `429 Too Many Requests` | | Retry hint | `Retry-After: 1` header on every `429` response | | WebSocket limits | Published once they are finalized | No. The public RPC and WebSocket endpoints need no API key and no IP allowlist. Reads and writes are open to any client, subject to the limits above. The endpoint returns HTTP `429 Too Many Requests` with a `Retry-After` header. Retry with exponential backoff, starting from the `Retry-After` value. A client that retries immediately stays above the limit and keeps failing. Batch related calls into one JSON-RPC request, because the limiter counts one HTTP request per batch. Subscribe to `newHeads` over WebSocket instead of polling `eth_blockNumber`, and cache receipts and finalized blocks on your side. See [Working within the limits](/learn/network/reference#working-within-the-limits). Not yet. Whitechain is evaluating partnerships with commercial RPC providers, and partner endpoints are published in the [Public RPC endpoints](/learn/network/reference#public-rpc-endpoints) table when they go live. Until then, `https://rpc.testnet.whitechain.io` and `wss://rpc.testnet.whitechain.io/ws` are the only public endpoints for Whitechain Sepolia. Treat any other endpoint claiming to serve chain ID `1874` as unofficial. Yes. Whitechain Sepolia exposes `wss://rpc.testnet.whitechain.io/ws`. It serves the standard Ethereum JSON-RPC API and supports `eth_subscribe`, including `newHeads`, `logs`, and `newPendingTransactions`. Reuse one connection for multiple subscriptions rather than opening a connection per query. See [WebSocket and subscriptions](/learn/network/testnet#websocket-and-subscriptions). No. The Flashblocks stream is separate from the public RPC, which does not carry it. To read it, request the Flashblocks WebSocket URL from the Whitechain team. See [Flashblocks](/learn/network/transaction-finality#flashblocks). Run your own node. The stack is a pair of services, `op-reth` and `op-node`, and ships three profiles: `full-snap-node` (recommended default), `full-node`, and `archive-node`. Archive is the one required for historical tracing and `eth_call` at old blocks. Your node holds no Whitechain keys and forwards `eth_sendRawTransaction` to the public RPC. See [Node operators](/operate/run-a-node/overview). Yes to both. Whitechain uses Blockscout, and its API is public, read-only, and needs no API key. It is rate limited per IP, so batch related lookups and cache results. For sustained querying across full chain history, run your own indexer or Blockscout instance. See [Block explorer overview](/build/block-explorer/overview). Check [status.whitechain.io](https://status.whitechain.io/) for the RPC, explorer, and faucet. A `429` response is a rate limit on your side, not an outage, and it clears once your request rate drops. ## Bridging and withdrawals Use the OP Stack canonical bridge. Deposits from Ethereum to Whitechain arrive on L2 in about 2 minutes. For an ERC-20 token, you approve the L1 standard bridge and call `depositERC20`; the L2 token must already be deployed. Native WBT cannot go through the canonical bridge, because Whitechain runs in custom gas token mode: use the faucet on testnet, and the liquidity portal on mainnet. You can also move WBT through WhiteBIT deposits and withdrawals. See the [OP Stack canonical bridge](/build/bridge/bridge-assets). On testnet a full withdrawal takes about an hour, because the dispute-resolution delays are intentionally shortened. On mainnet it takes minimum 7 days. This delay is enforced by the protocol and cannot be skipped, so plan treasury and time-sensitive flows accordingly. Deposits run the other way in about 2 minutes. See [Transaction finality](/learn/network/transaction-finality). Whitechain runs in OP Stack custom gas token mode, where the native token is WBT instead of ETH. The canonical bridge does not carry the native gas token in that mode. Use the faucet on testnet, the liquidity portal on mainnet, or WhiteBIT deposits and withdrawals. ERC-20 tokens do go through the canonical bridge. See [OP Stack canonical bridge](/build/bridge/bridge-assets). No. The dispute window is enforced by the protocol on Ethereum, so no fee or support request shortens it. A withdrawal is proven and finalized in two steps, and it cannot be cancelled once proven. Plan treasury flows around the 7-day minimum on mainnet. See [Transaction finality](/learn/network/transaction-finality). After Mainnet launch, yes. WhiteBIT users withdraw WBT directly to Whitechain L2 from the exchange UI, with no third-party bridge in the path. New users land on Whitechain with WBT in their wallet through the exchange flow they already use. See [Getting WBT](/learn/general/get-wbt). ## For projects The Whitechain Foundation runs a Grants Program with financial, marketing, and technical support for projects building on Whitechain. Grants are distributed via vesting rather than lump sums, which aligns funding with delivery milestones. See the [Whitechain Grants Program](https://whitechain.io/grants) and [For project partners](/build/ecosystem/for-partners). ## Related * [Introduction to Whitechain](/learn/general/what-is-whitechain) * [Network reference](/learn/network/reference) * [Public RPC endpoints and rate limits](/learn/network/reference#public-rpc-endpoints) * [Whitechain Sepolia full parameters](/learn/network/testnet) * [Network fees](/learn/network/network-fees) * [Transaction finality](/learn/network/transaction-finality) * [Getting WBT](/learn/general/get-wbt) * [Migration to L2](/learn/general/migration) * [Wallets](/build/wallet/wallets) * [Block explorer overview](/build/block-explorer/overview) * [Node operators](/operate/run-a-node/overview) * [OP Stack canonical bridge](/build/bridge/bridge-assets) # Getting $WBT Source: https://l2docs.whitechain.io/learn/general/get-wbt How to acquire WBT for gas on Whitechain L2: buy on the WhiteBIT exchange, use fiat on-ramps, or bridge ERC-20 WBT from Ethereum mainnet. WBT is the native gas token on Whitechain L2 and an ERC-20 token on Ethereum mainnet. WhiteBIT is the canonical issuer of WBT and the team behind Whitechain. This page covers the three ways to acquire WBT for use on Whitechain: buying on the exchange, using a fiat on-ramp, or bridging from Ethereum. For test WBT on the public testnet, see [Testnet faucet](/learn/get-started/get-testnet-wbt). ## On the WhiteBIT exchange Available now. Direct withdrawal to L2 Mainnet becomes available after Mainnet launch. [WhiteBIT](https://whitebit.com) is the primary listing for WBT and the deepest source of liquidity. As the canonical issuer of WBT, WhiteBIT supports direct deposits and withdrawals to and from the Whitechain network. You can buy WBT with crypto via the Convert tool or with fiat through a deposit and trade. ### Buy with crypto 1. Open [WhiteBIT](https://whitebit.com) and sign in. 2. Top up your balance with any supported asset. 3. Open the Convert tool, choose your source asset, and select WBT as the destination. 4. Confirm the conversion. WBT lands on your Main balance immediately. ### Buy with fiat 1. Deposit fiat to your WhiteBIT account using a card or bank transfer, depending on the supported methods in your region. 2. Use the Convert tool or the Spot Trading terminal to buy WBT. ### Withdraw to Whitechain 1. On the Balance page, find WBT and select Withdraw. 2. In the Network section, select the Whitechain option. 3. Enter the amount and your Whitechain address, then confirm with email and 2FA codes. Native WBT on L2 Mainnet becomes available through the same withdrawal flow once Mainnet is live. ## Bridge from Ethereum The Portal bridge's Ethereum to Whitechain route is deployed on testnet today. Mainnet routes are published at Mainnet launch. On the public testnet, you can also claim test WBT from the [faucet](/learn/get-started/get-testnet-wbt) instead of bridging. WBT is an ERC-20 token on Ethereum. The [Portal bridge](/build/bridge/portal-bridge), Whitechain's centralized bridge, is the path for moving WBT between Ethereum and Whitechain. The OP Stack canonical bridge has no deposit or withdrawal path for native WBT, on testnet or mainnet. 1. Confirm you hold WBT on Ethereum. On mainnet, the token is at `0x925206b8a707096ed26ae47c84747fe0bb734f59`; verify on [Etherscan](https://etherscan.io/token/0x925206b8a707096ed26ae47c84747fe0bb734f59). 2. Submit the transfer through the Portal on Ethereum and sign one transaction, with your Whitechain address as the recipient. 3. Wait for the source network to confirm, about 12 minutes on Ethereum. 4. Once the destination network holds enough liquidity, the Portal relayer releases the funds on Whitechain as a native WBT balance. Transfers are subject to the Portal's daily limits. For the full transfer flow, statuses, limits, and contract addresses, see [Portal bridge](/build/bridge/portal-bridge). ## On-ramps Limited at launch. WhiteBIT fiat purchases (see above) cover the primary path. Third-party on-ramp partners will be announced separately. Most users today should route through WhiteBIT for fiat purchases. When additional on-ramp providers (MoonPay, Transak, Ramp, and similar) integrate WBT or Whitechain directly, they will be listed here. ## Token reference | Network | Form | Address | Decimals | | ---------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------- | -------- | | Ethereum mainnet | ERC-20 | [`0x925206b8a707096ed26ae47c84747fe0bb734f59`](https://etherscan.io/token/0x925206b8a707096ed26ae47c84747fe0bb734f59) | 18 | | Whitechain L2 | Native gas token | n/a (native asset) | 18 | ## Testnet WBT For development and testing on Whitechain Sepolia (chain ID `1874`), do not use mainnet WBT. Claim test WBT from the [Testnet faucet](/learn/get-started/get-testnet-wbt) instead. Test WBT has no real value and cannot be bridged to mainnet. ## Related * [Network reference](/learn/network/reference) * [Faucet](/learn/get-started/get-testnet-wbt) * [Portal bridge](/build/bridge/portal-bridge) * [OP Stack canonical bridge](/build/bridge/bridge-assets) * [Migration to L2](/learn/general/migration) # Migration to L2 Source: https://l2docs.whitechain.io/learn/general/migration The five L1 sunset stages, what read-only mode means, the hostname cut-over, and what WBT holders and integrations do before L2 Mainnet launch. The legacy Layer 1 (L1) is live and producing blocks. The Layer 2 (L2) is live on Testnet as Whitechain Sepolia (chain ID `1874`). State migration and the end of L1 block production happen at L2 Mainnet launch, on a date that is not yet announced. When migration happens, **you will not need to do anything**. Whitechain runs as an EVM-compatible L1 operated by WhiteBIT. It is moving to an L2 built on the OP Stack. At migration the full L1 state becomes the L2 genesis, so your funds and contracts carry over as they are, at the same addresses. ## Who this page is for This article is for the people affected by the L1 to L2 migration: users of the L1, holders of WBT on the old chain, and the support and community teams who answer their questions. The answer is the same for all of them. No action is required. ### WBT holders on the old chain At migration, your WBT balance will be carried over to L2 exactly as it stands at the snapshot. You will not need to claim, swap, re-register, or move it. WBT stays the native gas token on L2, and its supply and tokenomics do not change. If you keep WBT on WhiteBIT, there is nothing to do. The exchange handles the transition for you. If you self-custody, your balance appears on L2 at the same address once migration completes. ### Users of the L1 Every account, contract, and storage slot will be migrated into the L2 genesis. The migration copies the full L1 state instead of restarting from empty, so there is no gap and no redeployment step. Work continues on L2 from the point where L1 stops. | Item | What happens at migration | | ---------------------------------- | ------------------------------------------------------------ | | Native WBT balances | Preserved to the wei | | Smart contracts | Bytecode, storage, and nonces preserved; addresses unchanged | | Token balances (ERC-20 and others) | Held in contract storage, so migrated with it | | Transaction history and event logs | Not migrated; final state only | | Pending (mempool) transactions | Not migrated | ### Integrations and service operators If you run software that reads or writes the legacy L1, you have changes to make, even though your users do not. User balances migrate on their own; connection settings do not. The legacy L1 mainnet runs at chain ID `1875` (hex `0x753`), with its JSON-RPC endpoint at `https://rpc.whitechain.io` and its explorer at `https://explorer.whitechain.io` today. Both hostnames serve the L2 after L2 Mainnet launch, and the L1 services move to `legacy` hostnames. See [Endpoint and domain changes](#endpoint-and-domain-changes). | Integration type | What to change | | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Exchange or custodian | Repoint deposit and withdrawal infrastructure to the L2 chain ID and RPC endpoint. Stop crediting L1 deposits at the snapshot block. Confirm each endpoint with `eth_chainId` rather than by hostname, because `https://rpc.whitechain.io` serves the L2 after the cut-over. | | Wallet or wallet provider | Update the network entry to the L2 chain ID, RPC endpoint, currency symbol, and explorer URL. Point any retained L1 entry at the legacy read-only endpoints, not at `https://rpc.whitechain.io` or `https://explorer.whitechain.io`. | | Dapp or smart contract owner | No redeployment. Contract addresses, code, and storage carry over. Repoint your RPC URL and chain ID, and re-verify your contracts on the L2 explorer, which serves at `https://explorer.whitechain.io` after the cut-over. | | Indexer, analytics, or accounting system | L1 transaction history and event logs are not migrated. Export the history you need before L1 decommissioning: from `https://explorer.whitechain.io` while the L1 is live, and from `https://legacy-explorer.whitechain.io` after the cut-over. Then index the L2 from its genesis block. | | Bridge or liquidity provider | The L2 has a native OP Stack canonical bridge. Review whether your existing L1 bridge route stays in service. See [OP Stack canonical bridge](/build/bridge/bridge-assets). | | Payment processor or bot | Update chain ID, RPC, and fee logic. The L1 charges a fixed 10 gwei gas price; the L2 uses EIP-1559 and adds an L1 data fee. See [Network fees](/learn/network/network-fees). | Test every change against Whitechain Sepolia (chain ID `1874`) before Mainnet migration. Sepolia runs the same OP Stack software as L2 Mainnet, so a fix that works there works at launch. See the [Network reference](/learn/network/reference). ### Support and community teams When a user asks what they must do, the answer is nothing. Balances, contracts, and tokens move automatically. Point users to this page, to the [FAQ](#faq), and to the channels in [Where to get help](#where-to-get-help). Migration events attract phishing. The guidance in [Staying safe](#staying-safe) is the part to repeat. ## Why Whitechain is moving to L2 The OP Stack separates execution, data availability, and settlement into distinct layers, which lowers upgrade complexity and sets up later performance work. Chain data is published and settled on Ethereum, so Whitechain inherits Ethereum's security and data-durability guarantees instead of carrying its own. The OP Stack is also the base for Base, Optimism, and the wider Superchain, so building on it gives Whitechain shared tooling and a path toward decentralized sequencing. Moving off a single-validator PoA model reduces the centralization of the previous L1. ## How the migration works The migration follows a freeze then dump procedure at Mainnet: 1. Freeze the L1 at a chosen snapshot block, so it produces no further blocks. 2. Export the full state at that block and verify it against the block state root and the known total supply. 3. Transform that state into the OP Stack L2 genesis, so L2 starts with the complete L1 state in place. ### Scope of the migration Migration applies to Mainnet only. | Network | Chain ID | Migrated to L2 | Replacement | | ----------------- | -------------------- | -------------------------------------------------- | ------------------------------------------------- | | Legacy L1 mainnet | `1875` (hex `0x753`) | Yes. Its full state becomes the L2 Mainnet genesis | Whitechain L2 Mainnet | | Legacy L1 testnet | `2625` (hex `0xa41`) | No | Whitechain Sepolia, chain ID `1874` (hex `0x752`) | The legacy L1 testnet is not migrated. Whitechain Sepolia starts from its own genesis, so contracts and balances on chain ID `2625` do not appear on chain ID `1874`. Redeploy test contracts on Whitechain Sepolia and claim test WBT from the [faucet](/learn/get-started/get-testnet-wbt). ## What changes on L2 | Aspect | Layer 1 | Layer 2 | | ------------------- | ------------------------------------------------ | --------------------------------------------------------------- | | Architecture | Execution, consensus, and data in one service | OP Stack: separate execution, data availability, and settlement | | Consensus | Proof of Authority (Clique, EIP-225) by WhiteBIT | OP Stack sequencer, operated by WhiteBIT at launch | | Gas token | WBT | WBT | | Gas model | Fixed 10 gwei, paid in WBT | EIP-1559 with OP Stack parameters, paid in WBT | | Data and settlement | Standalone L1 | Published and settled on Ethereum through blobs | | Bridging | Third-party bridge only | Native OP canonical bridge plus third-party bridges | For chain IDs, block times, block fields, and endpoints, see the [Network reference](/learn/network/reference). ## L1 sunset stages The L1 shuts down in five stages. Stages 1 to 3 happen on the day L2 Mainnet launches. Stages 4 and 5 follow it. | Stage | What happens | What stops working at this stage | | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | 1. L1 block production ends | The L1 sequencers stop. The last block they produce is the snapshot block. | L1 transactions. A transaction still in the L1 mempool at the snapshot block never executes. | | 2. State dump and L2 genesis | The full L1 state at the snapshot block is exported and transformed into the L2 genesis. | Nothing beyond stage 1. The L1 is already halted. | | 3. Domain cut-over | Every L1-linked domain moves to a `legacy` hostname. `whitechain.io` and `explorer.whitechain.io` begin serving the L2. | The L1 website and L1 explorer at their current hostnames. Requests to those hostnames reach the L2 instead. | | 4. L1 read-only period | The L1 explorer and the L1 JSON-RPC API answer read methods at the legacy hostnames. | Write methods on the L1 JSON-RPC API. No new L1 transaction is possible. | | 5. L1 decommissioning | The L1 is shut down permanently. | The L1 explorer and the L1 JSON-RPC API. L1 transaction history is no longer reachable from any Whitechain endpoint. | The length of the read-only period is not yet announced. ### What read-only mode means Read-only mode applies to the legacy L1 mainnet, chain ID `1875`. The L1 JSON-RPC API answers read methods against L1 state up to the snapshot block. It refuses the methods that would submit a transaction. | Category | Methods | Result during the read-only period | | --------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------ | | Account and state reads | `eth_getBalance`, `eth_getCode`, `eth_getStorageAt`, `eth_getTransactionCount` | Answered, as of the snapshot block | | Block and transaction reads | `eth_blockNumber`, `eth_getBlockByNumber`, `eth_getBlockByHash`, `eth_getTransactionByHash`, `eth_getTransactionReceipt` | Answered | | Log and call reads | `eth_getLogs`, `eth_call` | Answered | | Chain identity | `eth_chainId`, `net_version` | Answered, reporting chain ID `1875` | | Transaction submission | `eth_sendRawTransaction`, `eth_sendTransaction` | Refused. No new L1 transaction is possible | `eth_blockNumber` returns the snapshot block number and does not advance, because the L1 produces no further blocks. The L1 explorer stays available for the same reads: blocks, transactions, addresses, token transfers, and verified contract source up to the snapshot block. ### Endpoint and domain changes At the domain cut-over, `whitechain.io` and `explorer.whitechain.io` begin serving the Whitechain L2, and the legacy L1 services move to `legacy` hostnames. | Hostname | Serves today | Serves after the cut-over | Legacy L1 service moves to | | ------------------------ | ------------------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------- | | `whitechain.io` | Whitechain L1 website | Whitechain L2 website | `legacy.whitechain.io` | | `explorer.whitechain.io` | L1 block explorer, chain ID `1875` | L2 block explorer | `legacy-explorer.whitechain.io` | | `rpc.whitechain.io` | L1 JSON-RPC endpoint, chain ID `1875` | L2 JSON-RPC endpoint | Legacy read-only RPC hostname, published in the [Network reference](/learn/network/reference) before Mainnet launch | The legacy L1 testnet (chain ID `2625`) is not migrated, so `rpc-testnet.whitechain.io` and `testnet.whitechain.io` are not part of this cut-over. See [Scope of the migration](#scope-of-the-migration). A hostname does not identify a chain. After the cut-over, a client still configured with `https://rpc.whitechain.io` reads the L2, at a different chain ID, without any change on the client side. Confirm which chain an endpoint serves by calling `eth_chainId`: ```bash theme={null} curl -s -X POST https://rpc.whitechain.io \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":1,"method":"eth_chainId","params":[]}' ``` The legacy L1 mainnet returns `0x753`, chain ID `1875`. The L2 Mainnet chain ID is published in the [Network reference](/learn/network/reference) before Mainnet launch. Compare the response against that value in every environment you operate. L1 transaction history and event logs are not migrated to the L2, and they are not reachable once the L1 is decommissioned. Export the L1 history you need while the legacy explorer and the legacy read-only RPC endpoint are online. ## Moving WBT between L1 and L2 You do not have to move anything. Your balance will already exist on L2 after migration. To move WBT between Ethereum and Whitechain L2 by choice, use either route: | Route | Use | | -------------------------------------------- | ----------------------------------------------------- | | WhiteBIT | Deposit and withdraw WBT through the exchange | | [Portal bridge](/build/bridge/portal-bridge) | Transfer WBT on chain between Ethereum and Whitechain | ## Deadlines The migration has no deadline for holders, because holders have no task to complete. There is nothing to claim and no window to miss. The milestones below matter to integrations and to anyone who wants historical L1 data. No migration date is announced. Every milestone in this table is pending. | Milestone | Date | What it means for you | | ------------------------------------------------ | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | L1 snapshot block and end of L1 block production | Not yet announced | The snapshot block is the last L1 block. The L1 accepts no transactions after it | | L2 Mainnet launch | Not yet announced. Same day as the snapshot block | L2 Mainnet starts with the full L1 state. Its chain ID and endpoints are published at the same time | | Domain cut-over to legacy hostnames | Not yet announced. Same day as the snapshot block | `whitechain.io` and `explorer.whitechain.io` begin serving the L2. The L1 website and explorer move to `legacy.whitechain.io` and `legacy-explorer.whitechain.io` | | L1 read-only period | Start not yet announced. Length not yet announced | The L1 explorer and JSON-RPC API answer read methods only. Export the L1 history you need during this period | | L1 decommissioning | Not yet announced | The L1 explorer and JSON-RPC API shut down permanently. L1 transaction history is no longer reachable | Whitechain publishes migration dates on [X](https://x.com/Whitechain_io), in the [Whitechain Telegram channel](https://t.me/Whitechain_io), and on this page. For the stage-by-stage sequence, see [L1 sunset stages](#l1-sunset-stages). ## Where to get help | You need | Go to | | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | WBT held on the WhiteBIT exchange | WhiteBIT support, through the exchange's own help channels | | Technical help with an integration, node, or contract | [Whitechain Discord](https://discord.gg/eZwjxwNsU) | | Migration announcements and dates | [Whitechain on X](https://x.com/Whitechain_io) and the [Whitechain Telegram channel](https://t.me/Whitechain_io) | | Network and endpoint status | [status.whitechain.io](https://status.whitechain.io/) | | Chain IDs, endpoints, and parameters | [Network reference](/learn/network/reference) | Whitechain support never asks for your seed phrase, private key, or a wallet signature to complete a migration step. ## Staying safe Migration announcements attract phishing. Treat any message that pressures you to act before a deadline as suspicious. * No official process asks you to migrate, claim, or unlock WBT. * Never share your seed phrase or private key. No support agent asks for it. * Check contract addresses and links only against official Whitechain and WhiteBIT sources. * If a message pressures you, do nothing and confirm through an official channel first. ## FAQ ### For WBT holders No. Your balance migrates to L2 automatically and stays at the same address. You transact only when you choose to. No. Your address is the same on L2, and your existing seed phrase and private key control it. You do not create a new wallet or move funds to a new address. Nothing on your side. The exchange handles the transition for balances held on WhiteBIT. Trading and internal balances are unaffected by the on-chain snapshot. Yes. Token balances live in contract storage, and contract storage migrates in full. Your token and NFT holdings appear on L2 at the same contract addresses. Yes, once. The L2 uses a different chain ID and RPC URL than the legacy L1, so add the L2 network to your wallet to see your balance and transact. The L2 chain ID and endpoints are published before Mainnet launch in the [Network reference](/learn/network/reference). No. Only final state migrates, so balances, contract code, storage, and nonces carry over, but transaction history and event logs do not. To export the history you need, use `https://explorer.whitechain.io` while the L1 is live, and `https://legacy-explorer.whitechain.io` after L2 Mainnet launch, when `explorer.whitechain.io` serves the L2. Export before the L1 is decommissioned. See [Endpoint and domain changes](#endpoint-and-domain-changes). Pending mempool transactions are not migrated. Anything unconfirmed at the snapshot block never executes. Resubmit it on L2 after launch if you still want it. No. WBT stays the native gas token on L2, with 18 decimals and unchanged supply. The migration copies balances to the wei. ### For integrations Repoint your infrastructure to the L2 chain ID, RPC URL, and explorer, and stop crediting L1 deposits at the snapshot block. User balances migrate on their own; your connection settings do not. Pin each endpoint by the chain ID it reports, because `rpc.whitechain.io` and `explorer.whitechain.io` serve the L2 after the cut-over. See [Integrations and service operators](#integrations-and-service-operators) for the change list by integration type. No. Bytecode, storage, nonces, and addresses are preserved, so your contracts exist on L2 at the same addresses. Repoint your RPC URL and chain ID, then re-verify the source on the L2 explorer so users can read it there. Its logic does. The EVM is unchanged, so contract code runs identically. The fee model changes from a fixed 10 gwei gas price to EIP-1559 plus an L1 data fee, and blocks arrive every second instead of every two seconds. Review any logic that hardcodes a gas price or assumes a block interval. See [Network fees](/learn/network/network-fees). Use Whitechain Sepolia, chain ID `1874`, at `https://rpc.testnet.whitechain.io`. It runs the same OP Stack software as L2 Mainnet, so an integration that works there works at launch. Claim test WBT from the [faucet](/learn/get-started/get-testnet-wbt). Export what you need while the L1 endpoints are online, then index the L2 from its genesis block. L1 history is not reachable from the L2. | When | L1 JSON-RPC endpoint | L1 explorer | | ------------------------------ | ------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | | While the L1 is live | `https://rpc.whitechain.io` | `https://explorer.whitechain.io` | | During the L1 read-only period | Legacy read-only RPC hostname, published in the [Network reference](/learn/network/reference) before Mainnet launch | `https://legacy-explorer.whitechain.io` | | After L1 decommissioning | Not available | Not available | The length of the read-only period is not yet announced. After the snapshot block the L1 explorer and JSON-RPC API answer read methods at the legacy hostnames, and they shut down permanently at L1 decommissioning. See [L1 sunset stages](#l1-sunset-stages) and [Deadlines](#deadlines). Use `https://legacy-explorer.whitechain.io` for the L1 explorer, and the legacy read-only RPC hostname published in the [Network reference](/learn/network/reference) before Mainnet launch. `https://explorer.whitechain.io` and `https://rpc.whitechain.io` serve the L2 from the cut-over onward, so a client left pointing at them reads the L2 at a different chain ID. Confirm the chain with `eth_chainId`. See [Endpoint and domain changes](#endpoint-and-domain-changes). From the [Network reference](/learn/network/reference). Mainnet values are published there before Mainnet launch. Whitechain Sepolia values are published there now. ### Timing and safety Migration happens at L2 Mainnet launch, on a date that is not yet announced. Holders cannot miss anything, because holders have no task to complete. Integrations should be ready by the snapshot block, which is announced with the launch date. See [Deadlines](#deadlines). The L1 is live and accepts transactions until the snapshot block. After that block it answers read methods only, at the legacy hostnames, and it is decommissioned later. Export any historical data you need before decommissioning. See [What read-only mode means](#what-read-only-mode-means). No. Migration applies to Mainnet only. The legacy L1 testnet, chain ID `2625`, is superseded by Whitechain Sepolia, chain ID `1874`, which starts from its own genesis. Contracts and balances on chain ID `2625` do not appear on chain ID `1874`. See [Scope of the migration](#scope-of-the-migration). No. There is no manual migration step. Any such request is a scam. Do not connect your wallet or share any keys. Whitechain publishes migration dates on [X](https://x.com/Whitechain_io), in the [Whitechain Telegram channel](https://t.me/Whitechain_io), and on this page. No date is published yet. Check any date you see against those three sources. See [Deadlines](#deadlines). ## Related * [Introduction to Whitechain](/learn/general/what-is-whitechain) * [Network reference](/learn/network/reference) * [Getting WBT](/learn/general/get-wbt) # Introduction to Whitechain Source: https://l2docs.whitechain.io/learn/general/what-is-whitechain Whitechain is an EVM-compatible Layer 2 blockchain built on the OP Stack and backed by [WhiteBIT](https://whitebit.com), one of the largest centralized exchanges in Europe with more than 5 million active users. It settles on Ethereum via blob data availability and uses WBT (WhiteBIT Coin) as the native gas token. Available on Testnet today. Mainnet support is published at Mainnet launch. As an OP Stack L2, Whitechain inherits Ethereum's security guarantees while reducing transaction costs and latency for both end users and developers. The chain maintains full EVM compatibility, so any contract or toolchain that works on Ethereum works on Whitechain without modification. Key technical details for developers: * 1-second block time. * Flashblocks enabled: preconfirmations streamed about every 200 ms ahead of each full block. See [Transaction finality](/learn/network/transaction-finality#flashblocks). * A plain WBT transfer costs about 0.000105 WBT to execute at the 5 gwei base fee floor, well under one cent at \$50 per WBT, plus the L1 data fee. See [Network fees](/learn/network/network-fees). * Fees paid in WBT, the native asset of Whitechain with utility across the WhiteBIT ecosystem. * The L2 architecture nets sequencer margins against Ethereum blob costs, keeping fees structurally low at scale. ## What makes Whitechain different Whitechain is part of the broader WhiteBIT ecosystem. That alignment unlocks four things uncommon among Layer 2s: * **Listing and co-marketing pathway is part of the product.** Projects that meet the criteria have a defined route from on-chain deployment to WhiteBIT exchange listing and to joint announcements across WhiteBIT and Whitechain channels. Those channels reach the **more than 35 million users** of W Group, the fintech group behind Whitechain. On most chains, this path does not exist as a structured offer. * **The gas token is the exchange token.** WBT is used to pay fees on Whitechain and to access utility on WhiteBIT. A single asset carries two demand loops, which strengthens the economic case for projects denominated in WBT. * **Direct exchange-to-L2 withdrawal.** Once Mainnet launches, WhiteBIT users will withdraw WBT directly to Whitechain L2 from the exchange UI, with no third-party bridge in between. * **European focus.** WhiteBIT is one of the largest centralized exchanges in Europe. Whitechain inherits that geographic distribution. For the detailed partnership offer, see [For project partners](/build/ecosystem/for-partners). The Whitechain Foundation also runs a [Grants Program](https://whitechain.io/grants) with financial, marketing, and technical support, distributed via vesting. ## Useful resources * [White paper](https://cdn.whitechain.io/whitepaper-en.pdf) * [GitHub](https://github.com/whitebit-exchange/wbt?tab=readme-ov-file#whitechain) ### Social * [Discord community](https://discord.gg/eZwjxwNsU) * [X community](https://x.com/Whitechain_io) * [Telegram](https://t.me/Whitechain_io) ## Related * [Network reference](/learn/network/reference) * [Getting WBT](/learn/general/get-wbt) * [Migration to L2](/learn/general/migration) # Connect to Whitechain Sepolia Source: https://l2docs.whitechain.io/learn/get-started/connect-wallet This page shows how to add the Whitechain Sepolia testnet to an EVM wallet such as MetaMask. It is for developers who want to deploy or test contracts before Mainnet. Connect once, then use the same network for the Remix, Hardhat, and Foundry quickstarts. This page shows how to add the Whitechain Sepolia testnet to an EVM wallet such as MetaMask. It is for developers who want to deploy or test contracts before Mainnet. Connect once, then use the same network for the Remix, Hardhat, and Foundry quickstarts. ## Before you connect * An EVM wallet, for example, [MetaMask](https://metamask.io). ## Option 1: add the network with Chainlist 1. Open [Chainlist](https://chainlist.org/?search=Whitechain+Sepolia\&testnets=true). 2. Click **Connect Wallet**. 3. Click **Add to MetaMask** to add the network automatically. ## Option 2: add the network from the explorer 1. Open the [Whitechain Sepolia Explorer](https://explorer.testnet.whitechain.io/). 2. Click **Connect Wallet** in the top-right corner. 3. Approve the prompt in your wallet to add the network. ## Option 3: add the network manually 1. Open your wallet settings and select **Add network**, then **Add a network manually**. 2. Enter the values from the network parameters table below. 3. Save the network and switch to it. ## Network parameters | Parameter | Value | | --------------- | ----------------------------------------- | | Network name | Whitechain Sepolia | | RPC URL | `https://rpc.testnet.whitechain.io/` | | Chain ID | `1874` | | Currency symbol | `WBT` | | Block explorer | `https://explorer.testnet.whitechain.io/` | ## Verify the connection Open the network selector in your wallet. It shows Whitechain Sepolia, and your account balance appears in `WBT`. ## Related * [Deploy a contract](/learn/get-started/deploy-a-contract) * [Faucet](/learn/get-started/get-testnet-wbt) # Deploy a contract Source: https://l2docs.whitechain.io/learn/get-started/deploy-a-contract Choose a toolchain to deploy your first smart contract on Whitechain Sepolia: Remix, Hardhat, or Foundry. Whitechain Sepolia is a standard EVM network, so any Ethereum toolchain deploys to it by chain ID and RPC URL, with no Whitechain-specific plugin. Pick the toolchain you already use, or the one that fits how you want to work. ## Before you deploy * A wallet with Whitechain Sepolia added. See [Connect to Whitechain Sepolia](/learn/get-started/connect-wallet). * Test WBT for gas. Claim it from the [faucet](/learn/get-started/get-testnet-wbt). ## Choose a toolchain | Toolchain | Best for | Guide | | --------- | --------------------------------------------------- | -------------------------------------------------------- | | Remix | Deploying from the browser, no local setup | [Deploy with Remix](/build/deploy/deploy-with-remix) | | Hardhat | A TypeScript project with tests and scripts | [Deploy with Hardhat](/build/deploy/deploy-with-hardhat) | | Foundry | A Solidity-native toolchain with `forge` and `cast` | [Deploy with Foundry](/build/deploy/deploy-with-foundry) | ## Related * [Connect to Whitechain Sepolia](/learn/get-started/connect-wallet) * [Faucet](/learn/get-started/get-testnet-wbt) * [Next dapp template](/build/dapps/dapp-with-nextjs) # Faucet Source: https://l2docs.whitechain.io/learn/get-started/get-testnet-wbt Claim test WBT from the Whitechain Sepolia faucet (chain ID 1874) to pay gas while deploying contracts and testing dapps on the L2 network. The Whitechain Sepolia faucet distributes small amounts of test WBT to pay gas while deploying and testing on the network. Test WBT has no real value and exists only on testnet (chain ID 1874). The faucet is available at [faucet.testnet.whitechain.io](https://faucet.testnet.whitechain.io). ## Before you claim To claim test WBT, you need: * Whitechain Sepolia added to your wallet. Use chain ID 1874 and the public RPC at `https://rpc.testnet.whitechain.io/`. * A GitHub account at least 30 days old. One account can claim once per 24-hour window. ## Claim test WBT 1. Open [faucet.testnet.whitechain.io](https://faucet.testnet.whitechain.io). 2. Sign in with GitHub. 3. Paste your Whitechain address and complete the captcha. 4. Submit. The transaction lands on testnet within seconds. You can verify it on the [testnet explorer](https://explorer.testnet.whitechain.io). ## Drip limits | Tier | Eligibility | Drip per 24h | | -------- | ------------------------------ | ------------ | | Standard | GitHub OAuth (min 30 days old) | 0.5 WBT | The 24-hour window is rolling, not a midnight reset. If you claimed at 14:00 today, the next claim is allowed at 14:00 tomorrow. ## Troubleshooting Try a different browser, disable VPN or aggressive ad-blockers, or clear cookies for the faucet domain. The faucet uses Cloudflare. Check your address on the [testnet explorer](https://explorer.testnet.whitechain.io). If the claim succeeded, the tokens are already in your account. If you do not see the transaction, retry after a few minutes or reach out via the support channels. For a known incident, check the [status page](https://status.whitechain.io/). The faucet checks three limits independently: wallet address, IP address, and GitHub account. If another developer on the same network (corporate NAT, VPN) already claimed, the IP limit may be hit. Try a different network or reach out via the support channels to request a higher IP allowance. The standard daily limit covers normal deployment and testing. For larger needs (load tests, deeper integration), reach out to the team via the support channels. ## Related * [OP Stack canonical bridge](/build/bridge/bridge-assets) * [Connect to Whitechain Sepolia](/learn/get-started/connect-wallet) * [Deploy a contract](/learn/get-started/deploy-a-contract) * [Service status](https://status.whitechain.io/) # Start with AI Source: https://l2docs.whitechain.io/learn/get-started/start-with-ai Route your AI coding assistant or autonomous agent to the right Whitechain integration: llms.txt, llms-full.txt, or the whitechain-dev Agent Skill. This page is the starting point if you plan to build on Whitechain with an AI coding assistant or an autonomous agent instead of a manual toolchain. It routes you to the right integration method and lists a few prompts to try immediately. ## Quick setup Pick the method that fits your tool. ### Static docs files | File | What it contains | When to use it | | ----------------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------ | | [llms.txt](https://l2docs.whitechain.io/llms.txt) | Page index with titles and descriptions | Discovering which docs exist before going deeper | | [llms-full.txt](https://l2docs.whitechain.io/llms-full.txt) | Full content of every current-network page in one file | Giving an assistant broad context in one shot | Every page is also available as plain Markdown: append `.md` to any URL. See [Feed Whitechain docs to AI tools](/build-with-ai/llms-txt). ### Skills ```bash theme={null} npx skills add whitechain-labs/skills --skill whitechain-dev ``` See [Agent Skills](/build-with-ai/claude-skills) for what the `whitechain-dev` skill covers. ## Recommended starting points | What you're doing | Start here | | ------------------------------------------------------------ | -------------------------------------------------------- | | Check balances, deploy, and verify contracts from an AI chat | [Agent Skills](/build-with-ai/claude-skills) | | Get exact chain IDs and endpoints for a prompt | [Network reference](/learn/network/reference) | | Move assets between Ethereum and Whitechain | [OP Stack canonical bridge](/build/bridge/bridge-assets) | ## Example prompts Copy one of these into your assistant to test that the integration is working: * "Check the WBT balance of 0xYourAddress on Whitechain Sepolia." * "Check whether Whitechain Sepolia is up and report the latest block number." * "Guide me through claiming test WBT from the Whitechain Sepolia faucet for 0xYourAddress." * "Deploy a minimal ERC-20 to Whitechain Sepolia and verify it on Blockscout." * "Trace transaction 0xYourTxHash on Whitechain Sepolia and tell me whether it succeeded." Never paste a private key into an AI chat prompt. Keep it in a local `.env` file and let the assistant read the environment variable instead. ## Related * [Build with AI](/build-with-ai) * [Agent Skills](/build-with-ai/claude-skills) * [Connect to Whitechain Sepolia](/learn/get-started/connect-wallet) # Start building on Whitechain Source: https://l2docs.whitechain.io/learn/introduction/index Whitechain is an EVM-compatible OP Stack Layer 2 that settles on Ethereum and uses WBT as the native gas token. Start here to connect, build, and deploy. Available on Testnet today. Mainnet support is published at Mainnet launch. ## Get started Connect an AI assistant and deploy with AI only. Add Whitechain Sepolia. Claim test WBT to pay gas fees. Remix, Hardhat, Foundry. ## Explore How the L2 works. Inspect transactions and contracts. Move assets to and from Ethereum. Run and maintain an RPC node. # Network fees Source: https://l2docs.whitechain.io/learn/network/network-fees What a transaction costs on Whitechain: the L2 execution fee, the L1 data fee, who receives them, and how to estimate the total in WBT. Available on Testnet today. Final Mainnet fee parameters are published before Mainnet launch. ## What gas is Every action on Whitechain uses computing resources, whether you send WBT, swap tokens, or call a smart contract. Gas is the unit that measures that work. You pay for the gas your transaction uses, which keeps the network running and prices out spam. You pay gas fees in WBT, not ETH. WBT is the native gas token, so you need a WBT balance to send any transaction. On Testnet, claim test WBT from the [faucet](/learn/get-started/get-testnet-wbt). ## The two parts of a fee A fee on Whitechain has two parts: the cost to run your transaction, and the cost to publish it to Ethereum. On a Layer 2 (L2), the second part can be the larger one when Ethereum is busy. It rises and falls with Ethereum's own gas prices, even when Whitechain is quiet. | Part | Pays for | Set by | | ---------------- | --------------------------------------- | ------------------------------------------------------ | | L2 execution fee | Running your transaction on Whitechain | L2 gas used times the L2 gas price | | L1 data fee | Publishing your transaction to Ethereum | Your transaction size times current L1 and blob prices | Your total is the two parts added together. Your wallet shows the combined number before you sign. ## How the L2 fee is calculated Three values set your L2 execution fee. | Value | What it is | | ------------ | ---------------------------------------------------------------------------------------------------------------- | | Gas used | The amount of computation your transaction needs. A plain WBT transfer uses 21,000 gas; contract calls use more. | | Base fee | The protocol-set price per unit of gas. It adjusts with demand and never drops below 5 gwei. | | Priority fee | An optional tip you add to be included faster. | The L2 execution fee is the gas used times the gas price. The gas price is the base fee plus any priority fee, both measured per unit of gas. Whitechain is EVM-compatible, so an operation uses the same gas it would on Ethereum. The base fee follows EIP-1559 and moves from block to block with demand. It never drops below **5 gwei**, a floor that stops zero-fee spam. At that floor the base fee adds a fraction of a cent to a normal transfer. ## The L1 data fee Whitechain writes every transaction to Ethereum, so its state can always be rebuilt. Posting that data costs ETH, and the L1 data fee covers it. This is the one fee component that a plain Ethereum transaction does not have. The charge depends on three inputs: the compressed size of your transaction, the current Ethereum base fee, and the current blob base fee (EIP-4844). Whitechain reads both Ethereum prices on-chain and refreshes them each L1 block. Ethereum's base fee can move by at most 12.5% per block, so short-term swings stay small. The L1 data fee is charged on every transaction and cannot currently be limited by the transaction gas limit. The amount is an estimate taken at submission. Data is posted to Ethereum in compressed batches, so the real cost evens out over time and you never set it yourself. ## A worked example The figures below assume the 5 gwei minimum base fee, the current floor on Testnet. Each row shows the L2 execution fee at that floor, which is the gas used times the base fee. The L1 data fee is added on top and stays small in normal conditions. | Action | Gas used | L2 fee at 5 gwei | At \$50 per WBT | | ---------------------------------- | -------- | ---------------- | --------------- | | Send WBT | 21,000 | 0.000105 WBT | \$0.005 | | Call a contract, first state write | 43,718 | 0.00022 WBT | \$0.011 | | Deploy a minimal contract | 117,683 | 0.00059 WBT | \$0.030 | The USD column assumes \$50 per WBT, a round figure for illustration. WBT trades on the open market, so check the [live WBT price](https://www.coingecko.com/en/coins/whitebit) before using these numbers for planning. The WBT amounts are the fixed values; the dollar amounts move with the market. Sending WBT costs about 0.000105 WBT to execute, well under one cent. Deploying a small contract costs about 0.0006 WBT, roughly three cents. Complex contracts use more gas, so their fees are higher. The base fee rises above 5 gwei only when blocks fill up. The deploy row matches a real transaction on Testnet. Its full breakdown, including the L1 data fee, is below and on the [explorer](https://explorer.testnet.whitechain.io/tx/0xeaf6963ce0b1edc69412d993255cefce72420110462277a23d2bfd5fa203e4de). | Item | Value | At \$50 per WBT | | ---------------- | ---------------- | ------------------- | | Gas used | 117,683 | n/a | | L2 execution fee | 0.00058853 WBT | \$0.029 | | L1 data fee | 0.0000000222 WBT | less than \$0.00001 | | Total charged | 0.00058855 WBT | \$0.029 | The observed gas price was 5.001 gwei, including a small tip, so the execution fee sits just above the 5 gwei estimate. The L1 data fee is a small share here because posting to Ethereum Sepolia is inexpensive. When Ethereum is busy, this part grows and can exceed the L2 execution fee. Testnet L1 costs do not reflect Mainnet, where posting to Ethereum costs more. A failed transaction still pays for the gas it used, and both fee parts are always charged. The explorer may show only the L2 gas cost, so your balance can drop by more than that figure. The difference is the L1 data fee. ## Common scenarios | Action | Typical gas | Notes | | --------------------- | ----------------------------------- | --------------------------------------------------- | | Send WBT | 21,000 | Fixed for a plain transfer | | Swap tokens | Higher, varies | Depends on the pool and route | | Call a smart contract | Varies widely | More complex calls cost more | | Failed transaction | Gas used up to the point of failure | You still pay; a failed transaction is not refunded | Failed transactions still cost gas. The network does the work up to the point where the transaction fails, so you pay for the gas it used. Set a sufficient gas limit to avoid running out partway through a contract call. ## Who receives the fee Whitechain runs the sequencer that orders transactions and the batcher that posts their data to Ethereum. The protocol collects your fees into on-chain fee vaults. The base fee, the priority tip, and the L1 data fee each go to a separate vault. The L1 data fee offsets the cost of posting data to Ethereum. Whitechain sets the operator fee to zero, so you pay the L2 execution cost and the L1 settlement cost only. ## How Whitechain fees differ from Ethereum | Difference | On Whitechain | Why | | -------------- | --------------------------------- | ----------------------------------------------------- | | Fee token | WBT, not ETH | WBT is the native gas token | | Cost level | Far below Ethereum L1 | Data is batched and posted once for many transactions | | L1 sensitivity | Totals rise when Ethereum is busy | The L1 data fee tracks Ethereum's gas price | ## Fee parameters The values below are the current Testnet configuration. | Parameter | Testnet value | Effect | | -------------------- | ------------- | ---------------------------------------------- | | Native gas token | WBT | The token all fees are paid in | | Minimum base fee | 5 gwei | Floor for the L2 base fee | | EIP-1559 elasticity | 6 | A block can hold up to 6x its target gas | | EIP-1559 denominator | 250 | How fast the base fee adjusts between blocks | | L2 block gas limit | 40,000,000 | Maximum gas per L2 block | | Operator fee | None | No surcharge; you pay the settlement cost only | With elasticity 6 and denominator 250, the base fee changes by at most 2% per block. Large moves take many blocks, even when demand rises. Mainnet fee parameters are not final. The values above are the current Testnet configuration and may change. Confirmed Mainnet parameters are published here before Mainnet launch. ## Estimating a fee Use existing tooling. Libraries such as viem include OP Stack support and update when the fee formula changes across network upgrades, so your code keeps working without edits. Estimate the L2 execution fee the same way you would on Ethereum, and let the library return the L1 data fee. If you cannot use a library, read the L1 data fee from the `GasPriceOracle` predeploy at [`0x420000000000000000000000000000000000000F`](https://explorer.testnet.whitechain.io/address/0x420000000000000000000000000000000000000F) on Testnet. This predeploy sits at the same fixed address on every OP Stack chain. Call `getL1Fee` with your serialized, unsigned transaction, since estimation happens before signing. Do not implement the formula yourself. It changes with network upgrades, and `getL1Fee` is kept in step. Your total is the L2 execution fee (gas used times gas price) plus the L1 data fee. To avoid overpaying, let your wallet or library set the base fee, and add a priority fee only when you need faster inclusion. If an estimate looks high, check the current Ethereum gas price first, because the L1 data fee tracks it. Whitechain uses the standard OP Stack fee model. For the full mechanism, including the exact L1 data fee formula, see the [Optimism transaction fees documentation](https://docs.optimism.io/app-developers/transactions/fees). The specific OP Stack upgrade active on Whitechain is being confirmed with engineering and is stated here before Mainnet launch. ## Related * [Faucet](/learn/get-started/get-testnet-wbt) * [Network reference](/learn/network/reference) * [Use viem with Whitechain](/build/dapps/use-viem) * [Transaction finality](/learn/network/transaction-finality) # Network reference Source: https://l2docs.whitechain.io/learn/network/reference Chain IDs, public RPC endpoints and their rate limits, explorers, and other parameters for the Whitechain Mainnet and Whitechain Sepolia environments. Whitechain runs EVM-compatible environments built on the OP Stack. Each settles on Ethereum and uses WBT as the native gas token. ## At a glance | Network | Chain ID | Status | Intended use | | ------------------ | ------------------------------------ | ----------- | --------------------------------------------- | | Mainnet | TBD, published before Mainnet launch | Coming soon | Production | | Whitechain Sepolia | `1874` | Live | Public testing, integrations, dapp deployment | ## Mainnet L2 Mainnet is not yet live. Chain ID, endpoints, and the launch date will be published before public availability. ## Whitechain Sepolia Whitechain Sepolia is the public test environment. Use it for dapp deployment, integration testing, and to claim test WBT from the faucet. | Parameter | Value | | ----------------- | ----------------------------- | | Chain ID | `1874` (hex `0x752`) | | L1 settlement | Ethereum Sepolia (`11155111`) | | Block time | 1s | | Currency symbol | WBT | | Currency decimals | 18 | | Endpoint | URL | | ---------- | ------------------------------------------------------------------------- | | Public RPC | `https://rpc.testnet.whitechain.io` | | WebSocket | `wss://rpc.testnet.whitechain.io/ws` | | Explorer | [explorer.testnet.whitechain.io](https://explorer.testnet.whitechain.io/) | | Faucet | [faucet.testnet.whitechain.io](https://faucet.testnet.whitechain.io/) | To claim test WBT, see [Testnet faucet](/learn/get-started/get-testnet-wbt). To check current uptime for these endpoints, see the [status page](https://status.whitechain.io/). ## Public RPC endpoints This section lists every public JSON-RPC endpoint for Whitechain, operated by Whitechain and by third parties, with the rate limit that applies to each. No endpoint needs an API key or an IP allowlist. Reads and writes are open to any client, subject to the limits below. | Network | Type | URL | Operator | Auth | Status | | ------------------ | ---------------------------- | --------------------------------------------------------------------- | -------------------------------------- | --------------------------------- | ------- | | Whitechain Sepolia | HTTPS JSON-RPC | `https://rpc.testnet.whitechain.io` | Whitechain | None | Live | | Whitechain Sepolia | WebSocket JSON-RPC | `wss://rpc.testnet.whitechain.io/ws` | Whitechain | None | Live | | Whitechain Sepolia | HTTPS and WebSocket JSON-RPC | Not yet available, published when the first partner endpoint launches | Third-party provider, under evaluation | To be confirmed with the provider | Planned | | Mainnet | HTTPS and WebSocket JSON-RPC | Not yet available, published before Mainnet launch | Whitechain | None | Planned | Third-party public endpoints are not live yet. Whitechain is evaluating partnerships with commercial RPC providers for Whitechain Sepolia and Mainnet. Until a partner endpoint is published in the table above, the two Whitechain endpoints are the only public RPC endpoints for the network. Do not treat any other endpoint claiming to serve chain ID `1874` as official. ### Rate limits on Whitechain endpoints The public HTTPS endpoint `https://rpc.testnet.whitechain.io` is rate limited per client IP address. Neither Whitechain endpoint needs an API key or an IP allowlist. | Property | Value | | -------------------------------- | -------------------------------------------------------- | | Sustained rate | 50 requests per second per client IP | | Burst allowance | Up to 500 requests above the sustained rate | | What the limit counts | Each HTTP request, not each JSON-RPC call inside a batch | | Status code when you are limited | `429 Too Many Requests` | | Retry hint | `Retry-After: 1` header on every `429` response | | WebSocket limits | Published once they are finalized | Stay at or below 50 requests per second and you do not see a `429`. Above that, the endpoint absorbs a short burst, then returns `429` for the excess while it keeps serving 50 requests per second. Recovery is immediate once your rate drops. Mainnet limits are published before Mainnet launch. Rate limits on third-party endpoints are set by each provider, not by Whitechain, and are published in the table above when those endpoints go live. ### Working within the limits The limits suit dapp front ends, wallet traffic, and integration testing. They are not sized for indexers, block-by-block backfills, or load tests. If your workload needs more headroom: * Batch calls into a single JSON-RPC request. The limiter counts one HTTP request per batch, so 20 calls cost the same as one. * Poll new blocks over WebSocket with `eth_subscribe` on `newHeads` instead of calling `eth_blockNumber` in a loop. * Reuse one WebSocket connection for several subscriptions instead of opening a connection per query. * Cache immutable results, such as receipts and finalized blocks, on your side. * Run your own node for unmetered access. See [Run a node](/operate/run-a-node/overview). Handle `429` responses with exponential backoff, starting from the `Retry-After` value. A client that retries immediately after a `429` stays above the limit and keeps failing. ## Gas token WBT is the native gas token across all Whitechain environments. It uses 18 decimals and is configured as the OP Stack custom gas token. Every transaction fee on Whitechain is paid in WBT, regardless of network. ## Related * [Public RPC endpoints and rate limits](#public-rpc-endpoints) * [Whitechain Sepolia full parameters](/learn/network/testnet) * [Faucet](/learn/get-started/get-testnet-wbt) * [Network fees](/learn/network/network-fees) * [Use viem with Whitechain](/build/dapps/use-viem) * [OP Stack canonical bridge](/build/bridge/bridge-assets) * [Migration to L2](/learn/general/migration) * [Transaction finality](/learn/network/transaction-finality) * [Service status](https://status.whitechain.io/) # Whitechain Sepolia (testnet) Source: https://l2docs.whitechain.io/learn/network/testnet Network reference for the Whitechain Sepolia testnet: how to connect, public RPC limits, fees, and withdrawal timing. ## At a glance | Parameter | Value | | ------------- | -------------------------------- | | Network name | Whitechain Sepolia | | Short name | WCH Sepolia | | Chain ID | `1874` (hex `0x752`) | | L1 | Ethereum Sepolia (`11155111`) | | Gas token | WBT (WhiteBIT Coin, 18 decimals) | | L2 block time | 1s | | Flashblocks | Enabled, about every 200 ms | Flashblocks are preconfirmations the sequencer streams ahead of each full one-second L2 block. They carry the sequencer's word only, and they do not change when a transaction becomes safe or finalized. See [Flashblocks in Transaction finality](/learn/network/transaction-finality#flashblocks) for the mechanism and how the four confirmation stages differ. ## Endpoints | Type | URL | | ---------- | ------------------------------------------------------------------------ | | Public RPC | `https://rpc.testnet.whitechain.io` | | WebSocket | `wss://rpc.testnet.whitechain.io/ws` | | Explorer | [explorer.testnet.whitechain.io](https://explorer.testnet.whitechain.io) | | Faucet | [faucet.testnet.whitechain.io](https://faucet.testnet.whitechain.io) | To check current uptime for these endpoints, see the [status page](https://status.whitechain.io/). ## Public RPC rate limits The public RPC is rate limited per client IP address. When you exceed the limit, the endpoint returns `429 Too Many Requests` with a `Retry-After` header. | Property | Value | | -------------------------------- | -------------------------------------------------------- | | Sustained rate | 50 requests per second per client IP | | Burst allowance | Up to 500 requests above the sustained rate | | What the limit counts | Each HTTP request, not each JSON-RPC call inside a batch | | Status code when you are limited | `429 Too Many Requests` | | Retry hint | `Retry-After: 1` header on every `429` response | | WebSocket limits | Published once they are finalized | The public RPC and WebSocket endpoints need no API key and no IP allowlist. Reads and writes are open to any client, subject to the limits above. No third-party public endpoint serves Whitechain Sepolia yet. For the full list of public endpoints across networks, including planned third-party endpoints, and for guidance on handling `429` responses, see [Public RPC endpoints](/learn/network/reference#public-rpc-endpoints). ## WebSocket and subscriptions The node exposes a WebSocket endpoint at `wss://rpc.testnet.whitechain.io/ws`. It serves the standard Ethereum JSON-RPC API and supports `eth_subscribe` for real-time updates, including `newHeads`, `logs`, and `newPendingTransactions`. Reuse one connection for multiple subscriptions rather than opening a connection per query. See [Public RPC rate limits](#public-rpc-rate-limits) for the limits that apply. ## Fees Whitechain uses EIP-1559 with a 1-second block time, so fees stay low and predictable. | Parameter | Value | | --------------------- | -------------------------------------------------- | | `baseFeeScalar` | 100000 | | `blobBaseFeeScalar` | 1000000 | | `operatorFeeScalar` | 0 | | `operatorFeeConstant` | 0 | | `gasLimit` | 40000000 | | `minimumGasLimit` | 21000000 | | `maximumGasLimit` | 500000000 | | `eip1559Elasticity` | 6 | | `eip1559Denominator` | 250 (100 at genesis, 250 since the Canyon upgrade) | | `minBaseFee` | 5000000000 | ## Withdrawals L2 withdrawals finalize after the dispute window ends. On testnet the dispute delays total 20 minutes, but a full withdrawal takes about an hour, and up to three hours in the worst case. A withdrawal can only be proven against a dispute game that covers its L2 block, and the proposer publishes a new game every 30 minutes. On mainnet a withdrawal takes at least 7 days. See [OP Stack canonical bridge](/build/bridge/bridge-assets#timing-parameters) for the full breakdown. | Parameter | Value (seconds) | | --------------------------------- | --------------- | | `proofMaturityDelaySeconds` | 900 | | `disputeGameFinalityDelaySeconds` | 300 | | `faultGameWithdrawalDelay` | 600 | | `faultGameClockExtension` | 300 | | `faultGameMaxClockDuration` | 900 | | `preimageOracleChallengePeriod` | 300 | ## Related * [Connect to Whitechain Sepolia](/learn/get-started/connect-wallet) * [Deploy a contract](/learn/get-started/deploy-a-contract) * [Next dapp template](/build/dapps/dapp-with-nextjs) * [Transaction finality](/learn/network/transaction-finality) * [Service status](https://status.whitechain.io/) # Transaction finality Source: https://l2docs.whitechain.io/learn/network/transaction-finality How a Whitechain transaction moves from a one-second soft confirmation to Ethereum finality, and what keeps your funds safe at each stage. Available on Testnet today. Testnet finality delays are intentionally short. Mainnet values are published before Mainnet launch. A transaction passes through three states: soft confirmation, safe, and finalized. A Flashblock preconfirmation arrives before all three. Only finalization makes it irreversible. Only withdrawals to Ethereum wait for the dispute-resolution period. A normal transaction on Whitechain, such as a send, a swap, or a contract call, is included in about one second and reaches finality in under an hour on Testnet. ## Confirmation stages | Stage | What it means | Typical time | | ----------------- | ------------------------------------------------------------------- | -------------------------------------------- | | Preconfirmation | Included in a partial block (a Flashblock), on the sequencer's word | About 200 ms | | Soft confirmation | Included in an L2 block, on the sequencer's word | About 1 second | | Safe | Its batch is posted to and confirmed on Ethereum | Up to 30 minutes on Testnet | | Finalized | Backed by finalized Ethereum data, irreversible | About 13 minutes after the batch lands on L1 | The 200-millisecond, one-second, and 13-minute figures are protocol timings. The safe delay depends on how often batches are posted, up to about 30 minutes on Testnet. A preconfirmation is the weakest of the four stages; see [Flashblocks](#flashblocks). ## The transaction lifecycle 1. **Send** you submit the transaction and the sequencer receives it. 2. **Soft confirmation** the sequencer orders the transaction and includes it in an L2 block, about once per second. Your wallet shows it as complete, but the guarantee is only the sequencer's word. Because one sequencer orders every transaction, a soft confirmation can still be reordered or dropped before the batch reaches Ethereum. Treat it as fast feedback, not a final receipt. 3. **Safe** the sequencer groups transactions into batches, compresses them, and the op-batcher posts them to Ethereum as blobs (EIP-4844). Once that batch is confirmed on Ethereum, your block is safe. Separately, the op-proposer publishes the L2 state root to Ethereum through the [`DisputeGameFactory`](/build/bridge/bridge-assets#contract-addresses) contract. 4. **Finalized** when the Ethereum block that carries your batch reaches Ethereum finality, about 13 minutes later, your L2 block becomes finalized. From that point the L2 state derives from finalized Ethereum data and cannot be reversed. An Ethereum reorg does not roll back a finalized L2 block. A shallow reorg can move the safe head: the node re-derives from the canonical Ethereum chain and the affected batch is re-posted. Blocks that reached finality are protected the same way finalized Ethereum blocks are. Whitechain is in Testnet, so historical reorg rates are not yet published. Expected figures are added with Mainnet. ## Flashblocks Ahead of each full one-second L2 block, the sequencer streams Flashblocks: partial blocks published about every 200 milliseconds. They are powered by [Flashbots](https://www.flashbots.net) rollup-boost, which runs a block builder alongside the sequencer. A Flashblock is a preconfirmation. It tells you within about 200 milliseconds that your transaction is included and ordered. That feedback carries the same trust as a soft confirmation, the sequencer's word, and is not safe or finalized until its batch reaches Ethereum. Apps that read the Flashblocks stream can show sub-second feedback, and the finality stages above are unchanged. The stream is separate from the public RPC, which does not carry it. To read it, request the Flashblocks WebSocket URL from the Whitechain team; see [what a node does not provide](/operate/run-a-node/overview#what-this-node-does-not-provide). ## Withdrawing to Ethereum Finality makes your L2 state irreversible. Moving assets back to Ethereum takes longer, because a withdrawal must also clear a dispute-resolution period. On Testnet a full withdrawal takes about an hour. On Mainnet a withdrawal takes at least 7 days. Deposits run the other way and are quick: moving funds from Ethereum to Whitechain takes about 2 minutes. See the [canonical bridge guide](/build/bridge/bridge-assets#timing-parameters) for the steps and exact timing parameters. ## What keeps your funds safe Whitechain operates the sequencer today, so liveness, whether new blocks are produced, depends on it. Safety rests on Ethereum, but not unconditionally. The mechanisms below hold your funds as long as transaction data stays on Ethereum and an honest challenger disputes any invalid state root. ### Data availability Every transaction's data is posted to Ethereum. Anyone can download it and recompute the L2 state, so the sequencer cannot hide or alter a transaction. ### Fault proofs A proposed state root can be disputed on Ethereum through an on-chain game. The game replays the disputed step in Cannon, a MIPS virtual machine, and the side proven wrong loses its bond. Today that game is permissioned, so only Whitechain's configured challenger can dispute a root. The design moves toward permissionless challenging, where anyone can take part. A Guardian role can also pause withdrawals or blacklist a game that resolves incorrectly. ### Censorship resistance If the sequencer refuses your transaction, you can submit it through the [`OptimismPortal`](/build/bridge/bridge-assets#contract-addresses) contract on Ethereum. The protocol must then include it within the sequencing window, about 12 hours, or the chain is invalid. ## Related * [OP Stack canonical bridge](/build/bridge/bridge-assets) * [Network fees](/learn/network/network-fees) * [Network reference](/learn/network/reference) # Node configuration Source: https://l2docs.whitechain.io/operate/run-a-node/configuration Environment variables, network ports, data layout, and RPC limits for Whitechain RPC node profiles. Reference for the `.env` variables, network ports, and data paths used by the Whitechain `public-rpc-node` stack. ## Required variables | Variable | Description | | ----------------------- | ------------------------------------------------------------------------------------ | | `WHITECHAIN_NETWORK` | Subdirectory under `artifacts/`, for example `mainnet` or `testnet`. | | `PUBLIC_IP` | Public IP of this host, advertised for op-reth and op-node P2P. | | `WHITECHAIN_PUBLIC_RPC` | Public Whitechain RPC, used as `--rollup.sequencer-http` for transaction forwarding. | | `L1_RPC_URL` | Your Ethereum L1 RPC endpoint. | | `L1_BEACON_URL` | Your Ethereum L1 Beacon endpoint. | The `full-snap-node` profile also requires `WHITECHAIN_RETH_TRUSTED_PEERS`, a trusted reth enode to snap-sync from, in the form `enode://@:30303`. ## Optional variables | Variable | Default | Description | | ------------------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | `L1_RPC_KIND` | `basic` | One of `alchemy`, `quicknode`, `infura`, `parity`, `nethermind`, `debug_geth`, `erigon`, `standard`, `any` if your provider supports extra receipt methods. | | `WHITECHAIN_PUBLIC_OP_NODE_P2P` | empty | Static op-node peer multiaddr, `/dns4//tcp/9222/p2p/`. | | `OP_NODE_ONLY_REQ_TO_STATIC` | `false` | Restrict unsafe-block requests to the static peer only. | | `OP_RETH_IMAGE` | `op-reth:latest` | Pin the op-reth image. | | `OP_NODE_IMAGE` | `op-node:latest` | Pin the op-node image. | ## Archive-only RPC limits | Variable | Default | | --------------------------- | -------- | | `RPC_MAX_CONNECTIONS` | `1000` | | `RPC_MAX_LOGS_PER_RESPONSE` | `20000` | | `RPC_MAX_BLOCKS_PER_FILTER` | `100000` | | `RPC_MAX_TRACING_REQUESTS` | `8` | ## Network ports Only one profile runs at a time, so all profiles share the same host ports. Each is remappable through the env var in parentheses. | Port | Default | Env var | Published on | | --------------------- | ---------------------------------- | ----------------------- | --------------------- | | HTTP RPC | `8545` | `HOST_HTTP_PORT` | All profiles | | WebSocket RPC | `8546` | `HOST_WS_PORT` | All profiles | | op-node RPC | `9545` (loopback `127.0.0.1` only) | `HOST_OP_NODE_RPC_PORT` | All profiles | | op-node P2P (TCP+UDP) | `9222` | `HOST_OP_NODE_P2P_PORT` | All profiles | | EL P2P (TCP+UDP) | `30303` | `HOST_EL_P2P_PORT` | `full-snap-node` only | The Engine API (`8551`) stays on the internal Compose network and is never published. op-node RPC (`9545`) is bound to loopback only on every profile, so it is reachable for local monitoring on the host but never from the network. The EL P2P port is only mapped for `full-snap-node`, which needs it to snap-sync state over devp2p from `WHITECHAIN_RETH_TRUSTED_PEERS`. Its mapping is commented out in `docker-compose.yml` for `full-node` and `archive-node`; those profiles sync over L1 derivation and libp2p only and never use EL P2P. To run two profiles side by side on one host, override one profile's ports in `.env`. ## Data layout Each profile keeps its data in its own subtree, so profiles never clash: ``` data/ full-snap-node/ op-reth/ # execution db, static_files, blobstore op-node/ # peerstore, discovery, safedb full-node/ op-reth/ op-node/ archive-node/ op-reth/ op-node/ ``` Snapshots are restored into `data//op-reth/`. ## Related * [Node operators overview](/operate/run-a-node/overview) * [Run a node](/operate/run-a-node/run-a-node) * [Node maintenance](/operate/run-a-node/maintenance) * [Troubleshooting](/operate/run-a-node/troubleshooting) # Node maintenance Source: https://l2docs.whitechain.io/operate/run-a-node/maintenance Operate, update, snapshot, and restore a Whitechain RPC node: make targets, snapshot procedures, upgrade workflow, and security notes. Day-to-day operations for a running Whitechain RPC node: start/stop commands, snapshot restoration, upgrades, and security practices. ## Operate the node Generic targets take `PROFILE=full-snap-node|full-node|archive-node` (default `full-snap-node`): ```bash theme={null} make up PROFILE=archive-node # start make down PROFILE=archive-node # stop make reup PROFILE=archive-node # down + up make ps PROFILE=archive-node # status make logs PROFILE=archive-node # tail logs make config PROFILE=archive-node # render merged compose config ``` Per-profile shortcuts exist for each, for example `make up-full-snap-node`, `make logs-archive-node`, `make reup-full-node`. `make ensure-jwt` generates `keys/jwt.txt` manually, and `make help` lists every target. ## Restore from a snapshot This applies to the non-snap profiles, `full-node` and `archive-node`. They sync in consensus-layer mode, re-executing every transaction from genesis, which is slow on a long-running chain. The Whitechain team publishes periodic `op-reth` database snapshots. Restoring one lets you start near a recent block and derive only the gap since the snapshot was taken. `full-snap-node` does not need this. It snap-syncs the state directly from `WHITECHAIN_RETH_TRUSTED_PEERS`. 1. Stop the node if it is running. ```bash theme={null} make down PROFILE=full-node ``` 2. Download and extract the snapshot for your network and profile into the matching data directory. ```bash theme={null} mkdir -p data/full-node/op-reth curl -L "/op-reth-testnet-full.tar.zst" \ | zstd -d \ | tar -x -C data/full-node/op-reth ``` After extraction you should have `data/full-node/op-reth/db`, `data/full-node/op-reth/static_files`, and related folders. 3. Start the node and watch it catch up. ```bash theme={null} make up-full-node make logs-full-node ``` Match the snapshot to the profile: a pruned (`full-node`) snapshot cannot serve archive queries, so restore an archive snapshot into `data/archive-node/op-reth` for an archive node. Restore only the `op-reth` data; the `op-node` directory (`peerstore`, `discovery`, `safedb`) is rebuilt automatically. You still need a working L1 RPC and Beacon to derive everything after the snapshot block. ## Create a snapshot This is the procedure the Whitechain team uses to produce the published `op-reth` snapshots, and the same steps you can follow to take your own backup of a `full-node` or `archive-node` datadir. Back up only the `op-reth` data; the `op-node` directory rebuilds itself. 1. Stop the node to get a consistent on-disk database. ```bash theme={null} make down PROFILE=full-node ``` 2. Archive and compress the `op-reth` datadir (`db`, `static_files`, and related folders). ```bash theme={null} tar -c -C data/full-node/op-reth . | zstd -o op-reth-testnet-full.tar.zst ``` 3. Restart the node so it resumes following the chain. ```bash theme={null} make up PROFILE=full-node ``` 4. Copy the archive to wherever clients download snapshots from (object storage, mirror, and so on). Match the archive name to the network and profile it was taken from. A pruned (`full-node`) snapshot cannot serve archive queries. ## Update the node Image versions are pinned in `docker-compose.yml`. To upgrade: ```bash theme={null} git pull docker compose pull make reup PROFILE=full-snap-node ``` The Whitechain team announces hardforks in advance. If an upgrade includes a hardfork, replace `artifacts//rollup.json` (and `genesis.json` if it changed) with the published version before `make reup`, and apply it before the activation timestamp to avoid a chain-divergence stall. ## Wipe and resync from genesis To wipe local state and resync a profile from genesis, run `make down PROFILE=`, then `rm -rf data//op-reth data//op-node`, then `make up PROFILE=`. This deletes the local chain database for that profile. For `full-node` and `archive-node`, prefer restoring a snapshot over a full genesis resync, and do not run this against a production node without a maintenance window. ## Security * The Engine API on `8551` stays on the internal Compose network and is never published to the host. * op-node RPC on `9545` is bound to loopback (`127.0.0.1`) only, on every profile, so it is reachable from the host but never from the network. The `admin` namespace is not enabled, so it serves only the read-only `optimism`, `superroot`, and `opp2p` namespaces. * `op-reth` exposes no `admin` namespace on any profile. The public JSON-RPC (`8545`) and WebSocket (`8546`) ports serve only read-only namespaces. * `keys/jwt.txt` is generated locally and used only between `op-node` and `op-reth` in this stack. It does not need to match anything outside. * The node holds no project-side private keys. Operate it as a read-and-forward node. * Restrict inbound access to the JSON-RPC ports you choose to expose. Put them behind a firewall, reverse proxy, or rate limiter before serving untrusted clients. ## Related * [Node operators overview](/operate/run-a-node/overview) * [Run a node](/operate/run-a-node/run-a-node) * [Node configuration](/operate/run-a-node/configuration) * [Troubleshooting](/operate/run-a-node/troubleshooting) # Node operators Source: https://l2docs.whitechain.io/operate/run-a-node/overview What a Whitechain RPC node is, how it follows the canonical L2 chain, the available node profiles, and what the node does and does not provide. Run an external Whitechain RPC node that follows the canonical L2 chain. A node is a pair of services: `op-reth` (execution client, JSON-RPC and WebSocket) and `op-node` (consensus client, derives the chain from L1 and peers over libp2p). This section is for teams that want their own RPC endpoint, an indexer or explorer backend, or a private node instead of the public RPC. Available on Testnet today; Mainnet at launch. ## How it works The Whitechain sequencer is closed and is not reachable from the public internet. Your node never needs access to it. Instead, the node: 1. Reads L1 batches from your own Ethereum L1 RPC and Beacon endpoints. 2. Pulls unsafe blocks from the network over libp2p. 3. Forwards `eth_sendRawTransaction` calls to the public Whitechain RPC, which routes them to the sequencer. This stack holds no project-side private keys. The sequencer, batcher, proposer, and challenger keys stay on the Whitechain side. You operate a follow-only node. ## Concepts: storage vs. sync method A node is defined by two independent choices. Each node profile is a fixed combination of them. **Storage**: how much state `op-reth` keeps: * **Pruned** (`--full`): keeps only recent state, prunes history. Smallest disk footprint. Serves a complete public RPC for current data. * **Archive** (no `--full`): keeps the full historical state. Largest disk footprint. Required for historical tracing and `eth_call` at old blocks. **Sync method**: how `op-reth` obtains that state: * **consensus-layer** (the `op-node` default): `op-node` derives the chain from L1 and feeds blocks to `op-reth` one by one; `op-reth` re-executes every transaction from genesis. No EL P2P peer needed, but the initial sync is long unless you restore a snapshot. * **execution-layer** (`--syncmode=execution-layer`, "snap"): `op-node` only drives the head; `op-reth` snap-syncs the state directly from a trusted reth peer over EL P2P. Fast, no snapshot restore needed, but it requires a reachable seed peer (`WHITECHAIN_RETH_TRUSTED_PEERS`). Snap sync cannot build archive state, only pruned. | Storage | consensus-layer (re-execute) | execution-layer (snap) | | ----------------- | ---------------------------- | ---------------------- | | Pruned (`--full`) | `full-node` | `full-snap-node` | | Archive | `archive-node` | Not supported | ## Node profiles The stack ships three profiles. Pick one with `PROFILE=` or a per-profile `make` target. | Profile | Use it for | Storage | Sync method | Snapshot | Public RPC | | ---------------- | ------------------------------------------------------ | ----------------- | ---------------------- | ----------- | ---------- | | `full-snap-node` | Recommended default, fastest and simplest to bootstrap | Pruned (`--full`) | execution-layer (snap) | Not needed | Yes | | `full-node` | Public RPC without a snap-sync peer | Pruned (`--full`) | consensus-layer | Recommended | Yes | | `archive-node` | Explorers, indexers, historical tracing | Full history | consensus-layer | Recommended | Yes | `full-snap-node` is the recommended default: pruned execution state and the basic RPC namespaces (`eth`, `net`, `web3`, `rpc`). It bootstraps by snap-syncing from a trusted reth peer (`WHITECHAIN_RETH_TRUSTED_PEERS`) instead of re-executing the chain, so it needs no snapshot. Use it when you have a reachable seed reth enode. `full-node` keeps the same pruned state and namespaces but syncs in consensus-layer mode, re-executing the chain from L1. Use it when you have no trusted peer to snap-sync from, and restore a snapshot to avoid a long initial sync. `archive-node` keeps the full historical state and adds the `debug`, `trace`, `txpool`, and `reth` namespaces, so it is required for historical `eth_call`, `debug_traceTransaction`, and log-heavy indexing. Archive cannot snap-sync, so it always re-executes from L1. Restore a snapshot to avoid a very long initial sync. It needs the most disk and RAM. ## Available RPC namespaces `op-reth` namespaces depend on the profile: | Profile | HTTP | WebSocket | | ------------------------------ | --------------------------------------------------------------- | --------------------------- | | `full-snap-node` / `full-node` | `eth`, `net`, `web3`, `rpc` | `eth`, `net`, `web3`, `rpc` | | `archive-node` | `eth`, `net`, `web3`, `rpc`, `debug`, `trace`, `txpool`, `reth` | `eth`, `net`, `web3`, `rpc` | `op-reth` exposes no `admin` namespace on any profile. `op-node` exposes an RPC on port `9545`, bound to loopback (`127.0.0.1`) only on every profile, reachable for local monitoring on the host, never from the network: | Namespace | Description | | ----------- | -------------------------------------------------------------------------- | | `optimism` | Rollup state, including `optimism_syncStatus` and `optimism_outputAtBlock` | | `superroot` | Read-only super-root status methods | | `opp2p` | P2P peer information, including `opp2p_self` and `opp2p_peers` | The `admin` namespace is not enabled on `op-node` either, so no profile exposes administrative or state-mutating methods. ## Sending transactions Applications submit transactions to your local `op-reth` HTTP port. The node forwards them to `WHITECHAIN_PUBLIC_RPC`, which routes them to the sequencer. You need no direct access to the sequencer. ```bash theme={null} curl -s -X POST http://127.0.0.1:8545 \ -H 'Content-Type: application/json' \ --data '{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x..."],"id":1}' ``` If you restart your node, in-flight transactions remain in the public RPC mempool, not in your local node. ## What this node does not provide This is a follow-only RPC node. It does not include block builder or sequencer roles, the batcher, proposer, or challenger, or the Flashblocks pre-confirmation stream. If your application needs sub-second pre-confirmations, contact the Whitechain team for the Flashblocks WebSocket URL. The `public-rpc-node` stack does not subscribe to the Flashblocks stream. ## Related * [Run a node](/operate/run-a-node/run-a-node) * [Node configuration](/operate/run-a-node/configuration) * [Node maintenance](/operate/run-a-node/maintenance) * [Troubleshooting](/operate/run-a-node/troubleshooting) * [Testnet](/learn/network/testnet) * [Network reference](/learn/network/reference) # Run a node Source: https://l2docs.whitechain.io/operate/run-a-node/run-a-node Prerequisites and step-by-step instructions to deploy a Whitechain RPC node with op-reth and op-node using Docker Compose. Deploy an external Whitechain RPC node on your own server. By the end of this guide you will have a running `op-reth` + `op-node` pair that follows the canonical L2 chain and exposes JSON-RPC and WebSocket endpoints. Available on Testnet today; Mainnet at launch. ## Prerequisites ### Hardware | Component | `full-snap-node` / `full-node` | `archive-node` | | --------- | --------------------------------------------------------------------- | ---------------------------------------- | | CPU | 4+ cores | 8+ cores | | RAM | 16 GB | 32 GB | | Storage | NVMe SSD, 500 GB min / 1 TB recommended (2x current chain size + 20%) | NVMe SSD, sized for full history (1 TB+) | | Network | 100 Mbps+ | 1 Gbps | Disk usage grows with the chain. Restoring a snapshot saves the initial sync time but does not change steady-state growth. ### Software * A Linux server with a public IP. * Docker with Compose v2. * `make`, `git`, `openssl`, `curl`, `jq`. * `zstd` and `tar` if you restore from a snapshot. ### L1 RPC and Beacon You need your own Ethereum L1 RPC and Beacon endpoints. `op-node` reads L1 batches through the RPC. It reads blob data through the Beacon API, since Whitechain batches are posted to L1 as EIP-4844 blobs, and blobs are only retrievable through the Beacon API, not the execution RPC. A regular execution-only endpoint is not enough; whatever you use must expose both. Run your own L1 node, or use a provider that offers a matching pair of endpoints for the network below. Not every provider exposes a Beacon API on its free tier, so confirm both endpoints respond before filling in `.env`. | Whitechain network | L1 chain | | ------------------ | ---------------- | | Whitechain mainnet | Ethereum mainnet | | Whitechain Sepolia | Ethereum Sepolia | ### Files from the Whitechain team * `genesis.json` (L2 execution genesis) and `rollup.json` (OP rollup configuration) for your network. * `WHITECHAIN_PUBLIC_RPC`: the public Whitechain RPC URL, used as the transaction-forwarding target. * `WHITECHAIN_RETH_TRUSTED_PEERS`: a trusted reth enode to snap-sync from (required for `full-snap-node`). * `WHITECHAIN_PUBLIC_OP_NODE_P2P` (optional): a static op-node peer. * An `op-reth` database snapshot URL (optional, recommended for `full-node` and `archive-node`). For testnet, the `artifacts/testnet/` files and `.env.testnet.example` already ship with working values for all of these. You only need to request values from the Whitechain team for mainnet, or if you need a fresh snapshot URL. ## Set up the node 1. Get the `public-rpc-node` manifests onto your server. 2. Place the network artifacts under `artifacts//`. The folder name must match `WHITECHAIN_NETWORK` in `.env`. ``` public-rpc-node/artifacts/testnet/genesis.json public-rpc-node/artifacts/testnet/rollup.json ``` 3. Create your `.env` from the example and fill in the required values. ```bash theme={null} cp .env.testnet.example .env # or .env.mainnet.example ``` ```bash theme={null} WHITECHAIN_NETWORK=testnet PUBLIC_IP=203.0.113.10 WHITECHAIN_PUBLIC_RPC=https://rpc.testnet.whitechain.io L1_RPC_URL=https://your-l1-rpc.example.com L1_BEACON_URL=https://your-l1-beacon.example.com L1_RPC_KIND=basic # required for full-snap-node WHITECHAIN_RETH_TRUSTED_PEERS=enode://@:30303 ``` For the full list of variables, see [Node configuration](/operate/run-a-node/configuration). 4. To skip the long initial sync on `full-node` or `archive-node`, restore a snapshot first. See [Restore from a snapshot](/operate/run-a-node/maintenance#restore-from-a-snapshot). `full-snap-node` skips this step. It snap-syncs directly from `WHITECHAIN_RETH_TRUSTED_PEERS`. 5. Start the profile you chose. ```bash theme={null} make up-full-snap-node # recommended default: pruned, snap-syncs from a trusted peer # make up-full-node # pruned, consensus-layer sync (re-executes from L1) # make up-archive-node # archive node ``` `make up` validates `.env` and the artifacts, generates `keys/jwt.txt` if missing, then runs `docker compose --profile up -d`. 6. Confirm the node responds on the profile's HTTP port. ```bash theme={null} curl -s -X POST http://127.0.0.1:8545 \ -H 'Content-Type: application/json' \ --data '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["latest",false],"id":1}' ``` For `full-node` and `archive-node`, initial sync from genesis re-executes every transaction and can take from minutes on a fresh testnet to many hours on a long-running chain. Restore a snapshot to cut this down, or use `full-snap-node`, which snap-syncs from its trusted peer instead and typically completes in about an hour on testnet. ## Verify the sync Watch the logs with `make logs-full-snap-node` (or `logs-full-node` / `logs-archive-node`). In `op-node` you should see `Connected to L1 Beacon API` and `started p2p host` with your local peerID early on. What follows depends on the profile's sync method: * `full-snap-node` (execution-layer / snap): `Starting EL sync`, then repeating `Inserting unsafe L2 execution payload to drive EL sync` and `Inserted new L2 unsafe block` lines. These appear as blocks are pulled from the trusted peer and applied to `op-reth`. * `full-node` / `archive-node` (consensus-layer): repeating `Advancing bq origin` lines (L1 batch derivation), then `Inserted new L2 unsafe block` lines as `op-reth` re-executes each block. Underneath this, `op-reth` runs its own staged sync pipeline (14 stages) independently of what `op-node` reports. The RPC keeps reporting block `0` until the pipeline finishes, which is expected, not a hang. Watch the `op-reth` container logs directly: `docker logs -f whitechain--op-reth 2>&1 | grep --line-buffered -iE "stage=|received headers|finished stage"`. Header download logs `Received headers ... to_block=N`; later stages log `checkpoint=X target=Y`. In the first few minutes after start, the node may report no peers ready to handle block requests until the static peer handshake completes. Wait for a connected peer to appear before treating this as an error. `op-node` RPC on `9545` is bound to loopback (`127.0.0.1`) only, on every profile. Query it from the host the node runs on, not over the network. Check how far the node is behind the wall clock: ```bash theme={null} echo "behind by $((($(date +%s) - $( \ curl -s -X POST http://127.0.0.1:9545 \ -H 'Content-Type: application/json' \ --data '{"jsonrpc":"2.0","method":"optimism_syncStatus","params":[],"id":1}' \ | jq -r .result.unsafe_l2.timestamp)) / 60)) minutes" ``` Check connected peers: ```bash theme={null} curl -s -X POST http://127.0.0.1:9545 \ -H 'Content-Type: application/json' \ --data '{"jsonrpc":"2.0","method":"opp2p_peers","params":[true],"id":1}' \ | jq '.result.totalConnected' ``` ## Related * [Node operators overview](/operate/run-a-node/overview) * [Node configuration](/operate/run-a-node/configuration) * [Node maintenance](/operate/run-a-node/maintenance) * [Troubleshooting](/operate/run-a-node/troubleshooting) # Troubleshooting Source: https://l2docs.whitechain.io/operate/run-a-node/troubleshooting Common problems and fixes when running a Whitechain RPC node: missing configuration, snapshot issues, slow sync, and transaction errors. Common issues and their fixes when running a Whitechain RPC node. ## Missing `.env` No `.env` file found. Copy from `.env.testnet.example` or `.env.mainnet.example` and fill the required values. ```bash theme={null} cp .env.testnet.example .env ``` See [Node configuration](/operate/run-a-node/configuration) for the full list of variables. ## Missing `artifacts//genesis.json` Artifacts are missing or the folder name does not match `WHITECHAIN_NETWORK`. Place `genesis.json` and `rollup.json` under `artifacts//`. The folder name must match the `WHITECHAIN_NETWORK` value in `.env`. ``` public-rpc-node/artifacts/testnet/genesis.json public-rpc-node/artifacts/testnet/rollup.json ``` ## Compose refuses to start, asks for `PUBLIC_IP` `PUBLIC_IP` is not set in `.env`. Set it to the public IP the node advertises for P2P. ## Compose refuses to start, asks for `WHITECHAIN_PUBLIC_RPC` The transaction-forwarding target is not set. Set `WHITECHAIN_PUBLIC_RPC` to the public Whitechain RPC URL in `.env`. ## `WHITECHAIN_RETH_TRUSTED_PEERS` missing (`full-snap-node` only) The `full-snap-node` profile needs a trusted reth enode to snap-sync from. Set `WHITECHAIN_RETH_TRUSTED_PEERS` to `enode://@:30303` in `.env`. ## `failed to insert unsafe payload ... node is syncing` Expected during the initial consensus-layer sync of a fresh `full-node` or `archive-node` database. `op-node` receives an unsafe head over gossip, but `op-reth` has not re-executed up to its parent yet, so the forkchoice update returns `SYNCING`. Meanwhile `Advancing bq origin` log lines show L1 derivation is still progressing. The messages stop once the node catches up. To avoid the long catch-up, restore a snapshot or use `full-snap-node`. ## `full-snap-node` is not snap-syncing * Confirm `WHITECHAIN_RETH_TRUSTED_PEERS` is a reachable reth enode (`enode://@:30303`) and the peer is up. * Confirm outbound EL P2P (devp2p) to that peer is not blocked by a firewall. * Check that `op-node` logs show `--syncmode=execution-layer` is active and `op-reth` reports syncing against the trusted peer. ## Restored a snapshot but the node resyncs from genesis Check these three things: 1. Confirm the data landed in the right place: `data//op-reth/db` and `data//op-reth/static_files` must exist. 2. Confirm the snapshot matches `WHITECHAIN_NETWORK` and the profile you start. 3. Make sure the node was stopped during extraction. See [Restore from a snapshot](/operate/run-a-node/maintenance#restore-from-a-snapshot) for the full procedure. ## Sync is slow Common causes, in order of likelihood: 1. No snapshot restored on a long-running `full-node` or `archive-node`, so the node re-executes from genesis. Restore one, or use `full-snap-node`. 2. L1 endpoint is rate-limited or slow. Switch to a faster L1 RPC and Beacon, or run your own L1 node. 3. Disk I/O is the bottleneck. Move `data/` to NVMe storage. ## `optimism_syncStatus` shows `unsafe_l2.number = 0` The node has not yet inserted the first L2 block. Wait a minute. If it stays at 0: * Check op-node logs for `Reset of Engine is completed` and `Inserted new L2 unsafe block`. If absent, the engine is not connected. Confirm `op-reth` is healthy on its HTTP port. * Confirm the genesis block hash in op-node logs matches the one in `rollup.json`. ## `nonce has already been used` when deploying The node is not fully synced. Wait for the `optimism_syncStatus` lag to drop close to zero before submitting transactions. ```bash theme={null} echo "behind by $((($(date +%s) - $( \ curl -s -X POST http://127.0.0.1:9545 \ -H 'Content-Type: application/json' \ --data '{"jsonrpc":"2.0","method":"optimism_syncStatus","params":[],"id":1}' \ | jq -r .result.unsafe_l2.timestamp)) / 60)) minutes" ``` ## Related * [Node operators overview](/operate/run-a-node/overview) * [Run a node](/operate/run-a-node/run-a-node) * [Node configuration](/operate/run-a-node/configuration) * [Node maintenance](/operate/run-a-node/maintenance)