UNPKG

@ledgerhq/live-common

Version:
123 lines (104 loc) 4.05 kB
import { Account } from "./enum/Account"; import { sanitizeError } from "./index"; import axios, { AxiosRequestConfig } from "axios"; // Target a sensible USD amount that works for most pairs const FALLBACK_TARGET_USD = 50; const COUNTERVALUES_URL = "https://countervalues.live.ledger.com/v3/spot/simple"; const SWAP_QUOTE_URL = "https://swap-stg.ledger-test.com/v5/quote"; /** Smallest amount sent to the quote API to discover minimum thresholds. */ const PROBE_AMOUNT = 0.0001; const PROBE_NETWORK_FEES = 0.001; const PROVIDERS_WHITELIST = "changelly_v2,exodus,thorswap,uniswap,cic_v2,nearintents,swapsxyz"; type QuoteErrorItem = { parameter?: { minAmount?: string }; }; function isQuoteErrorItem(item: unknown): item is QuoteErrorItem { return typeof item === "object" && item !== null && "parameter" in item; } /** * Fetches the current USD price for a currency from the Ledger countervalues API * and converts a target USD value into the equivalent crypto amount. */ export async function getAmountFromUSD( currencyId: string, targetUSD: number, ): Promise<number | null> { try { const { data } = await axios.get(COUNTERVALUES_URL, { params: { froms: currencyId, to: "USD", }, }); const price = data?.[currencyId]; if (!price || price <= 0) { console.warn(`No USD price found for ${currencyId}`); return null; } return targetUSD / price; } catch (error: unknown) { console.warn(`Failed to fetch countervalue for ${currencyId}:`, sanitizeError(error)); return null; } } export async function getMinimumSwapAmount( accountFrom: Account, accountTo: Account, providersWhitelist?: string[], ): Promise<number | null> { try { const addressFrom = accountFrom.address || accountFrom.parentAccount?.address; if (!addressFrom) { throw new Error("No address available from accounts when requesting minimum swap amount."); } const requestConfig: AxiosRequestConfig = { method: "GET", url: SWAP_QUOTE_URL, params: { from: accountFrom.currency.id, to: accountTo.currency.id, amountFrom: PROBE_AMOUNT, addressFrom, fiatForCounterValue: "USD", slippage: 1, networkFees: PROBE_NETWORK_FEES, networkFeesCurrency: accountTo.currency.speculosApp.name.toLowerCase(), displayLanguage: "en", theme: "light", "providers-whitelist": providersWhitelist?.join(",") ?? PROVIDERS_WHITELIST, tradeType: "INPUT", uniswapOrderType: "uniswapxv1", }, headers: { accept: "application/json" }, }; const { data } = await axios(requestConfig); if (!Array.isArray(data)) { console.warn("Unexpected quote API response, falling back to countervalues"); return await getAmountFromUSD(accountFrom.currency.id, FALLBACK_TARGET_USD); } // Try to extract minAmount from AMOUNT_OFF_LIMITS errors const minimumAmounts = data .filter(isQuoteErrorItem) .filter(item => item.parameter?.minAmount !== undefined) .map(item => Number.parseFloat(item.parameter!.minAmount!)) .filter((amount: number) => !Number.isNaN(amount) && amount > 0); if (minimumAmounts.length > 0) { return Math.max(...minimumAmounts); } // No minAmount returned — compute a sensible fallback from countervalues console.warn( `No minAmount from quote API for ${accountFrom.currency.id}${accountTo.currency.id}, ` + `computing fallback from countervalues (~$${FALLBACK_TARGET_USD} USD)`, ); return await getAmountFromUSD(accountFrom.currency.id, FALLBACK_TARGET_USD); } catch (error: unknown) { const sanitizedError = sanitizeError(error); console.warn("Error fetching swap minimum amount:", sanitizedError); // Last resort: try to compute a sensible amount even if the quote call failed entirely try { return await getAmountFromUSD(accountFrom.currency.id, FALLBACK_TARGET_USD); } catch { return null; } } }