ZkRoninsMeta64.sol

Testing phase

This contract, while fundamentally being done, is still being tested by us. We will hold back with its deployment as to not scatter the focus and rush into the next phase too soon.

ZkRoninsMeta64.sol is the main Base64 metadata contract in the META64 module and contains all the logic for enabling, storing and retrieving Base64 encrypted token URIs. This will make it easier to change metadata, for here we construct the (majority of the) JSON scheme on-chain.

With the non-Base64 URI methods we simply point towards an URI that's stored someplace else and therefore making it impossible to change the metadata without having to re-upload a new JSON file. With Base64 changes can be made directly on-chain and it would reflect immediately in the URI.

A case study

Let's give an example of the power behind leveraging the Base64 format.

The following code is a simplified version of the return statement in our current ZkRoninsMeta64.sol contract:

return string.concat(
    'data:application/json;base64,',
    Base64.encode(
        bytes(string.concat(
            '{"name":"', namePrefix, tokenId.toString(), " - ", name,
            '","description":"', description,
            '","image":"', image,
            '","animation_url":"', animation_url,
            '","attributes":[', attributes, ']}'
        )
    ))
);

Normally, metadata would be returned as a simple URI (https://example.com/2.json). But here we create one that is based on values stored inside of the contract itself (variables like namePrefix, tokenId, name, image, animation_url, attributes and description represent these).

With this method we could for instance change the value of namePrefix from zkRonin # to Ronin which would instantly change the name format for all the ronins on-chain from zkRonin #10 - Hira Kyoshi to Ronin 10 - Hira Kyoshi.

This simple change would have been costly with non-Base64 metadata as it can only be done by changing all the .json files, re-uploading them and finally re-linking them to the contract. Whereas here we change only one variable with a single transaction on-chain.


This example may seem irrelevant, but it showcases the sturdiness of non-Base64 URI versus the flexibility of Base64 URI metadata.

Fields

Name, description, image, animation_url, attributes or individual traits are all Fields we can use to describe and create a ronin. In our code we've made it possible to add and remove Fields, which is where the dynamic nature of the project really comes into play. Because now we can introduce new traits to a character based on whatever mechanism we choose to create. Like e.g. a Slot Counter, Revision Amount or Royalty Bonuses allowing a holder to gain a percentage of made secondary sales (see Royalty Collector for this very early-stage concept).

Breakdown

  • Administration

    • getField returns the value of a field for a specific token ID, slot and fieldName.

    • getTraits returns a list of all the available traits. These are basically Fields that have been labeled attributes.

    • setField sets the value for a field for a specific token ID and slot combination. Can only be called by the admin.

    • setFields sets multiple fields for a specific token ID and slot combination. Can only be called by the admin.

    • setFieldsMany sets multiple fields for multiple token ID and slot combinations or sets multiple fields for a token ID range, where slot is a fixed value (for example: if we want to populate all the first slots for a large chunk of token IDs). Can only be called by the admin.

    • addTrait pushes the name of a field we should consider as a trait to the list of traits. Can only be called by the admin and is only possible if the trait doesn't exist.

    • addTraits is the many variant of addTrait and skips the traits that have already been added. Can only be called by the admin.

    • removeTrait is self-explanatory. Can only be called by the admin and is only possible if the trait exists.

    • setContractSlots sets the address to the contract having the storage for the token slots, which in our case happens to be the NFT (ERC721) contract. Can only be called by the admin.

    • setContractMeta64X sets the address for the ZkRoninsMeta64X.sol contract. Can only be called by the admin.

  • Metadata functionality

    • slotURI only returns a Base64 URI if the specific token ID and slot has Base64 enabled. This function gathers all necessary Fields and creates a Base64 encoded URI.

    • slotURIs returns multiple slotURIs for a given tokenID. The slots that have disabled Base64 will return as an empty string.

    • tokenURI only returns a Base64 URI if the specific token ID has Base64 enabled on the currently selected slot (see selectedSlot in ZkRoninsMeta.sol).

    • tokenURIs is the many variant for tokenURI. Base64 disabled slots return an empty string.

    • slotFields is similar to slotURI but returns the key-value pairs ¹ stored in a specific slot for a ronin ².

    • slotFieldsMany is the Fields equivalent of slotURIs; meaning it returns key-value pairs ¹ instead of URIs ².

    • tokenFields is similar to tokenURI but returns the key-value pairs ¹ stored for a ronin ².

    • tokenFieldsMany is the Fields equivalent of tokenURIs; meaning it returns key-value pairs ¹ instead of URIs ².

    • setReadyBase64 sets whether the Base64 metadata is ready or not. Since we as the contract owners have to populate the contracts with all the metadata, holders shouldn't be able to enable Base64 if it is not deemed ready. Can only be called by the admin.

    • setReadyBase64Many is self-explanatory. Can only be called by the admin.

    • setEnableBase64 enable or disable Base64 metadata on a per token or per slot basis. If the owner chooses to enable it on a per token basis, then every slot will have Base64 enabled by default. This function is guarded by the upgrade role modifier ³.

    • setNamePrefix sets the prefix for the name of all the NFTs in the collection; see the case study above. Can only be called by the admin.

NOTE: slotURI is the function where most of the logic lies. Moreover, it's upgradable, which will allow us to create more advanced algorithms for the computation of the Base64 URIs (e.g. fully-autonomous metadata; see ZkRoninsMeta64X.sol for more on this).


¹ Key-value pairs are useful if we don't necessarily want to create an URI, but just need all the information a ronin is composed of.

² The fields getter functions, which only developers might use, will only return correct values if ZkRoninsMeta64X.sol isn't linked to this contract. This can be noticed by checking whether contractMeta64X equals the zero-address (0x0000...0000). If it does link, then we will also have to call the Meta64X's slotFields-function for a complete result.

³ Functions with the upgrade role modifier can only be called by the UpgradeHandler.sol contract in the STORE module. This is done to separate storage and logic for modularity, to improve the contract's upgradability and to circumvent Solidity's immutable nature. See our Code of Conduct for more about this.


Transparency and Security

  • No withdraw method This contract cannot recover funds and shouldn't receive any, so be extra attentive not to accidentally send any funds to this contract.

  • Static and dynamic An NFT in our collection will be able to have both non-Base64 and Base64 types of metadata with our Token Slots approach. The current thought process for this is to give owners the possibility to have static metadata if they don't want to join the Base64 movement. Allowing both also makes it possible to give owners a way to copy over their Base64 metadata to the NFT (ERC721) contract's storage, switch back to non-Base64 and treat their metadata as a static URI.

Last updated