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 # -> 31337Writing 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:
| Token | Decimals | Address | Behaviour |
|---|---|---|---|
| USDC | 6 | 0x5FbDB2315678afecb367f032d93F642f64180aa3 | standard |
| WBTC | 8 | 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 | standard |
| FEE | 18 | 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 | fee-on-transfer (1% burned on transfer) |
| REB | 18 | 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9 | rebasing (rebase(uint256) rescales balances) |
| NRT | 6 | 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 | no 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:
| Contract | Address | Used by |
|---|---|---|
| Multicall3 | 0xcA11bde05977b3631167028862bE2a173976CA11 | viem / ethers / wagmi batch reads |
| Permit2 | 0x000000000022D473030F116dDEE9F6B43aC78BA3 | Uniswap & signature-based approvals |
| CREATE2 Deployer | 0x4e59b44847b379578588920cA78FbF26c0B4956C | forge 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 USDCAmounts 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 snapshotimpersonate 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:
[[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:
| Chain | RPC | Explorer |
|---|---|---|
| anvil-1 | http://127.0.0.1:8545 | http://127.0.0.1:5100 |
| anvil-2 | http://127.0.0.1:8546 | http://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.