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.
Simulates a bundle of transactions in order against the same 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:
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.
Starts a new stateful simulation, allowing you to persist the state of a single EVM across multiple subsequent simulation requests.
See the full request and response types below.
Example body:
[
{
"chainId": 1,
"gasLimit": 500000,
"blockNumber": 16784600
}
]Example response:
[{
"statefulSimulationId": "aeb708a5-81d7-4126-a0b5-0f2a78b3830e",
}]Simulates a bundle of transactions in order against the EVM referred to by the UUID in the URL. After the result is obtained, the EVM state will be retained for subsequent requests.
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.
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
- Support simulating a bundle of transactions against different blocks, applying state as the simulation progresses. Would help support paradigmxyz/reth#2018
- Support more authentication methods
export type SimulationRequest = {
chainId: number;
from: string;
to: string;
data?: string;
gasLimit: number;
value: string;
accessList?: AccessListItem[];
blockNumber?: number; // if not specified, latest used,
blockTimestamp?: number; // if not specified, timestamp of latest block is 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 = {
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
