アプリケーション開発ポータルサイト
ServerNote.NET
カテゴリー【仮想通貨Node.jsUbuntu
最新Node.js環境でプライベートブロックチェーンとNFTを構築する
POSTED BY
2024-05-10

自社で構築したプライベートチェーンにてNFTアイテムを発行管理して、ユーザーが望むなら本物のイーサリアムパブリックチェーンに出庫するようなサービスを考える。

自前でブロックチェーンを構築するにはNodeのモジュールである「Truffle」「Ganache」「OpenZeppelin」を使うのが鉄板である。このあたりの仕様は日々進化しているため、なるべくならときの最新モジュールを使っていきたい。

1、Node.js環境の初期化(削除)と最新版の入れ直し

システムでなく$HOMEに入れているのならいったん全削除(※自己責任注意)

cd $HOME
rm -Rf .nodebrew .npm node_modules

最新版を入れ直す

cd $HOME
curl -L git.io/nodebrew | perl - setup
nodebrew install-binary v22.1.0
nodebrew use v22.1.0

2、Truffle, Ganache, OpenZeppelinのインストール

cd $HOME
npm install -g ganache
npm install -g truffle
npm install @openzeppelin/contracts

3、Ganacheを単体起動しランダムチェーン情報を得る

まずはganacheとだけ打って単体起動させ、ランダムな初期アカウント群(ETH保持)とネットワークIDを確定させる。

ganache

ganache v7.9.2 (@ganache/cli: 0.10.2, @ganache/core: 0.10.2)
Starting RPC server
Migrating database from version `null` to `0`…
Migration complete

Available Accounts
==================
(0) 0x464F4A6940C8a74ffa563892A85A6EBd2030643c (1000 ETH)
(1) 0x8C25A0314D864b4A5974339E4255325Dab86ad6b (1000 ETH)
(2) 0x131BD92afD5756FE508eCCD4F65E1CbF9d9ddEe7 (1000 ETH)
(3) 0x0e6f0EfAeC2a6c73B0182D8B689D2C8A777eC6CC (1000 ETH)
(4) 0xa5ec5e0e749EbAD1Fce6cDA9e87A68Ed9e257CB1 (1000 ETH)
(5) 0xeb0722fa1D72c307f7371F6f07fd6AD78411a8f2 (1000 ETH)
(6) 0xE513eC642378A6f308848Ecf8a562c4d8146b7FB (1000 ETH)
(7) 0xD77Ecf6430439034e1dD9AE28CfA22c6D58F8E07 (1000 ETH)
(8) 0x8bA8F34c7891052cC53A8B6B635370f3a7c33e42 (1000 ETH)
(9) 0xA8460900900d1b004C655Ee0698B97aC652ff4E3 (1000 ETH)

Private Keys
==================
(0) 0xc5d469e6e999822720a2ce6b3046ba4000be0ab219dbbe4aae4dec0bd477300c
(1) 0x6b039a97577d59781d82aa66c2ec183a82620a94d19935ce0eb215010bbbc1a7
(2) 0x5d0fd2dc7c7711664757e8b70985b2ce228e535649f820c91f94ba98400a4486
(3) 0x1d5c1b17989b44a26a7e9d6fbdedae68a5bcd3284883d204e3f6d8325b4b7554
(4) 0x8cf641596f9c1425e970b136fa3c200a2f582a19cdd1e74b04b01ef6b5a93cfd
(5) 0xd3f9b0eded43bb2f23c2d4382437b39c09252a44a7bdfd622177aab0541e9a13
(6) 0x37091b654223263f9a46826f6523a6b9da30a80c481950b6ec188c7f61f6f917
(7) 0x6cbec54ac72308606bdfc39cfbdebfbc9626b73a954c7eb3f9260e91b0ae44e4
(8) 0x0de7e770fdc6c904a125befb8ce95af40c9f91eb4bdede642a0b579df8bed348
(9) 0xa98be3513c96949789da1846c748b1f24afc89236b5f0621052c60c24c0d4266

HD Wallet
==================
Mnemonic:      shoot ivory truly only regular resource agent junior auto issue crew rookie
Base HD Path:  m/44'/60'/0'/0/{account_index}

Default Gas Price
==================
2000000000

BlockGas Limit
==================
30000000

Call Gas Limit
==================
50000000

Chain
==================
Hardfork: shanghai
Id:       1337

RPC Listening on 127.0.0.1:8545

Mnemonicは判明したがネットワークIDがわからないため、仮のTruffleでコンソールから取得する。

mkdir truffle_temp
cd truffle_temp
truffle init
vi truffle-config.js
// 以下、コメントをはずす
development: {
     host: "127.0.0.1",     // Localhost (default: none)
     port: 8545,            // Standard Ethereum port (default: none)
     network_id: "*",       // Any network (default: none)
    },

8545ポートで起動している任意のネットワークIDに接続する設定を有効にし、

truffle console

とし、

truffle(development)> accounts
[
  '0x464F4A6940C8a74ffa563892A85A6EBd2030643c',
  '0x8C25A0314D864b4A5974339E4255325Dab86ad6b',
  '0x131BD92afD5756FE508eCCD4F65E1CbF9d9ddEe7',
  '0x0e6f0EfAeC2a6c73B0182D8B689D2C8A777eC6CC',
  '0xa5ec5e0e749EbAD1Fce6cDA9e87A68Ed9e257CB1',
  '0xeb0722fa1D72c307f7371F6f07fd6AD78411a8f2',
  '0xE513eC642378A6f308848Ecf8a562c4d8146b7FB',
  '0xD77Ecf6430439034e1dD9AE28CfA22c6D58F8E07',
  '0x8bA8F34c7891052cC53A8B6B635370f3a7c33e42',
  '0xA8460900900d1b004C655Ee0698B97aC652ff4E3'
]
truffle(development)>  web3.eth.net.getId()
1715326484756
truffle(development)>  web3.eth.getChainId()
1337
truffle(development)> .exit

とし、ネットワークIDが1715326484756であることが判明した。さきほど取得したニーモニックとあわせて起動時に指定すれば、いつでもこのアカウント設定のGanacheが起動できる。

4、Ganacheをデータ保持+デーモンモードで起動する

ブロックチェーン本体のGanache、公式サイトのドキュメントが実際のコマンド機能に追いついていない。自分でhelpを見る。

ganache --help

デーモンモードは--detach、詳細ログは--logging.verbose=true、データ保持パスは--database.dbPath=PATHで指定、ログファイルは--logging.file=PATHで指定、ニーモニックは--wallet.mnemonic=MNEMONIC、ネットワークIDは--chain.networkId=NETWORKIDで指定すればよいことがわかる。

データ保持パスをganache_dataとしてデーモンモードでさきほどの初期アカウントとネットワークIDを再現して起動する。

mkdir ganache_data
ganache --detach --logging.verbose=true --database.dbPath=ganache_data --logging.file=ganache_data/ganache.log --wallet.mnemonic="shoot ivory truly only regular resource agent junior auto issue crew rookie" --chain.networkId="1715326484756"

以下でデーモンを確認、名前を指定で停止できる。

ganache instances list

┌────────┬───────────────────────┬──────────┬─────────┬────────────────┬────────┐
│    PID │ Name                  │ Flavor   │ Version │ Host           │ Uptime │
├────────┼───────────────────────┼──────────┼─────────┼────────────────┼────────┤
│ 198515 │ deepfried_milk_friand │ ethereum │ 7.9.2   │ 127.0.0.1:8545 │ 4m 36s │
└────────┴───────────────────────┴──────────┴─────────┴────────────────┴────────┘

ganache instances stop deepfried_milk_friand

Instance stopped

5、TruffleからネットワークIDを正式に指定して接続する

TruffleはSolidity言語ソースをコンパイルしてコントラクト作成&ブロックチェーンに登録する機能を持つが、consoleコマンドにより、ブロックチェーンに接続してWeb3.jsのコマンドを実行し情報を見たり送金操作ができたりする。

作業ディレクトリ作成、初期化

mkdir truffle_work
cd truffle_work
truffle init

truffle-config.jsで以下の部分をアンコメントし、デーモン起動中のGanacheのネットワークIDを明示指定する

development: {
     host: "127.0.0.1",     // Localhost (default: none)
     port: 8545,            // Standard Ethereum port (default: none)
     network_id: "1715326484756",       // Any network (default: none)
    },

以下truffle consoleで、上記ホスト&ポート=さっきデーモンで起動させたGanache=へ接続する。

truffle console

ganache_data/ganache.logに、接続してきた旨が記録されいるか確認。

あとはWeb3.jsのコマンドが直接打てる。

https://web3js.readthedocs.io/en/v1.10.0/

truffle(development)> accounts
[
  '0x464F4A6940C8a74ffa563892A85A6EBd2030643c',
  '0x8C25A0314D864b4A5974339E4255325Dab86ad6b',
  '0x131BD92afD5756FE508eCCD4F65E1CbF9d9ddEe7',
  '0x0e6f0EfAeC2a6c73B0182D8B689D2C8A777eC6CC',
  '0xa5ec5e0e749EbAD1Fce6cDA9e87A68Ed9e257CB1',
  '0xeb0722fa1D72c307f7371F6f07fd6AD78411a8f2',
  '0xE513eC642378A6f308848Ecf8a562c4d8146b7FB',
  '0xD77Ecf6430439034e1dD9AE28CfA22c6D58F8E07',
  '0x8bA8F34c7891052cC53A8B6B635370f3a7c33e42',
  '0xA8460900900d1b004C655Ee0698B97aC652ff4E3'
]
truffle(development)> web3.eth.net.getId()
1715326484756
truffle(development)> web3.eth.getChainId()
1337
truffle(development)> web3.eth.getBalance('0x464F4A6940C8a74ffa563892A85A6EBd2030643c')
'1000000000000000000000'
.exit

ニーモニックによりアカウントが再現されており、ネットワークIDも一致し1000ETH所持してることも確認できた。

6、NFT発行コントラクトを作成して登録する

ERC-721規格に則ったコントラクトコードをSOLで書く。truffle_work/contracts/NameNFT.solとする。

ShellNameNFT.solGitHub Source
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.11;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

contract NameNFT is ERC721 {
    mapping(uint256 => string) public tokenName;

    constructor(string memory name, string memory symbol)
        ERC721(name, symbol)
    {}

    function createToken(uint256 tokenId, string memory name) external {
        _safeMint(msg.sender, tokenId);
        tokenName[tokenId] = name;
    }

    function getTokenName(uint256 tokenId) external view returns (string memory) {
        return tokenName[tokenId];
    }
}

コンパイルする

truffle compile

truffle_work/build/contracts/NameNFT.jsonにABIファイルが作成されたようだ。

コンソールでデプロイする

truffle console
truffle(development)> instance = await NameNFT.new('NameNFTTestName','NameNFTTestSymbol')
truffle(development)> instance.transactionHash
'0xd9ac42e60131c5ed42203e672ae0b7e52ac8991725cae67f1fb308a7196cfa76'
truffle(development)> web3.eth.getTransaction('0xd9ac42e60131c5ed42203e672ae0b7e52ac8991725cae67f1fb308a7196cfa76');
{
  type: 2,
  hash: '0xd9ac42e60131c5ed42203e672ae0b7e52ac8991725cae67f1fb308a7196cfa76',
  chainId: '0x539',
  nonce: 0,
  blockHash: '0x7c4670c180a097e84a2455c403e338eabd72c7a63e73f4a2853d2d10feed0c61',
  blockNumber: 1,
  transactionIndex: 0,
  from: '0x464F4A6940C8a74ffa563892A85A6EBd2030643c',
  to: null,
  value: '0',
  maxPriorityFeePerGas: '2500000000',
  maxFeePerGas: '4500000000',
  gasPrice: '3375000000',
  gas: '0x26a220',
  input: '0x608060405234801562000010575f80fd5b5060405162002819380380620028198339818101604052810190620000369190620001ea565b818'... 10916 more characters,
  accessList: [],
  v: '0x1',
  r: '0x991d0321388bec546a3dfc8716a5744b58b188c2f99b3e636f9f5948709bbccf',
  s: '0xde500b21a01933b4ca3bc9f8ae8c0d65afd9975dcb52e9409dd932665083994',
  yParity: '0x1'
}

コントラクト生成トランザクションが成功した。

truffle(development)> web3.eth.getBalance('0x464F4A6940C8a74ffa563892A85A6EBd2030643c')
'999993163944250000000'

ガス代を消費しているのがわかる。truffle consoleでは、先頭のaccounts[0]が自分のアカウントとなるようだ。

7、NFT(Non Fungible Token)を発行する

生成したインスタンスがどのようなメソッドを持っているかは、instance.とした状態でTABキーを押すと出てくる。

truffle(development)> instance.
instance.__proto__             instance.hasOwnProperty        instance.isPrototypeOf         instance.propertyIsEnumerable  instance.toLocaleString
instance.toString              instance.valueOf

instance.Approval              instance.ApprovalForAll        instance.Transfer              instance.abi                   instance.address
instance.allEvents             instance.approve               instance.balanceOf             instance.call                  instance.constructor
instance.contract              instance.createToken           instance.estimateGas           instance.getApproved           instance.getPastEvents
instance.getTokenName          instance.isApprovedForAll      instance.methods               instance.name                  instance.ownerOf
instance.safeTransferFrom      instance.send                  instance.sendTransaction       instance.setApprovalForAll     instance.supportsInterface
instance.symbol                instance.tokenName             instance.tokenURI              instance.transactionHash       instance.transferFrom

truffle(development)> instance.address
'0xDb5dCB56Ad8B67dFa6ec9c79aB8c4381cefeDE65'

では、createTokenでトークンID11223344のNFTを発行する。

truffle(development)> instance.createToken(11223344, 'Hello this is NameNFT Token ID 11223344')
{
  tx: '0xe129ed833ea3866a3957707fa541fa904cea9b083343f10c7e2833fe7f69f62a',
  receipt: {
    transactionHash: '0xe129ed833ea3866a3957707fa541fa904cea9b083343f10c7e2833fe7f69f62a',
    transactionIndex: 0,
    blockNumber: 2,
    blockHash: '0x5879919b2d9b0a5285b8850433cb206928434038e6f19890f15f17b08fd699c8',
    from: '0x464f4a6940c8a74ffa563892a85a6ebd2030643c',
    to: '0xdb5dcb56ad8b67dfa6ec9c79ab8c4381cefede65',
    cumulativeGasUsed: 137734,
    gasUsed: 137734,
    contractAddress: null,
    logs: [ [Object] ],
    logsBloom: '0x00000000000000000000000000000000000000000000000000010000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000001000000000000000000000000000000000000020000000000000000001800000000000000000000000010000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000800000000080000000020000000000200000000000000000000000000000000000000000000000000000000',
    status: true,
    effectiveGasPrice: 3280394257,
    type: '0x2',
    rawLogs: [ [Object] ]
  },
  logs: [
    {
      address: '0xDb5dCB56Ad8B67dFa6ec9c79aB8c4381cefeDE65',
      blockHash: '0x5879919b2d9b0a5285b8850433cb206928434038e6f19890f15f17b08fd699c8',
      blockNumber: 2,
      logIndex: 0,
      removed: false,
      transactionHash: '0xe129ed833ea3866a3957707fa541fa904cea9b083343f10c7e2833fe7f69f62a',
      transactionIndex: 0,
      id: 'log_0c420c32',
      event: 'Transfer',
      args: [Result]
    }
  ]
}
truffle(development)> instance.getTokenName(11223344);
'Hello this is NameNFT Token ID 11223344'
truffle(development)> instance.ownerOf(11223344);
'0x464F4A6940C8a74ffa563892A85A6EBd2030643c'

バッチリ文字列NFTが発行され発行者が所有者にセットされている。

8、TruffleとGanache両方を終了させて、再度復旧させる

ganache instances stopでGanacheを終了させても、--dbオプションを指定しているので、指定ディレクトリにブロックチェーンは丸ごと保存されている。全く同じ起動オプションで起動すればチェーンは復帰する。

さてTruffleのほうだが、.exitで抜けて、再度truffle consoleしたときに、さっき作ったコントラクトのinstanceはどのように再ロードすればよいのだろうか??日本の参考サイトはどこもこの事に全く触れていない。まさかそのたんびにデプロイしているのではあるまいな??それではアドレスが違う同じコントラクトが何個もできてしまう。デプロイしたことに満足して実用していないのだろうな。苦労して調べた結果、

https://ethereum.stackexchange.com/questions/45305/truffle-develop-get-new-contract-instance-from-contract-address

にようやく求める答えが書いてあり、試したらinstanceをロードできた。

ブロックチェーンにデプロイ済みのコントラクトを変数に再ロードする方法

truffle(development)> let sol = artifacts.require("./contracts/NameNFT.sol");
truffle(development)> let instance = await sol.at('0xDb5dCB56Ad8B67dFa6ec9c79aB8c4381cefeDE65');

これでOK!コントラクトのソースを読み込んで、デプロイ済みアドレスをatするとのこと。以下ステータス確認で紛れもなくさきほどのトークン発行までのチェーン状態を再現できていることが確認できる。

truffle(development)> instance.address
'0xDb5dCB56Ad8B67dFa6ec9c79aB8c4381cefeDE65'
truffle(development)> instance.getTokenName(11223344);
'Hello this is NameNFT Token ID 11223344'
truffle(development)> instance.ownerOf(11223344);
'0x464F4A6940C8a74ffa563892A85A6EBd2030643c'

※本記事は当サイト管理人の個人的な備忘録です。本記事の参照又は付随ソースコード利用後にいかなる損害が発生しても当サイト及び管理人は一切責任を負いません。
※本記事内容の無断転載を禁じます。
【WEBMASTER/管理人】
自営業プログラマーです。お仕事ください!
ご連絡は以下アドレスまでお願いします★

【キーワード検索】