A TRC20 contract deployment, an explorer’s token registration, and a token transfer are different operations. The original note connected them through TronBox, Tronscan, and TronWeb on a development network.
Historical, version-mixed source. The earlier page linked a Solidity 0.5.16 Remix configuration. The retrieved contract Gist now declares Solidity 0.8.3. Its pinned revision below must not be mistaken for the exact contract source used in the earlier screenshots. Dependencies and compiler settings need a matching review.
The recorded deployment paths
npm install -g tronbox
npm install -g tronweb
tronbox init
- Open tronbox.js -> Uncomment compiler solc version
- Create a .env file containing private key
export PRIVATE_KEY_MAINNET="<TRON_PRIVATE_KEY>"
- Update 2_deploy_contract.js file
var MyContract = artifacts.require("./NCToken.sol");
module.exports = function(deployer) {
deployer.deploy(MyContract);
};
- To deploy:
tronbox compile --compile-all
source .env && tronbox migrate --reset --network shasta
Access: https://shasta.tronscan.org/#/contracts/contract-compiler
Upload contract file(s) -> Select RC20Token.sol
Select Compile -> Solidity Version: tron-0.5.8_Odyssey_v3.6.0 -> Confirm
Deploy -> Accept
Explorer registration was a separate step:
Access https://shasta.tronscan.org/#/tokens/create/Type
Select TRC20 Token -> Yes
Fill token info same with info defined in contract
Next -> Submit -> Confirm -> Accept
An explorer listing does not establish contract correctness or a successful transfer.
The retrieved contract bundle
The bundle includes token logic, owner-controlled maintenance/minting behavior, presenter hooks, interfaces, and test source. None is audited or executed by rendering this page.
Read the pinned contract and test-source files
ERC20FullToken.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.3;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./interfaces/IBurnable.sol";
import "./ERC20Token.sol";
/**
Function to receive approval and execute function in one call.
*/
abstract contract TokenRecipient {
function receiveApproval(address _from, uint256 _value, address _token, bytes memory _extraData) virtual public;
}
/**
ERC20FullToken
- Burnable
- Allow receive and approval in one call
- Mintable
- Exchangeable
*/
contract ERC20FullToken is ERC20Token, IBurnable {
constructor(string memory name_, string memory symbol_, uint8 decimals_, uint256 initialSupply_)
ERC20Token(name_, symbol_, decimals_, initialSupply_) {}
// Owner can transfer out any accidentally sent ERC20 tokens
function transferAnyERC20Token(address tokenAddress, uint tokens) public onlyOwner returns (bool success) {
return IERC20(tokenAddress).transfer(owner(), tokens);
}
// Approves and then calls the receiving contract
function approveAndCall(address _spender, uint256 _value, bytes memory _extraData) public returns (bool success) {
TokenRecipient spender = TokenRecipient(_spender);
approve(_spender, _value);
spender.receiveApproval(_msgSender(), _value, address(this), _extraData);
return true;
}
// Owner can mint more coin.
function mint(address _to, uint256 _amount) public onlyOwner {
_mint(_to, _amount);
}
// Handle if ether is sent to this address
receive() external payable {
// In-case: None Exchangeable
// If ether is sent to this address, send it back.
//revert();
// In-case: Exchangeable
// Send 1 Eth to get 100 ExchangeableToken
uint256 _amountEth = msg.value;
require(_amountEth >= 1, "1 ETH to get 1000000 Token");
uint256 _tokens = (_amountEth * 1000000 * 10 ** uint256(decimals())) / 1 ether;
_mint(_msgSender(), _tokens);
// Transfer ether to Owner
address payable payableOwner = payable(owner());
(bool success,) = payableOwner.call{value : _amountEth}("");
require(success, "Transfer failed.");
}
/**
* @dev Destroys `amount` tokens from `msg.sender`, reducing the total supply.
*/
function burn(uint amount) override external {
require(presenter == _msgSender(), "N07Token: presenter only");
_burn(_msgSender(), amount);
}
/**
* @dev Destroys `amount` tokens from `account`, deducting from the caller's allowance
*/
function burnFrom(address account, uint amount) override external {
require(presenter == _msgSender(), "N07Token: presenter only");
uint256 currentAllowance = allowance(account, _msgSender());
require(currentAllowance >= amount, "N07Token: burn amount exceeds allowance");
_approve(account, _msgSender(), currentAllowance - amount);
_burn(account, amount);
}
}
contract BUSD is ERC20FullToken {
constructor() ERC20FullToken("BUSD Token", "BUSD", 18, 10000000000) {}
}
ERC20Token.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.3;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "./interfaces/ITokenPresenter.sol";
/**
ERC20Token implementation
*/
contract ERC20Token is ERC20, Ownable {
address public presenter;
uint8 private _decimals;
constructor(string memory name_, string memory symbol_, uint8 decimals_, uint256 initialSupply_) ERC20(name_, symbol_) {
_decimals = decimals_;
_mint(_msgSender(), initialSupply_ * 10 ** uint256(decimals_));
}
/**
* @dev set the decimal
*/
function decimals() override public view returns (uint8) {
return _decimals;
}
/**
* @dev set the presenter of the token to decide transfer functionality
* @param _presenter address of presenter
*/
function setPresenter(address _presenter) onlyOwner public {
presenter = _presenter;
}
/**
* @dev transfer the tokens, if presenter is not set, normal behaviour
*/
function _transfer(address _from, address _to, uint256 _amount) internal override {
// Transfer fund and responsibility to presenter
if (presenter != address(0) && presenter != _msgSender()) {
super._transfer(_from, presenter, _amount);
ITokenPresenter(presenter).receiveTokens(_msgSender(), _from, _to, _amount);
} else {
super._transfer(_from, _to, _amount);
}
}
}
IBurnable.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.3;
interface IBurnable {
function burn(uint amount) external;
function burnFrom(address account, uint amount) external;
}
ITokenPresenter.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.3;
interface ITokenPresenter {
function receiveTokens(address trigger, address _from, address _to, uint256 _amount) external returns (bool);
}
Maintainable.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.3;
import "@openzeppelin/contracts/access/Ownable.sol";
contract Maintainable is Ownable {
bool public isMaintenance = false;
bool public isOutdated = false;
// Check if contract is not in maintenance
function ifNotMaintenance() internal view {
require(!isMaintenance, "Maintenance");
require(!isOutdated, "Outdated");
}
// Check if contract on maintenance for restore
function ifMaintenance() internal view {
require(isMaintenance, "!Maintenance");
}
// Enable maintenance
function enableMaintenance(bool status) onlyOwner public {
isMaintenance = status;
}
// Enable outdated
function enableOutdated(bool status) onlyOwner public {
isOutdated = status;
}
}
TestERC20Token.js
const BN = web3.utils.BN;
const ERC20Token = artifacts.require("ERC20Token");
const ERC20FullToken = artifacts.require("ERC20FullToken");
contract('ERC20Token', ([owner, bob]) => {
it('should put 777999777 ERC20Token token in the first account', async () => {
const tokenInstance = await ERC20Token.deployed();
const balance = await tokenInstance.balanceOf.call(owner);
assert.equal(balance.valueOf(), 777999777000000000000000000, "777999777000000000000000000 wasn't in the first account");
});
it('should send coin correctly', async () => {
const tokenInstance = await ERC20Token.deployed();
// Get initial balances of first and second account.
const accountOneStartingBalance = await tokenInstance.balanceOf.call(owner);
const accountTwoStartingBalance = await tokenInstance.balanceOf.call(bob);
// Make transaction from first account to second.
const amount = 10;
await tokenInstance.transfer(bob, amount.toString());
assert.equal((await tokenInstance.balanceOf.call(owner)).toString(), accountOneStartingBalance.sub(new BN(amount)).toString(), "Amount wasn't correctly taken from the sender");
assert.equal((await tokenInstance.balanceOf.call(bob)).toString(), accountTwoStartingBalance.add(new BN(amount)).toString(), "Amount wasn't correctly sent to the receiver");
});
it('should create ERC20FullToken correctly', async () => {
const tokenInstance = await ERC20FullToken.new("Example Token", "nhancv", 18, 777999777, {from: owner});
// mintable
assert.equal((await tokenInstance.balanceOf.call(bob)).toString(), 0, "Bob's balance should be zero");
await tokenInstance.mint(bob, 10, {from: owner});
assert.equal((await tokenInstance.balanceOf.call(bob)).toString(), 10, "Bob's balance should be 10");
// exchangeable
await tokenInstance.sendTransaction({from: bob, value: web3.utils.toWei('1.1', 'ether')});
assert.equal((await tokenInstance.balanceOf.call(bob)).toString(), 1100000000000000000000010, "Bob's balance should be 1100000000000000000000010");
// burnable
await tokenInstance.burn(10, {from: bob});
assert.equal((await tokenInstance.balanceOf.call(bob)).toString(), 1100000000000000000000000, "Bob's balance should be 1100000000000000000000000");
// burnable from
await tokenInstance.approve(owner, '1100000000000000000000000', {from: bob});
await tokenInstance.burnFrom(bob, '1100000000000000000000000', {from: owner});
assert.equal((await tokenInstance.balanceOf.call(bob)).toString(), 0, "Bob's balance should be 0");
});
});
TestERC20Token.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.3;
import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../src/active/ERC20Token.sol";
contract TestERC20Token {
function testInitialBalanceUsingDeployedContract() public {
ERC20Token meta = ERC20Token(DeployedAddresses.ERC20Token());
uint expected = 777999777000000000000000000;
Assert.equal(meta.totalSupply(), expected, "Owner should have 777999777000000000000000000 ERC20Token initially");
}
function testInitialBalanceWithNewERC20Token() public {
ERC20Token meta = new ERC20Token("Example Token", "nhancv", 18, 777999777);
uint expected = 777999777000000000000000000;
Assert.equal(meta.balanceOf(meta.owner()), expected, "Owner should have 777999777000000000000000000 ERC20Token initially");
}
}
TokenPresenter.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.3;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./interfaces/ITokenPresenter.sol";
import "./utils/Maintainable.sol";
import "./utils/EmergencyWithdraw.sol";
contract TokenPresenter is ITokenPresenter, EmergencyWithdraw, Maintainable {
address public token;
constructor() {
token = address(0);
}
/**
* @dev set the main token
* @param _token address of main token
*/
function setToken(address _token) onlyOwner public {
token = _token;
}
/**
* @dev this is the main function to distribute the tokens call from only main token via external app
* @param _trigger trigger address
* @param _from from address
* @param _to to address
* @param _amount amount of tokens
*/
function receiveTokens(address _trigger, address _from, address _to, uint256 _amount) public override returns (bool) {
ifNotMaintenance();
require(msg.sender == token, "TokenPresenter: Only trigger from token");
IERC20(token).transfer(_to, _amount);
return true;
}
}
Source: contract Gist revision.
The older transfer client
npm i tronweb --save
Read the retrieved TronWeb client
import TronWeb from 'tronweb'
interface TRC20Token {
name(): {call()},
symbol(): {call()},
decimals(): {call()},
totalSupply(): {call()},
balanceOf(address: string): {call()}
transfer(toAddress: string, amount: number): {send(any)}
}
export default class TRC20TokenClient {
async start(): Promise<any> {
this.sendTRC20Token('TW4hHzQWhkDPe4yVGUerbo1wdsKCJEBhcV', 0.1)
}
/**
* Send Token fund
* @param recipientAddress
* @param tokenAmount
*/
async sendTRC20Token(recipientAddress: string, tokenAmount: number = 1): Promise<any | null> {
try {
const mainNetProvider = 'https://api.trongrid.io';
const testNetProvider = 'https://api.shasta.trongrid.io';
const netProvider = testNetProvider;
const HttpProvider = TronWeb.providers.HttpProvider; // Optional provider, can just use a url for the nodes instead
const fullNode = new HttpProvider(`${netProvider}`); // Full node http endpoint
const solidityNode = new HttpProvider(`${netProvider}`); // Solidity node http endpoint
const eventServer = `${netProvider}`; // Contract events http endpoint
const privateKey = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
const tronWeb = new TronWeb(
fullNode,
solidityNode,
eventServer,
privateKey
);
const ownerAddress = tronWeb.address.fromPrivateKey(privateKey);
console.log({ownerAddress});
const contractAddressHex = tronWeb.address.toHex("TARCZ9Bw53DdsZ5eLYwc1GhfEkBAJsoFsv");
const contractInstance: TRC20Token = await tronWeb.contract().at(contractAddressHex);
const args = {
callValue: 0,
shouldPollResponse: true
};
const name = await contractInstance.name().call();
console.log({name});
const symbol = await contractInstance.symbol().call();
console.log({symbol});
const decimals = await contractInstance.decimals().call();
console.log({decimals});
const totalSupply = await contractInstance.totalSupply().call();
console.log({totalSupply: totalSupply.toString(10)});
const balanceOf = await contractInstance.balanceOf(ownerAddress).call();
console.log({balanceOf: balanceOf.balance.toString(10)});
let balanceOfrecipientAddress = await contractInstance.balanceOf(recipientAddress).call();
console.log({balanceOfrecipientAddress: balanceOfrecipientAddress.balance.toString(10)});
const amount = tokenAmount * Math.pow(10, decimals);
//{
// block: 1330340,
// timestamp: 1579589196000,
// contract: 'TARCZ9Bw53DdsZ5eLYwc1GhfEkBAJsoFsv',
// name: 'Transfer',
// transaction: '8158dda44af6f3f1ae2e65eb5d69787707f4e1ac7a1512b69cae8552e82065be',
// result: {
// _from: '41cba6374124b2320e7ff309033ecba4be5f81b2ea',
// _value: '100000',
// _to: '41dc6c365f620cef5fc9949bc008d362c711e9c3fc'
// },
// resourceNode: 'fullNode',
// unconfirmed: true
// }
contractInstance['Transfer']().watch((err, event) => {
if (err) return console.error('Error with "method" event:', err);
if (event) { // some function
console.log(event);
}
});
const transfer = await contractInstance.transfer(recipientAddress, amount).send(args);
console.log({transfer}); // { transfer: { success: true } }
balanceOfrecipientAddress = await contractInstance.balanceOf(recipientAddress).call();
console.log({balanceOfrecipientAddress: balanceOfrecipientAddress.balance.toString(10)});
return transfer.success;
} catch (e) {
Log.error(e);
return null;
}
}
}
The client depends on its surrounding logger, account configuration, token decimals, and SDK generation. Broadcast success is not proof of final on-chain state. Source: client Gist revision. No contract or transfer was submitted for this revision.