👋

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

2022/03/11に公開10

概要

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

hajisanhajisan

何も記事を投稿しておりませんが、フォローいただきありがとうございます。
実は、来年度中の実装を目指して、web3的教育エコシステムの構築を進めております。中小規模だけど面白い教育プログラムを提供する教育機関やコミュニティがそれぞれ独自のトークンを発行し、エコシステム共通の独自トークンに交換できることで、ジョイントイベントへの参加など面白いことができるようになるのではと考えております。
そんな私にとって、Harukiさんの記事は、とても勉強になるものばかりで大変ありがたいです。twitterにランダムですがプロジェクトの構想を共有しておりますので、もしよろしければ覗いてみてください。
https://twitter.com/hajimeyamaya

HarukiHaruki

コメントいただきありがとうございます!!
そう言っていただけますと何よりです!

Dappについては書籍についてもまだまだ少ないのでキャッチアップが大変なのですが独学で頑張って作りました。UNCHAINなどは、この他様々なチェーン上で動くDAppを開発できるコンテンツを提供していますのでもしよろしければご参加ください!!

twitterもフォローさせていただきました!

hajisanhajisan

すみません、ブロックアカデミーさんのdexを作っているのですが、誰にも相談できなくてHarukiさんなら以前参考にされていたとのことでしたので、こちらに投稿させていただいております。
添付の写真にあるように、swapまではうまくいくんですが、metamaskにトークンをインポートしようとすると、いつもトークンの額が写真のようになってしまいます。
このエラーの修正についてご指南いただけますでしょうか。
突然のお願い事で大変恐縮ですが、よろしくお願いします。

HarukiHaruki

ありがとうございます!
原因としては下記2つが考えられると思います!

  1. ものすごい量のトークンを保有している
  2. 表示する桁数が18ではなく、他の桁数になってしまっている

1.が原因だった場合は適当なアドレスに少し送金してみると直るかも知れません!
2. が原因だった場合にはトークンをインポートする時に指定する桁数をチェックしてみてください!

hajisanhajisan

早速のご回答ありがとうございます!
1については、そこまで購入してないので考えにくいですね。
2については、インポートの際になぜか自動表記されず手入力で桁数を入力しても、スワップした額に関係なく10000…となってしまいます。また、インポートもできません。
ただ、手動で桁数を24にすると10000となり、32にすると0.0001になりました。

HarukiHaruki

なるほどです。。普通のERC20に準拠しているトークンであれば18桁になるはずなのですが少し変ですね・・、お手数なんですが今開発されているGithubのリンクか何か共有していただくことは可能でしょうか?

HarukiHaruki

ブロックアカデミーさんの動画にそって開発されているのであれば基本的には問題ないはずですが、スマートコントラクト側のプログラムに抜けか何かがあるかも知れないです。

hajisanhajisan

ありがとうございます。下記のリンクがGithubのリンクになります。コード自体はほとんどブロックアカデミーさんと同じなんですが…よろしくお願いします!
https://github.com/EpisShenzhen/test_dex

あと、別件なのですが、私もUNCHAINやHENKAKUのDiscordコミュニティに入っておりまして(hajisan#5703)、現在構築を進めているweb3的教育エコシステムで使用する独自トークン同士を交換できるdexを探しておりました。
そこで、Harukiさんが開発されたdexを今後活用させていただいてもよろしいでしょうか。そして、テスト運用がうまくいくようであれば、実装したいと考えております。
ですので、今後色々とご相談をさせていただければと思いますが、Discordの方でお話しする方がよろしいでしょうか。

hajisanhajisan

metamaskをアンインストールして、再アップロードしたら桁数やインポートされる予定のトークンは表示されるようになりましたが、どうもbuyToken、sellTokenともに失敗してますね。


HarukiHaruki

UNCHAINに参加されているんですね! そうしましたらDiscordの方が良いかも知れません!お話しを聞いた感じだと興味を持ってくれるエンジニアがUNCHAINにいる気がするので開発チーム作ってDEXを開発するということも面白いかもしれません!

私が作ったDEXは、AMMのところのアルゴリズムがめちゃ適当でして笑
Uniswapのスマートコントラクトを研究して真似て作られた方が良いかも知れないです!

私のDiscordID は、mashharuki#9415です!