[개발] 트론(TRC20) 토큰 만들기
개요
요즘 스팀엔진이 뒤숭숭 하여 나름 대안으로 트론의 토큰생성을 해보려 합니다.
참조 : 개발 : 트론 계정 생성하기 by @wonsama
빠르게 만들어 보자
누구나 비용 없이 만들어 보고 테스트 할 수 있도록 테스트넷에서 진행 해보도록 하겠습니다.
TL;DR
- 개발 : 트론 계정 생성하기 글 참조
- 테스트용 토큰(TRX) 받기
- 테스트 NODE로 변경하기
- 소스복사 ( 약간의 설정만 변경 )
- 토큰 생성 코드 컴파일 / 배포
- 토큰 생성 등록
- 토큰 확인하기
테스트용 토큰(TRX) 받기
https://www.trongrid.io/faucet 으로 접속 후
Test wallet address
에 토큰을 받을 트론 지갑 주소를 입력 합니다. 이후 submit 버튼을 누르면 아래와 같은 메시지를 볼 수 있습니다. ( 지갑에 1만 TRX 가 들어옴, 테스트서버로 ... ㅋ )
Your request was successfully submitted, please check your wallet.
[그림] 자신의 트론 주소를 입력 후 submit
버튼을 누른다
테스트 NODE로 변경하기
Set up - Node manage - Shasta Testnet 을 선택한다. 1만 트론이 들어온 것을 확인할 수 있다.
[그림] 우측 상단의 set up
버튼을 누른다
[그림] Node manage
버튼을 눌러 노드를 변경한다
[그림] shasta testnet
을 선택한다
[그림] 테스트 노드로 변경 되었으며, 10000만 트론이 들어 온 것을 확인
소스복사 ( 약간의 설정만 변경 )
맨아래 설정 정보만 약간 변경한다. 더 이상의 자세한 설명은 생략한다.
constructor() CommonToken("WONSAMA TOKEN", "WONSAMA", 3, 1000000000) public {}
토큰명, 토큰심볼, 소숫점자리수, 발행량
이 4가지만 바꿔준다.- 메모장에 token.sol 이라고 저장한다. ( 확장자만 .sol 로 하면 됨 )
pragma solidity ^0.5.0;
// ---------------------------------------------------------------------
// ERC-20 Token Standard Interface
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md
// ---------------------------------------------------------------------
contract ERC20Interface {
/**
Returns the name of the token - e.g. "MyToken"
*/
string public name;
/**
Returns the symbol of the token. E.g. "HIX".
*/
string public symbol;
/**
Returns the number of decimals the token uses - e. g. 8
*/
uint8 public decimals;
/**
Returns the total token supply.
*/
uint256 public totalSupply;
/**
Returns the account balance of another account with address _owner.
*/
function balanceOf(address _owner) public view returns (uint256 balance);
/**
Transfers _value amount of tokens to address _to, and MUST fire the Transfer event.
The function SHOULD throw if the _from account balance does not have enough tokens to spend.
*/
function transfer(address _to, uint256 _value) public returns (bool success);
/**
Transfers _value amount of tokens from address _from to address _to, and MUST fire the Transfer event.
*/
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success);
/**
Allows _spender to withdraw from your account multiple times, up to the _value amount.
If this function is called again it overwrites the current allowance with _value.
*/
function approve(address _spender, uint256 _value) public returns (bool success);
/**
Returns the amount which _spender is still allowed to withdraw from _owner.
*/
function allowance(address _owner, address _spender) public view returns (uint256 remaining);
/**
MUST trigger when tokens are transferred, including zero value transfers.
*/
event Transfer(address indexed _from, address indexed _to, uint256 _value);
/**
MUST trigger on any successful call to approve(address _spender, uint256 _value).
*/
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}
/**
Owned contract
*/
contract Owned {
address public owner;
address public newOwner;
event OwnershipTransferred(address indexed _from, address indexed _to);
constructor () public {
owner = msg.sender;
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
function transferOwnership(address _newOwner) public onlyOwner {
newOwner = _newOwner;
}
function acceptOwnership() public {
require(msg.sender == newOwner);
emit OwnershipTransferred(owner, newOwner);
owner = newOwner;
newOwner = address(0);
}
}
/**
Function to receive approval and execute function in one call.
*/
contract TokenRecipient {
function receiveApproval(address _from, uint256 _value, address _token, bytes memory _extraData) public;
}
/**
Token implement
*/
contract Token is ERC20Interface, Owned {
mapping (address => uint256) _balances;
mapping (address => mapping (address => uint256)) _allowed;
// This notifies clients about the amount burnt
event Burn(address indexed from, uint256 value);
function balanceOf(address _owner) public view returns (uint256 balance) {
return _balances[_owner];
}
function transfer(address _to, uint256 _value) public returns (bool success) {
_transfer(msg.sender, _to, _value);
return true;
}
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
require(_value <= _allowed[_from][msg.sender]);
_allowed[_from][msg.sender] -= _value;
_transfer(_from, _to, _value);
return true;
}
function approve(address _spender, uint256 _value) public returns (bool success) {
_allowed[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
function allowance(address _owner, address _spender) public view returns (uint256 remaining) {
return _allowed[_owner][_spender];
}
/**
Owner can transfer out any accidentally sent ERC20 tokens
*/
function transferAnyERC20Token(address tokenAddress, uint tokens) public onlyOwner returns (bool success) {
return ERC20Interface(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(msg.sender, _value, address(this), _extraData);
return true;
}
/**
Destroy tokens.
Remove `_value` tokens from the system irreversibly
*/
function burn(uint256 _value) public returns (bool success) {
require(_balances[msg.sender] >= _value);
_balances[msg.sender] -= _value;
totalSupply -= _value;
emit Burn(msg.sender, _value);
return true;
}
/**
Destroy tokens from other account.
Remove `_value` tokens from the system irreversibly on behalf of `_from`.
*/
function burnFrom(address _from, uint256 _value) public returns (bool success) {
require(_balances[_from] >= _value);
require(_value <= _allowed[_from][msg.sender]);
_balances[_from] -= _value;
_allowed[_from][msg.sender] -= _value;
totalSupply -= _value;
emit Burn(_from, _value);
return true;
}
/**
Internal transfer, only can be called by this contract
*/
function _transfer(address _from, address _to, uint _value) internal {
// Prevent transfer to 0x0 address. Use burn() instead
require(_to != address(0x0));
// Check if the sender has enough
require(_balances[_from] >= _value);
// Check for overflows
require(_balances[_to] + _value > _balances[_to]);
// Save this for an assertion in the future
uint previousBalances = _balances[_from] + _balances[_to];
// Subtract from the sender
_balances[_from] -= _value;
// Add the same to the recipient
_balances[_to] += _value;
emit Transfer(_from, _to, _value);
// Asserts are used to use static analysis to find bugs in your code. They should never fail
assert(_balances[_from] + _balances[_to] == previousBalances);
}
}
contract CommonToken is Token {
constructor(string memory _name, string memory _symbol, uint8 _decimals, uint256 _initialSupply) public {
name = _name;
symbol = _symbol;
decimals = _decimals;
totalSupply = _initialSupply * 10 ** uint256(decimals);
_balances[msg.sender] = totalSupply;
}
/**
If ether is sent to this address, send it back.
*/
function () external payable {
revert();
}
}
contract NCToken is CommonToken {
// wonsama's token publish
// name : wonsama Token
// symbol : WSM
// decimals : 3
// init supply : 1000000000
constructor() CommonToken("WONSAMA TOKEN", "WONSAMA", 3, 1000000000) public {}
}
토큰 생성 코드 컴파일 / 배포
https://shasta.tronscan.org/#/contracts/contract-Compiler 주소로 접속 하여 파일을 업로드 이후 컴파일 및 배포를 진행합니다.
[그림] 컨트렉트 파일 업로드
버튼을 누른다.
[그림] 컴파일
버튼을 누른 후, 팝업에서 solidity 버전을 tron-0.5.8 ...
로 변경 후 확인 버튼을 눌러준다.
[그림] 컴파일이 정상적으로 되면 위와 같이 보여진다.
[그림] NCToken
으로 변경 해준 다음 확인 버튼을 누른다.
[그림] Accept
버튼을 눌러 해당 컨트렉트를 전송한다
[그림] 정상적으로 배포가 완료되면, 트렉젝션 주소(27... ) 및 컨트렉트 주소 ( TDBB... ) 를 확인할 수 있다. ( 주소 정보는 꼭 기억 해주도록 한다. )
토큰 생성 등록
새탭을 열어서 https://shasta.tronscan.org/#/tokens/create/Type 주소를 들어간다
[그림] TRC20
을 선택
- 위에서 소스코드 맨 하단에 입력한 것과 동일한 정보를 입력 해준다.
constructor() CommonToken("WONSAMA TOKEN", "WONSAMA", 3, 1000000000) public {}
[그림] 위에서 입력한 것과 동일한 정보를 채워 넣어준다. 토큰이름, 토큰 약자, 정밀도, 발행량 ( 다르면 진행 안됨 )
[그림] 나머지 정보도 입력해준다, 컨트렉트 주소는 이전 소스 배포할때 나온 값인 (TDBB... ) 을 넣어주도록 한다. ( Contract address 옆에 있는 T 로 시작하는 정보임 )
[그림] 제출 버튼을 눌러 토큰 발행을 최종적으로 확인한다.
[그림] 지갑을 통해 Accept 를 눌러 서명을 완료하면
[그림] 토큰 생성이 된 것을 확인할 수 있다.
토큰 확인하기
[그림] 지갑을 들어가도 아직은 안보인다. 토큰 주소를 추가해야 보이기 때문이다.
[그림] 우측 햄버거 버튼 누르고 Asset management
버튼을 눌러준다
[그림] 아까 생성한 계약 주소를 넣어준다. TDBBKzSMMR7uPQXrQxynKZ5SSBuuGEj34c
를 넣으면 원사마 토큰을 볼 수 있다.
[그림] 자 이제 초기 화면에서도 원사마 토큰을 볼 수 있다. 물론 테스트넷 기준으로 타인에게도 송금 또한 가능하다 :)
맺음말
스샷 때문에 내용이 길어 보이겠지만 실제는 금방 만든다. 복붙이니 뭐 한번 해보면 어렵지도 않다. 참고로 토큰명은 중복이 가능하다. 그러기 때문에 반드시 토큰의 컨트랙트 주소를 확인해야 된다.
누군가 가짜 원사마 토큰을 만들고 전송했다고 우길 수도 있는지라 ... 하지만 contract address 를 확인하면 된다 이럴경우엔 :)
토큰을 만드는데에는 소정의 TRX 가 소모 됩니다. 테스트 넷 기준으로 대충 8.3 TRX 정도가 소모 되었네요 :) 사실상 bandwidth 와 energy 가 소모 되는 것인데 현재 TEST1 계정에는 Energy 가 부족한 관계로 TRX가 대체로 소모된 것 입니다. 이는 실제 MainNet 에서도 동일하게 적용 되는 사항 입니다. 다만 선택한 Node 에 따라 비용은 약간 차이는 있을 수 있으나 큰 차이는 발생하지 않습니다.
와우 한번 따라해봐야겠군요.
실제 트론 메인넷에 토큰을 올리려면 얼마나 들까요?
비슷할거에요 테스트 넷이랑 / TRC10 1024 트론인데 TRC20은 매우 저렴하죠 :) 하지만 스마트 컨트랙트인 관계로 에너지랑 밴드위스 소모 부분은 잘 채크 해보셔야 될거에요
저도 거긴 안해봐서 / 일단 테스트 넷에서 충분한 테스트 후 메인넷 적용하길 권장 드립니다.
감사합니다. 테스트넷에서 저도 한번 해봐야겠네요
오!! 따라해서 금방 만들었습니다!!! ㅎㅎ 토큰을 가져보니 또 나름 기분이 새롭군요 ㅎㅎ
자 이제 staking 가즈아 ~
https://medium.com/hackernoon/implementing-staking-in-solidity-1687302a82cf
아직안해봄 전 ... ㅋ / 그리고 컨트랙트 수행시 발생되는 에너지 소모가 커서 음 ... 그것도 줄여 줄 수 있는 방법도 생각해 봐야 되긴 할듯여 ; BTT 스테이킹 할떄도 에너지 22만 소모 되던데 ;;; ( 트론소비 시 대충 1-2 TRX 는 나갈듯 )
https://www.guest-articles.com/education/1z0-1067-oracle-cloud-infrastructure-2019-cloud-operations-associate-02-12-2020
안녕하세요 trc10 과 trc20 발행 비용은 똑같은 1024 트론 인가요? 그리고 이더리움의 토큰 발행 비용은 몇 이더인가요. 그리고 이더, trc10, 20 기반 발행의 가성비는 어떻게 되나요? 원사마님께서 올리신 포스팅 방법으로 테스트넷 말고 메인넷에서 똑같이 trc20 기반 토큰을 발행할 수 있는건가요? 마지막으로 아래처럼 되어 있는데 화살표 친 곳은 수정을 안해도 되는 부분인가요? 질문에 대답을 해주실지는 모르겠고 너무 많은 질문을 해서 죄송합니다. 어찌됬든 좋은 포스팅 써주셔서 많은 도움이 되었다는 말씀을 드리고요 정말 감사합니다!
contract NCToken is CommonToken {
// wonsama's token publish <------
// name : wonsama Token <------
// symbol : WSM <------
// decimals : 3 <------
// init supply : 1000000000 <------
constructor() CommonToken("WONSAMA TOKEN", "WONSAMA", 3, 1000000000) public {}
}
일단 테스트넷에서 해보시면 됩니다.
그리고 숙련되었다 싶음 메인넷에서 하심 되고여
발행비는(테스트vs메인) 네트웍 트레픽에 따라 상이하나 거의 비슷할 것 입니다.
trc10만 발행 비용 있음다. 20은 초기 트랜젝션 피만 존재 하고여 대신 네트웍과 에너지 둘 다 소모 하기 땜시 비용이 아예 없진 않습니다
일단 테스트넷에서 충분히 해보시고 그 이후 질문 부탁 댓으로 주심 답 드릴께요
감사합니다. 저는 중소 규모의 거래소를 운영하고 있는데요 덕분에 토큰 10개 발행해서 거래소에 모의 상장 성공했습니다. 허구 btc eth yfi 등 메이저 페어를 활성화 시킨후 유저들에게 거래 체험을 하려고 준비중이거든요. 매 토큰 발행을 30만원~60만원 정도로 해야하나 하면서 막막했었는데 토큰 하나 발행하는데 체감상 1usdt 정도밖에 안드니 너무 좋네요! 정말 감사드립니다.
추가로 하나 여쭤보고 싶은데요, 이 trc20 토큰은 아임토큰에서는 인식이 불가능한가요?
트론 지갑은 보통 트론링크 또는 클레버를 사용해서 아임토큰은 잘 모르겠네요 TRX 자체는 지원하나 하위 토큰까지 지원하는지는 사용하지 않아서 잘 모르겠네요.
개인적으로는 트론만 따지고 본다면 트론링크 또는 클레버 지갑을 사용하길 권장 드립니다.
몇일 있다 확인하니 아임토큰에도 트론 기반 토큰 인식이 가능더라고요. 친절한 답변 감사합니다
혹시 트론 기반으로 토큰 발행한 후에 스왑, 스테이킹, 락업 등 기능에 관해서 포스팅 하실생각은 없으신가요? 개발 관련 공부를 하지 않아서 정말 아쉽네요 ㅠㅠ 관련 내용을 찾기도 쉽지 않고요
토큰 개발 후에 정말 잘 키워나가고 싶은데 말이죠..
안녕하세요? 한참 궁금한게 많아서 이것저것 둘러보다가 원사마님 글을 우연치 않게 접하고나서 위 안내사항대로 코인을 만들어봤습니다
시간이 지나서 이 글을봐서 그런지 코인발행까지는 다 완료했는데 트론링크 지갑에 제가 만든 코인이 안보여요ㅠ
트랜젝션을 그대로 복사해서 추가해보려했는데 안되는데 혹시 다른 방법이 있을까요?
asset에서 +버튼 누르고 입력하는곳이 없길래 돋보기 누르고 했는데도 안되더라고요..
트론스캔에서 추가해보니까 됐는데 트론링크에서는 안되네요
그리고 물량도 발행은됐는데 보유수량은 0이에요,,
저도 얼마전에 메인넷에서 토큰을 발행 후 컨트랙 배포, 토큰 입력까지 완료 했는데 자산에서 보유수량이 0이라고 뜨네요,, 원인이 무엇일까요? 제가 잘못 조작을 한줄 알고 테스트넷에서 다시 배포해 보려고 테스트넷 토큰 받아보려고 했는데 무슨 이유인지는 몰라도 토큰 수량 부족? 인가 그래서 테스트넷 토큰도 받을수가 없습니다..
테스트넷 토큰은 그 테스트넷에서 없어서 못 준다고 UCT 0:00(우리시간 오전 9시)에 누르면 줘요 그리고 제가 해보니까 오른쪽 위에 마우스 가져가면 SHASHA TEST NET 나오는데 그거로 눌러줘야지 제대로 뜨네요
메인넷에서 안 되는거는 이유를 모르겠네요 ㅠ
_mint 함수로 찍어내는 것을 같이 해야할 것 같아요
그 트론스캔에 초보자용 토큰이라고 나와있는거 코드엔 마지막에
_mint(msg.sender, 10000000000 * (10 ** uint256(decimals())));
이 코드가 추가돼있네요
메인넷에서 아직도 토큰 이 방식대로 발행할수가 없을까요?
혹시 저 대학생 과제로 파이썬 오픈소스 활용해서 앱 만드는게 있는데 저거 파이썬으로도 trc20코인 만드는거 사용이 가능하나요