👋

ERC20トークンを独自に作成するアプリを作ってみる

2022/03/11に公開約12,000字

概要

Ganacheを用いてプライベートブロックチェーン上にERC20トークンを独自に作成し、発行・送金・償却が可能な簡易アプリを作成してみました。
簡単な画面キャプチャとソースコードを共有していきたいと思います。

ソースコード

以前作成したアプリケーションのソースコードをベースに機能追加をしています。

https://github.com/mashharuki/fundraiser-dapp

フレームワークなど

名称 内容
truffle スマートコントラクト開発用のフレームワークとして使用している。テストやデプロイを行う。
React フロントエンド側の開発をするために使用している。
Material-UI(MUI) React向けのUIコンポーネントライブラリ
Open Zeppelin solidity用のフレームワーク
ERC20 代替性トークンを実装するため標準規格

独自コントラクトについて

openzeppelin Wizardをベースにほんの少しだけ独自のメソッドを追加実装してERC20コントラクトを作成しました。

MyToken.solの内容は下記の通りです。
基本的には上記サイトで作成したもので、トークンの名前とシンボル、decimalを好きな数値に設定できるようになっています。

/**
 * ERC20トークン用のスマートコントラクトファイル
 */
pragma solidity >=0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";

/**
 * MyTokenコントラクト
 */
contract MyToken is ERC20, ERC20Burnable, Pausable, Ownable, ERC20Permit, ERC20Votes {

      // トークン名
      string tokenName;
      // シンボル名
      string tokenSymbol;

      /**
       * コンストラクター
       * @param _name トークン名
       * @param _symbol シンボル名
       */
      constructor(string memory _name, string memory _symbol, uint8 _decimal) ERC20(_name, _symbol, _decimal) ERC20Permit(_name) {
            // トークン名とシンボル名を設定する
            tokenName = _name;
            tokenSymbol = _symbol;
      }

      /**
       * トークンを停止するための関数
       */
      function pause() public onlyOwner {
            _pause();
      }

      /**
       * 停止状態を解除するための関数
       */
      function unpause() public onlyOwner {
            _unpause();
      }

      /**
       * トークンを発行する関数
       * @param to 発行先アドレス
       * @param amount 発行数 
       */
      function mint(address to, uint256 amount) public onlyOwner {
            _mint(to, amount);
      }

      /**
       * トークンを償却する関数
       * @param to 発行先アドレス
       * @param amount 発行数 
       */
      function burn(address to, uint256 amount) public onlyOwner {
            _burn(to, amount);
      }

      /**
       * トークン移転用の関数
       * @param from 発行元アドレス
       * @param to 発行先アドレス
       * @param amount 発行数
       */
      function _beforeTokenTransfer(address from, address to, uint256 amount) internal whenNotPaused override {
            super._beforeTokenTransfer(from, to, amount);
      }
   
      /**
       * トークン移転用の関数
       * @param from 発行元アドレス
       * @param to 発行先アドレス
       * @param amount 発行数
       */
      function _afterTokenTransfer(address from, address to, uint256 amount) internal override(ERC20, ERC20Votes) {
            super._afterTokenTransfer(from, to, amount);
      }

      /**
       * 発行用の関数
       */
      function _mint(address to, uint256 amount) internal override(ERC20, ERC20Votes) {
            super._mint(to, amount);
      }

      /**
       * 償却用の関数
       */
      function _burn(address account, uint256 amount) internal override(ERC20, ERC20Votes) {
            super._burn(account, amount);
      }
}

また、MyTokenコントラクトを生成するためのMyTokenFactory.solは以下の通りです。
このFactoryコントラクトを介して画面上から独自のERC20トークンを作成します。

pragma solidity >=0.8.0;

import './MyToken.sol';

/**
 * MyTokenFactoryコントラクト
 */
contract MyTokenFactory {
    // MyToken型の配列
    MyToken[] private _myTokens;
    // myTokens関数から返すことのできる最大値
    uint256 constant maxLimit = 20;

    // インスタンスが生成された時のイベント
    event MyTokenCreated (MyToken indexed myToken, address indexed owner);

    /**
     * インスタンス数を取得する関数
     */
    function myTokensCount () public view returns (uint256) {
        return _myTokens.length;
    }

    /**
     * MyTokenコントラクト生成関数
     * @param name トークン名
     * @param symbol シンボル名
     * @param decimal 小数点桁数
     */
    function createMyToken (string memory name, string memory symbol, uint8 decimal) public {
        // インスタンスを生成
        MyToken myToken = new MyToken(name, symbol, decimal);
        // コントラクト呼び出し元アドレスに権限を移譲する。
        myToken.transferOwnership(msg.sender);
        // 配列に格納する。
        _myTokens.push(myToken);
        // イベントの発行
        emit MyTokenCreated(myToken, msg.sender);
    }

    /**
     * MyTokenコントラクト群を取得する関数
     * @param limit 上限取得値
     * @param offset 取得数
     * @return coll MyTokenコントラクトの配列 
     */
    function myTokens (uint256 limit, uint256 offset) public view returns (MyToken[] memory coll) {
        // 取得前に確認
        require (offset <= myTokensCount(), "offset out of bounds");
        // 最大値を上回っている場合は、limitを格納する。
        uint256 size = myTokensCount() - offset;
        size = size < limit ? size : limit;
        // sizeは、maxLimitを超えてはならない。
        size = size < maxLimit ? size : maxLimit;
        // コントラクト用の配列
        coll = new MyToken[](size);

        for (uint256 i = 0; i < size; i++) {
            coll[i] = _myTokens[offset + i];
        }

        return coll;    
    }
}

起動からアプリの操作の流れまで

次に上記コントラクトをプライベートブロックチェーン上にデプロイしてアプリ上から発行・作成までの操作の流れを共有いたします。

  1. Ganacheを登録・起動する。
    最初は、Ganacheを起動させます。
    スクリーンショット 2022-03-11 11.30.46.png
    Ganacheにワークスペースを作成していない場合には、右下の「NEW WORKSPACE」をクリックしてワークスペースを追加しましょう。
    設定ファイルを選ぶような画面が出てきますので、truffle-config.jsファイルまでのパスをしてしてください。
    起動したら下のような画面になるはずです。
    スクリーンショット 2022-03-11 11.33.40.png

  2. コントラクトをデプロイする。
    次にMyTokenFactory.solをデプロイします。MyTokenコントラクトはこのFactoryコントラクトを使って作成するのでこのタイミングでのデプロイは不要です。

    プロジェクトのルートディレクトリ直下で、次のコマンドを打ち込みます。
    truffle migrate --network develop --f 9 --to 9

    デプロイに用いるコードは下記の通りです。

    /**
     * MyTokenFactoryコントラクトデプロイ用JSファイル
     */ 
    
    // MyTokenFactoryコントラクトを読み込んでインスタンス化する。
    const MyTokenFactoryContract = artifacts.require("MyTokenFactory");
    
    module.exports = function (deployer) {
      // コントラクトをデプロイする。
      deployer.deploy (MyTokenFactoryContract);
    }
    

    ここまでできたら前準備は完了です。いよいよアプリを起動させて実際にトークンを作成してみたいと思います。

  3. アプリの起動
    今回作成したアプリは、フロントエンドの開発で人気になっている「React」を採用しています。
    「client」フォルダ配下に移動して次のコマンドを打ち込んでアプリを起動させます。
    npm run start

  4. アプリの操作
    問題なければ、上記コマンドを打ち込んだ後に<a href="http://localhost:3000">localhost:3000</a>にアクセスして次のような画面になっているはずです。(MetaMaskでのログインが求められるのでログインしてください。)
    スクリーンショット 2022-03-11 11.43.38.png

    もしMetaMaskにカスタムネットワークが追加されていなかった場合には次のような設定をMetaMaskに追加してください。
    スクリーンショット 2022-03-11 11.45.12.png

    無事にログインが済んだら右上のメニューボタンから「MyToken」を選択してERC20トークン作成画面に遷移したいと思います。
    スクリーンショット 2022-03-11 11.47.34.png

  5. ERC20トークン作成
    ERC20トークンは、下記画面から作成します。
    好きな名前、シンボル、decimal(0~18を選択します。)を設定したら「MyTokenデプロイ」ボタンを押しましょう。
    するとMetaMaskのポップアップが出てきてガス代などを指定するように求められるので適用に設定してOKしましょう!
    今回は、「Mash2」という名前、「MCH2」というシンボルのトークンを作成してみることにします。
    スクリーンショット 2022-03-11 11.51.18.png

    問題なければ次のポップアップが表示されるはずです。(失敗する時はガス代などを見直してみてください。)
    スクリーンショット 2022-03-11 11.53.18.png

  6. ERC20トークンの確認
    早速作成したトークンを確認しに行きましょう。
    ホーム画面に次のような要素が増えているはずです。

    スクリーンショット 2022-03-11 11.54.32.png

    今回作成したのは下の方なので「VIEW MORE」ボタンをクリックしてください。
    このERC20トークンのアドレス情報などが記載されたポップアップが表示されるはずです。

スクリーンショット 2022-03-11 11.55.40.png

さぁ、これでこのトークンのもつアドレス情報などがわかりました!
MetaMaskでも数量等を確認できるようにトークンの情報をインポートしてみましょう。
MetaMaskを開いて下までスライドするとトークンをインポートというリンクが出てくるのでクリックしましょう。
すると、トークンのアドレスを入力するフォームが出てきますので、ここにトークンのアドレスをコピぺします。

スクリーンショット 2022-03-11 11.56.22.png
  スクリーンショット 2022-03-11 12.01.29.png

成功すれば下記のように表示されるようになります。

スクリーンショット 2022-03-11 12.02.25.png
 
トークンはまだ発行していないので数量が0になっています。なので次は発行機能を使ってこのブロックチェーン上にMCH2トークンを発行してみたいと思います。

さっきのポップアップを表示させて、toとamountにそれぞれ発行先のアドレスと発行量を入力しましょう。
今回は自分自身に10000トークン発行してみたいと思います。

スクリーンショット 2022-03-11 18.43.14.png

うまくいけば「発行成功!」のポップアップが出ます!
MetaMaskでも確認したいと思います。
スクリーンショット 2022-03-11 18.44.04.png

増えていますね!それでは今度は別のアドレスに送金してみたいと思います。
先ほど同様、送金したいアドレスと送金料をそれぞれ入力して「送金」ボタンを押します。

スクリーンショット 2022-03-11 18.45.24.png

うまくいけば「送金成功!」のポップアップが出ます。
MetaMaskでも減っていることを確認できました!
スクリーンショット 2022-03-11 18.46.38.png

それでは送金したアドレスに切り替えてトークンが送金されていることを確認しましょう。
スクリーンショット 2022-03-11 18.48.01.png

↑しっかり6000MCH2が送金されていますね!

最後に4000MCH2を償却してしまいましょう!
今回は、償却したい量を指定して「償却」ボタンを押します。
うまくいけば、「償却成功!」のポップアップが出ます!

スクリーンショット 2022-03-11 18.49.12.png

MetaMaskでも0になっていることを確認できました!
スクリーンショット 2022-03-11 18.50.29.png

以上で、実装してみたERC20トークン作成アプリの基本操作は以上になります。

最後に

ERC20トークンを実際に開発してみることでより理解が深めることができました。
通貨のようなものと考えていましたが、実態としては(少なくともEthereumを利用した場合には)solidityでプログラムされたコントラクトであることを認識することができました。発行や償却といった操作もコントラクト上のメソッドで行っており、これらの操作によって発生するデータの状態や更新履歴をブロックチェーン上に記録することで耐改ざん性と透明性を担保でき、通貨のように価値のあるものとしてやりとりすることができているのだと理解しています。

次は、ERC721トークン(NFT)コントラクトを独自に作成できるアプリを作成して、非代替トークンの正体についても理解できるようにしたいと思います。

今後の課題としては、Rinkebyなどのテストネット上にデプロイしてみてこのコントラクトが使用できるかテストしてみたいと考えています。
ありがとうございました。

参考情報

以下、参考にさせていただいたサイトや書籍のリンクを貼らせていただきます。
開発する上で大変参考になったものばかりです。

実践スマートコントラクト開発
Rinkeby Faucet
REMIX IDE
OpenZeppelin Docs
wizard.openzeppelin.com
Ethereum Smart Contract Best Practices(和訳)
ERC20規格で開発された暗号資産一覧
ERC20トークンについて
CoinTool
ERC20トークンでマイトークン作成・実行までまるっと解説!

Discussion

ログインするとコメントできます