Lean EVM transaction simulator. Simulates what a transaction would do without sending it.
Works on any EVM chain. Supports both standard EVM and EraVM (ZkSync stack) chains.
Decodes: native transfers, ERC-20/721/1155 transfers & approvals, setApprovalForAll, Permit/Permit2, internal calls, contract creations, selfdestructs, revert reasons.
| Engine | Chains | How |
|---|---|---|
| revm | Ethereum, Base, Arbitrum, Optimism, Polygon, BSC, Avalanche, etc. | eth_createAccessList → parallel state prefetch → in-memory EVM execution with inspector |
| RPC tracing | ZkSync Era, Lens, Cronos zkEVM, Zircuit, etc. | Parallel eth_call + debug_traceCall → calldata-level effect decoding |
Engine is auto-selected by chain ID. The revm path gives full event log output. The RPC path decodes effects from calldata since EraVM's callTracer doesn't emit logs.
npm install simulate-tximport { simulate, simulateFromObject } from 'simulate-tx';
// Simulate a USDC approve on Ethereum
const result = await simulate('https://eth.llamarpc.com', JSON.stringify({
from: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
to: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
data: '0x095ea7b3...',
value: '0x0',
chain_id: 1,
}));
// Or pass a JS object directly
const result = await simulateFromObject('https://base-rpc.publicnode.com', {
from: '0x...',
to: '0x...',
data: '0x...',
value: '0x0',
chain_id: 8453, // Base
});
console.log(result.success); // true
console.log(result.effects); // [{ type: "Erc20Approval", token: "0x...", ... }]
console.log(result.gas_used); // 55000cargo build --release
./target/release/rtxsimulator-cli \
--from 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 \
--to 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 \
--data 0x095ea7b3... \
--value 0 \
--chain-id 1 \
--rpc-url https://eth.llamarpc.com--rpc-url can also be set via RPC_URL env var.
[dependencies]
rtxsimulator = { git = "https://github.com/kkpsiren/rtxsimulator" }use rtxsimulator::{simulate, SimulationRequest};
let req = SimulationRequest {
from: "0xd8dA...".parse()?,
to: Some("0xA0b8...".parse()?),
data: "0x095ea7b3...".parse()?,
value: U256::ZERO,
chain_id: 1,
block_number: None,
gas_limit: None,
};
let result = simulate(&req, "https://eth.llamarpc.com").await?;{
"success": true,
"gas_used": 55582,
"return_data": "0x...0001",
"revert_reason": null,
"effects": [
{
"type": "Erc20Approval",
"token": "0xa0b8...",
"owner": "0xd8da...",
"spender": "0x6e4c...",
"value": "0x2386f26fc10000"
}
],
"logs": [...],
"calls": [...]
}NativeTransfer— ETH/native token transferErc20Transfer/Erc20Approval— ERC-20 transfer and approveErc721Transfer— NFT transferErc1155TransferSingle/Erc1155TransferBatch— multi-token transfersApprovalForAll— operator grant (ERC-721/1155)Permit— EIP-2612 permitPermit2Approval— Uniswap Permit2ContractCreated— new contract deployedSelfDestruct— contract self-destructed
| Field | Type | Required | Description |
|---|---|---|---|
from |
address | yes | Sender |
to |
address | no | Recipient (omit for contract creation) |
data |
hex bytes | yes | Calldata |
value |
uint256 | yes | Wei to send |
chain_id |
u64 | yes | Chain ID |
block_number |
u64 | no | Block to simulate against (default: latest) |
gas_limit |
u64 | no | Gas limit override (default: 30M) |
approve() simulation with public RPCs (cold start, no local node):
| Chain | Engine | Time |
|---|---|---|
| EraVM chain | RPC tracing | ~400ms |
| Ethereum | revm | ~780ms |
Bottleneck is RPC latency. With a local node, revm execution is <10ms.
- Call
eth_createAccessListto discover all addresses + storage slots the tx touches - Fetch all account state (nonce, balance, code) and storage slots in parallel
- Populate an in-memory
CacheDBwith the pre-fetched state - Execute the tx in revm with a custom
Inspectorthat collects logs, internal calls, and selfdestructs - Decode event logs into typed effects (Transfer, Approval, etc.)
- Fire
eth_callanddebug_traceCallconcurrently - Walk the call trace tree, filtering out system contract noise
- Decode effects from calldata (since EraVM's callTracer doesn't emit event logs)
The npm package is a WebAssembly binary compiled from Rust. It runs in Node.js, Deno, and browsers.
- revm uses pure-Rust crypto on WASM (no C FFI)
- HTTP uses the browser
fetchAPI via reqwest + wasm-bindgen - Binary size: ~2.2MB (after
wasm-opt)
Build from source:
# Node.js
wasm-pack build crates/wasm --target nodejs --release
# Browser / ESM
wasm-pack build crates/wasm --target web --release
# npm bundlers (webpack, vite)
wasm-pack build crates/wasm --target bundler --releasecrates/core → Rust library (revm + RPC simulator, decoders, types)
crates/cli → Native CLI binary
crates/wasm → WebAssembly package (npm: simulate-tx)
MIT