BlockBaseのエンジニアの@suhara_pontaです
2021年に入ってからの流行でNFTを知っている人も増えましたが、実際EthereumでNFTなどのコントラクトを開発するときに、どのような手順が必要なのでしょうか。調べてみるとかなり古い情報に当たることが多いので、現時点でのシンプルなやり方を開発初心者の方向けに共有したいと思います。
前提
- MacOS Big Sur (Intel)
- Node.jsが入っている
コントラクト開発の環境構築
プロジェクトのディレクトリを作成して、packagesの中にfrontendとcontractsフォルダを作成する(monorepoにするため)
-
contractsに移動して
npm init -y
-
hardhatの初期化
-
package.jsonのscripts部分を以下に変更
- yarn test:テスト
- yarn localchain:ローカル環境にブロックチェーンを立ち上げる
- yarn deploy:ローカルチェーンにコントラクトをデプロイする
"scripts": {
"test": "hardhat test",
"localchain": "hardhat node",
"deploy": "hardhat run scripts/sample-script.js --network localhost"
},
※yarn localchainでチェーンを立ち上げている状態で、別のターミナルのタブを開き、yarn deployして試してみる
NFTコントラクトの開発
-
yarn add @openzeppelin/contracts
でOpenZeppelinのコントラクトを使えるようになる - contract/contarcts内のGreeter.solをNFT.solにして内容を変更していく
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
import "hardhat/console.sol";
import "@openzeppelin/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol";
contract NFT is ERC721PresetMinterPauserAutoId {
constructor() ERC721PresetMinterPauserAutoId("name", "symbol", "http://localhost:3000") {}
}
ERC721PresetMinterPauserAutoIdというpresetを継承して、name, symbol, tokenURIを固定で引数に渡しているコード
- hardhat.config.tsのsolidityのバージョンをコントラクトのバージョンに合わせて変更する
module.exports = {
solidity: "0.8.0",
};
- testとscriptsも修正する
- コントラクトがデプロイできているか(nameが一致しているか)
- NFTをmintできるか
- minter role以外がmintできないかをテストで確認している
const { expect } = require("chai");
describe("NFT", function() {
it("NFT basic test", async function() {
const [signer, badSigner] = await ethers.getSigners();
const NFT = await ethers.getContractFactory("NFT");
const nft = await NFT.deploy();
expect(await nft.name()).to.equal("name");
await nft.mint(signer.address);
expect(await nft.balanceOf(signer.address)).to.equal(1);
await expect(nft.connect(badSigner).mint(signer.address)).to.revertedWith("ERC721PresetMinterPauserAutoId: must have minter role to mint")
});
});
const hre = require("hardhat");
async function main() {
const NFT = await hre.ethers.getContractFactory("NFT");
const nft = await NFT.deploy();
await nft.deployed();
console.log("Nft deployed to:", nft.address);
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
フロントエンド(本題ではないのでスキップしてもOK)
frontendディレクトリに移動してnpm init -y
yarn add -D serve
"scripts": {
"dev": "serve"
},
index.htmlを追加
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>title</title>
<link rel="stylesheet" href="style.css">
<script src="https://cdn.ethers.io/lib/ethers-5.0.umd.min.js" type="text/javascript"></script>
<script src="script.js"></script>
</head>
<body>
<h1>workshop</h1>
<p>test</p>
<button id="test">Button</button>
</body>
</html>
script.jsを追加
window.onload = async function() {
const abi = [] // packages/contracts/artifacts/NFT.sol/NFT.jsonからコピペする
}
const provider = new ethers.providers.Web3Provider(window.ethereum);
const address = "0x5FbDB2315678afecb367f032d93F642f64180aa3"
const contract = new ethers.Contract(address, abi, provider);
document.getElementById("test").onclick = async () => {
console.log(await contract.name())
}
abiはcontractをデプロイして生成されるもの(abiの配列全て)を利用する。
yarn dev
でフロントを立ち上げて、テストボタンを押してcontractのnameがconsoleに表示されればフロントとの接続が成功したことがわかる
monorepo化
rootディレクトリに戻って npm init -y
package.jsonに追加
"private": true,
"workspaces": [
"packages/**"
],
yarn add -D npm-run-all wait-on
"scripts": {
"test": "yarn workspace contracts test",
"dev": "run-p dev:*",
"dev:run-localchain": "yarn workspace contracts localchain",
"dev:deploy-contract-to-localchain": "wait-on http://localhost:8545 && yarn workspace contracts deploy",
"dev:frontend": "wait-on http://localhost:8545 && yarn workspace frontend dev"
},
yarn dev
で立ち上がる
ローカルでの開発環境は以上で構築できる。
typescript化(スキップしてもOK)
tsを使いたいのでrootディレクトリでyarn add -D typescript ts-node ts-generator
rootディレクトリにtsconfig.jsonを作成して
{
"compilerOptions": {
"target": "es2018",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"noImplicitAny": false
}
}
contractsディレクトリにもtsconfig.jsonを作成して
{
"extends": "../../tsconfig.json",
"include": ["./scripts", "./test", "arguments.js", "./deploy"],
"files": ["./hardhat.config.ts"]
}
テスト環境へのデプロイ
hardhat-deployを使ってデプロイする
contractsディレクトリにnetworks.jsonを作成
infuraのエンドポイントが必要になるので作成してください https://infura.io/
{
"rinkeby": {
"chainId": "4",
"rpc": "https://rinkeby.infura.io/v3/{YOUR_OWN_PROJECT}",
},
"localhost": {
"chainId": "31337",
"rpc": "http://localhost:8545",
}
}
contractsディレクトリで yarn add -D hardhat-deploy
hardhat.config.jsをhardhat.config.tsに変更
import "@nomiclabs/hardhat-waffle";
import "@nomiclabs/hardhat-ethers";
import "hardhat-deploy";
import network from "./networks.json";
const privateKey = process.env.PRIVATE_KEY || "0x0000000000000000000000000000000000000000000000000000000000000000"; // this is to avoid hardhat error
module.exports = {
solidity: "0.8.0",
namedAccounts: {
deployer: 0,
},
networks: {
hardhat: {
blockGasLimit: 10000000,
},
rinkeby: {
url: network.rinkeby.rpc,
accounts: [privateKey],
},
}
};
deployフォルダを作成し、デプロイのスクリプトを書く
import { HardhatRuntimeEnvironment } from 'hardhat/types';
import { DeployFunction } from 'hardhat-deploy/types';
const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { deployments, getNamedAccounts } = hre;
const { deploy } = deployments;
const { deployer } = await getNamedAccounts();
const { address } = await deploy('NFT', {
from: deployer,
args: [],
log: true,
});
};
export default func;
func.tags = ['NFT'];
package.jsonにscript追加
"scripts": {
"test": "hardhat test",
"localchain": "hardhat node",
"deploy": "hardhat deploy --network localhost --reset",
"deploy-rinkeby": "hardhat deploy --network rinkeby --reset"
},
※この時点でscripts/sample-script.jsは不要になるので削除してもOK
RinkebyのETHを持っている秘密鍵を用意する
ターミナルでexport PRIVATE_KEY="用意した秘密鍵"
contractsディレクトリでyarn deploy-rinkeby
rinkebyのEtherscanでデプロイできているか確認できる
https://rinkeby.etherscan.io/address/0xa10f1134b2E1Cdb364C77A172458b8AADD2f7FB5
以上になります!
BlockBaseでは誰でも簡単に独自コントラクトのNFTを作成できるChocofactoryというサービスを提供しています。(ソースコードはOSSです)
Chocofactory:https://factory.chocomint.app/
github:https://github.com/chocomintapp/chocofactory
開発の様子などもDiscordで公開しているのでぜひご覧ください
https://discord.com/invite/7P3NChCxhM