@ledgerhq/coin-algorand
Version:
Ledger Algorand Coin integration
140 lines • 5.53 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateIntent = validateIntent;
const errors_1 = require("@ledgerhq/errors");
const algosdk_1 = require("algosdk");
const errors_2 = require("../errors");
const network_1 = require("../network");
const common_1 = require("./common");
const validateMemo_1 = require("./validateMemo");
/**
* Validate a transaction intent for Algorand
* @param intent - The transaction intent
* @param balances - Current account balances
* @param customFees - Optional custom fees
* @returns Validation result with errors, warnings, and amounts
*/
async function validateIntent(intent, balances, customFees) {
const errors = {};
const warnings = {};
const fees = customFees?.value ?? 0n;
let amount = intent.amount;
// Validate recipient
if (!intent.recipient) {
errors.recipient = new errors_1.RecipientRequired();
}
else if (!(0, algosdk_1.isValidAddress)(intent.recipient)) {
errors.recipient = new errors_1.InvalidAddress();
}
else if (intent.sender === intent.recipient) {
errors.recipient = new errors_1.InvalidAddressBecauseDestinationIsAlsoSource();
}
// Get native balance
const nativeBalance = balances.find(b => b.asset.type === "native");
const balance = nativeBalance?.value ?? 0n;
const locked = nativeBalance?.locked ?? 0n;
// Check for token transfer
const isTokenTransfer = intent.asset.type !== "native";
let tokenBalance;
if (isTokenTransfer) {
const intentAssetRef = intent.asset.assetReference;
tokenBalance = balances.find(b => {
if (b.asset.type !== "asa")
return false;
const balanceAssetRef = b.asset.assetReference;
return balanceAssetRef === intentAssetRef;
});
if (!tokenBalance) {
errors.amount = new errors_1.NotEnoughBalance();
}
}
// Validate amount
if (amount <= 0n && !intent.useAllAmount) {
errors.amount = new errors_1.AmountRequired();
}
// Handle useAllAmount
if (intent.useAllAmount) {
if (isTokenTransfer && tokenBalance) {
amount = tokenBalance.value;
}
else {
const spendable = balance - locked - fees;
amount = spendable > 0n ? spendable : 0n;
}
}
// Calculate total spent
const totalSpent = isTokenTransfer ? amount : amount + fees;
// Check balance
if (!errors.amount) {
if (isTokenTransfer) {
// Check token balance
if (tokenBalance && amount > tokenBalance.value) {
errors.amount = new errors_1.NotEnoughBalance();
}
// Check native balance for fees
if (fees > balance - locked) {
errors.amount = new errors_1.NotEnoughBalanceInParentAccount();
}
}
else {
// Check native balance
const spendable = balance - locked;
if (totalSpent > spendable) {
errors.amount = new errors_1.NotEnoughBalance();
}
}
}
// Validate recipient account (fetch once for both ASA opt-in and native minimum balance checks)
if (!errors.recipient && intent.recipient) {
try {
const recipientAccount = await (0, network_1.getAccount)(intent.recipient);
if (isTokenTransfer) {
// Check if recipient has opted in to the ASA token
const intentAssetRef = intent.asset.assetReference;
if (intentAssetRef) {
const hasOptedIn = recipientAccount.assets.map(a => a.assetId).includes(intentAssetRef);
if (!hasOptedIn) {
errors.recipient = new errors_2.AlgorandASANotOptInInRecipient();
}
}
}
else if (amount > 0n) {
// Check minimum balance requirement for native transfers
const recipientBalance = BigInt(recipientAccount.balance.toString());
if (recipientBalance === 0n && amount < common_1.ALGORAND_MIN_ACCOUNT_BALANCE) {
errors.amount = new errors_1.NotEnoughBalanceBecauseDestinationNotCreated("", {
minimalAmount: "0.1 ALGO",
});
}
}
}
catch {
// Handle account fetch error
if (isTokenTransfer) {
// If we can't fetch the account, assume it doesn't exist and hasn't opted in
errors.recipient = new errors_2.AlgorandASANotOptInInRecipient();
}
else if (amount > 0n) {
// Account doesn't exist yet, need minimum balance for native transfer
if (amount < common_1.ALGORAND_MIN_ACCOUNT_BALANCE) {
errors.amount = new errors_1.NotEnoughBalanceBecauseDestinationNotCreated("", {
minimalAmount: "0.1 ALGO",
});
}
}
}
}
// Validate memo
const memoValue = intent.memo?.type === "string" ? intent.memo.value : undefined;
if (memoValue && !(0, validateMemo_1.validateMemo)(memoValue)) {
errors.transaction = new errors_2.AlgorandMemoExceededSizeError();
}
return {
errors,
warnings,
estimatedFees: fees,
amount,
totalSpent,
};
}
//# sourceMappingURL=validateIntent.js.map