Local EVM Testing with Foundry
Overview
Testing monitors against a local EVM node is faster, free, and gives you full control over blocks and transactions. You can iterate on monitor behavior without using public testnets or mainnet RPC.
This guide uses the Foundry stack:
- Anvil – Local Ethereum node (default:
http://127.0.0.1:8545, chain ID31337) - Forge – Build, test, and deploy Solidity contracts
- Cast – Send transactions and query the chain from the command line
Prerequisites
Install Foundry (Anvil, Forge, Cast). For example:
curl -L https://foundry.paradigm.xyz | bash
foundryupVerify:
anvil --version
forge --version
cast --versionStart Anvil
In a terminal, start a local node:
anvilDefault endpoint: http://127.0.0.1:8545, chain ID 31337. Anvil prints a list of prefunded accounts and private keys; use the first one (e.g. 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80) for deploying and sending transactions.
Deploy the Test Contract
The repository includes a minimal Foundry project under examples/contracts/ with an ERC-20-style token for local testing.
-
Install Forge std (if not already present):
cd examples/contracts forge install foundry-rs/forge-std -
Run tests (optional but recommended):
forge test -
Deploy (with Anvil running in another terminal):
forge script script/Deploy.s.sol --broadcast --rpc-url http://127.0.0.1:8545In the deploy output, find the line that looks like:
TestToken 0x5FbDB2315678afecb367f032d93F642f64180aa3The hex value after
TestTokenis your token contract address. Copy it; you will need it for the monitor config and for the trigger command below. -
Navigate back to the project root before continuing:
cd ../..
Configure the Monitor
-
Copy the example configs into your
config/directory:cp examples/config/networks/anvil_local.json config/networks/ cp examples/config/monitors/evm_transfer_local.json config/monitors/ cp examples/config/triggers/script_notifications.json config/triggers/ mkdir -p config/triggers/scripts cp examples/config/triggers/scripts/custom_notification.sh config/triggers/scripts/ -
Set the contract address in
config/monitors/evm_transfer_local.json: replace the placeholder address (0x0000000000000000000000000000000000000000) with the token address from the deploy output (the hex value from theTestToken 0x...line).
Run the Monitor
From the project root:
cargo runOr, if you have already built and moved the binary to project root:
./openzeppelin-monitorConfirm in the logs that the monitor connects to Anvil and processes blocks.
Tip: While the monitor is running, the Anvil terminal will show repeated RPC calls (eth_blockNumber, eth_getBlockByNumber, eth_getLogs, etc.). This is normal; the monitor polls every few seconds.
To validate configuration without starting the service:
cargo run -- --checkTrigger a Condition
Use Cast to send a transfer so the monitor can match it. Run this in a new terminal (Anvil and the monitor should stay running in their own terminals).
What you need:
| What | Where to find it |
|---|---|
| Token address | Deploy output: the line TestToken 0x... (use the 0x... part) |
| Private key | Anvil startup output: under "Private Keys", the first one (0) |
| Recipient (optional) | Anvil startup: under "Available Accounts", e.g. (1) for the second account |
Note: The private key below is Anvil’s default first-account key. It is for local use only and has no value on mainnet or other real networks.
Command: Replace YOUR_TOKEN_ADDRESS with the address from the deploy output (TestToken 0x...):
cast send YOUR_TOKEN_ADDRESS "transfer(address,uint256)" 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 1000000000000000000 --rpc-url http://127.0.0.1:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80Alternatively, with line continuation:
cast send YOUR_TOKEN_ADDRESS "transfer(address,uint256)" 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 1000000000000000000 \
--rpc-url http://127.0.0.1:8545 \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80When the monitor processes the block containing this transaction, it should fire and run the configured trigger (e.g. script trigger logs in the monitor terminal).
Next Steps
- Adjust
match_conditionsor expressions inevm_transfer_local.jsonto experiment with filters. - Use Testing your configuration (e.g.
--monitor-pathand--network/--block) to test against specific blocks. - See the Quickstart and main Configuration docs for more options.