토큰(Token)
정확한 정의보다는 간단히 말해, 토큰은 디지털 화폐의 일종입니다. 비트코인이나 이더리움도 토큰의 한 종류입니다. 이더리움의 스마트 컨트랙트를 활용하면 자신만의 토큰을 쉽게 개발할 수 있습니다. 토큰은 포인트, 자산, 증서 등 거래 가능한 모든 것을 대표할 수 있습니다. 따라서 상업적 목적이든 학습 목적이든 많은 사람들이 자신만의 토큰을 만들고 싶어 합니다. 먼저 생성된 토큰이 어떤 모습인지 살펴보겠습니다.
오늘은 이러한 토큰을 생성하는 방법에 대해 자세히 알아보겠습니다.
ERC20 토큰
ERC20이라는 용어가 토큰과 함께 자주 등장하는 것을 볼 수 있습니다. ERC20은 이더리움이 정의한 토큰 표준입니다. 이는 토큰을 구현할 때 반드시 준수해야 하는 프로토콜로, 토큰 이름, 총량, 토큰 거래 함수 구현 등을 지정해야 합니다. 이 프로토콜을 지원해야만 이더리움 지갑에서 지원받을 수 있습니다. ERC20 인터페이스는 다음과 같습니다:
contract ERC20Interface {
string public constant name = "Token Name";
string public constant symbol = "SYM";
uint8 public constant decimals = 18; // 18 is the most common number of decimal places
function totalSupply() public constant returns (uint);
function balanceOf(address tokenOwner) public constant returns (uint balance);
function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
function transfer(address to, uint tokens) public returns (bool success);
function approve(address spender, uint tokens) public returns (bool success);
function transferFrom(address from, address to, uint tokens) public returns (bool success);
event Transfer(address indexed from, address indexed to, uint tokens);
event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}
간단히 설명하자면: name : 토큰 이름 symbol: 토큰 기호 decimals: 토큰 소수점 자릿수, 토큰의 최소 단위입니다. 18은 0.000000000000000001 단위의 토큰을 가질 수 있음을 의미합니다. totalSupply () : 발행된 토큰 총량입니다. balanceOf (): 특정 계정의 토큰 잔액을 조회합니다. transfer (): 토큰 전송 거래를 구현하며, 사용자에게 토큰을 보낼 때 사용됩니다(자신의 계정에서). transferFrom (): 승인된 사용자가 사용하며, 사용자가 자신(from 매개변수)의 계정에서 다른 사용자(to 매개변수)에게 토큰을 보낼 수 있도록 합니다. allowance (): 승인된 토큰 사용량을 반환합니다. approve (): 다른 사용자가 대신 사용할 수 있는 토큰 수를 승인합니다.
토큰 컨트랙트 코드 작성
토큰 컨트랙트 코드:
pragma solidity ^0.4.16;
interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public; }
contract TokenERC20 {
string public name;
string public symbol;
uint8 public decimals = 18; // 18 is the recommended default
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 Burn(address indexed from, uint256 value);
function TokenERC20(uint256 initialSupply, string tokenName, string tokenSymbol) public {
totalSupply = initialSupply * 10 ** uint256(decimals);
balanceOf[msg.sender] = totalSupply;
name = tokenName;
symbol = tokenSymbol;
}
function _transfer(address _from, address _to, uint _value) internal {
require(_to != 0x0);
require(balanceOf[_from] >= _value);
require(balanceOf[_to] + _value > balanceOf[_to]);
uint previousBalances = balanceOf[_from] + balanceOf[_to];
balanceOf[_from] -= _value;
balanceOf[_to] += _value;
Transfer(_from, _to, _value);
assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
}
function transfer(address _to, uint256 _value) public returns (bool) {
_transfer(msg.sender, _to, _value);
return true;
}
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
require(_value <= allowance[_from][msg.sender]); // Check allowance
allowance[_from][msg.sender] -= _value;
_transfer(_from, _to, _value);
return true;
}
function approve(address _spender, uint256 _value) public
returns (bool success) {
allowance[msg.sender][_spender] = _value;
return true;
}
function approveAndCall(address _spender, uint256 _value, bytes _extraData) public returns (bool success) {
tokenRecipient spender = tokenRecipient(_spender);
if (approve(_spender, _value)) {
spender.receiveApproval(msg.sender, _value, this, _extraData);
return true;
}
}
function burn(uint256 _value) public returns (bool success) {
require(balanceOf[msg.sender] >= _value);
balanceOf[msg.sender] -= _value;
totalSupply -= _value;
Burn(msg.sender, _value);
return true;
}
function burnFrom(address _from, uint256 _value) public returns (bool success) {
require(balanceOf[_from] >= _value);
require(_value <= allowance[_from][msg.sender]);
balanceOf[_from] -= _value;
allowance[_from][msg.sender] -= _value;
totalSupply -= _value;
Burn(_from, _value);
return true;
}
}
배포
스마트 컨트랙트 개발 및 테스트 시, MetaMask와 Remix Solidity IDE는 매우 유용한 도구입니다. 오늘은 이 두 도구를 사용하여 배포를 완료하겠습니다.
- MetaMask 설치 및 구성은 "첫 번째 탈중앙화 애플리케이션 개발 및 배포" 문서를 참조하세요. 본문에서는 이더리움 테스트 네트워크인 Ropsten을 선택했습니다. 잔액이 없는 경우 구매 버튼을 클릭하여 접속한 웹사이트에서 테스트 이더리움을 일부 받을 수 있습니다. 구성이 완료되면 다음과 같은 인터페이스가 표시되어야 합니다.
- 브라우저에서 Remix Solidity IDE를 열고 위 소스 코드를 복사하여 붙여넣으세요. 오른쪽 옵션은 다음 그림과 같이 설정하세요:
Environment, Account가 MetaMask와 일치하도록 한 후, TokenERC20 컨트랙트를 선택하고 원하는 발행량, 이름 및 기호를 입력하면 컨트랙트를 생성할 수 있습니다. 이때 MetaMask에 거래 확인 창이 나타나면 SUBMIT을 클릭하세요. 컨트랙트 배포 거래가 확인된 후 컨트랙트 주소를 복사하세요. 3. MetaMask 인터페이스에서 TOKENS로 전환하고 컨트랙트 추가를 클릭하면 다음과 같은 대화 상자가 나타납니다:
방금 복사한 주소를 입력하고 ADD를 클릭하면 생성한 토큰을 볼 수 있습니다.
이제 토큰 생성 및 배포가 완료되었습니다(메인넷과 테스트넷 배포 방법은 동일합니다). Etherscan에서 방금 배포한 토큰을 조회할 수 있습니다. 이를 통해 ICO를 진행하며 인생의 정상에 오를 수 있습니다(장난이니 의미 없는 토큰 발행을 권장하지 않습니다).
토큰 거래
MetaMask 플러그인은 토큰 거래 기능을 제공하지 않으며, 많은 사람들이 이더리움 지갑이 없거나 네트워크 동기화 문제로 어려움을 겪고 있어 오늘은 워렛 지갑을 사용하여 토큰 거래를 설명하겠습니다.
- 워렛 지갑 주소로 접속하여 첫 접속 시 보안 확인이 필요합니다.
- 접속 후 다음 그림과 같이 설정합니다:
- 연결되면 다음과 같은 화면이 표시됩니다:
토큰을 추가해야 하며, 토큰 컨트랙트 주소를 입력합니다. 4. 토큰 전송 거래를 수행합니다.
다음 거래 확인에서 확인을 클릭합니다. 5. 거래가 완료되면 MetaMask에서 토큰 잔액이 감소한 것을 볼 수 있습니다.