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.
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
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').txfeeAmountSun is the field name in both modes. For token_pay it holds USDT base units.
Sign both
The same wallet signs both.
const feeSignedTx = await tronWeb.trx.sign(feeTx, privateKey)
const userSignedTx = await tronWeb.trx.sign(userTx, privateKey)Execute
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:
txHashis the user's transaction.txHashSecondaryis 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
estimatereturns no fee fields, orconstruct-txsaysFee payment is not available right now, pass your ownfeeRecipientor contact support.