メインコンテンツまでスキップ

lesson-2_フロントエンドのベースを作成しよう

🚅 フロントエンドのベースを実装しましょう

コード量が多く貼り付ける作業が大変になってしまうのを防ぐため、本プロジェクトpackages/clientディレクトリからファイルやディレクトリごとコピーしてフロントエンドを構築していきます。

本レポジトリ自体をローカルにクローンしてからコピーしたほうが作業が楽かもしれません。

📁 stylesディレクトリ

stylesディレクトリにはcssのコードが入っています。 全てのページに適用されるよう用意されたglobal.cssと、ホームページ用のHome.module.cssがあります。

今回はtailwindを使用するので、Home.module.cssを削除してください。

client
└── styles
└── globals.css

📁 publicディレクトリ

Next.jsはルートディレクトリ直下のpublicディレクトリを静的なリソース(画像やテキストデータなど)の配置場所と認識します。 そのためソースコード内で画像のURLを/image.pngと指定した場合、 Next.jsは自動的にpublicディレクトリをルートとしたプロジェクトルート/image.pngを参照してくれます。

このディレクトリを本プロジェクトpackages/client/publicディレクトリと置き換えてください。

favicon.ico: ファビコンになります。お好きな画像で設定したい場合は同じファイル名で保存するとファビコンに設定できます。

あなたのアプリのファビコンをお好きな画像で設定したい場合は、favicon.icoという名前でpublic/favicon.icoと置き換えてください。

background.png: ホームページの背景画像になります。

tailwindで画像を扱えるようにtailwind.config.jsを以下の内容に書き換えてください。

vscodeがエラーを出す可能性もありますが、気にせず進めて問題ありません。

/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
backgroundImage: {
home: "url('../background.png')",
},
},
},
plugins: [],
};
client
└── public
├── background.png
└── favicon.ico

📁 typesディレクトリ

typesディレクトリを本プロジェクトpackages/clientディレクトリからコピーして貼り付けてください。

中身に関しては次のセクションで触れます。

client
└── types
└── ...

📁 artifactsディレクトリ

artifactsディレクトリを本プロジェクトpackages/clientディレクトリからコピーして貼り付けてください。

中身に関しては次のセクションで触れます。

client
└── artifacts
├── Bank.json
└── IAllowList.json

📁 utilsディレクトリ

utilsディレクトリを本プロジェクトpackages/clientディレクトリからコピーして貼り付けてください。

🐬 ethereum.ts

型定義に厳格なtypescriptでwindow.ethereumを使用するためには、windowethereumオブジェクトがあるということを明示する必要があります。 MetaMaskInpageProviderは環境設定時にインストールした@metamask/providersから取得したethereumの型定義です。

📓 window.ethereumとは Web アプリケーション上でユーザーがブロックチェーンネットワークと通信するためには、Web アプリケーションはユーザーのウォレット情報を取得する必要があります。

window.ethereumは MetaMask がwindow(JavaScript にデフォルトで存在するグローバル変数)の直下に用意するオブジェクトであり API です。 この API を使用して、ウェブサイトはユーザーのイーサリアムアカウントを要求し、ユーザーが接続しているブロックチェーンからデータを読み取り、ユーザーがメッセージや取引に署名するよう求めることができます。

また、getEthereum関数を呼び出すとwindowから取り出したethereumオブジェクトを取得できるようにしています。

🐬 formatter.ts

weiToAvax(or avaxToWei)はweiAVAXの単位変換を行なっています。 ※ APIでは「1 AVAX = 10^18 wei」で単位変換がされているため、formatEther(or parseEther)を使用できます。

また、blockTimeStampToDateはsolidity内のblock.timestampから、フロントエンドで使用するDateへの変換を行なっています。 block.timestampは単位がミリ秒で、Dateは秒単位の時間を元に作成するので* 1000を行なっています。

🐬 compare.ts

ここでは2つのアドレスを比較する関数を用意しています。

client
└── utils
├── compare.ts
├── ethereum.ts
└── formatter.ts

📁 hooksディレクトリ

hooksディレクトリを本プロジェクトpackages/clientディレクトリからコピーして貼り付けてください。

ウォレットやコントラクトの状態を扱うようなカスタムフック(独自で作ったフック)を実装したファイルを保存します。

🐬 useWallet.ts

ここでは、ユーザがMetamaskを持っていることの確認とウォレットへの接続機能を実装します。

connectWalletはWebアプリがユーザのウォレットにアクセスすることを求める関数です。 この後の実装でUIにユーザのウォレット接続ボタンを用意し、そのボタンとこの関数を連携します。 そのため外部で使用できるように返り値の中に含めています。

checkIfWalletIsConnectedは既にユーザのウォレットとWebアプリが接続しているかを確認する関数です。

また、それぞれの関数内で使用しているeth_requestAccountseth_accountsは,空の配列または単一のアカウントアドレスを含む配列を返す特別なメソッドです。 ユーザーがウォレットに複数のアカウントを持っている場合を考慮して、プログラムはユーザーの1つ目のアカウントアドレスを取得することにしています。

🐬 useWallet.ts

ユーザがMetamaskを持っていることの確認とウォレットへの接続機能を実装しています。

connectWalletはWebアプリがユーザのウォレットにアクセスすることを求める関数です。 この後の実装でUIにユーザのウォレット接続ボタンを用意し、そのボタンとこの関数を連携します。 そのため外部で使用できるように返り値の中に含めています。

checkIfWalletIsConnectedは既にユーザのウォレットとWebアプリが接続しているかを確認する関数です。

また、それぞれの関数内で使用しているeth_requestAccountseth_accountsは,空の配列または単一のアカウントアドレスを含む配列を返す特別なメソッドです。 ユーザーがウォレットに複数のアカウントを持っている場合を考慮して、プログラムはユーザーの1つ目のアカウントアドレスを取得することにしています。

🐬 useContract.ts

コントラクトとの接続を行います。

ファイル冒頭には、今回フロントエンドから接続する2つのコントラクトのアドレスBankAddressTxAllowListAddressが記載されています。

BankAddressBankコントラクトのアドレスに後ほど変更します。

TxAllowListAddressは(section-1のLesson_3で設定したPreCompileの)TransactionAllowListのアドレスになります。 このアドレス値は固定です。

その下には型の定義があり、その下にuseContractが定義されています。

主な関数はgetContractで、接続しているcurrentAccountやコントラクトのアドレス・ABIを元にコントラクトに接続し、stateに保存します。

getBills関数は、接続したBankコントラクトから全てのBillデータを取得しています。 取得したBillはフロントエンド内で扱うBillTypeに変換して、billsというstateに保存しています。

client
└── hooks
├── useContract.ts
└── useWallet.ts

📁 contextディレクトリ

contextディレクトリを本プロジェクトpackages/clientディレクトリからコピーして貼り付けてください。

🐬 CurrentAccountProvider.tsx

コンテキストを用意しています。 コンテキストは複数のコンポーネント間を跨いで値を渡せるもので、値にはstateも渡せます。

はじめに以下の部分で先ほど作成したuseWallet()を使用してウォレット接続に関わるオブジェクトを取得しています。

const { currentAccount, connectWallet } = useWallet();

重要なのは以下です。 valueに、取得したオブジェクトを渡します。

<CurrentAccountContext.Provider value={[currentAccount, connectWallet]}>
{children}
</CurrentAccountContext.Provider>

するとこのコンポーネントの子となるコンポーネント(children)以下ではどのコンポーネントでもコンテキストからvalueを取得することができます。 複数のコンポーネント間でstateを共有できます。

後ほどこのコンテキストを使用する実装を他のファイルで行います。

client
└── context
└── CurrentAccountProvider.tsx

📁 componentsディレクトリ

componentsディレクトリを本プロジェクトpackages/clientディレクトリからコピーして貼り付けてください。

こちらにはコンポーネントを実装したファイルを保存していきます。

📓 コンポーネントとは UI(ユーザーインターフェイス)を形成する一つの部品のことです。 コンポーネントはボタンのような小さなものから,ページ全体のような大きなものまであります。 レゴブロックのようにコンポーネントのブロックで UI を作ることで、機能の追加・削除などの変更を容易にすることができます。

📁 Buttonディレクトリ

🐬 NavButton.tsx

ページへリンクするナビゲートボタンを実装しています。

🐬 AdminButton.tsx

管理者ページへ進むボタンを実装しています。

txAllowListコントラクトにreadAllowListを呼び出し、接続しているアカウントcurrentAccountがどの権限を持っているかを確認しています。

currentAccountが管理者権限を持っていた場合、管理者ページへリンクしたボタンを表示します。

また、currentAccountの権限を表示します。

🐬 ConnectWalletButton.tsx

ウォレットへの接続ボタンを実装しています。 接続されていれば、そのアカウント名を表示します。

🐬 SubmitButton.tsx

引数で渡された関数を実行するボタンを実装しています。

client
└── components
└── Button
├── AdminButton.tsx
├── ConnectWalletButton.tsx
├── NavButton.tsx
└── SubmitButton.tsx

📁 Cardディレクトリ

🐬 ViewBillCard.tsx

1つのBillの情報を表示するコンポーネントです。 ボタンが押下された際に引数で渡された関数を実行します。

🐬 RecipientBillCards.tsx

billsに対してmap関数を実行します。

関数callBackFn内では、billの受取人がcurrentAccountでなかった場合は空のコンポーネントを返却し、そうであった場合はbillの情報を返却します。 こうすることでcurrentAccountが受取人のbill情報のみ表示するようにしています。

callBackFn内ではボタン押下時にonClickCashを実行するようにしているため、bill情報に付加されたボタンをユーザが押下するとonClickCash内のcashBillが呼び出されます。

🐬 IssuerBillCards.tsx

RecipientBillCardsと同じ構成です。 currentAccountが発行者のbill情報のみ表示するようにしています。

🐬 AllBillCards.tsx

RecipientBillCardsと同じ構成です。 全てのbillの情報を表示します。

🐬 DishonoredCards.tsx

不渡りを起こしたアカウントのアドレスをリスト表示するコンポーネントです。

client
└── components
└── Card
├── AllBillCards.tsx
├── DishonoredCards.tsx
├── IssuerBillCards.tsx
├── RecipientBillCards.tsx
└── ViewBillCard.tsx

📁 Fieldディレクトリ

🐬 InputField.tsx

入力フォーム内の1つの入力欄を構成するコンポーネントです。

client
└── components
└── Field
└── InputField.tsx

📁 Formディレクトリ

🐬 IssueBillForm.tsx

Billを発行する入力フォームを構成するコンポーネントです。

🐬 AdminForm.tsx

ネットワーク管理者が使用するフォームを構成するコンポーネントです。 アドレス値の入力フォームと、そのアドレスに対してトランザクションを提出する権限を付与する関数と剥奪する関数をそれぞれ用意しボタンに実装しています。

client
└── components
└── Form
├── AdminForm.tsx
└── IssueBillForm.tsx

📁 Layoutディレクトリ

🐬 Layout.tsx

レイアウトを構成するコンポーネントです。 画面左上にナビゲーションボタン、右上にウォレット接続ボタンを表示します。

client
└── components
└── Layout
└── Layout.tsx

📁 pagesディレクトリ

pagesディレクトリの中にはページ単位のコンポーネントが入ります。

このディレクトリを本プロジェクトpackages/clientディレクトリからコピーして貼り付けてください。

🐬 _app.tsx

_app.tsxファイルは標準で全てのページの親コンポーネントとなります。

ここでCurrentAccountProviderを使用し、全てのコンポーネントがCurrentAccountProviderの子コンポーネントとなります。

つまり本プロジェクトで作成する全てのコンポーネントで、currentAccountの参照とconnectWalletの実行ができます。

🐬 index.tsx

本プロジェクトのルートにアクセスすると表示されるページです。 ホームページを構成します。

🐬 IssueBill.tsxViewBills.tsxAdmin.tsx

それぞれ、Bill発行・Bill閲覧・管理者ページを構成します。

client
└── pages
├── Admin.tsx
├── IssueBill.tsx
├── ViewBills.tsx
├── _app.tsx
└── index.tsx

🖥️ 画面で確認しましょう

それではAVAX-Subnetディレクトリ直下で以下のコマンドを実行してください!

yarn client dev

そしてブラウザでhttp://localhost:3000 へアクセスしてください。

以下のような画面が表示されれば成功です!

画面右上のConnect to walletボタンを押下するとウォレットと接続することができます。 ⚠️ section-1で設定したネットワークの管理者アカウントで接続してください。ネットワークにmySubnetを選択した状態で行ってください。

MetaMaskの承認が終わると、Connect to walletボタンの部分があなたの接続しているウォレットのアドレスの表示に変更されます。 今はアラートが表示されるかと思いますが、無視してokを押下してください。

その他各ページはこのようになっております。

🌔 参考リンク

こちらに本プロジェクトの完成形のレポジトリがあります。 期待通り動かない場合は参考にしてみてください。

🙋‍♂️ 質問する

ここまでの作業で何かわからないことがある場合は、Discordの#avalancheで質問をしてください。

ヘルプをするときのフローが円滑になるので、エラーレポートには下記の3点を記載してください ✨

1. 質問が関連しているセクション番号とレッスン番号
2. 何をしようとしていたか
3. エラー文をコピー&ペースト
4. エラー画面のスクリーンショット

フロントエンドのベースとなるコードが出来ました! 次のレッスンではコントラクトとフロントエンドを連携する作業に入ります!