Metadata Parsing
The metadata standard, which covers NFTs, NFT Collections, and Jettons, is outlined in TON Enhancement Proposal 64 TEP-64.
On TON, entities can have three types of metadata: on-chain, semi-chain, and off-chain.
- On-chain metadata: stored inside the blockchain, including the name, attributes, and image.
- Off-chain metadata: stored using a link to a metadata file hosted outside of the chain.
- Semi-chain metadata: a hybrid between the two which allows for the storage of small fields such as names or attributes on the blockchain, while hosting the image off-chain and storing only a link to it.
Snake Data Encoding
The Snake encoding format allows part of the data to be stored in a standardized cell, while the remaining portion is stored in a child cell (in a recursive manner). The Snake encoding format must be prefixed using the 0x00 byte. TL-B scheme:
tail#_ {bn:#} b:(bits bn) = SnakeData ~0;
cons#_ {bn:#} {n:#} b:(bits bn) next:^(SnakeData ~n) = SnakeData ~(n + 1);
The Snake format is used to store additional data in a cell when the data exceeds the maximum size that can be stored in a single cell. This is achieved by storing part of the data in the root cell and the remaining part in the first child cell, and continuing to do so recursively until all the data has been stored.
Below is an example of Snake format encoding and decoding in TypeScript:
export function makeSnakeCell(data: Buffer): Cell {
const chunks = bufferToChunks(data, 127)
if (chunks.length === 0) {
return beginCell().endCell()
}
if (chunks.length === 1) {
return beginCell().storeBuffer(chunks[0]).endCell()
}
let curCell = beginCell()
for (let i = chunks.length - 1; i >= 0; i--) {
const chunk = chunks[i]
curCell.storeBuffer(chunk)
if (i - 1 >= 0) {
const nextCell = beginCell()
nextCell.storeRef(curCell)
curCell = nextCell
}
}
return curCell.endCell()
}
export function flattenSnakeCell(cell: Cell): Buffer {
let c: Cell | null = cell;
const bitResult = new BitBuilder();
while (c) {
const cs = c.beginParse();
if (cs.remainingBits === 0) {
break;
}
const data = cs.loadBits(cs.remainingBits);
bitResult.writeBits(data);
c = c.refs && c.refs[0];
}
const endBits = bitResult.build();
const reader = new BitReader(endBits);
return reader.loadBuffer(reader.remaining / 8);
}
It should be noted that the 0x00
byte prefix is not always required in the root cell when using the snake format, as is the case with off-chain NFT content. Additionally, cells are filled with bytes instead of bits to simplify parsing. To avoid the issue of adding a reference (within the next child cell) to a reference after it has already been written to its parent cell, the snake cell is constructed in reverse order.
Chunked Encoding
The chunked encoding format is used to store data using a dictionary data structure as from the chunk_index to the chunk. Chunked encoding must be prefixed using the 0x01
byte. TL-B scheme:
chunked_data#_ data:(HashMapE 32 ^(SnakeData ~0)) = ChunkedData;
Below is an example of chunked data decoding using TypeScript:
interface ChunkDictValue {
content: Buffer;
}
export const ChunkDictValueSerializer = {
serialize(src: ChunkDictValue, builder: Builder) {},
parse(src: Slice): ChunkDictValue {
const snake = flattenSnakeCell(src.loadRef());
return { content: snake };
},
};
export function ParseChunkDict(cell: Slice): Buffer {
const dict = cell.loadDict(
Dictionary.Keys.Uint(32),
ChunkDictValueSerializer
);
let buf = Buffer.alloc(0);
for (const [_, v] of dict) {
buf = Buffer.concat([buf, v.content]);
}
return buf;
}
NFT metadata attributes
Attribute | Type | Requirement | Description |
---|---|---|---|
uri | ASCII string | optional | a URI pointing to the JSON document with metadata that is used by the "Semi-chain content layout." |
name | UTF8 string | optional | identifies the asset |
description | UTF8 string | optional | describes the asset |
image | ASCII string | optional | a URI pointing to a resource with a mime type image |
image_data | binary* | optional | either a binary representation of the image for on-chain layout or base64 for off-chain layout |