UNPKG

@ledgerhq/coin-algorand

Version:
175 lines (146 loc) 5.28 kB
import { isValidAddress } from "algosdk"; import { BigNumber } from "bignumber.js"; import invariant from "invariant"; import { AmountRequired, FeeNotLoaded, FeeTooHigh, InvalidAddress, InvalidAddressBecauseDestinationIsAlsoSource, NotEnoughBalance, NotEnoughBalanceBecauseDestinationNotCreated, NotEnoughBalanceInParentAccount, RecipientRequired, } from "@ledgerhq/errors"; import type { AccountBridge } from "@ledgerhq/types-live"; import { ClaimRewardsFeesWarning } from "@ledgerhq/errors"; import { AlgorandASANotOptInInRecipient } from "./errors"; import { ALGORAND_MAX_MEMO_SIZE, computeAlgoMaxSpendable, isAmountValid, recipientHasAsset, } from "./logic"; import { extractTokenId } from "./tokens"; import type { AlgorandAccount, Transaction, TransactionStatus } from "./types"; /* * Here are the list of the differents things we check * - Check if recipient is the same in case of send * - Check if recipient is valid * - Check if amounts are set * - Check if fees are loaded * - Check if is a send Max and set the amount * - Check if Token is already optin at the recipient * - Check if memo is too long */ export const getTransactionStatus: AccountBridge< Transaction, AlgorandAccount, TransactionStatus >["getTransactionStatus"] = async (account, transaction) => { const errors: Record<string, Error> = {}; const warnings: Record<string, Error> = {}; const tokenAccount = !transaction.subAccountId ? null : account.subAccounts && account.subAccounts.find(ta => ta.id === transaction.subAccountId); if (!transaction.recipient) { errors.recipient = new RecipientRequired(); } else if (!(await isValidAddress(transaction.recipient))) { errors.recipient = new InvalidAddress("", { currencyName: account.currency.name, }); } else if (transaction.mode === "send" && account.freshAddress === transaction.recipient) { errors.recipient = new InvalidAddressBecauseDestinationIsAlsoSource(); } const estimatedFees = transaction.fees || new BigNumber(0); let amount = transaction.amount; let totalSpent = estimatedFees; invariant((account as AlgorandAccount).algorandResources, "Algorand family expected"); const algorandResources = (account as AlgorandAccount).algorandResources; const algoSpendableBalance = computeAlgoMaxSpendable({ accountBalance: account.balance, nbAccountAssets: algorandResources.nbAssets, mode: transaction.mode, }); switch (transaction.mode) { case "send": { if (amount.lte(0) && !transaction.useAllAmount) { errors.amount = new AmountRequired(); } if (!transaction.fees || !transaction.fees.gt(0)) { errors.fees = new FeeNotLoaded(); } if ( tokenAccount && tokenAccount.type === "TokenAccount" && !errors.recipient && !(await recipientHasAsset(transaction.recipient, extractTokenId(tokenAccount.token.id))) ) { errors.recipient = new AlgorandASANotOptInInRecipient(); } amount = transaction.useAllAmount ? tokenAccount ? tokenAccount.balance : algoSpendableBalance.minus(estimatedFees) : amount; if (amount.lt(0)) { amount = new BigNumber(0); } totalSpent = tokenAccount ? amount : amount.plus(estimatedFees); if (!errors.recipient && !(await isAmountValid(transaction.recipient, amount))) { errors.amount = new NotEnoughBalanceBecauseDestinationNotCreated("", { minimalAmount: "0.1 ALGO", }); } if (!tokenAccount && amount.gt(0) && estimatedFees.times(10).gt(amount)) { warnings.feeTooHigh = new FeeTooHigh(); } if ( (amount.lte(0) && transaction.useAllAmount) || // if use all Amount sets an amount at 0 (!errors.recipient && !errors.amount && tokenAccount ? totalSpent.gt(tokenAccount.balance) : totalSpent.gt(algoSpendableBalance)) // if spendable balance lower than total ) { errors.amount = new NotEnoughBalance(); } // if spendable balance lower than fees for token if (!errors.amount && tokenAccount && algoSpendableBalance.lt(estimatedFees)) { errors.amount = new NotEnoughBalanceInParentAccount(); } break; } case "optIn": { if (!transaction.fees || !transaction.fees.gt(0)) { errors.fees = new FeeNotLoaded(); } // This error doesn't need to be translate, // it will use to block until the user choose an assetId if (!transaction.assetId) { errors.assetId = new Error("Asset Id is not set"); } if (algoSpendableBalance.lt(estimatedFees)) { errors.amount = new NotEnoughBalance(); } break; } case "claimReward": { if (algoSpendableBalance.lt(totalSpent)) { errors.amount = new NotEnoughBalance(); } if (estimatedFees.gt(algorandResources.rewards)) { warnings.claimReward = new ClaimRewardsFeesWarning(); } break; } } if (transaction.memo && transaction.memo.length > ALGORAND_MAX_MEMO_SIZE) { throw new Error("Memo is too long"); } return { errors, warnings, estimatedFees, amount, totalSpent, }; };