/
onflow.org
Flow Playground

Marketplace


In this tutorial, we're going to create a marketplace that uses both the fungible and non-fungible token (NFTs) contracts that we have learned about in previous tutorials.


Open the starter code for this tutorial in the Flow Playground: https://play.onflow.org/5a5f4fdf-f499-47a4-aef7-f01713e61fde
The tutorial will be asking you to take various actions to interact with this code.

Instructions that require you to take action are always included in a callout box like this one. These highlighted actions are all that you need to do to get your code running, but reading the rest is necessary to understand the language's design.

Marketplaces are a popular application of blockchain technology and smart contracts. When there are NFTs in existence, users will want to be able to buy and sell them with their fungible tokens.

Now that there is a standard for both fungible and non-fungible tokens, we can build a marketplace that uses both. This is referred to as composability: the ability for developers to leverage shared resources, such as code or userbases, and use them as building blocks for new applications. Flow is designed to enable composability because it empowers developers to do more with less, which can lead to rapid innovation.

To create a marketplace, we need to integrate the functionality of both fungible and non-fungible tokens into a single contract that gives users control over their money and assets. To accomplish this, we're going to take you through these steps to create a composable smart contract and get comfortable with the marketplace:

  1. Ensure that your fungible token and non-fungible token contracts are deployed and set up correctly.
  2. Deploy the marketplace type declarations to account 0x03.
  3. Create a marketplace object and store it in your account storage, putting an NFT up for sale and publishing a public capability for your sale.
  4. Use a different account to purchase the NFT from the sale.
  5. Run a script to verify that the NFT was purchased.

Before proceeding with this tutorial, you need to complete the Fungible Tokens and Non-Fungible Token tutorials to understand how the building blocks of this smart contract work.

Marketplace Design


One way to implement a marketplace is to have a central smart contract that users deposit their NFTs and their price into, and have anyone come by and be able to buy the token for that price. This approach is reasonable, but it centralizes the process. We want users to be able to maintain ownership of the NFTs that they are trying to sell while they are trying to sell them.

Instead of taking this centralized approach, each user can list a sale in their account. Then, users could either provide a reference to their sale to an application that can list it centrally, or to a central sale aggregator smart contract if they want the entire transaction to stay on-chain. This way, the owner of the token keeps custody of their token while it is on sale.

Before we start, we need to confirm the state of your accounts.
If you haven't already, please perform the steps in the previous tutorial to ensure that the Fungible Token and Non-Fungible Token contracts are deployed to account 1 and 2 and own some tokens. Your accounts should look like this:

You can run the CheckSetupScript.cdc script to ensure that your accounts are correctly set up:

CheckSetupScript.cdc
import FungibleToken from 0x01
import NonFungibleToken from 0x02

// This script checks that the accounts are set up correctly for the marketplace tutorial.
//
// Account 0x01: Vault Balance = 40, NFT.id = 1
// Account 0x02: Vault Balance = 20, No NFTs
pub fun main() {
  // Get the accounts' public account objects
  let acct1 = getAccount(0x01)
  let acct2 = getAccount(0x02)

  // Get references to the account's receivers
  // by getting their public capability
  // and borrowing a reference from the capability
  let acct1ReceiverRef = acct1.getCapability(/public/MainReceiver)!
                          .borrow<&FungibleToken.Vault{FungibleToken.Balance}>()!
  let acct2ReceiverRef = acct2.getCapability(/public/MainReceiver)!
                          .borrow<&FungibleToken.Vault{FungibleToken.Balance}>()!

  // Log the Vault balance of both accounts and ensure they are
  // the correct numbers.
  // Account 0x01 should have 40.
  // Account 0x02 should have 20.
  log("Account 1 Balance")
  log(acct1ReceiverRef.balance)
  log("Account 2 Balance")
  log(acct2ReceiverRef.balance)

  // verify that the balances are correct
  if acct1ReceiverRef.balance != UFix64(40) || acct2ReceiverRef.balance != UFix64(20) {
      panic("Wrong balances!")
  }

  // Find the public Receiver capability for their Collections
  let acct1Capability = acct1.getCapability(/public/NFTReceiver)!
  let acct2Capability = acct2.getCapability(/public/NFTReceiver)!

  // borrow references from the capabilities
  let nft1Ref = acct1Capability.borrow<&{NonFungibleToken.NFTReceiver}>()!
  let nft2Ref = acct2Capability.borrow<&{NonFungibleToken.NFTReceiver}>()!

  // Print both collections as arrays of IDs
  log("Account 1 NFTs")
  log(nft1Ref.getIDs())

  log("Account 2 NFTs")
  log(nft2Ref.getIDs())

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

You should see something similar to this output if your accounts are set up correctly. They are in the same state that they would have been in if you followed the Fungible Tokens and Non-Fungible Tokens tutorials in succession:

"Account 1 Vault Balance"
40
"Account 2 Vault Balance"
20
"Account 1 NFTs"
[1]
"Account 2 NFTs"
[]

Now that your accounts are in the correct state, we can build a marketplace that enables the sale of NFT's between accounts.

Setting up an NFT Marketplace


Every user who wants to sell a NFT will store an instance of a SaleCollection resource in their account storage.

Switch to account 0x03.\nOpen Marketplace.cdc
With Marketplace.cdc open, click the Deploy button that appears at the bottom right of the editor\nMarketplace.cdc should contain the following contract definition:

Marketplace.cdc
import FungibleToken from 0x01
import NonFungibleToken from 0x02

// The Marketplace contract is a sample implementation of an NFT Marketplace on Flow.
//
// This contract allows users to put their NFTs up for sale. Other users
// can purchase these NFTs with fungible tokens.

pub contract Marketplace {

  // Event that is emitted when a new NFT is put up for sale
  pub event ForSale(id: UInt64, price: UFix64)

  // Event that is emitted when the price of an NFT changes
  pub event PriceChanged(id: UInt64, newPrice: UFix64)

  // Event that is emitted when a token is purchased
  pub event TokenPurchased(id: UInt64, price: UFix64)

  // Event that is emitted when a seller withdraws their NFT from the sale
  pub event SaleWithdrawn(id: UInt64)

  // Interface that users will publish for their Sale collection
  // that only exposes the methods that are supposed to be public
  //
  pub resource interface SalePublic {
    pub fun purchase(tokenID: UInt64, recipient: &AnyResource{NonFungibleToken.NFTReceiver}, buyTokens: @FungibleToken.Vault)
    pub fun idPrice(tokenID: UInt64): UFix64?
    pub fun getIDs(): [UInt64]
  }

  // SaleCollection
  //
  // NFT Collection object that allows a user to put their NFT up for sale
  // where others can send fungible tokens to purchase it
  //
  pub resource SaleCollection: SalePublic {

    // Dictionary of the NFTs that the user is putting up for sale
    pub var forSale: @{UInt64: NonFungibleToken.NFT}

    // Dictionary of the prices for each NFT by ID
    pub 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: &AnyResource{FungibleToken.Receiver}

    init (vault: &AnyResource{FungibleToken.Receiver}) {
        self.forSale <- {}
        self.ownerVault = vault
        self.prices = {}
    }

    // withdraw gives the owner the opportunity to remove a sale from the collection
    pub fun withdraw(tokenID: UInt64): @NonFungibleToken.NFT {
        // remove the price
        self.prices.remove(key: tokenID)
        // remove and return the token
        let token <- self.forSale.remove(key: tokenID) ?? panic("missing NFT")
        return <-token
    }

    // listForSale lists an NFT for sale in this collection
    pub fun listForSale(token: @NonFungibleToken.NFT, price: UFix64) {
        let id = token.id

        // store the price in the price array
        self.prices[id] = price

        // put the NFT into the the forSale dictionary
        let oldToken <- self.forSale[id] <- token
        destroy oldToken

        emit ForSale(id: id, price: price)
    }

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

        emit PriceChanged(id: tokenID, newPrice: newPrice)
    }

    // purchase lets a user send tokens to purchase an NFT that is for sale
    pub fun purchase(tokenID: UInt64, recipient: &AnyResource{NonFungibleToken.NFTReceiver}, buyTokens: @FungibleToken.Vault) {
        pre {
            self.forSale[tokenID] != nil && self.prices[tokenID] != nil:
                "No token matching this ID for sale!"
            buyTokens.balance >= (self.prices[tokenID] ?? UFix64(0)):
                "Not enough tokens to by the NFT!"
        }

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

        self.prices[tokenID] = nil

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

        // deposit the NFT into the buyers collection
        recipient.deposit(token: <-self.withdraw(tokenID: tokenID))

        emit TokenPurchased(id: tokenID, price: price)
    }

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

    // getIDs returns an array of token IDs that are for sale
    pub fun getIDs(): [UInt64] {
        return self.forSale.keys
    }

    destroy() {
        destroy self.forSale
    }
  }

  // createCollection returns a new collection resource to the caller
  pub fun createSaleCollection(ownerVault: &{FungibleToken.Receiver}): @SaleCollection {
    return <- create SaleCollection(vault: ownerVault)
  }
}

This marketplace contract has resources that function similarly to the NFT Collection that was explained in Non-Fungible Tokens, with a few differences and additions:

  • This marketplace contract has methods to add and remove NFTs, but these functions also involve setting and removing a price. When a user wants to put their NFT up for sale, they do so by depositing it into the collection with the listForSale function. Then, another user can call the purchase method, sending their Vault that contains the currency they are using to make the purchase. The buyer also includes a reference to their NFT Collection so that the purchased token can be immediately deposited into their collection when the purchase is made.
  • This marketplace contract stores a reference: pub let ownerVault: &FungibleToken.Receiver. The owner of the sale saves a reference to their Fungible Token Receiver within the sale. This allows the sale resource to be able to immediately deposit the currency that was used to buy the NFT into the owners Vault when a purchase is made.
  • This marketplace contract includes events. Cadence supports defining events within contracts that can be emitted when important actions happen.
pub event ForSale(id: UInt64, price: UFix64)
pub event PriceChanged(id: UInt64, newPrice: UFix64)
pub event TokenPurchased(id: UInt64, price: UFix64)
pub event SaleWithdrawn(id: UInt64)

Events are declared by indicating the access level, event, and the name and parameters of the event, like a function declaration. Events cannot modify state at all; they indicate when important actions happen in the smart contract. Events are emitted with the emit keyword followed by the invocation of the event as if it were a function call. External applications can monitor the blockchain to take action when certain events are emitted.

At this point, we should have a fungible token Vault and a NFT Collection in both accounts' storage. Account 0x01 should have an NFT in their collection.

You can create a SaleCollection and list account 0x01's token for sale by following these steps:

Open Transaction1.cdc
Select account 0x01 as the only signer and click the Send button to submit the transaction.

Transaction1.cdc
import FungibleToken from 0x01
import NonFungibleToken from 0x02
import Marketplace from 0x03

// 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: AuthAccount) {

    // Borrow a reference to the stored Vault
    let receiver = acct.borrow<&{FungibleToken.Receiver}>(from: /storage/MainVault)
            ?? panic("Could not borrow a reference to the owner's vault")

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

    // borrow a reference to the NFTCollection in storage
    let collectionRef = acct.borrow<&NonFungibleToken.Collection>(from: /storage/NFTCollection)
            ?? panic("Could not borrow a reference to the owner's nft collection")

    // Withdraw the NFT from the collection that you want to sell
    // and move it into the transaction's context
    let token <- collectionRef.withdraw(withdrawID: 1)

    // List the token for sale by moving it into the sale object
    sale.listForSale(token: <-token, price: UFix64(10))

    // Store the sale object in the account storage
    acct.save<@Marketplace.SaleCollection>(<-sale, to: /storage/NFTSale)

    // Create a public capability to the sale so that others can call its methods
    acct.link<&Marketplace.SaleCollection{Marketplace.SalePublic}>(/public/NFTSale, target: /storage/NFTSale)

    log("Sale Created for account 1. Selling NFT 1 for 10 tokens")
  }
}

This transaction:

  1. Borrows the reference to the owners Vault
  2. Creates the SaleCollection, which stores their Vault reference.
  3. Withdraws the owner's token from their normal collection
  4. Lists that token for sale and sets its price
  5. Stores the sale in their account storage and publishes a capability that allows others to purchase any NFTs for sale.

Lets run a script to ensure that the sale was created correctly.

Open Script2.cdc
Click the Execute button to print the ID and price of the NFT that account 0x01 has for sale.s

Script2.cdc
import FungibleToken from 0x01
import NonFungibleToken from 0x02
import Marketplace from 0x03

// This script prints the NFTs that account 0x01 has for sale.
pub fun main() {
  // Get the public account object for account 0x01
  let account1 = getAccount(0x01)

  // Find the public Sale reference to their Collection
  let acct1saleRef = account1.getCapability(/public/NFTSale)!
                             .borrow<&AnyResource{Marketplace.SalePublic}>()
                                                    ?? panic("Could not borrow a reference to the sale")

  // Los the NFTs that are for sale
  log("Account 1 NFTs for sale")
  log(acct1saleRef.getIDs())
  log("Price")
  log(acct1saleRef.idPrice(tokenID: 1))
}

This script should complete and print something like this:

"Account 1 NFTs for sale"
[1]
"Price"
10

Purchasing an NFT


The buyer can now purchase the seller's NFT by using the transaction in Transaction2.cdc.

Open the Transaction2.cdc file
Select account 0x02 as the only signer and click the Send button.

Transaction2.cdc
import FungibleToken from 0x01
import NonFungibleToken from 0x02
import Marketplace from 0x03

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

  // reference to the buyer's NFT collection where they
  // will store the bought NFT
  let collectionRef: &AnyResource{NonFungibleToken.NFTReceiver}

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

  prepare(acct: AuthAccount) {

    // get the references to the buyer's fungible token Vault
    // and NFT Collection Receiver
    self.collectionRef = acct.borrow<&AnyResource{NonFungibleToken.NFTReceiver}>
                                                        (from: /storage/NFTCollection)
            ?? panic("Could not borrow reference to the signer's nft collection")
    let vaultRef = acct.borrow<&FungibleToken.Vault>(from: /storage/MainVault)
            ?? panic("Could not borrow reference to the signer's vault")

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

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

    // get the reference to the seller's sale
    let saleRef = seller.getCapability(/public/NFTSale)!
                        .borrow<&AnyResource{Marketplace.SalePublic}>()
                                            ?? panic("could not borrow reference to the seller's sale")

    // purchase the NFT the the seller is selling, giving them the reference
    // to your NFT collection and giving them the tokens to buy it
    saleRef.purchase(tokenID: 1,
                     recipient: self.collectionRef,
                     buyTokens: <-self.temporaryVault)

    log("Token 1 has been bought by account 2!")
  }
}

This transaction:

  1. Gets the public account object for account 0x01
  2. Gets the references to the buyer's stored resources
  3. Withdraws the tokens that the buyer will use to purchase the NFT
  4. Gets the reference to the seller's public sale
  5. Calls the purchase function, passing in the tokens and the Collection reference. Then purchase deposits the bought NFT directly into the buyers collection.

Verifying the NFT Was Purchased Correctly


You can run now run a script to verify that the NFT was purchased correctly because:

  • account 0x01 has 50 tokens and does not have any NFTs for sale or in their collection and account
  • account 0x02 has 10 tokens and an NFT with id=1

To run a script that verifies the NFT was purchased correctly, follow these steps:

Open the `Script3.cdc` file.
Click `Execute` button\n`Script3.cdc` should contain the following code:
Script3.cdc
import FungibleToken from 0x01
import NonFungibleToken from 0x02
import Marketplace from 0x03

// This script checks that the Vault balances and NFT collections are correct
// for both accounts.
//
// Account 1: Vault balance = 50, No NFTs
// Account 2: Vault balance = 10, NFT ID=1
pub fun main() {
  // Get the accounts' public account objects
  let acct1 = getAccount(0x01)
  let acct2 = getAccount(0x02)

  // Get references to the account's receivers
  // by getting their public capability
  // and borrowing a reference from the capability
  let acct1ReceiverRef = acct1.getCapability(/public/MainReceiver)!
                          .borrow<&FungibleToken.Vault{FungibleToken.Balance}>()
                                            ?? panic("Could not borrow reference to acct1 vault")
  let acct2ReceiverRef = acct2.getCapability(/public/MainReceiver)!
                          .borrow<&FungibleToken.Vault{FungibleToken.Balance}>()
                                            ?? panic("Could not borrow reference to acct2 vault")

  // Log the Vault balance of both accounts and ensure they are
  // the correct numbers.
  // Account 0x01 should have 50.
  // Account 0x02 should have 10.
  log("Account 1 Balance")
  log(acct1ReceiverRef.balance)
  log("Account 2 Balance")
  log(acct2ReceiverRef.balance)

  // verify that the balances are correct
  if acct1ReceiverRef.balance != UFix64(50)
     || acct2ReceiverRef.balance != UFix64(10)
  {
      panic("Wrong balances!")
  }

  // Find the public Receiver capability for their Collections
  let acct1Capability = acct1.getCapability(/public/NFTReceiver)!
  let acct2Capability = acct2.getCapability(/public/NFTReceiver)!

  // borrow references from the capabilities
  let nft1Ref = acct1Capability.borrow<&{NonFungibleToken.NFTReceiver}>()
    ?? panic("Could not borrow reference to acct1 nft collection")
  let nft2Ref = acct2Capability.borrow<&{NonFungibleToken.NFTReceiver}>()
    ?? panic("Could not borrow reference to acct2 nft collection")

  // Print both collections as arrays of IDs
  log("Account 1 NFTs")
  log(nft1Ref.getIDs())

  log("Account 2 NFTs")
  log(nft2Ref.getIDs())

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

  // Get the public sale reference for Account 0x01
  let acct1SaleRef = acct1.getCapability(/public/NFTSale)!
                          .borrow<&AnyResource{Marketplace.SalePublic}>()!

  // Print the NFTs that account 0x01 has for sale
  log("Account 1 NFTs for sale")
  log(acct1SaleRef.getIDs())
  if acct1SaleRef.getIDs().length != 0 { panic("Sale should be empty!") }
}

If you did everything correctly, the transaction should succeed and it should print something similar to this:

"Account 1 Vault Balance"
50
"Account 2 Vault Balance"
10
"Account 1 NFTs"
[]
"Account 2 NFTs"
[1]
"Account 1 NFTs for Sale"
[]

Congratulations, you have successfully implemented a simple marketplace in Cadence and used it to allow one account to buy an NFT from another!

Scaling the Marketplace


A user can hold a sale in their account with these resources and transactions. Support for a central marketplace where users can discover sales is relatively easy to implement and can build on what we already have. If we wanted to build a central marketplace on-chain, we could use a contract that looks something like this:

CentralMarketplace.cdc
// Marketplace would be the central contract where people can post their sale
// references so that anyone can access them
pub contract Marketplace {
    // Data structure to store active sales
    pub var tokensForSale: [&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
    pub fun listSaleCollection(collection: &SaleCollection): Int {
        self.tokensForSale.append(collection)
        return (self.tokensForSale.length - 1)
    }

    // removeSaleCollection removes a user's sale from the array
    // of sale references
    pub fun removeSaleCollection(index: Int) {
        self.tokensForSale.remove(at: index)
    }

}

This contract isn't meant to be a working or production-ready contract, but it could be extended to make a complete central marketplace by having:

  • Sellers list a reference to their SaleCollection in this contract
  • Other functions that buyers could call to get info about all the different sales and to make purchases.

A central marketplace in an off-chain application is easier to implement because:

  • The app could host the marketplace and a user would simply log in to the app and give the app its account address.
  • The app could read the user's public storage and find their sale reference.
  • With the sale reference, the app could get all the information they need about how to display the sales on their website.
  • Any buyer could discover the sale in the app and login with their account, which gives the app access to their public references.
  • When the buyer wants to buy a specific NFT, the app would automatically generate the proper transaction to purchase the NFT from the seller.

Creating a Marketplace for Any Generic NFT


The previous examples show how a simple marketplace could be created for a specific class of NFTs. However, users will want to have a marketplace where they can buy and sell any NFT they want, regardless of its type. Support for this feature is in development and will be shared soon.

Composable Resources on Flow


Now that you have an understanding of how composable smart contracts and the marketplace work on Flow, you're ready to play with composable resources!

Edit on GitHub