Flow: How to Create an NFT Marketplace with Filebase + Flow
Learn how to create an NFT marketplace using Filebase and Flow.
NFT marketplaces like OpenSea are skyrocketing in value right now due to the rise in popularity of NFTs and the revenue generated by being a marketplace for users to buy and sell NFTs. Most marketplaces take a percentage of the NFT’s sale price, similar to other ecommerce marketplaces such as Etsy or Ebay.
But what if you create your own NFT marketplace for your NFT collection? You can cut out the middleman and avoid paying any fees associated with listing your NFTs on a third party marketplace.
In this guide, we’ll go over how to create your own NFT marketplace app using the Flow blockchain and Filebase for storage. You will learn:
- How to create smart contracts with Cadence Language.
- How to mint tokens and store the metadata in a Filebase bucket.
- How to authenticate user wallets.
- How to transfer tokens.
Once you've completed this guide, you'll have all the backend fundamentals of an NFT marketplace that you can use to develop a frontend user interface to create a full marketplace.
Let’s go over a few terms before we get started. Flow uses the Cadence Language for creating smart contracts, tracking transactions, and running scripts.
A blockchain is a distributed, digital ledger that tracks and records transactions and the ownership of a resource.
A resource can be any object or item, such as currency or goods, both physical and digital. In this case, we will be using cryptocurrency. Resources are stored in user accounts and are accessible through access control properties that are defined in a smart contract.
An interface defines the behaviors or abilities of a resource.
An account owns resources or has access to the resources. Each account keeps a personal ledger of its spending or transferring of resources.
A contract is a ruleset used to govern actions. Actions that break a contract are punishable and have repercussions. In our guide, contracts are the initial program that gets deployed to a blockchain that initiates your application’s ability to access resources you create on the blockchain.
A script in this guide refers to a Cadence program used to read blockchain information and availability. A common script is a blockchain explorer that queries the current state of the blockchain.
Now that we have a basic understanding of these terms, let’s get started building out the NFT marketplace app.
To do this, navigate to console.filebase.com. If you don’t have an account already, sign up, then log in.
Select ‘Create Bucket’ in the top right corner to create a new bucket for your NFTs.

Bucket names must be unique across all Filebase users, be between 3 and 63 characters long, and can contain only lowercase characters, numbers, and dashes.




Take note of the IPFS CID. We will reference this later.
npx create-react-app nftmarketplace; cd nftmarketplace
Then we need to initialize a Flow project with the command:
flow init
This will result in a new React project with the file
flow.json
stored inside. This file is important because it contains configuration information for our app such as where our contracts will be stored.Do not remove the other lines that are in the file:
{
// Existing code
"contracts": {
"nftmarketplace": "./src/flow/contract/nftmarketplace.cdc"
},
"deployments": {
"emulator": {
"emulator-account": ["nftmarketplace"]
}
},
// Existing code
}
Create these directories with the following command:
mkdir -p src/flow/{contract,transaction,script}
Then in each directory, create a Cadence file with the following names:
contract/nftMarketplace.cdc
transaction/mintToken.cdc
script/getTokenIds.cdc
Copy and paste the following code into the file:
pub contract nftmarketplace {
// This dictionary stores token owners' addresses.
pub var owners: {UInt64: Address}
pub resource NFT {
// The Unique ID for each token, starting from 1.
pub let id: UInt64
// String -> String dictionary to hold
// token's metadata.
pub var metadata: {String: String}
// The NFT's constructor. All declared variables are
// required to be initialized here.
init(id: UInt64, metadata: {String: String}) {
self.id = id
self.metadata = metadata
}
}
}
With this code we have declared a dictionary for storing the variable owners. This variable will be used to keep track of all current token owners globally in our marketplace.
Add the following code to the
nftMarketplace.cdc
file:pub contract nftmarketplace {
// ... The @NFT code …
pub resource interface NFTReceiver {
// Can withdraw a token by its ID and returns
// the token.
pub fun withdraw(id: UInt64): @NFT
// Can deposit an NFT to this NFTReceiver.
pub fun deposit(token: @NFT)
// Can fetch all NFT IDs belonging to this
// NFTReceiver.
pub fun getTokenIds(): [UInt64]
// Can fetch the metadata of an NFT instance
// by its ID.
pub fun getTokenMetadata(id: UInt64) : {String: String}
// Can update the metadata of an NFT.
pub fun updateTokenMetadata(id: UInt64, metadata: {String: String})
}
}
In this code, the
withdraw(id: UInt64): @NFT
method takes in the id of an NFT and in return withdraws a token with the type @NFT
.The
deposit(token: @NFT)
method deposits the token to the current NFTReceiver
.The
getTokenMetadata(id: UInt64) : {String : String}
method takes the token ID and its associated metadata and returns the ID and metadata as a dictionary.The
updateTokenMetadata(id: UInt64, metadata: {String: String})
updates the target’s NFT metadata.Copy and paste the following code into your file:
pub contract nftmarketplace {
// ... The @NFT code ...
// ... The @NFTReceiver code ...
pub resource NFTCollection: NFTReceiver {
// Keeps track of NFTs in this collection.
access(account) var ownedNFTs: @{UInt64: NFT}
// Constructor
init() {
self.ownedNFTs <- {}
}
// Destructor
destroy() {
destroy self.ownedNFTs
}
// Withdraws and returns an NFT token.
pub fun withdraw(id: UInt64): @NFT {
let token <- self.ownedNFTs.remove(key: id)
return <- token!
}
// Deposits a token to this NFTCollection instance.
pub fun deposit(token: @NFT) {
self.ownedNFTs[token.id] <-! token
}
// Returns an array of the IDs that are in this collection.
pub fun getTokenIds(): [UInt64] {
return self.ownedNFTs.keys
}
// Returns the metadata of an NFT based on the ID.
pub fun getTokenMetadata(id: UInt64): {String : String} {
let metadata = self.ownedNFTs[id]?.metadata
return metadata!
}
// Updates the metadata of an NFT based on the ID.
pub fun updateTokenMetadata(id: UInt64, metadata: {String: String}) {
for key in metadata.keys {
self.ownedNFTs[id]?.metadata?.insert(key: key, metadata[key]!)
}
}
}
// Public factory method to create a collection
// so it is callable from the contract scope.
pub fun createNFTCollection(): @NFTCollection {
return <- create NFTCollection()
}
}
This code creates a dictionary called
ownedNFTs
that can be edited. This dictionary works by mapping the token ID to the NFT resource.Then in the constructor method, the code creates an empty directory called
ownedNFTs
that will contain any deposited NFTs, and includes a destructor method that will withdraw any sold NFTs.Copy and paste the following code into your file:
pub contract nftmarketplace {
// ... NFT code ...
// ... NFTReceiver code ...
// ... NFTCollection code ...
pub resource NFTMinter {
// Declare a global variable to count ID.
pub var idCount: UInt64
init() {
// Instantialize the ID counter.
self.idCount = 1
}
pub fun mint(_ metadata: {String: String}): @NFT {
// Create a new @NFT resource with the current ID.
let token <- create NFT(id: self.idCount, metadata: metadata)
// Save the current owner's address to the dictionary.
nftmarketplace.owners[self.idCount] = nftmarketplace.account.address
// Increment the ID
self.idCount = self.idCount + 1 as UInt64
return <-token
}
}
}
Copy and paste the following code into your file:
pub contract nftmarketplace {
// ... @NFT code ...
// ... @NFTReceiver code ...
// ... @NFTCollection code ...
// This contract constructor is called once when the contract is deployed.
// It does the following:
// - Creating an empty Collection for the deployer of the collection so
// the owner of the contract can mint and own NFTs from that contract.
// - The `Collection` resource is published in a public location with reference
// to the `NFTReceiver` interface. This is how we tell the contract that the functions defined
// on the `NFTReceiver` can be called by anyone.
// - The `NFTMinter` resource is saved in the account storage for the creator of
// the contract. Only the creator can mint tokens.
init() {
// Set `owners` to an empty dictionary.
self.owners = {}
// Create a new `@NFTCollection` instance and save it in `/storage/NFTCollection` domain,
// which is only accessible by the contract owner's account.
self.account.save(<-create NFTCollection(), to: /storage/NFTCollection)
// "Link" only the `@NFTReceiver` interface from the `@NFTCollection` stored at `/storage/NFTCollection` domain to the `/public/NFTReceiver` domain, which is accessible to any user.
self.account.link<&{NFTReceiver}>(/public/NFTReceiver, target: /storage/NFTCollection)
// Create a new `@NFTMinter` instance and save it in `/storage/NFTMinter` domain, accessible
// only by the contract owner's account.
self.account.save(<-create NFTMinter(), to: /storage/NFTMinter)
}
}
This last bit of code uses the three domain namespaces that are used by Cadence. These are:
- /public: This namespace is accessible by any account that can interact with the contract.
- /private: This namespace is only accessible by accounts that are given access to the contract.
- /storage: This namespace is only accessible to the owner of the account.
All together, your contract should look like this:
pub contract nftmarketplace {
pub var ownerMap: {UInt64 : Address}
pub resource NFT {
pub let id: UInt64
init(initId: UInt64) {
self.id = initId
}
}
pub resource interface NFTReceiver {
pub fun withdraw(withdrawId: UInt64): @NFT
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}
}
pub resource Collection: NFTReceiver {
pub var ownedNFTs: @{UInt64: NFT}
pub var metadataObjs: {UInt64: { String : String }}
init() {
self.ownedNFTs <- {}
self.metadataObjs = {}
}
destroy() {
destroy self.ownedNFTs
}
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
}
// getIds returns an array of the IDs that are in the collection
pub fun getIds(): [UInt64] {
return self.ownedNFTs.keys
}
pub fun getMetadata(id: UInt64): {String : String} {
// return self.ownedNFTs[id]?.metadata!
// let token <- self.ownedNFTs[id]!
// return token.metadata
return self.metadataObjs[id]!
}
pub fun updateMetadata(id: UInt64, metadata: {String: String}) {
self.metadataObjs[id] = metadata
// let token <- self.ownedNFTs[id]!
// token.metadata = metadata
// self.ownedNFTs[id] <- token
// self.ownedNFTs[id].metadata! = metadata
// self.ownedNFTs[id]?.updateMetadata(metadata: metadata)
}
}
pub fun createEmptyCollection(): @Collection {
return <- create Collection()
}
// The minting resource.
pub resource NFTMinter {
pub var idCount: UInt64
init() {
self.idCount = 1
}
pub fun mint(): @NFT {
var newNFT <- create NFT(initId: self.idCount)
nftmarketplace.ownerMap[self.idCount] = nftmarketplace.account.address
self.idCount = self.idCount + 1 as UInt64
return <-newNFT
}
}
init() {
self.ownerMap = {}
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)
}
}
flow emulator
This will return information, such as the
FlowServiceAccount
and the port of your local HTTP server. Take note of these values.Use the following command to deploy your contract:
flow project deploy
You should receive the following output confirming your contract was successfully deployed:
Deploying 1 contracts for accounts: emulator-account
nftmarketplace -> 0xf8d6e0586b0a20c7 (589ab0b48ca8dd962a48ba991621a3e3bbd53c293f20b750f650bcab4d2fb083)
All contracts deployed successfully
This is one of the most important pieces of any NFT app. Without this, the NFTs won’t be created to be sold or traded.
Open the
mintToken.cdc
file that we created earlier. Copy and paste the following code into the file:import nftmarketplace from 0xf8d6e0586b0a20c7
transaction(metadata: {String : String}) {
let receiverRef: &{nftmarketplace.NFTReceiver}
let minterRef: &nftmarketplace.NFTMinter
// Take the account info of the user trying to execute the transaction and validate.
// Here we try to "borrow" the capabilities available on `NFTMinter` and `NFTReceiver`
// resources, and will fail if the user executing this transaction does not have access
// to these resources.
prepare(acct: AuthAccount) {
self.receiverRef = acct.getCapability<&{nftmarketplace.NFTReceiver}>(/public/NFTReceiver)
.borrow()
?? panic("Could not borrow receiver reference")
self.minterRef = acct.borrow<&nftmarketplace.NFTMinter>(from: /storage/NFTMinter)
?? panic("Could not borrow minter reference")
}
execute {
let metadata : {String : String} = {
"name": "Filebase Robot",
"type": "Robot",
"uri": "https://ipfs.filebase.io/ipfs/[IPFS-CID]"
}
// Mint the token by calling `mint()` on `@NFTMinter` resource, which returns
// an `@NFT` resource, and move it to a variable `newNFT`.
let newNFT <- self.minterRef.mint()
// Call `deposit(..)` on the `@NFTReceiver` resource to deposit the token.
// Note that this is where the metadata can be changed before transferring.
self.receiverRef.deposit(token: <-newNFT, metadata: metadata)
log("NFT Minted and deposited to Account 2's Collection")
}
}
There are a few things in this code you will need to replace:
- In the first line, replace the address to import your contract from. This will be the Flow address that you recorded when you ran the Flow emulator.
- In the execute section, there are some metadata fields. Edit these to reflect the desired metadata for your NFT. In our example, the NFT has a name and a type metadata field. Add or remove as many metadata fields as desired.
- Replace the URI metadata CID with your Filebase object IPFS CID that you recorded at the beginning of this guide.
This transaction requires metadata to mint the NFT, so we’ll pass the metadata to the code with the following syntax:
flow transactions send src/flow/transaction/mintToken.cdc {}
This command should return a message that says that the transaction has been ‘sealed’. This means that the transaction was successful.
Transaction ID: 5eb708246e30d2d8d626ca7516e995511ea84ae548e8a9bfb36cd1aee7c9c40e
Status SEALED
ID 5eb708246e30d2d8d626ca7516e995511ea84ae548e8a9bfb36cd1aee7c9c40e
Payer f8d6e0586b0a20c7
Authorizers [f8d6e0586b0a20c7]
Proposal Key:
Address f8d6e0586b0a20c7
Index 0
Sequence 1
You’ve just minted your first NFT! For our example, we created an NFT with the name ‘Apple’ and the type of ‘Fruit’ with the image we uploaded to Filebase.
This is vital to a marketplace, since what good is a marketplace where goods can’t be exchanged?
Since so far we’ve only used our Flow emulator account, we’ll need to create another account to test this transaction with. Start by getting a key pair for a new account with the command:
flow keys generate
To keep these keys safe and referenceable, create a new file called
.keys.json
and paste the keys that were returned from the generate command.flow accounts create –key PUBLIC_KEY –signer emulator-account
Take note of the address that is returned, then open the
flow.json
file and add an entry under ‘accounts’ for your test account. Replace PRIVATE_KEY
with the test account’s private key."accounts": {
"emulator-account": {
"address": "f8d6e0586b0a20c7",
"key": "cdd4101e032a8a6fd0666276c496d2d79cabc9d889635e9d74c96c7039bf63e9"
},
"test-account": {
"address": "0x01cf0e2f2f715450",
"key": "PRIVATE_KEY"
}
Like the
mintToken.cdc
file, you will need to replace the address to import your contract from. This will be the Flow address that you recorded when you ran the Flow emulator.flow transactions send src/flow/transaction/initCollection.cdc –signer test-account
Now that our test account has been initialized, let’s create our transfer transaction to transfer an NFT from our emulator account to our test account.
Copy and paste the following code into this file:
import nftmarketplace from 0xf8d6e0586b0a20c7
// This transaction is a template for a transaction that
// could be used by anyone to send tokens to another account
// that owns a Vault.
//
transaction {
// Temporary Vault object that holds the balance that is being transferred.
var tempVault: @nftmarketplace.Vault
// Recipient's account address.
var recipientAddr: Address
prepare(
acct: AuthAccount,
amount: UFix64,
recipientAddr: Address
) {
// withdraw tokens from your vault by borrowing a reerence to it
// and calling the withdraw function with that reference.
let vaultRef = acct.borrow<&nftmarketplace.Vault>(from: /storage/NFTCollection)
?? panic("Could not borrow a reference to the owner's vault")
self.tempVault <-vaultRef.withdraw(amount: amount)
self.recipientAddr = recipientAddr
}
execute {
// get the recipient's public account object
let recipient = getAccount(self.recipientAddr)
let recipientRef = recipient.getCapability(/public/NFTCollection)
.borrow<&nftmarketplace.Vault{nftmarketplace.NFTCollection}>()
?? panic("Could not borrow a reference to the recipient")
// Deposit your tokens to their Vault
receiverRef.deposit(from: <-self.tempVault)
log("Transfer succeeded!")
}
}
This code is a sequel to the initial minting process code. This code moves the token stored in a source account’s
NFTCollection
by receiving the destination account’s token and calling the withdraw(id: UInt64)
and deposit(token: @NFT)
.This command needs the
TokenID
of the NFTs to move and the destination address. TokenID
s are unsigned integers starting from 1:flow transactions send src/flow/transaction/transferToken.cdc 1 0x01cf0e2f2f715450
You just withdrew and deposited your NFT into the test account!
To start, let’s query the NFT Token that we just transferred to our test account to confirm it was successfully received.
In your scripts directory, create the file named
getTokenOwner.cdc
with the following content:import nftmarketplace from 0xf8d6e0586b0a20c7
// All scripts start with the `main` function,
// which can take an arbitrary number of argument and return
// any type of data.
//
// This function accepts a token ID and returns an Address.
pub fun main(id: UInt64): Address {
// Access the address that owns the NFT with the provided ID.
let ownerAddress = nftmarketplace.owners[id]!
return ownerAddress
}
This script will return the NFT’s metadata, which in this case should be Apple, Fruit, and the Filebase image URL.
Create a new file in the scripts directory called
getTokenMetadata.cdc
with the following content:import nftmarketplace from 0xf8d6e0586b0a20c7
// All scripts start with the `main` function,
// which can take an arbitrary number of argument and return
// any type of data.
//
// This function accepts a token ID and returns a metadata dictionary.
pub fun main(id: UInt64) : {String: String} {
// Access the address that owns the NFT with the provided ID.
let ownerAddress = nftmarketplace.owners[id]!
// We encounter the `getAccount(_ addr: Address)` function again.
// Get the `AuthAccount` instance of the current owner.
let ownerAcct = getAccount(ownerAddress)
// Borrow the `NFTReceiver` capability of the owner.
let receiverRef = ownerAcct.getCapability<&{nftmarketplace.NFTReceiver}>(/public/NFTReceiver)
.borrow()
?? panic("Could not borrow receiver reference")
// Happily delegate this query to the owning collection
// to do the grunt work of getting its token's metadata.
return receiverRef.getTokenMetadata(id: id)
}
flow scripts execute src/flow/script/getTokenMetadata.cdc 1
Now we have all the back end fundamentals for creating a marketplace! From here, you can create a user interface for the front end of your marketplace using Reactjs that references these scripts, or you can continue to use the scripts by themselves to mint and transfer NFTs.
If you have any questions, please join our Discord server, or send us an email at [email protected]
Last modified 8mo ago