# Agent Skills
Source: https://docs.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://docs.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://docs.whitechain.io/llms.txt) | Page index with titles and descriptions | Discovering which docs exist before going deeper |
| [llms-full.txt](https://docs.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://docs.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 | [docs.whitechain.io/llms.txt](https://docs.whitechain.io/llms.txt) | Index of every page with a short description |
| llms-full.txt | [docs.whitechain.io/llms-full.txt](https://docs.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://docs.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 the reported coin supply.
* Transaction fees for the most recent completed day.
* Chain counters: total transactions, addresses, average block time, and a live chain head.
## Run the template
The [`whitechain-indexer-gas-tracker`](https://github.com/whitechain-labs/templates/tree/main/whitechain-indexer-gas-tracker) template implements every request on this page as a client-only React app. It reads the price, daily fees, and chain head from the RPC API, and the gas tiers and counters from REST API v2, on the two refresh timers described below. For the supply card it reads `total_supply` from `GET /api/v2/addresses` rather than the RPC action below, for the reason given in that section. Read it alongside this page to see the same calls in working code.
```bash Terminal theme={null}
npx degit whitechain-labs/templates/whitechain-indexer-gas-tracker indexer-gas-tracker
cd indexer-gas-tracker
pnpm install
pnpm dev
```
The app needs Node 20.19 or later. It connects no wallet and needs no API key, because every request it sends is one of the read-only calls documented below.
## 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` |
| Reported coin 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.
### Reported coin 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 coin supply in whole WBT as a string, not in wei. It is the sum of every address balance already divided by 10^18, so do not divide it again. Keep it a string and use big-integer or decimal math, because the value overflows a JavaScript number.
On Whitechain Sepolia this figure is not a meaningful supply. Two addresses hold sentinel balances set at genesis, `2^256 - 1` wei and `2^248 - 1` wei, and they dominate the sum, which is why the value runs to 60 digits. Cross-check it against the balances in `GET /api/v2/addresses`, whose response also carries a `total_supply` field suitable for display. Present this endpoint as the reported coin supply rather than as a circulating supply.
### 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": 5, "average": 5, "fast": 5 },
"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 are equal on a quiet chain, as here, where all three sit at the 5 gwei base fee floor. |
| `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
* [`whitechain-indexer-gas-tracker` template on GitHub](https://github.com/whitechain-labs/templates/tree/main/whitechain-indexer-gas-tracker)
* [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://docs.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.
## Run the template
The [`whitechain-indexer-token-dashboard`](https://github.com/whitechain-labs/templates/tree/main/whitechain-indexer-token-dashboard) template implements every request on this page as a client-only React app. It reads the transfer feed from the GraphQL API and the metadata, counters, and holders from REST API v2, then labels each value it renders with the request that produced it. Read it alongside this page to see the same calls in working code.
```bash Terminal theme={null}
npx degit whitechain-labs/templates/whitechain-indexer-token-dashboard indexer-token-dashboard
cd indexer-token-dashboard
pnpm install
pnpm dev
```
The app needs Node 20.19 or later. It connects no wallet and needs no API key, because every request it sends is one of the read-only calls documented below.
## 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
* [`whitechain-indexer-token-dashboard` template on GitHub](https://github.com/whitechain-labs/templates/tree/main/whitechain-indexer-token-dashboard)
* [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://docs.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.
## Run the template
The [`whitechain-indexer-wallet`](https://github.com/whitechain-labs/templates/tree/main/whitechain-indexer-wallet) template implements every request on this page as a client-only React app. It reads the address record, token balances, and transaction history from REST API v2, and the balance, nonce, and chain head from the ETH RPC API. It labels each value it renders with the request that produced it. Read it alongside this page to see the same calls in working code.
```bash Terminal theme={null}
npx degit whitechain-labs/templates/whitechain-indexer-wallet indexer-wallet
cd indexer-wallet
pnpm install
pnpm dev
```
The app needs Node 20.19 or later. It connects no wallet and needs no API key, because every request it sends is one of the read-only calls documented below.
## 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
* [`whitechain-indexer-wallet` template on GitHub](https://github.com/whitechain-labs/templates/tree/main/whitechain-indexer-wallet)
* [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://docs.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/)
# Token list
Source: https://docs.whitechain.io/build/block-explorer/token-list
The Uniswap-standard token list for Whitechain: where it is served, what it contains, how to load it in a dapp or wallet, and how to add a token to it.
The list covers Whitechain Sepolia (chain ID `1874`) only. Whitechain Mainnet is not live, so it holds no mainnet entries. The generator handles several chains per token, so mainnet addresses go into the same list at Mainnet launch.
Whitechain publishes a [Uniswap-standard token list](https://github.com/Uniswap/token-lists), so a wallet or a swap interface can load the token set at runtime instead of carrying hardcoded addresses. The list is a single JSON file, validated against the official `@uniswap/token-lists` schema before it ships.
| Artifact | URL |
| - | - |
| Token list | `https://whitechain.io/tokens/whitechain.tokenlist.json` |
| List logo | `https://whitechain.io/tokens/whitechain_logo.svg` |
| Token logo | `https://whitechain.io/tokens/data//logo.svg` |
The file is public, static, and needs no key. Serve it straight to a client or cache it behind your own origin.
## What the list contains
| Symbol | Name | Decimals | Chain ID | Address |
| - | - | - | - | - |
| USDT.e | Bridged USDT (Whitechain) | 6 | `1874` | [`0xaa7962d073935D387e8d442DA1D7149C81388f46`](https://explorer.testnet.whitechain.io/address/0xaa7962d073935D387e8d442DA1D7149C81388f46) |
| WWBT | Wrapped WhiteBIT Coin | 18 | `1874` | [`0x4200000000000000000000000000000000000006`](https://explorer.testnet.whitechain.io/address/0x4200000000000000000000000000000000000006) |
`WWBT` is the ERC-20 form of WBT, the native gas token. A list entry needs a contract address and a native coin has none, so the wrapped contract is what appears. The [Portal bridge](/build/bridge/portal-bridge#token-addresses) lists the same contract under the same symbol.
The bridge carries more than the list holds. Its Whitechain Sepolia table also has `USDC.e` and `ETH`, which have no entry here. A token joins the list when someone opens a pull request for it, so the list tracks what has been submitted rather than what is bridgeable.
Every address in the list is checksummed per EIP-55, and every `chainId` resolves to a network the build knows about. Both are asserted by the validator, so a list that ships has passed them.
Alongside `tokens`, the file carries a `name`, a `logoURI`, `keywords`, a `timestamp`, and a `version` object with `major`, `minor`, and `patch` fields. The [token-lists specification](https://github.com/Uniswap/token-lists) defines what each version field means for a consumer deciding whether to refresh.
## Load the list in a dapp
Fetch the file and filter by the chain your app is connected to:
```ts theme={null}
const CHAIN_ID = 1874; // Whitechain Sepolia
const res = await fetch("https://whitechain.io/tokens/whitechain.tokenlist.json");
if (!res.ok) throw new Error(`token list: ${res.status}`);
const list = await res.json();
const tokens = list.tokens.filter((t) => t.chainId === CHAIN_ID);
```
Each entry carries `chainId`, `address`, `name`, `symbol`, `decimals`, and `logoURI`, which is what a token selector needs to render a row and build a transfer.
Read `list.version` and `list.timestamp` to decide when to refetch. Pin a copy in your build if you need the token set to be stable across a release, and treat the hosted file as the source you reconcile against.
Interfaces that accept a token list URL, such as a Uniswap-derived swap UI, take `https://whitechain.io/tokens/whitechain.tokenlist.json` directly.
## Add a token
The list is generated from per-token source files, so you add a token by adding its folder and regenerating. Source lives in [`whitechain-labs/templates/whitechain-token-lists`](https://github.com/whitechain-labs/templates/tree/main/whitechain-token-lists).
1. Fork the repository and create `data//`, named for the token symbol.
2. Add `data.json`:
```json data/WWBT/data.json theme={null}
{
"name": "Wrapped WhiteBIT Coin",
"symbol": "WWBT",
"decimals": 18,
"description": "Short human-readable description.",
"website": "https://example.com",
"twitter": "@handle",
"tokens": {
"whitechain-testnet": {
"address": "0x4200000000000000000000000000000000000006"
}
}
}
```
3. Add `logo.svg`, 256 by 256, in the same folder.
4. Run `npm run generate` and commit both your `data/` changes and the regenerated `whitechain.tokenlist.json`.
5. Open a pull request.
The keys under `tokens` are network names, not chain IDs. They are defined in `src/chains.ts`, which maps `whitechain-testnet` to `1874` and is the single place a new network is added. The build resolves each name to its chain ID. To list one token on several networks, add another key under `tokens`; each becomes its own entry in the output.
Write the `address` in its EIP-55 checksummed form. `generate` normalizes an all-lowercase address to that form for you, but a mixed-case address whose checksum is wrong throws and stops the build. `validate` then requires the address in the generated list to be checksummed, so an address hand-edited into `whitechain.tokenlist.json` fails there.
## Run the tooling
The repository needs Node 20 or newer and npm.
```bash Terminal theme={null}
npx degit whitechain-labs/templates/whitechain-token-lists whitechain-token-lists
cd whitechain-token-lists
npm install
```
| Command | What it does |
| - | - |
| `npm run generate` | Reads every `data//data.json`, resolves each network name to its chain ID, and rewrites `whitechain.tokenlist.json`. Sorts by symbol then chain ID, bumps the version, and sets a fresh timestamp. |
| `npm run validate` | Checks the committed `whitechain.tokenlist.json` against the `@uniswap/token-lists` schema, asserts every address is a checksummed 40-hex EVM address, and asserts every chain ID exists in the chain config. Exits non-zero on any error. |
`generate` validates in memory before it writes, so it cannot emit an invalid list.
Use `degit` to run the tooling and inspect the output. To propose a token, fork the repository instead, because `degit` copies the tree without git history.
## How the list is published
Two workflows own the file.
| Workflow | Trigger | What it does |
| - | - | - |
| `validate.yml` | Every pull request | Installs, checks the committed list is up to date with `data/`, then runs `npm run validate`. |
| `publish.yml` | Merge to `main` | Regenerates and validates, commits the refreshed list if it changed, and deploys the repository root. |
The repository root is served verbatim under `https://whitechain.io/tokens/`, which is why a logo at `data/WWBT/logo.svg` in the repository answers at `https://whitechain.io/tokens/data/WWBT/logo.svg`.
## Related
* [Network reference](/learn/network/reference) for chain IDs and endpoints
* [Portal bridge](/build/bridge/portal-bridge) for the contract addresses of bridged assets
* [Wallets](/build/wallet/wallets) for adding Whitechain to a wallet
# OP Stack canonical bridge
Source: https://docs.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()`. Use the [Portal bridge](/build/bridge/portal-bridge) to move ETH between Ethereum and Whitechain. |
| 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 |
| `AnchorStateRegistryProxy` | [`0x4da165963420deeb0788f338b7a1df41e535a32c`](https://sepolia.etherscan.io/address/0x4da165963420deeb0788f338b7a1df41e535a32c) | n/a |
| `EthLockboxProxy` | [`0x7becd75f3e1ce952cb63fd7ef5220f205a6e1469`](https://sepolia.etherscan.io/address/0x7becd75f3e1ce952cb63fd7ef5220f205a6e1469) | n/a |
| `SuperchainConfigProxy` | [`0x11854d3146a460d095943c3231f2755ba342426e`](https://sepolia.etherscan.io/address/0x11854d3146a460d095943c3231f2755ba342426e) | 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.
The complete L1 deployment, including every implementation contract behind these proxies, is published as `l1-addresses.json` in the [whitechain-bootstrap repository](https://github.com/whitechain-labs/whitechain-bootstrap). See [Network artifacts](/operate/run-a-node/network-artifacts#l1-contracts-on-ethereum-sepolia).
## 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 |
| Bridging ETH | CGT mode blocks the native ETH deposit path, and `depositETH` reverts with `OptimismPortal_NotAllowedOnCGTMode()` | Use the [Portal bridge](/build/bridge/portal-bridge), which carries ETH on the Ethereum leg |
| 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://docs.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://docs.whitechain.io/build/bridge/portal-bridge
Move USDC, USDT, WBT, and ETH between Ethereum, Tron, and Whitechain in minutes with the Portal, Whitechain's centralized bridge.
The Portal contracts, routes, and app are on Testnet (Ethereum Sepolia, Tron Nile, and Whitechain Sepolia). The app runs at [bridge.testnet.whitechain.io](https://bridge.testnet.whitechain.io). 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 assets, USDC, USDT, WBT, and ETH, between Ethereum, Tron, and Whitechain in minutes, and it is the only route that connects Tron to Whitechain. The bridge liquidity sits in Portal contracts that Whitechain administers, and a Whitechain-operated relayer 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: funds in transit sit in Portal contracts that Whitechain controls | 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, ETH | 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. ETH also keeps its symbol, and it arrives as a pegged ERC-20 minted against WETH locked on Ethereum.
| Route | Assets | Daily limit (testnet) |
| - | - | - |
| Ethereum to and from Whitechain | USDC (arrives as USDC.e), WBT, ETH | USDC 10,000 per day; WBT 1,000 per day; ETH TBD, published once the limit is confirmed |
| 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.
ETH runs on the Ethereum leg only. There is no ETH route between Tron and Whitechain, where the assets stay USDT and WBT.
On the Ethereum side the route holds WETH, and the app wraps your native ETH for you. You supply ETH, so you never have to obtain WETH yourself. That is why the asset picker says ETH while the transaction details name WETH.
On Whitechain, ETH is a bridged ERC-20 with 18 decimals whose `remoteToken` is the Ethereum WETH contract. It is not the native asset of the network and it cannot pay gas. Gas on Whitechain is always WBT.
## How a transfer works
For the same flow screen by screen in the app, see [Use the Portal bridge](/build/bridge/use-the-portal).
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 app tracks each transfer through three stages, then marks it complete.
| Status | Meaning |
| - | - |
| Submitting | The transaction is being sent on the source network, and your wallet is waiting for your confirmation. |
| Confirming | The source network is confirming the transaction. |
| Sending | The Portal has seen the deposit and is releasing the funds on the destination network. |
| Completed | The funds have been sent to the destination network. |
A transfer that runs past its expected time is labelled as delayed rather than failed. For the tracker screen, see [Use the Portal bridge](/build/bridge/use-the-portal#track-the-transfer).
## 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. The liquidity sits in the Bridge contract on each network, a Whitechain-operated backend signs every transfer with a dedicated key, and each Bridge contract only accepts transfers authorized by that key. No balance moves into a Whitechain wallet, so what makes the Portal custodial is control rather than location: Whitechain administers those contracts and authorizes every release. This is what makes transfers fast, and it is the tradeoff against the canonical bridge, where a dispute game rather than an operator decides whether a withdrawal completes.
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) |
| WETH | Ethereum Sepolia | [`0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9`](https://sepolia.etherscan.io/address/0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9) |
| USDT | Tron Nile | [`TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf`](https://nile.tronscan.org/#/address/TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf) |
| WBT | Tron Nile | [`TGa8yeUzkCmuajSedYoPiA9bA8YBnvXvEL`](https://nile.tronscan.org/#/address/TGa8yeUzkCmuajSedYoPiA9bA8YBnvXvEL) |
| WWBT | 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) |
| ETH | Whitechain Sepolia | [`0x1c7E0793c69838D1da9D5b98B5893866B977bA67`](https://explorer.testnet.whitechain.io/address/0x1c7E0793c69838D1da9D5b98B5893866B977bA67) |
Every address above is listed under the symbol its own contract reports. On Whitechain Sepolia the WBT routes appear as `WWBT`, Wrapped WhiteBIT Coin, the ERC-20 form of the gas token with 18 decimals, because native WBT has no contract address of its own. A Portal transfer into Whitechain still arrives as a native WBT balance. The same contract is listed as `WWBT` in the [Whitechain token list](/build/block-explorer/token-list).
## When to use the Portal
| Scenario | Use |
| - | - |
| Fast exit from Whitechain to Ethereum | Portal |
| Move assets between Tron and Whitechain | Portal (the only route) |
| Move ETH between Ethereum and Whitechain | Portal (the only route, because the canonical bridge cannot carry ETH) |
| 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 sit in contracts the operator controls, not in a trustless protocol.
## Related
* [Use the Portal bridge](/build/bridge/use-the-portal)
* [OP Stack canonical bridge](/build/bridge/bridge-assets)
* [Faucet](/learn/get-started/get-testnet-wbt)
* [Network reference](/learn/network/reference)
# Use the Portal bridge
Source: https://docs.whitechain.io/build/bridge/use-the-portal
Step by step walkthrough of the Portal bridge app: connect a wallet, choose a route, set a recipient, confirm a transfer, and track it to completion.
The Portal app and its contracts run on Testnet: Whitechain Sepolia (chain ID 1874), Ethereum Sepolia, and Tron Nile. The app runs at [bridge.testnet.whitechain.io](https://bridge.testnet.whitechain.io). Mainnet routes and addresses are published at Mainnet launch.
## Overview
This page walks through one Portal transfer in the app, screen by screen. Open [bridge.testnet.whitechain.io](https://bridge.testnet.whitechain.io) to follow along. For the trust model, routes, fees, limits, and contract addresses, see [Portal bridge](/build/bridge/portal-bridge).
Every route runs both ways. The screens below follow WBT from Whitechain to Ethereum. The reverse direction differs in two places, both called out below. Fees are denominated in the source network's token, and a token route asks for an approval before the transfer.
The app is a browser page that your own wallet signs for. It never holds your keys or your funds, and it never asks for them. The bridge itself is a different matter: while a transfer is in flight, your funds sit in Portal contracts that Whitechain controls. See [Custody and the alternative path](#custody-and-the-alternative-path).
## Before you start
| Requirement | Detail |
| - | - |
| A wallet | An EVM wallet for Ethereum and Whitechain routes. A Tron wallet for Tron routes. |
| Whitechain Sepolia in the wallet | The app can add it for you, or add it manually. The app labels this network Whitechain Testnet on screen. See [Configure a wallet](/build/wallet/wallets). |
| Testnet WBT for gas | WBT is the native gas token on Whitechain. Get it from the [faucet](/learn/get-started/get-testnet-wbt). |
| The asset you want to move | The Portal carries four configured assets, USDC, USDT, WBT, and ETH, and the app shows which networks each one reaches. See [Supported routes and assets](/build/bridge/portal-bridge#supported-routes-and-assets). |
## The bridge form
The form is explorable before you connect anything. You can pick networks and a token, and read the estimate line, while only the submit button stays gated.
| Field | What it does |
| - | - |
| From | The source network and the token you are sending. Shows your balance once a wallet is connected, plus a Max action. |
| To | The destination network and the amount that arrives. |
| Swap arrows | The control between From and To reverses the direction. |
| Estimate line | Estimated time, the bridge fee, and the gas for your own transaction. |
| Send to a different address | Sends the funds to an address other than the connected wallet. |
### Choose the asset and the network
Both rows carry an asset chip, and selecting one opens a two-step picker. The first step is the asset.
Read the icons on the right of each row before you pick. They are the networks that asset reaches, so they tell you which routes exist for it without trial and error. Search filters by name, and **All networks** narrows the list to a single network.
The second step is the network, and it offers only the networks the chosen asset supports. The back arrow returns to the asset list.
An asset that the Portal does not carry in the direction you want is not selectable, so a route you cannot build never reaches the point of a failed transaction.
The picker lists ETH, and the route behind it holds WETH on Ethereum. You supply native ETH and the app wraps it for the transfer, so you never have to obtain WETH yourself. On Whitechain the arriving ETH is a bridged ERC-20 that you add by contract address like any other bridged token, and it cannot pay gas. See [Supported routes and assets](/build/bridge/portal-bridge#supported-routes-and-assets).
## Connect a wallet
Select **Connect wallet**. The dialog has a search field, marks a detected wallet as Installed, and scrolls through the full list.
| Wallet | In the app today |
| - | - |
| MetaMask | Available |
| WalletConnect | Available |
| Trust | Available |
| Phantom | Available, EVM mode only |
| OKX Wallet | Available |
| Coinbase Wallet | Available |
| Bitget Wallet | Available |
| Rabby | Available |
| WhiteWallet | Listed as coming soon, not connectable yet |
An EVM wallet outside the named set still connects through WalletConnect v2, so the list is a support commitment rather than a whitelist that blocks other wallets.
Phantom connects in EVM mode only: no Solana account, network, or route appears anywhere in the flow. A wallet that is not installed shows an install action instead of a connect attempt that fails.
### Tron routes need a Tron wallet
Tron routes connect through the TronLink or OKX wallet adapters.
A Tron route requires the Tron wallet, and the app never asks an EVM wallet to sign a Tron transaction, or the reverse.
## Switch to the source network
Your wallet has to be on the network you are sending from. When it is not, the submit button becomes a switch action naming the network you need, and no transfer can be submitted until you take it.
## Enter an amount
Type an amount, or select **Max**. Once there is an amount, the estimate line prices the transfer.
Max resolves to the largest amount you can actually send, which is the tightest of your balance, the remaining daily limit, and the liquidity available on the destination network.
The estimate line separates two costs that are easy to confuse. Both are denominated in the native token of the network you are sending from, so they read in WBT leaving Whitechain and in ETH leaving Ethereum.
| Line | What it covers |
| - | - |
| Bridge fee | The Portal charges no bridging fee today. On a Whitechain to Ethereum transfer this line carries the Ethereum gas the Portal pays for you. |
| Gas | The source network gas for your own transaction, which your wallet charges you directly. |
For how the WBT figure is derived, see [Fees and limits](/build/bridge/portal-bridge#fees-and-limits).
If the amount breaks a rule, the form says which one: below the minimum, above your balance, above the remaining daily limit, or above the destination liquidity. Each case names the cause and what to do next, so a rejected amount never reaches your wallet.
## Send to a different address
Select **Send to a different address** to enter a recipient other than the connected wallet. Leave it clear and the funds arrive at the address you connected with.
The address is checked against the destination network, so a Tron address entered for an EVM destination is rejected with a specific message rather than accepted. A custom recipient asks you to confirm a second time, because the transfer cannot be reversed once it is signed.
## Review and confirm
With the wallet on the right network and a valid amount, the submit button becomes **Bridge**.
Selecting Bridge opens the review, which is the last screen before any signature.
| Review field | Why it matters |
| - | - |
| Amount and You receive | What leaves and what arrives. Compare the two before signing. |
| Route | The engine handling the transfer. The Portal is the only route in the app today. |
| From and To | Both networks, and the recipient address in full rather than truncated. |
| Estimated time | How long the transfer is expected to take. |
| Total fee (max) | The ceiling on what you pay. The transfer cannot cost more than this. |
| Bridge contract | The contract your wallet is about to call. Check it against [Contract addresses](/build/bridge/portal-bridge#contract-addresses). |
Select **Confirm in wallet**, then confirm in your wallet.
### Token routes ask for two signatures
How many signatures you give depends on what you are sending. The native token of the source network takes one, the transfer itself, which is the case for WBT leaving Whitechain, because WBT is the gas token there. A token contract takes two, first an approval and then the transfer, which is the case for the same WBT leaving Ethereum, where it is an ERC-20.
The review screen tells you which one you are in for before you sign anything.
The app requests an approval for the exact transfer amount, never an unlimited allowance.
The tracker then waits on that first signature before it asks for the second, so a transfer sitting at Waiting for the approval to confirm needs you to act in your wallet.
On a token route the Details menu carries a **Review token approvals** link as well, so you can check what you have granted.
Verify the bridge contract address on the review screen against [Contract addresses](/build/bridge/portal-bridge#contract-addresses) before you sign. A transfer sent to the wrong contract cannot be recovered.
## Track the transfer
After you sign, the app shows a live tracker. You can close the tab: the transfer continues, and you can reopen it from **Activity**.
The tracker moves through three stages, then marks the transfer complete. The screens below show them in order.
| Stage | What is happening |
| - | - |
| Submitting | Your transaction is being sent on the source network, and your wallet is waiting for your confirmation. |
| Confirming | The source network is confirming your transaction. |
| Sending | The Portal has seen your deposit and is releasing the funds on the destination network. |
| Completed | The funds have been sent on the destination network. |
**Details** links the source transaction and the destination network in a block explorer, so you can follow the transfer outside the app.
The app labels a transfer that runs past its expected time as delayed rather than failed, and offers the support path. Nothing is lost while it is delayed.
Select **View statuses** to expand the full stage list on a finished transfer.
A bridged token does not appear in your wallet on its own. Add the destination token by its contract address, from [Token addresses](/build/bridge/portal-bridge#token-addresses).
### When a transfer fails
A stage that fails is marked **Failed**, and a banner names the network it failed on. The stages before it keep their state, so you can see how far the transfer got.
| Action | When to use it |
| - | - |
| Try again | Retry the same transfer. Check your wallet activity first, so you do not send a second transfer for one that did go through. |
| Support | Reach support with this transfer already on screen. |
| New Bridge | Leave this transfer and start a different one. |
A source transaction that fails moves no tokens, so the amount stays in your wallet. The source network still charges gas for the attempt.
A slow-mining transaction is never reported as a failed transfer.
So Failed means the transaction itself failed, not that it was taking a long time. A transfer that is merely slow stays in progress and is labelled as delayed.
## Find a past transfer
Activity in the header lists the transfers of the connected wallet, with the amount, the direction, the date, and the status. Filters narrow the list, and every row opens the same tracker as the live transfer.
A transfer keeps its own address, so the link survives a reload, a wallet disconnect, and a new browser session. **Action needed** collects the transfers that are waiting on you.
## Manage connected wallets
The wallet button in the header opens a popover listing what is connected. It copies the address, disconnects the wallet, and adds a second one, which is what a Tron route needs alongside an EVM wallet.
## Custody and the alternative path
The transfer is not trustless, and the app states that on the form and again on the review screen.
Your funds sit in the Portal's Bridge contract on the source network until the relayer releases the matching amount from the Bridge contract on the destination network. No balance moves into a Whitechain wallet. What makes the transfer custodial is control rather than location: Whitechain administers both contracts and authorizes every release, so you depend on the operator instead of a protocol guarantee.
Two separate facts are easy to merge into one. The app never holds your keys, because your own wallet signs every transaction. The Bridge contracts hold your funds in transit, and Whitechain decides when they leave. The first fact does not soften the second, and speed is what the second one buys.
The OP Stack canonical contracts carry a second path between Ethereum and Whitechain, and it is available programmatically rather than through this app.
That path replaces the operator with an on-chain dispute game, which only Whitechain's configured challenger can play today. See [Fault proofs](/learn/network/transaction-finality#fault-proofs) for what the game currently guarantees, and [OP Stack canonical bridge](/build/bridge/bridge-assets) for how to use it. It is the slower path: a withdrawal waits out the dispute period.
## Related
* [Portal bridge](/build/bridge/portal-bridge)
* [OP Stack canonical bridge](/build/bridge/bridge-assets)
* [Configure a wallet](/build/wallet/wallets)
* [Get testnet WBT](/learn/get-started/get-testnet-wbt)
# Connect a wallet with Reown AppKit
Source: https://docs.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://docs.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.
* Optional: a Reown project id from [dashboard.reown.com](https://dashboard.reown.com). Only WalletConnect needs it. Without one the template connects injected browser wallets such as MetaMask, and everything else on this page works.
* 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
Pull the [`whitechain-dapp-nextjs`](https://github.com/whitechain-labs/templates/tree/main/whitechain-dapp-nextjs) folder on its own. `degit` downloads a tarball of that one directory and keeps no git history, so you start from a clean tree.
```bash Terminal theme={null}
npx degit whitechain-labs/templates/whitechain-dapp-nextjs dapp-nextjs
cd 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` | Optional. Your Reown project id, for WalletConnect. Blank falls back to injected wallets. |
| `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
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
* [`whitechain-dapp-nextjs` template on GitHub](https://github.com/whitechain-labs/templates/tree/main/whitechain-dapp-nextjs)
* [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://docs.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.
* Optional: a Reown project id from [dashboard.reown.com](https://dashboard.reown.com). Only WalletConnect needs it. Without one the template connects injected browser wallets such as MetaMask, and everything else on this page works.
* 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
Pull the [`whitechain-dapp-vite`](https://github.com/whitechain-labs/templates/tree/main/whitechain-dapp-vite) folder on its own. `degit` downloads a tarball of that one directory and keeps no git history, so you start from a clean tree.
```bash Terminal theme={null}
npx degit whitechain-labs/templates/whitechain-dapp-vite dapp-vite
cd 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` | Optional. Your Reown project id, for WalletConnect. Blank falls back to injected wallets. |
| `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
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
* [`whitechain-dapp-vite` template on GitHub](https://github.com/whitechain-labs/templates/tree/main/whitechain-dapp-vite)
* [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://docs.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://docs.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.
Ready-made template: [**`Foundry`**](https://github.com/whitechain-labs/templates/tree/main/Foundry) in the Whitechain templates repository. The fastest path below clones it and deploys. The companion Hardhat template is [**`Hardhat`**](https://github.com/whitechain-labs/templates/tree/main/Hardhat).
## Fastest path
The Foundry template is a finished project for Whitechain Sepolia: the RPC alias
is configured, `forge-std` is vendored so the folder builds with no `forge install`
step, and the contract, tests, and deploy script are in place. You need Foundry and
a funded key. The walkthrough after this section builds the same project by hand,
if you would rather see every step.
1. Get the `Foundry` folder.
```bash theme={null}
npx degit whitechain-labs/templates/Foundry whitechain-foundry
cd whitechain-foundry
```
2. Copy the environment file, then load it into your shell.
```bash theme={null}
cp .env.example .env
source .env
```
Set `PRIVATE_KEY` to a throwaway key funded from the [faucet](/learn/get-started/get-testnet-wbt). `WHITECHAIN_SEPOLIA_RPC_URL` already points at the public endpoint. `forge build` and `forge test` both run before you fill anything in.
3. Deploy.
```bash theme={null}
forge script script/DeployStorage.s.sol:DeployStorage \
--rpc-url whitechain_sepolia \
--private-key $PRIVATE_KEY \
--broadcast
```
`forge` prints the deployed address under `Contract Address`.
4. Verify, using that address.
```bash theme={null}
forge verify-contract src/Storage.sol:Storage \
--rpc-url whitechain_sepolia \
--verifier blockscout \
--verifier-url https://explorer.testnet.whitechain.io/api/
```
Source: [whitechain-labs/templates](https://github.com/whitechain-labs/templates/tree/main/Foundry).
## 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
* [`Foundry` template on GitHub](https://github.com/whitechain-labs/templates/tree/main/Foundry)
* [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://docs.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.
Ready-made template: [**`Hardhat`**](https://github.com/whitechain-labs/templates/tree/main/Hardhat) in the Whitechain templates repository. The fastest path below clones it and deploys. The companion Foundry template is [**`Foundry`**](https://github.com/whitechain-labs/templates/tree/main/Foundry).
## Fastest path
The Hardhat template is a finished project for Whitechain Sepolia: the network is
configured, the contract and tests are in place, and the deploy script is written.
You need Node.js 22 or later and a funded key. The walkthrough after this section
builds the same project by hand, if you would rather see every step.
1. Get the `Hardhat` folder and install its dependencies.
```bash theme={null}
npx degit whitechain-labs/templates/Hardhat whitechain-hardhat
cd whitechain-hardhat
npm install
```
2. Copy the environment file.
```bash theme={null}
cp .env.example .env
```
Set `PRIVATE_KEY` to a throwaway key funded from the [faucet](/learn/get-started/get-testnet-wbt). `TESTNET_RPC_URL` already points at the public endpoint. `npm run compile` and `npm test` both run before you fill anything in.
3. Deploy.
```bash theme={null}
npm run deploy:testnet
```
The script prints the deployed address and its explorer URL.
4. Verify, using the address from step 3.
```bash theme={null}
npx hardhat verify --network whitechainSepolia
```
The template registers the network as `whitechainSepolia` (chain ID `1874`) and points verification at the Whitechain Blockscout explorer. Source: [whitechain-labs/templates](https://github.com/whitechain-labs/templates/tree/main/Hardhat).
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: {
whitechainSepolia: {
type: "http",
url: "https://rpc.testnet.whitechain.io",
accounts: [process.env.PRIVATE_KEY!],
chainId: 1874,
},
},
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. `hardhat verify` submits to every verifier configured for the network, so the same source also lands on [Sourcify](https://sourcify.dev).
If you deployed the `Storage` example unchanged, the explorer may show its source before you verify anything. That is a verified twin: the same bytecode is already verified at another address, so the explorer matches it. Hardhat then skips Blockscout and verifies only on Sourcify, and your address stays unverified in its own right. Record it with `npx hardhat verify blockscout --force --network whitechainSepolia `. Your own contracts have unique bytecode, so they have no twin and the plain command covers both verifiers.
## Related
* [`Hardhat` template on GitHub](https://github.com/whitechain-labs/templates/tree/main/Hardhat)
* [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://docs.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://docs.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://docs.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 10 million clients. 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 10 million clients. 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.
## Builders Program
The Whitechain Foundation runs the Builders Program: financial, marketing, and technical support for projects building on Whitechain. The Foundation releases support against delivery milestones rather than as a lump sum. Projects that need more than the program covers are evaluated under the Investment Program, where terms are agreed per project. Details on the [Whitechain Builders Program](https://whitechain.io/builders).
## 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
* 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, Tron, and Whitechain. See [Portal bridge](/build/bridge/portal-bridge).
* **Block Explorer.** Production-grade Blockscout-based explorer. See [Block explorer](/build/block-explorer/overview).
These products work as integration surfaces for partner projects: 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 to the Builders Program** if your project qualifies. [Whitechain Builders Program](https://whitechain.io/builders).
3. **Reach out** via the [Discord community](https://discord.gg/7GwAXYmaW9) 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 Builders Program](https://whitechain.io/builders)
# Wallets
Source: https://docs.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).
## 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.
## 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.
## 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.
## Related
* [Connect to Whitechain Sepolia](/learn/get-started/connect-wallet)
* [Next dapp template](/build/dapps/dapp-with-nextjs)
# Frequently asked questions
Source: https://docs.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 the Builders Program.
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. All forward-looking roadmap schedules, Mainnet launch dates, and specifications are subject to change without prior notice.
## General
Whitechain is an EVM-compatible Layer 2 built on the OP Stack, developed in alignment with the WhiteBIT ecosystem. It settles on Ethereum through blob data availability and uses WBT (WhiteBIT Coin) as the native gas token. At launch, a single sequencer node is run by a designated infrastructure operator under the standard OP Stack consensus configuration. Sequencer management operates independently of user dApps, and the protocol roadmap targets progressive transition to permissionless, decentralized sequencing. 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. This technical design relies on underlying Ethereum mechanics; Whitechain provides no independent warranties regarding Ethereum network integrity. 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. Users and application operators are solely responsible for archiving historical L1 data prior to decommissioning; Whitechain assumes no liability for unexported data loss. 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 that inclusion is 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).
A designated infrastructure operator runs the single sequencer at launch strictly on an "AS IS" and "AS AVAILABLE" technical basis, without operational SLAs, uptime warranties, or liveness guarantees. Sequencer liveness does not compromise protocol security. In the event of sequencer downtime, latency, or transaction omission, the open-source OP Stack architecture allows users to bypass the sequencer and force transaction inclusion. That path runs directly through the L1 `OptimismPortal` smart contract on Ethereum. 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/7GwAXYmaW9). 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. Cost calculations and token price references are purely illustrative and non-binding; actual transaction costs fluctuate based on dynamic L1 and L2 market conditions. 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. Exchange purchases and withdrawals remain subject to WhiteBIT's Terms of Use, legal restrictions, and KYC/AML verification. 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 100, so a full block raises it by 5%, an empty block lowers it by 1%, and it never drops below the 5 gwei base fee floor. 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. Developers are solely responsible for conducting independent security audits and verifying regulatory compliance of their deployed code. 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. Third-party wallets are external tools operated by independent entities; Whitechain disclaims all liability for their software security or operational failures. 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, and there is no stream to read yet: Flashblocks are not enabled on Whitechain Sepolia. When they are, the stream is separate from the public RPC, which does not carry it. 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, following Ethereum block times. 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. Whitechain Mainnet is not live, so its figure is not published; on OP Mainnet the equivalent delay is a minimum of 7 days. Whichever network you are on, the dispute-resolution delay is enforced by the contracts and cannot be skipped, so plan treasury and time-sensitive flows accordingly. Deposits run the other way in about 2 minutes. Both figures move with L1 block times and congestion. 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).
Subject to technical deployment upon Mainnet launch and applicable exchange terms, yes. WhiteBIT users are expected to have the option to 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. Direct withdrawal functionality remains subject to WhiteBIT's Terms of Use, jurisdictional availability, and identity verification requirements. See [Getting WBT](/learn/general/get-wbt).
## For projects
The Whitechain Foundation runs the Builders Program with financial, marketing, and technical support for projects building on Whitechain. Support is released against delivery milestones rather than as a lump sum. Projects that need more than the program covers are evaluated under the Investment Program, where terms are agreed per project. Participation in the Builders Program is discretionary, non-binding, and subject to separate legal contracts, KYC screening, and sanctions compliance. See the [Whitechain Builders Program](https://whitechain.io/builders) 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://docs.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. Information provided herein is for informational purposes only and does not constitute financial, investment, or legal advice, nor a solicitation to purchase digital assets.
WBT is the native gas token on Whitechain L2 and an ERC-20 token on Ethereum mainnet. 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. 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. All exchange operations, fiat transactions, and withdrawals are governed exclusively by WhiteBIT's documents.
### 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. Fiat availability is subject to geographical restrictions, local banking regulations, and user identity verification.
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. A transfer depends on liquidity on the destination network and on relayer processing, so no execution time is guaranteed. 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). Always independently verify token contract addresses. Sending assets to incorrect contracts or unsupported networks will result in permanent loss.
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. Acquiring WBT via independent exchange venues (such as WhiteBIT) provides the standard path for acquiring gas assets. Third-party on-ramp integrations operate under their own jurisdictional licenses and compliance terms.
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.
Please note: third-party on-ramp providers operate independently. Whitechain assumes no liability for services, fees, or data processing by external payment processors.
## 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://docs.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. All migration timelines, protocol upgrade specifications, and launch schedules are forward-looking and subject to technical readiness and regulatory compliance. When migration happens, **you will not need to do anything**.
Whitechain runs as an EVM-compatible L1 maintained by a single authorized validator. 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. State migration is executed on a best-effort basis without warranties of uninterrupted availability or absolute immunity from data conversion discrepancies.
## 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 the balances it holds, under its own User Agreement and compliance terms. 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. Users, node operators, and application developers bear sole operational and legal responsibility for exporting and archiving historical L1 state data prior to permanent decommissioning. Protocol developers, software maintainers, and infrastructure contributors assume no custody duties or financial liability for unbacked-up data loss. |
| 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, subject to underlying Ethereum consensus and OP Stack framework performance. 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), one authorized validator | OP Stack sequencer, run by a designated infrastructure operator at launch, moving toward permissionless sequencing |
| 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. All sunset stages, domain cut-over schedules, and decommissioning dates remain subject to modification or emergency extension based on operational requirements.
| 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, subject to exchange Terms of Use and compliance checks |
| [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/7GwAXYmaW9) |
| 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, subject to WhiteBIT's policies and compliance terms. 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. Users assume sole responsibility for archiving transaction histories prior to decommissioning. 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. WBT remains a functional utility token for network gas settlement.
### 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://docs.whitechain.io/learn/general/what-is-whitechain
Whitechain is an EVM-compatible Layer 2 blockchain built on the OP Stack, developed in strategic ecosystem alignment with [WhiteBIT](https://whitebit.com). WhiteBIT is one of the largest centralized exchanges by traffic in Europe with more than 10 million clients. Those channels connect with a broader ecosystem audience of over 35 million users across W Group initiatives. 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.
Statements regarding Mainnet functionality and integrations are forward-looking, subject to technical readiness, and may change without prior notice.
As an OP Stack L2, Whitechain inherits Ethereum's security guarantees while reducing transaction costs and latency for both end users and developers. This technical architecture relies on underlying Ethereum consensus mechanics. Whitechain does not provide warranties or speak on behalf of Ethereum. It does not guarantee absolute immunity from smart contract, sequencer, or network-level failure risks. 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 are not enabled yet: the preconfirmation stream is prepared but not live, and the confirmation stages do not depend on it. 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, plus the L1 data fee. That is well under one cent based on a purely hypothetical reference price of \$50 per WBT. See [Network fees](/learn/network/network-fees). Fee estimates and token price references are illustrative only and subject to dynamic market and gas conditions.
* 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:
* **Ecosystem review pathway.** Projects deploying on Whitechain may apply for independent review by ecosystem partners, including WhiteBIT Platform. Any exchange listing, trading pair integration, or joint marketing remains subject strictly to independent corporate, legal, regulatory, and AML/KYC evaluation by the respective Platform operator. Protocol deployment provides no express or implied guarantee of exchange listing.
* **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. Economic references do not represent financial projections or investment advice.
* **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. Availability is subject to applicable jurisdictional restrictions and exchange compliance terms.
* **European roots, global ecosystem.** WhiteBIT is one of the largest centralized exchanges in Europe. Whitechain benefits from strong ecosystem synergy and brand recognition across the region, while operating as an open, global, and permissionless Layer 2 protocol.
For the detailed partnership offer, see [For project partners](/build/ecosystem/for-partners). The Whitechain Foundation also runs the [Builders Program](https://whitechain.io/builders): financial, marketing, and technical support released against delivery milestones. Grant distributions are strictly discretionary and subject to compliance approval and binding contractual terms.
## Important ecosystem notice
Whitechain is an independent Layer 2 software protocol. WhiteBIT is a separate, centralized digital asset exchange platform operating under its own legal terms and compliance framework. Mention of WhiteBIT platform features, WBT utility, or potential exchange integrations within this documentation serves technical reference purposes only. It does not imply joint corporate liability, legal agency, or partnership obligations between Whitechain protocol maintainers and the WhiteBIT exchange operator.
## 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/7GwAXYmaW9)
* [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://docs.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.
Whitechain Sepolia is a testnet environment provided "AS IS" without availability guarantees. Testnet WBT tokens have no real economic value.
## Before you connect
* An EVM wallet, for example, [MetaMask](https://metamask.io).
Third-party wallet applications are provided by independent software vendors. Whitechain does not endorse, control, or assume any liability for third-party wallets, and users assume full responsibility for their selection, security, and operation.
## 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. Always verify custom RPC URLs. Connecting to untrusted endpoints may expose your IP address or transaction metadata to third parties.
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`. Always confirm your wallet is switched to the testnet before executing non-production transactions.
## Related
* [Deploy a contract](/learn/get-started/deploy-a-contract)
* [Faucet](/learn/get-started/get-testnet-wbt)
# Deploy a contract
Source: https://docs.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.
Contract deployment is executed at your sole risk; Whitechain does not audit user-deployed contracts, guarantee execution accuracy, or assume liability for bugs or security vulnerabilities. Developers and users deploying contracts on Whitechain assume sole legal and operational responsibility for code auditing, smart contract security, and compliance with applicable financial, virtual asset, and AML laws in their respective jurisdictions.
## 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). Ensure your contract architecture and tokenomics comply with all applicable local financial, regulatory, and anti-money laundering laws prior to deployment.
## 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) |
Remix, Hardhat, and Foundry are independent third-party toolchains. Whitechain does not control, maintain, or guarantee the security or performance of these external software frameworks.
## 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://docs.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).
Test WBT tokens are non-economic testing units, not legal tender, financial instruments, or tradeable securities, and cannot be redeemed or exchanged for monetary value. Test WBT cannot be redeemed, sold, or converted. Faucet access is a discretionary debugging tool; access may be restricted or geoblocked at any time to comply with international sanctions regimes (including OFAC) and anti-abuse safeguards.
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). Faucet access and token delivery are provided "AS IS" without uptime guarantees.
## 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. Whitechain reserves the right to modify drip limits, adjust eligibility criteria, or deny requests to comply with anti-abuse and sanctions regulations.
## 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://docs.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.
All AI-generated code, prompt templates, and autonomous agent skills (including `whitechain-dev`) are provided strictly "AS IS" without warranties of code integrity, security, or fitness for purpose. Developers and operators of autonomous agents assume sole legal, financial, and operational liability for transaction signing, automated calldata execution, smart contract auditing, and local regulatory compliance. Whitechain protocol maintainers hold zero control over or liability for transactions signed by automated agents.
## Quick setup
Pick the method that fits your tool.
### Static docs files
| File | What it contains | When to use it |
| - | - | - |
| [llms.txt](https://docs.whitechain.io/llms.txt) | Page index with titles and descriptions | Discovering which docs exist before going deeper |
| [llms-full.txt](https://docs.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. Verify the underlying open-source licenses and security of any third-party skill package before execution.
## 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) (subject to applicable regulatory, sanctions, and compliance requirements) |
## 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." Ensure any prospective token deployment complies with local financial and virtual asset regulations.
* "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. Always inspect and manually confirm any transaction payload before signing.
## 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://docs.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://docs.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. All fee calculations, network parameters, and valuation estimates are provided "AS IS" for technical guidance and do not constitute a guaranteed price quote or financial warranty.
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. WBT is utilized solely as a functional utility token for network resource settlement. 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 the 5 gwei base fee floor. |
| 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, so each block adjusts it according to how full the previous block was. It never drops below the **5 gwei base fee floor**, a protocol parameter that stops zero-fee spam. Protocol parameters including fee floors remain subject to future network upgrades and governance updates. At the floor the base fee adds a fraction of a cent to a normal transfer. The floor is recorded in [Fee parameters](#fee-parameters) on this page, which is the reference the other pages cite.
## 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. Whitechain transaction finality, data availability (EIP-4844 blobs), and settlement depend entirely on underlying Ethereum L1 consensus mechanics. L1 data fees, batch submission cadence, and L1 finalization delays are dynamically dictated by external Ethereum network demand beyond the control of protocol maintainers. Protocol maintainers disclaim all liability for L1 network congestion, L1 block reorgs, or Ethereum consensus failures. 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 base fee floor. 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. All USD values and token price assumptions are purely illustrative, non-binding, and subject to real-time market volatility.
| 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 calculation column uses a hypothetical reference benchmark of \$50 per WBT strictly for mathematical formula demonstration. WBT trades dynamically on public markets, so check the [live WBT price](https://www.coingecko.com/en/coins/whitebit) before using these numbers for planning. Numerical fee examples are non-binding, static illustrations and shall not be interpreted as financial projections, market quotes, or token value guarantees.
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 or reverted transactions consume EVM execution gas and L1 blob storage capacity up to the point of execution failure. Network validators and sequencers expend real-time computational resources and pay Ethereum settlement fees regardless of transaction outcome. All gas fees consumed by failed or reverted transactions are therefore strictly non-refundable and unrecoverable under applicable technology and consumer standards. Set a sufficient gas limit to avoid running out partway through a contract call.
## Who receives the fee
The designated infrastructure sequencer node orders transactions, while the batcher component posts transaction data to Ethereum. The protocol collects transaction execution and data fees into automated on-chain fee vaults. The L1 data fee offsets the underlying L1 settlement costs incurred on Ethereum. Network fee structures, vault distributions, and operator fee parameters remain subject to protocol updates and operational governance.
## 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 | 100 | 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 100, a full block raises the base fee by 5% and an empty block lowers it by 1%. 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. Whitechain provides no warranties regarding the precision of client-side software estimates.
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://docs.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. All network reference data and infrastructure specifications are provided "AS IS" for technical guidance and are subject to modification without prior notice.
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. Statements regarding Mainnet deployment schedules and parameters are forward-looking and subject to technical readiness and regulatory compliance.
## 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 |
| Minimum base fee | 5 gwei, the floor the L2 base fee never drops below. Protocol fee floors are subject to future network upgrades and governance updates. See [Network fees](/learn/network/network-fees#fee-parameters) |
| 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/) |
| Bridge | [bridge.testnet.whitechain.io](https://bridge.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. Whitechain assumes no liability for losses, data interception, or phishing attacks resulting from interaction with unverified or third-party RPC endpoints.
### 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.
## Predeployed contracts
These contracts exist on Whitechain Sepolia at fixed addresses. They are part of the genesis state, so they exist at block 0 and nobody has to deploy them. Predeployed smart contracts are open-source components provided "AS IS" without warranties of fitness, security, or uninterrupted execution.
| Contract | Address | Purpose |
| - | - | - |
| Multicall3 | [`0xcA11bde05977b3631167028862bE2a173976CA11`](https://explorer.testnet.whitechain.io/address/0xcA11bde05977b3631167028862bE2a173976CA11) | Batches many read-only contract calls into one `eth_call` |
| `GasPriceOracle` | [`0x420000000000000000000000000000000000000F`](https://explorer.testnet.whitechain.io/address/0x420000000000000000000000000000000000000F) | Reads the L1 data fee for a transaction. See [Network fees](/learn/network/network-fees) |
Multicall3 sits at the same address it uses on Ethereum and on other OP Stack chains, so tooling that already knows the address needs no configuration.
### Using Multicall3
Multicall3 turns a screenful of separate reads into a single request, which matters here because the public RPC endpoint is rate limited per HTTP request. Twenty reads batched through Multicall3 cost one request against your 50 per second, not twenty.
viem and wagmi use it automatically once the chain entry carries the address. viem ships that entry, so importing the chain is enough:
```ts theme={null}
import { createPublicClient, http } from 'viem';
import { whitechainSepolia } from 'viem/chains';
const client = createPublicClient({ chain: whitechainSepolia, transport: http() });
// One eth_call to Multicall3, not three round trips.
const results = await client.multicall({
contracts: [
{ address: token, abi, functionName: 'name' },
{ address: token, abi, functionName: 'symbol' },
{ address: token, abi, functionName: 'decimals' },
],
});
```
In wagmi, `useReadContracts` batches through the same contract. If you define the chain by hand instead of importing it, include the address under `contracts.multicall3` or both calls fall back to one request per read:
```ts theme={null}
contracts: {
multicall3: { address: '0xcA11bde05977b3631167028862bE2a173976CA11', blockCreated: 0 },
},
```
Batching is for reads. Multicall3 cannot bundle transactions that need your signature, and every call in a batch shares one block, so results are consistent with each other.
## 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.
Whitechain is an open-source, permissionless Layer 2 software protocol. WBT functions strictly as a native Custom Gas Token and technical execution fuel required for EVM computational resource settlement and L1 data posting. WBT does not represent equity, debt, an investment contract, or a regulated financial instrument in any jurisdiction.
## Related
* [Public RPC endpoints and rate limits](#public-rpc-endpoints)
* [Whitechain Sepolia full parameters](/learn/network/testnet)
* [Network artifacts](/operate/run-a-node/network-artifacts)
* [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://docs.whitechain.io/learn/network/testnet
Network reference for the Whitechain Sepolia testnet: how to connect, public RPC limits, fees, and withdrawal timing. This reference is provided for testing and evaluation purposes only. Testnet parameters and endpoints are offered on an "AS IS" and "AS AVAILABLE" basis without uptime SLAs or warranties.
## 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). Testnet tokens carry no economic value |
| L2 block time | 1s |
| Flashblocks | Not enabled |
Flashblocks are not enabled on this network. The rollout is prepared and no date is set, so no interval is published. Once enabled, they 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. Forward-looking features and deployment schedules remain subject to modification or cancellation without prior notice. See [Flashblocks in Transaction finality](/learn/network/transaction-finality#flashblocks) for the mechanism and how the three 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) |
| Bridge | [bridge.testnet.whitechain.io](https://bridge.testnet.whitechain.io) |
To check current uptime for these endpoints, see the [status page](https://status.whitechain.io/). Whitechain does not guarantee unbroken service availability, response rates, or uninterrupted operation of public infrastructure.
## 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. Gas mechanics aim for fee stability, but execution costs remain subject to variable L1/L2 network demand and gas price fluctuations.
| Parameter | Value |
| - | - |
| `baseFeeScalar` | 100000 |
| `blobBaseFeeScalar` | 1000000 |
| `operatorFeeScalar` | 0 |
| `operatorFeeConstant` | 0 |
| `gasLimit` | 40000000 |
| `minimumGasLimit` | 21000000 |
| `maximumGasLimit` | 500000000 |
| `eip1559Elasticity` | 6 |
| `eip1559Denominator` | 100. `eip1559DenominatorCanyon` is 250, but never applies here because Holocene is active from genesis |
| `minBaseFee` | 5000000000 wei, that is 5 gwei. The L2 base fee never drops below it |
## 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. Whitechain Mainnet is not live, so its figure is not published; on OP Mainnet a withdrawal takes a minimum of 7 days. The spread between one hour and three comes from L1 block finality and from how long dispute resolution takes. 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://docs.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. All operational timings, confirmation phases, and roadmap values are provided "AS IS" for technical reference and do not constitute performance guarantees or SLAs.
A transaction passes through three states: soft confirmation, safe, and finalized. Only finalization makes it irreversible under standard protocol execution.
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 |
| - | - | - |
| 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 one-second and 13-minute figures are protocol timings. Block production runs at one second while the sequencer is live, and the 13-minute figure is Ethereum's finality interval, so it follows L1. The safe delay depends on how often batches are posted, up to about 30 minutes on Testnet. A soft confirmation is the weakest of the three stages: it carries the sequencer's word and nothing more.
## 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. Whitechain assumes no liability for losses arising from reliance on unbatched soft confirmations.
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. Finality guarantees depend strictly on underlying Ethereum consensus mechanics.
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
Flashblocks are not enabled on Whitechain Sepolia. The rollout is prepared and staged per sequencer, and no date is set. Flashblocks rollout schedules and preconfirmation features are forward-looking and subject to technical modification or delays.
Once enabled, the sequencer streams partial blocks ahead of each full one-second L2 block, powered by [Flashbots](https://www.flashbots.net) rollup-boost, which runs a block builder alongside the sequencer. Each partial block is a preconfirmation: it says your transaction is included and ordered. It carries the same trust as a soft confirmation, the sequencer's word, and is not safe or finalized until its batch reaches Ethereum. The interval is published here once it is measured on a live stream.
The stream is separate from the public RPC and stays that way, so there is no Flashblocks endpoint to connect to today; see [what a node does not provide](/operate/run-a-node/overview#what-this-node-does-not-provide). Build against the three confirmation stages above. The rollout does not change them.
## 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. Whitechain Mainnet is not live, so its dispute period is not published. On OP Mainnet the equivalent period is a minimum of 7 days, set in the L1 contracts. The dispute period is enforced by those contracts and cannot be shortened, waived, or overridden by the operator or any third party. Deposits run the other way in about 2 minutes. Both figures move with Ethereum block times and L1 congestion. See the [canonical bridge guide](/build/bridge/bridge-assets#timing-parameters) for the steps and exact timing parameters.
## What keeps your funds safe
A designated infrastructure operator runs 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. Protocol mechanics do not guarantee absolute protection against unforeseen smart contract vulnerabilities or zero-day exploits.
### 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 the 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. Users acknowledge that emergency Guardian interventions, permissioned roles, or protocol pauses may restrict or halt transaction execution and withdrawals without prior notice.
### 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)
# How the migration works
Source: https://docs.whitechain.io/operate/migration/how-migration-works
The freeze and dump procedure that turns the legacy Whitechain L1 state into the L2 Mainnet genesis: the phases, the three allocations, and the tooling that performs each step.
The legacy Whitechain L1 does not stop and restart empty. Its full state is exported at a chosen block and written into the genesis block of the L2. Every account, contract and storage slot then exists on the L2 from block 0, at the same address. This page describes how that is done and which tool performs each step.
Migration applies to Mainnet only, and it has not happened yet. The legacy L1 mainnet, chain ID `1875`, is live and producing blocks. Whitechain Sepolia started from its own genesis and carries no legacy state. If you are a user or an integrator asking what you have to do, read [Migration to L2](/learn/general/migration) instead: this page is the mechanics, not the checklist.
## The strategy
The migration is a freeze and dump. L1 block production stops at a snapshot block. The full state trie at that block is exported, the export is transformed into an OP Stack genesis allocation, and the L2 boots from it.
| Carried into the L2 genesis | Not carried |
| - | - |
| Native WBT balances, to the wei | Transaction history |
| Contract bytecode | Event logs and receipts |
| Contract storage, so ERC-20 and NFT balances come with it | Pending mempool transactions |
| Account nonces | Block headers of the legacy chain |
| Addresses, unchanged | |
Final state migrates. History does not, because the L2 starts a new chain whose block 0 holds that state rather than replaying the blocks that produced it.
## The three allocations
Three files in this procedure are all called an allocation at some point, and confusing them is the most common way to produce a chain that will not boot. They are kept in separate files with distinct names.
| File | What it is | What it is not |
| - | - | - |
| `genesis.base.json` | Output of `op-deployer inspect genesis`. Contains the OP Stack predeploys, the preinstalls, and the pre-funded allocations for the new chain. | Bootable as the migrated chain. It holds none of the legacy state. |
| `state_dump.json` | Output of `geth dump` at the snapshot block. JSON Lines: a header line carrying the state root, then one account per line. | Readable by `op-node`. The format is different in every field. |
| `alloc.json` | The two above merged into a single OP Stack allocation map. | A genesis file. It is an allocation map, and `op-node` turns it into one. |
The final `genesis.json` and `rollup.json` are emitted together by one `op-node genesis l2` run. They are a pair: `op-node` recomputes the L2 block 0 hash from the allocation and writes it into `rollup.json`, so a `rollup.json` from an earlier run paired with a later `genesis.json` fails at boot with a genesis hash mismatch.
## Why the merge step needs its own tool
`op-node genesis l2 --l2-allocs` reads its input as an OP Stack allocations document. Each key is an address and each value is that account, with `balance` and `nonce` as hex quantities, `code` as `0x` prefixed bytes, and every storage key and value a full 32 byte `0x` prefixed hash. It also replaces the allocation rather than merging into it, so the predeploys have to be inside the file it is given.
A `geth dump` satisfies neither requirement.
| Field | `geth dump` produces | `op-node` expects |
| - | - | - |
| Document shape | JSON Lines, one account per line, plus a state root header line | A single JSON object mapping address to account |
| `balance` | Decimal string | Hex quantity |
| `nonce` | JSON number | Hex quantity |
| `storage` keys and values | Unpadded hex, no `0x` prefix | Full 32 bytes, `0x` prefixed |
| Predeploys | Absent | Required to be present |
Upstream OP Stack tooling ships no converter for this. `scripts/dump-to-alloc.mjs` in the migration repository does exactly those two jobs, the format conversion and the merge, in a single streaming pass. Only the base allocation is held in memory, so peak memory does not grow with the size of the dump. Everything before and after that step is stock `op-deployer` and `op-node`.
## Address collisions
An address defined by both the base allocation and the legacy state dump is a collision, and the dump account wins. The one exception is a dump account that is itself empty and gets pruned, which leaves the base entry standing. Two groups are exposed, and they carry different risk.
| Group | Addresses | Why it happens | Risk |
| - | - | - | - |
| OP Stack predeploys | `0x42000000000000000000000000000000000000xx` and `0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000` | Nobody holds a key for these, but anyone can send WBT to one on the legacy L1, which creates a codeless account that the dump then carries over | High. The predeploy loses its code and storage |
| Preinstalls at ordinary addresses | Multicall3, Create2Deployer, DeterministicDeploymentProxy, Permit2, the Safe singletons, the ERC-4337 EntryPoints, CreateX | These deploy at the same address on every EVM chain, so a collision is expected rather than exceptional | Low, if the bytecode is identical on both sides. Compare it |
`dump-to-alloc.mjs` reports every collision on stderr as it happens, repeats the full list at the end of the run, and states explicitly when there were none. The authoritative list of addresses at risk for a given deployment is the base allocation itself, read with `jq -r '.alloc | keys[]' genesis.base.json`.
Empty accounts, meaning zero balance, zero nonce, no code and no storage, are pruned by default, so account counts between the dump and the allocation differ by that number. `--no-prune-empty` keeps them.
## The phases
The L1 is read only from the moment production stops until the L2 is serving, so everything in phase 2 is time boxed.
| Phase | Steps | Reversible |
| - | - | - |
| Before migration | Confirm readiness, deploy the L1 contracts with `op-deployer apply`, generate the base genesis artifacts with `op-deployer inspect` | Yes |
| Migration | Freeze the L1 at the snapshot block, dump the state, build the merged allocation, regenerate `genesis.json` and `rollup.json`, generate the challenger prestates, boot the L2 | No, from the freeze onward |
| After migration | Health checks, state reconciliation, functional QA, sign off | Verification only |
What each step produces:
| Step | Command | Output |
| - | - | - |
| Deploy L1 contracts | `op-deployer apply` | `state.json`, the record of every deployed L1 address |
| Base artifacts | `op-deployer inspect genesis`, `rollup`, `deploy-config`, `l1` | The base genesis, `rollup.json`, the deploy config, `l1-addresses.json`. The runbook writes the base genesis as `genesis.json` in the deployer workdir; this page calls it `genesis.base.json` to keep it apart from the final one |
| Freeze | Stop the block producer | The snapshot block number, hash and state root, recorded |
| Dump | `geth dump ` with `--cache.preimages` | `state_dump.json`, validated against the snapshot block state root |
| Merge | `dump-to-alloc.mjs` | `alloc.json`, one entry per line |
| Regenerate | `op-node genesis l2 --l2-allocs` | Final `genesis.json` and `rollup.json`, as a pair |
| Prestates | `make reproducible-prestate` in the Optimism monorepo | The Cannon64 absolute prestate, which must match what is registered on L1 for the proposer game type |
| Boot | Start the stack on empty volumes | A sequencer whose block 0 hash equals `genesis.l2.hash` in `rollup.json` |
Two details decide whether the boot succeeds. The L1 node that is dumped must be a full, non pruned node synced with `--cache.preimages`, stopped gracefully so the trie cache is flushed, and the dump header state root must equal the snapshot block state root. The execution client volumes must be empty before the first start, because booting on top of an old database is the most common cause of a genesis hash mismatch.
## Where the tooling lives
| Repository | Contents |
| - | - |
| [whitechain-labs/whitechain-state-migration](https://github.com/whitechain-labs/whitechain-state-migration) | The runbooks, `dump-to-alloc.mjs`, and the reconciliation tooling used afterwards |
| [whitechain-labs/whitechain-bootstrap](https://github.com/whitechain-labs/whitechain-bootstrap) | The published genesis, rollup config and deployment record for each network. See [Network artifacts](/operate/run-a-node/network-artifacts) |
The migration repository carries two runbooks. The default one builds the genesis with `op-deployer`, `dump-to-alloc.mjs` and `op-node genesis l2`, and is used for any migration that carries real L1 state. The second uses upstream OP Stack tooling only, and applies when the allocation is small and hand authored rather than dumped from a chain.
## Related
* [Migration to L2](/learn/general/migration)
* [Verify the migration](/operate/migration/verify-the-migration)
* [Network artifacts](/operate/run-a-node/network-artifacts)
* [Node operators](/operate/run-a-node/overview)
* [Network reference](/learn/network/reference)
# Verify the migration
Source: https://docs.whitechain.io/operate/migration/verify-the-migration
Check the migrated state yourself: reconcile every account, token and NFT between the legacy Whitechain L1 at the snapshot block and the L2, using the published reconciliation tooling.
The migration is checkable by anyone, not only by the team that runs it. Every account that existed on the legacy L1 at the snapshot block has a counterpart on the L2, and the comparison is a matter of reading both chains over JSON-RPC and diffing the result. The tooling that does it is published, so you can reproduce the check rather than take the outcome on trust.
Migration applies to Mainnet only and has not happened yet, so there is nothing to reconcile today. This page describes the procedure and the tooling. Whitechain Sepolia started from its own genesis, so it carries no legacy state.
## What you need
| Requirement | Why |
| - | - |
| A legacy L1 archive RPC that still serves state at the snapshot block, with `debug_accountRange` and `eth_getProof` enabled | The account walk reads L1 state as it stood when production stopped |
| An L2 RPC with `eth_getProof` enabled | Code hashes and storage hashes are compared through proofs on both sides. `eth_getProof` is in the standard `eth` namespace, so any RPC serves it. Only the L1 side needs `debug` |
| The snapshot block, as a hex tag | The last legacy L1 block. Published with the migration record |
| A pinned L2 block | Pin it, or a moving head produces false mismatches |
| A contract inventory | The tokens, NFTs and contracts you want reconciled by name |
```bash theme={null}
export L1_RPC=
export L2_RPC=
export L1_BLOCK=
export L2_BLOCK=
```
Verification depends on a legacy L1 archive endpoint that answers at the snapshot block. That endpoint goes away at L1 decommissioning. Run the checks you care about, and export what you need, while the legacy read only endpoints are still online. See [L1 sunset stages](/learn/general/migration#l1-sunset-stages).
## The layers
Each layer gates the next. A failure at a lower number invalidates everything above it.
| Layer | Tool | What it establishes |
| - | - | - |
| 0 | `cast`, plain JSON-RPC calls | The chain is alive and is the chain you think it is |
| 1 | `staterecon` | Every account matches on balance, nonce, code hash and storage hash |
| 2 | `tokenrecon` | Every token and NFT matches on view function returns and raw storage slots |
| 3 | `soul-verification` | The SoulDrop and Soul registry state matches |
A fourth tool, `tokensync`, ships alongside these. It backfills token metadata into the explorer database from the legacy explorer API. It changes explorer data rather than chain state, so it is a migration step, not a verification step, and a clean `tokensync` run proves nothing about the chain.
The published runbook, `docs/post-migration-verification.md`, numbers its layers differently: it carries `tokensync` as layer 4 and adds functional QA as layer 5. Functional QA is the acceptance testing that follows reconciliation, not a check on the migrated state, so it sits in the [migration phases](/operate/migration/how-migration-works#the-phases) rather than here.
## Layer 0: chain sanity
Run this first. It is quick and it fails loudly.
```bash theme={null}
cast chain-id --rpc-url "$L2_RPC"
```
```bash theme={null}
curl -s -X POST "$L2_RPC" -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
```
Confirm that the chain ID is the published L2 value, that the block number advances at the configured block time, and that a test transaction is charged in WBT.
## Layer 1: account reconciliation
`staterecon` walks every account on the legacy L1 with `debug_accountRange`, page by page, and compares each one against the L2. Results stream to CSV as it goes, so it holds no full copy of the state in memory.
```bash theme={null}
cd staterecon
go build -o staterecon ./cmd/staterecon
```
```bash theme={null}
./staterecon \
--l1-rpc "$L1_RPC" \
--l2-rpc "$L2_RPC" \
--l1-block "$L1_BLOCK" \
--l2-block "$L2_BLOCK" \
--output-dir ./out \
--print-mismatches
```
Compared per account:
| Field | Read from L1 with | Read from L2 with |
| - | - | - |
| `balance` | `debug_accountRange` | `eth_getBalance` |
| `nonce` | `debug_accountRange` | `eth_getTransactionCount` |
| `code_hash` | `eth_getProof` | `eth_getProof` |
| `storage_hash` | `eth_getProof` | `eth_getProof` |
Code hash and storage hash are compared for contract accounts only. The CSV carries `address`, `status`, `field`, `l1_value`, `l2_value` and `error` per row.
A clean run has zero rows with `status=mismatch` and zero rows with `status=error`. An `error` row is not a pass: it means the comparison did not happen, usually an RPC timeout, and it has to be rerun rather than waved through. Resume an interrupted walk with `--account-range-start`, and tune throughput with `--page-size`, `--batch-size`, `--rpc-batch-size` and `--rpc-timeout`.
Two expected differences are not findings. The OP Stack predeploys exist only on the L2 and have no L1 counterpart. Empty accounts pruned while the allocation was built are absent on the L2 by design, so the account count differs by that number.
## Layer 2: token and NFT reconciliation
`tokenrecon` calls view functions on both chains for the contracts you list, compares the raw return data, and additionally compares raw storage slots with `eth_getStorageAt`.
```bash theme={null}
cd staterecon
go build -o tokenrecon ./cmd/tokenrecon
cp config/config.example.yml config/config.yml
```
Fill in the config with the contracts you want checked:
```yaml config/config.yml theme={null}
config:
erc20:
- name: USDC
address: 0x
addresses:
- 0x
storages:
- 0x
rules:
- general
- balance
- roles
- codehash
- storagehash
```
```bash theme={null}
./tokenrecon \
--l1-rpc "$L1_RPC" \
--l2-rpc "$L2_RPC" \
--l1-block "$L1_BLOCK" \
--l2-block "$L2_BLOCK" \
--config config/config.yml \
--output-dir ./token_out
```
What each rule covers:
| Rule | Compares |
| - | - |
| `general` | Name, symbol, decimals, version, currency, `totalSupply`, `DOMAIN_SEPARATOR` and the authorization typehashes |
| `balance` | `balanceOf` and `nonces` for each listed holder |
| `roles` | Owner, blacklister, master minter, pauser, rescuer |
| `codehash` | The `codeHash` returned by `eth_getProof` |
| `storagehash` | The `storageHash` returned by `eth_getProof` |
A clean run has zero `mismatch` and zero `error` rows, `totalSupply` equal on both chains for every token, and matching roles and owners. The check is only as complete as the inventory you give it, so a token absent from the config is a token nobody verified.
## Layer 3: SoulDrop and Soul registries
`soul-verification` is a set of RPC tests that diff the pre-migration deployment against the post-migration one, including storage layout checks, which is why it needs the contract sources compiled first.
```bash theme={null}
cd soul-verification
npm run compile
cp .env.example .env
```
```bash theme={null}
npx hardhat test test/migrationStateDiff.rpc.ts
```
Pin a block number on both sides so a moving head cannot produce a false diff. For a fast signal on a subset rather than the full run, use the sample variant:
```bash theme={null}
MIGRATION_SAMPLE_SOUL_IDS=1,42,100 npx hardhat test test/migrationStateDiffSample.rpc.ts
```
## What counts as verified
| Check | Pass condition |
| - | - |
| Chain sanity | Chain ID matches the published value, blocks advance, fees are charged in WBT |
| Accounts | Zero `mismatch` rows and zero `error` rows from `staterecon` |
| Tokens and NFTs | Zero `mismatch` rows and zero `error` rows from `tokenrecon`, and `totalSupply` equal on both chains |
| Registries | The full migration state diff passes with storage layout checks enabled |
| Record | Snapshot block number, hash and state root, the dump and allocation checksums, the final `genesis.json` and `rollup.json` checksums, the L2 block 0 hash and the prestate hash, all archived |
The last row is what makes the rest reproducible later. The checksums of the published artifacts are verifiable independently at any time: see [Network artifacts](/operate/run-a-node/network-artifacts).
## Where the tooling lives
Both tools are in [whitechain-labs/whitechain-state-migration](https://github.com/whitechain-labs/whitechain-state-migration), under `staterecon/`, with the full verification runbook in `docs/post-migration-verification.md`. Building them needs Go 1.25 or later. `soul-verification` needs Node.js and the SoulDrop contract sources.
## Related
* [How the migration works](/operate/migration/how-migration-works)
* [Migration to L2](/learn/general/migration)
* [Network artifacts](/operate/run-a-node/network-artifacts)
* [Node operators](/operate/run-a-node/overview)
# Node configuration
Source: https://docs.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 |
| - | - | - |
| `HTTP_API` | `eth,net,web3,rpc` | op-reth HTTP (`8545`) RPC namespaces, the same variable for all three profiles. Comma-separated, no spaces. See [RPC namespaces](#rpc-namespaces). |
| `WS_API` | `eth,net,web3,rpc` | op-reth WebSocket (`8546`) RPC namespaces, the same variable for all three profiles. Same rules as `HTTP_API`. |
| `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:v2.3.3` | Override the pinned op-reth image. |
| `OP_NODE_IMAGE` | `op-node:v1.19.3` | Override the pinned op-node image. |
Both example `.env` files set `HTTP_API` and `WS_API` explicitly to the read-only default, so the value you run is visible in your own configuration rather than inherited from the Compose file.
## RPC namespaces
`HTTP_API` and `WS_API` drive every profile, `archive-node` included, so an archive node serves the same read-only set as the pruned profiles unless you widen it deliberately. The two ports are configured independently: widening `HTTP_API` does not change WebSocket, and the reverse.
To enable tracing on an archive node, widen the HTTP set in `.env` and restart the profile:
```bash theme={null}
HTTP_API=eth,net,web3,rpc,debug,trace,txpool,reth
WS_API=eth,net,web3,rpc
```
```bash theme={null}
make reup PROFILE=archive-node
```
Add the wide set to `WS_API` only if you also need those methods over WebSocket. The wider set is pointless on the pruned profiles, which do not hold the history these methods read.
The stack ships no authentication, CORS, or vhost restriction on `8545` and `8546`, and `RPC_MAX_TRACING_REQUESTS` bounds how many tracing calls run at once, not what one call costs. A single `trace_block` or `debug_traceTransaction` on a heavy block costs seconds of CPU and gigabytes of RAM. Enable `debug`, `trace`, `txpool`, and `reth` only behind a reverse proxy that allowlists methods and rate-limits clients, never on a port open to untrusted clients.
## 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` |
`RPC_MAX_TRACING_REQUESTS` only matters once `debug` or `trace` is enabled through `HTTP_API` or `WS_API`.
## 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 profile's own 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 mapped only for `full-snap-node`, on all interfaces (`0.0.0.0`), TCP and UDP. That profile bootstraps over EL P2P: `op-reth` snap-syncs the state from the trusted reth peer and uses reth discovery (UDP `30303`) and devp2p (TCP `30303`) for it, so the port accepts inbound EL peers as well. It carries devp2p traffic only, no RPC and no `admin` surface. If you do not want inbound EL peering, remap it with `HOST_EL_P2P_PORT` or block it at the firewall. For `full-node` and `archive-node` the mapping is commented out in `docker-compose.yml` and the port is not published; 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:
```text theme={null}
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/
keys/
full-snap-node/jwt.txt
full-node/jwt.txt
archive-node/jwt.txt
```
Each profile also owns its Engine API secret in `keys//jwt.txt` and its own Compose network (`public_rpc_full_snap`, `public_rpc_full`, `public_rpc_archive`), so two profiles share neither a credential nor a network path. `make up` generates the secret for the selected profile if it is missing.
## 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://docs.whitechain.io/operate/run-a-node/maintenance
Operate, back up, and update a Whitechain RPC node: make targets, datadir backup and restore, the upgrade and hardfork workflow, and security notes.
Day-to-day operations for a running Whitechain RPC node: start and stop commands, datadir backups, 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.
## Back up and restore the datadir
There are no published `op-reth` database snapshots. `full-snap-node` bootstraps from its trusted reth peer, and `full-node` and `archive-node` sync by re-executing the chain from L1.
What you can keep is your own backup of a `full-node` or `archive-node` datadir, so a disk failure or a corrupted database does not cost you a full resync from genesis. Back up only the `op-reth` data; the `op-node` directory (`peerstore`, `discovery`, `safedb`) 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
```
To restore: stop the profile, replace `data//op-reth` with the extracted archive, and start the profile again. The node derives everything after the backup point from L1 and catches up the unsafe head over P2P, so it still needs a working L1 RPC and Beacon.
A backup only fits the profile it was taken from: a pruned (`full-node`) datadir cannot serve archive queries. Keep the network and the profile in the archive name.
## Update the node
`op-reth` and `op-node` image versions are pinned in `docker-compose.yml`, and `OP_RETH_IMAGE` and `OP_NODE_IMAGE` in `.env` override the pin. 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, pull the new `rollup.json` (and `genesis.json` if it changed) from [whitechain-bootstrap](https://github.com/whitechain-labs/whitechain-bootstrap), check it against the SHA-256 hashes published there, copy it into `artifacts//`, and only then run `make reup`. The published hashes change whenever a hardfork changes the artifacts, so verify again on every such update. Apply the new artifacts before the activation timestamp to avoid a chain-divergence stall. See [Network artifacts](/operate/run-a-node/network-artifacts).
## 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. On `full-node` and `archive-node` the next start re-executes the chain from genesis, so do not run this against a production node without a maintenance window. Restoring your own backup is the cheaper recovery when you have one.
## Security
* The Engine API on `8551` stays on the profile's own Compose network (`public_rpc_full_snap`, `public_rpc_full`, or `public_rpc_archive`) 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, by default `eth`, `net`, `web3`, and `rpc` on every profile, `archive-node` included. `debug`, `trace`, `txpool`, and `reth` are off unless you add them to `HTTP_API` or `WS_API`. They are read-only too, but expensive enough to be a denial-of-service vector, so put a method-allowlisting, rate-limiting proxy in front before enabling them. See [RPC namespaces](/operate/run-a-node/configuration#rpc-namespaces).
* On `full-snap-node` the EL P2P port `30303` is published on all interfaces, TCP and UDP, because `op-reth` needs it to snap-sync and to peer over devp2p. It carries no RPC and no administrative methods. `full-node` and `archive-node` do not publish it.
* `keys//jwt.txt` is generated locally and used only between the `op-node` and `op-reth` of that profile. Each profile has its own secret and its own Compose network, so one profile's Engine API credential never grants access to another's. It does not need to match anything outside. If you upgraded from a version that used a single `keys/jwt.txt`, the next `make up` generates the per-profile secret and recreates both containers with it, and the old file can be deleted.
* 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)
* [Network artifacts](/operate/run-a-node/network-artifacts)
* [Node configuration](/operate/run-a-node/configuration)
* [Troubleshooting](/operate/run-a-node/troubleshooting)
# Network artifacts
Source: https://docs.whitechain.io/operate/run-a-node/network-artifacts
Download, verify, and regenerate the Whitechain genesis, rollup config, and L1 contract addresses that a node needs to start from block 0.
Every Whitechain node starts from a fixed set of files: the L2 execution genesis, the rollup config, and the record of what was deployed on L1. They are published in the [whitechain-bootstrap repository](https://github.com/whitechain-labs/whitechain-bootstrap), one directory per network, and this page explains what each file is, how to check that the copy you downloaded is the real one, and how to regenerate the derived files yourself.
Whitechain Sepolia artifacts are published today. Mainnet artifacts are published at Mainnet launch.
## Networks and directories
| Network | Directory | L2 chain ID | Settlement layer | Status |
| - | - | - | - | - |
| Whitechain Sepolia | `testnet/` | `1874` | Ethereum Sepolia (`11155111`) | Published |
| Mainnet | `mainnet/` | Published before Mainnet launch | Ethereum mainnet | Not yet published |
Each network directory is self-contained. Never mix files across networks: a genesis from one network does not match the rollup config of another, and a node started on a mismatched pair stalls instead of syncing.
## What each file is
| File | What it is | Who consumes it |
| - | - | - |
| `genesis.json` | L2 execution-layer genesis. Builds the block 0 state, including every OP Stack predeploy and the pre-funded allocations. | The execution client: `op-geth init`, or `op-reth --chain` |
| `rollup.json` | L2 consensus-layer (rollup) config. Defines the L1 origin block, protocol activation times, sequencing window, and the L1 system contract addresses. | `op-node` |
| `l1-addresses.json` | Every L1 contract deployed for the chain: portal, bridges, messenger, `SystemConfig`, dispute game factory, and all implementation contracts behind the proxies. | Humans and tooling verifying the deployment on Ethereum |
| `deploy-config.json` | The resolved OP Stack deploy config: the full parameter set the deployment was applied with, after defaults and overrides were merged. Derived from `state.json`, published so it can be read and diffed without running `op-deployer`. | Humans and tooling verifying the chain configuration |
| `intent.toml` | The deployment intent handed to `op-deployer`: chain parameters, role addresses, fee vault recipients, and the custom gas token. The human-authored source of truth for how the chain was configured. | `op-deployer apply` |
| `state.json` | The `op-deployer` output state: the applied intent plus every deployed contract address. `genesis.json`, `rollup.json`, and `deploy-config.json` are all derivable from it. | `op-deployer inspect` |
Only `genesis.json` and `rollup.json` are read by a running node. The other four exist so that anyone can check the chain configuration and the L1 deployment without asking Whitechain for anything.
## Download the artifacts
```bash theme={null}
git clone https://github.com/whitechain-labs/whitechain-bootstrap.git
```
To run a node you need `genesis.json` and `rollup.json` for your network. Copy them into the node stack under `artifacts//`, a path relative to the root of the [whitechain-labs/node repository](https://github.com/whitechain-labs/node), where the directory name matches `WHITECHAIN_NETWORK` in your `.env`:
```text theme={null}
artifacts/testnet/genesis.json
artifacts/testnet/rollup.json
```
The `public-rpc-node` stack ships no copy of these files, and no checksum file of its own, so `whitechain-bootstrap` is the single source of truth for both the artifacts and their hashes. Download the pair for your network, compare the SHA-256 against the hashes below before you start a node, and download them again whenever a hardfork changes them.
## Verify what you downloaded
Compare the SHA-256 checksums of your copies against the published values:
```bash theme={null}
shasum -a 256 testnet/*
```
| File | SHA-256 |
| - | - |
| `testnet/deploy-config.json` | `1aa1680515f0118dec543342a6a6689c553b7e2e576a3014eb3975af8f738a40` |
| `testnet/genesis.json` | `7097a49f0ac0a7a9dc36a68c8e401018d0238a15730b08a2d1874efd2d422cf5` |
| `testnet/intent.toml` | `c48a33129a238f97af788dc9805894cd7761a58c20ac108d2a1daf5315eeaef7` |
| `testnet/l1-addresses.json` | `0b84cc7b4f818846c40874cd9c0c3afc964978dfaa9e312959a8069997b405c3` |
| `testnet/rollup.json` | `722deb00df45e276cff35d66c40d15cbbb3c5e42f1086077ad6beb55b8bb16b5` |
| `testnet/state.json` | `25aed7f30c4f8807a20a56a5202766e84c076bfb90924ef1a9bb0052adbfb793` |
These values cover the current Whitechain Sepolia set. They change when a hardfork changes the artifacts, and the repository is the authoritative copy of both the files and their checksums.
## Artifact validation
`make check-env`, a prerequisite of `make up` in the node stack, does more than check that the two files exist. It cross-checks `artifacts//genesis.json` against `artifacts//rollup.json` with `jq` and refuses to start the node on any of these:
| Check | Fails when |
| - | - |
| `genesis.config.chainId` equals `rollup.l2_chain_id` | The two files describe different chains, for example a `genesis.json` from one network next to a `rollup.json` from another |
| `genesis.timestamp` equals `rollup.genesis.l2_time` | The files come from different deployments of the same chain ID |
| `rollup.l1_chain_id` matches the network | The pair belongs to the other network. `mainnet` must settle on Ethereum mainnet (`1`), `testnet` on Ethereum Sepolia (`11155111`) |
These are consistency checks on your configuration, not authenticity checks. A pair of files tampered with consistently passes all three. Authenticity comes from the source: take the files from `whitechain-bootstrap` and compare their SHA-256 against the hashes above.
## Regenerate the derived files
`genesis.json`, `rollup.json`, `l1-addresses.json`, and `deploy-config.json` are not hand-written. They are derived from `state.json`, so you can rebuild them with `op-deployer` (v0.6.0 or later) and confirm that the committed files match what the deployer produces:
```bash theme={null}
op-deployer inspect genesis --workdir ./testnet 1874 > /tmp/genesis.json
op-deployer inspect rollup --workdir ./testnet 1874 > /tmp/rollup.json
op-deployer inspect deploy-config --workdir ./testnet 1874 > /tmp/deploy-config.json
op-deployer inspect l1 --workdir ./testnet 1874 > /tmp/l1-addresses.json
```
```bash theme={null}
diff <(jq -S . testnet/genesis.json) <(jq -S . /tmp/genesis.json)
diff <(jq -S . testnet/rollup.json) <(jq -S . /tmp/rollup.json)
diff <(jq -S . testnet/deploy-config.json) <(jq -S . /tmp/deploy-config.json)
diff <(jq -S . testnet/l1-addresses.json) <(jq -S . /tmp/l1-addresses.json)
```
Every `diff` prints nothing when the files match. Any output means your copy differs from what the recorded deployment produces, and you should not start a node from it.
## Chain parameters recorded in the artifacts
These are the Whitechain Sepolia values as they appear in `rollup.json`, `genesis.json`, and `intent.toml`. They are reproduced here as text so the numbers are readable without opening the files.
| Parameter | Value | Source file |
| - | - | - |
| L2 chain ID | `1874` (hex `0x752`) | `genesis.json`, `rollup.json` |
| L1 settlement | Ethereum Sepolia (`11155111`) | `rollup.json` |
| L1 origin block | `11071328` | `rollup.json` |
| L2 genesis timestamp | `1781596368` (2026-06-16 07:52:48 UTC) | `rollup.json` |
| Block time | 1 second | `rollup.json` |
| Block gas limit | `40000000` | `genesis.json`, `rollup.json` |
| Minimum base fee | `5000000000` wei (5 gwei) | `rollup.json` |
| EIP-1559 denominator | `100` | `rollup.json` |
| EIP-1559 elasticity | `6` | `rollup.json` |
| Sequencing window | `3600` L1 blocks (about 12 hours) | `rollup.json` |
| Channel timeout | `300` L1 blocks | `rollup.json` |
| Maximum sequencer drift | `600` seconds | `rollup.json` |
| Batch inbox address | `0x00be0e81141c6fb68542d5f4b29900c42ad8d43e` | `rollup.json` |
| L1 base fee scalar | `1368` | `deploy-config.json` |
| L1 blob base fee scalar | `801949` | `deploy-config.json` |
| Native gas token | WBT (WhiteBIT Coin), OP Stack custom gas token | `intent.toml` |
| Protocol upgrades | Every upgrade through Jovian is active from block 0 | `genesis.json`, `rollup.json` |
Withdrawal and dispute timings come from `intent.toml`:
| Parameter | Value |
| - | - |
| `proofMaturityDelaySeconds` | `900` (15 minutes) |
| `disputeGameFinalityDelaySeconds` | `300` (5 minutes) |
| `faultGameWithdrawalDelay` | `600` (10 minutes) |
| `faultGameClockExtension` | `300` (5 minutes) |
| `faultGameMaxClockDuration` | `900` (15 minutes) |
| `preimageOracleChallengePeriod` | `300` (5 minutes) |
Whitechain Sepolia delays are intentionally short so that a full withdrawal can be tested end to end. Mainnet values are different. See [OP Stack canonical bridge](/build/bridge/bridge-assets#timing-parameters) for what these mean for a withdrawal.
`intent.toml` also records the role addresses for the deployment: the L1 and L2 `ProxyAdmin` owners, the `SystemConfig` owner, the batcher, the proposer, the challenger, the unsafe block signer, and the fee vault recipients.
## L1 contracts on Ethereum Sepolia
`l1-addresses.json` carries the complete set, proxies and implementations. The proxies below are the ones an operator or an integrator usually needs; read the file for the implementation addresses behind them.
| Contract | Address on Ethereum Sepolia |
| - | - |
| `OptimismPortalProxy` | [`0xff9b597b0781457ae6aa7256ca5ed5839bf7d0c3`](https://sepolia.etherscan.io/address/0xff9b597b0781457ae6aa7256ca5ed5839bf7d0c3) |
| `SystemConfigProxy` | [`0x9328ea869949f33c57b7b680b6edb58769e2181c`](https://sepolia.etherscan.io/address/0x9328ea869949f33c57b7b680b6edb58769e2181c) |
| `L1StandardBridgeProxy` | [`0x0c50be539ab5d72d226038928f2eb25100899ded`](https://sepolia.etherscan.io/address/0x0c50be539ab5d72d226038928f2eb25100899ded) |
| `L1CrossDomainMessengerProxy` | [`0x547967f45d19dc4ca0a056ba525afe99bda18a88`](https://sepolia.etherscan.io/address/0x547967f45d19dc4ca0a056ba525afe99bda18a88) |
| `L1Erc721BridgeProxy` | [`0x1e419fc5575bbd6d352582e9d8073918d79f0c5b`](https://sepolia.etherscan.io/address/0x1e419fc5575bbd6d352582e9d8073918d79f0c5b) |
| `OptimismMintableErc20FactoryProxy` | [`0x5898f6c319842a379799d634c6ed2da960a42b9b`](https://sepolia.etherscan.io/address/0x5898f6c319842a379799d634c6ed2da960a42b9b) |
| `DisputeGameFactoryProxy` | [`0xfaa2faa8912c069c01abc169c33713c79027c833`](https://sepolia.etherscan.io/address/0xfaa2faa8912c069c01abc169c33713c79027c833) |
| `AnchorStateRegistryProxy` | [`0x4da165963420deeb0788f338b7a1df41e535a32c`](https://sepolia.etherscan.io/address/0x4da165963420deeb0788f338b7a1df41e535a32c) |
| `EthLockboxProxy` | [`0x7becd75f3e1ce952cb63fd7ef5220f205a6e1469`](https://sepolia.etherscan.io/address/0x7becd75f3e1ce952cb63fd7ef5220f205a6e1469) |
| `SuperchainConfigProxy` | [`0x11854d3146a460d095943c3231f2755ba342426e`](https://sepolia.etherscan.io/address/0x11854d3146a460d095943c3231f2755ba342426e) |
| `ProtocolVersionsProxy` | [`0x811de5c619960e417dc8bc40aa4b052376ec211f`](https://sepolia.etherscan.io/address/0x811de5c619960e417dc8bc40aa4b052376ec211f) |
These addresses are specific to the Whitechain Sepolia deployment on Ethereum Sepolia. They are not the same on Mainnet, and they change if the deployment is ever redone. The L2 side of the bridge sits at fixed `0x4200...` predeploy addresses that are identical on every OP Stack chain: see [OP Stack canonical bridge](/build/bridge/bridge-assets#contract-addresses).
## Start a node from the artifacts
The same `genesis.json` is accepted by both execution clients. Whitechain's own `public-rpc-node` stack runs `op-reth`; `op-geth` works from the identical file.
Initialize `op-geth` from the genesis:
```bash theme={null}
op-geth init --datadir=./datadir testnet/genesis.json
```
Initialize `op-reth` from the same file:
```bash theme={null}
op-reth init --chain=testnet/genesis.json --datadir=./datadir
```
`op-reth` takes the genesis through `--chain` on every invocation, not only at init, so pass the same `--chain=testnet/genesis.json` to `op-reth node`. The explicit `init` step is optional, because `op-reth node` initializes an empty datadir from `--chain` on first start. This is what the `public-rpc-node` stack does: it mounts `artifacts//genesis.json` into the container and passes `--chain` to `op-reth node`, with no separate init step.
Start the consensus client against the same network:
```bash theme={null}
op-node \
--rollup.config=testnet/rollup.json \
--l1= \
--l1.beacon= \
--l2= \
--l2.jwt-secret=
```
`` is the authenticated engine API of the execution client started above, configured with the `--authrpc.*` flags on both `op-geth` and `op-reth`, and `` must be the same JWT secret that client was given.
Both clients must be pointed at the same pair of artifacts. In practice you do not run these commands directly: the `public-rpc-node` Docker Compose stack wraps them and generates the JWT for you. See [Run a node](/operate/run-a-node/run-a-node).
## When the artifacts change
Artifacts change at a hardfork, and the repository is updated before the activation timestamp. Replace `artifacts//rollup.json` (and `genesis.json` if it changed) with the published version, re-checking its SHA-256 against the new published hash, and restart before that timestamp, or the node stalls on a chain divergence. See [Node maintenance](/operate/run-a-node/maintenance#update-the-node).
A genesis change never applies to a node that is already synced past block 0. If both files change, wipe and resync.
## 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)
* [Whitechain Sepolia parameters](/learn/network/testnet)
* [Network reference](/learn/network/reference)
* [OP Stack canonical bridge](/build/bridge/bridge-assets)
# Node operators
Source: https://docs.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 on a chain with history behind it.
* **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, 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 | Initial sync | Public RPC |
| - | - | - | - | - | - |
| `full-snap-node` | Recommended default, fastest and simplest to bootstrap | Pruned (`--full`) | execution-layer (snap) | Fast, snap-syncs from the trusted peer | Yes |
| `full-node` | Public RPC without a snap-sync peer | Pruned (`--full`) | consensus-layer | Long, re-executes from genesis | Yes |
| `archive-node` | Explorers, indexers, historical tracing | Full history | consensus-layer | Longest, re-executes from genesis | Yes |
`full-snap-node` is the recommended default: pruned execution state, bootstrapped by snap-syncing from a trusted reth peer (`WHITECHAIN_RETH_TRUSTED_PEERS`) instead of re-executing the chain. Use it when you have a reachable seed reth enode. `full-node` keeps the same pruned state 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 expect a long initial sync on a chain with history behind it. `archive-node` keeps the full historical state, so it is the profile that can answer historical `eth_call`, `debug_traceTransaction`, and log-heavy indexing. Archive cannot snap-sync, so it always re-executes from L1, which makes its initial sync the longest of the three. It needs the most disk and RAM.
There are no published `op-reth` database snapshots to download. `full-snap-node` bootstraps from its trusted peer, and `full-node` and `archive-node` sync by re-executing the chain from L1.
## Available RPC namespaces
`op-reth` namespaces come from two `.env` variables that apply to all three profiles, and the two ports are configured independently:
| Port | Variable | Default |
| - | - | - |
| HTTP RPC `8545` | `HTTP_API` | `eth`, `net`, `web3`, `rpc` |
| WebSocket RPC `8546` | `WS_API` | `eth`, `net`, `web3`, `rpc` |
`archive-node` is no exception: by default it serves the same read-only set as the pruned profiles. `debug`, `trace`, `txpool`, and `reth` are an explicit opt-in through `HTTP_API` or `WS_API`, and only `archive-node` holds the history those methods read. Enable them only behind a proxy that allowlists methods and rate-limits clients. See [RPC namespaces](/operate/run-a-node/configuration#rpc-namespaces).
`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.
Flashblocks are not enabled on Whitechain Sepolia, so there is no pre-confirmation stream to subscribe to today. When the rollout lands, the stream stays separate from the public RPC and the `public-rpc-node` stack does not carry it. See [Flashblocks](/learn/network/transaction-finality#flashblocks).
## Related
* [Run a node](/operate/run-a-node/run-a-node)
* [Network artifacts](/operate/run-a-node/network-artifacts)
* [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://docs.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.
### Software
* A Linux server with a public IP.
* Docker with Compose v2.
* `make`, `git`, `openssl`, `curl`, `jq`. `make up` uses `jq` to cross-check the artifacts before it starts anything, so the node does not come up without it.
### 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. Both are published, with checksums and a regeneration recipe, in the [whitechain-bootstrap repository](https://github.com/whitechain-labs/whitechain-bootstrap). See [Network artifacts](/operate/run-a-node/network-artifacts).
* `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.
The node repository ships no copy of `genesis.json` and `rollup.json`, and no checksum file of its own, so there is a single source of truth to check against. Copy both files in from [whitechain-bootstrap](https://github.com/whitechain-labs/whitechain-bootstrap) and compare their SHA-256 against the hashes published there. For testnet, `.env.testnet.example` already ships with working values for the rest. You only need to request values from the Whitechain team for mainnet.
## Set up the node
1. Get the `public-rpc-node` manifests onto your server, from the [whitechain-labs/node repository](https://github.com/whitechain-labs/node).
2. Copy the network artifacts into `artifacts//`. They are not shipped in the node repository. The folder name must match `WHITECHAIN_NETWORK` in `.env`. See [Network artifacts](/operate/run-a-node/network-artifacts) for where to download them and how to verify them.
```bash theme={null}
git clone https://github.com/whitechain-labs/whitechain-bootstrap.git
cd whitechain-bootstrap
shasum -a 256 testnet/genesis.json testnet/rollup.json
cp testnet/genesis.json testnet/rollup.json /artifacts/testnet/
```
```text theme={null}
artifacts/testnet/genesis.json
artifacts/testnet/rollup.json
```
Do not skip the hash comparison. `genesis.json` decides which chain your node treats as canonical, and `rollup.json` decides how it derives that chain, so a wrong or tampered file puts the node on a different chain.
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).
`HTTP_API` and `WS_API` are `eth,net,web3,rpc` by default on every profile. Read [RPC namespaces](/operate/run-a-node/configuration#rpc-namespaces) before you widen them.
4. 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`, cross-checks the two artifacts against each other (see [Artifact validation](/operate/run-a-node/network-artifacts#artifact-validation)), generates `keys//jwt.txt` if missing, then runs `docker compose --profile up -d`.
5. 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. There are no published database snapshots to cut this down. Use `full-snap-node` instead if you need a fast bootstrap: it snap-syncs from its trusted peer 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)
* [Network artifacts](/operate/run-a-node/network-artifacts)
* [Node configuration](/operate/run-a-node/configuration)
* [Node maintenance](/operate/run-a-node/maintenance)
* [Troubleshooting](/operate/run-a-node/troubleshooting)
# Troubleshooting
Source: https://docs.whitechain.io/operate/run-a-node/troubleshooting
Common problems and fixes when running a Whitechain RPC node: missing configuration, artifact validation failures, 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`
The artifacts are not part of the node repository. Copy `genesis.json` and `rollup.json` for your network in from [whitechain-bootstrap](https://github.com/whitechain-labs/whitechain-bootstrap), verifying their SHA-256 against the hashes published there, and place them under `artifacts//`. The folder name must match the `WHITECHAIN_NETWORK` value in `.env`. See [Network artifacts](/operate/run-a-node/network-artifacts).
```text theme={null}
artifacts/testnet/genesis.json
artifacts/testnet/rollup.json
```
## `L2 chain ID mismatch`, `L2 genesis timestamp mismatch`, or `L1 chain ID mismatch`
`genesis.json` and `rollup.json` in `artifacts//` do not belong together, or the pair does not belong to the network selected by `WHITECHAIN_NETWORK`. Re-copy both files from the same network directory of [whitechain-bootstrap](https://github.com/whitechain-labs/whitechain-bootstrap) and re-check their SHA-256. See [Artifact validation](/operate/run-a-node/network-artifacts#artifact-validation).
## `jq not found`
`make check-env` uses `jq` to cross-check the artifacts before the node starts. Install it (`apt install jq` or `brew install jq`) and run the target again.
## 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, 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 your own backup 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 backup was taken from the same `WHITECHAIN_NETWORK` and the same profile you start.
3. Make sure the node was stopped during extraction.
See [Back up and restore the datadir](/operate/run-a-node/maintenance#back-up-and-restore-the-datadir) for the full procedure.
## Sync is slow
Common causes, in order of likelihood:
1. A consensus-layer profile (`full-node` or `archive-node`) on a chain with history behind it re-executes every transaction from genesis. Use `full-snap-node` if you need a fast bootstrap.
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)