From 2b1e13c859770bc20a0ef25550e6853db94f5d19 Mon Sep 17 00:00:00 2001 From: AlexG <39581753+Reveloper@users.noreply.github.com> Date: Sun, 22 Jan 2023 14:59:28 +0400 Subject: [PATCH] on-chain-writing-fix --- package.json | 1 + sources/utils/jetton-helpers.ts | 133 ++++++++++++-------------------- 2 files changed, 49 insertions(+), 85 deletions(-) diff --git a/package.json b/package.json index 642780b..8b13c4a 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@types/jest": "^29.2.4", + "@aws-crypto/sha256-js": "^3.0.0", "@types/node": "^18.11.14", "@types/qs": "^6.9.7", "jest": "^29.3.1", diff --git a/sources/utils/jetton-helpers.ts b/sources/utils/jetton-helpers.ts index 1f880cd..4ceab82 100644 --- a/sources/utils/jetton-helpers.ts +++ b/sources/utils/jetton-helpers.ts @@ -1,99 +1,62 @@ -import {Address, Cell, Builder, beginCell} from "ton"; +import { Sha256 } from "@aws-crypto/sha256-js"; +import { beginCell, Cell } from "ton"; +import { Dictionary } from "ton-core"; + +function bufferToChunks(buff: Buffer, chunkSize: number) { + let chunks: Buffer[] = []; + while (buff.byteLength > 0) { + chunks.push(buff.slice(0, chunkSize)); + buff = buff.slice(chunkSize); + } + return chunks; +} +const CELL_MAX_SIZE_BYTES = Math.floor((1023 - 8) / 8); + +export function makeSnakeCell(data: Buffer) { + let chunks = bufferToChunks(data, CELL_MAX_SIZE_BYTES); + const b = chunks.reduceRight((curCell, chunk, index) => { + if (index === 0) { + curCell.storeInt(SNAKE_PREFIX, 8); + } + curCell.storeBuffer(chunk); + if (index > 0) { + const cell = curCell.endCell(); + return beginCell().storeRef(cell); + } else { + return curCell; + } + }, beginCell()); + return b.endCell(); +} 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 } = {}; +const toKey = (key: string) => { + return BigInt(`0x${sha256(key).toString("hex")}`); +}; - 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; +export function buildOnchainMetadata(data: { + name: string; + description: string; + image: string; +}): Cell { + let dict = Dictionary.empty( + Dictionary.Keys.BigUint(256), + Dictionary.Values.Cell() + ); + Object.entries(data).forEach(([key, value]) => { + dict.set(toKey(key), makeSnakeCell(Buffer.from(value, "utf8"))); }); - return res; + return beginCell() + .storeInt(ONCHAIN_CONTENT_PREFIX, 8) +.storeDict(dict) + .endCell(); } \ No newline at end of file