DEV Community

Cover image for Beyond a JPEG: NFT as a Primary Key
Austin Vance for Focused

Posted on • Originally published at focusedlabs.io

5 2

Beyond a JPEG: NFT as a Primary Key

These days, we most often see NFTs used to record the ownership information of artwork. There are the pie-in-the-sky folks who think NFTs will become a new standard for ownership for everything from houses, to music, to in-game items - only time will tell.

NFTs are powerful, but they ​get a bad rap. I don't have strong opinions about the disruptive nature of NFTs or how they affect art, games, or anything else. I do see that the "Non-Fungible" attribute of an NFT has some remarkable properties that allow smart contract developers to store state in a permissionless manner and enable that state to be transferred or sold.

Ok, I admit that's rather abstract and confusing. Since this is a dev blog, let's build an app that uses an NFT contract and holds information specific to a user.

Before we dive in, this is not revolutionary. Several projects already do this. The QiDAO uses NFTs to manage loan ownership and Uniswap V3 uses NFTs to manage a Liquidity Position. Before the release of V3, Uniswap recorded Liquidity by issuing the liquidity provider an LP Token. These tokens are an ERC-20 token, and ERC-20's are fungible. Fungibility means that one token is the same as another. Think if someone hands you a 1€ note. That note is the same as the other 1€'s in your wallet. The ERC-20's are 100% transferable, farmable, and sellable, but they cannot store any secondary information about a Liquidity Position.

NFTs allow for encapsulation and transfer and sale of metadata in a permissionless manner

Uniswap V3 enables the addition of metadata to a liquidity position. Instead of issuing an ERC-20 token to represent a liquidity position, they issue an ERC-721 (NFT). With that ERC-721, Uniswap can now add unique features (like impermanent loss protection) to each liquidity position.

How cool is that! But how does it work? Let's build it.


Without giving too much background, at Focused Labs, we offer a blog bounty and a bonus for blogging streaks. The more frequently the company blogs, the larger the blog bounty becomes.

I want to move this bounty from a spreadsheet to the blockchain and use an NFT to identify wallets contributing a blog to the streak.

We will need an ERC-721 contract. Let's use OpenZeppelin to enforce the correct interface for our NFT.

// contracts/FocusedBlogPost.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

contract FocusedBlogPost is ERC721 {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    const 
    constructor() ERC721("FocusedBlogPost", "FCSD") {}

    function publishBlog(address blogger)
        public
        returns (uint256)
    {
        _tokenIds.increment();

        uint256 newItemId = _tokenIds.current();
        _mint(blogger, newItemId);

        return newItemId;
    }
}
Enter fullscreen mode Exit fullscreen mode

Whenever we call publishBlog() with an address, we will mint and transfer an NFT to the blogger.

A new blog must be published every two weeks to earn a streak. Each week at least one new blog post goes out our streak counter increases. This streak counter is a multiple on our Blog Bounty! Now let’s set up our contract to add logic around a streak.

Let's start by tracking when new posts are published.

contract FocusedBlogPost is ERC721 {
    // ...

    struct BlogPost {
        string postUri;
        uint256 publishedAt;
        address originalAuthor;
    }

    // ...

    // two weeks in seconds
    uint constant twoWeeks = 60 * 60 * 24 * 14;

    // map of NFT ids to Blog Posts
    mapping(uint256 => BlogPost) public blogPosts;

    function publishBlog(address blogger, string memory postUri)
    public
    returns (uint256)
    {
        // ...
        _mint(player, newPostId);

        blogPosts[newPostId] = BlogPost({
            postUri: postUri,
            publishedAt: block.timestamp,
            originalAuthor: blogger
        });

        // ...
    }
}
Enter fullscreen mode Exit fullscreen mode

Now every time a new blog post is published, we not only create an NFT for the post, but we record when it was published and keep track of the original author.

We still don't track a streak, though, so let's add a new method to our token contract.

contract FocusedBlogPost is ERC721Enumerable {
    using SafeMath for uint256;

    // ...

     function getCurrentStreak() public view returns (uint) {
        uint streak = 0;
        if (totalSupply() == 0 || totalSupply() == 1) {
            return streak;
        }

        for (uint256 i = totalSupply().sub(1); i > 0; i--) {
            BlogPost memory currentBlog = blogPosts[tokenByIndex(i)];
            BlogPost memory previousBlog = blogPosts[tokenByIndex(i).sub(1)];

            if (currentBlog.publishedAt - previousBlog.publishedAt >= twoWeeks) {
                break;
            }

            streak++;
        }
        return streak;
    }
Enter fullscreen mode Exit fullscreen mode

We use OpenZeppelin's ERC721Enumerable; this gives us a few new methods to loop through each NFT we have minted. Then we can check the timestamp of each BlogPosts.publishedAt. Pretty easy, right?!

In a future part of this series, we will continue to add features to the NFT, allowing for payouts to streak contributors and adding validations like only increasing a streak if the author isn't in the current streak.


Although this example is a bit contrived, honestly, why would someone want to transfer their streak? I think the practical applications are straightforward.

The NFT can act as a unique identifier, recording metadata about an event or individual actions. A dApp can then use that NFT to make decisions like providing access to a "secret" website or paying a dividend for contributing a blogpost.

Source code is available on GitHub where there is a complete example with passing tests.

Image of Timescale

Timescale – the developer's data platform for modern apps, built on PostgreSQL

Timescale Cloud is PostgreSQL optimized for speed, scale, and performance. Over 3 million IoT, AI, crypto, and dev tool apps are powered by Timescale. Try it free today! No credit card required.

Try free

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Explore a sea of insights with this enlightening post, highly esteemed within the nurturing DEV Community. Coders of all stripes are invited to participate and contribute to our shared knowledge.

Expressing gratitude with a simple "thank you" can make a big impact. Leave your thanks in the comments!

On DEV, exchanging ideas smooths our way and strengthens our community bonds. Found this useful? A quick note of thanks to the author can mean a lot.

Okay