📱

【Flutter】アプリをMetamaskウォレットと接続させてみた

2022/09/25に公開

はじめに

今回は、Flutter を用いてモバイルアプリと Metamask の接続を行う方法について記載をしていきます。

今回の実装にあたって以下の記事を参考にしてみました。
こちらも併せてご覧下さい。

https://github.com/MetaMask/metamask-mobile/issues/3735
https://dev.to/bhaskardutta/building-with-flutter-and-metamask-8h5

完成イメージ

Metamaskの接続イメージ
動作自体はシンプルでアプリ側にてボタンを押すと Metamask へ遷移し、接続をユーザーに依頼する。
その後アプリ側では

  • パブリックアドレスの取得
  • パブリックアドレスを用いた処理
  • Metamask とコネクション確立時・切断時に処理を走らせる

といったことが出来る流れになります。

実装

Flutter のコマンドに関しては本記事で言及したい内容ではないので特に触れません。

$ flutter create connect_to_metamask

プロジェクトを作成した後は以下のコマンドを実行します。

$ flutter pub add url_launcher walletconnect_dart

それぞれのパッケージのリポジトリは以下になります。

  • url_launcher
    • url から Metamask のアプリを起動する際に使用
  • walletconnect_dart
    • walletConnect とのセッションを作成し、Metamask と接続をする際に使用

問題なくパッケージの追加まで出来たらコードを書いていきます。
lib/main.dart ファイルがあるので、以下の様に修正します。

lib/main.dart
import 'package:flutter/material.dart';
import 'package:connect_to_metamask/pages/login_page.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: "/login",
      routes: {
        "/login": (context) => const LoginPage(),
      },
    );
  }
}

今回はログインページを用意してそこで Metamask と接続を行うケースを想定しています。そして他のページは設けないため、main.dart ではまず /login を表示させるようにしましょう。
この時点では存在していない /pages/login_page.dart を読み込んでいますが、今から作成しますので一旦無視しておいてください。

次は lib/ 配下に pages というディレクトリを作って、その配下に login_page.dart を作成します。
中身は以下の様な形です。

lib/pages/login_page.dart
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:walletconnect_dart/walletconnect_dart.dart';

class LoginPage extends StatefulWidget {
  const LoginPage({Key? key}) : super(key: key);

  
  State<LoginPage> createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  late SessionStatus _session;
  late Uri _uri;

  // connector を作成する関数
  WalletConnect createConnector() {
    return WalletConnect(
        bridge: 'https://bridge.walletconnect.org',
        clientMeta: const PeerMeta(
            name: 'create_to_Metamask_App',
            description: 'アプリの説明文です',
            url: 'https://yamawo.info',
            icons: [
              'https://files.gitbook.com/v0/b/gitbook-legacy-files/o/spaces%2F-LJJeCjcLrr53DcT1Ml7%2Favatar.png?alt=media'
            ]));
  }

  // ボタンが押された際に発火させる、walletConnect との session を確立する関数
  Future<void> connectToMetamask() async {
    final connector = createConnector();

    // session が確立されていない時
    if (!connector.connected) {
      try {
        final session =
            await connector.createSession(onDisplayUri: (uri) async {
          _uri = Uri.parse(uri);
          // Metamask アプリを立ち上げる
          await launchUrl(Uri.parse(uri));
        });

        setState(() {
          _session = session;
        });
      } catch (e) {
        print(e);
      }
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Login Page'),
      ),
      body: Center(
        child: SingleChildScrollView(
          child: ElevatedButton(
            onPressed: () => connectToMetamask(),
            child: const Text("Connect with Metamask"),
            style: ElevatedButton.styleFrom(
              onPrimary: Colors.black,
            ),
          ),
        ),
      ),
    );
  }
}

少し説明をすると、

WalletConnect createConnector() {
  return WalletConnect(
    bridge: 'https://bridge.walletconnect.org',
    clientMeta: const PeerMeta(
      name: 'create_to_Metamask_App',
      description: 'アプリの説明文です',
      url: 'https://yamawo.info',
      icons: [
        'https://files.gitbook.com/v0/b/gitbook-legacy-files/o/spaces%2F-LJJeCjcLrr53DcT1Ml7%2Favatar.png?alt=media'
      ]));
}

上記の関数は walletConnect とのコネクタを作成しています。
walletConnect の機能を用いて Metamask との接続を行なっていきます。

// ボタンが押された際に発火させる、walletConnect との session を確立する関数
Future<void> connectToMetamask() async {
  final connector = createConnector();

  // session が確立されていない時
  if (!connector.connected) {
    try {
      final session =
          await connector.createSession(onDisplayUri: (uri) async {
        _uri = Uri.parse(uri);
        // Metamask アプリを立ち上げる
        await launchUrl(Uri.parse(uri));
      });

      setState(() {
        _session = session;
      });
    } catch (e) {
      print(e);
    }
  }
}

connectToMetamask()は実際にボタンが押された際に走る処理になります。
流れとしては、まず初めに先ほどのcreateConnector()でコネクタを作成して、connector.connctedで接続が既に確立されているかの確認を行ないます。
確立されていなければcreateSession()を実行し、引数で URI を生成時に実行される関数を渡します。
その後、生成された URI を launchUrl() に渡して立ち上げ、ユーザーが Metamask 側でアプリとの接続をすると session が返され、session.accounts[0]session.chainIdでユーザーの情報が取得出来る様になるという流れです。
これで Metamask とアプリでの接続は終わりになります。

他にも出来ること

walletconnect_dartパッケージの README を見ても分かりますが、「session を確立した時」や「session が更新された時」、「接続が切れた時」に特定の処理を走らせるということも可能そうです。

// session を確立した時
connector.on('connect', (session) => print(session));
// session が更新された時
connector.on('session_update', (payload) => print(payload));
// 接続が切れた時
connector.on('disconnect', (session) => print(session));

今回実装はしていませんが、例ではこのように各コネクタのイベントを subscribe してイベントが起きたときに走る様にしているみたいでした。
この他にもトランザクションの実装なども出来そうで活用すればモバイルアプリでもより多くの機能が実現できそうですね。

おわりに

今回の実装こちらにありますので参考にしてみてください。
Twitterも繋がっていただけると嬉しいです。

Discussion