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.
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
tronPostandwaitForConfirmationfrom the TRON gas recipe’s step 2, andtronwebto 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:
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.
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- Your frontend asks for a quote and shows the fee to the user.
- When the user confirms, your backend delegates Energy for both transfers and tops up TRX for any Bandwidth that free Bandwidth doesn’t cover.
- The user’s wallet signs and sends the fee, and your backend verifies it on-chain.
- 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 itstronPost, 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 burnFEE_MULTIPLIERandPENDING_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.
2. Verify the fee, then the transfer (backend)
The backend records each sponsored wallet’s pending fee and the wallet’sEnergyUsed 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.
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. ExposecancelSponsorship 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.
- Store
pendingFeesandusedTxidsin a database that every server instance and the scheduled job share. On a serverless host, runexpireStaleSponsorshipsfrom a scheduled endpoint, because an in-memoryMapand a timer work only in one long-lived process. - Set
PENDING_FEE_TTL_MSlonger 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
topUpBandwidthcan’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 callingsendGaslessTransferWithFee, 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.
/api/sponsor/fee, and then call /api/sponsor/settle with the original mainTxid.
Next steps
Footnotes
-
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 was89e08c43…; each used 13,276 Energy. A run with a Dynamic embedded wallet sent the feefd433876…and then the transfer2019f83a…six seconds later. Fees without sponsorship and percentage fees weren’t tested separately. ↩