Skip to content
Logo

Example: decode_log

To run this example:

  • Clone the examples repository: git clone https://github.com/throgxyz/examples.git
  • Run: cargo run -p examples-contracts --example decode_log
//! Decode raw event logs from a receipt into typed `SolEvent` structs.
//!
//! Demonstrates two log-decoding approaches:
//!
//! 1. **Static** — `sol!`-generated type + `decode_logs::<E>()` helper
//! 2. **Dynamic** — `Interface::decode_logs()` when the ABI is only known at runtime
//!
//! No private key required (read-only).
//!
//! Required env:
//!   TRON_TX_ID    — hex transaction id that emitted TRC20 Transfer events
//!   TRON_CONTRACT — TRC20 contract address (for the dynamic ABI approach)
//!
//! Optional env:
//!   TRON_API_KEY  — TronGrid API key
//!
//! ```bash
//! TRON_TX_ID=<txid> TRON_CONTRACT=<contract> \
//!   cargo run -p examples-contracts --example decode_log
//! ```
 
use alloy_json_abi::JsonAbi;
use tronz::{
    ProviderBuilder, TRONGRID_NILE, TronProvider,
    contract::{ContractExt, Interface, SolEvent, decode_logs, trc20::ITRC20},
    primitives::B256,
};
 
#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let tx_id_hex = std::env::var("TRON_TX_ID").expect("TRON_TX_ID env var required");
    let contract_str = std::env::var("TRON_CONTRACT").expect("TRON_CONTRACT env var required");
    let api_key = std::env::var("TRON_API_KEY").ok();
 
    let tx_id_bytes = hex::decode(tx_id_hex.trim_start_matches("0x"))?;
    let tx_id: B256 = B256::from_slice(&tx_id_bytes);
    let contract: tronz::Address = contract_str.parse()?;
 
    let provider = ProviderBuilder::new().maybe_api_key(api_key).on_grpc(TRONGRID_NILE).await?;
 
    // ── Fetch receipt ─────────────────────────────────────────────────────────
 
    let info = provider
        .get_transaction_info(tx_id)
        .await?
        .ok_or_else(|| anyhow::anyhow!("transaction not found or not yet confirmed"))?;
    println!("=== Transaction 0x{} ===", hex::encode(tx_id));
    println!("  status : {:?}", info.status);
    println!("  logs   : {}", info.logs.len());
 
    // ── Approach 1: Static decode via sol!-generated type ────────────────────
 
    println!("\n=== Static decode (ITRC20::Transfer) ===");
    println!("  Transfer topic0 : 0x{}", hex::encode(ITRC20::Transfer::SIGNATURE_HASH));
 
    let transfers: Vec<_> =
        decode_logs::<ITRC20::Transfer>(&info.logs).collect::<Result<_, _>>()?;
 
    if transfers.is_empty() {
        println!("  no Transfer events found");
    } else {
        for (i, t) in transfers.iter().enumerate() {
            let from: tronz::Address = t.from.into();
            let to: tronz::Address = t.to.into();
            println!("  [{}] from={from} to={to} value={}", i, t.value);
        }
    }
 
    // Also decode Approval events.
    let approvals: Vec<_> =
        decode_logs::<ITRC20::Approval>(&info.logs).collect::<Result<_, _>>()?;
 
    if !approvals.is_empty() {
        println!("\n  Approval events:");
        for (i, a) in approvals.iter().enumerate() {
            let owner: tronz::Address = a.owner.into();
            let spender: tronz::Address = a.spender.into();
            println!("  [{}] owner={owner} spender={spender} value={}", i, a.value);
        }
    }
 
    // ── Approach 2: Dynamic decode via Interface ───────────────────────────────
 
    println!("\n=== Dynamic decode (Interface from JSON ABI) ===");
 
    // Load the ABI for the contract (could come from a file, API, etc.)
    let abi_json = r#"[
        {"name":"Transfer","type":"event","inputs":[{"name":"from","type":"address","indexed":true},{"name":"to","type":"address","indexed":true},{"name":"value","type":"uint256","indexed":false}],"anonymous":false},
        {"name":"Approval","type":"event","inputs":[{"name":"owner","type":"address","indexed":true},{"name":"spender","type":"address","indexed":true},{"name":"value","type":"uint256","indexed":false}],"anonymous":false}
    ]"#;
    let abi: JsonAbi = serde_json::from_str(abi_json)?;
    let interface = Interface::new(abi);
 
    // Bind to the contract so decode_log knows which contract emitted each log.
    let instance = provider.contract(contract, interface);
 
    for (i, result) in instance.decode_logs(&info.logs).enumerate() {
        match result {
            Ok((name, decoded)) => {
                println!("  [{}] event={}", i, name);
                for val in &decoded.indexed {
                    println!("       indexed : {val:?}");
                }
                for val in &decoded.body {
                    println!("       body    : {val:?}");
                }
            }
            Err(e) => {
                println!("  [{}] decode error: {e}", i);
            }
        }
    }
 
    Ok(())
}

Find the source code on GitHub here.