Table of Contents

Truffle

Abstract

コマンドラインベースで自らの Smart Contract を作り試験環境に NFT を Mint するところまでの忘備録

Glossary

Smart Contract		: 契約をバイトコードでプログラムとして定義したもの
Deploy			: Smart Contract を Blockchain 上に配置すること
Mint			: Deploy した Smart Contract に従い NFT を発行すること
Solidity		: Blockchain で走らせる Smart Contract を書くためのプログラム言語
OpenZeppelin		: Solidity を使用した Smart Contract 用のライブラリ集
Truffle			: Solidity を使用したトークン処理のフレームワーク
OpenSea			: 世界最大の NFT マーケットプレイス
Infura			: Blockchain API

Preparation

接続の際に以下でプロジェクトを作成して取得した Project ID を使用する
https://infura.io/

OpenSea で試験的に結果を確認する場合は Rinkeby という試験用ネットワークを使用する
Rinkeby の使用料金(ガス代)は 0.1 ETH/day なら無料で以下から取得可能
https://rinkebyfaucet.com/

Install to Debian

必要なパッケージを追加

sudo apt update
sudo apt install nodejs
sudo apt install npm
sudo npm install -g truffle

バージョン確認

truffle version

作業ディレクトリ初期化

mkdir test
cd test
truffle init

初期コンパイルテスト

truffle compile

必要なパッケージを追加

npm init -y
npm install @openzeppelin/contracts
npm install @truffle/hdwallet-provider

以下2つのファイルは差分取得に使用されるため削除はしないこと (see Truffle Tutorial)

## ./contracts/Migrations.sol
## ./migrations/1_initial_migration.js

Smart Contract (OpenZeppelin)

OpenZeppelin ERC721
https://docs.openzeppelin.com/contracts/api/token/erc721

Smart Contract の実装 (アドレスは適宜編集)

touch ./contracts/Test.sol
vi ./contracts/Test.sol
================================
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/Context.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract Test is Context, ERC721Burnable, Ownable {
  constructor(string memory name, string memory symbol) ERC721(name, symbol) {}
  
  function _baseURI() internal view virtual override returns (string memory) {
    return "https://192.168.0.1/nft/";
  }
  
  function mint(address to, uint256 tokenId) public virtual onlyOwner {
    _mint(to, tokenId);
  }
}
================================

コンパイル

truffle compile

主な注意点

Migration Script

Smart Contract を Deploy するためのスクリプト
ファイル名の先頭は数字で連番にしておく必要がある

touch ./migrations/2_deploy_contracts.js
vi ./migrations/2_deploy_contracts.js
================================
var Test = artifacts.require("Test");

module.exports = async function(deployer) {
  await deployer.deploy(Test, "Test Contract", "TEST");
  const instance = await Test.deployed();
  console.log("Contract Address: ", instance.address);
};
================================

Mint Script

Deploy した Smart Contract で Mint するためのスクリプト (アドレスは適宜編集)
最後に callback() すなわち deployer() を宣言しないとスクリプトが終了しないので注意

mkdir scripts
touch ./scripts/test_mint_1.js
vi ./scripts/test_mint_1.js
================================
var Test = artifacts.require("Test");
const tokenId = 1;

module.exports = async function(deployer) {
  const instance = await Test.deployed();
  console.log("Contract Address: ", instance.address);
  await instance.mint("0x1234...[WALLET_ADDRESS]", tokenId);
  console.log("Token URI: ", await instance.tokenURI(tokenId));
  console.log("Owner: ", await instance.ownerOf(tokenId));
  console.log("");
  console.log("Done");
  deployer();
};
================================

Truffle Configuration

Truffle Infura
https://trufflesuite.com/guides/using-infura-custom-provider/

Wallet の秘密フレーズ(もしくは暗号鍵も可能)をファイルに登録

vi .secret
================================
orange apple banana ...
================================

設定ファイルを編集 (今回は Rinkeby の試験ネットワークを使用)
あらかじめ Infura で作成しておいた Project ID を以下で定義

vi ./truffle-config.js
================================
const HDWalletProvider = require("@truffle/hdwallet-provider");
const fs = require("fs");
const mnemonic = fs.readFileSync(".secret").toString().trim();

module.exports = {
  networks: {
    rinkeby: {
      provider: () => new HDWalletProvider(mnemonic, "https://rinkeby.infura.io/v3/[YOUR-PROJECT-ID]"),
      network_id: 4,
      gas: 5500000,
      confirmations: 2,
      timeoutBlocks: 200,
      skipDryRun: true
    },
    mumbai: {
      provider: () => new HDWalletProvider(mnemonic, "https://polygon-mumbai.infura.io/v3/[YOUR-PROJECT-ID]"),
      network_id: 80001,
      gas: 5500000,
      confirmations: 2,
      timeoutBlocks: 200,
      skipDryRun: true
    }
  },
  compilers: {
    solc: {
      version: "^0.8.0"
    }
  }
}
================================

コンソール接続が可能なことを確認

truffle console --network rinkeby

Metadata Preparation

トークンで紐づけるメタデータを用意して Web サーバに置く (アドレスは適宜編集)
現状の設定だと tokenId がそのままメタデータのファイル名になる

vi 1
================================
{
  "name": "NFT Test #01",
  "image": "https://192.168.0.1/image/1.png",
  "external_url": "https://192.168.0.1/",
  "description": "NFT Test #01",
  "attributes": [{"trait_type": "Tag", "value": "Test"}]
}
================================

Data Preparation

メタデータで定義した画像ファイルを Web サーバに置く

ちゃんとやるなら Pinata の様な IPFS も検討した方が良い

Truffle Migration

Smart Contract を実際にデプロイする

truffle compile --network rinkeby | tee out_compile.log
truffle migrate --network rinkeby | tee out_migrate.log

デプロイに関しては 0.0006 + 0.0066 = 0.0072 ETH ほどガス代を使用

Starting migrations...
======================
> Network name:    'rinkeby'
> Network id:      4
> Block gas limit: 30000000 (0x1c9c380)


1_initial_migration.js
======================

   Deploying 'Migrations'
   ----------------------
   > transaction hash:    0x83fd ...
   > Blocks: 1            Seconds: 8
   > contract address:    0x9005 ...
   > block number:        1091 ...
   > block timestamp:     1656 ...
   > account:             0xD55B ...
   > balance:             0.3618 ...
   > gas used:            250154 (0x3d12a)
   > gas price:           2.500000007 gwei
   > value sent:          0 ETH
   > total cost:          0.000625385001751078 ETH

   Pausing for 2 confirmations...

   -------------------------------
   > confirmation number: 1 (block: 1091 ...)
   > confirmation number: 2 (block: 1091 ...)
   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:     0.000625385001751078 ETH


2_deploy_contracts.js
=====================

   Deploying 'Test'
   ----------------
   > transaction hash:    0xa9946 ...
   > Blocks: 0            Seconds: 12
   > contract address:    0x3248 ...
   > block number:        1091 ...
   > block timestamp:     1656 ...
   > account:             0xD55B ...
   > balance:             0.3551 ...
   > gas used:            2639926 (0x284836)
   > gas price:           2.500000007 gwei
   > value sent:          0 ETH
   > total cost:          0.006599815018479482 ETH

   Pausing for 2 confirmations...

   -------------------------------
   > confirmation number: 1 (block: 1091 ...)
   > confirmation number: 2 (block: 1091 ...)
Contract Address:  0x3248 ...
   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:     0.006599815018479482 ETH

Summary
=======
> Total deployments:   2
> Final cost:          0.00722520002023056 ETH

デプロイした Smart Contract で NFT を Mint する
Mint 処理に関しては 0.0002 ETH ほどガス代を使用

truffle exec ./scripts/test_mint_1.js --network rinkeby | tee out_mint.log

以下から結果を確認
https://testnets.opensea.io/account

Truffle Commands

truffle compile							: Smart Contract Compile
truffle deploy							: Smart Contract Deploy
truffle migrate							: Smart Contract Compile and Deploy
truffle migrate --reset						: Smart Contract Compile and Deploy from scratch
truffle console							: ノードに接続
truffle console --network rinkeby				: 設定したノードに接続
truffle develop							: 開発用ノードに接続
truffle exec ./scripts/mint.js --network rinkeby		: 個別に実行

Truffle Console

以下コマンドでデプロイした結果を確認できる (./build/contracts 以下にコンパイル済みの json が存在する必要がある)

truffle console --network rinkeby
> let instance = await test.deployed()
> instance
> let instance_at = test.at("0x1234...[CONTRACT_ADDRESS]")
> instance_at
> instance.address

Mint by OpenSea vs Truffle

OpenSea で Mint すると以下のような属性になる

TokenID = 96503965242968.....
Token Tracker = OpenSea Collections (OPENSTORE)
Token Standard = ERC-1155
Token From = NullAddress (0x0000000000000000000000000000000000000000)
Token To = You

今回の Truffle で Mint すると以下のような属性になる

TokenID = 1
Token Tracker = [None]
Token Standard = ERC-721
Token From = NullAddress (0x0000000000000000000000000000000000000000)
Token To = You

Can We Burn NFT ?

どのような形であれ Blockchain にいちど登録してしまうと変更や削除は実質的に不可能となるので注意
登録した Smart Contract のバイトコードは Etherscan で全て確認可能

後から Smart Contract を変更するスキームとして以下の様なものも考えられている
https://docs.openzeppelin.com/upgrades-plugins/

発行(Mint)した NFT なら以下の Burn Address に送ると手元からは無くなるが、それでもデータが消えることはない

0x000000000000000000000000000000000000dEaD

ERC721Burnable で Smart Contract を作成していた場合は burn 関数で NullAddress に NFT を送ることは可能
その場合もデータが消えることはなく、あくまで NullAddress の持ち物になるだけ

touch ./scripts/test_burn_1.js
vi ./scripts/test_burn_1.js
================================
var Test = artifacts.require("Test");
const tokenId = 1;

module.exports = async function(deployer) {
  const instance = await Test.deployed();
  console.log("Contract Address: ", instance.address);
  console.log("Token URI: ", await instance.tokenURI(tokenId));
  console.log("Owner: ", await instance.ownerOf(tokenId));
  await instance.burn(tokenId);
  console.log("");
  console.log("Done");
  deployer();
};
================================
truffle exec ./scripts/test_burn_1.js --network rinkeby | tee out_burn.log

References

Truffle
https://trufflesuite.com/tutorial/

Solidity
https://soliditylang.org/

OpenZeppelin
https://www.openzeppelin.com/