If you’ve followed along with the Flow series so far, you already know that the Flow Blockchain excels in handling digital assets, such as NFTs. It was built from the ground up as a better alternative to Ethereum’s network congestion and high fee issues.
In addition, the Cadence smart contract language is a first-of-its-kind resource-oriented programming 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.
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:
- NodeJs and NPM
- The Flow Command Line Interface (Flow CLI)
- 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 the Flow Faucet 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:
- 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 flow.json
file inside the folder, where we'll place all the information we need to deploy our smart contract.
Open the flow.json
file in your code editor and we'll set up a testnet account. Inside the accounts
section, we'll add a new entry called testnet-account
, which contains our new address and the private key generated in the flow keys generate
command earlier.
{
"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
NonFungibleToken
andMetadataViews
contracts to create our NFTs using Flow standards - We define our NFT resource in the
pub resource NFT
function - The
mintNFT
function mints an NFT into the account that calls the function
Now we need to go back into our flow.json file to add a few things:
- In the
contracts
section, add the contract and its path. - In the
deployment
s section add the network (testnet
), the account that we will use to perform the deployment (testnet-account
), and the contract name (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 deploy it to the testnet. To do that, type the following command in the project folder in your terminal:
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 Flow Client Library (FCL):
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 src
directory, create a new folder named flow
. Within this new folder, create a file called config.js
.
In this config.js
file, we will import the FCL, call the fcl.config
function and create some settings for our dapp, such as:
- app.detail.title
- accessNode.api
- discovery.wallet
Open the config.js
file and fill it with the following code:
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 additional settings we can configure for our dapp, but for now, this is all we will need.
With the configuration out of the way, let’s move on to building!
3.b. The Initial Structure
First, navigate to the App.js
file in the src
folder and replace the code with this:
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 index.css
file and replace the code with the following:
@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 src
directory, create a new folder called components
, where we will build all of our custom React 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 Navbar.jsx
and fill it with the following code:
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
authenticate
,unauthenticate
, and determine thecurrentUser
. - Next, we import the other dependencies we need and then use styled-components to create the basic styling of our
Navbar
inside the Wrapper variable. - Then, we define some React state variables (
user
andflow
). - 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
html
for the Navbar wrapped in our styling.
With a complete Navbar
component, we can now import it into the App.js
file:
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 npm start
, we see our Navbar
gives us the functionality we defined in our code. Awesome!
Next, let's build our NFT minting component!
3.d. The NFT Minting Component
Inside the components
folder, create a new file called MintComponent.jsx
, then copy the following code:
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
styled-components
to add some styling. - The
mintNFT
function uses thefcl.mutate
function to perform the actual mint by:- 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
fcl.mutate
function, we are importing the smart contract we deployed with the line:import FlowTutorialMint from 0x8e0dac5df6e8489e
- We also import the
NonFngibleToken
andMetadataViews
standards. - In the transaction, we specify the NFT
type
andurl
of the image. - Cadence transactions have two phases:
prepare
andexecute
-
prepare
– we ask for the user’s signature to access their account and perform private functions. In this case, creating a new FlowTutorial Mint collection if they don’t already have one. We also initialize a public Capability restricted to NonFungibleToken.CollectionPublic. For more context on Capabilities, check out this link. -
execute
– call the mintNFT function inside of our contract on the testnet.
-
In the html
portion of the code, we display three images from which the user can mint an NFT.
With our MintComponent
complete, we can import it into the App.js
file:
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 component
s folder, create a new file called ShowNfts.jsx
, and we will use the following code:
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 App.js
, and we are good to go!
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 npm start
and then open our browser to 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 Login button, a dialogue will pop up, giving us two options to login with. We will choose 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 Mint 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.
Several seconds after hitting Approve, we should receive a message that our mint was successful, and our newly minted Swag Dog will display under the My NFTs section of our dapp.
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!