Skip to content
Logo

Contracts

TRON's virtual machine (TVM) is EVM-compatible, so smart contract interaction in tronz looks a lot like alloy. The tronz-contract crate (re-exported as tronz::contract, behind the contract feature) provides token bindings and the building blocks for arbitrary contract calls.

use tronz::contract::Trc20Ext;

What's included

ItemPurpose
Trc20Ext / Trc20InstanceHigh-level TRC20 token interface
Trc721Ext / Trc721InstanceHigh-level TRC721 NFT interface
tron_sol!Alpha: generate provider-bound, typed contract bindings
ContractInstance / ContractExtGeneric provider-bound contract handle
CallBuilderBuild, simulate, and send a single contract call
DeployBuilderDeploy a new contract
InterfaceA runtime ABI loaded from JSON
decode_log / decode_logsDecode event logs from a receipt

ABI compatibility with alloy

Because the TVM uses the same ABI as the EVM, tronz reuses alloy's ABI tooling directly. The TRC20 bindings are generated by alloy's sol! macro, and these types are re-exported for working with your own contracts:

use tronz::contract::{SolCall, SolError, SolEvent, SolInterface, SolValue};

The one TRON-specific detail is addresses: a TRON Address carries a 0x41 prefix, but ABI encoding uses the 20-byte EVM body. tronz handles this for you — the Address ↔ alloy_primitives::Address conversion strips and re-attaches the prefix automatically (see Addresses).

Reads vs. writes

  • Reads (constant calls) need only a provider — no signer.
  • Writes go through the same transaction lifecycle as native operations: they're filled, signed, broadcast, and confirmed, and they consume energy. ProviderBuilder::new() installs EnergyFiller, which estimates energy and derives fee_limit; override it explicitly only when needed (see Fillers).

Reading against any provider

Contract handles are generic over ContractReadProvider, a read capability implemented by both FullNode providers and the read-only SolidityProvider. The same Trc20Instance, Trc721Instance, tron_sol! binding, and ContractInstance therefore read either latest or solidified (irreversible) state, without ever gaining write ability against a SolidityNode:

use tronz::contract::Trc20Ext;
 
// Bound to a SolidityProvider: reads solidified state, cannot send.
let balance = solidity.trc20(usdt).balance_of(who).await?;

Setting the contract sender

A read-only call made without a signer defaults msg.sender to the zero address — most view functions ignore the caller, so this is usually fine. When a view branches on msg.sender (allowances, per-caller quotes, access checks), set it explicitly with .caller(address), available on ContractInstance, CallBuilder, and tron_sol! call builders:

let instance = instance.caller(caller); // presents `caller` as msg.sender

In 0.5, .caller() also sets the sender/owner used when that contract call is sent as a write; it is not read-only configuration. Contract deployment uses .from(address) on DeployBuilder instead.

See the .caller() example.

Start with TRC20 tokens for the most common case.

For custom contracts, use tron_sol! when the ABI is known at compile time, or the dynamic ABI example when it is only known at runtime.