フロントエンドからメタマスクを接続する
フロントエンドから、メタマスクを接続するための方法を紹介します。
こちらの記事の続きとなります。
Reactを使って、画面を開発していきます。
next.jsのプロジェクトを作成します。
npx create-next-app@latest
メタマスクの接続やコントラクトを実行するため、ethers.jsをインストールします。
イーサリアムチェーンをフロントエンドで操作するために、推奨されるライブラリです。
npm install --save ethers
続けてUIを簡単に実装できるマテリアルUIもインストールします。
npm install @mui/material @emotion/react @emotion/styled
layout.tsxにマテリアルUIのプロパイダーを追加します。
import { AppRouterCacheProvider } from "@mui/material-nextjs/v14-appRouter";
<html lang="en">
<body>
<AppRouterCacheProvider>{children}</AppRouterCacheProvider>
</body>
</html>
page.tsxにethersをインポートします。
import { ethers } from "ethers";
接続したメタマスクのアカウントを保存するために、useStateを追加します。
const [currentAccount, setCurrentAccount] = useState<string>("");
メタマスクを接続する関数を追加します。
ethersライブラリの、「eth_requestAccounts」で簡単にメタマスクとのコネクトができます。
接続されたら、メタマスクにログインしているユーザウォレットをリストで取得します。
リストの最初のアドレスをcurrentAccountの状態変数に入れます。
const connectWallet = async () => {
try {
/* ユーザーがメタマスクをインストールしているか確認する */
const { ethereum } = window as any;
/* 持っていなければアラートを表示して終了 */
if (!ethereum) {
alert("Get MetaMask!");
return;
}
/* 持っていれば、ユーザーにウォレットへのアクセス許可を求める */
const accounts = (await ethereum.request({
method: "eth_requestAccounts",
})) as string[];
console.log("Connected: ", accounts[0]);
/* ユーザーの最初のウォレットアドレスをcurrentAccountに入れる */
setCurrentAccount(accounts[0]);
} catch (error) {
console.log(error);
}
};
currentAccountにウォレットアドレスがあれば、ウォレット接続ボタンを表示します。
押すとメタマスクの認証が開きます。
{!currentAccount ? (
<Box sx={{ display: 'flex', justifyContent: 'center', gap: 2, width: '100%' }}>
<Button
onAbort={() => connectWallet()}
variant="contained"
color="primary"
size="large"
sx={{ flex: 1 }}
>
ウォレット接続
</Button>
</Box>
) : (
<Box sx={{ display: 'flex', justifyContent: 'center', gap: 2, width: '100%' }}>
<Button
onAbort={() => console.log("connected wallet")}
variant="contained"
color="primary"
size="large"
sx={{ flex: 1 }}
>
ウォレット接続済み
</Button>
</Box>
)}
今のままだと、ウォレットが接続されても画面の状態が未接続になってしまいます。
画面を更新した時に、接続状態を確認して画面に反映させます。
メタマスクがインストールされているかチェックする処理を追加します。
const checkIfWalletIsConnected = () => {
const { ethereum } = window as any;
if (!ethereum) {
console.log("Make sure you have MetaMask!");
} else {
console.log("We have the ethereum object", ethereum);
}
};
続いてウォレットの接続状態をチェックし、接続状態ならcurrentAccountを更新します。
const checkIfWalletIsConnected = async () => {
const { ethereum } = window as any;
if (!ethereum) {
console.log("Make sure you have MetaMask!");
} else {
console.log("We have the ethereum object", ethereum);
}
/* メタマスクにアクセスできるかを確認して、アクセスできるなら状態変数に入れる */
const accounts = await ethereum.request({ method: "eth_accounts" });
if (accounts.length !== 0) {
const account = accounts[0];
console.log("Found an authorized account:", account);
setCurrentAccount(account);
} else {
console.log("No authorized account found");
}
};
これで完成です。
layout.tsx
import type { Metadata } from "next";
import { AppRouterCacheProvider } from "@mui/material-nextjs/v14-appRouter";
import "./globals.css";
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body>
<AppRouterCacheProvider>{children}</AppRouterCacheProvider>
</body>
</html>
);
}
page.tsx
"use client";
import styles from "./page.module.css";
import {
Button,
Container,
Typography,
Divider,
TextField,
Box,
} from "@mui/material";
import { ethers } from "ethers";
import React, { useState, useEffect } from "react";
export default function OutlinedCard() {
const [currentAccount, setCurrentAccount] = useState<string>("");
const connectWallet = async () => {
try {
/* ユーザーが認証可能なウォレットアドレスを持っているか確認する */
const { ethereum } = window as any;
if (!ethereum) {
alert("Get MetaMask!");
return;
}
/* 持っている場合は、ユーザーに対してウォレットへのアクセス許可を求める
* 許可されれば、ユーザーの最初のウォレットアドレスを currentAccount に格納する */
const accounts = (await ethereum.request({
method: "eth_requestAccounts",
})) as string[];
console.log("Connected: ", accounts[0]);
setCurrentAccount(accounts[0]);
} catch (error) {
console.log(error);
}
};
const checkIfWalletIsConnected = async () => {
const { ethereum } = window as any;
if (!ethereum) {
console.log("Make sure you have MetaMask!");
} else {
console.log("We have the ethereum object", ethereum);
}
/* メタマスクにアクセスできるかを確認して、アクセスできるなら状態変数に入れる */
const accounts = await ethereum.request({ method: "eth_accounts" });
if (accounts.length !== 0) {
const account = accounts[0];
console.log("Found an authorized account:", account);
setCurrentAccount(account);
} else {
console.log("No authorized account found");
}
};
useEffect(() => {
checkIfWalletIsConnected();
}, []);
return (
<Container maxWidth="sm" sx={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', height: '80vh' }}>
<div>
{/* タイトル部分をTypographyに置き換え */}
<Typography variant="h4" component="h1" gutterBottom className={styles.title}>
Welcome to Material UI!
</Typography>
</div>
{/* Dividerを追加して区切り線を入れる */}
<Divider sx={{ mb: 3 }} />
{/* ボタン部分 */}
<Box sx={{ display: 'flex', justifyContent: 'center', gap: 2, width: '100%' }}>
{!currentAccount ? (
<Box sx={{ display: 'flex', justifyContent: 'center', gap: 2, width: '100%' }}>
<Button
onAbort={() => connectWallet()}
variant="contained"
color="primary"
size="large"
sx={{ flex: 1 }}
>
ウォレット接続
</Button>
</Box>
) : (
<Box sx={{ display: 'flex', justifyContent: 'center', gap: 2, width: '100%' }}>
<Button
onAbort={() => console.log("connected wallet")}
variant="contained"
color="primary"
size="large"
sx={{ flex: 1 }}
>
ウォレット接続済み
</Button>
</Box>
)}
</Box>
</Container>
);
}
完成した画面です。
メタマスクのチェーンをhardhatのローカル、hardhatの開発用アカウントインポートして変更します。
ウォレット接続ボタンを押すとメタマスクに接続できました!
参考記事
https://zenn.dev/lycp152/books/bb2010b7cb37f9/viewer/13069d