Skip to content
Logo

Example: contract_deploy

To run this example:

  • Clone the examples repository: git clone https://github.com/throgxyz/examples.git
  • Run: cargo run -p examples-contracts --example contract_deploy
//! Deploy a compiled contract from bytecode.
//!
//! Deploys a minimal Solidity contract to the Nile testnet and extracts the
//! deployed contract address from the receipt.
//!
//! Required env:
//!   TRON_PRIVATE_KEY — funded Nile private key
//!
//! Optional env:
//!   TRON_API_KEY     — TronGrid API key
//!
//! ```bash
//! TRON_PRIVATE_KEY=<key> cargo run -p examples-contracts --example contract_deploy
//! ```
 
use tronz::{
    LocalSigner, ProviderBuilder, TRONGRID_NILE, TronProvider, TronSigner, Trx,
    contract::ContractExt,
};
 
// Hand-assembled EVM bytecode for a minimal Counter contract:
//
// ```solidity
// contract Counter {
//     uint256 public count;           // slot 0
//     function increment() external { count += 1; }
// }
// ```
//
// Selectors (first 4 bytes of keccak256 of signature):
//   count()      → 0x06661abd
//   increment()  → 0x7cf5dab0
//
// Layout (64 bytes total):
//   [0x00–0x0a] constructor  – CODECOPY(0, 0x0b, 0x35) then RETURN 0x35 bytes
//   [0x0b–0x3f] runtime      – dispatcher + count() + increment()
const COUNTER_BYTECODE: &str = concat!(
    // ── Constructor (11 bytes) ────────────────────────────────────────────────
    // PUSH1 0x35   DUP1   PUSH1 0x0b   PUSH1 0x00   CODECOPY   PUSH1 0x00   RETURN
    "603580600b6000396000f3",
    // ── Runtime (53 bytes) ───────────────────────────────────────────────────
    // Dispatcher: calldataload >> 0xe0 → selector
    "6000", // PUSH1 0  (calldataload offset)
    "35",   // CALLDATALOAD
    "60e0", // PUSH1 0xe0
    "1c",   // SHR  → top 4 bytes = selector
    "80",   // DUP1
    // count() branch
    "6306661abd", // PUSH4 count() selector
    "14",         // EQ
    "601e",       // PUSH1 0x1e = 30  (count() JUMPDEST)
    "57",         // JUMPI
    "80",         // DUP1
    // increment() branch
    "637cf5dab0", // PUSH4 increment() selector
    "14",         // EQ
    "602a",       // PUSH1 0x2a = 42  (increment() JUMPDEST)
    "57",         // JUMPI
    // fallback: revert
    "6000", // PUSH1 0
    "80",   // DUP1
    "fd",   // REVERT
    // count() at PC=30 (0x1e)
    "5b",   // JUMPDEST
    "6000", // PUSH1 0  (storage slot 0)
    "54",   // SLOAD
    "6000", // PUSH1 0  (memory offset)
    "52",   // MSTORE
    "6020", // PUSH1 32 (return size)
    "6000", // PUSH1 0  (return offset)
    "f3",   // RETURN
    // increment() at PC=42 (0x2a)
    "5b",   // JUMPDEST
    "6001", // PUSH1 1
    "6000", // PUSH1 0  (slot 0)
    "54",   // SLOAD
    "01",   // ADD
    "6000", // PUSH1 0  (slot 0)
    "55",   // SSTORE
    "00",   // STOP
);
 
// JSON ABI for the Counter contract.
const COUNTER_ABI: &str = r#"[
    {"name":"count","type":"function","inputs":[],"outputs":[{"type":"uint256"}],"stateMutability":"view"},
    {"name":"increment","type":"function","inputs":[],"outputs":[],"stateMutability":"nonpayable"}
]"#;
 
#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let key_hex = std::env::var("TRON_PRIVATE_KEY").expect("TRON_PRIVATE_KEY env var required");
    let api_key = std::env::var("TRON_API_KEY").ok();
 
    let signer = LocalSigner::from_hex(&key_hex)?;
    let deployer = signer.address();
 
    let provider = ProviderBuilder::new()
        .with_recommended_fillers()
        .with_signer(signer)
        .maybe_api_key(api_key)
        .on_grpc(TRONGRID_NILE)
        .await?;
 
    let balance = provider.get_account(deployer).await?.balance;
    println!("=== Deployer {} ===", deployer);
    println!("  balance : {} TRX", balance);
 
    // Deployment costs energy; make sure there's enough TRX for the fee_limit.
    if balance < Trx::from_sun(5_000_000)? {
        anyhow::bail!("balance too low — need at least 5 TRX for deployment");
    }
 
    // ── Deploy ────────────────────────────────────────────────────────────────
 
    let bytecode = hex::decode(COUNTER_BYTECODE)?;
 
    println!("\n=== Deploying Counter contract ===");
    println!("  bytecode size : {} bytes", bytecode.len());
 
    let pending = provider
        .deploy(bytecode)
        .abi(COUNTER_ABI)
        .name("Counter")
        .fee_limit(Trx::from_sun(20_000_000)?) // 20 TRX fee limit
        .send()
        .await?;
 
    println!("  tx_id  : 0x{}", hex::encode(pending.tx_id()));
    println!("  waiting for confirmation…");
    let info = pending.get_receipt().await?;
 
    println!("\n=== Deployment receipt ===");
    println!("  status          : {:?}", info.status);
    println!("  energy used     : {}", info.energy_usage);
    println!("  energy fee      : {} sun", info.energy_fee.as_sun());
 
    let contract_addr = info.contract_address.ok_or_else(|| {
        anyhow::anyhow!("no contract address in receipt — deployment may have failed")
    })?;
 
    println!("\n=== Deployed contract ===");
    println!("  address : {contract_addr}");
    println!("  explorer: https://nile.tronscan.org/#/contract/{contract_addr}");
 
    // ── Verify by calling count() ─────────────────────────────────────────────
 
    use alloy_dyn_abi::DynSolValue;
    use alloy_json_abi::JsonAbi;
    use tronz::contract::Interface;
 
    let abi: JsonAbi = serde_json::from_str(COUNTER_ABI)?;
    let interface = Interface::new(abi);
    let instance = provider.contract(contract_addr, interface);
 
    let vals = instance.call("count", &[]).await?;
    let count =
        vals.first().and_then(|v| if let DynSolValue::Uint(n, _) = v { Some(*n) } else { None });
    println!("\n=== Initial state ===");
    println!("  count() = {:?}", count);
 
    Ok(())
}

Find the source code on GitHub here.