見出し画像

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

概要

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

ソースコード

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

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

フレームワークなど


1. truffle: スマートコントラクト開発用のフレームワークとして使用している。テストやデプロイを行う。
2. React: フロントエンド側の開発をするために使用している。
3. Material-UI(MUI): React向けのUIコンポーネントライブラリ
4. Open Zeppelin: solidity用のフレームワーク
5. 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を起動させます。

画像1


Ganacheにワークスペースを作成していない場合には、右下の「NEW WORKSPACE」をクリックしてワークスペースを追加しましょう。
設定ファイルを選ぶような画面が出てきますので、truffle-config.jsファイルまでのパスをしてしてください。
起動したら下のような画面になるはずです。

Ganache起動画面

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. アプリの操作
問題なければ、上記コマンドを打ち込んだ後にlocalhost:3000にアクセスして次のような画面になっているはずです。(MetaMaskでのログインが求められるのでログインしてください。)

ホーム画面

もしMetaMaskにカスタムネットワークが追加されていなかった場合には次のような設定をMetaMaskに追加してください。

画像4

無事にログインが済んだら右上のメニューボタンから「MyToken」を選択してERC20トークン作成画面に遷移したいと思います。

スクリーンショット 2022-03-11 11.47.34


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

スクリーンショット 2022-03-11 11.51.18

問題なければ次のポップアップが表示されるはずです。(失敗する時はガス代などを見直してみてください。)

画像7


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

作成ずみトークン

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

画像9

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

トークンをインポート

トークンインポート2

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

スクリーンショット 2022-03-11 12.02.25

 
トークンはまだ発行していないので数量が0になっています。なので次は発行機能を使ってこのブロックチェーン上にMCH2トークンを発行してみたいと思います。

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

発行成功

うまくいけば「発行成功!」のポップアップが出ます!
MetaMaskでも確認したいと思います。

画像14

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

画像15

うまくいけば「送金成功!」のポップアップが出ます。
MetaMaskでも減っていることを確認できました!

送金後1

それでは送金したアドレスに切り替えてトークンが送金されていることを確認しましょう。

送金後2

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

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

償却

MetaMaskでも0になっていることを確認できました!

償却後2

以上で、実装してみた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トークンでマイトークン作成・実行までまるっと解説!

#つくってみた

この記事が参加している募集

この記事が気に入ったらサポートをしてみませんか?