Simulation

Aomi uses a simulation-first approach for all on-chain transactions. Before any transaction reaches your wallet for signing, it is executed against a forked copy of the current network state. You review the exact outcome — token changes, gas, contract calls — before approving.

This is how the system prevents LLM-generated transactions from being sent without verification.

Overview

Anvil Integration

ForkProvider

The aomi-anvil crate manages Anvil forks for transaction simulation:

Initialization

use aomi_anvil::{
    init_fork_provider, fork_endpoint, ForkProvider,
    AnvilParams, ForksConfig,
};

// Initialize from configuration
let config = ForksConfig::from_yaml("config.yaml")?;
init_fork_providers(config).await?;

// Or initialize a single fork
let params = AnvilParams {
    fork_url: "https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY".into(),
    fork_block_number: Some(18_500_000),
    chain_id: Some(1),
    port: Some(8545),
};

init_fork_provider(params).await?;

// Get the endpoint
let rpc_url = fork_endpoint()?;
println!("Fork running at: {}", rpc_url);

Provider Types

// Managed Anvil instance (spawns a local process)
let provider = ForkProvider::spawn(params).await?;
assert!(provider.is_managed());

// External RPC (no process management)
let provider = ForkProvider::external("http://localhost:8545").await?;
assert!(!provider.is_managed());

// Common interface
println!("Endpoint: {}", provider.endpoint());
println!("Block: {}", provider.block_number());

Snapshots

Snapshots capture the full network state at a point in time and allow reverting after a simulation run.

use aomi_anvil::{fork_snapshot, ForkSnapshot};

// Capture current state
let snapshot: ForkSnapshot = fork_snapshot()?;
println!("Snapshot at block: {}", snapshot.block_number());

// Execute some transactions
execute_test_transactions().await?;

// Revert to snapshot (reinitialize fork)
shutdown_and_reinit_all().await?;

Transaction Simulation

SimulateContractCall Tool

The SimulateContractCall tool is the primary mechanism for pre-signing simulation:

use aomi_tools::cast::SimulateContractCall;

#[derive(Debug, Deserialize, JsonSchema)]
pub struct SimulateParams {
    /// Target contract address
    pub to: String,

    /// Encoded calldata (hex)
    pub data: String,

    /// Value to send in ETH
    #[serde(default)]
    pub value: String,

    /// Sender address to simulate from
    pub from: String,

    /// Network to simulate on
    #[serde(default = "default_network")]
    pub network: String,
}

#[tool(description = "Simulate a contract call without broadcasting")]
pub async fn simulate_contract_call(
    params: SimulateParams,
) -> Result<SimulationResult, ToolError> {
    let client = external_clients().await.cast_client(&params.network)?;

    let result = client.simulate(
        &params.from,
        &params.to,
        &params.data,
        &params.value,
    ).await?;

    Ok(SimulationResult {
        success: result.success,
        return_data: result.output,
        gas_used: result.gas_used,
        logs: result.logs,
    })
}

Batch Simulation

// Simulate multiple transactions in sequence
let fork_url = fork_endpoint()?;
let provider = ProviderBuilder::new().on_http(fork_url.parse()?);

let mut results = Vec::new();

for tx in transactions {
    // Impersonate sender
    provider.anvil_impersonate_account(tx.from).await?;

    // Execute transaction
    let result = provider.send_transaction(tx.clone()).await?;
    let receipt = result.get_receipt().await?;

    results.push(SimulationResult {
        tx_hash: receipt.transaction_hash,
        success: receipt.status(),
        gas_used: receipt.gas_used,
    });
}

// Revert all changes
shutdown_and_reinit_all().await?;

Wallet Approval Flow

After simulation succeeds, the transaction is queued for wallet approval:

Multi-Network Support

Simulation is supported across all configured networks:

# config.yaml
networks:
  ethereum:
    rpc_url: "https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}"
    chain_id: 1

  base:
    rpc_url: "https://base-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}"
    chain_id: 8453

  arbitrum:
    rpc_url: "https://arb-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}"
    chain_id: 42161

  testnet:
    rpc_url: "http://localhost:8545"
    chain_id: 31337

Error Handling

ErrorCauseResolution
ForkNotInitializedinit_fork_provider not calledInitialize before use
SimulationRevertedTransaction would fail on-chainCheck calldata and state
InsufficientFundsNot enough ETH/tokensFund the account
GasEstimationFailedComplex transactionProvide manual gas limit
RpcErrorNetwork issuesRetry or switch RPC

Security Best Practices

PracticeDescription
Always simulateNever skip the simulation step
Show state changesDisplay balance deltas clearly
Require confirmationNever auto-execute transactions
Validate addressesCheck checksums and formats
Warn on high valueAlert for large transfers
Check approvalsVerify token approvals before swaps

Further Reading

On this page