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", |
||||||
|
}; |
||||||
|
|
||||||
enum OPS { |
const sha256 = (str: string) => { |
||||||
ChangeAdmin = 3, |
const sha = new Sha256(); |
||||||
ReplaceMetadata = 4, |
sha.update(str); |
||||||
Mint = 21, |
return Buffer.from(sha.digestSync()); |
||||||
InternalTransfer = 0x178d4519, |
}; |
||||||
Transfer = 0xf8a7ea5, |
|
||||||
Burn = 0x595f07bc, |
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 type JettonMetaDataKeys = |
export function parseTokenMetadataCell(contentCell: Cell): { |
||||||
| "name" |
[s in JettonMetaDataKeys]?: string; |
||||||
| "description" |
} { |
||||||
| "image" |
// Note that this relies on what is (perhaps) an internal implementation detail:
|
||||||
| "symbol" |
// "ton" library dict parser converts: key (provided as buffer) => BN(base10)
|
||||||
| "image_data" |
// and upon parsing, it reads it back to a BN(base10)
|
||||||
| "decimals"; |
// 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); |
||||||
async function parseJettonOffchainMetadata(contentSlice: Slice): Promise<{ |
|
||||||
metadata: { [s in JettonMetaDataKeys]?: string }; |
const KEYLEN = 256; |
||||||
isIpfs: boolean; |
const contentSlice = contentCell.beginParse(); |
||||||
}> { |
if (contentSlice.readUint(8).toNumber() !== ONCHAIN_CONTENT_PREFIX) |
||||||
const jsonURI = contentSlice |
throw new Error("Expected onchain content marker"); |
||||||
.loadBits(await () => (contentSlice.remainingBits())) |
|
||||||
.toString("ascii") |
const dict = contentSlice.readDict(KEYLEN, (s) => { |
||||||
.replace("ipfs://", "https://ipfs.io/ipfs/"); |
const buffer = Buffer.from(""); |
||||||
|
|
||||||
return { |
const sliceToVal = (s: Slice, v: Buffer, isFirst: boolean) => { |
||||||
metadata: (await axios.get(jsonURI)).data, |
s.toCell().beginParse(); |
||||||
isIpfs: /(^|\/)ipfs[.:]/.test(jsonURI), |
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; |
||||||
} |
} |
Loading…
Reference in new issue