I wrote this for anyone looking to get a more bottom-up understanding of NFTs and decentralized applications. It’s a summary of what I learned as I went about learning to create an NFT and building a web app around it.

This is a birds-eye-view of all the different parts that go into creating, deploying and working with smart contracts on the Ethereum blockchain, and how you interact with them over the common web to build decentralized applications.

This is not a tutorial on creating a NFT, nor is it an exhaustive account of everything that is available to you when building and deploying smart contracts on Ethereum. It’s just an account of the path I found.

What is an NFT, techically?

An NFT is essentially some data that represents a unique digital asset, like art or a deed to a property, that’s stored on the Ethereum blockchain alongside the wallet address of who (currently) owns it. The wallet address is simply a unique string that represents an entity on Ethereum; wallets hold Ether, the currency of Ethereum.

Most of the work of creating and assigning owership of an NFT is handled by what’s called a smart contract. You can think of a smart contract as some lines of code that can be deployed to and executed on the Ethereum blockchain.

Here’s a very simple smart contract written in Solidity for the Ethereum blockchain. All the contract does is store some data that anyone calling the contract can set and read via the set and get methods respectively.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

contract SimpleStorage {
    uint storedData;

    function set(uint x) public {
        storedData = x;

    function get() public view returns (uint) {
        return storedData;

Smart contracts can do anything really, they are simply computer programs that execute autonomously. But in the context of NFTs, its smart contract lets you mint a token (create a token) with all the necessary details to represent your ownership of it and usually also handles the buying, selling, auctioning and transfer of these digital assets between wallet addresses.

All of the information about a NFT, including the contract used to create it, is stored permanently on the blockchain. Anytime someone wants to take some action, like auctioning off an NFT, they essentially send a message to the contract, which then handles the auction and transfer.

There really are no limits to what you can make a smart contract do, it all depends on what it’s written to do. This is why we have ERC-721.


ERC-721 is a standard for NFTs, meaning that it implements a common set of methods (actions) for the NFTs created by the contract. Things like functionality for issuing and transferring tokens, checking account balance and validating ownership of tokens are all standardized by ERC 721 such that contracts using the same standard can interoperate with each other. This makes it accessable to other developers, who can build marketplaces and contracts that interact with your contracts and tokens.

Here’s the ERC721 interface, if you implement all these methods and events it can be called an ERC-721 Non-Fungible Token Contract.

If you give this a read it’s fairly easy to understand that a contract is simply a set of methods that let you keep track of who owns what and some helpers to move value around.

pragma solidity ^0.4.20;

/// @title ERC-721 Non-Fungible Token Standard
/// @dev See https://eips.ethereum.org/EIPS/eip-721
interface ERC721 /* is ERC165 */ {
    event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
    event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);
    event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);

    function balanceOf(address _owner) external view returns (uint256);
    function ownerOf(uint256 _tokenId) external view returns (address);
    function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;
    function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;
    function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
    function approve(address _approved, uint256 _tokenId) external payable;
    function setApprovalForAll(address _operator, bool _approved) external;
    function getApproved(uint256 _tokenId) external view returns (address);
    function isApprovedForAll(address _owner, address _operator) external view returns (bool);

There are many standards that exists for smart contracts. Ones like ERC-20 for fungible tokens (aka coins) and ERC-721 for non-fungible tokens (aka unique tokens for assets) are just the most common. They just implement most of the common functionality you need and you’re free to modify and extend them to fit your use.


Web2 is the internet we know, love and hate today. An internet run on top of web servers paid for by companies and organizations that provide services, some in exhange for money, others for your data.

Web3 on the other hand refers to the decentralized internet. The internet that runs on the blockchain where anyone can participate and no one is denied service or blocked becuase control is fundamentally decentralized.

Things to keep in mind

Coming from a web 2.0 world, I had a lot of expectations about building on the Ethereum ecosystem that carried over from what we have come to expect with web 2.0. Little things like being about the update your applications, to fix bugs or add features, or testing to make sure your contract runs properly. The immutability of blockchains force you to think about these things a little differently.

Testing (Mainnet and Testnets)

If you’ve rummaged around deploying a smart contract you’ve likely come across words like Mainnet and Testnet. This is just a side-effect of Ethereum being a protocol; so essentially anyone can implement it to create their own network. However the network in this case is only as useful as what’s in it and who uses it.

In the case of Ethereum the Mainnet is the the primary network; it is the public, production-ready blockchain of Ethereum. Alongside this mainnet, there exists other Testnets; which essentially implement the same protocol, but let you test your contracts at a fraction of the cost of using the Mainnet, and without the risk.

It’s generally important to test any contract code you write on a testnet before deploying to the mainnet.

Gas costs

Whenever you want to execute a contract, maybe to buy a token or sell a token you own, you take some action on the contract in the form of a transaction that you initiate. These transactions update the state of a contract on the Ethereum network. There’s a computational and storage costs associated with all this, and you pay for it using Gas.

Technically, Gas refers to the unit that measures the amount of computational effort required to execute specific operations on the Ethereum network. Gas is paid for in Ethereum’s native currency, ether (ETH). Gas fees are generally paid for by the user initiating the transaction, this is usually what you want, any other implementations, like having someone else pay for the transaction, are not as straightforward since it’s not natively supported.

When you pay Gas to create a transaction, you are paying for the computational energy needed to validate and store the transaction on Ethereum.

Gas prices have been pretty steep as of late. When I tested a simple NFT contract it cost me about $30 in gas fees to mint a new token. Gas prices fluctuate depending on the demand and supply of computational power available to the network and who’s willing to pay what.

The Ethereum 1.0 network is a proof-of-work system, which requires more computational resources to validate and store transactions. This also means higher Gas per transaction. Once Ethereum 2.0 rolls out, implementing proof-of-stake to the blockchain, gas prices are expected to go down with time. However Ethereum 2.0 is yet to support smart-contracts, as of now all that is actively in the works and if you want to learn more about Ethereum 2.0 read more at https://ethereum.org/en/eth2.


Once you deploy a contract there’s no updating it. You could publish a new contract and ask that everyone uses it, but your program as-is, once published, will be forever stored and available on the blockchain.

This is the reason there’s a lot of focus put into testing contracts and putting other practices in place to afford you some upgradibility without introducing security risks or taking away from the decentralized aspect of smart contracts.

The most common of these is the proxy pattern; which is essentially a contract you deploy, which points to the address of the contract that contains the actual logic. It’s fairly safe to setup a way for these pointers to be updated by a trusted address if you need to update your contracts for some reason.

There’s still a lot of caveats to watch for when doing this, so please do your own thorough due-diligence when it comes to writing and deploying contracts. What you’re looking for here is a good balance of having the ability to fix bugs and improve code without breaking the trust created by decentralized contracts; think - if someone could change the core logic of a contract on the fly, why would anyone trust it?

Decentralized applications (DApps)

The simplest stack for a DApp includes the front-end application, which can connect to the Ethereum network via an RPC interface using a library like web3.js (noted below). Via this connection, your application can now interact with any smart contract on the Ethereum network that requires a user’s wallet.

Contract; credits Image credits: robertgreenfieldiv.medium.com

This was the part I had most questions about at first, how do you connect to the blockchain and what are the best practices? How do I let users perform actions on the blockchain via a web application?

All that functionality is wrapped into a few core libraries like web3.js and other tooling that have then been built on top of them.

  • Web3.js is the collection of javascript libraries that let you interact with Ethereum nodes over HTTP and IPC. It can be used to fetch contracts, view accounts and etc. Think of this as a gateway into the decentralized web when you want to build front-end applications for blockchain applications.
  • Drizzle a collection of UI libraries that can be used to interact with your smart contracts in Plain JavaScript, React and Vue. Drizzle uses web3.js under-the-hood.

I won’t dive too deep into any of this since these libraries are well documented and plenty of examples exist. However, I’ll show you how you log a user into your app with their Ethereum wallet using just vanilla Javascript. It’s mostly here to illustrate that it’s not all that complicated.

window.addEventListener('load', async () => {
    // Modern dapp browsers...
    if (window.ethereum) {
        window.web3 = new Web3(ethereum);
        try {
            // Request account access if needed
            await ethereum.enable();
            // Acccounts now exposed
            web3.eth.sendTransaction({/* ... */});
        } catch (error) {
            // User denied account access...
    // Legacy dapp browsers...
    else if (window.web3) {
        window.web3 = new Web3(web3.currentProvider);
        // Acccounts always exposed
        web3.eth.sendTransaction({/* ... */});
    // Non-dapp browsers...
    else {
        console.log('Non-Ethereum browser detected. You should consider trying MetaMask!');

For a user to access your DApp they need to first have a supported wallet provider like Metamask installed on their browser. Metamask is essentially an Ethereum wallet and our gateway to the blockchain world in this context.

The jist of it is that Metamask will inject a window.ethereum object into the browser window. Which your application can use to access a users Ethereum account, which then allows you to perform transactions using their address.

Other useful resources and tools

This is not an exhaustive list, but these few pieces gave me a good idea of common best practices and what exists to learn from.

  • Solidity is an object-oriented programming language for writing smart contracts. It is at present also the primary language for working with Ethereum. It’s best to atleast have a cursory understanding of Solidity before you dive in any deeper. I found the guides at https://cryptozombies.io very useful for getting upto speed on the basics.
  • Truffle a development environment and testing framework for Ethereum that can help you test and deploy smart contracts.
  • Openzeppelin contracts provieds a lot of standardized contracts that you can use to build your own. This is a great resource for learning about how to write secure contracts and highlights a lot of the common pitfalls.
  • This article by Consensys covers an exhaustive list of tools and resources for Ethereum developers: https://media.consensys.net/an-definitive-list-of-ethereum-developer-tools-2159ce865974


As I mentioned at the beginning, this post is essentially a compilation of what I learned about the Ethereum ecosystem in the process of writing a smart contract. These were some of the unanswered questions I had in mind as I approached it with a cursory understanding of blockchains and ethereum.

I’ll continue to update this as I learn more and hopefully it helped you answer some of your own questions or at the very least made you ask a few more.