【初めてのDApps開発①】ReactでシンプルなDAppsを作ってみよう!~コントラクト編~


はじめに

私はつい最近までブロックチェーンって何?なんか面白そう!ってゆー感じだったDApps開発初心者です。
この記事は、Ethereumで簡単なDappsを作ってDapps開発の流れを掴んでいくのが目的です。
自分が初めてDAppsを作る際にあったらよかったなあ〜と感じたロードマップを書きました。

この開発では以下の3つの項目を行っていきます。
3項目の内この記事では、1と2を行なっていきます。3については、【初めてのDApps開発②】の記事を参照して下さい。

1. Remixを用いたスマートコントラクト開発
2. Truffleを用いたスマートコントラクトのデプロイ
3. Reactを用いたフロントエンド開発

作っていくアプリケーション

今回作成するアプリケーションはブロックチェーン上に自分の氏名、年齢、趣味をブロックチェーン上に記録するだけのシンプルなものです。
URL: https://diarydapp-2a69c.web.app

完成するアプリケーションは、以下のものなります。
1. 左側のフォームでユーザーの氏名、年齢、趣味を入力。入力された情報は、ブロックチェーン上に記録。
2. 右側のフォームでユーザー情報の検索を行う。検索したいユーザーのアドレスを入力。
3. 入力されたアドレスを持つユーザーの情報を表示。

本記事では、スマートコントラクトの開発からデプロイまでを行っていきます!
では、一緒に開発を進めていきましょう!!

Remixでスマートコントラクト開発

Remixはブラウザ上でスマートコントラクトを作成し、テストができるツールです。
リンクからRemixを開き、
左上の+マークをクリックしてファイルを作成し、コントラクトを書いていきます。

今回扱う言語Solidityについては、こちらを参考にしてください。
【Solidity入門】まず最初にSolidityで覚えるべき基本構文まとめ
Solidity公式ドキュメント

コントラクト作成

今回使用するコントラクトは以下のものを使います。

Resister.sol
// コンパイラのバージョン指定(0.5.0以上)
pragma solidity ^0.5.0;

contract Resister {

    // アカウント情報
    struct Data {
        string name; // 名前
        uint256 age; // 年齢
        string hobby; // 趣味
    }

    address[] public users; // 全ユーザーのアドレスを格納

    mapping(address => Data) accounts;

    // アカウントを登録する関数
    function registerAccount(string memory _name, uint256 _age, string memory _hobby)
        public
        returns (bool)
    {
        //アカウントが登録されていなければ新規会員登録をする
        if (!isUserExist(msg.sender)) {
            users.push(msg.sender);
        }
        accounts[msg.sender].name = _name;
        accounts[msg.sender].age = _age;
        accounts[msg.sender].hobby = _hobby;
        return true;
    }

    // アカウントが登録されているかどうかを確認
    function isUserExist(address user) public view returns (bool) {
        for (uint256 i = 0; i < users.length; i++) {
            if (users[i] == user) {
                return true;
            }
        }
        return false;
    }

    // アカウント情報を読み込む
    function viewAccount(address user) public view returns (string memory, uint256, string memory) {
        string memory _name = accounts[user].name;
        uint256 _age = accounts[user].age;
        string memory _hobby = accounts[user].hobby;

        return (_name, _age, _hobby);
    }   
}

一つずつ見ていきましょう。

struct Data {
        string name; 
        uint256 age; 
        string hobby; 
    }

これはデータの構造体です。氏名、年齢、趣味を記録します。stringは文字列を指定します。uint256 はデータの型で256bitまでの符号なし整数(自然数)を指定します。

address[] public users; 

mapping(address => Data) accounts;

上は配列です。氏名、年齢、趣味を記録したユーザーのETHアドレスを保存します。 solidityにはaddress型というデータ型が存在します。

配列の下はデータとデータを紐付けるためのものです。例えば, accounts[ETHアドレス]とすると上で定義した任意のData構造体にアクセスできます。

function registerAccount(string memory _name, uint256 _age, string memory _hobby)
        public
        returns (bool)
    {
        if (!isUserExist(msg.sender)) {
            users.push(msg.sender);
        }
        accounts[msg.sender].name = _name;
        accounts[msg.sender].age = _age;
        accounts[msg.sender].hobby = _hobby;
        return true;
    }

これは、アカウント情報(氏名、年齢、趣味)の登録を行う関数です。
msg.senderはトランザクションの送信者のアドレスです。ユーザーがすでに配列に含まれているかを確認して, users[]に追加します。
accounts[msg.sender].name = _nameのようにしてアドレスに紐づく構造体にデータを追加します。

function isUserExist(address user) public view returns (bool) {
        for (uint256 i = 0; i < users.length; i++) {
            if (users[i] == user) {
                return true;
            }
        }
        return false;
    }

これは、ユーザーが配列(users[])に含まれているかどうかを調べる関数です。

function viewAccount(address user) public view returns (string memory, uint256, string memory) {
        string memory _name = accounts[user].name;
        uint256 _age = accounts[user].age;
        string memory _hobby = accounts[user].hobby;

        return (_name, _age, _hobby);
    }   

アドレスに紐づくデータをみるための関数です。
string memory _name = accounts[user].name のようにして, 構造体に記録されているデータを取得します。最後に氏名、年齢、趣味を返り値として返します。

コントラクトのテスト

次は、コントラクトのテストを行っていきます。
まず最初に、画面左のSOLIDITY COMPILERタブに移動。COMPILERを以下の画像で選択されている0.5.0+commit. 1d4f565aに選択。それ以上のバージョンなら問題ないと思います。

選択したら、画面中部の青いボタンCompile Resister.solを押せばコンパイルしてくれます(⌘+Sでも可能)。
コンパイルが完了すると、画像のようにチェックマークが付きます。

コンパイルが完了したら、次にDEPLOY & RUN TRANSACTIONSタブに移動します。
ENVIRONMENT を JavaScriptVMに設定, CONTRACTはResisterを選択し, Deploy をクリックします。

次に関数をテストしていきます。
Deployed Contracts欄から
まず, resisterAccount に 適当に氏名、年齢、趣味を入力し, transactボタンをクリックします。

以下のように表示されれば関数呼び出し成功です。

次に users0 を入力し, callボタン をクリックします。これは usersの配列の0番目の要素を取得しています。すると先ほど resisterAccountを実行したアドレスが表示されます。
そのアドレスをコピーして viewAccount に入力します。そして call ボタンをクリックすると登録したデータが呼び出されます。

以上のことが問題なく動作できればテストは完了です。

Truffleでスマートコントラクトをデプロイ

Truffleはスマートコントラクトをテストしたり, デプロイしたりできるツールです。 今回はRinkebyテストネットにコントラクトをデプロイするためだけに使います。

今後nodeとnpmがインストールされていることを前提に話を進めます。
インストールされていない方はこちらの記事などを参考にしてください。
Mac用:買いたてのMacにNode.jsとnpmをインストール
Windows用:Node.js / npmをインストールする(for Windows)

↓私のバージョンです。

node:v14.2.0
npm:v6.14.8

まずTruffleのコマンドラインツールをインストールします。

$ npm install -g truffle

ディレクトリを作って移動し,truffle init します. このコマンドで色々ファイルが生成されます。その後 truffle-hdwallet-provider をインストールしてください。

$ mkdir dapps-deploy
$ cd dapps-deploy
$ truffle init
$ npm i --save truffle-hdwallet-provider

infuraエンドポイント& ニーモニック取得

次にinfuraでプロジェクトを作ります。infuraはEthereumノードを提供してくれるツールです。infuraを利用することでEthereumブロックチェーンに接続できるようになります。

ユーザー登録を済ませたらプロジェクトを作成。作成したプロジェクトに移動しSETTING画面へ。 KEYSENDPOINTRINKEBY を選択します。このエンドポイントはあとで使用します。

次に、Metamask のニーモニック(12個の英単語)を取得します。

まず、Metamaskをインストールしましょう。
インストール後、Metamaskを起動して右上のアカウントマークをクリック→、設定→, Security & Privacyを選択。パスフレーズを表示をクリックし, ニーモニックを取得します。

※[注意]ニーモニックの流出は秘密鍵の流出を意味するので, 絶対にGithubなどにそのまま公開しないように!

ファイルの作成&編集

dapps-deploy ディレクトリ下の truffle-config.js を編集します。
以下をコピペして先ほど取得したメタマスク のニーモニックとinfuraのエンドポイントを指定の場所に貼ります。

var HDWalletProvider = require("truffle-hdwallet-provider");
var mnemonic = 'メタマスクのニーモニック';
var accessToken = 'infuraのエンドポイント';
const gas = 4000000;
const gasPrice = 1000000000 * 60;
module.exports = {
  networks: {
    rinkeby: {
      provider: function () {
        return new HDWalletProvider(
          mnemonic,
          accessToken
        );
        },
        network_id: 4,
        gas: gas,
        gasPrice: gasPrice,
        skipDryRun: true
      }
    },
    compilers: {
      solc: {
        version: "0.5.2",
      }
    }
};

次に dapps-sample 下の migrations ディレクトリに 2_deploy_contract.js というファイルを作成してください。以下のように編集してください。
.require('ここで.solのコントラクト名を指定')

var contract = artifacts.require('Resister');
module.exports = function(deployer) {
  deployer.deploy(contract)
};

最後にdapps-deploy 下の contract ディレクトリに Resister.sol というファイルを作成し, Remixで作成したコントラクトを貼ります。

Resister.sol
pragma solidity ^0.5.0;

contract Resister {

    struct Data {
        string name; 
        uint256 age; 
        string hobby; 
    }

    address[] public users; 

    mapping(address => Data) accounts;

    function registerAccount(string memory _name, uint256 _age, string memory _hobby)
        public
        returns (bool)
    {
        if (!isUserExist(msg.sender)) {
            users.push(msg.sender);
        }
        accounts[msg.sender].name = _name;
        accounts[msg.sender].age = _age;
        accounts[msg.sender].hobby = _hobby;
        return true;
    }

    function isUserExist(address user) public view returns (bool) {
        for (uint256 i = 0; i < users.length; i++) {
            if (users[i] == user) {
                return true;
            }
        }
        return false;
    }

    function viewAccount(address user) public view returns (string memory, uint256, string memory) {
        string memory _name = accounts[user].name;
        uint256 _age = accounts[user].age;
        string memory _hobby = accounts[user].hobby;

        return (_name, _age, _hobby);
    }

}

最終的なファイル構成はこのようになっています。

Rinkebyネットワークへのデプロイ

いよいよコントラクトをデプロイします!
rinkebyのETHを持っていないとデプロイ時にGAS代が足りずにはじかれるので、ある程度ETHを持っている必要があります。
僕は以下の記事を参考にして補充しました。

EtherumのRinkebyテストネットでethを取得する

完了したら以下のコマンドを実行してください。

$ truffle compile
$ truffle migrate --network rinkeby --reset

このように返ってきたらデプロイ成功です!お疲れ様です!
あなたが作成したスマートコントラクトがブロックチェーンにデプロイされました!

最後に

ここまでで、コントラクトのデプロイまでを行うことができるはずです!!
次は、アプリケーションの見た目の部分であるフロントエンドの開発に取り掛かります。
続編となっている【初めてのDApps開発②】の記事も見てもらえると嬉しいです!