Skip to main content

Overview

Tron uses the secp256k1 elliptic curve with keccak256 hashing — the same cryptographic scheme as Ethereum. Tron addresses are derived directly from the EVM address without key recovery: swap the 0x prefix for 41 and base58check-encode.
PropertyValue
Curvesecp256k1
Root WalletEVM
Address Formatbase58check (T...)
HashingKeccak-256
SerializationTronWeb SDK
Smallest UnitSun (1 TRX = 10^6 sun)

Dependencies

npm install tronweb

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

import { keccak256, toUtf8Bytes, concat } from "tronweb/utils";

async function signTronMessage(message: string): Promise<string> {
  const wallet = dynamicClient.wallets.userWallets.find(w => w.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 dynamicClient.wallets.waas.signRawMessage(wallet.id, {
    accountAddress: wallet.address,
    message: messageDigest.replace(/^0x/, ""),
  });

  return signature;
}

Sign a Transaction

Build the unsigned transaction with TronWeb, then sign the transaction ID and broadcast:
import { TronWeb } from "tronweb";

const tw = new TronWeb({ fullHost: "https://nile.trongrid.io" });

async function sendTrxTransfer(
  to: string,
  amount: number,
  tronAddress: string,
): Promise<string> {
  const wallet = dynamicClient.wallets.userWallets.find(w => w.chain === "EVM")!;
  const amountSun = BigInt(Math.round(amount * 1_000_000));

  const unsignedTx = await tw.transactionBuilder.sendTrx(
    to,
    Number(amountSun),
    tronAddress,
  );

  // The transaction ID is the hash to sign
  const txId = unsignedTx.txID.replace(/^0x/, "");
  const signature = await dynamicClient.wallets.waas.signRawMessage(wallet.id, {
    accountAddress: wallet.address,
    message: txId,
  });

  // Normalize the v-byte to Tron's expected range (27 or 28)
  const sigBytes = hexToBytes(signature.replace(/^0x/, ""));
  const sig65 = new Uint8Array(65);
  sig65.set(sigBytes.slice(0, 64));
  let v = sigBytes[64];
  if (v < 27) v += 27;
  sig65[64] = v;

  const signedTx = { ...unsignedTx, signature: [bytesToHex(sig65)] };
  const result = await tw.trx.broadcast(signedTx);

  if (!result.result) throw new Error(`Broadcast failed: ${result.message}`);
  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.

Verify a Signature

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

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();
}
For full implementation details including TRC20 transfers see the JavaScript Tron guide. The only difference in React Native is the signing call.