Tenderly Fork Development for Testing
Tenderly Fork is a managed mainnet fork with REST API for state manipulation: change any address balance, impersonate any account, rewind time, set contract storage directly. Unlike anvil --fork-url, Tenderly Fork lives in the cloud, accessible via URL, and can be shared with your team or handed to auditors.
When to Use Tenderly Fork Instead of Anvil
anvil --fork-url runs locally and dies with the process. Tenderly Fork is created via API, lives until you delete it, and its RPC URL can be passed to frontend or mobile apps.
Real scenario: a vault contract goes to audit. The audit team wants to reproduce a specific attack scenario — draining via flash loan in a particular protocol state. Instead of setting up a local environment for each auditor, you create a Tenderly Fork, pin it to the block with the right state, set up the attacker's balance. The auditor gets one RPC URL.
Creating a Fork via API
const response = await fetch("https://api.tenderly.co/api/v1/account/MY_ACCOUNT/project/MY_PROJECT/fork", {
method: "POST",
headers: {
"X-Access-Key": process.env.TENDERLY_API_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({
network_id: "1", // Ethereum mainnet
block_number: 19500000, // specific block
transaction_index: 0,
initial_balance: 100,
chain_config: {
chain_id: 1
}
})
});
const { simulation_fork } = await response.json();
const forkRpcUrl = `https://rpc.tenderly.co/fork/${simulation_fork.id}`;
State Manipulation via JSON-RPC
Tenderly Fork supports non-standard methods:
// Set address balance
await provider.send("tenderly_setBalance", [
["0xUserAddress"],
"0x56BC75E2D63100000" // 100 ETH in hex
]);
// Impersonate account (sign from its behalf)
await provider.send("tenderly_addBalance", [
["0xWhaleAddress"],
"0xDE0B6B3A7640000"
]);
// Set storage value directly
await provider.send("tenderly_setStorageAt", [
contractAddress,
storageSlot, // keccak256 slot
newValue
]);
// Change timestamp
await provider.send("evm_setNextBlockTimestamp", [futureTimestamp]);
await provider.send("evm_mine", []);
Direct storage writes are a powerful testing tool: set paused = true without calling pause(), simulate a user already invested $1M, bypass cooldown period.
Foundry Integration
# Run tests against Tenderly Fork
forge test --fork-url $TENDERLY_FORK_RPC --fork-block-number 19500000 -vvv
In Foundry tests you can use vm.prank(whale) and vm.deal(attacker, 1000 ether) — they work through Tenderly Fork the same as through anvil. Difference: state persists between runs if the fork isn't recreated.
Transaction Simulation Without Sending
Tenderly Simulate API lets you simulate a transaction and get detailed trace before sending to mainnet:
const simulation = await fetch(`https://api.tenderly.co/api/v1/account/${account}/project/${project}/simulate`, {
method: "POST",
headers: { "X-Access-Key": apiKey },
body: JSON.stringify({
network_id: "1",
from: senderAddress,
to: contractAddress,
input: calldata,
gas: 500000,
value: "0",
save: true // save for dashboard view
})
});
Result — full call trace with gas per call, storage changes, events. Cheaper than actual mainnet deployment for debugging complex scenarios.
Common Scenarios
Testing mainnet upgrade. Fork at the block before upgrade, execute the upgrade transaction, verify all user positions read correctly with the new implementation.
Reproducing incidents. Fork at the block before the breach, reproduce the attack vector. Find root cause. Patch. Verify the patched version is resilient.
Demo for investors. Create a Fork with the desired initial state (users, positions, balances), pass the RPC URL. Investor interacts with the protocol like it's mainnet, but with no real funds.
Timelines
Setting up Tenderly Fork with basic state manipulation scripts: 1 day. Full test infrastructure with automatic fork creation in CI and scenario reproduction: 2-3 days.
Cost is calculated after clarifying test scenario complexity and CI integration requirements.







