Learn about decentralized applications, or dApps, and non-fungible tokens, or NFTs, along with the ERC-721 token standard.
By the end of this post, readers will be able to:
We will create a smart contract for NFTs that uses the Ethereum Request for Comments (ERC)-721 standard. We will also build a dApp that can register new digital artwork on a blockchain by minting the NFTs.
From the perspective of a user, a dApp is just like any other application that they’d use on the internet. What makes a dApp different is that it’s built on a decentralized network, such as a blockchain.
The structure of each application typically consists of a back end and a front end, as shown in the following image:
dApps embrace decentralization by using new blockchain technologies, such as smart contracts, so they become robust against centralization issues, such as having a single point of failure, being susceptible to attack, or even encountering access problems.
NFTs represent unique assets, such as land, art, or other items with no interchangeable counterpart. This stands in contrast to fungible tokens like cryptocurrencies where, for example, a single bitcoin is easily interchangeable with several ether.
With dApps specifically designed for NFTs, users can buy or sell artwork NFTs in a decentralized marketplace. They can also play games for which the avatars and game items are NFTs on a blockchain.
We’ll use the ERC-721 Non-Fungible Token Standard defined by the Ethereum Improvement Proposal (EIP)-721. ERC-721 is considered the default standard for implementing most NFTs.
As with the fungible tokens, first import the code from the OpenZeppelin ERC721Full
contract, as the following code shows:
pragma solidity ^0.5.0;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v2.5.0/contracts/token/ERC721/ERC721Full.sol";
By creating the ArtToken
contract and linking it to the ERC-721 GitHub page, the ArtToken
contract will inherit all the functions that we need to satisfy the requirements of the ERC-721 standard.
Inside the ArtToken
contract, a constructor is defined that calls the ERC721Full
constructor. The full code for the art contract is thus as follows:
pragma solidity ^0.5.0;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v2.5.0/contracts/token/ERC721/ERC721Full.sol";
contract ArtToken is ERC721Full {
constructor() public ERC721Full("ArtToken", "ART") { }
function registerArtwork(address owner, string memory tokenURI)
public
returns (uint256)
{
uint256 tokenId = totalSupply();
_mint(owner, tokenId);
_setTokenURI(tokenId, tokenURI);
return tokenId;
}
}
The registerArtwork
function accepts an address for the artwork owner
and a string named tokenURI
, which will consist of a link to where the digital artwork exists online.
URI, or Uniform Resource Identifier, originated from a World Wide Web initiative to standardize the way that files or objects get encoded for the web. The URI is an identifier of a specific resource, like a page, a document, or a book. This is in contrast to the more familiar URL, which is referred to as a locator, as it includes how to access a resource like HTTPs or FTP. The URL is a subset of the URI.
As with the fungible tokens, the next step involves compiling and deploying the ArtToken
contract by using the Remix IDE, MetaMask, and Ganache.
There is a new step we must undertake as part of the process of creating our dApp. The details of the contract will need to be saved for use by the front end of the application. This is accomplished by copying its application binary interface (ABI) file, found at the bottom of the Compiler page, into a local JSON file.
Navigate to the Deploy & Run Transactions pane and select “Injected Web 3” as the environment. When the MetaMask window opens, select at least two Ganache accounts to make available in Remix. Select the “ArtToken - artwork.sol” contract and click Deploy. Click Confirm when the MetaMask window opens.
For this dApp's front end, we’ll use Streamlit components and the Web3.py library to provide the capability of interaction with the contract, which resides on the blockchain.
Specifically, users will be able to select their accounts and register new artwork tokens through a web interface. Users will also be able to display the tokens for their accounts so that they can display the artwork on the webpage.
Code the front-end application, starting with the required imports:
import os
import json
from web3 import Web3
from pathlib import Path
from dotenv import load_dotenv
import streamlit as st
load_dotenv()
The next step is to define and connect to a new Web3 provider, which, in our dApp, will be the Ganache local blockchain.
WEB3_PROVIDER_URI=http://127.0.0.1:7545
w3 = Web3(Web3.HTTPProvider(os.getenv("WEB3_PROVIDER_URI"))
Combine the above with the Streamlit dApp code below:
@st.cache(allow_output_mutation=True)
def load_contract():
# Load the contract ABI
with open(Path('./contracts/compiled/artwork_abi.json')) as f:
artwork_abi = json.load(f)
# Set the contract address (this is the address of the deployed contract)
contract_address = os.getenv("SMART_CONTRACT_ADDRESS")
# Get the contract
contract = w3.eth.contract(
address=contract_address,
abi=artwork_abi
)
return contract
# Load the contract
contract = load_contract()
###
# Register New Artwork
###
st.title("Register New Artwork")
accounts = w3.eth.accounts
address = st.selectbox("Select Artwork Owner", options=accounts)
artwork_uri = st.text_input("The URI to the artwork")
if st.button("Register Artwork"):
tx_hash = contract.functions.registerArtwork(address, artwork_uri).transact({'from': address, 'gas': 1000000})
receipt = w3.eth.waitForTransactionReceipt(tx_hash)
st.write("Transaction receipt mined:")
st.write(dict(receipt))
st.markdown("---")
###
# Display a Token
###
st.markdown("## Display an Art Token")
selected_address = st.selectbox("Select Account", options=accounts)
tokens = contract.functions.balanceOf(selected_address).call()
st.write(f"This address owns {tokens} tokens")
token_id = st.selectbox("Artwork Tokens", list(range(tokens)))
if st.button("Display"):
# Get the art token owner
owner = contract.functions.ownerOf(token_id).call()
st.write(f"The token is registered to {owner}")
# Get the art token's URI
token_uri = contract.functions.tokenURI(token_id).call()
st.write(f"The tokenURI is {token_uri}")
st.image(token_uri)
The dApp includes a drop-down list for selecting the artwork owner, a box for typing the URI to the artwork, and a Register Artwork button. We can now use our dApp to mint new NFTs to register digital artwork.
If you do not have a URI, use the following: https://www.artic.edu/iiif/2/25c31d8d-21a4-9ea1-1d73-6a2eca4dda7e/full/843,/0/default.jpg
Until next time, here’s a twitter thread summary of this post: