8. Marketplace

[Previous << 7. Marketplace Setup](https://qiita.com/flowcadence/items/20c399a90a9cd9eb5250)
[Next >> 9. Voting Contract](https://qiita.com/flowcadence/items/284a96590784650acc3c)

https://qiita.com/flowcadence/items/3073d69c6e9895e68b5a#cadenceの言語仕様を学ぶ

このチュートリアルでは、以前のチュートリアルで学んだファンジブルトークン（FT）とノンファンジブルトークン（NFT）の両方のスマートコントラクトを使用するマーケットプレイスを作成します。これは教育目的のみであり、本番環境で使用することを目的としたものではありません。本番環境対応のマーケットプレイスについては、[NFT storefrontのリポジトリ](https://github.com/onflow/nft-storefront)を参照してください。このスマートコントラクトはすでにテストネットとメインネットにデプロイされており、誰でも一般的なNFT販売に利用できます！

:::note
Action
このチュートリアル用のスターターコードをFlow Playgroundで開きます。
https://play.flow.com/7355d51c-066b-46be-adab-a3da6c28b645
チュートリアルでは、このコードとやりとりするためにさまざまなアクションを取るよう求められます。[マーケットプレイスのセットアップガイド](https://qiita.com/flowcadence/items/20c399a90a9cd9eb5250)では、このチュートリアルを行うために必要なPlaygroundのセットアップ方法が説明されています。
:::
:::note 
Action
ユーザーにアクションを求める指示は、常にこのような吹き出しボックス内に記載されています。ハイライトされたアクションは、コードを実行するために必要なすべてですが、言語の設計を理解するには、残りの部分を読むことも必要です。
:::
マーケットプレイスは、ブロックチェーン技術とスマートコントラクトの一般的な応用例です。NFTが存在する場合、ユーザーは通常、交換可能なトークンでそれらを売買できるようにしたいと考えます。

可換トークンと非可換トークンの両方の例がある今、両方を使用するマーケットプレイスを構築することができます。これは**コンポーザビリティ**と呼ばれ、開発者がコードやユーザーベースなどの共有リソースを活用し、新しいアプリケーションの構築ブロック(building block)として使用できることを意味します。

Flowは、インタフェース(interface)、リソース(resource)、Capabilityの設計される方法により、コンポーザビリティを可能にするように設計されています。
* [Interface](https://qiita.com/flowcadence/items/765a76e4fac1220ea7d9)は、インタフェースによって指定された標準的な機能セットをサポートする限り、あらゆる汎用タイプ(generic type)をサポートするプロジェクトを可能にします。
* [Resource](https://qiita.com/flowcadence/items/f9b95df70c80a90d3a20)は、アカウント、スマートコントラクト、さらには他のリソースによって所有され、転送されることが可能であり、リソースが保存されている場所に応じて、さまざまなユースケースに対応します。
* [Capability](https://qiita.com/flowcadence/items/85e1aaf7e41d1134fa6c)は、Cadenceのタイプシステムで厳格なセキュリティを確保する特別なオブジェクトを介して、ユーザー定義の機能セットを公開することを可能にします。

これらの組み合わせにより、開発者は少ない労力で多くのことを行うことができ、既知の安全なコードやデザインパターンを再利用して、新しい、強力でユニークなインタラクションを作成することができます。

:::note 
Action
このチュートリアルの前後に、Interface、Resource、およびCapabilityに関する上記の公式ドキュメントを必ず確認してください。これにより、複雑ですが強力なこれらの機能の理解を深めることができます。
:::
マーケットプレイスを作成するには、ファンジブルトークンとノンファンジブルトークンの両方の機能を、ユーザーがお金や資産を管理できる単一のスマートコントラクトに統合する必要があります。これを達成するために、以下の手順に従って、組み立て可能なスマートコントラクトを作成し、マーケットプレイスに慣れていきましょう。

1. ファンジブルトークンとノンファンジブルトークンのスマートコントラクトが正しくデプロイされ、セットアップされていることを確認します。
2. マーケットプレイスの型の宣言をアカウント`0x08`に対してデプロイします。
3. マーケットプレイスオブジェクトを作成し、アカウントストレージに保存します。NFTを販売用に公開し、販売するためのパブリックCapabilityを公開します。
4. 別のアカウントを使用して、販売されているものからNFTを購入します。
5. スクリプトを実行して、NFTが購入されたことを確認します。

**このチュートリアルを進める前に**、このスマートコントラクトの構成要素を理解するために、[Fungible Tokens](https://qiita.com/flowcadence/items/d4f98c76d5f88d447345)と[Non-Fungible Token](https://qiita.com/flowcadence/items/ee22ed2ce344620c79da)のチュートリアルを完了する必要があります。

<h1 id="Marketplace-Design">Marketplace Design</h1>

マーケットプレイスを実装する一つの方法は、ユーザーがNFTと価格を預ける中央のスマートコントラクトを用意し、誰もがそのトークンをその価格で購入できるようにすることです。このアプローチは合理的ですが、プロセスが中央集権化され、所有者の選択肢が奪われてしまいます。私たちは、ユーザーがNFTを売却しようとしている間、そのNFTの所有権を維持できるようにしたいと考えています。

この中央集権的なアプローチを取らずに、各ユーザーは自身のアカウントからNFTを出品することができます。

次に、ユーザーは販売のリンクを、ウェブサイト上で一元的に出品NFTをリストアップできるアプリケーションに提供するか、または取引全体をオンチェーンに維持したい場合は、中央販売集約型スマートコントラクトに提供することができます。このようにして、トークンの所有者は、販売中もトークンの管理を維持することができます。

:::note
Action
開始する前に、あなたのAccountの状態を確認する必要があります。
まだ実行していない場合は、[marketplace setup guide](https://qiita.com/flowcadence/items/20c399a90a9cd9eb5250)を実行し、Fungible Token（ファンジブルトークン）とNon-Fungible Token（ノンファンジブルトークン）のスマートコントラクトがアカウント6と2にデプロイされ、トークンがいくつか所有されていることを確認してください。
あなたのアカウントは次のようになっているはずです。
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3912821/fa9e0fad-1680-cde8-410b-1560c4b24238.png)

:::note 
ACTION
`1. Check Setup`スクリプトを実行して、アカウントが正しく設定されていることを確認します。
:::
```ruby:CheckSetupScript.cdc
/* CheckSetupScript.cdc */

import ExampleToken from 0x06
import ExampleNFT from 0x07

/** Allows the script to return the ownership info
    of all the accounts */
access(all) struct OwnerInfo {
  access(all) let acct6Balance: UFix64
  access(all) let acct7Balance: UFix64

  access(all) let acct6IDs: [UInt64]
  access(all) let acct7IDs: [UInt64]

  init(balance1: UFix64, balance2: UFix64, acct6IDs: [UInt64], acct7IDs: [UInt64]) {
    self.acct6Balance = balance1
    self.acct7Balance = balance2
    self.acct6IDs = acct6IDs
    self.acct7IDs = acct7IDs
  }
}

/* This script checks that the accounts are set up correctly for the marketplace tutorial.

   Account 0x06: Vault Balance = 40, NFT.id = 1
   Account 0x07: Vault Balance = 20, No NFTs */
access(all) fun main(): OwnerInfo {
    /* Get the accounts' public account objects */
    let acct6 = getAccount(0x06)
    let acct7 = getAccount(0x07)

    /* Get references to the account's receivers
       by getting their public capability
       and borrowing a reference from the capability */
    let acct6ReceiverRef = acct6.capabilities.get<&{ExampleToken.Balance}>
                          (/public/CadenceFungibleTokenTutorialReceiver)
                          .borrow()
            ?? panic("Could not borrow a balance reference to "
                     .concat("0x06's ExampleToken.Vault")
                     .concat(". Make sure 0x06 has set up its account ")
                     .concat("with an ExampleToken Vault and valid capability."))

    let acct7ReceiverRef = acct7.capabilities.get<&{ExampleToken.Balance}>
                          (/public/CadenceFungibleTokenTutorialReceiver)
                          .borrow()
            ?? panic("Could not borrow a balance reference to "
                     .concat("0x07's ExampleToken.Vault")
                     .concat(". Make sure 0x07 has set up its account ")
                     .concat("with an ExampleToken Vault and valid capability."))

    let returnArray: [UFix64] = []

    /* verify that the balances are correct */
    if acct6ReceiverRef.balance != 40.0 || acct7ReceiverRef.balance != 20.0 {
        panic("Wrong balances!")
    }

    /* Find the public Receiver capability for their Collections */
    let acct6Capability = acct6.capabilities.get<&{ExampleNFT.NFTReceiver}>(ExampleNFT.CollectionPublicPath)
    let acct7Capability = acct7.capabilities.get<&{ExampleNFT.NFTReceiver}>(ExampleNFT.CollectionPublicPath)

    /* borrow references from the capabilities */
    let nft1Ref = 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 nft2Ref = 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."))

    /* verify that the collections are correct */
    if nft1Ref.getIDs()[0] != 1 || nft2Ref.getIDs().length != 0 {
        panic("Wrong Collections!")
    }

    /* Return the struct that shows the account ownership info */
    return OwnerInfo(balance1: acct6ReceiverRef.balance,
                     balance2: acct7ReceiverRef.balance,
                     acct6IDs: nft1Ref.getIDs(),
                     acct7IDs: nft2Ref.getIDs())
}
```
アカウントが正しく設定されていれば、以下のような出力結果が表示されるはずです。これは、[Fungible Tokens](https://qiita.com/flowcadence/items/d4f98c76d5f88d447345)と[Non-Fungible Tokens](https://qiita.com/flowcadence/items/ee22ed2ce344620c79da)のチュートリアルを連続して実行した場合と同じ状態です。
```
"Account 6 Balance"
40.00000000
"Account 7 Balance"
20.00000000
"Account 6 NFTs"
[1]
"Account 7 NFTs"
[]
```
アカウントの状態が適切になったので、アカウント間でNFTの販売を可能にするマーケットプレイスを構築することができます。

<h1 id="Setting-up-an-NFT-Marketplace">Setting up an NFT Marketplace</h1>

NFTを販売したいすべてのユーザーは、`@SaleCollection`リソースのインスタンスをアカウントストレージに保存します。

さぁマーケットプレイススマートコントラクトをデプロイする時間です。

:::note 
ACTION
1 . ExampleMarketplace コントラクト（Contract3）に切り替えます。
2 . `ExampleMarketplace.cdc`を開き、右下のデプロイモーダルからアカウント`0x08`を選択し、デプロイします。
:::
`ExampleMarketplace.cdc`には以下のコントラクト定義が含まれているはずです。
```ruby:ExampleMarketplace.cdc
import ExampleToken from 0x06
import ExampleNFT from 0x07

/* ExampleMarketplace.cdc
  
   The ExampleMarketplace contract is a very basic sample implementation of an NFT ExampleMarketplace on Flow.

   This contract allows users to put their NFTs up for sale. Other users
   can purchase these NFTs with fungible tokens.

   Learn more about marketplaces in this tutorial: https:\/\/developers.flow.com\/cadence\/tutorial\/marketplace-compose

   This contract is a learning tool and is not meant to be used in production.
   See the NFTStorefront contract for a generic marketplace smart contract that
   is used by many different projects on the Flow blockchain:

   https:\/\/github.com\/onflow\/nft-storefront
*/
access(all) contract ExampleMarketplace {

    /* Event that is emitted when a new NFT is put up for sale */
    access(all) event ForSale(id: UInt64, price: UFix64, owner: Address?)

    /* Event that is emitted when the price of an NFT changes */
    access(all) event PriceChanged(id: UInt64, newPrice: UFix64, owner: Address?)

    /* Event that is emitted when a token is purchased */
    access(all) event TokenPurchased(id: UInt64, price: UFix64, seller: Address?, buyer: Address?)

    /* Event that is emitted when a seller withdraws their NFT from the sale */
    access(all) event SaleCanceled(id: UInt64, seller: Address?)

    access(all) entitlement Owner

    /* SaleCollection
    
       NFT Collection object that allows a user to put their NFT up for sale
       where others can send fungible tokens to purchase it
    */
    access(all) resource SaleCollection  {

        /** A capability for the owner's collection */
        access(self) var ownerCollection: Capability<auth(ExampleNFT.Withdraw) &ExampleNFT.Collection>

        /* Dictionary of the prices for each NFT by ID */
        access(self) var prices: {UInt64: UFix64}

        /* The fungible token vault of the owner of this sale.
           When someone buys a token, this resource can deposit
           tokens into their account. */
        access(account) let ownerVault: Capability<&{ExampleToken.Receiver}>

        init (ownerCollection: Capability<auth(ExampleNFT.Withdraw) &ExampleNFT.Collection>,
              ownerVault: Capability<&{ExampleToken.Receiver}>) {

            pre {
                /* Check that the owner's collection capability is correct */
                ownerCollection.check():
                    "ExampleMarketplace.SaleCollection.init: "
                    .concat("Owner's NFT Collection Capability is invalid! ")
                    .concat("Make sure the owner has set up an `ExampleNFT.Collection` ")
                    .concat("in their account and provided a valid capability")

                /* Check that the fungible token vault capability is correct */
                ownerVault.check():
                    "ExampleMarketplace.SaleCollection.init: "
                    .concat("Owner's Receiver Capability is invalid! ")
                    .concat("Make sure the owner has set up an `ExampleToken.Vault` ")
                    .concat("in their account and provided a valid capability")
            }
            self.ownerCollection = ownerCollection
            self.ownerVault = ownerVault
            self.prices = {}
        }

        /* cancelSale gives the owner the opportunity to cancel a sale in the collection */
        access(Owner) fun cancelSale(tokenID: UInt64) {
            /* remove the price */
            self.prices.remove(key: tokenID)
            self.prices[tokenID] = nil

            /* Nothing needs to be done with the actual token because it is already in the owner's collection */
        }

        /* listForSale lists an NFT for sale in this collection */
        access(Owner) fun listForSale(tokenID: UInt64, price: UFix64) {
            pre {
                self.ownerCollection.borrow()!.idExists(id: tokenID):
                    "ExampleMarketplace.SaleCollection.listForSale: "
                    .concat("Cannot list token ID ").concat(tokenID.toString())
                    .concat(" . This NFT ID is not owned by the seller.")
                    .concat("Make sure an ID exists in the sellers NFT Collection")
                    .concat(" before trying to list it for sale")
            }
            /* store the price in the price array */
            self.prices[tokenID] = price

            emit ForSale(id: tokenID, price: price, owner: self.owner?.address)
        }

        /* changePrice changes the price of a token that is currently for sale */
        access(Owner) fun changePrice(tokenID: UInt64, newPrice: UFix64) {
            self.prices[tokenID] = newPrice

            emit PriceChanged(id: tokenID, newPrice: newPrice, owner: self.owner?.address)
        }

        /* purchase lets a user send tokens to purchase an NFT that is for sale */
        access(all) fun purchase(tokenID: UInt64,
                                 recipient: Capability<&ExampleNFT.Collection>, buyTokens: @ExampleToken.Vault) {
            pre {
                self.prices[tokenID] != nil:
                    "ExampleMarketplace.SaleCollection.purchase: "
                    .concat("Cannot purchase NFT with ID ")
                    .concat(tokenID.toString())
                    .concat(" There is not an NFT with this ID available for sale! ")
                    .concat("Make sure the ID to purchase is correct.")
                buyTokens.balance >= (self.prices[tokenID] ?? 0.0):
                    "ExampleMarketplace.SaleCollection.purchase: "
                    .concat(" Cannot purchase NFT with ID ")
                    .concat(tokenID.toString())
                    .concat(" The amount provided to purchase (")
                    .concat(buyTokens.balance.toString())
                    .concat(") is less than the price of the NFT (")
                    .concat(self.prices[tokenID]!.toString())
                    .concat("). Make sure the ID to purchase is correct and ")
                    .concat("the correct amount of tokens have been used to purchase.")
                recipient.borrow != nil:
                    "ExampleMarketplace.SaleCollection.purchase: "
                    .concat(" Cannot purchase NFT with ID ")
                    .concat(tokenID.toString())
                    .concat(". The buyer's NFT Collection Capability is invalid.")
            }

            /* get the value out of the optional */
            let price = self.prices[tokenID]!

            self.prices[tokenID] = nil

            let vaultRef = self.ownerVault.borrow()
                ?? panic("Could not borrow reference to owner token vault")

            /* deposit the purchasing tokens into the owners vault */
            vaultRef.deposit(from: <-buyTokens)

            /* borrow a reference to the object that the receiver capability links to
               We can force-cast the result here because it has already been checked in the pre-conditions */
            let receiverReference = recipient.borrow()!

            /* deposit the NFT into the buyers collection */
            receiverReference.deposit(token: <-self.ownerCollection.borrow()!.withdraw(withdrawID: tokenID))

            emit TokenPurchased(id: tokenID, price: price, seller: self.owner?.address, buyer: receiverReference.owner?.address)
        }

        /* idPrice returns the price of a specific token in the sale */
        access(all) view fun idPrice(tokenID: UInt64): UFix64? {
            return self.prices[tokenID]
        }

        /* getIDs returns an array of token IDs that are for sale */
        access(all) view fun getIDs(): [UInt64] {
            return self.prices.keys
        }
    }

    /* createCollection returns a new collection resource to the caller */
    access(all) fun createSaleCollection(
        ownerCollection: Capability<auth(ExampleNFT.Withdraw) &ExampleNFT.Collection>,
        ownerVault: Capability<&{ExampleToken.Receiver}>
    ): @SaleCollection
    {
        return <- create SaleCollection(ownerCollection: ownerCollection, ownerVault: ownerVault)
    }
}
```
このマーケットプレイススマートコントラクトには、[Non-Fungible Tokens](https://qiita.com/flowcadence/items/ee22ed2ce344620c79da)で説明したNFT`Collection`と類似した機能を持つリソースがあります。ただし、いくつかの違いと追加があります。
* このマーケットプレイススマートコントラクトには、NFTを追加および削除するメソッドがありますが、NFTリソースオブジェクトを販売コレクションに保存するのではなく、出品されたNFTが購入された際に引き出され、転送されるように、メインのコレクションにユーザーはCapabilityを渡します。ユーザーがNFTを販売したい場合、`listForSale()`関数にIDと価格を指定して販売します。次に、別のユーザーが`purchase()`関数を呼び出し、購入に使用する通貨が含まれている`ExampleToken.Vault`を送信します。購入時に購入したトークンを自分のコレクションに即座に預け入れることができるように、購入者はNFTに`ExampleNFT.Collection`のCapabilityも追加します。
* このマーケットプレイススマートコントラクトには、`access(all) let ownerVault: Capability<&{FungibleToken.Receiver}>`というCapabilityが保存されます。販売者はFungible Token `Receiver`に対するCapabilityをsale内に保存します。これにより、saleリソースは購入が行われた際に、NFTの購入に使用された通貨を所有者の`Vault`に即座に預け入れることができます。
* このマーケットプレイススマートコントラクトにはイベントが含まれています。Cadenceは、重要なアクションが発生した際に発行できるスマートコントラクト内のイベントの定義をサポートしています。外部アプリは、スマートコントラクトの状態(state)を知るために、これらのイベントを監視することができます。
```ruby
    /* Event that is emitted when a new NFT is put up for sale */
    access(all) event ForSale(id: UInt64, price: UFix64, owner: Address?)

    /* Event that is emitted when the price of an NFT changes */
    access(all) event PriceChanged(id: UInt64, newPrice: UFix64, owner: Address?)

    /* Event that is emitted when a token is purchased */
    access(all) event TokenPurchased(id: UInt64, price: UFix64, seller: Address?, buyer: Address?)

    /* Event that is emitted when a seller withdraws their NFT from the sale */
    access(all) event SaleCanceled(id: UInt64, seller: Address?)
```
このスマートコントラクトには、カバーすべき重要な新しい機能や概念がいくつかあります。

<h1 id="Events">Events</h1>

[Events](https://qiita.com/flowcadence/items/07a1cd2e6ca15272900f)は、プログラムの実行中に発せられる特別な値です。通常は、スマートコントラクトで重要なアクションが起こったことを示す情報、例えばNFTの移転、許可の変更、その他さまざまなことが含まれています。オフチェーンアプリケーションは、Flow SDKを使用してイベントを監視し、スマートコントラクトに直接問い合わせることなく、オンチェーンで何が起こっているかを知ることができます。

多くのアプリケーションでは、ユーザーのアカウントに関する情報を取得したり、分析したりする際にパフォーマンスを向上させるため、オンチェーンで発生していることをオフチェーンで記録しておきたいと考えています。

イベントは、関数の宣言のように[Access Level](https://qiita.com/flowcadence/items/572df270135946f5290e)、`event`、イベント名とパラメータを指定して宣言します。
```ruby
access(all) event ForSale(id: UInt64, price: UFix64, owner: Address?)
```
Eventsはステートを一切変更できません。スマートコントラクトで重要なアクションがいつ発生したかを指し示します。

Eventsは、あたかも関数呼び出しであるかのように`emit`キーワードと、その後のイベントの呼び出し部分で発せされます。
```ruby
emit ForSale(id: tokenID, price: price, owner: self.owner?.address)
```
外部アプリケーションはブロックチェーンを監視し、特定のイベントが発せられた際にアクションを起こすことができます。

<h1 id="Resource-Owned-Capabilities">Resource-Owned Capabilities</h1>

[以前のチュートリアル](https://qiita.com/flowcadence/items/87b1cc1ff58417dc086d)では、Capabilityについて取り上げましたが、基本的なことだけでした。Capabilityは、もっと多くのことに使用できます！

ご理解いただけたと思いますが、[Capability](https://qiita.com/flowcadence/items/b70151356f4e548f2c91)とは、アカウントストレージ内のプライベートオブジェクトへのリンクであり、リンク先のリソースのサブセットを指定し、公開するものです。

Capabilityを作成するには、ユーザーは通常、[`account.capabilities.storage.issue`](https://qiita.com/flowcadence/items/5ddbb79fe8438850cf57)メソッドを使用して、プライベートストレージ内のリソースへのリンクを作成し、リンク先のCapabilityのタイプを次のように指定します。
```ruby
let cap = acct.capabilities.storage.issue<&ExampleNFT.Collection>(ExampleNFT.CollectionStoragePath)
```
その後、所有者はアカウント内のパブリックパス(public path)にそのCapabilityを保存することができます。
```ruby
acct.capabilities.publish(cap, at: ExampleNFT.CollectionPublicPath)
```
その後、ユーザーはパブリックパス（[Public Path](https://qiita.com/flowcadence/items/9767ec3f9b8d757d75bf)）からそのCapabilityを取得し、借用し、所有者が指定した機能にアクセスすることができます。
```ruby
/* Get the account object for address 0x06 */
let publicAccount = getAccount(0x06)

/* Retrieve a Vault Receiver Capability from the account's public storage */
let acct6Capability = acct.capabilities.get<&{ExampleToken.Receiver}>(
        ExampleToken.VaultPublicPath
    )

/* Borrow a reference */
let acct6ReceiverRef = acct6Capability.borrow()
    ?? panic("Account 0x06's Receiver Capability is invalid! ")
            .concat("Make sure the owner has set up an `ExampleToken.Vault` ")
            .concat("in their account and provided a valid capability")

/* Deposit tokens */
acct6ReceiverRef.deposit(from: <-tokens)
```
マーケットプレイス スマートコントラクトにより、私たちはCapabilityの新しい機能を活用しています。Capabilityはどこにでも保存できます！多くの機能はリソース内に含まれており、開発者は異なるリソースやスマートコントラクト内からリソースの機能の一部にアクセスできるようにしたいと思うことがあります。

マーケットプレイス販売コレクションには、2つの異なるcapabilityが保存されています。
```ruby
/** A capability for the owner's collection */
access(self) var ownerCollection: Capability<auth(ExampleNFT.Withdraw) &ExampleNFT.Collection>

/* The fungible token vault of the owner of this sale.
   When someone buys a token, this resource can deposit
   tokens into their account. */
access(account) let ownerVault: Capability<&{ExampleToken.Receiver}>
```
スマートコントラクトやリソースのようなオブジェクトがCapabilityを保有している場合、そのオブジェクトはいつでもそのCapabilityへの参照を借りる(borrow)ことができ、その機能にアクセスする際に、その都度所有者のアカウントから取得する必要がなくなります。

これは、所有者がある機能を1人のユーザーのみに公開したい場合、つまり、Capabilityのリンクがパブリックパス(public path)に保存されない場合に特に重要です。この例では、販売コレクションが、`ExampleNFT.Collection`の引き出し(withdraw)機能にアクセスできる機能性を保存しており、その機能性は、`ExampleNFT.Withdraw`権限でアクセスできます。これは、`purchase()`メソッドで指定のNFTを引き出して購入者に送信するために必要です。

重要なのは、Capabilityの制御がそのリソースの所有権を意味するわけではないということです。Capabilityを使用してそのリソースの機能にアクセスすることはできますが、Capabilityを使用して所有権を偽ることはできません。所有権を証明するには、実際のリソース（接頭辞付きの`@`記号で識別される）が必要です。

さらに、これらのCapabilityはどこにでも保存できますが、ユーザーがCapabilityの使用を望まなくなった場合、`getControllers` メソッドでアカウントからCapabilityのコントローラを取得し、それを無効にすることができます。また、`delete` を使用してCapabilityを削除することもできます。以下は、指定の保存パスにあるすべてのコントローラを削除する例です。
```ruby
let controllers = self.account.capabilities.storage.getControllers(forPath: storagePath)
for controller in controllers {
    controller.delete()
}
After this, any capabilities that use that storage path are rendered invalid.

One last piece to consider about capabilities is the decision about
when to use them instead of storing the resource directly.
This tutorial used to have the `SaleCollection` directly store the NFTs that were for sale, like so:

```cadence
access(all) resource SaleCollection {

    /// Dictionary of NFT objects for sale
    /// Maps ID to NFT resource object
    /// Not recommended
    access(self) var forSale: @{UInt64: ExampleNFT.NFT}
}
```

これは論理的方法であり、Cadenceにおけるもう一つの重要な概念、つまりリソースが他のリソースを所有できることを示しています。この概念についてさらに詳しく知りたい場合は、[Kitty Hatsチュートリアル](https://qiita.com/flowcadence/items/729d4317f64df7882783)を参照してください。

ただし、このケースではリソースのネストは意味がありません。ユーザーが販売用NFTをmainコレクションとは別の場所に保存することを選択した場合、それらのNFTはmainコレクションに問い合わせを行うアプリやスマートコントラクトには表示されないため、あたかも所有者がNFTを実際に所有していないかのように見えます。

このような場合、mainコレクションにCapabilityを使用することを通常は推奨します。これにより、mainコレクションは変更されず、他のスマートコントラクトやアプリによって完全に使用可能になります。また、販売用NFTが購入以外の手段で転送された場合、古い出品リストを削除する方法が必要になります。ただし、これはこのチュートリアルの範囲外です。

説明は十分ですね！コードを実行してみよう！

<h1 id="Using-the-Marketplace">Using the Marketplace</h1>

この時点で、両方のアカウントのストレージに`ExampleToken.Vault`と`Example.NFT.Collection`があるはずです。アカウント`0x06`にはコレクションにNFTがあり、`ExampleMarketplace`スマートコントラクトは`0x08`にデプロイされているはずです。

`SaleCollection`を作成し、アカウント`0x06`のトークンを販売用に出品するには、以下の手順に従います。

:::note 
ACTION
1 . Transaction 4 `Create Sale`を開き、
2 . アカウント`0x06`を唯一の署名者として選択し、`Send`ボタンをクリックしてトランザクションを送信します。
:::
```ruby:Transaction4.cdc
/* CreateSale.cdc */

import ExampleToken from 0x06
import ExampleNFT from 0x07
import ExampleMarketplace from 0x08

/* This transaction creates a new Sale Collection object,
   lists an NFT for sale, puts it in account storage,
   and creates a public capability to the sale so that others can buy the token. */
transaction {

    prepare(acct: auth(SaveValue, StorageCapabilities) &Account) {

        /* Borrow a reference to the stored Vault */
        let receiver = acct.capabilities.get<&{ExampleToken.Receiver}>(ExampleToken.VaultPublicPath)

        /* Create an entitled capability to the NFT Collection */
        let collectionCapability = acct.capabilities.storage.issue
                                   <auth(ExampleNFT.Withdraw) &ExampleNFT.Collection>
                                   (ExampleNFT.CollectionStoragePath)

        /* Create a new Sale object,
           initializing it with the reference to the owner's vault */
        let sale <- ExampleMarketplace.createSaleCollection(ownerCollection: collectionCapability, ownerVault: receiver)

        /* List the token for sale by moving it into the sale object */
        sale.listForSale(tokenID: 1, price: 10.0)

        /* Store the sale object in the account storage */
        acct.storage.save(<-sale, to: /storage/NFTSale)

        /* Create a public capability to the sale so that others can call its methods */
        acct.capabilities.storage.issue<&ExampleMarketplace.SaleCollection>(/public/NFTSale, target: /storage/NFTSale)

        log("Sale Created for account 6. Selling NFT 1 for 10 tokens")
    }
}
```
このトランザクションは、
1. 所有者の`Vault`に`Receiver`Capabilityを持たせます。
2. 所有者のプライベートな`ExampleNFT.Collection`Capabilityを作成します。
3. `SaleCollection`を作成し、その中に`NFT所有者のVault`と`ExampleNFT.Collection`Capabilityを格納します。
4. `ID = 1`のトークンを販売用に出品し、価格を10.0に設定します。
5. `SaleCollection`をアカウントストレージ内に格納し、販売中のNFTを他のユーザーが購入できるパブリックCapabilityにリンク(link)します。


Scriptを実行して、saleが正しく作成されたことを確認しましょう。
1. Script 2`GetSaleIDs.cdc`を開きます。
2. `Execute`ボタンをクリックして、アカウント`0x06`が販売しているNFTのIDとその価格を出力します。
```ruby:GetSaleIDs.cdc
/* GetSaleIDs.cdc */

import ExampleToken from 0x06
import ExampleNFT from 0x07
import ExampleMarketplace from 0x08

/* This script returns the NFTs that account 0x06 has for sale. */
access(all)
fun main(): [UInt64] {
    /* Get the public account object for account 0x06 */
    let account1 = getAccount(0x06)

    /* Find the public Sale reference to their Collection */
    let acct6saleRef = account1.capabilities.get<&ExampleMarketplace.SaleCollection>(/public/NFTSale)>
                       .borrow()
        ?? panic("Could not borrow a reference to the SaleCollection capability for account 0x06 ")
            .concat("at path /public/NFTSale. ")
            .concat("Make sure the owner has set up the SaleCollection ")
            .concat("in their account with the Create Sale transaction")

    /* Return the NFT IDs that are for sale */
    return acct6saleRef.getIDs()
}
```
このScriptを実行すると、次のような内容が完了し出力されるはずです。
```
[1]
```
<h1 id="Purchasing-an-NFT">Purchasing an NFT</h1>

購入者は、`Transaction2.cdc`のトランザクションを使用して、販売者のNFTを購入することができます。

:::note 
ACTION
1 . Transaction 5: `PurchaseSale.cdc`ファイルを開きます
2 . アカウント`0x07`を唯一の署名者として選択し、`Send`ボタンをクリック
:::
```ruby:PurchaseSale.cdc
/* PurchaseSale.cdc */

import ExampleToken from 0x06
import ExampleNFT from 0x07
import ExampleMarketplace from 0x08

/* This transaction uses the signers Vault tokens to purchase an NFT
   from the Sale collection of account 0x06. */
transaction {

    /* Capability to the buyer's NFT collection where they will store the bought NFT */
    let collectionCapability: Capability<&ExampleNFT.Collection>

    /* Vault that will hold the tokens that will be used to but the NFT */
    let temporaryVault: @ExampleToken.Vault

    prepare(acct: auth(BorrowValue) &Account) {

        /* get the references to the buyer's fungible token Vault and NFT Collection Receiver */
        self.collectionCapability = acct.capabilities.get<&ExampleNFT.Collection>(ExampleNFT.CollectionPublicPath)

        let vaultRef = acct.storage.borrow<&ExampleToken.Vault>(from: /storage/CadenceFungibleTokenTutorialVault)
            ?? panic("Could not borrow a reference to "
                     .concat("0x07's ExampleToken.Vault")
                     .concat(". Make sure 0x07 has set up its account ")
                     .concat("with an ExampleToken Vault and valid capability."))

        /* withdraw tokens from the buyers Vault */
        self.temporaryVault <- vaultRef.withdraw(amount: 10.0)
    }

    execute {
        /* get the read-only account storage of the seller */
        let seller = getAccount(0x06)

        /* get the reference to the seller's sale */
        let saleRef = seller.capabilities.get<&ExampleMarketplace.SaleCollection>(/public/NFTSale)
                            .borrow()
            ?? panic("Could not borrow a reference to "
                     .concat("0x06's ExampleMarketplace.SaleCollection")
                     .concat(". Make sure 0x06 has set up its account ")
                     .concat("with an ExampleMarketplace SaleCollection and valid capability."))

        /* purchase the NFT the seller is selling, giving them the capability
           to your NFT collection and giving them the tokens to buy it */
        saleRef.purchase(tokenID: 1, recipient: self.collectionCapability, buyTokens: <-self.temporaryVault)
    }
}
```
このトランザクションは、
1. 購入者のNFT receiverにCapabilityを付与し
2. トークンvaultへの参照(reference)を取得し、販売購入額分のトークンを引き出します。
3. アカウント`0x06`のパブリックアカウント オブジェクト(public account object)を取得します。
4. 販売者のpublic saleへの参照を取得します。
5. `purchase`関数を呼び出し、2で引き出したトークンと`Collection`参照を引数に渡します。すると`purchase`関数は直接購入者のコレクションに購入したNFTを預け入れます。
<h1 id="Verifying-the-NFT-Was-Purchased-Correctly">Verifying the NFT Was Purchased Correctly</h1>

NFTが正しく購入されたことを確認するスクリプトを今実行できます。なぜなら、
* アカウント`0x06`はトークンを50個所有しており、販売用またはcollectionとアカウントにNFTを一切所有していないためです。
* アカウント`0x07`はトークンを10個所有しており、id=1のNFTを所有しています

NFTが正しく購入されたことを確認するスクリプトを実行するには、以下の手順に従います。

:::note 
ACTION
1 . Script 3：`VerifyAfterPurchase.cdc`を開きます。
2 . `Execute`ボタンをクリックします。
:::
`VerifyAfterPurchase.cdc`には以下のコードが含まれるようにします。
```ruby:Script3.cdc
/* VerifyAfterPurchase */
import ExampleToken from 0x06
import ExampleNFT from 0x07

/** Allows the script to return the ownership info
    of all the accounts */
access(all) struct OwnerInfo {
  access(all) let acct6Balance: UFix64
  access(all) let acct7Balance: UFix64

  access(all) let acct6IDs: [UInt64]
  access(all) let acct7IDs: [UInt64]

  init(balance1: UFix64, balance2: UFix64, acct6IDs: [UInt64], acct7IDs: [UInt64]) {
    self.acct6Balance = balance1
    self.acct7Balance = balance2
    self.acct6IDs = acct6IDs
    self.acct7IDs = acct7IDs
  }
}

/* This script checks that the accounts are in the correct state after purchasing a listing.

   Account 0x06: Vault Balance = 50, No NFTs
   Account 0x07: Vault Balance = 10, NFT.id = 1 */
access(all) fun main(): OwnerInfo {
    /* Get the accounts' public account objects */
    let acct6 = getAccount(0x06)
    let acct7 = getAccount(0x07)

    /* Get references to the account's receivers
       by getting their public capability
       and borrowing a reference from the capability */
    let acct6ReceiverRef = acct6.capabilities.get<&{ExampleToken.Balance}>
                          (/public/CadenceFungibleTokenTutorialReceiver)
                          .borrow()
            ?? panic("Could not borrow a balance reference to "
                     .concat("0x06's ExampleToken.Vault")
                     .concat(". Make sure 0x06 has set up its account ")
                     .concat("with an ExampleToken Vault and valid capability."))

    let acct7ReceiverRef = acct7.capabilities.get<&{ExampleToken.Balance}>
                          (/public/CadenceFungibleTokenTutorialReceiver)
                          .borrow()
            ?? panic("Could not borrow a balance reference to "
                     .concat("0x07's ExampleToken.Vault")
                     .concat(". Make sure 0x07 has set up its account ")
                     .concat("with an ExampleToken Vault and valid capability."))

    let returnArray: [UFix64] = []

    /* verify that the balances are correct */
    if acct6ReceiverRef.balance != 50.0 || acct7ReceiverRef.balance != 10.0 {
        panic("Wrong balances! Account 6 Balance should be 50 and Account 7 balance should be 10.")
    }

    /* Find the public Receiver capability for their Collections */
    let acct6Capability = acct6.capabilities.get<&ExampleNFT.Collection>(ExampleNFT.CollectionPublicPath)
    let acct7Capability = acct7.capabilities.get<&ExampleNFT.Collection>(ExampleNFT.CollectionPublicPath)

    /* borrow references from the capabilities */
    let nft1Ref = 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 nft2Ref = 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."))

    /* verify that the collections are correct */
    if nft2Ref.getIDs()[0] != 1 || nft1Ref.getIDs().length != 0 {
        panic("Wrong Collections! Account 6 should own zero NFTs and account 7 should own one.")
    }

    /* Return the struct that shows the account ownership info */
    return OwnerInfo(balance1: acct6ReceiverRef.balance,
                     balance2: acct7ReceiverRef.balance,
                     acct6IDs: nft1Ref.getIDs(),
                     acct7IDs: nft2Ref.getIDs())
}
```
すべて正しく実行された場合、トランザクションは成功し、以下のような内容が出力されます。
```
"account 6 Vault Balance"
50
"account 7 Vault Balance"
10
"account 6 NFTs"
[]
"account 7 NFTs"
[1]
```
おめでとう、Cadenceでシンプルなマーケットプレイスを実装し、それを使用して、あるアカウントから別のアカウントにNFTを販売することができました！

<h1 id="Scaling-the-Marketplace">Scaling the Marketplace</h1>

ユーザーは、これらのリソースとトランザクションを利用して、アカウント内でセール(sale)を開催することができます。ユーザーがsaleを発見できる中央マーケットプレイスのサポートは、比較的簡単に実装でき、すでに持っているものの上に構築することができます。もしオンチェーンで中央マーケットプレイスを構築したいのであれば、次のようなスマートコントラクトを使用することになります。
```ruby:CentralMarketplace.cdc
/* Marketplace would be the central contract where people can post their sale
   references so that anyone can access them */
access(all) contract Marketplace {
    /* Data structure to store active sales */
    access(all) var tokensForSale: {Address: Capability<&SaleCollection>)}

    /* listSaleCollection lists a users sale reference in the array
       and returns the index of the sale so that users can know
       how to remove it from the marketplace */
    access(all) fun listSaleCollection(collection: Capability<&SaleCollection>) {
        let saleRef = collection.borrow()
        ?? panic("Could not borrow a reference to the SaleCollection capability ")
            .concat("Make sure the owner has set up the SaleCollection ")
            .concat("in their account and provided a valid capability")

        self.tokensForSale[saleRef.owner!.address] = collection
    }

    /* removeSaleCollection removes a user's sale from the array
       of sale references */
    access(all) fun removeSaleCollection(owner: Address) {
        self.tokensForSale[owner] = nil
    }

}
```
このスマートコントラクトは、実用または本番利用が可能なスマートコントラクトを意図したものではありませんが、以下を追加することで完全な中央マーケットプレイスに拡張することができます。
* 売り手が`SaleCollection`へのCapabilityをスマートコントラクト内への出品（保存）を行う
* 買い手が、さまざまな販売(sale)に関する情報を取得したり購入したりするために呼び出すことができる他の関数

オフチェーンアプリケーションの中央マーケットプレイスは、実装が容易です。
* アプリケーションがマーケットプレイスをホストし、ユーザーはアプリケーションにログインし、アプリケーションにアカウントアドレスを伝えるだけです。
* アプリケーションはユーザーのパブリックストレージを読み取り、販売(sale)リファレンス(reference)を見つけることができます。
* 販売(sale)リファレンスがあれば、アプリケーションはウェブサイトに販売物を表示する方法に関する必要な情報をすべて取得できます。
* 購入者はアプリ内で販売物を発見したり、自分のアカウントでアプリにログインすることができ、アプリはpublic referenceにアクセスできるようになります。
* 購入者が特定のNFTを購入したい場合、アプリは自動的に適切なトランザクションを生成し、売り手からNFTを購入します。
<h1 id="Creating-a-Marketplace-for-Any-Generic-NFT">Creating a Marketplace for Any Generic NFT</h1>

これまでの例では、特定の種類のNFTのためのシンプルなマーケットプレイスがどのように作成できるかを示しました。しかし、ユーザーは、種類に関係なく、欲しいNFTを売買できるマーケットプレイスを求めるでしょう。現在、Flowには一般的なマーケットプレイスの優れた例がいくつかあります。
* Flowチームは、[NFTストアフロントのリポジトリ](https://github.com/onflow/nft-storefront)で、完全に分散化された汎用マーケットプレイスの例を作成しました。このスマートコントラクトはすでにテストネットとメインネットにデプロイされており、汎用NFTの販売であれば誰でも利用できます！

<h1 id="Composable-Resources-on-Flow">Composable Resources on Flow</h1>

Flowにおけるコンポーザブルスマートコントラクトとマーケットプレイスの仕組みについて理解したので、コンポーザブルリソースを実際に操作してみましょう！[Kitty Hatsチュートリアル](https://cadence-lang.org/docs/tutorial/resources-compose)をチェック！

翻訳元

https://cadence-lang.org/docs/tutorial/marketplace-compose

***
<b>[Previous << 7. Marketplace Setup](https://qiita.com/flowcadence/items/20c399a90a9cd9eb5250)
</b>

[Flow blockchain / Cadence version1.0ドキュメント](https://qiita.com/flowcadence/items/3073d69c6e9895e68b5a) (8. Marketplace)

<b>[Next >> 9. Voting Contract](https://qiita.com/flowcadence/items/284a96590784650acc3c)</b>