Step-by-step Guide on How to develop an NFT Marketplace Platform

develop nft marketplace platform

As the non-fungible token market reaches a vast number, it is good to look back on the initial days of NFTs and remember the challenges revealed by Cryptokitties.

Cryptokitties was the first example of potential NFT mass adoption built by the Dapper Labs team. Since then, new NFT marketplace platforms such as Rarible, Sorare and OpenSea have been introduced to encourage NFT Development. The research published on NonFungible.com says that the total value of NFT transactions in 2020 increased by 299% compared to 2019 and exceeded $250 million. Experts predict that non-fungible tokens will be the driving force behind the virtual economy in the coming 10 years.

The NFT marketplace is expected to grow tremendously, and developing an NFT marketplace is a great opportunity. Therefore, we have put together a complete guide that will answer questions about building an NFT marketplace.

What is an NFT Marketplace?

NFT marketplaces will continue to gain prominence in 2021 and NFTs are growing as a new asset class in the crypto space. NFT marketplace platform is a decentralized platform that allows users to create, buy, sell and store non-fungible tokens.

NFT marketplaces are focused on selling specific assets. For example, the Valuables NFT marketplace allows users to buy and sell tweets.

Niche marketplaces are gaining a lot of traction as they have a clearly defined target audience. Therefore, you should think about the type of platform you will launch before building an NFT marketplace.

How does an NFT Marketplace work?

Before diving deep into how an NFT marketplace is created, it is essential to understand how the NFT marketplace works from the client side. Usually, all NFT platforms share a similar workflow. Firstly, a user has to sign up on the platform and install a digital wallet to store NFTs.

Users can then create their assets by uploading items to exhibit their work. The user can also select which payment tokens they would like to accept for their art and set a fee if the platform suggests it. The next step in the workflow is to list items for sale. The user can choose a bid for a fixed price or an auction. A transaction is created when listing an item for sale to launch a personal trading smart contract for the user wallet.

The platform may need a collection moderation and NFTs will appear on the list after approval.

How to start NFT Marketplace Development?

Since you know how NFT marketplaces work and what types of platforms are on the market, you need to understand who you are developing your platform for and what problems it can solve.

You need to decide on a list of features and choose the technology stacks and NFT standards for your marketplace.

features of nft

Following are some of the features that can be added to the NFT marketplace:

  • Storefront
    NFT marketplace should have a storefront that offers users all the information required for an item: bids, owners, preview or price history.
  • Filters
    Using filters, it becomes easier to navigate a site, specifically if you plan to build a marketplace place for a lot of collectibles. By adding the filters feature, users can select items by payment method, listing status, category and collection.
  • Searching for items
    An NFT marketplace platform should support tagging and category management to allow users to search collectibles. Use a search bar on the site and add categories.
  • Create listings
    A user should be able to create and submit collectibles. Using this feature, a user should upload files and fill in the token information such as name, tags, description.
  • Buy and Bid
    The NFT marketplace platform should have a feature that allows users to buy and bid for NFTs listed on the platform. The bidding feature should include a bid expiration date and allow users to view details about the bids’ current status.
  • Wallet
    The NFT Marketplace Platform should have a wallet that allows users to store, send and receive non-fungible tokens. The easiest way to integrate this feature is to provide users with a connected wallet that they already use. For example, you can integrate the most popular wallets like Coinbase, Formatic or MyEtherWallet.

Once you identify features to be added to the platform, the next step is to consider the project’s technical implementation. To build an NFT marketplace, you should first decide the technology stack required for developing it.

Let’s look at some of the technology components needed for building an NFT marketplace platform.

Technology Stack Required for Building an NFT marketplace platform

nft tech stack

Blockchain Platforms

  • Ethereum
    Ethereum is one of the most popular platforms when it comes to NFT marketplace or non-fungible token development. Since transaction history and token metadata is publicly verifiable on Ethereum, it is easier to prove ownership history.As all Ethereum products share the common backend, NFTs become portable across products. Also, Ethereum never goes down; therefore, tokens will always be available for sale.
  • Flow
    Flow is a rapid and decentralized blockchain designed for a new generation of digital assets, apps and games. The platform is being widely used to create both the NFT marketplace and NFTs using the Cadence programming language.
  • Tezos
    Tezos is an open-source blockchain platform for applications and assets backed by a community of builders, researchers and validators. Using a pre-compiled FA2 NFT contract written in LIGO smart contract language, Tezos supports the development of NFT marketplaces and NFTs.
  • Cardano
    Cardano is a blockchain platform with technologies and tools required to provide unmatched security and sustainability to decentralized systems and applications. It also supports the development of NFTs and other digital assets. 

Storage Platforms

  • IPFS
    IPFS is a peer-to-peer hypermedia protocol designed to store media content in a decentralized way. As the media file related to NFTs cannot be stored directly on the blockchain, IPFS can store all that data.
  • Filecoin
    Filecoin is a decentralized storage network designed specifically to store the most critical information, such as media files. The storage platform includes everything that a developer will require to get started with decentralized storage for NFTs. Knowing its benefits, Truffle Suite has also launched NFT Development Template with Filecoin Box.
  • Pinata
    Pinata is also one of the popular platforms to upload and manage files on IPFS. It provides secure and verifiable files for NFTs.

NFT Standards

  • ERC-721
  • ERC-1155
  • FA2
  • dGoods
  • TRC-721

Front-end Frameworks

  • React
  • Angular
  • Vue

This article demonstrates the example of building an NFT Marketplace with Flow Blockchain and Pinata IPFS Network.

How to develop NFT Marketplace using IPFS and Flow?

We have shared an example of creating the contract, minting a token, building an app to view NFTs made via this contract and creating a marketplace to move NFTs to other users.

Let’s start with the creation of the contract and token minting.

Setting Up Tools 

Install Flow CLI on your system. There are different commands for installing CLI based on different operating systems.

For example, to install Flow CLI on macOS, use the command:

brew install flow-cli

On Windows:

iex “& { $(irm ‘https://storage.googleapis.com/flow-cli/install.ps1‘) }”

On Linux:

sh -ci “$(curl -fsSL https://storage.googleapis.com/flow-cli/install.sh)”

Asset files will be stored on IPFS. In this example, we are going to use Pinata for storing files. You can sign up for a free account and get an API key here.

It is also essential to install a NodeJS and a text editor to highlight Flow smart contract code.

The second step is to create a directory for the project using the command:

mkdir pinata-party

Initialize a new flow project and change it into that directory:

cd pinata-party

flow project init

Now, open the project in your code editor and let’s start working. First, create a folder named cadence. Add another folder called contracts within that folder. Finally, create a file within the contracts folder named PinataPartyContract.cdc.

Before going forward, it is essential to point out everything we do regarding the Flow blockchain platform. Set up the file for the emulator environment and then we can start writing the contract.

We need to update the contract’s object in flow.json using the code:

“contracts”: {
“PinataPartyContract”: “./cadence/contracts/PinataPartyContract.cdc”
}

Update the deployments object in that file using the below code:

“deployments”: {
“emulator”: {
“emulator-account”: [“PinataPartyContract”]
}
}

It will enable the Flow CLI to use the emulator for deploying our contract. This code is also referencing the account and the contract that we are going to write soon.

Contracts

We need to create contracts for minting NFT, associating metadata to the NFT and ensuring that metadata points to the underlying assets stored on IPFS.

Open the PinataPartyContract.cdc and execute the following code:

pub contract PinataPartyContract {

pub resource NFT {

pub let id: UInt64

init(initID: UInt64) {

self.id = initID

}

}

}

The first step is to define the contract. Let’ start by defining PinataPartyContract and create a resource within that. Resources are items saved in user accounts that are accessible via access control measures. NFTs need to be identifiable and the id property allows you to identify tokens.

Then, a resource interface is created to define what capabilities are made available to others.

pub resource interface NFTReceiver {

pub fun deposit(token: @NFT, metadata: {String : String})

pub fun getIDs(): [UInt64]

pub fun idExists(id: UInt64): Bool

pub fun getMetadata(id: UInt64) : {String : String}

}

Put the above code below the NFT resource code. The NFTReceiver resource interface says that the resource can call the following methods:

  • getIDs
  • idExists
  • deposit
  • getMetadata

Then, we have to define the token collection interface. Consider it as a wallet that stores NFTs of all the users.

pub resource Collection: NFTReceiver {

pub var ownedNFTs: @{UInt64: NFT}

pub var metadataObjs: {UInt64: { String : String }}

init () {

self.ownedNFTs <- {}

self.metadataObjs = {}

}

pub fun withdraw(withdrawID: UInt64): @NFT {

let token <- self.ownedNFTs.remove(key: withdrawID)!

return <-token

}

pub fun deposit(token: @NFT, metadata: {String : String}) {

self.metadataObjs[token.id] = metadata

self.ownedNFTs[token.id] <-! token

}

pub fun idExists(id: UInt64): Bool {

return self.ownedNFTs[id] != nil

}

pub fun getIDs(): [UInt64] {

return self.ownedNFTs.keys

}

pub fun updateMetadata(id: UInt64, metadata: {String: String}) {

self.metadataObjs[id] = metadata

}

pub fun getMetadata(id: UInt64): {String : String} {

return self.metadataObjs[id]!

}

destroy() {

destroy self.ownedNFTs

}

}

The variable ownedNFTs tracks all NFTs a user can own from the contactor. A variable called metadataObjs is unique as we are extending the Flow NFT contract functionality to store a mapping of metadata for every NFT.
It maps the token id to its related metadata, which means that the token id is required before we can set it. Variables are initialized to define them in a resource within Flow.

Finally, we will have all the available functions required for the NFT collection resource. The way the default NFT contract was extended to include metadataObjs mapping, we will extend the default deposit function to take an additional metadata parameter. It is done to ensure that only the token minter can add the metadata to the token. We keep the initial addition of metadata confined to the minting execution to keep it private. Add the following code below the Collection resource:

pub fun createEmptyCollection(): @Collection {

return <- create Collection()

}

pub resource NFTMinter {

pub var idCount: UInt64

init() {

self.idCount = 1

}

pub fun mintNFT(): @NFT {

var newNFT <- create NFT(initID: self.idCount)

self.idCount = self.idCount + 1 as UInt64

return <-newNFT

}

}

Firstly, we will have a function to create an empty NFT collection when called. A user who is interacting with the contract will have a storage location that maps the defined Collection resource.

We will create one more resource after that. We cannot mint tokens without it. The NFTMinter comprises an idCount that is increased every time to ensure we don’t have duplicate ids for NFTs. It also contains a function for creating NFTs.

Add the main contract initializer below the NFTMinter resource:

init() {

self.account.save(<-self.createEmptyCollection(),to: /storage/NFTCollection)

self.account.link<&{NFTReceiver}>(/public/NFTReceiver,target:/storage/NFTCollection)

self.account.save(<-create NFTMinter(), to: /storage/NFTMinter)

}

The initializer function is called only when deploying the contract. It performs three things:

  • Create an empty collection for the collection deployer so that the owner contract can create and own NFTs from the contract.
  • The NFTMinter resource is stored in account storage for the contract creator. It means only the contract creator can mint tokens.
  • The Collection resource is published in a public location with reference to the NFTReceiver interface created at the beginning. That is how we tell the contract that anyone can call functions defined on the NFTReceiver.

Once we have a contract ready to go, let’s deploy it. Before deploying it:

  1. Test it on the Flow Playground.
  2. Go to Flow Playground and click on the first account in the left sidebar.
  3. Replace all the code with the contract code and click Deploy.

If everything goes well, you should view a log at the bottom of the screen:

16:48:55 Deployment Deployed Contract To: 0x01

As it is now the time to deploy the contract to the locally running emulator, run the following command:

flow project start-emulator

With the emulator running and configured flow.json file, a contract can be deployed with the below command:

flow project deploy

If everything goes well, you should view an output like this:

Deploying 1 contracts for accounts: emulator-account

PinataPartyContract → 0xf8d6e0586b0a20c7

Now, we shall move to the minting of NFTs.

Minting NFTs

In this section, we will discuss the NFT minting process using an app and a user interface. For getting something minted and displaying how metadata works with NFTs on Flow, we will be using the command line and Cadence scripts.

Create a new directory within the root of our pinata-party project and call them “transactions.” Once a folder is created, create a new file within it called as MintPinataParty.cdc.

We should have a file to reference in the metadata we offer to the NFT. A file is uploaded to IPFS via Pinata. In this tutorial, NFTs are focused on tradable videos of Pinata being smashed at parties. In this demo, we will upload a kid’s video hitting a pinata at a  birthday party. You can upload any media file you want and associate it with the NFT.

Once the file is uploaded, you will be given an IPFS hash. Copy the hash as it will be used in the minting process. Now, add the following code inside MintPinataParty.cdc file.

import PinataPartyContract from 0xf8d6e0586b0a20c7

transaction {

let receiverRef: &{PinataPartyContract.NFTReceiver}

let minterRef: &PinataPartyContract.NFTMinter

prepare(acct: AuthAccount) {

self.receiverRef=acct.getCapability<&{PinataPartyContract.NFTReceiver}>(/public/NFTReceiver)

.borrow()

?? panic(“Could not borrow receiver reference”)

self.minterRef = acct.borrow<&PinataPartyContract.NFTMinter>(from: /storage/NFTMinter)

?? panic(“could not borrow minter reference”)

}

execute {

let metadata : {String : String} = {

“name”: “The Big Swing”,

“swing_velocity”: “29”,

“swing_angle”: “45”,

“rating”: “5”,

“uri”: “ipfs://QmRZdc3mAMXpv6Akz9Ekp1y4vDSjazTx2dCQRkxVy1yUj6”

}

let newNFT <- self.minterRef.mintNFT()

self.receiverRef.deposit(token: <-newNFT, metadata: metadata)

log(“NFT Minted and deposited to Account 2’s Collection”)

}

}

Firstly, we have defined two reference variables, minterRef and receiverRef. In this scenario, we are both the receiver and minter of the NFT. These variables reference resources that are created in the contract. The transaction gets failed if the person executing it does not have access to the resource.

The above contract will mint and deposit NFTs. Now, we will send the transaction and mint the NFT. But before that, we need to prepare the account. Create a private key for signing from the command line within the project’s root folder.

Run the below command:

flow keys generate

It will provide you with public and private keys. Make sure to protect your private key.

You will need the private key for signing the transaction that needs to be pasted into our flow.json file. It is also essential to specify the signing algorithm and here is what your accounts object in the flow.json file should look like:

“accounts”: {
“emulator-account”: {
“address”: “YOUR ACCOUNT ADDRESS”,
“privateKey”: “YOUR PRIVATE KEY”,
“chain”: “flow-emulator”,
“sigAlgorithm”: “ECDSA_P256”,
“hashAlgorithm”: “SHA3_256”
}
},

If you want to store any of this project on a remote git repository or Github, you should not include the private key. You may need to .gitignore the entire flow.json. Though we are only using a local emulator, it is good to keep keys protected.

The last thing that we need to do is to verify that the token is in our account and fetch the metadata. To verify it, we need to write a simple script and call it from the command line.

Create a new folder called scripts from the root of your project. Create a file called CheckTokenMetadata.cdc inside the folder. Add the following code in that file:

import PinataPartyContract from 0xf8d6e0586b0a20c7

pub fun main() : {String : String} {

let nftOwner = getAccount(0xf8d6e0586b0a20c7) // log(“NFT Owner”)

let capability = nftOwner.getCapability<&{PinataPartyContract.NFTReceiver}>(/public/NFTReceiver)

let receiverRef = capability.borrow()

?? panic(“Could not borrow the receiver reference”)

return

receiverRef.getMetadata(id: 1)

}

We are importing the contract from the deployed address in this script. We define the main function and define three variables inside it:

  • nftOwner
    This account owns the NFT.
  • capability
    Capabilities are access-controlled. If a capability is not available to the address attempting to borrow it, the script gets failed. In this example, we borrow capabilities from the NFTReceiver resource.
  • receiverRef
    The variable takes our capability and states the script to borrow from the deployed contract.

We want to ensure the address in the question has received the NFT we minted and then we want to view the metadata associated with the token.

Run the script using the following command and see what we get:

flow scripts execute ./scripts/CheckTokenMetadata.cdc

You will get an output like this:

{“name”: “The Big Swing”, “swing_velocity”: “29”, “swing_angle”: “45”, “rating”: “5”, “uri”: “ipfs://QmRZdc3mAMXpv6Akz9Ekp1y4vDSjazTx2dCQRkxVy1yUj6“}

Finally, you have created a Flow smart contract, created token, associated metadata to the token and saved the token’s underlying digital assets on IPFS. 

We will then build a front-end React app that allows you to display the NFT by fetching the metadata.

Displaying NFT Collectibles

 We will be building a simple React app that interacts with Flow Smart Contracts to validate and fetch NFTs owned by users. 

Setup React and Dependencies

 Create React app within the parent directory pinata-party. Run the below command to create a React app:

npx create-react-app pinata-party-frontend 

When you are done with installing, you will see a new directory called pinata-party-frontend. Switch to that directory and install dependencies. For the first part of the frontend setup, run:

npm i @onflow/fcl @onflow/types

We will store some values as global variables for our app and use environment variables. In react, it means creating a .env file and setting key-value pairs where you need to prefix it with REACT_APP. 

Then, create a config file that will be used to interact with the Flow JS SDK. Create a file in the src directory, config.js and add the following code:

import {config} from “@onflow/fcl”
config()
.put(“accessNode.api”, process.env.REACT_APP_ACCESS_NODE)
.put(“challenge.handshake”, process.env.REACT_APP_WALLET_DISCOVERY)
.put(“0xProfile”, process.env.REACT_APP_CONTRACT_PROFILE)

This configuration file just helps the JS SDK work with the Flow blockchain (or emulator in this case). To make this file available throughout the app, open up the index.js file and add this line:

import React, {useState, useEffect} from ‘react’

import * as fcl from “@onflow/fcl”

const AuthCluster = () => {

const [user, setUser] = useState({loggedIn: null})

useEffect(() => fcl.currentUser().subscribe(setUser), [])

if (user.loggedIn) {

return (

<div>

<span>{user?.addr ?? “No Address”}</span>

<button className=”btn-primary” onClick={fcl.unauthenticate}>Log Out</button>

</div>

)

} else {

return (

<div>

<button  className=”btn-primary”onClick={fcl.logIn}>Log In</button>

<button className=”btn-secondary” onClick={fcl.signUp}>Sign Up</button>

</div>

)

}

}

export default AuthCluster

To get this component into the app, replace the app.js file with the following:

After adding the above code, you will see a page with a login sign-up button on starting the app. Now, it’s time to build the ability to fetch NFTs for an account and display them.

 To display NFTs that we created, it is essential to communicate with the Flow blockchain. In this tutorial, we should be able to communicate with the Flow emulator. 

import React, { useState } from “react”;

import * as fcl from “@onflow/fcl”;

const TokenData = () => {

const [nftInfo, setNftInfo] = useState(null)

const fetchTokenData = async () => {

const encoded = await fcl

.send([

fcl.script`

import PinataPartyContract from 0xf8d6e0586b0a20c7

pub fun main() : {String : String} {

let nftOwner = getAccount(0xf8d6e0586b0a20c7)

let capability = nftOwner.getCapability<&{PinataPartyContract.NFTReceiver}>(/public/NFTReceiver)

let receiverRef = capability.borrow()

?? panic(“Could not borrow the receiver reference”)

return receiverRef.getMetadata(id: 1)

}

`

])

const decoded = await fcl.decode(encoded)

setNftInfo(decoded)

};

return (

<div className=”token-data”>

<div className=”center”>

<button className=”btn-primary” onClick={fetchTokenData}>Fetch Token Data</button>

</div>

{

nftInfo &&

<div>

{

Object.keys(nftInfo).map(k => {

return (

<p>{k}: {nftInfo[k]}</p>

)

})

}

<button onClick={()=>setNftInfo(null)} className=”btn-secondary”>Clear Token Info</button>

</div>

}

</div>

);

};

export default TokenData;

In this file, we are creating a component with a button to fetch token data. We have also created a button to clear token data. On clicking the fetch button, a function fetchTokenData is called. The function uses the Flow JS SDK to run the script we executed from the command line. Taking results of the execution and setting results to a state variable, nftInfo. If the variable exists, key-value pairs are rendered from the NFT metadata on screen and a button to clear the data.

Getting Media from IPFS

As we have already signed up on the Pinata account and added the video file to IPFS via the Pinata upload interface, you are navigated to the Pinata IPFS gateway where IPFS content is displayed when you click on a hash in the Pin Explorer.

In the TokenData.js file, add a way to display the video file retrieved from IPFS. Update the file to look like this:

import React, { useState } from “react”;

import * as fcl from “@onflow/fcl”;

const TokenData = () => {

const [nftInfo, setNftInfo] = useState(null)

const fetchTokenData = async () => {

const encoded = await fcl

.send([

fcl.script`

import PinataPartyContract from 0xf8d6e0586b0a20c7

pub fun main() : {String : String} {

let nftOwner = getAccount(0xf8d6e0586b0a20c7)

let capability = nftOwner.getCapability<&{PinataPartyContract.NFTReceiver}>(/public/NFTReceiver)

let receiverRef = capability.borrow()

?? panic(“Could not borrow the receiver reference”)

return receiverRef.getMetadata(id: 1)

}

`

])

const decoded = await fcl.decode(encoded)

setNftInfo(decoded)

};

return (

<div className=”token-data”>

<div className=”center”>

<button className=”btn-primary” onClick={fetchTokenData}>Fetch Token Data</button>

</div>

{

nftInfo &&

<div>

{

Object.keys(nftInfo).map(k => {

return (

<p>{k}: {nftInfo[k]}</p>

)

})

}

<div className=”center video”>

<video id=”nft-video” canplaythrough controls width=”85%”>

<source src={`https://ipfs.io/ipfs/${nftInfo[“uri”].split(“://”)[1]}`} type=”video/mp4″ />

</video>

<div>

<button onClick={() => setNftInfo(null)} className=”btn-secondary”>Clear Token Info</button>

</div>

</div>

</div>

}

</div>

);

};

export default TokenData;

A video element with a source points to the file on IPFS. URI created with NFT looks like ipfs://Qm…

We made it like this because the IPFS desktop client allows clicking on and opening links.

NFT will now be a real live digital asset on the blockchain.

Now, we will enable the transfer of NFTs.

Transferring NFTs

First of all, we need to create contracts for setting up a marketplace. Contracts will be created for:

  • Payment mechanism for fungible tokens
  • Token supply settings
  • Token transfer capabilities

Let’s create a fungible token contract used for payments when purchasing NFTs.

We will create a fungible token contract by defining the empty contract:

Token pub variables associated with token and provider resources need to be added to the contract.

pub var totalSupply: UFix64

pub var tokenName: String

pub resource interface Provider {

pub fun withdraw(amount: UFix64): @Vault {

post {

result.balance == UFix64(amount):

“Withdrawal amount must be the same as the balance of the withdrawn Vault”

}

}

}

Add the above contract within the empty contract.

The resource interface called Provider defines a public function, but the account owner can only call it. Then, we will define two more public resource interfaces:

pub resource interface Receiver {

pub fun deposit(from: @Vault)

}

pub resource interface Balance {

pub var balance: UFix64

}

The above interfaces go directly below the Provider resource interface. The Receiver interface involves a function that anyone can execute. It ensures that deposits into an account can be performed as long as the recipient initializes a vault to handle tokens created via the contract. The Balance resource returns a balance of the new token for any provided account.

Let’s create the Vault resource and add the following code below the Balance resource:

pub resource Vault: Provider, Receiver, Balance {

pub var balance: UFix64

init(balance: UFix64) {

self.balance = balance

}

pub fun withdraw(amount: UFix64): @Vault {

self.balance = self.balance – amount

return <-create Vault(balance: amount)

}

pub fun deposit(from: @Vault) {

self.balance = self.balance + from.balance

destroy from

}

}

Add the below function under the vault interface:

pub fun createEmptyVault(): @Vault {
return <-create Vault(balance: 0.0)
}

As the name implies, the function creates an empty Vault resource for an account. The balance is 0.

Therefore, we now need to set up the minting capability. Add the following code below the createEmptyVault function:

pub resource VaultMinter {

pub fun mintTokens(amount: UFix64, recipient: Capability<&AnyResource{Receiver}>)

{

let recipientRef = recipient.borrow()

?? panic(“Could not borrow a receiver reference to the vault”)

PinnieToken.totalSupply = PinnieToken.totalSupply + UFix64(amount)

recipientRef.deposit(from: <-create Vault(balance: amount))

}

}

The VaultMinter resource is public, but it is only available to the contract account owner.

The VaultMinter resource only includes one function: mintTokens that needs an amount to mint and a recipient. The newly minted tokens can be deposited into that account as long as the recipient will have the Vault resource stored.

The totalSupply variable needs to be updated when tokens are minted. Therefore, the minted amount is added to the previous supply for getting the new supply.

Now, we need to initialize the contract and add the below code after VaultMinter resource:

init() {

self.totalSupply = 30.0

self.tokenName = “Pinnie”

let vault <- create Vault(balance: self.totalSupply)

self.account.save(<-vault, to: /storage/MainVault)

self.account.save(<-create VaultMinter(), to: /storage/MainMinter)

self.account.link<&VaultMinter>(/private/Minter, target: /storage/MainMinter)

}

It is essential to set a total supply when initializing the contract. In this example, we have initialized the contract with a supply of 30 and set the token name as “Pinnie.”

Deploying and Minting Tokens

Update the flow.json file in the project to deploy the new contract. Ensure that the flow.json file references the new contract and has the emulator-account key reference:

{

“emulators”: {

“default”: {

“port”: 3569,

“serviceAccount”: “emulator-account”

}

},

“contracts”: {

“PinataPartyContract”: “./cadence/contracts/PinataPartyContract.cdc”,

“PinnieToken”: “./cadence/contracts/PinnieToken.cdc”

},

“networks”: {

“emulator”: {

“host”: “127.0.0.1:3569”,

“chain”: “flow-emulator”

}

},

“accounts”: {

“emulator-account”: {

“address”: “f8d6e0586b0a20c7”,

“keys”: “e5ca2b0946358223f0555206144fe4d74e65cbd58b0933c5232ce195b9058cdd”

}

},

“deployments”: {

“emulator”: {

“emulator-account”: [“PinataPartyContract”, “PinnieToken”]

}

}

}

In another terminal window within your pinata-party project directory, run flow project deploy. Now, let’s test the minting function. We will create a transaction that allows us to create Pinnie tokens. But first, we need to update the flow.json file.

Change json under the emulator-account:

“emulator-account”: {
“address”: “f8d6e0586b0a20c7”,
“privateKey”: “e5ca2b0946358223f0555206144fe4d74e65cbd58b0933c5232ce195b9058cdd”,
“sigAlgorithm”: “ECDSA_P256”,
“hashAlgorithm”: “SHA3_256”,
“chain”: “flow-emulator”
},

The key field becomes the privateKey field and we add properties, including sigAlgorithm, chain and hashAlgorithm.

Developing an NFT marketplace

Before working on the marketplace’s front end, we should have a contract to handle the marketplace creation and management.

In cadence/contracts folder, create a new file called MarketplaceContract.cdc.

import PinataPartyContract from 0xf8d6e0586b0a20c7

import PinnieToken from 0xf8d6e0586b0a20c7

pub contract MarketplaceContract {

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)

pub resource interface SalePublic {

pub fun purchase(tokenID: UInt64, recipient: &AnyResource{PinataPartyContract.NFTReceiver}, buyTokens: @PinnieToken.Vault)

pub fun idPrice(tokenID: UInt64): UFix64?

pub fun getIDs(): [UInt64]

}

}

We need to import both the NFT contract and the fungible token contract. We have defined four events inside the contract definition:

  • ForSale: NFTs for sale
  • PriceChanged: Change in the price for NFTs
  • TokenPurchased: When NFTs are purchased
  • SaleWithdrawn: When NFTs are removed from the marketplace

We have added a resource interface called SalePublic below event emitters. The interface should be public to everyone, not only the contract owner.

We need to add a SaleCollection resource below the SalePublic interface. We have defined a few variables within this resource. For example, mapping of tokens for sale, mapping prices for each token for sale and a protected variable that is only accessible by the contract owner called ownerVault.

We need to initialize variables when defining them on a resource. It is done in the init function and is initialized with the owner’s vault resource and empty values.

Then, it is important to define functions to control the actions of an NFT Marketplace. Functions are:

  • listForSale
  • changePrice
  • withdraw
  • idPrice
  • purchase
  • destroy
  • getIDs

As mentioned above, three of these functions are available publicly, it implies that listForSale, withdraw, destroy and changePrice are only available to the NFT owners being listed. For example, changePrice is not available publicly because we don’t want anyone to change the price of NFTs. The last part of the marketplace contract is the CreateSaleCollection function.

It allows a collection to be added as a resource to the account. After writing that contract, we will deploy it with the emulator account. Run the following command from the root of your project:

flow project deploy

It will finally deploy the marketplace contract and allow us to use it within the frontend application.

Once it is done, we need to work on the frontend and connect these contracts using the Flow CLI tool.

If you are looking to build a custom NFT marketplace, consult our NFT development experts who will help you from consulting to the marketplace platform’s development and launch. 

Author’s Bio

Akash Takyar
Akash Takyar
CEO LeewayHertz
Akash Takyar is the founder and CEO at LeewayHertz. With the experience of building over 100+ platforms for startups and enterprise 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