TON 元数据解析
元数据标准涵盖了 NFT、NFT 集合和 Jettons,在 TON 增强提案 64 TEP-64 中有所描述。
在 TON 上,实体可以有三种类型的元数据:链上、半链上和链下。
- 链上元数据: 存储在区块链内部,包括名称、属性和图像。
- 链下元数据: 使用链接存储到链外托管的元数据文件。
- 半链上元数据: 两者之间的混合体,允许在区块链上存储小字段,如名称或属性,而将图像托管在链外,并仅存储指向它的链接。
蛇形数据编码
蛇形编码格式允许部分数据存储在标准cell内,而剩余部分存储在子cell内(以递归方式)。蛇形编码格式必须使用 0x00 字节作为前缀。TL-B 方案:
tail#_ {bn:#} b:(bits bn) = SnakeData ~0;
cons#_ {bn:#} {n:#} b:(bits bn) next:^(SnakeData ~n) = SnakeData ~(n + 1);
当单个cell无法存储的数据超过最大大小时,使用 蛇形格式存储额外数据。这是通过在根cell中存储部分数据,其余部分存储在第一个子cell中,并继续 递归进行,直到所有数据都被存储。
以下是 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);
}
应该注意,使用 蛇形格式时在根cell中并不总是需要 0x00
字节前缀,就像链下 NFT 内容的情况一样。此外,cell中以字节而非位填充,以简化解析。为了避免在其父cell已经写入后再向下一个子cell添加引用的问题,snake cell是以反向顺序构造的。
分块编码
分块编码格式使用字典数据结构存储数据,从 chunk_index 到 chunk。分块编码必须使用 0x01
字节作为前缀。TL-B 方案:
chunked_data#_ data:(HashMapE 32 ^(SnakeData ~0)) = ChunkedData;
以下是使用 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;
}