How To Mint an NFT on Polygon
Tags: nft, polygon, truffle, infura
Published: 2022-05-10 22:32:28


## Introduction

Non-Fungible Tokens (NFTs) have exploded in popularity over the last year. And with NFTs being used for [art](https://opensea.io/), [PFPs](https://boredapeyachtclub.com/), lifestyle brands, sports, games, [the metaverse](https://pages.consensys.net/nfts-the-metaverse-ready-for-take-off), and more, they won’t be going away anytime soon. But the current state of NFTs has a few problems - notably, minting on Ethereum can be prohibitively expensive and the metadata behind those NFTs is often centralized.  

In this article, we’ll look at one way to solve those issues by creating and deploying an NFT to the Polygon Network and using IPFS to store the metadata. In our example, we’ll use [Truffle](https://trufflesuite.com/) as our development environment and [Infura](https://infura.io/) to connect and interact with the blockchain and IPFS. 


### A Quick Overview of NFTs

**Fungible** tokens are cryptographic tokens that can be exchanged for any other of the same token (1 == 1). These include cryptocurrencies such as Bitcoin (BTC) or ETH. One Bitcoin is exactly the same as any other Bitcoin.

In contrast, **[NFTs](https://consensys.net/knowledge-base/a-blockchain-glossary-for-beginners/#NFT)** are _**not** fungible_ - one token is **not** replaceable by another, and they may have varying values (1 != 1). An NFT is an undisputable representation of digital ownership. It can be used to represent any manner of digital property such as images, videos, music, and so much more. 


### NFTs on Ethereum and Layer Two

Most NFTs are created on the [Ethereum Network](https://ethereum.org/en/), as it allows for the greatest sense of security and decentralization. However, for the average person, there is a major issue: Transacting on Layer 1 of the Ethereum Network is expensive. 

Over the course of 2021, several solutions to this problem have gained popularity. Layer 2 solutions such as [Arbitrum](https://arbitrum.io/), [zkSync](https://zksync.io/userdocs/), and the [Polygon Network](https://polygon.technology/) are here to provide a more user-friendly experience. Transaction fees on these networks are a fraction of what they would be on Layer 1, and in most cases, significantly faster.

Another current problem with NFTs is that the metadata they contain isn’t always stored in a decentralized manner. If the organization hosting the metadata disappears, so does the value of your NFT. The best solution is to host the metadata using a global, peer-to-peer file system such as InterPlanetary File Storage (IPFS).

Combining technologies such as Polygon and IPFS creates a cost-effective method to deploy and store your NFT in a more decentralized and permanent way. Although we could use no-code services such as those provided by [ConsenSys](https://consensys.net/solutions/nft-experiences/), it’s more fun to actually deploy one yourself and you will get a better understanding of how it all works behind the scenes. 

So let’s look at how you can create and deploy an NFT to the Polygon Network. We will use Truffle as our development environment and Infura to connect and interact with the blockchain, and to store the metadata with IPFS. 

Before deploying, we will test on a local instance of a blockchain with Ganache. When it’s ready to go live, we will deploy to the Polygon Mumbai Test Network and will verify the NFT on OpenSea, the world’s most popular NFT platform.


## The Project


### What You Will Learn



* Upload the NFT image and metadata to IPFS via Infura
* Set up the Polygon Network in MetaMask and get test funds
* Set up Truffle Development Environment
* Write an NFT smart contract
* Test deployment locally using Ganache
* Deploy to Polygon testnet using Truffle console and Infura
* Verify the NFT on opensea.io


### What You Will Need



* [NodeJS / NPM](https://docs.npmjs.com/cli/v8/configuring-npm/install) - installation and package management
* [Truffle](https://trufflesuite.com/docs/truffle/getting-started/installation/) - development environment
* [Ganache](https://trufflesuite.com/docs/ganache/quickstart/) - local blockchain instance for testing
* [Infura account](https://infura.io/) - used to interact with IPFS and Polygon Networks
* [MetaMask account](https://metamask.io/) - supply funds to process transactions


### Additional Resources



* [Polygon Docs](https://docs.polygon.technology/docs/develop/getting-started)
* [Truffle Docs](https://trufflesuite.com/docs/truffle/)
* [Infura Docs](https://infura.io/docs/ethereum)
* [MetaMask Docs](https://docs.metamask.io/guide/)


![An image saying "let's build it!" with a nice light blue background and silhouette of a construction site with cranes holding the logos for Truffle, Polygon, and Infura](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/phy17jxcnaf4u4qk8862.jpg)


## Setting Up Accounts

Before we get to the fun parts like building and testing, let’s set up our accounts and get everything in place. This will ensure the rest of the process goes smoothly.


### Get an Infura Account

Infura is an Ethereum node provider and API. It will allow us to easily connect to the Polygon Network and also provides an easy-to-use method for utilizing IPFS.

The first thing we need to do is head to [infura.io](https://infura.io/register) and set up a free account. After verifying our email address, we can access the dashboard, where we can make new projects. The Polygon Network isn’t enabled by default, however.

We can add Polygon to our plan by selecting the “Add-Ons” tab on the left-hand side of the screen and scrolling down to “Network Add-Ons”. Next, we select “Polygon PoS Add-On” and go through the prompt to add it. 

Now we’re ready to create a new project. Let’s head back to the dashboard and click on the “Create New Project” button. We’ll select Ethereum as the product and name our project **Polygon-NFT**.

Once our project is created, there are several things to note:


* The Project ID
* The project secret
* Endpoints selection
* Our endpoint link

Let’s change our Endpoint from `MAINNET` to `POLYGON MUMBAI`, which is the Polygon  testnet. Our Endpoint link will then change. We will need the values on this page later, but for now, we are done with the project page.


![A view of our Infura project, with the endpoint switched from mainnet to polygon mumbai](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w7aoov6t0xl9kcwkbnaz.png)

### IPFS via Infura

Since we are already in our Infura account, let’s go ahead and create another project. This one will be for utilizing IPFS to store the image and metadata of our NFT. This time when we create a new project, we will select IPFS as the product. Let’s name this one **Polygon-NFT-Metadata**.

For the next steps we will need the Project ID, project secret, and endpoint URL, but first, we need an image for our NFT.


![he image to be used for our Polygon NFT -- It's dark navy blue with the words "My Amazing" in cursive at the top, the Polygon logo in purple, and the words "Polygon NFT" in purple pixely text underneath the logo.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cdo23fldtgfbmxrarw0c.jpg)
>Just right-click and save this to your machine ;)

Feel free to use your own image moving forward. OpenSea recommends the image size to be 350 x 350 pixels.

Now let’s upload the image to IPFS!

Using a command terminal, we will `cd` into the folder where our image is stored.

To upload the image, type the following command:


```
curl -X POST -F file=@myfile \ -u "PROJECT_ID:PROJECT_SECRET" \ "https://ipfs.infura.io:5001/api/v0/add"
```


**<span style="text-decoration:underline;">Note:</span>**



* `@myfile` should be the name of the image file in the current folder. So in our case `@polygon-nft.jpg`.
* `PROJECT_ID` and `PROJECT_SECRET` will be the keys provided in the settings for our Infura project.

The output from the command should look something like this:


![The console readout after uploading an image to IPFS via the command line](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/27wxccnkb8ec5pfjj0zk.png)

The most important part of the output will be our “Hash”. We can actually verify the image was uploaded successfully by pasting that Hash into the following URL: [https://ipfs.io/ipfs/YOUR_HASH_HERE](https://ipfs.io/ipfs/YOUR_HASH_HERE)

It’s important to note that Infura pins our data to IPFS by default. If we ever want to remove an item from IPFS we have to [unpin it](https://infura.io/docs/ipfs#section/Getting-Started/Unpin-a-file). Now that our image is stored on IPFS, let’s get the metadata stored there as well. According to [standards by OpenSea](https://docs.opensea.io/docs/metadata-standards), we will want our metadata in JSON format like so:


```
{ 
  "name": "My Sweet Polygon NFT", 	
  "description": "Our amazing NFT deployed to the Polygon Network", 	
  "image": "ipfs://YOUR_HASH_HERE" 
}
```


So let’s create a new text file in the same folder on our local machine as our NFT image. We can use any text editor for this; it doesn’t need to be anything fancy.

We’ll add the metadata in the same format as above. However, rather than using `YOUR_HASH_HERE` we’ll insert the actual Hash for our image that we just verified. We’ll save the file as `nft-metadata.json`. We also need to make sure there is no comma after the last item in our JSON file. If there is a comma, then OpenSea will not display the NFT properly. 

Now we can add this file to IPFS using the same command as last time; we just need to replace the file name with `@nft-metadata.j`.

Excellent! We now have our metadata pinned to IPFS which has a link that points to our already pinned image. We’ll copy the Hash in the output to use later.


### Setting Up MetaMask

In order to interact with the Polygon Network and to pay any transaction fees, we will need to set up a Web3 wallet. We will use MetaMask to do this. It is installed as a browser extension and allows us to connect to Decentralized Apps (dApps). 

Let’s head over to [metamask.io](https://metamask.io/) and download the extension for our browser. When using a crypto wallet like MetaMask, it’s a good idea to understand [wallet safety](https://metamask.zendesk.com/hc/en-us/articles/360015489591-Basic-Safety-and-Security-Tips-for-Metamask). 

**<span style="text-decoration:underline;">Remember</span>:** Never share your seed phrase with **anyone**! Whoever has your seed phrase can withdraw your tokens.

We will need to use the 12-word seed phrase when setting up our Truffle project. So be sure to write that down and save it for later. Once we install MetaMask and open it to the dashboard, we need to add Polygon to our Networks dropdown.


![Where to click to add new networks to metamask](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q920i3hflbwczvym5eq9.png)

When we click the dropdown, it reveals a button to add new networks to the list. So we’ll press the “Add Network” button, and [add both the Polygon Mainnet and Mumbai Testnet](https://docs.polygon.technology/docs/develop/metamask/config-polygon-on-metamask/). 

We will need to enter in the following information:

**Mumbai Testnet**

* Network Name: Mumbai (Polygon Testnet)
* New RPC URL: [https://rpc-mumbai.maticvigil.com/](https://rpc-mumbai.maticvigil.com/)
* Chain ID: 80001
* Currency Symbol: MATIC
* Block Explorer URL:[ https://mumbai.polygonscan.com/](https://mumbai.polygonscan.com/)

**Polygon Mainnet**

* Network Name: Polygon Mainnet
* New RPC URL: [https://polygon-rpc.com/](https://polygon-rpc.com/)
* Chain ID: 137
* Currency Symbol: MATIC
* Block Explorer URL:[ https://polygonscan.com/](https://polygonscan.com/)

Now we have the Polygon Network added to our MetaMask, but if we switch to the Mumbai Testnet, there’s no MATIC! Let’s change that. We’ll need to copy our wallet address, which is listed under the name of our Account. It starts with 0x…

Next, we’ll head over to [https://faucet.polygon.technology/](https://faucet.polygon.technology/) which is a faucet that provides test funds for development. We need to select the Mumbai Network and then paste our address into the text field. Once we hit “Submit” we should see some MATIC tokens in our MetaMask wallet after a minute or two.

**Note:** If you have multiple accounts in your MetaMask wallet, Truffle uses the first one by default. So be sure to add the test MATIC tokens to the first account in your list.

Now that we have loaded our wallet with some test tokens, we are ready to move on.


### Install Truffle and Ganache


#### **Truffle**

Truffle is a development environment that provides tools to make building on the blockchain much easier. In order to install it via the command line, we will need NodeJS v8.9.4 or later and Node Package Manager (npm). If it’s not already installed, [check out this link](https://nodejs.dev/learn/how-to-install-nodejs).

Once it’s installed on our machine, we can install Truffle using this command in the terminal:


```
npm install -g truffle
```


We can type the command `truffle version` afterward to ensure it was installed correctly. If there are any errors, be sure to add the npm modules to your path.


#### **Ganache**

Next we’ll install Ganache, a local blockchain instance used for testing before deploying to the Mumbai Testnet.

Ganache has a GUI version that can be downloaded [here](https://trufflesuite.com/ganache/), but we will use the terminal version for the rest of this tutorial. It can be installed using this command:


```
npm install ganache --global
```


Again, we can verify proper installation by checking the version: `ganache --version`


## Setting Up Our Project

Now that our accounts are set up and everything is installed properly, let’s start putting it all together. The first step is to navigate in our terminal to where we want to create our project and make a new directory.


```
mkdir polygon-nft-project && cd polygon-nft-project
```


One incredibly useful thing about Truffle is they offer a full suite of “[boxes](https://trufflesuite.com/boxes/)” for getting started quickly. They are essentially boilerplates that include helpful modules, pre-made smart contracts, frontend views, and more. For this project, we will utilize Truffle’s [Polygon Box](https://trufflesuite.com/boxes/polygon/). Installation is simple; we just need to type this command:


```
truffle unbox polygon
```


After installation, we can see there are several new files in our project folder. The most important ones we will be working with are the `README.md` file, `truffle-config.polygon.js`, the `1_deploy_simple_storage.js` under the `migrations` folder, and `SimpleStorage.sol` files, which can be found in the `contracts` folder. 

You will likely see an `ethereum` and `polygon` folder under `contracts`. This is because typically when a project is deployed to Polygon, it should also be deployed to Ethereum so users can easily bridge their assets back and forth. We can delete the `SimpleStorage.sol` contracts since we won’t be using them.  

Taking a quick glance through the `README.md` file, we can see that in order to deploy to the Polygon Network, we will have to add two things:


1. The Mnemonic phrase (seed phrase) of the wallet we are using
2. The Infura Project ID


We’ll want to store these in a `.env` file and make sure it’s added to our `.gitignore` so we don’t accidentally upload these secrets if storing our project on a public repository. 

Downloading the Polygon Truffle Box also installed the `.dotenv` package for us. All we need to do is create a `.env` file in our root folder. 

The mnemonic phrase for our wallet can be found in our MetaMask settings under the “Security & Privacy” heading. The Project ID can be found under the “Keys” heading in our Infura project settings. Our `.env` file will look something like this:


```
MNEMONIC="your twelve words here ..."
INFURA_PROJECT_ID="project ID # here"
```


The next thing we should do is update Truffle to use the latest version of Solidity. In our `truffle-config.polygon.js` file, we can add the compiler version we wish to use.

Under the `compilers` section, within the `solc` curly braces, we’ll add the following: `version: “0.8.11”` (the latest version of Solidity at the time of this writing).

It should look like this:


```js
// Configure your compilers
compilers: {
    solc: {
        version: "^0.8.11"
    }
},
```


**Note:** If you didn’t delete the `SimpleStorage.sol` contracts, then you will need to update their Solidity version in order to compile properly. Simply change the pragma line to the following: `pragma solidity >=0.4.21 <0.9.0;`

The last bit of prep work is just to install the [OpenZeppelin contract library](https://docs.openzeppelin.com/contracts/4.x/), as we will import several contracts. We can install it by typing this command in the root directory of our project:


```
npm install @openzeppelin/contracts
```


We are utilizing the OpenZeppelin library to make writing our smart contract easier and much safer. The contracts we will use have had thorough security audits and it ensures we are adhering to the industry standard for NFTs.

With our project set up properly, we are ready to get to the best part… Creating the NFT!


### The Smart Contract

We will open up our preferred code editor from our project root folder. (I’m using vscode so the command is `code .`.) Create a new file under the `contracts` folder named `PolygonNFT.sol`.

**Note:** If you need to brush up on the Solidity coding language, check out my [Solidity Basics Series](https://blog.paulmcaviney.ca/series/solidity-basics).

We’ll be using the following smart contract for our NFT:


```js
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.11;

// Import the OpenZeppelin contracts
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

// Declare our contract and inherit from the OpenZeppelin ERC721 and Ownable contracts 
contract PolygonNFT is ERC721, Ownable {
    // Helpers for counting safely and converting data to strings
    using Counters for Counters.Counter;
    using Strings for uint256;

    // State variable for storing the current token Id
    Counters.Counter private _tokenIds;
    // Map token Ids to token URI
    mapping (uint256 => string) private _tokenURIs;

    // ERC721 requires a name for the NFT collection and a symbol
    constructor() ERC721("PolygonNFT", "PNFT") {}

    // Set the URI (metadata) for tokenId
    function _setTokenURI(uint256 tokenId, string memory _tokenURI)
        internal
        virtual
    {
        _tokenURIs[tokenId] = _tokenURI;
    }

    // Return the Token URI - Required for viewing properly on OpenSea
    function tokenURI(uint256 tokenId)
        public
        view
        virtual
        override
        returns (string memory)
    {
        require(_exists(tokenId), "Token does not exist");
        string memory _tokenURI = _tokenURIs[tokenId];

        return _tokenURI;
    }

    // Mint the NFT to the provided address, using the provided metadata URI -- only the wallet address that deployed this contract can call this function
    function mint(address recipient, string memory uri)
        public
        onlyOwner
        returns (uint256)
    {
        _tokenIds.increment();
        uint256 newItemId = _tokenIds.current();

        _mint(recipient, newItemId);
        _setTokenURI(newItemId, uri);

        return newItemId;
    }
}
```


Pretty short, right?! That’s the beautiful thing about the composability of Web3. OpenZeppelin is doing most of the heavy lifting here with their ERC721 standard.

Basically, we are simply defining the NFT collection and symbol, supplying the NFT metadata via the `_setTokenURI()` function, putting it together with our `mint()` function, and then providing a way for OpenSea or anyone else to retrieve the NFT metadata through our `tokenURI()` function.


## Testing Deployment With Ganache

Deploying to the blockchain is pretty simple, but before we can do that, we need to modify the `1_deploy_simple_storage.js` file under the `migrations` folder. We just need to replace every instance of `SimpleStorage` with whatever we named our NFT smart contract. In our case: `PolygonNFT`. We should also rename the file to `1_deploy_polygon_nft.js` to eliminate any confusion. 

Our migration file should now look like this:


```js
const PolygonNFT = artifacts.require("PolygonNFT"); 

module.exports = function (deployer) { 
    deployer.deploy(PolygonNFT); 
};
```


Before deploying our project to a live blockchain, it is common practice to test it on a local blockchain instance. We will use Ganache to do this. In a new terminal window, we will use this command to get it up and running:


```
ganache 
```


The terminal should output some Available Accounts, their Private Keys, and so on.

To deploy our project to Ganache, open up the original terminal window. In the root directory of our project, type the command:


```
truffle migrate --config=truffle-config.polygon.js --network=development
```


Since there are two config files in our project, we need to specify which one to use. Truffle will use the `truffle-config.js` file by default. After hitting enter, we can see that Truffle compiles our contracts and then starts the migration. If all goes successfully, you will receive a transaction receipt that will look something like this:


![the transaction receipt returned in the command line after a successful migration](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nvmpvzbeogwnmtdmfifu.png)
>Take note of the contract address

Now that our contract has migrated successfully to our local blockchain instance, we can use the [Truffle Console](https://trufflesuite.com/docs/truffle/getting-started/interacting-with-your-contracts.html) to interact with it. The Truffle Console is a powerful tool that allows us to use javascript to interact directly with our contract without having to set up a frontend. To use it, type the command:


```
truffle console --config=truffle-config.polygon.js --network=development
```


Notice how the command prompt changes to `truffle(development)>`. We are now ready to start interacting with our smart contract. 

First, we need to get an instance of our contract. Copy the contract address from our transaction receipt to be used in this line of code:


```
let instance = await PolygonNFT.at("YOUR_CONTRACT_ADDRESS_HERE")
```


It will return undefined but if we type `instance` it should output our contract ABI. Now we can call the mint function. We will need a contract address in which to send the NFT and our IPFS URI from earlier in the format: `ipfs://YOUR_HASH_HERE`


```
await instance.mint("YOUR_WALLET_ADDRESS", "YOUR_METADATA_URI")
```


It’s important to note that the mint function is being called by the address that deployed the contract because that is the one we logged into the Truffle Console with by default. The address we placed in the code above is the recipient of the NFT.

If all went well with our code above, we shouldn’t see any errors after we hit enter! We now know our contract works and we are able to mint an NFT from it. Unfortunately, since this is a local blockchain instance, we don’t have OpenSea or PolygonScan to verify that our NFT actually exists. For that, we will deploy to the Mumbai Testnet.


## Deploy to Polygon Testnet

The process to deploy to the Mumbai Testnet is very similar to launching on our Ganache blockchain instance. We just need to exit the Truffle console by typing `ctrl+c` twice and then follow the exact same steps as above. The only difference is we will replace the network= `development` with `polygon_infura_testnet`. 

Before moving forward, we need to make sure our `Mnemonic phrase` and `Project ID` are set up properly in our `.env` file otherwise, the next steps won’t work. (See the steps in the Setting Up Our Project section.) With those in place, our commands will now look like this:


```
truffle migrate --config=truffle-config.polygon.js --network=polygon_infura_testnet
```


The migration will take a little bit longer than our Ganache instance. Once it’s complete it will output a transaction receipt that looks similar to our last one. This time we can verify that our contract was successfully migrated to the Mumbai Testnet by entering our contract address to [https://mumbai.polygonscan.com/](https://mumbai.polygonscan.com/address/)address/YOUR_ADDRESS_HERE


![what the transaction looks like on Polygonscan](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k0pnac4gzj5lg7u1agei.png)

Excellent! Our contract is live on the Mumbai Testnet! Now let’s interact with it and actually mint our NFT!

We’ll access the Truffle Console using the same commands as before, again, replacing the network= `development` with `polygon_infura_testnet`.


```
truffle console --config=truffle-config-polygon.js --network=polygon_infura_testnet
```


Get an instance of our contract using the contract address that was output on our Mumbai Testnet transaction receipt:


```
let instance = await PolygonNFT.at("YOUR_CONTRACT_ADDRESS_HERE")
```


And now, for the moment we’ve been building towards this entire article! Mint our NFT to our desired address using our IPFS URI in the format: `ipfs://YOUR_HASH_HERE`


```
await instance.mint("YOUR_WALLET_ADDRESS", "YOUR_METADATA_URI")
```


If there aren't any errors, we can check our contract on `mumbai.polygonscan` again and see that our `mint()` function was called! This time, we can verify that our NFT actually exists by checking it out on OpenSea.


## Verify Our NFT On OpenSea

We can easily check out our new NFT on OpenSea by copying our contract address into the search bar at [https://testnets.opensea.io/](https://testnets.opensea.io/). Ethereum addresses have [two versions](https://support.mycrypto.com/general-knowledge/ethereum-blockchain/ethereum-address-has-uppercase-and-lowercase-letters/), one that is _checksummed_ and one that is not. The difference being the non-checksummed version is all lowercase. We may need to use the non-checksummed version to find our NFT on OpenSea. We can get this address by clicking on the Txn Hash on `mumbai.polygonscan` and then copying the address across from `Interacted With (To):`


![The highlighted address](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/obu89gwof9iafifrkkcs.png)

Your contract address will be different

If our metadata was entered correctly we should now see our beautiful, new NFT live on OpenSea!


![our beautiful new NFT as seen on OpenSea](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kfyyseatajpba08dmi2v.png)

## Conclusion

Congratulations! You have successfully deployed an NFT to the Polygon Mumbai Testnet! You uploaded your NFT image and metadata to IPFS using Infura’s API, set up your Truffle project and wrote a smart contract to create an NFT, tested deployment locally using Ganache, deployed to the Polygon Testnet with Truffle, Infura, and MetaMask, and finally, verified your NFT on OpenSea.

You now have the knowledge to deploy to the Polygon Mainnet, just make sure you have real MATIC tokens in your MetaMask wallet. Additionally, when deploying, be sure to use network=`polygon_infura_mainnet` instead of `polygon_infura_testnet`. 

Your next steps would be to deploy to the Ethereum Mainnet to be able to bridge your NFT from Layer 2 to Layer 1. That is the topic for another article so be sure to check out the [Polygon Docs](https://docs.polygon.technology/docs/develop/ethereum-polygon/getting-started) in the meantime.

Thank you for following along with this tutorial! Take care, and happy building!

(The code for this project can be found here: [https://github.com/paul-mcaviney/polygon-nft-project](https://github.com/paul-mcaviney/polygon-nft-project))