MetaMaskと接続するWebページを作る_vol.01

みなさん、こんにちは。GMOインターネット株式会社の斉藤です。

さて、いま世間で注目のトレンドと言えばWeb 3.0ですね(いきなり)。

私もトレンドに乗り遅れてはいけないと言うことでいろいろ勉強しているところです。私個人としては特にそのインフラ面での技術スタックに注目してます。Web 1.0/2.0はHTTP、DNS、SMTP、TLS/SSL等のプロトコルに加え、仮想化、コンテナ、Kubernetesのようなリソース集約/分散化が基盤技術でした。そして、Webがリッチになるにつれてそれらの技術も進化し、エンジニアとしてそれを追いかけることで仕事をしてきました。

しかし、もしWeb2.0 → Web 3.0の完全な移行が起きると、これらの基盤技術がブロックチェーン技術に置き換わることを意味します。ブロックチェーンは全く違う技術要素で動くので、今までの技術ナレッジが通用しない可能性があります。せっかくここまで積み上げてきたのに!(笑)

もちろん、Web 3.0が今後どの程度支配的になるのかはわかりません。ただWebに関わってきたエンジニアとして、Webの仕事ができなくなるリスクは取れないでしょう。こんな楽しい仕事を手放してしまうなんてもったいですよね 。なのでWeb 3.0についても学習を進めていくべきだと思うのです。

ということでWeb 3.0と言えばブロックチェーンです。今回は入門編ということで、MetaMaskを使ってブロックチェーンと連携するWebページを作ってみます。

MetaMaskとは

MetaMaskはEthereumに対応した暗号資産(仮想通貨)のウォレットです。他のチェーンについても簡単に追加できます。この記事を書いている現在で非常にポピュラーで、多くの利用があります。Webブラウザの拡張機能としての実装と、モバイルアプリ(iOS / Android)の実装があります。

https://metamask.io/

MetaMaskはウォレット機能だけで無く、Webサイトとの連携も考慮されています。公式サイトにも「gateway to blockchain apps」とあり、WebページからMetaMaskのブラウザ拡張を通じて比較的容易にブロックチェーンにアクセスすることができます。

上記は私のウォレットの一つです。0.0096ETH(29.06USD相当)が入っています。

準備

WebページとMetaMaskのブラウザ拡張機能と連携して、ブロックチェーン上の情報にアクセスする実装について解説していきます。また、MetaMaskは複数のアカウントを管理することができるので、アカウントを切り替えたりウォレット間で送金をする実装も行ってみます。

作るものはWebページなのでHTMLとJavaScriptだけで作れますが、ここではReactを使います。

もしMetaMaskを持っていない場合は、公式サイトからインストールしておいてください。

インストールが終わったらアカウントの作成を行っておいてください。チュートリアルを進めるだけです。すでにアカウントを持っている方はそれを使ってもらって構いません。

今回は送金などは行わないので、暗号資産としてのETHを購入する必要はありません。ただ複数のネットワークを試すので、MetaMaskインストール後に設定からテストネットワークを有効にします。右上のアイコンから「設定」「高度な設定」と進んで「テストネットワークを表示」を有効にしてください。

デモ

こちらが今回作成するデモページです。

MetaMaskテストページ

GetAccountを押すとMetaMaskとの接続が行われて、ウォレットアドレスなどが表示されます。試しにMetaMask上からアカウントや接続先ネットワークを変更してみてください。Webページ側も追従して内容が変わるはずです。

ウォレットアプリをWebサイトと接続するというと、何が起きているのか分からないし不安だと言う方もいると思います。そういう方は、ぜひ以下の実装方法をご覧頂ければと思います。

実装

実装に当たってMetaMaskのドキュメントに目を通しておくと良いです。

Getting Started | MetaMask Docs

window.ethereumオブジェクト

WebサイトからMetaMaskを通じてブロックチェーンにアクセスするには、window.ethereumオブジェクトを使います。このオブジェクトの存在確認をして、MetaMaskがインストールされている/いないを判断します。

if (typeof window.ethereum !== 'undefined') {
  console.log('MetaMask is installed!');
}

ethereumオブジェクトには多くのメソッドがあります。このあたりにリファレンスがありますが、見ておいていただきたいのは request メソッドです。requestはMetaMaskを通じてEthereumブロックチェーンへRCPでメソッドコールを行います。要はブロックチェーン側の機能を呼び出すものです。

MetaMaskとの接続

次にWebサイトとMetaMaskを「接続」させます。接続というのは、現在Webブラウザで見ているページからMetaMaskアカウントへのアクセスを許可している状態だと思ってもらうとわかりやすいです。当然、初回の接続時にはMetaMaskからユーザーに接続許可を尋ねる画面が表示されるので、ユーザーはそれを元に許可/不許可を行います。

接続するにはrequestでeth_requestAccountsを呼び出します。以下は実装例です。

try {
    const acccounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
    if (acccounts.length > 0) {
        console.log(acccounts[0]);
    }
} catch (err) {
    if (err.code === 4001) {
        // EIP-1193 userRejectedRequest error
        // ユーザーが接続を拒否するとここに来ます
        console.log('Please connect to MetaMask.');
    } else {
        console.error(err);
    }
}

accountsには接続に成功したアカウントの一覧が返ってきますが、現在は必ず1つですが、将来的な拡張のために配列になっているとドキュメントにはありました。そしてaccounts[0]は接続したEthereumのウォレットアドレスが文字列で格納されています。

ネットワークの取得

接続に成功するとethereumオブジェクトを通じて利用可能なメソッドが増えます。eth_chainIdは現在ウォレットが接続しているネットワークのChain IDが取得できます。


const Chains = {
    1: "Mainnet",
    3: "Ropsten",
    4: "Rinkeby",
    42: "Kovan",
    1337: "Geth private chain(default )",
    61: "Ethereum Classic Mainnet",
    62: "Morden",
}

const hexChainId = await window.ethereum.request({ method: 'eth_chainId' });
const chainId = parseInt(hexChainId);
const name = Chains[chainId];

Chain IDはchainlist.orgなどで確認するとよいでしょう。

ネットワークの変更を検知する

ユーザーがMetaMaskのウインドウからアカウントやネットワークの接続先を変更するケースを考えてみましょう。変更に合わせてWebページ上の表示とMetaMaskの整合性を取る必要があります。

window.ethereumオブジェクトはNode.jsのEventEmitterを実装しているため、以下のようにイベントを監視できます。以下が実装例です。

window.ethereum.on("accountsChanged", (accountNo) => {
    // ここでアカウントの変更をハンドルします
});

window.ethereum.on("chainChanged", (accountNo) => {
    // こちらも同様です
});

それでは、実際にWebページを作ってみましょう。まず適当な名前でReactアプリを作成します。

npx create-react-app metamask-testapp
cd metamask-testapp

今回はWebページ上のボタンを押してMetaMaskへの接続を行い、接続が成功した場合はウォレットアドレスと接続先ネットワークを表示するようにしてみます。

実装はsrc/App.jsに書いていきます。

import './App.css';
import { useEffect, useState } from 'react';

const Wei = 1000000000000000000;

const Chains = {
    1: "Mainnet",
    3: "Ropsten",
    4: "Rinkeby",
    42: "Kovan",
    1337: "Geth private chain(default )",
    61: "Ethereum Classic Mainnet",
    62: "Morden",
}

const getAccount = async () => {
    try {
        const account = await window.ethereum.request({ method: 'eth_requestAccounts' });
        if (account.length > 0) {
            return account[0];
        } else {
            return "";
        }
    } catch (err) {
        if (err.code === 4001) {
            // EIP-1193 userRejectedRequest error
            // If this happens, the user rejected the connection request.
            console.log('Please connect to MetaMask.');
        } else {
            console.error(err);
        }
        return "";
    }
}

const handleAccountChanged = async (accountNo, setAccount, setChainId) => {
    const account = await getAccount();
    setAccount(account);

    const chainId = await getChainID();
    setChainId(chainId);
}

const getChainID = async () => {
    const chainId = await window.ethereum.request({ method: 'eth_chainId' });
    return parseInt(chainId);
}


function App() {
    const [account, setAccount] = useState("-");
    const [chainId, setChainId] = useState(0);
    const btnDisabled = account != "-";

    const initializeAccount = async () => {
        const account = getAccount();
        if (account != "") {
            handleAccountChanged(account, setAccount, setChainId);
        }
    };


    useEffect(() => {
        if (typeof window.ethereum !== 'undefined') {
            window.ethereum.on("accountsChanged", (accountNo) => handleAccountChanged(accountNo, setAccount, setChainId));
            window.ethereum.on("chainChanged", (accountNo) => handleAccountChanged(accountNo, setAccount, setChainId));
        }
    }, [account]);

    return (
        <div>
            <h2>MetaMask test</h2>
            <div>
                <h3>Account</h3>
                <button id="GetAccountButton" onClick={initializeAccount} disabled={btnDisabled}>Get Account</button>
                <p id="account">Address: {account}</p>
                <p id="account">Chain ID: {chainId}</p>
                <p id="account">Chain Name: {Chains[chainId]}</p>
            </div>

        </div >
    );
}

export default App;

おわりに

改めて、完成したデモページはこちらです。

デモページ

MetaMaskを利用するとブロックチェーンに簡単にアクセスできますね。HTMLとJavaScriptが書ければすぐにでも始められます。

次回は実際に送金トランザクションを作ってみます。この記事の1ヶ月後くらいに公開されますが、そちらもぜひご覧いただければと思います。

そして皆さんもSTEAM人材になりましょう笑。

このツイートはジョークですが「STEAM人材」も聞くことが増えてきたキーワードですね。おおよそこんな意味のようです。そして私が働いているGMOインターネット株式会社ではSTEAM人材を大募集しています。

募集職種 | GMOインターネット株式会社採用

「いきなり応募するもなぁ」と言う方は、Twitter DMで私に直接ご相談いただいても構いません。社内の雰囲気、開発現場の雰囲気、人間関係、待遇などなど、社内の人間からリアルな情報を知りたいという方はお気軽にどうぞ。

それではまた次回お目にかかりましょう。

ブログの著者欄

斉藤 弘信

GMOインターネットグループ株式会社

2001年に同社に入社。ユーザーサポートやデータセンターでのオペレーション業務等を担当し,その後社長室などのゼネラル部門を経て,2014年9月よりクラウド/ホスティング事業のテクニカルエバンジェリストを担当。現在はWebプロモーション研究室のソフトウェアエンジニアとして勤務。得意分野はWebアプリケーションの設計/開発,Linuxサーバー構築/運用。

採用情報

関連記事

KEYWORD

採用情報

SNS FOLLOW