@ledgerhq/coin-stellar
Version:
Ledger Stellar Coin integration
179 lines • 8.41 kB
JavaScript
"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