TRC-20 Token Development (Tron)
TRC-20 is the token standard on Tron, intentionally copied from ERC-20. If you're familiar with Solidity and OpenZeppelin, TRC-20 development takes days, not weeks. Primary reason to choose Tron — enormous USDT volume lives there (largest in world by late 2024), transaction costs lower than Ethereum, and audience from Asian region.
What differs from ERC-20
Tron uses Solidity-compatible compiler — most ERC-20 code transfers without changes. But there are fundamental differences:
Energy and Bandwidth instead of Gas — in Tron two resources:
- Bandwidth — for regular TRX transfers. Each account gets ~600 bandwidth free daily
- Energy — for smart contract execution. Can freeze TRX to get Energy, or pay TRX directly
Typical TRC-20 transfer transaction costs ~10–30 energy. Contract deploy — several thousand. With Energy deficit transaction still executes, but user pays TRX at market Energy rate.
TRC-20 vs TRC-10 — TRC-10 is native token without smart contract (like TRX, just custom). TRC-20 is smart contract with ERC-20 interface. For most use cases you need TRC-20.
Addresses — Tron addresses start with T (Base58Check encoding), not hex like Ethereum. In smart contract code addresses are same 20 bytes, just different representation.
Writing TRC-20 contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.6;
// On Tron use tronide.io or TronBox
// OpenZeppelin imports work through TronBox
contract MyTRC20 {
string public name;
string public symbol;
uint8 public decimals = 6; // 6 decimals — norm for Tron (like USDT TRC-20)
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
constructor(
string memory _name,
string memory _symbol,
uint256 _initialSupply
) {
name = _name;
symbol = _symbol;
totalSupply = _initialSupply * 10 ** decimals;
balanceOf[msg.sender] = totalSupply;
emit Transfer(address(0), msg.sender, totalSupply);
}
function transfer(address to, uint256 value) external returns (bool) {
require(balanceOf[msg.sender] >= value, "Insufficient balance");
balanceOf[msg.sender] -= value;
balanceOf[to] += value;
emit Transfer(msg.sender, to, value);
return true;
}
function approve(address spender, uint256 value) external returns (bool) {
allowance[msg.sender][spender] = value;
emit Approval(msg.sender, spender, value);
return true;
}
function transferFrom(address from, address to, uint256 value) external returns (bool) {
require(balanceOf[from] >= value, "Insufficient balance");
require(allowance[from][msg.sender] >= value, "Insufficient allowance");
balanceOf[from] -= value;
balanceOf[to] += value;
allowance[from][msg.sender] -= value;
emit Transfer(from, to, value);
return true;
}
}
Deploy via TronBox
// migrations/2_deploy_token.js
const MyTRC20 = artifacts.require("MyTRC20");
module.exports = function(deployer, network, accounts) {
deployer.deploy(
MyTRC20,
"My Token",
"MTK",
1000000 // 1M tokens
);
};
# tronbox-config.js — network configuration
module.exports = {
networks: {
shasta: { // testnet
privateKey: process.env.PRIVATE_KEY,
userFeePercentage: 100,
feeLimit: 1000000000, // 1000 TRX max fee
fullHost: "https://api.shasta.trongrid.io",
network_id: "2"
},
mainnet: {
privateKey: process.env.PRIVATE_KEY_MAINNET,
userFeePercentage: 100,
feeLimit: 1000000000,
fullHost: "https://api.trongrid.io",
network_id: "1"
}
}
};
# Deploy
tronbox migrate --network shasta
Integration with TronWeb (frontend/backend)
import TronWeb from "tronweb";
const tronWeb = new TronWeb({
fullHost: "https://api.trongrid.io",
headers: { "TRON-PRO-API-KEY": process.env.TRONGRID_API_KEY },
privateKey: process.env.PRIVATE_KEY // backend only
});
// Get balance
const contract = await tronWeb.contract().at("TContractAddress...");
const balance = await contract.balanceOf("TUserAddress...").call();
console.log("Balance:", tronWeb.fromSun(balance)); // decimals conversion
// Transfer
const tx = await contract.transfer(
"TRecipientAddress...",
tronWeb.toSun(100) // 100 tokens with decimals
).send({
feeLimit: 100_000_000, // 100 TRX fee limit
callValue: 0,
shouldPollResponse: true
});
Listing on exchanges and JustSwap
For listing on JustSwap (DEX on Tron, Uniswap v2 analogue):
// Add liquidity
const routerAddress = "TXF8e7cL5..."; // JustSwap Router
const router = await tronWeb.contract(JUSTSWAP_ROUTER_ABI).at(routerAddress);
// First approve token for router
await tokenContract.approve(routerAddress, ethers.MaxUint256).send();
// Add liquidity TOKEN/TRX
await router.addLiquidityTRX(
tokenContractAddress,
tokenAmount, // quantity of tokens
tokenAmountMin, // slippage tolerance
trxAmountMin,
Math.floor(Date.now() / 1000) + 600 // deadline
).send({ callValue: trxAmount }); // TRX sent as callValue
Contract verification after deploy is mandatory — via Tronscan (Etherscan analogue for Tron). Without verification users don't see source code and don't trust token.
Timeline for standard TRC-20 token development: 3–7 working days including Shasta testnet testing and deployment with verification.







