UNPKG

@ledgerhq/live-common

Version:
429 lines • 16.6 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildOptimisticOperation = void 0; exports.bigNumberToBigIntDeep = bigNumberToBigIntDeep; exports.findCryptoCurrencyByNetwork = findCryptoCurrencyByNetwork; exports.extractBalance = extractBalance; exports.extractBalances = extractBalances; exports.cleanedOperation = cleanedOperation; exports.adaptCoreOperationToLiveOperation = adaptCoreOperationToLiveOperation; exports.transactionToIntent = transactionToIntent; exports.applyMemoToIntent = applyMemoToIntent; const operation_1 = require("@ledgerhq/ledger-wallet-framework/operation"); const bignumber_js_1 = __importDefault(require("bignumber.js")); const utils_1 = require("@ledgerhq/coin-framework/utils"); const currencies_1 = require("@ledgerhq/cryptoassets/currencies"); function bigNumberToBigIntDeep(obj) { if (bignumber_js_1.default.isBigNumber(obj)) return BigInt(obj.toFixed()); if (Array.isArray(obj)) return obj.map(bigNumberToBigIntDeep); if (!!obj && typeof obj === "object") return Object.fromEntries(Object.entries(obj) .filter(([_, value]) => value !== undefined) .map(([key, value]) => [key, bigNumberToBigIntDeep(value)])); return obj; } function findCryptoCurrencyByNetwork(network) { const networksRemap = { xrp: "ripple", }; return (0, currencies_1.findCryptoCurrencyById)(networksRemap[network] ?? network); } function extractBalance(balances, type) { return (balances.find(balance => balance.asset.type === type) ?? { asset: { type }, value: 0n, }); } function extractBalances(account, getAssetFromToken) { const balances = [ { value: BigInt(account.balance.toFixed()), asset: { type: "native" }, locked: BigInt(account.balance.minus(account.spendableBalance).toFixed()), }, ]; if (!account.subAccounts?.length || !getAssetFromToken) { return balances; } for (const subAccount of account.subAccounts) { const asset = getAssetFromToken(subAccount.token, account.freshAddress); balances.push({ value: BigInt(subAccount.balance.toFixed()), asset, locked: BigInt(subAccount.balance.minus(subAccount.spendableBalance).toFixed()), }); } return balances; } function isStringArray(value) { return Array.isArray(value) && value.every(item => typeof item === "string"); } function cleanedOperation(operation) { if (!operation.extra) return operation; const extraToClean = new Set([ "assetReference", "assetAmount", "assetOwner", "assetSenders", "assetRecipients", "parentSenders", "parentRecipients", "ledgerOpType", ]); const cleanedExtra = Object.fromEntries(Object.entries(operation.extra).filter(([key]) => !extraToClean.has(key))); return { ...operation, extra: cleanedExtra }; } function adaptCoreOperationToLiveOperation(accountId, op) { const opType = op.type; const extra = {}; if (op.details?.ledgerOpType !== undefined) { extra.ledgerOpType = op.details.ledgerOpType; } if (op.details?.assetAmount !== undefined) { extra.assetAmount = op.details.assetAmount; } if (isStringArray(op.details?.assetSenders)) { extra.assetSenders = op.details?.assetSenders; } if (isStringArray(op.details?.assetRecipients)) { extra.assetRecipients = op.details?.assetRecipients; } if (isStringArray(op.details?.parentSenders)) { extra.parentSenders = op.details?.parentSenders; } if (isStringArray(op.details?.parentRecipients)) { extra.parentRecipients = op.details?.parentRecipients; } if (op.asset?.type !== "native") { extra.assetReference = "assetReference" in (op.asset ?? {}) ? op.asset.assetReference : ""; extra.assetOwner = "assetOwner" in (op.asset ?? {}) ? op.asset.assetOwner : ""; } if (op.details?.memo) { extra.memo = op.details.memo; } if (op.details?.internal === true) { extra.internal = op.details?.internal; } if (typeof op.tx.feesPayer === "string") { extra.feePayer = op.tx.feesPayer; } const bnFees = new bignumber_js_1.default(op.tx.fees.toString()); const hasFailed = op.tx.failed; let value; if (hasFailed) { value = bnFees; } else if (op.asset.type === "native" && ["OUT", "FEES", "DELEGATE", "UNDELEGATE"].includes(opType)) { value = new bignumber_js_1.default(op.value.toString()).plus(bnFees); } else { value = new bignumber_js_1.default(op.value.toString()); } const res = { id: (0, operation_1.encodeOperationId)(accountId, op.tx.hash, op.type), hash: op.tx.hash, accountId, type: opType, value, fee: bnFees, blockHash: op.tx.block.hash, blockHeight: op.tx.block.height, senders: extra.parentSenders ?? op.senders, recipients: extra.parentRecipients ?? op.recipients, date: op.tx.date, transactionSequenceNumber: op.details?.sequence ? new bignumber_js_1.default(op.details?.sequence.toString()) : undefined, hasFailed, extra, }; return res; } /** * Default implementation of `computeIntentType` is a simple whitelist * with a fallback to "Payment" */ function defaultComputeIntentType(transaction) { if (!transaction.mode) return "Payment"; // NOTE: assuming payment by default here, can be changed based on transaction.mode const modeRemap = { delegate: "stake", undelegate: "unstake", }; const mode = modeRemap[transaction.mode] ?? transaction.mode; if (["changeTrust", "send", "send-legacy", "send-eip1559", "stake", "unstake"].includes(mode)) return mode; throw new Error(`Unsupported transaction mode: ${transaction.mode}`); } /** * Converts a transaction object into a `TransactionIntent` object, which is used to represent * the intent of a transaction in a standardized format. * * @template MemoType - The type of memo supported by the transaction, defaults to `MemoNotSupported`. * * @param account - The account initiating the transaction. Contains details such as the sender's address. * @param transaction - The transaction object containing details about the operation to be performed. * - `assetOwner` (optional): The issuer of the asset, if applicable. * - `assetReference` (optional): The code of the asset, if applicable. * - `mode` (optional): The mode of the transaction, e.g., "changetrust" or "send". * - `fees` (optional): The fees associated with the transaction. * - `memoType` (optional): The type of memo to attach to the transaction. * - `memoValue` (optional): The value of the memo to attach to the transaction. * @param computeIntentType - An optional function to compute the intent type that supersedes the default implementation if present * * @returns A `TransactionIntent` object containing the standardized representation of the transaction. * - Includes details such as type, sender, recipient, amount, fees, asset, and an optional memo. * - If `assetReference` and `assetOwner` are provided, the asset is represented as a token. * - If `memoType` and `memoValue` are provided, a memo is included; otherwise, a default memo of type "NO_MEMO" is added. * * @throws An error if the transaction mode is unsupported. */ function transactionToIntent(account, transaction, computeIntentType) { const intentType = (computeIntentType ?? defaultComputeIntentType)(transaction); const isStaking = ["stake", "unstake"].includes(intentType); const amount = isStaking ? 0n : (0, utils_1.fromBigNumberToBigInt)(transaction.amount, 0n); const useAllAmount = isStaking || !!transaction.useAllAmount; const res = { intentType: isStaking ? "staking" : "transaction", type: intentType, sender: account.freshAddress, recipient: transaction.recipient, amount, asset: { type: "native", name: account.currency.name, unit: account.currency.units[0] }, useAllAmount, feesStrategy: transaction.feesStrategy ?? undefined, data: Buffer.isBuffer(transaction.data) ? { type: "buffer", value: transaction.data } : { type: "none" }, sequence: transaction.nonce !== null && transaction.nonce !== undefined ? BigInt(transaction.nonce.toString()) : undefined, sponsored: transaction.sponsored, }; if (transaction.assetReference && transaction.assetOwner) { const { subAccountId } = transaction; const { subAccounts } = account; const tokenAccount = subAccountId ? subAccounts?.find(ta => ta.id === subAccountId) : null; res.asset = { type: tokenAccount?.token.tokenType ?? "token", assetReference: transaction.assetReference, name: tokenAccount?.token.name ?? transaction.assetReference, // NOTE: for stellar, assetReference = tokenAccount.name, this is futureproofing unit: account.currency.units[0], assetOwner: transaction.assetOwner, }; } if (transaction.memoType && transaction.memoValue) { res.memo = { type: transaction.memoType, value: transaction.memoValue, }; } else { res.memo = { type: "NO_MEMO" }; } return res; } function toFeeDataRaw(data) { return { gasPrice: data.gasPrice?.toFixed() ?? null, maxFeePerGas: data.maxFeePerGas?.toFixed() ?? null, maxPriorityFeePerGas: data.maxPriorityFeePerGas?.toFixed() ?? null, nextBaseFee: data.nextBaseFee?.toFixed() ?? null, }; } function toGasOptionRaw(options) { return { fast: toFeeDataRaw(options.fast), medium: toFeeDataRaw(options.medium), slow: toFeeDataRaw(options.slow), }; } function toGenericTransactionRaw(transaction) { const raw = { amount: transaction.amount.toString(), recipient: transaction.recipient, family: transaction.family, }; const booleanFieldsToPropagate = ["useAllAmount", "sponsored"]; for (const field of booleanFieldsToPropagate) { if (field in transaction) { raw[field] = transaction[field]; } } const stringFieldsToPropagate = [ "memoType", "memoValue", "assetReference", "assetOwner", ]; for (const field of stringFieldsToPropagate) { if (field in transaction) { raw[field] = transaction[field]; } } const numberFieldsToPropagate = ["tag", "type", "chainId"]; for (const field of numberFieldsToPropagate) { if (field in transaction) { raw[field] = transaction[field]; } } const bigNumberFieldsToPropagate = [ "fees", "storageLimit", "nonce", "gasLimit", "gasPrice", "maxFeePerGas", "maxPriorityFeePerGas", "additionalFees", ]; for (const field of bigNumberFieldsToPropagate) { if (field in transaction) { raw[field] = transaction[field]?.toFixed(); } } if ("customFees" in transaction) { raw.customFees = transaction.customFees && "fees" in transaction.customFees.parameters ? { parameters: { fees: transaction.customFees.parameters.fees?.toFixed() }, } : { parameters: {} }; } if ("feesStrategy" in transaction) { raw.feesStrategy = transaction.feesStrategy; } if ("mode" in transaction) { raw.mode = transaction.mode; } if ("data" in transaction) { raw.data = transaction.data?.toString("hex"); } if ("networkInfo" in transaction) { raw.networkInfo = transaction.networkInfo && { fees: transaction.networkInfo.fees.toFixed(), }; } if ("gasOptions" in transaction) { raw.gasOptions = transaction.gasOptions && toGasOptionRaw(transaction.gasOptions); } if ("recipientDomain" in transaction) { raw.recipientDomain = transaction.recipientDomain; } return raw; } const buildOptimisticOperation = (account, transaction, sequenceNumber) => { let type; switch (transaction.mode) { case "changeTrust": type = "OPT_IN"; break; case "delegate": case "stake": type = "DELEGATE"; break; case "undelegate": case "unstake": type = "UNDELEGATE"; break; default: type = "OUT"; break; } const fees = BigInt(transaction.fees?.toString() || "0"); const { subAccountId } = transaction; const { subAccounts } = account; const parentType = subAccountId ? "FEES" : type; const tokenAccount = subAccountId ? subAccounts?.find(ta => ta.id === subAccountId) : null; const operation = { id: (0, operation_1.encodeOperationId)(account.id, "", parentType), hash: "", type: parentType, value: subAccountId ? new bignumber_js_1.default(fees.toString()) : transaction.amount, // match old behavior fee: new bignumber_js_1.default(fees.toString()), blockHash: null, blockHeight: null, senders: [account.freshAddress.toString()], recipients: [transaction.recipient], transactionSequenceNumber: new bignumber_js_1.default(sequenceNumber?.toString() ?? 0), accountId: account.id, date: new Date(), transactionRaw: toGenericTransactionRaw({ ...transaction, nonce: sequenceNumber !== undefined ? new bignumber_js_1.default(sequenceNumber.toString()) : undefined, ...(tokenAccount ? { recipient: tokenAccount.token.contractAddress, amount: new bignumber_js_1.default(0) } : {}), }), extra: { ledgerOpType: type, blockTime: new Date(), index: "0", }, }; if (tokenAccount && subAccountId) { operation.subOperations = [ { id: `${subAccountId}--${type}`, hash: "", type, value: transaction.useAllAmount ? tokenAccount.balance : transaction.amount, fee: new bignumber_js_1.default(fees.toString()), blockHash: null, blockHeight: null, senders: [account.freshAddress], recipients: [transaction.recipient], transactionSequenceNumber: new bignumber_js_1.default(sequenceNumber?.toString() ?? 0), accountId: subAccountId, date: new Date(), transactionRaw: toGenericTransactionRaw({ ...transaction, nonce: sequenceNumber !== undefined ? new bignumber_js_1.default(sequenceNumber.toString()) : undefined, }), extra: { ledgerOpType: type, }, }, ]; } return operation; }; exports.buildOptimisticOperation = buildOptimisticOperation; /** * Applies memo information to transaction intent * Handles both destination tags (XRP-like) and Stellar-style memos */ function applyMemoToIntent(transactionIntent, transaction) { // Handle destination tag memo (for XRP-like chains) if (typeof transaction.tag === "number") { const txWithMemoTag = transactionIntent; const txMemo = String(transaction.tag); txWithMemoTag.memo = { type: "map", memos: new Map(), }; txWithMemoTag.memo.memos.set("destinationTag", txMemo); return txWithMemoTag; } // Handle Stellar-style memo if (transaction.memoType && transaction.memoValue) { const txWithMemo = transactionIntent; const txMemoType = String(transaction.memoType); const txMemoValue = String(transaction.memoValue); txWithMemo.memo = { type: txMemoType, value: txMemoValue, }; return txWithMemo; } return transactionIntent; } //# sourceMappingURL=utils.js.map