Temper
High-performance EVM transaction simulator. Preview outcomes, inspect traces, and debug failures — without spending gas.
⚡ Fast Simulations
Simulate transactions in milliseconds with an optimized Rust backend forking from live chain state.
🔍 Visual Debugger
Step through execution with source code highlighting, decoded parameters, and call stack visualization.
🌐 Multi-Chain
Ethereum, Polygon, Arbitrum, Base, Optimism, BSC, Tempo and more — all from one API.
🛠 State Overrides
Test with custom balances, storage slots, and contract bytecode before anything hits the chain.
Base URL
https://temper-api.onrender.com/api/v1
Authentication
No API key is required at this time. All endpoints are publicly accessible.
If a key is configured on the server in the future, include it as:
X-API-KEY: your-api-key
Response Format
All endpoints return JSON. Field names are camelCase. Hex values use the 0x prefix.
Quick Start
Simulate your first transaction in under a minute.
1. Simulate a Token Transfer
Simulate a USDC transfer on Ethereum:
curl -X POST https://temper-api.onrender.com/api/v1/simulate \
-H "Content-Type: application/json" \
-d '{
"chainId": 1,
"from": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"to": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"data": "0xa9059cbb000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa960450000000000000000000000000000000000000000000000000000000005f5e100",
"gasLimit": "0x100000",
"value": "0x0"
}'
const USDC = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
const sender = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045';
const data = '0xa9059cbb'
+ sender.slice(2).padStart(64, '0')
+ (100_000_000).toString(16).padStart(64, '0'); // 100 USDC
const result = await fetch('https://temper-api.onrender.com/api/v1/simulate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
chainId: 1,
from: sender,
to: USDC,
data,
gasLimit: '0x100000',
value: '0x0'
})
}).then(r => r.json());
console.log('Success:', result.success);
console.log('Gas used:', result.gasUsed);
console.log('View:', result.simulationUrl);
import requests
USDC = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'
sender = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'
data = '0xa9059cbb' + sender[2:].zfill(64) + hex(100_000_000)[2:].zfill(64)
result = requests.post(
'https://temper-api.onrender.com/api/v1/simulate',
json={
'chainId': 1,
'from': sender,
'to': USDC,
'data': data,
'gasLimit': '0x100000',
'value': '0x0'
}
).json()
print('Success:', result['success'])
print('View:', result['simulationUrl'])
2. Inspect the Response
{
"id": "mHmgzXD28j18r8pnMDota",
"simulationUrl": "https://temper-api.onrender.com/simulation/mHmgzXD28j18r8pnMDota",
"chainId": 1,
"success": true,
"gasUsed": 51234,
"blockNumber": 19234567,
"returnData": "0x0000000000000000000000000000000000000000000000000000000000000001",
"exitReason": "Stop",
"logs": [ { "address": "0xa0b8...", "topics": ["0xddf2..."], "data": "0x..." } ],
"enhancedTrace": { "type": "CALL", "from": "0xd8da...", "calls": [] }
}
3. View in Browser
Every simulation gets a shareable simulationUrl. Open it to see the full trace, decoded calls, token transfers, and more. Click Open Debugger from any simulation page for step-by-step source navigation.
4. Test with State Overrides
Give any address a custom balance or storage state before simulating:
{
"chainId": 1,
"from": "0x0000000000000000000000000000000000000001",
"to": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"data": "0xa9059cbb...",
"gasLimit": "0x100000",
"value": "0x0",
"stateOverrides": {
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48": {
"state": {
"stateDiff": {
"0x<balance_slot>": "0x0000000000000000000000000000000000000000000000000000000005f5e100"
}
}
}
}
}
Supported Chains
Configure RPC URLs via environment variables — any chain with a compatible JSON-RPC node works.
Tempo Chains
| Network | Chain ID | Env Var |
|---|---|---|
| ⚡ Tempo Mainnet | 4217 | RPC_URL_TEMPO_MAINNET |
| ⚡ Tempo Testnet | 42429 | RPC_URL_TEMPO_TESTNET |
| ⚡ Tempo Moderato | 42431 | RPC_URL_TEMPO_MODERATO |
EVM Mainnets
| Network | Chain ID | Env Var |
|---|---|---|
| Ethereum | 1 | RPC_URL_ETHEREUM |
| Arbitrum One | 42161 | RPC_URL_ARBITRUM |
| Optimism | 10 | RPC_URL_OPTIMISM |
| Base | 8453 | RPC_URL_BASE |
| Polygon | 137 | RPC_URL_POLYGON |
| BSC | 56 | RPC_URL_BSC |
| Gnosis | 100 | RPC_URL_GNOSIS |
| Avalanche | 43114 | RPC_URL_AVALANCHE |
| HyperEVM | 999 | RPC_URL_HYPER |
debug_traceTransaction and historical block simulation. Alchemy Growth+ or QuickNode Build+ recommended.
Simulate Transaction
Simulate a single EVM transaction and get execution results, gas usage, logs, and a shareable trace URL.
Request Parameters
| Field | Type | Description |
|---|---|---|
| chainId* | number | Chain ID (e.g. 1 for Ethereum, 4217 for Tempo) |
| from* | address | Sender address (0x-prefixed) |
| to | address | Target contract address. Omit for contract deployment. |
| data | hex | Calldata — function selector + ABI-encoded arguments |
| value | hex | ETH value in wei (e.g. "0x0") |
| gasLimit* | hex | Maximum gas (e.g. "0x100000") |
| blockNumber | number | Simulate at a specific block. Defaults to latest. |
| blockTimestamp | hex | Override block timestamp |
| stateOverrides | object | Per-address state overrides. See State Overrides. |
| formatTrace | boolean | Return a human-readable trace string |
| txHash | string | Fetch and store a real on-chain tx trace instead of simulating |
| calls | array | Batch calls for Tempo type 0x76 transactions |
| feeToken | address | TIP-20 token used for gas fees (Tempo only) |
| feePayer | address | Sponsor address for fee-delegated transactions (Tempo only) |
| transactionType | string | Set to "batch" for Tempo 0x76 batch transactions |
Response Fields
| Field | Type | Description |
|---|---|---|
| id | string | Unique simulation ID (nanoid) |
| simulationUrl | string | Shareable URL to view the simulation |
| success | boolean | Whether the transaction succeeded |
| gasUsed | number | Gas consumed |
| blockNumber | number | Block the simulation was run at |
| returnData | hex | Raw return data |
| exitReason | string | Stop, Revert, OutOfGas, etc. |
| logs | array | Event logs emitted during execution |
| enhancedTrace | object | Full call trace tree with nested subcalls |
| isTempoTx | boolean | Whether this is a Tempo chain transaction |
| transactionType | string | "batch" for 0x76 transactions |
| feeToken | address | Fee token address (Tempo batch txs) |
| callResults | array | Per-call results for batch transactions |
Simulate Bundle
Simulate an ordered sequence of transactions sharing a single forked EVM. State from each transaction is visible to the next.
The request body is an array of simulation requests. All transactions must share the same chainId. Returns an array of simulation responses.
blockNumber sets the fork point for the entire bundle.
Example: Approve + Swap
curl -X POST https://temper-api.onrender.com/api/v1/simulate-bundle \
-H "Content-Type: application/json" \
-d '[
{
"chainId": 1,
"from": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"to": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"data": "0x095ea7b3000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff0000000000000000000000000000000000000000000000000000000005f5e100",
"gasLimit": "0x100000",
"value": "0x0"
},
{
"chainId": 1,
"from": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"to": "0xdef1c0ded9bec7f1a1670819833240f027b25eff",
"data": "0x...",
"gasLimit": "0x200000",
"value": "0x0"
}
]'
const results = await fetch('https://temper-api.onrender.com/api/v1/simulate-bundle', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify([
{ chainId: 1, from, to: USDC, data: encodeApprove(ROUTER, amount), gasLimit: '0x100000', value: '0x0' },
{ chainId: 1, from, to: ROUTER, data: encodeSwap(...), gasLimit: '0x200000', value: '0x0' }
])
}).then(r => r.json());
const totalGas = results.reduce((s, r) => s + r.gasUsed, 0);
console.log(`Both succeeded: ${results.every(r => r.success)}`);
console.log(`Total gas: ${totalGas}`);
Stateful Simulation
Create a persistent forked EVM session. Execute multiple independent API calls against the same state — perfect for interactive testing.
Workflow
- Create a session → get a
statefulSimulationId - POST transactions to the session ID (state persists between calls)
- DELETE the session when done
Create Session
// Request
{ "chainId": 1, "gasLimit": "0x1000000", "blockNumber": 19234567 }
// Response
{ "statefulSimulationId": "550e8400-e29b-41d4-a716-446655440000" }
Execute Transactions
Body is an array of simulation requests. Returns an array of simulation responses. State from previous calls is preserved.
End Session
Frees the in-memory EVM. Always call this when done.
Full Example
// 1. Create
const { statefulSimulationId } = await fetch('/api/v1/simulate-stateful', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ chainId: 1, gasLimit: '0x1000000' })
}).then(r => r.json());
// 2. Approve
const [approve] = await fetch(`/api/v1/simulate-stateful/${statefulSimulationId}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify([{ chainId: 1, from, to: USDC, data: encodeApprove(ROUTER, MAX), gasLimit: '0x100000', value: '0x0' }])
}).then(r => r.json());
// 3. Swap — sees the approval from step 2
const [swap] = await fetch(`/api/v1/simulate-stateful/${statefulSimulationId}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify([{ chainId: 1, from, to: ROUTER, data: encodeSwap(...), gasLimit: '0x200000', value: '0x0' }])
}).then(r => r.json());
// 4. Clean up
await fetch(`/api/v1/simulate-stateful/${statefulSimulationId}`, { method: 'DELETE' });
Retrieve Simulation
Fetch stored simulation results by ID. Simulations are stored when a database is configured.
Returns the full simulation response including trace, logs, and metadata.
Returns only the enhanced call trace tree. Lighter payload for trace-only use cases.
Returns the step-by-step debug trace (requires the simulation to have a txHash).
Web Viewer
Every simulation with storage enabled gets a shareable viewer page:
https://temper-api.onrender.com/simulation/{id}
Shows transaction overview, call trace, decoded events, token transfers, and links to the debugger.
Debug Trace
Fetch a step-by-step execution trace for a transaction using debug_traceTransaction (callTracer).
Request
{
"txHash": "0x97853001b7c7974d556953bc27af6a0b3d6931d313df6340a150e978754990f9",
"chainId": 4217
}
Response
Returns a DebugTrace with the full call tree. Used by the in-browser step debugger.
{
"gas": 300532,
"failed": false,
"returnValue": "0x",
"structLogs": [],
"callTree": {
"type": "CALL",
"from": "0x0000000000000000000000000000000000000000",
"to": "0x0000000000000000000000000000000000000000",
"calls": [
{
"type": "CALL",
"from": "0x980a...",
"to": "0x20c0...",
"input": "0x23b872dd...",
"output": "0x0000...0001",
"gasUsed": "0x44034"
}
]
}
}
Contract Source
Returns verified Solidity source for a contract. Tries (in order):
- TIP-20 hardcoded interface (for
0x20c0...addresses on Tempo) - Tempo Batch Dispatcher description (for
0x0000...on Tempo) contracts.tempo.xyz— Tempo's Sourcify instance (Tempo chains only)- Etherscan V2 API (Ethereum, Arbitrum, Base, Optimism, Polygon, BSC)
- Public Sourcify (
sourcify.dev/server) — all chains
State Overrides
Override the on-chain state of any address before simulation runs. Useful for testing without needing real balances or deploying contracts.
Schema
The stateOverrides field is a map of address → override:
"stateOverrides": {
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48": {
"balance": "0xde0b6b3a7640000",
"nonce": 5,
"code": "0x608060...",
"state": {
"stateDiff": {
"0x<slot>": "0x<value>"
}
}
}
}
Override Fields
| Field | Type | Description |
|---|---|---|
| balance | hex | Set ETH balance in wei |
| nonce | number | Set account nonce |
| code | hex | Replace deployed bytecode |
| state.stateDiff | object | Set specific storage slots (others unchanged) |
| state.state | object | Replace entire storage (clears all other slots) |
Example: Custom Token Balance
Compute the ERC-20 balanceOf storage slot and override it:
// ERC-20 balances mapping is at slot 9 (standard layout)
// slot = keccak256(abi.encode(address, uint256(9)))
import { keccak256, encodeAbiParameters } from 'viem';
const balanceSlot = keccak256(encodeAbiParameters(
[{ type: 'address' }, { type: 'uint256' }],
[senderAddress, 9n]
));
const body = {
chainId: 1,
from: sender,
to: USDC,
data: encodeTransfer(recipient, 1_000_000n),
gasLimit: '0x100000',
value: '0x0',
stateOverrides: {
[USDC]: {
state: {
stateDiff: {
[balanceSlot]: '0x' + (10_000_000n).toString(16).padStart(64, '0')
}
}
}
}
};
keccak256(address || uint256(9)) — same Solidity mapping layout, same slot number.
Tempo & TIP-20 ⚡ Tempo
Temper has native support for Tempo's transaction model including TIP-20 token precompiles and type 0x76 batch transactions.
Tempo Chains
| Network | Chain ID |
|---|---|
| Tempo Mainnet | 4217 |
| Tempo Testnet | 42429 |
| Tempo Moderato | 42431 |
TIP-20 Native Tokens
TIP-20 tokens are Tempo's native token standard. Their contract addresses all start with 0x20c0. They implement the full ERC-20 interface but are handled at the protocol level — not as deployed EVM bytecode.
- Always 6 decimals
- Standard ERC-20 function selectors (
transfer,approve,transferFrom, etc.) - Storage layout: slot 8 = totalSupply, slot 9 = balances, slot 10 = allowances
- Temper automatically injects a compatible ERC-20 bytecode at these addresses so simulations work correctly
Type 0x76 Batch Transactions
Tempo's native batch transaction type. Multiple calls execute atomically in one transaction, with optional fee sponsorship via a TIP-20 token.
{
"chainId": 4217,
"from": "0x60d770...",
"gasLimit": "0xf4240",
"transactionType": "batch",
"feeToken": "0x20c000000000000000000000b9537d11c60e8b50",
"calls": [
{ "to": "0x20c0...", "data": "0x70a08231..." },
{ "to": "0x20c0...", "data": "0xa9059cbb..." },
{ "to": "0xABC...", "data": "0x...", "value": "0x0" }
]
}
Batch Response
The response includes per-call results in callResults:
{
"success": true,
"isTempoTx": true,
"transactionType": "batch",
"feeToken": "0x20c000000000000000000000b9537d11c60e8b50",
"gasUsed": 65847,
"callResults": [
{ "success": true, "gasUsed": 21240, "returnData": "0x0000...0006" },
{ "success": true, "gasUsed": 32000, "returnData": "0x0000...0001" },
{ "success": true, "gasUsed": 12607, "returnData": "0x" }
]
}
Fetch Real On-Chain Tx
Pass a txHash to fetch and store the actual trace from the chain via debug_traceTransaction:
{
"chainId": 4217,
"from": "0x0000000000000000000000000000000000000000",
"gasLimit": "0x7a120",
"txHash": "0x97853001b7c7974d556953bc27af6a0b3d6931d313df6340a150e978754990f9"
}
Contract Source on Tempo
The /contract/{chainId}/{address}/source endpoint resolves sources for Tempo addresses:
- 0x20c0... addresses → returns the TIP-20 interface (hardcoded, always available)
- 0x0000... (zero address) → returns the Tempo Batch Dispatcher description
- Verified contracts → fetched from
contracts.tempo.xyz(Tempo's Sourcify instance)