@ledgerhq/live-common
Version:
Common ground for the Ledger Live apps
198 lines • 8.04 kB
JavaScript
import { encodeOperationId } from "@ledgerhq/coin-framework/operation";
import BigNumber from "bignumber.js";
import { fromBigNumberToBigInt } from "@ledgerhq/coin-framework/utils";
import { findCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies";
export function findCryptoCurrencyByNetwork(network) {
const networksRemap = {
xrp: "ripple",
};
return findCryptoCurrencyById(networksRemap[network] ?? network);
}
export function extractBalance(balances, type) {
return (balances.find(balance => balance.asset.type === type) ?? {
asset: { type },
value: 0n,
});
}
export 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 (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;
}
const bnFees = new BigNumber(op.tx.fees.toString());
const res = {
id: encodeOperationId(accountId, op.tx.hash, op.type),
hash: op.tx.hash,
accountId,
type: opType,
value: op.asset.type === "native" && ["OUT", "FEES", "DELEGATE", "UNDELEGATE"].includes(opType)
? new BigNumber(op.value.toString()).plus(bnFees)
: new BigNumber(op.value.toString()),
fee: bnFees,
blockHash: op.tx.block.hash,
blockHeight: op.tx.block.height,
senders: op.senders,
recipients: op.recipients,
date: op.tx.date,
transactionSequenceNumber: op.details?.sequence,
hasFailed: op.details?.status === "failed",
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.
*/
export function transactionToIntent(account, transaction, computeIntentType) {
const intentType = (computeIntentType ?? defaultComputeIntentType)(transaction);
const isStaking = ["stake", "unstake"].includes(intentType);
const amount = isStaking ? 0n : 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" },
};
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,
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;
}
export 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 operation = {
id: encodeOperationId(account.id, "", type),
hash: "",
type: type,
value: subAccountId ? new BigNumber(fees.toString()) : transaction.amount,
fee: new BigNumber(fees.toString()),
blockHash: null,
blockHeight: null,
senders: [account.freshAddress.toString()],
recipients: [transaction.recipient],
transactionSequenceNumber: sequenceNumber ?? 0,
accountId: account.id,
date: new Date(),
extra: {
ledgerOpType: type,
blockTime: new Date(),
index: "0",
},
};
const tokenAccount = subAccountId ? subAccounts?.find(ta => ta.id === subAccountId) : null;
if (tokenAccount && subAccountId) {
operation.subOperations = [
{
id: `${subAccountId}--OUT`,
hash: "",
type: "OUT",
value: transaction.useAllAmount ? tokenAccount.balance : transaction.amount,
fee: new BigNumber(0),
blockHash: null,
blockHeight: null,
senders: [account.freshAddress],
recipients: [transaction.recipient],
accountId: subAccountId,
date: new Date(),
extra: {
ledgerOpType: type,
},
},
];
}
return operation;
};
//# sourceMappingURL=utils.js.map