AlexG
2 years ago
2 changed files with 49 additions and 85 deletions
@ -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; |
||||
const toKey = (key: string) => { |
||||
return BigInt(`0x${sha256(key).toString("hex")}`); |
||||
}; |
||||
|
||||
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; |
||||
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(); |
||||
} |
Loading…
Reference in new issue