@ledgerhq/live-common
Version:
Common ground for the Ledger Live apps
131 lines • 5.9 kB
JavaScript
import { getAlpacaApi } from "./alpaca";
import { getBridgeApi } from "./bridge";
import { bigNumberToBigIntDeep, extractBalances, transactionToIntent } from "./utils";
import BigNumber from "bignumber.js";
import { decodeTokenAccountId } from "@ledgerhq/ledger-wallet-framework/account/index";
function bnEq(a, b) {
return !a && !b ? true : !a || !b ? false : a.eq(b);
}
function assetInfosFallback(transaction) {
return {
assetReference: transaction.assetReference ?? "",
assetOwner: transaction.assetOwner ?? "",
};
}
function propagateField(estimation, field, dest) {
const value = estimation?.parameters?.[field];
if (typeof value !== "bigint" && typeof value !== "number" && typeof value !== "string")
return;
switch (field) {
case "type":
dest[field] = Number(value.toString());
return;
case "storageLimit":
case "gasLimit":
case "gasPrice":
case "maxFeePerGas":
case "maxPriorityFeePerGas":
case "additionalFees":
dest[field] = new BigNumber(value.toString());
return;
default:
return;
}
}
export function genericPrepareTransaction(network, kind) {
return async (account, transaction) => {
const { computeIntentType, estimateFees, validateIntent } = getAlpacaApi(account.currency.id, kind);
const bridgeApi = getBridgeApi(account.currency, network);
const getAssetFromTokenForCurrency = bridgeApi.getAssetFromToken;
const { assetReference, assetOwner } = getAssetFromTokenForCurrency
? await getAssetInfos(transaction, account.freshAddress, getAssetFromTokenForCurrency)
: assetInfosFallback(transaction);
const customParametersFees = transaction.customFees?.parameters?.fees;
/**
* Ticking `useAllAmount` constantly resets the amount to 0. This is problematic
* because some Blockchain need the actual transaction amount to compute the fees
* (Example with EVM and ERC20 transactions)
* In case of `useAllAmount` and token transaction, we read the token account spendable
* balance instead.
*/
let amount = transaction.amount;
if (transaction.useAllAmount && transaction.subAccountId) {
const subAccount = account.subAccounts?.find(acc => acc.id === transaction.subAccountId);
amount = subAccount?.spendableBalance ?? amount;
}
// Pass any parameters that help estimating fees
// This includes `assetOwner` and `assetReference` that are not used by some apps that only rely on `subAccountId`
// TODO Remove `assetOwner` and `assetReference` in order to maintain one unique way of identifying the type of asset
// https://ledgerhq.atlassian.net/browse/LIVE-24044
const intent = transactionToIntent(account, {
...transaction,
assetOwner,
assetReference,
amount,
}, computeIntentType);
const customFeesParameters = bigNumberToBigIntDeep({
gasPrice: transaction.gasPrice,
maxFeePerGas: transaction.maxFeePerGas,
maxPriorityFeePerGas: transaction.maxPriorityFeePerGas,
gasLimit: transaction.customGasLimit,
gasOptions: transaction.gasOptions,
});
const estimation = customParametersFees
? { value: BigInt(customParametersFees.toFixed()) }
: await estimateFees(intent, customFeesParameters);
const fees = new BigNumber(estimation.value.toString());
if (!bnEq(transaction.fees, fees)) {
const next = {
...transaction,
fees,
assetReference,
assetOwner,
customFees: {
parameters: {
fees: customParametersFees ? new BigNumber(customParametersFees.toString()) : undefined,
},
},
};
// Propagate needed fields
const fieldsToPropagate = [
"type",
"storageLimit",
"gasPrice",
// gas limit must not change in case it is custom
...(transaction.customGasLimit ? [] : ["gasLimit"]),
"maxFeePerGas",
"maxPriorityFeePerGas",
"additionalFees",
];
for (const field of fieldsToPropagate) {
propagateField(estimation, field, next);
}
// align with stellar/xrp: when send max (or staking intents), reflect validated amount in UI
if (transaction.useAllAmount || ["stake", "unstake"].includes(transaction.mode ?? "")) {
// TODO Remove the call to `validateIntent` https://ledgerhq.atlassian.net/browse/LIVE-22228
const { amount } = await validateIntent(transactionToIntent(account, {
...transaction,
assetOwner,
assetReference,
}, computeIntentType), extractBalances(account, getAssetFromTokenForCurrency));
next.amount = new BigNumber(amount.toString());
}
return next;
}
return transaction;
};
}
export async function getAssetInfos(tr, owner, getAssetFromToken) {
if (tr.subAccountId) {
const { token } = await decodeTokenAccountId(tr.subAccountId);
if (!token)
return assetInfosFallback(tr);
const asset = getAssetFromToken(token, owner);
return {
assetOwner: ("assetOwner" in asset && asset.assetOwner) || "",
assetReference: ("assetReference" in asset && asset.assetReference) || "",
};
}
return assetInfosFallback(tr);
}
//# sourceMappingURL=prepareTransaction.js.map