TRON smart contract integration
This guide explains how to accept non-custodial crypto payments on TRON using Volet.com payment smart contracts and TronWeb.
Customers connect their wallets, approve transactions and pay directly to your receiving wallet through a smart contract. Funds are not credited to a Volet.com account.
Before proceeding, review Smart contract integration for an overview of the payment flow.
Integration at a glance
- Connect the customer's wallet and confirm that it is using TRON Mainnet.
- Configure the payment contracts and your receiving wallet.
- Create an order in your backend before initiating payment.
- Call
previewPayment()to calculate the total payment amount. - Call
transfer()to initiate the payment. For USDT, check the token allowance and request approval if needed. - Save the payment transaction hash (
txid) against the order. - Verify the transaction on your backend using solidified blockchain data before marking the order as paid.
Before you start
You will need:
- Customer wallet: TronLink for browser payments.
- Library: TronWeb for interacting with TRON wallets and smart contracts.
- Merchant wallet: A TRON wallet address you control to receive payments.
- Backend and database: To create orders, store transaction details and verify payments.
- Blockchain access: A TRON node or hosted service, such as TronGrid, to retrieve and verify transactions.
For mobile wallet connections, you will also need a WalletConnect Project ID and a compatible TRON wallet integration.
Supported networks and assets
| Network | Supported assets |
|---|---|
| TRON Mainnet | TRX, USDT (TRC-20) |
TRX and USDT use separate payment processor contracts.
TRX amounts are expressed in SUN, the smallest unit of TRX. One TRX equals 1,000,000 SUN.
USDT TRC-20 uses 6 decimals. One USDT equals 1,000,000 token base units.
The code examples below contain the processor and token addresses from the integration configuration.
For contract addresses, token decimals and function parameters, see Smart contract reference.
Before using the examples in production, confirm the processor deployment addresses and contract interface with Volet.com.
Step 1. Connect the customer's wallet
Use TronLink to connect the customer's browser wallet.
<button id="connect-tron">Connect TronLink</button>
<script>
let tronWeb;
document.getElementById('connect-tron').addEventListener('click', async () => {
if (!window.tronLink || !window.tronWeb) {
alert('Install and unlock TronLink.');
return;
}
const result = await window.tronLink.request({
method: 'tron_requestAccounts'
});
if (result?.code !== 200) {
throw new Error('Wallet connection rejected');
}
tronWeb = window.tronWeb;
document.getElementById('connect-tron').textContent =
tronWeb.defaultAddress.base58;
});
</script>
The customer must connect to TRON Mainnet.
Verify the active network before submitting a payment. Do not rely only on the RPC endpoint URL to identify the network, as wallets may use custom nodes.
Your application should also detect wallet account and network changes and revalidate the connection before initiating a transaction.
Step 2. Configure the payment contracts
Add the payment processor ABI and token functions to your application.
const PROCESSOR_ABI = [
{
name: 'previewPayment',
type: 'function',
stateMutability: 'view',
inputs: [
{ name: 'amount', type: 'uint256' },
{ name: 'buyerPaysCommission', type: 'bool' },
],
outputs: [
{ name: 'commission', type: 'uint256' },
{ name: 'totalPay', type: 'uint256' },
],
},
{
name: 'transfer',
type: 'function',
stateMutability: 'payable',
inputs: [
{ name: 'seller', type: 'address' },
{ name: 'amount', type: 'uint256' },
{ name: 'buyerPaysCommission', type: 'bool' },
{ name: 'optionalCommissionWallet', type: 'address' },
],
outputs: [],
},
];
const TOKEN_ABI = [
{
name: 'balanceOf',
type: 'function',
stateMutability: 'view',
inputs: [{ name: 'account', type: 'address' }],
outputs: [{ type: 'uint256' }],
},
{
name: 'allowance',
type: 'function',
stateMutability: 'view',
inputs: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
],
outputs: [{ type: 'uint256' }],
},
{
name: 'approve',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'spender', type: 'address' },
{ name: 'amount', type: 'uint256' },
],
outputs: [{ type: 'bool' }],
},
];
const TRON_PROCESSOR_TRX = 'TSK9sgBTjvZa6x2yW1bXNWqvQ57WsVUQ3W';
const TRON_PROCESSOR_USDT = 'TDuE6VueULx5oivQd4MsYr9DEhNMgXNfTm';
const TRON_USDT = 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t';
const TRON_ZERO = 'T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb';
const TRON_FEE_LIMIT = 500_000_000;
The payment processor provides two main functions:
previewPayment(amount, buyerPaysCommission)calculates the processing fee and the total amount the customer must pay.transfer(seller, amount, buyerPaysCommission, optionalCommissionWallet)executes the payment and distributes funds according to the contract's fee settings.
The amount parameter uses the asset's smallest units: SUN for TRX or token base units for USDT.
The buyerPaysCommission parameter determines who covers the processing fee:
true: The processing fee is added to the customer's payment.false: The processing fee is deducted from the merchant's payment amount.
Use TRON_ZERO for optionalCommissionWallet if no partner commission wallet applies.
Always obtain the total payment amount from previewPayment(). Do not calculate processing fees manually.
The example's TRON_FEE_LIMIT is expressed in SUN and represents a maximum fee limit of 500 TRX, not an expected transaction fee. Estimate the required Energy and review the appropriate fee limit before production use.
For complete parameter definitions, see Smart contract reference.
Step 3. Accept TRX
Native TRX payments require one wallet transaction.
Use previewPayment() to calculate the exact amount of SUN to send with the transaction.
async function payTrx(tronWeb, seller, humanAmount, buyerPaysCommission = true) {
if (!tronWeb.isAddress(seller)) {
throw new Error('Invalid TRON seller address');
}
const processor = tronWeb.contract(
PROCESSOR_ABI,
TRON_PROCESSOR_TRX
);
const amount = tronWeb.toSun(humanAmount).toString();
const preview = await processor.previewPayment(
amount,
buyerPaysCommission
).call();
return processor.transfer(
seller,
amount,
buyerPaysCommission,
TRON_ZERO
).send({
callValue: preview.totalPay.toString(),
feeLimit: TRON_FEE_LIMIT
});
}
The transaction's callValue must equal totalPay returned by previewPayment().
The customer must also have enough TRX to cover blockchain resources and transaction fees.
The function returns the payment transaction hash (txid). Save it against the existing order and verify the payment before fulfilment.
Step 4. Accept USDT
USDT TRC-20 payments use a token contract and may require an additional approval transaction.
Before making a payment, your application must:
- Check the customer's USDT balance using
balanceOf(). - Check the processor's spending allowance using
allowance(). - If the allowance is insufficient, request approval using
approve(). - Wait for the approval to be confirmed.
- Call
transfer()to execute the payment.
async function payTronUsdt(tronWeb, seller, amountBaseUnits, buyerPaysCommission = true) {
if (!tronWeb.isAddress(seller)) {
throw new Error('Invalid TRON seller address');
}
if (!/^\d+$/.test(amountBaseUnits) || BigInt(amountBaseUnits) <= 0n) {
throw new Error('USDT amount must be a positive integer in base units');
}
const processor = tronWeb.contract(
PROCESSOR_ABI,
TRON_PROCESSOR_USDT
);
const token = tronWeb.contract(
TOKEN_ABI,
TRON_USDT
);
const owner = tronWeb.defaultAddress.base58;
const preview = await processor.previewPayment(
amountBaseUnits,
buyerPaysCommission
).call();
const total = BigInt(preview.totalPay.toString());
const balance = BigInt(
(await token.balanceOf(owner).call()).toString()
);
if (balance < total) {
throw new Error('Insufficient USDT');
}
const allowance = BigInt(
(await token.allowance(owner, TRON_PROCESSOR_USDT).call()).toString()
);
if (allowance < total) {
const approvalTxid = await token.approve(
TRON_PROCESSOR_USDT,
total.toString()
).send({ feeLimit: TRON_FEE_LIMIT });
return {
state: 'approval_pending',
approvalTxid
};
}
const paymentTxid = await processor.transfer(
seller,
amountBaseUnits,
buyerPaysCommission,
TRON_ZERO
).send({ feeLimit: TRON_FEE_LIMIT });
return {
state: 'payment_pending',
paymentTxid
};
}
USDT approval requirements
Depending on the existing allowance, a USDT payment may require:
| Existing allowance | Required transactions |
|---|---|
| Sufficient | Payment only |
| Insufficient | Approval + payment |
If an approval is required, the function returns approval_pending with the approval transaction hash.
Wait for the approval to be confirmed before calling the payment function again. Once the allowance is sufficient, the function submits the payment and returns payment_pending with the payment transaction hash.
Important:
- An
approve()transaction is not a payment. - The customer needs enough TRX to cover Energy and Bandwidth costs, even when paying USDT.
- USDT amounts must be expressed as integers in token base units. Avoid floating-point calculations such as
Math.round(parseFloat(amount) * 1e6). - Track approval transactions separately from the final payment transaction.
Step 5. Save the payment transaction
Your backend must create an order before the customer submits a payment.
Each order should contain the expected:
- Order ID
- Blockchain network
- Asset and amount
- Merchant receiving wallet (
seller) - Approved payment processor contract
- Buyer wallet, if required by your checkout
- Processing fee setting (
buyerPaysCommission)
After the customer submits the payment:
- Obtain the payment transaction hash (
txid) returned by the wallet. - Send the hash to your backend.
- Store it together with the order ID and blockchain network.
- Start server-side payment verification.
Each payment transaction must be associated with no more than one order. Enforce this using a unique (network, txid) constraint in your database.
The smart contract's transfer() function does not accept an order ID. The relationship between your order and its payment transaction must be maintained in your application.
If a customer retries a payment, keep separate payment attempts rather than overwriting transaction records that may still be pending.
Step 6. Verify the payment
Payment verification is required before completing an order.
A returned transaction hash only indicates that a transaction was submitted. It does not prove that the merchant received the correct payment.
Your backend must independently:
- Retrieve the transaction and execution receipt from the blockchain.
- Confirm successful execution using a solidified TRON receipt.
- Verify that the transaction used the expected payment processor.
- Match the asset, merchant wallet, amount and contract call against the order.
- Confirm that the merchant received the expected funds.
- Ensure the transaction has not already been used for another order.
- Mark the order as paid and fulfil it only once.
For solidified transaction data, use the TRON SolidityNode endpoint:
POST https://api.trongrid.io/walletsolidity/gettransactioninfobyid
Check that receipt.result === 'SUCCESS', then verify the transaction body and actual payment details against the order.
A successful receipt alone is not sufficient. A transaction may have executed successfully without paying the correct merchant or amount.
If the blockchain service is temporarily unavailable or the transaction has not yet been solidified, keep the order pending and retry verification.
Follow Payment verification and order tracking for the complete backend implementation, TRON confirmation requirements and production checklist.
You can also review TRON confirmation semantics.
Optional: Support mobile wallets
To support mobile wallets through WalletConnect, use a compatible TRON wallet integration, such as @tronweb3/walletconnect-tron, together with TronWeb.
This requires a WalletConnect Project ID and an integration that can create TRON transactions, request signatures from mobile wallets and broadcast signed transactions.
The browser example described below supports TronLink. It does not implement the mobile WalletConnect flow.
Example integration
The example-tron.html demonstrates:
- Connecting TronLink.
- Calculating payment amounts using
previewPayment(). - Sending TRX and USDT TRC-20 payments.
- Displaying transaction hashes and tracking transaction status.
You can open the example in your browser or download the HTML file to study and adapt the code:
- Open the live example — opens the interactive demo. It uses TRON Mainnet, so confirming a payment transfers real funds.
- Download example-tron.html — saves the HTML file to your computer.
Browser wallet extensions such as TronLink may not work correctly with pages opened directly from disk (file://). Serve the downloaded file from a local web server instead. For example, run npx serve in the folder containing the file and open the localhost URL it provides.
Try the example
- Open the live example or serve the downloaded file locally in a browser with TronLink installed.
- Select Mainnet in TronLink, then click Connect TronLink. The demo warns you if the wallet is connected to another network.
- Select the payment method: TRX or USDT TRC-20.
- Enter the receiving TRON wallet in Seller address.
- Enter the payment amount and choose who pays the processing fee.
- Click Preview to see the processing fee and total payment amount. Then click Pay and confirm the transaction in TronLink. USDT payments may require a separate token approval first.
- The order and transaction hash appear in the Orders table. The demo checks the transaction status for up to 5 minutes.
Before you pay:
- Use two different wallets. The seller address must differ from the paying wallet. Otherwise, the contract rejects the payment with
SellerIsBuyer.- Transactions use real funds. The example is prefilled with
1TRX. Check the payment amount, processing fee and TRON network resource costs (Energy and Bandwidth) before confirming.
Demo limitations: This example stores transaction history in browser
localStorageand is not a production checkout. It checks the latest transaction receipt usinggetTransactionInfo(), which does not establish TRON solidification. The 5-minute tracking window is only a demo limit, not a payment confirmation deadline. Implement the full payment verification process before accepting customer orders.
Related
- Smart contract integration — overview of the non-custodial payment flow.
- Ethereum and BNB Smart Chain integration — accept ETH, BNB and USDT.
- Smart contract reference — processor addresses, token decimals and contract parameters.
- Troubleshooting — common wallet and transaction errors.