UNPKG

@ledgerhq/coin-canton

Version:
146 lines 6.75 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getTransactionStatus = exports.TO_MANY_UTXOS_WARNING_COUNT = exports.TO_MANY_UTXOS_CRITICAL_COUNT = void 0; exports.validateTopology = validateTopology; const index_1 = require("@ledgerhq/coin-module-framework/currencies/index"); const errors_1 = require("@ledgerhq/ledger-wallet-framework/errors"); const bignumber_js_1 = __importDefault(require("bignumber.js")); const utils_1 = require("../common-logic/utils"); const config_1 = __importDefault(require("../config")); const constants_1 = require("../constants"); const gateway_1 = require("../network/gateway"); const types_1 = require("../types"); const errors_2 = require("../types/errors"); exports.TO_MANY_UTXOS_CRITICAL_COUNT = 24; exports.TO_MANY_UTXOS_WARNING_COUNT = 10; const getTransactionStatus = async (account, transaction) => { const errors = {}; const warnings = {}; // reserveAmount is the minimum amount of currency that an account must hold in order to stay activated const reserveAmount = new bignumber_js_1.default(config_1.default.getCoinConfig(account.currency.id).minReserve || 0); const estimatedFees = new bignumber_js_1.default(transaction.fee || 0); const amount = new bignumber_js_1.default(transaction.amount); const isTokenTransaction = !!transaction.subAccountId; // For token transactions, fees are paid from parent account, so totalSpent is just amount const totalSpent = isTokenTransaction ? amount : amount.plus(estimatedFees); // Get the account balance to check against (subAccount for tokens, main account for native) let accountBalance = account.spendableBalance; if (isTokenTransaction && account.subAccounts) { const subAccount = account.subAccounts.find(sub => sub.type === "TokenAccount" && sub.id === transaction.subAccountId); accountBalance = subAccount?.spendableBalance ?? new bignumber_js_1.default(0); } if (amount.gt(0) && estimatedFees.times(10).gt(amount) && !isTokenTransaction) { // if the fee is more than 10 times the amount, we warn the user that fee is high compared to what he is sending warnings.feeTooHigh = new errors_1.FeeTooHigh(); } if (transaction.fee === null || transaction.fee === undefined) { // if the fee is not loaded, we can't do much errors.fee = new errors_1.FeeNotLoaded(); } if (!transaction.recipient) { errors.recipient = new errors_1.RecipientRequired(""); } else if (!(0, utils_1.isRecipientValid)(transaction.recipient)) { // We want to prevent user from sending to an invalid address errors.recipient = new errors_1.InvalidAddress("", { currencyName: account.currency.name, }); } if (amount.eq(0)) { // if the amount is 0, we prevent the user from sending the tx (even if it's technically feasible) errors.amount = new errors_1.AmountRequired(); } // For token transactions, check that parent account has enough native balance to pay fees if (isTokenTransaction && estimatedFees.gt(account.balance)) { errors.amount = new errors_1.NotEnoughBalanceInParentAccount(); } // Check if total spent exceeds available balance if (!errors.amount && totalSpent.gt(accountBalance)) { errors.amount = new errors_1.NotEnoughBalance(); } // For native coin transactions, check reserve amount constraints if (!isTokenTransaction && !errors.amount) { if (totalSpent.gt(account.balance.minus(reserveAmount))) { errors.amount = new errors_1.NotEnoughSpendableBalance("", { minimumAmount: (0, index_1.formatCurrencyUnit)(account.currency.units[0], reserveAmount, { disableRounding: true, useGrouping: false, showCode: true, }), }); } else if (transaction.recipient && amount.lt(reserveAmount)) { // if we send an amount lower than reserve amount AND target account is new, we need to warn the user that the target account will not be activated errors.amount = new errors_1.NotEnoughBalanceBecauseDestinationNotCreated("", { minimalAmount: (0, index_1.formatCurrencyUnit)(account.currency.units[0], reserveAmount, { disableRounding: true, useGrouping: false, showCode: true, }), }); } } const utxoWarning = validateUtxoCount(account, transaction); if (utxoWarning) { warnings.tooManyUtxos = utxoWarning; } const topologyError = await validateTopology(account); if (topologyError) { errors.topologyChange = topologyError; } return { errors, warnings, estimatedFees, amount, totalSpent, }; }; exports.getTransactionStatus = getTransactionStatus; function validateUtxoCount(account, transaction) { if (!account.cantonResources?.instrumentUtxoCounts) { return null; } if (!transaction.recipient) { return null; } if (!(0, utils_1.isRecipientValid)(transaction.recipient) || account.xpub === transaction.recipient) { return null; } if (transaction.recipient.includes(constants_1.CANTON_UNSET_RECIPIENT)) { return null; } const { instrumentUtxoCounts } = account.cantonResources; const instrumentKey = transaction.instrumentAdmin ? `${transaction.tokenId}-${transaction.instrumentAdmin}` : transaction.tokenId; const instrumentUtxoCount = instrumentUtxoCounts[instrumentKey] || 0; if (instrumentUtxoCount > exports.TO_MANY_UTXOS_CRITICAL_COUNT) { return new types_1.TooManyUtxosCritical(); } if (instrumentUtxoCount > exports.TO_MANY_UTXOS_WARNING_COUNT) { return new types_1.TooManyUtxosWarning("families.canton.tooManyUtxos.warning"); } return null; } async function validateTopology(account) { const publicKey = account.cantonResources?.publicKey; if (!publicKey) { return null; } try { const isTopologyChangeRequired = await (0, gateway_1.isTopologyChangeRequiredCached)(account.currency, publicKey); if (!isTopologyChangeRequired) { return null; } return new errors_2.TopologyChangeError("Topology change detected. Re-onboarding required."); } catch { // If topology check fails, don't block the transaction return null; } } //# sourceMappingURL=getTransactionStatus.js.map