2024-03-29
Ubuntu Server 21.10でイーサリアムブロックチェーン【その1】 Ubuntu Server 21.10でイーサリアムブロックチェーン【その2】 Ubuntu Server 21.10でイーサリアムブロックチェーン【その3】 Ubuntu Server 21.10でイーサリアムブロックチェーン【その4】 Ubuntu Server 21.10でイーサリアムブロックチェーン【その5】
続きです。前回はブロックチェーン上で実行できる任意プログラム=コントラクトを作成しましたが、今回は決まった規格にのっとった=決まった変数・関数を実装した=コントラクトを作成してみます。
規格は沢山あって、今最も注目されているのがERC-721という規格です。この規格を満たしたコントラクトが発行したトークンがいわゆるNFTと言われているものです。
仮想通貨がいわゆる「お金そのもの」であるのに対して、トークンとは「商品引換券」であると言えます。ユーザーは仮想通貨を受け取れば換金できるし、トークンを受け取れば商品と交換できるということです。
ブロックチェーンにはこの仮想通貨とトークンが混在して流通しています。ERC-721を満たしたコントラクトを作成することで、非代替性のトークンを自動販売機のように自動で発行させられます。
1、テスト用のNFTデータを作って設置しておく
・NFTのメタ情報を書いたJSON
JSON | nft_test_token_1.json | GitHub Source |
{ "description": "This is NFT token JSON for test only.", "external_url": "https://www.servernote.net/NFT_Test/contents/nft_test_token.html", "image": "https://www.servernote.net/NFT_Test/contents/nft_test_token_1.jpg", "name": "nft_test_token" }
external_urlはとりあえず紹介ページ、imageはNFTを写した写真などの紹介画像。
tokenURI = NFTのベースURI+トークンIDのJSONであるから、ここではトークンID番号1のNFT URIは
https://www.servernote.net/NFT_Test/contents/nft_test_token_1.json
という感じで用意した。
2、開発環境のインストール
ERC-721コントラクトを作成する用に既にSolidityで書かれたクラスライブラリがOpenZeppelinで提供されているので、通常それを継承して使います。Node.js、Truffle、Web3と組み合わせて作成します。
※2022/3/29追記※truffle developではdeployはできてもその後のmintでsendSignedTransactionがTransactionRevertedWithoutReasonErrorを吐いて強制ロールバックされる現象が当方環境で発生中
Node.jsのインストール
curl -L git.io/nodebrew | perl - setup export PATH=$HOME/.nodebrew/current/bin:$PATH nodebrew install-binary v16.7.0 nodebrew use v16.7.0
Truffle、Web3、OpenZeppelinのインストール
truffle, truffle-export-abiはコマンドとして使うので-gをつけてインストール。
truffle-export-abiは、分割コンパイルされた大量のSOL JSONオブジェクト群からコントラクト生成に必要なABI JSONを抜き出してくれるツール。
npm install -g truffle truffle-export-abi
ほかはライブラリとしてインストール。
npm install truffle-hdwallet-provider web3 openzeppelin-solidity
3、OpenZeppelinのクラスを継承してNFTコントラクトのソースを書く
Truffleプロジェクトを作成する。
mkdir NFT_Test cd NFT_Test truffle init
Migrationsは要らないので削除する。
rm contracts/Migrations.sol rm migrations/1_initial_migration.js
ソースコードcontracts/NFT_Test.solを作成する。
以下のような感じ。
Shell | NFT_Test.sol | GitHub Source |
// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; import "openzeppelin-solidity/contracts/token/ERC721/ERC721.sol"; import "openzeppelin-solidity/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; import "openzeppelin-solidity/contracts/utils/Counters.sol"; contract NFT_Test is ERC721URIStorage { using Strings for uint256; using Counters for Counters.Counter; Counters.Counter private _counter; uint private _selling_price = 0.01 ether; // 販売価格 uint private _purchase_price = 0.005 ether; //買取価格 constructor () ERC721 ("NFT_Test", "NFT_TEST") {} //msg = https://web3js.readthedocs.io/en/v1.7.1/web3-eth.html#sendtransaction function mint () public payable returns (uint256) { require(msg.value == _selling_price); //金銭受領確認 _counter.increment(); uint256 tokenId = _counter.current(); _mint(msg.sender, tokenId); _setTokenURI(tokenId, string(abi.encodePacked("https://raw.githubusercontent.com/servernote/NFT_Test/master/contents/nft_test_token_", tokenId.toString(), ".json"))); return tokenId; } function burn (uint256 tokenId) public { require(ownerOf(tokenId) == msg.sender); //所有者か確認 //NULL Address https://etherscan.io/address/0x000000000000000000000000000000000000dead _transfer(msg.sender, 0x000000000000000000000000000000000000dEaD, tokenId); address payable receiver = payable(msg.sender); receiver.transfer(_purchase_price); //買取処理 } }
このサンプルは、新規NFTトークン発行時に支払ってもらう価格を0.01ETHと定めて売り(発行し)、
もしそのトークンを返却するなら、0.005ETH(半額)で買い取ります(焼却します)というもの。
お客さんがmintをコールしてきたとき引数のmsg構造体のvalueで金銭0.01ether払っているかを確認。
OKならカウンタ変数をトークンIDとして、さきのベースURIと連結してtokenURIをセットしている。
買ったお客さんがこんなのいらんから返す=burnをコールしてきた場合、トークンをNULL Addressに捨てて、
お客さんに半額の0.005ETHをtransferして返している。
NULL Addressは以下に説明あり。
https://etherscan.io/address/0x000000000000000000000000000000000000dead
4、コンパイルしてABI JSONを作成する
プロジェクトトップディレクトリに戻ってコンパイルする
cd .. truffle compile
コンパイルが成功したら、ABI JSONを作成する。
truffle-export-abi notice: ABI extracted and output file wrote to: build/ABI.json
build/ABI.jsonにABI情報がまとめられる。このABIとコントラクトのアドレス情報があれば、外部ユーザーがコントラクトを生成できるのは【その5】の通りである。
5、デプロイ(ネットワーク登録)用のmigrateファイルを作成する
migrations/1_deploy_nft_test.jsを作成する。
JavaScript | 1_deploy_nft_test.js | GitHub Source |
const NFT_Test = artifacts.require("NFT_Test"); module.exports = function(deployer) { deployer.deploy(NFT_Test); };
ただ単純にデプロイするだけのもの。
6、Truffle Developテストネットでデプロイする
ここからはTruffleのコンソールでコマンドライン作業をする。truffle developでテストネットワークを起動する。
truffle develop Truffle Develop started at http://127.0.0.1:9545/ Accounts: (0) 0xcf60f2f433767159cdeed84ebda8d0015740f07a (1) 0x20358b38df839b400ec1832a9d40f8f467f317f3 (2) 0xa75e537bc51fca0e5d79f1136e30e7520f6ace17 (3) 0xcd0f6b804ee1cdbcb2f724623670b26e71e36447 (4) 0x8bbced392bc97d0374adbeb411c5794183693064 (5) 0x5dc0231d58d36383ef5d2ffe81cca8fadf80bf12 (6) 0x14c69b7731d024a065cf8f5bd54fb1b43937c1fb (7) 0xd8026e8de5155d2ce381fc6d6fb4f1cf930acb54 (8) 0xc14622e385583ed974f65e2a9ff557e04c981d54 (9) 0x71ba3a6701ef479abb754456918067f9a8c202f5 Private Keys: (0) 41babfe2dbe7f29e09909f30b1dce1653766ca4863bbe918db4995d15fc778bd (1) c10301199c24066ad558f33a82e0f2fd3d6104d84a3bf90eebedb0af91d498f4 (2) f5f342aebe1b4c1c04884ff2f958a7726077beb1278adb664a701d869e792a5a (3) eb30e92f017d0213ca66a3b8b41999f29f036d935724a45a2c57748b7bdfe4d6 (4) 01489b2b16675af429fbf676398b073a0aafb64e37c7918968ca120d14a38368 (5) f53a9d00950b6fa4f3a9d7ab1f164c7e94fd54c8cf8dd1cc46aa6a93ba291a78 (6) 69373797c0a44b32b0a082048e3c2257e3829d936ee556eb7589b238d97b7fa2 (7) 792c76d94545b4b0ae8968722db5277e9f8ea138e52e1e5609c97b0183d85444 (8) 394b48ca297cc836d1206c57dae73b3cd585171429131e719e6612d39b8bfaaa (9) c157cdb6a7b951cd3078cc4f69593ee9474b047035b9e8b6958dbc8feafb783f Mnemonic: already kitten jacket child oil muffin remember nuclear claim nothing scorpion caught ?? Important ?? : This mnemonic was created for you by Truffle. It is not secure. Ensure you do not use it on production blockchains, or else you risk losing funds.
デプロイする
truffle(develop)> migrate Compiling your contracts... =========================== > Everything is up to date, there is nothing to compile. Starting migrations... ====================== > Network name: 'develop' > Network id: 5777 > Block gas limit: 6721975 (0x6691b7) 1_deploy_nft_test.js ==================== Deploying 'NFT_Test' -------------------- > transaction hash: 0x7a3e99ec1858b37e16ba10a726d67589862647a5e008c7f004ad2f4117422073 > Blocks: 0 Seconds: 0 > contract address: 0x985Bfa426BD58F5596878A32b6Fdf3E8adA01E4e > block number: 1 > block timestamp: 1648540572 > account: 0xcf60F2f433767159CdEed84ebdA8D0015740F07a > balance: 99.99108248725 > gas used: 2642226 (0x285132) > gas price: 3.375 gwei > value sent: 0 ETH > total cost: 0.00891751275 ETH > Saving artifacts ------------------------------------- > Total cost: 0.00891751275 ETH Summary ======= > Total deployments: 1 > Final cost: 0.00891751275 ETH
NFT_Testコントラクトがテストネットワークに無事登録された模様。
このコントラクトアドレスとさきのABI JSONがあればどこからでも生成できる。
アカウント0が生成したようなので、残り保有ETH数を見てみる。
web3.eth.getBalance("0xcf60f2f433767159cdeed84ebda8d0015740f07a"); '99991082487250000000'
7、Truffle Developテストネットでmintトランザクションを発行する
ではこのNFTを0.01ETH支払って買ってみる。そのままtruffle developコマンドラインで打っていく。
アカウント0がNFTをデプロイしたわけなので、別アカウントの5から購入を試みる。
CONTRACT_ABI = JSON.parse(fs.readFileSync("./build/ABI.json").toString()); CONTRACT_ADDRESS = "0x985Bfa426BD58F5596878A32b6Fdf3E8adA01E4e"; PUBLIC_KEY = "0x5dc0231d58d36383ef5d2ffe81cca8fadf80bf12"; PRIVATE_KEY = "f53a9d00950b6fa4f3a9d7ab1f164c7e94fd54c8cf8dd1cc46aa6a93ba291a78";
コントラクトをロードし、関数群の確認をする
contract = new web3.eth.Contract(CONTRACT_ABI, CONTRACT_ADDRESS); contract.methods contract.methods.name().call()
mintをコールするためのトランザクションに渡す構造体を定義する。パラメータは以下に説明がある。
https://web3js.readthedocs.io/en/v1.7.1/web3-eth.html#sendtransaction
0.01ETH払いたい=10000000000000000Wei払いたい、になるのでvalueをweiで指定する。
web3.utils.toWei('0.01', 'ether'); '10000000000000000' web3.utils.fromWei('10000000000000000', 'ether'); '0.01'
nonce = await web3.eth.getTransactionCount(PUBLIC_KEY, "latest"); msg = {from: PUBLIC_KEY, to: CONTRACT_ADDRESS, nonce: "1", gas: 53000, gasLimit: 53000, maxPriorityFeePerGas: 1000000000, maxFeePerGas: 1000000000, value: 10000000000000000, data: contract.methods.mint().encodeABI(),}; { from: '0x5dc0231d58d36383ef5d2ffe81cca8fadf80bf12', to: '0x985Bfa426BD58F5596878A32b6Fdf3E8adA01E4e', nonce: '1', gas: 53000, gasLimit: 53000, maxPriorityFeePerGas: 1000000000, maxFeePerGas: 1000000000, value: 10000000000000000, data: '0x1249c58b' }
トランザクションに署名する
signed_msg = await web3.eth.signTransaction(msg, PRIVATE_KEY); '0x02f87782053901843b9aca00843b9aca0082cf0894985bfa426bd58f5596878a32b6fdf3e8ada01e4e87038d7ea4c68000841249c58bc001a0df0ec23baf7bfc9bedfb68bf0c8d5faf8e6c67200abeccd046bb6c055f43dffea0697d01f3ca5282c053196694ded240504536aecb6406af269691ac614b18d8f2'
署名つきトランザクションを送信する
receipt = await web3.eth.sendSignedTransaction(signed_msg); Uncaught Error: Transaction has been reverted by the EVM: { "transactionHash": "0x8d877970e1fe649df5490c3f96481650b477b26c2928459fdb7fc6e36568a6b7", "transactionIndex": 0, "blockNumber": 3, "blockHash": "0x4dda0a1f262df9867f1d93148f0502047b73b9b6da2970078fda17a8d6d0a7db", "from": "0x5dc0231d58d36383ef5d2ffe81cca8fadf80bf12", "to": "0x985bfa426bd58f5596878a32b6fdf3e8ada01e4e", "cumulativeGasUsed": 23350, "gasUsed": 23350, "contractAddress": null, "logs": [], "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "status": false, "effectiveGasPrice": "0x3b9aca00", "type": "0x2" } at Object.TransactionRevertedWithoutReasonError (/home/hogeuser/.nodebrew/node/v16.7.0/lib/node_modules/truffle/build/webpack:/node_modules/web3-core-helpers/lib/errors.js:98:1) at Object.TransactionError (/home/hogeuser/.nodebrew/node/v16.7.0/lib/node_modules/truffle/build/webpack:/node_modules/web3-core-helpers/lib/errors.js:87:1) { receipt: { transactionHash: '0x8d877970e1fe649df5490c3f96481650b477b26c2928459fdb7fc6e36568a6b7', transactionIndex: 0, blockNumber: 3, blockHash: '0x4dda0a1f262df9867f1d93148f0502047b73b9b6da2970078fda17a8d6d0a7db', from: '0x5dc0231d58d36383ef5d2ffe81cca8fadf80bf12', to: '0x985bfa426bd58f5596878a32b6fdf3e8ada01e4e', cumulativeGasUsed: 23350, gasUsed: 23350, contractAddress: null, logs: [], logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', status: false, effectiveGasPrice: '0x3b9aca00', type: '0x2' } }
自分の環境ではなにやらエラーが出て解決しないので、次回以降、truffleはいったんやめてオンラインIDEのRemixでやり直そうと思います。。
Ubuntu Server 21.10でイーサリアムブロックチェーン【その7】
※本記事内容の無断転載を禁じます。
ご連絡は以下アドレスまでお願いします★
Intel Macbook2020にBootCampで入れたWindows11 Pro 23H2のBluetoothを復活させる
Windowsのデスクトップ画面をそのまま配信するための下準備
WindowsでGPUの状態を確認するには(ASUS系監視ソフトの自動起動を停止する)
CORESERVER v1プランからさくらインターネットスタンダートプランへ引っ越しメモ
さくらインターネットでPython MecabをCGIから使う
さくらインターネットのPHPでAnalytics-G4 APIを使う
インクルードパスの調べ方
【Git】特定ファイルを除外する.gitignore
【Ubuntu/Debian】NVIDIA関係のドライバを自動アップデートさせない
【Apache】サーバーに同時接続可能なクライアント数を調整する
Windows版Google Driveが使用中と言われアンインストールできない場合
【Windows10】リモートデスクトップ間のコピー&ペーストができなくなった場合の対処法
Windows11+WSL2でUbuntuを使う【2】ブリッジ接続+固定IPの設定
【Linux】iconv/libiconvをソースコードからインストール
Googleスプレッドシートを編集したら自動で更新日時を入れる
【C/C++】小数点以下の切り捨て・切り上げ・四捨五入
【ひかり電話+VoIPアダプタ】LANしか通ってない環境でアナログ電話とFAXを使う
Windows11でMacのキーボードを使うには