LoginSignup
26
23

More than 1 year has passed since last update.

NFTを自分でつくってみる

Posted at

はじめに

今話題となっているNFTについて、自分が身につけた技術を活用しながら軽めに実装してみようと思います。
NFTの開発を通してブロックチェーン技術についての知見共有になれば幸いです。

この記事の対象者
NFTがやたらと話題になっているけど、中身はどんなものが動いているの?というのが気になる方
やること
ブロックチェーンの開発言語やフレームワークを利用したスマートコントラクト開発(題材としてNFT)
やらないこと
ブロックチェーンやNFTの知識・仕組みの詳解(NFTの概要のみ記述)

ブロックチェーンの各用語・各技術については最低限しか触れていないので、不明点は他ソースなどを参照ください。

スマートコントラクト 契約の自動化する仕組み。ブロックチェーンよりも前から提唱されている概念であり、有名な例として自動販売機がある。 (自動販売機におけるスマートコントラクト:機械による(複数人による合意のない)販売契約の自動化) ブロックチェーンの中では狭義として、ブロックチェーン上に記録され、自動的に成立するトランザクション(取引記録)のことを表す。

NFTとは?

2021年のバズワードとも言われるように流行った「NFT」について軽く説明をします。
そもそもNFTとは「Non-Fungible Token」の略で非代替性トークンという意味です。
通貨が代替性のある(1つ1つのトークンに同一の価値のある)トークンとして存在する一方で、トークン同士の代替が不可能なものとしてNFTの仕組みがあります。
非代替性のトークンを利用することで、唯一無二のモノ(NFTでいうとデジタルコンテンツ)に価値をもたせることができます。

NFTを支える仕組みとして、ブロックチェーン技術があります。
ブロックチェーン技術の説明は割愛しますが、ブロックチェーン技術を活用することでNFTを実装することができます。

開発環境の準備

今回は下記の開発環境でNFTを作成してみる。
後々出てくる言語やフレームワークなどのversionもまとめて下記にまとめています。

OS
- Ubuntu-20.04LTS on WSL2(Windows11)

Truffle
- Truffle v5.4.7 (core: 5.4.7)
- Solidity - 0.8.11 (solc-js)
- Node v16.7.0
- Web3.js v1.5.2

Ganache (on Windows)
v2.5.4

VSCode

プロジェクトフォルダの作成 (Truffleの利用)

Ethereumのスマートコントラクト開発用フレームワークであるTruffleを利用する。

作業用フォルダ作成後、truffleコマンドを利用してプロジェクトフォルダを作成する。

$ truffle init

truffle initを実行することで、コントラクト開発に必要な最低限のテンプレートが自動作成されます。

ネットワーク準備 (Ganacheの利用)

Ethereumのプライベートネットワークを簡単に作成できるGanacheを利用する。

image.png
「NEW WORKSPACE」を押して利用するワークスペースの名前を付ける(画像にあるNFT-Sample(ethereum)は作成したワークスペース)

今回はWSL2環境で開発をすすめるので、SERVERのHOSTNAMEでは画像のようになる形で設定
image.png

上記設定を元に先程truffle initで作成されたtruffle-config.jsにネットワーク設定を記載する

truffle-config.js
// ↑other settings ↑
  networks: {
    development: {
      host: "192.168.112.1",
      port: 7545,
      network_id: "5777",
    }
  }
// ↓other settings ↓

上記まで設定し、truffle cosoleを実行してtruffleのコンソールが表示されればネットワークの準備完了。

$ truffle console
truffle(development)>

上記まで終了すれば、ローカル環境でプライベートネットワークを利用して、自分の開発したスマートコントラクトでトークンのやり取りなどができます。

スマートコントラクトの実装

ERC721の準備

開発環境が整ったので、実際にNFTを作成するためのスマートコントラクトを開発します。
NFTはERC721に準拠したトークンとなります。

ERC(Ethereum Request for Comments) トークンの動作を実装するスマートコントラクトの共通の仕様を定めたもの。

上記のように、ERCはあくまでトークンの動作の共通規格(= Interface, 以後IF)を定義したものなので、IFを参照しながら独自にスマートコントラクトを開発することも可能です。

IFを基に独自のスマートコントラクト開発も可能ですが、今回はOpenZeppelinが作ったライブラリを活用することで簡単にERC721に準拠したトークンを作っていきます。

npmでOpenZeppelinライブラリを導入する。
プロジェクトのルートディレクトリ(truffle initを実行したディレクトリ)でnpm install openzeppelin-solidityを実行する。

$ npm install  openzeppelin-solidity

実行後、node_moduleフォルダ配下にopenzeppelin-solidityフォルダが生成される。

トークン作成用solidityファイルの作成

独自のNFTを作成するためのsolidityファイルを作成する。

Solidity スマートコントラクトを実装するためのオブジェクト指向プログラミング言語

contractフォルダ配下でNFT作成用のsolidityファイルを作成し、OpenZeppelinのERC721を継承する。
これだけでERC721に必要なステートやメソッド、イベントを持ったスマートコンストラクトに早変わりです。

MyNFT.sol
pragma solidity 0.8.11;

import "openzeppelin-solidity/contracts/token/ERC721/ERC721.sol";

contract MyNFT 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];
    }
}

継承元のERC721のコンストラクタの引数にname_symbol_があるため、継承先のコンストラクタの引数で対応する引数を入れる必要があります。
ERC721で実装されているステートやメソッド、イベントについては補足として最下部にまとめました。
また、今回は独自の実装として以下のようなステートとメソッド、イベントを定義しました。

tokenName
発行したNFTに紐づく名前ステート
IssueToken(uint256 tokenId, string tokenName)
NFTを発行する関数
function getTokenName(uint256 tokenId) external view returns (string memory)
対象のNFTの名前を取得する関数

動作確認

上記で実装したスマートコントラクトを利用して実際にNFTを発行するところまでやってみます。

動作確認準備(Truffle, Ganacheの準備)

環境準備のときに利用したTruffleコンソールを利用していきます。

$ truffle console  
truffle(development)> 

まずはスマートコントラクトを新たに実装したので、compile --allを実行します。

truffle(development)> compile --all

Compiling your contracts...
===========================
> Compiling ./contracts/Migrations.sol
> Compiling ./contracts/MyNFT.sol
> Compiling openzeppelin-solidity/contracts/token/ERC721/ERC721.sol
> Compiling openzeppelin-solidity/contracts/token/ERC721/IERC721.sol
> Compiling openzeppelin-solidity/contracts/token/ERC721/IERC721Receiver.sol
> Compiling openzeppelin-solidity/contracts/token/ERC721/extensions/IERC721Metadata.sol
> Compiling openzeppelin-solidity/contracts/utils/Address.sol
> Compiling openzeppelin-solidity/contracts/utils/Context.sol
> Compiling openzeppelin-solidity/contracts/utils/Strings.sol
> Compiling openzeppelin-solidity/contracts/utils/introspection/ERC165.sol
> Compiling openzeppelin-solidity/contracts/utils/introspection/IERC165.sol
> Compilation warnings encountered:

    Warning: SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: <SPDX-License>" to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information.
--> project:/contracts/MyNFT.sol


> Artifacts written to /home/wataru/Workspace/NFT_Sample/build/contracts
> Compiled successfully using:
   - solc: 0.8.11+commit.d7f03943.Emscripten.clang


truffle(development)> 

※ライセンスの明記が無いため、Warningが発生していますが、本トピックでは問題無いため、割愛します。
次に、NFTを発行するために実装したスマートコントラクトをデプロイします。

truffle(development)> instance = await MyNFT.new('testName','testSymbol')
undefined
truffle(development)> instance.transactionHash
'0x8448614004ce5779e4b438e396688b384ee892137c4267008bab4fe63990e3b1'
truffle(development)> 

上記のように変数に格納する形でデプロイすることで、transactionHash関数を利用してデプロイ時のトランザクションハッシュを確認することが出来ます。

Ganacheの「TRANSACTION」タブを確認するとスマートコントラクトをデプロイしたトランザクションハッシュと一致するトランザクションがあることが確認できます。

image.png

NFT発行と軽いトークン操作

上記まで完了したら実際にNFTを発行できます。上記手順同様にtruffleコンソールにて独自のトークン発行関数createTokenを利用してNFTを発行します。

truffle(development)> instance.createToken(3141592, 'Hello World!!!')
{
  tx: '0xcd126bad6bcaf558ea57cff55452aeea4b2f8e86d233180c46163452c4b1949b',
  receipt: {
    transactionHash: '0xcd126bad6bcaf558ea57cff55452aeea4b2f8e86d233180c46163452c4b1949b',
    transactionIndex: 0,
    blockHash: '0x07395322279eca89f3aaf978af17e26593d47e9e15a783b04d9ad946ec17052a',
    blockNumber: 2,
    from: '0x516423dcb54d1091d5f035238e4cbccbd620a092',
    to: '0xbcc3c99c160c447488f8f27e42baf866b714697e',
    gasUsed: 92741,
    cumulativeGasUsed: 92741,
    contractAddress: null,
    logs: [ [Object], [Object] ],
    status: true,
    logsBloom: '0x00000000000000000000000000000000000000000200000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000080000000000000000000020000000000400000000800000002000000000000000010000000001000000000080000000000000000000000000000000800000000000001000000000000000000000000000000000000000000000000000000004000000000000000001002000000000000000000000002000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000',
    rawLogs: [ [Object], [Object] ]
  },
  logs: [
    {
      logIndex: 0,
      transactionIndex: 0,
      transactionHash: '0xcd126bad6bcaf558ea57cff55452aeea4b2f8e86d233180c46163452c4b1949b',
      blockHash: '0x07395322279eca89f3aaf978af17e26593d47e9e15a783b04d9ad946ec17052a',
      blockNumber: 2,
      address: '0xbcC3C99C160C447488f8f27e42bAF866b714697E',
      type: 'mined',
      id: 'log_4cb86583',
      event: 'Transfer',
      args: [Result]
    },
    {
      logIndex: 1,
      transactionIndex: 0,
      transactionHash: '0xcd126bad6bcaf558ea57cff55452aeea4b2f8e86d233180c46163452c4b1949b',
      blockHash: '0x07395322279eca89f3aaf978af17e26593d47e9e15a783b04d9ad946ec17052a',
      blockNumber: 2,
      address: '0xbcC3C99C160C447488f8f27e42bAF866b714697E',
      type: 'mined',
      id: 'log_fd8d15ab',
      event: 'IssueToken',
      args: [Result]
    }
  ]
}

上記のようにcreateToken実行後に沢山のログが流れますが、一番最初のtxを参考にGanacheを確認してみると対象のトランザクションログを確認することが出来ます。

image.png

これでNFTの発行まで完了です。

独自のスマートコントラクトで作ったgetTokenNameメソッドを利用して発行したトークンの名前を取得することもできます。

truffle(development)> instance.getTokenName(3141592)
'Hello World!!!'
truffle(development)> 

また、ERC721ライブラリで実装されているメソッドも利用してトークン操作も可能です。
例えば、ownerOfを利用すると指定したNFTの所有者を確認できます。

truffle(development)> instance.ownerOf(3141592)
'0x516423dCb54D1091d5f035238e4CbccBD620a092'
truffle(development)> 

今回は独自メソッドでトークン発行者を所有者とする形で実装したので、トークンを発行したアカウントのアドレスが返ってきます。
アカウントのアドレスもGanacheで確認できます。

image.png

上記のようにERC721の標準規格を利用することで、対象アカウントがいくつトークンを所有しているのかを確認したり、トークンの移譲をするなどのトークン操作を簡単に実施出来ます。

以上で簡単にNFTの発行から操作まで実践してみました。

さいごに

話題になってる波に乗ってブロックチェーンの勉強がてらNFTの中身に触れてみました。
ERC721にはこの記事をきっかけで触っていること、まだまだブロックチェーン技術に半人前なので分かりづらい箇所もあったと思いますが、
少しでもNFTやブロックチェーン技術について知るきっかけになれば嬉しいです。

ご指摘事項などあれば優しく教えて下さい。。

参考文献

文献中に出てきていない参考文献となります。

【補足】ERC721の中身

もともとERC721で持っているステートやメソッド、イベントについても上述したOpenZeppelinライブラリを参考に軽くまとめます。

ステート

※変数名などに具体的な規則はないため、変数名はOpenZeppelinのERC721より引用

_name
トークンコレクションの名前
_symbol
トークンのシンボル
_owners
トークンの所有者
_balances
各トークン所有者が所持しているトークンの数
_tokenApprovals
トークンのやり取りについて承認されているアドレス
_operatorApprovals
トークン所有者として許可するオペレータのアドレス

メソッド

balanceOf(address _owner) external view returns (uint256)
指定したアドレス(_owner)が所持しているトークンの数を返却する
ownerOf(uint256 _tokenId) external view returns (address)
指定したトークン(_tokenId)の所有者のアドレスを返却する
safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable
安全に移転元(_From)から移転先(_to)に指定したトークン(_tokenId)を移転する
safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable
安全に移転元(_From)から移転先(_to)に指定したトークン(_tokenId)を移転する
transferFrom(address _from, address _to, uint256 _tokenId) external payable
指定したトークン(_tokenId)の所有権を別のアドレス(_to)に譲渡する(基本的に利用非推奨)
approve(address _approved, uint256 _tokenId) external payable
指定したトークン(_tokenId)の所有権を指定したアドレス(_approved)に移転する
setApprovalForAll(address _operator, bool _approved) external
オペレータ(_operator)の承認設定を変更する
getApproved(uint256 _tokenId) external view returns (address)
トークン(_tokenId)の所有者として承認されているアドレスを返却する
isApprovedForAll(address _owner, address _operator) external view returns (bool)
オペレータ(_operator)がトークン所有者(_owner)によって承認されているかどうかを返却する

イベント

Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId)
移転元(_form)が移転先(_to)にどのトークン(_tokenId)を移転したのかをトランザクションログに書き込む
Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId)
トークン所有者(_owner)が承認先(_approved)に指定したトークン(_tokenId)を移譲の承認することをトランザクションログに書き込む
ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved)
トークン所有者(_owner)に対応したオペレータ(_operator)がトークン操作可能であるかをトランザクションログに書き込む
26
23
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
23