Build a Digital Collectibles Portal Using Flow and Cadence (Part 1)
Tags: webdev, web3, blockchain, flow
Published: 2024-02-13 16:25:02

![Article Image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/52jkeccri5yr4ryzoq2k.jpg)

In this tutorial, we’ll learn how to build a website for collecting digital collectibles (or NFTs) on the blockchain Flow. We'll use the smart contract language Cadence along with React to make it all happen. We'll also learn about Flow, its advantages, and the fun tools we can use.

By the end of this article, you’ll have the tools and knowledge you need to create your own decentralized application on the Flow blockchain.

Let’s dive right in!

![Image #3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/394zlkyxsl7rbb6flaei.png)

## **What are we building?**

We're building an application for digital collectibles. Each collectible is a Non-Fungible Token (NFT). [(If you are new and don’t understand NFT, then take a look here.)](https://developers.flow.com/build/guides/nft) Our app will allow you to collect NFTs, and each item will be unique from the others.

To make all this work, we’ll use Flow's NonFungibleToken Standard, which is a set of rules that helps us manage these special digital items (similar to ERC-721 in Ethereum). 

### **Prerequisites**  

Before you begin, be sure to install the Flow CLI on your system. If you haven't done so, follow these [installation instructions](https://developers.flow.com/tooling/flow-cli/install).

### **Setting Up**

If you're ready to kickstart your project, first, type in the command flow setup.

This command does some magic behind the scenes to set up the foundation of your project. It creates a folder system and sets up a file called flow.json to configure your project, making sure everything is organized and ready to go!

![Image #6](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sfpm72lottjh3r2zd7f2.png)

## **Project Structure**

The project will contain a `cadence` folder and `flow.json` file. (A flow.json file is a configuration file for your project, automatically maintained.)  
  
The Cadence folder contains the following:

* /contracts: Contains all Cadence contracts.
    
* /scripts: Holds all Cadence scripts.
    
* /transactions: Stores all Cadence transactions.
    

Follow the steps below to use Flow NFT Standard.

### **Step 1: Create a file.**

First, go to the `flow-collectibles-portal` folder and find the `cadence` folder. Then, open the `contracts` folder. Make a new file and name it `NonFungibleToken.cdc`.

### **Step 2: Copy and paste.**

Now, open the link named [NonFungibleToken](https://github.com/onflow/flow-nft/blob/master/contracts/NonFungibleToken.cdc) which contains the NFT standard. Copy all the content from that file and paste it into the new file you just created ("NonFungibleToken.cdc"). That's it! You've successfully set up the standards for your project.  
  
Now, let’s write some code!

However, before we dive into coding, it's important for developers to establish a mental model of how to structure their code.

At the top level, our codebase consists of three main components:

1. NFT: Each collectible is represented as an NFT.
    
2. Collection: A collection refers to a group of NFTs owned by a specific user.
    
3. Global Functions and Variables: These are functions and variables defined at the global level for the smart contract and are not associated with any particular resource.
    

## **Smart Contract Structure**

### **Smart contract basic structure**

Create a new file named `Collectibles.cdc` inside `cadence/contracts`. This is where we will write the code.

Contract Structure

```javascript
import NonFungibleToken from "./NonFungibleToken.cdc"

pub contract Collectibles: NonFungibleToken{

  pub var totalSupply: UInt64
  // other code will come here

  init(){
  	self.totalSupply = 0
  }
}
```

Let's break down the code line by line:

1. First, we'll need to standardize that we are building an NFT by including the so-called "NonFungibleToken". This is an NFT standard built by Flow which defines the following set of functionality that must be included by each NFT smart contract.
    
2. After importing, let's create our contract. To do that we use `pub contract [contract name]`. Use the same syntax each time you create a new contract. You can fill in the `contract name` with whatever you’d like to call your contract. In our case, let’s call it `Collectibles`.
    
3. Next, we want to make sure our contract follows a certain set of functionality and rules of NonFungibleToken. To do that, we add NonFungibleToken interface with the help of `:`.  
    Like this (`` `pub contract Collectibles: NonFungibleToken{}` ``)
    

1. Every single contract **MUST** have the `init()` function. It is called when the contract is initially deployed. This is similar to what Solidity calls a Constructor.
    
2. Now let’s create a global variable called `totalSupply` with a data type `UInt64`. This variable will keep track of your total Collectibles.
    
3. Now initialize `totalSupply` with value `0`.
    

That's it! We set up the foundation for our `Collectibles` contract. Now we can start adding more features and functionalities to make it even more exciting.

Before moving forward, please check out the code snippet to understand how we define variables in Cadence:

![Image #4](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hgjvr13d2rcq4abri626.png)

#### **Resource NFT**

Add the following code to your smart contract:

```javascript
import NonFungibleToken from "./NonFungibleToken.cdc"
pub contract Collectibles: NonFungibleToken{
  // above code…

  pub resource NFT: NonFungibleToken.INFT{
        pub let id: UInt64
        pub var name: String
        pub var image: String

        init(_id:UInt64, _name:String, _image:String){
            self.id = _id
            self.name = _name
            self.image = _image
        }
    }
  // init()...
}
```

As you have seen before, the contract implements the NFT standard interface, represented by `pub contract Collectibles: NonFungibleToken`. Similarly, resources can also implement various resource interfaces. So let’s add `NonFungibleToken.INFT` interface to the NFT Resource, which mandates the existence of a public property called id within the resource.  
  
Here are the variables we will use in the NFT resource:

* `id:` Maintains the ID of NFT
    
* `name:` Name of the NFT.
    
* `image:` Image URL of NFT.
    

After defining the variable, be sure to initialize the variable in the `init()` function.

Let’s move forward and create another resource called `Collection Resource`.

#### **Collection Resource** 

First, you need to understand how `Collection Resources` work. 

If you need to store a music file and several photos on your laptop, what would you do? Typically, you’d navigate to a local drive (let’s say your D-Drive) and create a `music` folder and `photos` folder. You’d then copy and paste the music and photo files into your destination folders.  
  
Similarly, this is how your digital collectibles on Flow work.

 Imagine your laptop as a `Flow Blockchain Account`, your D-Drive as `Account Storage`, and Folder as a `Collection`.

So when interacting with any project to buy NFTs, the project creates its `collection` in your `account storage`, similar to creating a folder on your D-Drive. When you interact with 10 different NFT projects, you’ll end up with 10 different collections in your account. 

It's like having a personal space to store and organize your unique digital treasures!

```javascript
import NonFungibleToken from "./NonFungibleToken.cdc"
pub contract Collectibles: NonFungibleToken{

  //Above code NFT Resource…

  // Collection Resource
  pub resource Collection{

  }


 // Below code…
}
```

Each `collection` has a `ownedNFTs` variable to hold the `NFT Resources`.

```javascript
pub resource Collection {
        pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}
        init(){
            self.ownedNFTs <- {}
        }
}
```

**Resource Interfaces**

A `resource` interface in Flow is similar to interfaces in other programming languages. It sits on top of a resource and ensures that the resource that implements it has the required functionality as defined by the interface. It can also be used to restrict access to the whole resource and be more restrictive in terms of access modifiers than the resource itself. 

In the `NonFungibleToken` standard, there are several resource interfaces like `INFT`, `Provider`, `Receiver`, and `CollectionPublic`. Each of these interfaces has specific functions and fields that need to be implemented by the resource that uses them.

In this contract, we’ll use these three interfaces from `NonFungibleToken: Provider`, `Receiver`, and `CollectionPublic`. These interfaces define functions such as `deposit`, `withdraw`, `borrowNFT`, and `getIDs`. We’ll explain each of these in detail as we go.

We will also add some events that we’ll emit from these functions, as well as declare some variables we’ll use further along in the tutorial.

```javascript
pub contract Collectibles:NonFungibleToken{

  // rest of the code…

  pub event ContractInitialized()
  pub event Withdraw(id: UInt64, from: Address?)
  pub event Deposit(id: UInt64, to: Address?)

  pub let CollectionStoragePath: StoragePath
  pub let CollectionPublicPath: PublicPath


  pub resource interface CollectionPublic{
  		pub fun deposit(token: @NonFungibleToken.NFT)
  		pub fun getIDs(): [UInt64]
        	pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT
    	}

  pub resource Collection: CollectionPublic, 
				   NonFungibleToken.Provider,
				   NonFungibleToken.Receiver,
   NonFungibleToken.CollectionPublic{
        
	pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}
        init(){
            self.ownedNFTs <- {}
        }
  }
}
```

**Withdraw**

Now, let's create the `withdraw()` function required by the interface.

```javascript
pub resource Collection: CollectionPublic, 
				   NonFungibleToken.Provider,
				   NonFungibleToken.Receiver,
   NonFungibleToken.CollectionPublic{
  // other code
  pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
    let token <- self.ownedNFTs.remove(key: withdrawID) ??
                 panic("missing NFT")
    emit Withdraw(id: token.id, from: self.owner?.address)
    return <- token
  }
     
  init()...
}
```

With the help of this function, you can move the NFT resource out of the collection. If it:

* **Fails**: Panic and throws an error.
    
* **Successful**: It emits a withdraw event and returns the resource to the caller. 
    

The caller can then use this resource and save it within their account storage.

**Deposit**

Now it’s time for the `deposit()` function required by `NonFungibleToken.Receiver`.

```javascript
pub resource Collection: CollectionPublic, 
				   NonFungibleToken.Provider,
				   NonFungibleToken.Receiver,
   NonFungibleToken.CollectionPublic{
  // other code
  pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
    let token <- self.ownedNFTs.remove(key: withdrawID) ??
                 panic("missing NFT")
    emit Withdraw(id: token.id, from: self.owner?.address)
    return <- token
  }
  
  pub fun deposit(token: @NonFungibleToken.NFT) {
       let id = token.id
       let oldToken <- self.ownedNFTs[id] <-token
       destroy oldToken
       emit Deposit(id: id, to: self.owner?.address)
  }
     
  init()...
}
```

**Borrow and GetID**

Now let’s focus on the two functions required by `NonFungibleToken.CollectionPublic: borrowNFT()` and `getID()`.

```javascript
pub resource Collection: CollectionPublic, 
				   NonFungibleToken.Provider,
				   NonFungibleToken.Receiver,
   NonFungibleToken.CollectionPublic{

  // other code
  pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
    let token <- self.ownedNFTs.remove(key: withdrawID) ??
                 panic("missing NFT")
    emit Withdraw(id: token.id, from: self.owner?.address)
    return <- token
  }

  pub fun deposit(token: @NonFungibleToken.NFT) {
       let id = token.id
       let oldToken <- self.ownedNFTs[id] <-token
       destroy oldToken
       emit Deposit(id: id, to: self.owner?.address)
  }

  pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT {
    if self.ownedNFTs[id] != nil {
        return (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)!
    }
    panic("NFT not found in collection.")
  }

  pub fun getIDs(): [UInt64]{
    return self.ownedNFTs.keys
  }

  init()...
}
```

**Destructor**

The last thing we need for the Collection Resource is a destructor.

```javascript
destroy (){
    destroy self.ownedNFTs
}
```

Since the Collection resource contains other resources (NFT resources), we need to specify a destructor. A destructor runs when the object is destroyed. This ensures that resources are not left "homeless" when their parent resource is destroyed. We don't need a destructor for the NFT resource as it doesn’t contain any other resources. 

Let’s look at the complete collection resource source code:

```javascript
import NonFungibleToken from "./NonFungibleToken.cdc"

pub contract Collectibles: NonFungibleToken{

    pub var totalSupply: UInt64

    pub resource NFT: NonFungibleToken.INFT{
        pub let id: UInt64
        pub var name: String
        pub var image: String

        init(_id:UInt64, _name:String, _image:String){
            self.id = _id
            self.name = _name
            self.image = _image
        }
    }

    pub resource interface CollectionPublic{
        pub fun deposit(token: @NonFungibleToken.NFT)
        pub fun getIDs(): [UInt64]
        pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT
    }

    pub event ContractInitialized()
    pub event Withdraw(id: UInt64, from: Address?)
    pub event Deposit(id: UInt64, to: Address?)

    pub let CollectionStoragePath: StoragePath
    pub let CollectionPublicPath: PublicPath


    pub resource Collection: CollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic{
        pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}

        init(){
            self.ownedNFTs <- {}
        }

        destroy (){
            destroy self.ownedNFTs
        }

        pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
            let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
            emit Withdraw(id: token.id, from: self.owner?.address)
            return <- token
        }

        pub fun deposit(token: @NonFungibleToken.NFT) {
            let id = token.id
            let oldToken <- self.ownedNFTs[id] <-token
            destroy oldToken
            emit Deposit(id: id, to: self.owner?.address)
        }

        pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT {
            if self.ownedNFTs[id] != nil {
                return (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)!
            }
            panic("NFT not found in collection.")
        }

        pub fun getIDs(): [UInt64]{
            return self.ownedNFTs.keys
        }
    }

    init(){
        self.CollectionPublicPath = /public/NFTCollection
        self.CollectionStoragePath = /storage/NFTCollection

        self.totalSupply = 0
        emit ContractInitialized()
    }
}
```

Now we have finished all the resources. Next, we’ll look at the global function.

#### **Global Function**

Global Functions are functions that are defined on the global level of the smart contract, meaning they are not part of any resource. These are accessible and called by the public, and expose the core functionality of the smart contract to the public.

1. **createEmptyCollection**: This function initializes an empty `Collectibles.Collection` into caller account storage.
    
2. **checkCollection**: This handy function helps you discover whether or not your account already has a `collection` resource. 
    
3. **mintNFT**: This function is super cool because it allows anyone to create an NFT.
    

```javascript
// pub resource Collection…

pub fun createEmptyCollection(): @Collection{
        return <- create Collection()
}

pub fun checkCollection(_addr: Address): Bool{
    return getAccount(_addr)
    .capabilities.get<&{Collectibles.CollectionPublic}>
    (Collectibles.CollectionPublicPath)!
    .check()
}

pub fun mintNFT(name:String, image:String): @NFT{
    Collectibles.totalSupply = Collectibles.totalSupply + 1 
    let nftId = Collectibles.totalSupply
    var newNFT <- create NFT(_id:nftId, _name:name, _image:image)
    return <- newNFT
}

init()...
```

## **Wrapping Up the Smart Contract**

And now, FINALLY, with everything in place, we’re done writing our smart contract. Take a look at the final code [here](https://contractbrowser.com/A.b54ef3bb122808cb.Collectibles).

Now let’s look at how a user interacts with smart contracts deployed on the Flow blockchain.  
There are two steps to interact with the Flow blockchain:

1. Mutate the state by running transactions.
    
2. Query the blockchain by running a script.
    

### **Mutate the state by running transactions**

Transactions are cryptographically signed data that contain a set of instructions that interact with the smart contract to update the Flow state. In simple terms, this is like a function call that changes the data on the blockchain. Transactions usually involve some cost, which can vary depending on the blockchain you are on.

A transaction includes multiple optional phases: `prepare`, `pre`, `execute`, and `post` phase.

[You can read more about this in the Cadence reference document on transactions](https://cadence-lang.org/docs/language/transactions). Each phase has a purpose, the two most important phases are `prepare` and `execute`.

`Prepare Phase`: This phase is used to access data and information inside the signer's account (allowed by the AuthAccount type).

`Execute Phase`: This phase is used to execute actions.

Now let’s create a transaction for our project.

Follow the steps below to create a transaction in your project folder.

#### **Step 1: Create a file.**

First, go to the project folder and open the `cadence` folder. Inside it, open the `transaction` folder and make a new file with the name `Create_Collection.cdc` and `mint_nft.cdc`

#### **Step 2: Add the Create Collection Transaction code.**

```javascript
import Collectibles from "../contracts/Collectibles.cdc"
transaction {
    prepare(signer: AuthAccount) {
        if signer.borrow<&Collectibles.Collection>(from: Collectibles.CollectionStoragePath) == nil {
            let collection <- Collectibles.createEmptyCollection()
            signer.save(<-collection, to: Collectibles.CollectionStoragePath)
            let cap = signer.capabilities.storage.issue<&{Collectibles.CollectionPublic}>(Collectibles.CollectionStoragePath)
            signer.capabilities.publish( cap, at: Collectibles.CollectionPublicPath)
        }
    }
}
```

Let's break down this code line by line:

1. This transaction interacts with the Collectibles smart contract. Then, it checks if the sender (signer) has a Collection resource stored in their account by borrowing a reference to the Collection resource from the specified storage path `Collectibles.CollectionStoragePath`. If the reference is nil, it means the signer does not yet have a collection.
    
2. If the signer does not have a collection, then it creates an empty collection by calling the `createEmptyCollection()` function.
    
3. After creating the empty collection, place it into the signer's account under the specified storage path `Collectibles.CollectionStoragePath`.
    

This establishes a link between the signer's account and the newly created collection using `link()`.

#### **Step 3: Add the Mint NFT Transaction code.**

```javascript
import NonFungibleToken from "../contracts/NonFungibleToken.cdc"
import Collectibles from "../contracts/Collectibles.cdc"

transaction(name:String, image:String){
    let receiverCollectionRef: &{NonFungibleToken.CollectionPublic}
    
prepare(signer:AuthAccount){
     self.receiverCollectionRef =
            signer.borrow<&Collectibles.Collection>(from:
Collectibles.CollectionStoragePath)
?? panic("could not borrow Collection reference")
}
    
execute{
        let nft <- Collectibles.mintNFT(name:name, image:image)
        self.receiverCollectionRef.deposit(token: <-nft)
    }
}
```

Let's break down this code line by line:

1. We first import the `NonFungibleToken` and `Collectibles contract`.
    
2. `transaction(name: String, image: String)` This line defines a new transaction. It takes two arguments, name and image, both of type String. These arguments are used to pass the name and image of the NFT being minted.
    
3. `let receiverCollectionRef: &{NonFungibleToken.CollectionPublic}` This line declares a new variable `receiverCollectionRef.` It is a reference to a public collection of NFTs of type `NonFungibleToken.CollectionPublic`. This reference will be used to interact with the collection where we will deposit the newly minted NFT.
    
4. `prepare(signer: AuthAccount)` This line starts the prepare block, which is executed before the transaction. It takes an argument signer of type `AuthAccount`. `AuthAccount` represents the account of the transaction's signer.
    
5. It borrows a reference to the `Collectibles.Collection` from the signer's storage inside the prepare block. It uses the borrow function to access the reference to the collection and store it in the `receiverCollectionRef` variable. If the reference is not found (if the collection doesn't exist in the signer's storage, for example), it will throw the error message “could not borrow Collection reference”.
    
6. The `execute` block contains the main execution logic for the transaction. The code inside this block will be executed after the `prepare` block has successfully completed.
    
7. `nft <- Collectibles.mintNFT(_name: name, image: image)` Inside the `execute` block, this line calls the `mintNFT` function from the `Collectibles` contract with the provided name and image arguments. This function is expected to create a new NFT with the given name and image. The `<-` symbol indicates that the NFT is being received as an object that can be moved (a resource).
    

1. `self.receiverCollectionRef.deposit(token: <-nft)` This line deposits the newly minted NFT into the specified collection. It uses the deposit function on the `receiverCollectionRef` to transfer ownership of the NFT from the transaction's executing account to the collection. The `<-` symbol here also indicates that the NFT is being moved as a resource during the `deposit` process.
    

### **Query the blockchain by running a Script**

We use a script to view or read data from the blockchain. Scripts are free and don’t need signing.  
  
Follow the steps below to create a script in your project folder.

#### **Step 1: Create a file.**

First, go to the project folder and open the `cadence` folder. Inside it, open the `script` folder and make a new file with the name `view_nft.cdc`.

#### **Step 2: View NFT Script**

```javascript
import NonFungibleToken from "../contracts/NonFungibleToken.cdc"
import Collectibles from "../contracts/Collectibles.cdc"

pub fun main(user: Address, id: UInt64): &NonFungibleToken.NFT? {
  	let collectionCap= getAccount(user).capabilities
.get<&{Collectibles.CollectionPublic}>(/public/NFTCollection) 
      ?? panic("This public capability does not exist.")

  	let collectionRef = collectionCap.borrow()!

  	return collectionRef.borrowNFT(id: id)
}
```

Let's break down this code line by line:

1. First we import the `NonFungibleToken` and `Collectibles` contract.
    
2. `pub fun main(acctAddress: Address, id: UInt64): &NonFungibleToken.NFT?` This line defines the entry point of the script, which is a public function named main. The function takes two parameters:
    

* `acctAddress`: An `Address` type parameter representing the address of an account on the Flow blockchain.
    
* `id`: A `UInt64` type parameter representing the unique identifier of the NFT within the collection.
    

1. Then we use `getCapability` to fetch the `Collectibles.Collection` capability for the specified `acctAddress`. A capability is a reference to a resource that allows access to its functions and data. In this case, it is fetching the capability for the `Collectibles.Collection` resource type.
    
2. Then, we borrow an NFT from the `collectionRef` using the `borrowNFT` function. The `borrowNFT` function takes the `id` parameter, which is the unique identifier of the NFT within the collection. The `borrow` function on a capability allows reading the resource data.
    
3. Finally, we return the NFT from the function.
    

#### **Step 3: Testnet Deployment**

Now, it's time to deploy our smart contract to the Flow testnet.

**1\. Set up a Flow account.**

Run the following command in the terminal to generate a Flow account:

Be sure to write down your public key and private key.

Next, we’ll head over to [the Flow Faucet](https://testnet-faucet.onflow.org/), create a new address based on our keys, and fund our account with some test tokens. Complete the following steps to create your account:

1. Paste in your public key in the specified input field.
    
2. Keep the Signature and Hash Algorithms set to default.
    
3. Complete the Captcha.
    
4. Click on Create Account.

![Image #5](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ll2ag8a98bprd16672n.png)

After setting up an account, we receive a dialogue with our new Flow address containing 1,000 test Flow tokens. **Copy the address so we can use it going forward**.

**2\. Configure the project.**

Now let’s configure our project. Initially when we set up the project, it created a `flow.json` file. This is the configuration file for the Flow CLI, and defines the configuration for actions that the Flow CLI can perform for you. Think of this as roughly equivalent to `hardhat.config.js` on Ethereum.

Now open your code editor and copy and paste the below code into your `flow.json` file.

```javascript
{
  "contracts": {
	"Collectibles": "./cadence/contracts/Collectibles.cdc",
"NonFungibleToken": {
		"source": "./cadence/contracts/NonFungibleToken.cdc",
		"aliases": {
			"testnet": "0x631e88ae7f1d7c20"
		}
	}		
  },
  "networks": {
	"testnet": "access.devnet.nodes.onflow.org:9000"
  },
  "accounts": {
	"testnet-account": {
		"address": "ENTER YOUR ADDRESS FROM FAUCET HERE",
		"key": "ENTER YOUR GENERATED PRIVATE KEY HERE"
	}
  },
  "deployments": {
	"testnet": {
		"testnet-account": [
			"Collectibles"
		]
	}
  }
}
```

**3\. Copy and paste.**

Paste your generated private key at the place (key : “ENTER YOUR GENERATED PRIVATE KEY HERE”) in the code.

**4\. Execute.**

Now execute the code on the testnet. Go to the terminal and run the following code:

```javascript
flow project deploy --network testnet
```

**5\. Wait for confirmation.**

After submitting the transaction, you'll receive a transaction ID. Wait for the transaction to be confirmed on the testnet, indicating that the smart contract has been successfully deployed.

![Image #2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a0kwey00pwhwzkk4q4ki.png)

Check your deployed contract [here](https://contractbrowser.com/A.b54ef3bb122808cb.Collectibles).

Check the full code on [Github](https://github.com/DappCoderr/Flow-NFT/tree/master/cadence).

## **Final Thoughts and Congratulations!** 

Congratulations! You have now built a collectibles portal on the Flow blockchain and deployed it to the testnet. What’s next? Now you can work on building the frontend which we will cover in part 2 of this series. 

Have a really great day!