Разбор метаданных
Стандарт метаданных, который включает NFT, коллекции NFT и Jettons, описан в TON Enhancement Proposal 64 [TEP-64] (https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md).
В TON сущности могут иметь три типа метаданных: on-chain, semi-chain, и off-chain.
- On-chain метаданные: хранятся внутри блокчейна, включая название, атрибуты и изображение.
- Off-chain метаданные: хранятся с помощью ссылки на файл метаданных, размещенного вне блокчейна.
- Semi-chain метаданные: гибрид между этими двумя способами, который позволяет хранить небольшие поля, такие как имена или атрибуты, внутри блокчейне, в то время как изображение хранятся за пределами блокчейна и при этом только ссылка на него.
Кодирование данных Snake
Формат кодирования Snake позволяет часть данных хранить в стандартной ячейке, а оставшуюся часть - в дочерней ячейке (рекурсивно). Формат кодирования Snake должен иметь префикс в виде байта 0x00. Схема TL-B:
tail#_ {bn:#} b:(bits bn) = SnakeData ~0;
cons#_ {bn:#} {n:#} b:(bits bn) next:^(SnakeData ~n) = SnakeData ~(n + 1);
Формат Snake используется для хранения дополнительных данных в ячейке, когда данные превышают максимальный размер, который можно хранить в одной ячейке. Это достигается путем хранения части данных в корневой ячейке, а оставшиеся части - в первой дочерней ячейке, и так продолжается рекурсивно до тех пор, пока все данные не будут сохранены.
Вот при мер кодирования и декодирования формата Snake в 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);
}
Следует отметить, что префикс 0x00
байт в корневой ячейке не всегда требуется при использовании формата snake, как в случае с off-chain содержимым NFT. Также ячейки заполняются байтами вместо битов для упрощения анализа. Чтобы избежать проблемы добавления ссылки (в пределах следующей дочерней ячейки) на ссылку после того, как она уже была записана в родительскую ячейку, snake ячейка строится в обратном порядке.
Кодирование Chunked
Формат кодирования chunked используется для хранения данных с помощью словарной структуры данных, начиная с chunk_index и заканчивая chunk. Кодировка chunked должна иметь префикс из байта 0x01
. Схема TL-B:
chunked_data#_ data:(HashMapE 32 ^(SnakeData ~0)) = ChunkedData;
Вот пример декодирования данных в формате chunked с помощью 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
Атрибут | Тип | Условие | Описание |
---|---|---|---|
uri | ASCII string | необязательный параметр | URI, указывающий на JSON-документ с метаданными, который используется в формате "Semi-chain content layout". |
name | UTF8 string | необязательный параметр | идентифицирует asset |
description | UTF8 string | необязательный параметр | описывает актив |
image | ASCII string | необязательный параметр | URI, указывающий на ресурс с типом mime image |
image_data | binary* | необязательный параметр | либо двоичное представление изображения для on-chain размещения, либо base64 для off-chain размещения |
Атрибуты метаданных Jetton
uri
- Необязательный параметр. Используется в формате "Semi-chain content layout". Строка ASCII. URI, указывающий на JSON-документ с метаданными.Имя
- Необязательный параметр. Строка в формате UTF8. Идентифицирует asset.description
- Необязательный параметр. Строка в формате UTF8. Описывает asset.image
- Необязательный параметр. Строка ASCII. URI, указывающий на ресурс с типом mime image.image_data
- Необязательный параметр. Либо двоичное представление изображения для onchain размещения, либо base64 для offchain размещения.symbol
- Необязательный параметр. Строка в формате UTF8. Символ токена - например, "XMPL". Используется в форме "Вы получили 99 XMPL".decimals
- Необязательный параметр. Если не указано, по умолчанию используется значение 9. Строковое значение с числом от 0 до 255, кодированное в UTF8. Количество десятичных знаков, которые использует токен — например, 8, означает разделить количество токенов на 100000000 для получения его пользовательского представления.amount_style
- Необязательный параметр. Необходим для внешних приложений, чтобы они понимали формат отображения количества jetton.
- "n" - количество jetton (значение по умолчанию). Если у пользователя 100 токенов с десятичным числом 0, то отображается, что у пользователя 100 токенов
- "n-of-total" - количество jetton из общего количества выпущенных jetton. Например, totalSupply Jetton = 1000. У пользователя есть 100 jetton в jetton wallet. Например, должно отображаться в кошельке пользователя как 100 из 1000 или любым другим текстовым или графическим способом, чтобы показать конкретное значение в общем контексте.
- "%" - процент от общего количества выпускаемых jetton. Например, totalSupply Jetton = 1000. У пользователя есть 100 jetton в jetton wallet. Например, должно отображаться в кошельке пользователя как 10%.
render_type
- Необязательный параметр. Необходим для внешних приложений, чтобы они понимали к какой группе относится jetton и как его отображать.
- "currency" - отображать как валюту (значение по умолчанию).
- "game" - отображать для игр. Будет отображаться как NFT, но при этом отображать количество jetton, учитывая параметр
amount_style
Атрибут | Тип | Условие | Описание |
---|---|---|---|
uri | ASCII string | необязательный параметр | URI, указывающий на JSON-документ с метаданными, который используется в формате "Semi-chain content layout". |
name | UTF8 string | необязательный параметр | идентифицирует asset |
description | UTF8 string | необязательный параметр | описывает asset |
image | ASCII string | необязательный параметр | URI, указывающий на ресурс с типом mime image |
image_data | binary* | необязательный параметр | либо двоичное представление изображения для on-chain размещения, либо base64 для off-chain размещения |
symbol | UTF8 string | необязательный параметр | символ токена - например, "XMPL" и используется в форме "Вы по лучили 99 XMPL" |
decimals | UTF8 string | необязательный параметр | количество десятичных знаков, которые использует токен. Если не указано, по умолчанию используется значение 9. Строковое значение с числом от 0 до 255, например 8, означает, что количество токенов должно быть разделено на 100000000 для получения его пользовательского представления. |
amount_style | необязательный параметр | необходим внешним приложениям, чтобы они понимали формат отображения количества jetton. Определяется с помощью n, n-of-total, %. | |
render_type | необязательный параметр | Необходим для внешних приложений, чтобы они понимали в какую группу относится jetton и как его отображать. "currency" — отображается как валюта (значение по умолчанию). "game" — используется для игр, которые отображаются как NFT, но также показывают количество jetton и учитывают значение параметра amount_style. |
Параметры
amount_style
:
- n — количество jetton (значение по умолчанию). Если пользователь имеет 100 токенов с 0 десятичных знаков, то отображается, что у пользовате ля 100 токенов.
- n-of-total — количество jetton от общего количества выпущенных jetton. Например, если totalSupply выпускаемых jetton равно 1000 и у пользователя есть 100 jetton в кошельке, то должно отображаться в кошельке пользователя как 100 из 1000 или другим текстовым или графическим способом, чтобы показать соотношение токенов пользователя к общему количеству доступных токенов.
- % — процент от общего количества выпущенных jetton. Например, если общее количество выпускаемых jetton равно 1000 и у пользователя есть 100 jetton, то процент должен быть отображен как 10% от баланса кошелька пользователя (100 ÷ 1000 = 0,1 или 10%).
Параметры
render_type
:
- currency - отображается как валюта (значение по умолчанию).
- game - используется для игр, которые отображаются как NFT, но также показывают количество jetton и учитывают значение параметра
amount_style
.
Разбор метаданных
Чтобы разобрать метаданные, сначала необходимо получить данные NFT из блокчейна. Чтобы лучше понять этот процесс, рекомендуем ознакомиться с разделом Получение данных NFT нашей документации по обработке активов в TON.
После того, как данные NFT в блокчейне получены, их необходимо разобрать. Чтобы выполнить этот процесс, необходимо определить тип содержимого NFT, прочитав первый байт, составляющий внутреннюю структуру NFT.
Off-chain
Если строка байтов метаданных начинается с 0x01
, это означает, что тип содержимого NFT находится off-chain. Оставшаяся часть содержимого NFT декодируется с помощью формата кодирования Snake как ASCII-строка. После того, как правильно будет отпределен NFT URL и получены данные идентификации NFT, процесс завершен. Ниже приведен пример URL, который использует разбор метаданных содержимого NFT off-chain:
https://s.getgems.io/nft/b/c/62fba50217c3fe3cbaad9e7f/95/meta.json
Содержимое URL (сверху):
{
"name": "TON Smart Challenge #2 Winners Trophy",
"description": "TON Smart Challenge #2 Winners Trophy 1 place out of 181",
"image": "https://s.getgems.io/nft/b/c/62fba50217c3fe3cbaad9e7f/images/943e994f91227c3fdbccbc6d8635bfaab256fbb4",
"content_url": "https://s.getgems.io/nft/b/c/62fba50217c3fe3cbaad9e7f/content/84f7f698b337de3bfd1bc4a8118cdfd8226bbadf",
"attributes": []
}
On-chain и Semi-chain
Если строка байтов метаданных начинается с 0x00
, это указывает на то, что NFT использует либо on-chain, либо semi-chain формат.
Метаданные для нашего NFT хранятся в словаре, где ключ - это SHA256-хэш имени атрибута, а значение - данные, хранящиеся либо в формате Snake, либо в формате Chunked.
Чтобы определить тип используемого NFT, разработчик должен прочитать известные атрибуты NFT, такие как uri
, name
, image
, description
и image_data
. Если поле uri
присутствует в метаданных, это указывает на semi-chain расположение. В таких случаях off-chain содержимое, указанное в поле uri, должно быть загружено и объединено со значениями словаря.
Пример on-chain NFT: EQBq5z4N_GeJyBdvNh4tPjMpSkA08p8vWyiAX6LNbr3aLjI0
Пример semi-chain NFT: EQB2NJFK0H5OxJTgyQbej0fy5zuicZAXk2vFZEDrqbQ_n5YW
Пример on-chain Jetton Master: EQA4pCk0yK-JCwFD4Nl5ZE4pmlg4DkK-1Ou4HAUQ6RObZNMi
Пример разбора on-chain NFT: stackblitz/ton-onchain-nft-parser