UNPKG

@ledgerhq/coin-stellar

Version:
179 lines 8.41 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.validateIntent = void 0; const errors_1 = require("@ledgerhq/errors"); const index_1 = require("@ledgerhq/coin-framework/currencies/index"); const bignumber_js_1 = __importDefault(require("bignumber.js")); const network_1 = require("../network"); const horizon_1 = require("../network/horizon"); const serialization_1 = require("../network/serialization"); const types_1 = require("../types"); const utils_1 = require("./utils"); const validateMemo_1 = require("./validateMemo"); const validateIntent = async (transactionIntent, balances, customFees) => { const errors = {}; const warnings = {}; const useAllAmount = !!transactionIntent.useAllAmount; const destinationNotExistMessage = new errors_1.NotEnoughBalanceBecauseDestinationNotCreated("", { minimalAmount: `${serialization_1.MIN_BALANCE} XLM`, }); const { spendableBalance, balance } = await (0, horizon_1.fetchAccount)(transactionIntent.sender); const networkInfo = await (0, network_1.fetchAccountNetworkInfo)(transactionIntent.sender); const estimatedFees = customFees?.value ?? 0n; const baseReserve = networkInfo.baseReserve ? BigInt(Math.round(networkInfo.baseReserve.toNumber() * 10)) / 10n : 0n; const isAssetPayment = transactionIntent.asset.type !== "native"; const nativeBalance = BigInt(balance.toString()); const nativeAmountAvailable = BigInt(spendableBalance.toString()) - estimatedFees; let amount = 0n; let maxAmount = 0n; let totalSpent = 0n; // Enough native balance to cover transaction (with required reserve + fees) if (!errors.amount && nativeAmountAvailable < 0) { errors.amount = new types_1.StellarNotEnoughNativeBalance(); } const networkInfoBaseFee = BigInt(networkInfo.baseFee.toString() || "0"); const networkFees = BigInt(networkInfo.fees.toString() || "0"); // Entered fee is smaller than base fee if (estimatedFees < networkInfoBaseFee) { errors.transaction = new types_1.StellarFeeSmallerThanBase(); // Entered fee is smaller than recommended } else if (estimatedFees < networkFees) { warnings.transaction = new types_1.StellarFeeSmallerThanRecommended(); } // Operation specific checks if (transactionIntent.type === "changeTrust") { // Check asset provided if (transactionIntent.asset.type !== "native" && (("assetReference" in transactionIntent.asset && !transactionIntent.asset.assetReference) || ("assetOwner" in transactionIntent.asset && !transactionIntent.asset.assetOwner))) { // This is unlikely errors.transaction = new types_1.StellarAssetRequired(""); } // Has enough native balance to add new trustline // NOTE: need to do this as BASE_RESERVE is 0.5 const SCALE = 10n; const scaledNative = nativeAmountAvailable * SCALE; const scaledBaseReserve = BigInt(serialization_1.BASE_RESERVE * 10); // = 5n if (scaledNative - scaledBaseReserve < 0n) { errors.amount = new types_1.StellarNotEnoughNativeBalanceToAddTrustline(); } } else { // Payment // Check recipient address if (!transactionIntent.recipient) { errors.recipient = new errors_1.RecipientRequired(""); } else if (!(0, utils_1.isAddressValid)(transactionIntent.recipient)) { errors.recipient = new errors_1.InvalidAddress("", { currencyName: transactionIntent.asset.name ?? "", // NOTE: before account.currencyName, }); } else if (transactionIntent.sender === transactionIntent.recipient) { errors.recipient = new errors_1.InvalidAddressBecauseDestinationIsAlsoSource(); } const recipientAccount = await (0, network_1.getRecipientAccount)({ recipient: transactionIntent.recipient, }); // Check recipient account if (!recipientAccount?.id && !errors.recipient && !warnings.recipient) { if (recipientAccount?.isMuxedAccount) { errors.recipient = new types_1.StellarMuxedAccountNotExist(); } else { if (isAssetPayment) { errors.recipient = destinationNotExistMessage; } else { warnings.recipient = destinationNotExistMessage; } } } // Asset payment if (isAssetPayment) { const asset = transactionIntent.asset; if (asset.type === "native" || (!("assetReference" in asset) && !("assetOwner" in asset))) { throw new types_1.StellarAssetNotFound(); } // Check recipient account accepts asset if (recipientAccount?.id && !errors.recipient && !warnings.recipient && !recipientAccount.assetIds.includes(`${asset.assetReference}:${asset.assetOwner}`)) { errors.recipient = new types_1.StellarAssetNotAccepted("", { assetCode: asset.assetReference, }); } const assetBalance = balances.find(b => b.asset.type !== "native" && "assetReference" in b.asset && b.asset.assetReference === asset.assetReference && "assetOwner" in b.asset && b.asset.assetOwner === asset.assetOwner); if (!assetBalance) { // This is unlikely throw new types_1.StellarAssetNotFound(); } const assetSpendableBalance = assetBalance.value - (assetBalance?.locked || 0n); maxAmount = assetSpendableBalance || assetBalance.value; amount = useAllAmount ? maxAmount : transactionIntent.amount; totalSpent = amount; if (!errors.amount && amount > assetBalance.value) { errors.amount = new errors_1.NotEnoughBalance(); } } else { // Native payment maxAmount = nativeAmountAvailable; amount = useAllAmount ? maxAmount : transactionIntent.amount ?? 0n; if (amount > maxAmount) { errors.amount = new errors_1.NotEnoughBalance(); } totalSpent = useAllAmount ? nativeAmountAvailable : amount + estimatedFees; // Need to send at least 1 XLM to create an account if (!errors.recipient && !recipientAccount?.id && !errors.amount && amount < 10000000n) { errors.amount = destinationNotExistMessage; } if (totalSpent > nativeBalance - baseReserve) { errors.amount = new errors_1.NotEnoughSpendableBalance(undefined, { minimumAmount: transactionIntent.asset.unit ? (0, index_1.formatCurrencyUnit)(transactionIntent.asset.unit, new bignumber_js_1.default(baseReserve.toString()), { disableRounding: true, showCode: true, }) : "Unknown unit", }); } if (!errors.recipient && !errors.amount && (amount < 0n || totalSpent > nativeBalance)) { errors.amount = new errors_1.NotEnoughBalance(); totalSpent = 0n; amount = 0n; } } if (amount === 0n) { errors.amount = new errors_1.AmountRequired(); } } if (await (0, utils_1.isAccountMultiSign)(transactionIntent.sender)) { errors.recipient = new types_1.StellarSourceHasMultiSign(); } if (transactionIntent?.memo?.type !== "NO_MEMO" && !(0, validateMemo_1.validateMemo)(transactionIntent?.memo?.value, transactionIntent?.memo?.type)) { errors.transaction = new types_1.StellarWrongMemoFormat(); } return { errors, warnings, estimatedFees, amount, totalSpent, }; }; exports.validateIntent = validateIntent; exports.default = exports.validateIntent; //# sourceMappingURL=validateIntent.js.map