You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
99 lines
3.5 KiB
99 lines
3.5 KiB
import {Address, Cell, Builder, beginCell} from "ton"; |
|
|
|
const ONCHAIN_CONTENT_PREFIX = 0x00; |
|
const SNAKE_PREFIX = 0x00; |
|
|
|
export type JettonMetaDataKeys = "name" | "description" | "image" | "symbol"; |
|
|
|
const jettonOnChainMetadataSpec: { |
|
[key in JettonMetaDataKeys]: "utf8" | "ascii" | undefined; |
|
} = { |
|
name: "utf8", |
|
description: "utf8", |
|
image: "ascii", |
|
symbol: "utf8", |
|
}; |
|
|
|
const sha256 = (str: string) => { |
|
const sha = new Sha256(); |
|
sha.update(str); |
|
return Buffer.from(sha.digestSync()); |
|
}; |
|
|
|
export function buildTokenMetadataCell(data: { [s: string]: string | undefined }): Cell { |
|
const KEYLEN = 256; |
|
let rootCell = beginCell(); |
|
let dict = new Cell(); |
|
|
|
Object.entries(data).forEach(([k, v]: [string, string | undefined]) => { |
|
if (!jettonOnChainMetadataSpec[k as JettonMetaDataKeys]) |
|
throw new Error(`Unsupported onchain key: ${k}`); |
|
if (v === undefined || v === "") return; |
|
|
|
let bufferToStore = Buffer.from(v, jettonOnChainMetadataSpec[k as JettonMetaDataKeys]); |
|
|
|
const CELL_MAX_SIZE_BYTES = Math.floor((1023 - 8) / 8); |
|
|
|
rootCell.storeUint(SNAKE_PREFIX, 16); |
|
let currentCell = rootCell; |
|
|
|
//TODO need fix dictionary writing |
|
while (bufferToStore.length > 0) { |
|
currentCell.storeBuffer(bufferToStore.subarray(0, CELL_MAX_SIZE_BYTES)) // how to read from Buffer??? |
|
//currentCell.bits.writeBuffer(bufferToStore.slice(0, CELL_MAX_SIZE_BYTES)); |
|
//bufferToStore = bufferToStore.slice(CELL_MAX_SIZE_BYTES); |
|
if (bufferToStore.length > 0) { |
|
const newCell = new Builder(); |
|
newCell.storeRef(currentCell); |
|
currentCell = newCell; |
|
} |
|
} |
|
let dict = currentCell.endCell(); |
|
}); |
|
return beginCell().storeInt(ONCHAIN_CONTENT_PREFIX, 8).storeDict(dict).endCell(); |
|
} |
|
|
|
export function parseTokenMetadataCell(contentCell: Cell): { |
|
[s in JettonMetaDataKeys]?: string; |
|
} { |
|
// Note that this relies on what is (perhaps) an internal implementation detail: |
|
// "ton" library dict parser converts: key (provided as buffer) => BN(base10) |
|
// and upon parsing, it reads it back to a BN(base10) |
|
// tl;dr if we want to read the map back to a JSON with string keys, we have to convert BN(10) back to hex |
|
const toKey = (str: string) => new BN(str, "hex").toString(10); |
|
|
|
const KEYLEN = 256; |
|
const contentSlice = contentCell.beginParse(); |
|
if (contentSlice.readUint(8).toNumber() !== ONCHAIN_CONTENT_PREFIX) |
|
throw new Error("Expected onchain content marker"); |
|
|
|
const dict = contentSlice.readDict(KEYLEN, (s) => { |
|
const buffer = Buffer.from(""); |
|
|
|
const sliceToVal = (s: Slice, v: Buffer, isFirst: boolean) => { |
|
s.toCell().beginParse(); |
|
if (isFirst && s.readUint(8).toNumber() !== SNAKE_PREFIX) |
|
throw new Error("Only snake format is supported"); |
|
|
|
v = Buffer.concat([v, s.readRemainingBytes()]); |
|
if (s.remainingRefs === 1) { |
|
v = sliceToVal(s.readRef(), v, false); |
|
} |
|
|
|
return v; |
|
}; |
|
|
|
return sliceToVal(s.readRef(), buffer, true); |
|
}); |
|
|
|
const res: { [s in JettonMetaDataKeys]?: string } = {}; |
|
|
|
Object.keys(jettonOnChainMetadataSpec).forEach((k) => { |
|
const val = dict |
|
.get(toKey(sha256(k).toString("hex"))) |
|
?.toString(jettonOnChainMetadataSpec[k as JettonMetaDataKeys]); |
|
if (val) res[k as JettonMetaDataKeys] = val; |
|
}); |
|
|
|
return res; |
|
} |