Overview
Tron uses the secp256k1 elliptic curve with keccak256 hashing — the same cryptographic scheme as Ethereum. Since Tron addresses are derived from the same keccak256 hash of the public key, you can derive a Tron address directly from the EVM address without key recovery. The only difference is the address encoding: Tron uses base58check with a 0x41 version byte instead of Ethereum’s 0x hex prefix.
| Property | Value |
|---|
| Curve | secp256k1 |
| Root Wallet | EVM |
| Address Format | base58check (T...) |
| Hashing | Keccak-256 |
| Serialization | TronWeb SDK |
| Smallest Unit | Sun (1 TRX = 10^6 sun) |
Dependencies
Derive Address
Swap the EVM address prefix from 0x to 41 and base58check-encode:
import { TronWeb } from "tronweb";
function deriveTronAddress(evmAddress: string): string {
const hex = "41" + evmAddress.slice(2).toLowerCase();
return TronWeb.address.fromHex(hex);
}
This is the simplest derivation of all secp256k1 chains — no key recovery needed. The last 20 bytes of keccak256(uncompressed_pubkey) are identical for both EVM and Tron.
Sign a Message
Sign using Tron’s message prefix via the EVM WaaS provider. TronWeb’s hashMessage computes keccak256("\x19TRON Signed Message:\n" + len + message):
import { keccak256, toUtf8Bytes, concat } from "tronweb/utils";
import { createWaasProvider } from "@dynamic-labs-sdk/client/waas/core";
import type { DynamicClient } from "@dynamic-labs-sdk/client";
import type { WalletAccount } from "@dynamic-labs-sdk/client";
async function signTronMessage(
message: string,
evmWallet: WalletAccount,
dynamicClient: DynamicClient,
): Promise<string> {
const provider = createWaasProvider({
sdkClient: dynamicClient,
chain: "EVM",
});
const messageBytes = toUtf8Bytes(message);
const prefix = toUtf8Bytes("\x19TRON Signed Message:\n");
const lengthStr = toUtf8Bytes(String(messageBytes.length));
const messageDigest = keccak256(concat([prefix, lengthStr, messageBytes]));
const { signature } = await provider.signRawMessage({
message: strip0x(messageDigest),
walletAccount: evmWallet,
});
return signature;
}
Verify a Signature
TronWeb provides built-in verification that returns the recovered address:
import { TronWeb } from "tronweb";
async function verifyTronSignature(
message: string,
signature: string,
): Promise<string> {
const tw = new TronWeb({ fullHost: "https://nile.trongrid.io" });
return tw.trx.verifyMessageV2(message, signature);
}
Check Balance
Native TRX Balance
import { TronWeb } from "tronweb";
async function getTronBalance(tronAddress: string): Promise<string> {
const tw = new TronWeb({ fullHost: "https://nile.trongrid.io" });
const sunBalance = await tw.trx.getBalance(tronAddress);
return (Number(sunBalance) / 1_000_000).toString();
}
TRC20 Token Balances
interface TronToken {
symbol: string;
contractAddress: string;
decimals: number;
}
const TRC20_ABI = [
{
constant: true,
inputs: [{ name: "who", type: "address" }],
name: "balanceOf",
outputs: [{ type: "uint256" }],
stateMutability: "View",
type: "Function",
},
{
inputs: [
{ name: "to", type: "address" },
{ name: "value", type: "uint256" },
],
name: "transfer",
outputs: [{ type: "bool" }],
stateMutability: "Nonpayable",
type: "Function",
},
];
async function getTrc20Balance(
tronAddress: string,
token: TronToken,
): Promise<string> {
const tw = new TronWeb({ fullHost: "https://nile.trongrid.io" });
const contract = tw.contract(TRC20_ABI, token.contractAddress);
const rawBalance = await contract.balanceOf(tronAddress).call({ from: tronAddress });
return (Number(rawBalance) / 10 ** token.decimals).toString();
}
Send a Transfer
Native TRX Transfer
import { TronWeb } from "tronweb";
import { createWaasProvider } from "@dynamic-labs-sdk/client/waas/core";
async function sendTrxTransfer(
to: string,
amount: number,
tronAddress: string,
evmWallet: WalletAccount,
dynamicClient: DynamicClient,
): Promise<string> {
const tw = new TronWeb({ fullHost: "https://nile.trongrid.io" });
const amountSun = BigInt(Math.round(amount * 1_000_000));
// Build unsigned transaction
const unsignedTx = await tw.transactionBuilder.sendTrx(
to,
Number(amountSun),
tronAddress,
);
return signAndBroadcast(unsignedTx, evmWallet, dynamicClient);
}
TRC20 Token Transfer
async function sendTrc20Transfer(
to: string,
amount: number,
tronAddress: string,
evmWallet: WalletAccount,
dynamicClient: DynamicClient,
token: TronToken,
): Promise<string> {
const tw = new TronWeb({ fullHost: "https://nile.trongrid.io" });
const amountSmallest = BigInt(Math.round(amount * 10 ** token.decimals));
const result = await tw.transactionBuilder.triggerSmartContract(
token.contractAddress,
"transfer(address,uint256)",
{ feeLimit: 100_000_000, callValue: 0 },
[
{ type: "address", value: to },
{ type: "uint256", value: amountSmallest.toString() },
],
tronAddress,
);
return signAndBroadcast(result.transaction, evmWallet, dynamicClient);
}
Sign and Broadcast Helper
Both TRX and TRC20 transfers share the same signing and broadcasting logic:
async function signAndBroadcast(
unsignedTx: Record<string, any>,
evmWallet: WalletAccount,
dynamicClient: DynamicClient,
): Promise<string> {
const tw = new TronWeb({ fullHost: "https://nile.trongrid.io" });
const provider = createWaasProvider({
sdkClient: dynamicClient,
chain: "EVM",
});
// The transaction ID is the hash to sign
const txId = strip0x(unsignedTx.txID);
const { signature } = await provider.signRawMessage({
message: txId,
walletAccount: evmWallet,
});
// Normalize the v-byte to Tron's expected range (27 or 28)
const sigBytes = hexToBytes(strip0x(signature));
const sig65 = new Uint8Array(65);
sig65.set(sigBytes.slice(0, 64));
let v = sigBytes[64];
if (v < 27) v += 27;
sig65[64] = v;
// Attach signature and broadcast
const signedTx = {
...unsignedTx,
signature: [bytesToHex(sig65)],
};
const result = await tw.trx.broadcast(signedTx);
if (!result.result) {
throw new Error(`Broadcast failed: ${result.message || "unknown error"}`);
}
return result.txid;
}
Tron signing is simpler than other chains because TronWeb handles transaction construction and serialization. You only need to sign the transaction ID (a SHA-256 hash) using the EVM wallet’s raw signing capability.