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 ONCHAIN_CONTENT_PREFIX = 0x00; |
||||||
const SNAKE_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 sha256 = (str: string) => { |
||||||
const sha = new Sha256(); |
const sha = new Sha256(); |
||||||
sha.update(str); |
sha.update(str); |
||||||
return Buffer.from(sha.digestSync()); |
return Buffer.from(sha.digestSync()); |
||||||
}; |
}; |
||||||
|
|
||||||
export function buildTokenMetadataCell(data: { [s: string]: string | undefined }): Cell { |
const toKey = (key: string) => { |
||||||
const KEYLEN = 256; |
return BigInt(`0x${sha256(key).toString("hex")}`); |
||||||
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); |
export function buildOnchainMetadata(data: { |
||||||
}); |
name: string; |
||||||
|
description: string; |
||||||
const res: { [s in JettonMetaDataKeys]?: string } = {}; |
image: string; |
||||||
|
}): Cell { |
||||||
Object.keys(jettonOnChainMetadataSpec).forEach((k) => { |
let dict = Dictionary.empty( |
||||||
const val = dict |
Dictionary.Keys.BigUint(256), |
||||||
.get(toKey(sha256(k).toString("hex"))) |
Dictionary.Values.Cell() |
||||||
?.toString(jettonOnChainMetadataSpec[k as JettonMetaDataKeys]); |
); |
||||||
if (val) res[k as JettonMetaDataKeys] = val; |
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