LoginSignup
4
0

More than 1 year has passed since last update.

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

Last updated at Posted at 2023-02-05

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

アプリの内容としては、「アプリ→メタマスクに接続→口座にトークンを発行する」というFaucetアプリを作るというものになっています。
画像ではこんな感じ。

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

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

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

Dappsとは

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

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

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

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

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

メタマスク

メタマスクとは

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

今回はこのメタマスクを、

  • スマートコントラクトのガス代の支払い
  • トークンの受け取り・管理

に使います。

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

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

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

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

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

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

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

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

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

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

これでテストネットが表示されます。
MetaMask (5).png
MetaMask (3).png
MetaMask (4).png
その後、Goerliテストネットワークを選択しておきましょう。
MetaMask (5).png

設定が終わったら、上のリンクからGoerli ETHをゲットしましょう。
→alchemyのアカウントを作る
→メタマスクのアドレスを入力
→0.2goerli ETHを無料でゲット!
Goerli-Faucet.png
faucetは蛇口という意味で、今回作るアプリもこのサイトの機能と同じです。
→アドレスが入力されたら、その口座にトークンを配布するという仕組み。

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

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

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

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

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

新しいワークスペースを作成します。
Remix-Ethereum-IDE (7).png
テンプレートはなんでも良いですが、今回はERC20を使用し、名前は、FaucetTokenとしておきます。
Remix-Ethereum-IDE (8).png
FaucetTestToken.sol ファイルを作ります。
Remix-Ethereum-IDE (9).png
コードを以下のように書き換えます。

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というライブラリを使っています。

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
このとき、コンパイルした際に生成されるABIファイルは、Flutterでフロントエンドを作るときに必要になります。
Remix-Ethereum-IDE (3).png

デプロイ

最後に、スマートコントラクトをデプロイします。
このとき、Environment → Injected Provider (Goerli network)にしてからデプロイしましょう。
Remix-Ethereum-IDE (4).png
少し時間が立ったあと、デプロイに成功していることを確認したら、実際に試してみましょう。
Remix-Ethereum-IDE (6).png
faucet関数に、あなたのメタマスクのアドレス&振り込みたいトークンの数を入力し、transactを押します。
処理が終わった後、metamaskのActivityより、faucetの取引が確認できればスマートコントラクトを実行できています。
MetaMask.png
その際、コントラクトアドレスもコピーしておきます。
MetaMask (6).png
MetaMask (7).png
assetsに戻り、import token → token contract addressに、先ほどコピーしたコントラクトアドレスを入力します。
そして、Add custom tokenで、自分の保有しているトークンの量を見ることができます。
MetaMask (1).png
MetaMask (8).png
MetaMask (9).png
スマートコントラクトが正常に動作していることがわかったので、これからはflutterでフロントエンドを作っていきましょう。

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

infura

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

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

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

Flutter

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

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

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

ライブラリ

まずは、pubspec.yamlに依存関係を追加します。

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

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

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

walletconnect_dart

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

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

コードの解説をします。homepage.dartの68行目から

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行目から

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の使い方について解説していきます。

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

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

homepage.dartの101行目より、

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行目より、

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行目より、

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行目より、

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行目より、

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トークンを1つ送る関数(faucet)を作成しました。

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

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

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

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

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

使用方法


テストをする際には実機を使うので、あらかじめ、アプリ版メタマスクをインストールしておいてください。
"Connect wallet"を押します。
メタマスクに移動するので、接続を承認します。すると、アプリに自動的に戻り、メタマスクが接続されます。
あなたのメタマスクのアドレス、chainID、持っているFTTトークンの数が表示されます。
FTTトークンを持っていても、最初は0と表示されます。仕様です。

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

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

参照

改善点

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

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

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

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

まとめ

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

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

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

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0