day29. web3.0のためのフロントエンド開発 – フロントエンド開発 –

Day28では開発環境のセットアップを行いました。このチュートリアルでは、いよいよweb3.0のためのフロントエンドの開発を行っていきます。

Day28では開発環境のセットアップを行いました。このチュートリアルでは、いよいよweb3.0のためのフロントエンドの開発を行っていきます。

TOC

ファイル構造

Day28で言及した構造のうち、主にページを開発する上で編集するファイルは次のものになります。

Zsh
.
├── components/*
    ├── Fund.tsx
    └── Header.tsx
...
├── constants/*
    ├── abi.json
    ├── contractAddress
    └── index.ts
...
├── pages/*
    ...
    ├── _app.tsx
    └── index.tsx

componentsとpagesの下にあるファイル・ディレクトリーの構造を論理的に書いてみると、次の図のようになります。

_app.tsxは、Next.jsの特殊なファイルで、すべてのページの共通レイアウトや状態を管理するために使用されます。イメージとしては、アプリケーションのラッパーのようなものとして機能し、各ページが読み込まれる前に、このファイルを通して読み込まれます(グローバルなCSS, styles/globals.css もこのファイルで読み込みます)。

pagesの下にあるものは、ページコンポーネントと呼ばれ、ページの表示とルーティングの役割を担っています。ルーティングとは、例えば、pages/index.tsxというファイルの場合、/でアクセスすることができ、pages/about.tsxというファイルの場合、/aboutでアクセスすることができます。

componentsの下にあるものは、ページで利用されるUI部品を格納しておくためのものです。componentsのファイルは、ページコンポーネント内で呼んで(importして)利用するのが一般的です。

上記のファイルの場合、index.tsxにトップページが定義されており、その中では、HeaderとFundの2つのコンポーネントを呼んでいます。

constantsの下のファイルは、ブロックチェーン・ネットワークとのトランザクションを行うために利用するファイルです。具体的には、デプロイ済みのコントラクトのアドレスと該当のコントラクトのインターフェース(ABI)を定義しています。

プログラム

pages/_app.tsx

TypeScript
import "@/styles/globals.css";
import type { AppProps } from "next/app";
import { MoralisProvider } from "react-moralis";
import { NotificationProvider } from "web3uikit";

export default function App({ Component, pageProps }: AppProps) {
  return (
    <MoralisProvider initializeOnMount={false}>
      <NotificationProvider>
        <Component {...pageProps} />
      </NotificationProvider>
    </MoralisProvider>
  );
}
  • App({ Component, pageProps }: AppProps): この関数はNext.jsの全ページで共通のコンポーネントや設定を適用するためのメイン関数です。ページごとのコンポーネントはComponentとして、そのページのpropsはpagePropsとして渡されます。
  • <MoralisProvider initializeOnMount={false}>: Moralisのサービスを利用するためのコンテキストを提供するプロバイダーをアプリケーション全体にラップしています。これによって、子コンポーネントでMoralisの機能を利用できます(具体的にはコントラクトの関数を実行するなどのこと)。
    Moralisのサービスの認証情報を含めて使うのが通常の使い方ですが、Moralisのサービス自体を使いたいわけではないので、initializeOnMount={false}として、初期設定をせずに利用します。
  • <NotificationProvider>: 通知関連のプロバイダーを使用して、アプリケーション全体をラップしています。これにより、子コンポーネントで通知機能を利用できます。子コンポーネントでは、トランザクションが正常に完了した際に通知を行います。
  • <Component {...pageProps} />: 実際のページコンポーネント(例: index.tsxabout.tsxなど)をレンダリングします。ページごとのpropsはpagePropsを通して渡されます。

※ propsとは、簡単に言うと、親コンポーネントから子コンポーネントへデータを渡す手段として使用されます。コード例は次の通りです。

TypeScript
// コード例
// 親コンポーネント
function ParentComponent() {
  return <ChildComponent greeting="Hello, World!" />;
}

// 子コンポーネント
function ChildComponent(props) {
  return <h1>{props.greeting}</h1>;
}

pages/index.tsx

TypeScript
import Head from "next/head"
import Header from "@/components/Header"
import Fund from "@/components/Fund"

export default function Home() {
    return (
        <>
            <Head>
                <title>Web3 クラウドファンディング</title>
                <meta name="description" content="Our Smart Contract Lottery" />
                <meta
                    name="viewport"
                    content="width=device-width, initial-scale=1"
                />
                <link rel="icon" href="/favicon.ico" />
            </Head>
            <Header />
            <br />
            <Fund />
        </>
    )
}

index.tsxでは、Homeという名前のコンポーネントが定義されています。ここでは、更に、Headコンポーネント、Headerコンポーネント、Fundコンポーネントが使われています。

  • Head: Next.jsで提供される特別なコンポーネントで、<head>要素内に要素を追加することができます。
  • Header, Fund: 個別に定義されているコンポーネントです(次項)

全体のコンポーネントは、Reactのフラグメント(<></>)内にラップされています。これは、複数の要素を一つの親要素無しで返すための構文です。

components/Header.tsx

TypeScript
import { ConnectButton } from "web3uikit"

export default function Header() {
    return (
        <div className="p-5 border-b-2 flex flex-row">
            <h1 className="py-4 px-4 font-blog text-3xl">
                Web3 クラウドファンディング
            </h1>
            <div className="ml-auto py-2 px-4">
                <ConnectButton moralisAuth={false} />
            </div>
        </div>
    )
}

ここでは、Headerコンポーネントを定義しています。前述の通りトップページ(/)のコンポーネントpages/index.tsxで使われています。その中では、web3uikitで定義されている、ConnectButtonを使っています。

web3uikitは、web3向けのフロントエンドで利用できるUIコンポーネントを定義しているライブラリーです。web3uikitで定義しているUIコンポーネントは、このページから確認できます。

※ 上述のリンク先は、Storybookと呼ばれるもので、カタログ形式でUIコンポーネントを管理することができる機能です。アプリケーション全体のコンテキストから分離して、個別のコンポーネントを開発・テストすることが可能になります。

components/Fund.tsx

Fundコンポーネントでは、投資家のアドレスや投資額を表示したり、投資する、引き出すといったユーザーの操作を可能とする機能を実装します。冒頭は利用する機能のimport文、メインのFund関数内で利用する変数用のインターフェースを定義しています。Fund関数は、A〜Fの6つに分けて解説します。

TypeScript
import { useMoralis, useWeb3Contract } from "react-moralis"
import { abi, contractAddresses } from "../constants"
import { useEffect, useState } from "react"
import { ContractTransaction, ethers } from "ethers"
import { Button, Input, useNotification } from "web3uikit"

interface contractAddressInterface {
    [key: string]: string[]
}

export default function Fund() {
    // A. 変数定義
    const addresses: contractAddressInterface = contractAddresses
    const { chainId: chainIdHex, isWeb3Enabled, user, account } = useMoralis()
    const chainId = parseInt(chainIdHex!).toString()
    const fundingAddress = chainId in addresses ? addresses[chainId][0] : null

    //// state
    const [owner, setOwner] = useState("0")
    const [funder, setFunder] = useState("0")
    const [index, setIndex] = useState("0")
    const [addressToAmountFunded, setAddressToAmountFunded] = useState("0")

    const dispatch = useNotification()
    const userAddress = account ? ethers.getAddress(account!) : ""

    // B. コントラクトの関数定義
    const {
        runContractFunction: fund,
        isLoading,
        isFetching,
    } = useWeb3Contract({
        abi: abi,
        contractAddress: fundingAddress!,
        functionName: "fund",
        params: {},
        msgValue: 110000000000000000,
    })

    const { runContractFunction: withdraw } = useWeb3Contract({
        abi: abi,
        contractAddress: fundingAddress!,
        functionName: "withdraw",
        params: {},
    })

    const { runContractFunction: getOwner } = useWeb3Contract({
        abi: abi,
        contractAddress: fundingAddress!,
        functionName: "getOwner",
        params: {},
    })

    const { runContractFunction: getFunder } = useWeb3Contract({
        abi: abi,
        contractAddress: fundingAddress!,
        functionName: "getFunder",
        params: { index: index },
    })

    const { runContractFunction: getAddressToAmountFunded } = useWeb3Contract({
        abi: abi,
        contractAddress: fundingAddress!,
        functionName: "getAddressToAmountFunded",
        params: { funder: funder },
    })

    // C. 画面上のState変数の更新
    async function updateUI() {
        const ownerFromCall = (await getOwner()) as string
        const funderFromCall = (await getFunder()) as string
        const addressToAmountFundedFromCall = String(
            (await getAddressToAmountFunded()) as BigInt,
        )
        setOwner(ownerFromCall)
        setFunder(funderFromCall)
        setAddressToAmountFunded(addressToAmountFundedFromCall)
    }

    // D. トランザクション成功時の通知表示
    const handleSuccess = async function (txn: ContractTransactionResponse) {
        await txn.wait(1)
        handleNewNotification()
        updateUI()
    }
    const handleNewNotification = function () {
        dispatch({
            type: "info",
            message: "トランザクション成功!",
            title: "通知",
            position: "topR",
        })
    }

    // E. ReactのuseEffect Hooks
    useEffect(() => {
        if (isWeb3Enabled) {
            updateUI()
        }
    }, [isWeb3Enabled, index, addressToAmountFunded])

    // F. 表示画面
    return (
        <div>
            {fundingAddress ? (
                <div className="p-5">
                    <div>コントラクト所有者: {owner}</div>
                    <br />
                    投資家のインデックス番号0,1...)を入力してください
                    <div className="p-5">
                        <Input
                            label="投資家のインデックス番号"
                            name="投資家のインデックス番号"
                            type="number"
                            onChange={(event) => {
                                setIndex(event.target.value)
                            }}
                        />
                        <br />
                        <div>投資家のアドレス: {funder}</div>
                        <div>投資額: {addressToAmountFunded}</div>
                    </div>
                    投資するには次のボタンを押してください
                    <br />
                    <Button
                        text="投資する"
                        theme="outline"
                        onClick={async function () {
                            await fund({
                                onSuccess: (txn) =>
                                    handleSuccess(txn as ContractTransactionResponse),
                                onError: (error) => console.log(error),
                            })
                        }}
                    ></Button>
                    <br />
                    {userAddress == owner ? (
                        <div>
                            コントラクト所有者は次のボタンを押すとコントラクトの残高を引き出すことができます
                            <br />
                            <Button
                                text="引き出す"
                                theme="outline"
                                onClick={async function () {
                                    await withdraw({
                                        onSuccess: (txn) =>
                                            handleSuccess(
                                                txn as ContractTransactionResponse,
                                            ),
                                        onError: (error) => console.log(error),
                                    })
                                }}
                            ></Button>
                        </div>
                    ) : (
                        <div>
                            接続されているアドレスはコントラクト所有者ではありません
                        </div>
                    )}
                </div>
            ) : (
                <div>コントラクトのアドレス情報が見つかりません。</div>
            )}
        </div>
    )
}

A. 変数定義

TypeScript
const { chainId: chainIdHex, isWeb3Enabled, account } = useMoralis()

useMoralisというフックを呼び出して、Moralisから取得した情報を分割代入しています。具体的には、

  • chainIdHex: 現在接続しているブロックチェーンネットワークのID。続いての15行目で16進数から10進数の文字列に変換しています。
  • isWeb3Enabled: Web3が有効化されているか(画面を表示しているユーザーがウォレットを接続しているか)どうかのブール値。
  • account: 現在接続しているユーザーのEthereumアドレスを示す。

※ useMoralisフックで取得できる値は、この表に定義されています。

TypeScript
const fundingAddress = chainId in addresses ? addresses[chainId][0] : null

三項演算子を使って、chainIdaddressesオブジェクトのキーとして存在するかどうかをチェックしています。

  • もし存在する場合、addresses[chainId][0]を取得してfundingAddressに代入します。これは、特定のネットワークの最初のコントラクトアドレスを示していると思われます。
  • 存在しない場合、fundingAddressはnullになります。

addressesオブジェクトの値は、31337: ['0x5FbDB2315678afecb367f032d93F642f64180aa3']といった形のものになります(localhostのみの場合。後述、constantsの項も参照)。

19〜22行目は、ReactのuseStateを用いてownerfunderindexaddressToAmountFundedという4つの状態変数を定義しています。それぞれの状態変数には、状態を変更するための関数(setOwnersetFunderなど)が関連付けられています。これらの状態変数の値が変更されると、それを使用しているコンポーネントが再レンダリングされます。

TypeScript
const [index, setIndex] = useState("0")

例えば、indexは投資家のインデックス番号(何番目に投資しかを示す番号)を表しますが、これは後述のFでユーザーからの番号入力を受け付けて、新しい番号になる度にsetIndexが呼ばれ、indexの値が更新されます。indexは画面の表示には利用されていませんが、新しいindexになる度にuseEffectが実行され(Eで解説)、投資額などの付随的な情報が更新され、画面表示に反映されます。

24行目は、web3uikitの機能でユーザーに通知を行うための関数を定義しています。これは、Dで利用されており、この例の場合には、画面の右上にinfoレベルとして、トランザクションが正常に終了した旨のメッセージを表示する、というものです。

25行目は、アカウント情報からアドレス(このフロントエンドの画面に接続しているユーザーのアドレス)を取得しています。

TypeScript
const userAddress = account ? ethers.getAddress(account!) : ""

B. コントラクトの関数定義

useWeb3ContractというMoralisのHookを利用して、ブロックチェーン・ネットワークに対してスマートコントラクトの関数を実行するための準備を行っています。次の例は、投資家のindex番号から投資家のアドレスを取得するための関数を実行します。

TypeScript
    const { runContractFunction: getFunder } = useWeb3Contract({
        abi: abi,
        contractAddress: fundingAddress!,
        functionName: "getFunder",
        params: { index: index },
    })

{ runContractFunction: getFunder }: useWeb3Contract Hookが返すオブジェクトからrunContractFunctionという関数をgetFunderという名前で取得しています。

引数として以下のオブジェクトをuseWeb3Contractに渡しています:

  • abi: ABI (Application Binary Interface)、スマートコントラクトのメソッドと構造を定義するJSON形式のデータです。
  • contractAddress: 使用するスマートコントラクトのアドレスです。fundingAddress!!は、この変数がnullやundefinedでないことをTypeScriptに伝えるためのものです。
  • functionName: 実行するスマートコントラクトの関数の名前。この場合、”getFunder”という関数を指定しています。
  • params: スマートコントラクトの関数を実行する際の引数を指定するオブジェクト。indexという名前で引数を渡しています。

useWeb3Contractでは、スマートコントラクト内の該当する関数の定義で何かしらの値を返していれば、その値がここでの実行結果となります。

これらの関数は、次のCで利用されています。

C. 画面上のStateの更新

Cでは、updateUI関数が呼ばれる度に、その関数内でスマートコントラクトの関数実行(Bの処理)が行われ、そこで取得された値がCの中でセット(useFunderなど)され、useStateの仕組みによって、画面の表示が更新されます。

TypeScript
    async function updateUI() {
        const ownerFromCall = (await getOwner()) as string
        const funderFromCall = (await getFunder()) as string
        const addressToAmountFundedFromCall = String(
            (await getAddressToAmountFunded()) as BigInt,
        )
        setOwner(ownerFromCall)
        setFunder(funderFromCall)
        setAddressToAmountFunded(addressToAmountFundedFromCall)
    }

D. トランザクション終了時の通知表示

TypeScript
    const handleSuccess = async function (txn: ContractTransactionResponse) {
        await txn.wait(1)
        handleNewNotification()
        updateUI()
    }
    const handleNewNotification = function () {
        dispatch({
            type: "info",
            message: "トランザクション成功!",
            title: "通知",
            position: "topR",
        })
    }

Fの画面で投資する、引き出すと言ったユーザーのアクション(Bで定義したfundやwithdrawのスマートコントラクト関数)が実行された際に、成功だった場合には、上記のhandleSuccessが呼ばれます。handleSuccessは引数としてスマートコントラクトの関数を実行したトランザクションの結果(ContractTransactionResponse)を取ります。

await txn.wait(1)は、ブロックチェーンにブロックが取り込まれる(1つマイニングされる)のを待ち、トランザクションが確定してから次の処理に進むというものです。この場合は、fundで例えば投資して、それがブロックチェーンに取り込まれたのを待ってから、成功とみなして、handleNewNotification関数を実行しています(この関数は前述の通り、web3uikitの提供するHookで実行されると、トランザクション成功の通知がミニウィンドウとして画面の右上に表示されます)。

※ ContractTransactionResponseおよびwait関数は、ここに詳細が説明されています。ethers.jsのバージョンによっては、別のクラス(ContractTransaction)となっています。仮に該当するクラスが見つからないような場合には、こういった文書内でwaitやconfirmationと言ったキーワードから該当するクラスを類推していくことが有力な手段になります。

E. ReactのuseEffect Hooks

TypeScript
    useEffect(() => {
        if (isWeb3Enabled) {
            updateUI()
        }
    }, [isWeb3Enabled, index, addressToAmountFunded])

useEffectフックは、2つめの引数(依存配列)を監視し、これらの値が変更された際に、1つ目の引数の処理を実行するものです。つまり、最初にisWeb3Enabledの値を読み込んだ際に、これが真であれば(ウォレットがこのサイトに接続されていれば)、updateUI関数を実行し、画面を表示し、その後は、indexやaddressToAmountFundedの値が更新されたら、updateUIが実行されます。

F. 表示画面

Fの部分は、画面を表示する部分の処理が記述されています。次のような画面が表示されます(一番上のタイトルとアドレスが書いている箇所はHeader.tsxで定義されているものになります。それより下の箇所がFの部分で定義されているものです)。

Fの部分は、画面を表示する部分の処理が記述されています。次のような画面が表示されます(一番上のタイトルとアドレスが書いている箇所はHeader.tsxで定義されているものになります。それより下の箇所がFの部分で定義されているものです)。

Fの箇所では、通常のHTMLに加えて、いくつかweb3uikitのコンポーネントを利用しています。具体的には、InputコンポーネントとButtonコンポーネントです。

TypeScript
<Input
    label="投資家のインデックス番号"
    name="投資家のインデックス番号"
    type="number"
    onChange={(event) => {
        setIndex(event.target.value)
    }}
/>

Inputコンポーネントは、label, name, type(取りうる値)に加えて、onChangeというユーザーのアクションと連動して実行されるロジック部分があります。これは、ユーザーの入力値が変更される度に、onChange内の処理を実行するというものです。ここでは、setIndexが実行されます(その結果、indexが変更となり、indexを監視しているのでuseEffectが実行され、updateUIが実行され、getOwner, getFunder …が実行され…要はIndexの値変更に伴って、投資家関連の情報が一通り更新されます)。

TypeScript
<Button
    text="投資する"
    theme="outline"
    onClick={async function () {
        await fund({
            onSuccess: (txn) =>
                handleSuccess(
                    txn as ContractTransactionResponse,
                ),
            onError: (error) => console.log(error),
        })
    }}
>

Buttonコンポーネントでは、text, theme(見た目)に加えて、onClickがあります。ここでもやはりユーザーがクリックした際に実行されるロジックが定義されています。具体的には、Bで定義したfund(別の箇所ではwithdraw)が実行され、トランザクションが成功した場合にはonSuccessが、エラーの場合にはonErrorが実行されます。onSuccessの場合には、Dの関数を呼び、ユーザーにUI上でトランザクションの正常終了を知らせています。

主なユースケースにおける流れ

投資家のIndex番号を入力する
  1. Fで投資家のindex番号をユーザーが入力し、setIndexが実行される
  2. Eでindexの値を監視しているので、値が更新されたたため、updateUIが実行される
  3. CでgetOwner, getFunder, getAddressToAmountFundedが実行(コントラクト関数が実行)され、新しい値でsetOwner, setFunder, setAddressToAmountFundedが実行される
  4. Fの投資家のアドレス、投資額の表示が更新される
投資をする(引き出す)
  1. Fの「投資をする」ボタンをユーザーが押下し、fund(コントラクト関数)が実行される
  2. Fで実行結果を受けて、成功の場合には、handleSuccessが実行される
  3. Dでユーザー通知が実行され、updateUIが実行される
  4. CでgetOwner, getFunder, getAddressToAmountFundedが実行(コントラクト関数が実行)され、新しい値でsetOwner, setFunder, setAddressToAmountFundedが実行される
  5. Fの投資家のアドレス、投資額の表示が更新される

constants

abi.jsonとcontractAddresses.jsonの出力(手動)

abiとcontractAddressesには、それぞれコントラクトのインターフェース(どういう関数を持っていて、それぞれの関数のインプットとアウトプットの形式)とデプロイされたコントラクトのアドレスを定義しています。これらは、手動でjson形式のものをコピーまたは作成することが可能です。
具体的には、abiは、artifacts/contracts/Funding.sol/Funding.jsonのabiの部分をコピー、contractAddressesは、デプロイの際にコンソール上に表示されたアドレスとデプロイしたブロックチェーン・ネットワークのChain IDから作成できます。

abi.jsonとcontractAddresses.jsonの出力(自動)

コントラクト側のスクリプトにデプロイされた際に自動でファイルを出力させる形での対応も可能です。

TypeScript
// deploy/99-update-frontend-constants.ts
import fs from "fs"
import {DeployFunction} from "hardhat-deploy/types"
import {HardhatRuntimeEnvironment} from "hardhat/types"

const FRONT_END_ADDRESSES_FILE = "../nextjs-funding/constants/contractAddresses.json"
const FRONT_END_ABI_FILE = "../nextjs-funding/constants/abi.json"

const updateConstants: DeployFunction = async function (
    hre: HardhatRuntimeEnvironment
  ) {
    const { network, ethers } = hre
    const chainId = network.config.chainId ? network.config.chainId!.toString() : "31337"
    
    if (process.env.UPDATE_FRONT_END) {
        console.log("Writing abi and contract addresses to frontend...")
        const funding = await ethers.getContract("Funding")
        const contractAddresses = JSON.parse(fs.readFileSync(FRONT_END_ADDRESSES_FILE, "utf8"))
        if (chainId in contractAddresses) {
            if (!contractAddresses[network.config.chainId!].includes(await funding.getAddress())) {
                contractAddresses[network.config.chainId!].push(await funding.getAddress())
            }
        } else {
            contractAddresses[network.config.chainId!] = [await funding.getAddress()]
        }
        fs.writeFileSync(FRONT_END_ADDRESSES_FILE, JSON.stringify(contractAddresses))
        fs.writeFileSync(FRONT_END_ABI_FILE, funding.interface.formatJson())
        console.log("abi and contract addresses written!")
    }
}
export default updateConstants
updateConstants.tags = ["all", "frontend"]

6、7行目の変数は、フロントエンド側のconstantsフォルダ内のファイル(のパス)を定義しています。それぞれ、コントラクトのアドレスをChain ID毎に保存するcontractsAddresses.jsonとABIを保存するabi.jsonです。初回実行時は、次のような空のjsonファイルを作成しておきます。

JSON
{}

18行目では、すでに存在するコントラクトアドレスを保管しているJSON形式のファイルを読み取り、それをTypescriptのオブジェクト形式に変換(JSON.parse)しています。それぞれをもう少し詳細に解説します。

  1. fs: Node.jsの組み込みモジュールであるfs (ファイルシステム) を使用しています。このモジュールは、ファイルの読み書きなどのファイル操作を行うためのAPIを提供します。
  2. fs.readFileSync: readFileSync関数は、指定されたファイルの内容を同期的に読み込みます。つまり、ファイルの読み込みが完了するまで他の操作はブロックされます。
  3. FRONT_END_ADDRESSES_FILE: この定数(もしくは変数)は、読み込むファイルのパスを保持しています。
  4. “utf8”: readFileSyncの第二引数として文字列の”utf8″が渡されています。これはファイルのエンコーディングを示しており、この指定によってファイルはUTF-8エンコーディングで読み込まれます。
  5. JSON.parse: readFileSyncによって読み込まれたファイルの内容(JSON形式の文字列と仮定)をJavaScriptのオブジェクトに変換しています。

19~25行目は、すでに該当するchainIDをファイルが含んでいるかどうかによっての分岐がありますが、要は、24行目のような形式のデータを保存しています。await funding.getAddress()によってデプロイされたコントラクトのアドレスを取得でき、それをchainID(ローカルの場合は31337)に紐づけています(contractAddresses配列のキー31337の要素の値がコントラクトのアドレスとなる)。

最後に、26、27行目で対象のデータをそれぞれのファイルに書き出しています。

  • JSON.stringify(contractAddresses): stringifyは、引数のオブジェクトをJSON形式の文字列に変換します。
  • funding.interface.formatJson(): BaseContract.interface.formatJsonは、ABIをJSON形式の文字列として出力します。BaseContractというのは、ethers.jsで定義されているコントラクト用の基本型になります。保有している変数・関数の詳細については、etherの公式ドキュメントに記載があります。例えば、interface.formatJson関数は、ここに説明があります。

コンソールの表記を実行結果は次のようになりました。

Zsh
# console
% yarn hardhat deploy
yarn run v1.22.19
$ /Users/username/code/token-village/funding/node_modules/.bin/hardhat deploy
Nothing to compile
No need to generate any newer typings.
-----------------------------------------------------
Deploying Funding and waiting for confirmations...
deploying "Funding" (tx: 0xbc444ca6c542123b21a0f1a3515a8d8f2f43b980f8e747417596779369851eda)...: deployed at 0x5FbDB2315678afecb367f032d93F642f64180aa3 with 538126 gas
Funding deployed at 0x5FbDB2315678afecb367f032d93F642f64180aa3
Writing abi and contract addresses to frontend...
abi and contract addresses written!
  Done in 4.96s.
JSON
// nextjs-funding/constants/abi.json
[{"type":"constructor","stateMutability":"undefined","payable":false,"inputs":[]},{"type":"error","name":"Funding__NotEnoughETH","inputs":[]},{"type":"error","name":"Funding__NotOwner","inputs":[]},{"type":"event","anonymous":false,"name":"Funded","inputs":[{"type":"address","name":"funder","indexed":true},{"type":"uint256","name":"fundedAmount","indexed":true}]},{"type":"function","name":"fund","constant":false,"stateMutability":"payable","payable":true,"inputs":[],"outputs":[]},{"type":"function","name":"getAddressToAmountFunded","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"address","name":"funder"}],"outputs":[{"type":"uint256","name":""}]},{"type":"function","name":"getFunder","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"uint256","name":"index"}],"outputs":[{"type":"address","name":""}]},{"type":"function","name":"getOwner","constant":true,"stateMutability":"view","payable":false,"inputs":[],"outputs":[{"type":"address","name":""}]},{"type":"function","name":"withdraw","constant":false,"payable":false,"inputs":[],"outputs":[]}]
JSON
// nextjs-funding/constatns/contractAddresses.json
{"31337":["0x5FbDB2315678afecb367f032d93F642f64180aa3"]}

index.ts

実際に上記のファイルの内容を呼ぶのは、Fund.tsxですが、正しくインポートできるように次のような定義をしておきます。

TypeScript
export { default as contractAddresses } from "./contractAddresses.json";
export { default as abi } from "./abi.json";

Nonceに関するエラー

稀にローカルの実行環境の状況によっては、次の画面のようなエラー「Nonce too high. Expected noce to be x but got x…」といったエラーが出ることがあります。この場合には、Metamaskの場合は、設定 > 高度な設定 > アクティビティとノンスデータを消去 からリセットすることで解消すると思います。

次の画面のようなエラー「Nonce too high. Expected noce to be x but got x...」といったエラーが出ることがあります
Metamaskの場合は、設定 > 高度な設定 > アクティビティとノンスデータを消去 からリセットすることで解消する

ご意見をお聞かせください!

web3チュートリアルシリーズについてのご意見を是非お聞かせください。

この記事、または、web3チュートリアル全体について、是非、あなたのご意見をお聞かせください。
アンケートはこちらからご回答いただけます。

無料相談承ります

ITの専門家が無料相談を受け付けます。web3以外のテーマでもOKです。

オンラインでの無料相談を承っています。ご希望の方は、お問い合わせフォームよりご連絡ください。
ITの専門家があなたのご質問にお答えいたします。

Let's share this post !

Author of this article

After joining IBM in 2004, the author gained extensive experience in developing and maintaining distributed systems, primarily web-based, as an engineer and PM. Later, he founded his own company, designing and developing mobile applications and backend services. He is currently leading a Tech team at a venture company specializing in robo-advisory.

Comments

To comment

CAPTCHA


TOC