AlexG
2 years ago
2 changed files with 109 additions and 34 deletions
@ -1,37 +1,99 @@
|
||||
import { Cell, beginCell, Address, beginDict, Slice, toNano } from "ton"; |
||||
import {Address, Cell, Builder, beginCell} from "ton"; |
||||
|
||||
let contentSlice2 : Slice; |
||||
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.storeBits(bufferToStore.read(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(""); |
||||
|
||||
enum OPS { |
||||
ChangeAdmin = 3, |
||||
ReplaceMetadata = 4, |
||||
Mint = 21, |
||||
InternalTransfer = 0x178d4519, |
||||
Transfer = 0xf8a7ea5, |
||||
Burn = 0x595f07bc, |
||||
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); |
||||
} |
||||
|
||||
export type JettonMetaDataKeys = |
||||
| "name" |
||||
| "description" |
||||
| "image" |
||||
| "symbol" |
||||
| "image_data" |
||||
| "decimals"; |
||||
|
||||
async function parseJettonOffchainMetadata(contentSlice: Slice): Promise<{ |
||||
metadata: { [s in JettonMetaDataKeys]?: string }; |
||||
isIpfs: boolean; |
||||
}> { |
||||
const jsonURI = contentSlice |
||||
.loadBits(await () => (contentSlice.remainingBits())) |
||||
.toString("ascii") |
||||
.replace("ipfs://", "https://ipfs.io/ipfs/"); |
||||
|
||||
return { |
||||
metadata: (await axios.get(jsonURI)).data, |
||||
isIpfs: /(^|\/)ipfs[.:]/.test(jsonURI), |
||||
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; |
||||
} |
Loading…
Reference in new issue