UNPKG

@ledgerhq/live-common

Version:
186 lines (161 loc) 5.62 kB
import network from "@ledgerhq/live-network/network"; import type { Unit } from "@ledgerhq/types-cryptoassets"; import { BigNumber } from "bignumber.js"; import { getAccountCurrency } from "../../account"; import { formatCurrencyUnit } from "../../currencies"; import { SwapExchangeRateAmountTooHigh, SwapExchangeRateAmountTooLow, SwapExchangeRateAmountTooLowOrTooHigh, } from "../../errors"; import { getSwapAPIBaseURL, getSwapAPIError, getSwapUserIP } from "./"; import { mockGetExchangeRates } from "./mock"; import type { CustomMinOrMaxError, GetExchangeRates } from "./types"; import { isIntegrationTestEnv } from "./utils/isIntegrationTestEnv"; import { getSwapProvider } from "../providers/swap"; const getExchangeRates: GetExchangeRates = async ({ exchange, transaction, currencyTo, providers = [], timeout, timeoutErrorMessage, }) => { if (isIntegrationTestEnv()) return mockGetExchangeRates(exchange, transaction, currencyTo); const from = getAccountCurrency(exchange.fromAccount).id; const unitFrom = getAccountCurrency(exchange.fromAccount).units[0]; const to = (currencyTo ?? getAccountCurrency(exchange.toAccount)).id; const unitTo = (currencyTo && currencyTo.units[0]) ?? getAccountCurrency(exchange.toAccount).units[0]; const amountFrom = transaction.amount; const tenPowMagnitude = new BigNumber(10).pow(unitFrom.magnitude); const apiAmount = new BigNumber(amountFrom).div(tenPowMagnitude); const providerList = providers .filter(provider => provider.pairs.some(pair => pair.from === from && pair.to === to)) .map(item => item.provider); const request = { from, to, amountFrom: apiAmount.toString(), providers: providerList, }; const headers = getSwapUserIP(); const res = await network({ method: "POST", url: `${getSwapAPIBaseURL()}/rate`, ...(timeout ? { timeout } : {}), ...(timeoutErrorMessage ? { timeoutErrorMessage } : {}), data: request, ...(headers !== undefined ? headers : {}), }); const rates = res.data.map(async responseData => { const { rate: maybeRate, payoutNetworkFees: maybePayoutNetworkFees, rateId, provider, providerType, amountFrom, amountTo, tradeMethod, providerURL, expirationTime, } = responseData; const error = await inferError(apiAmount, unitFrom, responseData); if (error) { return { provider, tradeMethod, error, }; } const payoutNetworkFees = new BigNumber(maybePayoutNetworkFees || 0); const magnitudeAwarePayoutNetworkFees = payoutNetworkFees.times( new BigNumber(10).pow(unitTo.magnitude), ); const rate = maybeRate ? new BigNumber(maybeRate) : new BigNumber(amountTo).minus(payoutNetworkFees).div(amountFrom); // NB Allows us to simply multiply satoshi values from/to const magnitudeAwareRate = rate.div( new BigNumber(10).pow(unitFrom.magnitude - unitTo.magnitude), ); const toAmount = new BigNumber(amountTo).minus(payoutNetworkFees); const magnitudeAwareToAmount = toAmount.times(new BigNumber(10).pow(unitTo.magnitude)); // Nb no longer need to break it down on UI const out = { magnitudeAwareRate, provider, providerType, rate, rateId, expirationTime, toAmount: magnitudeAwareToAmount, tradeMethod, providerURL, }; if (tradeMethod === "fixed") { return { ...out, payoutNetworkFees: magnitudeAwarePayoutNetworkFees, rateId, }; } else { return { ...out, payoutNetworkFees: magnitudeAwarePayoutNetworkFees, }; } }); return rates; }; const inferError = async ( apiAmount: BigNumber, unitFrom: Unit, responseData: { amountTo: string; minAmountFrom: string; maxAmountFrom: string; errorCode?: number; errorMessage?: string; status?: string; provider: string; }, ): Promise<Error | CustomMinOrMaxError | undefined> => { const tenPowMagnitude = new BigNumber(10).pow(unitFrom.magnitude); const { amountTo, minAmountFrom, maxAmountFrom, errorCode, errorMessage, provider, status } = responseData; const isDex = (await getSwapProvider(provider)).type === "DEX"; // DEX quotes are out of limits error. We do not know if it is a low or high limit, neither the amount. if ((!minAmountFrom || !maxAmountFrom) && status === "error" && errorCode !== 300 && isDex) { return new SwapExchangeRateAmountTooLowOrTooHigh(undefined, { message: "", }); } if (!amountTo) { // We are in an error case regardless of api version. if (errorCode) { return getSwapAPIError(errorCode, errorMessage); } // For out of range errors we will have a min/max pairing const hasAmountLimit = minAmountFrom || maxAmountFrom; if (hasAmountLimit) { const isTooSmall = minAmountFrom ? new BigNumber(apiAmount).lte(minAmountFrom) : false; const MinOrMaxError = isTooSmall ? SwapExchangeRateAmountTooLow : SwapExchangeRateAmountTooHigh; const key: string = isTooSmall ? "minAmountFromFormatted" : "maxAmountFromFormatted"; const amount = isTooSmall ? minAmountFrom : maxAmountFrom; return new MinOrMaxError(undefined, { [key]: formatCurrencyUnit(unitFrom, new BigNumber(amount).times(tenPowMagnitude), { alwaysShowSign: false, disableRounding: true, showCode: true, }), amount: new BigNumber(amount), }); } } return; }; export default getExchangeRates;