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_upgradepost_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 )に変換する必要があります。 幸いなことに、 RbTreeVec の両方がイテレータとの変換を可能にする関数を実装しているので、変換は非常に簡単に行うことができます。 変換後はアップグレード中のデータを保存するために、別の StableState オブジェクトが使用されます。

認証データ( Certified Data )

<canister-id>.raw.ic0.app の代わりに <canister-id>.ic0.app を介して http でアセットを提供するには、レスポンスに以下のものが必要です。 証明書を含む レスポンスのコンテンツを検証する必要があります。 このような証明書の取得はコンセンサスを得なければならないため、クエリコール中に行うことはできず、アップデートコールで作成する必要があります。

証明書のコンテンツは非常に限定されています。この文章を書いている時点では、Canister は32バイト以上のデータを送信して証明書を発行することができません。 その少ないデータを最大限に活用するために、HashTree が使われています(前のセクションで出てきた RbTreeHashTree です)。 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 のオーナーとして設定することが可能です。