@ledgerhq/coin-ton
Version:
305 lines • 12.7 kB
JavaScript
;
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