Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.privy.io/llms.txt

Use this file to discover all available pages before exploring further.

Transatron is a Tron RPC provider that delegates the energy and bandwidth required to process a transaction, so end users can transact on Tron without holding TRX. Apps integrate Transatron as a drop-in replacement for the Tron full node and select a fee-payment mode that fits their UX. This recipe combines Privy’s Tron wallet support with Transatron’s gas sponsorship to broadcast TRX-less transfers from a user’s embedded wallet.

Resources

Transatron docs

Official Transatron integration documentation.

Tron raw signing

How Privy signs Tron transactions via raw_sign.

Prerequisites

  • A Privy app with Tron support enabled.
  • A funded Transatron account and a Spender API key issued from the dashboard. Coupon flows charge the app’s TFN balance and require a Spender key — Non-Spender keys are read-only and will reject coupon creation.
Keep the Transatron API key on the server. Exposing it to a browser or mobile binary lets any caller spend the app’s Transatron balance.

How fee payment works

Transatron supports two non-custodial fee-payment modes that pair well with Privy embedded wallets:
ModeWhen to useUser holds TRX?
Instant paymentThe user wallet already holds a small TRX or USDT balance to cover Transatron’s per-tx fee.Yes (small)
CouponThe app fully sponsors gas. The backend issues a coupon backed by its Transatron balance.No
Both modes rely on broadcasting through the Transatron RPC. Submitting the same signed transaction to a vanilla Tron node bypasses Transatron’s resource delegation logic, and Tron charges fees as usual. This recipe focuses on the coupon mode, where the app covers transaction fees on behalf of its users. For the instant-payment flow, see Transatron’s integration guide.

1. Create a Tron wallet for the user

Privy supports Tron at the Tier 2 level. Create the wallet with chain_type: 'tron' and store the returned id, address, and public_key. The wallet’s address can immediately receive TRX, USDT, and other TRC-20 assets.

2. Configure TronWeb to use the Transatron RPC

Point TronWeb at https://api.transatron.io and attach the API key as a header. All subsequent operations — fee estimation, simulation, and broadcasts — flow through Transatron.
import {TronWeb, providers} from 'tronweb';

const TRANSATRON_RPC = 'https://api.transatron.io';
const TRANSATRON_TIMEOUT = 60_000;
const headers = {'TRANSATRON-API-KEY': process.env.TRANSATRON_API_KEY!};

const tronWeb = new TronWeb({
  fullNode: new providers.HttpProvider(TRANSATRON_RPC, TRANSATRON_TIMEOUT, '', '', headers),
  solidityNode: new providers.HttpProvider(TRANSATRON_RPC, TRANSATRON_TIMEOUT, '', '', headers),
  eventServer: new providers.HttpProvider(TRANSATRON_RPC, TRANSATRON_TIMEOUT, '', '', headers)
});

3. Sign Tron transactions with Privy

Privy’s raw_sign returns a 64-byte ECDSA signature, but Tron expects 65 bytes — the trailing recovery byte is either 0x1b or 0x1c. Signing is the only step in the flow that can run on the client; coupon creation and broadcasting still happen on the server, since both require the Transatron API key.

4. Broadcast a sponsored transaction with a coupon

Coupons let the app fully sponsor a user’s transaction. The backend creates a coupon backed by the app’s Transatron balance, returns the coupon ID to the client, and the client attaches the ID to a Privy-signed transaction before broadcasting. The code below runs entirely on the server. If signing happens in a React client (per step 3), replace the inline signTronTransaction(...) call with a roundtrip — return the unsigned transaction’s txID to the client, receive the 64-byte signature back, and finalize via attachTronSignature(...) before broadcasting.
1

Compute the Tron fee limit

Tron rejects transactions whose fee_limit is lower than the energy required to execute them. Use triggerConstantContract to estimate energy_used and multiply by the live energy_fee from chain parameters. This is the Tron-side fee limit that gets baked into the signed transaction.
const USDT_CONTRACT = 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t'; // USDT on Tron mainnet

async function estimateFeeLimit({
  ownerAddress,
  recipientAddress,
  amount
}: {
  ownerAddress: string;
  recipientAddress: string;
  amount: number;
}): Promise<number> {
  const ownerHex = tronWeb.address.toHex(ownerAddress);
  const contractHex = tronWeb.address.toHex(USDT_CONTRACT);

  const constant = await tronWeb.transactionBuilder.triggerConstantContract(
    contractHex,
    'transfer(address,uint256)',
    {},
    [
      {type: 'address', value: recipientAddress},
      {type: 'uint256', value: amount}
    ],
    ownerHex
  );

  const params = await tronWeb.trx.getChainParameters();
  const energyFee = params.find((p) => p.key === 'getEnergyFee')?.value ?? 420;
  const energyUsed = constant.energy_used ?? 0;

  return Math.ceil(energyUsed * energyFee * 1.1); // 10% buffer
}
2

Get a Transatron fee quote

Simulate the same transfer through Transatron to receive a TFN quote. Transatron returns its quote inside a transatron object alongside the standard Tron response. Pass the feeLimit from the previous step so the simulation reflects the actual transaction you’ll broadcast.
async function quoteTransatronFee({
  ownerAddress,
  recipientAddress,
  amount,
  feeLimit
}: {
  ownerAddress: string;
  recipientAddress: string;
  amount: number;
  feeLimit: number;
}): Promise<number> {
  const ownerHex = tronWeb.address.toHex(ownerAddress);
  const contractHex = tronWeb.address.toHex(USDT_CONTRACT);

  const args = tronWeb.transactionBuilder._getTriggerSmartContractArgs(
    contractHex,
    'transfer(address,uint256)',
    {feeLimit, callValue: 0, txLocal: true},
    [
      {type: 'address', value: recipientAddress},
      {type: 'uint256', value: amount}
    ],
    ownerHex,
    0,
    '',
    0,
    feeLimit
  );

  const response = await tronWeb.fullNode.request<{
    transatron?: {tx_fee_rtrx_account: number};
  }>('wallet/triggersmartcontract', args, 'post');

  if (!response.transatron?.tx_fee_rtrx_account) {
    throw new Error('Transatron did not return a fee quote');
  }

  return response.transatron.tx_fee_rtrx_account; // TFN
}
tx_fee_rtrx_account is the fee Transatron will deduct from your account in TFN (1 TFN ≈ 1 SUN of TRX).
3

Create the coupon

Create a coupon sized slightly above the Transatron quote. Transatron refunds any unspent TFN to the app’s account after the transaction is processed.
async function createCoupon({
  rtrxLimit,
  ownerAddress
}: {
  rtrxLimit: number;
  ownerAddress: string;
}): Promise<string> {
  const response = await tronWeb.fullNode.request<{
    code: string;
    coupon: {id: string; rtrx_limit: number; valid_to: number};
  }>(
    'api/v1/coupon',
    {
      rtrx_limit: Math.ceil(rtrxLimit * 1.15), // 15% buffer
      address: ownerAddress,
      valid_to: Date.now() + 10 * 60 * 1000 // 10 minutes
    },
    'post'
  );

  if (response.code !== 'SUCCESS') {
    throw new Error(`Coupon creation failed: ${response.code}`);
  }

  return response.coupon.id;
}
4

Build, sign, and broadcast the user transaction

Build the TRC-20 transfer locally, sign the txID with Privy, attach the coupon ID, and broadcast through the Transatron RPC.
async function sendSponsoredTransfer({
  walletId,
  walletAddress,
  recipientAddress,
  amount,
  couponId,
  feeLimit
}: {
  walletId: string;
  walletAddress: string;
  recipientAddress: string;
  amount: number;
  couponId: string;
  feeLimit: number;
}) {
  const ownerHex = tronWeb.address.toHex(walletAddress);
  const contractHex = tronWeb.address.toHex(USDT_CONTRACT);

  const built = await tronWeb.transactionBuilder._triggerSmartContractLocal(
    contractHex,
    'transfer(address,uint256)',
    {feeLimit, callValue: 0, txLocal: true},
    [
      {type: 'address', value: recipientAddress},
      {type: 'uint256', value: amount}
    ],
    ownerHex
  );

  const signed = await signTronTransaction({
    tronWeb,
    walletId,
    walletAddress,
    transaction: built.transaction as Types.SignedTransaction<Types.TriggerSmartContract>
  });

  const signedWithCoupon = {...signed, coupon: couponId};

  return tronWeb.fullNode.request('wallet/broadcasttransaction', signedWithCoupon, 'post');
}
The broadcast response contains a nested transatron object that confirms how much TFN was actually charged (tx_fee_rtrx_account) and how much was refunded to the coupon.
5

Orchestrate the full flow

Chain the helpers from the previous steps to send a sponsored TRC-20 transfer. The user’s wallet never holds TRX — the app pays for everything in TFN out of its Transatron balance.
const feeLimit = await estimateFeeLimit({ownerAddress, recipientAddress, amount});
const rtrxQuote = await quoteTransatronFee({
  ownerAddress,
  recipientAddress,
  amount,
  feeLimit
});
const couponId = await createCoupon({rtrxLimit: rtrxQuote, ownerAddress});

const result = await sendSponsoredTransfer({
  walletId,
  walletAddress: ownerAddress,
  recipientAddress,
  amount,
  couponId,
  feeLimit
});
If the coupon balance is insufficient or the coupon has expired, Transatron does not broadcast the user transaction. Re-estimate the fee, issue a new coupon, and retry.