Deploying a Smart Contract and Selling NFTs

BarefootDev
13 min readDec 6, 2021

--

This tutorial walks through the core steps in Spectra.art’s smart contract development, testing and deployment process, which has been used to successfully launch 3 NFT collections to-date.

While intended for complete beginners to Web3 and NFTs, experienced developers looking to pick up new tools (e.g. Hardhat and Ethers.js) may also find some value.

We will cover:

  1. Writing the contract (and serving art from a decentralised data store)
  2. Testing the contract
  3. Deploying the contract
  4. Minting an NFT and setting up on OpenSea
  5. Bonus: a select list of online communities to grow and build in web3

If you want to skip through or just jump into some code check out the Github repo here: https://github.com/Barefoot-Dev/solidity-nft-boilerplate

Vortex by spectra.art

This article is the first in a three-part series on building and launching an NFT. If you’re brand new to Ethereum or NFTs, check-out the introductory post for some recommended reading.

Development environment

First, let’s set up a local development environment by installing Hardhat and setting up a boilerplate project.

mkdir nft-project
cd nft-project
npm install --save-dev "hardhat@^2.7.0"npx hardhat
# select "Create an advanced sample project that uses Typescript"
# accept all following default options

Workflow (optional)

If you’re after an IDE to code in, VS Code is an excellent choice.
If you do use VS Code, try the prettier plugin for solidity for automatic formatting.

npm install --save-dev prettier prettier-plugin-solidity

What’s our goal?

Now we’re all set up, where better to start than at the end.

For collectors to buy NFTs via your smart contract with real Ether, you’re ultimately going to need to put it on the main Ethereum network. Conveniently, it’s called the “mainnet”, and it’s freaking expensive.

Source: https://medium.com/coinmonks/a-short-guide-to-ethereum-gas-fees-5c4c53a05feb

Ethereum is highly secure, and also extremely popular. It costs gas to do anything on Ethereum, and because many people are using the network, prices can be very high. To simply save (“deploy”) a small contract to the mainnet recently cost us 0.37 Ether, or $1579 USD.

To store this on AWS cloud storage would cost ~$0.0000001 USD.

Thankfully, test networks allow us to deploy and run smart contracts for free. There are both global test networks, which are maintained by others around the world, and “local” ones, like the Hardhat network that can be run on your PC.

Fear not, the Hardhat network will automatically run in the background as the default runtime environment of your smart contracts with no extra setup.

We will use both of these to test various stages of the NFT pipeline, and then deploy to the mainnet!

Writing a smart contract: minting & metadata

Now that you have some idea of where the contract will be stored and executed, it’s time to make a simple smart contract that allows collectors to mint an NFT and access the art!

Returning to the recently set up Hardhat project, you should see some of the critical project files:

hardhat.config.ts tells Hardhat how we want the project to run

Greeter.sol is a boilerplate smart contract provided by Hardhat

Ethereum smart contracts are most commonly written in the Javascript-inspired Solidity language

Solidity is much less expressive than what you’re probably used to (eg there are no floating point numbers!). It’s also statically typed, with strings, signed and unisgned ints, structs, mappings, arrays and more.

To get started, let’s remove Greeter.sol and write our own smart contract from scratch!

rm contracts/Greeter.sol
touch contracts/CryptoArt.sol

Now, install the first dependency

npm install --save-dev @openzeppelin/contracts

OpenZeppelin effectively provide the Solidity standard library. Use Etherscan to look at the smart contracts of popular projects, such as Bored Ape Yacht Club, and you’ll typically see some trusty OpenZeppelin imports!

Open CryptoArt.sol, and paste in the following:

This code, plus the imported ERC721Enumerable contract, forms a smart contract capable of minting NFTs and providing links to the metadata and art!

Here’s a quick breakdown of what’s going on:

  • We’re initialising a new contract called CryptoArt, which extends OpenZeppelin’s ERC721Enumerable and Ownablecontracts. These give us some handy utilities that will be explained below.
  • The contract has a name (“CryptoArt”) and a symbol (“ART”).
  • The mint function takes one input, quantity and creates a new NFT with an incremental tokenId (starting at 0).
  • msg.sender is simply the address of whoever called the function, and _safeMint has been imported via OpenZeppelin’s ERC721Enumerable contract.
  • _baseURI and setBaseURI will be explained below; in short, currentBaseURIpoints the contract at metadata stored ‘off-chain’.
  • The function is marked as public meaning anyone can call it, and is payable meaning you can receive ETH when someone wants to mint.

We can test that the contract is syntactically correct by compiling it:

In a normal workflow, you won’t run this separately, as compilation automatically happens before any test or deployment.

npx hardhat compile

You should see the message Compilation finished successfully. Congratulations, you have written a valid Solidity smart contract!

Let’s add one more crucial element before proceeding, the art!

Art-based NFTs typically involve 3 components:

  1. A token ID (the one we defined in the smart contract above)
  2. Metadata (information about the token, such as its attributes, description and name)
  3. Art (provided as an image or video file)

Storing data on the Ethereum blockchain is extremely expensive. As a result, the metadata and art are typically stored on some server that the contract can point to.

Rather than a centralised Amazon or Google server, however, we can use IPFS, a decentralised data-store upheld by a peer-to-peer network.

Pinata is a helpful service for uploading files to IPFS. Sign-up for a free account here, and upload a file (it can be anything, but sticking to files like png and mp4that OpenSea will display is best). If you need a sample to play with, try this work-in-progress image from Spectra’s Chromospheres collection.

Chromospheres by Spectra.art

To access the file, go to ipfs://<CID> in (supported) browsers, using the CID shown in the Pinata dashboard.

Now for the metadata. We’re going to create one metadata file per tokenId, and simply name the file as the tokenId (with no extension). For the purpose of this demo, we’ll just create and upload metadata for token 0 (ultimately, you’ll want unique CIDs in each token’s metadata that refer to the unique art associated with each token).

mkdir metadata
touch metadata/0

In this file, paste the following (replacing <CID> with that from your Pinata dashboard):

{
"name": "Crypto Art #0",
"description": "Crypto Art is a collection of 1 PNG file, which I took from some Medium article",
"image": "ipfs://<CID>"
}

Now, via Pinata, upload this whole metadata folder to IPFS.

Each metadata file can now be found at ipfs://<METADATA-FOLDER-CID>/<TOKEN-ID>.

By default, OpenZeppelin’s ERC721 smart contract, which we’ve imported in our CryptoArt.sol file, has a function called tokenURI. This takes a tokenId, appends it to the contract’s currentBaseURI (which it accesses by calling _baseURI), and returns the result.

If we define the smart contract’scurrentBASEURI as the metadata folder IPFS link (ending in a ‘/’), then the tokenURI function will return a link to the token’s metadata. Note that this function is marked as onlyOwner, meaning only the contract’s owner (you) can call it!

For example, if the metadata folder IPFS link is ipfs://0123456789/, and we’re interested in tokenId 0, then calling tokenURI(0) will return ipfs://0123456789/0.

This means anyone can call the smart contract and by opening some IPFS links retrieve the associated piece of art!

All that’s left to do is tell the contract what this currentBaseURI should be by calling setBaseURI(ipfs://0123456789/) (we’ll do this via Etherscan after deployment — see below).

Writing a smart contract: the financials

Of course, we typically don’t want collectors to be able to mint an infinite number of NFTs from the contract, particularly when they can do so for free!

Adding a maximum supply and price to the contract is dead easy, simply update the contract as follows:

What’s changed?

  • We defined a mintPrice of 0.1 ether. This is public and constant, meaning it’s value can be checked from outside the contract, and it can’t change.
  • The maxSupply is set to 1024.
  • Inside the mint function, there are two require statements. One to check that there are still enough tokens left to satisfy the requested quantity, and another to check that sufficient ether is being paid.
  • Finally, a withdrawEth function is added. When someone mints an NFT, the ether sent in the transaction is simply held by the contract. In this function, we transfer that ether to the contract owner, you! Read more about the particular used syntax here (it’s for security reasons).

That’s it! While there’s plenty more functionality that could be added, this smart contract let’s you sell a fixed quantity of NFTs at a given price, provide unique metadata and art for each token, and withdraw all ether made. Not bad!

Testing a smart contract

Open up test/index.ts and paste in the following:

At the top, we make two imports.

  • Chai is an excellent library that hardhat uses to run smart contract tests. Here we use its expect function, which lets us test the values of things.
  • Ethers.js is a hugely important library that we’ll use in all elements of our NFT project (including the React frontend, which we’ll build in Part 3 of this series).

Structurally, we have created two groups of tests, with one test in each. In each we use Ethers.js to deploy the contract to a local Ethereum network (the Hardhat Network). Note that a brand new contract is made each deployment (the contract’s state does persevere between tests).

  • The first test calls the mint function with a quantity of 1, and then checks that the contract has a totalSupply of 1.
  • The second test checks that the tokenURI (which points to our metadata on IPFS) can be set and returned, with a unique address for token 0.

If these were run on the mainnet, we’d need to wait for the calls to be mined as a transaction, however on the Hardhat Network this is done immediately - neat!

Run the tests:

npx hardhat test

Of course this contract is not fully tested. In reality, we need to test that the contract behaves correctly in all foreseeable circumstances (e.g. if not enough ether is provided, if the maximum supply is hit, etc.). Try the solidity coverage plugin to help work out which parts of the contract aren’t being run in the tests so you can build up to 100% coverage.

Deploying a smart contract

Let’s deploy to the Rinkeby test network.

To help, we’ll use the hardhat-deploy plugin. Install it:

npm install hardhat-deploy

Now create a new file and populate it as follows:

mkdir deploy
touch deploy/001_deploy_token.ts

To deploy we need to connect to a network, which can be done via an Ethereum node. We’ll use the service provided by the excellent folks at Alchemy.io, who offer generous node access in the free tier. Sign-up and create an app on the Rinkeby network. You’ll be given a URL which ends in a key, something like: https://eth-rinkeby.alchemyapi.io/v2/<YOUR-KEY>

First, rename .example.env to .env, then paste your key into this file under ALCHEMY_KEY.

Finally, update the hardhat.config.ts file to look like:

To pay the deployment gas costs, we’re going to need some Ether, and as we’re on a test network, we can use fake Ether obtained from a faucet.

You’ll need an Ethereum wallet to receive, hold and spend this Ether. The most popular and simplest option here is MetaMask. Follow these steps to set up a wallet and configure Hardhat to use this wallet:

  1. Download the MetaMask browser extension
  2. Set up a new wallet
  3. In the .env file, put your wallet’s seed phrase under MNEMONIC and private key under PRIVATE_KEY

Security notes:

As your private key is stored in a local file, anyone who gets access to your computer could control your smart contract by acting as it’s owner. In my next post, we’ll walk through transferring the deployed contract to a secure multi-sig wallet to mitigate this risk.

For holding crypto or NFTs, it is highly recommended that you use a hardware wallet like Ledger (only purchase it from the official site), instead of a MetaMask wallet.

Input your public wallet address into one of these Faucets to get some delicious Rinkeby ETH!
- https://faucet.rinkeby.io/
- https://faucets.chain.link/ropsten

Once you can see the ETH in your MetaMask wallet (you’ll need to switch to the Rinkeby network in the extenson), it’s time to deploy!

npx hardhat deploy --network rinkeby

Helpful tips:

To save a record of your latest deployment, including the address it’s deployed to you can add --export-all deployments.json to the command.

If the transaction is hanging, it may be because the Rinkeby gas price has fluctuated above its normal and expected value of 1 Gwei. Try re-deploying with a gas price of 1.1 (or whatever it is on rinkeby.io) to speed things up with the flag --gasprice 1100000000. Note that Hardhat expects the price to be denominated in Wei; you can do the conversion here.

After a successful deployment you should see:

Deployer:  <YOUR PUBLIC WALLET ADDRESS>deploying "CryptoArt" (tx: <TX HASH>)...: deployed at <CONTRACT ADDRESS> with X gas

Once this transaction succeeds, you’re ready to mint an NFT!

Minting an NFT

In part 3 of this series we’ll build a simple React web-app to mint NFTs. For now, however, let’s test the deployment contract by minting through Etherscan.

First, we’ll need to verify the contract on Etherscan so we can see the source code and functions. To do this we need an API key from Etherscan, which you can get here. Don’t worry, this is also free!

Once you’ve got your key, add it to the .env file under ETHERSCAN_API_KEY .

Now, the following command will verify the contract’s source code. The contract address was output when we deployed the contract above.

npx hardhat verify <CONTRACT ADDRESS> --network rinkeby

If the verification is successful you should see a link, follow it to see your contract’s code on Etherscan.

More importantly, we can now find the mint function under the Write Contract tab. Try the following:

  1. Connect your MetaMask wallet (find the “Connect to Web3” button on Etherscan)
  2. Click “mint”
  3. Input “1” as the quantity, and “0.1” as the payableAmount
  4. Click “write”
  5. Confirm the transaction in MetaMask (the extension will automatically open)
  6. Wait for confirmation (may take minutes)

Similarly to the test function from earlier, we can verify a mint by checking the contract’s totalSupply. Go to the Read Contract tab in Etherscan, and click totalSupply to see a value of 1 (you may need to refresh the page).

Bravo! You’ve now deployed a contract AND minted an NFT.

Registering your smart contract on OpenSea and managing your art as a collection is straightforward, but first we need to tell the contract where token 0’s metadata and art is!

Find the setBaseURI function on Etherscan, input your IPFS link ipfs://<METADATA-FOLDER-CID>/, hit “Write”, and confirm the transaction in MetaMask. Once it’s done, you can read the tokenURI from the contract, which should return the IPFS link to your token’s metadata!

Now let’s see it on OpenSea!

  1. Input your contract address here: https://testnets.opensea.io/get-listed/step-two
  2. Edit the collection with a name, description and images, and define the royalties you’ll earn from secondary sales.

If OpenSea is taking it’s sweet time, which it probably is, you can always check that nothing’s gone wrong by using their API:
`https://testnets.opensea.io/assets/<CONTRACT ADDR>/0/validate`
If no errors are returned, just give their servers a bit more time.

Now that you’ve written and tested a smart contract, and deployed it on the Rinkeby test network, why not try all of the above steps on the mainnet?

  • Ensure you MetaMask wallet has some real ETH in it.
  • Replace --network rinkeby with --network mainnet in the commands run.
  • Use the main links for Etherscan and OpenSea by simply removing rinkeby and testnets from their respective URLs.

Bonus: Community

Apart from all the exciting tech, my favourite thing about this space is the number of incredibly positive and supportive communities springing up all over Web3.

If you want to throw ideas around or deep dive into the latest technical developments, come say gm!

Crypto Devs
- Discord

Web3 University (by Alchemy.io)
- Discord
- Twitter

Hardhat
- Discord
- Twitter

Developer DAO (very good, but you’ll need to buy an access token).
- Twitter
- Discord

For less of atechnical focus but still a lot of fun:

ZenAcademy
- Discord
- Twitter

Gm DAO
- Discord
- Twitter

Next up

In the next post we’ll use Hardhat to control the contract from code, so that we don’t have to do it manually through Etherscan.

I’ll also explain how a Gnosis safe can be used to secure the contract while still being able to make calls to it from our Hardhat environment, which is much safer given the current setup has the contract owner’s private key stored in a local .env file. This is exactly the process we’ve used at Spectra.art to ensure our contracts and Ether are secure.

Eternal Fragments by Spectra.art

If you’d like to discuss this process or alternatives please reach out. Web3 moves extremely fast, making it crucial to continuously learn and update.

Anything else you’d like me to cover? Reach out on Twitter!

Join Coinmonks Telegram Channel and Youtube Channel learn about crypto trading and investing

Also, Read

--

--

BarefootDev
BarefootDev

Written by BarefootDev

Co-founder at Spectra.art. Blockchain, AI & frontend developer. Find me on Twitter! https://twitter.com/BarefootDev

Responses (2)