Previous << 5.1 Non-Fungible Token Tutorial Part 1
Next >> 6. Fungible Token Tutorial
このチュートリアルでは、Non-Fungible Tokens(NFT)のフル実装について学びます。
💡 TIP
このチュートリアルのスターターコードをFlow Playgroundで開きます。
https://play.flow.com/63d827b3-0b49-48d5-91ba-4b222c23e217
チュートリアルでは、このコードと対話するためにさまざまな操作を行うよう求められます。
ⓘ ACTION
このチュートリアルでは、操作が必要な指示は、常にこのような吹き出しボックス内に表示されます。ハイライトされた操作は、コードを実行するために必要な操作すべてですが、言語の設計を理解するためには、その他の部分も読むことが必要です。
Storing Multiple NFTs in a Collection
前回のチュートリアルでは、シンプルなNFTリソースを作成し、ストレージパスに保存し、マルチシグネチャトランザクションを使用して、それをあるアカウントから別のアカウントに転送しました。
前回のチュートリアルで使用したセットアップと操作は、あまり拡張性がないことはお分かりいただけたと思います。ユーザーは、すべてのNFTを1か所で管理できる方法が必要です。
これを実現する方法はいくつかあります。
- すべてのNFTを配列またはディクショナリに格納することが出来ます。
/* Define a dictionary to store the NFTs in */
let myNFTs: @{Int: BasicNFT.NFT} = {}
/* Create a new NFT */
let newNFT <- BasicNFT.createNFT(id: 1)
/* Save the new NFT to the dictionary */
myNFTs[newNFT.id] <- newNFT
/* Save the NFT to a new storage path */
account.storage.save(<-myNFTs, to: /storage/basicNFTDictionary)
Dictionaries
この例では、Dictionary: 変更可能で順序のないキーと値の集合 を使用します。
/* Keys are `Int`
Values are `NFT` */
access(all) let myNFTs: @{Int: NFT}
ディクショナリでは、すべてのキーは同じ型でなければならず、すべての値も同じ型でなければなりません。この場合、整数(Int
)のIDをNFT
リソースオブジェクトにマッピングして、ディクショナリに存在する各Int
に対して1つのNFT
があることになります。
ディクショナリ定義では通常、型指定に@
記号は使用しませんがmyNFTs
mapping がリソースを格納するため、フィールド全体もリソース型になる必要があります。そのため、このフィールドにはリソース型であることを示す@
記号が付けられています。
つまり、リソースに適用されるすべてのルールは、この型にも適用されるということを意味します。
NFTを保存するためにディクショナリを使用すれば、NFTごとに異なる保存パスを使用しなければならないという問題は解決しますが、すべての問題が解決するわけではありません。このタイプは比較的曖昧であり、それ自体にはあまり有用な機能がありません。
その代わり、Cadenceの強力な機能である「リソースが他のリソースを所有する」という機能を使用できます。新しいCollection
リソースをNFTの保存場所として定義し、NFTとのより高度なやりとりを可能にします。
次に見るスマートコントラクトは、ExampleNFT
と呼ばれ、アカウント0x06
のContract 1に保存されています。
このコントラクトは、BasicNFT
を拡張し、以下を追加します。
- 固有のNFT IDを追跡する
idCount
コントラクトフィールド(contractの直下のフィールド)を追加します。 -
NFTReceiver
インターフェースを追加し、コレクション用の3つのパブリック関数を定義します。 - NFTを直感的に保存および管理する場所として
Collection
と呼ばれるリソースを宣言し、そこにNFTReceiver
インターフェースを実装します。 -
Collection
は、ownedNFTs
、init()
、withdraw()
、その他の重要な関数などを含む、NFTとやりとりするためのフィールドや関数を宣言します。 - 次に、このコントラクトは新しいNFTを作成する関数(
mintNFT()
)と空のコレクション(createEmptyCollection()
)を宣言します。 - 最後に、このコントラクトはpathフィールドを初期化し、空のコレクションとそれへの参照を作成し、さらにminterリソースをアカウントストレージに保存するイニシャライザー(init関数)を宣言します。
このスマートコントラクトではいくつかの新しい概念が導入されています。新しいスマートコントラクトについて見ていき、導入されている新しい概念をすべて分解していきます。
ⓘ ACTION
ExampleNFT
コントラクトを開きます。
エディタの右下にある「Deploy」ボタンをクリックしてコントラクトをデプロイします。
ExampleNFT.cdc
には以下のコードが含まれているはずです。このコードには、すでにBasicNFT.cdc
に含まれているものに加え、スマートコントラクト本体に追加のリソース宣言が含まれています。
/* ExampleNFT.cdc
This is a complete version of the ExampleNFT contract
that includes withdraw and deposit functionalities, as well as a
collection resource that can be used to bundle NFTs together.
Learn more about non-fungible tokens in this tutorial: https:\/\/developers.flow.com\/cadence\/tutorial\/non-fungible-tokens-1 */
access(all) contract ExampleNFT {
/* Declare Path constants so paths do not have to be hardcoded
in transactions and scripts */
access(all) let CollectionStoragePath: StoragePath
access(all) let CollectionPublicPath: PublicPath
access(all) let MinterStoragePath: StoragePath
/* Tracks the unique IDs of the NFTs */
access(all) var idCount: UInt64
/* Declare the NFT resource type */
access(all) resource NFT {
/* The unique ID that differentiates each NFT */
access(all) let id: UInt64
/* Initialize both fields in the initializer */
init(initID: UInt64) {
self.id = initID
}
}
access(all) entitlement Withdraw
/* The definition of the Collection resource that
holds the NFTs that a user owns */
access(all) resource Collection {
/* dictionary of NFT conforming tokens
NFT is a resource type with an `UInt64` ID field */
access(all) var ownedNFTs: @{UInt64: NFT}
/* Initialize the NFTs field to an empty collection */
init () {
self.ownedNFTs <- {}
}
/* withdraw
Function that removes an NFT from the collection
and moves it to the calling context */
access(Withdraw) fun withdraw(withdrawID: UInt64): @NFT {
/* If the NFT isn't found, the transaction panics and reverts */
let token <- self.ownedNFTs.remove(key: withdrawID)
?? panic("Could not withdraw an ExampleNFT.NFT with id="
.concat(withdrawID.toString())
.concat("Verify that the collection owns the NFT ")
.concat("with the specified ID first before withdrawing it."))
return <-token
}
/* deposit
Function that takes a NFT as an argument and
adds it to the collections dictionary */
access(all) fun deposit(token: @NFT) {
/* add the new token to the dictionary with a force assignment
if there is already a value at that key, it will fail and revert */
self.ownedNFTs[token.id] <-! token
}
/* idExists checks to see if a NFT
with the given ID exists in the collection */
access(all) view fun idExists(id: UInt64): Bool {
return self.ownedNFTs[id] != nil
}
/* getIDs returns an array of the IDs that are in the collection */
access(all) view fun getIDs(): [UInt64] {
return self.ownedNFTs.keys
}
}
/* creates a new empty Collection resource and returns it */
access(all) fun createEmptyCollection(): @Collection {
return <- create Collection()
}
/* mintNFT
Function that mints a new NFT with a new ID
and returns it to the caller */
access(all) fun mintNFT(): @NFT {
/* create a new NFT */
var newNFT <- create NFT(initID: self.idCount)
/* change the id so that each ID is unique */
self.idCount = self.idCount + 1
return <-newNFT
}
init() {
self.CollectionStoragePath = /storage/nftTutorialCollection
self.CollectionPublicPath = /public/nftTutorialCollection
self.MinterStoragePath = /storage/nftTutorialMinter
/* initialize the ID count to one */
self.idCount = 1
/* store an empty NFT Collection in account storage */
self.account.storage.save(<-self.createEmptyCollection(), to: self.CollectionStoragePath)
/* publish a capability to the Collection in storage */
let cap = self.account.capabilities.storage.issue<&Collection>(self.CollectionStoragePath)
self.account.capabilities.publish(cap, at: self.CollectionPublicPath)
}
}
このスマートコントラクトは、プロジェクトが実際に運用で使用するスマートコントラクトにより近いものですが、それでも公式のNFTスタンダードを使用していないため、いかなるプロダクションコードでも使用すべきではありません。
1つ以上のExampleNFT
を所有するユーザーは、この@ExampleNFT.Collection
リソースのインスタンスをアカウントに保存する必要があります。このコレクションは、整数IDを@NFT
にマッピングするディクショナリに、ユーザーのNFTをすべて保存します。
各コレクションには、deposit
およびwithdraw
関数があります。これらの関数により、ユーザーは標準的な一連の関数を通じて、コレクションへのトークンの出し入れを行うことができます。
ユーザーがNFTをアカウントに保存したい場合、ExampleNFT
スマートコントラクトのcreateEmptyCollection()
関数を呼び出して、空のCollection
を作成します。これにより、アカウントのストレージに保存できる空のCollection
オブジェクトが返されます。
この例では、いくつかの新しい機能を使用します。それらについて説明します。
The Resource Dictionary
ディクショナリがリソースを格納すると、そのディクショナリ自体もリソースになることを前述で説明しました。
つまり、コレクションは、そのコレクションが持つリソースを処理するための特別なルールを持つ必要があります。コレクションが誤って失われることは避けたいものです!
Resourceチュートリアルで学んだように、destroy
コマンドを明示的に呼び出すことで、任意のリソースを破棄することができます。
Collection
リソースが作成されると、イニシャライザー(init関数)が実行され、すべてのメンバー変数を明示的に初期化する必要があります。これにより、初期化されていないフィールドがバグの原因となるスマートコントラクトの問題を防ぐことができます。イニシャライザー(init関数)は、この後二度と実行されることはありません。ここでは、リソースタイプとして空のディクショナリを初期化しています。
init () {
self.ownedNFTs <- {}
}
ディクショナリに関するもう一つの特徴は、組み込みのkeys
関数を使用して、ディクショナリのキーの配列を取得できることです。
/* getIDs returns an array of the IDs that are in the collection */
access(all) view fun getIDs(): [UInt64] {
return self.ownedNFTs.keys
}
これは、ディクショナリ内を繰り返し処理したり、保存されているもののリストを表示したりするために使用できます。ご覧の通り、可変長配列型は、メンバーの型を角カッコ([UInt64]
)で囲むことで宣言されます。
Resources Owning Resources
このExampleNFT.cdc
のNFTコレクションの例は、重要な特徴を示しています。リソースは他のリソースを所有できるのです。
この例では、ユーザーは1つのNFTを別のユーザーに譲渡することができます。さらに、Collection
はその中のNFTを明示的に所有しているため、所有者はコレクション1つを譲渡するだけで、すべてのNFTを一度に譲渡することができます。
これは重要な機能であり、さまざまな追加の使用事例が可能になります。簡単にバッチ転送できることに加え、これは、CryptoKittyが帽子のアクセサリーを所有しているように、独自のNFTが別の独自のNFTを所有したい場合、そのNFTが文字通り自身のフィールドに帽子を格納し、事実上それを所有することを意味します。
また、所有権に関するCadenceの興味深い特徴も挙げられます。他の台帳ベースの言語では、所有権はアカウントのアドレスによって示されます。Cadenceは完全にオブジェクト指向の言語であるため、所有権は台帳上のエントリだけでなく、オブジェクトがどこに格納されているかによって示されます。
リソースは他のリソースを所有することができます。つまり、興味深いロジックにより、リソースは、そのリソースが保存されている実際の人物よりも、所有するリソースをより制御できるということです。
Cadenceをより深く理解するにつれ、所有権と相互運用性について、さらに興味深い意味合いが明らかになっていきます。
それでは、チュートリアルに戻りましょう!
Restricting Access to the NFT Collection
NFTコレクションでは、Collection
の重要な機能に誰もがアクセスできるCapabilityが保存されます。例えば、deposit()
やgetIDs()
などです。
ここで重要なアクセス制御のレイヤーが関わってきます。CadenceはCapabilityセキュリティを利用しており、これは任意のオブジェクトについて、ユーザーが以下のいずれかの条件を満たす場合に、そのオブジェクトのフィールドまたはメソッドへのアクセスが許可されることを意味します。
- オブジェクトの所有者である
- そのフィールドまたはメソッドへの有効な参照を持っている(参照はCapabilityからしか作成できず、Capabilityはオブジェクトの所有者によってしか作成できないことに注意してください)
ユーザーが自身のNFTCollection
をアカウントストレージに保存すると、デフォルトでは他のユーザーはアクセスできません。これは、所有者が承認し署名したトランザクションのみがアクセスできる認証済みアカウントオブジェクト(auth(Storage) &Account
)へのアクセスが必要となるためです。
外部アカウントにaccess(all)
フィールドおよび関数へのアクセス権を与えるには、所有者はストレージ内のオブジェクトへのリンクを作成します。
このリンクにより、Capabilityが作成されます。 オーナーは、この機能を使用して、望むことを何でも行うことができます。 Capabilityは、一度限りの使用のためにパラメータとして関数に渡すこともできますし、/public/
ドメインに置くことで、誰でもアクセスできるようにすることもできます。
Capabilityの作成と公開は、ExampleNFT.cdc
スマートコントラクトのイニシャライザー(init関数)で確認できます。
/* publish a capability to the Collection in storage */
let cap = self.account.capabilities.storage.issue<&Collection>(self.CollectionStoragePath)
self.account.capabilities.publish(cap, at: self.CollectionPublicPath)
issue
関数は、その機能が&Collection
として型付けされていることを指定します。次に、リンク(link関数)は/public/
に公開され、誰でもアクセスできるようになります。リンク(link関数)のターゲットは、/storage/NFTCollection
で、これは以前に作成したものです。
これで、ユーザーはアカウント/storage/
にNFTコレクションを持つことになり、他のユーザーにそのユーザーが所有するNFTを確認させることができ、そのユーザーにNFTを送信したりできるCapability(機能)も利用できるようになります。
スクリプトを実行して、これが正しいことを確認してみましょう!
Run a Script
Cadenceのスクリプト(script)は、アカウントの権限を必要とせず、ブロックチェーンから情報を読み取るだけのシンプルなものです。
ⓘ ACTION
Print 0x06 NFTs
という名前のスクリプトファイルを開きます。Print 0x06 NFTs
には、以下のコードを含んでいるはずです。
import ExampleNFT from 0x06
/* Print the NFTs owned by account 0x06. */
access(all) fun main(): [UInt64] {
/* Get the public account object for account 0x06 */
let nftOwner = getAccount(0x06)
/* Find the public Receiver capability for their Collection and borrow it */
let receiverRef = nftOwner.capabilities
.borrow<&ExampleNFT.Collection>(ExampleNFT.CollectionPublicPath)
?? panic("Could not borrow a receiver reference to 0x06's ExampleNFT.Collection"
.concat(" from the path ")
.concat(ExampleNFT.CollectionPublicPath.toString())
.concat(". Make sure account 0x06 has set up its account ")
.concat("with an ExampleNFT Collection."))
/* Log the NFTs that they own as an array of IDs */
log("Account 1 NFTs")
return receiverRef.getIDs()
}
ⓘ ACTION
Print 0x06 NFTs
を実行するには、エディタボックスの右上にあるExecuteボタンをクリックします。
このスクリプトは、アカウント0x06
が所有するNFTのリストを返します。
アカウント0x06
は現在、コレクションに何も所有していないため、空の配列が出力されます。
"Account 1 NFTs"
Result > []
スクリプトが実行できない場合、おそらくNFTコレクションがアカウント0x06
に正しく保存されていないことを意味します。問題が発生した場合は、アカウント0x06
にコントラクトをデプロイしたこと、および前述の手順を正しく実行したことを確認してください。
Using Entitlements
ネットワーク上の全員がwithdraw
関数を使用できる状態にはしたくないです。Cadenceでは、参照は、参照が準拠するサブタイプまたはスーパータイプに自由にアップキャストまたはダウンキャストできます。つまり、&ExampleNFT.Collection
型の参照を持っていた場合、Collection
上のすべてのaccess(all)
関数が公開されます。
これは非常に便利な強力な機能ですが、リソースにパブリックなCapabilityを持つ特権的な機能がある場合、その機能はaccess(all)
では利用できないことを開発者は理解しておく必要があります。Entitlementsを使用する必要があります。
エンタイトルメントにより、アプリ作者はアクセス範囲をきめ細かく制限することができ、同様の名称のエンタイトルメントの下に制限をグループ化するオプションも用意されています。リソースの所有者は、これらのエンタイトルメントを使用して、認証済みの参照によって有効化されたアクションのサブセットへのアクセスを許可することができます。
NFTスマートコントラクトで確認できるとおり、エンタイトルメントを追加しました。
access(all) entitlement Withdraw
また、withdraw()
メソッドにもこの権限(Entitlement)を追加しました。
access(Withdraw) fun withdraw(withdrawID: UInt64): @NFT {
アクセス権限(with entitled access)のある関数とは、その関数が、access(all)
であるかのように、その関数を含む型の具体的なオブジェクトを持つ誰かによって呼び出せることを意味します。しかし、そのオブジェクトへの通常の参照からは呼び出せません。したがって、Collection
のパブリック参照を借りた場合、&ExampleNFT.Collection
の型、withdraw()
関数を除いて、すべての関数を呼び出し、すべてのフィールドにアクセスできます。
/* Get the public capability and borrow a reference from it */
let collectionRef = recipient.capabilities
.borrow<&ExampleNFT.Collection>(ExampleNFT.CollectionPublicPath)
?? panic("Could not borrow a reference to the ExampleNFT.Collection")
/* Try to withdraw an NFT from their collection
ERROR: The reference is not entitled, so this call is not possible and will FAIL */
let stolenNFT <- collectionRef.withdraw(withdrawID: 1)
参照を通じて権限のある(entitled)フィールドまたは関数にアクセスするには、参照も権限を持つ必要があります。つまり、参照またはCapabilityが作成される際には、そのオブジェクトの所有者が明示的にその権限(entitlement)を指定する必要があります。
オブジェクトの所有者は、権限付き(entitled)のCapabilityや参照を作成できる唯一の存在です。上記の例で、withdraw関数をpublicで公開(accessible)したい場合、auth
キーワードを使用して、Capabilityの型指定ですべての権限(entitlement)を指定し、権限付きの機能(entitled capability)として発行します。
/* publish an entitled capability to the Collection in storage
This capability is issued with the `auth(ExampleNFT.Withdraw)` entitlement
This gives access to the withdraw function */
let cap = self.account.capabilities.storage.issue<auth(ExampleNFT.Withdraw) &ExampleNFT.Collection>(self.CollectionStoragePath)
self.account.capabilities.publish(cap, at: self.CollectionPublicPath)
そしてそれは今、そのCapabilityは、権利付きバージョン(entitled version)として以下のように発行され、誰でも利用することができるようになります。(補足: あまりしないほうがいいはず)
/* Get the public entitled capability and borrow a reference from it */
let entitledCollectionRef = recipient.capabilities
.borrow<auth(ExampleNFT.Withdraw) &ExampleNFT.Collection>(ExampleNFT.CollectionPublicPath)
?? panic("Could not borrow a reference to the ExampleNFT.Collection")
/* Try to withdraw an NFT from their collection
This will succeed because the reference is entitled */
let stolenNFT <- entitledCollectionRef.withdraw(withdrawID: 1)
もちろん、このようなパブリックな権限付き参照を作成したいとは思わないでしょう。なぜなら、誰もがあなたのwithdraw関数にアクセスできることを望まないからです。権限(Entitlement)は、主に信頼できるユーザーやスマートコントラクトの小規模なサブセットとプライベートな機能(private capability)を共有するために使用されるものであり、パブリックな機能(public capability)に使用されるべきではありません。
最も重要なことは、ネットワーク上の全員がリソース上の関数にアクセスできるようにしたくない場合は、デフォルトでその関数に権限(entitlement)を設定すべきであるということです。後悔するくらいなら、安全策を取る方が良いでしょう。
Mint and Distribute Tokens
NFTを作成する方法の1つは、管理者(Admin)が新しいトークンを発行(mint)し、それをユーザーに送信することです。学習を目的として、ここでは単純に発行(minting)をパブリック関数として実装します。通常は、NFT Minterリソースを使用して制限付きの発行(restricted minting)を実装するのが一般的です。このリソースの所有者(the owner of this resource)がトークンを発行(mint)できる唯一の存在であるため、発行が制限されます。
この例は、Marketplaceチュートリアルでご覧いただけます。
ⓘ ACTION
Mint NFT
という名前のファイルを開きます。アカウント0x06
を唯一の署名者として選択し、トランザクションを送信(send)します。
このトランザクションにより、アカウント所有者のNFTコレクションにNFTが発行されます。
import ExampleNFT from 0x06
/* This transaction allows the Minter account to mint an NFT
and deposit it into its own collection. */
transaction {
/* The reference to the collection that will be receiving the NFT */
let receiverRef: &ExampleNFT.Collection
prepare(acct: auth(BorrowValue) &Account) {
/* Get the owner's collection capability and borrow a reference */
self.receiverRef = acct.capabilities
.borrow<&ExampleNFT.Collection>(ExampleNFT.CollectionPublicPath)
?? panic("Could not borrow a collection reference to 0x06's ExampleNFT.Collection"
.concat(" from the path ")
.concat(ExampleNFT.CollectionPublicPath.toString())
.concat(". Make sure account 0x06 has set up its account ")
.concat("with an ExampleNFT Collection."))
}
execute {
/* Use the minter reference to mint an NFT, which deposits
the NFT into the collection that is sent as a parameter. */
let newNFT <- ExampleNFT.mintNFT()
self.receiverRef.deposit(token: <-newNFT)
log("NFT Minted and deposited to Account 0x06's Collection")
}
}
ⓘ ACTION
Print 0x06 NFTs
を再び開き、スクリプトを実行します。これにより、アカウント0x06
が所有するNFTのリストが表示されます。
import ExampleNFT from 0x06
/* Print the NFTs owned by account 0x06. */
access(all) fun main(): [UInt64] {
/* Get the public account object for account 0x06 */
let nftOwner = getAccount(0x06)
/* Find the public Receiver capability for their Collection */
let capability = nftOwner.capabilities.get<&ExampleNFT.Collection>(ExampleNFT.CollectionPublicPath)
/* borrow a reference from the capability */
let receiverRef = capability.borrow()
?? panic("Could not borrow a receiver reference to 0x06's ExampleNFT.Collection"
.concat(" from the path ")
.concat(ExampleNFT.CollectionPublicPath.toString())
.concat(". Make sure account 0x06 has set up its account ")
.concat("with an ExampleNFT Collection."))
/* Log the NFTs that they own as an array of IDs */
log("Account 0x06 NFTs")
return receiverRef.getIDs()
}
そのアカウント0x06
はid = 1
のNFTを所有していることがわかるでしょう。
"Account 0x06 NFTs"
[1]
Transferring an NFT
NFTを他のアカウントに転送する前に、そのアカウントを独自のNFTCollectionで設定し、NFTを受け取れるようにする必要があります。
ⓘ ACTION
Setup Account
という名前のファイルを開き、アカウント0x07
を唯一の署名者として使用して、トランザクションを送信します。
import ExampleNFT from 0x06
/* This transaction configures a user's account
to use the NFT contract by creating a new empty collection,
storing it in their account storage, and publishing a capability */
transaction {
prepare(acct: auth(SaveValue, Capabilities) &Account) {
/* Create a new empty collection */
let collection <- ExampleNFT.createEmptyCollection()
/* store the empty NFT Collection in account storage */
acct.storage.save(<-collection, to: ExampleNFT.CollectionStoragePath)
log("Collection created for account 0x07")
/* create a public capability for the Collection */
let cap = acct.capabilities.storage.issue<&ExampleNFT.Collection>(ExampleNFT.CollectionStoragePath)
acct.capabilities.publish(cap, at: ExampleNFT.CollectionPublicPath)
log("Capability created")
}
}
アカウント0x07
は、アカウントストレージに空のCollection
リソースが格納されているはずです。また、/public/
ドメインにコレクションの機能(Capability)を作成して格納しました。
ⓘ ACTION
Transfer
という名前のファイルを開き、アカウント0x06
を唯一の署名者として選択し、トランザクションを送信します。
このトランザクションは、アカウント0x06
からアカウント0x07
にトークンを転送します。
import ExampleNFT from 0x06
/* This transaction transfers an NFT from one user's collection
to another user's collection. */
transaction {
/* The field that will hold the NFT as it is being
transferred to the other account */
let transferToken: @ExampleNFT.NFT
prepare(acct: auth(BorrowValue) &Account) {
/* Borrow a reference from the stored collection */
let collectionRef = acct.storage
.borrow<auth(ExampleNFT.Withdraw) &ExampleNFT.Collection>(from: ExampleNFT.CollectionStoragePath)
?? panic("Could not borrow a collection reference to 0x06's ExampleNFT.Collection"
.concat(" from the path ")
.concat(ExampleNFT.CollectionPublicPath.toString())
.concat(". Make sure account 0x06 has set up its account ")
.concat("with an ExampleNFT Collection."))
/* Call the withdraw function on the sender's Collection
to move the NFT out of the collection */
self.transferToken <- collectionRef.withdraw(withdrawID: 1)
}
execute {
/* Get the recipient's public account object */
let recipient = getAccount(0x07)
/* Get the Collection reference for the receiver
getting the public capability and borrowing a reference from it */
let receiverRef = recipient.capabilities
.borrow<&ExampleNFT.Collection>(ExampleNFT.CollectionPublicPath)
?? panic("Could not borrow a collection reference to 0x07's ExampleNFT.Collection"
.concat(" from the path ")
.concat(ExampleNFT.CollectionPublicPath.toString())
.concat(". Make sure account 0x07 has set up its account ")
.concat("with an ExampleNFT Collection."))
/* Deposit the NFT in the receivers collection */
receiverRef.deposit(token: <-self.transferToken)
log("NFT ID 1 transferred from account 0x06 to account 0x07")
}
}
CollectionとCapabilityを使用することで、トークンを転送するトランザクションに署名する必要があるのは、トークンを送信する側のアカウントだけです。
次に、両方のアカウントのコレクションをチェックして、アカウント0x07
がトークンを所有しており、アカウント0x06
が何も所有していないことを確認します。
ⓘ ACTION
Print all NFTs
スクリプトを実行(Execute)して、各アカウントのトークンを確認します。
import ExampleNFT from 0x06
/* Print the NFTs owned by accounts 0x06 and 0x07. */
access(all) fun main() {
/* Get both public account objects */
let account6 = getAccount(0x06)
let account7 = getAccount(0x07)
/* Find the public Receiver capability for their Collections */
let acct6Capability = account6.capabilities.get<&ExampleNFT.Collection>(ExampleNFT.CollectionPublicPath)
let acct7Capability = account7.capabilities.get<&ExampleNFT.Collection>(ExampleNFT.CollectionPublicPath)
/* borrow references from the capabilities */
let receiver6Ref = acct6Capability.borrow()
?? panic("Could not borrow a collection reference to 0x06's ExampleNFT.Collection"
.concat(" from the path ")
.concat(ExampleNFT.CollectionPublicPath.toString())
.concat(". Make sure account 0x06 has set up its account ")
.concat("with an ExampleNFT Collection."))
let receiver7Ref = acct7Capability.borrow()
?? panic("Could not borrow a collection reference to 0x07's ExampleNFT.Collection"
.concat(" from the path ")
.concat(ExampleNFT.CollectionPublicPath.toString())
.concat(". Make sure account 0x07 has set up its account ")
.concat("with an ExampleNFT Collection."))
/* Print both collections as arrays of IDs */
log("Account 0x06 NFTs")
log(receiver6Ref.getIDs())
log("Account 0x07 NFTs")
log(receiver7Ref.getIDs())
}
出力には以下のような内容が表示されるはずです。
"Account 0x06 NFTs"
[]
"Account 0x07 NFTs"
[1]
アカウント0x07
にはID=1のNFTが1つあり、アカウント0x06
にはありません。これは、NFTがアカウント0x06
からアカウント0x07
に転送されたことを示しています。
Putting It All Together
これは、Flow上でNFTがどのように機能する可能性があるかについての基本的な例にすぎません。Flow NFTスタンダードと、実際のNFTスマートコントラクトの実装方法については、Flow NFT Standard repoおよびNFT Developer Guideを参照してください。
Fungible Tokens
NFTが使えるようになったので、おそらくそれを取引できるようになりたいと思うでしょう。そのためには、Flow上でファンジブルトークンがどのように機能するかを理解する必要があります。次のチュートリアルに進みましょう!
翻訳元
Previous << 5.1 Non-Fungible Token Tutorial Part 1
Flow BlockchainのCadence version1.0ドキュメント (5.2 Non-Fungible Token Tutorial Part 2)