Skip to Content

EVM

wharfnet up boots two Anvil  chains by default — anvil-1 on :8545 and anvil-2 on :8546 — each with pre-deployed test tokens, canonical infra contracts, and a block explorer.

wharfnet up cast chain-id --rpc-url http://127.0.0.1:8545 # -> 31337

Writing Rust tests? Don’t hard-code the addresses below — read them from a running localnet with wharfnet::testkit: net.evm().rpc_url(), .token("USDC"), .account(0), and .token_abi("USDC").

Test tokens

Every EVM chain boots with test tokens pre-deployed at fixed addresses (identical on all chains) from a baked-in Anvil state snapshot — no deploy step required. Each has a public mint(address,uint256) so a faucet (or your tests) can top up any address on demand. The first two are standard, well-behaved ERC-20s; the rest are deliberately non-standard for token-integration testing:

TokenDecimalsAddressBehaviour
USDC60x5FbDB2315678afecb367f032d93F642f64180aa3standard
WBTC80xe7f1725E7734CE288F8367e1Bb143E90bb3F0512standard
FEE180x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0fee-on-transfer (1% burned on transfer)
REB180xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9rebasing (rebase(uint256) rescales balances)
NRT60xDc64a140Aa3E981100a9becA4E685f962f0cF6C9no return value (USDT-style transfer/approve)

The non-standard tokens let you verify that your contracts and integrations handle real-world token behaviour — amount-received ≠ amount-sent, balances that move with no transfer, and calls that don’t return a decodable bool.

The dev accounts are pre-seeded with a balance of each. Regenerate the snapshot after editing the token sources with ./scripts/gen-token-state.sh.

Canonical contracts

Every EVM chain also boots with the infrastructure contracts that live at the same address on every real chain, so client libraries and deploy tooling that hardcode these addresses work without per-chain wiring:

ContractAddressUsed by
Multicall30xcA11bde05977b3631167028862bE2a173976CA11viem / ethers / wagmi batch reads
Permit20x000000000022D473030F116dDEE9F6B43aC78BA3Uniswap & signature-based approvals
CREATE2 Deployer0x4e59b44847b379578588920cA78FbF26c0B4956Cforge create --create2, deterministic deploys

Multicall3 and Permit2 are etched from their real mainnet bytecode (see src/resources/presets/); the CREATE2 deployer is deployed by Anvil itself.

Faucet

The unified faucet command funds any address with the native coin and every bundled token, or a single token via --token:

# native coin + every bundled token, on all EVM chains wharfnet faucet evm 0xabc... 100 # just one token, on a specific chain wharfnet faucet anvil-1 0xabc... 100 --token USDC

Amounts are whole units, scaled by each token’s decimals. Funding is additive, so repeat top-ups accumulate.

Chain control

Drive a running localnet with thin wrappers over Anvil’s cheat RPCs, grouped under wharfnet evm. Each takes a --chain selector (evm for every EVM chain, or a name like anvil-1; defaults to evm):

wharfnet evm mine 10 # mine 10 blocks wharfnet evm increase-time 86400 # fast-forward time by a day wharfnet evm warp 1893456000 # set the next block to an absolute Unix time wharfnet evm impersonate 0xd8dA…6045 # then: cast send … --from 0xd8dA…6045 --unlocked wharfnet evm impersonate 0xd8dA…6045 --stop wharfnet evm snapshot # prints an id, e.g. 0x1 wharfnet evm revert 0x1 # roll state back to that snapshot

impersonate sends transactions as any address with no private key, which is useful against forked state; snapshot/revert give tests a state reset point. These live under evm because they are Anvil-specific — other chain kinds have their own namespaces (wharfnet starknet …, wharfnet solana …).

Forking

Point a chain at a live RPC and it boots as a fork of that network — real balances, contracts, and storage, mutable locally. Add fork_url (and optionally fork_block to pin a height) to a chain in wharfnet.toml:

wharfnet.toml
[[chains]] name = "mainnet" port = 8545 chain_id = 1 fork_url = "${MAINNET_RPC}" # ${VAR} is expanded from the environment fork_block = 21000000 # optional; omit to track the latest block

${VAR} references are resolved from the environment on load, so an RPC key never has to live in the file — and the manifest and status only ever record a redacted scheme://host, never the key.

Pinning fork_block to a past block needs an archive RPC; forking at the latest block works with an ordinary full-node endpoint. A forked chain mirrors live state, so it does not load the baked test tokens or canonical contracts — it already has whatever the source network has. Combine forking with chain control: wharfnet evm impersonate lets you send transactions as any address (a whale, a protocol admin) with no key.

MAINNET_RPC=https://… wharfnet up --config fork.toml --bare cast call 0xA0b8…eB48 'symbol()(string)' --rpc-url http://127.0.0.1:8545 # -> "USDC"

Block explorer

wharfnet up boots an Otterscan  instance for each EVM chain by default — a lightweight, open-source block explorer. Anvil implements Otterscan’s RPC API (ots_*), so the explorer needs no indexer or database — it is a static frontend that queries the chain directly. Each EVM chain has its own Otterscan on a dedicated port:

ChainRPCExplorer
anvil-1http://127.0.0.1:8545http://127.0.0.1:5100
anvil-2http://127.0.0.1:8546http://127.0.0.1:5101

Pass --bare to skip the explorer containers and run only the chains.

Persistence

EVM chains persist across up --resume / up --reset via Anvil’s --state (one session-<chain>.json per chain under .wharfnet/state/). See Resuming a session for the shared model.

Last updated on