If you’ve followed along with the Flow series so far, you already know that the , such as NFTs. It was built from the ground up as a better alternative to Ethereum’s network congestion and high fee issues. Flow Blockchain excels in handling digital assets In addition, the Cadence smart contract language is a language that makes creating and managing digital assets easy and efficient. Although Solidity is excellent at facilitating Web3 through smart contracts, there are drawbacks. Cadence improves upon Solidity’s flaws by providing the ability to upgrade smart contracts and features that reduce the risk of human error, among other improvements. first-of-its-kind resource-oriented programming And finally, the list of tools and libraries available to developers looking to get started is extensive. So let’s put it all together and build something on Flow. This article is a tutorial about creating a full-fledged NFT-minting dapp for the Flow Blockchain. Let’s Get To It For the rest of this article, we will walk through the process of creating an NFT minting dapp on the Flow blockchain. We will start with setting up and deploying a Cadence smart contract. Then, we will build a front end to connect to our smart contract and mint an NFT into the user’s account. The functionality we build will allow users to connect their Flow account, create an account if they don’t already have one, then select from one of three images to mint into an NFT. Then, the dapp will display the NFTs from our collection that are in the user’s account. It will be an excellent project to highlight how easy and efficient creating NFTs are on Flow and how effective the Flow Client Library (FCL) is for interacting with the blockchain. To follow along with this tutorial, you’ll need the following things: and NodeJs NPM (Flow CLI) The Flow Command Line Interface Your favorite IDE With all of these installed, let’s get started! 1. Set Up Flow Account Before we start building, we’ll need to set up an account on the Flow blockchain so we can deploy our smart contract. Run the following command to generate a new public and private key pair: flow keys generate Be sure to write down the values your console outputs, as we’ll need them in the following steps. Next, we’ll head over to to create a new address based on our keys and fund our account with some test tokens. Complete the following steps to create your account: Flow Faucet Paste in your public key in the specified input field Keep the Signature and Hash Algorithms set to default Complete the captcha Click on Create Account With a successful account generation, we get a dialogue with our new Flow address containing 1,000 FLOW tokens. Copy the address for use in the next step. 2. Set Up The Smart Contract Before we build the project frontend, let’s create the smart contract we will interact with later. In the command terminal, navigate to the folder you would like to work from and type the following command to initiate a project: flow init This command creates a file inside the folder, where we’ll place all the information we need to deploy our smart contract. flow.json Open the file in your code editor and we’ll set up a testnet account. Inside the section, we’ll add a new entry called t , which contains our new address and the key generated in the command earlier. flow.json accounts estnet-account private flow keys generate { "emulators": { "default": { "port": 3569, "serviceAccount": "emulator-account" } }, "contracts": {}, "networks": { "emulator": "127.0.0.1:3569", "mainnet": "access.mainnet.nodes.onflow.org:9000", "testnet": "access.devnet.nodes.onflow.org:9000" }, "accounts": { "emulator-account": { "address": "f8d6e0586b0a20c7", "key": "2becfbede2fb89796ab68df3ec2a23c3627235ec250a3e5da41df850a8dd4349" }, "testnet-account": { "address": "0x8e0dac5df6e8489e", "key": "c91f4716a51a66683ccb090ca3eb3e213b90e9f9ae2b1edd12defffe06c57edc" } }, "deployments": {} } Next, we’ll create a new file to write our smart contract. When writing out the code, you may notice some differences in how Cadence handles NFT creation compared to Solidity. For example, NFTs in Cadence are created as a resource and minted directly into the account of the user. In contrast, Solidity NFTs are essentially just an ID number referenced in a mapping to a specific address on the digital ledger. So with that in mind, in the same folder as the flow.json file, create a new file called FlowTutorialMint.cdc and type the following code: /* * * This is an example implementation of a Flow Non-Fungible Token. * This contract does not implement any sophisticated classification * system for its NFTs. It defines a simple NFT with minimal metadata. * */ import NonFungibleToken from 0x631e88ae7f1d7c20 import MetadataViews from 0x631e88ae7f1d7c20 pub contract FlowTutorialMint: NonFungibleToken { pub var totalSupply: UInt64 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 let MinterStoragePath: StoragePath pub struct FlowTutorialMintData{ pub let id: UInt64 pub let type: String pub let url: String init(_id: UInt64, _type: String, _url: String){ self.id = _id self.type = _type self.url = _url } } pub resource NFT: NonFungibleToken.INFT, MetadataViews.Resolver { pub let id: UInt64 pub let type: String pub let url: String init( id: UInt64, type: String, url: String, ) { self.id = id self.type = type self.url = url } pub fun getViews(): [Type] { return [ Type<FlowTutorialMintData>() ] } pub fun resolveView(_ view: Type): AnyStruct? { switch view { case Type<FlowTutorialMintData>(): return FlowTutorialMintData( _id: self.id, _type: self.type, _url: self.url ) } return nil } } pub resource interface FlowTutorialMintCollectionPublic { pub fun deposit(token: @NonFungibleToken.NFT) pub fun getIDs(): [UInt64] pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT pub fun borrowFlowTutorialMint(id: UInt64): &FlowTutorialMint.NFT? { post { (result == nil) || (result?.id == id): "Cannot borrow FlowTutorialMint reference: the ID of the returned reference is incorrect" } } } pub resource Collection: FlowTutorialMintCollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection { // dictionary of NFT conforming tokens // NFT is a resource type with an `UInt64` ID field pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT} init () { self.ownedNFTs <- {} } // withdraw removes an NFT from the collection and moves it to the caller 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 } // deposit takes an NFT and adds it to the collections dictionary // and adds the ID to the id array pub fun deposit(token: @NonFungibleToken.NFT) { let token <- token as! @FlowTutorialMint.NFT let id: UInt64 = token.id // add the new token to the dictionary which removes the old one let oldToken <- self.ownedNFTs[id] <- token emit Deposit(id: id, to: self.owner?.address) destroy oldToken } // getIDs returns an array of the IDs that are in the collection pub fun getIDs(): [UInt64] { return self.ownedNFTs.keys } // borrowNFT gets a reference to an NFT in the collection // so that the caller can read its metadata and call its methods pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT { return (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)! } pub fun borrowFlowTutorialMint(id: UInt64): &FlowTutorialMint.NFT? { if self.ownedNFTs[id] != nil { // Create an authorized reference to allow downcasting let ref = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)! return ref as! &FlowTutorialMint.NFT } return nil } pub fun borrowViewResolver(id: UInt64): &AnyResource{MetadataViews.Resolver} { let nft = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)! let flowTutorialMintNFT = nft as! &FlowTutorialMint.NFT return flowTutorialMintNFT as &AnyResource{MetadataViews.Resolver} } destroy() { destroy self.ownedNFTs } } // public function that anyone can call to create a new empty collection pub fun createEmptyCollection(): @NonFungibleToken.Collection { return <- create Collection() } pub fun mintNFT( recipient: &{NonFungibleToken.CollectionPublic}, type: String, url: String, ) { // create a new NFT var newNFT <- create NFT( id: FlowTutorialMint.totalSupply, type: type, url: url ) // deposit it in the recipient's account using their reference recipient.deposit(token: <-newNFT) FlowTutorialMint.totalSupply = FlowTutorialMint.totalSupply + UInt64(1) } init() { // Initialize the total supply self.totalSupply = 0 // Set the named paths self.CollectionStoragePath = /storage/flowTutorialMintCollection self.CollectionPublicPath = /public/flowTutorialMintCollection self.MinterStoragePath = /storage/flowTutorialMintMinter // Create a Collection resource and save it to storage let collection <- create Collection() self.account.save(<-collection, to: self.CollectionStoragePath) // create a public capability for the collection self.account.link<&FlowTutorialMint.Collection{NonFungibleToken.CollectionPublic, FlowTutorialMint.FlowTutorialMintCollectionPublic, MetadataViews.ResolverCollection}>( self.CollectionPublicPath, target: self.CollectionStoragePath ) emit ContractInitialized() } } Important things to note in the smart contract above: We are importing the and contracts to create our NFTs using Flow standards NonFungibleToken MetadataViews We define our NFT resource in the function pub resource NFT The function mints an NFT into the account that calls the function mintNFT Now we need to go back into our file to add a few things: flow.json In the section, add the contract and its path. contracts In the section add the network ( ), the account that we will use to perform the deployment ( , and the contract name ( ). deployments testnet testnet-account) FlowTutorialMint { "emulators": { "default": { "port": 3569, "serviceAccount": "emulator-account" } }, "contracts": { "FlowTutorialMint": "./FlowTutorialMint.cdc" }, "networks": { "emulator": "127.0.0.1:3569", "mainnet": "access.mainnet.nodes.onflow.org:9000", "testnet": "access.devnet.nodes.onflow.org:9000" }, "accounts": { "emulator-account": { "address": "f8d6e0586b0a20c7", "key": "2becfbede2fb89796ab68df3ec2a23c3627235ec250a3e5da41df850a8dd4349" }, "testnet-account": { "address": "0x8e0dac5df6e8489e", "key": "c91f4716a51a66683ccb090ca3eb3e213b90e9f9ae2b1edd12defffe06c57edc" } }, "deployments": { "testnet": { "testnet-account": [ "FlowTutorialMint" ] } } } The final step in setting up the smart contract is to it to the testnet. To do that, type the following command in the project folder in your terminal: deploy flow project deploy -n=testnet We should receive an output stating the contract was deployed successfully: It’s important to note here that Cadence smart contracts exist in the storage of the account that deploys them, whereas with Solidity, the smart contract exists at its own address on the blockchain. Although there are limits to the account’s storage capacity, these are relative to the amount of FLOW tokens reserved in the account. You can learn more about account storage in the . Flow Developer Portal Awesome! Now let’s build a simple frontend to interact with our contract. 3. Creating The Frontend For the frontend of this project, we will be using React. First, navigate to a new folder and run the following command to create a React project: npx create-react-app flow-tutorial Next, navigate into the flow-tutorial folder and install the (FCL): Flow Client Library npm i -S @onflow/fcl The FCL will allow us to communicate with the Flow blockchain, call transactions, and integrate all other FCL-compatible wallets without needing to add custom integrations. Once that finishes, we will install a few additional dependencies: npm i elliptic sha3 styled-components After installing all our dependencies, we are ready to start working on the dapp frontend. 3.a. Configure the FCL Before we begin structuring and styling things, let’s create an FCL configuration file where we’ll define important settings, such as whether we will interact with testnet or mainnet. In the directory, create a new folder named . Within this new folder, create a file called config.js. src flow In this file, we will import the FCL, call the function and create some settings for our dapp, such as: config.js fcl.config app.detail.title accessNode.api discovery.wallet Open the file and fill it with the following code: config.js const fcl = require("@onflow/fcl"); fcl.config({ "app.detail.title": "Flow Mint Page Tutorial", // this adds a custom name to our wallet "accessNode.api": "https://rest-testnet.onflow.org", // this is for the local emulator "discovery.wallet": "https://fcl-discovery.onflow.org/testnet/authn", // this is for the local dev wallet }) There are for our dapp, but for now, this is all we will need. additional settings we can configure With the configuration out of the way, let’s move on to building! 3.b. The Initial Structure First, navigate to the file in the folder and replace the code with this: App.js src import './App.css'; function App() { return ( <div className="App"> <h1>Mint Your Dog!</h1> </div> ); } export default App; This will give us the initial structure of our dapp, from which we will expand upon. Next, we’ll style this structure. Open the file and replace the code with the following: index.css @import url('https://fonts.googleapis.com/css2?family=Michroma&family=Montserrat:wght@200;300;600;700&display=swap'); body { margin: 0; font-family: 'Montserrat', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } If you run npm start, you will see a blank page with the title ! Mint Your Dog Next, let’s create some components! 3.c. The Nav Component Inside the directory, create a new folder called , where we will build all of our custom React components. src components The first component we will create is the Navbar, which will show the Login button if the user isn’t connected, or the Logout button next to the user’s address and the number of FLOW tokens the account has if they are connected. Create a file called and fill it with the following code: Navbar.jsx import * as fcl from "@onflow/fcl"; import styled from "styled-components"; import { useState, useEffect } from "react"; import "../flow/config"; const Wrapper = styled.nav` width: -webkit-fill-available; background-color: #8dfe89; position: fixed; top: 0; display: flex; justify-content: space-between; align-items: center; padding: 10px 50px; button { background-color: white; padding: 5px 40px; max-width: 200px; border: none; border-radius: 20px; font-size: 18px; height: 50px; &:hover { color: white; background-color: black; cursor: pointer; } } div { display: flex; gap: 15px; } box { display: flex; flex-direction: column; gap: 10px; } `; function Navbar() { const [user, setUser] = useState({ loggedIn: false, addr: undefined }); const [flow, setFlow] = useState(0); useEffect(() => { fcl.currentUser.subscribe(setUser); if (user.addr !== "") getFlow(user.addr); }, [user.addr]); const logOut = async () => { await fcl.unauthenticate(); setUser({ addr: undefined, loggedIn: false }); }; const logIn = async () => { await fcl.authenticate(); }; async function getFlow(address) { try { const res = await fcl.query({ cadence: ` import FlowToken from 0x7e60df042a9c0868 import FungibleToken from 0x9a0766d93b6608b7 pub fun main(address: Address): UFix64{ let balanceVault = getAccount(address).getCapability(/public/flowTokenBalance).borrow<&FlowToken.Vault{FungibleToken.Balance}>()! return balanceVault.balance }`, args: (arg, t) => [arg(address, t.Address)], }); setFlow(res); } catch (error) { console.log("err:", error); } } return ( <Wrapper> <h1>Flow Tutorial Mint</h1> {user.loggedIn ? ( <div> <button onClick={() => logOut()}>Logout</button> <box> <span>Address - {user.addr}</span> <span>Flow Balance - {flow}</span> </box> </div> ) : ( <button onClick={() => logIn()}>Login</button> )} </Wrapper> ); } export default Navbar; Let’s walk through the code to see what’s going on here. First, we are importing the Flow Client Library, which will provide us with functions to , , and determine the . authenticate unauthenticate currentUser Next, we import the other dependencies we need and then use styled-components to create the basic styling of our Navbar inside the variable. Wrapper Then, we define some React state variables ( and ). user flow Next is the functionality of the dapp, such as logOut, logIn, and getFlow (get the connected account’s FLOW balance). After that, we return the for the Navbar wrapped in our styling. html With a complete component, we can now import it into the file: Navbar App.js import './App.css'; import Navbar from './components/Navbar.jsx'; function App() { return ( <div className="App"> <Navbar /> <h1>Mint your Dog!</h1> </div> ); } export default App; Now, if we run the project with , we see our gives us the functionality we defined in our code. Awesome! npm start Navbar Next, let’s build our NFT minting component! 3.d. The NFT Minting Component Inside the folder, create a new file called , then copy the following code: components MintComponent.jsx import styled from "styled-components"; import * as fcl from "@onflow/fcl"; const Wrapper = styled.div` display: flex; flex-direction: column; gap: 10px; align-items: center; justify-content: center; margin-top: 80px; padding: 100px; main{ display: flex; } div{ width: 300px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 5px; } button{ width: 100px; padding: 10px; border: none; background-color: #8dfe89; border-radius: 20px; font-weight: 500; &:hover { color: white; background-color: black; cursor: pointer; } } img{ width: 200px; } `; function MintComponent() { async function mintNFT(type, url) { try { const res = await fcl.mutate({ cadence: ` import FlowTutorialMint from 0x8e0dac5df6e8489e import NonFungibleToken from 0x631e88ae7f1d7c20 import MetadataViews from 0x631e88ae7f1d7c20 transaction(type: String, url: String){ let recipientCollection: &FlowTutorialMint.Collection{NonFungibleToken.CollectionPublic} prepare(signer: AuthAccount){ if signer.borrow<&FlowTutorialMint.Collection>(from: FlowTutorialMint.CollectionStoragePath) == nil { signer.save(<- FlowTutorialMint.createEmptyCollection(), to: FlowTutorialMint.CollectionStoragePath) signer.link<&FlowTutorialMint.Collection{NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection}>(FlowTutorialMint.CollectionPublicPath, target: FlowTutorialMint.CollectionStoragePath) } self.recipientCollection = signer.getCapability(FlowTutorialMint.CollectionPublicPath) .borrow<&FlowTutorialMint.Collection{NonFungibleToken.CollectionPublic}>()! } execute{ FlowTutorialMint.mintNFT(recipient: self.recipientCollection, type: type, url: url) } } `, args: (arg, t) => [arg(type, t.String), arg(url, t.String)], limit: 9999, }); fcl.tx(res).subscribe((res) => { if (res.status === 4 && res.errorMessage === "") { window.alert("NFT Minted!") window.location.reload(false); } }); console.log("txid", res); } catch (error) { console.log("err", error); } } return ( <Wrapper> <h1>Mint your Dog!</h1> <main> <div> <img src="https://images.unsplash.com/photo-1517849845537-4d257902454a" alt="Mad Dog"/> <h3>Mad Dog</h3> <button onClick={() => mintNFT("Mad Dog", "https://images.unsplash.com/photo-1517849845537-4d257902454a")}>Mint</button> </div> <div> <img src="https://images.unsplash.com/photo-1517423568366-8b83523034fd" alt="Swag Dog"/> <h3>Swag Dog</h3> <button onClick={() => mintNFT("Swag Dog", "https://images.unsplash.com/photo-1517423568366-8b83523034fd")}>Mint</button> </div> <div> <img src="https://images.unsplash.com/photo-1517519014922-8fc06b814a0e" alt="French Dog"/> <h3>French Dog</h3> <button onClick={() => mintNFT("French Dog", "https://images.unsplash.com/photo-1517519014922-8fc06b814a0e")}>Mint</button> </div> </main> </Wrapper> ) } export default MintComponent; Again, let’s walk through the code to ensure we understand what’s going on. We need to import the FCL in this component to gain access to the function that will let us mint our NFT. Again, we use to add some styling. styled-components The function uses the function to perform the actual mint by: mintNFT fcl.mutate Validating whether the user has a Flow Tutorial Mint NFT collection in their account and creating one if not. Calling the existing mint function inside the FlowTutorialMint contract and passing the parameters. The function returns the resource (NFT), which we deposit into the user’s account. In the function, we are importing the smart contract we deployed with the line: fcl.mutate import FlowTutorialMint from 0x8e0dac5df6e8489e We also import the and standards. NonFngibleToken MetadataViews In the transaction, we specify the NFT and of the image. type url Cadence transactions have two phases: and prepare execute – we ask for the user’s signature to access their account and perform private functions. In this case, creating a new Mint collection if they don’t already have one. We also initialize a public restricted to . For more context on Capabilities, . prepare FlowTutorial Capability NonFungibleToken.CollectionPublic check out this link – call the function inside of our contract on the testnet. execute mintNFT In the portion of the code, we display three images from which the user can mint an NFT. html With our complete, we can import it into the file: MintComponent App.js import './App.css'; import Navbar from './components/Navbar.jsx'; import MintComponent from './components/MintComponent.jsx'; function App() { return ( <div className="App"> <Navbar /> <h1>Mint your Dog!</h1> <MintComponent /> </div> ); } export default App; Now the user can log into the dapp and mint an NFT to their account! The final piece of the puzzle is to create a component that will fetch the user’s NFTs and display them. 3.e. Showing The User’s NFTs In the folder, create a new file called , and we will use the following code: components ShowNfts.jsx import * as fcl from "@onflow/fcl"; import { useState, useEffect } from "react"; import styled from "styled-components"; const Wrapper = styled.div` background-color: #e5e5e5; display: flex; flex-direction: column; gap: 10px; align-items: center; justify-content: center; padding: 50px; button { width: 100px; padding: 10px; border: none; background-color: #8dfe89; border-radius: 10px; font-weight: 700; &:hover { color: white; background-color: black; cursor: pointer; } } section { display: flex; justify-content: center; align-items: center; flex-wrap: wrap; gap: 30px; padding: 10%; } .nftDiv{ padding: 10px; background-color: #141414; border-radius: 20px; color: white; box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.25); img{ width: 140px; border-radius: 10px; } p{ font-size: 14px; } } `; export default function ShowNfts() { const [nfts, setNfts] = useState([]); const [user, setUser] = useState({ loggedIn: false, addr: undefined }); useEffect(() => { fcl.currentUser.subscribe(setUser); getNFTs(user.addr) }, [user.addr]); async function getNFTs(addr) { try { const result = await fcl.query({ cadence: ` import FlowTutorialMint from 0x8e0dac5df6e8489e import MetadataViews from 0x631e88ae7f1d7c20 pub fun main(address: Address): [FlowTutorialMint.FlowTutorialMintData] { let collection = getAccount(address).getCapability(FlowTutorialMint.CollectionPublicPath) .borrow<&{MetadataViews.ResolverCollection}>() ?? panic("Could not borrow a reference to the nft collection") let ids = collection.getIDs() let answer: [FlowTutorialMint.FlowTutorialMintData] = [] for id in ids { let nft = collection.borrowViewResolver(id: id) let view = nft.resolveView(Type<FlowTutorialMint.FlowTutorialMintData>())! let display = view as! FlowTutorialMint.FlowTutorialMintData answer.append(display) } return answer } `, args: (arg, t) => [arg(addr, t.Address)], }); setNfts(result); } catch (error) { console.log("err", error); } } return ( <Wrapper> <h1>My NFTs</h1> <main> <button onClick={() => getNFTs(user.addr)}>Get NFTs</button> <section> {nfts.map((nft, index) => { return ( <div key={index} className="nftDiv"> <img src={nft.url} alt="nft" /> <p>Type: {nft.type}</p> <p>Id: {nft.id}</p> </div> ); })} </section> </main> </Wrapper> ); } Essentially what we are doing in this code is querying the Flow Blockchain using the FCL, and gathering the NFTs in the connected account that are from our FlowTutorialMint collection. We just need to add this component to our , and we are good to go! App.js import './App.css'; import Navbar from './components/Navbar.jsx'; import MintComponent from './components/MintComponent.jsx'; import ShowNfts from './components/ShowNfts'; function App() { return ( <div className="App"> <Navbar /> <h1>Mint your Dog!</h1> <MintComponent /> <ShowNfts /> </div> ); } export default App; That’s everything! Now let’s test our dapp and make sure we can mint some NFTs. 4. Let’s Mint Some NFTs! So first, let’s start the app with and then open our browser to . npm start http://localhost:3000/ If everything goes well, your screen should look like this: The beautiful thing about using the FCL in our Login sequence is that it gives our users easy access to making an account right on the spot using just an email address. Let's walk through the process to make sure it works properly. By clicking the button, a dialogue will pop up, giving us two options to login with. We will choose . Login Blocto Blocto will prompt us to enter an email address and, upon doing so, gives us the ability to Register a new account. Then, once we input the code emailed to our address, Blocto sets us up with a shiny, new Flow address! From here, we can choose which dog image we want to mint as an NFT. I chose the Swag Dog because it reminds me a bit of myself! Pressing the button will pop up another dialogue telling us about the transaction we are about to perform. We can see that Blocto is graciously covering the minting fees, and if we want to look at the script we are calling, we can do so. Mint Several seconds after hitting , we should receive a message that our mint was successful, and our newly minted Swag Dog will display under the section of our dapp. Approve My NFTs Here’s a link to our dapp in action: https://s1.gifyu.com/images/flow_tutorial-min.gif The entire source code for this project can be found in . this repository Conclusion As you can see, building an NFT minting dapp on the Flow Blockchain is straightforward once you understand how it all works together. Additionally, the Flow Client Library is a powerful tool at our disposal that gives us access to extensive built-in functionality and helps give our dapp a better user experience. In contrast to Ethereum, Flow handles NFT creation and management much more efficiently and securely. This is achieved by deploying smart contracts and minting the NFTs directly into the user’s account, rather than creating a reference to addresses or mappings stored on the digital ledger. For more information about building on Flow, check out the . Flow Developer Portal Have a really great day!