Day28では開発環境のセットアップを行いました。このチュートリアルでは、いよいよweb3.0のためのフロントエンドの開発を行っていきます。
ファイル構造
Day28で言及した構造のうち、主にページを開発する上で編集するファイルは次のものになります。
.
├── 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
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.tsx
やabout.tsx
など)をレンダリングします。ページごとのpropsはpageProps
を通して渡されます。
※ propsとは、簡単に言うと、親コンポーネントから子コンポーネントへデータを渡す手段として使用されます。コード例は次の通りです。
// コード例
// 親コンポーネント
function ParentComponent() {
return <ChildComponent greeting="Hello, World!" />;
}
// 子コンポーネント
function ChildComponent(props) {
return <h1>{props.greeting}</h1>;
}
pages/index.tsx
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
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つに分けて解説します。
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. 変数定義
const { chainId: chainIdHex, isWeb3Enabled, account } = useMoralis()
useMoralis
というフックを呼び出して、Moralisから取得した情報を分割代入しています。具体的には、
chainIdHex
: 現在接続しているブロックチェーンネットワークのID。続いての15行目で16進数から10進数の文字列に変換しています。isWeb3Enabled
: Web3が有効化されているか(画面を表示しているユーザーがウォレットを接続しているか)どうかのブール値。account
: 現在接続しているユーザーのEthereumアドレスを示す。
※ useMoralisフックで取得できる値は、この表に定義されています。
const fundingAddress = chainId in addresses ? addresses[chainId][0] : null
三項演算子を使って、chainId
がaddresses
オブジェクトのキーとして存在するかどうかをチェックしています。
- もし存在する場合、
addresses[chainId][0]
を取得してfundingAddress
に代入します。これは、特定のネットワークの最初のコントラクトアドレスを示していると思われます。 - 存在しない場合、
fundingAddress
はnullになります。
addresses
オブジェクトの値は、31337: ['0x5FbDB2315678afecb367f032d93F642f64180aa3']
といった形のものになります(localhostのみの場合。後述、constantsの項も参照)。
19〜22行目は、ReactのuseState
を用いてowner
、funder
、index
、addressToAmountFunded
という4つの状態変数を定義しています。それぞれの状態変数には、状態を変更するための関数(setOwner
、setFunder
など)が関連付けられています。これらの状態変数の値が変更されると、それを使用しているコンポーネントが再レンダリングされます。
const [index, setIndex] = useState("0")
例えば、index
は投資家のインデックス番号(何番目に投資しかを示す番号)を表しますが、これは後述のFでユーザーからの番号入力を受け付けて、新しい番号になる度にsetIndex
が呼ばれ、index
の値が更新されます。index
は画面の表示には利用されていませんが、新しいindex
になる度にuseEffect
が実行され(Eで解説)、投資額などの付随的な情報が更新され、画面表示に反映されます。
24行目は、web3uikitの機能でユーザーに通知を行うための関数を定義しています。これは、Dで利用されており、この例の場合には、画面の右上にinfoレベルとして、トランザクションが正常に終了した旨のメッセージを表示する、というものです。
25行目は、アカウント情報からアドレス(このフロントエンドの画面に接続しているユーザーのアドレス)を取得しています。
const userAddress = account ? ethers.getAddress(account!) : ""
B. コントラクトの関数定義
useWeb3Contract
というMoralisのHookを利用して、ブロックチェーン・ネットワークに対してスマートコントラクトの関数を実行するための準備を行っています。次の例は、投資家のindex
番号から投資家のアドレスを取得するための関数を実行します。
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の仕組みによって、画面の表示が更新されます。
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",
})
}
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
useEffect(() => {
if (isWeb3Enabled) {
updateUI()
}
}, [isWeb3Enabled, index, addressToAmountFunded])
useEffectフックは、2つめの引数(依存配列)を監視し、これらの値が変更された際に、1つ目の引数の処理を実行するものです。つまり、最初にisWeb3Enabledの値を読み込んだ際に、これが真であれば(ウォレットがこのサイトに接続されていれば)、updateUI関数を実行し、画面を表示し、その後は、indexやaddressToAmountFundedの値が更新されたら、updateUIが実行されます。
F. 表示画面
Fの部分は、画面を表示する部分の処理が記述されています。次のような画面が表示されます(一番上のタイトルとアドレスが書いている箇所はHeader.tsxで定義されているものになります。それより下の箇所がFの部分で定義されているものです)。
Fの箇所では、通常のHTMLに加えて、いくつかweb3uikitのコンポーネントを利用しています。具体的には、InputコンポーネントとButtonコンポーネントです。
<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の値変更に伴って、投資家関連の情報が一通り更新されます)。
<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番号を入力する
- Fで投資家のindex番号をユーザーが入力し、setIndexが実行される
- Eで
index
の値を監視しているので、値が更新されたたため、updateUIが実行される - CでgetOwner, getFunder, getAddressToAmountFundedが実行(コントラクト関数が実行)され、新しい値でsetOwner, setFunder, setAddressToAmountFundedが実行される
- Fの投資家のアドレス、投資額の表示が更新される
投資をする(引き出す)
- Fの「投資をする」ボタンをユーザーが押下し、fund(コントラクト関数)が実行される
- Fで実行結果を受けて、成功の場合には、handleSuccessが実行される
- Dでユーザー通知が実行され、updateUIが実行される
- CでgetOwner, getFunder, getAddressToAmountFundedが実行(コントラクト関数が実行)され、新しい値でsetOwner, setFunder, setAddressToAmountFundedが実行される
- Fの投資家のアドレス、投資額の表示が更新される
constants
abi.jsonとcontractAddresses.jsonの出力(手動)
abiとcontractAddressesには、それぞれコントラクトのインターフェース(どういう関数を持っていて、それぞれの関数のインプットとアウトプットの形式)とデプロイされたコントラクトのアドレスを定義しています。これらは、手動でjson形式のものをコピーまたは作成することが可能です。
具体的には、abiは、artifacts/contracts/Funding.sol/Funding.json
のabiの部分をコピー、contractAddressesは、デプロイの際にコンソール上に表示されたアドレスとデプロイしたブロックチェーン・ネットワークのChain IDから作成できます。
abi.jsonとcontractAddresses.jsonの出力(自動)
コントラクト側のスクリプトにデプロイされた際に自動でファイルを出力させる形での対応も可能です。
// 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ファイルを作成しておきます。
{}
18行目では、すでに存在するコントラクトアドレスを保管しているJSON形式のファイルを読み取り、それをTypescriptのオブジェクト形式に変換(JSON.parse
)しています。それぞれをもう少し詳細に解説します。
- fs: Node.jsの組み込みモジュールである
fs
(ファイルシステム) を使用しています。このモジュールは、ファイルの読み書きなどのファイル操作を行うためのAPIを提供します。 - fs.readFileSync:
readFileSync
関数は、指定されたファイルの内容を同期的に読み込みます。つまり、ファイルの読み込みが完了するまで他の操作はブロックされます。 - FRONT_END_ADDRESSES_FILE: この定数(もしくは変数)は、読み込むファイルのパスを保持しています。
- “utf8”:
readFileSync
の第二引数として文字列の”utf8″が渡されています。これはファイルのエンコーディングを示しており、この指定によってファイルはUTF-8エンコーディングで読み込まれます。 - 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関数は、ここに説明があります。
コンソールの表記を実行結果は次のようになりました。
# 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.
// 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":[]}]
// nextjs-funding/constatns/contractAddresses.json
{"31337":["0x5FbDB2315678afecb367f032d93F642f64180aa3"]}
index.ts
実際に上記のファイルの内容を呼ぶのは、Fund.tsxですが、正しくインポートできるように次のような定義をしておきます。
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の場合は、設定 > 高度な設定 > アクティビティとノンスデータを消去 からリセットすることで解消すると思います。
ご意見をお聞かせください!
この記事、または、web3チュートリアル全体について、是非、あなたのご意見をお聞かせください。
アンケートはこちらからご回答いただけます。
無料相談承ります
オンラインでの無料相談を承っています。ご希望の方は、お問い合わせフォームよりご連絡ください。
ITの専門家があなたのご質問にお答えいたします。
Comments