UNPKG

@ledgerhq/coin-ton

Version:
278 lines 10.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BotScenario = exports.getLedgerTonPath = exports.getTonEstimatedFees = exports.getTransferExpirationTime = exports.commentIsValid = exports.buildTonTransaction = exports.findSubAccountById = exports.addressesAreEqual = exports.isAddressValid = void 0; const index_1 = require("@ledgerhq/coin-framework/account/index"); const ton_1 = require("@ton/ton"); const bignumber_js_1 = __importDefault(require("bignumber.js")); const api_1 = require("./bridge/bridgeHelpers/api"); const config_1 = require("./config"); const constants_1 = require("./constants"); /** * Checks if the given recipient address is valid. */ const isAddressValid = (recipient) => { try { return Boolean((ton_1.Address.isRaw(recipient) || ton_1.Address.isFriendly(recipient)) && ton_1.Address.parse(recipient)); } catch { return false; } }; exports.isAddressValid = isAddressValid; /** * Compares two addresses to check if they are equal. */ const addressesAreEqual = (addr1, addr2) => { try { return ((0, exports.isAddressValid)(addr1) && (0, exports.isAddressValid)(addr2) && ton_1.Address.parse(addr1).equals(ton_1.Address.parse(addr2))); } catch { return false; } }; exports.addressesAreEqual = addressesAreEqual; /** * Returns the known jetton ID and workchain for a given token address. * Returns null if the token is not found in the known jettons list. */ function getKnownJettonId(tokenAddress, knownJettons) { const index = knownJettons.findIndex(jetton => jetton.masterAddress.toString() === tokenAddress); return index > -1 ? { jettonId: index, workchain: constants_1.WORKCHAIN } : null; } /** * Finds a sub-account by its ID in a TON account. * Returns undefined if no matching sub-account is found. */ function findSubAccountById(account, id) { return account.subAccounts?.find(a => a.id === id); } exports.findSubAccountById = findSubAccountById; /** * Builds a TonTransaction object based on the given transaction details. */ function buildTonTransaction(transaction, seqno, account) { const { subAccountId, useAllAmount, amount, comment: commentTx, recipient, payload, } = transaction; let recipientParsed = recipient; // if recipient is not valid calculate fees with empty address // we handle invalid addresses in account bridge try { ton_1.Address.parse(recipientParsed); } catch { recipientParsed = new ton_1.Address(0, Buffer.alloc(32)).toRawString(); } // if there is a sub account, the transaction is a token transfer const subAccount = findSubAccountById(account, subAccountId ?? ""); if (subAccount && !subAccount.jettonWallet) { throw new Error("[ton] jetton wallet not found"); } const finalAmount = subAccount ? (0, ton_1.toNano)(constants_1.TOKEN_TRANSFER_MAX_FEE) // for commission fees, excess will be returned : useAllAmount ? BigInt(0) : BigInt(amount.toFixed()); const to = subAccount?.jettonWallet ?? recipientParsed; const tonTransaction = { to: ton_1.Address.parse(to), seqno, amount: finalAmount, bounce: ton_1.Address.isFriendly(to) ? ton_1.Address.parseFriendly(to).isBounceable : true, timeout: (0, exports.getTransferExpirationTime)(), sendMode: useAllAmount && !subAccount ? ton_1.SendMode.CARRY_ALL_REMAINING_BALANCE : ton_1.SendMode.IGNORE_ERRORS + ton_1.SendMode.PAY_GAS_SEPARATELY, payload, }; if (commentTx.text.length) { tonTransaction.payload = { type: "comment", text: commentTx.text }; } if (subAccount) { const forwardPayload = commentTx.text.length ? (0, ton_1.comment)(commentTx.text) : null; const currencyConfig = (0, config_1.getCoinConfig)(); const knownJettons = currencyConfig.infra.KNOWN_JETTONS; tonTransaction.payload = { type: "jetton-transfer", queryId: BigInt(constants_1.TOKEN_TRANSFER_QUERY_ID), amount: BigInt(amount.toFixed()), destination: ton_1.Address.parse(recipientParsed), responseDestination: ton_1.Address.parse(account.freshAddress), customPayload: null, forwardAmount: BigInt(constants_1.TOKEN_TRANSFER_FORWARD_AMOUNT), forwardPayload, knownJetton: knownJettons ? getKnownJettonId(subAccount?.token.contractAddress, knownJettons) : null, }; } return tonTransaction; } exports.buildTonTransaction = buildTonTransaction; /** * Validates if the given comment is valid. */ const commentIsValid = (msg) => !msg.isEncrypted && msg.text.length <= constants_1.MAX_COMMENT_BYTES && /^[\x20-\x7F]*$/.test(msg.text); exports.commentIsValid = commentIsValid; /** * Gets the transfer expiration time. */ const getTransferExpirationTime = () => Math.floor(Date.now() / 1000 + 60); exports.getTransferExpirationTime = getTransferExpirationTime; /** * Estimates the fees for a Ton transaction. */ const getTonEstimatedFees = async (account, needsInit, tx) => { const { xpubOrAddress: pubKey } = (0, index_1.decodeAccountId)(account.id); if (pubKey.length !== 64) throw Error("[ton] pubKey can't be found"); // build body depending the payload type let body; let isJetton = false; if (tx.payload) { switch (tx.payload.type) { case "comment": body = (0, ton_1.comment)(tx.payload.text); break; case "jetton-transfer": body = buildTokenTransferBody(tx.payload); isJetton = true; break; } } const contract = ton_1.WalletContractV4.create({ workchain: 0, publicKey: Buffer.from(pubKey, "hex") }); const transfer = contract.createTransfer({ seqno: tx.seqno, secretKey: Buffer.alloc(64), // secretKey set to 0, signature is not verified messages: [ (0, ton_1.internal)({ bounce: tx.bounce, to: tx.to, value: tx.amount, body, }), ], sendMode: tx.sendMode, }); const initCode = needsInit ? contract.init.code.toBoc().toString("base64") : undefined; const initData = needsInit ? contract.init.data.toBoc().toString("base64") : undefined; const fee = await (0, api_1.estimateFee)(account.freshAddress, transfer.toBoc().toString("base64"), initCode, initData); return isJetton ? (0, bignumber_js_1.default)((0, ton_1.toNano)(constants_1.TOKEN_TRANSFER_MAX_FEE).toString()) : (0, bignumber_js_1.default)(fee.fwd_fee + fee.gas_fee + fee.in_fwd_fee + fee.storage_fee); }; exports.getTonEstimatedFees = getTonEstimatedFees; /** * Converts a Ledger path string to an array of numbers.length. */ const getLedgerTonPath = (path) => { const numPath = []; if (!path) throw Error("[ton] Path is empty"); if (path.startsWith("m/")) path = path.slice(2); const pathEntries = path.split("/"); if (pathEntries.length !== 6) throw Error(`[ton] Path length is not right ${path}`); for (const entry of pathEntries) { if (!entry.endsWith("'")) throw Error(`[ton] Path entry is not hardened ${path}`); const num = parseInt(entry.slice(0, entry.length - 1)); if (!Number.isInteger(num) || num < 0 || num >= 0x80000000) throw Error(`[ton] Path entry is not right ${path}`); numPath.push(num); } return numPath; }; exports.getLedgerTonPath = getLedgerTonPath; /** * Builds the body of a token transfer transaction. */ function buildTokenTransferBody(params) { const { queryId, amount, destination, responseDestination, forwardAmount } = params; let forwardPayload = params.forwardPayload; let builder = new ton_1.Builder() .storeUint(constants_1.JettonOpCode.Transfer, 32) .storeUint(queryId ?? generateQueryId(), 64) .storeCoins(amount) .storeAddress(destination) .storeAddress(responseDestination) .storeBit(false) .storeCoins(forwardAmount ?? BigInt(0)); if (forwardPayload instanceof Uint8Array) { forwardPayload = packBytesAsSnake(forwardPayload); } if (!forwardPayload) { builder.storeBit(false); } else if (typeof forwardPayload === "string") { builder = builder.storeBit(false).storeUint(0, 32).storeBuffer(Buffer.from(forwardPayload)); } else if (forwardPayload instanceof Uint8Array) { builder = builder.storeBit(false).storeBuffer(Buffer.from(forwardPayload)); } else { builder = builder.storeBit(true).storeRef(forwardPayload); } return builder.endCell(); } /** * Generates a random BigInt of the specified byte length. */ function bigintRandom(bytes) { let value = BigInt(0); for (const randomNumber of randomBytes(bytes)) { const randomBigInt = BigInt(randomNumber); value = (value << BigInt(8)) + randomBigInt; } return value; } /** * Generates a random byte array of the specified size. */ function randomBytes(size) { return self.crypto.getRandomValues(new Uint8Array(size)); } /** * Generates a random query ID. */ function generateQueryId() { return bigintRandom(8); } /** * Packs a byte array into a TonCell using a snake-like structure. */ function packBytesAsSnake(bytes) { return packBytesAsSnakeCell(bytes); } /** * Packs a byte array into a TonCell using a snake-like structure. */ function packBytesAsSnakeCell(bytes) { const buffer = Buffer.from(bytes); const mainBuilder = new ton_1.Builder(); let prevBuilder; let currentBuilder = mainBuilder; for (const [i, byte] of buffer.entries()) { if (currentBuilder.availableBits < 8) { prevBuilder?.storeRef(currentBuilder); prevBuilder = currentBuilder; currentBuilder = new ton_1.Builder(); } currentBuilder = currentBuilder.storeUint(byte, 8); if (i === buffer.length - 1) { prevBuilder?.storeRef(currentBuilder); } } return mainBuilder.asCell(); } var BotScenario; (function (BotScenario) { BotScenario["DEFAULT"] = "default"; BotScenario["TOKEN_TRANSFER"] = "token-transfer"; })(BotScenario || (exports.BotScenario = BotScenario = {})); //# sourceMappingURL=utils.js.map