#blockchain #ethereum #web3 #tenderly

tndrly

Unofficial Rust client for the Tenderly API - simulations, virtual testnets, alerts, and more

13 releases

new 0.3.8 Feb 14, 2026
0.3.7 Feb 13, 2026
0.3.5 Jan 27, 2026
0.2.2 Jan 12, 2026
0.1.1 Jan 12, 2026

#938 in Web programming

28 downloads per month
Used in ethcli

MIT license

390KB
7.5K SLoC

yld_fi

tndrly

Unofficial Rust client for the Tenderly API

crates.io MIT License

Features

  • Simulation API - Simulate transactions without broadcasting
  • Virtual TestNets API - Create isolated blockchain environments
  • Alerts API - Monitor on-chain activity with notifications
  • Contract API - Manage and verify smart contracts
  • Web3 Actions API - Deploy serverless functions
  • Wallets API - Track and monitor wallet addresses

Installation

[dependencies]
tndrly = "0.3"
tokio = { version = "1", features = ["full"] }

Quick Start

use tndrly::Client;
use tndrly::simulation::SimulationRequest;

#[tokio::main]
async fn main() -> Result<(), tndrly::Error> {
    // Create client from environment variables
    let client = Client::from_env()?;

    // Simulate a transaction
    let request = SimulationRequest::new(
        "0x0000000000000000000000000000000000000000",
        "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
        "0x70a08231000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045"
    )
    .gas(100000)
    .save(true);

    let result = client.simulation().simulate(&request).await?;
    println!("Gas used: {}", result.simulation.gas_used);
    println!("Status: {}", if result.simulation.status { "success" } else { "failed" });

    Ok(())
}

Environment Variables

export TENDERLY_ACCESS_KEY="your-access-key"
export TENDERLY_ACCOUNT="your-account-slug"
export TENDERLY_PROJECT="your-project-slug"

API Modules

Simulation

use tndrly::simulation::{SimulationRequest, BundleSimulationRequest};

// Single simulation
let request = SimulationRequest::new(from, to, calldata)
    .network_id("1")
    .value_wei(1_000_000_000_000_000_000u128)
    .gas(100000)
    .save(true);
let result = client.simulation().simulate(&request).await?;

// Bundle simulation
let bundle = BundleSimulationRequest::new(vec![tx1, tx2, tx3]);
let results = client.simulation().simulate_bundle(&bundle).await?;

// List saved simulations
let sims = client.simulation().list(0, 10).await?;

// Share a simulation
let url = client.simulation().share("sim-id").await?;

Virtual TestNets

use tndrly::vnets::{CreateVNetRequest, ListVNetsQuery};

// Create a VNet
let request = CreateVNetRequest::new("my-testnet", "My TestNet", 1)
    .block_number(18000000)
    .sync_state(true);
let vnet = client.vnets().create(&request).await?;

// List VNets
let vnets = client.vnets().list(None).await?;

// Delete VNets (CI cleanup)
client.vnets().delete_many(vec!["id1".into(), "id2".into()]).await?;

// Admin RPC - Balance management
let admin = client.vnets().admin_rpc(&vnet.id).await?;
admin.set_balance("0xAddress", "0xDE0B6B3A7640000").await?;  // 1 ETH
admin.set_erc20_balance("0xToken", "0xWallet", "0xAmount").await?;

// Admin RPC - State management
let snapshot_id = admin.snapshot().await?;
admin.revert(&snapshot_id).await?;

// Admin RPC - Simulate transaction (tenderly_simulateTransaction)
use tndrly::vnets::SimulateTransactionParams;
let tx = SimulateTransactionParams::new("0xFrom".into())
    .to("0xTo".into())
    .data("0xCalldata".into());
let result = admin.simulate_transaction(&tx, "latest", None, None).await?;

// Admin RPC - Simulate bundle (tenderly_simulateBundle)
let txs = vec![tx1, tx2, tx3];
let result = admin.simulate_bundle(&txs, None, None).await?;

Alerts

Note: The Tenderly Alerts API uses an undocumented request format. Alert creation (create()) may not work as expected. Read operations (list(), get(), history()) work correctly. See src/alerts/types.rs for details.

use tndrly::alerts::AlertHistoryQuery;

// List existing alerts
let alerts = client.alerts().list().await?;

// Get alert history
let history = client.alerts()
    .history(Some(AlertHistoryQuery::new().page(1).per_page(50)))
    .await?;

// Get a specific alert
let alert = client.alerts().get("alert-id").await?;

Contracts

use tndrly::contracts::{AddContractRequest, VerifyContractRequest};

// Add a contract
let contract = client.contracts()
    .add(&AddContractRequest::new("1", "0xAddress")
        .display_name("My Contract")
        .tag("defi"))
    .await?;

// Verify source code
let result = client.contracts()
    .verify(&VerifyContractRequest::new(
        "1",
        "0xAddress",
        "MyContract",
        source_code,
        "v0.8.19+commit.7dd6d404",
    ).optimization(true, 200))
    .await?;

Web3 Actions

use tndrly::actions::{CreateActionRequest, ActionTrigger, TriggerConfig};

// Create an action
let action = client.actions()
    .create(&CreateActionRequest::new(
        "Notify Slack",
        ActionTrigger::Alert,
        source_code,
    )
    .trigger_config(TriggerConfig::alert("alert-id"))
    .secret("SLACK_WEBHOOK", webhook_url))
    .await?;

// View execution logs
let logs = client.actions().logs(&action.id).await?;

Wallets

use tndrly::wallets::{AddWalletRequest, UpdateWalletRequest};

// Add a wallet to monitor
let wallet = client.wallets()
    .add(&AddWalletRequest::new("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")
        .display_name("vitalik.eth")
        .tag("whale"))
    .await?;

// List all wallets
let wallets = client.wallets().list().await?;

// Update wallet metadata
client.wallets()
    .update("0xd8dA...", &UpdateWalletRequest::new()
        .display_name("Vitalik Buterin"))
    .await?;

// Remove a wallet
client.wallets().remove("0xd8dA...").await?;

Terms of Service

This is an unofficial client. By using this library, you agree to comply with Tenderly's Terms of Service.

Disclaimer

This crate is not affiliated with or endorsed by Tenderly.

License

MIT

Dependencies

~6–21MB
~225K SLoC