Skip to main content
This recipe shows how your app can charge users a fee on TRON token transfers, such as a flat amount or a percentage of each transfer, and verify on-chain that the fee was paid. A fee is separate from gas sponsorship, which covers the user’s TRX gas. You can charge a fee without sponsoring gas, sponsor gas without charging a fee, or combine them, for example to recover what sponsorship costs you.

Fees and gas sponsorship

How to collect a fee on TRON

A TRON transaction makes one contract call, so a wallet can’t add the fee to the same transaction the way a batched EVM call can. You can collect it in one of two ways:
  • A second transfer that your backend verifies. The user sends the fee to your fee wallet, then the transfer, and your backend checks both on-chain. This page covers this approach.
  • A contract that moves both amounts in one transaction. The user approves the contract once, and each transfer through it pays the fee atomically. This page doesn’t cover this approach.
Collecting the fee before the transfer ensures that a user who abandons the flow has already paid it.

Prerequisites

  • A backend that verifies the user’s Dynamic JWT and checks that the user’s address is one of the user’s wallets, as in the TRON gas recipe’s step 1.
  • The full-node helpers tronPost and waitForConfirmation from the TRON gas recipe’s step 2, and tronweb to encode and decode addresses.
  • A fee wallet: a TRON address you control that receives fees. It should already hold the token, because a transfer to an address that doesn’t hold it costs more Energy.
  • To combine the fee with gas sponsorship, the backend and frontend from the TRON gas recipe.

Set the fee amount

Amounts are in the token’s base units; USDT has 6 decimals, so 1 USDT is '1000000'. A flat fee is a constant, and a percentage fee is a share of the transfer amount, for example in basis points:
To price the fee from the gas your sponsor wallet covers, use priceGasFee in Combine the fee with gas sponsorship.

Verify the fee (backend)

verifyTransfer decodes a transaction and checks its sender, token, recipient, amount, and result. Verify both the transfer and the fee before you provide the service the fee pays for, and record each transaction ID so that it can’t be reused.
Without gas sponsorship, the user’s wallet pays its own TRX gas for both transfers, and your backend checks them like this:
Compute fee.amount on your backend from your own fee rule, not from a value the frontend sends.

Combine the fee with gas sponsorship

When you also sponsor gas, sponsorship gives your backend something to withhold: it refuses to sponsor a wallet again until its fee arrives. In this flow, the user sends the fee first, and your backend verifies it before the user sends the transfer.1
  1. Your frontend asks for a quote and shows the fee to the user.
  2. When the user confirms, your backend delegates Energy for both transfers and tops up TRX for any Bandwidth that free Bandwidth doesn’t cover.
  3. The user’s wallet signs and sends the fee, and your backend verifies it on-chain.
  4. The user’s wallet signs and sends the transfer, and your backend verifies it and reclaims the Energy. If the user cancels or abandons the flow before paying the fee, it reclaims the Energy without a fee. After the fee, it reclaims the Energy and keeps the fee.

1. Price the fee and lend the gas (backend)

These functions run in the same backend as the TRON gas recipe and use its tronPost, signAndBroadcast, waitForConfirmation, and sponsorAddress from steps 1 and 2. Activate the user’s address as in step 4 before sponsoring it. They also use verifyTransfer, usedTxids, TOKEN_CONTRACT, and FEE_WALLET from Verify the fee, and read these environment variables:
  • FIXED_FEE_BASE_UNITS (optional): a flat fee in the token’s base units, instead of pricing from the TRX the gas would burn
  • FEE_MULTIPLIER and PENDING_FEE_TTL_MS (optional)
priceGasFee estimates both transfers and converts the TRX they would burn into the token, so replace getTrxUsdtPrice with your own price source. The fee transfer’s Energy is estimated with 1 base unit; the amount doesn’t change it.
The backend delegates Energy as in the recipe’s step 4, sized for both transfers. For Bandwidth, it counts free and delegated Bandwidth in whole transfers, because a transfer can’t split its Bandwidth between them, and sends the user the TRX for the rest.

2. Verify the fee, then the transfer (backend)

The backend records each sponsored wallet’s pending fee and the wallet’s EnergyUsed at sponsorship, which shows later whether the user spent the delegated Energy. payFee verifies the fee before the user sends the transfer, and settleFee refuses a transfer until the fee is paid.
Expose getGasQuote, sponsorWithFee, payFee, and settleFee as endpoints, here /api/gas-quote, /api/sponsor, /api/sponsor/fee, and /api/sponsor/settle, and give each one the JWT and wallet checks from the recipe’s step 1.

3. Cancel and expire sponsorships (backend)

If the user cancels before sending, or stops after paying the fee, reclaim the Energy and clear the pending fee. Expose cancelSponsorship as /api/sponsor/cancel. Run expireStaleSponsorships on a schedule, such as every minute, to reclaim Energy from abandoned flows. A wallet that spent sponsored Energy without paying the fee keeps its unsettled fee until the user pays it.
In production, keep these points in mind:
  • Store pendingFees and usedTxids in a database that every server instance and the scheduled job share. On a serverless host, run expireStaleSponsorships from a scheduled endpoint, because an in-memory Map and a timer work only in one long-lived process.
  • Set PENDING_FEE_TTL_MS longer than the slowest user takes to approve both transfers. An expired sponsorship loses its Energy, and a transfer sent afterwards can fail.
  • If your backend also reclaims from other wallets when the sponsor runs low, skip wallets with a pending fee. Otherwise, it can take back the Energy between the user’s two transfers.
  • The TRX from topUpBandwidth can’t be reclaimed. A user who cancels keeps it, so rate-limit sponsorships per user.

4. Send the fee, then the transfer (frontend)

Show the quoted fee on your confirm screen before calling sendGaslessTransferWithFee, because the user signs two transfers: the fee and the one they asked for. The function sends the fee first and waits for your backend to verify it, which takes a few seconds, before it asks the user to sign the transfer. If either transfer fails to send, it cancels the sponsorship. These functions reuse USDT_CONTRACT from the recipe’s step 5.
A user who sends the transfer without the fee, outside this function, keeps an unsettled fee and can’t be sponsored again. To let them pay it, send the fee transfer, call /api/sponsor/fee, and then call /api/sponsor/settle with the original mainTxid.

Next steps

Footnotes

  1. Tested with gas sponsorship on Nile on November 4 and 5, 2026, with BTT and a fixed 0.1 BTT fee, in this order: fee, then transfer. The test ran eight cases: the normal flow, a skipped fee, stopping after the fee, cancelling before sending, a failed transfer after the fee, expiry, a failed fee, and a failed decoy fee. In the normal flow, the fee was 1524d96a… and the transfer was 89e08c43…; each used 13,276 Energy. A run with a Dynamic embedded wallet sent the fee fd433876… and then the transfer 2019f83a… six seconds later. Fees without sponsorship and percentage fees weren’t tested separately. ↩
Last modified on November 5, 2026