CIP | Title | Category | Status | Authors | Discussions | Created | License | |
---|---|---|---|---|---|---|---|---|
98? |
DAT Metadata Standard |
Metadata |
Proposed |
|
2023-06-30 |
CC-BY-4.0 |
This standard was initially developed for on-chain generative tokens, but it is also suitable for other use cases. It describes a method to store code and data on the blockchain in a space-efficient way using Cardano's Native Tokens.
Generative tokens created following this standard are called Distributed Artifact Tokens or DATs. They are not necessarily a replacement for NFTs but rather a layer on top of Cardano's Native Tokens. They can be fungible, semi-fungible, or non-fungible.
DATs introduce a way to instruct token viewers to query information from the blockchain. Creators can use the queried data to create dynamic, evolving, or interlinked token collections. Queries may include:
- Information about the current state of the blockchain
- Details from the token's mint transaction
- Details from previously minted tokens
The four following problems describe the motive for creating this standard.
Cardano is very well suited for on-chain generative tokens. Compared to other blockchains, it has a low L1 storage cost per kB, but the maximum transaction size of 16 kB is more limited than other chains.
How can we create generative tokens with larger on-chain codebases without hurting the blockchain?
Some existing on-chain projects on Cardano make inefficient use of block space by repeatedly storing the same monolithic blob accompanied by a few unique parameters. The result is thousands of copies of the same code, close to or at the total capacity of the 16 kB transaction limit.
Without imposing more restrictions on creators, how do we drastically reduce the on-chain storage footprint of generative token collections?
While JavaScript has become the dominant language for on-chain art, it's only a fraction of other languages and tools available to artists. Many established artists use tools that are not based on web technologies and sometimes adapt or limit their workflow to make their work run in a web browser.
What is the best interface for token viewers to support a diverse set of tools and languages?
Storing all dependencies for a generative token on the blockchain is not always convenient, sensible, or even viable. Examples are p5.js, three.js, Processing, and Blender, to name a few. There is currently no way to describe token dependencies, so third parties can render or reproduce digital artifacts.
What's the best way to describe external dependencies and maximize the archival qualities of digital artifacts?
A single DAT is distributed over at least two tokens, represented by three possible token types: scene, renderer and optionally, dependency.
Typically, scene tokens don't hold any code. They carry a reference to the renderer token, arguments for invocation of the renderer and token-specific properties. Scene tokens are therefore relatively small (less than 3kB). All scene tokens in a collection reference one, or at most a few, renderer tokens.
The code to render all scene tokens is stored in a renderer token, which in turn can reference dependency tokens containing shared libraries or assets. Dependency tokens are considered extensions to the renderer and can not be referenced directly by scene tokens.
Note: The DAT Metadata Standard builds on the existing CIP-0025 standard.
The scene token is the part the end user will receive in their wallet. It contains all the information to render the DAT.
Scene tokens require a renderer
property to be present, referencing the token containing the code of the renderer.
Although not mandatory, it's advisable to bundle token-specific properties in the properties
object to avoid pollution of the token's root namespace. It will help token viewers locate and render those details.
{
"721": {
"<policy_id>": {
"<asset_name>": {
"name": <string>,
"description": <string>,
"image": <uri | array>,
"mediaType": image/<mime_sub_type>,
"files": [{
"name": <string>,
"mediaType": <mime_type>,
"src": <uri | array>,
+ "license": <string>,
<other_properties>
}],
+ "renderer": {
+ "main": <asset_name>,
+ "arguments": <array>
+ },
+ "properties": {
+ <properties>
+ }
}
}
}
}
Properties for the scene token:
renderer
(required): an object with two propertiesmain
(required): theasset_name
of the renderer token within the currentpolicy_id
(e.g.my_renderer
)arguments
(required): an array with arbitrary values used as arguments for the invocation of the renderer (e.g.:[123]
)
properties
(optional): an object with arbitrary key/value pairs describing the token's (unique) properties
Several directives for dynamic arguments can be passed to the renderer. Before rendering a DAT, viewers must resolve any directives by querying the requested data from the blockchain and passing the actual values to the renderer.
Current token
@tx_hash
(string
): transaction hash of the mint (can for example, be used as the seed value for a Sfc32 PRNG)@epoch
(number
): epoch in which the token was minted@slot
(number
): slot in which the token was minted@block
(number
): block in which the token was minted@block_size
(number
): the size of the token's block@block_hash
(string
): hash of the token's block@owner_addresses
(string[]
): an array of owner addresses
Previously minted token
@tx_hash.previous
(string | null
): transaction hash@epoch.previous
(number | null
): epoch in which the token was minted@slot.previous
(number | null
): slot in which the token was minted@block.previous
(number | null
): block in which the token was minted@block_size.previous
(number | null
): the size of the token's block@block_hash.previous
(string | null
): hash of the token's block@arguments.previous
(array | null
): token's renderer arguments
Specific token (within the same policy_id)
@tx_hash.asset_name
(string | null
): transaction hash@epoch.asset_name
(number | null
): epoch in which the token was minted@slot.asset_name
(number | null
): slot in which the token was minted@block.asset_name
(number | null
): block in which the token was minted@block_size.asset_name
(number | null
): the size of the token's block@block_hash.asset_name
(string | null
): hash of the token's block@arguments.asset_name
(array | null
): token's renderer arguments
Current blockchain state
@current_epoch
(number
): current (latest) epoch@current_slot
(number
): current (latest) slot@current_block
(number
): current (latest) minted block@current_block_size
(number
): the size of the current block@current_block_hash
(string
): hash of the current block
Passing argument directives to the renderer works just like static arguments. For example:
[
123,
"@tx_hash",
"@block",
"@current_block",
"@epoch.previous",
"@arguments.the_perfect_nft_000"
]
Referencing another token's arguments does not work recursively.
Note: The list of directives mentioned above is not final. Proposals for new directives will be considered and added over time.
The renderer token is at the heart of a DAT collection and can either be a self-contained program or one with dependencies. It is always part of the same policy_id
as the scene tokens. Multiple renderer tokens can exist within a policy, but scene tokens can only reference one renderer at a time.
The renderer's code is stored in the files
property as-is or as a base64-encoded string. The name
property of the file must match the asset_name
with the appropriate file extension, so viewers can filter out the renderer-related files.
A renderer must always define an outputType
. Token viewers decide which output types to support.
Note: A single renderer token can have multiple files of different mime types, as long as the file names match the token's asset_name
.
{
"721": {
"<policy_id>": {
"<asset_name>": {
"files": [{
"name": <asset_name>.<extension>,
"mediaType": <mime_type>,
"src": <uri | array>,
+ "license": <string>,
<other_properties>
}],
+ "outputType": <mime_type>,
+ "dependencies": [{
+ "type": <string>,
+ <other_properties>
+ }],
+ "browsers": {
+ <browser_name>: <major_version>
+ }
}
}
}
}
Properties for the renderer token:
outputType
(required): the mime type of the renderer's output (it's up to the viewer to define the supported formats)dependencies
(optional): an array of objects with dependency definitionsbrowsers
(required for browser-based DATs): an object with versions of browsers against which the renderer is tested
Note: While not mandatory, adding a license
property to every file in the files
section is advisable—more info on licenses is below in section 2.f.
The renderer token should be burned after minting to free up the UTxO.
These are policy-specific on-chain dependencies managed by the creator. They must be minted within the same policy_id
and are referenced by their asset_name
.
{
"type": "onchain",
"asset_name": <asset_name>
}
type
(required): dependency typeasset_name
(required): name of the asset within the same policy
These are on-chain dependencies managed internally by the viewer and made available to the renderer at runtime.
They can be referenced by using the dependency's asset fingerprint
:
{
"type": "internal",
"fingerprint": <asset_fingerprint>
}
type
(required): dependency typefingerprint
(required): asset fingerprint
Alternatively, the asset's policy_id
and asset_name
can be used:
{
"type": "internal",
"policy_id": <policy_id>,
"asset_name": <asset_name>
}
type
(required): dependency typeasset_name
(required): name of the assetpolicy_id
(required): policy id of the asset
These are off-chain dependencies managed by the viewer and made available to the renderer at runtime.
{
"type": "external",
"name": <library_name>,
"version": <version_number>,
"source": <uri>,
"module": <boolean>
}
type
(required): dependency typename
(required): name of the library (list provided by viewers)version
(required): version of the librarysource
(required): URI of the library (IPFS, Arweave, etc.)module
(required): module flag; if set tofalse
, an iife variant is used
Note: The external dependency definitions are not referencing any token on the blockchain, unlike the onchain and internal variants. Their sole purpose is instructing token viewers which external dependency to load at runtime. A list of available libraries is maintained in the venster-external-dependencies repo.
This section only applies to non-browser-based renderers.
The build of a locally executed token revolves around a Dockerfile that should be included in the files of the renderer token. Optionally, package or configuration files can be included that are required by the Dockerfile to build the image.
{
"files": [{
"name": "Dockerfile",
"mediaType": "text/plain",
"src": <array>
}]
}
For creators, it is recommended to choose a license that aligns with their values and with the purpose of the digital artifact. Any license can be used, but popular licenses are:
Using no license is also an option to explicitly indicate that no one other than the creator may use the software.
This section applies to onchain and internal dependencies. Aside from how they are referenced, they are technically the same.
Dependency tokens can have multiple parts if they do not fit into one 16kB transaction. The dependency referenced from the renderer serves as an entry point, referencing the additional parts.
The code is stored in the files
property as-is or as a base64-encoded string. The name
property of the file must match the asset_name
. Like the renderer, every file can have an individual license
property.
{
"721": {
"<policy_id>": {
"<asset_name>": {
"files": [{
"name": <asset_name>.<extension>,
"mediaType": <mime_type>,
"src": <uri | array>,
"license": <string | null>,
<other_properties>
}],
+ "parts": [
+ <part_asset_name>
+ ]
}
}
}
}
Properties for the dependency token:
parts
(optional): an array with asset names (e.g.asset_name_part_2
)
Note:
While not mandatory, adding a license
property to each file in the files
section is advisable—more info on licenses is in section 2.f.
Dependency tokens should be burned after minting to free up the UTxOs.
This section presents the resolutions to the four problems mentioned in the motivation section of this CIP. Given that this standard has already been embraced by a few collections, real-world examples are used to demonstrate the implementation and advantages of DATs.
The distributed nature of DATs allows bigger on-chain codebases with dependencies. The separation of code and metadata effectively doubles the available space.
Additionally, dependencies can be stored in chunks, which allows for the realistic storage of up to fifty times more code than was previously possible.
Note: It is important to note that the increase of storage capacity applies to renderer tokens, not scene tokens. Therefore, the additional space is only available for code used by the renderer and not for static data like images in scene tokens.
This collection contains 3409 tokens and the first one ever minted using the DAT Metadata Standard.
token type | token size | minted | collection size |
---|---|---|---|
scene | 1.42 kB | 3409 | 4840.78 kB |
renderer (JS/CSS/HTML) | 12.85 kB | 1 | 12.85 kB |
lib dependency (JS) | 11.77 kB | 1 | 11.77 kB |
font dependency 1 | 15.35 kB | 1 | 15.35 kB |
font dependency 2 | 15.24 kB | 1 | 15.24 kB |
total | 56.63 kB | 3413 | 4895.99 kB |
This collection contains 512 tokens and the second one ever minted using the DAT Metadata Standard.
token type | token size | minted | collection size |
---|---|---|---|
scene | 2.44 kB | 512 | 1249.50 kB |
renderer (JS/CSS/HTML) | 9.72 kB | 1 | 12.85 kB |
lib dependency (JS) | 11.77 kB | 1 | 11.77 kB |
font dependency | 15.35 kB | 1 | 15.35 kB |
total | 39.28 kB | 515 | 1289.47 kB |
By looking at three real-world cases, it becomes clear that using DATs requires roughly 90% less block space than using isolated monolithic NFTs.
The data used in the following examples is extracted from existing token collections. The first two examples used monolithic NFTs, while the third one used DATs.
This collection contains 17190 tokens as monolithic HTML NFTs.
renderer size | token size | total kB | total MB | |
---|---|---|---|---|
as monolithic NFTs | N/A | 5.34 kB | 91792 kB | 89.6 MB |
as DATs | 4.91 kb | 0.53 kB | 9116 kB | 8.9 MB |
This collection would have used 90.03 % less block space with DATs
This collection contains 1744 tokens as monolithic HTML NFTs.
renderer size | token size | total kB | total MB | |
---|---|---|---|---|
as monolithic NFTs | N/A | 13.9 kB | 24241 kB | 23.7 MB |
as DATs | 13.40 kB | 1.46 kB | 2556 kB | 2.5 MB |
This collection would have used 89.54 % less block space with DATs.
This is a collection of 3409 tokens and the first one ever minted using the DAT Metadata Standard.
renderer size | token size | total kB | total MB | |
---|---|---|---|---|
as monolithic NFTs | N/A | 56.63 kB | 193052 kB | 188.5 MB |
as DATs | 55.21 kB | 1.42 kB | 4896 kB | 4.8 MB |
By using DATs, 97.46 % less block space was used. It is also important to note that, since the token as monolithic NFT is larger than 16 kB, this collection wouldn't have been possible on Cardano.
Although problems 3 and 4 address different topics, one solution solves both: providing a standardized way to describe dependencies and build instructions.
There are two variants of DATs: browser-based and non-browser-based. Almost all on-chain generative tokens on any blockchain fall into the first group. This standard supports both variants.
Existing token viewers and blockchain explorers already support monolithic browser-based generative tokens. The most commonly used output formats are HTML and SVG, which can handle embedded JavaScript but can't have any dependencies.
Through dependency definitions, DATs can instruct token viewers to load libraries, such as p5.js, to allow generative tokens beyond just plain JavaScript. Token viewers can decide which libraries, and versions thereof, to support.
To enhance a token's archival qualities, DATs require a list of supported browsers with their versions in which the program is tested and confirmed to work correctly. It will help collectors to reproduce their token collections in the distant future.
Generative artists regularly use tools that run outside the confines of web browsers. At least two on-chain generative token collections on Cardano do not use web technologies for rendering and describe the reproduction process in an improvised way.
DATs solve this by including a Dockerfile and, if necessary, a package file for the appropriate language. Viewers supporting this variant only need Docker installed to render the token using the unique arguments given by the scene token.
Note: The choice for Docker is one in favor of simplicity and familiarity. It requires the least effort and additional tooling on the side of the token viewer or anyone wanting to reproduce a generative token. If a more suitable solution presents itself with the same ease of use in the future, then it may be added as an alternative.
This standard was developed through implementation, which means it is already in use. There is also a viewer prototype publicly available and roughly 4000 tokens minted using the standard.
- Build a token-viewer prototype
- Create a real-world use case of browser-based DATs
- Create a real-world use case of non-browser-based DATs
N/A