Creating your own NFT from scratch and listing it on OpenSea

Marco Colapietro
gft-engineering
Published in
13 min readNov 24, 2021

--

NFT MCH+, CC0, via Wikimedia Commons

Introduction

NFTs (or Non-Fungible Tokens) have become increasingly popular, first after the CryptoKitties game was released (and saturated the Ethereum network) and lately when the creator of Twitter sold its first tweet as an NFT for almost 3 million dollars.

In this article we will explain how to create your own NFT collection, adding “digital pieces of art” as tokens. For this, we will be using the Ethereum blockchain and the ERC-721 standard. We will be storing the “pieces of art” (images) in IPFS and given that we will follow the metadata standards of the ERC721 Json Schema, the NFT will also be available in OpenSea.

So … what it is an NFT?

An NFT or Non-Fungible Token is a unique digital asset (that may represent non-digital assets), which uses the blockchain to ensure its authenticity. This type of token is a great resource to be used in platforms or organizations that offer different types of products where they need to prove unique properties and attributes such as:

  • Collectibles (e.g: NBA TopShot)
  • Art (e.g: CryptoPunks)
  • Game items (e.g: Gods Unchained)
  • Virtual worlds (e.g: Decentraland)
  • Documentation and real-world assets (eg: real estate)

And what it is the ERC721 standard?

The ERC-721 (Ethereum Request for Comments 721), proposed by William Entriken, Dieter Shirley, Jacob Evans, Nastassia Sachs in January 2018, is a Non-Fungible Token Standard that implements an API for tokens within Smart Contracts.

It provides functionalities like to transfer non fungible tokens from one account to another, to get the current token balance of an account, to get the owner of a specific token, as well as the total supply of the token available on the network.

Also allows optional implementation of metadata for your tokens.

Tools & Development Assets

Pre-requisites to go through the article:

Here is a brief description of each element we use throughout the process based on its documentations:

  • OpenZeppelin is a library of modular, reusable, secure smart contracts for the Ethereum network, written in Solidity. It allows to leverage standard, tested, and community-reviewed contracts for its own purposes and can significantly reduce the development time of applications. We will use Presets contracts in OpenZeppelin Contracts 3 to create an ERC721 and deploy using Truffle.
  • Truffle is a development environment, testing framework and asset pipeline for Ethereum, aiming to make life as an Ethereum developer easier.
  • Node JS. We need to use some Javascript code and Truffle, what needs Node Package Manager (npm) to be installed.
  • Virtual Studio Code or any other IDE which supports Solidity and JavaScript to modify some code and in order to customize your token.
  • Infura , that provides access to public nodes for all testnets and the main network, via both free and paid plans.
  • IPFS (InterPlanetary File System) is a protocol and peer-to-peer network for storing and sharing data in a distributed file system. IPFS uses content-addressing to uniquely identify each file in a global namespace connecting all computing devices.
  • Pinata provides secure and verifiable files for your NFTs. Whether you are a platform, creator, or collector, simply upload through Pinata, copy your IPFS CID, and attach to your NFT before minting to have secure and verifiable NFT files.
  • OpenSea is a decentralized peer-to-peer marketplace for buying, selling and trading rare digital goods, from gaming items to collectibles to art, which are built on non-fungible token (NFT) technology and run on the Ethereum blockchain.
  • The asset that you want to tokenize (image file in this case).

After putting in context the different elements that we will use … Let’s get started!

Metadata

There are many approaches to solve the metadata storage. For this article I’ve chosen to do it off-chain (so, I’ll host the metadata out of the blokchain) but in a decentralized way using IPFS to retrieve the metadata.

It could be centralized, and in that way we will get different benefits, as a faster response or bigger control about the project … or directly on-chain, what due to the current limitations on fees and gas-cost increases massively deployment cost.

I’d recommend you to read more about it in this article from Opensea’s Blog, in which they explain pretty well what are the circunstamces when it comes to talk about metadata and it’s de/centralization:

https://opensea.io/blog/announcements/decentralizing-nft-metadata-on-opensea/

To retrieve the data we have off-chain, OpenSea reads the ERC-721 Json Schema, what makes things easier.

Since we are going to deploy static NFT, we will need to set-up all the metadata in advance for every token we want to deploy.

To do this, we will write the metadata of every token we want to create in individual text files (with no file extension, but following the json schema), and then we will put them all in the same directory, which we will upload later to IPFS to pin it with Pinata.

This is a reference for the ERC721 Json Schema:

{
"title": "Asset Metadata",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Identifies the asset to which this NFT represents"
},
"description": {
"type": "string",
"description": "Describes the asset to which this NFT represents"
},
"image": {
"type": "string",
"description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive."
}
}
}

This would be a real example of an NFT metadata:

{
"description": "Galaxy & stars photos",
"external_url": "https://www.pexels.com/photo/milky-way-galaxy-during-nighttime-1252890/",
"image": "https://gateway.pinata.cloud/ipfs/QmZ6iJbUpEfKxUodwx4DgaF9zquvRjJEMXAkH8EJtWPLKm",
"name": "Starry Night #1",
"attributes": [
{
"trait_type": "Author",
"value": "Hristo Fidanov"
},
{
"trait_type": "Camera",
"value": "NIKON D750"
},
{
"trait_type": "Resolution",
"value": "6016px x 3385 px"
},
{
"display_type": "date",
"trait_type": "Published",
"value": 1531951200
}
]
}

And in the following image we could see how the result would be according to the OpenSea conventions (more info about metadata standards in here):

OpenSea’s Metadata documentation

You can check the links below the article if you want to see examples already made and its result.

Upload metadata to IPFS & Pinata

Pinata IPFS Service

First thing we will do is upload the images to Pinata that we want to attach to our tokens, it will allow us to keep the image stored immutably and decentralized manner.

After that, we’ll see our image in ‘My Files’. Click on it and copy the URL to the metadata text file associated.

Finally, we should have a folder as the one below containing the different files with our metadata.

Our metadata folder for tokens 0, 1 and 2.

We will finally upload the folder to Pinata.

Pinata Dashboard

And it will give you an URL that you will need later for the contract deployment script.

If you click on it, should bring you to a folder like this:

IPFS Folder

Should be something like this:

https://gateway.pinata.cloud/ipfs/QmcdnHCTuPoazQG8ft3tsRQ6RZTob6EDgAR5Mo3LV2Weif/

You can check inside how my examples have been done.

Setting up the environment

You will need WSL2 to execute every command on Windows OS

Start creating a new project:

mkdir <FOLDER_NAME_PROJECT>cd <FOLDER_NAME_PROJECT>npm init -y

Then, install OpenZeppelin Contracts which contains many different Solidity files which we will use for our ERC721’s implementation:

npm install --save-dev @openzeppelin/contracts

And installing the development framework for deployment, which will be Truffle for this tutorial:

npm install truffle

Setting the Truffle project

To use most Truffle commands, you need to run them against an existing Truffle project. So the first step is to create a Truffle project.

npx truffle init

This command will create a directory called ‘contracts’ and a configuration file (‘truffle-config.js’), a Javascript file which can execute the necessary code to create and manage your truffle environment configuration, e.g:

  • Coherence between the compiler you use in your scripts and the one selected in the config.
  • Your network config for the project (Development, Rinkeby, Kovan, Göerli … etc).

We’ ll do a dive into this file later.

Smart contracts and deployment files.

ERC721 & Extensions

Smart-contracts in Solidity are kind of similar to the “classes” concept in the OOP paradigm. Contracts contain persistent data in state variables, and functions that can modify these variables. Calling a function on a different contract (instance) will perform an EVM function call and thus switch the context such that state variables in the calling contract are inaccessible.

We are going to use Preset ERC721PresetMinterPauserAutoIdwhich is an ERC721 that is preset with the ERC721 standard and its extensions.

Basically we are importing different Solidity smart contract with different functionalities in just one file that combine all of them to give us most of the potential of the standard.

We can check the ERC721PresetMinterPauserId.sol and all the others Solidity files used for this smart-contract following the path\node_modules\@openzeppelin\contracts\token\ERC721 and/or in OpenZeppelin’s Github.

ERC721PresetMinterPauserAutoId.sol

And these functionalities allow us to execute different transactions related to NFTs and the ERC721 standard :

  • Mint. To ‘mint’ an NFT which means to create the digital representation of something and its belonging to the blockchain.
  • Burn. Which allow you to send the token to a ‘black hole’ account with no-access to the public, whom can only check the balance but not its content.
  • Pause. Allows you to pause the transfers of the NFT, what could be useful if for any reason you would like to stop the marketability of the asset.
  • Transfer. Needed to transfer and keep a trace of the asset in the blockchain.
  • Add Metadata. Return a token URI. Our token URI will be baseUri + tokenId, which will be automatically added and increased with every minting.

The Preset contracts have already been compiled, so we only need to copy the artifacts to the build/contracts directory:

mkdir -p build/contracts/
cp node_modules/@openzeppelin/contracts/build/contracts/* build/contracts/

Deploy contract script

Using your IDE create 2_deploy_token.js in the migrations directory with the following contents:

Don’t forget the last ‘/’ on the baseUri!

Here we are setting (in order):

  • The contract we are using
  • The name of the collection
  • Symbol of the NFT
  • baseTokenURI pointing to the IPFS which points to our metadata.

Migrations

As written in Truffle docs: Migrations are Javascript files that help you deploy contracts to the Ethereum network. These files are responsible for staging your deployment tasks, and they’re written under the assumption that your deployment needs will change over time.

So, migrations are really useful when you want to manage the interactions of a bigger project and you want to deploy different smart-contracts than rely on each other at different times.

You can find this file inside the migrations directory as ‘1_initial_migrations.js’

And it’s Solidity code, which you can find in the contracts folder inside your project directory.

Deploy to a public testnet (Rinkeby)

An Ethereum testnet is a network very similar to the main one but in which the ether has no value and can be obtained for free. This makes them especially useful for purposes like testing the transactions cost.

We will deploy to Rinkeby public testnet as OpenSea supports Rinkeby (as well as Ropsten, Göerli … ) for testing.

What do we need to deploy on a public testnet?

  • Get hold of a testnet node
  • Create a new account
  • Update our networks configuration in our Truffle configuration file
  • Fund our testing account

Connect to a node in the testnet

Easiest way to access a testnet is via a public node service such as Infura.

Create an account on Infura, set up a new project and get the ‘ProjectID’ since we will need it later.

Infura’s UI

Create a new testing account

To send transactions on a testnet you need an Ethereum account. Truffle and mnemonics package provides you this:

npx mnemonics 

*(If you don’t have it installed, you’ll be asked for it)

And this will print on your console screen a serie of words which you will need to sign the transactions with your account, so keep them to write them later in the secrets.json file.

Keep your mnemonics safe, even for testing purposes.

Update your networks configuration in our Truffle configuration file

Since we are using public nodes, we will have to sign all our transactions locally.
We will use @truffle/hdwallet-provider together with our set of mnemonic words previously generated.
Through the Infura endpoint we will tell the provider how to connect to the Rinkeby testnet.

npm install --save-dev @truffle/hdwallet-provider

After the installation is completed, we will modify the truffle-config.js file with a new connection to the Rinkeby’s testnet.

Should look like this:

A secrets.json file is required inside the project directory in order to not hardcode your mnemonics and your projectId (from Infura). You can use any other secrets-management solution you like as long as it’s safe.

In this case, the file used should contain something like this:

{     
"mnemonic": "car photo eye hurricane hawk ...",
"projectId": "505c127050..."
}

Fund your testing account

To get the testing accounts Truffle is providing you in the project, execute:

npx truffle console --network rinkeby

and then:

truffle (rinkeby) > accounts

This command will drop a list with the different accounts you can use:

  1. Select the first one
  2. Write a tweet or Facebook post with it.
  1. Copy the URL of it (tweet in my case) and submit it to the Rinkeby’s Faucet.
Rinkeby’ Faucet

Now, (if everything is alright) you got your Rinkeby ethers so you can start to spend some rETH!

Deployment

Again on your project directory, execute these commands:

npx truffle console --network rinkeby 
truffle(rinkeby)> migrate

And now deploy your contract:

mynft = await ERC721PresetMinterPauserAutoId.deployed()

Once the contract has been deployed

If everything has gone as it should, you should be able to interact with the contract, so we will mint 1 NFT :

truffle(rinkeby)> await mynft.mint("YOUR_METAMASK_ACCOUNT_ADDRESS")

OpenSea

Now we will list our NFT in OpenSea to make it visible and buyable by anyone.

To obtain the address of our contract on Rinkeby we can use the Truffle console:

truffle(rinkeby)> mynft.address

Go to Opensea and click on ‘My Collections’, and then the three vertical dots to ‘Import an existing smart contract’.

After choosing ‘Live on testnet’, copy & paste your smart contract address:

You may receive the following message: ‘We couldn’t find this contract. Please ensure that this is a valid ERC721 or ERC1155 contract deployed on Rinkeby and that you have already minted items on the contract’.

This is a common issue, Opensea will display your tokens, but it might not be instantaneous. As long as you can see the token contract and transactions on Etherscan Rinkeby, you know it is a valid ERC-721 contract and has minted X items. Sometimes you have to wait 12/24 hours to get their NFTs to appear.

And assuming there aren’t issues at this point, we should be able to see our NFT listed on OpenSea, following the next structure:

https://rinkeby.opensea.io/assets/[nft contract address]/[token id]

It could get a bit of time until OpenSea gets the metadata, just refresh pressing that button.

The example I made for this tutorial can be found in here:

https://testnets.opensea.io/assets/0xd76f8f14933e83dd4a80f2065eb0d20add9a7fcd/0

And more in another profile where you can find a collection made with Generative Adversarial Networks and some different types of properties on its metadata:

https://testnets.opensea.io/collection/my-nft-stones-collectibles

Import it to your Metamask wallet

As well, you should be able to import your NFTs into your Metamask wallet. Just open the Metamask Chrome extension of the address you passed for the minting earlier, and select ‘Import Tokens’ down in the menu. You will be asked for the contract address and after you copy it there, it will set the token symbol you passed earlier automatically. Also, you need to select the decimals.

*I recommend to not use 18 decimals as I did if you don’t want to see lot of zeros …

Next steps

Many possibilities at this point to further develop your NFTs:

  • One of them could be to study how to make the deployment on the mainnet as well as how to estimate the cost.
  • Another could be the incorporation of oracles in contracts to carry out verified off-chain operations.
  • Check how to incorporate all metadata directly on-chain and check how the gas and fees work.
  • You could use an API to expose centralized metadata.
  • Create dynamic NFTs.

Conclusion

We have followed a simple process during which we have used an OpenZeppelin preset written with Solidity and deployed through Truffle to create an NFT with the ERC721 standard and using tools like Pinata to ensure the permanence of our metadata in a decentralized storage service like IPFS, as well as listing it in OpenSea following the metadata standards, to finally import the tokens into our Metamask wallet.

If you got interested about it or want to know more about NFTs and digital art, feel free to listen to the podcast by our colleagues David Creer and Julien Donnet — “Your Immutable Future” — Let’s talk NFT’s

--

--