DEV Community

Cover image for Create an NFT as a Developer: Deep Dive into Smart Contracts
Scofield Idehen
Scofield Idehen

Posted on • Originally published at blog.learnhub.africa

Create an NFT as a Developer: Deep Dive into Smart Contracts

Why NFTs?

First, I do not believe NFTs are a big deal or critical to the future of web3. yes, I said so, but I am a programmer. I love coding and spending time on Patrick's course; I came across NFTs and learned a lot about them.

Does that mean I have changed my mind that NFTs are just a bunch of overhyped arts or whatever with no direct impact towards a sustainable future?

Absolutely Yes!.

NFTs (non-fungible tokens) have exploded in popularity recently as a way to represent ownership of unique digital assets. But why exactly are NFTs taking off?

NFTs provide a way to prove scarcity and ownership in the digital world. Unlike most digital content, which can be freely copied and distributed, each NFT is unique and not interchangeable.

This makes them similar to physical assets like artwork or collectibles. Just as you can prove you own an original Picasso, you can prove an NFT tied to a specific digital asset.

For creators, NFTs provide a new stream of revenue and a way to connect with fans. By selling limited edition NFTs, creators can monetize their work without relying solely on advertising or subscriptions. Fans also benefit from getting special access, content, or status by owning their favorite creators' NFTs.

NFTs introduce transparency and traceability into digital ownership. All NFT transactions are recorded on the blockchain ledger permanently. This prevents fakes or fraud while allowing owners to trace an NFT's full ownership history.

Finally, NFTs enable new types of digital art, content, and experiences. Without NFTs, most digital content lacks scarcity and authenticity. But NFTs allow the creation of provably rare digital goods, ushering in new trends like NFT art and virtual fashion.

Smart Contracts for NFTs

Most NFTs are issued and traded through smart contracts deployed on blockchains like Ethereum. A smart contract is a code that runs on a chain and controls logic like NFT issuance, ownership transfers, listings, bids, and more.

Writing smart contracts is a key skill for developing and understanding NFT projects. We can demystify how NFT mechanics work under the hood by diving into real NFT smart contract code.

We'll walk step-by-step through a basic NFT smart contract written in the Solidity language. We'll deploy it on the Ethereum testnet to mint some test NFTs for free. You'll have hands-on experience deploying an NFT smart contract from scratch by the end!

OpenZeppelin Contracts

We'll utilize some helpful building blocks from OpenZeppelin Contracts for our contract. This is a library of secure, community-audited smart contracts for Ethereum and other blockchains.

As you can imagine, writing smart contracts from scratch introduces risks like security vulnerabilities and subtle bugs. By using community code that's heavily tested and audited, we can stand on the shoulders of giants.

Specifically, we'll inherit from OpenZeppelin's implementation of the ERC-721 standard. This standard defines a set of functions all NFT contracts should implement, like mint, transfer, and ownerOf.

By inheriting from a standard base, our NFTs will be compatible with external apps and marketplaces looking for contracts following ERC-721.

Here's how our contract import looks:

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

We also have to pass this contract as the parent contract in our inheritance:

contract myNFT is ERC721 {
  // Our contract code
}
Enter fullscreen mode Exit fullscreen mode

This contract inheritance allows us to focus on our custom NFT behavior rather than reimplementing NFT basics.

NFT Contract Walkthrough

Below, I'll walk through our contract section-by-section, explaining how each piece works:

// Contract name
contract myNFT is ERC721 {
}

First we define our contract name as myNFT and inherit from ERC721.

Next, we define some contract state variables:

uint private s_tokenCounter; // Internal counter for NFTs minted
mapping(uint256 => string) private s_tokenURIs; // Map NFT ids to associated URI
Enter fullscreen mode Exit fullscreen mode

Here, we define a unit counter to track how many NFTs have been minted. This will be the unique identifier for each NFT.

We also define a mapping from the uint ID to a string URI. This will point out our NFT resources like images, metadata, etc.

In the contract constructor, we initialize the counters:

 constructor() ERC721("UselessCoin", "ULC") {
      s_tokenCounter = 0;
  }
Enter fullscreen mode Exit fullscreen mode

Note we call the ERC721 constructor with a name and symbol for our NFT collection.

Then, we set our counter to 0.

Next, we add our main NFT minting function:

function mintNFT(string memory tokenURI) public {
      _safeMint(msg.sender, s_tokenCounter);
      s_tokenURIs[s_tokenCounter] = tokenURI;
      s_tokenCounter = s_tokenCounter + 1;
  }
Enter fullscreen mode Exit fullscreen mode

This takes in a tokenURI string pointing to the NFT resource.

It uses OpenZeppelin's _safeMint function to mint an NFT to msg.sender (the function caller).

We increment s_tokenCounter to set the new NFT's unique ID.

Finally, we save the resource tokenURI mapping to this ID.

The tokenURI external apps use a method to fetch the resource for a particular NFT ID:

function tokenURI(uint256 tokenId) public view override returns (string memory) {
    return s_tokenURIs[tokenId];
  }
Enter fullscreen mode Exit fullscreen mode

We implement the required tokenURI function to return the URI simply mapped to the passed tokenId.

And that covers the basics of our NFT smart contract!

While simplified, this demonstrates:

  • Using OpenZeppelin contracts for quicker development
  • Inheriting from the ERC721 standard
  • Minting unique NFTs with IDs tracked by a counter
  • Mapping tokenIds to off-chain resource URIs
  • Overriding the tokenURI function to relay URI data

We could make improvements, like access controls, error handling, automating URI creation, etc. But this minimal contract already implements core NFT behavior and standards under the hood!

Deploying and Testing Our NFT Contract

We'll use Foundry - a modular toolkit for Ethereum application development, to test and deploy our contract.

Foundry provides excellent support for testing, scripting, and deploying Solidity smart contracts.

First, our deployment script deploy.s.sol:

//SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import {Script} from "forge-std/script.sol";
import {moodNFT} from "../src/moodNft.sol";
contract DeployNFT is Script{
    function run() external returns(moodNFT){
        vm.startBroadcast();
        moodNFT moodnft = new moodNFT();
        vm.stopBroadcast();
        return moodnft;
    }
}
Enter fullscreen mode Exit fullscreen mode

This handles deploying an instance of our myNFT contract and broadcasting it onto the blockchain.

Next, our simple test suite test.t.sol:

//SPDX-License-Identifier: MIT

pragma solidity ^0.8.18;

import {Test} from "forge-std/test.sol";

import {myNFT} from "../src/myNFT.sol";

import {DeployNFT} from "../script/DeployNFr.s.sol";

contract BasicNFTTest is Test{

    address public USER = makeAddr("user");

    string public constant PUG = 

        "ipfs://bafybeig37ioir76s7mg5oobetncojcm3c3hxasyd4rvid4jqhy4gkaheg4/?filename=0-PUG.json";

    DeployNFT public deployer;

    myNFT public mynft;

    function setUp() public {

        deployer = new DeployNFT();

        mynft = deployer.run();

    }

    function testnameiscorrect() public view {

        string memory expectedname = "DogieSco";

        string memory actualname = mynft.name();

        assert(keccak256(abi.encodePacked(expectedname)) == keccak256(abi.encodePacked(actualname)));

       

    }

    function testcanMintandBal() public {

        vm.prank(USER);

        mynft.mintNFT(PUG);

        assert(mynft.balanceOf(USER) == 1);

        assert(keccak256(abi.encodePacked(PUG)) == keccak256(abi.encodePacked(mynft.tokenURI(0))));

    }

}
Enter fullscreen mode Exit fullscreen mode

Here, we validate the contract's name variable and test out minting an NFT.

We can run the full test flow with:

forge test

This handles deploying fresh contract instances for our test cases, checking expected behavior and revert conditions, gas usage, and more.

Ensure all your test functions start with test as forge test will not be able to pick it up.

Foundry provides excellent tooling for testing, scripting, and deploying smart contracts from development through production. As we scale up our NFT project, Foundry will help rapidly iterate and confidently ship across environments.

Why Smart Contracts Are Critical for NFTs

While NFT basics are simple in abstract (tokenized ownership records on a blockchain), smart contracts power NFTs under the hood.

Smart contracts formally encode business logic like ownership, transfers, limitations, royalties, etc. Everything from minting and burning NFTs to facilitating trades on a marketplace happens via executing contract code.

Upgrades, migrations, governance decisions, and more are enacted by shipping updated smart contracts. Things like transaction history and off-chain data availability are also managed in contract storage.

By learning smart contract development with real NFT code, as we've covered here, you gain quite deep insight into the inner workings of NFT projects:

  • Core mechanics like minting, ownership records
  • Upgradeability patterns and governance capabilities
  • Security protections and risk management
  • Standards compatibility and interoperability

Plus, low-level understanding improves security posture as both builder and trader.

As NFTs bridge more industries, from art to finance to Web3 identity, understanding smart contracts becomes more critical for builders and traders.

So, while buying and selling NFTs is accessible to anyone with a wallet, smart contract literacy is invaluable to understanding this new digital asset paradigm.

Conclusion

We've covered quite a lot of ground here! To recap:

  • We discussed the value proposition and emergence of NFTs
  • Provided background on smart contracts and inheritance
  • Walked through an NFT contract section-by-section
  • Deployed our contract with Foundry.
  • Discussed the critical role contracts play in NFT mechanics

While simplified, this demonstrates core NFT behavior like issuance, token tracking, metadata lookups, etc. We also used tooling like OpenZeppelin contracts for speedier and safer development.

There's always more to learn, but being able to read, write, and deploy real smart contract code unlocks a deeper understanding of the NFT space.

If you find this article thrilling, discover extra thrilling posts like this on Learnhub Blog; we write a lot of tech-related topics from Cloud computing to Frontend Dev, Cybersecurity, AI, and Blockchain. Take a look at How to Build Offline Web Applications. 

Resource

Gift us a cup of coffee if you are so kind. 

Top comments (0)