provider.anvil
Documentation for eth_defi.provider.anvil Python module.
Anvil integration.
_ ..anvil:
This module provides Python integration for Anvil.
Anvil is a blazing-fast local testnet node implementation in Rust from Foundry project
Anvil can replace
eth_tester.main.EthereumTesteras the unit/integration test backend.Anvil is mostly used in mainnet fork test cases.
Anvil is a more stable an alternative to Ganache (
eth_defi.ganache)Anvil is part of Foundry, a toolkit for Ethereum application development.
To install Anvil on:
curl -L https://foundry.paradigm.xyz | bash
PATH=~/.foundry/bin:$PATH
foundryup # Needs to be in path, or installation fails
This will install foundryup, anvil at ~/.foundry/bin and adds the folder to your shell rc file PATH.
For more information see Anvil reference.
See also eth_defi.trace for Solidity tracebacks using Anvil.
To measure how long a cold vs warm mainnet-fork setup takes for a given chain and
archive provider — e.g. to diagnose read_timeout fork-setup failures on CI
(the misleading “out of API credits” hint) — run
scripts/measure-cold-fork-time.py. See
eth_defi.testing.anvil_fork_pool (the shared-fork pattern and its
“Cold-fork read-timeout failures” section) for how to interpret the numbers: a
healthy cold fork completes in ~seconds, so a 60 s timeout means a slow or
rate-limited upstream, not an undersized timeout.
The code was originally lifted from Brownie project.
Module Attributes
Prefix for the per-port advisory lock files shared by pytest-xdist workers. |
|
Maximum duration of one automatic Anvil proxy request to an upstream. |
|
Keep the automatic proxy's full provider pass below Web3's 60-second localhost read timeout. |
|
Seconds to wait for Anvil to exit cleanly ( |
|
HyperEVM mainnet/testnet chain ids. |
|
How many blocks behind the tip we pin HyperEVM Anvil forks by default. |
|
Mappings between Anvil command line parameters and our internal argument names |
|
Anvil default account #0 private key |
|
Anvil default account #0 address |
|
Anvil default account #1 address (useful as a Safe owner) |
|
Anvil default account #2 address (useful as a Safe owner) |
Functions
Capture the current Anvil state for later reuse. |
|
|
On Anvil forked mainnet, create a wallet with some USDC funds. |
|
Call evm_snapshot on Anvil |
|
Find the ERC-20 |
|
Creates Anvil unit test backend or mainnet fork. |
|
Fund an address with ERC-20 tokens by directly setting Anvil storage. |
|
Are we connected to Anvil node. |
|
Have we forked mainnet for this test. |
|
Creates Anvil unit test backend or mainnet fork. |
|
Call evm_snapshot on Anvil |
|
Make a request to special named EVM JSON-RPC endpoint. |
|
Call evm_setNextBlockTimestamp on Anvil. |
|
Revert a shared Anvil backend to a stored snapshot and resave it. |
|
Call evm_revert on Anvil |
|
Call anvil_setBalance on Anvil |
|
Call emv_increaseTime on Anvil |
|
Call evm_snapshot on Anvil |
|
Make Anvil mainnet fork to accept transactions to any Ethereum account. |
Classes
Metadata for a locally running Anvil JSON-RPC endpoint. |
|
Control Anvil processes launched on background. |
|
Mutable reset point for a shared Anvil backend. |
Exceptions
RPC endpoint does not provide archive node access. |
|
Lifted from Brownie. |
|
Lifted from Brownie. |
- ANVIL_PORT_LOCK_FILE_PREFIX = 'web3-ethereum-defi-anvil-port'
Prefix for the per-port advisory lock files shared by pytest-xdist workers.
The files themselves are not reservations.
fcntl.flock()associates the reservation with an open file descriptor and the operating system releases it automatically if a worker crashes. Lock files are deliberately retained: unlinking a live lock file could let another worker create a new inode for the same port and acquire an independent lock.
- ANVIL_PROXY_MAX_ATTEMPT_TIMEOUT: float = 15.0
Maximum duration of one automatic Anvil proxy request to an upstream.
- ANVIL_PROXY_TOTAL_TIMEOUT: float = 55.0
Keep the automatic proxy’s full provider pass below Web3’s 60-second localhost read timeout. Explicit
RPCProxyConfigvalues are preserved unchanged.
- ANVIL_GRACEFUL_SHUTDOWN_TIMEOUT: float = 5.0
Seconds to wait for Anvil to exit cleanly (
SIGTERM) beforeSIGKILLonAnvilLaunch.close(). Anvil flushes its fork RPC cache (~/.foundry/cache/rpc/<network>/<block>/storage.json) only on a graceful shutdown; a straightSIGKILLdiscards it, so without this the on-disk fork cache never accumulates and CI keeps cold-fetching (and getting throttled). The flush of a single block is fast; the bounded wait plus SIGKILL fallback keeps teardown from hanging.
- class AnvilForkMetadata
Bases:
objectMetadata for a locally running Anvil JSON-RPC endpoint.
This metadata lets later
Web3objects created from only the local Anvil URL still report which upstream fork RPC providers Anvil was configured to use.- Variables
chain_id – Chain id reported by the local Anvil instance after startup.
upstream_rpc_urls – Original upstream RPC URLs passed to Anvil fork mode. If Anvil was started as a standalone local backend, this is empty.
fork_block_number – Explicit fork block used for Anvil, if any.
effective_fork_url – URL passed to Anvil as
--fork-url. With multiple upstreams this can be a local failover proxy URL instead of one of the upstream RPC URLs.
- HYPEREVM_CHAIN_IDS: set[int] = {998, 999}
HyperEVM mainnet/testnet chain ids.
HyperEVM RPCs are special when forking with Anvil: asking Anvil to fork the chain tip without an explicit
--fork-block-numbercan fail even aftereth_blockNumbersucceeded against the upstream RPC.The failure mode we have seen in production is:
Anvil launches with
--fork-url <HyperEVM RPC>and no--fork-block-numberduring genesis creation, Anvil asks the upstream RPC for the default Anvil deployer account state at
latestHyperEVM sometimes responds with HTTP 400 and JSON-RPC error
{"message":"Unknown block","code":26}Anvil aborts with
Error: failed to create genesis
Because of this, HyperEVM forks must be pinned slightly behind the tip unless the caller already supplied an explicit, known-good block number.
- HYPEREVM_ANVIL_FORK_TIP_LATENCY = 4
How many blocks behind the tip we pin HyperEVM Anvil forks by default.
Four blocks matches the manual workaround already used by HyperEVM test fixtures in this repository and keeps us away from the unstable chain tip that can return
Unknown blockduring Anvil genesis creation.
- exception InvalidArgumentWarning
Bases:
WarningLifted from Brownie.
- __init__(*args, **kwargs)
- __new__(**kwargs)
- add_note(note, /)
Add a note to the exception
- with_traceback(tb, /)
Set self.__traceback__ to tb and return self.
- exception RPCRequestError
Bases:
ExceptionLifted from Brownie.
- __init__(*args, **kwargs)
- __new__(**kwargs)
- add_note(note, /)
Add a note to the exception
- with_traceback(tb, /)
Set self.__traceback__ to tb and return self.
- exception ArchiveNodeRequired
Bases:
ExceptionRPC endpoint does not provide archive node access.
This is raised when a fork test requires historical block access but the RPC endpoint only provides recent blocks.
- __init__(message, rpc_url=None, requested_block=None, available_block=None, response_headers=None)
- __new__(**kwargs)
- add_note(note, /)
Add a note to the exception
- with_traceback(tb, /)
Set self.__traceback__ to tb and return self.
- CLI_FLAGS = {'block_time': '--block-time', 'chain_id': '--chain-id', 'code_size_limit': '--code-size-limit', 'default_balance': '--balance', 'fork': '--fork-url', 'fork_block_number': '--fork-block-number', 'gas_limit': '--gas-limit', 'hardfork': '--hardfork', 'host': '--host', 'port': '--port', 'steps_tracing': '--steps-tracing', 'verbose': '-vvvvv'}
Mappings between Anvil command line parameters and our internal argument names
- make_anvil_custom_rpc_request(web3, method, args=None)
Make a request to special named EVM JSON-RPC endpoint.
- Parameters
- Returns
RPC result
- Raises
RPCRequestError – In the case RPC method errors
- Return type
- class AnvilLaunch
Bases:
objectControl Anvil processes launched on background.
Comes with a helpful
close()method when it is time to put Anvil rest.The
chain_id,upstream_rpc_urls,fork_block_numberandeffective_fork_urlfields are the canonical launch metadata exposed to callers. The module-level metadata registry mirrors these values only so that latercreate_multi_provider_web3(launch.json_rpc_url)calls can attach the same context to retry diagnostics.- process: psutil.Popen
UNIX process that we opened
- proxy: Optional[eth_defi.provider.rpc_proxy.RPCProxy] = None
Optional JSON-RPC failover proxy sitting between Anvil and upstream RPCs. Automatically started by
launch_anvil()when multiple RPCs are configured in space-separatedfork_urlandproxy_multiple_upstreamis notFalse. Seeeth_defi.provider.rpc_proxy.
- close(log_level=None, block=True, block_timeout=30)
Close the background Anvil process.
If this instance owns the
RPCProxy(i.e. it was auto-created, not passed in by the caller), the proxy is shut down after Anvil exits and its per-provider statistics are logged. Port-lease release runs from nestedfinallyblocks, so a shutdown or managed-proxy cleanup error cannot strand the lease until the Python worker exits.
- __init__(port, cmd, json_rpc_url, process, chain_id=None, upstream_rpc_urls=(), fork_block_number=None, effective_fork_url=None, proxy=None, _proxy_managed=True, _port_lease=None)
- launch_anvil(fork_url=None, unlocked_addresses=None, cmd='anvil', port=(19999, 29999, 25), block_time=0, launch_wait_seconds=20.0, attempts=3, hardfork='cancun', gas_limit=None, steps_tracing=False, test_request_timeout=3.0, fork_block_number=None, log_wait=False, code_size_limit=None, rpc_smoke_test=True, verbose=False, inherit_stdio=False, warm_up_block=False, archive=True, proxy_multiple_upstream=True)
Creates Anvil unit test backend or mainnet fork.
Anvil can be used as web3.py test backend instead of EthereumTester. Anvil offers faster execution and tracing - see
eth_defi.trace.Forking a mainnet is a common way to test against live deployments. This function invokes anvil command and tells it to fork a given JSON-RPC endpoint.
When called, a subprocess is started on the background. To stop this process, call
eth_defi.anvil.AnvilLaunch.close().This function waits launch_wait_seconds in order to anvil process to start and complete the chain fork.
Unit test backend:
See eth_defi.tests.enzyme.conftest for an example how to use Anvil in your Python based unit test suite
Mainnet fork: Here is an example that forks BNB chain mainnet and transfer 500 BUSD stablecoin to a test account we control:
from eth_defi.anvil import fork_network_anvil from eth_defi.chain import install_chain_middleware from eth_defi.gas import node_default_gas_price_strategy @pytest.fixture() def large_busd_holder() -> HexAddress: # An onchain address with BUSD balance # Binance Hot Wallet 6 return HexAddress(HexStr("0x8894E0a0c962CB723c1976a4421c95949bE2D4E3")) @pytest.fixture() def user_1() -> LocalAccount: # Create a test account return Account.create() @pytest.fixture() def anvil_bnb_chain_fork(request, large_busd_holder, user_1, user_2) -> str: # Create a testable fork of live BNB chain. mainnet_rpc = os.environ["BNB_CHAIN_JSON_RPC"] launch = fork_network_anvil(mainnet_rpc, unlocked_addresses=[large_busd_holder]) try: yield launch.json_rpc_url finally: # Wind down Anvil process after the test is complete launch.close(log_level=logging.ERROR) @pytest.fixture() def web3(anvil_bnb_chain_fork: str): # Set up a local unit testing blockchain # https://web3py.readthedocs.io/en/stable/examples.html#contract-unit-tests-in-python web3 = Web3(HTTPProvider(anvil_bnb_chain_fork)) # Anvil needs POA middlware if parent chain needs POA middleware install_chain_middleware(web3) web3.eth.set_gas_price_strategy(node_default_gas_price_strategy) return web3 def test_anvil_fork_transfer_busd(web3: Web3, large_busd_holder: HexAddress, user_1: LocalAccount): # Forks the BNB chain mainnet and transfers from USDC to the user. # BUSD deployment on BNB chain # https://bscscan.com/token/0xe9e7cea3dedca5984780bafc599bd69add087d56 busd_details = fetch_erc20_details(web3, "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56") busd = busd_details.contract # Transfer 500 BUSD to the user 1 tx_hash = busd.functions.transfer(user_1.address, 500 * 10**18).transact({"from": large_busd_holder}) # Because Ganache has instamine turned on by default, we do not need to wait for the transaction receipt = web3.eth.get_transaction_receipt(tx_hash) assert receipt.status == 1, "BUSD transfer reverted" assert busd.functions.balanceOf(user_1.address).call() == 500 * 10**18
See the full example in tests source code.
If anvil refuses to terminate properly, you can kill a process by a port in your terminal:
# Kill any process listening to localhost:19999 kill -SIGKILL $(lsof -ti:19999)
See also
Note
Looks like we have some issues Anvil instance lingering around even after AnvilLaunch.close() if scoped pytest fixtures are used.
If you intentionally keep a fork alive across multiple tests, pair a module-scoped
launch_anvil()/fork_network_anvil()fixture withcreate_anvil_snapshot_state()andreset_anvil_snapshot()so you can reset state cheaply between tests.- Parameters
cmd – Override anvil command. If not given we look up from PATH.
HTTP JSON-RPC URL of the network we want to fork.
If not given launch an empty test backend.
unlocked_addresses (list[eth_typing.evm.HexAddress | str]) – List of addresses of which ownership we take to allow test code to transact as them
port (Union[int, tuple[int, int], tuple[int, int, int]]) –
Localhost port we bind for Anvil JSON-RPC.
The tuple format is
(minimum port, exclusive maximum port)or(minimum port, exclusive maximum port, reservation attempts). The two-element form retains its historical default of 20 attempts.A tuple makes parallel launches choose random candidates from the range. Each candidate is protected by a per-port
fcntl.flock()lease from immediately before Anvil starts untilAnvilLaunch.close()finishes. This closes the check-then-bind race where twopytest-xdistworkers previously found the same port free and one worker then connected to the other worker’s chain.The advisory lock coordinates eth-defi launchers on the same host. The allocator also rejects ports with an existing TCP listener, and fork startup checks that the child is still alive and that its chain id matches the upstream RPC. Together these checks fail closed instead of returning a JSON-RPC URL backed by an unrelated Anvil process.
You can also specify an individual port.
launch_wait_seconds – How long we wait anvil to start until giving up
block_time –
How long Anvil takes to mine a block. Default is zero: Anvil is in automining mode and creates a new block for each new transaction.
Set to 1 or higher so that you can poll the transaction as you would do with a live JSON-RPC node.
attempts –
How many attempts we do to start anvil.
Anvil launch may fail without any output. This could be because the given JSON-RPC node is throttling your API requests. In this case we just try few more times again by killing the Anvil process and starting it again.
step_tracing –
Enable Anvil step tracing.
Needed to get structured logs.
Only needed on GoEthereum style tracing, not needed for Parity style tracing.
test_request_timeout – Set the timeout fro the JSON-RPC requests that attempt to determine if Anvil was successfully launched.
fork_block_number (Optional[int]) –
For at a specific block height of the parent chain.
If not given, fork at the latest block. Needs an archive node to work.
HyperEVM is a special case: if the caller does not supply an explicit fork block,
launch_anvil()automatically pins the fork a few blocks behind the tip. This avoids the recurring HyperEVM failure where Anvil aborts withError: failed to create genesisbecause the upstream RPC responds with{"message":"Unknown block","code":26}while resolvinglatestduring genesis creation.rpc_smoke_test – Check that the RPC is working before attempting to start Anvil
verbose –
Make Anvil the proces to dump a lot of stuff to stdout/stderr.
inherit_stdio (bool) –
If
True, let the Anvil subprocess inherit the parent process stdout/stderr instead of capturing them in pipes.This is useful in Docker and other supervised environments where you want Anvil logs to appear live in the container logs.
Warning
When
False(default), stdout/stderr are captured and only read when the process is shut down. If Anvil is very chatty, those pipe buffers can fill up and stall the subprocess.warm_up_block (bool) –
If
Trueand running in fork mode, eagerly calleth_getBlockByNumber(fork_block, true)against the freshly started local Anvil instance.This can move an expensive first fork hydration from an arbitrary later request to startup. It does not remove the cost, but it makes it happen once, predictably, before the caller starts using the node.
archive (bool) –
Check that the RPC endpoint provides archive node access.
When True (default) and
fork_block_numberis specified, performs a smoke test to verify the RPC can access historical blocks. If the RPC cannot access the requested block, raisesArchiveNodeRequiredwith HTTP response headers to help identify the problematic RPC provider.proxy_multiple_upstream (Union[eth_defi.provider.rpc_proxy.RPCProxy, eth_defi.provider.rpc_proxy.RPCProxyConfig, bool]) –
Controls how multiple upstream RPC providers in
fork_urlare handled.Background: Anvil accepts only a single
--fork-urland has no internal retry or failover logic. When the upstream is slow or rate-limited, Anvil hangs indefinitely. This parameter enables a transparent JSON-RPC proxy (RPCProxy) that sits between Anvil and multiple upstreams, providing automatic failover, per-request timeouts, and diagnostics.The proxy is only relevant when
fork_urlcontains multiple space-separated RPC URLs. With a single URL this parameter is ignored.Accepted values:
``True`` (default) — automatically start a proxy with default settings when multiple RPCs are detected. The proxy lifecycle is tied to
AnvilLaunch.close(): it starts before Anvil and shuts down (logging per-provider statistics) after Anvil exits.``False`` — disable the proxy entirely. Falls back to the legacy behaviour of picking one RPC in round-robin order per
launch_anvil()call, with no intra-session failover.An :py:class:`~eth_defi.provider.rpc_proxy.RPCProxyConfig` instance — automatically start a proxy with custom settings. Any fields not set explicitly use their dataclass defaults. Example:
from eth_defi.provider.rpc_proxy import RPCProxyConfig launch_anvil( fork_url="https://rpc-a.example.com https://rpc-b.example.com", proxy_multiple_upstream=RPCProxyConfig( timeout=15.0, retries=5, auto_switch_request_count=50, ), )An :py:class:`~eth_defi.provider.rpc_proxy.RPCProxy` instance — use a proxy that you created yourself via
start_rpc_proxy(). Useful when you need full control over the proxy lifecycle or want to share a single proxy across multiple Anvil instances. In this caselaunch_anvildoes not manage the proxy lifecycle — you must callclose()yourself.
See
eth_defi.provider.rpc_proxyfor the full proxy API.code_size_limit (int) –
- Parma code_size_limit
Max smart contract size
- Parma log_wait
Display info level logging while waiting for Anvil to start.
- Raises
ArchiveNodeRequired – When
archive=Trueand the RPC endpoint cannot access the requested historical block.- Return type
- unlock_account(web3, address)
Make Anvil mainnet fork to accept transactions to any Ethereum account.
This is even when we do not have a private key for the account.
- Parameters
web3 (web3.main.Web3) – Web3 instance
address (str) – Account to unlock
- sleep(web3, seconds)
Call emv_increaseTime on Anvil
- mine(web3, timestamp=None, increase_timestamp=0)
Call evm_setNextBlockTimestamp on Anvil.
Mine blocks, optionally set the time of the new block.
- class AnvilSnapshotState
Bases:
objectMutable reset point for a shared Anvil backend.
This helper is designed for pytest suites that keep one Anvil process alive across multiple tests and reset it cheaply using
evm_snapshot/evm_revertinstead of relaunching the fork each time.Use
create_anvil_snapshot_state()to take the initial snapshot andreset_anvil_snapshot()to restore it between tests.Note
Only use this pattern in self-contained test modules or conftest files where the
web3fixture is not overridden by sibling modules. Placing anautouse=Truerestore fixture in a sharedconftest.pycausesScopeMismatcherrors when other test modules in the same directory overrideweb3with function scope (e.g. for a different chain). Additionally, module-scoped Anvil forks combined with repeated snapshot/revert cycles can hang on CI runners underpytest-xdistparallel execution, likely due to Anvil process responsiveness degradation after many revert cycles.Example:
import pytest from eth_defi.provider.anvil import AnvilSnapshotState, create_anvil_snapshot_state, reset_anvil_snapshot @pytest.fixture(scope="module") def deployed_state(web3, deploy_info) -> AnvilSnapshotState: # deploy_info is resolved first so the snapshot captures the # expensive post-deployment baseline return create_anvil_snapshot_state(web3) @pytest.fixture(autouse=True) def restore_deployed_state(web3, deployed_state) -> None: reset_anvil_snapshot(web3, deployed_state)
- revert(web3, snapshot_id)
Call evm_revert on Anvil
- create_anvil_snapshot_state(web3)
Capture the current Anvil state for later reuse.
This is the manual building block for snapshot-based fixtures. Call this once after the expensive setup you want to reuse, such as a mainnet fork or a full protocol deployment.
See
AnvilSnapshotStatefor a pytest usage example.- Parameters
web3 (web3.main.Web3) –
- Return type
- reset_anvil_snapshot(web3, state)
Revert a shared Anvil backend to a stored snapshot and resave it.
evm_revertconsumes the snapshot it restores. Because of this, the helper immediately creates a new snapshot after each reset so the sameAnvilSnapshotStateinstance can be reused by the next test.See
AnvilSnapshotStatefor a pytest usage example.- Parameters
web3 (web3.main.Web3) –
- Return type
None
- load_state(web3, state)
Call evm_snapshot on Anvil
- set_balance(web3, address, raw_amount)
Call anvil_setBalance on Anvil
- is_anvil(web3)
Are we connected to Anvil node.
You need to change some behavior depending if you are connected to a real node or Anvil simulation.
This can be either
Mainnet work (chain id copied from the forked blockchain)
Anvil test backend
See also
launch_anvil()Warning
This method will crash with Base mainnet sequencer:
requests.exceptions.HTTPError: 403 Client Error: Forbidden for url: https://mainnet-sequencer.base.org/.- Parameters
web3 (web3.main.Web3) – Web3 connection instance to check
- Returns
True if we think we are connected to Anvil
- Return type
- is_mainnet_fork(web3)
Have we forked mainnet for this test.
Only relevant with
is_anvil()
- Returns
True if we think we are connected to a forked mainnet, False if we think we are a standalone local dev chain.
- Parameters
web3 (web3.main.Web3) –
- Return type
- create_fork_funded_wallet(web3, usdc_address, large_usdc_holder, usdc_amount=Decimal('10000'), eth_amount=Decimal('10'))
On Anvil forked mainnet, create a wallet with some USDC funds.
Make a new private key account on a forked mainnet
Top this up with ETH and USDC from a large USDC holder
- Parameters
web3 (web3.main.Web3) –
usdc_address (eth_typing.evm.HexAddress) –
large_usdc_holder (eth_typing.evm.HexAddress) –
- Return type
eth_defi.hot_wallet.HotWallet
- ANVIL_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'
Anvil default account #0 private key
- ANVIL_DEPLOYER = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'
Anvil default account #0 address
- ANVIL_OWNER_1 = '0x70997970C51812dc3A010C7d01b50e0d17dc79C8'
Anvil default account #1 address (useful as a Safe owner)
- ANVIL_OWNER_2 = '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC'
Anvil default account #2 address (useful as a Safe owner)
- find_erc20_balance_slot(web3, token_address, holder_address)
Find the ERC-20
balanceOfmapping storage slot by brute force.Tries slots 0-19 using Anvil snapshots, which covers all common ERC-20 implementations (OpenZeppelin, Solmate, USDC proxy, etc.).
Note
Only works on Anvil forks, as it uses
evm_snapshot,evm_revert, andanvil_setStorageAtRPC methods.- Parameters
web3 (web3.main.Web3) – Web3 connected to an Anvil fork.
token_address (Union[eth_typing.evm.HexAddress, str]) – ERC-20 token contract address.
holder_address (Union[eth_typing.evm.HexAddress, str]) – Address whose balance slot to find.
- Returns
Storage slot number (0-19).
- Raises
RuntimeError – If no matching slot is found in the first 20 slots.
- Return type
- fund_erc20_on_anvil(web3, token_address, recipient, amount)
Fund an address with ERC-20 tokens by directly setting Anvil storage.
Auto-detects the
balanceOfmapping slot usingfind_erc20_balance_slot(), then writes the amount directly to the token’s storage.Example — mint 1000 USDC on an Arbitrum Anvil fork:
from eth_defi.provider.anvil import launch_anvil, fund_erc20_on_anvil from eth_defi.provider.multi_provider import create_multi_provider_web3 from eth_defi.token import USDC_NATIVE_TOKEN, fetch_erc20_details anvil = launch_anvil(fork_url="https://arb1.arbitrum.io/rpc") web3 = create_multi_provider_web3(anvil.json_rpc_url) chain_id = web3.eth.chain_id # 42161 usdc_address = USDC_NATIVE_TOKEN[chain_id] usdc = fetch_erc20_details(web3, usdc_address) recipient = "0xYourAddress..." fund_erc20_on_anvil( web3, usdc_address, recipient, usdc.convert_to_raw(1000), # 1000 USDC ) balance = usdc.fetch_balance_of(recipient) assert balance == 1000
- Parameters
web3 (web3.main.Web3) – Web3 connected to an Anvil fork.
token_address (Union[eth_typing.evm.HexAddress, str]) – ERC-20 token contract address.
recipient (Union[eth_typing.evm.HexAddress, str]) – Address to receive the tokens.
amount (int) – Token amount in raw wei.
- Returns
The storage slot that was written to.
- Return type
- fork_network_anvil(fork_url=None, unlocked_addresses=None, cmd='anvil', port=(19999, 29999, 25), block_time=0, launch_wait_seconds=20.0, attempts=3, hardfork='cancun', gas_limit=None, steps_tracing=False, test_request_timeout=3.0, fork_block_number=None, log_wait=False, code_size_limit=None, rpc_smoke_test=True, verbose=False, inherit_stdio=False, warm_up_block=False, archive=True, proxy_multiple_upstream=True)
Creates Anvil unit test backend or mainnet fork.
Anvil can be used as web3.py test backend instead of EthereumTester. Anvil offers faster execution and tracing - see
eth_defi.trace.Forking a mainnet is a common way to test against live deployments. This function invokes anvil command and tells it to fork a given JSON-RPC endpoint.
When called, a subprocess is started on the background. To stop this process, call
eth_defi.anvil.AnvilLaunch.close().This function waits launch_wait_seconds in order to anvil process to start and complete the chain fork.
Unit test backend:
See eth_defi.tests.enzyme.conftest for an example how to use Anvil in your Python based unit test suite
Mainnet fork: Here is an example that forks BNB chain mainnet and transfer 500 BUSD stablecoin to a test account we control:
from eth_defi.anvil import fork_network_anvil from eth_defi.chain import install_chain_middleware from eth_defi.gas import node_default_gas_price_strategy @pytest.fixture() def large_busd_holder() -> HexAddress: # An onchain address with BUSD balance # Binance Hot Wallet 6 return HexAddress(HexStr("0x8894E0a0c962CB723c1976a4421c95949bE2D4E3")) @pytest.fixture() def user_1() -> LocalAccount: # Create a test account return Account.create() @pytest.fixture() def anvil_bnb_chain_fork(request, large_busd_holder, user_1, user_2) -> str: # Create a testable fork of live BNB chain. mainnet_rpc = os.environ["BNB_CHAIN_JSON_RPC"] launch = fork_network_anvil(mainnet_rpc, unlocked_addresses=[large_busd_holder]) try: yield launch.json_rpc_url finally: # Wind down Anvil process after the test is complete launch.close(log_level=logging.ERROR) @pytest.fixture() def web3(anvil_bnb_chain_fork: str): # Set up a local unit testing blockchain # https://web3py.readthedocs.io/en/stable/examples.html#contract-unit-tests-in-python web3 = Web3(HTTPProvider(anvil_bnb_chain_fork)) # Anvil needs POA middlware if parent chain needs POA middleware install_chain_middleware(web3) web3.eth.set_gas_price_strategy(node_default_gas_price_strategy) return web3 def test_anvil_fork_transfer_busd(web3: Web3, large_busd_holder: HexAddress, user_1: LocalAccount): # Forks the BNB chain mainnet and transfers from USDC to the user. # BUSD deployment on BNB chain # https://bscscan.com/token/0xe9e7cea3dedca5984780bafc599bd69add087d56 busd_details = fetch_erc20_details(web3, "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56") busd = busd_details.contract # Transfer 500 BUSD to the user 1 tx_hash = busd.functions.transfer(user_1.address, 500 * 10**18).transact({"from": large_busd_holder}) # Because Ganache has instamine turned on by default, we do not need to wait for the transaction receipt = web3.eth.get_transaction_receipt(tx_hash) assert receipt.status == 1, "BUSD transfer reverted" assert busd.functions.balanceOf(user_1.address).call() == 500 * 10**18
See the full example in tests source code.
If anvil refuses to terminate properly, you can kill a process by a port in your terminal:
# Kill any process listening to localhost:19999 kill -SIGKILL $(lsof -ti:19999)
See also
Note
Looks like we have some issues Anvil instance lingering around even after AnvilLaunch.close() if scoped pytest fixtures are used.
If you intentionally keep a fork alive across multiple tests, pair a module-scoped
launch_anvil()/fork_network_anvil()fixture withcreate_anvil_snapshot_state()andreset_anvil_snapshot()so you can reset state cheaply between tests.- Parameters
cmd – Override anvil command. If not given we look up from PATH.
HTTP JSON-RPC URL of the network we want to fork.
If not given launch an empty test backend.
unlocked_addresses (list[eth_typing.evm.HexAddress | str]) – List of addresses of which ownership we take to allow test code to transact as them
port (Union[int, tuple[int, int], tuple[int, int, int]]) –
Localhost port we bind for Anvil JSON-RPC.
The tuple format is
(minimum port, exclusive maximum port)or(minimum port, exclusive maximum port, reservation attempts). The two-element form retains its historical default of 20 attempts.A tuple makes parallel launches choose random candidates from the range. Each candidate is protected by a per-port
fcntl.flock()lease from immediately before Anvil starts untilAnvilLaunch.close()finishes. This closes the check-then-bind race where twopytest-xdistworkers previously found the same port free and one worker then connected to the other worker’s chain.The advisory lock coordinates eth-defi launchers on the same host. The allocator also rejects ports with an existing TCP listener, and fork startup checks that the child is still alive and that its chain id matches the upstream RPC. Together these checks fail closed instead of returning a JSON-RPC URL backed by an unrelated Anvil process.
You can also specify an individual port.
launch_wait_seconds – How long we wait anvil to start until giving up
block_time –
How long Anvil takes to mine a block. Default is zero: Anvil is in automining mode and creates a new block for each new transaction.
Set to 1 or higher so that you can poll the transaction as you would do with a live JSON-RPC node.
attempts –
How many attempts we do to start anvil.
Anvil launch may fail without any output. This could be because the given JSON-RPC node is throttling your API requests. In this case we just try few more times again by killing the Anvil process and starting it again.
step_tracing –
Enable Anvil step tracing.
Needed to get structured logs.
Only needed on GoEthereum style tracing, not needed for Parity style tracing.
test_request_timeout – Set the timeout fro the JSON-RPC requests that attempt to determine if Anvil was successfully launched.
fork_block_number (Optional[int]) –
For at a specific block height of the parent chain.
If not given, fork at the latest block. Needs an archive node to work.
HyperEVM is a special case: if the caller does not supply an explicit fork block,
launch_anvil()automatically pins the fork a few blocks behind the tip. This avoids the recurring HyperEVM failure where Anvil aborts withError: failed to create genesisbecause the upstream RPC responds with{"message":"Unknown block","code":26}while resolvinglatestduring genesis creation.rpc_smoke_test – Check that the RPC is working before attempting to start Anvil
verbose –
Make Anvil the proces to dump a lot of stuff to stdout/stderr.
inherit_stdio (bool) –
If
True, let the Anvil subprocess inherit the parent process stdout/stderr instead of capturing them in pipes.This is useful in Docker and other supervised environments where you want Anvil logs to appear live in the container logs.
Warning
When
False(default), stdout/stderr are captured and only read when the process is shut down. If Anvil is very chatty, those pipe buffers can fill up and stall the subprocess.warm_up_block (bool) –
If
Trueand running in fork mode, eagerly calleth_getBlockByNumber(fork_block, true)against the freshly started local Anvil instance.This can move an expensive first fork hydration from an arbitrary later request to startup. It does not remove the cost, but it makes it happen once, predictably, before the caller starts using the node.
archive (bool) –
Check that the RPC endpoint provides archive node access.
When True (default) and
fork_block_numberis specified, performs a smoke test to verify the RPC can access historical blocks. If the RPC cannot access the requested block, raisesArchiveNodeRequiredwith HTTP response headers to help identify the problematic RPC provider.proxy_multiple_upstream (Union[eth_defi.provider.rpc_proxy.RPCProxy, eth_defi.provider.rpc_proxy.RPCProxyConfig, bool]) –
Controls how multiple upstream RPC providers in
fork_urlare handled.Background: Anvil accepts only a single
--fork-urland has no internal retry or failover logic. When the upstream is slow or rate-limited, Anvil hangs indefinitely. This parameter enables a transparent JSON-RPC proxy (RPCProxy) that sits between Anvil and multiple upstreams, providing automatic failover, per-request timeouts, and diagnostics.The proxy is only relevant when
fork_urlcontains multiple space-separated RPC URLs. With a single URL this parameter is ignored.Accepted values:
``True`` (default) — automatically start a proxy with default settings when multiple RPCs are detected. The proxy lifecycle is tied to
AnvilLaunch.close(): it starts before Anvil and shuts down (logging per-provider statistics) after Anvil exits.``False`` — disable the proxy entirely. Falls back to the legacy behaviour of picking one RPC in round-robin order per
launch_anvil()call, with no intra-session failover.An :py:class:`~eth_defi.provider.rpc_proxy.RPCProxyConfig` instance — automatically start a proxy with custom settings. Any fields not set explicitly use their dataclass defaults. Example:
from eth_defi.provider.rpc_proxy import RPCProxyConfig launch_anvil( fork_url="https://rpc-a.example.com https://rpc-b.example.com", proxy_multiple_upstream=RPCProxyConfig( timeout=15.0, retries=5, auto_switch_request_count=50, ), )An :py:class:`~eth_defi.provider.rpc_proxy.RPCProxy` instance — use a proxy that you created yourself via
start_rpc_proxy(). Useful when you need full control over the proxy lifecycle or want to share a single proxy across multiple Anvil instances. In this caselaunch_anvildoes not manage the proxy lifecycle — you must callclose()yourself.
See
eth_defi.provider.rpc_proxyfor the full proxy API.code_size_limit (int) –
- Parma code_size_limit
Max smart contract size
- Parma log_wait
Display info level logging while waiting for Anvil to start.
- Raises
ArchiveNodeRequired – When
archive=Trueand the RPC endpoint cannot access the requested historical block.- Return type