import BN from "bn.js"; import { Buffer } from "buffer"; import { BitString } from "../../../boc/bitString"; import { Cell } from "../../../boc/cell"; import TonHttpProvider from "../../../providers/httpProvider"; import Address from "../../../utils/address"; export const SNAKE_DATA_PREFIX = 0x00; export const CHUNK_DATA_PREFIX = 0x01; export const ONCHAIN_CONTENT_PREFIX = 0x00; export const OFFCHAIN_CONTENT_PREFIX = 0x01; /** * @param uri {string} * @returns {Uint8Array} */ export const serializeUri = (uri: string) => { return new TextEncoder().encode(encodeURI(uri)); }; /** * @param bytes {Uint8Array} * @return {string} */ export const parseUri = (bytes: Uint8Array) => { return new TextDecoder().decode(bytes); }; /** * @param uri {string} * @return {Cell} */ export const createOffchainUriCell = (uri: string) => { const cell = new Cell(); cell.bits.writeUint(OFFCHAIN_CONTENT_PREFIX, 8); cell.bits.writeBytes(serializeUri(uri)); return cell; }; /** * @param cell {Cell} * @returns {string} */ export const parseOffchainUriCell = (cell: Cell) => { if (cell.bits.array[0] !== OFFCHAIN_CONTENT_PREFIX) { throw new Error("no OFFCHAIN_CONTENT_PREFIX"); } let length = 0; let c = cell; while (c) { length += c.bits.array.length; c = c.refs[0]; } const bytes = new Uint8Array(length); length = 0; c = cell; while (c) { bytes.set(c.bits.array, length); length += c.bits.array.length; c = c.refs[0]; } return parseUri(bytes.slice(1)); // slice OFFCHAIN_CONTENT_PREFIX }; 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; } export function makeSnakeCell(data: Buffer) { let chunks = bufferToChunks(data, 127); let rootCell = new Cell(); let curCell = rootCell; for (let i = 0; i < chunks.length; i++) { let chunk = chunks[i]; curCell.bits.writeBuffer(chunk); if (chunks[i + 1]) { let nextCell = new Cell(); curCell.refs.push(nextCell); curCell = nextCell; } } return rootCell; } export const createOffChainContent = (content: string) => { let data = Buffer.from(content); let offChainPrefix = Buffer.from([OFFCHAIN_CONTENT_PREFIX]); data = Buffer.concat([offChainPrefix, data]); return makeSnakeCell(data); }; /** * @param bs {BitString} * @param cursor {number} * @param bits {number} * @return {BigInt} */ const readIntFromBitString = (bs: BitString, cursor: number, bits: number) => { let n = BigInt(0); for (let i = 0; i < bits; i++) { n *= BigInt(2); n += BigInt(bs.get(cursor + i)); } return n; }; /** * @param cell {Cell} * @return {Address|null} */ export const parseAddress = (cell: Cell) => { let n = readIntFromBitString(cell.bits, 3, 8); if (n > BigInt(127)) { n = n - BigInt(256); } const hashPart = readIntFromBitString(cell.bits, 3 + 8, 256); if (n.toString(10) + ":" + hashPart.toString(16) === "0:0") return null; const s = n.toString(10) + ":" + hashPart.toString(16).padStart(64, "0"); return new Address(s); }; /** * @param provider {TonHttpProvider} * @param address {string} * @return {Promise<{royalty: number, royaltyFactor: number, royaltyBase: number, royaltyAddress: Address}>} */ export const getRoyaltyParams = async ( provider: TonHttpProvider, address: string ) => { const result = await provider.call2(address, "royalty_params"); const royaltyFactor = result[0].toNumber(); const royaltyBase = result[1].toNumber(); const royalty = royaltyFactor / royaltyBase; const royaltyAddress = parseAddress(result[2]); return { royalty, royaltyBase, royaltyFactor, royaltyAddress }; }; export const nftTransferBody = (params: { queryId?: number; newOwnerAddress: Address; forwardAmount?: BN; forwardPayload?: Uint8Array; responseAddress: Address; }) => { const cell = new Cell(); cell.bits.writeUint(0x5fcc3d14, 32); // transfer op cell.bits.writeUint(params.queryId || 0, 64); cell.bits.writeAddress(params.newOwnerAddress); cell.bits.writeAddress(params.responseAddress); cell.bits.writeBit(false); // null custom_payload cell.bits.writeCoins(params.forwardAmount || new BN(0)); cell.bits.writeBit(false); // forward_payload in this slice, not separate cell if (params.forwardPayload) { cell.bits.writeBytes(params.forwardPayload); } return cell; }; export const nftGetStaticDataBody = (params: { queryId?: number }) => { const body = new Cell(); body.bits.writeUint(0x2fcb26a2, 32); // OP body.bits.writeUint(params.queryId || 0, 64); // query_id return body; };