All Products

Simulations
Bundled Simulations

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.

example
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

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.

example
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

request.ts
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

Where to go next