シンプルなERC721のコントラクトをデプロイする手順

BlockBaseのエンジニアの[@suhara_ponta](https://twitter.com/suhara_ponta)です

2021年に入ってからの流行でNFTを知っている人も増えましたが、実際EthereumでNFTなどのコントラクトを開発するときに、どのような手順が必要なのでしょうか。調べてみるとかなり古い情報に当たることが多いので、現時点でのシンプルなやり方を開発初心者の方向けに共有したいと思います。

### 前提
- MacOS Big Sur (Intel)
- Node.jsが入っている

### コントラクト開発の環境構築
プロジェクトのディレクトリを作成して、packagesの中にfrontendとcontractsフォルダを作成する（monorepoにするため）
![スクリーンショット 2021-04-20 21.01.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505435/9efc27a8-ba8a-dcdd-748c-b8c8cf60909d.png)

- contractsに移動して`npm init -y`
- hardhatの初期化
    - `npx hardhat init`
    ![スクリーンショット 2021-04-20 21.08.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505435/63593df4-4ad3-0602-c44a-8033d2310e39.png)
    - create a sample project　(yesで他のnpm modulesも追加しておく)
    - インストールが終わるとフォルダの中に色々生えている
    ![スクリーンショット 2021-04-20 21.12.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505435/93e1e9dc-4833-c430-6251-83e5a13340fa.png)
    - hardhatのテストを動かして正常にインストールできたか確認する `yarn hardhat test`

- package.jsonのscripts部分を以下に変更
    - yarn test：テスト
    - yarn localchain：ローカル環境にブロックチェーンを立ち上げる
    - yarn deploy：ローカルチェーンにコントラクトをデプロイする
    
```javascript:package.json
"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にして内容を変更していく

```solidity: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のバージョンをコントラクトのバージョンに合わせて変更する

```javascript:hardhat.config.ts
module.exports = {
  solidity: "0.8.0",
};
```

- testとscriptsも修正する
    - コントラクトがデプロイできているか(nameが一致しているか)
    - NFTをmintできるか
    - minter role以外がmintできないかをテストで確認している

```javascript:sample-test.js
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")
  });
});
```
```javascript:sample-script.js
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`

```json:packages/frontend/package.json
"scripts": {
  "dev": "serve"
},
```

index.htmlを追加

```html: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を追加

```javascript: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の配列全て）を利用する。
![スクリーンショット 2021-05-17 9.49.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505435/533a9c2a-c2a7-dc5e-7988-5b58c989d060.png)

`yarn dev`でフロントを立ち上げて、テストボタンを押してcontractのnameがconsoleに表示されればフロントとの接続が成功したことがわかる

### monorepo化
rootディレクトリに戻って `npm init -y`
package.jsonに追加

```json:package.json
"private": true,
"workspaces": [
  "packages/**"
],
```
`yarn add -D npm-run-all wait-on`

```json:package.json
"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を作成して

```json:tsconfig.json
{
  "compilerOptions": {
    "target": "es2018",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "noImplicitAny": false
  }
}
```
contractsディレクトリにもtsconfig.jsonを作成して

```json:packages/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/

```json:contracts/networks.json
{
    "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に変更

```typescript: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フォルダを作成し、デプロイのスクリプトを書く

```typescript:contracts/deploy/0_deployAuctionEngine.ts
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追加

```json:package.json
"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`
![スクリーンショット 2021-04-22 11.05.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505435/45e5a450-1178-212b-8e90-4c7aa7c4ed9b.png)
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