Bundled Simulations
Simulation Bundles enable you to simulate several transactions consecutively. For example, to set a particular conditions before simulating a desired transaction, you first need to execute several other transactions.
Both Simulation API and Simulation RPC support bundled simulations. The endpoints receive an array of transactions that get simulated one after another within the same block.
RPC bundled simulations
Tenderly Node allows you to read blockchain data and send transactions. But you can also simulate bundled transactions on the Node through a single RPC URL. Simulations via RPC are only available on supported networks. If your network is not supported on Node, run bundled simulations via Simulation API instead.
Retrieve the RPC URL from the Dashboard.
Go to Node > Copy HTTPS URL of the desired network.
https://mainnet.gateway.tenderly.co/$TENDERLY_NODE_ACCESS_KEY
To simulate bundled transactions via RPC, call the tenderly_simulateBundle
method. This custom method allows you to simulate several transactions by passing them as an array. See RPC reference.
Transactions are simulated one after the other in the same block.
Example
curl https://mainnet.gateway.tenderly.co/$TENDERLY_NODE_ACCESS_KEY \
-X POST \
-H "Content-Type: application/json" \
-d \
'{
"id": 0,
"jsonrpc": "2.0",
"method": "tenderly_simulateBundle",
"params": [
[
{
"from": "0xe2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2",
"to": "0x6b175474e89094c44da98b954eedeac495271d0f",
"data": "0x40c10f19000000000000000000000000e58b9ee93700a616b50509c8292977fa7a0f8ce10000000000000000000000000000000000000000000000001bc16d674ec80000"
},
{
"from": "0xe58b9ee93700a616b50509c8292977fa7a0f8ce1",
"to": "0x6b175474e89094c44da98b954eedeac495271d0f",
"gas": "0x7a1200",
"data": "0x095ea7b3000000000000000000000000f7ddedc66b1d482e5c38e4730b3357d32411e5dd0000000000000000000000000000000000000000000000000de0b6b3a7640000"
},
{
"from": "0xf7ddedc66b1d482e5c38e4730b3357d32411e5dd",
"to": "0x6b175474e89094c44da98b954eedeac495271d0f",
"gas": "0x7a1200",
"data": "0x23b872dd000000000000000000000000e58b9ee93700a616b50509c8292977fa7a0f8ce1000000000000000000000000bd8daa414fda8a8a129f7035e7496759c5af8570000000000000000000000000000000000000000000000000006a94d74f430000"
}
],
"latest",
{
"0x6b175474e89094c44da98b954eedeac495271d0f": {
"stateDiff": {
"0xedd7d04419e9c48ceb6055956cbb4e2091ae310313a4d1fa7cbcfe7561616e03": "0x0000000000000000000000000000000000000000000000000000000000000001"
}
}
}
]
}'
API bundled simulations
Use the simulate-bundle
API endpoint to simulate multiple consecutive transactions at once. See API reference.
https://api.tenderly.co/api/v1/account/${TENDERLY_ACCOUNT_SLUG}/project/${TENDERLY_PROJECT_SLUG}/simulate-bundle
The simulation output shows the emitted events and state changes the transaction might cause if you send it to the blockchain.
In case a transaction fails, there will be an early return with all simulations up until the failure point.
Example
import axios from 'axios';
import * as dotenv from 'dotenv';
import { ethers } from 'ethers';
dotenv.config();
// assuming environment variables TENDERLY_ACCOUNT_SLUG, TENDERLY_PROJECT_SLUG and TENDERLY_ACCESS_KEY are set
const { TENDERLY_ACCOUNT_SLUG, TENDERLY_PROJECT_SLUG, TENDERLY_ACCESS_KEY } = process.env;
const batchedSimulations = async () => {
console.time('Batch Simulation');
const daiSequence = (
await axios.post(
`https://api.tenderly.co/api/v1/account/${TENDERLY_ACCOUNT_SLUG}/project/${TENDERLY_PROJECT_SLUG}/simulate-bundle`,
// the transaction
{
simulations: getTxSequence().map((transaction) => ({
network_id: '1', // network to simulate on
save: true,
save_if_fails: true,
simulation_type: 'full',
...transaction,
})),
},
{
headers: {
'X-Access-Key': TENDERLY_ACCESS_KEY as string,
},
},
)
).data;
console.timeEnd('Batch Simulation');
console.log(JSON.stringify(daiSequence, null, 2));
};
function getTxSequence() {
const fakeWardAddress = '0xe2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2';
const daiOwner = 'e58b9ee93700a616b50509c8292977fa7a0f8ce1';
const daiSpend = 'f7ddedc66b1d482e5c38e4730b3357d32411e5dd';
const daiRecip = 'e58b9ee93700a616b50509c8292977fa7a0f8ce1';
const daiContract = '0x6b175474e89094c44da98b954eedeac495271d0f';
return [
// TX1: Mint 2 DAI for e58b9ee93700a616b50509c8292977fa7a0f8ce1.
// Must do state override so sender (from) is considered a ward for this simulation
{
from: fakeWardAddress,
to: '0x6b175474e89094c44da98b954eedeac495271d0f',
input:
'0x40c10f19000000000000000000000000e58b9ee93700a616b50509c8292977fa7a0f8ce10000000000000000000000000000000000000000000000001bc16d674ec80000',
// make 0xcace477c66b1f2e151974eeada9ac8b95a31c50593ae1db2f048f0a2b54c1424 a ward
state_objects: {
'0x6b175474e89094c44da98b954eedeac495271d0f': {
storage: {
'0xedd7d04419e9c48ceb6055956cbb4e2091ae310313a4d1fa7cbcfe7561616e03':
'0x0000000000000000000000000000000000000000000000000000000000000001',
},
},
},
},
// TX2: e58b9ee93700a616b50509c8292977fa7a0f8ce1 approves 1 DAI to f7ddedc66b1d482e5c38e4730b3357d32411e5dd
{
from: '0xe58b9ee93700a616b50509c8292977fa7a0f8ce1',
to: '0x6b175474e89094c44da98b954eedeac495271d0f',
input:
'0x095ea7b3000000000000000000000000f7ddedc66b1d482e5c38e4730b3357d32411e5dd0000000000000000000000000000000000000000000000000de0b6b3a7640000',
},
{
// TX3: 0xf7ddedc66b1d482e5c38e4730b3357d32411e5dd calls transferFrom to transfer 0.5 DAI belonging to e58b9ee93700a616b50509c8292977fa7a0f8ce1, sending them to e58b9ee93700a616b50509c8292977fa7a0f8ce1
from: '0xf7ddedc66b1d482e5c38e4730b3357d32411e5dd',
to: '0x6b175474e89094c44da98b954eedeac495271d0f',
input:
'0x23b872dd000000000000000000000000e58b9ee93700a616b50509c8292977fa7a0f8ce1000000000000000000000000bd8daa414fda8a8a129f7035e7496759c5af8570000000000000000000000000000000000000000000000000006a94d74f430000',
},
];
}
function getWardStorageLocation(wardAddress: string) {
// calculate the storage location of `wards[wardAddress]`
// yields 0xedd7d04419e9c48ceb6055956cbb4e2091ae310313a4d1fa7cbcfe7561616e03
return ethers.utils.keccak256(
ethers.utils.concat([
ethers.utils.hexZeroPad(wardAddress, 32), // the ward address (address 0x000..0) - mapping key
ethers.utils.hexZeroPad('0x0', 32), // the wards slot is 0th in the DAI contract - the mapping variable
]),
);
}
batchedSimulations();
Explore use cases
- Asset and balance changes: Get exact dollar values for all balance and asset changes that will happen.
- Gas estimation: Accurately predict the gas costs before sending the transaction.
- State overrides: Modify blockchain conditions like timestamps and contract data to test different scenarios.
- Preview transaction outcomes: Identify and fix issues that could cause transactions to fail.