UNPKG

@ledgerhq/coin-ton

Version:
305 lines 12.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.decodeForwardPayload = exports.loadSnakeBytes = exports.dataToSlice = exports.mapJettonTxToOps = exports.mapTxToOps = exports.getJettonTransfers = exports.getTransactions = void 0; const index_1 = require("@ledgerhq/coin-framework/account/index"); const operation_1 = require("@ledgerhq/coin-framework/operation"); const tokens_1 = require("@ledgerhq/cryptoassets/tokens"); const core_1 = require("@ton/core"); const bignumber_js_1 = __importDefault(require("bignumber.js")); const utils_1 = require("../../utils"); const api_1 = require("./api"); async function getTransactions(addr, startLt) { const txs = await (0, api_1.fetchTransactions)(addr, { startLt }); if (txs.transactions.length === 0) return txs; let tmpTxs; let isUncompletedResult = true; while (isUncompletedResult) { const { lt, hash } = txs.transactions[txs.transactions.length - 1]; tmpTxs = await (0, api_1.fetchTransactions)(addr, { startLt, endLt: lt }); // we found the last transaction if (tmpTxs.transactions.length === 1) { isUncompletedResult = false; break; } // it should always match if (hash !== tmpTxs.transactions[0].hash) throw Error("[ton] transaction hash does not match"); tmpTxs.transactions.shift(); // first element is repeated txs.transactions.push(...tmpTxs.transactions); txs.address_book = { ...txs.address_book, ...tmpTxs.address_book }; } return txs; } exports.getTransactions = getTransactions; async function getJettonTransfers(addr, startLt) { const txs = await (0, api_1.fetchJettonTransactions)(addr, { startLt }); if (txs.length === 0) return txs; let tmpTxs; let isUncompletedResult = true; while (isUncompletedResult) { const { transaction_hash, transaction_lt } = txs[txs.length - 1]; tmpTxs = await (0, api_1.fetchJettonTransactions)(addr, { startLt, endLt: transaction_lt }); // we found the last transaction if (tmpTxs.length === 1) { isUncompletedResult = false; break; } // it should always match if (transaction_hash !== tmpTxs[0].transaction_hash) throw Error("[ton] transaction hash does not match"); tmpTxs.shift(); // first element is repeated txs.push(...tmpTxs); } return txs; } exports.getJettonTransfers = getJettonTransfers; function getFriendlyAddress(addressBook, rawAddr) { if (!rawAddr) return []; if (addressBook[rawAddr]) return [addressBook[rawAddr].user_friendly]; if (!(0, utils_1.isAddressValid)(rawAddr)) throw new Error("[ton] address is not valid"); return [core_1.Address.parse(rawAddr).toString({ urlSafe: true, bounceable: true })]; } function mapTxToOps(accountId, addr, addressBook) { return (tx) => { const ops = []; if (tx.out_msgs.length > 1) throw Error(`[ton] txn with > 1 output not expected ${tx}`); const accountAddr = core_1.Address.parse(tx.account).toString({ urlSafe: true, bounceable: false }); if (accountAddr !== addr) throw Error(`[ton] unexpected address ${accountAddr} ${addr}`); const isReceiving = tx.in_msg && tx.in_msg.source && tx.in_msg.source !== "" && tx.in_msg.value && tx.in_msg.value !== "0" && tx.account === tx.in_msg.destination; const isSending = tx.out_msgs.length !== 0 && tx.out_msgs[0].source && tx.out_msgs[0].source !== "" && tx.out_msgs[0].value && tx.out_msgs[0].value !== "0" && tx.account === tx.out_msgs[0].source; const date = new Date(tx.now * 1000); // now is defined in seconds const hash = tx.in_msg?.hash ?? tx.hash; // this is the hash we know in signature time const hasFailed = tx.description.compute_ph.success === false && tx.description.compute_ph.exit_code !== 0; if (isReceiving) { let subOperations; if (tx.total_fees !== "0") { // these are small amount of fees payed when receiving // we don't want to show them in the charts subOperations = [ { id: (0, operation_1.encodeOperationId)(accountId, hash, "NONE"), hash, type: "NONE", value: (0, bignumber_js_1.default)(tx.total_fees), fee: (0, bignumber_js_1.default)(0), blockHeight: tx.mc_block_seqno ?? 1, blockHash: null, hasFailed, accountId, senders: [accountAddr], recipients: [], date, extra: { lt: tx.lt, explorerHash: tx.hash, comment: { isEncrypted: false, text: "", }, }, }, ]; } ops.push({ id: (0, operation_1.encodeOperationId)(accountId, hash, "IN"), hash, type: "IN", value: (0, bignumber_js_1.default)(tx.in_msg?.value ?? 0), fee: (0, bignumber_js_1.default)(tx.total_fees), blockHeight: tx.mc_block_seqno ?? 1, blockHash: null, hasFailed, accountId, senders: getFriendlyAddress(addressBook, tx.in_msg?.source), recipients: [accountAddr], date, extra: { lt: tx.lt, explorerHash: tx.hash, comment: { isEncrypted: tx.in_msg?.message_content?.decoded?.type === "binary_comment", text: tx.in_msg?.message_content?.decoded?.type === "text_comment" ? tx.in_msg.message_content.decoded.comment : "", }, }, subOperations, }); } if (isSending) { ops.push({ id: (0, operation_1.encodeOperationId)(accountId, hash, "OUT"), hash: tx.out_msgs[0].hash, type: "OUT", value: (0, bignumber_js_1.default)(tx.out_msgs[0].value ?? 0), fee: (0, bignumber_js_1.default)(tx.total_fees), blockHeight: tx.mc_block_seqno ?? 1, blockHash: null, hasFailed, accountId, senders: [accountAddr], recipients: getFriendlyAddress(addressBook, tx.out_msgs[0].destination), date, extra: { lt: tx.lt, explorerHash: tx.hash, comment: { isEncrypted: tx.out_msgs[0].message_content?.decoded?.type === "binary_comment", text: tx.out_msgs[0].message_content?.decoded?.type === "text_comment" ? tx.out_msgs[0].message_content.decoded.comment : "", }, }, }); } return ops; }; } exports.mapTxToOps = mapTxToOps; function mapJettonTxToOps(accountId, addr, addressBook, jettonTxMessageHashesMap) { return (tx) => { const accountAddr = core_1.Address.parse(addr).toString({ urlSafe: true, bounceable: false }); if (accountAddr !== addr) throw Error(`[ton] unexpected address ${accountAddr} ${addr}`); const jettonMasterAddr = core_1.Address.parse(tx.jetton_master).toString({ urlSafe: true, bounceable: true, }); const tokenCurrency = (0, tokens_1.findTokenByAddressInCurrency)(jettonMasterAddr.toLowerCase(), (0, index_1.decodeAccountId)(accountId).currencyId); if (!tokenCurrency) return []; const tokenAccountId = (0, index_1.encodeTokenAccountId)(accountId, tokenCurrency); const ops = []; const isReceiving = (0, utils_1.addressesAreEqual)(accountAddr, core_1.Address.parse(tx.destination).toString({ urlSafe: true, bounceable: false })); const isSending = (0, utils_1.addressesAreEqual)(accountAddr, core_1.Address.parse(tx.source).toString({ urlSafe: true, bounceable: false })); if (!isSending && !isReceiving) throw Error("[ton] unexpected addresses"); const date = new Date(tx.transaction_now * 1000); // now is defined in seconds const hash = tx.transaction_hash; if (isReceiving) { ops.push({ id: (0, operation_1.encodeOperationId)(tokenAccountId, hash, "IN"), hash, type: "IN", value: (0, bignumber_js_1.default)(tx.amount), fee: (0, bignumber_js_1.default)(0), blockHeight: 1, // we don't have block info blockHash: null, hasFailed: false, accountId: tokenAccountId, senders: getFriendlyAddress(addressBook, tx.source), recipients: [accountAddr], date, extra: { lt: tx.transaction_lt, explorerHash: hash, comment: { isEncrypted: false, text: tx.forward_payload ? decodeForwardPayload(tx.forward_payload) : "", }, }, }); } if (isSending) { const hash_message = jettonTxMessageHashesMap ? jettonTxMessageHashesMap.get(hash) ?? hash : hash; ops.push({ id: (0, operation_1.encodeOperationId)(tokenAccountId, hash_message, "OUT"), hash, type: "OUT", value: (0, bignumber_js_1.default)(tx.amount), fee: (0, bignumber_js_1.default)(0), blockHeight: 1, // we don't have block info blockHash: null, hasFailed: false, accountId: tokenAccountId, senders: [accountAddr], recipients: getFriendlyAddress(addressBook, tx.destination), date, extra: { lt: tx.transaction_lt, explorerHash: hash, comment: { isEncrypted: false, text: tx.forward_payload ? decodeForwardPayload(tx.forward_payload) : "", }, }, }); } return ops; }; } exports.mapJettonTxToOps = mapJettonTxToOps; // Export these functions for testing function dataToSlice(data) { let buffer; if (typeof data === "string") { buffer = Buffer.from(data, "base64"); try { return core_1.Cell.fromBoc(buffer)[0].beginParse(); } catch (err) { return new core_1.Slice(new core_1.BitReader(new core_1.BitString(buffer, 0, buffer.length * 8)), []); } } return undefined; } exports.dataToSlice = dataToSlice; function loadSnakeBytes(slice) { let buffer = Buffer.alloc(0); while (slice.remainingBits >= 8) { buffer = Buffer.concat([buffer, slice.loadBuffer(slice.remainingBits / 8)]); if (slice.remainingRefs) { slice = slice.loadRef().beginParse(); } else { break; } } return buffer; } exports.loadSnakeBytes = loadSnakeBytes; function decodeForwardPayload(payload) { if (!payload) return ""; try { const slice = dataToSlice(payload); if (!slice) return ""; const opcode = slice.loadUint(32); // Format with opcode 0 followed by text if (opcode !== 0) { return ""; } const buffer = loadSnakeBytes(slice); const comment = buffer.toString("utf-8"); return comment; } catch (error) { // Silent failure, returning empty string return ""; } } exports.decodeForwardPayload = decodeForwardPayload; //# sourceMappingURL=txn.js.map