Temper is an Ethereum Transaction Simulator. It provides a super simple HTTP API which simulates a given transaction request against a local EVM.
Simulates a single transaction against a local EVM.
See the full request and response types below.
Example body:
{
"chainId": 1,
"from": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"to": "0x66fc62c1748e45435b06cf8dd105b73e9855f93e",
"data": "0xffa2ca3b44eea7c8e659973cbdf476546e9e6adfd1c580700537e52ba7124933a97904ea000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001d0e30db00300ffffffffffffc02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000186a0",
"gasLimit": 500000,
"value": "100000",
"blockNumber": 16784600
}Example response:
{
"gasUsed": 214622,
"blockNumber": 16784600,
"success": true,
"trace": { ... },
"logs": [ ... ],
"exitReason": "Return"
}Notes:
blockNumbercan be omitted and the latest block will be used, however providing ablockNumberis recommended where possible to use the cache.- Simulation requests may include
request_id; responses echo the same value, ornullwhen omitted.
Simulates a bundle of transactions.
The server first attempts to simulate every transaction through QuickNode. If any transaction cannot be handled by QuickNode, the whole bundle falls back to the local workflow and runs sequentially on the same fork, so later transactions can observe state changes from earlier transactions.
See the full request and response types below.
Example body:
[
{
"chainId": 1,
"from": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"to": "0x66fc62c1748e45435b06cf8dd105b73e9855f93e",
"data": "0xffa2ca3b44eea7c8e659973cbdf476546e9e6adfd1c580700537e52ba7124933a97904ea000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001d0e30db00300ffffffffffffc02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000186a0",
"gasLimit": 500000,
"value": "100000",
"blockNumber": 16784600
}
]Example response:
[
{
"gasUsed": 214622,
"blockNumber": 16784600,
"success": true,
"trace": [ ... ],
"logs": [ ... ],
"exitReason": "Return"
}
]Notes:
- The request body must contain at least one transaction.
- All transactions must use the same
chainId. - For local fallback, block numbers must be non-decreasing.
- Each transaction may include
request_id; its response echoes the same value, ornullwhen omitted.
Starts a warm simulation session backed by a reusable fork/EVM context.
This endpoint is intentionally warm-stateless: it reuses fork/session setup for performance, but simulated transaction effects are not committed between requests. Use it for repeated independent simulations against the same chain/block context, not for sequential flows where transaction N+1 must observe state changes from transaction N.
See the full request and response types below.
Example body:
[
{
"chainId": 1,
"gasLimit": 500000,
"blockNumber": 16784600
}
]Example response:
[{
"statefulSimulationId": "aeb708a5-81d7-4126-a0b5-0f2a78b3830e",
}]Runs simulations against the warmed EVM session referred to by the UUID in the URL.
Simulation results are returned to the caller, but transaction state changes are not committed back into the session. For example, an approval simulation followed by a swap simulation will not make the swap observe the approval unless that allowance already exists in the fork state or is provided through stateOverrides.
Each transaction may include request_id; its response echoes the same value, or null when omitted. The session creation endpoint does not use request_id.
See the full request and response types below.
Example body:
[
{
"chainId": 1,
"from": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"to": "0x66fc62c1748e45435b06cf8dd105b73e9855f93e",
"data": "0xffa2ca3b44eea7c8e659973cbdf476546e9e6adfd1c580700537e52ba7124933a97904ea000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001d0e30db00300ffffffffffffc02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000186a0",
"gasLimit": 500000,
"value": "100000",
"blockNumber": 16784600
}
]Example response:
[{
"gasUsed": 214622,
"blockNumber": 16784600,
"success": true,
"trace": { ... },
"logs": [ ... ],
"exitReason": "Return"
}]Notes:
chainIdmust be the same in all transactions.blockNumbercan be included and incremented when a multi-block simulation is required, or omitted in all transactions to use latest.- Transaction effects are not persisted between requests in this warm-stateless mode.
Ends a current stateful simulation, freeing associated memory.
See the full request and response types below.
Example response:
{
"success": true
}If you set an API_KEY environment variable then all calls to the API must be accompanied by a X-API-KEY header which contains this API Key.
Copy .env.example to .env, fill out required values and run:
$ cargo runIf you want the server to restart on any code changes run:
$ cargo watch -x runRun:
$ cargo testbody.json contains a simple request in the root of the project so once the API is running you can just run:
$ curl -H "Content-Type: application/json" --data @tests/body.json http://localhost:8080/api/v1/simulateIf you have jq installed, you can run this to see pretty traces:
$ curl -H "Content-Type: application/json" --data @tests/body.json http://localhost:8080/api/v1/simulate | jq -r ".formattedTrace"- Support any RPC endpoint, not just Alchemy
- Connect to local node via IPC
- Connect to local reth DB
- Reintroduce bundle simulation as a dedicated sequential feature if needed.
- Support more authentication methods
export type SimulationRequest = {
request_id?: string;
chainId: number;
from: string;
to: string;
data?: string;
gasLimit: number;
value: string;
accessList?: AccessListItem[];
blockNumber?: number; // if not specified, latest used,
stateOverrides?: Record<string, StateOverride>;
formatTrace?: boolean;
};
export type AccessListItem = {
address: string;
storageKeys: string[];
};
export type StateOverride = {
balance?: string;
nonce?: number;
code?: string;
state?: Record<string, string>;
stateDiff?: Record<string, string>;
};
export type SimulationResponse = {
request_id: string | null;
simulationId: string;
gasUsed: number;
blockNumber: number;
success: boolean;
trace: CallTrace[];
logs?: Log[];
exitReason?: InstructionResult;
bytes: string;
formattedTrace?: string;
};
export type Log = {
topics: string[];
data: string;
address: string;
};
export type CallTrace = {
callType: CallType;
from: string;
to: string;
value: string;
};
export enum CallType {
CALL,
STATICCALL,
CALLCODE,
DELEGATECALL,
CREATE,
CREATE2,
}
export enum InstructionResult {
//success codes
Continue,
Stop,
Return,
SelfDestruct,
// revert code
Revert, // revert opcode
CallTooDeep,
OutOfFund,
// error codes
OutOfGas,
OpcodeNotFound,
CallNotAllowedInsideStatic,
InvalidOpcode,
InvalidJump,
InvalidMemoryRange,
NotActivated,
StackUnderflow,
StackOverflow,
OutOfOffset,
FatalExternalError,
GasMaxFeeGreaterThanPriorityFee,
PrevrandaoNotSet,
GasPriceLessThenBasefee,
CallerGasLimitMoreThenBlock,
/// EIP-3607 Reject transactions from senders with deployed code
RejectCallerWithCode,
LackOfFundForGasLimit,
CreateCollision,
OverflowPayment,
PrecompileError,
NonceOverflow,
/// Create init code exceeds limit (runtime).
CreateContractLimit,
/// Error on created contract that begins with EF
CreateContractWithEF,
}- Leverages a lot of crates from Foundry
- Inspired by gakonst's example pyrevm
