Tronergydocs

Pay fees in tokens

Let the user pay you a fee in USDT or TRX as part of the same relay.

In these modes the user signs two transactions: a small fee payment, and the transaction they actually want. Tronergy broadcasts the fee first, waits for it to confirm, then broadcasts the user's transaction.

Your balance still pays the network cost, the same as Send USDT. The fee is how the user pays you back.

Pick a mode

  • token_pay: the fee is a USDT transfer. The user needs zero TRX. Use this for most users.
  • trx_fee_token_pay: the fee is a plain TRX transfer. The user must have an active account with enough TRX for the fee and for the fee transfer's own bandwidth.

trx_fee_token_pay does not work for a new wallet with no TRX. Use token_pay for those users.

Get a fee quote

Call estimate with the user's transaction. The response tells you the suggested fee and where to send it.

quote.ts
const USDT = 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t'

const quote = await call('/chain_relayers/portal/estimate', {
  chain: 'tron',
  mode: 'token_pay', // or 'trx_fee_token_pay'
  environment: 'live',
  payload: {
    // token_pay only: describe the fee transfer (any small amount, used to simulate energy)
    fee_contract_address: USDT,
    fee_function_selector: 'transfer(address,uint256)',
    fee_parameter: [
      { type: 'address', value: 'TFee...' },
      { type: 'uint256', value: '1000000' },
    ],
    fee_user_address: 'TXyz...',
    // both modes: the user's own transaction
    user_contract_address: USDT,
    user_function_selector: 'transfer(address,uint256)',
    user_parameter: [
      { type: 'address', value: 'TAbc...' },
      { type: 'uint256', value: '25000000' },
    ],
    user_user_address: 'TXyz...',
  },
})

For trx_fee_token_pay, leave out the fee_* fields.

What you get back:

  • token_pay: feeAmount (USDT base units, 6 decimals), feeTokenContract, feeRecipient.
  • trx_fee_token_pay: feeAmountSun (TRX in sun, 1 TRX = 1,000,000 sun), feeRecipient.

call() is a small fetch helper. See the quickstart script.

Build both transactions

build.ts
const built = await call('/chain_relayers/portal/construct-tx', {
  chain: 'tron',
  mode: 'token_pay',
  environment: 'live',
  senderAddress: 'TXyz...',
  contractAddress: USDT,
  functionSelector: 'transfer(address,uint256)',
  parameter: [
    { type: 'address', value: 'TAbc...' },
    { type: 'uint256', value: '25000000' },
  ],
  feeTokenContract: quote.feeTokenContract, // token_pay only
  feeAmountSun: quote.feeAmount,            // trx_fee_token_pay: quote.feeAmountSun
  feeRecipient: 'TFee...',                  // optional: your own address
})

const feeTx = built.unsignedTxs.find((entry) => entry.id === 'fee_tx').tx
const userTx = built.unsignedTxs.find((entry) => entry.id === 'user_tx').tx

feeAmountSun is the field name in both modes. For token_pay it holds USDT base units.

Sign both

The same wallet signs both.

sign.ts
const feeSignedTx = await tronWeb.trx.sign(feeTx, privateKey)
const userSignedTx = await tronWeb.trx.sign(userTx, privateKey)

Execute

execute.ts
const { operationId } = await call('/chain_relayers/portal/execute', {
  chain: 'tron',
  mode: 'token_pay',
  environment: 'live',
  idempotencyKey: randomUUID(),
  payload: {
    fee_signed_tx: feeSignedTx,
    user_signed_tx: userSignedTx,
    fee_recipient: 'TFee...', // required if you set feeRecipient when building
  },
})

If you sent the fee to your own address, pass the same address as fee_recipient. Otherwise the relay fails with FEE_RECIPIENT_MISMATCH.

Read the result

Track the operation as usual. When it is completed:

  • txHash is the user's transaction.
  • txHashSecondary is the fee transaction.

Good to know

  • You choose the amount. Tronergy checks who receives the fee, not how much. The quoted fee matches the relay cost, but you can charge more or less.
  • The fee lands first. If the user's transaction fails after the fee confirmed, the fee stays paid.
  • Balance check. Before anything is spent, Tronergy checks that the user can cover the fee. If not, the relay fails and costs you nothing.
  • Time limit. Each signed transaction needs at least 50 seconds of life left when you call execute.
  • Fee collection off? If estimate returns no fee fields, or construct-tx says Fee payment is not available right now, pass your own feeRecipient or contact support.

See construct and execute for every field.