T
Temper / docs

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

NetworkChain IDEnv Var
⚡ Tempo Mainnet4217RPC_URL_TEMPO_MAINNET
⚡ Tempo Testnet42429RPC_URL_TEMPO_TESTNET
⚡ Tempo Moderato42431RPC_URL_TEMPO_MODERATO

EVM Mainnets

NetworkChain IDEnv Var
Ethereum1RPC_URL_ETHEREUM
Arbitrum One42161RPC_URL_ARBITRUM
Optimism10RPC_URL_OPTIMISM
Base8453RPC_URL_BASE
Polygon137RPC_URL_POLYGON
BSC56RPC_URL_BSC
Gnosis100RPC_URL_GNOSIS
Avalanche43114RPC_URL_AVALANCHE
HyperEVM999RPC_URL_HYPER
Archive node required for 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.

POST /api/v1/simulate

Request Parameters

FieldTypeDescription
chainId*numberChain ID (e.g. 1 for Ethereum, 4217 for Tempo)
from*addressSender address (0x-prefixed)
toaddressTarget contract address. Omit for contract deployment.
datahexCalldata — function selector + ABI-encoded arguments
valuehexETH value in wei (e.g. "0x0")
gasLimit*hexMaximum gas (e.g. "0x100000")
blockNumbernumberSimulate at a specific block. Defaults to latest.
blockTimestamphexOverride block timestamp
stateOverridesobjectPer-address state overrides. See State Overrides.
formatTracebooleanReturn a human-readable trace string
txHashstringFetch and store a real on-chain tx trace instead of simulating
callsarrayBatch calls for Tempo type 0x76 transactions
feeTokenaddressTIP-20 token used for gas fees (Tempo only)
feePayeraddressSponsor address for fee-delegated transactions (Tempo only)
transactionTypestringSet to "batch" for Tempo 0x76 batch transactions

Response Fields

FieldTypeDescription
idstringUnique simulation ID (nanoid)
simulationUrlstringShareable URL to view the simulation
successbooleanWhether the transaction succeeded
gasUsednumberGas consumed
blockNumbernumberBlock the simulation was run at
returnDatahexRaw return data
exitReasonstringStop, Revert, OutOfGas, etc.
logsarrayEvent logs emitted during execution
enhancedTraceobjectFull call trace tree with nested subcalls
isTempoTxbooleanWhether this is a Tempo chain transaction
transactionTypestring"batch" for 0x76 transactions
feeTokenaddressFee token address (Tempo batch txs)
callResultsarrayPer-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.

POST /api/v1/simulate-bundle

The request body is an array of simulation requests. All transactions must share the same chainId. Returns an array of simulation responses.

One EVM fork — all transactions share a single forked state. The first transaction's 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

  1. Create a session → get a statefulSimulationId
  2. POST transactions to the session ID (state persists between calls)
  3. DELETE the session when done

Create Session

POST/api/v1/simulate-stateful
// Request
{ "chainId": 1, "gasLimit": "0x1000000", "blockNumber": 19234567 }

// Response
{ "statefulSimulationId": "550e8400-e29b-41d4-a716-446655440000" }

Execute Transactions

POST/api/v1/simulate-stateful/{id}

Body is an array of simulation requests. Returns an array of simulation responses. State from previous calls is preserved.

End Session

DELETE/api/v1/simulate-stateful/{id}

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.

GET/api/v1/simulation/{id}

Returns the full simulation response including trace, logs, and metadata.

GET/api/v1/simulation/{id}/trace

Returns only the enhanced call trace tree. Lighter payload for trace-only use cases.

GET/api/v1/simulation/{id}/debug

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).

POST/api/v1/debug

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

GET/api/v1/contract/{chainId}/{address}/source

Returns verified Solidity source for a contract. Tries (in order):

  1. TIP-20 hardcoded interface (for 0x20c0... addresses on Tempo)
  2. Tempo Batch Dispatcher description (for 0x0000... on Tempo)
  3. contracts.tempo.xyz — Tempo's Sourcify instance (Tempo chains only)
  4. Etherscan V2 API (Ethereum, Arbitrum, Base, Optimism, Polygon, BSC)
  5. 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

FieldTypeDescription
balancehexSet ETH balance in wei
noncenumberSet account nonce
codehexReplace deployed bytecode
state.stateDiffobjectSet specific storage slots (others unchanged)
state.stateobjectReplace 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')
        }
      }
    }
  }
};
TIP-20 storage layout differs from standard ERC-20. Slot 9 = balances, but the key encoding uses 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

NetworkChain ID
Tempo Mainnet4217
Tempo Testnet42429
Tempo Moderato42431

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)