アプリケーション開発ポータルサイト
ServerNote.NET
Amazon.co.jpでPC関連商品タイムセール開催中!
カテゴリー【仮想通貨UbuntuDebian
Ubuntu Server 21.10でイーサリアムブロックチェーン【その6】
POSTED BY
2023-03-30

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

JSONnft_test_token_1.jsonGitHub 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を作成する。

以下のような感じ。

sourceNFT_Test.solGitHub 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を作成する。

JavaScript1_deploy_nft_test.jsGitHub 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】

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

☆ServerNote.NETショッピング↓
ShoppingNote / Amazon.co.jp
☆お仲間ブログ↓
一人社長の不動産業務日誌
【キーワード検索】