0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

5.2 Non-Fungible Token Tutorial Part 2

Last updated at Posted at 2024-11-02

このチュートリアルでは、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があることになります。

ディクショナリ定義では通常、型指定に@記号は使用しませんがmyNFTsmapping がリソースを格納するため、フィールド全体もリソース型になる必要があります。そのため、このフィールドにはリソース型であることを示す@記号が付けられています。

つまり、リソースに適用されるすべてのルールは、この型にも適用されるということを意味します。

NFTを保存するためにディクショナリを使用すれば、NFTごとに異なる保存パスを使用しなければならないという問題は解決しますが、すべての問題が解決するわけではありません。このタイプは比較的曖昧であり、それ自体にはあまり有用な機能がありません。

その代わり、Cadenceの強力な機能である「リソースが他のリソースを所有する」という機能を使用できます。新しいCollectionリソースをNFTの保存場所として定義し、NFTとのより高度なやりとりを可能にします。

次に見るスマートコントラクトは、ExampleNFTと呼ばれ、アカウント0x06のContract 1に保存されています。

このコントラクトは、BasicNFTを拡張し、以下を追加します。

  1. 固有のNFT IDを追跡するidCountコントラクトフィールド(contractの直下のフィールド)を追加します。
  2. NFTReceiverインターフェースを追加し、コレクション用の3つのパブリック関数を定義します。
  3. NFTを直感的に保存および管理する場所としてCollectionと呼ばれるリソースを宣言し、そこにNFTReceiverインターフェースを実装します。
  4. Collectionは、ownedNFTsinit()withdraw()、その他の重要な関数などを含む、NFTとやりとりするためのフィールドや関数を宣言します。
  5. 次に、このコントラクトは新しいNFTを作成する関数(mintNFT())と空のコレクション(createEmptyCollection())を宣言します。
  6. 最後に、このコントラクトは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()
}

そのアカウント0x06id = 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に転送されたことを示しています。
image.png
これで、動作するNFTが完成しました!おめでとうございます

Putting It All Together

これは、Flow上でNFTがどのように機能する可能性があるかについての基本的な例にすぎません。Flow NFTスタンダードと、実際のNFTスマートコントラクトの実装方法については、Flow NFT Standard repoおよびNFT Developer Guideを参照してください。

Fungible Tokens

NFTが使えるようになったので、おそらくそれを取引できるようになりたいと思うでしょう。そのためには、Flow上でファンジブルトークンがどのように機能するかを理解する必要があります。次のチュートリアルに進みましょう!

翻訳元->https://cadence-lang.org/docs/tutorial/non-fungible-tokens-2

Flow BlockchainのCadence version1.0ドキュメント (5.2 Non-Fungible Token Tutorial Part 2)

Previous << 5.1 Non-Fungible Token Tutorial Part 1

Next >> 6. Fungible Token Tutorial

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?