「NFT」という語からは、美術品やコレクションを思い浮かべる方が多いと思います。この本で読んでいる取引でも NFT が 1 つ作られていますが、そこにあるのは古典籍の IIIF マニフェストです。

同じ規格の上に、まったく違うものが載っています。この章では、規格が何を決めていて、何を決めていないのかを分けます。

規格が要求していること

ERC-721 が定めているのは、突き詰めると次の 3 つです。

  1. 1 つずつ区別できること(トークン ID)
  2. 持ち主を答えられ、譲渡できること(ownerOf / transferFrom
  3. 自分が ERC-721 だと名乗れること(supportsInterface

厳密には、これに加えて譲渡の代理(approve / setApprovalForAll)と、受け取り先が契約の場合の安全確認(safeTransferFrom)も必須です。

名前・記号・tokenURI は付随的な拡張(ERC721Metadata)です。しかも tokenURI が何を返すべきかは、「URI であること」以上には決まっていません。

規格が決める範囲と決めていない範囲を示した図。左上に「ERC-721 が決めていること」の箱があり、1つずつ区別できる(トークン ID)、持ち主を答える/譲渡する、自分が ERC-721 だと名乗る、必須は 9 個で任意拡張を含めて 15 個、と列挙されている。左下に「ERC-721 が決めていないこと」の箱があり、何を載せるか、tokenURI の中身と置き場所、誰が読むのか、持ち主以外の権限、と列挙されている。左下の箱から「空いている部分に実装が入る」というラベルの矢印が中央の「Ocean が入れたもの」の箱へ伸びており、そこには createERC20(子トークンを産む)、manager / deployer などの役割、暗号化したメタデータの登録、独自の関数 36 個、と書かれている。さらに右へ矢印が伸び「ウォレットから見るとふつうの NFT」に繋がり、その下に「51 個中 36 個が規格の外側」と記されている。図の下部に、器は共通で中身は共通ではないこと、「NFT=画像へのリンク」は規格ではなく慣習であることが記されている。

規格には適合している

この本で読んでいる NFT に、直接問い合わせてみます。3 章で使った eth_getCode と同じ要領で、eth_call を使うと契約の関数を呼べます。

curl -s -X POST https://ethereum-sepolia-rpc.publicnode.com \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"to":"0x04ECc497632847529FC431bA780Ffc573Fe4aC47","data":"0x01ffc9a780ac58cd00000000000000000000000000000000000000000000000000000000"},"latest"]}'

data は「呼びたい関数」と「引数」を並べたものです。ここでは supportsInterface0x01ffc9a7)に 0x80ac58cd(ERC-721 の識別子)を渡しています。末尾が …01 なら true、…00 なら false です。以降は同じ形で関数を差し替えれば読めます。

supportsInterface(0x80ac58cd)  ERC-721            → true
supportsInterface(0x5b5e139f)  ERC721Metadata     → true
supportsInterface(0x780e9d63)  ERC721Enumerable   → true

ownerOf(1)   = 0xa60eF4E6E8F821F3bd5D42f8067BD5d4a96e0CEf(公開した本人)
totalSupply  = 1
name/symbol  = PX Data NFT / PX-NFT

ふつうの NFT です。譲渡もできますし、ウォレットに入れれば NFT として表示されます。「データスペース専用の特別なトークン」ではありません。

ERC-165 だけ結果が異なりました

ついでに調べたところ、1 つだけ想定と違う応答がありました。

supportsInterface(0x01ffc9a7)  ERC-165 自身  → false
supportsInterface(0xffffffff)                → false

ERC-721 は ERC-165(対応規格を問い合わせる仕組み)の上に成り立っており、ERC-165 は「自分自身の識別子には true を返すこと」を求めています。ここが false なので、仕様の求めるところとは異なる挙動のようです。2 行目の 0xfffffffffalse を返すのは仕様どおりです。

実務上の影響は限定的だと思われます。ウォレットも流通市場も 0x80ac58cd(ERC-721 の識別子)を見るので、0x01ffc9a7 を単独で確認する実装はまれです。ただし規格適合を機械的に検査する場面では、引っかかる可能性があります。

譲渡できない場合がある

こちらのほうが重い逸脱です。ERC-721 が必須としている safeTransferFrom には引数が 3 つの版と 4 つの版があり、この契約には 4 つの版が実装されていませんでした。必須の 16 関数のうち 15 しかない、ということです。

問題は、呼んだときに何が起きるかです。この契約には fallback が定義されているので、実装されていない関数を呼んでも失敗しません。何もせずに成功します。

ガスの見積もりを取ると、その差がはっきり出ます。

存在しない関数 0xdeadbeef        → 24,271 gas   (fallback)
safeTransferFrom(4引数)        → 25,275 gas   (fallback。譲渡は起きない)
safeTransferFrom(3引数)        → 260,242 gas  (実際に譲渡する)
transferFrom                     → 261,499 gas  (実際に譲渡する)

4 引数版だけ、譲渡する版の 10 分の 1 以下です。中身が動いていません。

4 引数の safeTransferFrom を使うウォレットや流通市場は少なくありません。それらから見ると、取引は成功したのに資産が移っていないという状態になります。エラーも出ません。

前節の「ふつうの NFT です。譲渡もできます」は、3 引数版を使う限りは正しいのですが、経路によっては成り立たないということになります。実物を読むと、こういうものが出てきます。

関数の 7 割は規格の外側にある

契約が持つ関数を数えると、こうなりました。テンプレート(0x9C9eE07b…)の ABI(どんな関数があるかの一覧)から、外部から呼べる関数を数えています。supportsInterface は ERC-721 系の 15 個に含めました。

関数の総数51
うち ERC-721 系の標準15
それ以外36

独自の関数には、たとえば次のようなものがあります。

  • createERC20 — この NFT から別のトークンを発行する
  • addManager / getPermissions / cleanPermissions — 役割の管理
  • setMetaData / getMetaData — メタデータの登録と参照
  • executeCall — 任意の呼び出しの実行

2 つ目に注目してください。標準の NFT にも所有者以外の権限はありますが、approve / setApprovalForAll による「譲渡の代理」に限られます。ここにあるのは manager、deployer、メタデータ更新者といった、譲渡以外の操作まで含む役割です。個人の所有物ではなく、組織で運用することを想定した作りです。

1 つ目も標準にはない構造です。NFT が子トークンを産む、という関係は ERC-721 の想定外で、Ocean が資産(NFT)とアクセス権(トークン)を分けるために足したものです。

中身が 45 バイトしかない

3 章で触れた点をもう一度確認します。この NFT にコードは 45 バイトしか置かれていません。51 個の関数を持つプログラムとしては、明らかに足りない大きさです。

0x04ECc497…(今回の NFT・転送役)   45 バイト
0x9C9eE07b…(テンプレート・実体)   21,504 バイト
0xEF62FB49…(工場)                16,900 バイト

中身が「本体はあちらです」と転送するだけだからです。ERC-1167 という規格で、最小プロキシと呼ばれます。実体は別の契約(テンプレート、0x9C9eE07b… で 21,504 バイト)にあり、そこを共用しています。工場はテンプレートとは別で、転送役を新しく置く係です。3 章で中身を分解しています。

これは費用の工夫です。契約を新しく置く操作はチェーン上で高い部類なので、毎回本体を複製していれば、1 件登録するたびに大きなガスがかかります。45 バイトの転送役を置いて中身を借りることで、前章までに見た「契約 2 つを新設して約 1,300,000 ガス」に収まっています。

「NFT はこういうもの」という像

日本語の NFT 解説では、次のような説明をよく見かけます。

実際の作品データは大きすぎるため別の場所に保存し、そのリンク情報を NFT が持つのが一般的です。

一般的な運用としては、そのとおりです。ただしこれは規格の要求ではありません。ERC-721 は「何を載せるか」を一切決めていないので、リンクを持たせるのも、データそのものを埋め込むのも、何も持たせないのも、すべて規格の範囲内です。

「JSON に nameimage を入れる」という書式は、実は規格本文にも「ERC721 Metadata JSON Schema」として載っています。ただし tokenURI の説明は「その URI は、この書式の JSON を指してもよい」という書き方で、従う義務はありません。次章で開ける JSON にある external_urlbackground_color に至っては規格本文になく、流通市場が事実上の標準として広めたものです。いずれも強制力はありません。

つまり「NFT は共通の規格か」という問いには、器は共通、中身は共通ではない、と答えるのが正確だと思います。規格が保証するのは「誰のものか」までで、そこから先は実装ごとに違います。

考えてみれば妥当な線引きです。所有権の移転はチェーンが保証すべきことで、中身の書式は用途ごとに違って当然です。NFT を「デジタル所有権の器」と捉え直すと、美術品も資料も同じ器に収まります。

この章で見たこと

  • ERC-721 が決めているのは、区別・所有・名乗りの 3 つ
  • この NFT は規格に適合している。ウォレットから見ればふつうの NFT
  • ただし規格からの逸脱が 2 つあった。supportsInterface の ERC-165 自身への応答と、4 引数の safeTransferFrom の未実装
  • 関数 51 個のうち 36 個は規格の外側。役割の管理や子トークンの発行はここに入る
  • コードが 45 バイトなのは、本体を共用する仕組み(ERC-1167)のため
  • 「NFT=画像へのリンク」は規格ではなく慣習

次章では、その慣習の中心にある tokenURI の中身を実際に開けてみます。