Select Page

Develop NFT marketplace on Flow

develop-nft-marketplace-on-flow

Flow is a decentralized and high-performing Blockchain that has been specifically designed to support the next generation of games, applications, and other kinds of digital assets. As a developer-centric Blockchain, Flow utilizes multi-role architecture models that makes the platform ideal for extensive scaling without sharding. Flow is highly popular among people interacting with NFTs like crypto-infused games, in-game items, and collectibles.

The history of Flow can be traced back to 2017 when Dapper Labs launched the Blockchain game CryptoKitties on Ethereum. CryptoKitties was a viral digital trading game that allowed users to buy, collect and breed digital cats. The sudden surge and popularity of Cryptokitties slowed Ethereum transactions while increasing gas fees to resolve such challenges. It was then that Dapper Labs decided to launch a dedicated Blockchain for crypto games and collectibles that helped overcome challenges of network congestion, slow speeds, and costly transactions. Cyrptokitties set the stage for the development of Flow that facilitated the trading of gaming collectibles and other kinds of crypto games. With NFTs gradually reaching their popularity and maturity levels, Flow serves as a successful Blockchain to create Web3 assets, NFTs, and even Decentralized Autonomous Organizations (DAOs).

Flow facilitates the trade of gaming assets and is primarily designed for a highly-scalable gaming experience. The Blockchain is fast with proof-of-stake consensus, eliminating sharding techniques and facilitating low-cost transactions. In this article, we will look into the steps required to create an NFT marketplace on Flow in detail. Right from setting up the Flow Playground to testing the first NFT transaction, the article will cover essential topics to help you create your marketplace from scratch.

Smart Contracts and Account State on Flow

Smart contract programming in Flow blockchain is done using Cadence. Cadence helps developers ensure that the codes used are safe, easy and secure. As a resource-oriented programming language, Cadence helps assert digital ownership by ensuring that resources and associated assets are not replicated, copied, or lost/deleted. Cadence also sets pre-conditions and post-conditions necessary for current and future Blockchain transactions.

Developers use the Flow Playground to write and deploy smart contracts. The Flow Developer Playground comprises an in-browser editor and an emulator for developers to experiment with on Flow. Before setting up your NFT marketplace on Flow, the Flow Playground comes with several features that make it easier for developers to create and execute smart contracts along with familiarizing them with Cadence on the Playground. It is important for developers to first get accustomed to the Flow Playground to understand the deployment of Cadence smart contracts without errors.

How to develop the NFT Marketplace on Flow?

To build a marketplace on Flow, it is important to integrate both fungible and non-fungible tokens into a single contract, known as ‘composable smart contract’. For developing the NFT marketplace, we have to create a composable smart contract. But before that, you have to set up the playground for marketplace development. Ensure that your fungible tokens and non-fungible token contracts are deployed and set up correctly, then follow the steps below to prepare the playground for NFT marketplace development.

Set up the Playground for Marketplace Development

After understanding the creation and deployment of smart contracts on Cadence, the following starter code in Flow Playground can help developers through the further stages of building an NFT marketplace.

// FungibleToken.cdc
// The FungibleToken contract is a sample implementation of a fungible token on Flow.
// Fungible tokens behave like everyday currencies -- they can be minted, transferred or
// traded for digital goods.
// Follow the fungible tokens tutorial to learn more: https://docs.onflow.org/docs/fungible-tokens 
pub contract FungibleToken {
    // Total supply of all tokens in existence.
    pub var totalSupply: UFix64
    // Provider
   // Interface that enforces the requirements for withdrawing
    // tokens from the implementing type.
    // We don't enforce requirements on self.balance here because
    // it leaves open the possibility of creating custom providers
    // that don't necessarily need their own balance.
    pub resource interface Provider {
        // withdraw
       // Function that subtracts tokens from the owner's Vault
        // and returns a Vault resource (@Vault) with the removed tokens.
        // The function's access level is public, but this isn't a problem
        // because even the public functions are not fully public at first.
        // anyone in the network can call them, but only if the owner grants
        // them access by publishing a resource that exposes the withdraw
        // function.
        pub fun withdraw(amount: UFix64): @Vault {
            post {
                // `result` refers to the return value of the function
                result.balance == UFix64(amount):
                    "Withdrawal amount must be the same as the balance of the withdrawn Vault"
            }
        }
    }
    // Receiver 
    // Interface that enforces the requirements for depositing
    // tokens into the implementing type.
    // We don't include a condition that checks the balance because
    // we want to give users the ability to make custom Receivers that
    // can do custom things with the tokens, like split them up and
    // send them to different places.
    pub resource interface Receiver {
        // deposit
        // Function that can be called to deposit tokens 
        // into the implementing resource type
        pub fun deposit(from: @Vault)
    }
    // Balance
    // Interface that specifies a public `balance` field for the vault
    pub resource interface Balance {
        pub var balance: UFix64
    }
    // Vault
    // Each user stores an instance of only the Vault in their storage
    // The functions in the Vault and governed by the pre and post conditions
    // in the interfaces when they are called. 
    // The checks happen at runtime whenever a function is called.
    // Resources can only be created in the context of the contract that they
    // are defined in, so there is no way for a malicious user to create Vaults
    // out of thin air. A special Minter resource needs to be defined to mint
    // new tokens.
    pub resource Vault: Provider, Receiver, Balance {     
        // keeps track of the total balance of the account's tokens
       pub var balance: UFix64
        // initialize the balance at resource creation time
        init(balance: UFix64) {
            self.balance = balance
        }
        // withdraw
        // Function that takes an integer amount as an argument
        // and withdraws that amount from the Vault.
        // It creates a new temporary Vault that is used to hold
        // the money that is being transferred. It returns the newly
        // created Vault to the context that called so it can be deposited
        // elsewhere.
        pub fun withdraw(amount: UFix64): @Vault {
            self.balance = self.balance - amount
            return <-create Vault(balance: amount)
        }    
        // deposit
        // Function that takes a Vault object as an argument and adds
        // its balance to the balance of the owners Vault.
        // It is allowed to destroy the sent Vault because the Vault
        // was a temporary holder of the tokens. The Vault's balance has
        // been consumed and therefore can be destroyed.
        pub fun deposit(from: @Vault) {
            self.balance = self.balance + from.balance
           destroy from
        }
    }
    // createEmptyVault
    // Function that creates a new Vault with a balance of zero
    // and returns it to the calling context. A user must call this function
    // and store the returned Vault in their storage in order to allow their
    // account to be able to receive deposits of this token type.
    pub fun createEmptyVault(): @Vault {
        return <-create Vault(balance: 0.0)
    }
    // VaultMinter
    // Resource object that an admin can control to mint new tokens
    pub resource VaultMinter {
        // Function that mints new tokens and deposits into an account's vault
        // using their `Receiver` reference.
        // We say `&AnyResource{Receiver}` to say that the recipient can be any resource
        // as long as it implements the Receiver interface
        pub fun mintTokens(amount: UFix64, recipient: &AnyResource{Receiver}) {
            FungibleToken.totalSupply = FungibleToken.totalSupply + amount
            recipient.deposit(from: <-create Vault(balance: amount))
        }
    }
    // The init function for the contract. All fields in the contract must
    // be initialized at deployment. This is just an example of what
    // an implementation could do in the init function. The numbers are arbitrary.
    init() {
        self.totalSupply = 30.0
        // create the Vault with the initial balance and put it in storage
        // account.save saves an object to the specified `to` path
        // The path is a literal path that consists of a domain and identifier
        // The domain must be `storage`, `private`, or `public`
        // the identifier can be any name
        let vault <- create Vault(balance: self.totalSupply)
        self.account.save(<-vault, to: /storage/MainVault)
        // Create a new MintAndBurn resource and store it in account storage
        self.account.save(<-create VaultMinter(), to: /storage/MainMinter)
        // Create a private capability link for the Minter
        // Capabilities can be used to create temporary references to an object
        // so that callers can use the reference to access fields and functions
        // of the objet.
        // 
        // The capability is stored in the /private/ domain, which is only
        // accesible by the owner of the account
        self.account.link<&VaultMinter>(/private/Minter, target: /storage/MainMinter)
    }
}

With the above starter code, you can prepare the Flow Playground for the state required to build a marketplace for NFTs.

Setting up the NFT Marketplace

After having the starter code and NFT token contracts in place, the following steps will help you set up the marketplace on Flow-

  • Open account 0x01. The Fungible Token definitions in ‘FungibleToken.cdc’ need to be included in this account. You can refer to the Fungible Token tutorial to ensure the same.
  • The Fungible Token code needs to be deployed to account 0x01
  • Select and switch to account 0x02 from the account selection menu.
  • Follow the Non-fungible token tutorial to make sure you have all NFT definitions from account 0x02 in NFTv2.cdc
  • Deploy the NFT code to account 0x02

Run the transaction in Transaction 3, which is the SetupAccount1Transaction.cdc file. You can use account 0x01 as the only signer to set up account 0x01’s account.

import FungibleToken from 0x01
import NonFungibleToken from 0x02
// This transaction sets up account 0x01 for the marketplace tutorial
// by publishing a Vault reference and creating an empty NFT Collection.
transaction {
  prepare(acct: AuthAccount) {
    // Create a public Receiver capability to the Vault
    acct.link<&FungibleToken.Vault{FungibleToken.Receiver, FungibleToken.Balance}>
             (/public/MainReceiver, target: /storage/MainVault)
    log("Created Vault references")
    // store an empty NFT Collection in account storage
    acct.save<@NonFungibleToken.Collection>(<-NonFungibleToken.createEmptyCollection(), to: /storage/NFTCollection)
    // publish a capability to the Collection in storage
    acct.link<&{NonFungibleToken.NFTReceiver}>(/public/NFTReceiver, target: /storage/NFTCollection)
    log("Created a new empty collection and published a reference")
  }
}

Run the transaction in Transaction 4, which is the SetupAccount2Transaction.cdc file. You need to use account 0x02 as the only signer to set up account 0x02’s account.

import FungibleToken from 0x01
import NonFungibleToken from 0x02
// This transaction adds an empty Vault to account 0x02
// and mints an NFT with id=1 that is deposited into
// the NFT collection on account 0x01.
transaction {
  // Private reference to this account's minter resource
  let minterRef: &NonFungibleToken.NFTMinter
  prepare(acct: AuthAccount) {
    // create a new vault instance with an initial balance of 0
    let vaultA <- FungibleToken.createEmptyVault()
    // Store the vault in the account storage
    acct.save<@FungibleToken.Vault>(<-vaultA, to: /storage/MainVault)
    // Create a public Receiver capability to the Vault
    let ReceiverRef = acct.link<&FungibleToken.Vault{FungibleToken.Receiver, FungibleToken.Balance}>(/public/MainReceiver, target: /storage/MainVault)
    log("Created a Vault and published a reference")
    // Borrow a reference for the NFTMinter in storage
    self.minterRef = acct.borrow<&NonFungibleToken.NFTMinter>(from: /storage/NFTMinter)
            ?? panic("Could not borrow an nft minter reference")
  }
  execute {
    // Get the recipient's public account object
    let recipient = getAccount(0x01)
    // Get the Collection reference for the receiver
    // getting the public capability and borrowing a reference from it
    let receiverRef = recipient.getCapability<&{NonFungibleToken.NFTReceiver}>(/public/NFTReceiver)
        .borrow()
        ?? panic("Could not borrow an nft receiver reference")
    // Mint an NFT and deposit it into account 0x01's collection
    self.minterRef.mintNFT(recipient: receiverRef)
   log("New NFT minted for account 1")
  }
}

Run the transaction in Transaction 5, which is the SetupAccount1TransactionMinting.cdc file. Use account 0x01 as the only signer to mint fungible tokens for accounts 1 and 2.

import FungibleToken from 0x01
import NonFungibleToken from 0x02
// This transaction mints tokens for both accounts using
// the minter stored on account 0x01.
transaction {
    // Public Vault Receiver References for both accounts
    let acct1Ref: &AnyResource{FungibleToken.Receiver}
    let acct2Ref: &AnyResource{FungibleToken.Receiver}
    // Private minter references for this account to mint tokens
    let minterRef: &FungibleToken.VaultMinter
    prepare(acct: AuthAccount) {
        // Get the public object for account 0x02
        let account2 = getAccount(0x02)
        // Retrieve public Vault Receiver references for both accounts
        self.acct1Ref = acct.getCapability<&FungibleToken.Vault{FungibleToken.Receiver}>(/public/MainReceiver)
            .borrow()
            ?? panic("Could not borrow acct1 vault receiver reference")
        self.acct2Ref = account2.getCapability<&FungibleToken.Vault{FungibleToken.Receiver}>(/public/MainReceiver)
            .borrow()
            ?? panic("Could not borrow acct2 vault receiver reference")
        // Get the stored Minter reference for account 0x01
        self.minterRef = acct.borrow<&FungibleToken.VaultMinter>(from: /storage/MainMinter)
            ?? panic("Could not borrow vault minter reference")
    }
    execute {
        // Mint tokens for both accounts
        self.minterRef.mintTokens(amount: 20.0, recipient: self.acct2Ref)
        self.minterRef.mintTokens(amount: 10.0, recipient: self.acct1Ref)
        log("Minted new fungible tokens for account 1 and 2")
    }
}

To ensure everything is set up properly, run the script CheckSetupScript.cdc file in Script 1.

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<&FungibleToken.Vault{FungibleToken.Balance}>(/public/MainReceiver)
        .borrow<&FungibleToken.Vault{FungibleToken.Balance}>()
        ?? panic("Could not borrow acct1 vault receiver reference")
    let acct2ReceiverRef = acct2.getCapability<&FungibleToken.Vault{FungibleToken.Balance}>(/public/MainReceiver)
        .borrow()
        ?? panic("Could not borrow acct2 vault receiver reference")
    // 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 != 40.0 || acct2ReceiverRef.balance != 20.0 {
        panic("Wrong balances!")
    }
    // Find the public Receiver capability for their Collections
    let acct1Capability = acct1.getCapability<&{NonFungibleToken.NFTReceiver}>(/public/NFTReceiver)
    let acct2Capability = acct2.getCapability<&{NonFungibleToken.NFTReceiver}>(/public/NFTReceiver)
    // borrow references from the capabilities
    let nft1Ref = acct1Capability.borrow()
        ?? panic("Could not borrow acct1 nft receiver reference")
    let nft2Ref = acct2Capability.borrow()
        ?? panic("Could not borrow acct2 nft receiver reference")
    // 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] != 1 as UInt64 || nft2Ref.getIDs().length != 0 {
        panic("Wrong Collections!")
    }
}

The following should be the output —

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

With the above steps, you can ensure that the Flow Playground is in the correct state to create your NFT marketplace to enable the sale of NFTs between accounts.

Selling NFT on the Marketplace

Users trying to sell an NFT will store an instance of a ‘SaleCollection’ resource in the account storage. Switch to account 0x03 and open Marketplace.cdc. Select the Deploy button that appears at the bottom right of the editor within the Marketplace.cdc file. The file should then contain the following contraction-

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.
    pub let ownerVault: Capability<&AnyResource{FungibleToken.Receiver}>
    init (vault: Capability<&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] ?? 0.0):
                "Not enough tokens to by the NFT!"
        }
        // 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)
        // 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: Capability<&AnyResource{FungibleToken.Receiver}>): @SaleCollection {
    return <- create SaleCollection(vault: ownerVault)
  }
}

The marketplace contract allows users to add and remove NFTs, as well as set and remove a price. A user can put up their NFT on sale after depositing it into the collection with the listForSale function. Another user can buy the NFT by sending their Vault containing currency to make the purchase. The buyer will also include a reference to their NFT collection for the purchased token to get deposited and reflected into their collection immediately after making the purchase.

The capability that the marketplace contract stores is:

 
pub let ownerVault: Capability<&AnyResource{FungibleToken.Receiver}>.

The seller (owner of the sale) saves a capability to their Fungible Token Receiver within the sale, which helps deposit the currency into the owner’s vault once the NFT purchase is made.

The marketplace contract also includes events that are supported by Cadence. Cadence can be used to add or remove events within contracts when important actions/events take place.

 
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)

Testing and Verifying Transactions

By now, you should have a fungible token Vault and an NFT Collection in both accounts. Account 0x01 should have an NFT in their collection. Now you can create a SaleCollection and list account 0x01’s token for sale by following the steps below-
Step 1:
Open Transaction1.cdc. Select account 0x01 as the only signer and click on Send to submit the transaction

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.getCapability<&{FungibleToken.Receiver}>(/public/MainReceiver)
    // 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: 10.0)
    // 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 sale transaction ensures the following-

  • Creates the SalesCollection in the owner’s vault, storing their vault reference
  • Gets a capability for the owner’s vault
  • Withdraws the owner’s token from their collection
  • Sets the price and lists the token
  • Stores the sale in their account storage
  • Enables other users to purchase any NFTs for sale by establishing a capability

Step 2:
You need to run a script to check if the sale has been created correctly. For this, open script2.cdc. Click on theExecutebutton to print the ID and price of thw NFT that has been put up on sale by account 0x01

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<&AnyResource{Marketplace.SalePublic}>(/public/NFTSale)
        .borrow()
        ?? 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))
}

The Print output should look something like this:

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

Purchasing the NFT – Buyers

The transaction in Transaction2.cdc can be used by the buyer to purchase the seller’s NFT. Open Transaction2.cdc file and select account 0x02 as the only signer. Click the Send button.

import FungibleToken from 0x01
import NonFungibleToken from 0x02
import Marketplace from 0x03
// This transaction uses the signer's 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: 10.0)
  }
  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<&AnyResource{Marketplace.SalePublic}>(/public/NFTSale)
        .borrow()
        ?? 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!")
  }
}

What the above transaction can enable-

  • Gets the public account object for account 0x01
  • Acquires relevant references to the buyer’s stored resources
  • Withdraws buyer’s tokens to be used for NFT purchase
  • Gets the reference to seller’s public sale
  • Calls and executes the purchase function, passing in the tokens and Collection reference
  • The Purchase function deposits the purchased NFTs directly into the buyer’s collection.

Running a Script to Verify NFT Purchase

There are two steps to verify that the NFT was purchased correctly and verify the nature of the NFT transaction.

  1. Open the Script3.cdc file
  2. Click ‘Execute’ button with ‘Script3.cdc’ containing the following code-
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<&FungibleToken.Vault{FungibleToken.Balance}>(/public/MainReceiver)
        .borrow()
        ?? panic("Could not borrow reference to acct1 vault")
  let acct2ReceiverRef = acct2.getCapability<&FungibleToken.Vault{FungibleToken.Balance}>(/public/MainReceiver)
        .borrow()
        ?? 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 != 50.0
     || acct2ReceiverRef.balance != 10.0
  {
      panic("Wrong balances!")
  }
  // Find the public Receiver capability for their Collections
  let acct1Capability = acct1.getCapability<&{NonFungibleToken.NFTReceiver}>(/public/NFTReceiver)
  let acct2Capability = acct2.getCapability<&{NonFungibleToken.NFTReceiver}>(/public/NFTReceiver)
  // borrow references from the capabilities
  let nft1Ref = acct1Capability.borrow()
    ?? panic("Could not borrow reference to acct1 nft collection")
  let nft2Ref = acct2Capability.borrow()
    ?? 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] != 1 as UInt64 || nft1Ref.getIDs().length != 0 {
      panic("Wrong Collections!")
  }
  // Get the public sale reference for Account 0x01
  let acct1SaleRef = acct1.getCapability<&AnyResource{Marketplace.SalePublic}>(/public/NFTSale)
       .borrow()
        ?? panic("Could not borrow a reference to the sale")
  // 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!") }
}

After completing the verification process, you can ensure the success of the transaction when you observe a print similar to the following-

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

The codes for each of the steps, i.e. setting up the marketplace, designing the marketplace, and facilitating transactions from one account to another, can help you create a simple marketplace on Cadence. You can also scale your marketplace further by using the CentralMarketplace.cdc contract.

Build an NFT Marketplace with LeewayHertz

Developing an NFT marketplace on a platform like Flow requires businesses to consider several things for their development. A good way to develop scalable marketplaces would be to partner with Blockchain experts with adequate experience and knowledge in NFT development. Building a custom NFT marketplace becomes quicker and easier with an experienced team of Blockchain experts. LeewayHertz offers NFT marketplace development services, enabling businesses to build dynamic marketplaces on Flow and other platforms and help them scale their business.

Webinar Details

Author’s Bio

Akash Takyar
Akash Takyar
CEO LeewayHertz
Akash Takyar is the founder and CEO at LeewayHertz. The experience of building over 100+ platforms for startups and enterprises allows Akash to rapidly architect and design solutions that are scalable and beautiful.
Akash's ability to build enterprise-grade technology solutions has attracted over 30 Fortune 500 companies, including Siemens, 3M, P&G and Hershey’s. Akash is an early adopter of new technology, a passionate technology enthusiast, and an investor in AI and IoT startups.

Start a conversation by filling the form

Once you let us know your requirement, our technical expert will schedule a call and discuss your idea in detail post sign of an NDA.

All information will be kept confidential.

 Send me the signed Non-Disclosure Agreement (NDA)

Insights

NFT APIs as a service

NFT APIs as a service

NFT APIs make working with non-fungible tokens easy. Tasks like extracting data from multiple smart contracts and integrating it into an app can be performed without manual effort.

read more