Ethereum cho lập trình viên – Phần 3: Tạo ra token ERC20 của chính bạn

Như hình trên các bạn có thể nhận ra một số token nổi tiếng: Augur, Golem, Maker, Blockchain Capital, Gnosis, SingularDTV. Những token này có tốc độ tăng trưởng giá trị vốn hoá vô cùng nhanh và ở thời điểm viết bài này thì Qtum đang có giá trị vốn hoá cao nhất lên đến hơn 1 tỷ USD. Theo thống kê trên etherscan thì hiện tại đã có tổng cộng 17328 token chuẩn ERC20 đã được deploy lên blockchain của Ethereum. Trong bài viết này, mình sẽ hướng dẫn các bạn tạo ra token của chính mình trên nền tảng blockchain của Ethereum, tương tự như các token đã được tạo ở trên.

Trước tiên bạn cần phải hiểu token ERC20 là gì. ERC là viết tắt của Ethereum Request for Comment, là dạng sơ khởi của EIP (Ethereum Improvement Proposal – Đề xuất cải tiến Ethereum). EIP20 là một đề xuất liên quan đến việc chuẩn hoá API dành cho token. Trước khi có đề xuất này, toàn bộ những token khi được tạo ra đều phải cài đặt tất cả những hàm và biến số liên quan tới định nghĩa về số token supply, kiểm tra số dư token trên một địa chỉ, chuyển token giữa các địa chỉ v.v. Việc này không chỉ gây lãng phí thời gian mà còn làm cho các token khác nhau khi muốn hỗ trợ chuyển đổi cho nhau thì bên này phải nghiên cứu API của bên kia để chuyển cho đúng. Với mỗi token được tạo ra, nó phải tìm hiểu tất cả những token đã được tạo ra trước đó để có thể cài đặt được tính năng chuyển đổi giữa nó và các token trước đó. Như vậy với mỗi token được tạo ra thì độ khó để tương thích với các token trước đó tăng lên theo cấp số mũ. ERC20 là một tiêu chuẩn mà bạn có thể tuân theo hoặc không tuân theo khi tạo token, và khi tuân theo thì token bạn tạo ra sẽ được gọi là token ERC20.

Toàn bộ mô tả về chuẩn ERC20 có thể tìm thấy tại đây.

Ok, bắt đầu nào!

Bước 1: Lên đặc điểm của token

Để tạo ra một token ERC20, bạn phải định nghĩa những thuộc tính sau:

  • Tên của token
  • Mã của token
  • Số chữ số thập phân sau dấu phẩy của token
  • Tổng số token muốn phát hành
Ví dụ chúng ta sẽ tạo token như sau:
  • Tên: Cryptolife
  • Mã: CTL
  • Số chữ số thập phân sau dấu phẩy: 0
  • Tổng số token phát hành: 1000
Thuộc tính Số chữ số thập phân chỉ có ý nghĩa trong hiển thị, và nó được thiết lập khác nhau tuỳ vào ý đồ của người tạo ra nó. Theo mặc định thì sẽ có 18 chữ số thập phân sau dấu phẩy, tức là token có thể được chia nhỏ đến .0000000000000000001 token. Với ví dụ trên, chúng ta muốn token sẽ ở dạng nguyên nên sẽ không có chữ số thập phân nào.
Tổng số token phát hành được tính theo đơn vị nhỏ nhất của token đó. Ví dụ khi số chữ số thập phân là 0 thì với tổng số token phát hành sẽ được đếm trong khoảng 1 – 1000. Khi chữ số thập phân là 1, tổng số token là 1000 thì token sẽ được hiển thị trong phạm vi 0.1 – 100

Bước 2: Viết mã cho phần Smart Contract chuẩn ERC20

Trước tiên bạn sẽ cần copy một contract chuẩn ERC20 về để từ đó sửa những chỗ cần thiết thôi. Contract chúng ta sẽ tham khảo tại Token-Factory của ConsenSys. Copy nội dung của Token.sol, StandardToken.sol và HumanStandardToken.sol vào một file Solidity duy nhất như dưới.
pragma solidity ^0.4.4;

contract Token {

    /// @return total amount of tokens
    function totalSupply() constant returns (uint256 supply) {}

    /// @param _owner The address from which the balance will be retrieved
    /// @return The balance
    function balanceOf(address _owner) constant returns (uint256 balance) {}

    /// @notice send `_value` token to `_to` from `msg.sender`
    /// @param _to The address of the recipient
    /// @param _value The amount of token to be transferred
    /// @return Whether the transfer was successful or not
    function transfer(address _to, uint256 _value) returns (bool success) {}

    /// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from`
    /// @param _from The address of the sender
    /// @param _to The address of the recipient
    /// @param _value The amount of token to be transferred
    /// @return Whether the transfer was successful or not
    function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {}

    /// @notice `msg.sender` approves `_addr` to spend `_value` tokens
    /// @param _spender The address of the account able to transfer the tokens
    /// @param _value The amount of wei to be approved for transfer
    /// @return Whether the approval was successful or not
    function approve(address _spender, uint256 _value) returns (bool success) {}

    /// @param _owner The address of the account owning tokens
    /// @param _spender The address of the account able to transfer the tokens
    /// @return Amount of remaining tokens allowed to spent
    function allowance(address _owner, address _spender) constant returns (uint256 remaining) {}

    event Transfer(address indexed _from, address indexed _to, uint256 _value);
    event Approval(address indexed _owner, address indexed _spender, uint256 _value);

}

contract StandardToken is Token {

    function transfer(address _to, uint256 _value) returns (bool success) {
        //Default assumes totalSupply can't be over max (2^256 - 1).
        //If your token leaves out totalSupply and can issue more tokens as time goes on, you need to check if it doesn't wrap.
        //Replace the if with this one instead.
        //if (balances[msg.sender] >= _value && balances[_to] + _value > balances[_to]) {
        if (balances[msg.sender] >= _value && _value > 0) {
            balances[msg.sender] -= _value;
            balances[_to] += _value;
            Transfer(msg.sender, _to, _value);
            return true;
        } else { return false; }
    }

    function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {
        //same as above. Replace this line with the following if you want to protect against wrapping uints.
        //if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && balances[_to] + _value > balances[_to]) {
        if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && _value > 0) {
            balances[_to] += _value;
            balances[_from] -= _value;
            allowed[_from][msg.sender] -= _value;
            Transfer(_from, _to, _value);
            return true;
        } else { return false; }
    }

    function balanceOf(address _owner) constant returns (uint256 balance) {
        return balances[_owner];
    }

    function approve(address _spender, uint256 _value) returns (bool success) {
        allowed[msg.sender][_spender] = _value;
        Approval(msg.sender, _spender, _value);
        return true;
    }

    function allowance(address _owner, address _spender) constant returns (uint256 remaining) {
      return allowed[_owner][_spender];
    }

    mapping (address => uint256) balances;
    mapping (address => mapping (address => uint256)) allowed;
    uint256 public totalSupply;
}


//name this contract whatever you'd like
contract ERC20Token is StandardToken {

    function () {
        //if ether is sent to this address, send it back.
        throw;
    }

    /* Public variables of the token */

    /*
    NOTE:
    The following variables are OPTIONAL vanities. One does not have to include them.
    They allow one to customise the token contract & in no way influences the core functionality.
    Some wallets/interfaces might not even bother to look at this information.
    */
    string public name;                   //fancy name: eg Simon Bucks
    uint8 public decimals;                //How many decimals to show. ie. There could 1000 base units with 3 decimals. Meaning 0.980 SBX = 980 base units. It's like comparing 1 wei to 1 ether.
    string public symbol;                 //An identifier: eg SBX
    string public version = 'H1.0';       //human 0.1 standard. Just an arbitrary versioning scheme.

//
// CHANGE THESE VALUES FOR YOUR TOKEN
//

//make sure this function name matches the contract name above. So if you're token is called TutorialToken, make sure the //contract name above is also TutorialToken instead of ERC20Token

    function ERC20Token(
        ) {
        balances[msg.sender] = NUMBER_OF_TOKENS_HERE; // Give the creator all initial tokens (100000 for example)
        totalSupply = NUMBER_OF_TOKENS_HERE;          // Update total supply (100000 for example)
        name = "NAME OF YOUR TOKEN HERE";             // Set the name for display purposes
        decimals = 0;                                 // Amount of decimals for display purposes
        symbol = "SYM";                               // Set the symbol for display purposes
    }

    /* Approves and then calls the receiving contract */
    function approveAndCall(address _spender, uint256 _value, bytes _extraData) returns (bool success) {
        allowed[msg.sender][_spender] = _value;
        Approval(msg.sender, _spender, _value);

        //call the receiveApproval function on the contract you want to be notified. This crafts the function signature manually so one doesn't have to include a contract in here just for this.
        //receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData)
        //it is assumed that when does this that the call *should* succeed, otherwise one would use vanilla approve instead.
        if(!_spender.call(bytes4(bytes32(sha3("receiveApproval(address,uint256,address,bytes)"))), msg.sender, _value, this, _extraData)) { throw; }
        return true;
    }
}

Chúng ta sẽ sửa lại từ đoạn comment CHANGE THESE VALUES FOR YOUR TOKEN:

  • Tên token
  • Mã token
  • Số chữ số thập phân
  • Số token mà người phát hành nắm
  • Tổng cung
Bạn cần chú ý một số chỗ sau:

  1. Bạn có thể đặt tên tuỳ ý cho contract ERC20Token, chẳng hạn như mình sẽ đặt tên là CryptolifeToken. Miễn sao hàm khởi tạo cũng phải đúng tên đó.
  2. Tổng cung token có liên quan tới số chữ số thập phân của token. Ví dụ khi bạn muốn phát hành 1000 token có số chữ số thập phân là 2 thì bạn cần tổng cung là 10000. Nhưng khi bạn muốn phát hành 1 token có số chữ số thập phân là 5 thì bạn cần tổng cung là 100000
  3. Thiết lập số token mà người phát hành nắm giữ balances[msg.sender] là số token sẽ được tạo ra tại ví của người phát hành smart contract.
Dưới đây là phần code mà chúng ta đã chỉnh:
...
//Ham khoitao token
function CryptolifeToken() {
    balances[msg.sender] = 1000;   // Nguoi phathanh se namgiu toanbo token
    totalSupply = 1000;            // Tong cung token
    name = "Cryptolife Token";     // Ten cua token
    decimals = 0;                  // Token khong co phan thapphan (so nguyen thoi)
    symbol = "CTL";                // Ma token
}
...

Đây là file .sol khi đã thiết lập xong: https://goo.gl/9KEtTu
Sau khi đã thiết lập xong, giờ là lúc test token thôi!

Bước 3: Test token trên Testnet

Ở bước này, chúng ta sẽ phát hành token lên một mạng lưới test (Test net) để xem nó có hoạt động ok hay không. Chúng ta sẽ chọn Ropsten Testnet để test token này.

À quên, nhưng trước tiên để tương tác với Ropsten Testnet này thì bạn cần phải cài đặt MetaMask. Mình sẽ có một bài viết riêng hướng dẫn một số thiết lập và thao tác quản lý ví cơ bản sử dụng MetaMask sau.

Sau khi cài xong MetaMask vào trình duyệt Chrome, bạn đăng nhập và chọn network là Ropsten Network như hình.

Địa chỉ ví này sẽ là chủ của contract sắp phát hành nên bạn không được để mất ví. Ngoài MetaMask ra bạn có thể dùng Mist hoặc MyEtherWallet để tạo contract cũng được. Mình sử dụng MetaMask vì nó khá đơn giản và bạn có thể export ra private key để có thể dùng với MyEtherWallet sau này.

Tiếp theo là biên dịch mã nguồn contract bằng Solidity của chúng ta. Để làm việc này, chúng ta sẽ sử dụng Solidity Remix Compiler, một trình biên dịch online cho phép chúng ta phát hành contract ngay lên blockchain sau khi biên dịch xong.

Bạn copy và dán mã nguồn đã chỉnh sửa vào Soidity Remix Compiler. Đổi tên file contract lại một chút chúng ta sẽ có kết quả như hình dưới.

Tiếp theo bạn cần thiết lập phiên bản compiler phù hợp với phiên bản Solidity của mã nguồn (như hiện tại là 0.4.4)

Sau đó bạn chuyển qua tab Compile và đợi các contract được build. Sau khi build thành công kết quả sẽ hiện như dưới:

Để deploy contract, bạn chuyển sang tab Run như dưới:

Bạn lưu ý chọn đúng tên contract cần deploy, trong trường hợp bài viết này sẽ là CryptolifeToken. Vì là lần đầu thực thi tạo contract nên sẽ không cần At Address, bạn chỉ cần nhấn button Create sẽ hiện ra cửa sổ sau:

Nhân Submit để yêu cầu thực thi tạo một contract trên Robsten Testnet. Sau khi giao dịch tạo contract đã được xác nhận, kết quả sẽ trả về như dưới:

Xem trên etherscan.io, chúng ta có thể thấy contract đã được tạo với một địa chỉ cụ thể:
https://ropsten.etherscan.io/address/0x1082d14a311480a1494a284eabdc01773e1d5399
Bạn lưu địa chỉ của contract này lại để lát nữa dùng để claim token.

Trên MetaMask, giao dịch tạo contract cũng hiển thị như dưới:

Ok, đến đây nếu mọi thứ ok thì chúng ta sẽ xem token đã được phân phối như thế nào. Nếu bạn vẫn chưa deploy được thì hãy kiểm tra lại source code hoặc đôi khi là số ETH trong ví deploy đã đủ để trả tiền gas cho giao dịch hay chưa nhé 🙂

Hướng dẫn: Cách nạp ETH miễn phí vào ví trên Testnet (comming soon)

Bước 3.5: Xem token trên MetaMask

Theo source code thì chúng ta sẽ giữ 1000 token CTL khi contract được tạo ra. Vậy giờ chúng ta sẽ kiểm tra xem code đã chạy đúng hay chưa bằng cách mở MetaMask lên.

Bạn nhấn vào button Add Token. Bạn dán địa chỉ của contract được tạo ở trên vào, thông tin của token sẽ tự động hiện ra.

Bạn nhấn button Add.

Đúng như code đã quy định, 1000 token CTL đã được chuyển đến ví này (chính là ví gửi giao dịch tạo contract).

Tuyệt vời! Xong bước này chúng ta đã hoàn thành việc deploy một contract với quy định về token lên mạng lưới testnet và code tạm hoạt động như ý muốn.

Trong bài tiếp theo mình sẽ hướng dẫn các bạn cách verify source code. Việc verify source code không bắt buộc, tuy nhiên nếu bạn đang thực hiện một công việc kinh doanh nghiêm túc thì việc verify source code của contract sẽ tạo được sự tin tưởng của người dùng vì họ sợ rằng bạn làm điều gì đó mờ ám trong source code. Và ở bài tiếp theo nữa mình sẽ hướng dẫn cách deploy contract này lên MainNet, ra ngoài thế giới thực 🙂

Cảm ơn các bạn đã đọc bài!

Be the first to comment

Leave a Reply

Your email address will not be published.