@ledgerhq/live-common
Version:
Common ground for the Ledger Live apps
228 lines (203 loc) • 6.83 kB
text/typescript
import { getAccountCurrency, getFeesUnit } from "@ledgerhq/coin-framework/account/index";
import { formatCurrencyUnit } from "@ledgerhq/coin-framework/currencies/index";
import {
AmountRequired,
FeeNotLoaded,
NotEnoughBalanceSwap,
NotEnoughGas,
NotEnoughGasSwap,
} from "@ledgerhq/errors";
import { Account } from "@ledgerhq/types-live";
import { useEffect, useMemo, useState } from "react";
import useBridgeTransaction, { Result } from "../../../bridge/useBridgeTransaction";
import { Transaction } from "../../../generated/types";
import {
ExchangeRate,
OnNoRatesCallback,
SwapSelectorStateType,
SwapTransactionType,
} from "../types";
import { useFromState } from "./useFromState";
import { useReverseAccounts } from "./useReverseAccounts";
import { useToState } from "./useToState";
import { useUpdateMaxAmount } from "./useUpdateMaxAmount";
import { useProviderRates } from "./v5/useProviderRates";
export const selectorStateDefaultValues = {
currency: undefined,
account: undefined,
parentAccount: undefined,
amount: undefined,
};
export type SetExchangeRateCallback = (exchangeRate?: ExchangeRate) => void;
export const useFromAmountStatusMessage = (
{ account, parentAccount, status, transaction }: Result<Transaction>,
// The order of errors/warnings here will determine the precedence
statusTypeToInclude: string[],
): Error | undefined => {
const statusEntries = useMemo(
() => statusTypeToInclude.map(statusType => (status.errors || status.warnings)?.[statusType]),
[status.errors, status.warnings, statusTypeToInclude],
);
const currency = useMemo(() => {
if (parentAccount) {
return getAccountCurrency(parentAccount);
}
if (account) {
return getAccountCurrency(account);
}
return undefined;
}, [account, parentAccount]);
const estimatedFees = useMemo(() => {
return status.estimatedFees;
}, [status]);
return useMemo(() => {
// don't return an error/warning if we have no transaction or if transaction.amount <= 0
if (transaction?.amount.lte(0)) return undefined;
const [relevantStatus] = statusEntries
.filter(maybeError => maybeError instanceof Error)
.filter(errorOrWarning => !(errorOrWarning instanceof AmountRequired));
const isRelevantStatus = (relevantStatus as Error) instanceof NotEnoughGas;
if (isRelevantStatus && currency && estimatedFees) {
const query = new URLSearchParams({
// get account id first and set it equal to account.
// if parent account exists then overwrite the former.
...(account?.id ? { account: account.id } : {}),
...(parentAccount?.id ? { account: parentAccount.id } : {}),
});
return new NotEnoughGasSwap(undefined, {
fees: formatCurrencyUnit(getFeesUnit(currency), estimatedFees),
ticker: currency.ticker,
cryptoName: currency.name,
links: [`ledgerlive://buy?${query.toString()}`],
});
}
// convert to swap variation of error to display correct message to frontend.
if (relevantStatus instanceof FeeNotLoaded) {
return new NotEnoughBalanceSwap();
}
return relevantStatus;
}, [statusEntries, currency, estimatedFees, transaction?.amount, account?.id, parentAccount?.id]);
};
type UseSwapTransactionProps = {
accounts?: Account[];
setExchangeRate?: SetExchangeRateCallback;
defaultCurrency?: SwapSelectorStateType["currency"];
defaultAccount?: SwapSelectorStateType["account"];
defaultParentAccount?: SwapSelectorStateType["parentAccount"];
onNoRates?: OnNoRatesCallback;
excludeFixedRates?: boolean;
refreshRate?: number;
allowRefresh?: boolean;
isEnabled?: boolean;
};
export const useSwapTransaction = ({
accounts,
setExchangeRate,
defaultCurrency = selectorStateDefaultValues.currency,
defaultAccount = selectorStateDefaultValues.account,
defaultParentAccount = selectorStateDefaultValues.parentAccount,
onNoRates,
excludeFixedRates,
refreshRate,
allowRefresh,
isEnabled,
}: UseSwapTransactionProps): SwapTransactionType => {
const bridgeTransaction = useBridgeTransaction(() => ({
account: defaultAccount,
parentAccount: defaultParentAccount,
}));
const { fromState, setFromAccount, setFromAmount } = useFromState({
accounts,
defaultCurrency,
defaultAccount,
defaultParentAccount,
bridgeTransaction,
});
const { toState, setToAccount, setToAmount, setToCurrency, targetAccounts } = useToState({
accounts,
fromCurrencyAccount: fromState.account,
});
const {
account: fromAccount,
parentAccount: fromParentAccount,
currency: fromCurrency,
} = fromState;
const { account: toAccount } = toState;
const fromAmountError = useFromAmountStatusMessage(bridgeTransaction, [
"gasPrice",
"amount",
"gasLimit",
]);
const { isSwapReversable, reverseSwap } = useReverseAccounts({
accounts,
fromAccount,
toAccount,
fromParentAccount,
fromCurrency,
setFromAccount,
setToAccount,
});
const { isMaxEnabled, toggleMax, isMaxLoading } = useUpdateMaxAmount({
setFromAmount,
account: fromAccount,
parentAccount: fromParentAccount,
bridge: bridgeTransaction,
});
const { rates, refetchRates, updateSelectedRate, countdown } = useProviderRates({
fromState,
toState,
onNoRates,
setExchangeRate,
countdown: refreshRate,
allowRefresh,
isEnabled,
});
const [maxAmountLowerThanBallanceError, setMaxAmountLowerThanBallanceError] = useState<
Error | undefined
>(undefined);
useEffect(() => {
const timer = setTimeout(
() => {
// libs/coin-modules/coin-evm/src/prepareTransaction.ts L47
// returns 0 if the balance - fees is less than 0
const error =
isMaxEnabled && !isMaxLoading && fromState.amount?.eq(0)
? new NotEnoughBalanceSwap()
: undefined;
setMaxAmountLowerThanBallanceError(error);
},
isMaxEnabled ? 500 : 0,
);
// Cleanup the timeout if the component unmounts or the dependencies change
return () => clearTimeout(timer);
}, [isMaxEnabled, isMaxLoading, fromState]);
return {
...bridgeTransaction,
swap: {
to: toState,
from: fromState,
isMaxEnabled,
isMaxLoading,
isSwapReversable,
rates:
rates.value && excludeFixedRates
? {
...rates,
value: rates.value.filter(v => v.tradeMethod !== "fixed"),
}
: rates,
countdown,
refetchRates,
updateSelectedRate,
targetAccounts,
},
setFromAmount,
toggleMax,
fromAmountError: maxAmountLowerThanBallanceError || fromAmountError,
setToAccount,
setToCurrency,
setFromAccount,
setToAmount,
reverseSwap,
};
};