Building OnChainNFT: A Simple Guide to On-Chain SVG NFTs

Muhammed MusaMuhammed Musa
5 min read

Creating NFTs with on-chain artwork is a powerful way to ensure the permanence and integrity of digital collectibles. This article walks through a simple implementation of an on-chain SVG NFT contract called "OnChainNFT" and explains the key concepts in easy-to-understand terms.

What Makes On-Chain NFTs Special?

Traditional NFTs typically store their artwork on IPFS or centralized servers, with only a reference to that location stored on the blockchain. This creates potential problems:

  • If the server goes down, the artwork could be lost

  • The referenced content could be changed

  • The link between the token and artwork might break

On-chain NFTs solve these problems by storing the artwork directly on the blockchain, creating a truly permanent and immutable digital asset.

Understanding the OnChainNFT Contract

Our OnChainNFT contract is a minimal implementation that demonstrates the core concepts of on-chain SVG storage. Let's break it down:

Contract Setup

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/Base64.sol";

contract OnChainNFT is ERC721, Ownable {
    using Strings for uint256;

    uint256 public price = 0.01 ether;
    uint256 public totalSupply = 0;
    uint256 public maxSupply = 100;

    // Color choices for the NFTs
    string[] public colors = ["red", "blue", "green", "purple", "orange"];

    // Mapping from token ID to color index
    mapping(uint256 => uint256) public tokenColors;

    constructor() ERC721("OnChainNFT", "OCNFT") Ownable(msg.sender) {}

The contract:

  • Inherits from ERC721 (the NFT standard) and Ownable

  • Sets a mint price of 0.01 ETH

  • Caps the collection at 100 NFTs

  • Defines an array of color options

  • Creates a mapping to track which color is assigned to each token

Minting Function

function mint(uint256 colorIndex) external payable {
    require(totalSupply < maxSupply, "Max supply reached");
    require(msg.value >= price, "Insufficient ETH sent");
    require(colorIndex < colors.length, "Invalid color index");

    totalSupply++;
    uint256 tokenId = totalSupply;

    tokenColors[tokenId] = colorIndex;
    _mint(msg.sender, tokenId);
}

The mint function:

  • Verifies we haven't hit the maximum supply

  • Checks that enough ETH was sent

  • Validates the color index is within range

  • Increments the supply counter

  • Stores the chosen color index

  • Mints the token to the sender

SVG Generation

function generateSVG(uint256 tokenId) internal view returns (string memory) {
    string memory color = colors[tokenColors[tokenId]];

    return string(abi.encodePacked(
        '<svg xmlns="http://www.w3.org/2000/svg" width="300" height="300" viewBox="0 0 300 300">',
        '<rect width="100%" height="100%" fill="white" />',
        '<circle cx="150" cy="150" r="120" fill="', color, '" />',
        '<text x="150" y="150" font-family="Arial" font-size="20" text-anchor="middle" fill="white">',
        'OnChainNFT #', tokenId.toString(),
        '</text>',
        '</svg>'
    ));
}

This function:

  • Gets the color for the specific token

  • Creates an SVG image with:

    • A white background

    • A colored circle in the middle

    • Text showing the NFT number

  • Uses string concatenation via abi.encodePacked

The SVG is simple but effective - a colored circle with the token number displayed in the center.

The On-Chain Magic: tokenURI

function tokenURI(uint256 tokenId) public view override returns (string memory) {
    require(_exists(tokenId), "Token does not exist");

    string memory svg = generateSVG(tokenId);
    string memory svgBase64 = Base64.encode(bytes(svg));

    string memory json = string(abi.encodePacked(
        '{',
            '"name": "OnChainNFT #', tokenId.toString(), '",',
            '"description": "A simple SVG NFT stored 100% on-chain",',
            '"attributes": [{"trait_type": "Color", "value": "', colors[tokenColors[tokenId]], '"}],',
            '"image": "data:image/svg+xml;base64,', svgBase64, '"',
        '}'
    ));

    string memory jsonBase64 = Base64.encode(bytes(json));

    return string(abi.encodePacked('data:application/json;base64,', jsonBase64));
}

The tokenURI function is where the on-chain storage happens:

  1. It generates the SVG image

  2. Encodes the SVG as Base64

  3. Creates a JSON metadata object with:

    • Name: "OnChainNFT #[tokenId]"

    • Description

    • Attributes (color)

    • Image data URI with the Base64-encoded SVG

  4. Encodes the entire JSON as Base64

  5. Returns everything as a data URI

This approach stores both the image and metadata directly on the blockchain, making the NFT completely self-contained and immune to link rot.

How Data URIs Work

The key to on-chain NFTs is using data URIs, which embed the content directly in the URI itself. The format is:

data:[<mediatype>][;base64],<data>

For example, the SVG is embedded as:

data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDov...

And the entire metadata as:

data:application/json;base64,eyJuYW1lIjoiT25DaGFpbk...

NFT marketplaces and wallets can decode these URIs to display the image and metadata without needing to access any external servers.

Deployment Steps

To deploy your own OnChainNFT contract:

  1. Set Up Your Environment

     npm init -y
     npm install @openzeppelin/contracts hardhat
     npx hardhat init
    
  2. Place Contract in Contracts Folder Save the contract code in a file named OnChainNFT.sol in your project's contracts directory.

  3. Deploy to a Test Network

     npx hardhat run scripts/deploy.js --network rinkeby
    
  4. Verify Your Contract Once deployed, verify your contract on Etherscan for transparency and easy interaction.

Cost Considerations

Storing data on-chain is gas-intensive. This simple implementation works well for basic SVGs, but for more complex artwork, consider:

  • Optimizing SVG code to be as compact as possible

  • Using layer 2 solutions like Polygon for lower gas fees

  • Creating algorithmic generation patterns that require minimal storage

Extending the Contract

This basic implementation can be extended in many ways:

  1. Random Generation: Add randomness to create unique variations

  2. Multiple Elements: Build SVGs with multiple components or layers

  3. Animation: Include SVG animations for dynamic NFTs

  4. Interactive Elements: Create SVGs that change based on blockchain data

  5. Upgradable Art: Allow NFTs to evolve over time

Conclusion

On-chain SVG NFTs represent the gold standard of digital ownership. By storing artwork directly on the blockchain, they solve the permanence problems of traditional NFTs and create truly self-contained digital assets.

The simple OnChainNFT contract we've explored demonstrates the core principles of on-chain storage without unnecessary complexity. From this foundation, you can build more advanced implementations while maintaining the key benefit: complete on-chain storage.

Whether you're a creator looking to ensure the permanence of your digital art or a collector seeking truly immutable digital assets, on-chain SVG NFTs offer a compelling solution that aligns with the core promise of blockchain technology.

0
Subscribe to my newsletter

Read articles from Muhammed Musa directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Muhammed Musa
Muhammed Musa

From optimizing search results to building the future of the web - that's my journey. I'm Muhammed Musa, an SEO specialist with 5 years of experience, now venturing into the exciting realms of full-stack development and blockchain technology. I aim to blend my SEO expertise with cutting-edge web development and blockchain skills to create innovative, discoverable, decentralized solutions. I'm passionate about staying at the forefront of digital technology and eager to contribute to projects that push the boundaries of what's possible on the web.