「早起きは0.003ETHの得」ってほんと！？→ほんとです

# はじめに
ブロックチェーン上で稼働するアプリケション(Dapp)を作ってみた話です。
https://www.wakeup-challenge.com
5日間毎朝決まった時間に起きると、報酬としてETHがホントにもらえるアプリケーションです。
「 __早起きは三文の徳__ 」を表現したアプリです。

この記事では、本アプリで実装したスマートコントラクトの説明、そしてローカル・本番でのデプロイ方法を説明します。
Dappを作ってみたい方に刺さる記事を目指します。

とんでもない長文記事ですが、現時点でDappを作るにはこれぐらいの作業が必要です、ということを示すためにそのまま投稿します。
アプリの作成期間は1ヶ月、この記事を書くのに2週間かかりました。

目次
* [アプリケーションの説明](#アプリケーションの概要)
* [ブロックチェーンネットワークの説明](#ブロックチェーンネットワークの環境構成)
* [バックエンドソースの説明](#バックエンドのソース説明solidity--truffle--ganache)
* [フロントエンドソースの説明](#フロントエンドのソース説明vuejs--web3--metamask)

# アプリケーションの概要
このアプリケーションは、毎朝5日間、連続して早起きをすることができたら報酬をもらえるというものです。
![スクリーンショット 2022-04-17 9.41.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201287/71afe445-f321-2612-458a-e625f82e5a06.png)
ユーザはまず、起床時刻をセットします。そして毎朝5日間、その時間に目覚めたボタンを押すことができたら、規定の報酬を受け取ることができます。
スマートコントラクトの製作者である私でさえ、このスマートコントラクトを変更することはできず、ユーザの支払ったデポジットを不当に持ち逃げしたり、ルールを途中で変えることはできません。
また、ユーザは会員登録する必要なく、ルールさえ知っていれば誰であっても参加可能です。
ユーザはフロント画面を通じて、スマートコントラクト中のロジックを実行することによって、ブロックチェーン上のデータに対する操作やETHの受授を行うことができます。

## 初めてDappsを遊ぶ人
初めてDappsを遊ぶ場合、いくつか事前準備が必要になります。
まず、お使いのブラウザ拡張機能のMetaMaskをインストールしてください。
続いてMetaMaskのウォレットに仮想通貨ETHを用意してください。(最低限0.015ETH程度必要です。)
MetaMaskのウォレットにETHを用意できたら、Ethereumレイヤー2の1つであるOptimismメインネットにETHをデポジットします。
https://app.optimism.io/bridge
そして、下記のURLをクリックしてMetaMaskにOptimismメインネットを追加してネットワークを切り替えてください。
https://chainid.link/?network=optimism
[アプリケーション](https://www.wakeup-challenge.com)のページを開いて、MetaMaskに接続した結果下記のようになればOKです。
![スクリーンショット 2022-04-24 14.36.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201287/acad96b0-7830-8a72-8bfd-604f180797ec.png)
* ネットワークにOptimistic Ethereumが選択されていること
* 0.01ETH以上保持していること

## アプリケーションの状態遷移
このアプリケーションの状態遷移図を説明します。
大きく3つの状態遷移があります。

1. 起床時刻を設定する
何月何日の何時から早起きチャレンジを行うか設定します。例えば次の月曜日の朝7時を設定します。(一度設定した起床時刻は5日間変更できません。)
この際、デポジットとして0.01ETHを支払う必要があります。このデポジットは、チャレンジに成功した場合は返還されますが、__チャレンジに失敗した場合はコントラクトのウォレットに回収されます。このデポジットが他の人のチャレンジの成功報酬の原資となっています。__

1. 起床を報告する
チャレンジを開始したら、毎朝、設定した起床時刻から15分以内に起床報告をする必要があります。設定した日時から15分以内に起床報告がされなかった場合、寝坊とみなしチャレンジ失敗となります。
__起床報告が成功したら、次の日の同じ時刻に起床予定時刻が更新されます。__

1. 報酬を取得する
5日間早起きができた場合、成功報酬を受け取る権利を得ます。成功報酬はデポジットとして預かっていた0.01ETHに加え、チャレンジ報酬0.003ETHを受け取ることができます。次のチャレンジを行うためには、再度起床時刻の設定を行います。
![スクリーンショット 2022-04-17 5.59.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201287/7e6db162-f195-8cdd-ca1c-5f23d6fcebbf.png)

## ユーザの収支
* チャレンジ成功時
ユーザは起床時刻を設定するときにデポジット0.01ETH支払い、5日後に成功報酬0.013ETHを受け取るので、収支は __プラス0.003ETH__ となります。
* チャレンジ失敗時
チャレンジに失敗した場合、デポジットの0.01ETHが没収されるため、収支は __マイナス0.01ETH__ となります。

## アプリケーションのアーキテクチャ
フロント画面はVue.js、サーバサイドのロジックはEthereumのスマートコントラクト言語Solidityで実装されています。
Vue.jsの画面はAWS S3にデプロイされ、スマートコントラクトはEthereumのL2レイヤーであるOptimismメインネットにデプロイされています。
全ソースは下記にあります。Vue.jsのプロジェクト構成とSolidity(Truffle)のプロジェクト構成がトップ配下にごちゃっとなっていてわかりにくい点はご容赦ください。
https://github.com/sh0yu/wakeup-dapp

# ブロックチェーンネットワークの環境構成
スマートコントラクトの開発環境はある程度整ってきているとはいえ、めちゃくちゃ複雑です。
スマートコントラクトをデプロイする先として、 __実本番環境であるメインネット、テスト用に運用されているテストネット、ローカルテスト用のローカルネット__ の3種類が存在します。
メインネットとテストネットは、スマートコントラクトをデプロイすると全世界からアクセス可能となるパブリック環境、ローカルネットはプライベート環境です。
また、デプロイに必要な手数料として、メインネットについては当然本物のETHが必要になりますが、テストネット、ローカルネットではテスト用のETHを使うことができるため、無料で利用できます。
それでは、各環境を少し深堀していきます。

### ローカル環境
まずローカルで開発する際の環境ですが、[Truffleスイート](https://trufflesuite.com/)と呼ばれる一連のエコシステムを活用しました。
* [_Truffle_](https://trufflesuite.com/truffle/) :Solidityのプロジェクト管理・ビルド・デプロイを行う。
* [_Ganache_](https://trufflesuite.com/ganache/) :ローカル環境に立てるブロックチェーン。テスト用に100ETHを持ったアカウントが10個用意されている。

ローカル開発時は、Ganacheでブロックチェーン環境を構築し、Truffleを使ってスマートコントラクトをGanacheにデプロイします。さらに、ブラウザのMetaMask拡張機能から、Ganacheで払い出されたアカウントを使ってアプリケーションに接続し、トランザクションを発行してテストをする、といった感じになります。
Ganacheを起動すると、テスト用のETHを持ったアカウントが用意されており、ブロック、トランザクション、コントラクトなどの情報が可視化されています。GanacheのアカウントをMetaMaskから使うための設定は後述。

### テストネット
テストネット、メインネットでは、世界のどこかに存在するP2Pネットワークとのコミュニケーションが必要になります。それらをよしなにやってくれるサービスの１つが [_Infura_](https://infura.io/product/ethereum)です。
今回テストネットとしてOptimism Kovanを利用しました。Truffleを使って、Infuraから払い出されたテストネットのエンドポイントに向かってデプロイする流れになります。
Infuraではアカウントを作成し、プロジェクトを作成します。プロジェクトの設定画面では、下記のようにネットワークを選択するとエンドポイントが表示されますが、Optimismはデフォルトでは設定がないため、アドオンする必要があります。
↓プロジェクトの設定画面。
初めて利用する場合は赤枠でくくった部分を押下して、Optimism Kovanの右にあるAdd-onを押下して、Optimismネットワークを追加します。
![スクリーンショット 2022-04-22 5.36.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201287/ec7c6774-42f7-c0b5-4424-6d6d6e66d7f8.png)
ここで表示されているエンドポイントは、Truffleでスマートコントラクトをデプロイする際に利用します。

また、MetaMaskの方でも、アプリケーションに接続するためにOptimism Kovanのネットワークを追加する必要があります。下記をクリックして、Optimism Kovanネットワークに接続してください。
https://chainid.link/?network=optimism-kovan

さらに、テスト用のETHは[ファセットサービス](https://faucet.paradigm.xyz/)などから取得する必要があります。

### メインネット
メインネットは[Optimism Mainnet](https://www.optimism.io/)を使いました。Ethereumメインネットは手数料が高い、捌けるトランザクション量が少ない、といったデメリットがあり、それらを解消するため、Ethereumの上にL2(レイヤー2)が開発されています。その１つがOptimismです。
Truffleを使ってデプロイするのは変わりませんが、デプロイに必要なETHとして実際のETHが必要になります。
~~もしETHを買ったことがないという人であれば、この作業はアホみたいにめんどくさいです。~~
私はコ○ンチェックでETHを買っていて、一部MetaMaskのアカウントに置いてあったので、それをOptimism Ethereumに移すという方法にしました。
その方法については下記を参照してください。
https://crypto-times.jp/optimistic-ethereum/

### 環境まとめ
各環境において、フロント画面からアプリケーションを触れるようになるまでに必要な準備とサービスをまとめておきます。

|                      | ローカルネット        | テストネット             | メインネット                                                 | 
| -------------------- | --------------------- | ------------------------ | ------------------------------------------------------------ | 
| ビルド               | Truffle               | Truffle                  | Truffle                                                      | 
| デプロイ             | Truffle               | Truffle                  | Truffle                                                      | 
| ブロックチェーン環境 | Ganache               | Infura<br>Optimism Kovan | Infura<br>Optimism Mainnet                                   | 
| ETH準備              | Ganache               | ファセットサービス       | 円→ETH購入<br>L2へデポジット                                | 
| MetaMaskアカウント   | Ganache              | Ganache    | 本番用アカウント<br>(テスト環境とはアカウントを分けたいので) | 

~~常人はメインネットを利用する準備だけで心折れる~~

Truffleが参照するデプロイ先情報は下記ファイルに記載してあるので参考にしてください。
https://github.com/sh0yu/wakeup-dapp/blob/master/truffle-config.js
`$ truffle migrate --network==optimism_kovan`などど実行時にネットワークを設定してデプロイ環境を切り替えできます。
ニーモニックなどは.envファイルに記載しておきGitHubにはアップしないようにしておきます。

## Ganache + MetaMask
ローカル環境でGanacheを実行し、そこで払い出されるアカウントを利用してアプリケーションのフロント画面のテストをするための準備について記載しておきます。
今回のアプリケーションでは仮想通貨の入ったウォレットを管理するサービスとして[MetaMask](https://metamask.io/)を利用します。
そのため大前提として、ブラウザ拡張機能のMetaMaskを[インストール](https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn?hl=ja)しておいてください。
また、Ganacheのインストールですが、私はMacを使っており、[公式ページ](https://trufflesuite.com/ganache/)からGanacheアプリケーション(バージョン2.5.4)をインストールしました。

Ganacheを起動してWorkspaceを新規作成します。下記のような画面が表示されればOKです。
![スクリーンショット 2022-04-17 11.08.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201287/712b0f0a-b607-c829-0385-4d1fae1ac800.png)
黒塗りで潰してある部分にはMNEMONIC(ニーモニック)と呼ばれるリカバリフレーズが記載されており、これをブラウザのMetaMaskに入力することで、これらのアカウントを利用できるようになります。
GanacheのMNEMONICは
```
You should only use this mnemonic during development. If you use a wallet application configured with this mnemonic, ensure you switch to a separate configuration when using that wallet with production blockchains.
```
とあるので、本番環境では使わないように注意してください。

次にブラウザを開いて、右上の方から、インストールした拡張機能のMetaMaskを起動します。すると下記のようにMetaMaskの認証画面が表示されます。
![スクリーンショット 2022-04-18 7.26.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201287/597f7447-b35a-d016-9322-1c7b94a4fcbf.png)
ここで、下方の「シークレットリカバリーフレーズを使用してインポート」を選択します。
すると新しいブラウザタブが開き、シークレットリカバリーフレーズの入力を求められます。ここにGanacheのACCOUNTS画面の上部に表示されているMNEMONICを入力します。（ちなみにGanacheの画面でMNEMONICを1行コピーし、そのままMetaMaskの1番目の入力欄にペーストすると、12フレーズ一気に入力できます。）
すると下記のようにアカウントと保持しているETHが表示されると思います。
![スクリーンショット 2022-04-18 7.32.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201287/7ca51232-db15-c3e8-050d-8071653bed21.png)
続いて、ローカル環境のGanacheを認識させるため、MetaMaskにネットワークの追加を行います。上記画面で、イーサリアムメインネットを押下し「ネットワークを追加」を選択します。また新しいブラウザタブが開くと思いますので、Ganacheの画面に表示されているNETWORK IDやRPC SERVERの値を適切に入力してください。
![スクリーンショット 2022-04-18 7.34.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201287/3e3a087a-cc8c-f128-2aaa-5b87f58fdd6f.png)
ネットワークが追加できたら、MetaMask拡張機能で追加したネットワークを選択し、Ganache画面に表示されているアカウントアドレスと、100ETHが保持されていることが表示されればOKです。
![スクリーンショット 2022-04-18 7.37.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201287/351a756e-f56b-da17-6eef-dd92d361ccbc.png)

ここまで完了すれば、ローカル環境で、本アプリケーションに対して実際の画面操作ができるようになります。

テストネット環境でテストするには、上記と同様に、Optimism Kovanのネットワーク情報を追加してください。
ただし、ETHは[ファセットサービス](https://faucet.paradigm.xyz/)から取得してください。
リンクのファセットサービスでは、1Twitterアカウントごと、1日1ETH受け取ることができます。

# バックエンドのソース説明(Solidity + truffle + Ganache)
バックエンドはスマートコントラクトそのものです。
スマートコントラクトの開発で最も注意すべきことは、一度デプロイされたスマートコントラクトは変更できないということです。
バグや機能拡張を行う前提であれば、[プロキシパターン](https://blog.openzeppelin.com/proxy-patterns/)などを参照してください。

### Solidity初めての方へ
初めてSolidityを触るという人は、まず[CryptoZombies](https://cryptozombies.io/jp/)を一通りチャレンジすることを強くお勧めします。ただし、CryptoZombiesで使われているSolidityソースはバージョンが低く、最新のSolidity(0.8系)ではコンパイルエラーが頻発しますので、そこは注意が必要です。 ~~CryptoZombies日本語サイトは文体がアレで、私は好きではありませんでした。そういう方は[英語版](https://cryptozombies.io/)をどうぞ~~

### バージョン情報
```
$ truffle version                                                                                                                        Truffle v5.5.0 (core: 5.5.0)
Ganache v7.0.1
Solidity - 0.8.11 (solc-js)
Node v17.4.0
Web3.js v1.5.3
```

## バックエンドの機能一覧
バックエンドは大きく5つの機能を持っています。
1. 起床時刻をセットする
1. 起床報告をする
1. 報酬を受け取る
1. ユーザ情報を取得する
1. コントラクトのETH残高を取得する

本スマートコントラクトは、ユーザのアカウント(ETHアドレス)ごとに下記の情報を保持するようにします。
そして、上記の機能が呼び出されたときにこれらの情報が更新されます。
```solidity
    struct User {
        uint wakeupTime;   //起床時刻
        uint wakeupTimeTo; //起床時刻+15分後
        uint lastUpdated;  //最終更新日時
        uint contDays;     //起床報告完了日数
        bool depositFlag;  //デポジット済みフラグ
        bool rewardFlag;   //報酬取得可能フラグ
    }
```

それでは各機能のソースを見ていきます。
https://github.com/sh0yu/wakeup-dapp/blob/master/contracts/wakeup.sol

### 起床時刻をセットする
```solidity
    function setWakeupTime(uint _wakeupTime) public payable {
        require(user[msg.sender].rewardFlag == false, "You must receive reward first");
        require(msg.value == DEPOSIT, "Initial Deposit invalid error");
        user[msg.sender] = User({
            lastUpdated: block.timestamp,
            wakeupTime: _wakeupTime,
            wakeupTimeTo: _wakeupTime + INVALID_WAKUP_TIME,
            contDays: 0,
            depositFlag: true,
            rewardFlag: false
        });
    }
```
solidityでは`msg.sender`でトランザクションを送信してきたユーザのアカウントが取得できます。
また、`msg.value`でトランザクションに付与されたETHの値を取得できます。
ユーザがこの関数を呼び出すと、引数として渡されてきた起床時刻と、その他初期状態のデータを詰めたUser構造体を作成し、userマッピングに追加します。
ポイントとしては、 __この関数をpayableとして定義することで、ユーザからETHを受け取ることのできる関数として定義すること__ です。そして、require文の中で、送信されたETHが指定の値(0.01ETH)であることを確認し、ETHを受け取ります。

### 起床報告をする
```solidity
    function incrementContDays() public {
        require(user[msg.sender].depositFlag, "You have to deposit first");
        require(user[msg.sender].wakeupTime <= block.timestamp, "You can still sleep");
        require(user[msg.sender].wakeupTimeTo >= block.timestamp, "You overslept... restart routine");
        user[msg.sender].lastUpdated = block.timestamp;
        if (user[msg.sender].contDays == (CONT_DAYS_MAX-1)) {
            user[msg.sender].wakeupTime = 0;
            user[msg.sender].wakeupTimeTo = 0;
            user[msg.sender].depositFlag = false;
            user[msg.sender].rewardFlag = true;
            user[msg.sender].contDays = 0;
        } else {
            user[msg.sender].wakeupTime = user[msg.sender].wakeupTime + NEXT_INVALID_TIME;
            user[msg.sender].wakeupTimeTo = user[msg.sender].wakeupTimeTo + NEXT_INVALID_TIME;
            user[msg.sender].contDays++;
        }
    }
```
毎朝ユーザは起床報告を行い、この機能を呼び出します。
2,3番目のrequire文によって、ユーザが起床報告を行った時刻に対して、最初にセットされた起床時刻から15分以内に行われた場合のみ、このトランザクションは成功するように制限します。

* 1日目から4日目までは、`contDays` をインクリメントし、起床時刻を次の日の同じ時刻に更新します。
* 5日目に起床報告が完了した場合は、報酬を受け取る権利を表すために、`rewardFlag`を`true`にセットし、その他のデータは初期化して更新します。

__ユーザが寝坊した場合、そのユーザはこの関数を成功させることができなくなるため、`rewardFlag`が`true`になることはなく、デポジットは回収できず報酬は永遠に受け取れません。__ 起床時刻を再度セットして再チャレンジするしかなくなります。

### 報酬を受け取る
```solidity
    function receiveReward() public {
        require(user[msg.sender].rewardFlag, "You have to keep waking up for 5days");
        user[msg.sender].rewardFlag = false;
        (bool success,) = payable(msg.sender).call{value: DEPOSIT + REWARD}("");
        require(success, "We couldn't pay reward");
    }
```
初めのrequire文で`rewardFlag`が`true`の場合のみ実行できるようにします。
msg.senderに向けて、初めに受け取ったデポジットと報酬を送信します。
このときセキュリティ上、この処理をする前に`rewardFlag`を`false`にする必要があります。

### ユーザ情報を取得する
```solidity
    function getUserInfo (address _user) public view returns (uint, uint, uint, uint, bool, bool) {
        return (user[_user].wakeupTime, user[_user].wakeupTimeTo, user[_user].lastUpdated, user[_user].contDays, user[_user].depositFlag, user[_user].rewardFlag);
    }
```
この関数はview関数として定義します。これはユーザの情報を返却するだけで、それらのデータに対する更新が発生しないためです。
__view関数を実行する場合、ガスは必要ありません。__ （無料で呼び出せる）
solidityでは関数の返り値に複数の値をセットすることができます。

### コントラクトのETH残高を取得する
```solidity
    function getBalance() public view returns (uint) {
        return address(this).balance;
    }
```
このコントラクトの残高を返却します。こちらもview関数です。
ユーザは、早起きチャレンジ成功時の報酬が支払われるだけの残高がコントラクトにあるか確認できます。

### コントラクト残高引き出し（隠し機能）
```
    function withdraw() external {
        require(msg.sender == owner, "You can't withdraw deposit");
        require(block.timestamp >= 1672531200, "You can't withdraw before 2023/1/1");
        payable(owner).transfer(address(this).balance);
    }
```
この関数は、本来あるべきではないかもしれません。なぜなら、コントラクトの残高を引き出すことができるからです。
ただし、引き出せるのは、コントラクトをデプロイしたアカウントのみであり、2023年1月1日以降しか実行できません。
このアプリケーションが廃れたとき、コントラクトに残された残高を引き出す方法がないと、ETHが闇に消えてしまうので実装しました。

## コントラクトのビルドとデプロイ
ビルドは `truffle compile`、デプロイは`truffle migrate`で実行できます。
ローカルにデプロイする際は、Ganacheが起動していることと、Truffleの設定ファイルに接続情報が記載されていることを確認してください。
https://github.com/sh0yu/wakeup-dapp/blob/master/truffle-config.js
テストネット・メインネットにデプロイする際は、`truffle migrate --network=optimism_kovan`などと、ネットワークを指定することでデプロイ先を切り替えることができます。

__ネットワークにスマートコントラクトをデプロイすると、そのコントラクトにコントラクトアドレスが割り振られます。
このアドレスは、次のフロントエンドのソースで必要になるため保存しておいてください。__
コントラクトアドレスは、デプロイ実行時の出力の中間あたりに`contract address`として出力されています。
```console:デプロイ実行時のコンソール出力
$ truffle migrate

Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.



Starting migrations...
======================
> Network name:    'development'
> Network id:      5777
> Block gas limit: 6721975 (0x6691b7)


1_migration.js
==============

   Replacing 'Wakeup'
   ------------------
   ⠋ Blocks: 0            Seconds: 0   > transaction hash:    0x2ff6f803c43dfc0a936d3ff6498c1c34ece66d3c342b9cf5da7239752f0595e0
   > Blocks: 0            Seconds: 0
   > contract address:    0xB595Ba1C880955128D4b4494Dab8af5e499E6308
   > block number:        1042
   > block timestamp:     1650155844
   > account:             0x6b1C4774bB91b46D48FC2ab03Eb219be4B6FE344
   > balance:             62.18764994
   > gas used:            1397811 (0x155433)
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.02795622 ETH

   > Saving artifacts
   -------------------------------------
   > Total cost:          0.02795622 ETH

Summary
=======
> Total deployments:   1
> Final cost:          0.02795622 ETH
```

# フロントエンドのソース説明(Vue.js + Web3 + MetaMask)
正直フロントエンドは詳しくないので、なんとか動く状態になっているものと思って読んでください。
Dappにおいては仮想通貨のやり取りが発生します(ブロックチェーン上のデータを操作するトランザクションを実行するためにも少量のETHが必要)。その制御が面倒です。
フロントエンドのWeb3からMetaMaskを呼び出して、そのユーザのETHアカウント情報を利用してスマートコントラクトを呼び出すような形式になっています。

## ABIとコントラクトアドレスの設定
ABI(Application Binary Interface)は、フロントエンド(のweb3)がスマートコントラクトとやり取りをするためのインターフェイスが定義されたものです。TruffleによるSolidityのビルド成果物として出力されます。
↓Solidity公式説明
https://docs.soliditylang.org/en/latest/abi-spec.html
↓日本語説明
https://y-nakajo.hatenablog.com/entry/2019/05/14/182854

ABIとコントラクトアドレスは下記のように指定して、コントラクトにトランザクションを投げることができます。
```javascript:sample.js
import text from "raw-loader!../../public/Wakeup.txt"
const Web3 = require("web3")
const wakeupABI = JSON.parse(text).abi
const wakeupAddress = "0xB595Ba1C880955128D4b4494Dab8af5e499E6308"
var web3 = new Web3(Web3.givenProvider || "ws://localhost:7545")
var wakeupContract = new web3.eth.Contract(wakeupABI, wakeupAddress)
wakeupContract.methods.incrementContDays().send({ from: userAccount })
```
Truffleで生成されたABIは.txtに変換して読み込んで利用しましたが、もう少しうまいやり方はありそうです。

## MetaMaskアカウント接続設定
MetaMaskの初期設定として、web3とmetamaskのdetect-providerを用意します。

```javascript:TopPage.vue(初期設定)
import detectEthereumProvider from '@metamask/detect-provider'
const Web3 = require("web3")
var web3
var metamask
var wakeupContract
var userAccount
```
そして、画面起動時にMetaMaskの接続を要求します。
```javascript:TopPage.vue(画面起動時)
        async startApp() {
            if (metamask !== window.ethereum) {
                console.error('Do you have multiple wallets installed?')
            }
            web3 = new Web3(Web3.givenProvider || "ws://localhost:7545")
            wakeupContract = new web3.eth.Contract(cryptoZombiesABI, cryptoZombiesAddress)
            metamask.request({ method: 'eth_requestAccounts' })
            .then(this.handleUserAccountChanged)
            .catch((error)=>{
                if(error.code === 4001) {
                    console.log('Please connect to MetaMask.')
                }
                console.log(error)
            })
            metamask.on('accountsChanged', this.handleUserAccountChanged)
        },
        handleUserAccountChanged(accounts) {
            console.log("accounts changed detected: " + accounts[0])
            userAccount = accounts[0]
            this.displayContDays()
        },
```
すると、下記の様に、画面を開いたときにブラウザにインストールされたMetaMaskの拡張機能が起動し、アカウントの接続画面が開きます。
![スクリーンショット 2022-04-17 10.39.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201287/826d174d-fc88-4ba0-2760-27b056bbe0be.png)
ここで、アカウントを選択して接続することで、そのアカウントのETHを使ってスマートコントラクトとやり取りをすることができる様になります。
(上記のSSにあるアカウントは全てローカル環境に立てたGanacheが払い出したアカウントです。)

## 画面からコントラクト呼び出し
トップ画面では起床時刻をセットするためのボタンが配置されており、下部ではユーザの情報・コントラクトのETH残高を表示しています。
図中の赤字を見てください。
![スクリーンショット 2022-04-19 15.39.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201287/442a6ec7-3cbf-bf82-26e9-3a77079ea351.png)

フロントエンドのソースについては起床時刻をセットするボタンを押したときの挙動と、トップページのユーザ情報の取得の2種類のみ説明します。スマートコントラクトの呼び出し方が理解できれば残りも同じことですので。

### 起床時刻をセットする(フロントエンド)
起床時刻をセットするボタンを押下すると、早起きチャレンジの注意事項が書かれたモーダルが表示され、さらに同意するボタンを押下するとMetaMaskの拡張機能が起動します。右下の「確認」を押すと、スマートコントラクトに向けてトランザクションが発行されます。
![スクリーンショット 2022-04-19 15.50.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201287/1ead8585-2f92-a004-a083-8941001c5784.png)
ソースとしては下記になります。
`.send({ from: userAccount, value: web3.utils.toWei("0.01", "ether")})`で0.01ETHを送ることを指定しており、それがMetaMaskの中央付近に表示されている「コントラクトインタラクション」◆0.01と対応しています。
```javascript:TopPage.vue
        setWakeupTime() {
            this.setWakeupLoading = true
            wakeupContract.methods.setWakeupTime(this.dateToEpoch)
            .send({ from: userAccount, value: web3.utils.toWei("0.01", "ether")})
            .then((result)=>{
                this.setWakeupTimeSuccessAlert = true
                console.log(result)
            })
            .catch((error)=>{
                this.setWakeupTimeFailedAlert = true
                console.log(error)
            })
            .finally(()=>{
                this.displayContDays()
                this.setWakeupLoading = false
            })
        },
```

### ユーザ情報を取得する(フロントエンド)
ユーザ情報を取得する部分の呼び出しは下記です。
```javascript:TopPage.vue
        displayUserInfo(){
            this.retry(this.getUserInfo, 5)
            .then((userInfo)=>{
                this.wakeupTime = new Date(userInfo[0]*1000)
                this.wakeupTimeTo = new Date(userInfo[1]*1000)
                this.lastUpdated = new Date(userInfo[2]*1000)
                this.contDays = userInfo[3]
                this.depositFlag = userInfo[4]
                this.rewardFlag = userInfo[5]
                if(userInfo[0] != 0){
                    this.dateInput = new Date(this.wakeupTime)
                }
            })
        },
        getUserInfo() {
            return wakeupContract.methods.getUserInfo(userAccount).call()
        },
```
ポイントとなるのは`getUserInfo()`の中で`.call()`を使ってコントラクトのメソッドを呼び出しているところです。
__コントラクトの状態を変更する呼び出しは`.send({from:xxxx, ...})`、変更しない呼び出しは`.call()`を使います。__
Solidityのview関数は状態を変更しないものなので、`.call()`で呼び出します。
web3の仕様は[こちらを参照](https://web3js.readthedocs.io/en/v1.2.11/web3-eth-contract.html#methods-mymethod-call)

## フロントエンドのビルドとデプロイ
Vueのビルドの説明は他の記事に任せるとして、デプロイ先はAWS S3にし、S3の静的ホスティング機能を利用しました。さらに、Route53でS3バケットにルーティングしました。
詳しい方法は下記を参照ください。
https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/website-hosting-custom-domain-walkthrough.html
https://dev.classmethod.jp/articles/cloudfront-s3-customdomain/

# 運用系
スマートコントラクトをデプロイすると、下記のようにトランザクションハッシュやコントラクトアドレスが返されます。
```console
   Replacing 'Wakeup'
   ------------------
   ⠋ Blocks: 0            Seconds: 0   > transaction hash:    0xb8d94a918762c079232f1530ae9237f4a0442c5c9b7df9c3677a99b1de12515a
   ⠴ Blocks: 0            Seconds: 0undefined
   ⠼ Blocks: -1           Seconds: 5undefined
   > Blocks: 0            Seconds: 9
   > contract address:    0x23fA2A91bD94d46eEf4E1aB69B09a20B16032978
   > block number:        2086234
   > block timestamp:     1649990528
   > account:             0x400f16c73A467E8Eedd24354cB561d37425dda32
   > balance:             0.650999942156296948
   > gas used:            1399111 (0x155947)
   > gas price:           0.00001 gwei
   > value sent:          0 ETH
   > total cost:          0.00000001399111 ETH

   > Saving artifacts
   -------------------------------------
   > Total cost:     0.00000001399111 ETH

Summary
=======
> Total deployments:   1
> Final cost:          0.00000001399111 ETH
```
この値を[このサイト](https://kovan-optimistic.etherscan.io/)に入力すると、トランザクションが正常に処理されたか確認できます。
Optimism Mainnetのサイトは[こちら](https://optimistic.etherscan.io/)
![スクリーンショット 2022-04-22 6.17.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201287/1bdf6d7b-4042-ebdb-8135-65684925fc88.png)

__Truffleでデプロイを実行したとき、タイムアウトしてデプロイに失敗したように見えましたが、トランザクションハッシュで検索すると、コントラクトは正常にデプロイされていた、ということがありました。__ ですので、トランザクションの結果は上記のサイトで確認するようにすると良いと思います。

では、フロント画面からトランザクションを操作したとき、これらの画面がどうなるか見てみましょう。
起床時刻を現在時刻でセットし、すぐ目覚めたボタンを押してみました。
Optimismの画面でコントラクトアドレスを入力して検索してみます。すると、このコントラクトに対するトランザクションがリストされます。
![スクリーンショット 2022-04-23 9.36.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201287/4351a449-294c-7760-50ab-0e82a6ffefd5.png)
トランザクションは新しい順に上から並べてあり、従って一番下はコントラクトのデプロイトランザクションとなっています。

それでは、画面の操作に対するトランザクションを確認してみます。
上から2番目のトランザクションは起床時刻をセットしたトランザクションで、Valueが0.01Etherとなっており、__0.01Etherのデポジットを支払っている__ ことがわかります。
そして、一番上のトランザクションは、目覚めたボタンを押したときのトランザクションです。Valueは0Etherになっており、TxnFeeのみ発生していることがわかります。
各トランザクションハッシュをクリックすると、そのトランザクションの詳細にリンクします。

ここでポイントになるのは、__コントラクトのView関数を呼び出しても、この画面には表示されない__ ことです。View関数はコントラクトの状態を変えない関数であり、トランザクションフィーも発生しませんし、こういったトランザクション履歴にも表示されません。

# さいごに
長々と説明してきましたがいかがでしたでしょうか。
いろいろなエコシステムが充実してきていますが、初めての人には少し高いハードルが、、、
一度この世界に入ってしまえば、うん、どうだろう、、、

今回はL2を触ってみたい・ガス代を削減したいというのもありOptimismを使いましたが、素直にEthereumメインネットを使えば、ETHの準備やMetaMaskの設定の手間は省けると思います。
しかし、Ethereumのガス代は結構変動します。[Ethereumガス代推移](https://public-grafana.optimism.io/d/9hkhMxn7z/public-dashboard?orgId=1&refresh=5s&var-network=mainnet&var-prometheus_replica=prometheus-k8s-0&viewPanel=7&from=now-7d&to=now)
こういったガス代の変動による不確定要素は、ビジネス上、若干のリスクになると思います。

ということで、長文記事になりましたが、Dappを作ってみたい方の参考になれば幸いです。
コメントや指摘もお待ちしております。

※本アプリケーションにおいて、バグや接続不良など、いかなる理由による損失についても責任は負いません。