Welcome to the Complete Guide on How to Create and Deploy an NFT Smart Contract on RSK. Introduction In this tutorial, we will take you through a step by step process on how to write a ERC-721 smart contract, and deploy to the RSK testnet using the following tools: Solidity, Hardhat, Ether.js, RSK Testnet, IPFS, Pinata, and Test RBTC from a faucet. Not to worry, if these terms are new to you — we will explain them! We will do the following in this tutorial A brief introduction to NFTs, and ERC-721 Setting up your environment What is IPFS? How to set up your Pinata account Create NFT metadata (JSON File) Create ERC 721 smart contract Deploy smart contract on the RSK blockchain Prerequisites To follow this tutorial, you should have knowledge in: Smart contract fundamentals using Solidity JavaScript Command line Git If you are not familiar with the above, it will be advisable to learn the basics by clicking the links above, before proceeding with this tutorial on how to create and deploy your NFT project to the RSK Testnet. What is a Non-fungible Token (NFT)? Fungibility, is about whether something is interchangeable and divisible. A fungible token has both of those properties, and behave in a manner similar to fiat cash; whereas a non-fungible token is neither interchangeable nor divisible, and behave in a manner similar to many real world objects. ERC-20 is the most commonly used technical standard for fungible tokens (FTs); and ERC-721 is the most commonly used technical standard for non-fungible tokens (NFTs). Non-fungible is a term used to describe digital assets that represent real world objects like art, furniture, a song file, in-game items, your computer or even real estate. Unlike ERC-20 tokens, tokens that are created under ERC-721 are not interchangeable, this comes from the fact that while two NFTs may look identical to each other, they both hold unique information. Non-fungible tokens also lack another feature of their ERC-20 counterparts – they are not divisible, which means that you cannot own a portion of an NFT. NFTs can be used in the following use cases: - NFT ledgers can store an individual’s medical records without compromising confidentiality or risking tampering from external sources. Medical Records and Identity Verification - NFTs could be used to transfer land deeds, provide proof of ownership and even keep track of changes in property value over time using timestamped NFTs. Real Estate - NFTs can be used to ensure that the product you are purchasing is authentic. NFTs can also be used to store information about the manufacturing process, ensuring that everything is fair trade. Ensuring Authenticity of Products - NFTs are also a good way to represent academic credentials. NFTs can provide proof of attendance, degree earned, and other important information which will be stored on the NFT chain that cannot be altered or hacked into. Academic Credentials - NFTs can be integrated into the gaming world by allowing NFT cross-platform playability. Gaming Industry What is ERC-721? is a standard for representing ownership of non-fungible tokens, where each token is unique. It provides functionalities like to transfer tokens from one account to another, to get the current token balance of an account, to get the owner of a specific token and also the total supply of the token available on the network. ERC-721 is a standard interface for non-fungible tokens. EIP-721 Setting up your Development Environment What is Hardhat? Hardhat is a development environment that enables you to compile, deploy, test, and debug your RSK software. It helps to manage and automate the recurring tasks that are inherent to the process of building Blockchain applications. Check out this step-by-step tutorial on . how to set up a Hardhat project to connect to the RSK Testnet What is ethers.js? Ethers.js is a JavaScript library that allows developers to interact with the blockchain. The library includes utility functions in JavaScript and TypeScript, and can also support wallets. What is tRBTC (Smart Bitcoin in Testnet)? The Test Smart Bitcoin (tRBTC) is the token used to pay for the execution of transactions in the RSK Testnet environment. Watch this video tutorial on . How to Get tRBTC from the RSK Testnet Faucet What is IPFS? IPFS (Interplanetary File System) is a file system/protocol for storing and sharing content, it allows you to store files, keeping track of them on a distributed network. This storage system allows direct interaction through a secure and global P2P network. IPFS files are content-addressable. This means that it uses content identifiers, or CIDs, as a label to point to material in IPFS. These CIDs are based on the contents of the files themselves, and may be thought of as . It doesn’t indicate where the content is stored, but it forms a kind of address based on the content itself. This property makes IPFS a suitable platform for referencing images within smart contracts. We will use IPFS to host/store our NFT images. hashes Configure the metadata for your NFT using Pinata To get our parameter, that should resolve to a JSON document describing our NFT’s metadata which will include properties such as name, description, image, and other attributes, we would need to set up , a convenient IPFS API and toolkit, to store our NFT asset and metadata. tokenURI Pinata To simply put, a on an NFT is a unique identifier of what the token “looks” like. A URI could be an API call over HTTPS, an IPFS hash, or anything else that is unique. tokenURI If you don’t have a Pinata account, sign up for a and complete the steps to verify your email. free account here Once you’ve created an account: Navigate to the “Files” page and click the blue “Upload” button at the top-left of the page. Upload two cat images to Pinata — this will be the image asset for your NFTs. Feel free to name the asset whatever you wish After you upload, you’ll see the file info in the table on the “Files” page. You’ll also see a CID column. You can copy the CID by clicking the copy button next to it. You can view your upload at: https://gateway.pinata.cloud/ipfs/<CID>. Now, let’s create and upload two other files to pinata, each containing details of two cats in JSON format. In your root directory, make a new folder called nft- metadata and add the following json codes: File one is named doerak.json { "attributes": [ { "trait_type": "Breed", "value": "European short hair" }, { "trait_type": "Parent", "value": "Gino Osahon" } ], "description": "Doerak. Gray & white kitty", "image": "ipfs://QmX7P1aswXLKLPd7RqbtwrNGD9CzGjvNFKmdEhWtBmoyiS", "name": "Doerak" } File two is called luna.json { "attributes": [ { "trait_type": "Breed", "value": "European short hair" }, { "trait_type": "Parent", "value": "Alex Shenshin" } ], "description": "Luna. Ginger kitty", "image": "ipfs://QmZZfJcrppaRiq5dWtC6zpnGZRNoVwdyh69VoA89xGW5Yt", "name": "Luna" } Feel free to change the data in the json. You can remove or add to the attributes section. Most importantly, make sure the image field points to the location of your IPFS image. Once you’re done editing the JSON file, save it and upload it to Pinata, following the same steps we did for uploading the image. Generate a seed phrase In your root directory, create a file called and add your seed phrase. secret.json Seed phrases are a human-readable version of your private keys. You can sign transactions and recover lost accounts using part of your mnemonic phrase. Mnemonic or seed phrases can range from 12 - 24 words depending on the blockchain ecosystem you are dealing with. Any app can generate its mnemonic phrase for security purposes. { "mnemonic": "please put your twelve words long mnemonic phrase to this string now" } To interact with the RSK blockchain, you need an account, which consists of a private key, a public key, and an address. BIP-39 is a technical standard that allows the generation of multiple accounts from a set of dictionary words, plus a derivation path. Many software libraries and wallet software implement this technical standard, including both ethers.js and MetaMask, which we’ll be using in this tutorial. Note that seed phrases should be treated as securely as private keys, so do not use the one in this tutorial on RSK Mainner - the usage here is sufficient for use on RSK Testnet only. Hardhat Configuration Let’s explain the file, the configuration file is always executed on startup before anything else happens, and it has two main tasks. You can define a task as a with some associated metadata. hardhat.config.js JavaScript async function This metadata is used by Hardhat to automate tasks, or as asynchronous JavaScript functions that get access to the Hardhat Runtime Environment, which exposes its configuration and parameters. task('deploy', 'Deploys smart contract to a blockchain').setAction(async () => { const meowContractFactory = await ethers.getContractFactory('Meow'); const meowNft = await meowContractFactory.deploy(); await meowNft.deployed(); console.log( `Meow NFT deployed to: ${meowNft.address}\nCopy this address and paste to the 'mint' task in 'hardhat.config.js'`, ); }); When the task is called, hardhat will deploy your smart contract to the RSK Blockchain. The function takes two metadata, and the second line gets a of the compiled source code. deploy const meowContractFactory = await ethers.getContractFactory('Meow'); contractFactory in is an abstraction used to deploy new smart contracts, so here is a factory for instances of our Meow contract. ContractFactory ethers.js Meow The line const sends a deploy transaction, and the next line waits for the transaction to be mined. The last line in the deploy task uses print logging message that says NFT has been deployed to the contract variable . meowNft = await meowContractFactory.deploy(); await meowNft.deployed(); console.log address task('mint', 'Mint new NFT collectibles').setAction(async () => { const deployedAddress = '0xE360F4BFb74A1B2B1d102f40BE6c7D0f5C9d12C8'; const newCIDsToMint = [ 'QmaXZxVGYcCY36seYTVuGeY9mWchC1WjMscV1FLNfZsM3f', 'QmR5mspowKw6B68QPSuYE9SGH1A6gPKxjdVRokhAZZh4LD', ]; const api = (await ethers.getContractFactory('Meow')).interface; const [signer] = await ethers.getSigners(); const meowNft = new ethers.Contract(deployedAddress, api, signer); async function mintSequentially() { const cid = newCIDsToMint.shift(); if (cid) { const tx = await meowNft.mintNFT(signer.address, `ipfs://${cid}`); const receipt = await tx.wait(); const { tokenId } = receipt.events[0].args; console.log(`Minted NFT ${deployedAddress} #${tokenId}`); await mintSequentially(); } } await mintSequentially(); }); Task when called will mint a new NFT. The line takes the address where the smart contract source code was deployed to in the task. This means that you need to first run the deploy task, then copy the address of the deployed source code and then assign it here to the deployedAddress constant. mint const deployedAddress = '0xE360F4BFb74A1B2B1d102f40BE6c7D0f5C9d12C8'; deploy The below lines contain the IPFS content identifier (CIDs) obtained from Pinata. const newCIDsToMint = [ 'QmaXZxVGYcCY36seYTVuGeY9mWchC1WjMscV1FLNfZsM3f', 'QmR5mspowKw6B68QPSuYE9SGH1A6gPKxjdVRokhAZZh4LD', ]; The lines below get the smart contract application programming interface, get the deployers account information, and instantiates the smart contract representation object. const api = (await ethers.getContractFactory('Meow')).interface; const [signer] = await ethers.getSigners(); const meowNft = new ethers.Contract(deployedAddress, api, signer); The below function mints all items from the array one after another. The statement removes the first CID from the array, if the array is already empty (minted all items). The statement is called, if there are still items to be minted, it calls the smart contracts function thereby initiating a transaction. It then waits for the transaction to be mined, gets the transaction receipt, extracts the ID of the newly minted NFT from the transfer event emitted by the smart contract, and recursively calls itself until the array is empty. mintSequentially newCIDsToMint const cid = newCIDsToMint.shift(); newCIDsToMint if mintNFT newCIDsToMint async function mintSequentially() { const cid = newCIDsToMint.shift(); if (cid) { const tx = await meowNft.mintNFT(signer.address, `ipfs://${cid}`); const receipt = await tx.wait(); const { tokenId } = receipt.events[0].args; console.log(`Minted NFT ${deployedAddress} #${tokenId}`); await mintSequentially(); } } The module.export section has already been explained in the section of the "How to set up a Hardhat project for RSK Testnet " tutorial referred to in this article. JSON-RPC base networks Complete Config File Your final configuration file should now look like this: /* eslint-disable no-undef */ require('@nomiclabs/hardhat-waffle'); const { mnemonic } = require('./.secret.json'); task('deploy', 'Deploys smart contract to a blockchain').setAction(async () => { const meowContractFactory = await ethers.getContractFactory('Meow'); const meowNft = await meowContractFactory.deploy(); await meowNft.deployed(); console.log( `Meow NFT deployed to: ${meowNft.address}\nCopy this address and paste to the 'mint' task in 'hardhat.config.js'`, ); }); task('mint', 'Mint new NFT collectibles').setAction(async () => { const deployedAddress = '0xE360F4BFb74A1B2B1d102f40BE6c7D0f5C9d12C8'; const newCIDsToMint = [ 'QmaXZxVGYcCY36seYTVuGeY9mWchC1WjMscV1FLNfZsM3f', 'QmR5mspowKw6B68QPSuYE9SGH1A6gPKxjdVRokhAZZh4LD', ]; const api = (await ethers.getContractFactory('Meow')).interface; const [signer] = await ethers.getSigners(); const meowNft = new ethers.Contract(deployedAddress, api, signer); async function mintSequentially() { const cid = newCIDsToMint.shift(); if (cid) { const tx = await meowNft.mintNFT(signer.address, `ipfs://${cid}`); const receipt = await tx.wait(); const { tokenId } = receipt.events[0].args; console.log(`Minted NFT ${deployedAddress} #${tokenId}`); await mintSequentially(); } } await mintSequentially(); }); module.exports = { solidity: '0.8.12', defaultNetwork: 'rsktestnet', networks: { hardhat: {}, rsktestnet: { chainId: 31, url: 'https://public-node.testnet.rsk.co/', accounts: { mnemonic, path: "m/44'/60'/0'/0", }, }, }, }; Create your NFT Smart Contract In your root directory, start by creating a new directory called contracts and create a file inside the directory called . Meow.sol mkdir contracts touch contracts/Meow.sol code contracts/Meow.sol Below is our NFT smart contract code, which is based on the OpenZeppelin library’s ERC-721 implementation. Copy and paste the contents below into your file. Meow.sol // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; // using OpenZeppelin libraries import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/utils/Counters.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; contract Meow is ERC721URIStorage, Ownable { /** From the `Counters` docs: Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number of elements in a mapping, issuing ERC721 ids, or counting request ids. Include with `using Counters for Counters.Counter;` */ using Counters for Counters.Counter; // tracks the number of minted NFTs Counters.Counter private _tokenIds; // calling ERC721 constructor constructor() ERC721("Meow NFT", "MEO") {} // mints new NFTs. Can be called only by the deployer (s/c owner) function mintNFT(address recipient, string memory tokenURI) public onlyOwner returns (uint256) { // increment counter _tokenIds.increment(); // get new NFT id uint256 newItemId = _tokenIds.current(); // call internal ERC721 mint function _mint(recipient, newItemId); // write token URI to newly minted NFT _setTokenURI(newItemId, tokenURI); return newItemId; } } We are inheriting classes from the OpenZeppelin contracts library, in the command line run npm to install the library into our folder. install @openzeppelin/contracts So, what does this code do exactly? Let’s break it down, line-by-line. import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/utils/Counters.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; At the top of our smart contract, we import three smart contract classes and one extension: OpenZeppelin contains the implementation of the ERC-721 standard, which our NFT smart contract will inherit. To be a valid NFT, your smart contract must implement all the methods of the ERC-721 standard. To learn more about the inherited ERC-721 functions, . @openzeppelin/contracts/token/ERC721/ERC721.sol see the full ERC-721 specification provides counters that can only be incremented or decremented by one. Our smart contract uses a counter to keep track of the total number of NFTs minted and set the unique ID on our new NFT. (Each NFT minted using a smart contract must be assigned a unique ID—here our unique ID is just determined by the total number of NFTs in existence. For example, the first NFT we mint with our smart contract has an ID of 1, our second NFT has an ID of 2, etc.) @openzeppelin/contracts/utils/Counters.sol sets up on our smart contract, so only the owner of the smart contract (you) can mint NFTs. Note that including access control is entirely a preference. If you’d like anyone to be able to mint an NFT using your smart contract, remove the word on line 10 and on line 17. @openzeppelin/contracts/access/Ownable.sol access control Ownable onlyOwner - @openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol This is a more flexible but more expensive way of storing metadata. using Counters for Counters.Counter; // tracks the number of minted NFTs Counters.Counter private _tokenIds; // calling ERC721 constructor constructor() ERC721("Meow NFT", "MEO") {} After our import statements, we have our custom NFT smart contract, which is surprisingly short — it only contains a counter, a constructor, and single function! This is thanks to our inherited OpenZeppelin contracts, which implement most of the methods we need to create an NFT, such as which returns the owner of the NFT, and , which transfers ownership of the NFT from one account to another. ownerOf transferFrom In our ERC-721 constructor, you’ll notice we passed 2 strings, , and . The first variable is the smart contract’s name, and the second is its symbol. You can name each of these variables whatever you wish! Meow-NFT MEO // mints new NFTs. Can be called only by the deployer (s/c owner) function mintNFT(address recipient, string memory tokenURI) public onlyOwner returns (uint256) { // increment counter _tokenIds.increment(); // get new NFT id uint256 newItemId = _tokenIds.current(); // call internal ERC721 mint function _mint(recipient, newItemId); // write token URI to newly minted NFT _setTokenURI(newItemId, tokenURI); return newItemId; } Finally, we have our function that allows us to mint an NFT! You’ll notice this function takes in two variables: mintNFT(address recipient, string memory tokenURI) specifies the address that will receive your freshly minted NFT address recipient is a string that should resolve to a JSON document that describes the NFT’s metadata. An NFT’s metadata is really what brings it to life, allowing it to have configurable properties, such as a name, description, image, and other attributes. string memory tokenURI calls some methods from the inherited ERC-721 library, and ultimately returns a number that represents the ID of the freshly minted NFT. mintNFT How to deploy your NFT on RSK Steps We will do the following steps: Switch to Node.js 12 for Hardhat to work nvm use 12 compile Meow-NFT smart contract npx hardhat compile deploy Meow-NFT smart contract to RSK testnet. See other possible networks in hardhat.config.js npx hardhat deploy --network rsktestnet You will see a message: Meow NFT deployed to: 0xE360F4BFb74A1B2B1d102f40BE6c7D0f5C9d12C8 Copy this address and paste to the 'mint' task in 'hardhat.config.js' Paste the address to the mint task! mint your NFTs from CIDs specified in the mint task of project.config.js npx hardhat mint --network rsktestnet View NFT using Metamask Metamask Metamask is a kind of web wallet which facilitates transactions using yours accounts. It can be used with RSK networks too. It has versions for several browsers, like Chrome, Firefox, Opera and Brave. Go to and install it. metamask.io Create an account. Write down your 12 word seed phrase. This is used to recover your account, in case you lose your password. The seed phrase is the most important thing in a wallet / account! Connect MetaMask to RSK testnet Go to networks -> Custom RPC, and enter the following values: Network Name RSK Testnet New RPC URL https://public-node.testnet.rsk.co ChainID (optional) 31 Symbol (optional) tRBTC Block Explorer URL (optional) https://explorer.testnet.rsk.co After configuring it, select the RSK Testnet. MetaMask screenshot before adding NFT collection. You should now see an account connected to the RSK Testnet. Not to worry if you see “No NFTs yet” under the NFTs tab,there is one more step before we’ll be able to see them! Add NFT to Metamask Once you’re on the RSK network, select the “NFTs” tab on the right and add the NFT smart contract address and the ERC-721 token ID of your NFT — which you should be able to find on based on the transaction hash from your NFT deployed to RSK Testnet. RSK Testnet Explorer You may need to refresh the page to see your newly minted NFT. MetaMask screenshot after adding NFT collection Congratulations! In this article, we learnt about NFTs, IPFS, Hardhat, and we have successfully created and deployed our NFT project to the RSK Testnet. If you would like to delve deeper, here are some resources and tools that we recommend. Resources Visit Our Developers Portal RSK Open Slack Community RSK Youtube