ERC223トークンを時短でパブリックチェーンに公開する(忙しい人向け)

ERC223トークンとは

Ethereum上で発行できるトークンの仕様の1つ。他にはERC20,ERC721等があります。
最も普及している仕様であるERC20トークンの欠点を補ったもの。

具体的には、ERC20においては、ユーザーアドレス宛でなく、誤ってコントラクトアドレス宛にトークンを送ってしまった場合、トークンが使えなくなってしまう(=セルフGOXしてしまう)という欠点があるのに対し、

ERC223トークンにおいては、トークンを送る際、宛先がコントラクトアドレスであった場合には、

tokenFallback
tokenFallback(address _from, uint256 _value, bytes _data)

というメソッドを通じて送り主に返金するように構成されます。

ERC223トークンの例

有名どころでは、国産トークンのNANJ COINが挙げられます。
https://nanjcoin.com/
※正確には、ERC223とERC20の仕様を兼ねたトークンです。

前提条件

今回、EthereumのテストネットであるパブリックチェーンのRopstenで作成します。

・npmインストール済み。
・Ropstenアドレスを1つ以上作成済み。
・Ropstenアドレスに1Ether程度の残高がある。

Ropstenアドレスがない場合→ MyEtherWallet等で作成してください。

Ropsten Etherがない場合→「ropsten faucet」等で検索してfaucetからEhterを取得してください。

作るもの

今回、練習用にCurryRiceCoinというトークンを作ってみます。

手順1 infura.ioでAccessTokenを取得する(3分)

gethをインストールして、localhostをノードにしてもいいのですが、ブロックチェーンの同期に非常に時間がかかる(半日~数日)ため、パブリックノードの1つであるinfura.ioを使えば3分で環境構築できます。
https://infura.io/

名前とメールアドレスを入力すると、AccessTokenが取得できます。Ropstenのノードアドレスは、
https://ropsten.infura.io/[Your Access Token]
となります。

手順2 truffleをインストールする(1分)

truffleはEthereumコントラクトのデプロイを簡単にしてくれるライブラリです。

npm install -g truffle

プロジェクトフォルダ直下で

 truffle init

と打つと、以下のスケルトンが生成されます。

[Project Home]
|
|_  contracts
|    |_  Migrations.sol
|
|_  migrations
|    |_  1_initial_migration.js
|
|_  truffle-config.js
|_  truffle.js

手順3 truffle-hdwallet-provider-privkeyをインストールする(1分)

infura.ioは、セキュリティのため、トークンの送信やコントラクトのデプロイ等、Private Keyを使用するような処理は行えません。そこで、truffle-hdwallet-provider-privkeyを用いると、ローカルで署名をしてからRPCリクエストを行うことにより、それらが行えるようになります。

 npm install truffle-hdwallet-provider-privkey

後ほどtruffle.jsにrequireで読ませるので、グローバルオプション-gは付けません。

手順4 Solidityでトークンのコードを書く(1~2時間)

まず、contractsフォルダに、作成するトークンと同じ名前のSolidityファイルを作ります。今回はCurryRiceCoin.solとします。

[Project Home]
|
|_  contracts
|    |_  Migrations.sol
|    |_  CurryRiceCoin.sol *
|
|_  migrations
|    |_  1_initial_migration.js
|
|_  truffle-config.js
|_  truffle.js

コードの内容は
https://github.com/Dexaran/ERC223-token-standard/blob/master/README.md
https://github.com/NANJ-COIN/token/blob/master/NANJCOIN.sol
を参考にし、なるべく最小構成に近い形で書いていきます。

pragma solidity ^0.4.11;

import './ownable.sol';
 /**
 *@contract ERC223Interface  ERC223の仕様に沿ったメソッド群を定義するコントラクト
 */
contract ERC223Interface {
    uint256 public total_supply;

    function name() public view returns (string _name);
    function symbol() public view returns (string _symbol);
    function decimals() public view returns (uint8 _decimals);
    function totalSupply() public view returns (uint256 _supply);
    function balanceOf(address who) public view returns (uint256);
    function transfer(address to, uint256 value) public;
    function transfer(address to, uint256 value, bytes data) public;
    event Transfer(address indexed from, address indexed to, uint256 value, bytes data);
}
 /**
 *@contract ERC223ReceivingContract ERC223の肝であるtokenFallbackを定義するコントラクト 
 */
contract ERC223ReceivingContract { 
    /**
    * @dev Standard ERC223 function that will handle incoming token transfers.
    */
    function tokenFallback(address _from, uint256 _value, bytes _data) public;
}

 /**
 *@contract CurryRiceCoin トークン本体のコントラクト(ERC223Interface, Ownableを継承) 
 */
contract CurryRiceCoin is ERC223Interface, Ownable{
    string public name = "CurryRiceCoin";
    string public symbol = "CURRY";
    uint8 public decimals = 8;
    address public founder = 0xFa2A634c13d73f9d9A745be8D8Cf7dfe3993f873; 

    mapping(address => uint256) balances; // 任意のアドレスのトークン残高を格納する配列
     /** 
     * ERC223標準にはない独自のメソッドに係るイベント
     */
    event Burn(address indexed from, uint256 amount); 
    event Mint(address indexed to, uint256 amount);
     /** 
     * @function constructorはデプロイ時1回のみ実行される。
     */
    constructor(uint256 initial_supply) public {
        total_supply = initial_supply;
        owner = founder;
        balances[founder] = total_supply; //管理者アドレスに100%配分する
    }

    function name() public view returns (string _name) {
        return name;
    }

    function symbol() public view returns (string _symbol) {
        return symbol;
    }

    function decimals() public view returns (uint8 _decimals) {
        return decimals;
    }

    function totalSupply() public view returns (uint256 _total_supply) {
        return total_supply;
    }

    /**
     * @function balanceOf web3から任意のアドレスの残高をとれるようにする
     */
    function balanceOf(address _owner) public view returns (uint256 balance) {
        return balances[_owner];
    }
    /**
     * @function transfer web3からトークンを送金する
     *  送金先がコントラクトアドレスの場合、送金元に返金される
     */
    function transfer(address _to, uint _value, bytes _data) public {
        uint codeLength;
        assembly {
            // Retrieve the size of the code on target address, this needs assembly .
            codeLength := extcodesize(_to)
        }
        balances[msg.sender] = balances[msg.sender].sub(_value);
        balances[_to] = balances[_to].add(_value);

        if(codeLength>0) {
            ERC223ReceivingContract receiver = ERC223ReceivingContract(_to);
            receiver.tokenFallback(msg.sender, _value, _data);
        }
        emit Transfer(msg.sender, _to, _value, _data);
    }

    /**
     * ERC20への後方互換を持たせるために_data無し版も定義する必要がある
     */
    function transfer(address _to, uint _value) public {
        bytes memory empty;
        uint codeLength;
        assembly {
            // Retrieve the size of the code on target address, this needs assembly .
            codeLength := extcodesize(_to)
        }
        balances[msg.sender] = balances[msg.sender].sub(_value);
        balances[_to] = balances[_to].add(_value);

        if(codeLength>0) {
            ERC223ReceivingContract receiver = ERC223ReceivingContract(_to);
            receiver.tokenFallback(msg.sender, _value, empty);
        }
        emit Transfer(msg.sender, _to, _value, empty);
    }
    /**
     *@function mint トークンの総量を増やす
     *管理者のみ実行できる
     */
    function mint(address _to, uint256 _unitAmount) onlyOwner public {
        require(_unitAmount > 0);
        bytes memory empty;

        total_supply = total_supply.add(_unitAmount);
        balances[_to] = balances[_to].add(_unitAmount);
        emit Mint(_to, _unitAmount);
        emit Transfer(address(0), _to, _unitAmount, empty);
    }
    /**
     *@function burn トークンの総量を減らす
     *管理者のみ実行できる
     */
    function burn(address _from, uint256 _unitAmount) onlyOwner public {
        require(_unitAmount > 0 && balances[_from] >= _unitAmount);

        balances[_from] = balances[_from].sub(_unitAmount);
        total_supply = total_supply.sub(_unitAmount);
        emit Burn(_from, _unitAmount);
    }
}

ここではトークン総量を増やすメソッドmintの実行に制約を設けていないため、発行元が自由に通貨を増刷できるインフレ通貨となっています。通貨価値が下落しやすく、実際に通貨として使用する場合には本来もうひと工夫が必要ですが、今回は実験用なのでとりあえずそのままとします。

ownable.solは管有者のみに処理を許可するためのコントラクトです。

pragma solidity ^0.4.11;

contract Ownable {
    address public owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    constructor() public {
        owner = msg.sender;
    }

    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }

    function transferOwnership(address newOwner) onlyOwner public {
        require(newOwner != address(0));
        emit OwnershipTransferred(owner, newOwner);
        owner = newOwner;
    }
}

コードを書き終えたら、

truffle compile

でコンパイルし、エラーが出なくなるまでデバッグします。

手順5 Migrationファイルを作成する (10分)

migrationフォルダに2_deploy_token.jsというファイルを作成します。(Migrationファイルの書き方として、[処理順番号]_[処理名].jsとする必要があります。)

[Project Home]
|
|_  contracts
|    |_  Migrations.sol
|    |_  CurryRiceCoin.sol
|
|_  migrations
|    |_  1_initial_migration.js
|    |_  2_deploy_token.js *
|
|_  truffle-config.js
|_  truffle.js

CurryRiceCoinコントラクトのコンストラクタの引数にinitialSupply=100000000を渡して初期化します。

const Token = artifacts.require('./CurryRiceCoin.sol')

module.exports = (deployer) => {
  const initialSupply = 100000000;
  deployer.deploy(Token, initialSupply)
}

手順6 truffle.jsを編集する (10分)

先ほどインストールしたtruffle-hdwallet-provider-privkeyをrequireで読み込みます。
network_id: 3 はRopstenネットワークのidです。デプロイの際には、privkeyの所有アドレスからGasが引かれます。

const HDWalletProvider = require('truffle-hdwallet-provider-privkey');
const privkey = "プライベートキー";
const accessToken = "infura.ioのアクセストークン";

module.exports = {
  networks: {
    ropsten: {
      provider: function() {
        return new HDWalletProvider(
          privkey,
          "https://ropsten.infura.io/" + accessToken
        );
      },
      network_id: 3,
      gas: 2000000
    }   
  }
};

手順7 デプロイする(3分)

truffle migrate --network ropsten

ガスが足りない場合は次のエラーが出ます。2000000 GWeiあれば十分だと思います。

Error encountered, bailing. Network state unknown. Review successful transactions manually.
Error: The contract code couldn't be stored, please check your gas amount.

うまく行くと、次のようにコントラクトアドレスが得られます。

> truffle migrate --network ropsten
Using network 'ropsten'.

Running migration: 2_deploy_token.js
  Deploying CurryRiceCoin...
  ... 0x03517ac92e4149f018f0c2363c6e76d4595fc74a866593443076b7228eae85b7
  CurryRiceCoin: 0x9a08cfeb69414888919a54c4cf6a11a4b3779ad
Saving successful migration to network...
  ... 0x22790252da1b37247d9cb0227a56fc5e1358a722dcbe9af545787166917d2611
Saving artifacts...

CurryRiceCoinのアドレスは0x9a08cfeb69414888919a54c4cf6a11a4b3779adのようです。

手順8 確認(5分)

Etherscanでトークンが作られたか確認します。
https://ropsten.etherscan.io/token/0x9a08cfeb69414888919a54c4cf6a11a4b3779add

Total Supplyが1CURRYとなっており、トークンが作られているのがわかります。

次に、MyEtherWalletで管理者アドレスにトークンが割り当てられているか確認します。確認するには、Add Custome Token からトークンのアドレスを指定する必要があります。

無事1CURRYが割り当てられているようです。

まとめ

今回、EthereumのテストネットであるRopstenでERC223トークンの作成を行いました。手順さえわかっていれば1時間半~2時間半程度で割と迅速に作成できると思います。(私はそもそも手順がわからなかったので、5時間ほどかかりましたが・・・)もっと早い方法があるかもしれないので、またどなたか教えてください。

今回のソースはGithubに上げてあります。
https://github.com/pragma-curry/curryricecoin

参考にさせていただいたサイト

https://github.com/Dexaran/ERC223-token-standard/blob/master/README.md
https://github.com/NANJ-COIN/token/blob/master/NANJCOIN.sol
https://tech.pepabo.com/2017/12/06/erc20-token-on-ropsten/
https://wakuwaku-currency.com/virtual-currency/ethereum/about-use-hosting-service-infura.html

Leave a Reply

Your email address will not be published. Required fields are marked *