Beyond a JPEG: NFT as a Primary Key
Austin Vance
Posted on January 12, 2022
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;
}
}
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
});
// ...
}
}
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;
}
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.
Posted on January 12, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.