All Articles

dNFTs

ocean-rio

Dynamic NFTs are tokens whose metadata is not fixed, but can change over time; depending on on-chain or external conditions. Multiple use cases can be accomplished. It allows us to represent objects in an evolving matter; particularly useful for gaming, nesting, and modeling real-world assets. For a while it has been a thing hard to accomplish; one common pattern is using oracles to update the metadata, but one has to trust the oracle, and tends to be a less decentralized, and not on-chain solution. Alternatively, on-chain data can be used, but is either limiting or simple, such as block and transaction properties (block.number, block.prevrandao), and tracking complex relationships can be cumbersome and complex.

Recently with the introduction of EIP-6551, we have an easier way to model dynamic NFTs based on their token-bound account (TBA) state. Meaning we can allow the NFT to be aware of its TBA state, and have that reflected on its metadata. As an example we constructed a dNFT that changes color based on a specific ERC20 balance MANA. It is completely on chain metadata, built using SVG and Base64.encode JSON object from solidity as the returned output of the tokenURI method.

    function tokenURI(uint256 tokenId)
        public
        view
        override(ERC721)
        returns (string memory)
    {
        uint256 tokenBalance = this.getTokenManaBalance(tokenId);
        string memory tokenBalanceAsString = _toString(tokenBalance);

        string[5] memory parts;
        parts[0] = '<svg ...  fill="#';
        parts[1] = _getColor(tokenBalance);
        parts[2] = '"/><text x="10" y="20" class="base">Balance: ';
        parts[3] = tokenBalanceAsString;
        parts[4] = '</text></svg>';

        string memory output = string(abi.encodePacked(parts[0], parts[1], parts[2], parts[3], parts[4]));
        string memory json = Base64.encode(bytes(string(abi.encodePacked('{"name": "Dynamic NFT#', _toString(tokenId), '", "balance": "', tokenBalanceAsString, '", "description": "", "image": "data:image/svg+xml;base64,', Base64.encode(bytes(output)), '"}'))));
        output = string(abi.encodePacked('data:application/json;base64,', json));
        return output;
    }

Overall the above code is well understood and it is used in many NFTs whose intention is to show their compromise with decentralization; since every part of the NFT depends on on-chain elements, a good example being rLOOT. The interesting part lies in this method call.

uint256 tokenBalance = this.getTokenManaBalance(tokenId);

This is actually what is making our contract aware ofitselff. Different colors will be expressed, based on the balance the NFT holds in its TBA. This is just one simple example, but one gets an idea of how things could scale easily.

function getTokenManaBalance(uint256 tokenId) 
  external
  view
  returns (uint256)    
{
  address tokenBoundAccount = this.getAccount(tokenId);
  uint256 tokenBalance = ManaToken(manaTokenAddress).balanceOf(tokenBoundAccount);
  return tokenBalance;
}

Full code can be found here.