Deploying a Smart Contract and Selling NFTs
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:
- Writing the contract (and serving art from a decentralised data store)
- Testing the contract
- Deploying the contract
- Minting an NFT and setting up on OpenSea
- 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
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-projectnpm 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.
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
andOwnable
contracts. 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 incrementaltokenId
(starting at 0). msg.sender
is simply the address of whoever called the function, and_safeMint
has been imported via OpenZeppelin’sERC721Enumerable
contract._baseURI
andsetBaseURI
will be explained below; in short,currentBaseURI
points the contract at metadata stored ‘off-chain’.- The function is marked as
public
meaning anyone can call it, and ispayable
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:
- A token ID (the one we defined in the smart contract above)
- Metadata (information about the token, such as its attributes, description and name)
- 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 mp4
that OpenSea will display is best). If you need a sample to play with, try this work-in-progress image from Spectra’s Chromospheres collection.
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
andconstant
, 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 tworequire
statements. One to check that there are still enough tokens left to satisfy the requestedquantity
, 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:
- Download the MetaMask browser extension
- Set up a new wallet
- In the .env file, put your wallet’s seed phrase under
MNEMONIC
and private key underPRIVATE_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
- Put the transaction hash into this etherscan url and you’ll be able to see the transaction’s status:
https://rinkeby.etherscan.io/tx/<TX HASH>
- Put the contract address into this etherscan url and you’ll be able to see your contract (though we need to verify it first before the code will be visisble, keep reading!):
https://rinkeby.etherscan.io/tx/<CONTRACT ADDRESS>
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:
- Connect your MetaMask wallet (find the “Connect to Web3” button on Etherscan)
- Click “mint”
- Input “1” as the quantity, and “0.1” as the payableAmount
- Click “write”
- Confirm the transaction in MetaMask (the extension will automatically open)
- 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!
- Input your contract address here: https://testnets.opensea.io/get-listed/step-two
- 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
andtestnets
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
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:
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.
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
- iTop VPN Review | Mandala Exchange Review
- Coinbase vs WazirX | Bitrue Review | Poloniex vs Bittrex
- Best Crypto Trading Bots in the US | Changelly Review
- A-Ads Review | Bingbon Review | Mudrex Invest
- Best Cardano Wallets | Bingbon Copy Trading
- Best P2P Crypto Exchanges in India | Shiba Inu Wallets
- Top 8 Crypto Affiliate Programs | eToro vs Coinbase
- Best Ethereum Wallets | Cryptocurrency Bots on Telegram
- Best Exchanges to Trade Leveraged Tokens
- 5 Best Social Trading Platforms | WazirX NFT India
- 10 Best Books on Crypto | 5 Best Crypto Bots in the UK
- Koinly Review | Binaryx Review | Hodlnaut vs CakeDefi
- Bitsler Review | WazirX vs CoinSwitch vs CoinDCX