diff --git a/sources/jetton.deploy.ts b/sources/jetton.deploy.ts index 70e7da0..b0103ec 100644 --- a/sources/jetton.deploy.ts +++ b/sources/jetton.deploy.ts @@ -1,12 +1,25 @@ import { beginCell, contractAddress, toNano, TonClient, TonClient4, Address, WalletContractV4, internal, fromNano} from "ton"; import {mnemonicToPrivateKey} from "ton-crypto"; -import { packAdd, init } from "./output/jetton_SampleJetton"; -import { printAddress, printDeploy, printHeader } from "./utils/print"; -import { randomAddress } from "./utils/randomAddress"; -import {SampleJetton_errors} from "./output/jetton_SampleJetton"; +import {JettonMetaDataKeys} from 'utils/jetton-helpers'; + + + + (async () => { //need changes for jetton + // This is example data - Modify these params for your own jetton! + // - Data is stored on-chain (except for the image data itself) + // - Owner should usually be the deploying wallet's address. + const jettonParams = { + name: "MyJetton", + symbol: "JET1", + image: "https://www.linkpicture.com/q/download_183.png", // Image url + description: "My jetton", + }; + + + //create client for testnet Toncenter API const client = new TonClient({ endpoint: 'https://testnet.toncenter.com/api/v2/jsonRPC', diff --git a/sources/utils/jetton-helpers.ts b/sources/utils/jetton-helpers.ts index c4a5675..4e4d429 100644 --- a/sources/utils/jetton-helpers.ts +++ b/sources/utils/jetton-helpers.ts @@ -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 { - ChangeAdmin = 3, - ReplaceMetadata = 4, - Mint = 21, - InternalTransfer = 0x178d4519, - Transfer = 0xf8a7ea5, - Burn = 0x595f07bc, +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 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), - }; +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 } = {}; + + 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; } \ No newline at end of file