import { BigNumber } from "bignumber.js"; import { NotEnoughBalance, RecipientRequired, InvalidAddress, FeeTooHigh, InvalidAddressBecauseDestinationIsAlsoSource, NotSupportedLegacyAddress, NotEnoughBalanceInParentAccount, AmountRequired, RecommendUndelegation, RecommendSubAccountsToEmpty, NotEnoughBalanceToDelegate, } from "@ledgerhq/errors"; import type { TezosAccount, Transaction } from "../types"; import type { Account, AccountBridge, AccountLike, CurrencyBridge } from "@ledgerhq/types-live"; import { getMainAccount } from "../../../account"; import { scanAccounts, signOperation, signRawOperation, broadcast, sync, isInvalidRecipient, makeAccountBridgeReceive, } from "../../../bridge/mockHelpers"; import { getSerializedAddressParameters, updateTransaction, } from "@ledgerhq/ledger-wallet-framework/bridge/jsHelpers"; import { isAccountDelegating } from "../staking"; import { validateAddress } from "../../../bridge/validateAddress"; const isAccountBalanceSignificant = (a: AccountLike): boolean => a.balance.gt(100); const receive = makeAccountBridgeReceive(); const estimateGasLimitAndStorage = () => { const storage = new BigNumber(257); const gasLimit = new BigNumber(10600); return { storage, gasLimit, }; }; const defaultGetFees = (a, t: any) => (t.fees || new BigNumber(0)).times(t.gasLimit || new BigNumber(0)); const estimateMaxSpendable = ({ account, parentAccount, transaction }) => { const mainAccount = getMainAccount(account, parentAccount); const estimatedFees = transaction ? defaultGetFees(mainAccount, transaction) : new BigNumber(10); return Promise.resolve(BigNumber.max(0, account.balance.minus(estimatedFees))); }; const createTransaction = (): Transaction => ({ family: "tezos", mode: "send", amount: new BigNumber(0), fees: null, gasLimit: null, storageLimit: null, recipient: "", networkInfo: null, useAllAmount: false, taquitoError: null, estimatedFees: null, }); const getTransactionStatus = (a: Account, t: Transaction) => { const errors: { recipient?: Error; amount?: Error; } = {}; const warnings: { amount?: Error; feeTooHigh?: Error; } = {}; const subAcc = !t.subAccountId ? null : a.subAccounts && a.subAccounts.find(ta => ta.id === t.subAccountId); const account = subAcc || a; if (t.mode !== "undelegate") { if ((account as TezosAccount).freshAddress === t.recipient) { errors.recipient = new InvalidAddressBecauseDestinationIsAlsoSource(); } else { if (!t.recipient) { // Fill up recipient errors... errors.recipient = new RecipientRequired(""); } else if (isInvalidRecipient(t.recipient)) { errors.recipient = new InvalidAddress(""); } else if (t.recipient.startsWith("KT")) { errors.recipient = new NotSupportedLegacyAddress(); } } } let amount = t.amount; // FIXME: maybe we need this // if (!t.fees) { // errors.fees = new FeeNotLoaded(); // } else if (!errors.recipient) { // estimatedFees = defaultGetFees(a, t); // } const estimatedFees = defaultGetFees(a, t); const useAllAmount = !!t.useAllAmount; let totalSpent = useAllAmount ? account.balance : subAcc ? new BigNumber(t.amount) : new BigNumber(t.amount).plus(estimatedFees); amount = useAllAmount ? subAcc ? new BigNumber(t.amount) : account.balance.minus(estimatedFees) : new BigNumber(t.amount); if (amount.gt(0) && estimatedFees.times(10).gt(amount)) { warnings.feeTooHigh = new FeeTooHigh(); } if (!errors.amount && subAcc && estimatedFees.gt(a.balance)) { errors.amount = new NotEnoughBalanceInParentAccount(); } if (!errors.recipient && !errors.amount && (amount.lt(0) || totalSpent.gt(account.balance))) { errors.amount = new NotEnoughBalance(); totalSpent = new BigNumber(0); amount = new BigNumber(0); } if (t.mode === "send") { if (!errors.amount && amount.eq(0)) { errors.amount = new AmountRequired(); } else if (amount.gt(0) && estimatedFees.times(10).gt(amount)) { warnings.feeTooHigh = new FeeTooHigh(); } const thresholdWarning = 0.5 * 10 ** a.currency.units[0].magnitude; if (!subAcc && !errors.amount && account.balance.minus(totalSpent).lt(thresholdWarning)) { if (isAccountDelegating(account)) { warnings.amount = new RecommendUndelegation(); } else if ((a.subAccounts || []).some(isAccountBalanceSignificant)) { warnings.amount = new RecommendSubAccountsToEmpty(); } } } else { // delegation case, we remap NotEnoughBalance to a more precise error if (errors.amount instanceof NotEnoughBalance) { errors.amount = new NotEnoughBalanceToDelegate(); } } return Promise.resolve({ errors, warnings, estimatedFees, amount, totalSpent, }); }; const prepareTransaction = async (a, t) => { let networkInfo = t.networkInfo; if (!networkInfo) { const ni = { family: "tezos", fees: t.fees || new BigNumber(0), }; networkInfo = ni; } let gasLimit = t.gasLimit; let storageLimit = t.storageLimit; if (!gasLimit || storageLimit) { if (t.mode === "undelegate" || isInvalidRecipient(t.recipient)) { const r = estimateGasLimitAndStorage(); gasLimit = r.gasLimit; storageLimit = r.storage; } } const fees = t.fees || networkInfo.fees; if ( t.networkInfo !== networkInfo || t.gasLimit !== gasLimit || t.storageLimit !== storageLimit || t.fees !== fees ) { return { ...t, networkInfo, storageLimit, gasLimit, fees }; } return t; }; const accountBridge: AccountBridge = { createTransaction, updateTransaction, getTransactionStatus, estimateMaxSpendable, prepareTransaction, sync, receive, signOperation, signRawOperation, broadcast, getSerializedAddressParameters, validateAddress, }; const currencyBridge: CurrencyBridge = { preload: () => Promise.resolve({}), hydrate: () => {}, scanAccounts, }; export default { currencyBridge, accountBridge, };