FlutterでERC20トークンを発行しMetamaskに送る方法

今回の記事では、Solidity & Flutterを使って、Dappsを作る方法を解説します。

アプリの内容としては、「アプリ→メタマスクに接続→口座にトークンを発行する」というFaucetアプリを作るというものになっています。
画像ではこんな感じ。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/d2d56190-8bb6-cc9a-92f2-4de2c1a4e724.png" width=33%><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/a3592a0d-2dee-4da4-2155-3d49e99b7824.png" width=33%><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/6c46c90c-28ee-e603-075a-b34e7581c34f.png" width=33%>


トークンの発行、送付、メタマスクとの接続を学べるので、この記事を通してDappsの基本を学べると思います。

では、さっそく作っていきましょう。

あと、英語の記事も書いたので、よかったら見てください！
リンク）

## Dappsとは

Dappsについて知らない人向けに、簡単にDappsについて説明させてください。
Dappsとは、スマートコントラクト（プログラム）を、ブロックチェーンに組み込むことで、

- ブロックチェーン上にあるので　→　改ざんができない
- 分散して保存されているので　　→　破壊できない
- 中央管理者がいないので　　　　→　誰もシステムを止められない

といった特徴を持つアプリのことです。

これらの特徴から、Dappsは、世界に新しい価値を生み出すのではないかと期待されています。

詳しく知りたい方は、公式サイトからどうぞ。

https://ethereum.org/ja/




## メタマスク
### メタマスクとは

仮想通貨やNFT、トークンなどを管理できる財布のひとつです。

今回はこのメタマスクを、
- スマートコントラクトのガス代の支払い
- トークンの受け取り・管理

に使います。




### メタマスクをインストールする

https://metamask.io/download/

まずは、上記のリンクからメタマスクの拡張機能をブラウザにインストール
そして、口座を作ってください。

注意：シークレットキー、リカバリーフレーズは公開しないようにしましょう。漏れると口座が乗っ取られる危険性があります。




また、スマートコントラクトを、ネットワークに展開＆使用するには、ガス代を払う必要があります。

ブロックチェーンのネットワークには多くの種類があります。
メインネットではETH（リアルマネー）を使います。
ただ、今回のアプリでは、Goerliテストネットを使用します。テストネットでは、ガス代に必要な通貨を無料で受け取ることができます。

### テストネットのトークンをもらう

まずは、ガス代に使うテストネット用の通貨を、無料でゲットしましょう。




そのために、メタマスクのネットワークを、Goerliテストネットワークに設定する必要があります。

しかし、最初はテストネットが表示されていないので、設定を変更しましょう。

メタマスクで、
→ネットワークを追加
→高度な設定
→テストネットワークを表示をオン。

これでテストネットが表示されます。
![MetaMask (5).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/64211c6f-737b-bd0f-fb24-25c9f7837b6b.png)
![MetaMask (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/043a4764-0041-f4e0-8491-b666d41a7a2f.png)
![MetaMask (4).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/d4fd9d5b-8d0d-ae7a-4d09-7d39d1d56336.png)
その後、Goerliテストネットワークを選択しておきましょう。
![MetaMask (5).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/96cbdafc-5c90-4916-40ff-396ad55be56f.png)



https://goerlifaucet.com/

設定が終わったら、上のリンクからGoerli ETHをゲットしましょう。
→alchemyのアカウントを作る
→メタマスクのアドレスを入力
→0.2goerli ETHを無料でゲット！
![Goerli-Faucet.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/7161b86c-4ac9-d9e7-1e37-a8ee6b6d73fd.png)
faucetは蛇口という意味で、今回作るアプリもこのサイトの機能と同じです。
→アドレスが入力されたら、その口座にトークンを配布するという仕組み。




## スマートコントラクト (Solidity)

次に、Solidityを使用して、スマートコントラクトを作成しましょう。

### remixでスマートコントラクトを書く

今回は、オンラインエディタであるremixを使用します。

https://remix.ethereum.org/

上のリンクからremixを開いてください。




新しいワークスペースを作成します。
![Remix-Ethereum-IDE (7).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/bd85ca4e-639b-6f03-a6ea-8df9e9d4c0c5.png)
テンプレートはなんでも良いですが、今回はERC20を使用し、名前は、FaucetTokenとしておきます。
![Remix-Ethereum-IDE (8).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/8aa51773-335a-156f-e853-0ba68c71c015.png)
FaucetTestToken.sol ファイルを作ります。
![Remix-Ethereum-IDE (9).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/874c237e-fd0b-79f4-3e51-02a49b9c449d.png)
コードを以下のように書き換えます。
```solidity:FaucetTestToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract FaucetTestToken is ERC20 {
    constructor() ERC20("FaucetTestToken", "FTT") {}

    function faucet (address recipient , uint amount) external {
      _mint(recipient, amount * (10 ** decimals()));
    }
}
```



今回は、トークンを発行するのにOpenZeppelinというライブラリを使っています。

https://docs.openzeppelin.com/contracts/4.x/erc20



faucet関数では、受取人のアドレスにトークンを送る(mint)という処理を行っています。




また今回は、Solidityの基礎部分のコードについて、説明は省略させていただきます。
わからない部分があったら、以下のサイトでSolidityの基礎を学べます。(どちらも日本語に対応)

公式サイト
https://ethereum.org/en/developers/learning-tools/
CryptZombies
https://cryptozombies.io/jp/course




特にCryptZombiesはおすすめです。
チャプター1-3（solidityの高度なコンセプト）まで終わらせれば、今回のコードは理解できると思います。



### コンパイル
次に、コードをコンパイルします。
![Remix-Ethereum-IDE (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/aea77245-9195-5215-1391-2656fbb1457c.png)
このとき、コンパイルした際に生成されるABIファイルは、Flutterでフロントエンドを作るときに必要になります。
![Remix-Ethereum-IDE (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/87e107b3-b9d1-acc6-2436-ab8cedcc788f.png)

### デプロイ
最後に、スマートコントラクトをデプロイします。
このとき、Environment → Injected Provider (Goerli network)にしてからデプロイしましょう。
![Remix-Ethereum-IDE (4).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/c25575ca-bf19-a7fd-b94b-49f15f7be153.png)
少し時間が立ったあと、デプロイに成功していることを確認したら、実際に試してみましょう。
![Remix-Ethereum-IDE (6).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/bcfacf39-d0ca-e68f-277e-41ab909eea10.png)
faucet関数に、あなたのメタマスクのアドレス＆振り込みたいトークンの数を入力し、transactを押します。
処理が終わった後、metamaskのActivityより、faucetの取引が確認できればスマートコントラクトを実行できています。
![MetaMask.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/784cd63e-f71d-97cc-e4f0-638a9676d943.png)
その際、コントラクトアドレスもコピーしておきます。
![MetaMask (6).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/2f9da42a-db68-4e10-65b5-0957c6661d87.png)
![MetaMask (7).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/e9ec357c-3dce-49bd-e6a0-38a18f04cccb.png)
assetsに戻り、import token → token contract addressに、先ほどコピーしたコントラクトアドレスを入力します。
そして、Add custom tokenで、自分の保有しているトークンの量を見ることができます。
![MetaMask (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/09da179b-5323-aac3-96ba-4fcb53396385.png)
![MetaMask (8).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/83e1e47b-4c4b-66fd-eee6-514cdf8e065d.png)
![MetaMask (9).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/3cac6657-80a6-ca66-b89f-009dadd09978.png)
スマートコントラクトが正常に動作していることがわかったので、これからはflutterでフロントエンドを作っていきましょう。

その前に、dappのリクエストを処理＆スマートコントラクトとやり取りするためのAPIを設定します。




## infura

今回はエンドポイントに、infuraを使用します。

https://infura.io/

上のリンクより、アカウントを作成したら、CREATE NEW KEYで新たなプロジェクトを作成。
ENDPOINTSをGoerliに設定します。


このURLはFlutterでプロジェクトを作るときに使用します。




## Flutter

最後に、Flutterプロジェクトを作っていきます。

完成品は、以下のgithubのリンクからダウンロードできますので、参照しながら記事をご覧ください。

https://github.com/KeiIT11/test_faucet_token/

それぞれのコードについて、簡単に解説していきます。

### ライブラリ
まずは、pubspec.yamlに依存関係を追加します。
```dart:pubspec.yaml
dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2

  web3dart: ^2.3.3
  http: ^0.13.4

  walletconnect_dart: ^0.0.11
  url_launcher: ^6.1.8
```

web3dart
→トランザクションを送信したり、スマートコントラクトとやり取りするのに必要。

http
→Dappsとmetamaskの接続を確立するために必要。

walletconnect_dart
→アプリからmetamaskに接続するのに必要。

url_launcher
→urlからmetamaskを起動する際に必要。

### abiファイル

デプロイされたスマートコントラクトと、アプリがやり取りするためには、abiファイルが必要です。

先ほど、コンパイルしたときに取得したabiファイルをコピーします。
![Remix-Ethereum-IDE (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/6f6d927d-bf43-158b-951f-5f4e25f506c0.png)


flutterプロジェクトにassetsフォルダを作り、その中にcontract.jsonを作成し、abiファイルをペーストします。
これで、abiファイルの設定は完了です。

ここからは、homepage.dartの内容について説明していきます。




### walletconnect_dart

walletconnect_dartの機能を使って、アプリとmetamaskを接続することができます。

挙動としては、
Dapp→　bridgeサーバー　→　wallet
という形で、bridgeサーバーを通して、metamask等のwalletとの通信を行っています。

コードの解説をします。homepage.dartの68行目から
```dart:homepage.dart
  final connector = WalletConnect(
    bridge: 'https://bridge.walletconnect.org',
    clientMeta: const PeerMeta(
      name: 'WalletConnect',
      description: 'WalletConnect Developer App',
      url: 'https://walletconnect.org',
      icons: [
        'https://gblobscdn.gitbook.com/spaces%2F-LJJeCjcLrr53DcT1Ml7%2Favatar.png?alt=media'
      ],
    ),
  );
```

connectorを作り、Dappとbridgeサーバーの接続を行っています。
公式を参照：
https://github.com/RootSoft/walletconnect-dart-sdk/tree/58581538e321537d42cd1cdb3d92cc6a1cfcf75a

homepage.dartの80行目から
```dart:homepage.dart
var _session, session, _uri;
connectMetamaskWallet(BuildContext context) async {
  if (!connector.connected) {
    try {
      session = await connector.createSession(
          chainId: 5,
          onDisplayUri: (uri) async {
            _uri = uri;
            await launchUrlString(uri, mode: LaunchMode.externalApplication);
          });
      print(session.accounts[0]);
      setState(() {
        _session = session;
      });
    } catch (exp) {
      print(exp);
    }
  }
}
```
connectorを通して、ブロックチェーンネットワーク(GoerliネットワークのchainIdは5)とのsessionを確立しています。

### web3dart

web3dartでは、トランザクションを送信したり、スマートコントラクトとやり取りしたりすることができます。

web3dartの使い方について解説していきます。

まずは、サーバーに対して、１度だけのやり取りだけでなく、複数回のやり取りを行うために、Clientを作成します。
Clientを使用することで、サーバーに対して、永続的な接続を開いたままにすることができます。

公式を参照：
https://github.com/xclud/web3dart

homepage.dartの101行目より、
```dart:homepage.dart
late Client httpClient;
late Web3Client ethClient;
//change this to your Infura goerli endpoints
final String blockchainUrl =
    "https://goerli.infura.io/v3/0170d757246f418f999960b7f36484f1";

var tokenBalance;

@override
void initState() {
  httpClient = Client();
  ethClient = Web3Client(blockchainUrl, httpClient);
  super.initState();
}
```

このとき、blockchainUrlは、infuraで取得した、goerliネットワークのENDPOINTSを使用します。

（tokenBalanceは、ユーザーのERC20トークンを持っている数を保存する変数です。）

次に、スマートコントラクトを使用するために、getContract関数を用意します。

116行目より、
```dart:homepage.dart
Future<DeployedContract> getContract() async {
  //change this to your abi file.
  String abiFile = await rootBundle.loadString("assets/contract.json");
  //change this to your ContractAddress
  String contractAddress = "0xF49CE5D5f85A9f1f7eDe1a4150c86Db9D97b337F";
  final contract = DeployedContract(
      ContractAbi.fromJson(abiFile, "FaucetTestToken"),
      EthereumAddress.fromHex(contractAddress));

  return contract;
}
```
abiファイルを用いて、スマートコントラクトの内容をアプリ側から理解することができます。
contractAddressは、metamaskから取得した、ご自身のものに書き換えてください。

次に、スマートコントラクト内の関数を使用するために、callFunction関数を作成します。
128行目より、
```dart:homepage.dart
  Future<List<dynamic>> callFunction(String name, List<dynamic> args) async {
    final contract = await getContract();
    final function = contract.function(name);
    final result = await ethClient.call(
        contract: contract, function: function, params: args);
    return result;
  }
```
nameは呼び出すスマートコントラクトの関数の名前で、paramsは引数を指定しています。

次は、実際にスマートコントラクト内の関数を呼び出す関数を作成します。

136行目より、
```dart:homepage.dart
Future<void> getTokenBalance(String account) async {
  final userAddress = EthereumAddress.fromHex(account);
  List<dynamic> results = await callFunction("balanceOf", [userAddress]);
  setState(() {
    tokenBalance = (results[0] ~/ BigInt.from(pow(10, 18))).toInt();
  });
}
```
利用者のアドレスのwalletに存在するFTTトークンの数を返す関数(getTokenBalance)を作成しました。

今回書いたスマートコントラクトでは、openzeppelinを使用しているので、ERC20に備わっている、balanceOfという標準関数を使用しています。

また、スマートコントラクトから帰ってくる変数が、10の18乗と大きいので、扱いやすいようにBigInt型で、小数点切り捨ての割り算をしてからInt型に戻しています。




また、162行目より、
```dart:homepage.dart
Future<void> faucet(String account) async {
  snackBar(label: "move to metamask and verify.");
  final userAddress = EthereumAddress.fromHex(account);

  //obtain our contract from abi in json file
  final contract = await getContract();
  // extract function from json file
  final function = contract.function("faucet");
  //get secret key from metamask
  EthereumWalletConnectProvider provider =
      EthereumWalletConnectProvider(connector);
  //obtain private key for write operation
  final credentials = WalletConnectEthereumCredentials(provider: provider);
  //send transaction using the our private key, function and contract
  await ethClient.sendTransaction(
      credentials,
      Transaction.callContract(
          from: userAddress,
          contract: contract,
          function: function,
          parameters: [userAddress, BigInt.from(1)]),
      chainId: 5);
  ScaffoldMessenger.of(context).removeCurrentSnackBar();
  snackBar(label: "verifying transaction");
  //set a 20 seconds delay to allow the transaction to be verified before trying to retrieve the balance
  Future.delayed(const Duration(seconds: 20), () {
    ScaffoldMessenger.of(context).removeCurrentSnackBar();
    snackBar(label: "retrieving transaction");

    getTokenBalance(account);

    ScaffoldMessenger.of(context).clearSnackBars();
  });
}
```
利用者のアドレスのwalletに、FTTトークンを１つ送る関数(faucet)を作成しました。

スマートコントラクトを使用するときには、ガス代を支払う必要があります。
そのときに、秘密鍵を入力する必要があるのですが、アプリ側で秘密鍵を保持するのは危険です。

なので、metamaskから秘密鍵を取得することで、アプリ内に保持しないDappsを作ることが可能です。

credentialsに秘密鍵を保存しているのですが、それをメタマスクから貰うために、20行目からのWalletConnectEthereumCredentialsクラスが必要になります。

また、スマートコントラクトを呼び出す際に、トークンを送る個数を、引数のparametersから、１つに指定しています。

以上で、Flutterについての簡単な説明を終わりたいと思います。

## 使用方法
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/473fcdde-f05f-e8a4-fc8f-ec289aacd7b2.png" width=33%><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/4c5cc7e2-27fc-daaa-99dc-bae23e15d4cb.png" width=33%><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/3125396e-431e-dcd7-086a-0cb00365cdd0.png" width=33%>
テストをする際には実機を使うので、あらかじめ、アプリ版メタマスクをインストールしておいてください。
"Connect wallet"を押します。
メタマスクに移動するので、接続を承認します。すると、アプリに自動的に戻り、メタマスクが接続されます。
あなたのメタマスクのアドレス、chainID、持っているFTTトークンの数が表示されます。
FTTトークンを持っていても、最初は0と表示されます。仕様です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/8962c437-3042-9231-9aa4-0e313e9bf1a6.png" width=33%>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/d8f0333c-07fe-54fe-f0fb-e66f6997eae3.png" width=33%><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/edccf9c8-50ad-b5d6-40fb-3a34bc1507ae.png" width=33%>

Reloadを押すと、持っているFTTトークンの数が更新されます。
Get a Token!を押したあと、メタマスクに手動で移動し、トランザクションを承認します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/ff9c09b4-72e4-6675-0e47-b825b80bc558.png" width=33%><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/24c4a978-b517-f307-b22d-8b4c420382c4.png" width=33%><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1241644/08bcc0ad-abdb-ec6d-0056-6a3db26ad497.png" width=33%>

その後、アプリに手動で戻リます。時間が経って、処理が完了すると、トークンの数が更新されています。
アプリからトークンを発行して、ウォレットに送付することができました！！！

## 参照

https://medium.com/geekculture/simple-dapp-using-flutter-and-solidity-b64f5267acf4

https://mirror.xyz/0x5C40d264716c12755b1C1ea3395518fFe4bc76ed/HqugiLl1mX9-PtUXL5IrgZeADBEA1wrurlO8jWMPzuw

## 改善点
シミュレータでアプリを動かす方法はわかりませんでした。メタマスクとかのアプリをシミュレータにインストールできるんでしょうか？

あとは、metamaskを接続した直後に、保有しているトークン数を更新する方法がわかりませんでした。なのでreloadボタンを採用しています。
非同期処理で、口座にあるトークンの数を受け取ったあと、画面に反映するのができていないと思うので、多分自分のflutterへの理解が足りていないことが原因です。

Get a Tokenボタンを押したあと、メタマスクに自動に行き、自動でアプリに戻ってくる処理を実装できませんでした。メタマスクを接続するときはできているので、これも実装できると思うのですが、自分の技術力不足です。。。

今後、調べて改善したいと思います。

## まとめ
長い記事なのに読んでくださり、ありがとうございました。

今回、初めてqiitaに記事を書かさせていただきました。
アプリからメタマスクに接続してスマートコントラクトを使用する方法は、日本だけでなく、英語の記事でも情報が少なく、情報を得るのに苦労しました。
本当に情報がないので、後から作る人の助けになればと記事を書かさせていただきました。
読みにくかったり、自分の技術力不足があるとは思いますが、参考になっていたら幸いです。

~~ちかれた。
参考になってたら嬉しいです。ハートください💕~~