概要
ライブの電子チケットを取ったとき、「A列1番」と「A列2番」は、同じライブのチケットでも別物として扱われます。
会員証も、見た目が同じカードであっても、会員番号が違えば別の人に紐づくものです。
シリアル番号付きの商品、番号付き座席、整理券、ゲーム内の一点物アイテム、デジタル証明書なども同じです。
私たちは普段から、「同じように見えても、一つずつ区別されるもの」を自然に扱っています。
では、これがデジタルの世界ではどうでしょうか。
画像ファイル、PDF、音楽データ、動画ファイルは簡単にコピーできます。
コピーできること自体は、デジタルデータの大きな便利さです。
一方で、次のようなことを考え始めると、少し話が難しくなります。
- このデジタルなものを、誰が持っていると記録するのか
- 同じように見えるデータのうち、どれを正規のものとして扱うのか
- 「トークンを持っていること」と「画像や著作権を持っていること」は同じなのか
- 画像やメタデータが別の場所にある場合、それは長く参照できるのか
- 他人が作った画像を勝手にNFTとして発行した場合、何が問題になるのか
このような話と関係するのが、NFTです。
Ethereum公式ドキュメントでは、ERC-721を、一つ一つが一意で、同じスマートコントラクト内の別トークンと異なる価値を持ち得るトークン標準として説明しています。
ERC-721の仕様でも、スマートコントラクト内でNFTを追跡・移転するための標準APIを定めるものだと説明されています。
参考: Ethereum Documentation: ERC-721 Non-Fungible Token Standard
参考: EIP-721: Non-Fungible Token Standard
ここで大切なのは、NFTをいきなり「デジタルアートを売買するもの」と決めつけないことです。
NFTは、ざっくり言えば ブロックチェーン上で一つずつ区別できるトークン です。
そのトークンが画像を指している場合もあれば、チケット、会員権、ゲームアイテム、証明書のようなものを表す場合もあります。
ただし、NFTを持っていることは、画像・音楽・動画・キャラクター・物理商品・著作権そのものを自動的に持っていることとは限りません。
この点を混同すると、NFTはとても分かりにくくなります。
本記事では、NFTを次のように分けて整理します。
| 観点 | ざっくりした意味 | 身近なたとえ |
|---|---|---|
| NFT | ブロックチェーン上で一意に識別されるトークン | 番号付きチケットそのもの |
| tokenId | NFTを区別するための番号 | 座席番号、整理番号、会員番号 |
| 所有者アドレス | そのNFTを持っているウォレット | チケットを持っている人の記録 |
| メタデータ | NFTが何を表すかを説明する情報 | チケット詳細、イベント名、座席情報 |
| コンテンツ | 画像、動画、音楽などの実体 | チケットに描かれた画像や特典データ |
| 権利・規約 | 著作権、利用権、入場権、会員権など | チケット利用規約や会員規約 |
図にすると、NFTのまわりには次のような層があります。
この図で見ておきたいのは、NFTが単独ですべてを表すわけではないという点です。
ブロックチェーン上のトークン、メタデータ、画像などのコンテンツ、そして権利や規約は、それぞれ分けて考える必要があります。
米国著作権局とUSPTOの共同報告書でも、NFTの所有と、NFTに関連づけられた作品の著作権の所有は別であり、NFTを移転しても関連する著作権まで当然に移るわけではないと整理されています。
参考: U.S. Copyright Office / USPTO: Non-Fungible Tokens and Intellectual Property
つまり、NFTを理解するときの入口は、次の一文です。
NFTは「画像そのもの」ではなく、ブロックチェーン上で一意に識別・移転できるトークンです。
そのトークンが何を表し、どのような権利を伴うかは、メタデータ、保存場所、規約、ライセンス、現実世界の制度とあわせて確認する必要があります。
本記事では、この考え方を土台にして、ERC-721、ERC-1155、メタデータ、コンテンツ保存場所、著作権・所有権・利用権、ロイヤリティ、ユースケース、セキュリティ上の注意点を順番に整理します。
💡 豆知識
NFTの画像そのものが、必ずブロックチェーン上に保存されているわけではありません。
多くの場合、ブロックチェーン上にはトークンIDやメタデータへの参照があり、画像や動画などのコンテンツは別の場所に保存されています。
そのため、「NFTがあるから画像も長期的に見られる」とは限らず、保存場所や運用も確認する必要があります。
この記事の立ち位置
本記事では最初に扱う範囲を絞ります。
本記事で中心にするのは、NFTの価格や流行ではありません。
中心にするのは、NFTを構成する技術的な層と、権利関係を分けて理解すること です。
まずは、次のように分けて考えると読みやすくなります。
| 観点 | ざっくりした問い | 本記事での扱い |
|---|---|---|
| トークン | ブロックチェーン上で何を持っているのか | ERC-721やERC-1155を中心に扱う |
| 所有者アドレス | そのNFTを持っているウォレットはどれか |
ownerOf などの考え方として扱う |
| メタデータ | NFTが何を表すかをどこで説明しているのか |
tokenURI やJSONの関係として扱う |
| コンテンツ | 画像や動画はどこに保存されているのか | オンチェーン・オフチェーン・IPFSなどを扱う |
| 権利関係 | 著作権や利用権は自動的に移るのか | トークン所有とは分けて整理する |
| セキュリティ | どのようなリスクがあるのか | フィッシング、偽NFT、承認悪用などを扱う |
ERC-721の仕様では、ownerOf によって特定のNFTの所有者を確認し、Transfer イベントによって移転を扱い、メタデータ拡張では tokenURI によって外部のメタデータを参照できるようにしています。
参考: EIP-721: Non-Fungible Token Standard
一方で、ERC-1155は、1つのコントラクトで代替可能トークンと非代替トークンを含む複数種類のトークンを扱える標準として説明されています。
参考: EIP-1155: Multi Token Standard
また、ロイヤリティについてはERC-2981という標準があります。
ただし、ERC-2981はマーケットプレイスなどがロイヤリティ情報を取得するための標準であり、ロイヤリティ支払いそのものを常に強制する仕組みではありません。
参考: EIP-2981: NFT Royalty Standard
このように、NFTは一つの単語で語られがちですが、実際には複数の部品が組み合わさっています。
この記事で分かること
この記事で分かることは、次のとおりです。
- NFTを身近な「一つずつ区別できるもの」から理解する
- NFTを「画像そのもの」ではなく「一意に識別できるトークン」として整理する
- ERC-721における
tokenId、ownerOf、Transfer、tokenURIの基本を理解する - ERC-721とERC-1155の違いをざっくり整理する
- メタデータと画像・動画などのコンテンツ保存場所を分けて考える
- オンチェーン保存、HTTP URL、IPFSなどの違いを知る
- NFTを持つことと、著作権・所有権・利用権を持つことを分けて理解する
- NFTロイヤリティの仕組みと限界を整理する
- チケット、会員権、ゲームアイテム、証明書などのユースケースを整理する
- フィッシング、偽サイト、承認悪用などのセキュリティ上の注意点を知る
- NFTについてよくある誤解を避ける
特にこの記事では、次の混同を避けることを重視します。
| 混同しやすいこと | 分けて考えたいこと |
|---|---|
| NFT = 画像そのもの | NFTは、画像などを参照することがある一意なトークン |
| NFTを買う = 著作権も買う | 著作権や商用利用権は、規約や契約で別途確認するもの |
tokenId = 画像ファイル |
tokenId はトークンを識別する番号で、画像とは別 |
tokenURI = 永久保証された保存場所 |
tokenURI はメタデータへの参照で、保存方式や運用は設計次第 |
| IPFS = 必ず永久保存 | IPFSは内容に基づく参照ができるが、長期保存にはpinningなどの運用が重要 |
| ロイヤリティ = 必ず自動徴収 | ERC-2981はロイヤリティ情報の取得標準であり、支払い強制とは分けて考える |
| NFTなら正規品だと必ず分かる | 発行元、コントラクトアドレス、規約、マーケットプレイス表示などの確認が必要 |
IPFSについても、よく誤解される点があります。
IPFS公式ドキュメントでは、CIDはコンテンツを指すラベルであり、保存場所そのものを示すものではなく、コンテンツ自体に基づくアドレスのようなものだと説明されています。
参考: IPFS Docs: Content Addressing and CIDs
つまり、NFTを理解するときは、次のように層を分けると見通しがよくなります。
本記事では、この図のように、NFTを一枚岩で見るのではなく、いくつかの層に分けて整理していきます。
対象読者
この記事は、次のような人を想定しています。
- ブロックチェーンを学び始めた人
- NFTという言葉は聞いたことがあるが、仕組みはまだ曖昧な人
- 「NFTを持つ」とは何を意味するのか整理したい人
- ERC-721、ERC-1155、
tokenId、メタデータなどの用語が気になっている人 - NFTと著作権・所有権・利用権の違いで混乱したことがある人
- 情報セキュリティの観点からNFTの注意点を知りたい人
- いきなり仕様書を読む前に、まず全体像をつかみたい人
途中で、ERC-721、ERC-1155、tokenId、ownerOf、tokenURI、メタデータ、IPFS、ロイヤリティ、setApprovalForAll といった言葉が出てきます。
ただし、最初から全部を知っている必要はありません。
それぞれの用語は、できるだけ身近な例と一緒に説明します。
たとえば、tokenId は「コントラクト内でNFTを区別する番号」、ownerOf は「その番号のNFTをどのウォレットが持っているかを確認するための関数」、tokenURI は「そのNFTの説明書の場所を示すもの」として、まずはざっくり理解すれば十分です。
本記事で扱わないこと
本記事は、NFTを初学者向けに整理する記事です。
そのため、以下は深く扱いません。
- NFTの価格予測
- NFT投資の判断
- 特定NFTプロジェクトの評価や購入推奨
- 特定マーケットプレイスの利用推奨
- NFTの売買手順の詳細
- 法律上の助言や契約書レビュー
- 著作権法の詳細な解釈
- ERC-721やERC-1155の完全なSolidity実装
- NFTマーケットプレイスの内部実装
- すべてのチェーンにおけるNFT仕様の網羅的比較
- 実際の秘密鍵管理や資産運用の手順
NFTは、技術だけでなく、法律、契約、デザイン、コミュニティ運営、マーケットプレイス、金融犯罪対策などとも関わるテーマです。
そのため、深掘りしようとすると非常に広い話になります。
本記事では、まず初学者が全体像をつかめるように、次の観点に絞って扱います。
| 扱う観点 | 扱わない観点 |
|---|---|
| NFTの基本的な考え方 | 価格や投資判断 |
| ERC-721とERC-1155の入口 | 完全な実装詳細 |
tokenId、ownerOf、tokenURI、メタデータ |
すべての拡張仕様の網羅 |
| コンテンツ保存場所の考え方 | 個別ストレージサービスの優劣断定 |
| 著作権・所有権・利用権を分ける考え方 | 法律上の助言 |
| ロイヤリティの仕組みと限界 | 特定マーケットプレイスの運用評価 |
| セキュリティ上の注意点 | 実際の資産運用手順 |
また、詐欺やフィッシングに関する話題は扱いますが、個別事件の詳細な追跡や、特定のサービス・人物を評価することは目的にしません。
FBI/IC3は、犯罪者が正規のNFT開発者になりすまし、偽のNFTリリースや偽サイトへ誘導する手口について注意喚起しています。
本記事では、このようなリスクを踏まえ、ウォレット接続、承認操作、公式情報確認の注意点を一般的に整理します。
参考: FBI IC3: Criminals Pose as Non-Fungible Token Developers to Target Cryptocurrency Assets
コード・コントラクト例を読む場合の前提
本記事では、後半の章で、ERC-721やERC-1155の考え方を説明するために、小さなコード片やインターフェース例を使う可能性があります。
ただし、コードはすべて 考え方を理解するための学習用 です。
実際のNFT発行、NFT売買、秘密鍵管理、資産運用、商用コントラクトの開発へそのまま使うことは想定していません。
| 項目 | 内容 |
|---|---|
| 目的 | NFTの構造を理解するための学習用 |
| 主に扱うもの | ERC-721の ownerOf、tokenURI、Transfer などの考え方 |
| 想定する知識 | Solidityを完全に読めなくても理解できる程度に補足する |
| 注意点 | 実際のNFT発行、売買、秘密鍵管理、資産運用へ流用しない |
たとえば、次のような概念を扱う可能性があります。
-
tokenIdから所有者を確認するイメージ -
tokenURIからメタデータJSONへたどるイメージ -
setApprovalForAllのような承認操作がなぜ注意点になるのか - ERC-721とERC-1155でトークンの扱い方がどう違うのか
ここで扱うコードや図は、実際のスマートコントラクト監査や本番運用に使うものではありません。
あくまで、「NFTはどのような部品に分かれているのか」を理解するための補助として使います。
全体の流れ
この記事では、次の順番で話を進めます。
最初は、限定チケットや会員証のような身近な例から入ります。
そこで、同じように見えるものでも、番号や所有者が違えば別物として扱われることを確認します。
次に、NFTを「画像そのもの」ではなく、スマートコントラクト上で識別・移転できるトークンとして整理します。
この段階では、tokenId、所有者アドレス、スマートコントラクト、メタデータといった基本要素を、できるだけ具体例と一緒に見ます。
その後、ERC-721を中心に、NFTを扱うための代表的な標準を見ていきます。
ERC-721では、トークンの所有者を確認したり、トークンを移転したり、メタデータを参照したりするための基本的なインターフェースが定められています。
参考: EIP-721: Non-Fungible Token Standard
次に、メタデータとコンテンツ保存場所を整理します。
ここでは、NFTが参照するJSON、画像や動画の保存先、オンチェーン保存とオフチェーン保存、IPFSのCIDなどを扱います。
IPFS公式ドキュメントでは、CIDはコンテンツに基づくアドレスのようなものであり、コンテンツがどこに保存されているかを直接示すものではないと説明されています。
参考: IPFS Docs: Content Addressing and CIDs
そのうえで、ERC-1155との違いを見ます。
ERC-1155は、1つのスマートコントラクトで複数種類のトークンを扱える標準です。ゲームアイテムのように、同じ種類のアイテムが複数個あるものと、一点物に近いものをまとめて扱いたい場面でイメージしやすいです。
参考: EIP-1155: Multi Token Standard
後半では、NFTを持つことと、著作権・所有権・利用権を持つことを分けて整理します。
ここが本記事の中でも特に重要な部分です。
米国著作権局とUSPTOの共同報告書では、NFTの移転はトークンの移転であり、関連する著作権や知的財産権の移転を必ず伴うわけではないと整理されています。
参考: U.S. Copyright Office / USPTO: Non-Fungible Tokens and Intellectual Property
さらに、NFTロイヤリティについても扱います。
ERC-2981は、NFTのロイヤリティ情報を取得するための標準ですが、支払いそのものを常に強制する仕組みではありません。ここでは、「情報を返す標準」と「実際に支払われるか」を分けて整理します。
参考: EIP-2981: NFT Royalty Standard
最後に、チケット、会員権、ゲームアイテム、証明書などのユースケースを見たうえで、フィッシング、偽サイト、ウォレット接続、承認操作、秘密鍵管理などの注意点を整理します。
NFTは、技術としては一意なトークンを扱う仕組みですが、利用者にとってのリスクはスマートコントラクトだけで完結しません。
ウォレット、Webサイト、SNS、マーケットプレイス、規約、保存先、権利関係など、複数の層をあわせて確認する必要があります。
この記事を通じて、最終的には次のような見方ができるようになることを目指します。
NFTは、画像そのものではなく、ブロックチェーン上で一意に識別・移転できるトークンです。
そのトークンが何を表し、どのような権利を伴うかは、メタデータ、保存場所、規約、ライセンス、現実世界の制度とあわせて確認する必要があります。
次の章では、まずNFTという言葉に入る前に、身近な「一つずつ区別できるもの」 から考えます。
限定チケットや会員証の例を使いながら、「デジタルなものを一つずつ区別する」とはどういうことなのかを整理していきます。
1. 身近な「一つずつ区別できるもの」から考える
この章では、NFTの詳しい仕様に入る前に、身近な「一つずつ区別できるもの」から考えます。
ポイントは、同じ種類に見えても、番号や持ち主が違えば別物として扱うものがある という感覚です。
概要では、NFTを「画像そのもの」ではなく、ブロックチェーン上で一意に識別・移転できるトークンとして見る、という話をしました。
ただ、いきなり ERC-721、tokenId、ownerOf のような言葉から入ると、少し難しく感じるかもしれません。
そこでこの章では、まず日常生活にある「一つずつ区別するもの」を見ていきます。
たとえば、同じライブのチケットでも、次の2つは別物です。
| チケット | イベント名 | 座席 | 持ち主 |
|---|---|---|---|
| チケットA | サマーライブ | A列1番 | Alice |
| チケットB | サマーライブ | A列2番 | Bob |
どちらも同じイベントのチケットです。
しかし、座席番号が違うため、同じものとして交換できるわけではありません。
A列1番のチケットを持っている人はA列1番に座れます。
A列2番のチケットを持っている人はA列2番に座れます。
見た目が似ていても、番号が違えば、意味も使い方も変わります。
NFTも、まずはこの感覚から入ると分かりやすいです。
1.1 同じ種類でも「別物」として扱う場面
私たちの身近なところには、「同じ種類だけれど、一つずつ区別するもの」がたくさんあります。
| 身近な例 | 一つずつ区別するための情報 | なぜ区別するのか |
|---|---|---|
| コンサートチケット | 座席番号、整理番号、チケットID | 入場権や座席が違うため |
| 会員証 | 会員番号、氏名、期限 | 誰の会員資格かを確認するため |
| シリアル番号付き商品 | シリアル番号 | 正規品確認や保証管理に使うため |
| ゲーム内アイテム | アイテムID、レア度、所有者 | 同じ名前でも個体差や所有者があるため |
| デジタル証明書 | 証明書ID、発行者、対象者 | 誰に対する証明かを確認するため |
| 整理券 | 番号、発行時刻 | 順番を管理するため |
たとえば、会員証は見た目が同じでも、会員番号が違えば別の人のものです。
シリアル番号付きの商品も、同じ型番の商品であっても、シリアル番号によって個体を区別できます。
ここで重要なのは、「見た目」だけではなく、識別するための番号や記録 があることです。
この図では、3枚とも同じイベントのチケットです。
しかし、チケットID、座席、所有者が違うため、それぞれ別のものとして扱われます。
NFTでも、これに近い考え方をします。
ERC-721の仕様では、スマートコントラクト内でNFTを追跡・移転するための標準APIが定められています。
また、Ethereum公式ドキュメントでは、ERC-721は一つ一つが一意で、同じスマートコントラクト内の別トークンと異なる価値を持ち得るトークン標準として説明されています。
参考: EIP-721: Non-Fungible Token Standard
参考: Ethereum Documentation: ERC-721 Non-Fungible Token Standard
ここではまだ細かい仕様を覚える必要はありません。
まずは、NFTを次のようにイメージします。
NFTは、ブロックチェーン上で「この番号のトークンは、いま誰が持っているか」を区別して扱うための仕組みです。
💡 豆知識
NFTのNon-Fungibleは、日本語では「非代替性」と訳されることが多いです。
少し固い言葉ですが、ざっくり言えば「同じ種類でも、別のものとして区別される」という意味です。
コンサートのA列1番チケットとA列2番チケットが、同じイベントのチケットでもそのまま同じものとしては扱えない、という感覚に近いです。
1.2 「交換できるもの」と「一つずつ区別するもの」
NFTを理解するうえで、よく出てくる対比が 代替可能 と 非代替 です。
代替可能というのは、同じ種類・同じ単位であれば、どれを受け取っても基本的に同じものとして扱える、という意味です。
たとえば、100円玉を考えてみます。
自分の100円玉と友人の100円玉を交換しても、通常は価値や使い道は変わりません。
銀行口座の残高やポイント残高も、基本的には「何ポイント持っているか」が重要で、1ポイントごとの個体差はあまり意識しません。
一方で、番号付きチケットや会員証は違います。
A列1番のチケットとA列2番のチケットは、同じライブのチケットでも座席が違います。
会員番号001の会員証と会員番号002の会員証は、見た目が似ていても別の人に紐づきます。
整理すると、次のようになります。
| 種類 | 身近な例 | 何が重要か | 交換したらどうなるか |
|---|---|---|---|
| 代替可能なもの | 100円玉、ポイント、残高 | 数量 | 同じ数量なら基本的に同じものとして扱える |
| 一つずつ区別するもの | 座席指定チケット、会員証、証明書 | ID、所有者、属性 | IDや属性が違うと別物として扱う必要がある |
NFTは、このうち後者に近い考え方です。
ただし、NFTは現実のチケットそのものではありません。
NFTは、ブロックチェーン上で一つずつ区別される トークン です。
この区別を忘れないようにすることが大切です。
1.3 デジタルデータはコピーしやすい
ここで、デジタルデータの特徴も考えてみます。
画像ファイルやPDFは、簡単にコピーできます。
コピーしても、見た目は元のファイルとほとんど同じです。
たとえば、次のような状況です。
original_image.png
copy_1.png
copy_2.png
copy_3.png
この4つのファイルが同じ内容なら、見た目だけではどれが「元のファイル」なのか判断しにくいかもしれません。
もちろん、コピーできること自体は悪いことではありません。
デジタルデータが簡単に共有できるからこそ、Web、SNS、資料共有、バックアップなどが便利になっています。
一方で、次のような場面では「どれが正規のものか」「誰が持っていることになっているか」を記録したくなります。
- 限定チケットの所有者を確認したい
- 会員権をウォレット単位で管理したい
- ゲーム内アイテムの所有者を記録したい
- デジタル証明書の発行先を確認したい
- 作品と紐づくトークンの移転履歴を確認したい
このとき、単に画像ファイルそのものをコピーできないようにするだけではなく、トークンとして何を誰が持っているかを記録する という発想が出てきます。
NFTは、この「一つずつ区別できるトークンを記録する」という文脈で理解すると、見通しがよくなります。
ただし、ここで注意が必要です。
NFTは、デジタルデータのコピーそのものを技術的に禁止する仕組みではありません。
画像を右クリックで保存できる場合もありますし、画面をスクリーンショットすることもできます。
NFTが扱う中心は、画像ファイルのコピー防止そのものではなく、ブロックチェーン上のトークンの識別と所有者の記録 です。
この点を押さえておくと、「NFTなのに画像がコピーできるのはおかしいのでは?」という疑問も整理しやすくなります。
1.4 小さなコードで見る:ポイント残高と番号付きチケットの違い
ここで、代替可能なものと、一つずつ区別するものの違いを、簡単なPythonコードで見てみます。
次のコードは、実際のNFTやブロックチェーンを再現するものではありません。
ポイント残高と番号付きチケットの違いを理解するための学習用コードです。
# これは「代替可能なもの」と「一つずつ区別するもの」の違いを理解するための学習用コードです。
# 実際のNFT、Ethereum、ERC-721の実装ではありません。
# 代替可能なものの例: ポイント残高
# ポイントは、基本的に「何ポイント持っているか」が重要です。
point_balances = {
"Alice": 100,
"Bob": 100,
}
print("ポイント残高:")
for owner, amount in point_balances.items():
print(f"{owner}: {amount}ポイント")
# Aliceの100ポイントとBobの100ポイントは、通常は同じ100ポイントとして扱えます。
print("\nAliceの100ポイントとBobの100ポイントは、数量としては同じです。")
# 一つずつ区別するものの例: 番号付きチケット
# チケットは、同じイベントでもticket_idや座席が違えば別物として扱います。
tickets = {
"TICKET-1001": {
"event": "サマーライブ",
"seat": "A列1番",
"owner": "Alice",
},
"TICKET-1002": {
"event": "サマーライブ",
"seat": "A列2番",
"owner": "Bob",
},
}
print("\n番号付きチケット:")
for ticket_id, ticket in tickets.items():
print(f"{ticket_id}: {ticket['event']} / {ticket['seat']} / 所有者: {ticket['owner']}")
# 同じイベントのチケットでも、seatが違うため別物として扱う必要があります。
print("\n同じイベントでも、TICKET-1001とTICKET-1002は別のチケットです。")
このコードでは、前半でポイント残高を扱っています。
ポイントは、基本的に「何ポイント持っているか」が重要です。
一方、後半では番号付きチケットを扱っています。
こちらは、同じイベントのチケットでも、TICKET-1001 と TICKET-1002 が別物として扱われます。
NFTを理解するときも、最初はこの違いを押さえると分かりやすいです。
| コード上の例 | NFT理解へのつながり |
|---|---|
| ポイント残高 | 数量が重要な代替可能トークンのイメージ |
| 番号付きチケット | 1つずつIDで区別するNFTのイメージ |
ticket_id |
NFTにおける tokenId の入口イメージ |
owner |
NFTにおける所有者アドレスの入口イメージ |
もちろん、実際のNFTでは owner は人名ではなく、ウォレットアドレスで表されます。
また、ticket_id のような文字列ではなく、スマートコントラクト内の tokenId として扱われることが一般的です。
ただし、考え方としては「一つずつIDで区別し、誰が持っているかを記録する」という点が共通しています。
1.5 小さなコードで見る:所有者を記録し、移転する
次に、「一つずつ区別できるもの」の所有者が変わる様子を見てみます。
たとえば、Aliceが持っているチケットをBobに譲るとします。
このとき、単に「チケット画像を送る」のではなく、チケットIDに対する所有者の記録を更新する必要があります。
NFTでも、トークンの移転では「この tokenId の所有者が、どのアドレスからどのアドレスへ変わったか」が重要になります。
ERC-721の仕様では、NFTの所有者を調べるための ownerOf、NFTの移転を表す Transfer イベントなどが定義されています。
参考: EIP-721: Non-Fungible Token Standard
ここでは、ERC-721をそのまま実装するのではなく、所有者が変わるイメージだけをPythonで表します。
# これは「IDで区別されるチケットの所有者を移転する」考え方を理解するための学習用コードです。
# 実際のNFT、Ethereum、ERC-721の実装ではありません。
# チケットIDごとに、イベント名・座席・所有者を記録します。
tickets = {
"TICKET-1001": {
"event": "サマーライブ",
"seat": "A列1番",
"owner": "Alice",
},
"TICKET-1002": {
"event": "サマーライブ",
"seat": "A列2番",
"owner": "Bob",
},
}
def get_owner(ticket_id: str) -> str:
"""
指定されたチケットIDの所有者を返します。
NFTの文脈では、ERC-721の ownerOf(tokenId) のように、
特定のIDに対応する所有者を確認する考え方に近いです。
"""
if ticket_id not in tickets:
raise ValueError("存在しないチケットIDです。")
return tickets[ticket_id]["owner"]
def transfer_ticket(ticket_id: str, from_owner: str, to_owner: str) -> None:
"""
チケットの所有者を from_owner から to_owner へ変更します。
処理の流れ:
1. チケットIDが存在するか確認する
2. 現在の所有者が from_owner と一致するか確認する
3. 問題なければ owner を to_owner に更新する
注意:
- これは学習用の単純な例です。
- 実際のERC-721では、署名、承認、アドレス、イベント、スマートコントラクトの状態変更などが関係します。
"""
if ticket_id not in tickets:
raise ValueError("存在しないチケットIDです。")
current_owner = tickets[ticket_id]["owner"]
# 現在の所有者と、移転元として指定された人が一致するか確認します。
if current_owner != from_owner:
raise ValueError(f"移転できません。現在の所有者は {current_owner} です。")
# 所有者を更新します。
tickets[ticket_id]["owner"] = to_owner
# 実際のERC-721では、このような移転はTransferイベントとして記録されます。
print(f"Transfer: {ticket_id} / {from_owner} -> {to_owner}")
print("移転前の所有者:", get_owner("TICKET-1001"))
# Aliceが持っているTICKET-1001をBobへ移転します。
transfer_ticket("TICKET-1001", from_owner="Alice", to_owner="Bob")
print("移転後の所有者:", get_owner("TICKET-1001"))
このコードで重要なのは、チケットの画像やイベント名ではなく、チケットIDに対する所有者の記録 を更新している点です。
NFTでも、トークンの所有者を考えるときは、「画像を誰が見られるか」だけでなく、「ブロックチェーン上の特定の tokenId がどのアドレスに紐づいているか」を見ます。
ここでは人名で表していますが、実際のNFTでは次のようなイメージになります。
| 学習用コード | NFTでのイメージ |
|---|---|
TICKET-1001 |
tokenId |
Alice |
Aliceのウォレットアドレス |
Bob |
Bobのウォレットアドレス |
get_owner() |
ownerOf(tokenId) の入口イメージ |
transfer_ticket() |
NFTの移転処理の入口イメージ |
Transfer: Alice -> Bob |
ERC-721の Transfer イベントの入口イメージ |
もちろん、実際のNFTでは、勝手に他人のNFTを移転できないように、署名や承認の仕組みがあります。
この点は、後のERC-721の章やセキュリティの章で扱います。
この章ではまず、次の点だけ押さえれば十分です。
一つずつ区別されるトークンでは、「どのIDを、誰が持っているか」が重要になります。
移転とは、そのIDに紐づく所有者の記録が変わることです。
1.6 小さなコードで見る:同じ説明でもIDが違えば別物になる
もう一つ、NFTで大切な考え方があります。
それは、見た目や説明が同じでも、IDが違えば別のトークンとして扱われる場合がある という点です。
たとえば、同じイベント名、同じデザイン、同じ種類のチケットでも、チケットIDが違えば別物です。
これはNFTでも似ています。
同じコレクション内に似た画像が複数あっても、tokenId が違えば、それぞれ別のトークンとして扱われます。
次のコードでは、同じイベント名と同じデザインを持つチケットでも、IDが違えば別のチケットとして扱う様子を確認します。
# これは「同じ説明や見た目でも、IDが違えば別物として扱う」ことを理解するための学習用コードです。
# 実際のNFTメタデータ管理やERC-721実装ではありません。
from dataclasses import dataclass
@dataclass(frozen=True)
class Ticket:
"""
一つのチケットを表す学習用データ構造です。
ticket_id:
チケットを一意に区別するIDです。
event:
イベント名です。
design:
チケットの見た目やデザインの名前です。
owner:
現在の所有者です。
"""
ticket_id: str
event: str
design: str
owner: str
# イベント名とデザインは同じですが、ticket_idとownerが違います。
ticket_a = Ticket(
ticket_id="TICKET-1001",
event="サマーライブ",
design="blue-design",
owner="Alice",
)
ticket_b = Ticket(
ticket_id="TICKET-1002",
event="サマーライブ",
design="blue-design",
owner="Bob",
)
print("ticket_a:", ticket_a)
print("ticket_b:", ticket_b)
# Pythonでは、dataclassの各フィールドがすべて同じでなければ、別の値として比較されます。
print("\nticket_a と ticket_b は同じですか?")
print(ticket_a == ticket_b)
# IDだけを比較しても、別のチケットであることが分かります。
print("\nチケットIDは同じですか?")
print(ticket_a.ticket_id == ticket_b.ticket_id)
このコードでは、event と design は同じです。
しかし、ticket_id が違うため、ticket_a と ticket_b は別のチケットとして扱われます。
NFTでも、似たような考え方が出てきます。
たとえば、NFTのメタデータに同じ画像URLが入っていたとしても、tokenId が違えば別トークンです。
逆に、同じ tokenId でも、メタデータの参照先がどのように管理されているかによって、表示される内容が変わる可能性もあります。
このあたりは少しややこしいため、後の「メタデータとコンテンツ保存場所」の章で詳しく整理します。
ここで大切なのは、NFTでは次の3つを混同しないことです。
| 観点 | ざっくりした意味 |
|---|---|
tokenId |
トークンを一つずつ区別するための番号 |
| メタデータ | そのトークンが何を表すかを説明する情報 |
| コンテンツ | 画像、動画、音楽など、参照される実体 |
この3つを分けて考えることが、NFT理解の大きな入口になります。
1.7 「本物らしさ」はトークンだけでは決まらない
ここまで、「一つずつ区別する」ことを中心に見てきました。
ただし、IDがあるからといって、それだけで何でも信頼できるわけではありません。
たとえば、誰かが勝手に「有名アーティストの作品です」と書いたチケット風の画像を作り、それにIDを付けたとします。
IDがあること自体は確認できても、そのIDを発行した人が本当に正規の権利者なのかは別問題です。
これはNFTでも重要な注意点です。
NFTでは、ブロックチェーン上で「このコントラクトのこの tokenId は、このアドレスが持っている」と確認できる場合があります。
しかし、それだけで次のことまで自動的に分かるわけではありません。
- そのNFTが正規の発行者によって発行されたか
- 画像や音楽の著作権者が発行を許可しているか
- NFTを持つことで、どの範囲の利用権が得られるか
- 画像やメタデータが長期的に参照できるか
- マーケットプレイス上の表示が正確か
米国著作権局とUSPTOの共同報告書でも、NFTの所有と、NFTに関連づけられた作品の著作権の所有は別であり、NFTの移転が関連する著作権の移転を当然に伴うわけではないと整理されています。
参考: U.S. Copyright Office / USPTO: Non-Fungible Tokens and Intellectual Property
この点は、今後の章で繰り返し出てきます。
NFTを理解するときは、次のように段階を分けて見る必要があります。
つまり、NFTの「一意性」は、まず トークンとして一つずつ区別できる という意味です。
それだけで、権利関係や正規性がすべて自動的に保証されるわけではありません。
💡 豆知識
NFTの説明では「唯一無二」という言葉がよく使われます。
ただし、何が唯一なのかを丁寧に見ることが大切です。
多くの場合、まず唯一なのは「特定のコントラクト内の特定のtokenId」です。画像ファイル、著作権、利用権、現実世界の所有権まで自動的に唯一になるわけではありません。
1.8 この章のまとめ
この章では、NFTの詳しい仕様に入る前に、身近な「一つずつ区別できるもの」から考えました。
限定チケット、会員証、シリアル番号付き商品、ゲーム内アイテム、デジタル証明書などは、同じ種類に見えても、IDや所有者が違えば別物として扱われます。
NFTを理解するときも、まずはこの感覚が入口になります。
この章で押さえたいポイントは、次の通りです。
| ポイント | 説明 |
|---|---|
| 同じ種類でも別物として扱うものがある | 座席指定チケットや会員証は、番号や所有者によって区別される |
| 代替可能なものと非代替なものは違う | ポイントや残高は数量が重要だが、NFTではIDごとの違いが重要になる |
| デジタルデータはコピーしやすい | 画像やPDFはコピーできるため、トークンの所有記録とは分けて考える必要がある |
| NFTではIDと所有者の記録が重要 |
tokenId と所有者アドレスの関係が基本になる |
| IDがあるだけで正規性や権利が保証されるわけではない | 発行元、規約、著作権、利用権は別途確認が必要 |
ここまでの内容を一言でまとめると、次のようになります。
NFTを理解する入口は、「デジタル画像をコピーできなくする技術」と考えることではありません。
まずは、ブロックチェーン上で一つずつ区別できるトークンを作り、その所有者を記録する仕組みとして考えると分かりやすくなります。
次の章では、この流れを受けて、いよいよ NFTをざっくり整理する 章に入ります。
そこで、NFT、tokenId、所有者アドレス、スマートコントラクト、メタデータ を、今回のチケットの例とつなげながら整理していきます。
2. NFTをざっくり整理する
この章では、1章で見た「一つずつ区別できるもの」の感覚を、NFTの基本構造へつなげます。
ポイントは、NFTを 画像そのもの として見るのではなく、スマートコントラクト上で識別・移転されるトークン として見ることです。
前の章では、限定チケットや会員証、シリアル番号付き商品のように、同じ種類に見えても一つずつ区別する必要があるものを見ました。
NFTも、まずはこの延長で考えると分かりやすいです。
たとえば、同じイベントのチケットでも、TICKET-1001 と TICKET-1002 は別のチケットでした。
NFTでも同じように、スマートコントラクトの中で tokenId という番号を使い、トークンを一つずつ区別します。
ただし、NFTの場合は、単に「番号がある」だけではありません。
ブロックチェーン上では、そのトークンを管理するスマートコントラクトがあり、特定の tokenId があり、その tokenId をどのウォレットアドレスが持っているかを確認できます。
Ethereum公式ドキュメントでは、NFTは何か、または誰かを一意に識別するために使われるトークンであり、収集品、アクセスキー、宝くじ券、コンサートやスポーツの番号付き座席などに向いていると説明されています。
また、ERC-721では、すべてのNFTが uint256 型の tokenId を持ち、ERC-721コントラクトでは contract address と tokenId の組み合わせが一意な識別子になると説明されています。
参考: Ethereum Documentation: ERC-721 Non-Fungible Token Standard
参考: EIP-721: Non-Fungible Token Standard
この章では、NFTを次の部品に分けて整理します。
| 部品 | ざっくりした意味 | 身近な例でいうと |
|---|---|---|
| スマートコントラクト | NFTを管理するプログラム | チケット発行・管理システム |
| コントラクトアドレス | そのコントラクトの住所 | チケット管理会社や発行システムのID |
| tokenId | コントラクト内でNFTを区別する番号 | チケット番号、会員番号 |
| 所有者アドレス | そのNFTを持っているウォレット | チケットの現在の持ち主 |
| tokenURI | メタデータの場所を示すURI | チケット詳細ページのURL |
| メタデータ | 名前、説明、画像URIなど | イベント名、座席、チケット画像などの説明 |
| コンテンツ | 画像、動画、音楽など | チケット画像、会員証画像、特典データ |
| 権利・規約 | 利用権、会員権、著作権など | 入場条件、会員規約、利用許諾 |
ここで大切なのは、これらを一つにまとめて「NFT」と呼んでしまうと、誤解しやすくなることです。
NFTを理解するときは、まず次のように分けます。
この図では、NFTの中心にあるのは tokenId です。
ただし、tokenId だけを見ても、画像や権利の中身まですべて分かるわけではありません。
どのコントラクトの tokenId なのか。
その tokenId の所有者は誰なのか。
その tokenId はどのメタデータを参照しているのか。
そのメタデータはどのコンテンツを指しているのか。
そして、トークン所有者にどのような権利があるのか。
このように、段階的に見る必要があります。
2.1 NFTは「コントラクトアドレス + tokenId」で区別する
まず、NFTの識別から見ていきます。
1章では、番号付きチケットを例にしました。
チケットでは、TICKET-1001 のようなチケットIDがありました。
NFTでも、これに近いものとして tokenId があります。
ただし、ERC-721で重要なのは、tokenId だけではなく、どのスマートコントラクトの tokenId なのか もセットで見ることです。
たとえば、次のように考えると分かりやすいです。
| コントラクト | tokenId | 意味のイメージ |
|---|---|---|
| ライブチケットNFTコントラクト | 1 | ライブチケットの1番 |
| 会員証NFTコントラクト | 1 | 会員証の1番 |
| ゲームアイテムNFTコントラクト | 1 | ゲームアイテムの1番 |
どれも tokenId = 1 ですが、管理しているコントラクトが違えば、別のNFTです。
身近な例でいうと、学校Aの出席番号1番と、学校Bの出席番号1番は別の人を指す、という感覚に近いです。
「1番」という数字だけでは足りず、「どの学校の1番なのか」まで見ないと区別できません。
EIP-721では、すべてのNFTはERC-721スマートコントラクト内で一意な uint256 IDによって識別され、contract address と uint256 tokenId の組み合わせが、Ethereumチェーン上の特定の資産を指す完全な識別子になると説明されています。
参考: EIP-721: Non-Fungible Token Standard
ここでのポイントは、次の通りです。
NFTを一意に見るときは、tokenIdだけではなく、
「どのコントラクトのtokenIdか」までセットで見る。
💡 豆知識
ERC-721の仕様では、tokenIdはuint256という非常に大きな整数型で表されます。
ただし、IDが必ず1, 2, 3...と順番に増えるとは限りません。
仕様上、呼び出し側はIDに特定のパターンがあると決めつけず、「中身の分からない識別子」として扱うべきだと説明されています。
2.2 「NFTを持っている」とは何を指すのか
次に、「NFTを持っている」とは何を意味するのかを考えます。
日常会話では、NFTを「持っている」と言うことがあります。
ただし、技術的には少し丁寧に見る必要があります。
ERC-721の文脈では、ある tokenId の所有者は ownerOf(tokenId) のような関数で確認できます。
ERC-721の仕様では、ownerOf(uint256 _tokenId) は、そのNFTの所有者アドレスを返す関数として定義されています。
参考: EIP-721: Non-Fungible Token Standard
かなり単純化すると、次のような状態です。
ownerOf(1001) が Alice のウォレットアドレスを返す
= tokenId 1001 のNFTは、Aliceのウォレットが持っている状態
身近なチケットの例に戻すと、次のようになります。
| チケットの例 | NFTの例 |
|---|---|
| チケットIDを確認する | tokenIdを確認する |
| チケット管理システムで持ち主を確認する | NFTコントラクトで所有者アドレスを確認する |
| 持ち主がAliceと記録されている |
ownerOf(tokenId) がAliceのアドレスを返す |
| 譲渡すると持ち主の記録が変わる | Transferにより所有者アドレスが変わる |
つまり、NFTを「持っている」と言うとき、まずオンチェーンで確認できるのは その tokenId がどのアドレスに割り当てられているか です。
ただし、ここでも注意があります。
オンチェーンで分かるのは、基本的には「このアドレスがこのトークンを持っている」ということです。
そのアドレスの現実世界の持ち主が誰か、著作権が移ったか、商用利用してよいか、入場権として有効か、といった話は別途確認が必要です。
この章ではまず、技術的な入口として、次のように理解しておきます。
NFTを持っているとは、まず「特定のNFTコントラクト内の特定の
tokenIdが、自分のウォレットアドレスに紐づいている状態」と考えると分かりやすいです。
2.3 小さなコードで見る:コントラクトアドレスとtokenIdでNFTを識別する
ここで、NFTを識別する考え方を、Pythonで小さく確認してみます。
次のコードは、実際のEthereumやERC-721を再現するものではありません。
目的は、tokenId だけではなく、コントラクトアドレスと組み合わせてNFTを区別する という考え方をつかむことです。
# これは「NFTはコントラクトアドレス + tokenIdで区別する」ことを理解するための学習用コードです。
# 実際のEthereum、ERC-721、スマートコントラクトの実装ではありません。
from dataclasses import dataclass
@dataclass(frozen=True)
class NFTKey:
"""
NFTを一意に識別するためのキーを表します。
contract_address:
NFTを管理しているスマートコントラクトのアドレスです。
token_id:
そのコントラクト内でNFTを区別する番号です。
"""
contract_address: str
token_id: int
# どちらもtoken_idは1ですが、コントラクトアドレスが違います。
# そのため、別のNFTとして扱います。
live_ticket_nft = NFTKey(
contract_address="0xLiveTicketContract",
token_id=1,
)
membership_nft = NFTKey(
contract_address="0xMembershipContract",
token_id=1,
)
print("ライブチケットNFT:", live_ticket_nft)
print("会員証NFT:", membership_nft)
print("同じNFTですか?", live_ticket_nft == membership_nft)
このコードでは、token_id はどちらも 1 です。
しかし、contract_address が違うため、live_ticket_nft と membership_nft は別のNFTとして扱われます。
NFTを識別するときは、tokenId だけを見て「1番だから同じ」と考えるのではなく、どのコントラクトの1番なのか を見る必要があります。
この考え方は、後のERC-721の章でも重要になります。
2.4 NFTを構成する基本要素
ここまで、NFTの識別と所有者の考え方を見てきました。
次に、NFTを構成する基本要素をもう少し整理します。
NFTを説明するときに混乱しやすいのは、次のようなものをまとめて「NFT」と呼んでしまうことです。
- ブロックチェーン上のトークン
- トークンを管理するスマートコントラクト
tokenId- 所有者アドレス
- メタデータ
- 画像や動画などのコンテンツ
- 著作権や利用権などの権利関係
これらは関連していますが、同じものではありません。
整理すると、次のようになります。
| 要素 | 役割 | 具体例 |
|---|---|---|
| スマートコントラクト | NFTの所有者や移転ルールを管理する | ERC-721コントラクト |
| コントラクトアドレス | コントラクトの場所を示すアドレス |
0x... で始まるアドレス |
| tokenId | コントラクト内でNFTを区別する番号 |
1、1001 など |
| 所有者アドレス | NFTを持っているウォレット | ユーザーのEthereumアドレス |
| tokenURI | メタデータへのURI |
ipfs://...、https://... など |
| メタデータ | NFTの名前、説明、画像URIなど | JSONファイル |
| コンテンツ | 画像、動画、音声など | PNG、MP4、音楽ファイルなど |
| 権利・規約 | NFT保有者が何をできるか | 利用規約、ライセンス、会員規約など |
ERC-721では、トークンの所有者を確認する ownerOf、所有数を確認する balanceOf、移転に関わる safeTransferFrom や transferFrom、承認に関わる approve や setApprovalForAll などが定義されています。
参考: EIP-721: Non-Fungible Token Standard
また、ERC-721のメタデータ拡張では、name、symbol、tokenURI が定義されています。
tokenURI は、特定のNFTに対するURIを返す関数です。仕様では、そのURIがERC-721 Metadata JSON Schemaに従うJSONファイルを指す場合があると説明されています。
参考: EIP-721: Non-Fungible Token Standard
ここで大切なのは、tokenURI が「画像そのもの」とは限らないことです。
多くの場合、tokenURI はメタデータJSONへの参照であり、そのJSONの中に image のような項目が入ります。
この図のように、NFTの基本構造は「トークン」「メタデータ」「コンテンツ」に分けて見ると理解しやすくなります。
2.5 小さなコードで見る:ownerOfとtransferの最小モデル
次に、NFTコントラクトが持っている状態を、かなり単純化してPythonで見てみます。
このコードは、実際のERC-721実装ではありません。
署名、承認、イベント、ガス、ブロックチェーン上の状態変更などは扱いません。
目的は、次の3つを理解することです。
-
tokenIdごとに所有者を記録する -
ownerOf(tokenId)のように所有者を確認する - 移転すると、その
tokenIdの所有者が変わる
# これはERC-721の ownerOf と transfer の考え方を理解するための学習用コードです。
# 実際のEthereum、Solidity、ERC-721の実装ではありません。
class SimpleNFTContract:
"""
NFTコントラクトの状態をかなり単純化したクラスです。
owners:
token_idごとの所有者を記録します。
token_uris:
token_idごとのメタデータURIを記録します。
"""
def __init__(self, contract_address: str):
self.contract_address = contract_address
self.owners = {}
self.token_uris = {}
def mint(self, to_address: str, token_id: int, token_uri: str) -> None:
"""
新しいNFTを発行する学習用メソッドです。
処理の流れ:
1. すでに同じtoken_idが存在しないか確認する
2. token_idの所有者をto_addressとして記録する
3. token_idに対応するtoken_uriを記録する
注意:
- ERC-721仕様そのものはmint関数の名前や処理を標準化しているわけではありません。
- ここでは説明のためにmintという名前を使っています。
"""
if token_id in self.owners:
raise ValueError("このtokenIdはすでに存在します。")
self.owners[token_id] = to_address
self.token_uris[token_id] = token_uri
def owner_of(self, token_id: int) -> str:
"""
指定したtoken_idの所有者を返します。
ERC-721の ownerOf(tokenId) の考え方に近い処理です。
"""
if token_id not in self.owners:
raise ValueError("存在しないtokenIdです。")
return self.owners[token_id]
def token_uri(self, token_id: int) -> str:
"""
指定したtoken_idのメタデータURIを返します。
ERC-721の tokenURI(tokenId) の考え方に近い処理です。
"""
if token_id not in self.token_uris:
raise ValueError("存在しないtokenIdです。")
return self.token_uris[token_id]
def transfer_from(self, from_address: str, to_address: str, token_id: int) -> None:
"""
NFTの所有者をfrom_addressからto_addressへ変更します。
処理の流れ:
1. token_idが存在するか確認する
2. 現在の所有者がfrom_addressと一致するか確認する
3. 問題なければ所有者をto_addressへ更新する
注意:
- 実際のERC-721では、msg.sender、承認、safeTransferFrom、イベントなどが関係します。
- ここでは所有者が変わるイメージだけに絞っています。
"""
current_owner = self.owner_of(token_id)
if current_owner != from_address:
raise ValueError(f"移転できません。現在の所有者は {current_owner} です。")
self.owners[token_id] = to_address
print(f"Transfer: tokenId={token_id} / {from_address} -> {to_address}")
# 学習用のNFTコントラクトを作ります。
contract = SimpleNFTContract(contract_address="0xExampleNFTContract")
# AliceにtokenId 1001のNFTを発行します。
contract.mint(
to_address="0xAlice",
token_id=1001,
token_uri="ipfs://example-metadata-1001",
)
print("所有者:", contract.owner_of(1001))
print("tokenURI:", contract.token_uri(1001))
# AliceからBobへNFTを移転します。
contract.transfer_from(
from_address="0xAlice",
to_address="0xBob",
token_id=1001,
)
print("移転後の所有者:", contract.owner_of(1001))
このコードでは、token_id = 1001 のNFTをAliceに発行し、その後Bobへ移転しています。
実際のERC-721では、さらに多くの処理があります。
たとえば、呼び出し元が本当に所有者なのか、承認されたアドレスなのか、移転先がNFTを受け取れるコントラクトなのか、などを確認します。
ただし、入口としては次の理解で十分です。
NFTコントラクトは、tokenIdごとに所有者を管理する。
ownerOf(tokenId) で所有者を確認できる。
移転すると、そのtokenIdに紐づく所有者が変わる。
2.6 tokenURIは「NFTの説明書の場所」
NFTの話では、tokenURI という言葉もよく出てきます。
tokenURI は、かなりざっくり言えば そのNFTの説明書の場所 です。
たとえば、番号付きチケットで考えると、チケットIDだけでは次のような情報は分かりません。
- イベント名
- 開催日
- 座席
- チケット画像
- 注意事項
- 特典の説明
そのため、チケットIDから詳細ページを見に行くような仕組みがあると便利です。
NFTでも似ています。
tokenId はトークンを区別する番号ですが、そのNFTが何を表すのかを説明する情報は、メタデータとして別に用意されることがあります。
ERC-721のメタデータ拡張では、tokenURI(tokenId) が、その資産に対応するURIを返す関数として定義されています。
また、ERC-721 Metadata JSON Schemaでは、name、description、image といった項目が示されています。
参考: EIP-721: Non-Fungible Token Standard
イメージとしては、次のようなJSONです。
{
"name": "Summer Live Ticket #1001",
"description": "2026年のサマーライブ入場チケットです。",
"image": "ipfs://example-ticket-image-1001"
}
ここで注意したいのは、tokenURI が直接画像を指すとは限らないことです。
多くの場合、tokenURI はメタデータJSONを指し、そのJSONの中に画像のURIが入ります。
つまり、次のような流れです。
tokenId
-> tokenURI
-> メタデータJSON
-> image
-> 画像や動画などのコンテンツ
この構造を知っておくと、「NFTの画像はどこにあるのか」「NFTの表示内容はどこから来るのか」を理解しやすくなります。
2.7 小さなコードで見る:tokenURIからメタデータをたどるイメージ
ここで、tokenURI からメタデータをたどる流れを、Pythonで単純化して見てみます。
このコードでは、実際にIPFSやHTTPへアクセスしません。
代わりに、URIとメタデータJSONを辞書で対応づけて、tokenId -> tokenURI -> metadata の流れだけを確認します。
# これは tokenId -> tokenURI -> メタデータJSON の流れを理解するための学習用コードです。
# 実際のIPFS、HTTP、Ethereumノードへアクセスするものではありません。
# tokenIdごとにtokenURIを管理している、という想定です。
token_uris = {
1001: "ipfs://metadata/summer-live-ticket-1001.json",
1002: "ipfs://metadata/summer-live-ticket-1002.json",
}
# tokenURIの先にあるメタデータJSONを、ここでは辞書で表します。
# 実際には、tokenURIが指す先からJSONを取得することがあります。
metadata_store = {
"ipfs://metadata/summer-live-ticket-1001.json": {
"name": "Summer Live Ticket #1001",
"description": "A列1番のサマーライブ入場チケットです。",
"image": "ipfs://images/summer-live-ticket-1001.png",
},
"ipfs://metadata/summer-live-ticket-1002.json": {
"name": "Summer Live Ticket #1002",
"description": "A列2番のサマーライブ入場チケットです。",
"image": "ipfs://images/summer-live-ticket-1002.png",
},
}
def get_metadata(token_id: int) -> dict:
"""
tokenIdからメタデータを取得する学習用関数です。
処理の流れ:
1. tokenIdに対応するtokenURIを探す
2. tokenURIに対応するメタデータJSONを探す
3. メタデータを返す
注意:
- 実際のNFTでは、tokenURIがHTTP URLやIPFS URIを返す場合があります。
- そのURIの先にあるデータが長期的に参照できるかは、保存方式や運用に依存します。
"""
if token_id not in token_uris:
raise ValueError("存在しないtokenIdです。")
uri = token_uris[token_id]
if uri not in metadata_store:
raise ValueError("メタデータが見つかりません。")
return metadata_store[uri]
metadata = get_metadata(1001)
print("name:", metadata["name"])
print("description:", metadata["description"])
print("image:", metadata["image"])
このコードでは、tokenId = 1001 から tokenURI を調べ、そのURIに対応するメタデータを取得しています。
ここで重要なのは、NFTの表示情報が、ブロックチェーン上の tokenId だけで完結しない場合があることです。
tokenURI の先にあるメタデータや、さらにその先の画像URIをたどることで、アプリやマーケットプレイス上に表示される内容が決まります。
この話は、後の「メタデータとコンテンツ保存場所」の章でさらに詳しく扱います。
2.8 NFTで分かること・分からないこと
ここまでで、NFTの基本構造が少し見えてきました。
NFTによってオンチェーンで確認しやすいことはあります。
一方で、NFTだけでは分からないこともあります。
整理すると、次のようになります。
| 観点 | NFTから確認しやすいこと | NFTだけでは分からないこと |
|---|---|---|
| 識別 | どのコントラクトのどのtokenIdか | それが現実世界で何を意味するか |
| 所有 | どのアドレスがtokenIdを持っているか | そのアドレスの現実の持ち主が誰か |
| 移転 | どのアドレスからどのアドレスへ移ったか | なぜ移転したのか、契約内容は何か |
| メタデータ | tokenURIがどこを指しているか | 参照先が将来も維持されるか |
| コンテンツ | image URIなどを参照できる場合がある | 画像の著作権や正規性が自動的に保証されるか |
| 権利 | 規約へのリンクがある場合がある | 著作権や商用利用権が自動的に移るか |
この表で特に大切なのは、NFTが得意なのは トークンの識別と所有者の記録 だという点です。
NFTは、「このトークンはどのコントラクトの何番か」「いまどのアドレスが持っているか」「どのように移転したか」を確認しやすくします。
一方で、NFTだけで、著作権、商用利用権、現実世界の所有権、正規発行者であること、保存先の永続性まで自動的に保証するわけではありません。
この切り分けは、NFTを安全に理解するうえでとても重要です。
たとえば、NFTマーケットプレイスで画像が表示されていたとしても、確認すべきことは画像だけではありません。
- コントラクトアドレスは正しいか
- 発行元は正規か
-
tokenIdは何か - メタデータはどこを指しているか
- 画像や動画はどこに保存されているか
- 保有者にどのような権利があるか
- ウォレット接続や承認操作に危険はないか
このように、NFTは「見るもの」ではなく、たどって確認するもの と考えると、かなり理解しやすくなります。
💡 豆知識
NFTを表示するアプリやマーケットプレイスは、ブロックチェーン上のtokenIdやtokenURIをもとに、画像や説明を表示します。
つまり、利用者が画面で見ているNFT画像は、オンチェーンのトークン情報、メタデータ、保存先、アプリ側の表示処理が組み合わさって見えていることがあります。
そのため、画面に画像が表示されていることと、権利や正規性がすべて保証されることは分けて考える必要があります。
2.9 この章のまとめ
この章では、NFTをざっくり整理しました。
1章では、限定チケットや会員証のように、一つずつ区別できるものから考えました。
この章では、その感覚をNFTの基本構造に接続しました。
NFTは、単なる画像ファイルではありません。
まずは、スマートコントラクト上で一意に識別され、所有者を記録できるトークンとして見ることが大切です。
この章で押さえたいポイントは、次の通りです。
| ポイント | 説明 |
|---|---|
| NFTはコントラクトアドレスとtokenIdで区別する |
tokenId だけでなく、どのコントラクトのIDかを見る必要がある |
| NFTを持つとは、まず所有者アドレスに紐づいている状態 | ERC-721では ownerOf(tokenId) で所有者を確認できる |
| NFTは複数の層でできている | トークン、メタデータ、コンテンツ、権利関係を分けて見る |
| tokenURIは説明書の場所に近い |
tokenURI の先にメタデータJSONがあり、そこから画像URIなどを参照する場合がある |
| NFTだけでは分からないこともある | 著作権、利用権、正規性、保存先の継続性は別途確認が必要 |
ここまでの内容を一言でまとめると、次のようになります。
NFTは、ブロックチェーン上で「どのコントラクトの、どのtokenIdを、どのアドレスが持っているか」を記録する仕組みとして理解すると分かりやすいです。
ただし、そのNFTが何を表すか、画像がどこにあるか、どのような権利があるかは、メタデータや規約まで分けて確認する必要があります。
次の章では、このNFTの代表的な標準である ERC-721 をもう少し詳しく見ていきます。
ERC-721では、ownerOf、balanceOf、Transfer、approve、setApprovalForAll、tokenURI など、NFTを扱うための基本的なインターフェースが定義されています。
次章では、それぞれの役割を、今回の「番号付きチケット」の例とつなげながら整理します。
3. ERC-721とは何か
この章では、NFTの代表的な標準である ERC-721 を整理します。
ポイントは、ERC-721を「NFTを作るための魔法」ではなく、ウォレットやマーケットプレイス、アプリがNFTを同じように扱えるようにする共通の約束事 として見ることです。
前の章では、NFTを「どのコントラクトの、どの tokenId を、どのアドレスが持っているか」という形で整理しました。
ここからは、その考え方をEthereum上の代表的な標準である ERC-721 に接続します。
ERC-721は、かなりざっくり言うと、NFTを扱うスマートコントラクトが持つべき基本的な関数やイベントを決めた標準です。
EIP-721では、ERC-721はスマートコントラクト内でNFTの標準APIを実装できるようにし、NFTを追跡・移転する基本機能を提供すると説明されています。
参考: EIP-721: Non-Fungible Token Standard
ここで大切なのは、ERC-721が「どんな画像を使うべきか」「どんな権利を与えるべきか」「いくらで売るべきか」を決めているわけではない、という点です。
ERC-721が主に決めているのは、次のような基本操作です。
- あるアドレスがNFTを何個持っているかを確認する
- ある
tokenIdの所有者を確認する - NFTを別のアドレスへ移転する
- 第三者に移転操作を許可する
- 所有者変更や承認変更をイベントとして記録する
- 必要に応じて、NFTの名前やメタデータURIを取得する
身近な例で言えば、ERC-721は「チケットのデザイン」ではなく、番号付きチケットを管理するための共通フォーマット に近いです。
チケットの見た目やイベント内容は発行者ごとに違っていても、次のような確認方法が共通していると便利です。
| 共通化したい操作 | チケットの例 | ERC-721でのイメージ |
|---|---|---|
| 誰が持っているか確認する | 座席A-12のチケット所有者を確認する | ownerOf(tokenId) |
| 何枚持っているか確認する | Aさんが何枚チケットを持っているか数える | balanceOf(owner) |
| 別の人へ渡す | AさんからBさんへチケットを譲る |
transferFrom / safeTransferFrom
|
| 代理で扱える人を決める | チケット販売サイトに出品を許可する |
approve / setApprovalForAll
|
| 移転履歴を残す | チケットの持ち主が変わった記録を残す |
Transfer イベント |
| 説明情報をたどる | チケットのイベント名や画像を確認する | tokenURI |
Ethereum公式ドキュメントでも、ERC-721はユニークなデジタル資産を表現するためのNFT標準であり、コレクション、アクセスキー、宝くじ券、コンサートやスポーツの番号付き座席などに向いていると説明されています。
参考: Ethereum.org: ERC-721 Non-Fungible Token Standard
3.1 ERC-721は「共通の呼び出し方」を決める
まず、ERC-721を理解するときは、標準という言葉に注目すると分かりやすいです。
もしNFTコントラクトごとに、所有者を確認する関数名がバラバラだったらどうなるでしょうか。
| コントラクト | 所有者確認の関数名 |
|---|---|
| Aコントラクト | ownerOf(tokenId) |
| Bコントラクト | getOwner(id) |
| Cコントラクト | ticketHolder(number) |
| Dコントラクト | whoHasThis(token) |
このように関数名や引数の意味がバラバラだと、ウォレットやマーケットプレイスは、NFTコントラクトごとに個別対応しなければなりません。
一方で、ERC-721に従っていれば、アプリ側は次のように考えられます。
ERC-721に対応しているなら、ownerOf(tokenId) で所有者を確認できるはず。
ERC-721に対応しているなら、Transfer イベントで移転を追えるはず。
ERC-721に対応しているなら、approve や setApprovalForAll の考え方で代理操作を扱えるはず。
このように、ERC-721はNFTそのものの価値を決めるものではなく、NFTを外部のアプリから扱いやすくするための共通インターフェース として重要です。
💡 豆知識
ERC-721のERCは、Ethereum Request for Comments の文脈で使われる言葉です。
ざっくり言えば、Ethereum上のアプリケーションやスマートコントラクトで使う標準的な仕様を提案・整理する枠組みの一つです。
ERC-20が「同じ種類のトークンを数量で扱う」標準として有名なのに対し、ERC-721は「一つずつ区別するトークン」を扱う標準として広く使われています。
EIP-721でも、ERC-20は各トークンが同一である代替可能トークンには向いている一方、NFTでは各資産が区別されるため、それぞれの所有権を別々に追跡する必要があると説明されています。
3.2 ERC-721の基本関数とイベント
ERC-721の仕様には、いくつかの関数とイベントが定義されています。
いきなりSolidityのインターフェースを読むと少し固く感じるため、まずは役割ごとに分けて見てみます。
| 分類 | 名前 | ざっくりした役割 |
|---|---|---|
| 所有確認 | ownerOf(tokenId) |
指定したNFTの所有者アドレスを返す |
| 数量確認 | balanceOf(owner) |
指定したアドレスが持つNFTの数を返す |
| 移転 | transferFrom(from, to, tokenId) |
NFTを別のアドレスへ移転する |
| 安全な移転 | safeTransferFrom(from, to, tokenId) |
受け取り側がNFTを受け取れるか確認しながら移転する |
| 個別承認 | approve(to, tokenId) |
あるNFTだけを第三者が扱えるようにする |
| 包括承認 | setApprovalForAll(operator, approved) |
自分のNFT群を第三者が扱えるようにする |
| 承認確認 | getApproved(tokenId) |
あるNFTに承認されたアドレスを確認する |
| 包括承認確認 | isApprovedForAll(owner, operator) |
あるoperatorが所有者のNFTを扱えるか確認する |
| 所有変更イベント | Transfer(from, to, tokenId) |
NFTの所有者が変わったことを記録する |
| 個別承認イベント | Approval(owner, approved, tokenId) |
個別承認が変わったことを記録する |
| 包括承認イベント | ApprovalForAll(owner, operator, approved) |
包括承認が変わったことを記録する |
EIP-721では、balanceOf、ownerOf、safeTransferFrom、transferFrom、approve、setApprovalForAll、getApproved、isApprovedForAll といった関数や、Transfer、Approval、ApprovalForAll というイベントが定義されています。
ここで注意したいのは、ERC-721が「画面にどう表示するか」を直接決めているわけではないことです。
ERC-721が主に扱うのは、トークンの所有、移転、承認 です。
画像や説明文、属性などは、後で出てくる tokenURI やメタデータの話につながります。
3.3 小さなコードで見る:ownerOf・balanceOf・Transferイベント
ここで、ERC-721の基本操作を、Pythonでかなり小さく再現してみます。
次のコードは、実際のEthereumやSolidityの実装ではありません。
目的は、ownerOf、balanceOf、Transfer イベントの考え方をつかむことです。
# ERC-721の基本操作を理解するための学習用コードです。
# 実際のEthereum上のスマートコントラクトやSolidity実装ではありません。
from dataclasses import dataclass, field
@dataclass
class MiniERC721:
"""ERC-721の一部の考え方だけを小さく再現した学習用クラスです。"""
name: str
symbol: str
owners: dict[int, str] = field(default_factory=dict)
event_log: list[dict] = field(default_factory=list)
def mint(self, to: str, token_id: int) -> None:
"""
新しいNFTを発行する処理です。
注意:
- ERC-721仕様では、mintの具体的な関数名や処理は標準化されていません。
- ここでは学習用に、token_idをtoの所有物として登録します。
"""
if token_id in self.owners:
raise ValueError("このtokenIdはすでに存在します。")
self.owners[token_id] = to
# ERC-721では、所有者変更をTransferイベントとして扱います。
# mintの場合は、fromを0x0のような空アドレスとして表すことがあります。
self.event_log.append({
"event": "Transfer",
"from": "0x0000000000000000000000000000000000000000",
"to": to,
"tokenId": token_id,
})
def owner_of(self, token_id: int) -> str:
"""指定したtokenIdの現在の所有者を返します。"""
if token_id not in self.owners:
raise ValueError("存在しないtokenIdです。")
return self.owners[token_id]
def balance_of(self, owner: str) -> int:
"""指定したアドレスが持つNFT数を数えます。"""
return sum(1 for current_owner in self.owners.values() if current_owner == owner)
def transfer_from(self, from_addr: str, to_addr: str, token_id: int) -> None:
"""
NFTの所有者をfrom_addrからto_addrへ変更します。
注意:
- 実際のERC-721では、呼び出し元が所有者または承認済みかも確認します。
- この最小モデルでは、まず所有者チェックとTransferイベントだけに絞っています。
"""
if self.owner_of(token_id) != from_addr:
raise PermissionError("from_addrは現在の所有者ではありません。")
self.owners[token_id] = to_addr
self.event_log.append({
"event": "Transfer",
"from": from_addr,
"to": to_addr,
"tokenId": token_id,
})
# 学習用のNFTコントラクトを作ります。
tickets = MiniERC721(name="Sample Concert Ticket", symbol="SCT")
# tokenId 101と102をAliceへ発行します。
tickets.mint("Alice", 101)
tickets.mint("Alice", 102)
print("Aliceの保有数:", tickets.balance_of("Alice"))
print("tokenId 101の所有者:", tickets.owner_of(101))
# tokenId 101をAliceからBobへ移転します。
tickets.transfer_from("Alice", "Bob", 101)
print("Aliceの保有数:", tickets.balance_of("Alice"))
print("Bobの保有数:", tickets.balance_of("Bob"))
print("tokenId 101の所有者:", tickets.owner_of(101))
print("イベントログ:", tickets.event_log)
このコードでは、tokenId ごとに所有者を記録しています。
balance_of("Alice") は、Aliceが持っているNFTの数を数えます。
owner_of(101) は、tokenId = 101 の現在の所有者を返します。
transfer_from("Alice", "Bob", 101) は、Aliceが持っていた tokenId = 101 をBobへ移します。
実際のERC-721では、さらに「呼び出し元が本当に移転してよい人か」「移転先がゼロアドレスではないか」「移転先がスマートコントラクトならNFTを受け取れるか」など、多くの条件を確認します。
ただし、入口としては次のイメージで十分です。
ownerOf(tokenId)
-> このNFTを現在持っているアドレスを確認する
balanceOf(owner)
-> このアドレスが持っているNFTの個数を確認する
Transfer(from, to, tokenId)
-> NFTの所有者が変わったことを記録する
3.4 transferFromとsafeTransferFromの違い
ERC-721には、NFTを移転する関数として transferFrom と safeTransferFrom があります。
名前だけ見ると似ていますが、重要な違いがあります。
| 関数 | ざっくりした役割 | 注意点 |
|---|---|---|
transferFrom |
所有者を別のアドレスへ移す | 呼び出し側が、受け取り先がNFTを扱えるか確認する責任を持つ |
safeTransferFrom |
受け取り先がスマートコントラクトの場合、NFTを受け取れるか確認する | 対応していないコントラクトへ送って失われるリスクを減らす |
EIP-721では、transferFrom について、呼び出し側が受け取り先がNFTを受け取れることを確認する責任を持つと説明されています。
一方、safeTransferFrom では、移転先がスマートコントラクトの場合に onERC721Received を呼び出し、期待される戻り値でなければ移転をrevertする仕組みが説明されています。
参考: EIP-721: transferFrom and safeTransferFrom
身近な例で言えば、次のような違いです。
| 例 |
transferFrom のイメージ |
safeTransferFrom のイメージ |
|---|---|---|
| 紙のチケットを渡す | 相手にそのまま渡す | 相手が受け取れる状態か確認してから渡す |
| 宅配便 | 住所だけ見て発送する | 受取可能な窓口か確認してから発送する |
| NFT | 指定アドレスへ移転する | 相手がコントラクトならNFT受取口があるか確認する |
もちろん、実際にはスマートコントラクトの仕様やウォレットの挙動が関係します。
ここでは、safe は「絶対安全」という意味ではなく、受け取り先がNFTに対応しているかを確認する仕組みがある くらいに理解するとよいです。
3.5 小さなコードで見る:safeTransferFromの受け取り確認
次のコードでは、safeTransferFrom の考え方をかなり単純化して再現します。
実際のERC-721では、移転先がスマートコントラクトの場合、onERC721Received が期待される値を返すかを確認します。
ここではPythonで、「NFTを受け取れる窓口を持っているか」を確認するイメージだけを見ます。
# safeTransferFromの考え方を理解するための学習用コードです。
# 実際のSolidityやEVMの挙動を再現するものではありません。
from dataclasses import dataclass, field
# ERC-721の受け取り成功を表す値を、学習用に文字列で表します。
ERC721_RECEIVED = "onERC721Received:OK"
class NFTReceiverContract:
"""NFTを受け取れるスマートコントラクトを表す学習用クラスです。"""
def on_erc721_received(self, operator: str, from_addr: str, token_id: int, data: str) -> str:
"""
ERC-721のonERC721Receivedに相当する受け取り口です。
実際の仕様では、決められたbytes4の値を返す必要があります。
"""
print(f"受け取り処理: tokenId={token_id}, from={from_addr}, operator={operator}, data={data}")
return ERC721_RECEIVED
class PlainContract:
"""NFT受け取り口を持たないスマートコントラクトを表す学習用クラスです。"""
pass
@dataclass
class SafeTransferDemo:
"""safeTransferFromの受け取り確認だけに絞った学習用モデルです。"""
owners: dict[int, str] = field(default_factory=dict)
def owner_of(self, token_id: int) -> str:
"""指定したtokenIdの所有者を返します。"""
if token_id not in self.owners:
raise ValueError("存在しないtokenIdです。")
return self.owners[token_id]
def safe_transfer_from(self, operator: str, from_addr: str, to, token_id: int, data: str = "") -> None:
"""
NFTを安全に移転するイメージの処理です。
toが文字列なら通常のウォレットアドレスとみなします。
toがオブジェクトならスマートコントラクトとみなし、
on_erc721_receivedを持っているか確認します。
"""
if self.owner_of(token_id) != from_addr:
raise PermissionError("from_addrは現在の所有者ではありません。")
# 移転先がスマートコントラクトらしい場合、NFT受け取り口を確認します。
if not isinstance(to, str):
if not hasattr(to, "on_erc721_received"):
raise TypeError("移転先コントラクトはNFTを受け取る口を持っていません。")
result = to.on_erc721_received(operator, from_addr, token_id, data)
if result != ERC721_RECEIVED:
raise TypeError("移転先コントラクトが期待した受け取り値を返しません。")
# 学習用に、コントラクトの名前を所有者として記録します。
self.owners[token_id] = to.__class__.__name__
else:
# 移転先が通常のアドレスなら、そのまま所有者を更新します。
self.owners[token_id] = to
nft = SafeTransferDemo(owners={1: "Alice"})
receiver = NFTReceiverContract()
plain_contract = PlainContract()
# NFTを受け取れるコントラクトへ送る例です。
nft.safe_transfer_from(operator="Alice", from_addr="Alice", to=receiver, token_id=1, data="ticket transfer")
print("tokenId 1の所有者:", nft.owner_of(1))
# もう一度Aliceに戻したことにして、受け取れないコントラクトへ送る例を試します。
nft.owners[1] = "Alice"
try:
nft.safe_transfer_from(operator="Alice", from_addr="Alice", to=plain_contract, token_id=1)
except TypeError as error:
print("移転失敗:", error)
このコードでは、NFTReceiverContract は on_erc721_received という受け取り口を持っています。
そのため、safe_transfer_from による移転を受け取れます。
一方、PlainContract は受け取り口を持っていないため、移転に失敗します。
実際のEthereumでは、ここでいう受け取り口が onERC721Received であり、期待される戻り値が返らない場合はトランザクションがrevertされます。
この仕組みは、NFTをスマートコントラクトへ送ったあと、そのコントラクトがNFTを扱えずに取り出せなくなるリスクを減らすために重要です。
💡 豆知識
safeTransferFromのsafeは、「秘密鍵を盗まれない」「詐欺に遭わない」という意味での安全ではありません。
主に、移転先がスマートコントラクトだった場合に、そのコントラクトがNFTを受け取れるか確認するという意味合いです。
そのため、怪しいサイトにウォレット接続してよいという意味ではない点に注意が必要です。
3.6 approveとsetApprovalForAllは「代理操作の許可」
NFTをマーケットプレイスに出品するとき、利用者が毎回手作業で相手へ直接NFTを送るとは限りません。
たとえば、次のような場面を考えます。
AliceがNFTを持っている。
Aliceは、そのNFTをマーケットプレイスに出品したい。
購入者が現れたら、マーケットプレイスがNFT移転を実行できるようにしたい。
このときに関係するのが、approve や setApprovalForAll です。
| 関数 | 許可の範囲 | 身近なイメージ |
|---|---|---|
approve(to, tokenId) |
特定のNFTだけを許可 | この1枚のチケットだけ代理で扱ってよい |
setApprovalForAll(operator, true) |
自分のNFT群をまとめて許可 | 自分のチケット全体をこの販売窓口で扱ってよい |
setApprovalForAll(operator, false) |
包括許可を取り消す | もうこの販売窓口には任せない |
EIP-721では、approve は特定NFTの承認アドレスを変更する関数、setApprovalForAll は第三者であるoperatorが所有者のすべてのNFTを管理できるようにする、または取り消す関数として定義されています。
参考: EIP-721: Approval and operator
この仕組みは便利です。
ただし、同時に注意も必要です。
特に setApprovalForAll は、自分のNFT群に対する強い操作権限を第三者に与えるものです。
信頼できないサイトや偽サイトに対して承認してしまうと、意図しないNFT移転につながる可能性があります。
この話は、後の「情報セキュリティの観点で見る注意点」の章で詳しく扱います。
ここではまず、approve と setApprovalForAll は 代理操作を許可する仕組み だと押さえておきます。
3.7 小さなコードで見る:個別承認と包括承認
次のコードでは、approve と setApprovalForAll の違いを学習用に再現します。
実際のERC-721では、承認の状態、呼び出し元、移転処理、イベント発行などがスマートコントラクト上で扱われます。
ここでは、誰がNFTを移転してよいか という判定だけに絞ります。
# ERC-721のapproveとsetApprovalForAllを理解するための学習用コードです。
# 実際のEthereum上のスマートコントラクトではありません。
from dataclasses import dataclass, field
@dataclass
class ApprovalDemoERC721:
"""個別承認と包括承認の違いを確認する学習用クラスです。"""
owners: dict[int, str] = field(default_factory=dict)
token_approvals: dict[int, str] = field(default_factory=dict)
operator_approvals: dict[tuple[str, str], bool] = field(default_factory=dict)
def owner_of(self, token_id: int) -> str:
"""指定したtokenIdの所有者を返します。"""
if token_id not in self.owners:
raise ValueError("存在しないtokenIdです。")
return self.owners[token_id]
def approve(self, owner: str, approved: str, token_id: int) -> None:
"""
特定のtokenIdだけを第三者に操作許可します。
例: AliceがMarketにtokenId 10だけを扱う権限を与える。
"""
if self.owner_of(token_id) != owner:
raise PermissionError("承認できるのは現在の所有者だけです。")
self.token_approvals[token_id] = approved
def set_approval_for_all(self, owner: str, operator: str, approved: bool) -> None:
"""
ownerが持つNFT群をoperatorが扱えるかどうかを設定します。
例: AliceがMarketに、自分のNFT全体を扱う権限を与える。
"""
self.operator_approvals[(owner, operator)] = approved
def is_approved_or_owner(self, caller: str, token_id: int) -> bool:
"""
callerがtoken_idを移転してよいかを判定します。
所有者本人、個別承認された人、包括承認されたoperatorならTrueです。
"""
owner = self.owner_of(token_id)
if caller == owner:
return True
if self.token_approvals.get(token_id) == caller:
return True
if self.operator_approvals.get((owner, caller), False):
return True
return False
def transfer_from(self, caller: str, from_addr: str, to_addr: str, token_id: int) -> None:
"""
callerが権限を持っていれば、NFTをfrom_addrからto_addrへ移転します。
移転後は、そのtokenIdに対する個別承認を解除します。
"""
if self.owner_of(token_id) != from_addr:
raise PermissionError("from_addrは現在の所有者ではありません。")
if not self.is_approved_or_owner(caller, token_id):
raise PermissionError("callerにはこのNFTを移転する権限がありません。")
self.owners[token_id] = to_addr
self.token_approvals.pop(token_id, None)
nft = ApprovalDemoERC721(owners={10: "Alice", 11: "Alice"})
# tokenId 10だけをMarketに個別承認します。
nft.approve(owner="Alice", approved="Market", token_id=10)
print("MarketはtokenId 10を移転できるか:", nft.is_approved_or_owner("Market", 10))
print("MarketはtokenId 11を移転できるか:", nft.is_approved_or_owner("Market", 11))
# AliceがMarketに包括承認を与えます。
nft.set_approval_for_all(owner="Alice", operator="Market", approved=True)
print("包括承認後、MarketはtokenId 11を移転できるか:", nft.is_approved_or_owner("Market", 11))
# MarketがtokenId 11をBobへ移転します。
nft.transfer_from(caller="Market", from_addr="Alice", to_addr="Bob", token_id=11)
print("tokenId 11の所有者:", nft.owner_of(11))
このコードでは、approve は tokenId = 10 だけに効きます。
そのため、最初はMarketが tokenId = 10 を移転できても、tokenId = 11 は移転できません。
一方で、set_approval_for_all を使うと、MarketはAliceが持つNFT群を扱えるようになります。
これは便利な反面、許可する相手を間違えると危険です。
NFTを扱うときに、ウォレット画面で「承認」「Approve」「Set approval for all」のような表示を見かけたら、単なる確認ボタンではなく、第三者に操作権限を与える可能性がある操作 と考える必要があります。
3.8 tokenURIとメタデータ拡張
ERC-721には、メタデータ拡張も用意されています。
メタデータ拡張では、コレクション名を返す name()、略称を返す symbol()、そして特定のNFTのURIを返す tokenURI(tokenId) が定義されています。
EIP-721では、tokenURI は特定の資産に対するURIを返し、そのURIはERC721 Metadata JSON Schemaに従うJSONファイルを指す場合があると説明されています。
参考: EIP-721: Metadata Extension
2章でも触れた通り、tokenURI はNFTの「説明書の場所」に近いです。
ERC-721コントラクト
└─ tokenId
└─ tokenURI
└─ メタデータJSON
├─ name
├─ description
└─ image
ここで大切なのは、tokenURI が返すのは多くの場合、画像そのものではなく、画像などへの参照を含むメタデータだという点です。
たとえば、次のようなJSONが考えられます。
{
"name": "Sample Ticket #101",
"description": "A sample NFT ticket for learning ERC-721.",
"image": "ipfs://bafy.../ticket-101.png"
}
ただし、ERC-721のメタデータ拡張はoptionalです。
つまり、すべてのERC-721コントラクトが必ず tokenURI を実装しているとは限りません。
また、EIP-721では、URIが時間とともに変わる可能性があることにも触れられています。
そのため、実際にNFTを確認するときは、「tokenURI があるか」「どこを指しているか」「その内容が固定されているか」「保存先は継続的に参照できそうか」を見る必要があります。
この保存場所の話はとても重要なので、次の章で tokenURI、メタデータJSON、IPFS、HTTP URL、オンチェーン保存の違いをもう少し詳しく扱います。
3.9 ERC-721が決めていること・決めていないこと
ここまで見ると、ERC-721はNFTの重要な標準であることが分かります。
ただし、ERC-721がNFTに関するすべてを決めているわけではありません。
| 観点 | ERC-721が主に決めていること | ERC-721だけでは決まらないこと |
|---|---|---|
| 識別 |
tokenId でNFTを区別する |
そのIDにどんな意味を持たせるか |
| 所有 |
ownerOf で所有者を確認する |
現実世界の所有権まで移るか |
| 移転 |
transferFrom / safeTransferFrom の基本形 |
移転を禁止・制限する独自ルールの有無 |
| 承認 |
approve / setApprovalForAll の基本形 |
どのサービスを信頼して承認するか |
| イベント |
Transfer などのイベント |
その履歴をどう表示・分析するか |
| メタデータ | optionalな tokenURI
|
メタデータが固定か、保存先が維持されるか |
| 権利関係 | 直接は扱わない | 著作権、商用利用権、会員権、規約など |
EIP-721では、mintingやburningの具体的な関数は仕様に含まれておらず、コントラクト側が別の手段で実装できると説明されています。
参考: EIP-721: Transfer Mechanism
また、ERC-721は、NFTが表す対象の権利関係を自動的に保証する標準でもありません。
たとえば、あるNFTが画像を参照していたとしても、ERC-721だけで次のことが分かるわけではありません。
- その画像の著作権者は誰か
- NFT保有者に商用利用権があるか
- NFTが正規発行者によって発行されたものか
- メタデータや画像が将来も同じ場所で見られるか
- 現実世界の会員権やチケット権利とどう結びついているか
ERC-721は、NFTを扱ううえで非常に重要な土台です。
しかし、その上にどのような意味や権利を載せるかは、プロジェクトの設計、規約、保存方式、外部システムとの連携によって変わります。
💡 豆知識
EIP-721では、NFTの例として家やユニークなアート、バーチャルな収集品などが挙げられています。
ただし、ERC-721が定義しているのは、それらの資産をどう法的に所有するかではなく、スマートコントラクト上で区別できるトークンとしてどう追跡・移転するかです。
そのため、「NFTがある」ことと「現実世界の権利がどう扱われるか」は、分けて確認する必要があります。
3.10 ERC-721を読むときの注意点
最後に、ERC-721を学ぶときの注意点を整理します。
ERC-721はNFTの代表的な標準ですが、実際のNFTプロジェクトはERC-721だけで成り立っているわけではありません。
たとえば、NFTコントラクトには次のような独自実装が追加されることがあります。
- mintの条件
- mint価格
- 発行上限
- whitelistやallowlist
- transfer制限
- burn処理
- ロイヤリティ情報
- メタデータの更新可否
- 管理者権限
- pause機能
これらは、ERC-721の基本インターフェースだけを見ても分からない場合があります。
そのため、NFTを技術的に確認するときは、次の順番で見ると整理しやすいです。
この順番で見ると、NFTを「画像が見えるかどうか」だけではなく、技術的な層と権利・運用の層に分けて確認できます。
3.11 この章のまとめ
この章では、NFTの代表的な標準であるERC-721を整理しました。
ERC-721は、NFTを扱うスマートコントラクトの共通インターフェースです。
所有者の確認、保有数の確認、移転、承認、イベント、メタデータURIなど、NFTをウォレットやマーケットプレイスから扱いやすくするための基本操作を定義しています。
この章で押さえたいポイントは、次の通りです。
| ポイント | 説明 |
|---|---|
| ERC-721はNFTの代表的な標準 | NFTを追跡・移転するための標準APIを定義する |
ownerOf は所有者確認 |
特定の tokenId をどのアドレスが持っているか確認する |
balanceOf は保有数確認 |
あるアドレスが持つNFTの数を確認する |
Transfer は所有者変更イベント |
NFTの移転、発行、焼却などに関係する所有変更を追いやすくする |
transferFrom と safeTransferFrom は移転関数 |
safeTransferFrom は受け取り先コントラクトの対応確認を行う |
approve と setApprovalForAll は代理操作の許可 |
便利だが、信頼できない相手への承認は危険につながる |
tokenURI はメタデータへの入口 |
NFTの説明や画像URIなどをたどるための情報になる場合がある |
| ERC-721だけで権利関係は決まらない | 著作権、利用権、会員権などは規約や外部の取り決めも確認する必要がある |
ここまでの内容を一言でまとめると、次のようになります。
ERC-721は、NFTを「一つずつ区別し、所有者を追跡し、移転や承認を共通の方法で扱う」ための標準です。
ただし、NFTが参照する画像、保存場所、著作権、利用権まで自動的に決めるものではありません。
次の章では、ERC-721の tokenURI からつながる メタデータとコンテンツ保存場所 を詳しく見ていきます。
NFTを理解するときに、「トークンはブロックチェーン上にある」と「画像や説明文も全部ブロックチェーン上にある」を混同すると、かなり誤解しやすくなります。
次章では、メタデータJSON、画像URI、IPFS、HTTP URL、オンチェーン保存の違いを、具体例と小さなコードを使いながら整理します。
4. メタデータとコンテンツ保存場所
この章では、NFTで特に誤解されやすい メタデータ と コンテンツ保存場所 を整理します。
ポイントは、NFTを「ブロックチェーン上のトークン」「トークンが指す説明書」「説明書が指す画像や動画」「それらを保存している場所」に分けて見ることです。
前の章では、ERC-721を使うと、ownerOf、transferFrom、safeTransferFrom、approve、tokenURI などを共通の形で扱えることを見ました。
その中でも、NFTの見た目や説明に深く関係するのが tokenURI です。
ただし、ここで注意したいことがあります。
tokenURI が返すのは、画像そのものとは限りません。
多くの場合、tokenURI は メタデータJSONの場所 を返します。
そして、そのメタデータJSONの中に、画像や動画などのURIが書かれている、という構造になります。
身近な例でいうと、NFT本体は「番号付きチケット」、メタデータは「チケットの説明書」、画像は「チケットに印刷されているデザイン画像」のようなものです。
この図のように、NFTを理解するときは、少なくとも次の4つを分ける必要があります。
| 層 | 内容 | 身近なたとえ |
|---|---|---|
| NFT本体 | コントラクトアドレスと tokenId で識別されるトークン |
チケット番号 |
| メタデータ | 名前、説明、画像URIなどが書かれたJSON | チケットの説明書 |
| コンテンツ | 画像、動画、音声、3Dデータなど | チケットのデザインや特典内容 |
| 保存場所 | HTTPサーバー、IPFS、オンチェーンなど | 説明書や画像を置いている保管場所 |
この章では、ここを丁寧に分けていきます。
参考: ERC-721: Non-Fungible Token Standard
4.1 NFT本体とメタデータは別物
まず、NFT本体とメタデータを分けて考えます。
ERC-721では、tokenURI(uint256 _tokenId) は、あるNFTに対応するURIを返す関数として定義されています。
仕様では、このURIはERC-721 Metadata JSON Schemaに従うJSONファイルを指す場合があると説明されています。
参考: ERC-721: Metadata extension
ここで大事なのは、ERC-721の中心はあくまで NFTの所有者を追跡し、移転できるようにすること だという点です。
tokenURI は、NFTが何を表しているのかをアプリやマーケットプレイスが表示するための入口です。
しかし、tokenURI の先にあるメタデータや画像がどこに保存されているか、後から変更できるか、長期的に参照できるかは、コントラクトやプロジェクトの設計に依存します。
かなり単純化すると、次のように分けられます。
NFT本体:
- コントラクトアドレス
- tokenId
- 所有者アドレス
- 移転や承認のルール
メタデータ:
- name
- description
- image
- attributes
- external_url など
コンテンツ:
- 画像
- 動画
- 音声
- 3Dモデル
- 会員ページや特典へのリンク など
この分け方をしておくと、「NFTを持っている」という言葉を聞いたときに、何を持っているのかを冷静に見られるようになります。
| 言い方 | より丁寧に分けると |
|---|---|
| NFTを持っている | ある tokenId の所有者として自分のアドレスが記録されている |
| NFTの画像を持っている | メタデータが画像URIを指しており、その画像を表示できる状態 |
| NFTの権利を持っている | 規約やライセンスによって、何らかの利用権が認められている可能性がある |
| NFTの著作権を持っている | 著作権が別途移転されている必要がある。NFT所有だけでは通常自動的には決まらない |
最後の権利関係については、6章で詳しく扱います。
この章ではまず、技術的な構造として、NFT本体、メタデータ、コンテンツ、保存場所を分けます。
💡 豆知識
NFTの話でよく出てくるmetadataは、「データについて説明するデータ」という意味です。
写真ファイルでいう撮影日時やカメラ情報、音楽ファイルでいう曲名やアーティスト名に近いイメージです。
NFTでは、名前、説明、画像URI、属性などがメタデータとして扱われることが多いです。
4.2 ERC-721のメタデータJSON
ERC-721では、任意のメタデータ拡張として、name()、symbol()、tokenURI() が定義されています。
そのうち tokenURI() が返すURIの先には、次のようなJSONが置かれることがあります。
{
"name": "Sample Ticket #1",
"description": "A sample numbered ticket used for learning NFT metadata.",
"image": "ipfs://bafy.../ticket-1.png"
}
ERC-721のMetadata JSON Schemaでは、代表的な項目として name、description、image が示されています。
| 項目 | ざっくりした意味 | 表示例 |
|---|---|---|
name |
NFTが表す資産の名前 | Sample Ticket #1 |
description |
NFTが表す資産の説明 | 学習用の番号付きチケットです |
image |
画像リソースを指すURI |
ipfs://... や https://...
|
参考: ERC-721: Metadata JSON Schema
ここで、JSONという言葉に慣れていない場合は、次のように考えると分かりやすいです。
名前: Sample Ticket #1
説明: 学習用の番号付きチケットです
画像: ipfs://.../ticket-1.png
これを、プログラムが読み取りやすい形で書いたものがJSONです。
もちろん、実際のNFTでは attributes、external_url、animation_url など、仕様やマーケットプレイスの慣習に応じて追加の項目が使われることもあります。
ただし、ERC-721仕様そのものが示している中心的な項目は、まず name、description、image と押さえておけば十分です。
4.3 小さなコードで見る:メタデータJSONを読み取る
ここで、メタデータJSONをPythonで読み取ってみます。
このコードは、実際にブロックチェーンやIPFSへアクセスするものではありません。
NFTの tokenURI の先にあるJSONを、アプリやマーケットプレイスがどのように読み取るのかをイメージするための学習用コードです。
# NFTのメタデータJSONを読み取る学習用コードです。
# 実際のブロックチェーンやIPFSにはアクセスしていません。
# ここでは、JSON文字列をPythonの辞書として扱う流れだけを確認します。
import json
# tokenURI の先に、このようなJSONが置かれていると仮定します。
metadata_json = """
{
"name": "Sample Ticket #1",
"description": "A sample numbered ticket used for learning NFT metadata.",
"image": "ipfs://bafyexample/ticket-1.png"
}
"""
# json.loads() は、JSON形式の文字列をPythonの辞書に変換します。
metadata = json.loads(metadata_json)
# アプリやマーケットプレイスは、このような項目を読み取って表示に使います。
print("NFTの名前:", metadata["name"])
print("NFTの説明:", metadata["description"])
print("画像URI:", metadata["image"])
このコードで確認したいのは、メタデータが「表示に使う説明書」のように働くことです。
NFT本体はブロックチェーン上の tokenId と所有者情報です。
一方で、画面に表示される名前や画像URIは、tokenURI の先にあるJSONから読み取られることがあります。
このように分けると、NFTの画面表示が次のような流れで作られていることが見えてきます。
1. NFTコントラクトに tokenURI(tokenId) を問い合わせる
2. tokenURI が返したURIへアクセスする
3. メタデータJSONを読み取る
4. name、description、image などを画面に表示する
4.4 tokenURIから画像までをたどる
次に、tokenURI から画像までをたどる流れをもう少し具体的に見ます。
たとえば、あるNFTの tokenURI が次のURIを返したとします。
https://example.com/metadata/1.json
このURIの先に、次のようなJSONがあるとします。
{
"name": "Sample Ticket #1",
"description": "A sample numbered ticket.",
"image": "https://example.com/images/ticket-1.png"
}
この場合、NFTの画像そのものは tokenURI の場所にあるのではありません。
tokenURI の先にあるJSONの image が、さらに画像の場所を指しています。
つまり、2段階でたどることになります。
ここで誤解しやすいのは、NFTの画像が必ずブロックチェーン上に保存されていると思ってしまうことです。
実際には、画像や動画などの大きなデータは、ブロックチェーン外に置かれていることが多いです。
ブロックチェーン上には、tokenURI のような参照情報だけがある場合があります。
もちろん、すべてのNFTが同じ設計ではありません。
メタデータや画像をオンチェーンに保存する設計もあります。
しかし、コストや扱いやすさの都合から、オフチェーンのURIを参照する設計はよく使われます。
4.5 小さなコードで見る:tokenURIからimageまでたどる
次のコードでは、tokenURI からメタデータを取得し、さらに image を読み取る流れを、ローカルの辞書だけで再現します。
インターネットにはアクセスせず、mock_web という辞書を「仮のWeb」として使います。
これにより、外部通信なしで、NFT表示の流れだけを確認できます。
# tokenURI からメタデータJSONをたどり、さらに image URI を取り出す学習用コードです。
# 実際のHTTPリクエストやIPFSアクセスは行いません。
# mock_web を「仮のWebサーバー」として扱います。
import json
# NFTコントラクトが tokenId ごとに返す tokenURI を、説明用に辞書で表します。
token_uri_by_id = {
1: "https://example.com/metadata/1.json",
2: "https://example.com/metadata/2.json",
}
# tokenURI の先にあるメタデータJSONを、仮のWebとして用意します。
mock_web = {
"https://example.com/metadata/1.json": """
{
"name": "Sample Ticket #1",
"description": "Front row seat ticket.",
"image": "https://example.com/images/ticket-1.png"
}
""",
"https://example.com/metadata/2.json": """
{
"name": "Sample Ticket #2",
"description": "Second row seat ticket.",
"image": "https://example.com/images/ticket-2.png"
}
""",
}
def token_uri(token_id: int) -> str:
"""
tokenIdに対応するtokenURIを返します。
実際のERC-721では、スマートコントラクトの tokenURI(tokenId) を呼び出します。
ここでは、辞書から値を取り出すだけに単純化しています。
"""
return token_uri_by_id[token_id]
def fetch_metadata(uri: str) -> dict:
"""
URIの先にあるメタデータJSONを取得して、Pythonの辞書に変換します。
実際のアプリではHTTPやIPFSゲートウェイなどを使う場合があります。
ここでは、mock_web からJSON文字列を取り出しているだけです。
"""
metadata_json = mock_web[uri]
return json.loads(metadata_json)
# tokenId = 1 のNFTについて、tokenURIからimageまでをたどります。
target_token_id = 1
uri = token_uri(target_token_id)
metadata = fetch_metadata(uri)
print("tokenId:", target_token_id)
print("tokenURI:", uri)
print("name:", metadata["name"])
print("description:", metadata["description"])
print("image:", metadata["image"])
このコードで見たいのは、tokenURI と image の役割の違いです。
| 値 | 役割 |
|---|---|
tokenURI |
メタデータJSONの場所 |
metadata["image"] |
画像ファイルの場所 |
metadata["name"] |
表示名 |
metadata["description"] |
説明文 |
NFTを表示するアプリは、このように複数の情報をたどって、画面に名前や画像を表示します。
4.6 保存場所にはいくつかのパターンがある
NFTのメタデータや画像の保存場所には、いくつかのパターンがあります。
代表的には、次のようなものがあります。
| 保存方式 | ざっくりした説明 | 注意点 |
|---|---|---|
| HTTP URL | 通常のWebサーバー上にJSONや画像を置く | サーバー停止、URL変更、運営終了の影響を受ける |
| IPFS | コンテンツに基づくCIDでデータを参照する | pinningなどでデータを保持しないと、長期可用性に注意が必要 |
| オンチェーン保存 | メタデータや画像をスマートコントラクト上に直接持たせる | ガスコストやデータサイズの制約が大きい |
| その他の分散ストレージ | Arweave、Filecoinなどを組み合わせる場合がある | 仕組みごとに保存保証、費用、取得方法が異なる |
ここで大事なのは、どれが常に正解という話ではないことです。
たとえば、HTTP URLは分かりやすく扱いやすいですが、サーバーが止まると表示できなくなる可能性があります。
IPFSはコンテンツに基づいて参照できる点が特徴ですが、データを誰かが保持し続ける設計が必要です。
オンチェーン保存は、ブロックチェーン上にデータを置くため参照関係は分かりやすい一方、コストやサイズの制約が重くなります。
NFTを見るときは、次の問いを持つと安全です。
-
tokenURIはどこを指しているか - メタデータJSONは固定されているか、変更可能か
- 画像や動画はどこに保存されているか
- その保存先は長期的に維持されそうか
- 画像が見えなくなった場合、NFT本体や権利関係はどう扱われるか
このように、保存場所はNFTの見た目や信頼性に大きく関係します。
4.7 IPFSのCIDは「場所」ではなく「内容にもとづく識別子」
NFTのメタデータや画像では、IPFSが使われることがあります。
IPFSでは、CID、つまりContent Identifierという識別子が出てきます。
IPFS公式ドキュメントでは、CIDはIPFS上の素材を指すラベルであり、コンテンツがどこに保存されているかを示すものではなく、コンテンツ自体に基づくアドレスのようなものだと説明されています。
参考: IPFS Docs: Content Identifiers
ここは少し大切なので、身近な例で考えます。
普通のHTTP URLは、だいたい「場所」を指します。
https://example.com/images/ticket.png
これは、「example.comというサーバーの、images/ticket.pngという場所を見に行く」という感覚です。
一方、IPFSのCIDは、より「内容」に近いものを指します。
ipfs://bafy...
これは、「この内容に対応する識別子を持つデータを探す」という感覚に近いです。
そのため、内容が少しでも変わると、基本的には別のCIDになります。
| 観点 | HTTP URL | IPFS CID |
|---|---|---|
| 指しているもののイメージ | サーバー上の場所 | コンテンツに基づく識別子 |
| 内容が変わった場合 | 同じURLで別内容を返すこともできる | 内容が変わると別CIDになる |
| 長期可用性 | サーバー運営に依存する | データを保持するノードやpinningに依存する |
| 読み方 | その場所へ取りに行く | その内容をネットワーク上で探す |
ただし、ここで「IPFSなら永久保存される」と言い切るのは危険です。
CIDは内容に基づく識別子ですが、そのデータを誰も保持していなければ取得できなくなる可能性があります。
この点は、次で説明するpinningと関係します。
💡 豆知識
IPFSのCIDは、ざっくり言えば「中身から作られる名札」のようなものです。
ファイル名を変えただけなら中身は同じかもしれませんが、中身そのものが変わると名札も変わります。
そのため、同じCIDで別の内容をこっそり返す、ということは通常のHTTP URLより難しくなります。
4.8 小さなコードで見る:内容が変わると識別子も変わる
IPFSのCIDは実際には、ハッシュ、コーデック、エンコード形式など複数の情報を含みます。
ここでは本物のCIDを作るのではなく、内容が変わるとハッシュも変わる という感覚をPythonで確認します。
このコードはIPFSの実装ではありません。
CIDの入口を理解するために、SHA-256ハッシュで「内容にもとづく識別子」のイメージを見ています。
# 内容にもとづく識別子のイメージを確認する学習用コードです。
# これは本物のIPFS CIDを生成するコードではありません。
# IPFSのCIDは、ハッシュだけでなく、codecやmultibaseなどの情報も含みます。
import hashlib
def content_hash(text: str) -> str:
"""
入力された文字列からSHA-256ハッシュを作ります。
ここでは、コンテンツの内容が変わると識別子も変わることを
直感的に確認するために使います。
"""
return hashlib.sha256(text.encode("utf-8")).hexdigest()
metadata_a = '{"name":"Sample Ticket #1","image":"ipfs://example/ticket-1.png"}'
metadata_b = '{"name":"Sample Ticket #1","image":"ipfs://example/ticket-1-fixed.png"}'
hash_a = content_hash(metadata_a)
hash_b = content_hash(metadata_b)
print("metadata_a:", metadata_a)
print("hash_a:", hash_a)
print()
print("metadata_b:", metadata_b)
print("hash_b:", hash_b)
print()
print("同じ識別子ですか?", hash_a == hash_b)
このコードでは、image の文字列を少し変えただけで、ハッシュが大きく変わります。
ここから分かるのは、内容にもとづく参照では、データの中身が変わると識別子も変わるということです。
もちろん、本物のIPFS CIDはこのコードのハッシュ文字列そのものではありません。
しかし、NFTのメタデータや画像をIPFSで扱うときの基本イメージとしては、次の理解が役に立ちます。
IPFSでは、データの「置き場所」ではなく、データの「中身」に基づいて参照する。
そのため、中身が変われば、通常は別の識別子として扱われる。
4.9 IPFSは「永久保存を自動で保証する仕組み」ではない
IPFSについてもう一つ大事なのが、保存の継続性です。
IPFS公式ドキュメントでは、IPFS上でデータを保持し、ガベージコレクションで削除されないようにするには、1つ以上のIPFSノードにデータをpinできると説明されています。
また、重要なデータを保持するためにpinning serviceを使う場合がある一方、第三者のpinning serviceが将来も維持される保証はないため、利用前に調査することが推奨されています。
ここはNFTの誤解につながりやすいところです。
ipfs://... と書かれていると、それだけで永久に見られるような印象を持つかもしれません。
しかし、IPFSのCIDは「その内容を指す識別子」であって、「誰かが必ず永遠に保存してくれる保証」ではありません。
身近なたとえで言えば、図書館の本のISBNに近いです。
ISBNが分かれば、その本を特定しやすくなります。
しかし、ISBNが分かることと、その本が手元の図書館に必ず置いてあることは別です。
| 観点 | たとえ | IPFSでの意味 |
|---|---|---|
| CID | 本のISBNのような識別子 | どの内容かを指す |
| pinning | 図書館がその本を所蔵し続けること | ノードがデータを保持し続ける |
| pinning service | 外部の保管サービス | 第三者がデータ保持を支援する |
| 取得できない状態 | ISBNは分かるが本が見つからない | CIDは分かるが保持ノードが見つからない |
そのため、NFTの保存設計を見るときは、次のような観点が重要になります。
- メタデータはIPFSに置かれているか
- 画像もIPFSに置かれているか
- pinningは誰が行っているか
- 複数の場所で保存されているか
- 長期保存の費用や運用責任は誰が負っているか
4.10 小さなコードで見る:pinningされていないデータは取得できない可能性がある
次のコードでは、IPFSのpinningをかなり単純化して表します。
実際のIPFSでは、DHT、Bitswap、ゲートウェイ、ノード間通信などが関係します。
ここでは、CIDを知っていても、そのデータを保持しているノードがなければ取得できない という考え方だけを確認します。
# IPFSにおけるpinningの考え方を理解するための学習用コードです。
# 実際のIPFSネットワーク、DHT、Bitswap、ゲートウェイの挙動を再現するものではありません。
from dataclasses import dataclass, field
@dataclass
class MockIPFSNode:
"""
IPFSノードを単純化した学習用モデルです。
pinned_data:
このノードが保持し続けるデータを表します。
実際のIPFSではCIDを使ってデータを参照しますが、
ここでは説明用に文字列のCIDをキーにしています。
"""
name: str
pinned_data: dict[str, str] = field(default_factory=dict)
def pin(self, cid: str, content: str) -> None:
"""
データをpinします。
pinすることで、このノードがそのデータを保持し続ける、というイメージです。
"""
self.pinned_data[cid] = content
def has_content(self, cid: str) -> bool:
"""このノードが指定CIDのデータを持っているか確認します。"""
return cid in self.pinned_data
def get(self, cid: str) -> str:
"""指定CIDのデータを返します。持っていなければ例外を出します。"""
if cid not in self.pinned_data:
raise KeyError(f"{self.name} は {cid} を保持していません。")
return self.pinned_data[cid]
def find_content(cid: str, nodes: list[MockIPFSNode]) -> str | None:
"""
複数ノードの中から、指定CIDのデータを持つノードを探します。
実際のIPFSではネットワーク上でデータを探します。
ここでは、リスト内のノードを順番に確認するだけです。
"""
for node in nodes:
if node.has_content(cid):
return node.get(cid)
# どのノードも持っていなければ、取得できない状態を表すためNoneを返します。
return None
cid = "bafyexample-ticket-image"
node_a = MockIPFSNode(name="node_A")
node_b = MockIPFSNode(name="node_B")
# node_Aだけがデータをpinしている状態です。
node_a.pin(cid, "ticket image data")
nodes = [node_a, node_b]
print("取得結果:", find_content(cid, nodes))
# node_Aがデータを保持しなくなった、という状況を再現します。
node_a.pinned_data.clear()
print("pin解除後の取得結果:", find_content(cid, nodes))
このコードでは、最初は node_A がデータを持っているため取得できます。
しかし、node_A がデータを保持しなくなると、同じCIDを知っていても取得できなくなります。
実際のIPFSでは、複数のノードが同じデータを保持していれば取得できる可能性が高まります。
一方で、誰も保持していなければ、CIDが分かっていてもデータを取り出せません。
この点から、NFTのメタデータや画像をIPFSに置く場合は、pinningや長期保存の運用が重要になります。
4.11 メタデータは変更可能な場合もある
NFTを見るときにもう一つ大切なのが、メタデータが変更可能かどうかです。
NFTによっては、mint直後は仮の画像が表示され、後から本来の画像に切り替わることがあります。
いわゆる「reveal」と呼ばれる仕組みです。
また、ゲームアイテムや会員証のように、状態が変化するNFTでは、メタデータを更新できる設計が使われることもあります。
一方で、アート作品や証明書のように、後から内容が変わると困るものでは、メタデータを固定したい場合があります。
| 設計 | ざっくりした意味 | 向いている例 | 注意点 |
|---|---|---|---|
| 変更可能なメタデータ | 管理者やコントラクトの処理でURIや内容を変えられる | ゲームアイテム、会員証、reveal前のNFT | 運営側の権限や信頼が重要 |
| 固定されたメタデータ | 後から変更しにくい設計 | アート、証明書、記録性を重視するNFT | 最初に間違えると修正しにくい |
| 動的メタデータ | 状態や条件に応じて表示内容が変わる | レベルが上がるゲームアイテム、参加履歴に応じた会員証 | どの条件で変わるか確認が必要 |
ここで重要なのは、変更可能なメタデータが必ず悪いわけではないことです。
たとえば、ゲームアイテムなら、キャラクターのレベルや装備状態に応じて見た目が変わることがあります。
会員証なら、有効期限やランクが変わることもあります。
一方で、「作品として固定されていると思って買ったのに、後から画像や説明が変わった」という場合は、トラブルにつながる可能性があります。
そのため、NFTを見るときは、次の点を確認することが大切です。
-
tokenURIは固定されているか - メタデータを変更できる管理者権限があるか
- reveal前なのか、reveal後なのか
- 画像や説明が変わる条件は明示されているか
- メタデータを固定する仕組みがあるか
💡 豆知識
NFTコレクションでは、mint直後は共通画像を表示し、後から各NFTの画像や属性を公開する「reveal」が行われることがあります。
これは開封前のカードパックに近い楽しさがありますが、後から何が出るか、運営がメタデータをどう管理しているかを確認することも大切です。
4.12 小さなコードで見る:変更可能なメタデータと固定されたメタデータ
次のコードでは、メタデータを変更できる状態と、固定された状態を簡単に再現します。
実際のスマートコントラクトでは、管理者権限、イベント、アクセス制御、ストレージ更新などが関係します。
ここでは、frozen というフラグを使って、メタデータを変更できるかどうかを表します。
# メタデータが変更可能か、固定されているかを理解するための学習用コードです。
# 実際のスマートコントラクト実装ではありません。
# ここでは、frozen=True になったら tokenURI を変更できない、という単純なモデルにしています。
from dataclasses import dataclass
@dataclass
class MetadataRegistry:
"""
tokenIdごとのtokenURIを管理する学習用モデルです。
token_uris:
tokenIdからtokenURIへの対応です。
frozen:
Trueになると、tokenURIを変更できない状態として扱います。
"""
token_uris: dict[int, str]
frozen: bool = False
def set_token_uri(self, token_id: int, new_uri: str) -> None:
"""
tokenURIを更新します。
frozen=Trueの場合は、メタデータが固定されたとみなし、
更新できないようにします。
"""
if self.frozen:
raise RuntimeError("メタデータは固定されているため、tokenURIを変更できません。")
self.token_uris[token_id] = new_uri
def freeze(self) -> None:
"""メタデータを固定します。"""
self.frozen = True
def token_uri(self, token_id: int) -> str:
"""指定tokenIdのtokenURIを返します。"""
return self.token_uris[token_id]
registry = MetadataRegistry(
token_uris={
1: "https://example.com/metadata/hidden.json"
}
)
print("mint直後:", registry.token_uri(1))
# reveal後に、仮メタデータから本来のメタデータへ更新するイメージです。
registry.set_token_uri(1, "https://example.com/metadata/1.json")
print("reveal後:", registry.token_uri(1))
# ここでメタデータを固定します。
registry.freeze()
# 固定後に変更しようとすると、エラーになります。
try:
registry.set_token_uri(1, "https://example.com/metadata/changed.json")
except RuntimeError as error:
print("更新失敗:", error)
このコードでは、最初は hidden.json を指しています。
その後、revealのように 1.json へ変更しています。
最後に freeze() を呼ぶと、以降は tokenURI を変更できなくなります。
このように、メタデータの変更可否はNFTの設計上とても重要です。
ただし、実際のコントラクトで本当に変更できないかどうかは、コードや権限設計を確認する必要があります。
画面上で「固定済み」と表示されていても、最終的にはコントラクトの実装や運用ルールを見る必要があります。
4.13 オンチェーン保存という選択肢
ここまで、HTTP URLやIPFSのように、ブロックチェーン外の保存先を参照する例を中心に見ました。
一方で、NFTのメタデータや画像をオンチェーン、つまりスマートコントラクト上に直接保存する設計もあります。
たとえば、画像をSVGとして生成し、そのSVGやメタデータをBase64形式で tokenURI から返すような設計があります。
この場合、外部サーバーやIPFS上の画像ファイルを参照しなくても、コントラクトからメタデータや画像情報を得られることがあります。
ただし、オンチェーン保存には注意点もあります。
| 観点 | オンチェーン保存の特徴 |
|---|---|
| 良い点 | 外部サーバー停止の影響を受けにくい |
| 良い点 | コントラクト上のロジックで画像や属性を生成できる場合がある |
| 注意点 | データ量が増えるとガスコストが高くなりやすい |
| 注意点 | 大きな画像や動画をそのまま置くのは現実的でない場合が多い |
| 注意点 | 実装が複雑になると、バグや表示互換性にも注意が必要 |
身近なたとえで言えば、オフチェーン保存は「チケット番号は台帳にあり、デザイン画像は別の保管庫にある」状態です。
オンチェーン保存は「チケット番号だけでなく、デザインの作り方や説明まで台帳内に入っている」状態に近いです。
どちらが常に良いというより、目的に応じた設計です。
- 長期的な記録性を重視するなら、オンチェーン保存や十分な分散保存を検討する
- 大きな画像や動画を扱うなら、オフチェーン保存と参照先の維持が現実的になりやすい
- ゲームや会員証のように状態変化があるなら、動的メタデータの設計が必要になる
ここでは、次のように整理しておくと分かりやすいです。
NFTの見た目や説明がどこに保存されるかは、プロジェクトごとに異なります。
そのため、「NFTだから画像もブロックチェーン上にある」と決めつけず、tokenURIとメタデータの保存先を確認することが大切です。
4.14 メタデータと権利関係はさらに別
ここまで、NFT、メタデータ、コンテンツ保存場所を分けて見てきました。
最後にもう一つ、次章以降につながる重要な点を確認します。
メタデータに画像URIが書かれているからといって、その画像の著作権や商用利用権が自動的に移るわけではありません。
たとえば、メタデータに次のように書かれていたとします。
{
"name": "Sample Art #1",
"description": "A sample digital artwork.",
"image": "ipfs://bafy.../art.png"
}
このJSONから分かるのは、基本的には次のようなことです。
- このNFTの表示名
- 説明文
- 画像URI
- メタデータが指しているコンテンツ
一方で、次のようなことは、JSONだけでは十分に分かりません。
- その画像の著作権者は誰か
- NFT購入者に商用利用権があるか
- 二次創作が認められているか
- 会員権やチケットとして使える条件は何か
- サービス終了後にどのように扱われるか
このため、NFTを理解するときは、次の4つを分ける必要があります。
| 観点 | 確認するもの |
|---|---|
| トークン | コントラクトアドレス、tokenId、所有者アドレス |
| メタデータ |
tokenURI、JSON、name、description、image
|
| コンテンツ | 画像、動画、音声、保存先 |
| 権利・規約 | 著作権、利用権、商用利用、会員条件、サービス規約 |
権利関係については、6章で詳しく整理します。
この章ではまず、「メタデータに書かれていること」と「法律上・契約上の権利」は別の層にある、と押さえておきます。
4.15 この章のまとめ
この章では、NFTで特に誤解されやすいメタデータとコンテンツ保存場所を整理しました。
NFTは、単に「画像がブロックチェーンに入っているもの」ではありません。
多くの場合、ブロックチェーン上のNFT本体から tokenURI をたどり、その先のメタデータJSONを読み取り、さらに image などのURIから画像や動画を表示します。
この章で押さえたいポイントは、次の通りです。
| ポイント | 説明 |
|---|---|
| NFT本体とメタデータは別 | NFT本体は tokenId と所有者情報、メタデータは表示用の説明書に近い |
tokenURI はメタデータへの入口 |
画像そのものではなく、JSONの場所を返す場合が多い |
| メタデータJSONには名前・説明・画像URIなどが入る | ERC-721では name、description、image が代表的に示されている |
| 画像や動画はオフチェーンにあることが多い | HTTP URL、IPFS、その他の保存先を参照する場合がある |
| IPFSのCIDは場所ではなく内容にもとづく識別子 | 内容が変わると識別子も変わるが、永久保存を自動保証するわけではない |
| pinningや長期保存の運用が重要 | CIDを知っていても、誰もデータを保持していなければ取得できない可能性がある |
| メタデータは変更可能な場合もある | reveal、ゲームアイテム、会員証などでは動的・変更可能な設計もある |
| オンチェーン保存もある | 外部依存を減らせる一方、コストやサイズの制約がある |
| 権利関係はさらに別 | メタデータが画像を指していても、著作権や商用利用権が自動的に移るとは限らない |
ここまでの内容を一言でまとめると、次のようになります。
NFTを見るときは、「トークン」「メタデータ」「コンテンツ」「保存場所」「権利関係」を分けて考えることが大切です。
画像が表示されているからといって、画像そのものがブロックチェーン上にあるとは限らず、権利まで自動的に移るわけでもありません。
次の章では、ERC-721とは別の重要な標準である ERC-1155 を見ていきます。
ERC-721は「一つずつ区別されるNFT」を扱う代表的な標準でした。
一方で、ゲームアイテムや複数種類のチケットのように、「同じ種類が複数個あるもの」と「一点物」をまとめて扱いたい場面では、ERC-1155が使われることがあります。
次章では、ERC-721とERC-1155の違いを、身近なゲームアイテムやチケットの例から整理します。
5. ERC-1155との違い
この章では、ERC-721と並んでNFT文脈でよく出てくる ERC-1155 を整理します。
ポイントは、ERC-1155を「ERC-721の上位互換」と雑に見るのではなく、複数種類のトークンを1つのコントラクトでまとめて扱うための標準 として見ることです。
前の章では、NFTのメタデータとコンテンツ保存場所を見ました。
ここまでの流れでは、主にERC-721を前提にして、tokenId、ownerOf、tokenURI、メタデータJSON、画像URIなどを整理してきました。
ERC-721は、限定チケットや会員証のように、1つ1つのトークンを個別に区別したいときに理解しやすい標準です。
ただ、実際のアプリケーションでは、すべてのアイテムが「完全な一点物」とは限りません。
たとえば、ゲームを考えてみます。
| アイテム | 性質 | 例 |
|---|---|---|
| ゲーム内コイン | 同じ数量なら交換できる | 100コイン、500コイン |
| 回復薬 | 同じ種類なら複数個持てる | ポーションを10個持つ |
| 通常武器 | 同じ種類が複数本ある | 鉄の剣を3本持つ |
| 限定スキン | 1つずつ区別したい場合がある | シリアル番号付きスキン |
| イベントチケット | 同じ席種なら交換できる場合も、座席番号で区別する場合もある | A席チケット、座席番号付きチケット |
このように、1つのサービス内には、数量で扱いたいもの と 一つずつ区別したいもの が混ざることがあります。
ERC-721だけで表そうとすると、「1種類のアイテムごとに別コントラクトを作るのか」「同じ種類が100個あるアイテムを全部NFTとして個別管理するのか」といった設計上の悩みが出てきます。
そこで登場するのが ERC-1155 です。
ERC-1155の仕様では、1つのデプロイ済みコントラクトが、代替可能トークン、非代替トークン、その他の構成を含む複数種類のトークンを管理できる標準インターフェースとして説明されています。
参考: ERC-1155: Multi Token Standard
Ethereum公式ドキュメントでも、ERC-1155は複数種類のトークンを管理する標準であり、ERC-20のような代替可能トークンとERC-721のような非代替トークンの機能を、1つのコントラクトで扱えると説明されています。
参考: Ethereum Documentation: ERC-1155 Multi-Token Standard
まずは、ERC-721とERC-1155の違いを、身近な例から見ていきます。
5.1 ERC-721は「1つずつ」、ERC-1155は「種類と数量」も扱いやすい
ERC-721では、基本的に tokenId ごとに1つのNFTを識別します。
たとえば、次のようなイメージです。
| tokenId | 内容 | 所有者 |
|---|---|---|
| 101 | 座席 A-1 のチケット | Alice |
| 102 | 座席 A-2 のチケット | Bob |
| 103 | 座席 A-3 のチケット | Carol |
この場合、tokenId = 101 と tokenId = 102 は、同じイベントのチケットであっても別物です。
座席番号やシリアル番号が違えば、1つずつ区別して扱います。
一方、ERC-1155では、id が トークンの種類 を表し、その id について各アドレスがいくつ持っているかを残高として管理できます。
たとえば、次のようなイメージです。
| id | 種類 | Aliceの残高 | Bobの残高 |
|---|---|---|---|
| 1 | ゲーム内コイン | 500 | 200 |
| 2 | 回復薬 | 10 | 3 |
| 3 | A席チケット | 2 | 1 |
| 4 | 限定アイテム | 1 | 0 |
ここで、id = 2 は「回復薬」という種類を表し、Aliceはそれを10個持っている、という形で扱えます。
id = 4 のように供給量を1にすれば、一点物のNFTに近い扱いもできます。
ERC-1155の仕様でも、各トークンIDは独自のメタデータ、供給量、その他の属性を持つ新しい設定可能なトークンタイプを表せると説明されています。
参考: ERC-1155: Multi Token Standard
違いをざっくり整理すると、次のようになります。
| 観点 | ERC-721 | ERC-1155 |
|---|---|---|
| 基本イメージ | 1つ1つのNFTを個別に扱う | 複数種類のトークンと数量をまとめて扱う |
| token IDの意味 | 1つのNFTを識別する番号 | トークンの種類を表す番号として使われることが多い |
| 残高の見方 | あるアドレスがNFTを何個持つか | あるアドレスが、あるIDのトークンを何個持つか |
| 向いている例 | 一点物、会員証、番号付きチケット | ゲームアイテム、複数種類のチケット、消耗品、半代替的なアイテム |
| バッチ処理 | 標準としては中心ではない | 複数IDをまとめて転送・残高確認できる |
💡 豆知識
ERC-1155は、NFTだけのための標準ではありません。
「Multi Token Standard」という名前の通り、1つのコントラクトで複数種類のトークンを扱うための標準です。
その中で、供給量を1にしたり、IDの設計を工夫したりすることで、NFTのような使い方もできます。
5.2 ゲームアイテムで考えると分かりやすい
ERC-1155は、ゲームアイテムを例にするとかなり理解しやすいです。
たとえば、あるゲームに次のようなアイテムがあるとします。
| id | アイテム | 性質 |
|---|---|---|
| 1001 | ゴールド | 数量で扱う通貨のようなもの |
| 1002 | 回復薬 | 同じ種類が複数個ある消耗品 |
| 1003 | 鉄の剣 | 同じ種類が複数本ある装備 |
| 2001 | 限定ドラゴン | 1体だけの特別なキャラクター |
| 3001 | イベント参加券 | 同じ券種が複数枚あるチケット |
このような場合、ゴールドや回復薬は、1つ1つを別NFTとして扱うより、「何個持っているか」で管理した方が自然です。
一方で、限定ドラゴンのようなアイテムは、1つしか存在しない特別なものとして扱いたいかもしれません。
ERC-1155では、こうした複数の性質を1つのコントラクト内で扱えるように設計できます。
ERC-1155の仕様でも、もともとのモチベーションとして、ゲーム開発者が何千種類ものトークンタイプを作る可能性があり、そのような場面で複数種類のトークンを扱える標準が必要になると説明されています。
ただし、ERC-1155はゲーム専用ではなく、他の多くの用途にも使える標準です。
参考: ERC-1155: Multi Token Standard
ここで大事なのは、ERC-1155を「NFTだけの標準」と思い込まないことです。
ERC-1155は、次のような混在を扱いやすくします。
| 種類 | 説明 | ERC-1155での扱い方の例 |
|---|---|---|
| 代替可能トークン | 同じ種類なら交換できる |
id = 1001 のゴールドを500個持つ |
| 非代替トークン | 一つずつ区別する |
id = 2001 の供給量を1にする |
| 半代替的なトークン | 状況によって同じものとして扱ったり、後で個別化したりする | イベント前の同一券種チケット、使用後の記念証明など |
「半代替的」という言葉は少し難しいですが、身近な例で考えると、イベントのチケットが分かりやすいです。
イベント前は「A席チケット」であればどれも同じように扱えるかもしれません。
しかし、入場後には「2026年7月9日のイベントに参加した記念証明」のように、一つずつの履歴や意味を持つものとして扱いたくなる場合があります。
ERC-1155は、このような柔軟な設計をしやすい標準として理解するとよいです。
5.3 小さなコードで見る:ERC-721型の管理とERC-1155型の管理
ここで、ERC-721型の管理とERC-1155型の管理の違いを、簡単なPythonコードで見てみます。
次のコードは、実際のスマートコントラクトではありません。
あくまで、ERC-721では tokenId -> owner のように考えやすく、ERC-1155では (owner, id) -> balance のように考えやすい、という違いを理解するための学習用コードです。
# ERC-721型とERC-1155型の管理の違いを理解するための学習用コードです。
# 実際のERC-721/ERC-1155コントラクトを再現するものではありません。
# ERC-721型のイメージ:
# token_idごとに、所有者が1人決まります。
erc721_owners = {
101: "Alice", # tokenId 101 は Alice が持っている
102: "Bob", # tokenId 102 は Bob が持っている
103: "Carol", # tokenId 103 は Carol が持っている
}
# ERC-1155型のイメージ:
# 「誰が」「どのidを」「何個持っているか」を管理します。
erc1155_balances = {
("Alice", 1001): 500, # Alice は id 1001 のゴールドを500個持っている
("Alice", 1002): 10, # Alice は id 1002 の回復薬を10個持っている
("Bob", 1001): 200, # Bob は id 1001 のゴールドを200個持っている
("Bob", 1002): 3, # Bob は id 1002 の回復薬を3個持っている
}
def owner_of_erc721(token_id: int) -> str:
"""ERC-721のownerOf(tokenId)に近い考え方で、所有者を返します。"""
return erc721_owners[token_id]
def balance_of_erc1155(owner: str, token_id: int) -> int:
"""ERC-1155のbalanceOf(owner, id)に近い考え方で、残高を返します。"""
return erc1155_balances.get((owner, token_id), 0)
print("ERC-721型の確認:")
print("tokenId 101 の所有者:", owner_of_erc721(101))
print("\nERC-1155型の確認:")
print("Alice が持つ id 1001 の数量:", balance_of_erc1155("Alice", 1001))
print("Alice が持つ id 1002 の数量:", balance_of_erc1155("Alice", 1002))
このコードでは、ERC-721型では tokenId ごとに所有者を見ています。
一方で、ERC-1155型では、owner と id の組み合わせごとに数量を見ています。
つまり、ERC-1155で大事なのは、次の見方です。
あるアドレスが、あるidのトークンを、何個持っているか
ERC-1155の仕様でも、balanceOf(address _owner, uint256 _id) は、指定した所有者が指定したトークンIDをいくつ持っているかを返す関数として定義されています。
参考: ERC-1155: Multi Token Standard
5.4 ERC-1155では複数種類をまとめて扱える
ERC-1155の大きな特徴の一つが、batch、つまりまとめて処理する機能です。
ERC-721では、基本的に1つの tokenId を1つずつ移転するイメージが中心です。
一方、ERC-1155では、複数の id と複数の value を配列として渡し、まとめて移転できます。
Ethereum公式ドキュメントでは、safeBatchTransferFrom に ids=[3, 6, 13]、values=[100, 200, 5] のように渡すと、id 3を100個、id 6を200個、id 13を5個転送する例が示されています。
参考: Ethereum Documentation: ERC-1155 Multi-Token Standard
身近な例でいえば、ゲーム内で友達に次のアイテムをまとめて渡すようなものです。
| 渡すもの | 数量 |
|---|---|
| ゴールド | 100 |
| 回復薬 | 5 |
| 鉄の剣 | 1 |
1つずつ別々に送るのではなく、「この3種類をまとめて送る」という処理ができます。
このバッチ処理は、ゲームアイテムやチケットのように、複数種類のアイテムを一度に扱いたい場面で便利です。
ただし、「まとめて扱えるから常にERC-1155がよい」というわけではありません。
コレクションの見せ方、マーケットプレイス対応、メタデータ設計、権利表現、利用するライブラリ、開発チームの理解度などによって、ERC-721とERC-1155のどちらが向いているかは変わります。
5.5 小さなコードで見る:safeBatchTransferFromの考え方
次に、ERC-1155の safeBatchTransferFrom に近い考え方を、学習用コードで見てみます。
実際のERC-1155では、承認確認、ゼロアドレス確認、残高確認、イベント発行、受け取り先がコントラクトの場合のhook呼び出しなど、細かいルールがあります。
ここでは、初学者向けに、複数IDと数量をまとめて検証し、問題がなければまとめて移転する という流れだけを再現します。
# ERC-1155のsafeBatchTransferFromの考え方を理解するための学習用コードです。
# 実際のスマートコントラクトではなく、Python上の簡易モデルです。
from collections import defaultdict
# balances[(owner, token_id)] = amount という形で残高を管理します。
balances = defaultdict(int)
balances[("Alice", 1001)] = 500 # ゴールド
balances[("Alice", 1002)] = 10 # 回復薬
balances[("Alice", 1003)] = 2 # 鉄の剣
# 転送イベントを学習用に記録するリストです。
events = []
def safe_batch_transfer_from(sender: str, receiver: str, ids: list[int], values: list[int]) -> None:
"""
ERC-1155のsafeBatchTransferFromをかなり単純化した関数です。
sender:
トークンを送る人
receiver:
トークンを受け取る人
ids:
転送するトークンIDの一覧
values:
各IDについて転送する数量の一覧
この関数では、まず全体を検証してから残高を更新します。
途中で残高不足が見つかった場合、何も更新しないようにしています。
"""
if receiver == "0x0":
raise ValueError("ゼロアドレスには送れません。")
if len(ids) != len(values):
raise ValueError("idsとvaluesの長さが一致していません。")
# まず、すべてのIDについて残高が足りるか確認します。
# ここで先に検証しておくことで、途中まで更新してから失敗する状態を避けます。
for token_id, amount in zip(ids, values):
if amount < 0:
raise ValueError("転送数量は0以上である必要があります。")
if balances[(sender, token_id)] < amount:
raise ValueError(f"id {token_id} の残高が不足しています。")
# すべて問題なければ、まとめて残高を更新します。
for token_id, amount in zip(ids, values):
balances[(sender, token_id)] -= amount
balances[(receiver, token_id)] += amount
# 実際のERC-1155ではTransferBatchイベントなどが使われます。
# ここでは学習用に、転送内容をeventsへ記録します。
events.append({
"event": "TransferBatch",
"from": sender,
"to": receiver,
"ids": ids,
"values": values,
})
# AliceからBobへ、ゴールド100個、回復薬5個、鉄の剣1本をまとめて送ります。
safe_batch_transfer_from(
sender="Alice",
receiver="Bob",
ids=[1001, 1002, 1003],
values=[100, 5, 1],
)
print("Aliceの残高:")
for token_id in [1001, 1002, 1003]:
print(f" id {token_id}: {balances[("Alice", token_id)]}")
print("\nBobの残高:")
for token_id in [1001, 1002, 1003]:
print(f" id {token_id}: {balances[("Bob", token_id)]}")
print("\nイベント:")
print(events[-1])
このコードでは、ids と values を同じ順番で対応させています。
ids = [1001, 1002, 1003]
values = [100, 5, 1]
つまり、次の意味になります。
| id | value | 意味 |
|---|---|---|
| 1001 | 100 | id 1001を100個送る |
| 1002 | 5 | id 1002を5個送る |
| 1003 | 1 | id 1003を1個送る |
ERC-1155の仕様でも、safeBatchTransferFrom では _ids と _values の配列の長さが一致している必要があり、それぞれの順番に対応するトークンIDと数量が扱われます。
参考: ERC-1155: Multi Token Standard
ここで重要なのは、複数種類をまとめて扱う場合、IDと数量の対応関係を間違えないこと です。
アプリケーション側で表示するときも、「どのIDを何個送るのか」をユーザーに分かりやすく見せる必要があります。
5.6 balanceOfBatchで複数の残高をまとめて確認できる
ERC-1155では、転送だけでなく、残高確認もまとめて行えます。
balanceOf(address _owner, uint256 _id) は、1つのアドレスと1つのIDについて残高を確認します。
一方、balanceOfBatch(address[] _owners, uint256[] _ids) は、複数の所有者と複数のIDの組み合わせについて、まとめて残高を確認します。
ERC-1155の仕様では、balanceOfBatch は所有者配列とID配列を受け取り、それぞれの組み合わせに対応する残高を返す関数として定義されています。
参考: ERC-1155: Multi Token Standard
身近な例でいえば、在庫表を1行ずつ確認するのではなく、必要な項目だけまとめて取り出すようなものです。
| 確認したいもの | 結果 |
|---|---|
| Aliceのゴールド | 400 |
| Aliceの回復薬 | 5 |
| Bobの鉄の剣 | 1 |
5.7 小さなコードで見る:balanceOfBatchの考え方
次のコードでは、balanceOfBatch の考え方を簡単に再現します。
# ERC-1155のbalanceOfBatchの考え方を理解するための学習用コードです。
# 複数のowner/idペアについて、残高をまとめて取得します。
from collections import defaultdict
balances = defaultdict(int)
balances[("Alice", 1001)] = 400
balances[("Alice", 1002)] = 5
balances[("Bob", 1001)] = 100
balances[("Bob", 1003)] = 1
def balance_of(owner: str, token_id: int) -> int:
"""1つのowner/idペアについて残高を返します。"""
return balances[(owner, token_id)]
def balance_of_batch(owners: list[str], ids: list[int]) -> list[int]:
"""
複数のowner/idペアについて残高をまとめて返します。
owners[0] と ids[0]、owners[1] と ids[1] のように、
同じ位置にある要素同士をペアとして扱います。
"""
if len(owners) != len(ids):
raise ValueError("ownersとidsの長さが一致していません。")
results = []
for owner, token_id in zip(owners, ids):
results.append(balance_of(owner, token_id))
return results
owners = ["Alice", "Alice", "Bob"]
ids = [1001, 1002, 1003]
print(balance_of_batch(owners, ids))
このコードでは、次の3つをまとめて確認しています。
| 配列の位置 | owner | id | 確認するもの |
|---|---|---|---|
| 0 | Alice | 1001 | Aliceのゴールド残高 |
| 1 | Alice | 1002 | Aliceの回復薬残高 |
| 2 | Bob | 1003 | Bobの鉄の剣残高 |
ERC-1155では、こうしたまとめて処理する設計によって、複数種類のトークンを扱うアプリケーションを作りやすくしています。
5.8 ERC-1155のメタデータでは{id}置換が使える
ERC-1155にもメタデータの仕組みがあります。
ERC-721では、tokenURI(tokenId) で特定NFTのメタデータURIを返す形を見ました。
ERC-1155では、uri(uint256 _id) によって、特定IDに対応するURIを返す拡張があります。
ERC-1155の仕様では、URI内に {id} という文字列が含まれる場合、クライアントはそれを実際のトークンIDに置き換える必要があります。
このとき、IDは 0x なしの小文字16進数で、必要に応じて64文字になるように先頭を0で埋めます。
参考: ERC-1155: Multi Token Standard
たとえば、次のようなURIテンプレートがあるとします。
https://example.com/metadata/{id}.json
トークンIDが 1234 の場合、1234 を16進数に変換し、64文字に0埋めしてから {id} に入れます。
https://example.com/metadata/00000000000000000000000000000000000000000000000000000000000004d2.json
この仕組みを使うと、1つのURIテンプレートで大量のトークンIDに対応しやすくなります。
💡 豆知識
ERC-1155の{id}置換では、ふだん人間が見る10進数の1234ではなく、64文字に0埋めした16進数表記を使います。
そのため、メタデータファイルを自分で用意するときは、ファイル名の付け方を間違えないように注意が必要です。
5.9 小さなコードで見る:{id}を64文字の16進数へ置換する
次のコードでは、ERC-1155の {id} 置換の考え方を再現します。
# ERC-1155のURI内にある{id}を、64文字の16進数IDに置き換える学習用コードです。
# 実際のクライアント実装そのものではありません。
def erc1155_id_to_hex(token_id: int) -> str:
"""
ERC-1155の{id}置換で使うID文字列を作ります。
仕様では、0xなし、小文字、64文字になるようにゼロ埋めした
16進数文字列を使います。
"""
if token_id < 0:
raise ValueError("token_idは0以上である必要があります。")
return format(token_id, "064x")
def build_erc1155_uri(uri_template: str, token_id: int) -> str:
"""URIテンプレート内の{id}を、ERC-1155形式のID文字列に置き換えます。"""
id_hex = erc1155_id_to_hex(token_id)
return uri_template.replace("{id}", id_hex)
uri_template = "https://example.com/metadata/{id}.json"
token_id = 1234
print("置換前:", uri_template)
print("token_id:", token_id)
print("16進数ID:", erc1155_id_to_hex(token_id))
print("置換後:", build_erc1155_uri(uri_template, token_id))
このコードでは、1234 が 00000000000000000000000000000000000000000000000000000000000004d2 に変換されます。
人間から見ると少し長く感じますが、クライアントやマーケットプレイスがERC-1155のメタデータを正しく取得するうえで重要なルールです。
5.10 ERC-1155でもNFTのような扱いはできる
ERC-1155は、複数数量を扱える標準です。
そのため、「ERC-1155はNFTではないのでは?」と感じるかもしれません。
ここは少し丁寧に分けて考える必要があります。
ERC-1155は、NFTだけを表す標準ではありません。
しかし、ERC-1155の中で供給量を1にしたり、IDの設計を工夫したりすることで、非代替トークンのように扱うことができます。
Ethereum公式ドキュメントでも、供給量が1であれば、そのトークンは本質的にNFTとして扱えると説明されています。
参考: Ethereum Documentation: ERC-1155 Multi-Token Standard
また、ERC-1155の仕様では、非代替トークンを表す方法として、IDの一部をベースID、一部を個別インデックスとして使う方法や、各非代替トークンの最大数量を1にする方法が例として示されています。
ただし、仕様は「必ずこの方法で実装しなければならない」とは決めていません。
参考: ERC-1155: Multi Token Standard
この点は重要です。
ERC-721では、tokenId は基本的に1つのNFTそのものを識別する番号として理解しやすいです。
一方、ERC-1155では、id が「種類」を表しているのか、「一点物」を表しているのかは、実装や設計に依存します。
| 観点 | ERC-721 | ERC-1155 |
|---|---|---|
| NFTらしさの見方 |
tokenId ごとに一意なNFTとして扱う |
id と供給量・実装設計を見て判断する |
| 所有確認 | ownerOf(tokenId) |
balanceOf(owner, id) が1かどうかなどを見る |
| 一点物の表現 | tokenId自体が個別NFT | 供給量1、またはID設計で個別性を表す |
| 注意点 | tokenId単位で考えやすい | idが種類なのか個体なのかを確認する必要がある |
したがって、ERC-1155のNFTを見るときは、単に「ERC-1155だからNFT」「ERC-1155だからNFTではない」と決めるのではなく、次を確認する必要があります。
- その
idは何を表しているのか - その
idの供給量はいくつなのか - 同じ
idを複数人が複数個持てるのか - メタデータはIDごとにどう設定されているのか
- マーケットプレイスやアプリではどう表示されているのか
5.11 小さなコードで見る:供給量1ならNFTのように扱える
次のコードでは、ERC-1155の中で、数量が複数あるトークンと、供給量が1のトークンを分けて扱う例を見てみます。
# ERC-1155で、数量が複数あるトークンと供給量1のトークンを分けて考える学習用コードです。
# 実際のERC-1155コントラクトでは、mintやburn、イベントなども関係します。
from collections import defaultdict
# token_idごとの総供給量を記録します。
total_supply = {
1001: 1_000_000, # ゴールド: 大量に存在する
1002: 10_000, # 回復薬: 複数個存在する
2001: 1, # 限定ドラゴン: 1つだけ存在する
}
# balances[(owner, token_id)] = amount の形で保有数量を管理します。
balances = defaultdict(int)
balances[("Alice", 1001)] = 500
balances[("Alice", 1002)] = 10
balances[("Alice", 2001)] = 1
def is_nft_like(token_id: int) -> bool:
"""
総供給量が1なら、NFTのように扱えると判断する簡易関数です。
注意:
- 実際には、供給量の取得方法やmint/burnの設計も確認する必要があります。
- ここでは学習用に、total_supplyが1かどうかだけを見ています。
"""
return total_supply.get(token_id, 0) == 1
for token_id in [1001, 1002, 2001]:
print(f"id {token_id}:")
print(f" total_supply: {total_supply[token_id]}")
print(f" NFTのように扱えるか: {is_nft_like(token_id)}")
このコードでは、id = 2001 だけが供給量1なので、NFTのように扱えると判断しています。
ただし、実際のスマートコントラクトでは、供給量をどのように確認できるか、後から追加発行できる設計なのか、burnが可能なのか、メタデータが固定されているのかなども関係します。
そのため、ERC-1155のNFTを見るときは、単純に規格名だけで判断せず、コントラクトの設計やイベント、メタデータ、発行方針を確認することが大切です。
5.12 safe transferと受け取りhook
ERC-1155では、safeTransferFrom や safeBatchTransferFrom という名前の通り、転送時の安全確認が重視されています。
ERC-1155の仕様では、受け取り先がスマートコントラクトの場合、onERC1155Received または onERC1155BatchReceived のような受け取りhookを呼び出し、期待される値が返らなければ転送をrevertするルールが示されています。
参考: ERC-1155: Multi Token Standard
これは、3章で見たERC-721の safeTransferFrom と似た考え方です。
なぜこのような確認が必要なのでしょうか。
たとえば、NFTやトークンをスマートコントラクトへ送ったとします。
もしそのコントラクトが受け取り処理に対応していなければ、トークンが取り出せない状態になる可能性があります。
そのため、ERC-1155では、受け取り先コントラクトが「ERC-1155を受け取れる」と明示的に返すかを確認します。
この仕組みは、トークンを誤って対応していないコントラクトへ送るリスクを減らすためのものです。
5.13 小さなコードで見る:受け取りhookの確認
次のコードでは、ERC-1155の受け取りhookをかなり単純化して再現します。
# ERC-1155の受け取りhookを理解するための学習用コードです。
# 実際のスマートコントラクトではなく、Python上の簡易モデルです。
ERC1155_BATCH_ACCEPTED = "0xbc197c81" # onERC1155BatchReceivedが成功時に返す値のイメージ
class ReceiverContract:
"""ERC-1155を受け取れるスマートコントラクトの学習用モデルです。"""
def on_erc1155_batch_received(self, operator, sender, ids, values, data):
"""
ERC-1155のbatch受け取りhookに相当する関数です。
実際のSolidityではbytes4の特定値を返します。
"""
print("受け取りhookが呼び出されました。")
print(" operator:", operator)
print(" sender:", sender)
print(" ids:", ids)
print(" values:", values)
return ERC1155_BATCH_ACCEPTED
class NonReceiverContract:
"""ERC-1155の受け取りに対応していないコントラクトの学習用モデルです。"""
pass
def check_receiver_hook(receiver, operator, sender, ids, values, data=None):
"""
受け取り先がERC-1155のbatch受け取りに対応しているか確認します。
receiver:
受け取り先の学習用オブジェクト
operator:
転送を実行した人
sender:
元の所有者
ids / values:
転送されるIDと数量
"""
if not hasattr(receiver, "on_erc1155_batch_received"):
raise ValueError("受け取り先はERC-1155 batch受け取りに対応していません。")
result = receiver.on_erc1155_batch_received(operator, sender, ids, values, data)
if result != ERC1155_BATCH_ACCEPTED:
raise ValueError("受け取りhookの戻り値が期待値と違います。")
return True
receiver = ReceiverContract()
# 対応している受け取り先なので成功します。
accepted = check_receiver_hook(
receiver=receiver,
operator="Alice",
sender="Alice",
ids=[1001, 1002],
values=[100, 5],
)
print("受け取り成功:", accepted)
このコードでは、受け取り先が on_erc1155_batch_received を持っているか確認し、さらに戻り値が期待値かどうかを確認しています。
実際のERC-1155では、これがスマートコントラクト上の安全な転送ルールとして定義されています。
ただし、これは「すべてのリスクを防ぐ」という意味ではありません。
受け取り先コントラクト自体の実装ミスや、ユーザーが怪しいコントラクトを承認してしまうリスクは別に考える必要があります。
5.14 setApprovalForAllは便利だが注意も必要
ERC-1155でも、第三者に操作権限を与える仕組みがあります。
代表的なのが setApprovalForAll です。
ERC-1155の仕様では、setApprovalForAll(address _operator, bool _approved) によって、あるoperatorに対して、呼び出し元のすべてのトークンを管理する権限を有効化または無効化できると定義されています。
参考: ERC-1155: Multi Token Standard
Ethereum公式ドキュメントでも、ERC-1155の承認はERC-20とは少し違い、特定数量ではなく、setApprovalForAll によってoperatorを承認または解除する形だと説明されています。
また、この承認は基本的にall-or-nothingであり、どのトークンクラスや数量だけを承認するかを標準上細かく指定するものではないと説明されています。
参考: Ethereum Documentation: ERC-1155 Multi-Token Standard
これは、マーケットプレイスや交換コントラクトを使うときには便利です。
一方で、怪しいコントラクトに setApprovalForAll を許可してしまうと、自分が持つERC-1155トークンを広く操作されるリスクがあります。
| 観点 | 説明 |
|---|---|
| 便利な点 | マーケットプレイスや交換コントラクトが、ユーザーの代わりに移転処理を実行できる |
| 注意点 | 承認先が悪意ある場合、複数種類のトークンを操作される可能性がある |
| 確認すべきこと | 承認先のコントラクト、表示される権限内容、公式サイトかどうか |
| 後続章との関係 | セキュリティ章で、フィッシングや承認悪用として詳しく扱う |
ここでも、ERC-721と同じく「承認」は非常に重要なポイントです。
NFTの被害は、秘密鍵そのものを盗まれる場合だけでなく、ユーザーが悪意あるコントラクトへ操作権限を与えてしまう場合にも起こり得ます。
5.15 ERC-721とERC-1155をどう使い分けるか
ここまで見てきた内容を踏まえると、ERC-721とERC-1155は、どちらが常に優れているというより、用途によって向き不向きが違います。
| 用途 | ERC-721が向きやすい場合 | ERC-1155が向きやすい場合 |
|---|---|---|
| デジタル会員証 | 1人1枚の会員証を個別に扱いたい | 会員ランクや複数種類の権利をまとめて扱いたい |
| アート作品 | 1作品1トークンとして見せたい | 複数エディションや複数種類をまとめたい |
| ゲームアイテム | 一点物キャラクターや特別装備 | 通貨、消耗品、装備、限定アイテムをまとめて扱う |
| チケット | 座席番号付きチケットを個別管理 | 同じ券種を複数枚まとめて扱う |
| 証明書 | 1つずつ個別に発行したい | 複数種類の証明やバッジをまとめて扱いたい |
ERC-721は、1つ1つのNFTを直感的に扱いやすいです。
そのため、記事や初学者向け説明では、まずERC-721から入ると分かりやすいです。
一方で、ERC-1155は、複数種類のトークンや数量を扱うアプリケーションでは便利です。
特に、ゲーム、複数種類のチケット、複数エディションのアイテム、バッジや証明書のように、さまざまな種類を同じ仕組みで管理したい場合に候補になります。
ただし、標準が柔軟であるほど、読み手や利用者にとっては確認すべきことも増えます。
ERC-1155を見るときは、次の点を意識すると安全です。
- その
idは何の種類を表しているのか - その
idの数量は複数あるのか、1つだけなのか - メタデータはIDごとにどう取得されるのか
-
setApprovalForAllでどのoperatorに権限を渡しているのか - マーケットプレイスやウォレットがどのように表示しているのか
- 本当に権利や利用条件まで伴うのか
5.16 ERC-1155を理解するときの注意点
ERC-1155は便利な標準ですが、誤解しやすい点もあります。
| 誤解しやすいこと | より丁寧な見方 |
|---|---|
| ERC-1155はERC-721の完全な上位互換 | 目的が違う。ERC-1155は複数種類・数量・バッチ処理に強い標準 |
| ERC-1155なら全部NFT | 代替可能トークンも非代替トークンも扱える。idや供給量、実装を見る必要がある |
| idは必ず個別NFTを表す | ERC-1155ではidがトークンタイプを表すことが多い。個別性は実装次第 |
| batch処理なら必ず安全 | 配列の対応、承認、受け取り先、コントラクト実装の確認が必要 |
| setApprovalForAllは気軽に押してよい | すべてのトークンを管理できる権限を与える可能性があるため注意が必要 |
特に、ERC-1155では ownerOf(tokenId) のように「このtokenIdの所有者は誰か」と単純に考えるより、balanceOf(owner, id) で「このownerはこのidを何個持っているか」と考える方が自然です。
これは、ERC-721に慣れていると少し混乱しやすい点です。
ERC-721では、tokenId = 101 の所有者を聞けば、基本的には1人の所有者が返ってきます。
ERC-1155では、id = 1002 の回復薬をAliceもBobも複数個持てるかもしれません。
この違いを押さえておくと、NFTマーケットプレイスやウォレットでERC-1155のアイテムを見たときも、少し落ち着いて理解できます。
5.17 この章のまとめ
この章では、ERC-721とERC-1155の違いを整理しました。
ERC-721は、1つ1つのNFTを個別に扱う代表的な標準です。
一方、ERC-1155は、1つのコントラクトで複数種類のトークンを扱えるMulti Token Standardです。
この章で押さえたいポイントは、次の通りです。
| ポイント | 説明 |
|---|---|
| ERC-1155は複数種類のトークンを扱える | 1つのコントラクトで、代替可能・非代替・その他の構成を扱える |
| idはトークンタイプを表すことが多い | ERC-721のtokenIdのように、常に1つの個体を表すとは限らない |
| balanceOf(owner, id)で数量を見る | あるアドレスが、あるIDを何個持っているかを確認する |
| batch処理ができる | 複数IDと数量をまとめて転送・残高確認できる |
| {id}置換でメタデータURIを作れる | 64文字の小文字16進数IDをURIに埋め込む仕様がある |
| NFTのような使い方もできる | 供給量1やID設計により、一点物として扱える場合がある |
| 承認には注意が必要 |
setApprovalForAll は便利だが、広い操作権限を与える可能性がある |
| ERC-721とERC-1155は用途で使い分ける | どちらが常に上位というより、管理したい対象によって向き不向きがある |
ここまでで、NFTを支える代表的な標準として、ERC-721とERC-1155を見てきました。
ただし、どちらの標準を使っていても、まだ大事な問題が残っています。
それは、NFTを持っていることと、画像・音楽・ゲームアイテム・著作権・利用権を持っていることは同じなのか という問題です。
技術的には、NFTの所有者アドレスや残高を確認できます。
しかし、それが現実世界の所有権や著作権、商用利用権、会員権とどう結びつくかは、スマートコントラクトだけでは決まりません。
次の章では、この記事の中心テーマである NFTと所有権・著作権・利用権の違い を整理します。
NFTを「買った」「持っている」と言うとき、何を持っていて、何を持っていない可能性があるのかを分けて見ていきます。
6. NFTと所有権・著作権・利用権を分ける
この章では、この記事の中心テーマである 「NFTを持っている」とは何を意味するのか を整理します。
ポイントは、NFT、メタデータ、画像や音楽などのコンテンツ、著作権、商用利用権、会員権などを、ひとまとめにせず分けて考えることです。
前の章までで、ERC-721とERC-1155の違いを見てきました。
ERC-721では、tokenId ごとに所有者を追跡できます。
ERC-1155では、あるアドレスがある id を何個持っているかを確認できます。
つまり、ブロックチェーン上では、少なくとも次のようなことを確認できます。
- このNFTは、どのコントラクトの何番のトークンか
- そのNFTは、どのウォレットアドレスが持っているか
- どのアドレスからどのアドレスへ移転されたか
- メタデータURIがどこを指しているか
一方で、ここから先は少し注意が必要です。
たとえば、あるNFTが画像を表示しているとしても、NFTを持っている人がその画像の著作権まで持っているとは限りません。
また、NFTが会員証やチケットのように使われる場合でも、実際にどのようなサービスを受けられるかは、スマートコントラクトだけではなく、発行者の規約や運用にも依存します。
米国著作権局とUSPTOのNFTに関する共同研究でも、NFTの作成・マーケティング・移転において、買い手や売り手がどの知的財産権に関わるのかを理解していないという懸念が広く認識されています。
また、同研究では、NFTに関する懸念の多くは、知的財産法そのものの変更よりも、商品説明の透明性や消費者教育によって対応すべきものとして整理されています。
参考: U.S. Copyright Office: Non-Fungible Token Study
参考: USPTO: U.S. Patent and Trademark Office and U.S. Copyright Office conclude joint study on non-fungible tokens
本記事は法律上の助言を目的としたものではありません。
ここでは、NFTを技術的に理解するうえで混同しやすいポイントを、できるだけ分かりやすく分けて整理します。
6.1 「NFTを持っている」は、まずオンチェーン上の状態を指す
まず、技術的な意味から考えます。
ERC-721の仕様では、NFTの所有者を確認するために ownerOf(tokenId) という関数が定義されています。
これは、指定した tokenId のNFTを現在どのアドレスが持っているかを返す関数です。
参考: ERC-721: Non-Fungible Token Standard
つまり、技術的に「NFTを持っている」と言うとき、まずは次の状態を指します。
ownerOf(tokenId) が、自分のウォレットアドレスを返す
かなり単純化すると、次のようなイメージです。
NFTコントラクト
tokenId: 101
owner: 0xAlice
この場合、tokenId = 101 のNFTは、オンチェーン上では 0xAlice のアドレスが持っていると扱われます。
ただし、ここで分かるのは トークンの所有者アドレス です。
そのNFTが参照している画像の著作権者や、商用利用できるかどうか、会員特典を受けられるかどうかまでは、ownerOf だけでは分かりません。
| 確認したいこと | オンチェーンだけで分かるか | 補足 |
|---|---|---|
| NFTの現在の所有者アドレス | 分かる場合が多い | ERC-721では ownerOf(tokenId) で確認できる |
| NFTの移転履歴 | 分かる場合が多い |
Transfer イベントなどを追跡できる |
| メタデータURI | 分かる場合がある |
tokenURI(tokenId) が実装されている場合 |
| 画像の著作権者 | それだけでは分からない | 作品情報、規約、契約、権利者情報の確認が必要 |
| 商用利用できるか | それだけでは分からない | ライセンスやプロジェクト規約による |
| 会員特典を受けられるか | それだけでは分からない | 発行者の運用やサービス条件による |
ここを分けておくと、NFTの誤解をかなり減らせます。
💡 豆知識
NFTの「所有者」は、まずブロックチェーン上のアドレスとして表れます。
ただし、アドレスは人間の名前そのものではありません。
そのアドレスを誰が管理しているのか、正当に取得したのか、どのような権利まで持っているのかは、別の確認が必要になります。
6.2 NFT、メタデータ、コンテンツ、権利は別の層にある
NFTで混乱しやすい理由は、いくつかの層がまとめて語られやすいからです。
たとえば、「このNFTを買った」と言うとき、実際には次のような複数のものが関係しています。
この図で大事なのは、NFT本体と権利関係が同じ場所にあるわけではないことです。
NFTは、ブロックチェーン上のトークンです。
メタデータは、そのNFTが何を表しているかを説明する情報です。
画像や音楽などのコンテンツは、メタデータから参照されることがあります。
著作権や商用利用権は、法律や契約、利用規約、ライセンスによって決まります。
これらをまとめると、次のようになります。
| 層 | 内容 | 例 |
|---|---|---|
| NFT本体 | ブロックチェーン上の一意なトークン | contract address + tokenId |
| 所有者アドレス | NFTを持っているウォレット | ownerOf(101) = 0xAlice |
| メタデータ | 名前、説明、画像URIなど |
name、description、image
|
| コンテンツ | 画像、音楽、動画、ゲームアイテム情報など |
ipfs://... や https://... の先 |
| 著作権 | 作品を複製・翻案・公衆送信などする権利 | 作者や権利者が持つ場合がある |
| 利用権 | NFT保有者に許された使い方 | 個人表示のみ、商用利用可、二次創作可など |
| サービス上の権利 | 会員特典、チケット入場、ゲーム内利用など | 発行者や運営サービスの規約に依存 |
NFTを理解するときは、この表のどの層の話をしているのかを意識することが大切です。
「NFTを買った」という言葉だけでは、どの層まで手に入れたのかは分かりません。
手に入れたのはトークンだけなのか、特定の利用権も含むのか、商用利用まで許されているのか、発行者の規約を確認する必要があります。
6.3 小さなコードで見る:NFTの所有者と作品の権利者を分ける
ここで、NFTの所有者と作品の権利者を分ける考え方を、小さなPythonコードで確認します。
次のコードは、実際のNFTコントラクトや法律関係を再現するものではありません。
目的は、NFTの所有者、作品の作者、著作権者、利用条件は別々の情報として管理できる という感覚をつかむことです。
# NFTの所有者と、作品の作者・著作権者・利用条件を分けて考えるための学習用コードです。
# 実際のNFTコントラクトや法律上の権利関係を再現するものではありません。
from dataclasses import dataclass
@dataclass
class NFTToken:
"""ブロックチェーン上のNFTを、説明用に単純化したデータです。"""
contract_address: str
token_id: int
owner_address: str
metadata_uri: str
@dataclass
class Work:
"""NFTが参照している作品やコンテンツを表す、説明用のデータです。"""
work_id: str
title: str
creator: str
copyright_owner: str
@dataclass
class RightsTerms:
"""NFTに関連する利用条件を、説明用に単純化したデータです。"""
personal_display: bool
commercial_use: bool
derivative_work: bool
# NFTそのものの情報です。
# ここでは、token_id=101 のNFTを Alice のアドレスが持っているとします。
token = NFTToken(
contract_address="0xNFTCollection",
token_id=101,
owner_address="0xAlice",
metadata_uri="ipfs://metadata/101",
)
# NFTが参照している作品の情報です。
# NFTの所有者と、作品の作者・著作権者は同じとは限りません。
work = Work(
work_id="art-001",
title="Sample Digital Art",
creator="Artist A",
copyright_owner="Artist A",
)
# NFT保有者に許可される使い方を、説明用に単純化しています。
terms = RightsTerms(
personal_display=True,
commercial_use=False,
derivative_work=False,
)
print("NFTの所有者:", token.owner_address)
print("作品の作者:", work.creator)
print("著作権者:", work.copyright_owner)
print("個人表示の許可:", terms.personal_display)
print("商用利用の許可:", terms.commercial_use)
print("二次創作の許可:", terms.derivative_work)
このコードでは、NFTの所有者は 0xAlice です。
一方で、作品の作者と著作権者は Artist A です。
つまり、AliceがNFTを持っていても、コード上は著作権者までAliceになっているわけではありません。
また、利用条件を見ると、個人表示は許可されていますが、商用利用や二次創作は許可されていません。
この例で伝えたいのは、次の点です。
NFTの所有者、作品の作者、著作権者、利用条件は、分けて管理される情報です。
NFTを持っていることだけを見ても、著作権や商用利用権まで分かるとは限りません。
6.4 「著作権」と「利用権」は同じではない
NFTで特に混同しやすいのが、著作権 と 利用権 です。
著作権は、作品を作った人や権利を譲り受けた人が持つ権利です。
一方で、利用権は、権利者が他の人に対して「この範囲なら使ってよい」と許可するものです。
身近な例で考えると、マンガを1冊買った場合、その本を読むことはできます。
しかし、そのマンガを勝手にコピーして販売したり、キャラクターを使ってグッズを作ったりできるわけではありません。
NFTでも似たことが起こります。
NFTを買うことで、そのNFTをウォレットに保有し、マーケットプレイス上で表示したり、移転したりできる場合があります。
しかし、画像を商用利用できるか、キャラクターを改変できるか、二次創作できるかは、別途決められた規約やライセンスによります。
| 項目 | ざっくりした意味 | NFTとの関係 |
|---|---|---|
| NFT所有 | トークンをウォレットで保有している状態 | オンチェーンで確認しやすい |
| 著作権 | 作品に関する法的な権利 | NFT移転だけで自動的に移るとは限らない |
| 個人利用権 | 自分で表示・鑑賞するなどの利用許可 | 規約で許可される場合がある |
| 商用利用権 | 商品化や収益化に使う許可 | プロジェクトやライセンス次第 |
| 二次創作権 | 改変・派生作品を作る許可 | 許可範囲を確認する必要がある |
| 会員権 | 特定コミュニティやサービスへ参加する権利 | 発行者の運用や規約に依存する |
米国著作権局とUSPTOの共同研究でも、NFTが知的財産権の管理・移転・ライセンスに役立つ可能性がある一方で、NFTの作成・販売・移転に関してどの権利が関係するのか分かりにくいという懸念が認識されています。
参考: U.S. Copyright Office: Non-Fungible Token Study
このため、NFTを説明するときは、次のように分けると誤解が少なくなります。
NFTを持っている
= そのトークンをウォレットで保有している
著作権を持っている
= 作品に関する法的な権利を持っている
利用権を持っている
= 規約やライセンスで許された範囲で使える
この3つは、重なることもあります。
しかし、常に同じとは限りません。
6.5 小さなコードで見る:利用できる範囲を判定する
次に、利用条件をプログラム風に整理してみます。
次のコードは、実際の法律判断やライセンス解釈を行うものではありません。
「どの利用が許可されているのかは、トークンの有無だけでなく、利用条件を見ないと分からない」という点を示すための学習用コードです。
# NFTに紐づくコンテンツの利用可否を、説明用に判定する学習用コードです。
# 実際の法律判断、契約解釈、ライセンス解釈には使えません。
rights = {
"personal_display": True,
"commercial_use": False,
"derivative_work": False,
}
def can_use_nft_content(action: str, rights: dict) -> bool:
"""
利用したい行為が、説明用の権利設定で許可されているか確認します。
注意:
- これは学習用の単純な判定です。
- 実際の権利判断は、法律、契約、利用規約、ライセンス本文によって変わります。
- 本記事は法律上の助言を目的としていません。
"""
if action == "personal_display":
return rights.get("personal_display", False)
if action == "commercial_use":
return rights.get("commercial_use", False)
if action == "derivative_work":
return rights.get("derivative_work", False)
# 想定していない使い方は、説明用には許可しない扱いにします。
return False
actions = [
("personal_display", "自分のプロフィールに表示する"),
("commercial_use", "Tシャツに印刷して販売する"),
("derivative_work", "キャラクターを改変して別作品を作る"),
]
for action, description in actions:
result = can_use_nft_content(action, rights)
print(f"{description}: {'許可されている' if result else '許可されていない'}")
このコードでは、個人表示だけが許可されています。
一方で、商用利用や二次創作は許可されていません。
もちろん、実際のNFTプロジェクトでは、もっと細かい利用条件が定められる場合があります。
たとえば、次のような条件が考えられます。
- NFT保有中だけ利用できる
- 商用利用はできるが、年間売上に制限がある
- 二次創作はできるが、元作品の信用を損なう使い方は禁止
- ロゴやブランド名の使用は別条件
- NFTを売却したら利用権も終了する
- 利用権はNFT購入者に移るが、著作権は作者に残る
このように、利用条件はプロジェクトや契約によって変わります。
そのため、NFTを見るときは、「トークンを持っているか」だけでなく、「どのような利用が許されているのか」を確認することが重要です。
6.6 NFTを移転しても、著作権者が自動で変わるとは限らない
NFTでは、トークンの移転は比較的分かりやすいです。
Aliceが持っていたNFTをBobへ送れば、オンチェーン上の所有者はAliceからBobへ変わります。
ERC-721であれば、Transfer イベントが発生し、ownerOf(tokenId) の結果もBobのアドレスへ変わります。
しかし、ここで注意したいのは、トークンの所有者が変わること と 著作権者が変わること は別だという点です。
NFTの移転によって、利用権も一緒に次の保有者へ移るように設計されている場合もあります。
一方で、利用権が限定的だったり、商用利用が認められていなかったり、そもそも発行者が権利を持っていなかったりする場合も考えられます。
この点は、NFTの購入者だけでなく、開発者やサービス設計者にとっても重要です。
NFTを会員証、チケット、証明書、ゲームアイテムなどに使う場合は、オンチェーンの所有者情報と、オフチェーンの利用条件をどう結びつけるかを設計する必要があります。
6.7 小さなコードで見る:トークン移転と権利者は別に扱う
ここでも、Pythonで単純化して考えます。
次のコードでは、NFTの所有者だけを移転しています。
しかし、作品の著作権者や利用条件はそのまま残しています。
# NFTの移転と、作品の著作権者が別の情報であることを確認する学習用コードです。
# 実際のERC-721 transfer処理や法律上の権利移転を再現するものではありません。
from dataclasses import dataclass
@dataclass
class TokenState:
"""NFTの現在の所有者を表す、説明用のデータです。"""
token_id: int
owner: str
@dataclass
class WorkRights:
"""NFTが参照する作品の権利者を表す、説明用のデータです。"""
work_id: str
copyright_owner: str
license_summary: str
def transfer_token(token: TokenState, new_owner: str) -> None:
"""
NFTの所有者だけを変更します。
注意:
- ERC-721のtransferを厳密に再現するものではありません。
- ここでは「トークンの所有者が変わっても、作品の著作権者は自動では変わらない」
という点を説明するために単純化しています。
"""
token.owner = new_owner
token = TokenState(token_id=101, owner="0xAlice")
rights = WorkRights(
work_id="art-001",
copyright_owner="Artist A",
license_summary="NFT保有者は、個人利用の範囲で表示できる",
)
print("移転前")
print(" NFT所有者:", token.owner)
print(" 著作権者:", rights.copyright_owner)
print(" 利用条件:", rights.license_summary)
transfer_token(token, "0xBob")
print("\n移転後")
print(" NFT所有者:", token.owner)
print(" 著作権者:", rights.copyright_owner)
print(" 利用条件:", rights.license_summary)
このコードでは、移転後にNFT所有者は 0xBob になります。
しかし、著作権者は Artist A のままです。
このように、NFTの移転はオンチェーン上で確認できても、著作権や利用条件は別の情報として扱う必要があります。
もちろん、契約によっては、NFTの移転に合わせて一定の利用権が新しい保有者へ移るように定められることもあります。
その場合でも、「どの権利が、どの条件で、いつまで、誰に移るのか」を規約やライセンスで確認する必要があります。
6.8 マーケットプレイスに表示される情報だけでは足りない場合がある
NFTは、マーケットプレイスやウォレットで画像付きで表示されることが多いです。
そのため、画面上では「この画像を買う」ように見えることがあります。
しかし、マーケットプレイスの表示は、NFTに関する情報を分かりやすく見せているだけです。
実際にどのような権利があるかは、NFTの発行者、クリエイター、売り手、プロジェクト規約、ライセンス、マーケットプレイス規約などを確認する必要があります。
OpenSeaの利用規約でも、NFTやデジタルアイテムに関連する権利や義務は、クリエイター・売り手・買い手が設定するNFT Termsの対象になり得ると説明されています。
また、OpenSeaはプラットフォーム上の表示やコンテンツに関する一定のライセンスを定めていますが、第三者が作成したNFTや関連コンテンツの知的財産権そのものをOpenSeaが所有するわけではないことも示されています。
ここで、マーケットプレイスを悪いものとして見る必要はありません。
むしろ、NFTを探したり、表示したり、取引したりするうえで便利な入口になります。
ただし、マーケットプレイスに表示されている画像や説明だけを見て、次のように早合点するのは危険です。
| 早合点しやすいこと | 確認したいこと |
|---|---|
| 画像が表示されているので、その画像の権利も手に入る | 著作権や利用権がどう定められているか |
| 公式らしい名前なので、本物に違いない | コントラクトアドレス、発行者、公式リンク |
| 商用利用できそう | 規約やライセンスで明示されているか |
| NFTを売れば権利も全部移る | どの権利が移転する設計か |
| 画面上で見えるので、ずっと使える | メタデータやコンテンツ保存場所、サービス運用 |
NFTを見るときは、画面上の見た目だけでなく、その裏側の情報も確認する姿勢が大切です。
6.9 無断ミントや権利侵害の問題
NFTでは、誰かが自分の作品をNFTとして発行できる設計になっている場合があります。
これは、クリエイターが自分の作品を直接発行しやすいという利点にもなります。
一方で、悪意ある人が他人の画像や作品を無断で使い、NFTとして発行する問題も起こり得ます。
ここで重要なのは、ブロックチェーン上にNFTが存在すること と その発行者が正当な権利者であること は別だという点です。
NFTコントラクトが存在し、tokenIdが発行され、画像が表示されていても、それだけでは次のことまでは保証されません。
- 発行者が本当に作者本人であること
- 発行者が著作権者から許可を得ていること
- 作品の商標やブランドの使用が許可されていること
- 画像や音楽が無断転載ではないこと
- 関連する規約が購入者に分かりやすく提示されていること
米国著作権局とUSPTOの共同研究でも、NFTが著作権や商標権の侵害を助長する可能性への懸念が認識されています。
ただし、同研究では、現時点でNFTのために知的財産法そのものを変更する必要はないと結論づけ、透明性や教育の重要性を示しています。
参考: U.S. Copyright Office: Non-Fungible Token Study
参考: USPTO: Joint study on non-fungible tokens
6.10 小さなコードで見る:発行者が権利者かを確認するイメージ
実際のNFT発行では、権利確認、本人確認、契約確認、マーケットプレイスの審査、コミュニティの検証など、さまざまな仕組みが関係します。
ここでは、かなり単純化したコードで、「NFTを発行できること」と「正当に発行してよいこと」は別である、という感覚を確認します。
# NFT発行者が作品の権利者または許可された主体かを確認するイメージの学習用コードです。
# 実際の本人確認、契約確認、著作権確認、マーケットプレイス審査を再現するものではありません。
authorized_minters = {
"art-001": {"Artist A", "Studio A"},
"music-001": {"Musician B"},
}
def can_mint_for_work(work_id: str, minter: str) -> bool:
"""
作品IDと発行者名をもとに、NFT発行を許可してよいかを確認する学習用関数です。
注意:
- 実際のNFT発行では、権利確認、本人確認、契約、マーケットプレイスの審査などが関係します。
- ブロックチェーン上にトークンを作れることと、その人が正当な権利者であることは別問題です。
"""
allowed = authorized_minters.get(work_id, set())
return minter in allowed
mint_requests = [
{"work_id": "art-001", "minter": "Artist A"},
{"work_id": "art-001", "minter": "Unknown User"},
{"work_id": "music-001", "minter": "Musician B"},
]
for request in mint_requests:
permitted = can_mint_for_work(request["work_id"], request["minter"])
print(
f"work={request['work_id']}, minter={request['minter']}: "
f"{'発行を許可' if permitted else '権利確認が必要'}"
)
このコードでは、Artist A は art-001 を発行できます。
一方で、Unknown User が同じ作品を発行しようとすると、権利確認が必要と表示されます。
もちろん、実際のブロックチェーンでは、誰でもコントラクトを作ったり、トークンを発行したりできる場合があります。
そのため、利用者側は「NFTが存在するか」だけでなく、「誰が発行したのか」「公式コントラクトか」「権利者が関わっているか」を確認する必要があります。
6.11 会員権・チケット・ゲームアイテムでも同じ考え方が必要
NFTはアートだけでなく、会員権、チケット、ゲームアイテム、証明書などにも使われます。
このような用途では、「著作権」というより、サービス上の権利 が問題になります。
たとえば、NFT会員証を持っている人だけが限定イベントに参加できるとします。
この場合、オンチェーン上ではNFTの所有者を確認できます。
しかし、実際にイベントへ入れるかどうかは、イベント運営側のルールにも依存します。
| 用途 | NFTで確認しやすいこと | 別途確認が必要なこと |
|---|---|---|
| 会員証 | 指定NFTを持っているか | 会員特典の内容、有効期限、譲渡可否 |
| チケット | 指定tokenIdを持っているか | 入場条件、本人確認、払い戻し、二次流通ルール |
| ゲームアイテム | アイテムNFTを持っているか | ゲーム内で使えるか、運営停止時の扱い |
| 証明書 | 証明トークンが発行されているか | 発行主体の信頼性、失効や訂正の扱い |
| 物理資産連動 | トークンを持っているか | 現物の保管、引き渡し、法的所有権 |
このように、NFTは「権利を確認する入口」にはなり得ます。
しかし、現実のサービスや物理的な権利と結びつけるには、発行者の運用や規約、本人確認、失効処理、紛争対応なども必要になります。
NFTを実用化するときは、スマートコントラクトだけで完結する部分と、現実の運用で支える部分を分けて設計することが重要です。
6.12 NFTを見るときに確認したいこと
ここまでの内容を踏まえると、NFTを見るときは、少なくとも次のような観点を確認するとよいです。
| 確認項目 | 見る理由 |
|---|---|
| コントラクトアドレス | 公式コントラクトか、偽物ではないかを確認するため |
| tokenId / id | どのトークンを指しているのかを確認するため |
| 発行者 | 作者本人、公式プロジェクト、権利者に関係する主体かを確認するため |
| メタデータURI | NFTがどの説明情報を参照しているかを確認するため |
| コンテンツ保存場所 | 画像や音楽がどこに保存されているかを確認するため |
| 利用規約・ライセンス | 個人利用、商用利用、二次創作、譲渡時の扱いを確認するため |
| マーケットプレイス規約 | 表示、取引、紛争対応、手数料、制限を確認するため |
| 会員権・特典の条件 | NFT保有で何ができるのかを確認するため |
| セキュリティ注意点 | 偽サイト、承認悪用、フィッシングを避けるため |
特に、利用規約やライセンスは読み飛ばされがちです。
しかし、NFTに関連するコンテンツをどう使えるかを考えるうえでは、とても重要です。
NFTの世界では、「トークンを持っている」という事実はブロックチェーンで確認しやすくなります。
一方で、「そのトークンにどのような権利が付いているのか」は、規約や契約、発行者の説明、サービス運用とセットで確認する必要があります。
6.13 この章のまとめ
この章では、NFTと所有権・著作権・利用権を分けて整理しました。
NFTを持っていることは、まずオンチェーン上で、そのトークンの所有者アドレスになっていることを意味します。
しかし、それだけで、画像や音楽などのコンテンツの著作権、商用利用権、二次創作権、会員特典、物理資産の所有権まで自動的に手に入るとは限りません。
この章で押さえたいポイントは、次の通りです。
| ポイント | 説明 |
|---|---|
| NFT所有はオンチェーン上の状態 |
ownerOf(tokenId) や残高情報で確認できる |
| NFTとコンテンツは別物 | NFTはトークンであり、画像や音楽そのものとは分けて考える |
| 著作権と利用権は別 | 著作権者が利用を許可する範囲は、規約やライセンスによる |
| NFT移転と著作権移転は同じではない | トークンを移転しても、著作権者が自動で変わるとは限らない |
| 無断ミントの問題がある | NFTが存在することだけでは、発行者が正当な権利者とは限らない |
| 実用NFTでは運用も重要 | 会員証、チケット、証明書などは発行者の運用や規約に依存する |
| 確認すべき情報が多い | コントラクト、発行者、メタデータ、保存場所、規約、権利範囲を確認する必要がある |
NFTを理解するときは、次の一文を意識すると整理しやすくなります。
NFTは、ブロックチェーン上で一意に識別・移転できるトークンです。
ただし、そのトークンが何を表し、どのような権利を伴うかは、メタデータ、規約、ライセンス、発行者の運用によって決まります。
ここまでで、NFTの技術的な構造と、権利関係を分けて見る準備ができました。
次の章では、NFTと一緒に語られやすい ロイヤリティ を整理します。
「NFTを二次流通すると、作者へ自動的にロイヤリティが入る」と説明されることがありますが、実際には標準仕様、マーケットプレイス、支払い実行の仕組みを分けて考える必要があります。
7. NFTロイヤリティの仕組みと限界
この章では、NFTと一緒に語られやすい ロイヤリティ を整理します。
ポイントは、ERC-2981が提供するのは「ロイヤリティ情報を取得する共通の呼び出し方」であり、すべての取引でロイヤリティ支払いを強制する仕組みではない という点です。
前の章では、NFTを持っていることと、著作権・利用権・会員権などを持っていることは別に考える必要がある、という話をしました。
この考え方は、ロイヤリティにもつながります。
NFTでは、「二次流通のたびに作者へロイヤリティが入る」と説明されることがあります。
たしかに、NFTの仕組みとマーケットプレイスを組み合わせることで、作品が再販売されたときにクリエイターや権利者へ一定額を支払う設計はできます。
ただし、ここで注意が必要です。
ロイヤリティは、NFTの標準仕様だけで常に自動徴収されるものではありません。
特に、ERC-2981は「誰に、いくら支払うべきか」を取得するための標準であり、実際に支払いを行う処理や、すべての取引で支払いを強制する仕組みまでは定義していません。
ERC-2981では、NFTコントラクトが royaltyInfo() を通じて、指定されたNFTと販売価格に対するロイヤリティ受取先と金額を返す標準が定義されています。
一方で、実際の資金移転はマーケットプレイスなどが行うものとして説明されています。
参考: ERC-2981: NFT Royalty Standard
この章では、NFTロイヤリティを次の順番で整理します。
| 観点 | ざっくりした問い |
|---|---|
| ロイヤリティとは何か | 二次流通時に、誰へどのくらい支払うのか |
| ERC-2981とは何か | ロイヤリティ情報をどう取得するのか |
| 支払いは強制されるのか | 標準仕様だけで必ず支払われるのか |
| transferとsaleの違い | NFTの移転はすべて売買なのか |
| 権利関係との関係 | ロイヤリティ設定は著作権の証明になるのか |
7.1 ロイヤリティは「再販売時の取り分」として語られることが多い
まず、身近な例から考えてみます。
あるイラスト作品のNFTを、最初に作者がAliceへ販売したとします。
その後、AliceがBobへ再販売したとき、販売価格の一部を作者へ支払う、という考え方があります。
この「再販売されたときに、作者や権利者へ一定額を支払う」という仕組みが、NFTロイヤリティとして説明されることが多いです。
たとえば、販売価格が100,000円相当で、ロイヤリティ率が5%なら、5,000円相当を作者へ支払う、というイメージです。
| 販売価格 | ロイヤリティ率 | 作者への支払い | 売り手の受け取り |
|---|---|---|---|
| 100,000 | 5% | 5,000 | 95,000 |
| 1,000,000 | 5% | 50,000 | 950,000 |
ただし、この表はあくまで分かりやすくするための例です。
実際には、マーケットプレイス手数料、支払い通貨、税務、規約、複数人への分配など、他の要素も関係します。
NFTロイヤリティを理解するときは、まず次のように分けると読みやすくなります。
| 層 | 内容 |
|---|---|
| NFTコントラクト | ロイヤリティ情報を返す場合がある |
| ERC-2981 |
royaltyInfo() の共通インターフェースを定義する |
| マーケットプレイス | 取得したロイヤリティ情報をもとに支払いを行う場合がある |
| 規約・契約 | ロイヤリティや権利関係の扱いを補足する |
| 実際の支払い | 売買処理の中で資金を分配する |
つまり、NFTロイヤリティは、スマートコントラクトだけで完結するというより、標準仕様、マーケットプレイス、規約、支払い処理が組み合わさって成り立つものです。
7.2 ERC-2981は「誰に、いくら支払うか」を返す標準
NFTロイヤリティの代表的な標準が ERC-2981 です。
ERC-2981は、NFTの販売価格を入力として受け取り、ロイヤリティの受取先アドレスと金額を返すためのインターフェースを定義しています。
中心になる関数は、次の royaltyInfo() です。
function royaltyInfo(
uint256 _tokenId,
uint256 _salePrice
) external view returns (
address receiver,
uint256 royaltyAmount
);
かなりざっくり言えば、マーケットプレイスが次のように質問できるようにする仕組みです。
このtokenIdのNFTを、この価格で売る場合、
誰に、いくらロイヤリティを支払えばよいですか?
そして、NFTコントラクト側は次のように答えます。
受取先は ArtistWallet です。
支払うロイヤリティ額は 50,000 です。
ERC-2981の仕様では、royaltyInfo() は _tokenId と _salePrice を受け取り、receiver と royaltyAmount を返すと定義されています。
また、ロイヤリティ額は販売価格に対する割合として計算されるべきであり、販売価格の単位を勝手に仮定する固定額を返すべきではない、と説明されています。
ここで重要なのは、ERC-2981が決めているのは 情報の取り出し方 だということです。
| ERC-2981が扱うこと | ERC-2981だけでは扱わないこと |
|---|---|
| ロイヤリティ受取先を返す | 実際に送金する処理そのもの |
| 販売価格に応じた金額を返す | マーケットプレイス手数料の設計 |
共通の royaltyInfo() を定義する |
著作権の有無を証明すること |
| ERC-721/ERC-1155などと組み合わせられる | すべての二次流通で支払いを強制すること |
💡 豆知識
ERC-2981は、ERC-721専用の標準ではありません。
仕様上はERC-721やERC-1155と一緒に使われることが多いですが、ERC-2981自体は「一意に識別できる資産に対してロイヤリティ情報を返す標準」として設計されています。
そのため、NFTロイヤリティのための小さな部品として見ると理解しやすいです。
7.3 小さなコードで見る:royaltyInfoの考え方
ここで、royaltyInfo() の考え方をPythonで小さく再現してみます。
次のコードは、実際のSolidity実装ではありません。
ERC-2981の「販売価格をもとに、受取先とロイヤリティ額を返す」という考え方を理解するための学習用コードです。
# ERC-2981の royaltyInfo の考え方を理解するための学習用コードです。
# 実際のSolidityコントラクトやEthereum上の処理ではありません。
from dataclasses import dataclass
@dataclass
class RoyaltyInfo:
"""ロイヤリティ受取先と料率を表す学習用データです。"""
receiver: str # ロイヤリティを受け取るウォレットやコントラクトの名前
bps: int # basis points。10000bps = 100%、500bps = 5% として扱います。
class SimpleRoyaltyRegistry:
"""tokenIdごとにロイヤリティ情報を管理する学習用クラスです。"""
def __init__(self):
# royalties[token_id] = RoyaltyInfo(...) の形で保存します。
self.royalties = {}
def set_royalty(self, token_id: int, receiver: str, bps: int) -> None:
"""tokenIdごとのロイヤリティ受取先と料率を設定します。"""
if not 0 <= bps <= 10_000:
raise ValueError("bpsは0から10000の範囲で指定してください。")
self.royalties[token_id] = RoyaltyInfo(receiver=receiver, bps=bps)
def royalty_info(self, token_id: int, sale_price: int) -> tuple[str, int]:
"""
ERC-2981のroyaltyInfoに近い考え方です。
token_idとsale_priceを受け取り、
ロイヤリティ受取先とロイヤリティ額を返します。
"""
royalty = self.royalties[token_id]
# royalty_amountは販売価格に対する割合で計算します。
# 例: sale_price=1_000_000, bps=500なら、5%なので50_000になります。
royalty_amount = sale_price * royalty.bps // 10_000
return royalty.receiver, royalty_amount
registry = SimpleRoyaltyRegistry()
# tokenId 101のNFTについて、ArtistWalletへ5%のロイヤリティを設定します。
registry.set_royalty(token_id=101, receiver="ArtistWallet", bps=500)
# 販売価格1,000,000に対するロイヤリティ情報を取得します。
receiver, amount = registry.royalty_info(token_id=101, sale_price=1_000_000)
print("ロイヤリティ受取先:", receiver)
print("ロイヤリティ額:", amount)
このコードでは、販売価格が 1_000_000、ロイヤリティ率が5%なので、ロイヤリティ額は 50_000 になります。
ここでのポイントは、ロイヤリティ額が固定値ではなく、販売価格に応じて計算されていることです。
ERC-2981でも、royaltyInfo() は販売価格に対して割合ベースでロイヤリティ額を計算することを前提にしています。
7.4 ロイヤリティ率は「販売価格に対する割合」として考える
ERC-2981では、ロイヤリティ額は販売価格に対する割合として計算されます。
これは、販売価格が大きくなればロイヤリティ額も大きくなり、販売価格が小さくなればロイヤリティ額も小さくなる、という考え方です。
# 販売価格に対する割合でロイヤリティ額が変わることを確認する学習用コードです。
# 実際のERC-2981実装やマーケットプレイスの会計処理ではありません。
sale_prices = [100, 1_000, 10_000]
royalty_bps = 750 # 750bps = 7.5%
for price in sale_prices:
# 販売価格に同じ割合をかけて、ロイヤリティ額を計算します。
royalty_amount = price * royalty_bps // 10_000
print(f"sale_price={price}, royalty={royalty_amount}")
このコードでは、販売価格が変わると、ロイヤリティ額も変わります。
たとえば、7.5%のロイヤリティなら、販売価格が1,000のときは75、10,000のときは750になります。
ここで注意したいのは、royaltyInfo() 自体は「ETHなのか、USDCなのか、日本円なのか」といった単位を理解しているわけではないことです。
ERC-2981では、_salePrice と返される royaltyAmount は同じ単位で扱われるべきだと説明されています。
つまり、販売価格がETH建てならロイヤリティもETH建て、USDC建てならロイヤリティもUSDC建て、という考え方です。
7.5 ERC-2981は支払いそのものを実行する標準ではない
ここが、NFTロイヤリティで特に誤解されやすい点です。
ERC-2981は、royaltyInfo() によってロイヤリティ情報を返す標準です。
しかし、実際にロイヤリティを送金する処理まで自動で実行する標準ではありません。
ERC-2981の仕様でも、実際の資金移転はマーケットプレイスが実行するものと説明されています。
また、transferFrom() のようなNFT移転は、必ずしも売買を意味しないため、すべての移転にロイヤリティ支払いを強制することはできない、と整理されています。
参考: ERC-2981: Rationale - Optional royalty payments
身近な例で考えると、チケットを友人へ譲る場合と、フリマで売る場合は違います。
| NFTの移転 | 売買か | ロイヤリティが必要になるとは限らない理由 |
|---|---|---|
| 自分の別ウォレットへ移す | いいえ | ただの保管場所変更かもしれない |
| 家族や友人へ送る | いいえ | 贈与や移動で、販売価格がない場合がある |
| マーケットプレイスで売る | はい | 販売価格があり、ロイヤリティ計算ができる |
| OTC取引で売る | 場合による | 売買だが、支払い実行は取引方式に依存する |
つまり、NFTが移転しただけでは、それが売買なのか、贈与なのか、ウォレット整理なのかをコントラクトだけで完全に判断するのは難しいです。
このため、ERC-2981は「販売価格が分かっている売買の場面で、ロイヤリティ情報を取得する」ための部品として理解するのが安全です。
7.6 小さなコードで見る:transferとsaleを分ける
次のコードでは、NFTの単なる移転と、ロイヤリティ支払いを伴う売買を分けて考えます。
このコードも、実際のNFTマーケットプレイスやスマートコントラクトではありません。
transfer_only() はNFTの所有者だけを変え、buy() は販売価格をもとにロイヤリティを計算し、売り手と作者へ資金を分配する、という学習用のモデルです。
# NFTの単なる移転と、販売価格を伴う売買を分けて考えるための学習用コードです。
# 実際のNFTマーケットプレイス、Ethereum、Solidity実装ではありません。
class MiniNftMarket:
"""ロイヤリティ支払いを伴う売買を単純化した学習用マーケットです。"""
def __init__(self, royalty_registry: SimpleRoyaltyRegistry):
# owners[token_id] = owner の形で、NFTの所有者を管理します。
self.owners = {101: "Alice"}
# balances[name] = amount の形で、説明用の残高を管理します。
self.balances = {
"Alice": 0,
"Bob": 1_000_000,
"ArtistWallet": 0,
"SellerWallet": 0,
}
self.royalty_registry = royalty_registry
def transfer_only(self, token_id: int, from_: str, to: str) -> None:
"""
NFTを単に移転する処理です。
これはウォレット移動や贈与のような場面を想定しています。
販売価格がないため、この関数ではロイヤリティ計算を行いません。
"""
if self.owners[token_id] != from_:
raise ValueError("from_は現在の所有者である必要があります。")
self.owners[token_id] = to
def buy(self, token_id: int, buyer: str, seller: str, sale_price: int) -> None:
"""
NFTを売買する処理です。
販売価格があるため、royalty_info()を呼び出して、
ロイヤリティ受取先と金額を確認します。
"""
if self.owners[token_id] != seller:
raise ValueError("sellerは現在の所有者である必要があります。")
# ERC-2981のroyaltyInfoに相当する処理で、受取先と金額を取得します。
receiver, royalty_amount = self.royalty_registry.royalty_info(token_id, sale_price)
# 売り手が受け取る金額は、販売価格からロイヤリティを引いた金額です。
seller_amount = sale_price - royalty_amount
if self.balances[buyer] < sale_price:
raise ValueError("buyerの残高が足りません。")
# buyerから販売価格を差し引きます。
self.balances[buyer] -= sale_price
# 作者または権利者へロイヤリティを支払います。
self.balances[receiver] += royalty_amount
# 売り手へ残りを支払います。
self.balances["SellerWallet"] += seller_amount
# 最後にNFTの所有者をbuyerへ変更します。
self.owners[token_id] = buyer
market = MiniNftMarket(registry)
# AliceがBobへNFTを販売します。
market.buy(token_id=101, buyer="Bob", seller="Alice", sale_price=1_000_000)
print("tokenId 101の新しい所有者:", market.owners[101])
print("残高:", market.balances)
このコードでは、buy() の中でロイヤリティ情報を取得し、販売価格の一部を ArtistWallet に加算しています。
一方で、transfer_only() はNFTの所有者を変えるだけです。
この違いが大切です。
NFTの移転は、必ずしも売買とは限りません。
そのため、ERC-2981は「すべての移転でロイヤリティを強制徴収する」設計にはなっていません。
7.7 transferFromが呼ばれても、売買とは限らない
ERC-721では、NFTを移転するために transferFrom() や safeTransferFrom() が使われます。
しかし、これらはあくまでNFTの所有者を移すための関数です。
ERC-721仕様では、transferFrom() や safeTransferFrom() はNFTの所有権を別アドレスへ移転するための関数として定義されています。
一方で、そこに「販売価格」や「ロイヤリティ支払い」の意味が必ず含まれるわけではありません。
たとえば、次の3つはすべてNFTの移転ですが、意味は異なります。
ここを混同すると、次のような誤解につながります。
| 誤解 | 正確な整理 |
|---|---|
| NFTが移転したら必ず売買である | ウォレット移動や贈与もある |
| transferFromには必ず販売価格が含まれる | ERC-721のtransferは所有者変更の関数であり、販売価格は標準関数の引数ではない |
| ERC-2981なら全移転で自動支払いされる | ERC-2981はロイヤリティ情報取得の標準であり、支払い実行は別 |
このため、NFTロイヤリティを理解するときは、移転 と 売買 を分けて考える必要があります。
7.8 小さなコードで見る:どの移転でロイヤリティを確認するか
次のコードでは、NFTの移転イベントが「売買」なのか「単なる移動」なのかを分けて、ロイヤリティ確認が必要かを判断するイメージを示します。
実際には、オンチェーンイベントだけで売買理由を完全に判断できるとは限りません。
ここでは、説明用に reason を持つデータを使っています。
# NFTの移転が「売買」なのか「単なる移動」なのかを分けて考えるための学習用コードです。
# 実際のオンチェーン解析やマーケットプレイス実装ではありません。
from dataclasses import dataclass
@dataclass
class TransferEvent:
"""NFT移転を表す学習用データです。"""
token_id: int
from_: str
to: str
reason: str
sale_price: int | None = None # 売買でなければ販売価格はない、という想定です。
def should_query_royalty(event: TransferEvent) -> bool:
"""
ロイヤリティ情報を確認すべき移転かどうかを判定します。
注意:
- 実際には、イベントだけで売買かどうかを完全に判断できるとは限りません。
- ここでは、reasonがsaleで、sale_priceがある場合だけ売買として扱います。
"""
return event.reason == "sale" and event.sale_price is not None
events = [
TransferEvent(token_id=101, from_="Alice", to="Bob", reason="sale", sale_price=1_000_000),
TransferEvent(token_id=101, from_="Bob", to="BobColdWallet", reason="wallet_move"),
TransferEvent(token_id=102, from_="Project", to="Carol", reason="gift"),
]
for event in events:
if should_query_royalty(event):
print(f"tokenId {event.token_id}: 売買なのでロイヤリティ情報を確認します。")
else:
print(f"tokenId {event.token_id}: 売買とは限らないため、ここでは確認しません。")
このコードでは、reason="sale" で販売価格がある移転だけを、ロイヤリティ確認の対象にしています。
実際のNFT取引では、マーケットプレイスの売買コントラクト、注文データ、決済処理、イベントログなどを組み合わせて判断することになります。
この章では、まず「移転と売買は同じではない」という考え方を押さえます。
7.9 マーケットプレイス依存になる部分がある
ERC-2981は、ロイヤリティ情報の取得方法をそろえるための標準です。
しかし、実際にロイヤリティを支払うかどうか、どのタイミングで支払うか、どのように売り手・買い手・作者へ分配するかは、マーケットプレイスや売買コントラクトの実装に依存します。
ERC-2981の仕様でも、マーケットプレイスが royaltyInfo() で情報を取得し、実際の資金移転はマーケットプレイスが実行するものとして説明されています。
また、ロイヤリティ支払いは任意であり、支払いと通知の具体的な仕組みはこの標準では定義しないとされています。
このため、記事内では次のような表現にしておくのが安全です。
ERC-2981に対応しているNFTは、ロイヤリティ受取先と金額を共通の方法で返せます。
ただし、実際にロイヤリティが支払われるかどうかは、マーケットプレイスや売買方式がその情報を尊重して支払い処理を実行するかに依存します。
表にすると、次のようになります。
| 場面 | ERC-2981ができること | 別途必要なこと |
|---|---|---|
| マーケットプレイス販売 | 受取先と金額を返す | マーケットプレイス側の支払い実装 |
| 個人間の直接取引 | 情報は取得できる場合がある | 当事者が支払うかどうか、決済方法 |
| オフチェーン取引 | 標準情報は参照できる場合がある | オフチェーンでの支払い・契約・通知 |
| ウォレット移動 | 売買ではない可能性が高い | 通常、ロイヤリティ支払い対象とは限らない |
つまり、ロイヤリティは「NFTに書いてあるから必ず支払われる」というより、標準情報を参照する側が、それをどう扱うか が重要になります。
7.10 複数人への分配はERC-2981だけでは細かく決めない
作品によっては、作者が1人ではないことがあります。
たとえば、次のようなケースです。
- イラスト担当と音楽担当がいる
- アーティストとプロデューサーがいる
- チーム制作で複数人に分配したい
- 一部をコミュニティ基金へ入れたい
このような場合、ロイヤリティを複数人に分けたいことがあります。
しかし、ERC-2981の royaltyInfo() は、基本的に1つの receiver と1つの royaltyAmount を返します。
複雑な分配、税務、会計、複数受取人への配分などは、受取側のコントラクトやオフチェーン処理で扱うものとして整理されています。
参考: ERC-2981: Simple royalty payments to a single address
このため、複数人へ分配したい場合は、たとえば次のような設計が考えられます。
ERC-2981の視点では、マーケットプレイスは receiver にロイヤリティ額を支払います。
その先で、受け取ったコントラクトが複数人へ分配する、という考え方です。
7.11 小さなコードで見る:ロイヤリティを複数人に分配するイメージ
次のコードは、ロイヤリティ額を複数人へ分配するイメージを示す学習用コードです。
これはERC-2981そのものではありません。
ERC-2981の royaltyInfo() が返す receiver の先に、分配用の仕組みがあると考えるための例です。
# ロイヤリティ受取先の中で、複数人へ分配するイメージを理解するための学習用コードです。
# 実際のPaymentSplitter、Solidityコントラクト、会計・税務処理ではありません。
from dataclasses import dataclass
@dataclass
class SplitReceiver:
"""ロイヤリティを複数の受取先へ分ける学習用クラスです。"""
recipients: list[tuple[str, int]] # (受取先名, bps) のリストです。
def split(self, royalty_amount: int) -> dict[str, int]:
"""
ロイヤリティ額を、bpsに応じて分配します。
注意:
- 10000bps = 100% として扱います。
- 端数処理は単純化しています。
- 実際には端数、税務、会計、送金失敗なども考慮が必要です。
"""
total_bps = sum(bps for _, bps in self.recipients)
if total_bps != 10_000:
raise ValueError("分配比率の合計は10000bpsにしてください。")
return {
name: royalty_amount * bps // 10_000
for name, bps in self.recipients
}
split_receiver = SplitReceiver(
recipients=[
("Artist A", 7_000),
("Artist B", 2_000),
("Community Treasury", 1_000),
]
)
# 受け取ったロイヤリティ額50,000を、設定した比率で分配します。
result = split_receiver.split(royalty_amount=50_000)
print(result)
このコードでは、50,000のロイヤリティを、Artist Aへ70%、Artist Bへ20%、Community Treasuryへ10%で分けています。
実際のスマートコントラクトでは、端数、送金失敗、ガス代、受取先の変更、税務、会計処理なども考える必要があります。
そのため、ERC-2981はあえて小さな標準として、まず「ロイヤリティの受取先と金額を返す」ことに絞っている、と見ると分かりやすいです。
7.12 ロイヤリティ設定は「権利者である証明」ではない
6章の話とつながりますが、ロイヤリティ設定も権利関係と分けて考える必要があります。
NFTコントラクトにロイヤリティ受取先が設定されていても、それだけで次のことが自動的に証明されるわけではありません。
- その受取先が本当に作者本人であること
- その受取先が著作権者であること
- そのNFTが正規に発行されたこと
- そのNFTの二次流通に法的なロイヤリティ請求権があること
- 購入者がどのような利用権を得るか
ロイヤリティ設定は、あくまでNFTコントラクトや関連システム上の設定です。
権利者であるか、どのような契約があるか、どの利用が許されるかは、別途確認が必要です。
米国著作権局とUSPTOのNFT共同研究でも、NFTの所有と、関連する作品の著作権や知的財産権の所有は分けて考える必要があることが整理されています。
参考: U.S. Copyright Office: NFT Study
ロイヤリティ章でも、次の整理を守ると誤解を減らせます。
| 確認したいこと | どこを見るか |
|---|---|
| ロイヤリティ受取先 | ERC-2981の royaltyInfo() やプロジェクト情報 |
| 実際に支払われるか | マーケットプレイスや売買コントラクトの実装・規約 |
| 権利者かどうか | 作者情報、公式サイト、契約、ライセンス、権利表記 |
| 利用できる範囲 | NFT Terms、ライセンス、マーケットプレイス規約 |
| 正規発行か | コントラクトアドレス、公式リンク、発行主体の確認 |
つまり、ロイヤリティはNFTの大切な機能の一つですが、権利関係をすべて解決するものではありません。
7.13 NFTロイヤリティを理解するときの注意点
ここまでの内容を踏まえると、NFTロイヤリティについては次のような誤解に注意が必要です。
| 誤解 | 正確な整理 |
|---|---|
| NFTなら必ず二次流通ロイヤリティが入る | ERC-2981は情報取得標準であり、実際の支払いはマーケットプレイスなどに依存する |
| ERC-2981があれば支払いが強制される |
royaltyInfo() は受取先と金額を返すが、資金移転そのものは別の処理が必要 |
| NFTが移転したら必ず売買である | ウォレット移動、贈与、担保移動などもあり得る |
| ロイヤリティ受取先なら著作権者である | 受取先設定と権利関係は別に確認する必要がある |
| ロイヤリティ率はどの場面でも同じように扱われる | 実装、マーケットプレイス、売買方式、通貨、規約によって扱いが変わる場合がある |
特に、NFTロイヤリティを「作者を守る完全な仕組み」と言い切るのは避けた方がよいです。
ERC-2981は、ロイヤリティ情報を共通の方法で取得できるようにする重要な標準です。
しかし、支払いの実行、規約との整合、権利者確認、複数人への分配、オフチェーン取引への対応などは、標準の外側にも多く残ります。
この章では、次の一文を押さえておきます。
NFTロイヤリティは、標準仕様だけで完結するものではありません。
ERC-2981は「誰に、いくら支払うべきか」を返す共通の仕組みであり、実際に支払われるかどうかはマーケットプレイスや売買方式、規約、運用に依存します。
7.14 この章のまとめ
この章では、NFTロイヤリティの仕組みと限界を整理しました。
NFTロイヤリティは、作品やプロジェクトが二次流通されたときに、クリエイターや権利者へ一定額を支払う仕組みとして語られます。
ただし、ERC-2981が提供するのは、ロイヤリティ受取先と金額を取得するための共通インターフェースです。
この章で押さえたいポイントは、次の通りです。
| ポイント | 説明 |
|---|---|
| ERC-2981はロイヤリティ情報取得の標準 |
royaltyInfo(tokenId, salePrice) で受取先と金額を返す |
| ロイヤリティ額は販売価格に応じて計算される | 固定額ではなく、販売価格に対する割合として考える |
| 支払い実行は別の処理 | ERC-2981だけで実際の送金が自動実行されるわけではない |
| transferとsaleは違う | NFTの移転は、売買、贈与、ウォレット移動など複数の意味を持つ |
| マーケットプレイス依存がある | ロイヤリティ情報を尊重して支払うかは売買方式や実装に依存する |
| 複数人への分配は別設計 | ERC-2981は基本的に受取先と金額を返す小さな標準 |
| 権利関係とは分けて考える | ロイヤリティ受取先であることは、著作権者であることの自動証明ではない |
NFTロイヤリティを理解するときは、次のように分けると整理しやすいです。
ロイヤリティは、NFTがクリエイターや権利者と継続的につながるための重要な考え方です。
一方で、標準仕様だけで自動的にすべて解決するわけではありません。
次の章では、ここまで見てきたNFTの構造、メタデータ、権利関係、ロイヤリティを踏まえて、NFTがどのような用途で使われるのかを整理します。
アートやコレクションだけでなく、チケット、会員権、ゲームアイテム、証明書など、より広い利用場面を見ていきます。
8. NFTはどこで使われるのか
この章では、NFTがどのような場面で使われるのかを整理します。
ポイントは、NFTを「デジタルアートを売買するもの」だけで見ないことです。
NFTは、一つずつ区別したいものを、ブロックチェーン上のトークンとして表すための仕組み として見ると、用途の幅が分かりやすくなります。
ここまで、NFTの基本構造を順番に見てきました。
1章では、限定チケットや会員証のように「同じ種類でも一つずつ区別したいもの」からNFTの入口を考えました。
2章では、NFTを コントラクトアドレス + tokenId で識別する考え方を整理しました。
3章では、ERC-721が ownerOf、transferFrom、safeTransferFrom、tokenURI などの共通の呼び出し方を決めていることを見ました。
4章では、NFT本体、メタデータ、画像などのコンテンツ、保存場所を分けました。
5章では、ERC-721とERC-1155の違いを見ました。
6章では、NFTの所有と著作権・利用権を分けました。
7章では、NFTロイヤリティは情報取得の標準と実際の支払いを分けて考える必要があることを整理しました。
ここまでの流れを踏まえると、NFTの用途は次のように整理できます。
| 用途 | NFTで表したいもの | 特に注意したいこと |
|---|---|---|
| デジタルアート・コレクション | 一意な作品やコレクション項目 | 画像そのもの・著作権・利用権は別に確認する |
| チケット | イベント入場権、座席、参加証 | 二重利用防止、本人確認、払い戻し、規約が重要 |
| 会員権・アクセス権 | 限定コミュニティ、限定コンテンツへの参加条件 | NFTを持つこととサービス利用条件を分ける |
| ゲームアイテム | 武器、スキン、カード、土地など | ゲーム運営、バランス調整、規約に依存する |
| 証明書・参加証 | 受講証明、イベント参加、貢献記録 | 個人情報、失効、発行者確認に注意する |
| ドメイン名・名前 | ブロックチェーン上の名前やハンドル | 名前解決、管理者、期限、紛争対応などが関係する |
| 現実資産との接点 | 不動産、会員権、証明書などの記録 | 法制度、契約、管理者、現物との対応が不可欠 |
Ethereum公式ドキュメントでも、NFTの用途として、イベント参加の証明、コース修了証明、ゲーム内アイテム、デジタルアート、現実資産のトークン化、オンラインID、コンテンツへのアクセス制御、チケット、分散型インターネットドメイン名などが挙げられています。
参考: ethereum.org: Non-fungible tokens
ただし、用途が広いからといって、NFTを使えば何でも自動的に解決するわけではありません。
NFTが得意なのは、主に トークンの発行、識別、所有者の確認、移転履歴の追跡 です。
一方で、現実世界の権利、本人確認、規約違反、返金、紛失対応、コンテンツの長期保存などは、NFTの外側にある仕組みと組み合わせて考える必要があります。
8.1 デジタルアート・コレクション
NFTの代表的な用途として、まず思い浮かびやすいのがデジタルアートやコレクションです。
たとえば、同じ作者が作ったデジタル作品でも、次のように一つずつ区別したい場面があります。
| 例 | 一つずつ区別したい理由 |
|---|---|
| 限定1点のデジタル作品 | その1点を誰が持っているかを記録したい |
| 100個限定のコレクション | 1番、2番、3番のように個体を区別したい |
| キャラクター画像のコレクション | 背景、服、表情、属性などが個体ごとに違う |
| 記念バッジ | いつ、どのイベントで配布されたかを残したい |
ERC-721の仕様でも、NFTは一つずつ区別できる資産を追跡・移転するための標準APIとして説明されており、仮想コレクションやユニークなアートなどの例が挙げられています。
参考: ERC-721: Non-Fungible Token Standard
ここで大切なのは、デジタルアートNFTを見たときに、次の4つを分けることです。
| 層 | 内容 |
|---|---|
| NFT | ブロックチェーン上のトークン |
| メタデータ | 作品名、説明、画像URI、属性など |
| コンテンツ | 実際の画像、動画、音楽など |
| 権利 | 著作権、商用利用権、二次創作の可否など |
たとえば、マーケットプレイスで見える画像は、NFT本体ではなく、メタデータが参照しているコンテンツである場合があります。
また、NFTを持っていることと、画像の著作権を持っていることは別です。
6章で見たように、NFTを移転しても、関連する著作権や利用権が自動的に移転するとは限りません。
そのため、デジタルアートNFTでは、トークンだけでなく、作者、発行元、コントラクトアドレス、メタデータ、保存場所、ライセンス、規約を一緒に確認する必要があります。
💡 豆知識
NFTコレクションでは、背景、服、目、アクセサリーなどの属性を組み合わせて多数の個体を作ることがあります。
このとき、見た目が似ていてもtokenIdが違えば別のNFTとして扱われます。
ただし、「見た目が珍しい」ことと「権利的に安全である」ことは別なので、見た目だけで判断しないことが大切です。
8.2 小さなコードで見る:用途ごとに必要な確認項目を変える
NFTは、用途によって確認すべき項目が変わります。
デジタルアートなら、作者やライセンスが重要です。
チケットなら、入場済みかどうかやイベント情報が重要です。
会員権なら、現在もサービス側がそのNFTを会員資格として認めているかが重要です。
次のコードは、NFTの用途ごとに、確認すべき項目を切り替える学習用コードです。
実際のNFT検証ツールではありません。
# NFTの用途ごとに確認したい項目が変わることを理解するための学習用コードです。
# 実際のNFTマーケットプレイスやウォレットの検証処理ではありません。
from dataclasses import dataclass
from typing import List
@dataclass
class NFTUseCase:
"""NFTの用途と、その用途で確認したい項目を表します。"""
name: str
example: str
checks: List[str]
use_cases = [
NFTUseCase(
name="デジタルアート",
example="限定アート作品",
checks=["正規コントラクトか", "作者は誰か", "画像の保存場所", "利用できる権利の範囲"],
),
NFTUseCase(
name="チケット",
example="座席番号付きイベントチケット",
checks=["イベント情報", "座席番号", "入場済みか", "払い戻しや譲渡のルール"],
),
NFTUseCase(
name="会員権",
example="限定コミュニティの会員証",
checks=["対象サービス", "有効期限", "アクセス条件", "規約変更の可能性"],
),
NFTUseCase(
name="証明書",
example="講座の修了証明",
checks=["発行者", "対象者", "発行日", "失効していないか"],
),
]
for use_case in use_cases:
print(f"\n用途: {use_case.name}")
print(f"例: {use_case.example}")
print("確認したい項目:")
# 用途ごとに、確認項目を箇条書きで表示します。
for check in use_case.checks:
print(f"- {check}")
このコードでは、NFTそのものの構造は同じでも、用途によって確認したい項目が変わることが分かります。
NFTを使う場面では、ownerOf(tokenId) で所有者を確認するだけでは足りないことがあります。
「何を表すNFTなのか」「どのサービスや権利と結びついているのか」「今も有効なのか」を、用途ごとに確認する必要があります。
8.3 チケット・参加証
NFTは、チケットや参加証のような用途とも相性があります。
チケットは、もともと一つずつ区別したいものです。
イベント名、開催日、座席番号、入場状態、譲渡可否など、個体ごとに管理したい情報があります。
たとえば、同じライブのチケットでも、次の2枚は別物です。
| チケット | 内容 |
|---|---|
| チケットA | 2026-08-01、Aブロック、12番席 |
| チケットB | 2026-08-01、Aブロック、13番席 |
このようなチケットをNFTで表す場合、tokenId ごとに座席やイベント情報を紐づけられます。
Ethereum公式ドキュメントでも、スポーツイベントのチケットを例に、一般入場券のように同じものを複数発行する場合や、座席番号つきで少しずつ異なるチケットを発行する場合が説明されています。
参考: ethereum.org: Non-fungible tokens
ただし、チケットNFTでは、オンチェーン上の所有者だけでなく、オフチェーンの運用も重要です。
| 観点 | 確認したいこと |
|---|---|
| 所有者 | 入場時点で誰のウォレットが持っているか |
| 入場状態 | すでに使用済みではないか |
| 本人確認 | ウォレット所有者と来場者をどう結びつけるか |
| 譲渡ルール | 転売や譲渡を認めるか |
| 払い戻し | 中止時にどう対応するか |
| 紛失対応 | 秘密鍵を失った場合にどうするか |
NFTを使えば、チケットの識別や所有者確認はしやすくなります。
しかし、入場時の本人確認、転売対策、払い戻し、イベント中止時の対応などは、スマートコントラクトだけで完結しない場合があります。
8.4 小さなコードで見る:チケットNFTの入場チェック
次のコードは、チケットNFTで入場確認を行うイメージを表したものです。
ここでは、次の3つを確認します。
- そのNFTを持っているか
- そのNFTが対象イベントのチケットか
- すでに入場済みではないか
実際のイベント運用では、本人確認、ウォレット署名、QRコード、通信障害対応など、もっと多くの要素が必要になります。
このコードは、あくまで考え方を理解するための学習用です。
# チケットNFTの入場確認を理解するための学習用コードです。
# 実際のNFTチケットサービスやスマートコントラクトの実装ではありません。
from dataclasses import dataclass
@dataclass
class TicketNFT:
"""チケットNFTの最小モデルです。"""
token_id: int
event_id: str
seat: str
owner: str
used: bool = False
# tokenIdごとにチケット情報を管理している想定です。
tickets = {
101: TicketNFT(token_id=101, event_id="LIVE-2026-08-01", seat="A-12", owner="0xAlice"),
102: TicketNFT(token_id=102, event_id="LIVE-2026-08-01", seat="A-13", owner="0xBob"),
}
def check_in(token_id: int, wallet: str, event_id: str) -> bool:
"""
チケットNFTを使って入場できるか確認します。
確認すること:
- tokenIdが存在するか
- 対象イベントのチケットか
- 入場しようとしているウォレットが所有者か
- すでに使用済みではないか
"""
ticket = tickets.get(token_id)
if ticket is None:
print("存在しないチケットです。")
return False
if ticket.event_id != event_id:
print("このイベントのチケットではありません。")
return False
if ticket.owner != wallet:
print("このウォレットはチケット所有者ではありません。")
return False
if ticket.used:
print("このチケットはすでに使用済みです。")
return False
# 入場を許可したら、二重利用を防ぐために使用済みにします。
ticket.used = True
print(f"入場を許可しました。座席: {ticket.seat}")
return True
check_in(token_id=101, wallet="0xAlice", event_id="LIVE-2026-08-01")
check_in(token_id=101, wallet="0xAlice", event_id="LIVE-2026-08-01") # 2回目は使用済み
この例では、1回目の入場は許可されますが、2回目は使用済みとして拒否されます。
NFTチケットでは、所有者確認だけでなく、使用済み状態 をどう管理するかが重要です。
この状態をオンチェーンで管理するのか、イベント会場側のシステムで管理するのか、両方を組み合わせるのかは設計によって変わります。
8.5 会員権・アクセス権
NFTは、会員権やアクセス権を表すためにも使われます。
たとえば、次のような場面です。
| 用途 | NFTで確認したいこと |
|---|---|
| 限定コミュニティ | 特定コレクションのNFTを持っているか |
| 限定記事 | 対象NFTを持つウォレットだけが読めるか |
| イベント先行申込 | 会員証NFTの保有者か |
| オンライン講座 | 受講権NFTを持っているか |
| ファンクラブ | 会員ランクに応じたNFTを持っているか |
このような使い方は、token gating と呼ばれることがあります。
日本語では「トークンを持っている人だけにアクセスを許可する仕組み」と考えると分かりやすいです。
ただし、ここでも注意が必要です。
NFTを持っていることは、サービス利用条件を満たすための一つの条件になり得ます。
しかし、実際にアクセスを許可するかどうかは、サービス側の規約やシステム設計に依存します。
| NFTで確認しやすいこと | NFTだけでは決まらないこと |
|---|---|
| 対象NFTを持っているか | サービスが今も運営されているか |
| どのウォレットが持っているか | 利用規約に違反していないか |
| いつ移転されたか | アカウント停止や返金対応をどうするか |
| どのコントラクトのNFTか | 本人確認が必要かどうか |
つまり、会員権NFTは「入場証」や「鍵」のように使えますが、サービスそのものを保証するわけではありません。
8.6 小さなコードで見る:NFT保有によるアクセス制御
次のコードは、特定のNFTを持っているウォレットだけにアクセスを許可するイメージです。
実際のWebサービスでは、ウォレット署名、セッション管理、API認可、規約確認などが必要です。
ここでは、ownerOf(tokenId) の結果を使ってアクセス可否を判定する流れだけを単純化しています。
# NFT保有によるアクセス制御を理解するための学習用コードです。
# 実際のWebサービスでは、ウォレット署名やセッション管理なども必要です。
# tokenId -> owner の形で、NFTの所有者を管理している想定です。
owners = {
1: "0xAlice",
2: "0xBob",
3: "0xCarol",
}
# アクセス権として認めるNFTのtokenId一覧です。
member_pass_token_ids = {1, 2, 3}
def owns_member_pass(wallet: str) -> bool:
"""指定されたウォレットが会員証NFTを持っているか確認します。"""
for token_id in member_pass_token_ids:
if owners.get(token_id) == wallet:
return True
return False
def access_members_page(wallet: str) -> None:
"""会員限定ページへのアクセス可否を表示します。"""
if owns_member_pass(wallet):
print(f"{wallet}: 会員限定ページへアクセスできます。")
else:
print(f"{wallet}: 会員証NFTを確認できないため、アクセスできません。")
access_members_page("0xAlice")
access_members_page("0xDave")
このコードでは、0xAlice は会員証NFTを持っているためアクセスできます。
一方、0xDave は対象NFTを持っていないためアクセスできません。
この仕組み自体はシンプルですが、実運用では次のような点も必要になります。
- 本当にそのウォレットの持ち主かを署名で確認する
- 会員規約に同意しているか確認する
- NFTが盗難品として報告されていないか確認する
- サービス終了時や規約変更時の扱いを決める
- 複数ウォレットを持つ利用者にどう対応するか決める
NFTはアクセス制御の「条件」として使えますが、ユーザー管理のすべてを置き換えるものではありません。
8.7 ゲームアイテム
ゲームアイテムも、NFTの用途としてよく挙げられます。
ゲームでは、次のようなアイテムを扱うことがあります。
| アイテム | NFTやトークンで表すときの考え方 |
|---|---|
| 伝説の剣 | 1本だけのユニークアイテムとして扱う |
| 通常のポーション | 同じ種類を複数個持つ数量管理に向いている |
| 限定スキン | シリアル番号付きの限定アイテムとして扱える |
| トレーディングカード | カード種類と個体を分けて扱うことがある |
| ゲーム内の土地 | 区画ごとに一意なトークンとして扱える |
ここで、ERC-721とERC-1155の使い分けが関係します。
1つずつ個別に区別したいアイテムであれば、ERC-721の考え方が分かりやすいです。
一方で、ゲームのように「同じ種類のアイテムが大量にある」「複数種類をまとめて移転したい」という場面では、ERC-1155が使いやすくなる場合があります。
ERC-1155の仕様では、1つのコントラクトで代替可能トークン、非代替トークン、半代替的な構成を混在させられると説明されています。
また、ブロックチェーンゲームなどで多数のトークン種類を扱う必要から、複数種類のトークンをまとめて扱える標準として設計されています。
参考: ERC-1155: Multi Token Standard
ただし、ゲームアイテムNFTにも注意点があります。
NFTがウォレットにあるとしても、そのアイテムをゲーム内でどう扱うかはゲーム側の実装や規約に依存します。
ゲームが終了した場合、ゲーム内での機能が失われる可能性もあります。
また、バランス調整や不正対策のために、ゲーム側がアイテム性能や利用条件を変更する場合も考えられます。
つまり、ゲームアイテムNFTでは、次のように分けて考える必要があります。
| 層 | 例 |
|---|---|
| トークン | ウォレットが特定アイテムNFTを持っている |
| メタデータ | アイテム名、画像、レアリティ、説明 |
| ゲーム内機能 | 攻撃力、装備可否、利用条件 |
| 運営ルール | 規約、バランス調整、禁止行為対応 |
NFTがあることで、アイテムの所有者や移転履歴をブロックチェーン上で確認しやすくなります。
一方で、ゲーム体験そのものは、ゲームサーバーや開発元の運用にも強く依存します。
8.8 小さなコードで見る:ゲームアイテムをERC-721型とERC-1155型で考える
次のコードは、ゲームアイテムをERC-721型とERC-1155型で表す違いを、もう一度用途の面から確認するものです。
3章・5章でも似た考え方を扱いましたが、ここでは「どの用途に向いているか」を意識して見ます。
# ゲームアイテムをERC-721型とERC-1155型で考えるための学習用コードです。
# 実際のスマートコントラクト実装ではありません。
# ERC-721型のイメージ:
# tokenIdごとに、個別のアイテムとして管理します。
erc721_items = {
1001: {"name": "伝説の剣", "owner": "0xAlice", "serial": "SWORD-0001"},
1002: {"name": "伝説の剣", "owner": "0xBob", "serial": "SWORD-0002"},
}
# ERC-1155型のイメージ:
# itemIdごとに種類を表し、所有者ごとに数量を管理します。
erc1155_balances = {
"0xAlice": {"potion": 10, "arrow": 50},
"0xBob": {"potion": 3, "arrow": 20},
}
def show_unique_item(token_id: int) -> None:
"""ERC-721型のユニークアイテムを表示します。"""
item = erc721_items[token_id]
print(f"tokenId {token_id}: {item['name']} / serial={item['serial']} / owner={item['owner']}")
def show_stackable_item(owner: str, item_id: str) -> None:
"""ERC-1155型の数量管理アイテムを表示します。"""
amount = erc1155_balances.get(owner, {}).get(item_id, 0)
print(f"{owner} は {item_id} を {amount} 個持っています。")
show_unique_item(1001)
show_unique_item(1002)
show_stackable_item("0xAlice", "potion")
show_stackable_item("0xBob", "arrow")
この例では、伝説の剣 は同じ名前でもシリアル番号が違うため、1本ずつ区別しています。
一方で、potion や arrow は数量として管理しています。
ゲームでは、この両方が必要になることがあります。
そのため、ゲームアイテムではERC-721だけでなく、ERC-1155の考え方が役立つ場面があります。
8.9 証明書・参加証・貢献記録
NFTは、証明書や参加証のような用途にも使われることがあります。
たとえば、次のような場面です。
| 用途 | 例 |
|---|---|
| イベント参加証 | カンファレンスや勉強会に参加した記録 |
| 講座修了証明 | オンライン講座や研修を完了した記録 |
| コミュニティ貢献記録 | 翻訳、レビュー、開発貢献などの記録 |
| メンバー証明 | 特定団体やプロジェクトへの参加証 |
Ethereum公式ドキュメントでも、イベントに参加したことの証明、コースを完了したことの証明、ethereum.orgへの貢献を示すNFT、POAPのような参加記録の例が紹介されています。
参考: ethereum.org: Non-fungible tokens
ただし、証明書や参加証としてNFTを使う場合は、個人情報やプライバシーへの注意が必要です。
たとえば、オンチェーン上のNFTは、第三者から見える場合があります。
もし「誰が、いつ、どのイベントに参加したか」が公開され続けると、本人が望まない形で行動履歴が追跡される可能性があります。
また、証明書として使う場合は、次の点も重要です。
| 確認したいこと | 理由 |
|---|---|
| 発行者は誰か | 信頼できる団体やサービスが発行したか確認するため |
| 対象者は誰か | その証明が誰に対するものか確認するため |
| 証明内容は何か | 参加、修了、資格、貢献などを区別するため |
| 失効できるか | 誤発行、不正取得、資格喪失などに対応するため |
| 公開してよい情報か | 個人情報やプライバシーを守るため |
W3CのVerifiable Credentials Data Modelでは、検証可能な資格情報は、運転免許証や教育証明書のような主張をWeb上で表現する方法として説明され、発行者、保有者、検証者といった役割が整理されています。
ただし、NFTとVerifiable Credentialsは同じものではありません。証明書用途を考える場合は、NFTだけでなく、VCのような資格情報の標準やプライバシー設計も比較対象になります。
参考: W3C Verifiable Credentials Data Model v2.0
💡 豆知識
参加証NFTは、イベントの思い出としては楽しい使い方です。
一方で、参加履歴が公開される設計だと、あとから「この人はこのイベントに参加していた」と追跡される可能性があります。
証明書や参加証では、「証明できること」と「公開しすぎないこと」のバランスが大切です。
8.10 小さなコードで見る:証明書NFTの発行者と失効状態を確認する
次のコードは、証明書NFTを確認するイメージです。
ここでは、次の3つを確認します。
- NFTを持っているか
- 信頼している発行者が発行したものか
- 失効していないか
実際の証明書システムでは、署名検証、発行者の公開鍵、失効リスト、プライバシー保護などが必要になります。
このコードは、NFTを証明書っぽく使うときに、所有者だけでは足りないことを理解するための学習用です。
# 証明書NFTの確認ポイントを理解するための学習用コードです。
# 実際のVerifiable CredentialsやNFT証明書システムの実装ではありません。
from dataclasses import dataclass
@dataclass
class CertificateNFT:
"""証明書NFTの最小モデルです。"""
token_id: int
owner: str
issuer: str
title: str
revoked: bool = False
certificates = {
501: CertificateNFT(
token_id=501,
owner="0xAlice",
issuer="KyutechSecurityCourse",
title="ブロックチェーン基礎講座 修了証",
),
502: CertificateNFT(
token_id=502,
owner="0xBob",
issuer="UnknownIssuer",
title="不明な証明書",
),
}
trusted_issuers = {"KyutechSecurityCourse", "TrustedConference"}
def verify_certificate(token_id: int, wallet: str) -> bool:
"""
証明書NFTが信頼できるか確認します。
確認すること:
- tokenIdが存在するか
- 確認対象のウォレットが所有者か
- 発行者が信頼リストに入っているか
- 証明書が失効していないか
"""
certificate = certificates.get(token_id)
if certificate is None:
print("証明書NFTが見つかりません。")
return False
if certificate.owner != wallet:
print("このウォレットは証明書NFTの所有者ではありません。")
return False
if certificate.issuer not in trusted_issuers:
print("信頼している発行者の証明書ではありません。")
return False
if certificate.revoked:
print("この証明書は失効しています。")
return False
print(f"証明書を確認できました: {certificate.title}")
return True
verify_certificate(token_id=501, wallet="0xAlice")
verify_certificate(token_id=502, wallet="0xBob")
このコードでは、0xAlice の証明書は信頼できる発行者から出ているため確認できます。
一方で、0xBob の証明書は発行者が信頼リストにないため、証明としては受け入れていません。
ここで大切なのは、証明書NFTでは 所有していることだけでなく、誰が発行したのか が重要になることです。
8.11 ドメイン名・名前・オンラインID
NFTは、名前やドメインのようなものを表す用途にも使われます。
Ethereum公式ドキュメントでは、NFTの用途として、オンラインIDの証明や分散型インターネットドメイン名が挙げられています。
また、ethereum.org自体も ethereum.eth というENS名に触れ、通常のDNSで管理される .org のアドレスとは異なり、ENS名はEthereum上で登録・管理されるものとして説明しています。
参考: ethereum.org: Non-fungible tokens
名前やドメインは、一つずつ区別する必要があります。
たとえば、example.eth という名前を複数人が同時に別々の意味で使えると混乱します。
そのため、ある時点で誰がその名前を管理しているのかを記録する仕組みが必要になります。
ただし、名前NFTにも注意点があります。
| 観点 | 注意点 |
|---|---|
| 有効期限 | 名前によっては更新期限や登録期間がある場合がある |
| 紛争対応 | 商標やなりすましに関する問題が起きる可能性がある |
| 名前解決 | 名前からアドレスや情報を引く仕組みが必要 |
| 秘密鍵管理 | 管理ウォレットを失うと名前を操作できなくなる可能性がある |
| フィッシング | 有名サービスに似た名前を使った偽サイトに注意が必要 |
NFTとして名前を持てることは便利ですが、名前が本当に公式のものかどうかは、別途確認が必要です。
8.12 現実資産との接点
NFTは、現実世界の資産や権利と結びつけて語られることもあります。
たとえば、次のような例です。
| 現実世界の対象 | NFTで表したいこと |
|---|---|
| 不動産 | 権利や持分の記録 |
| 会員権 | 特定施設やサービスの利用資格 |
| 商品 | シリアル番号や真正性の証明 |
| チケット | イベントや交通機関の利用権 |
| 証明書 | 資格や修了記録 |
ERC-721の仕様でも、NFTはデジタル資産だけでなく、物理的な資産の所有を表し得るものとして説明され、家やユニークなアートなどの例が挙げられています。
参考: ERC-721: Non-Fungible Token Standard
ただし、現実資産とNFTを結びつける場合は、特に慎重に考える必要があります。
なぜなら、ブロックチェーン上でNFTが移転しても、現実世界の権利が自動的に移転するとは限らないからです。
たとえば、不動産であれば、登記制度や契約、本人確認、規制、税務、紛争解決が関係します。
会員権であれば、サービス提供者の規約や本人確認、利用停止条件が関係します。
商品であれば、現物の保管、配送、偽造品対策、紛失対応が関係します。
つまり、現実資産NFTでは、次のような対応関係を明確にする必要があります。
| 確認したいこと | なぜ必要か |
|---|---|
| NFTが何を表すのか | 所有権、利用権、証明書、引換券などを区別するため |
| 現実の権利とどう結びつくのか | 法律上・契約上の効果を確認するため |
| 管理者は誰か | 現物、台帳、本人確認、紛争対応を担う主体を確認するため |
| NFT移転時に現実の権利も移るのか | トークン移転と権利移転が一致するとは限らないため |
| 失効や凍結はあるのか | 盗難、不正、規約違反、法的手続きに対応するため |
6章で整理した通り、NFTと権利関係は分けて考える必要があります。
現実資産と結びつくほど、スマートコントラクトの外側にある契約・制度・運用が重要になります。
8.13 NFTを使うべき場面・使わなくてもよい場面
ここまで見ると、NFTにはさまざまな用途があることが分かります。
ただし、どんな場面でもNFTを使えばよいわけではありません。
NFTが向いているのは、主に次のような場面です。
| NFTが向いている可能性がある場面 | 理由 |
|---|---|
| 一つずつ区別したい | tokenIdで個体を識別できる |
| 所有者を公開検証したい | ブロックチェーン上で所有者アドレスを確認できる |
| 移転履歴を追跡したい | Transferイベントなどから履歴を追いやすい |
| 複数サービスで使いたい | 標準に沿えばウォレットやアプリから扱いやすい |
| プログラムで条件判定したい | スマートコントラクトやWebアプリで保有確認できる |
一方で、次のような場面では、NFTを使わなくてもよい場合があります。
| NFTが不要かもしれない場面 | 理由 |
|---|---|
| 単なるポイント残高 | 数量だけなら通常のDBや代替可能トークンで十分な場合がある |
| 完全に中央管理でよい会員証 | サービス運営者のDBで十分な場合がある |
| 公開したくない個人情報 | パブリックチェーン上の記録はプライバシー面で注意が必要 |
| 失効や取り消しが頻繁に必要 | オンチェーン記録との整合を慎重に設計する必要がある |
| 現実の権利が複雑に絡む | 契約や法制度の方が中心になる場合がある |
NFTは便利な道具ですが、万能ではありません。
特に、次の問いを先に考えると、NFTを使う意味があるか判断しやすくなります。
1. その対象は、一つずつ区別する必要があるか
2. 所有者や移転履歴を公開検証したいか
3. 複数のアプリやサービスで扱いたいか
4. NFTの外側にある権利・規約・本人確認をどう扱うか
5. 秘密鍵紛失、盗難、返金、失効にどう対応するか
NFTを使うかどうかは、「流行っているから」ではなく、課題に合っているかで考えるのが大切です。
8.14 小さなコードで見る:NFTを使う意味があるかを簡易判定する
最後に、NFTが向いているかを簡単に判定する学習用コードを見てみます。
このコードは、実際の事業判断や設計判断に使うものではありません。
NFTを使うべきか考えるときの観点を整理するためのチェックリストです。
# NFTを使う意味があるかを考えるための学習用チェックリストです。
# 実際の事業判断、法律判断、セキュリティ設計を代替するものではありません。
from dataclasses import dataclass
@dataclass
class NFTFitCheck:
"""NFT利用の向き不向きを考えるための入力項目です。"""
unique_item: bool # 一つずつ区別する必要があるか
public_verification: bool # 所有者や履歴を公開検証したいか
transferable: bool # 利用者間で移転できる必要があるか
cross_app_use: bool # 複数アプリやサービスで扱いたいか
sensitive_personal_data: bool # 個人情報など公開に注意が必要な情報を含むか
def evaluate_nft_fit(check: NFTFitCheck) -> str:
"""
NFTが向いていそうかを、かなり単純化して判定します。
注意:
- これは学習用の目安です。
- 実際には、法務、セキュリティ、運用、UX、コストなどを別途確認します。
"""
score = 0
if check.unique_item:
score += 1
if check.public_verification:
score += 1
if check.transferable:
score += 1
if check.cross_app_use:
score += 1
# 公開に注意が必要な個人情報を含む場合は、慎重に考える必要があります。
if check.sensitive_personal_data:
score -= 2
if score >= 3:
return "NFTが向いている可能性があります。ただし権利・運用・セキュリティ設計は別途必要です。"
if score >= 1:
return "NFTを使う余地はありますが、通常のDBや別方式でもよいか比較が必要です。"
return "NFTを使う必要性は高くないかもしれません。まずは通常の管理方法で足りるか検討します。"
art_collection = NFTFitCheck(
unique_item=True,
public_verification=True,
transferable=True,
cross_app_use=True,
sensitive_personal_data=False,
)
private_medical_record = NFTFitCheck(
unique_item=True,
public_verification=False,
transferable=False,
cross_app_use=False,
sensitive_personal_data=True,
)
print("アートコレクション:")
print(evaluate_nft_fit(art_collection))
print("\n個人の医療記録:")
print(evaluate_nft_fit(private_medical_record))
このコードでは、アートコレクションはNFTが向いている可能性があると判定されます。
一方で、個人の医療記録のように公開に注意が必要な情報は、NFT化に慎重であるべきだと分かります。
もちろん、これはかなり単純化した例です。
実際には、オンチェーンに何を載せるのか、メタデータをどこに置くのか、個人情報をどう扱うのか、失効や削除要求にどう対応するのかなど、より細かな設計が必要です。
8.15 NFTの用途を理解するときの注意点
NFTの用途を整理するときは、次のような誤解に注意が必要です。
| 誤解 | 正確な整理 |
|---|---|
| NFTはデジタルアート専用である | アート以外にも、チケット、会員権、ゲームアイテム、証明書などに使われる |
| NFTにすれば本物だと証明できる | 正規発行者、コントラクトアドレス、規約、権利関係の確認が必要 |
| NFTチケットなら転売問題が完全に解決する | 本人確認、価格制限、払い戻し、規約、会場運用が必要 |
| NFT会員証ならサービス利用が永遠に保証される | サービス運営、規約変更、終了、アクセス条件に依存する |
| ゲームアイテムNFTならゲーム外でも必ず価値が残る | ゲーム内機能はゲーム運営やサーバーに依存する |
| 証明書NFTなら個人情報管理も安全になる | 公開性や追跡可能性に注意し、プライバシー設計が必要 |
| 現実資産NFTなら権利移転が自動で完結する | 法制度、契約、登記、本人確認、管理者の運用が関係する |
NFTの用途を考えるときは、次のように分けると整理しやすいです。
つまり、NFTを使う目的は「画像を売ること」だけではありません。
一つずつ区別したい対象を、トークンとして発行・確認・移転できるようにすることが中心です。
その一方で、NFTが表す意味は、用途ごとに変わります。
デジタルアートなら、作品やライセンスの確認が重要です。
チケットなら、入場状態や本人確認が重要です。
会員権なら、サービス利用条件が重要です。
ゲームアイテムなら、ゲーム内での扱いが重要です。
証明書なら、発行者や失効、プライバシーが重要です。
現実資産なら、契約や法制度との接続が重要です。
NFTを使うほど、周辺の設計も大切になります。
8.16 この章のまとめ
この章では、NFTがどのような場面で使われるのかを整理しました。
NFTは、デジタルアートやコレクションだけでなく、チケット、会員権、ゲームアイテム、証明書、ドメイン名、現実資産との接点など、さまざまな用途で考えられます。
ただし、どの用途でも共通して大切なのは、NFTを「何でも解決する魔法の証明書」として扱わないことです。
この章で押さえたいポイントは、次の通りです。
| ポイント | 説明 |
|---|---|
| NFTは一つずつ区別したい対象と相性がよい | tokenIdによって個体を識別できる |
| アートでは権利関係の確認が重要 | NFT所有と著作権・利用権は別に考える |
| チケットでは使用済み状態や本人確認が重要 | 所有者確認だけでは二重入場や不正利用を防ぎきれない |
| 会員権ではサービス運用が重要 | NFT保有はアクセス条件になり得るが、サービス継続を保証しない |
| ゲームアイテムでは運営ルールが重要 | NFTがあってもゲーム内機能はゲーム側に依存する |
| 証明書では発行者と失効、プライバシーが重要 | 何を公開し、何を隠すかを慎重に設計する必要がある |
| 現実資産では契約・法制度・管理者が重要 | NFT移転だけで現実の権利移転が完結するとは限らない |
NFTの用途を考えるときは、次の3つの問いに戻ると整理しやすいです。
1. その対象は、一つずつ区別する必要があるか
2. 所有者や移転履歴を公開検証したいか
3. NFTの外側にある権利・規約・運用をどう扱うか
NFTの本質は、「画像を売ること」ではなく、一意なトークンを使って、特定の対象を識別し、所有者や移転を確認しやすくすること です。
次の章では、このような用途を踏まえて、情報セキュリティの観点からNFTを見るときの注意点を整理します。
フィッシング、偽ミントサイト、承認操作、秘密鍵管理、正規コントラクト確認など、実際にNFTを扱うときに気をつけたいポイントを見ていきます。
9. 情報セキュリティの観点で見る注意点
この章では、NFTを扱うときに気をつけたいセキュリティ上の注意点を整理します。
ポイントは、NFTそのものだけを見るのではなく、ウォレット、秘密鍵、承認操作、ミントサイト、メタデータ、発行者、マーケットプレイス まで含めて見ることです。
前の章では、NFTがどのような場面で使われるのかを見ました。
デジタルアート、チケット、会員権、ゲームアイテム、証明書、ドメイン名、現実資産との接点など、NFTはさまざまな用途で考えられます。
ただし、用途が広いということは、それだけ確認すべきポイントも増えるということです。
NFTでは、次のようなリスクが出てきます。
- 偽のミントサイトにウォレットを接続してしまう
- 公式プロジェクトになりすましたSNS投稿を信じてしまう
-
setApprovalForAllで、NFT全体を操作できる権限を悪意ある相手に渡してしまう - ウォレットのシードフレーズや秘密鍵を入力してしまう
- 正規コントラクトではない偽コレクションを購入してしまう
- メタデータや画像の保存場所が後から変わる、または見られなくなる
- NFTを持っていることと、著作権や利用権を持っていることを混同してしまう
米国財務省は、2024年のNFT不正金融リスク評価において、NFTやNFTプラットフォームの脆弱性がマネーロンダリングなどに悪用され得ること、NFTは詐欺やスキャムに非常に悪用されやすく、盗難の対象にもなることを指摘しています。
参考: U.S. Treasury: Treasury Releases First Ever Non-fungible Token Illicit Finance Risk Assessment
また、FBI/IC3は、犯罪者が正規のNFT開発者になりすまし、偽のNFTリリースや偽ミントサイトへ誘導する手口を注意喚起しています。
偽サイトでウォレットを接続すると、drainer smart contract によって暗号資産やNFTが犯罪者のウォレットへ移転される可能性があると説明されています。
この章では、NFTを扱うときに特に注意したいポイントを、できるだけ具体例と小さなコードで確認していきます。
9.1 NFTのリスクは「NFT本体」だけにあるわけではない
NFTというと、スマートコントラクトやブロックチェーンの安全性だけに目が向きがちです。
しかし、実際にNFTを扱うときは、もっと広い範囲を見る必要があります。
| 層 | 例 | 注意点 |
|---|---|---|
| ウォレット | MetaMaskなど | シードフレーズ、秘密鍵、署名内容、接続先 |
| Webサイト | ミントサイト、マーケットプレイス | 偽URL、なりすまし、フィッシング |
| スマートコントラクト | ERC-721、ERC-1155など | 承認、移転、権限、実装バグ |
| メタデータ |
tokenURI の先のJSON |
変更可能性、保存場所、リンク切れ |
| コンテンツ | 画像、動画、音声など | 実体の保存先、IPFS pinning、HTTPサーバー停止 |
| 権利・規約 | 著作権、利用権、会員権など | NFT所有と権利の混同 |
| 運用 | プロジェクト運営、発行者、マーケットプレイス | 偽コレクション、無断ミント、サービス終了 |
つまり、NFTの安全性は、スマートコントラクトだけで完結しません。
たとえば、ERC-721の仕様通りに実装されたNFTであっても、偽サイトで悪意ある承認をしてしまえば、NFTを失う可能性があります。
また、正しくNFTを受け取っていても、メタデータの保存先が消えたり、権利関係を誤解していたりすると、期待していた使い方ができない可能性があります。
まずは、NFTを扱うときのリスクを、次のように広く見ることが大切です。
この章では、上の図のそれぞれを順番に見ていきます。
9.2 シードフレーズと秘密鍵は共有しない
NFTを扱うときに、最も基本的で重要なのがウォレットの保護です。
ウォレットには、暗号資産やNFTを操作するための鍵が関係します。
この鍵を失ったり、他人に渡したりすると、自分のNFTや暗号資産を移転されてしまう可能性があります。
Ethereum公式のセキュリティ解説では、リカバリーフレーズや秘密鍵を、どのような理由であっても共有してはいけないと説明しています。
また、リカバリーフレーズはウォレットのマスターキーであり、それを持つ人はアカウント内の資産へアクセスできると説明されています。
参考: Ethereum security and scam prevention
特に注意したいのは、次のような場面です。
| 危険な場面 | なぜ危ないか |
|---|---|
| 「サポート担当」を名乗る人にシードフレーズを聞かれる | 正規サポートが秘密鍵やシードフレーズを聞く必要はない |
| 偽サイトにシードフレーズ入力欄がある | 入力した瞬間にウォレットを乗っ取られる可能性がある |
| 画面共有や遠隔操作を求められる | ウォレット操作や秘密情報を見られる可能性がある |
| スクリーンショットで保存する | クラウド同期や端末侵害で漏えいする可能性がある |
ここは、何度強調してもしすぎではありません。
シードフレーズ・秘密鍵を入力するよう求められたら、
その時点でかなり危険だと考える。
NFTを受け取るため、ミントするため、サポートを受けるため、ウォレットを復旧するため、エアドロップを受け取るため。
どのような理由であっても、他人やWebサイトにシードフレーズを渡してはいけません。
💡 豆知識
ウォレット接続と、シードフレーズ入力はまったく別です。
正常なDAppでは、ウォレット拡張機能やウォレットアプリを通じて接続・署名します。
Webサイトの入力フォームにシードフレーズを直接打ち込ませるものは、基本的に非常に危険だと考えてください。
9.3 偽サイト・なりすまし・drainerに注意する
NFTでは、ミントサイトやマーケットプレイスにウォレットを接続する場面があります。
ここで問題になるのが、偽サイトです。
FBI/IC3の注意喚起では、犯罪者がNFT開発者のSNSアカウントへアクセスしたり、正規アカウントにそっくりなアカウントを作ったりして、偽のNFTリリースを宣伝する手口が説明されています。
このような投稿では、「限定」「サプライズ」「未発表ミント」といった言葉で焦らせ、フィッシングリンクへ誘導することがあります。
Ethereum公式の詐欺対策ページでも、フィッシングサイトは本物のウォレット、取引所、DAppのように見せかけ、シードフレーズの入力やウォレット接続を通じて資産を奪うことがあると説明されています。
参考: Ethereum.org: Scam help & reporting
偽サイトで特に注意したいのは、次のような点です。
| 確認すること | 注意点 |
|---|---|
| URLの綴り |
example.com と examp1e.com のような紛らわしい文字に注意 |
| ドメイン |
official.com.evil.example のように、本物の文字列を含むだけの偽ドメインに注意 |
| SNSアカウント | 作成日、過去投稿、フォロワー、認証、公式サイトからのリンクを確認 |
| 「今だけ」「急げ」 | 焦らせる言葉は詐欺でよく使われる |
| ウォレット接続要求 | 接続前に、何を署名・承認するのか確認 |
| シードフレーズ入力要求 | 入力を求められたら基本的に危険 |
9.4 小さなコードで見る:URLの怪しさを簡易チェックする
ここで、偽URLの見分け方を、かなり単純化したコードで見てみます。
次のコードは、実際のフィッシング検知ツールではありません。
あくまで、ドメインの綴りやHTTPS、サブドメインの見方に注意する ための学習用コードです。
# NFTのミントサイトらしきURLを簡易チェックする学習用コードです。
# 実際の安全性を保証するものではありません。
# 最終的には、公式サイト・公式SNS・複数の一次情報で確認する必要があります。
from urllib.parse import urlparse
from difflib import get_close_matches
TRUSTED_DOMAINS = {
"example-nft-project.com",
"opensea.io",
}
def hostname_matches_trusted(hostname: str, trusted_domain: str) -> bool:
"""
hostname が trusted_domain そのもの、またはその正規のサブドメインかを確認します。
OK:
- example-nft-project.com
- mint.example-nft-project.com
NG:
- example-nft-project.com.evil.example
"""
return hostname == trusted_domain or hostname.endswith("." + trusted_domain)
def inspect_url(url: str) -> list[str]:
"""
URLを学習用に簡易チェックします。
注意:
- この関数はフィッシング検知を完全に行うものではありません。
- 見た目だけで安全性を断定しないでください。
"""
warnings = []
parsed = urlparse(url)
hostname = (parsed.hostname or "").lower().rstrip(".")
if parsed.scheme != "https":
warnings.append("HTTPSではありません。")
if not hostname:
warnings.append("ホスト名を読み取れません。")
return warnings
if not any(hostname_matches_trusted(hostname, domain) for domain in TRUSTED_DOMAINS):
warnings.append("信頼済みドメイン一覧に一致しません。")
# 公式ドメインに似ている場合、タイプミスや偽ドメインの可能性を補足します。
close = get_close_matches(hostname, TRUSTED_DOMAINS, n=1, cutoff=0.75)
if close:
warnings.append(f"公式ドメイン {close[0]} に似ています。綴りを確認してください。")
if "@" in parsed.netloc:
warnings.append("URL内に@が含まれています。見かけ上のドメインに注意してください。")
if not parsed.path or parsed.path == "/":
warnings.append("パスが短すぎます。公式の案内と一致するか確認してください。")
return warnings or ["大きな警告はありません。ただし公式確認は別途必要です。"]
urls = [
"https://example-nft-project.com/mint",
"https://mint.example-nft-project.com/mint",
"https://examp1e-nft-project.com/mint",
"http://example-nft-project.com/mint",
"https://example-nft-project.com.evil.example/mint",
]
for url in urls:
print(url)
for warning in inspect_url(url):
print(" -", warning)
print()
このコードでは、次のような観点を確認しています。
-
httpsかどうか - ホスト名が信頼済みドメインと一致するか
- 本物に似た偽ドメインではないか
-
example.com.evil.exampleのような紛らわしいドメインではないか -
@を使った紛らわしいURLではないか
もちろん、これだけで安全性を判断することはできません。
しかし、NFTのミントサイトやマーケットプレイスへアクセスする前に、URLを丁寧に見る習慣はとても重要です。
9.5 承認操作に注意する:approveとsetApprovalForAll
NFTで特に注意したいのが、承認操作です。
ERC-721では、特定のNFTについて第三者に操作を許可する approve と、所有者が持つNFT全体について第三者をoperatorとして承認する setApprovalForAll が定義されています。
参考: EIP-721: Non-Fungible Token Standard
3章でも見たように、これらはマーケットプレイスやオークション、ウォレット連携を実現するうえで便利な仕組みです。
ただし、悪意ある相手に承認してしまうと、自分のNFTを移転される可能性があります。
特に setApprovalForAll は強い承認です。
| 操作 | ざっくりした意味 | 注意点 |
|---|---|---|
approve |
特定のtokenIdだけ操作を許可する | 対象NFTを確認する |
setApprovalForAll |
そのコレクション内のNFT全体を操作できるoperatorを設定する | 許可先が本当に信頼できるか慎重に確認する |
transferFrom |
NFTを移転する | 送信者、受信者、tokenIdを確認する |
safeTransferFrom |
受け取り確認を含む移転 | 送信先が受け取れるか確認する仕組みがある |
EIP-721では、所有者、承認済みアドレス、または現在の所有者のauthorized operatorが転送を開始できると説明されています。
つまり、承認は単なる表示上の許可ではなく、NFTを移転する権限につながる重要な操作です。
参考: EIP-721: Transfer Mechanism
9.6 小さなコードで見る:個別承認と包括承認のリスク差
次のコードでは、approve と setApprovalForAll の違いを、かなり単純化して確認します。
実際のERC-721コントラクトではありませんが、個別NFTだけを許可する場合 と 所有者のNFT全体を許可する場合 の違いをイメージできます。
# ERC-721の承認をかなり単純化した学習用コードです。
# 実際のスマートコントラクトではありません。
from dataclasses import dataclass, field
@dataclass
class SimpleERC721ApprovalModel:
"""
ERC-721の承認状態を学習用に表すモデルです。
owners:
tokenIdごとの所有者を表します。
token_approvals:
特定のtokenIdだけを操作できるアドレスを表します。
operator_approvals:
ownerが持つNFT全体を操作できるoperatorを表します。
"""
owners: dict[int, str] = field(default_factory=dict)
token_approvals: dict[int, str] = field(default_factory=dict)
operator_approvals: dict[tuple[str, str], bool] = field(default_factory=dict)
def owner_of(self, token_id: int) -> str:
"""tokenIdの現在の所有者を返します。"""
return self.owners[token_id]
def approve(self, owner: str, operator: str, token_id: int) -> None:
"""特定のtokenIdだけを操作できる相手を設定します。"""
if self.owner_of(token_id) != owner:
raise ValueError("所有者だけがapproveできます。")
self.token_approvals[token_id] = operator
def set_approval_for_all(self, owner: str, operator: str, approved: bool) -> None:
"""ownerが持つNFT全体を操作できる相手を設定します。"""
self.operator_approvals[(owner, operator)] = approved
def can_transfer(self, caller: str, token_id: int) -> bool:
"""callerがtokenIdを移転できるか確認します。"""
owner = self.owner_of(token_id)
return (
caller == owner
or self.token_approvals.get(token_id) == caller
or self.operator_approvals.get((owner, caller), False)
)
model = SimpleERC721ApprovalModel(
owners={
101: "Alice",
102: "Alice",
201: "Bob",
}
)
# MarketAには、AliceのtokenId 101だけを操作できる権限を与えます。
model.approve(owner="Alice", operator="MarketA", token_id=101)
# MarketBには、Aliceが持つこのコレクション内のNFT全体を操作できる権限を与えます。
model.set_approval_for_all(owner="Alice", operator="MarketB", approved=True)
for operator in ["MarketA", "MarketB"]:
print(f"{operator} が Alice のNFTを移転できるか")
for token_id in [101, 102]:
print(f" tokenId {token_id}: {model.can_transfer(operator, token_id)}")
print()
このコードでは、MarketA は tokenId 101 だけを移転できます。
一方で、MarketB は setApprovalForAll によって、Aliceが持つこのコレクション内のNFTをまとめて操作できる状態になります。
この違いはとても重要です。
もちろん、正規のマーケットプレイスやDAppでは、包括承認が必要になる場面もあります。
ただし、よく分からないサイトに setApprovalForAll を許可するのは危険です。
ウォレットが表示する内容に、次のようなニュアンスがあれば慎重に確認します。
このコレクション内のすべてのNFTを操作できる権限を与える
これは、単に「サイトへログインする」操作とは違います。
NFTを移転できる権限に関わる操作です。
9.7 署名・トランザクションの内容を読む
NFTを扱うときは、ウォレットに出てくる確認画面を読み飛ばさないことが大切です。
Ethereum公式のセキュリティ解説でも、トランザクションを送る前に内容を二重に確認すること、スマートコントラクトとやり取りする場合は署名前にトランザクションメッセージを読むことが推奨されています。
参考: Ethereum security and scam prevention
ウォレットの画面には、次のような操作が表示されることがあります。
| 表示される操作 | 意味の例 | 注意点 |
|---|---|---|
| Connect | サイトにウォレットアドレスを見せる | 接続先URLを確認 |
| Sign | メッセージへ署名する | 何に同意しているか確認 |
| Approve | 特定NFTやトークン操作を許可する | 対象と許可先を確認 |
| Set approval for all | コレクション全体の操作を許可する | 特に慎重に確認 |
| Transfer | NFTやETHを移転する | 送信先とtokenIdを確認 |
| Mint | NFTを発行・受け取る | 価格、発行元、コントラクトを確認 |
ここで注意したいのは、表示が分かりにくい場合に「まあ大丈夫だろう」で進めないことです。
特に、次のような場合は一度止まって確認します。
- 何の操作か分からない
- 見覚えのないコントラクトへ承認している
- NFT全体の操作権限を求めている
- 送信先アドレスが知らないものになっている
- 価格や手数料が想定と違う
- 公式サイトから開いたページか自信がない
9.8 小さなコードで見る:ウォレット操作を人間向けに要約する
次のコードでは、ウォレットで出てくる操作を、人間が読みやすい文章に変換するイメージを示します。
実際のウォレットやトランザクション解析ではありません。
目的は、署名や承認の前に「何を許可しているのか」を言葉にして確認する ことです。
# ウォレット操作を人間向けに要約する学習用コードです。
# 実際のウォレット表示やトランザクション解析を再現するものではありません。
from dataclasses import dataclass
@dataclass
class WalletAction:
"""
ウォレットで表示される操作内容を、記事用に単純化したデータ構造です。
"""
action: str
contract: str
token_id: int | None = None
operator: str | None = None
to: str | None = None
all_tokens: bool = False
eth_value: float = 0.0
def summarize_wallet_action(action: WalletAction) -> str:
"""
ウォレット操作を、人間が確認しやすい文章に変換します。
注意:
- 実際のウォレット表示やトランザクション解析を再現するものではありません。
- 署名前には、ウォレットが表示する内容と公式情報を必ず確認します。
"""
if action.action == "approve" and action.token_id is not None:
return f"{action.operator} に tokenId {action.token_id} の操作を許可します。"
if action.action == "setApprovalForAll" and action.all_tokens:
return f"{action.operator} に、このコレクション内のNFT全体を操作する権限を与えます。強い承認です。"
if action.action == "transferFrom" and action.token_id is not None:
return f"tokenId {action.token_id} を {action.to} へ移転します。移転先を確認してください。"
if action.action == "mint":
return f"{action.contract} でミントします。支払い額は {action.eth_value} ETH です。公式サイトか確認してください。"
return "内容を読み取れません。署名・送信前に詳しく確認してください。"
actions = [
WalletAction(action="approve", contract="NFTContract", token_id=101, operator="MarketA"),
WalletAction(action="setApprovalForAll", contract="NFTContract", operator="UnknownSite", all_tokens=True),
WalletAction(action="transferFrom", contract="NFTContract", token_id=101, to="0xReceiver"),
WalletAction(action="mint", contract="NFTContract", eth_value=0.05),
]
for action in actions:
print(summarize_wallet_action(action))
このコードで大切なのは、操作を日本語に言い換えることです。
たとえば、setApprovalForAll を見たときに、ただの英単語として流すのではなく、次のように読む必要があります。
この相手に、このコレクション内のNFT全体を操作する権限を渡す
ここまで言い換えると、クリック前に慎重になりやすくなります。
9.9 メタデータと保存場所のリスク
4章で見たように、NFT本体とメタデータ、画像などのコンテンツは別物です。
ERC-721では、tokenURI(tokenId) によって特定NFTのURIを返すメタデータ拡張が定義されています。
ただし、そのURIの先にあるJSONや画像がどこに保存されるか、後から変更できるかは、コントラクトやプロジェクトの設計に依存します。
参考: EIP-721: Metadata extension
保存場所には、たとえば次のようなパターンがあります。
| 保存方式 | 注意点 |
|---|---|
| HTTP URL | サーバー停止、URL変更、運営終了の影響を受ける |
| IPFS | CIDで内容を参照できるが、pinningなどの運用が必要 |
| オンチェーン | チェーン上に保存されるが、容量やガスコストが課題になりやすい |
| 変更可能なメタデータ | 表示内容が後から変わる可能性がある |
IPFS公式ドキュメントでは、CIDはコンテンツそのものにもとづく識別子であり、場所ではなく内容に基づいて参照する考え方だと説明されています。
また、IPFS上でデータを保持し続けるには、pinningなどの方法でデータを保持するノードが必要になります。
参考: IPFS Docs: Content addressing and CIDs
参考: IPFS Docs: Persistence, permanence, and pinning
ここで注意したいのは、「NFTがあるから画像も永久に見られる」とは限らないことです。
NFTを購入・発行・保管する場合、次の点を確認するとよいです。
-
tokenURIはどこを指しているか - メタデータはHTTP URLか、IPFSか、オンチェーンか
- 画像や動画のURIはどこを指しているか
- メタデータは後から変更可能か
- IPFSの場合、pinningや保存運用はどうなっているか
- プロジェクト終了時に参照先がどうなるか
9.10 小さなコードで見る:メタデータの変化を検知する
次のコードでは、メタデータJSONが変わったかどうかを簡易的に確認します。
実際のNFT監視ツールではありません。
目的は、メタデータの内容が変わると、表示される説明や画像も変わる可能性がある ことを理解することです。
# メタデータJSONの変化を検知する学習用コードです。
# 実際のNFT監視ツールではありません。
import json
import hashlib
def canonical_hash(data: dict) -> str:
"""
JSONをキー順に並べて文字列化し、SHA-256ハッシュを計算します。
ここでは、メタデータの内容が変わったかを比較するために使います。
"""
canonical = json.dumps(data, sort_keys=True, ensure_ascii=False)
return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
def compare_metadata(before: dict, after: dict) -> list[str]:
"""
変更前後のメタデータを比較し、変わった項目を返します。
"""
changed = []
for key in sorted(set(before) | set(after)):
if before.get(key) != after.get(key):
changed.append(key)
return changed
metadata_v1 = {
"name": "Sample Ticket #101",
"description": "2026 event ticket",
"image": "ipfs://bafy...old",
}
metadata_v2 = {
"name": "Sample Ticket #101",
"description": "2026 event ticket - updated",
"image": "ipfs://bafy...new",
}
print("変更前ハッシュ:", canonical_hash(metadata_v1))
print("変更後ハッシュ:", canonical_hash(metadata_v2))
print("変更された項目:", compare_metadata(metadata_v1, metadata_v2))
このコードでは、description と image が変わったことを検出できます。
もちろん、実際には tokenURI 自体が変わる場合、URIの先のJSONが変わる場合、画像URIの中身が変わる場合など、いくつかのパターンがあります。
そのため、NFTの長期的な信頼性を見るときは、「トークンがあるか」だけでなく、「参照先がどう管理されているか」も見る必要があります。
9.11 偽コレクション・無断ミント・権利関係の確認
NFTでは、見た目が同じ画像を使った偽コレクションや、権利者ではない人が作品をNFTとして発行する無断ミントが問題になることがあります。
これは、6章で扱った「NFT所有と権利は別」という話とつながります。
NFTのコントラクトが存在し、tokenIdがあり、所有者アドレスが記録されているとしても、それだけでは次のことまでは分かりません。
- 発行者が本当に権利者なのか
- 画像やキャラクターの利用許可を得ているのか
- 公式プロジェクトの正規コントラクトなのか
- そのNFTにどのような利用権が付いているのか
- マーケットプレイスの表示が正確なのか
米国著作権局とUSPTOのNFT共同研究では、NFTの購入者や販売者がどの知的財産権に関わるのかを理解しにくいという懸念が整理されています。
また、NFTの移転が関連する著作権やIP権利の移転を必ず伴うわけではない点も重要です。
参考: U.S. Copyright Office / USPTO: Non-Fungible Tokens and Intellectual Property
偽コレクションや無断ミントを避けるためには、次のような確認が必要です。
| 確認項目 | 見るポイント |
|---|---|
| 公式サイト | 公式サイトからコントラクトやマーケットプレイスへ移動する |
| 公式SNS | アカウント名、過去投稿、リンク、認証、作成日を確認する |
| コントラクトアドレス | 公式が示しているアドレスと一致するか確認する |
| 発行者情報 | プロジェクト運営者、クリエイター、権利者を確認する |
| 利用規約 | 商用利用、二次創作、会員特典、失効条件などを確認する |
| マーケットプレイス表示 | 公式認証や注意表示だけに頼りすぎない |
ここで大切なのは、NFTの見た目だけで判断しないことです。
画像が同じでも、正規コントラクトではない可能性があります。
名前が似ていても、公式コレクションではない可能性があります。
価格が安くても、それは偽物や権利関係に問題のあるものかもしれません。
9.12 取引は取り消せない前提で確認する
ブロックチェーン上の取引は、基本的に一度確定すると簡単には取り消せません。
Ethereum公式の詐欺対策ページでも、Ethereumは分散型ネットワークであり、中央機関が取引を取り消したり盗まれた資金を回復したりすることはできないと説明されています。
取引がブロックチェーン上で確認されるとfinalである、と説明されています。
参考: Ethereum.org: Scam help & reporting
そのため、NFTを扱うときは、次のように「送る前」「署名する前」「承認する前」の確認がとても重要です。
この図のように、少しでも分からない場合は止まることが大切です。
特にNFTでは、焦らせる表現が使われることがあります。
- 残りわずか
- 今だけ
- サプライズミント
- 先着順
- 無料エアドロップ
- 公式からの特別案内
もちろん、正規のキャンペーンでも似た言葉が使われることはあります。
しかし、攻撃者もこの「焦り」を利用します。
急いでいるときほど、URL、コントラクト、署名内容、承認範囲を確認する必要があります。
9.13 小さなコードで見る:確認項目を簡易スコア化する
最後に、NFTを扱う前の確認項目を、簡単なスコアとして見るコードを用意します。
これは実際の安全性評価ツールではありません。
目的は、危険な兆候が複数重なるほど、立ち止まって確認すべき という感覚をつかむことです。
# NFTを扱う前の確認項目を、簡易的に点数化する学習用コードです。
# 実際の安全性を保証するものではありません。
from dataclasses import dataclass
@dataclass
class NFTSecurityChecklist:
"""
NFTを扱う前に確認したい項目を表します。
点数が高いほど注意が必要、という扱いにします。
"""
official_url_verified: bool
contract_address_verified: bool
asks_seed_phrase: bool
surprise_mint: bool
unlimited_approval: bool
issuer_rights_checked: bool
metadata_storage_checked: bool
def evaluate_security_risk(check: NFTSecurityChecklist) -> tuple[int, str]:
"""
確認項目から簡易リスクスコアを計算します。
注意:
- 実際の安全性を保証するものではありません。
- 最終判断には、公式情報、コントラクト、規約、ウォレット表示の確認が必要です。
"""
score = 0
if not check.official_url_verified:
score += 2
if not check.contract_address_verified:
score += 2
if check.asks_seed_phrase:
score += 5
if check.surprise_mint:
score += 2
if check.unlimited_approval:
score += 2
if not check.issuer_rights_checked:
score += 1
if not check.metadata_storage_checked:
score += 1
if score >= 7:
return score, "高リスクです。接続・署名・送金を止めて再確認してください。"
if score >= 3:
return score, "注意が必要です。公式URL、コントラクト、承認内容を確認してください。"
return score, "大きな警告は少なめですが、最終確認は必要です。"
checks = [
NFTSecurityChecklist(
official_url_verified=True,
contract_address_verified=True,
asks_seed_phrase=False,
surprise_mint=False,
unlimited_approval=False,
issuer_rights_checked=True,
metadata_storage_checked=True,
),
NFTSecurityChecklist(
official_url_verified=False,
contract_address_verified=False,
asks_seed_phrase=True,
surprise_mint=True,
unlimited_approval=True,
issuer_rights_checked=False,
metadata_storage_checked=False,
),
]
for check in checks:
score, message = evaluate_security_risk(check)
print(f"score={score}: {message}")
このコードでは、シードフレーズを求められる、公式URLを確認していない、コントラクトアドレスを確認していない、包括承認を求められている、という条件が重なるほどスコアが高くなります。
もちろん、これは学習用の単純なモデルです。
実際には、ウォレット表示、コントラクトの実装、マーケットプレイス、公式情報、規約、過去の活動、コミュニティの状況など、より多くの情報を確認する必要があります。
ただし、考え方としては有効です。
危険な兆候が1つでもあれば注意する。
危険な兆候が複数重なるなら、署名・送金・承認を止める。
9.14 NFTを扱う前のチェックリスト
NFTを扱う前に、最低限次のような点を確認するとよいです。
| 確認項目 | 確認する理由 |
|---|---|
| 公式URLか | 偽ミントサイトやフィッシングサイトを避けるため |
| 公式SNSからリンクされているか | 偽アカウントやクローンサイトを避けるため |
| コントラクトアドレスは正しいか | 偽コレクションを避けるため |
| ウォレットが表示する操作内容を読んだか | 不要な承認や移転を避けるため |
setApprovalForAll を求められていないか |
NFT全体の操作権限を渡す可能性があるため |
| シードフレーズ入力を求められていないか | 求められた時点で非常に危険なため |
| メタデータ保存場所を確認したか | 画像や説明が見られなくなる可能性を考えるため |
| 発行者や権利関係を確認したか | 無断ミントや権利誤認を避けるため |
| 価格や手数料が想定通りか | 予期しない支払いを避けるため |
| 急かされていないか | 焦りを利用した詐欺を避けるため |
このチェックリストは、NFTを購入・ミントする人だけでなく、NFTを発行する側にも関係します。
発行者側も、次の点を考える必要があります。
- ユーザーに不要な包括承認を求めていないか
- ミントサイトのURLが分かりやすく、公式導線が明確か
- コントラクトアドレスを公式に明示しているか
- メタデータや画像の保存方針を説明しているか
- 権利・利用範囲を明確にしているか
- フィッシング対策や偽アカウント対策を行っているか
NFTは、利用者だけが気をつければよいものではありません。
発行者やマーケットプレイス側の設計・運用も、利用者の安全性に大きく関わります。
9.15 NFTのセキュリティを理解するときの注意点
ここまで、NFTを扱うときの注意点を整理してきました。
最後に、誤解しやすい点をまとめます。
| 誤解 | 正確な整理 |
|---|---|
| ブロックチェーン上にあるなら全部安全 | ウォレット、承認、偽サイト、秘密鍵管理など別のリスクがある |
| 公式っぽいSNS投稿なら安全 | アカウント乗っ取りやクローンアカウントの可能性がある |
| ウォレット接続だけなら絶対安全 | 接続後に署名・承認を求められる場合がある |
approve は軽い操作 |
NFTを移転できる権限につながるため慎重に扱う |
setApprovalForAll は普通に押してよい |
コレクション全体の操作権限を与える強い承認 |
| IPFSなら必ず永久に見られる | CIDは内容に基づく識別子だが、データ保持にはpinningなどが必要 |
| NFTなら本物だと自動で分かる | 正規発行者、コントラクト、権利、規約の確認が必要 |
| 盗まれても運営に頼めば必ず戻せる | 分散型ネットワークでは、確定済み取引を中央機関が簡単に取り消せるわけではない |
NFTのセキュリティを考えるときは、次のように分けると整理しやすくなります。
1. 自分の鍵を守る
2. 接続先を確認する
3. 署名・承認内容を読む
4. コントラクトと発行者を確認する
5. メタデータと保存場所を確認する
6. 権利・規約を確認する
7. 分からないときは署名しない
この中でも、特に重要なのは最初の3つです。
秘密鍵やシードフレーズを守る。
偽サイトへ接続しない。
署名・承認内容を読まずに進めない。
ここを守るだけでも、多くの事故を避けやすくなります。
9.16 この章のまとめ
この章では、NFTを情報セキュリティの観点から見るときの注意点を整理しました。
NFTは、ブロックチェーン上で一意に識別・移転できるトークンです。
しかし、実際にNFTを扱うときのリスクは、トークン本体だけではありません。
ウォレット、秘密鍵、承認操作、ミントサイト、SNS、メタデータ、保存場所、発行者、権利関係、マーケットプレイスの運用など、複数の層を見ておく必要があります。
この章で押さえたいポイントは、次の通りです。
| ポイント | 説明 |
|---|---|
| 秘密鍵・シードフレーズは共有しない | それを持つ人はウォレット内の資産へアクセスできる可能性がある |
| 偽サイトやなりすましに注意する | 公式URL、公式SNS、コントラクトアドレスを確認する |
approve と setApprovalForAll は慎重に扱う |
NFT移転権限につながる操作であり、特に包括承認は強い |
| 署名・トランザクション内容を読む | 何を許可し、どこへ何を送るのか確認する |
| メタデータと保存場所も確認する | NFT本体と画像・説明・保存先は別に考える |
| 発行者と権利関係を確認する | 偽コレクション、無断ミント、権利誤認を避ける |
| 取引は取り消せない前提で慎重に進める | 署名・送信後に簡単に戻せるとは限らない |
NFTを安全に扱うためには、技術仕様だけでなく、利用者の行動やプロジェクトの運用も重要です。
次の章では、ここまでの内容を踏まえて、NFTについてよくある誤解を整理します。
「NFTを買えば著作権も手に入る」「NFTなら画像をコピーできなくなる」「IPFSなら永久保存される」「ロイヤリティは必ず自動で支払われる」といった誤解を、一つずつ分けて確認していきます。
10. よくある誤解
この章では、ここまでの内容を踏まえて、NFTについてよくある誤解を整理します。
ポイントは、NFT、メタデータ、コンテンツ、権利、ロイヤリティ、セキュリティを一つにまとめて考えないこと です。
ここまで、NFTをいろいろな角度から見てきました。
1章では、限定チケットや会員証のような「一つずつ区別できるもの」から入りました。
2章では、NFTを コントラクトアドレス + tokenId で識別する考え方を整理しました。
3章では、ERC-721の ownerOf、transferFrom、safeTransferFrom、approve、setApprovalForAll を見ました。
4章では、tokenURI の先にあるメタデータやコンテンツ保存場所を分けました。
5章では、ERC-721とERC-1155の違いを見ました。
6章では、NFT所有と著作権・利用権は別に考える必要があることを整理しました。
7章では、ロイヤリティ情報を返す仕組みと、実際の支払いは別であることを見ました。
8章では、NFTの用途を広く整理しました。
9章では、ウォレット、承認、偽サイト、メタデータ、権利確認などの注意点を見ました。
この章では、それらをもう一度「誤解しやすい言い方」と「より丁寧な見方」に分けて確認します。
NFTは話題になりやすい分、短い言葉で説明されることが多いです。
「NFTは画像を所有する仕組み」
「NFTならコピーできない」
「NFTを買えば著作権も手に入る」
「IPFSなら永久保存される」
「ロイヤリティは必ず自動で支払われる」
このような説明は、入口としては分かりやすい場合もあります。
しかし、技術的・権利的には省略が多く、誤解につながることがあります。
まずは全体像を表にすると、次のようになります。
| よくある誤解 | より丁寧な見方 |
|---|---|
| NFTは画像そのもの | NFTはブロックチェーン上のトークンであり、画像はメタデータから参照されることが多い |
| NFTを買うと著作権も手に入る | NFT所有と著作権・利用権は別であり、規約や契約を確認する必要がある |
| NFTなら画像をコピーできない | 画像ファイルのコピー自体を止める仕組みではない |
| IPFSなら永久保存される | IPFSは内容にもとづく参照をしやすくするが、長期可用性にはpinningなどの運用が必要 |
| ロイヤリティは必ず自動で支払われる | ERC-2981はロイヤリティ情報を返す標準であり、支払い実行はマーケットプレイス等に依存する |
| NFTなら本物だと分かる | 正規コントラクト、発行者、権利者、規約、保存場所などの確認が必要 |
| ウォレットにNFTが届けば安全 | 偽NFT、スパムNFT、フィッシング誘導、承認悪用などに注意が必要 |
この表だけでも、NFTを理解するときに大切なことが見えてきます。
NFTは便利な概念ですが、トークンだけですべてを保証する仕組みではありません。
何がオンチェーンにあり、何がオフチェーンにあり、何が規約や契約で決まるのかを分けて見る必要があります。
10.1 誤解1:NFTは画像そのものである
まず多い誤解が、NFT = 画像そのもの という見方です。
たとえば、マーケットプレイスでNFTを見ると、画面には画像や動画が表示されます。
そのため、「この画像ファイルがNFTなのかな」と感じるのは自然です。
しかし、ERC-721の仕様で定義されている中心は、画像ファイルそのものではありません。
ERC-721は、スマートコントラクト内でNFTを追跡・移転するための標準APIを定義しています。
たとえば、ownerOf(tokenId) で所有者を確認し、transferFrom や safeTransferFrom でNFTを移転します。
参考: ERC-721: Non-Fungible Token Standard
また、ERC-721のメタデータ拡張では、tokenURI(tokenId) がURIを返し、そのURIがメタデータJSONを指す場合があります。
そのメタデータJSONの中に、name、description、image などが含まれる、という構造です。
つまり、よくあるNFTは次のような層に分かれます。
この図で見ると、NFT本体と画像は同じ階層にないことが分かります。
NFT本体は、ブロックチェーン上で識別・移転されるトークンです。
画像は、そのNFTのメタデータから参照されることが多いコンテンツです。
もちろん、オンチェーンNFTのように、画像やメタデータをできるだけチェーン上に持たせる設計もあります。
ただし、多くのNFTでは、少なくとも画像や動画などの大きなデータは、外部ストレージや分散ストレージを参照する形になります。
そのため、記事全体では次のように整理します。
| 言い方 | 注意点 |
|---|---|
| NFT画像 | 日常的には通じるが、技術的には画像とトークンは分けた方がよい |
| NFT本体 | コントラクト上で識別・移転されるトークン |
| NFTが参照する画像 | メタデータの image などからたどるコンテンツ |
💡 豆知識
NFTの画面に表示される画像は、マーケットプレイスやウォレットがtokenURIからメタデータを読み取り、その中のimageなどを表示している場合があります。
つまり、ユーザーからは「NFT画像」に見えていても、裏側ではNFT → メタデータ → 画像URIという段階をたどっていることがあります。
10.2 小さなコードで見る:NFT本体と画像を分けて考える
ここで、NFT本体と画像を分けて考えるための小さなコードを見てみます。
次のコードは、実際のブロックチェーンに接続するものではありません。
tokenId、所有者、メタデータURI、画像URIを分けて持つことで、NFT本体と画像が別の情報であることを確認するための学習用コードです。
# NFT本体と画像URIを分けて考えるための学習用コードです。
# 実際のERC-721コントラクトへ接続するものではありません。
from dataclasses import dataclass
@dataclass
class SimpleNFT:
"""NFTをかなり単純化したデータ構造です。"""
contract_address: str # NFTコントラクトのアドレス
token_id: int # コントラクト内でNFTを識別するID
owner: str # 現在の所有者アドレス
token_uri: str # メタデータJSONを指すURI
# NFT本体を表す情報です。
# ここには画像ファイルそのものは入れていません。
nft = SimpleNFT(
contract_address="0xNFTContract",
token_id=101,
owner="0xAlice",
token_uri="ipfs://metadata-cid/101.json",
)
# tokenURIの先にあるメタデータJSONを、説明用に辞書で表します。
metadata = {
"name": "Sample Ticket #101",
"description": "イベント参加券を表すサンプルNFTです。",
"image": "ipfs://image-cid/ticket-101.png",
}
print("NFT本体:")
print(f" contract: {nft.contract_address}")
print(f" tokenId: {nft.token_id}")
print(f" owner: {nft.owner}")
print(f" tokenURI: {nft.token_uri}")
print("\nメタデータから参照される画像:")
print(f" image: {metadata['image']}")
このコードでは、SimpleNFT の中に画像ファイルそのものは入っていません。
NFT本体には、コントラクトアドレス、tokenId、所有者、tokenURI があり、画像はメタデータ側の image から参照されています。
もちろん実際のNFTでは、コントラクトやメタデータの設計によって構成は異なります。
ただし、最初の理解としては、NFT本体と画像を分ける ことが大切です。
10.3 誤解2:NFTを買えば著作権も手に入る
次に大切なのが、NFTを買えば著作権も手に入る という誤解です。
これは、NFTを理解する上で特に重要です。
NFTを買うと、ブロックチェーン上ではそのNFTの所有者アドレスが変わる場合があります。
ERC-721で言えば、ownerOf(tokenId) が返すアドレスが買い手のウォレットになる、という状態です。
しかし、それだけで画像・音楽・動画・キャラクターなどの著作権が自動的に移るとは限りません。
米国著作権局とUSPTOのNFT報告書では、NFTの移転はデジタルトークンの移転であり、関連する資産や著作権の移転を必ず伴うわけではないと説明されています。
また、NFTの所有と、そのNFTに関連づけられた作品の著作権の所有は別であり、著作権などを移すには通常、別の合意が必要だと整理されています。
参考: Non-Fungible Tokens and Intellectual Property: A Report to Congress
身近な例で考えると、絵画を買った場合に近いです。
絵画そのものを買ったとしても、必ずしもその絵の著作権まで買ったことにはなりません。
購入者はその絵を部屋に飾れるかもしれませんが、勝手に複製してグッズ化したり、商用利用したりできるかは別問題です。
NFTでも同じように、次のように分けて考える必要があります。
| 項目 | 何を意味するか |
|---|---|
| NFT所有 | ブロックチェーン上で、そのtokenIdの所有者として記録されていること |
| コンテンツへのアクセス | 画像、動画、音楽、会員ページなどを見られること |
| 利用権 | 個人利用、表示、商用利用などが許可されている範囲 |
| 著作権 | 複製、公衆送信、翻案などに関わる権利 |
| 規約・契約 | NFT保有者に何が許されるかを定める文書 |
このため、記事では「NFTを買う」という表現を使う場合でも、何を買ったのかを分けて書く必要があります。
NFTを買った
= tokenIdの所有者アドレスが自分のウォレットになった
ただし、
= 著作権・商用利用権・会員特典まで自動で手に入った
とは限らない
10.4 小さなコードで見る:トークン所有と著作権者を分ける
次のコードでは、NFTの所有者と、作品の著作権者を別々のフィールドとして持ちます。
実際の法律関係を判定するものではありません。
あくまで、トークンの所有者が変わっても、著作権者が自動で変わるとは限らない という考え方を確認するための学習用コードです。
# NFT所有者と著作権者を分けるための学習用コードです。
# 実際の法律判断や契約判断には使えません。
from dataclasses import dataclass
@dataclass
class NFTWithRights:
"""NFTと関連作品の権利情報をかなり単純化して表します。"""
token_id: int
token_owner: str # NFTを持っているウォレット
copyright_owner: str # 関連作品の著作権者
license_terms: str # NFT保有者に許される利用範囲の説明
def transfer_nft(nft: NFTWithRights, new_owner: str) -> None:
"""
NFTの所有者だけを移転します。
著作権者やライセンス条件は、この関数では変更しません。
"""
nft.token_owner = new_owner
nft = NFTWithRights(
token_id=101,
token_owner="0xAlice",
copyright_owner="Creator Studio",
license_terms="個人鑑賞のみ。商用利用は別途許可が必要。",
)
print("移転前:")
print(nft)
# AliceからBobへNFTを移転します。
transfer_nft(nft, "0xBob")
print("\n移転後:")
print(nft)
このコードでは、transfer_nft() を実行すると token_owner は変わります。
しかし、copyright_owner は変わりません。
実際のNFTでも、トークンの移転と著作権・利用権の移転は分けて確認する必要があります。
どのような権利がNFT保有者に与えられるかは、プロジェクトの規約、ライセンス、契約、法律関係によって変わります。
10.5 誤解3:NFTなら画像をコピーできない
次の誤解は、NFTなら画像をコピーできない というものです。
NFTは、画像ファイルのコピーそのものを技術的に防ぐ仕組みではありません。
インターネット上に表示されている画像は、スクリーンショットを撮られたり、保存されたり、別の場所へ転載されたりする可能性があります。
NFTがあるからといって、その画像データのコピー自体が不可能になるわけではありません。
NFTが主に提供するのは、次のような情報です。
- どのコントラクトのNFTか
- どの
tokenIdか - 現在どのアドレスが持っているか
- どのメタデータを参照しているか
- どのような移転履歴があるか
つまり、NFTは「画像をコピーできなくする仕組み」というより、特定のトークンの所有者や移転履歴を確認しやすくする仕組み と捉えた方が正確です。
たとえば、コンサートチケットを写真に撮ることはできても、その写真だけで正規の入場権になるとは限りません。
大切なのは、正規のシステム上でそのチケットIDが有効であり、自分がそのチケットの保有者として確認されることです。
NFTもこれに少し似ています。
画像ファイルそのものはコピーされるかもしれません。
しかし、正規コントラクト上の tokenId を保有しているかどうかは、別の問題です。
ここで大切なのは、「コピーできるからNFTに意味がない」とも、「NFTだからコピーを防げる」とも言い切らないことです。
NFTが何の意味を持つかは、そのプロジェクトや用途によって変わります。
アートなら来歴やコレクション性、チケットなら入場確認、会員権ならアクセス制御、証明書なら発行者確認など、使い方ごとに見るべき点が違います。
10.6 誤解4:IPFSなら永久保存される
4章で扱ったように、NFTのメタデータや画像はIPFSに置かれることがあります。
IPFSは、コンテンツにもとづいてデータを参照できる仕組みです。
CIDは、ざっくり言えば「その内容を指す識別子」のようなものです。
ただし、ここで誤解しやすいのが、IPFSに置けば自動的に永久保存される という見方です。
IPFS公式ドキュメントでは、IPFS上でデータを保持し続けるには、データを1つ以上のIPFSノードにpinする必要があると説明されています。
また、IPFSはネットワーク上のコンテンツを発見可能にする一方で、任意のコンテンツが常に利用可能であることまでは保証しない、と説明されています。
参考: IPFS Docs: Persistence, permanence, and pinning
つまり、IPFSを使う場合でも、次のように分ける必要があります。
| 観点 | 説明 |
|---|---|
| CID | コンテンツにもとづく識別子 |
| IPFSネットワーク | CIDをもとにコンテンツを見つける仕組み |
| pinning | データを特定ノードで保持し続けるための運用 |
| pinning service | 第三者のノードでデータ保持を支援するサービス |
| 長期保存 | 誰が保存コストを負担し続けるかが重要 |
たとえるなら、IPFSのCIDは「本のISBN」や「内容から作られる指紋」に近い感覚です。
その本を特定することはできますが、図書館や書店のどこにも在庫がなければ、実際には手に取れません。
同じように、CIDが分かっていても、そのデータを保持しているノードがなければ取得できない可能性があります。
💡 豆知識
IPFSの「content addressing」は、場所ではなく内容にもとづいてデータを指す考え方です。
これはHTTP URLのように「どのサーバーのどの場所か」を中心に考える方法とは少し違います。
ただし、内容を指せることと、その内容を誰かが保存し続けることは別の問題です。
10.7 小さなコードで見る:CIDがあっても取得できるとは限らない
次のコードでは、IPFSのCIDをかなり単純化して、保存されているCIDだけが取得できる、というモデルを作ります。
実際のIPFSの仕組みを再現するものではありません。
目的は、識別子があること と データが利用可能であること を分けて理解することです。
# CIDがあることと、データを取得できることを分けて考えるための学習用コードです。
# 実際のIPFSネットワークを再現するものではありません。
# この辞書は「現在どこかのノードに保存されていて取得できるデータ」を表します。
available_storage = {
"cid-ticket-101": "ticket image data",
"cid-metadata-101": "{ name: 'Sample Ticket #101' }",
}
def fetch_by_cid(cid: str) -> str | None:
"""
CIDを指定してデータを取得する処理を単純化しています。
保存されていないCIDの場合は None を返します。
"""
return available_storage.get(cid)
for cid in ["cid-ticket-101", "cid-missing-image"]:
data = fetch_by_cid(cid)
if data is None:
print(f"{cid}: CIDは分かっていても、現在データを取得できません。")
else:
print(f"{cid}: データを取得できました -> {data}")
このコードでは、cid-ticket-101 は取得できますが、cid-missing-image は取得できません。
実際のIPFSでは、ネットワーク探索、ノード、pinning、ゲートウェイ、キャッシュなどが関係します。
ただし、初学者向けには、次のように理解すると分かりやすいです。
CIDがある
= その内容を指す識別子がある
データが取得できる
= その内容を保持しているノードがあり、実際にアクセスできる
この2つは、似ていますが同じではありません。
10.8 誤解5:ロイヤリティは必ず自動で支払われる
NFTでは、クリエイターに二次流通ロイヤリティが入る、という説明を見かけることがあります。
この考え方自体は、NFTが注目された理由の一つです。
作品が二次流通するときにも、クリエイターへ一定割合を返したい、という発想です。
ただし、技術的には、ロイヤリティ情報を返すこと と 実際に支払われること は別です。
ERC-2981は、NFTロイヤリティ情報を取得するための標準です。
royaltyInfo(tokenId, salePrice) を呼び出すことで、販売価格に対して誰にいくら支払うべきかを取得できます。
参考: ERC-2981: NFT Royalty Standard
しかし、ERC-2981の仕様では、transferFrom() のようなNFT移転はウォレット間の移転も含み、必ずしも売買を意味しないため、ロイヤリティ支払いは任意であり、実際の資金移転はマーケットプレイスなどが実行する必要があると説明されています。
つまり、次のように分ける必要があります。
| 項目 | 説明 |
|---|---|
| ERC-2981 | ロイヤリティ受取先と金額を取得する標準 |
royaltyInfo |
tokenId と販売価格から、受取先と金額を返す関数 |
| 実際の支払い | マーケットプレイスや取引システム側の実装・運用に依存する |
| NFT移転 | 売買とは限らない。贈与、移動、管理用移転などもあり得る |
ここで大切なのは、ロイヤリティ設定があるからといって、すべての二次流通で自動的・強制的に支払われるとは限らないことです。
また、ロイヤリティ受取先に設定されているアドレスが、必ず法的な権利者であるとは限りません。
ロイヤリティは、権利確認とは別に扱う必要があります。
10.9 小さなコードで見る:ロイヤリティ情報と支払い実行を分ける
次のコードでは、royaltyInfo が返す情報と、実際に支払う処理を分けて書いています。
実際のERC-2981やマーケットプレイス処理を再現するものではありません。
あくまで、情報を取得すること と 支払いを実行すること が別であると確認するための学習用コードです。
# royaltyInfoで情報を返すことと、実際に支払うことを分ける学習用コードです。
# 実際のERC-2981コントラクトやマーケットプレイス実装ではありません。
from dataclasses import dataclass
@dataclass
class RoyaltyInfo:
"""ロイヤリティ情報を表します。"""
receiver: str # ロイヤリティの受取先
amount: int # 支払うべき金額
def royalty_info(token_id: int, sale_price: int) -> RoyaltyInfo:
"""
販売価格の5%をロイヤリティとして返す例です。
ここでは情報を返すだけで、支払いは実行しません。
"""
receiver = "0xCreator"
amount = sale_price * 5 // 100
return RoyaltyInfo(receiver=receiver, amount=amount)
def execute_sale(buyer: str, seller: str, sale_price: int, royalty: RoyaltyInfo, pay_royalty: bool) -> None:
"""
売買処理を単純化した関数です。
pay_royalty が True の場合だけ、ロイヤリティ支払いを実行したことにします。
"""
print(f"買い手 {buyer} が 売り手 {seller} から {sale_price} で購入")
if pay_royalty:
print(f"ロイヤリティ支払い: {royalty.receiver} に {royalty.amount}")
else:
print("ロイヤリティ情報は取得しましたが、この例では支払いを実行していません。")
sale_price = 10_000
royalty = royalty_info(token_id=101, sale_price=sale_price)
# 同じロイヤリティ情報を取得しても、支払い実行は取引システム側の処理に依存します。
execute_sale("0xBuyer", "0xSeller", sale_price, royalty, pay_royalty=True)
print()
execute_sale("0xBuyer", "0xSeller", sale_price, royalty, pay_royalty=False)
このコードでは、どちらのケースでも royalty_info() は同じ情報を返します。
しかし、execute_sale() の中で pay_royalty が True かどうかによって、支払いを実行するかが変わります。
このように、ロイヤリティ情報と支払い実行は分けて考える必要があります。
10.10 誤解6:NFTなら本物だと自動で分かる
NFTは、ブロックチェーン上で一意に識別できます。
そのため、「NFTなら本物だと分かる」と言われることがあります。
しかし、これも注意が必要です。
NFTで確認しやすいのは、たとえば次のような情報です。
- どのコントラクトで発行されたか
- どの
tokenIdか - 現在の所有者アドレスはどれか
- 過去にどのように移転したか
- どのメタデータURIを参照しているか
一方で、次のことはNFTだけでは自動的に分かりません。
- そのコントラクトが本当に公式のものか
- 発行者が本当に権利者か
- 画像や作品が無断利用されていないか
- メタデータや画像が今後も維持されるか
- NFT保有者にどのような利用権があるか
- マーケットプレイス上の表示が正確か
つまり、NFTは「そのトークンの存在や移転履歴を確認しやすくする」ことには役立ちます。
しかし、現実世界の真正性や権利関係まで自動的に保証するわけではありません。
たとえば、誰かが有名キャラクターの画像を勝手に使ってNFTを作ったとします。
そのNFTは、ブロックチェーン上では確かに存在するかもしれません。
しかし、それが正規ライセンス品であるとは限りません。
この違いを分けると、次のようになります。
| 確認したいこと | NFTで確認しやすいか | 補足 |
|---|---|---|
| tokenIdの所有者 | 確認しやすい |
ownerOf などで確認できる |
| 移転履歴 | 確認しやすい | イベントログやブロックチェーンエクスプローラーで確認できる場合がある |
| 正規コントラクトか | 追加確認が必要 | 公式サイト、公式SNS、発行元情報などを照合する |
| 発行者が権利者か | 追加確認が必要 | 権利者情報、ライセンス、公式発表などを確認する |
| 商用利用できるか | 追加確認が必要 | 規約やライセンスに依存する |
9章でも触れたように、FBI/IC3は、犯罪者が正規NFT開発者になりすまし、偽のSNSアカウントや偽サイトを使ってウォレット接続を誘導し、drainer smart contractによって暗号資産やNFTを盗む手口を注意喚起しています。
参考: FBI IC3: Criminals Pose as NFT Developers
このことからも、「NFTっぽく見える」「有名プロジェクトっぽく見える」だけで判断しないことが大切です。
10.11 小さなコードで見る:本物らしさの確認項目を分ける
次のコードでは、NFTの「本物らしさ」を確認するときの項目を、かなり単純化してスコア化します。
これは本物判定ツールではありません。
実際には、公式サイト、公式SNS、コントラクトアドレス、権利者情報、マーケットプレイス表示、コミュニティ情報などを総合的に確認する必要があります。
# NFTの本物らしさを確認するときの項目を整理する学習用コードです。
# 実際の正規品判定や投資判断には使えません。
checks = {
"official_contract_matched": True, # 公式サイト等に掲載されたコントラクトアドレスと一致するか
"creator_verified": True, # 発行者・クリエイター情報を確認できるか
"rights_terms_available": False, # 権利・利用規約が確認できるか
"metadata_available": True, # メタデータへアクセスできるか
"suspicious_mint_site": False, # 急かす偽ミントサイトのような兆候があるか
}
def review_authenticity(checks: dict[str, bool]) -> list[str]:
"""
確認項目を見て、注意メッセージを返します。
True/Falseの意味は項目ごとに違うため、条件を個別に見ています。
"""
messages = []
if not checks["official_contract_matched"]:
messages.append("公式コントラクトか確認できていません。")
if not checks["creator_verified"]:
messages.append("発行者・クリエイター情報を確認できていません。")
if not checks["rights_terms_available"]:
messages.append("権利・利用規約が確認できていません。")
if not checks["metadata_available"]:
messages.append("メタデータへアクセスできません。")
if checks["suspicious_mint_site"]:
messages.append("偽ミントサイトの可能性があるため注意が必要です。")
if not messages:
messages.append("主な確認項目は満たしています。ただし、安全性を保証するものではありません。")
return messages
for message in review_authenticity(checks):
print("-", message)
このコードでは、公式コントラクトや発行者を確認できても、権利・利用規約が見つからない場合には注意メッセージを出します。
ここで伝えたいのは、NFTの確認は1つの項目だけでは足りないということです。
コントラクトがある
= NFTとして存在する
公式コントラクトである
= 公式情報との照合が必要
権利者が発行した
= 権利者情報やライセンス確認が必要
利用してよい
= 規約・契約・ライセンス確認が必要
このように、確認したいことごとに見る資料が違います。
10.12 誤解7:ウォレットに届いたNFTはすべて価値がある
NFTを使っていると、知らないNFTがウォレットに届くことがあります。
一見すると「無料でNFTがもらえた」と感じるかもしれません。
しかし、知らないNFTやスパムNFTには注意が必要です。
NFT自体がウォレットに表示されるだけなら、すぐに危険とは限りません。
しかし、そのNFTの説明文や外部リンクから偽サイトへ誘導されたり、「claim」「reward」「airdrop」などの名目でウォレット接続や署名を求められたりする場合があります。
9章で扱ったように、危険なのは「NFTが届いたこと」だけではなく、その後に次のような操作をしてしまうことです。
- 不明なリンクを開く
- 怪しいサイトにウォレットを接続する
- 内容を読まずに署名する
-
setApprovalForAllのような強い承認を与える - シードフレーズや秘密鍵を入力する
そのため、知らないNFTが届いた場合は、慌てて触らず、表示・非表示設定やマーケットプレイスのスパム報告機能などを確認する方が安全です。
ここでも大切なのは、NFTを「持っているように見える」ことと、「価値がある」「安全である」「公式である」ことを分けて考えることです。
| 状態 | 意味 |
|---|---|
| ウォレットに表示される | そのウォレットに関連づけられて見えている可能性がある |
| 価値がある | 市場や用途、権利、需要などによって判断される |
| 安全である | リンク、承認、発行者、規約などの確認が必要 |
| 公式である | 公式情報との照合が必要 |
知らないNFTは、むやみに操作しないことが大切です。
10.13 誤解8:スマートコントラクトなら人間の確認はいらない
NFTはスマートコントラクトで扱われます。
そのため、「コードで動くなら、人間が確認しなくても大丈夫」と感じるかもしれません。
しかし、これも危険な考え方です。
スマートコントラクトは、決められた処理を自動で実行できます。
ただし、その処理が自分にとって望ましいものかどうかは、署名する前に人間が確認する必要があります。
たとえば、次のような操作はどれもウォレット画面で承認を求められる可能性があります。
| 操作 | 確認したいこと |
|---|---|
| NFTを購入する | 支払額、受取NFT、相手コントラクト |
| NFTを移転する | 送り先、tokenId、コントラクト |
approve する |
どのNFTを誰に操作させるか |
setApprovalForAll する |
自分のNFT全体を誰に操作させるか |
| メッセージ署名する | 署名内容が何を意味するか |
スマートコントラクトは、悪意ある処理も忠実に実行してしまいます。
そのため、ユーザー側で「何に署名しているのか」「何を許可しているのか」を確認することが重要です。
Ethereum公式のセキュリティページでも、シードフレーズを共有しないこと、詐欺やフィッシングに注意すること、署名内容や取引内容を確認することが強調されています。
参考: Ethereum Security and scam prevention
NFTの安全性は、スマートコントラクトだけでは完結しません。
ウォレットの表示、利用者の確認、マーケットプレイスの実装、プロジェクトの運用、公式情報の発信なども関係します。
10.14 誤解を避けるための確認表
ここまでの誤解を、確認表としてまとめます。
| 確認したいこと | 見るべき情報 | 注意点 |
|---|---|---|
| どのNFTか | コントラクトアドレス、tokenId | 見た目だけで判断しない |
| 誰が持っているか |
ownerOf、ウォレット、ブロックチェーンエクスプローラー |
現実の人物とアドレスは別 |
| 何が表示されるか |
tokenURI、メタデータ、image
|
表示画像とNFT本体は別 |
| どこに保存されているか | HTTP URL、IPFS CID、オンチェーンデータ | 長期可用性は保存方式と運用に依存 |
| どんな権利があるか | 規約、ライセンス、契約、公式説明 | 著作権や商用利用権は自動で移らない場合がある |
| ロイヤリティはどうなるか | ERC-2981、マーケットプレイス仕様 | 情報取得と支払い実行は別 |
| 安全か | 公式URL、承認内容、署名内容、発行者 | 偽サイト、drainer、スパムNFTに注意 |
この表は、NFTを買う・受け取る・作る・説明するときのチェックリストとしても使えます。
特に、初学者向けの記事では、次の3つを何度も意識すると誤解を減らせます。
1. NFT本体と画像は別
2. NFT所有と権利所有は別
3. 技術仕様と運用・規約は別
この3つを分けるだけでも、NFTの話はかなり整理しやすくなります。
10.15 小さなコードで見る:誤解を分類して注意点を返す
最後に、よくある誤解を分類して、対応する注意点を返す簡単なコードを見てみます。
このコードは、文章を自然言語処理で厳密に判定するものではありません。
NFTの誤解を「トークン」「権利」「保存」「ロイヤリティ」「セキュリティ」に分けて考えるための学習用コードです。
# NFTに関する誤解を分類するための学習用コードです。
# 実際の自然言語処理や法的判断に使うものではありません。
misconceptions = {
"nft_is_image": {
"example": "NFTは画像そのもの",
"category": "トークンとコンテンツの混同",
"advice": "NFT本体、メタデータ、画像URI、コンテンツを分けて確認しましょう。",
},
"nft_gives_copyright": {
"example": "NFTを買えば著作権も手に入る",
"category": "所有と権利の混同",
"advice": "著作権・商用利用権・利用規約は、NFT所有とは別に確認しましょう。",
},
"ipfs_is_permanent": {
"example": "IPFSなら永久保存される",
"category": "保存方式の誤解",
"advice": "CIDだけでなく、pinningや長期保存の運用も確認しましょう。",
},
"royalty_is_enforced": {
"example": "ロイヤリティは必ず自動で支払われる",
"category": "ロイヤリティ標準の誤解",
"advice": "ERC-2981は情報取得の標準であり、実際の支払い実装は別に確認しましょう。",
},
"nft_is_always_authentic": {
"example": "NFTなら本物だと自動で分かる",
"category": "真正性の誤解",
"advice": "公式コントラクト、発行者、権利者、規約を照合しましょう。",
},
}
def explain_misconception(key: str) -> None:
"""誤解の種類に応じて、分類と注意点を表示します。"""
item = misconceptions.get(key)
if item is None:
print("登録されていない誤解です。個別に確認しましょう。")
return
print("誤解の例:", item["example"])
print("分類:", item["category"])
print("注意点:", item["advice"])
for key in misconceptions:
explain_misconception(key)
print()
このコードでは、誤解を5つのカテゴリに分けています。
| カテゴリ | 分けて考えたいこと |
|---|---|
| トークンとコンテンツの混同 | NFT本体と画像・動画を分ける |
| 所有と権利の混同 | tokenIdの所有と著作権・利用権を分ける |
| 保存方式の誤解 | CIDと長期保存の運用を分ける |
| ロイヤリティ標準の誤解 | 情報取得と支払い実行を分ける |
| 真正性の誤解 | オンチェーン情報と現実世界の権利確認を分ける |
NFTを理解するときは、このように「どの層の話をしているのか」を分類すると、かなり読みやすくなります。
10.16 この章のまとめ
この章では、NFTについてよくある誤解を整理しました。
NFTは、ブロックチェーン上で一意に識別・移転できるトークンです。
しかし、それだけで画像そのもの、著作権、商用利用権、長期保存、ロイヤリティ支払い、真正性、安全性がすべて自動で保証されるわけではありません。
この章で押さえたいポイントは、次の通りです。
| ポイント | 説明 |
|---|---|
| NFTは画像そのものではない | 多くの場合、NFT本体はメタデータや画像URIを参照する |
| NFT所有と著作権所有は別 | 著作権や利用権は規約・契約・ライセンスを確認する必要がある |
| NFTは画像コピーを防ぐ仕組みではない | 正規トークンの所有確認と、画像ファイルのコピー可否は別 |
| IPFSは自動永久保存ではない | CIDとpinning、長期保存の運用を分けて考える |
| ロイヤリティは必ず強制されるわけではない | ERC-2981は情報取得の標準であり、支払い実行は別 |
| NFTなら本物と自動判定できるわけではない | 公式コントラクト、発行者、権利者、規約の確認が必要 |
| ウォレットに届いたNFTが安全とは限らない | スパムNFTやフィッシング誘導に注意する |
| スマートコントラクトでも人間の確認は必要 | 署名・承認・送信内容を確認する必要がある |
NFTを理解するうえで一番大切なのは、一つの言葉で全部をまとめないこと です。
NFT
= トークン
≠ 画像そのもの
≠ 著作権そのもの
≠ 永久保存の保証
≠ ロイヤリティ支払いの絶対保証
≠ 本物・安全の自動保証
少し細かく見えるかもしれませんが、この分け方を押さえると、NFTの話はかなり整理しやすくなります。
次の章では、この記事全体をまとめます。
NFTを「画像を所有する仕組み」としてではなく、一意なトークンを使って、何かを識別・移転・確認しやすくする仕組み として捉え直し、トークン、メタデータ、コンテンツ、権利、セキュリティを分けて振り返ります。
11. まとめ
この章では、記事全体を振り返りながら、NFTをどのように理解するとよいかを整理します。
ポイントは、NFTを「画像そのもの」として見るのではなく、トークン、メタデータ、コンテンツ、権利、運用を分けて見ること です。
ここまで、NFTについて、限定チケット、会員証、番号付き座席、シリアル番号付き商品、ゲームアイテム、証明書のような身近な例から順番に整理してきました。
最初に見たのは、「同じように見えても、一つずつ区別したいもの」です。
同じライブのチケットでも、A列1番とA列2番は別物です。
同じデザインの会員証でも、会員番号が違えば別の人に紐づきます。
デジタルの世界では、画像や動画、PDFのようなデータは簡単にコピーできます。
コピーできること自体は便利ですが、「どれを正規のものとして扱うのか」「誰が持っていると記録するのか」「その記録はどの権利と結びつくのか」を考えると、話は少し複雑になります。
このような問題を整理する入口として、NFTを見てきました。
ただし、この記事で繰り返し確認したように、NFTという言葉にすべてを詰め込むと誤解しやすくなります。
NFTを理解するときは、トークン、メタデータ、コンテンツ、保存場所、権利、ウォレット操作を一つずつ分けて見ることが大切です。
11.1 記事全体の振り返り
この記事では、NFTをいきなり定義から覚えるのではなく、次の流れで整理しました。
最初に確認したように、NFTは「画像そのもの」ではありません。
ブロックチェーン上で一意に識別され、所有者アドレスや移転履歴を確認しやすいトークンとして捉えると、全体像が見えやすくなります。
ただし、そのトークンが何を表すのか、画像や動画はどこに保存されているのか、著作権や商用利用権はどう扱われるのか、ウォレット操作にどのようなリスクがあるのかは、NFT本体だけでは完結しません。
この点を分けて考えることが、NFTを落ち着いて理解するための土台になります。
11.2 本記事で整理したこと
本記事で整理した内容を、章ごとに振り返ると次のようになります。
| 章 | 整理したこと | 大事なポイント |
|---|---|---|
| 1章 | 身近な「一つずつ区別できるもの」 | 同じ種類でもIDや所有者が違えば別物として扱う |
| 2章 | NFTの基本構造 | コントラクトアドレス、tokenId、所有者アドレスを分けて見る |
| 3章 | ERC-721 | NFTを追跡・移転・確認するための共通インターフェースを整理する |
| 4章 | メタデータと保存場所 | NFT本体、メタデータ、画像、保存先は別の層にある |
| 5章 | ERC-1155との違い | 一点物だけでなく、種類と数量を扱う場面もある |
| 6章 | 所有権・著作権・利用権 | NFT所有と著作権・商用利用権は自動的には同じにならない |
| 7章 | ロイヤリティ | ERC-2981はロイヤリティ情報を返す標準であり、支払い強制とは別 |
| 8章 | ユースケース | アート、チケット、会員権、ゲームアイテム、証明書などに応用され得る |
| 9章 | セキュリティ上の注意点 | 偽サイト、承認操作、秘密鍵管理、メタデータ変更などに注意する |
| 10章 | よくある誤解 | 「NFT = 画像」「NFT購入 = 著作権取得」などを避ける |
こうして見ると、NFTは単なる流行語ではなく、スマートコントラクト、メタデータ、ストレージ、権利関係、ウォレット操作、サービス運用が重なったテーマだと分かります。
11.3 NFTは「何を持っているのか」を分けて見る
この記事全体を一言でまとめるなら、次のようになります。
NFTは、ブロックチェーン上で一意に識別できるトークンです。
ただし、そのトークンが何を表し、どのような権利や効力を持つのかは、メタデータ、保存場所、規約、ライセンス、運用によって変わります。
NFTでまず確認できるのは、オンチェーン上の状態です。
たとえば、ERC-721では、ある tokenId の所有者アドレスを ownerOf(tokenId) で確認できます。
しかし、そこから先は別の確認が必要になります。
| 観点 | NFTで確認しやすいこと | 別途確認したいこと |
|---|---|---|
| トークン | コントラクトアドレス、tokenId、所有者アドレス | 発行元が正規かどうか |
| メタデータ |
tokenURI、名前、説明、画像URI |
メタデータが固定されているか、変更可能か |
| コンテンツ | 画像や動画への参照 | 保存先が長期的に参照できるか |
| 権利 | 規約やライセンスへの入口 | 著作権、商用利用権、二次利用の範囲 |
| 運用 | NFT保有を条件にしたアクセス制御など | サービス側のルール、本人確認、失効条件 |
| セキュリティ | 移転履歴や承認状態を確認しやすい | 偽サイト、drainer、秘密鍵漏えいへの対策 |
ここで重要なのは、NFTを否定的に見ることではありません。
むしろ、NFTが何を得意とし、どこから先は別の仕組みや確認が必要なのかを分けることで、技術としての使いどころが見えやすくなります。
💡 豆知識
NFTの話で混乱したときは、「トークン」「メタデータ」「コンテンツ」「権利」「運用」のどの話をしているのかを一度分けてみると整理しやすくなります。
たとえば「NFTを買った」という一文でも、トークンを買ったのか、利用権も付いているのか、会員特典があるのかは、プロジェクトごとに確認が必要です。
11.4 NFTはブロックチェーンだけで完結しない
この記事ではNFTを中心に扱いましたが、NFTはブロックチェーン上のトークンだけで完結する話ではありません。
NFTのまわりには、次のような要素が関係します。
- スマートコントラクト
- トークン標準
- ウォレット
- メタデータ
- 画像・動画・音声などのコンテンツ
- HTTPサーバーやIPFSなどの保存方式
- マーケットプレイス
- 著作権、利用規約、ライセンス
- チケットや会員権などの現実世界の運用
- フィッシング対策や秘密鍵管理
NFTは、この中でも特に 一意なトークンを発行・移転・確認する部分 に関わります。
この図のように、NFTは仕組み全体の重要な部品ですが、すべてを自動的に解決する部品ではありません。
たとえば、NFTを持っていることは、オンチェーン上では確認しやすくなります。
一方で、そのNFTが正規発行されたものか、画像の著作権者が誰か、会員特典が今も有効か、メタデータが長期的に参照できるかは、別の情報や運用と合わせて確認する必要があります。
この意味で、NFTは「ブロックチェーンだけで完結する魔法の証明書」ではなく、スマートコントラクトと周辺の運用を組み合わせて使う仕組みとして見る方が自然です。
11.5 NFTを学ぶときに意識したいこと
NFTを学ぶときは、次の点を意識すると理解しやすくなります。
| 意識したいこと | 理由 |
|---|---|
| 「NFT = 画像」と考えない | NFT本体はトークンであり、画像はメタデータから参照されることが多いため |
| 「何を持っているのか」を分ける | トークン、コンテンツ、著作権、利用権は同じとは限らないため |
| コントラクトアドレスとtokenIdを見る | 見た目や名前だけでは正規のNFTか判断しにくいため |
| tokenURIと保存場所を見る | メタデータや画像がどこにあるかで、長期参照性が変わるため |
| 権利・規約を確認する | 商用利用や二次利用の範囲はNFT標準だけでは決まらないため |
| 承認操作を慎重に見る |
approve や setApprovalForAll はNFT移転権限に関わるため |
| 強すぎる表現を避ける | 「永久保存」「必ず本物」「完全に安全」とは言い切れないため |
| 最新情報を確認する | マーケットプレイス仕様、ウォレットUI、詐欺手口、規制や標準周辺は変化するため |
特に、次のような表現には注意が必要です。
| 避けたい表現 | より丁寧な表現 |
|---|---|
| NFTは画像である | NFTは画像などを参照し得る一意なトークンである |
| NFTを買えば著作権も手に入る | NFT所有と著作権・利用権は別に確認する必要がある |
| IPFSなら永久保存される | CIDと長期保存の運用は分けて考える必要がある |
| ロイヤリティは必ず自動で支払われる | ERC-2981はロイヤリティ情報を返す標準であり、支払い実行は実装に依存する |
| NFTなら本物だと分かる | 発行者、公式コントラクト、権利関係、マーケットプレイス表示を確認する必要がある |
| ウォレットに届いたNFTは安全で価値がある | スパムNFTや詐欺誘導の可能性もあるため、むやみに操作しない |
技術記事として書く場合も、学習する場合も、強すぎる言い切りを避けることが大切です。
NFTは「すべてを証明してくれる便利な仕組み」ではありません。
一方で、「画像がコピーできるから意味がない」と単純に片付けられるものでもありません。
どの部分がオンチェーンで確認でき、どの部分がメタデータや保存方式に依存し、どの部分が規約や現実世界の制度に依存するのかを分けて考えることが重要です。
11.6 今後深掘りしたいテーマ
NFTの基本を理解すると、次に気になってくるテーマがいくつかあります。
| テーマ | ざっくりした内容 | NFTとの関係 |
|---|---|---|
| ERC-721実装 | 実際にNFTコントラクトを作る |
ownerOf、tokenURI、safeTransferFrom などの理解が深まる |
| OpenZeppelin Contracts | 安全な実装ライブラリを使う | 標準実装や拡張機能を学びやすい |
| IPFSとpinning | メタデータや画像を保存する | NFTの長期参照性を考えるうえで重要 |
| オンチェーンNFT | メタデータや画像をチェーン上に置く | コストと永続性のトレードオフを考える入口になる |
| ウォレット承認の読み方 |
approve や setApprovalForAll を確認する |
NFT詐欺やdrainer対策と関係する |
| マーケットプレイスの仕組み | 売買、オファー、手数料、ロイヤリティ | ERC-2981と実際の支払い処理の違いを理解しやすい |
| NFTチケット | 入場権や失効、本人確認を扱う | オンチェーン所有と現実世界の運用をつなぐ例になる |
| NFTと知的財産 | 著作権、商標、利用許諾を確認する | NFT所有と権利所有の違いをさらに深掘りできる |
| Soulbound Token / 譲渡不可トークン | 移転できない証明書や参加証 | NFT的な識別と移転可能性を分けて考えるきっかけになる |
| DID / Verifiable Credentials | 発行者・保有者・検証者の関係を扱う | 証明書用途でNFTと比較しやすい |
この記事では、NFTの全体像を初学者向けに整理することを優先しました。
そのため、Solidityでの実装、マーケットプレイスコントラクトの詳細、各プロジェクトのライセンス比較、法的な所有権移転の詳細、DID/VCとの厳密な比較などは深く扱っていません。
これらは、別記事として一つずつ掘り下げる価値があります。
11.7 最後に
NFTは、ブロックチェーンを学ぶうえで、とても身近に感じやすいテーマです。
画像、チケット、会員証、ゲームアイテム、証明書など、具体的な例が多いため、ブロックチェーンの使い道をイメージしやすいからです。
一方で、NFTは誤解も起きやすいテーマです。
「NFTを買った」と言っても、実際には何を買ったのか。
トークンなのか、画像なのか、商用利用権なのか、会員特典なのか。
画像はどこに保存されているのか。
発行者は本当に権利者なのか。
ウォレットで署名しようとしている操作は安全なのか。
こうした問いを分けずに扱うと、NFTはとても分かりにくくなります。
そのため、本記事では、NFTを「一意なトークン」として整理しながら、メタデータ、コンテンツ、保存場所、権利、セキュリティを分けて見てきました。
もちろん、NFTにも限界があります。
画像やメタデータの保存方式、権利関係、マーケットプレイスの仕様、ウォレットの安全性、詐欺対策など、トークン標準だけでは解決できない問題があります。
それでも、NFTは「デジタルなものを一つずつ区別し、所有者や移転履歴を確認しやすくする」という大きな問いに対する、重要な設計の一つです。
この記事を通じて、NFTを単なる流行語としてではなく、次のように捉えられるようになっていれば幸いです。