Skip to content
Logo

Example: tron_sol_bindings

To run this example:

  • Clone the examples repository: git clone https://github.com/throgxyz/examples.git
  • Run: cargo run -p examples-sol-macro --example tron_sol_bindings
//! Generate type-safe contract bindings with the `tron_sol!` macro.
//!
//! `tron_sol!` is tronz's flagship binding macro. From a Solidity `interface`
//! (or `contract`) it generates:
//!
//!   * a **type layer** — `xxxCall` structs, return decoders, event types
//!   * a **provider-bound `Instance`** — `IErc20::new(addr, provider)` with one ergonomic method
//!     per function: `token.balanceOf(addr).call().await?`
//!
//! A single invocation can mix several interfaces with bare `struct`/`enum`
//! definitions, and top-level attributes like `#[derive(...)]` are forwarded to
//! the generated types.  This example shows all of that, then makes read-only
//! (`eth_call`-style) calls against a live TRC20 contract.
//!
//! No private key required (read-only).
//!
//! Required env:
//!   TRON_CONTRACT — TRC20 contract address (e.g. USDT on Nile)
//!
//! Optional env:
//!   TRON_API_KEY  — TronGrid API key
//!
//! ```bash
//! TRON_CONTRACT=<addr> cargo run -p examples-sol-macro --example tron_sol_bindings
//! ```
 
use tronz::{
    ProviderBuilder, TRONGRID_NILE,
    contract::tron_sol,
    primitives::{Address as TronAddress, U256},
};
 
// A single `tron_sol!` invocation can declare multiple items. Here we mix a
// bare `struct` (with forwarded `#[derive(...)]`) and an `#[sol(rpc)]`
// interface that becomes a provider-bound `Instance`.
tron_sol! {
    // `#[derive(...)]` on a bare struct is forwarded to the generated type,
    // so `TokenMeta` is a normal Rust struct you can build and debug-print.
    #[derive(Debug, Default, PartialEq)]
    struct TokenMeta {
        string name;
        string symbol;
        uint8 decimals;
    }
 
    // `#[sol(rpc)]` turns this interface into `IErc20::Instance`, with a typed
    // async method for every function below.
    #[sol(rpc)]
    interface IErc20 {
        function name() external view returns (string);
        function symbol() external view returns (string);
        function decimals() external view returns (uint8);
        function totalSupply() external view returns (uint256);
        function balanceOf(address owner) external view returns (uint256);
        function transfer(address to, uint256 amount) external returns (bool);
    }
}
 
#[tokio::main]
async fn main() -> anyhow::Result<()> {
    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 contract: TronAddress = contract_str.parse()?;
 
    let provider = ProviderBuilder::new().maybe_api_key(api_key).on_grpc(TRONGRID_NILE).await?;
 
    // ── Bind the interface to a live contract ─────────────────────────────────
    //
    // `IErc20::new` takes (address, provider) and returns a typed `Instance`.
    let token = IErc20::new(contract, provider);
 
    // ── Read state with typed methods ─────────────────────────────────────────
    //
    // Each call returns a builder; `.call().await` performs a constant
    // (read-only) `trigger_constant_contract` and decodes the return value to
    // its native Rust type — no manual ABI encoding or decoding.
    let name: String = token.name().call().await?;
    let symbol: String = token.symbol().call().await?;
    let decimals: u8 = token.decimals().call().await?;
    let supply: U256 = token.totalSupply().call().await?;
 
    println!("=== tron_sol! typed reads ===");
    println!("  contract : {contract}");
    println!("  name     : {name}");
    println!("  symbol   : {symbol}");
    println!("  decimals : {decimals}");
    println!("  supply   : {supply}");
 
    // `balanceOf(address)` accepts any `Into<Address>`, so a tronz `Address`
    // works directly (its 0x41 prefix is stripped for the ABI encoding).
    let balance: U256 = token.balanceOf(contract).call().await?;
    println!("  self-bal : {balance}");
 
    // ── The bare struct is a first-class Rust type ────────────────────────────
    let meta = TokenMeta { name, symbol, decimals };
    println!("\n=== forwarded #[derive] on bare struct ===");
    println!("  {meta:?}");
    assert_ne!(meta, TokenMeta::default());
 
    // ── Building a state-changing call (not sent here) ────────────────────────
    //
    // With a signer-backed provider you would send it:
    //   let pending = token.transfer(to, U256::from(1u64)).send().await?;
    // `.send()` fills TAPOS + fee_limit, signs, and broadcasts.
    println!("\n(transfer(...).send() requires a signer-backed provider — see contract_send)");
 
    Ok(())
}

Find the source code on GitHub here.