NFT ミント( NFT Minting )
このサンプルでは NFT Canister の実装を説明します。NFT( non-fungible tokens )は、任意のメタデータ(通常は何らかの画像)を持つ一意のトークンで、トレーディングカードに相当するデジタルデータを形成するものです。いくつかの異なる Internet Computer の NFT 規格はいくつかありますが、最も Cycle 効率がよく機能が充実しているのが DIP-721 規格なので、この Canister はこの規格を採用しています。
Canister はミント、バーンおよび通知の拡張インタフェースをサポートした規格の基本的な実装です。
サンプルコードは Rust の サンプルリポジトリで公開されており、Motoko は近日公開予定です。 デモ用の Rust Canister の実行インスタンスは t5l7c-7yaaa-aaaab-qaehq-cai で公開されています。 このインターフェースはプログラムによるものですが、Rust 版には HTTP 機能が追加されており、<canister URL>/<NFT ID>/<file ID>
のメタデータファイルを閲覧することが可能です。 6 つの NFT が含まれていて、 <canister URL>/0/0
から <canister URL>/5/0
までのアイテムを見ることができます。
コマンドラインの長さの制限で画像やビデオのような大きなファイルを dfx
経由で NFT をミントすることができません。そのため minting tool (コマンドライン) が用意されており、簡単な NFT をミントすることができます。
アイディア
NFT Canister は DIP-721 規格が主に CRUD 操作を指定しているので、それほど複雑なものではありません。 しかし Internet Computer の Dapp 開発に関する 3 つの重要なコンセプトを説明するには十分利用可能です。
Canister アップグレードのためのステーブルメモリ( Stable Memory )
Internet Computer は 直交永続性( Orthogonal Persistence ) を採用しているため、開発者は通常、データの保存について深く考える必要はありません。 しかし、Canister コードをアップグレードする場合、Canister データを明示的に処理する必要があります。NFT Canister のサンプルでは、 pre_upgrade
と post_upgrade
を使ってステーブルメモリをどのように処理するかを示しています。
認証データ( Certified Data )
一般的に、関数が( Canister のステートを変更するのではなく)データを読み取るだけの場合は、 update call の代わりに query call を使用するのが便利です。 しかし、クエリコールはコンセンサスを経ないため、 認証済みレスポンス を可能な限り使用する必要があります。Rust 実装の HTTP インターフェースは認証済みデータをどのように処理できるかを示しています。
アセットに対するコントロールの委任
さまざまな理由から、ユーザーは自分の資産の管理を他の ID に委ねたり、アイテムを削除(バーン)したい場合があります。 NFT Canister のサンプルはこれらのケースをすべて含んでどのように実行できるかを示しています。
アプローチ
DIP-721 で必要とされる基本的な機能は非常に簡単に実装できるため、ここでは上記の考え方をどのように処理・実装するかについてのみ説明します。
Canister アップグレードのためのステーブルストレージ
Canister コードのアップグレード中、異なる Canister 呼び出し間でメモリは保持されません。ステーブルメモリ内のメモリのみが引き継がれます。 そのため、アップグレードが行われる前にすべてのデータをステーブルメモリに書き込む必要があり、これは通常 pre_upgrade
関数で実行されます。 この関数はアップグレードが行われる前にシステムから呼び出されます。アップグレード後は通常、ステーブルメモリからメモリにデータをロードします。 post_upgrade
関数はアップグレード後にシステムから呼び出されます。 アップグレードの途中でエラーが発生した場合、アップグレード( post_upgdrade
を含む)はすべて元に戻されます。
Rust CDK( Canister Development Kit )は現在、ステーブルメモリに 1 つの値しかサポートしていないので、気になるものをすべて保持できるオブジェクトを作成する必要があります。 さらに、すべてのデータ型をステーブルメモリに格納できるわけではありません。 CandidType trait を実装したものだけがステーブルメモリに格納できます。 (通常は CandidType derive macro を介して)ステーブルメモリに書き込むことができます。
この Canister のステートには CandidType
を実装していない RbTree
が含まれているので、これを CandidType
を実装したデータ構造(この場合は Vec
)に変換する必要があります。 幸いなことに、 RbTree
と Vec
の両方がイテレータとの変換を可能にする関数を実装しているので、変換は非常に簡単に行うことができます。 変換後はアップグレード中のデータを保存するために、別の StableState
オブジェクトが使用されます。
認証データ( Certified Data )
<canister-id>.raw.ic0.app
の代わりに <canister-id>.ic0.app
を介して http でアセットを提供するには、レスポンスに以下のものが必要です。 証明書を含む レスポンスのコンテンツを検証する必要があります。 このような証明書の取得はコンセンサスを得なければならないため、クエリコール中に行うことはできず、アップデートコールで作成する必要があります。
証明書のコンテンツは非常に限定されています。この文章を書いている時点では、Canister は 32 バイト以上のデータを送信して証明書を発行することができません。 その少ないデータを最大限に活用するために、HashTree
が使われています(前のセクションで出てきた RbTree
も HashTree
です)。 HashTree
はツリー状のデータ構造で、ツリー全体を 32 バイトの小さなハッシュにまとめることができます(ハッシュ化される)。 ツリーの内容が変更されるたびにハッシュも変更されます。このようなツリーのハッシュが認証されていれば、ツリーの内容は認証されていると考えてもよいです。 NFT のサンプル Canister でデータがどのように認証されるかを見るには、 http.rs
の関数 add_hash
を見てください。
レスポンスが検証されるためには、a) 送られたコンテンツがツリーの一部であること、b) そのコンテンツを含むツリーが実際に認証済みハッシュにハッシュ化できること、を確認しないといけません。 関数 witness
は、a) と b) を満たすように検証可能な最小限のコンテンツを含むツリーを作成する責任を負います。 この最小限のツリーが構築されると、証明書と最小限のハッシュツリーが IC-Certificate
ヘッダーの一部として送信されます。
認証の仕組みについては、 こちらの解説動画 をご覧ください。
アセットに対するコントロール管理
DIP-721 では、NFT に対する複数の管理レベルを規定しています。 - 所有者:この人物は NFT を所有しています。NFT の譲渡、オペレータの追加・削除、NFT のバーンが可能です。 - オペレータ:委任された所有者に近いです。オペレーターは NFT を所有しませんが、所有者と同じ操作を行うことができます。 - カストディアン:NFT コレクション・ Canister の作成者です。NFT の譲渡、オペレーターの追加・削除、バーン、バーン解除が可能です。また、新しい NFT をミントしたりコレクションのシンボルや説明を変更することもできます。
NFT の Canister のサンプルではこれら 3 つのレベルのアクセス制御を非常にシンプルに保っています。制御レベルごとに、Principal の個別のリスト(またはセット)が保持されます。 制御レベルごとに Principal のリスト(またはセット)が作成され、誰かが認証を必要とする操作を行うたびに、この 3 つのレベルが手動でチェックされます。 もしユーザーがある関数を呼び出す権限がない場合は、エラーが返されます。
NFT のバーンは特殊なケースです。NFT をバーンということは NFT を削除するか( DIP-721 では意図していない)、所有権を null
(または類似の値)に設定することを意味します。 Internet Computer ではこの存在しない Principal は Management Canister と呼ばれています。 以下はリンク先からの引用です。「 IC Management Canister は単なる見せかけであり、実際には(独立したステートや Wasm コードなどの持つ) Canister として存在しません。」(引用終わり)アドレスは aaaa-aa
になります。 この Management Canister のアドレスを使って Principal を構築し、Management Canister をバーンした NFT のオーナーとして設定することが可能です。