週休七日

趣味のこととか、技術のこととか、読書感想文とか

ERC-20トークンを爆速で作ってみる

ERC-20とは

github.com

Ethereum上で発行できるトークンの標準規格です。

Truffle

truffleframework.com

トリュフです。
Ethereumのフレームワークで手軽にトークンが作れます。
まずはnpm経由でインストールします。

npm install -g truffle

次に任意のディレクトリ内でtruffleを初期化します。

truffle init

以下のように表示されたら成功です。
f:id:luca3104:20180404213327p:plain:w300

OpenZeppelin

github.com

OpenZeppelinはスマートコントラクトのフレームワークで手軽にスマートコントラクトを書くことができます。
OpenZeppelinのZeppelin Solidityを利用します。
まずはプロジェクト内でpackage.jsonを生成します。

npm init -f

次にモジュールをインストールします

npm install truffle-hdwallet-provider --save

これで準備は完了です。

solidityファイルの作成

token/contracts以下に.solのファイルを作成します。(今回はkj3104.sol)
f:id:luca3104:20180404230717p:plain:w400

zeppelin-solidity/contracts/examples/SimpleToken.solを参考に書いていきます。

pragma solidity ^0.4.18;

import "zeppelin-solidity/contracts/token/ERC20/StandardToken.sol";

contract kj3104 is StandardToken {
    string public constant name = "kj3104"; // 通貨の名前 Bitcoin, Ethereumなど
    string public constant symbol = "XKJ"; // 通貨シンボル BTC, ETHなど
    uint8 public constant decimals = 18; // 少数以下の桁数

    // migration時に初期発行量を決めれるようにする。
    function kj3104(uint256 _initialSupply) public {
      totalSupply_ = _initialSupply; 
      balances[msg.sender] = _initialSupply;
      Transfer(0x0, msg.sender, _initialSupply);
    }
}

これでコントラクトコードの完成です。
次にこのコードをコンパイルしましょう。
コマンドラインで以下を実行します。

truffle compile

すると、build/contracts以下にズラズラとファイルが生成されます。
今回作成したkj3014.solに対応するコンパイル後のファイルはkj3104.jsonです。
次にmigrationを書きます。
ファイル名は[数字]_[トークン名]_migration.jsとします。
今回は2_kj3104_migration.jsとなります。 初期化時に生成される1_initial_migration.jsを参考にして記述します。

const kj3104 = artifacts.require('./kj3104.sol')

module.exports = (deployer) => {
  const initialSupply = 1000000e18
  deployer.deploy(kj3104, initialSupply) 
}

これで完了です。

デプロイ

デプロイします。
今回はテストネット等は使わずにローカルでのみ動かします。
コマンドラインで以下を実行します。

truffle develop

するとアカウントとプライベートキー、ニーモニックが表示され、対話型のコンソールになります。
まずはmigrateと入力しましょう。
先程作成したmigrationファイルを利用してコントラクトコードをデプロイします。
これでデプロイの完了です。

コントラクトコードを編集後、または対話コンソール再起動後

migrateをすると以下のようなエラーが出てしまいます。

Error: Attempting to run transaction which calls a contract function, but recipient address 0x8cdaf0cd259887258bc13a92c0a6da92698644c0 is not a contract address

この場合はmigrate --resetと入力しましょう。

動かす

実際にコンソールから動かします。
まずはコントラクトを変数に入れます。

truffle(develop)> token = kj3104.at(kj3104.address)

こうすることで、 tokenから様々な情報が取れるようになります。

truffle(develop)> token.name()
'kj3104'

truffle(develop)> token.symbol()
'XKJ'

アカウント一覧はweb3.eth.accountsから取得できます。

truffle(develop)> web3.eth.accounts
[ '0x627306090abab3a6e1400e9345bc60c78a8bef57',
  '0xf17f52151ebef6c7334fad080c5704d77216b732',
  '0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef',
  '0x821aea9a577a9b44299b9c15c88cf3087f3b5544',
  '0x0d1d4e623d10f9fba5db95830f7d3839406c6af2',
  '0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e',
  '0x2191ef87e392377ec08e7c08eb105ef5448eced5',
  '0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5',
  '0x6330a553fc93768f612722bb8c2ec78ac90b3bbc',
  '0x5aeda56215b167893e80b4fe645ba6d5bab767de' ]

最後に送金をします。
0x627306090abab3a6e1400e9345bc60c78a8bef57から0xf17f52151ebef6c7334fad080c5704d77216b732に100XKJを送金します。

truffle(develop)> token.transfer(web3.eth.accounts[1], 100e18)
{ tx: '0x58147cfec997910d11bdab81d79abc4573b4bf50e1cd6b14b281fc2fa5118a7b',
  receipt:
   { transactionHash: '0x58147cfec997910d11bdab81d79abc4573b4bf50e1cd6b14b281fc2fa5118a7b',
     transactionIndex: 0,
     blockHash: '0xa3c03f135dc87ffef92c58e303bc22975d7162e72b1399ea0c28f5d8024e4def',
     blockNumber: 5,
     gasUsed: 51925,
     cumulativeGasUsed: 51925,
     contractAddress: null,
     logs: [ [Object] ],
     status: '0x01',
     logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000010000008000000000000000000010000000080000000000000000000000000000000000000000000000000000000000000000010000000000000000000010000000000000000000000000000000000000000010000000002000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000010000000000000' },
  logs:
   [ { logIndex: 0,
       transactionIndex: 0,
       transactionHash: '0x58147cfec997910d11bdab81d79abc4573b4bf50e1cd6b14b281fc2fa5118a7b',
       blockHash: '0xa3c03f135dc87ffef92c58e303bc22975d7162e72b1399ea0c28f5d8024e4def',
       blockNumber: 5,
       address: '0x345ca3e014aaf5dca488057592ee47305d9b3e10',
       type: 'mined',
       event: 'Transfer',
       args: [Object] } ] }

送金されているか残高を見ます。

truffle(develop)> token.balanceOf(web3.eth.accounts[0])
{ [String: '9.999e+23'] s: 1, e: 23, c: [ 9999000000 ] }

truffle(develop)> token.balanceOf(web3.eth.accounts[1])
{ [String: '100000000000000000000'] s: 1, e: 20, c: [ 1000000 ] }

これで送金ができました!

おまけ

中央銀行的な発想で追加発行ができるようにします。
コントラクトコードに、addTotalSupplyを追加し、totalSupply_及びownerの残高に追加します。
以下がコードです。

contract kj3104 is StandardToken {
    string public constant name = "kj3104"; // 通貨の名前 Bitcoin, Ethereumなど
    string public constant symbol = "XKJ"; // 通貨シンボル BTC, ETHなど
    uint8 public constant decimals = 18; // 少数以下の桁数

    address public owner; // オーナーのアドレスを初期化時に持つ

    function kj3104(uint256 _initialSupply) public {
      owner = msg.sender; // オーナーの代入

      totalSupply_ = _initialSupply;
      balances[msg.sender] = _initialSupply;
      Transfer(0x0, msg.sender, _initialSupply);
    }

    function addTotalSupply(uint256 _value) public {
      require(owner == msg.sender);  // オーナーでないと実行できない

      totalSupply_ += _value;
      balances[msg.sender] += _value;
  }
}

これで追加発行機能を実装できました。

最後に

Open Zeppelinは実は知らなくて前まで結構面倒くさいなあと思いながら書いてました。
ぜひ使ってみてください

これからトークンや仮想通貨、トークンエコノミーなど乱立する時代になってくると思います。
一時期流行ったVALUのような感覚で自分自身をトークン化して価値をつけるというのも面白いですね。