RPC 捆绑模拟
Node RPC 允许您读取区块链数据并发送交易。但您也可以通过单个 RPC URL 在 Node 上模拟捆绑交易。通过 RPC 进行的模拟仅在受支持的网络上可用。如果您的网络不受 Node 支持,请改为通过 Simulation API 运行捆绑模拟。 从 Dashboard 获取 RPC URL。 前往 Node > Copy HTTPS URL 复制所需网络的 URL。example
https://mainnet.gateway.tenderly.co/$TENDERLY_NODE_ACCESS_KEY
tenderly_simulateBundle 方法。该自定义方法允许您通过将多笔交易作为数组传递来对其进行模拟。请参阅 RPC 参考。
交易会在同一区块内依次进行模拟。
- example
- response.json
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"
}
}
}
]
}'
response.json
{
"id": 0,
"jsonrpc": "2.0",
"result": [
{
"status": true,
"gasUsed": "0xd0d9",
"cumulativeGasUsed": "0x0",
"blockNumber": "0x121c950",
"type": "0x0",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000010000000020000000000000000000800000000000000000000000010000000000000000000004000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000020000000002000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000",
"logs": [
{
"name": "Transfer",
"anonymous": false,
"inputs": [
{
"value": "0x0000000000000000000000000000000000000000",
"type": "address",
"name": "src"
},
{
"value": "0xe58b9ee93700a616b50509c8292977fa7a0f8ce1",
"type": "address",
"name": "dst"
},
{
"value": "2000000000000000000",
"type": "uint256",
"name": "wad"
}
],
"raw": {
"address": "0x6b175474e89094c44da98b954eedeac495271d0f",
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0x000000000000000000000000e58b9ee93700a616b50509c8292977fa7a0f8ce1"
],
"data": "0x0000000000000000000000000000000000000000000000001bc16d674ec80000"
}
}
],
"trace": [
{
"type": "CALL",
"from": "0xe2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2",
"to": "0x6b175474e89094c44da98b954eedeac495271d0f",
"gas": "0xbeb6d8c",
"gasUsed": "0x7c65",
"value": "0x0",
"input": "0x40c10f19000000000000000000000000e58b9ee93700a616b50509c8292977fa7a0f8ce10000000000000000000000000000000000000000000000001bc16d674ec80000",
"decodedInput": [
{
"value": "0xe58b9ee93700a616b50509c8292977fa7a0f8ce1",
"type": "address",
"name": "usr"
},
{
"value": "2000000000000000000",
"type": "uint256",
"name": "wad"
}
],
"method": "mint",
"output": "0x",
"subtraces": 0,
"traceAddress": []
}
],
"assetChanges": [
{
"assetInfo": {
"standard": "ERC20",
"type": "Fungible",
"contractAddress": "0x6b175474e89094c44da98b954eedeac495271d0f",
"symbol": "dai",
"name": "Dai",
"logo": "https://assets.coingecko.com/coins/images/9956/large/Badge_Dai.webp?1696509996",
"decimals": 18,
"dollarValue": "0.9996880292892456"
},
"type": "Mint",
"to": "0xe58b9ee93700a616b50509c8292977fa7a0f8ce1",
"rawAmount": "0x1bc16d674ec80000",
"amount": "2",
"dollarValue": "1.9993760585784912109"
}
],
"balanceChanges": [
{
"address": "0xe58b9ee93700a616b50509c8292977fa7a0f8ce1",
"dollarValue": "1.9993760585784912109",
"transfers": [
0
]
}
]
},
{
"status": true,
"gasUsed": "0xb442",
"cumulativeGasUsed": "0x0",
"blockNumber": "0x121c950",
"type": "0x0",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000200000000000000000000000000008000000000000000000000000000000000000000010000000000000000000020000000000000000000000000000000000000000000000000000004000800000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000002000000000000000000000000100010000000000000000000000000000000000000000000000000000000000000",
"logs": [
{
"name": "Approval",
"anonymous": false,
"inputs": [
{
"value": "0xe58b9ee93700a616b50509c8292977fa7a0f8ce1",
"type": "address",
"name": "src"
},
{
"value": "0xf7ddedc66b1d482e5c38e4730b3357d32411e5dd",
"type": "address",
"name": "guy"
},
{
"value": "1000000000000000000",
"type": "uint256",
"name": "wad"
}
],
"raw": {
"address": "0x6b175474e89094c44da98b954eedeac495271d0f",
"topics": [
"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925",
"0x000000000000000000000000e58b9ee93700a616b50509c8292977fa7a0f8ce1",
"0x000000000000000000000000f7ddedc66b1d482e5c38e4730b3357d32411e5dd"
],
"data": "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000"
}
}
],
"trace": [
{
"type": "CALL",
"from": "0xe58b9ee93700a616b50509c8292977fa7a0f8ce1",
"to": "0x6b175474e89094c44da98b954eedeac495271d0f",
"gas": "0x79bd80",
"gasUsed": "0x5fc2",
"value": "0x0",
"input": "0x095ea7b3000000000000000000000000f7ddedc66b1d482e5c38e4730b3357d32411e5dd0000000000000000000000000000000000000000000000000de0b6b3a7640000",
"decodedInput": [
{
"value": "0xf7ddedc66b1d482e5c38e4730b3357d32411e5dd",
"type": "address",
"name": "usr"
},
{
"value": "1000000000000000000",
"type": "uint256",
"name": "wad"
}
],
"method": "approve",
"output": "0x0000000000000000000000000000000000000000000000000000000000000001",
"decodedOutput": [
{
"value": true,
"type": "bool",
"name": ""
}
],
"subtraces": 0,
"traceAddress": []
}
]
},
{
"status": true,
"gasUsed": "0xe38b",
"cumulativeGasUsed": "0x0",
"blockNumber": "0x121c950",
"type": "0x0",
"logsBloom": "0x00000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000010000000000000000000004000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000002000000000000000000020000000002000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000",
"logs": [
{
"name": "Transfer",
"anonymous": false,
"inputs": [
{
"value": "0xe58b9ee93700a616b50509c8292977fa7a0f8ce1",
"type": "address",
"name": "src"
},
{
"value": "0xbd8daa414fda8a8a129f7035e7496759c5af8570",
"type": "address",
"name": "dst"
},
{
"value": "30000000000000000",
"type": "uint256",
"name": "wad"
}
],
"raw": {
"address": "0x6b175474e89094c44da98b954eedeac495271d0f",
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x000000000000000000000000e58b9ee93700a616b50509c8292977fa7a0f8ce1",
"0x000000000000000000000000bd8daa414fda8a8a129f7035e7496759c5af8570"
],
"data": "0x000000000000000000000000000000000000000000000000006a94d74f430000"
}
}
],
"trace": [
{
"type": "CALL",
"from": "0xf7ddedc66b1d482e5c38e4730b3357d32411e5dd",
"to": "0x6b175474e89094c44da98b954eedeac495271d0f",
"gas": "0x79bc28",
"gasUsed": "0x8db3",
"value": "0x0",
"input": "0x23b872dd000000000000000000000000e58b9ee93700a616b50509c8292977fa7a0f8ce1000000000000000000000000bd8daa414fda8a8a129f7035e7496759c5af8570000000000000000000000000000000000000000000000000006a94d74f430000",
"decodedInput": [
{
"value": "0xe58b9ee93700a616b50509c8292977fa7a0f8ce1",
"type": "address",
"name": "src"
},
{
"value": "0xbd8daa414fda8a8a129f7035e7496759c5af8570",
"type": "address",
"name": "dst"
},
{
"value": "30000000000000000",
"type": "uint256",
"name": "wad"
}
],
"method": "transferFrom",
"output": "0x0000000000000000000000000000000000000000000000000000000000000001",
"decodedOutput": [
{
"value": true,
"type": "bool",
"name": ""
}
],
"subtraces": 0,
"traceAddress": []
}
],
"assetChanges": [
{
"assetInfo": {
"standard": "ERC20",
"type": "Fungible",
"contractAddress": "0x6b175474e89094c44da98b954eedeac495271d0f",
"symbol": "dai",
"name": "Dai",
"logo": "https://assets.coingecko.com/coins/images/9956/large/Badge_Dai.webp?1696509996",
"decimals": 18,
"dollarValue": "0.9996880292892456"
},
"type": "Transfer",
"from": "0xe58b9ee93700a616b50509c8292977fa7a0f8ce1",
"to": "0xbd8daa414fda8a8a129f7035e7496759c5af8570",
"rawAmount": "0x6a94d74f430000",
"amount": "0.03",
"dollarValue": "0.029990640878677368163"
}
],
"balanceChanges": [
{
"address": "0xbd8daa414fda8a8a129f7035e7496759c5af8570",
"dollarValue": "0.029990640878677368163",
"transfers": [
0
]
},
{
"address": "0xe58b9ee93700a616b50509c8292977fa7a0f8ce1",
"dollarValue": "-0.029990640878677368163",
"transfers": [
0
]
}
]
}
]
}
API 捆绑模拟
使用simulate-bundle API 端点一次性模拟多笔连续的交易。请参阅 API 参考。
example
https://api.tenderly.co/api/v1/account/${TENDERLY_ACCOUNT_SLUG}/project/${TENDERLY_PROJECT_SLUG}/simulate-bundle
- Shell
- request.ts
curl --request POST \
--url https://api.tenderly.co/api/v1/account/me/project/project/simulate-bundle \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header "X-Access-Key: $TENDERLY_ACCESS_KEY" \
--data '{
"simulations": [
{
"network_id": "1",
"save": true,
"save_if_fails": true,
"simulation_type": "full",
"from": "0xe2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2",
"to": "0x6b175474e89094c44da98b954eedeac495271d0f",
"input": "0x40c10f19000000000000000000000000e58b9ee93700a616b50509c8292977fa7a0f8ce10000000000000000000000000000000000000000000000001bc16d674ec80000",
"state_objects": {
"0x6b175474e89094c44da98b954eedeac495271d0f": {
"storage": {
"0xedd7d04419e9c48ceb6055956cbb4e2091ae310313a4d1fa7cbcfe7561616e03": "0x0000000000000000000000000000000000000000000000000000000000000001"
}
}
}
},
{
"network_id": "1",
"save": true,
"save_if_fails": true,
"simulation_type": "full",
"from": "0xe58b9ee93700a616b50509c8292977fa7a0f8ce1",
"to": "0x6b175474e89094c44da98b954eedeac495271d0f",
"input": "0x095ea7b3000000000000000000000000f7ddedc66b1d482e5c38e4730b3357d32411e5dd0000000000000000000000000000000000000000000000000de0b6b3a7640000"
},
{
"network_id": "1",
"save": true,
"save_if_fails": true,
"simulation_type": "full",
"from": "0xf7ddedc66b1d482e5c38e4730b3357d32411e5dd",
"to": "0x6b175474e89094c44da98b954eedeac495271d0f",
"input": "0x23b872dd000000000000000000000000e58b9ee93700a616b50509c8292977fa7a0f8ce1000000000000000000000000bd8daa414fda8a8a129f7035e7496759c5af8570000000000000000000000000000000000000000000000000006a94d74f430000"
}
]
}'
request.ts
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();
探索使用场景
- 资产与余额变化:获取即将发生的所有余额与资产变化的确切美元价值。
- Gas 估算:在发送交易之前准确预测 gas 费用。
- 状态覆盖:修改时间戳、合约数据等区块链状态,以测试不同的场景。
- 预览交易结果:识别并修复可能导致交易失败的问题。