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とする。
Shell | NameNFT.sol | GitHub 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はどのように再ロードすればよいのだろうか??日本の参考サイトはどこもこの事に全く触れていない。まさかそのたんびにデプロイしているのではあるまいな??それではアドレスが違う同じコントラクトが何個もできてしまう。デプロイしたことに満足して実用していないのだろうな。苦労して調べた結果、
にようやく求める答えが書いてあり、試したら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'
※本記事内容の無断転載を禁じます。
ご連絡は以下アドレスまでお願いします★
さくらインターネットでPython MecabをCGIから使う
さくらインターネットのPHPでAnalytics-G4 APIを使う
インクルードパスの調べ方
【Git】特定ファイルを除外する.gitignore
【Ubuntu/Debian】NVIDIA関係のドライバを自動アップデートさせない
【Python】Spacyを使用して文章から出発地と目的地を抜き出す
HomeBrewでApache2を入れて自動起動つきで動かしPHPモジュールと連携する
macOSに標準付属のApacheを自動起動つきで動かす
HomeBrewでPostgreSQLを入れて自動起動つきで動かす
Windows版Google Driveが使用中と言われアンインストールできない場合
【C/C++】小数点以下の切り捨て・切り上げ・四捨五入
進研ゼミチャレンジタッチをAndroid端末化する
Windows11+WSL2でUbuntuを使う【2】ブリッジ接続+固定IPの設定
Googleスプレッドシートで図形をコピーして使いまわすには
【Linux】iconv/libiconvをソースコードからインストール
【Apache】サーバーに同時接続可能なクライアント数を調整する
Pythonで処理にかかった時間を計測するには
Windows11のコマンドプロンプトでテキストをコピーする