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
| Item | Purpose |
|---|---|
Trc20Ext / Trc20Instance | High-level TRC20 token interface |
Trc721Ext / Trc721Instance | High-level TRC721 NFT interface |
tron_sol! | Alpha: generate provider-bound, typed contract bindings |
ContractInstance / ContractExt | Generic provider-bound contract handle |
CallBuilder | Build, simulate, and send a single contract call |
DeployBuilder | Deploy a new contract |
Interface | A runtime ABI loaded from JSON |
decode_log / decode_logs | Decode 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()installsEnergyFiller, which estimates energy and derivesfee_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.senderIn 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.
