Dynamic Renderer

Renderer Contract

The Renderer is a completely customizable contract that can have any rendering logic. It only needs to integrate our IDynamicUriRenderer interface, which provides the render function:

function render(address subject, string calldata plainUri, bytes calldata inputData, address caller, bytes calldata rendererData) external view returns (string memory)

render is called by the resolve function of the Resolver; at least one render function must be provided in the Renderer contract.

As input, render takes the same parameters of the resolve function, but the logic of the contract is completely customizable.

A developers can write his own Renderer contract to retrieve and render any info from the blockchain.

Renderer Contract Example

Let’s take a look at an example of a Renderer contract.

pragma solidity >=0.7.0;

import "@ethereansos/swissknife/contracts/dynamicMetadata/model/IDynamicUriRenderer.sol";
import "../contracts/model/IItemMainInterface.sol";

import { AddressUtilities, Uint256Utilities, BytesUtilities, Bytes32Utilities } from "@ethereansos/swissknife/contracts/lib/GeneralUtilities.sol";

contract MyUriRenderer is IDynamicUriRenderer {
    using AddressUtilities for address;
    using Uint256Utilities for uint256;
    using Bytes32Utilities for bytes32;
    using BytesUtilities for bytes;

    function render(address subject, string calldata plainUri, bytes calldata inputData, address caller, bytes calldata rendererData) external override view returns (string memory) {
        return string(abi.encodePacked(
            _1(subject, plainUri, inputData, caller, rendererData),
            _2(subject, plainUri, inputData, caller, rendererData),
            _renderRendererData(rendererData),
            _renderInputData(subject, caller, inputData)
        ));
    }

    function _1(address, string calldata, bytes calldata, address, bytes calldata) private view returns (string memory) {
        return string(abi.encodePacked(
            "Greetings from the Dynamic Uri Renderer Contract\n\n",
            address(this).toString(),
            ",\n\n directly called by the Dynamic Uri Resolver Contract\n\n",
            msg.sender.toString(),
            ".\n\n"
        ));
    }

    function _2(address subject, string calldata plainUri, bytes calldata, address caller, bytes calldata) private pure returns (string memory) {
        return string(abi.encodePacked(
            "This message has been automatically generated when your address\n\n",
            caller.toString(),
            '\n\ncalled the contract\n\n',
            subject.toString(),
            '\n\npassing the uri\n\n"',
            plainUri,
            '"\n\nand it is pretty fun to see what a so simple string can generate!\n'
        ));
    }

    function _renderInputData(address subject, address caller, bytes calldata inputData) private view returns(string memory) {
        if(inputData.length == 0) {
            return "\n";
        }
        (bytes32 collectionId, uint256 itemId) = abi.decode(inputData, (bytes32, uint256));
        if(collectionId == bytes32(0)) {
            uint256 balance = IItemMainInterface(subject).balanceOf(caller, itemId);
            balance /= (10**IItemMainInterface(subject).decimals(itemId));
            return string(abi.encodePacked(
                "You are watching the metadata of the item #:\n",
                itemId.toString(),
                "\nand the caller balance is:\n",
                balance.toString(),
                ".\n"
            ));
        }
        (address host, string memory name, string memory symbol,) = IItemMainInterface(subject).collection(collectionId);
        return string(abi.encodePacked(
            "You are watching the metadata of the collection with Id:\n\n",
            collectionId.toString(),
            ",\n\nHaving host:\n",
            host.toString(),
            ',\nname:\n',
            name,
            '\n and symbol:\n',
            symbol,
            '.\n'
        ));
    }

    function _renderRendererData(bytes calldata rendererData) private pure returns(string memory) {
        if(rendererData.length == 0) {
            return "\n";
        }
        return string(abi.encodePacked(
            'The optional fixed data you can attach to every Uri, in this case is a string containing the sentence:\n\n"',
            rendererData.asString(),
            '".\n\n'
        ));
    }

MyUriRenderer’, the example contract, implements the standard IDynamicUriRenderer interface, which (as noted above) provides the render function. render’s logic allows it to generate non-standard complex metadata in the form of a rendered string.

MyUriRenderer provides four functions, called internally by render, each generating different metadata. All of the metadata are encoded using the abi.encodedPacked in the rendered uri string.

As you can see in the example, the _1, _2, _renderInputData and _renderRendererData functions regenerate the Item’s metadata 100% on-chain ‘on the fly’.

This is the power of dynamic on-chain metadata: every time the Renderer is called, it regenerates the Item’s metadata, taking the info directly from the chain.

For example, _renderInputData retrieves, from the bytes calldata inputData, the Item’s collectionId and itemId, and calculates the balance of the caller address.

After that render call, the balance of the caller might change. If so, when the caller calls render again, the new balance will be dynamically regenerated, 100% on-chain.

_renderRendererdata takes care of generating the metadata as per the optional payload encoded in the plainUri string.

Because the Renderer is completely customizable, a payload can also hypothetically contain utility code to customize the rendering, such as hard-cabled values.

Last updated