Skip to content

Commit

Permalink
feat(LIVE-8241): implement call to v5 /currencies/from endpoint for s…
Browse files Browse the repository at this point in the history
…wap (#4258)

* feat(LIVE-8241): add api call and mock data for swap/from endpoint

* feat(LIVE-8241): refactor code to use new helper

* feat(LIVE-8241): changeset
  • Loading branch information
cng-ledger authored Aug 9, 2023
1 parent 707e59f commit e8a7bc5
Show file tree
Hide file tree
Showing 11 changed files with 296 additions and 13 deletions.
5 changes: 5 additions & 0 deletions .changeset/tame-spiders-carry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ledgerhq/live-common": patch
---

feat(LIVE-8241): implement call to v5 /currencies/from endpoint for swap
5 changes: 5 additions & 0 deletions libs/env/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,11 @@ const envDefinitions: Record<string, EnvDef<boolean | string | number | string[]
parser: stringParser,
desc: "Swap API base",
},
SWAP_API_BASE_V5: {
def: "https://swap-stg.ledger.com/v5",
parser: stringParser,
desc: "Swap API base staging version 5",
},
SYNC_ALL_INTERVAL: {
def: 8 * 60 * 1000,
parser: intParser,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import { ResponseData } from "../fetchCurrencyFrom";

export const fetchCurrencyFromMock: ResponseData = {
currencyGroups: [
{
network: "ethereum",
supportedCurrencies: [
"ethereum/erc20/celer",
"ethereum/erc20/powerledger",
"ethereum/erc20/dydx",
"ethereum/erc20/lcx",
"ethereum/erc20/amp",
"ethereum/erc20/pundi_x_token",
"ethereum/erc20/usd_tether__erc20_",
"ethereum/erc20/paxos_standard__pax_",
"ethereum/erc20/gala",
"ethereum/erc20/spell_token",
"ethereum/erc20/alchemy",
"ethereum/erc20/funfair",
"ethereum/erc20/mask_network",
"ethereum/erc20/ultratoken",
"ethereum/erc20/dusk_network",
"ethereum/erc20/alchemix",
"ethereum/erc20/meta_masters_guild",
"ethereum/erc20/makerdao",
"ethereum/erc20/bitdegree_token",
"ethereum/erc20/aave",
"ethereum/erc20/revv",
"ethereum/erc20/audius",
"ethereum/erc20/gitcoin",
"ethereum/erc20/radicle",
"ethereum/erc20/perpetual",
"ethereum/erc20/alice",
"ethereum/erc20/selfkey",
"ethereum/erc20/trace",
"ethereum/erc20/injective_token",
"ethereum/erc20/gods_unchained",
"ethereum/erc20/bounce_token_auction",
"ethereum/erc20/axie_infinity_shard",
"ethereum/erc20/render_token",
"ethereum/erc20/uma_voting_token_v1",
"ethereum/erc20/ampleforth_governance",
"ethereum/erc20/digix_dao",
"ethereum/erc20/stpt",
"ethereum/erc20/superrare",
"ethereum/erc20/dent",
"ethereum/erc20/eqifi_token",
"ethereum/erc20/dai_stablecoin_v2_0",
"ethereum/erc20/smooth_love_potion",
"ethereum/erc20/telcoin",
"ethereum/erc20/steth",
"ethereum/erc20/wootrade_network",
"ethereum/erc20/apecoin",
"ethereum/erc20/status_network_token",
"ethereum/erc20/nmr",
"ethereum/erc20/orbs",
"ethereum/erc20/vera_",
"ethereum/erc20/polkastartertoken",
"ethereum/erc20/akropolis",
"ethereum/erc20/looksrare_token",
"ethereum/erc20/concentrated_voting_power",
"ethereum/erc20/chiliz",
"ethereum/erc20/link_chainlink",
"ethereum/erc20/fantom_token",
"ethereum/erc20/sushi",
"ethereum/erc20/omg",
"ethereum/erc20/reserve_rights",
"ethereum/erc20/gnosis",
"ethereum/erc20/kucoin_token",
"ethereum/erc20/zap",
"ethereum/erc20/binance_usd",
"ethereum/erc20/alphatoken",
"ethereum/erc20/1inch_token",
"ethereum/erc20/dentacoin",
"ethereum/erc20/wliticapital",
"ethereum/erc20/healthcare_administration_token",
"ethereum/erc20/league_of_kingdoms_arena",
"ethereum/erc20/marlin_pond",
"ethereum/erc20/tribe",
"ethereum/erc20/aragon_network_token",
"ethereum/erc20/hedgetrade_token",
"ethereum/erc20/stasis_eurs_token",
"ethereum/erc20/reef_finance",
"ethereum/erc20/bancor",
"ethereum/erc20/chsb",
"ethereum/erc20/trusttoken",
"ethereum/erc20/metalpay",
"ethereum/erc20/yield_guild_games_token",
"ethereum/erc20/coin98",
"ethereum/erc20/loom_token",
"ethereum/erc20/biconomy_token",
"ethereum/erc20/api3",
"ethereum/erc20/ftx_token",
"ethereum/erc20/pumapay",
"ethereum/erc20/okb",
"ethereum/erc20/terra_virtua_kolect",
"ethereum/erc20/iexec_rlc",
"ethereum/erc20/frax_share",
"ethereum/erc20/jasmycoin",
"ethereum/erc20/euro_stable_token",
"ethereum/erc20/bluezelle",
"ethereum/erc20/dodo_bird",
"ethereum/erc20/stormx",
"ethereum/erc20/yearn_finance",
"ethereum/erc20/dogelon",
"ethereum/erc20/illuvium",
"ethereum/erc20/perlin",
"ethereum/erc20/threshold_network_token",
"ethereum/erc20/utrust_token",
"ethereum/erc20/enjin",
"ethereum/erc20/huobitoken",
"ethereum/erc20/synthetix_network_token",
"ethereum/erc20/nexo",
"ethereum/erc20/idex_token",
"ethereum/erc20/kyber_network_crystal_v2",
"ethereum/erc20/ssv_token",
"ethereum/erc20/naga_coin",
"ethereum/erc20/republic_protocol",
"ethereum/erc20/shiba_inu",
"ethereum/erc20/celsius",
"ethereum/erc20/0x_project",
"ethereum/erc20/edgeless",
"ethereum/erc20/ethereum_name_service",
"ethereum/erc20/populous",
"ethereum/erc20/derace_token",
"ethereum/erc20/frontier_token",
"ethereum/erc20/vib",
"ethereum/erc20/golem_network_token",
"ethereum/erc20/decentraland_mana",
"ethereum/erc20/xy_oracle",
"ethereum/erc20/berry",
"ethereum/erc20/usd__coin",
"ethereum/erc20/civic",
"ethereum/erc20/ankr_network",
"ethereum/erc20/paxos_gold",
"ethereum/erc20/tenx",
"ethereum/erc20/curve_dao_token",
"ethereum/erc20/compound",
"ethereum/erc20/origintoken",
"ethereum/erc20/orion_protocol",
"ethereum/erc20/sand",
"ethereum/erc20/skale",
"ethereum/erc20/singularitynet_token",
"ethereum",
"ethereum/erc20/dexe",
"ethereum/erc20/yfii_finance",
"ethereum/erc20/orchid",
"ethereum/erc20/occ",
"ethereum/erc20/btrst",
"ethereum/erc20/graph_token",
"ethereum/erc20/loopringcoin_v2",
"ethereum/erc20/livepeer",
"ethereum/erc20/hifi_finance",
"ethereum/erc20/wrapped_bitcoin",
"ethereum/erc20/stargatetoken",
"ethereum/erc20/badger",
"ethereum/erc20/melon_token",
"ethereum/erc20/ripio_credit_network",
"ethereum/erc20/immutable_x",
"ethereum/erc20/quant",
"ethereum/erc20/arpa_token",
"ethereum/erc20/machine_xchange_coin",
"ethereum/erc20/voyager_token",
"ethereum/erc20/holotoken",
"ethereum/erc20/convex_token",
"ethereum/erc20/matic",
"ethereum/erc20/storj",
"ethereum/erc20/aergo",
"ethereum/erc20/suku",
"ethereum/erc20/merit_circle",
"ethereum/erc20/efinity_token",
"ethereum/erc20/farm_reward_token",
"ethereum/erc20/linear_token",
"ethereum/erc20/roobee",
"ethereum/erc20/adventure_gold",
"ethereum/erc20/student_coin",
"ethereum/erc20/bat",
"ethereum/erc20/rally",
"ethereum/erc20/lido_dao_token",
"ethereum/erc20/chroma",
"ethereum/erc20/project_galaxy",
"ethereum/erc20/standard",
"ethereum/erc20/ocean",
"ethereum/erc20/constitutiondao",
"ethereum/erc20/starlink",
"ethereum/erc20/kryll",
"ethereum/erc20/dash_2_trade",
"ethereum/erc20/uniswap",
"ethereum/erc20/blur",
"ethereum/erc20/leo_token",
"ethereum/erc20/boson_token",
"ethereum/erc20/trueusd",
"ethereum/erc20/unbound",
],
},
],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import network from "@ledgerhq/live-network/network";
import { fetchCurrencyFrom } from "../fetchCurrencyFrom";
import { fetchCurrencyFromMock } from "../__mocks__/fetchCurrencyFrom.mocks";
import { DEFAULT_SWAP_TIMEOUT_MS } from "../../../const/timeout";

jest.mock("@ledgerhq/live-network/network");

describe("fetchCurrencyFrom", () => {
it("success with 200", async () => {
(network as jest.Mock).mockImplementation(() => ({
data: fetchCurrencyFromMock,
}));

const result = await fetchCurrencyFrom({
providers: ["changelly", "cic", "oneinch"],
currencyTo: "bitcoin",
});

expect(result).toStrictEqual(fetchCurrencyFromMock);
expect(network as jest.Mock).toHaveBeenCalledWith({
method: "GET",
timeout: DEFAULT_SWAP_TIMEOUT_MS,
url: "https://swap-stg.ledger.com/v5/currencies/from?providers-whitelist=changelly%2Ccic%2Coneinch&additional-coins-flag=false&currencyTo=bitcoin",
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import network from "@ledgerhq/live-network/network";
import { isPlaywrightEnv } from "../../utils/isPlaywrightEnv";
import { getEnv } from "../../../../env";
import { DEFAULT_SWAP_TIMEOUT_MS } from "../../const/timeout";

type Props = {
providers: Array<string>;
currencyTo?: string;
additionalCoinsFlag?: boolean;
};

export type ResponseData = {
currencyGroups: Array<{
network: string;
supportedCurrencies: Array<string>;
}>;
};

export async function fetchCurrencyFrom({
providers,
currencyTo,
additionalCoinsFlag = false,
}: Props) {
if (isPlaywrightEnv()) return Promise.resolve(fetchCurrencyFrom);

const url = new URL(`${getEnv("SWAP_API_BASE_V5")}/currencies/from`);
url.searchParams.append("providers-whitelist", providers.join(","));
url.searchParams.append("additional-coins-flag", additionalCoinsFlag.toString());
if (currencyTo) {
url.searchParams.append("currencyTo", currencyTo);
}

try {
const { data } = await network<ResponseData>({
method: "GET",
url: url.toString(),
timeout: DEFAULT_SWAP_TIMEOUT_MS,
});
return data;
} catch (e) {
throw Error("Something went wrong in fetchCurrencyFrom call");
}
}
1 change: 1 addition & 0 deletions libs/ledger-live-common/src/exchange/swap/const/timeout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const DEFAULT_SWAP_TIMEOUT_MS = 10000;
13 changes: 9 additions & 4 deletions libs/ledger-live-common/src/exchange/swap/getExchangeRates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ import type { Unit } from "@ledgerhq/types-cryptoassets";
import { BigNumber } from "bignumber.js";
import { getAccountCurrency, getAccountUnit } from "../../account";
import { formatCurrencyUnit } from "../../currencies";
import { getEnv } from "../../env";
import {
SwapExchangeRateAmountTooHigh,
SwapExchangeRateAmountTooLow,
SwapExchangeRateAmountTooLowOrTooHigh,
} from "../../errors";
import { getProviderConfig, getSwapAPIBaseURL, getSwapAPIError } from "./";
import { getAvailableProviders, getProviderConfig, getSwapAPIBaseURL, getSwapAPIError } from "./";
import { mockGetExchangeRates } from "./mock";
import type { CustomMinOrMaxError, GetExchangeRates } from "./types";
import { fetchCurrencyFrom } from "./api/v5/fetchCurrencyFrom";
import { isPlaywrightEnv } from "./utils/isPlaywrightEnv";

const getExchangeRates: GetExchangeRates = async ({
exchange,
Expand All @@ -21,8 +22,7 @@ const getExchangeRates: GetExchangeRates = async ({
timeout,
timeoutErrorMessage,
}) => {
if (getEnv("MOCK") && !getEnv("PLAYWRIGHT_RUN"))
return mockGetExchangeRates(exchange, transaction, currencyTo);
if (isPlaywrightEnv()) return mockGetExchangeRates(exchange, transaction, currencyTo);

const from = getAccountCurrency(exchange.fromAccount).id;
const unitFrom = getAccountUnit(exchange.fromAccount);
Expand All @@ -36,6 +36,11 @@ const getExchangeRates: GetExchangeRates = async ({
.filter(provider => provider.pairs.some(pair => pair.from === from && pair.to === to))
.map(item => item.provider);

fetchCurrencyFrom({
providers: getAvailableProviders(),
currencyTo: "bitcoin",
});

const request = {
from,
to,
Expand Down
4 changes: 2 additions & 2 deletions libs/ledger-live-common/src/exchange/swap/getProviders.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import network from "@ledgerhq/live-network/network";
import qs from "qs";
import { getEnv } from "../../env";
import { SwapNoAvailableProviders } from "../../errors";
import { getAvailableProviders, getSwapAPIBaseURL } from "./";
import { mockGetProviders } from "./mock";
import type { GetProviders, ProvidersResponseV4 } from "./types";
import { isPlaywrightEnv } from "./utils/isPlaywrightEnv";

const getProviders: GetProviders = async () => {
if (getEnv("MOCK") && !getEnv("PLAYWRIGHT_RUN")) return mockGetProviders();
if (isPlaywrightEnv()) return mockGetProviders();

const res = await network({
method: "GET",
Expand Down
4 changes: 2 additions & 2 deletions libs/ledger-live-common/src/exchange/swap/getStatus.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import network from "@ledgerhq/live-network/network";
import { getEnv } from "../../env";
import { getSwapAPIBaseURL } from "./";
import { mockGetStatus } from "./mock";
import type { GetMultipleStatus } from "./types";
import { isPlaywrightEnv } from "./utils/isPlaywrightEnv";

export const getMultipleStatus: GetMultipleStatus = async statusList => {
if (getEnv("MOCK") && !getEnv("PLAYWRIGHT_RUN")) return mockGetStatus(statusList);
if (isPlaywrightEnv()) return mockGetStatus(statusList);

const res = await network({
method: "POST",
Expand Down
8 changes: 3 additions & 5 deletions libs/ledger-live-common/src/exchange/swap/postSwapState.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import network from "@ledgerhq/live-network/network";
import { getEnv } from "../../env";
import { getSwapAPIBaseURL } from "./";
import { mockPostSwapAccepted, mockPostSwapCancelled } from "./mock";
import type { PostSwapAccepted, PostSwapCancelled } from "./types";
import { isPlaywrightEnv } from "./utils/isPlaywrightEnv";

export const postSwapAccepted: PostSwapAccepted = async ({
provider,
swapId = "",
transactionId,
}) => {
if (getEnv("MOCK") && !getEnv("PLAYWRIGHT_RUN"))
return mockPostSwapAccepted({ provider, swapId, transactionId });
if (isPlaywrightEnv()) return mockPostSwapAccepted({ provider, swapId, transactionId });

/**
* Since swapId is requiered by the endpoit, don't call it if we don't have
Expand All @@ -34,8 +33,7 @@ export const postSwapAccepted: PostSwapAccepted = async ({
};

export const postSwapCancelled: PostSwapCancelled = async ({ provider, swapId = "" }) => {
if (getEnv("MOCK") && !getEnv("PLAYWRIGHT_RUN"))
return mockPostSwapCancelled({ provider, swapId });
if (isPlaywrightEnv()) return mockPostSwapCancelled({ provider, swapId });

/**
* Since swapId is requiered by the endpoit, don't call it if we don't have
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { getEnv } from "../../../env";

export const isPlaywrightEnv = () => getEnv("MOCK") && !getEnv("PLAYWRIGHT_RUN");

1 comment on commit e8a7bc5

@vercel
Copy link

@vercel vercel bot commented on e8a7bc5 Aug 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.