lesson-3_コントラクトと接続しよう
これまでフロントエンドにUIを用意し、ウォレットとの接続も出来ました!
このレッスンではあなたのスマートコントラク トをデプロイし、フロントエンドと連携します。
🌵 コントラクトとの接続部分を実装しましょう
引き続きpackages/client
ディレクトリのファイルを操作していきます。
📁 hooks
ディレクトリ
hooks
ディレクトリ内にuseContract.ts
というファイルを作成し、以下のコードを記述してください。
💁 現時点ではまだ用意していないファイルからimportしている箇所があるためエラーメッセージが出ても無視して大丈夫です。
import { BigNumber, ethers } from "ethers";
import { useEffect, useState } from "react";
import { USDCToken as UsdcContractType } from "../typechain-types";
import { JOEToken as JoeContractType } from "../typechain-types";
import { AMM as AmmContractType } from "../typechain-types";
import AmmArtifact from "../utils/AMM.json";
import { getEthereum } from "../utils/ethereum";
import UsdcArtifact from "../utils/USDCToken.json";
import JoeArtifact from "../utils/USDCToken.json";
export const UsdcAddress = "コントラクトのデプロイ先アドレス";
export const JoeAddress = "コントラクトのデプロイ先アドレス";
export const AmmAddress = "コントラクトのデプロイ先アドレス";
export type TokenType = {
symbol: string;
contract: UsdcContractType | JoeContractType;
};
export type AmmType = {
sharePrecision: BigNumber;
contract: AmmContractType;
};
type ReturnUseContract = {
usdc: TokenType | undefined;
joe: TokenType | undefined;
amm: AmmType | undefined;
};
export const useContract = (
currentAccount: string | undefined
): ReturnUseContract => {
const [usdc, setUsdc] = useState<TokenType>();
const [joe, setJoe] = useState<TokenType>();
const [amm, setAmm] = useState<AmmType>();
const ethereum = getEthereum();
const getContract = useCallback(
(
contractAddress: string,
abi: ethers.ContractInterface,
storeContract: (_: ethers.Contract) => void
) => {
if (!ethereum) {
console.log("Ethereum object doesn't exist!");
return;
}
if (!currentAccount) {
// ログインしていない状態でコントラクトの関数を呼び出すと失敗するため
// currentAccountがundefinedの場合はcontractオブジェクトもundefinedにします。
console.log("currentAccount doesn't exist!");
return;
}
try {
const provider = new ethers.providers.Web3Provider(
ethereum as unknown as ethers.providers.ExternalProvider
);
const signer = provider.getSigner(); // 簡易実装のため、引数なし = 初めのアカウント(account#0)を使用する
const Contract = new ethers.Contract(contractAddress, abi, signer);
storeContract(Contract);
} catch (error) {
console.log(error);
}
},
[ethereum, currentAccount]
);
const generateUsdc = async (contract: UsdcContractType) => {
try {
const symbol = await contract.symbol();
setUsdc({ symbol: symbol, contract: contract } as TokenType);
} catch (error) {
console.log(error);
}
};
const generateJoe = async (contract: UsdcContractType) => {
try {
const symbol = await contract.symbol();
setJoe({ symbol: symbol, contract: contract } as TokenType);
} catch (error) {
console.log(error);
}
};
const generateAmm = async (contract: AmmContractType) => {
try {
const precision = await contract.PRECISION();
setAmm({ sharePrecision: precision, contract: contract } as AmmType);
} catch (error) {
console.log(error);
}
};
useEffect(() => {
getContract(UsdcAddress, UsdcArtifact.abi, (Contract: ethers.Contract) => {
generateUsdc(Contract as UsdcContractType);
});
getContract(JoeAddress, JoeArtifact.abi, (Contract: ethers.Contract) => {
generateJoe(Contract as JoeContractType);
});
getContract(AmmAddress, AmmArtifact.abi, (Contract: ethers.Contract) => {
generateAmm(Contract as AmmContractType);
});
}, [ethereum, currentAccount, getContract]);
return {
usdc,
joe,
amm,
};
};
ファイル上部は必要な関数などのimportと、型定義をしています。 💁 現時点ではまだ用意していないファイルからimportしている箇所があるためエラーメッセージが出ても無視して大丈夫です。
export type TokenType = {
symbol: string;
contract: UsdcContractType | JoeContractType;
};
フロントエンドで使用するトークンコントラクトのオブジェクトの型になります。
USDCまたはJOEのコントラクトのインスタンスとトークンシンボルをstring型で保持します。
export type AmmType = {
sharePrecision: BigNumber;
contract: AmmContractType;
};
フロントエンドで使用するAMMコントラクトのオブジェクトの型になります。
AMMのコントラクトのインスタンスとPRECISIONを保持します。
const getContract = useCallback(
(
contractAddress: string,
abi: ethers.ContractInterface,
storeContract: (_: ethers.Contract) => void
) => {
if (!ethereum) {
console.log("Ethereum object doesn't exist!");
return;
}
if (!currentAccount) {
// ログインしていない状態でコントラクトの関数を呼び出すと失敗するため
// currentAccountがundefinedの場合はcontractオブジェクトもundefinedにします。
console.log("currentAccount doesn't exist!");
return;
}
try {
const provider = new ethers.providers.Web3Provider(
ethereum as unknown as ethers.providers.ExternalProvider
);
const signer = provider.getSigner(); // 簡易実装のため、引数なし = 初めのアカウント(account#0)を使用する
const Contract = new ethers.Contract(contractAddress, abi, signer);
storeContract(Contract);
} catch (error) {
console.log(error);
}
},
[ethereum, currentAccount]
);
getContract
は引数で指定されたアドレスとabiのコントラクトのインスタンスを取得する関数です。
インスタンスを取得できたら引数で渡された関数に渡します。
storeContract(Contract);
const generateUsdc = async (contract: UsdcContractType) => {
try {
const symbol = await contract.symbol();
setUsdc({ symbol: symbol, contract: contract } as TokenType);
} catch (error) {
console.log(error);
}
};
const generateJoe = async (contract: UsdcContractType) => {
try {
const symbol = await contract.symbol();
setJoe({ symbol: symbol, contract: contract } as TokenType);
} catch (error) {
console.log(error);
}
};
const generateAmm = async (contract: AmmContractType) => {
try {
const precision = await contract.PRECISION();
setAmm({ sharePrecision: precision, contract: contract } as AmmType);
} catch (error) {
console.log(error);
}
};
それぞれ引数で渡されたコントラクトのインスタンスから、フロントエンドで使用するオブジェクトに変換しています。
useEffect(() => {
getContract(UsdcAddress, UsdcArtifact.abi, (Contract: ethers.Contract) => {
generateUsdc(Contract as UsdcContractType);
});
getContract(JoeAddress, JoeArtifact.abi, (Contract: ethers.Contract) => {
generateJoe(Contract as JoeContractType);
});
getContract(AmmAddress, AmmArtifact.abi, (Contract: ethers.Contract) => {
generateAmm(Contract as AmmContractType);
});
}, [ethereum, currentAccount, getContract]);
各コントラクトの取得からオブジェクトの作成までを行っています。
💥 コントラクトをテストネットにデプロイしましょう
コントラクトとの接続部分を作成したので、コントラクトを使用するために、テストネットへデプロイします。
packages/contract
ディレクトリへ移動してください。
.env
という名前のファイルを作成し、以下を記入してください。
"YOUR_PRIVATE_KEY"
の部分をあなたのアカウントの秘密鍵と入れ替えてください。
TEST_ACCOUNT_PRIVATE_KEY="YOUR_PRIVATE_KEY"
YOUR_PRIVATE_KEY
の取得
- お使いのブラウザから、MetaMask プラグインをクリックして、ネットワークを
Avalanche FUJI C-Chain
に変更します。
- それから、
Account details
を選択してください。
Account details
からExport Private Key
をクリックしてください。
- MetaMask のパスワードを求められるので、入力したら
Confirm
を押します。 あなたの秘密鍵(=Private Key
)が表示されるので、クリックしてコピーします。
.env
のYOUR_PRIVATE_KEY
の部分をここで取得した秘密鍵とを入れ替えます。
⚠️gitignoreファイルに.envが記述されていることを確認して下さい。 秘密鍵は外部に漏れないようにGitHub上に上げません。
✍️: スマートコントラクトをデプロイするのに秘密鍵が必要な理由 > 新しくスマートコントラクトをブロックチェーン上にデプロイすることも、トランザクションの一つです。
トランザクションを行うためには、ブロックチェーンに「ログイン」する必要があります。
「ログイン」には公開アドレスと秘密鍵の情報が必要となります。
次にpackages/contract
ディレクトリ直下にあるhardhat.config.ts
中身を以下のコードに書き換えてください。
※ solidityのバージョンの部分(solidity: '0.8.17',
)は元々記載されているものを使用してください。
import * as dotenv from "dotenv"; // 環境構築時にこのパッケージはインストールしてあ ります。
import "@nomicfoundation/hardhat-toolbox";
import { HardhatUserConfig } from "hardhat/config";
// .envファイルから環境変数をロードします。
dotenv.config();
if (process.env.TEST_ACCOUNT_PRIVATE_KEY === undefined) {
console.log("private key is missing");
}
const config: HardhatUserConfig = {
solidity: "0.8.17",
networks: {
fuji: {
url: "https://api.avax-test.network/ext/bc/C/rpc",
chainId: 43113,
accounts:
process.env.TEST_ACCOUNT_PRIVATE_KEY !== undefined
? [process.env.TEST_ACCOUNT_PRIVATE_KEY]
: [],
},
},
};
export default config;
続いて、scripts
ディレクトリ内にあるdeploy.ts
を以下のコードに書き換えてください。
import { ethers } from "hardhat";
async function deploy() {
// コントラクトをデプロイするアカウントのアドレスを取得します。
const [deployer] = await ethers.getSigners();
// USDCトークンのコントラクトをデプロイします。
const USDCToken = await ethers.getContractFactory("USDCToken");
const usdc = await USDCToken.deploy();
await usdc.deployed();
// JOEトークンのコントラクトをデプロイします。
const JOEToken = await ethers.getContractFactory("JOEToken");
const joe = await JOEToken.deploy();
await joe.deployed();
// AMMコントラクトをデプロイします。
const AMM = await ethers.getContractFactory("AMM");
const amm = await AMM.deploy(usdc.address, joe.address);
await amm.deployed();
console.log("usdc address:", usdc.address);
console.log("joe address:", joe.address);
console.log("amm address:", amm.address);
console.log("account address that deploy contract:", deployer.address);
}
deploy()
.then(() => process.exit(0))
.catch((err) => {
console.error(err);
process.exit(1);
});
deploy
関数の中身はtest/AMM.ts
内のdeployContract
関数と同じようなことをしています。
このスクリプトを実行する際に先ほどhardhat.config.ts
で設定したネットワークを指定すると、ethers.getSigners()
の返す初めのアカウントの値はあなたのアカウントのアドレスになります。
ターミナル上でAVAX-AMM/
直下にいることを確認して、下記を実行しましょう。
yarn contract deploy
このような出力結果が出たら成功です!
yarn run v1.22.19
$ yarn workspace contract deploy
$ npx hardhat run scripts/deploy.ts --network fuji
usdc address: 0x5aC2B0744ACD8567c1c33c5c8644C43147645770
joe address: 0x538589242114BCBcD0f12B1990865E57b3344448
amm address: 0x1d09929346a768Ec6919bf89dae36B27D7e39321
account address that deploy contract: 0xdf90d78042C8521073422a7107262D61243a21D0
ログに出力された各アドレスはコントラクトがデプロイされた先のアドレスです。
次の項目で必要になるのでどこかに保存しておいてください。
最後に.gitignore
に.env
が含まれていることを確認してください!