How to Mint an NFT on Monad
Written by: Steve Simkins
Republished from here: https://bit.ly/4ibpwOT
Monad is a quickly approaching L1 blockchain that has been getting a lot of attention lately. Its testnet is already thriving with transactions and builders racing to use it. This blockchain features new approaches to processing transactions, including parallel execution and the MonadBFT. Monad boasts a mind-blowing 10,000 TPS, robust decentralization, better scalability, and there is already a host of tools and providers already building with it. We’re really excited to see where it goes, and there’s no better way to say “hello world” to a new chain than with a classic NFT mint. In this post we’ll show you how you can mint NFTs on Monad and Pinata!
Setup
Before we can write our smart contract and mint an NFT we have a few things we’ll need first.
Pinata Account and CLI
We’ll be storing our NFT image and metadata on IPFS through Pinata, and to keep things smooth we’ll do it through the CLI! First create a free Pinata account, then make an API key. Be sure to save the longer JWT as that’s what we’ll be using. Once you’ve done that, go ahead and install the Pinata CLI in just one command:
curl -fsSL https://cli.pinata.cloud/install | bash
This will download the latest binary to your machine and install it. However, if you prefer other installation methods, you can check them out here. Once installed, run pinata to make sure it’s working. Then you’ll want to authorize it by running this command:
pinata auth
It will prompt you for your API key and then ask which Dedicated Gateway you want to use (yeah, it’s slick). Then you should be all set!
Phantom with Monad
Since Monad is an EVM testnet there are multiple compatible wallets, particularly if they allow custom chains. Personally, I would recommend Phantom as they have a great integration with Monad Testnet, which you can add by visiting this page then click the Phantom logo. After the network has been added, you need to get some testnet funds from a faucet. This is a bit tricky as the network is really active and you might need more than usual to deploy a contract and mint an NFT. The best faucets we’ve found have been on their Testnet Page and inside BlockVision’s Discord. You’ll need at least 0.1 MON, but the more the better.
Foundry
We’ll be using Foundry as our tool to build our smart contracts, deploy them, and even mint our NFTs! It’s incredibly powerful and becoming the gold standard for EVM contract development. To install it, we’ll first download Foundryup, which is kinda like an install wizzard:
curl -L https://foundry.paradigm.xyz| bash
Once installed, run the command below:
foundryup
This will install multiple tools we’ll use to build our contract, one of them being cast. Cast lets us manage secure wallets as well as interacting with contracts and getting onchain data. To start, we’ll import the Phantom wallet we used earlier into a keystore, which safely encrypts it to a local file. Inside the terminal, run the following:
cast wallet import monad --interactive
This will ask for your Phantom wallet private key, which you can get by navigating to Settings, clicking on the name of the account, then selecting Show Private Key. Keep in mind, you should never give anyone this key, and it’s best to make sure you are only using testnet funds! Once you paste in the private key into the terminal, it will also ask for a password that will encrypt the private key. If it worked, then you should be able to run cast wallet list to see your new imported wallet!
Now it’s time to setup our smart contract project. To do this, run the command below, which will automatically setup the Monad Testnet network info:
forge init --template monad-developers/foundry-monad monad-nfts
After the project has been setup, run cd monad-nfts and open the project in your code editor of choice. The last command we’ll run here is to install the OpenZeppelin contracts as a dependency:
forge soldeer install @openzeppelin-contracts~5.2.0
Then you should be all set!
Image and Metadata
All NFTs are composed of metadata, generally in the form of a JSON file. This metadata has info like the name of the NFT, a description, website link, image link, and more. To build our NFT we’ll need an image, as well as that JSON metadata. If you want to use one of our images, you can download Pinnie! Inside the forge project monad-nfts we just built, let’s make a new directory called assets:
mkdir assets
Go ahead and paste in the image there, and for this tutorial we’ll name it pinnie.png but feel free to replace that with whatever you want! Now we’ll upload the image to IPFS using the Pinata CLI, and it only takes one command:
pinata upload assets/pinnie.png
That was easy! You should see a result like this:
{
"id": "01929106-f653-76bf-924c-00aaf9fe02fe",
"name": "pinnie.png",
"cid": "bafkreih5aznjvttude6c3wbvqeebb6rlx5wkbzyppv7garjiubll2ceym4",
"size": 32928,
"created_at": "2024-10-15T16:33:26.368307Z",
"number_of_files": 1,
"mime_type": "image/png",
"group_id": null,
"keyvalues": null,
"vectorized": false,
"network": "public",
}
What we want to save and use shortly is the cid, which for this picture is bafkreih5aznjvttude6c3wbvqeebb6rlx5wkbzyppv7garjiubll2ceym4. If you upload a different image, then this will be different, as the CID is based on the content of the file. Now let’s make a new file in the assets folder called metadata.json and paste in the following contents:
{
"name": "Pinnie",
"description": "A classic Pinnie",
"external_url": "<https://pinata.cloud>",
"image": "ipfs://bafkreih5aznjvttude6c3wbvqeebb6rlx5wkbzyppv7garjiubll2ceym4"
}
If you’re using something other than Pinnie, then you can change this information such as the name, description, external_url, and of course the image CID. After saving that file, we will want to upload it to IPFS as well using the same command:
pinata upload assets/metadata.json
You should get a CID for that as well, and this is one we’ll want to hold onto for the next section!
Writing the Contract
For this NFT mint, we’ll do a modified OpenZeppelin ERC721A contract. It’s the standard in the EVM ecosystem and is a great place to start. Inside your project, delete the files under src, test, and script. Then make a new file called Pinnies.sol inside src. There, we’ll paste the following code:
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.22;
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {ERC721URIStorage} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
contract Pinnies is ERC721, ERC721URIStorage, Ownable {
uint256 private _nextTokenId;
string private uri = "ipfs://bafkreibcbcnfk2v57p3e6fmyvukq43r5kb5ydqat2x2ox7yy45to5bzlpe";
constructor() ERC721("Pinnies", "PINS") Ownable(msg.sender) {}
function safeMint(address to) public returns (uint256) {
uint256 tokenId = _nextTokenId++;
_safeMint(to, tokenId);
_setTokenURI(tokenId, uri);
return tokenId;
}
// The following functions are overrides required by Solidity.
function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) {
return super.tokenURI(tokenId);
}
function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721URIStorage) returns (bool) {
return super.supportsInterface(interfaceId);
}
}
We’ve made a few changes to the default, so we’ll walk through everything done so far. To begin, we’ve created a new state variable at the top called uri which is where we are putting our metadata.json cid, prefixed with the IPFS protocol prefix: ipfs://. Next we’ve taken out the standard constructor argument and simple made the owner of the contract msg.sender, so whoever deploys this contract will be the owner. Then, inside the safeMint function, we’ve taken out the uri argument as we’ll just use the global variable, and we’ve removed the restriction of onlyOwner from the mint function so anyone can mint it! Go ahead and run the command below to make sure everything compiles correctly:
forge compile
If it worked, then you should get a message like this:
[?] Compiling...
[?] Compiling 2 files with Solc 0.8.28
[?] Solc 0.8.28 finished in 447.04ms
Compiler run successful!
If that is the case, then we can move onto deploying our contract!
Deployment and Minting
Thanks to Foundry deployment is a breeze. It’s a simple combination of CLI commands:
forge create src/Pinnies.sol:Pinnies --account monad --broadcast
We’ll break this down just so you understand what’s going on:
If you run that command, then you should be prompted to enter in the password for your wallet, and you should see something like this:
Deployer: 0x2952A5a8519689D5Eb7426963aAA981F225eA0c2
Deployed to: 0x3705A79c492b898E4534e523ABc79D3f385B743a
Transaction hash: 0x7df698eced3f68a4b93111c5b28222cf107df5f5e28ef2429683990f4e67643c
We did it! If you happen to have problems, definitely see if it’s a balance issue since you might need more funds. Now that our contract is deployed we can verify it with this command:
forge verify-contract 0x3705A79c492b898E4534e523ABc79D3f385B743a \\
src/Pinnies.sol:Pinnies \\
--chain 10143 \\
--verifier sourcify \\
--verifier-url <https://sourcify-api-monad.blockvision.org>
If successful, then you should see something like this:
Start verifying contract `0x3705A79c492b898E4534e523ABc79D3f385B743a` deployed on 10143
Submitting verification for [Pinnies] "0x3705A79c492b898E4534e523ABc79D3f385B743a".
Contract successfully verified
Ok, so our contract is deployed and verified, and now we can view it on the explorer!
https://testnet.monadexplorer.com/address/CONTRACT_ADDRESS
Now minting an NFT is actually really simple, and we can do it from the terminal through cast.
cast send --account monad \\
0x3705A79c492b898E4534e523ABc79D3f385B743a \\
"safeMint(address)(uint256)" 0x2952A5a8519689D5Eb7426963aAA981F225eA0c2
In this command, we’re sending a transaction with our wallet, designating the contract address we’re interacting with (0x5F459128356F0875F353a18E6e7d6704deC1c6B7), then the function we’re calling with the argument. After running it, you should be prompted to enter you password for the wallet again, and then after a few seconds you’ll see a whole bunch of logs and transaction info. If you open your wallet in Phantom and click on squares icon for NFTs, you should see your NFT there!
Wrapping Up
In this tutorial, we did a full end to end flow of setting up a smart contract development environment and deploying it to Monad. You could take these same principles and apply them to pretty much any EVM chain. If you want to reference the code we used in this tutorial, then you can check out the repo here. Monad is looking promising, and now you have the tools to go forth and build!
Happy Pinning!