Tron Proof Validation Guide
Overview
Cross-chain proof validation between TRON and EVM chains is fully compatible with proper address format handling. With proper format conversion, the same proof can be validated successfully on both TRON and EVM chains, providing true cross-chain interoperability.
The key principles are:
🔒 Data Integrity
All proof data remains identical across chains
📍 Address Formats
TRON uses base58, EVM uses hex format
🔗 Cross-Chain
Same proof validates on both networks
Network Information
Network | Chain ID | Public RPC | Explorer | Prover Contract |
---|---|---|---|---|
TRON Nile Testnet | 3448148188 | nile.trongrid.io | nile.tronscan.org | TN1hKC3qzkRzRgSCzmEY613KLjMJHqQ1Ly |
TRON Mainnet | 728126428 | api.trongrid.io/jsonrpc | tronscan.org | TNKCTuonyurjAXBYpZtSZEo7WdFUKW9cbN |
Address Format Differences
Understanding address formats is crucial for TRON integration:
- Address Formats
- Address Conversion
Address Format Comparison
Format Type | Example | Usage |
---|---|---|
TRON Base58 | TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf | TRON Explorer, TronWeb interactions |
TRON Hex | 41eca9bc828a3005b9a3b909f2cc5c2a54794de05f | Raw logs, internal processing |
EVM Hex | 0xECa9bC828A3005B9a3b909f2cc5c2a54794DE05F | Standard EVM format |
Key Insight: These are the same address in different formats. TRON uses 41
prefix instead of 0x
for hex addresses.
Converting Between Formats
import TronWeb from 'tronweb';
// Base58 to Hex
const base58Address = "TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf";
const hexAddress = TronWeb.address.toHex(base58Address);
// Returns: "41eca9bc828a3005b9a3b909f2cc5c2a54794de05f"
// Hex to Base58
const tronHex = "41eca9bc828a3005b9a3b909f2cc5c2a54794de05f";
const base58 = TronWeb.address.fromHex(tronHex);
// Returns: "TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf"
// TRON Hex to EVM Hex
const tronAddress = "41eca9bc828a3005b9a3b909f2cc5c2a54794de05f";
const evmAddress = "0x" + tronAddress.substring(2);
// Returns: "0xeca9bc828a3005b9a3b909f2cc5c2a54794de05f"
// EVM Hex to TRON Hex
const evmAddr = "0xeca9bc828a3005b9a3b909f2cc5c2a54794de05f";
const tronAddr = "41" + evmAddr.substring(2);
// Returns: "41eca9bc828a3005b9a3b909f2cc5c2a54794de05f"
Cross-Chain Proof Validation
EVM to TRON Validation
When validating proofs from EVM chains (like Optimism) on TRON, the data remains consistent with only address format differences:
// Same proof validated on different chains
// --- Base Sepolia Validation Result ---
const baseResult = {
chainId: 11155420,
emittingContract: "0xB84644c24B4D0823A0770ED698f7C20B88Bcf824", // EVM format
topics: "0xc0ae438737d82fdd04b48b08fb95c82fdbc3e4c6afb...",
unindexedData: "0x00000000000000000000000000000000000000000000000029a2241af62c0000..."
};
// --- TRON Nile Validation Result ---
const tronResult = {
chainId: 11155420n, // Same chain ID
emittingContract: "41b84644c24b4d0823a0770ed698f7c20b88bcf824", // TRON format
topics: "0xc0ae438737d82fdd04b48b08fb95c82fdbc3e4c6afb...", // Identical
unindexedData: "0x00000000000000000000000000000000000000000000000029a2241af62c0000..." // Identical
};
// Comparison Results
console.log("Chain ID Match:", baseResult.chainId === Number(tronResult.chainId)); // ✅ true
console.log("Topics Match:", baseResult.topics === tronResult.topics); // ✅ true
console.log("Data Match:", baseResult.unindexedData === tronResult.unindexedData); // ✅ true
// Only difference: address format
const convertedAddress = "0x" + tronResult.emittingContract.substring(2);
console.log("Address Match:", baseResult.emittingContract.toLowerCase() === convertedAddress.toLowerCase()); // ✅ true
TRON to EVM Validation
When validating TRON events on EVM chains, the prover automatically handles format conversion:
- TRON Raw Event
- EVM Validation Result
Original TRON Transaction: View on Explorer
{
"address": "eca9bc828a3005b9a3b909f2cc5c2a54794de05f", // No prefix
"topics": [
"ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", // Event signature
"0000000000000000000000003701e24fabdec5be006d04331b977d974a0e64c8", // From address
"000000000000000000000000d3ba0080540dd44a65e299d215c594a89c820fc5" // To address
],
"data": "0000000000000000000000000000000000000000000000000000000000989680" // Amount
}
const validationResult = [
"3448148188", // TRON chain ID
"0xECa9bC828A3005B9a3b909f2cc5c2a54794DE05F", // Auto-converted to EVM format
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef0000000000000000000000003701e24fabdec5be006d04331b977d974a0e64c8000000000000000000000000d3ba0080540dd44a65e299d215c594a89c820fc5", // Concatenated topics
"0x0000000000000000000000000000000000000000000000000000000000989680" // Unindexed data
];
// Topics are automatically:
// 1. Concatenated into single hex string
// 2. Prefixed with "0x"
// 3. Address formats converted where applicable
TRON Contract Interactions
Static Calls (Read-Only)
- Using contract.at()
- Using triggerConstantContract
const tronWeb = new TronWeb({
fullHost: "https://nile.trongrid.io"
});
// Using base58 address format
const contract = await tronWeb.contract().at("TD39R92XN4HqXUQCPmtnZV1mhcgMy7Qbn8");
try {
const result = await contract.validateEvent(proofHex).call();
console.log("Validation Result:", {
chainId: result.chainId.toString(),
emittingContract: result.emittingContract, // TRON hex format (41...)
topics: result.topics,
unindexedData: result.unindexedData
});
} catch (error) {
console.error("Validation failed:", error.message);
}
Advantages: Simple syntax, automatic ABI handling, and built-in error handling
const functionSelector = 'validateEvent(bytes)';
const parameters = [{ type: 'bytes', value: proofHex }];
try {
const result = await tronWeb.transactionBuilder.triggerConstantContract(
"TD39R92XN4HqXUQCPmtnZV1mhcgMy7Qbn8", // base58 address
functionSelector,
{}, // options
parameters
);
if (result.result.result) {
// Decode the raw result
const decodedTypes = ['uint32', 'address', 'bytes', 'bytes'];
const decoded = tronWeb.utils.abi.decodeParams(
decodedTypes,
result.constant_result[0]
);
console.log("Decoded result:", decoded);
} else {
console.error("Contract call failed:", result.result.message);
}
} catch (error) {
console.error("Error:", error);
}
Advantages: More control over parameters, access to raw results, and custom error handling
Transactions (State-Changing)
- Using .send()
- Using triggerSmartContract
const tronWeb = new TronWeb({
fullHost: "https://nile.trongrid.io",
privateKey: "your_private_key_here" // Required for transactions
});
const contract = await tronWeb.contract().at("TContractAddress123");
try {
const tx = await contract.setValueFromSource(proofHex).send({
feeLimit: 100_000_000, // 100 TRX max fee (in SUN)
callValue: 0, // TRX to send with transaction
shouldPollResponse: false // Don't wait for confirmation
});
console.log("Transaction ID:", tx);
// Optional: Wait for confirmation
const receipt = await tronWeb.trx.getTransactionInfo(tx);
console.log("Transaction receipt:", receipt);
} catch (error) {
console.error("Transaction failed:", error);
}
// More control and better error handling
try {
const transaction = await tronWeb.transactionBuilder.triggerSmartContract(
"TContractAddress123", // Contract address (base58)
"setValueFromSource(bytes)", // Full function signature
{
feeLimit: 100_000_000, // In SUN (1 TRX = 1,000,000 SUN)
callValue: 0 // TRX value to send
},
[
{type: 'bytes', value: proofHex}
],
tronWeb.defaultAddress.base58 // Sender address
);
// Check if transaction was built successfully
if (!transaction.result || !transaction.result.result) {
throw new Error('Transaction build failed: ' + transaction.result.message);
}
// Sign the transaction
const signedTx = await tronWeb.trx.sign(transaction.transaction);
// Broadcast the signed transaction
const receipt = await tronWeb.trx.sendRawTransaction(signedTx);
if (receipt.result) {
console.log("Transaction successful!");
console.log("Transaction ID:", receipt.txid);
} else {
console.error("Transaction failed:", receipt.message);
}
} catch (error) {
console.error("Error:", error);
}
Resource Management
TRON uses Energy & Bandwidth instead of gas:
- Resource Types
- Fee Calculation
TRON Resource System
Resource | Purpose | Cost |
---|---|---|
Energy | Smart contract execution | Consumed per operation |
Bandwidth | Transaction size | Consumed per byte |
TRX | Backup payment | When resources insufficient |
const account = await tronWeb.trx.getAccountResources(address);
console.log("Available Resources:", {
energy: account.EnergyLimit - account.EnergyUsed,
bandwidth: account.freeNetLimit - account.freeNetUsed,
frozenBalance: account.frozen?.[0]?.frozen_balance || 0
});
// Estimate resource usage
const energyEstimate = await tronWeb.transactionBuilder.estimateEnergy(
contractAddress,
functionSelector,
options,
parameters,
issuerAddress
);
Fee Structure
// 1 TRX = 1,000,000 SUN
const TRX_TO_SUN = 1_000_000;
// Recommended fee limits for different operations
const feeStructure = {
simpleTransfer: 1 * TRX_TO_SUN, // 1 TRX
contractCall: 50 * TRX_TO_SUN, // 50 TRX
proofValidation: 100 * TRX_TO_SUN, // 100 TRX (recommended)
complexOperation: 200 * TRX_TO_SUN // 200 TRX
};
// Set appropriate feeLimit based on operation complexity
const feeLimit = feeStructure.proofValidation;
// Energy consumption examples
const energyCosts = {
transfer: 345, // TRC20 transfer
approve: 600, // TRC20 approve
validateEvent: 15000, // Proof validation (estimate)
complexLogic: 50000 // Complex contract logic
};
Type Definitions & Common Patterns
Parameter Type Mapping
// Common type conversions for TRON
const parameterExamples = [
// Addresses
{type: 'address', value: "TAddress123..."}, // TRON base58 address
{type: 'address', value: "41eca9bc828a3005b..."}, // TRON hex address
// Numbers (always as strings for large numbers)
{type: 'uint256', value: "1000000000000000000"}, // 1 ETH in wei
{type: 'uint32', value: "11155420"}, // Chain ID
{type: 'uint8', value: "18"}, // Decimals
// Bytes data
{type: 'bytes', value: "0x1234abcd"}, // Hex data
{type: 'bytes32', value: "0x" + "0".repeat(64)}, // 32-byte hash
// Strings and booleans
{type: 'string', value: "Hello TRON"}, // Text data
{type: 'bool', value: true} // Boolean
];
// Address conversion utilities
function convertAddressFormats(address) {
if (address.startsWith('T')) {
// Base58 to hex
return TronWeb.address.toHex(address);
} else if (address.startsWith('41')) {
// TRON hex to EVM hex
return '0x' + address.substring(2);
} else if (address.startsWith('0x')) {
// EVM hex to TRON hex
return '41' + address.substring(2);
}
throw new Error('Invalid address format');
}
Error Handling & Best Practices
Common Issues & Solutions
Common Pitfalls: Address format mismatches, insufficient resources, and incorrect parameter types are the most frequent issues.
async function validateProofOnTron(proofHex) {
try {
// 1. Check account resources first
const resources = await tronWeb.trx.getAccountResources(
tronWeb.defaultAddress.base58
);
if (resources.EnergyLimit - resources.EnergyUsed < 15000) {
console.warn("Low energy. Consider freezing TRX for energy.");
}
// 2. Validate proof format
if (!proofHex.startsWith('0x')) {
proofHex = '0x' + proofHex;
}
// 3. Call contract with proper error handling
const contract = await tronWeb.contract().at(PROVER_CONTRACT_ADDRESS);
const result = await contract.validateEvent(proofHex).call();
// 4. Process result with address format conversion
return {
chainId: result.chainId.toString(),
emittingContract: convertToEvmFormat(result.emittingContract),
topics: result.topics,
unindexedData: result.unindexedData
};
} catch (error) {
// Handle specific TRON errors
if (error.message.includes('REVERT')) {
console.error("Contract reverted:", error.message);
} else if (error.message.includes('OUT_OF_ENERGY')) {
console.error("Insufficient energy for transaction");
} else if (error.message.includes('BANDWIDTH_NOT_ENOUGH')) {
console.error("Insufficient bandwidth");
} else {
console.error("Unknown error:", error);
}
throw error;
}
}
function convertToEvmFormat(tronAddress) {
if (tronAddress.startsWith('41')) {
return '0x' + tronAddress.substring(2);
}
return tronAddress; // Already in correct format
}
Complete Integration Example
import TronWeb from 'tronweb';
class TronProofValidator {
constructor(nodeUrl, privateKey = null) {
this.tronWeb = new TronWeb({
fullHost: nodeUrl,
privateKey: privateKey
});
}
async validateCrossChainProof(proofHex, contractAddress) {
try {
// Initialize contract
const contract = await this.tronWeb.contract().at(contractAddress);
// Validate proof
const result = await contract.validateEvent(proofHex).call();
// Process and return standardized result
return this.processValidationResult(result);
} catch (error) {
throw new Error(`TRON validation failed: ${error.message}`);
}
}
processValidationResult(result) {
return {
chainId: parseInt(result.chainId.toString()),
emittingContract: this.convertToEvmAddress(result.emittingContract),
topics: this.parseTopics(result.topics),
unindexedData: result.unindexedData,
// Keep original TRON format for reference
originalTronContract: result.emittingContract
};
}
convertToEvmAddress(tronAddress) {
if (tronAddress.startsWith('41')) {
return '0x' + tronAddress.substring(2);
}
return tronAddress;
}
parseTopics(topicsHex) {
// Split concatenated topics into array
if (!topicsHex.startsWith('0x')) return [];
const cleanHex = topicsHex.substring(2);
const topics = [];
for (let i = 0; i < cleanHex.length; i += 64) {
topics.push('0x' + cleanHex.substring(i, i + 64));
}
return topics;
}
}
// Usage example
const validator = new TronProofValidator("https://nile.trongrid.io");
const result = await validator.validateCrossChainProof(
proofHex,
"TD39R92XN4HqXUQCPmtnZV1mhcgMy7Qbn8"
);
console.log("Validation successful:", result);
Best Practice: Always convert addresses to a consistent format in your application logic to avoid comparison issues between TRON and EVM chains.