Skip to content

Commit

Permalink
feat: filtering providers in hooks based on FF (#6991)
Browse files Browse the repository at this point in the history
* feat: filtering providers in hooks based on FF

* chore: changeset

* chore: lint

* chore: lint
  • Loading branch information
CremaFR committed Jun 7, 2024
1 parent 917b0e9 commit 6eec3f9
Show file tree
Hide file tree
Showing 15 changed files with 99 additions and 52 deletions.
6 changes: 6 additions & 0 deletions .changeset/fluffy-boats-notice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@ledgerhq/types-live": patch
"@ledgerhq/live-common": patch
---

hooks to filter swap providers based on FF
2 changes: 1 addition & 1 deletion libs/ledger-live-common/src/exchange/providers/swap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ export const getProvidersCDNData = async () => {
}
};

const fetchAndMergeProviderData = async () => {
export const fetchAndMergeProviderData = async () => {
if (providerDataCache) {
return providerDataCache;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ describe("fetchCurrencyAll", () => {
(network as jest.Mock).mockImplementation(() => ({
data: fetchCurrencyAllMock,
}));
const providers = ["changelly", "cic", "moonpay", "oneinch", "paraswap"];

const result = await fetchCurrencyAll({});
const result = await fetchCurrencyAll({ providers });

expect(result).toStrictEqual(flattenV5CurrenciesAll(fetchCurrencyAllMock));
expect(network as jest.Mock).toHaveBeenCalledWith({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ describe("fetchCurrencyFrom", () => {
data: fetchCurrencyFromMock,
}));

const providers = ["changelly", "cic", "moonpay", "oneinch", "paraswap"];

const result = await fetchCurrencyFrom({
currencyTo: "bitcoin",
providers,
});

expect(result).toStrictEqual(flattenV5CurrenciesToAndFrom(fetchCurrencyFromMock));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,23 @@ import { fetchCurrencyAllMock } from "./__mocks__/fetchCurrencyAll.mocks";
import { ResponseData as ResponseDataTo } from "./fetchCurrencyTo";
import { ResponseData as ResponseDataFrom } from "./fetchCurrencyFrom";
import { flattenV5CurrenciesAll } from "../../utils/flattenV5CurrenciesAll";
import { getSwapAPIBaseURL, getSwapUserIP, getAvailableProviders } from "../..";
import { getSwapAPIBaseURL, getSwapUserIP } from "../..";
import { getEnv } from "@ledgerhq/live-env";

type Props = {
additionalCoinsFlag?: boolean;
providers: string[];
};

export type ResponseDataAll = {
from: ResponseDataFrom["currencyGroups"];
to: ResponseDataTo["currencyGroups"];
};

export async function fetchCurrencyAll({ additionalCoinsFlag = false }: Props) {
export async function fetchCurrencyAll({ providers, additionalCoinsFlag = false }: Props) {
if (getEnv("MOCK") || getEnv("PLAYWRIGHT_RUN"))
return Promise.resolve(flattenV5CurrenciesAll(fetchCurrencyAllMock));

const providers = getAvailableProviders();
const url = new URL(`${getSwapAPIBaseURL()}/currencies/all`);
url.searchParams.append("providers-whitelist", providers.join(","));
url.searchParams.append("additional-coins-flag", additionalCoinsFlag.toString());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import network from "@ledgerhq/live-network/network";
import { DEFAULT_SWAP_TIMEOUT_MS } from "../../const/timeout";
import { flattenV5CurrenciesToAndFrom } from "../../utils/flattenV5CurrenciesToAndFrom";
import { fetchCurrencyFromMock } from "./__mocks__/fetchCurrencyFrom.mocks";
import { getAvailableProviders, getSwapAPIBaseURL, getSwapUserIP } from "../..";
import { getSwapAPIBaseURL, getSwapUserIP } from "../..";
import { getEnv } from "@ledgerhq/live-env";

type Props = {
currencyTo?: string;
providers: string[];
additionalCoinsFlag?: boolean;
};

Expand All @@ -17,13 +18,16 @@ export type ResponseData = {
}>;
};

export async function fetchCurrencyFrom({ currencyTo, additionalCoinsFlag = false }: Props) {
export async function fetchCurrencyFrom({
currencyTo,
providers,
additionalCoinsFlag = false,
}: Props) {
if (getEnv("MOCK") || getEnv("PLAYWRIGHT_RUN"))
return flattenV5CurrenciesToAndFrom(fetchCurrencyFromMock);

const headers = getSwapUserIP();
const url = new URL(`${getSwapAPIBaseURL()}/currencies/from`);
const providers = getAvailableProviders();
url.searchParams.append("providers-whitelist", providers.join(","));
url.searchParams.append("additional-coins-flag", additionalCoinsFlag.toString());

Expand Down
10 changes: 3 additions & 7 deletions libs/ledger-live-common/src/exchange/swap/api/v5/fetchRates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import { SwapGenericAPIError } from "../../../../errors";
import { enrichRatesResponse } from "../../utils/enrichRatesResponse";
import { isIntegrationTestEnv } from "../../utils/isIntegrationTestEnv";
import { fetchRatesMock } from "./__mocks__/fetchRates.mocks";
import { getAvailableProviders, getSwapAPIBaseURL, getSwapUserIP } from "../..";
import { getSwapAPIBaseURL, getSwapUserIP } from "../..";

type Props = {
removeProviders: Array<string>;
providers: Array<string>;
currencyFrom?: string;
toCurrencyId?: string;
fromCurrencyAmount: string;
Expand Down Expand Up @@ -76,7 +76,7 @@ export const throwRateError = (response: ExchangeRate[]) => {
};

export async function fetchRates({
removeProviders,
providers,
currencyFrom,
toCurrencyId,
unitTo,
Expand All @@ -90,10 +90,6 @@ export async function fetchRates({
}

const url = new URL(`${getSwapAPIBaseURL()}/rate`);
const providers = await getAvailableProviders();
removeProviders.forEach(provider => {
providers.splice(providers.indexOf(provider), 1);
});
const requestBody = {
from: currencyFrom,
to: toCurrencyId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { useFeature } from "../../../../featureFlags";
import { useAPI } from "../../../../hooks/useAPI";
import { fetchCurrencyAll } from "../../api/v5";
import { useFilteredProviders } from "./useFilteredProviders";

export function useFetchCurrencyAll() {
const fetchAdditionalCoins = useFeature("fetchAdditionalCoins");
const { providers, loading, error } = useFilteredProviders();

const { data, ...rest } = useAPI({
queryFn: fetchCurrencyAll,
queryProps: {
additionalCoinsFlag: fetchAdditionalCoins?.enabled,
providers,
},
// assume the all currency list for the given props won't change during a users session.
staleTimeout: Infinity,
enabled: !loading && !error,
});
return {
...rest,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useFeature } from "../../../../featureFlags";
import { useAPI } from "../../../../hooks/useAPI";
import { fetchCurrencyFrom } from "../../api/v5/fetchCurrencyFrom";
import { useFilteredProviders } from "./useFilteredProviders";

type Props = {
currencyTo?: string;
Expand All @@ -10,15 +11,17 @@ type Props = {

export function useFetchCurrencyFrom({ currencyTo, enabled }: Props = {}) {
const fetchAdditionalCoins = useFeature("fetchAdditionalCoins");
const { providers, loading, error } = useFilteredProviders();

return useAPI({
queryFn: fetchCurrencyFrom,
queryProps: {
currencyTo,
additionalCoinsFlag: fetchAdditionalCoins?.enabled,
providers,
},
// assume a currency list for the given props won't change during a users session.
staleTimeout: Infinity,
enabled,
enabled: enabled && !loading && !error,
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { getAccountCurrency } from "@ledgerhq/coin-framework/account/helpers";
import { fetchCurrencyTo } from "../../api/v5";
import { useAPI } from "../../../../hooks/useAPI";
import { useFeature } from "../../../../featureFlags";
import { getAvailableProviders } from "../../../providers";
import { useFilteredProviders } from "./useFilteredProviders";

type Props = {
fromCurrencyAccount: AccountLike | undefined;
Expand All @@ -13,12 +13,7 @@ type Props = {

export function useFetchCurrencyTo({ fromCurrencyAccount }: Props) {
const fetchAdditionalCoins = useFeature("fetchAdditionalCoins");
const providers = getAvailableProviders();
const ptxSwapMoonpayProviderFlag = useFeature("ptxSwapMoonpayProvider");

const providersFiltered = ptxSwapMoonpayProviderFlag?.enabled
? providers
: providers.filter(provider => provider !== "moonpay");
const { providers, loading, error } = useFilteredProviders();

const currencyFromId = fromCurrencyAccount
? getAccountCurrency(fromCurrencyAccount).id
Expand All @@ -28,11 +23,10 @@ export function useFetchCurrencyTo({ fromCurrencyAccount }: Props) {
queryFn: fetchCurrencyTo,
queryProps: {
currencyFromId,
providers: providersFiltered,
providers,
additionalCoinsFlag: fetchAdditionalCoins?.enabled,
},
// assume a currency list for the given props won't change during a users session.
staleTimeout: Infinity,
enabled: !!currencyFromId,
enabled: !!currencyFromId && !loading && !error,
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { getAccountCurrency } from "@ledgerhq/coin-framework/account/helpers";
import { CryptoOrTokenCurrency } from "@ledgerhq/types-cryptoassets";
import { AccountLike } from "@ledgerhq/types-live";
import BigNumber from "bignumber.js";
import { useFeature } from "../../../../featureFlags";
import { useAPI } from "../../../../hooks/useAPI";
import { fetchRates } from "../../api/v5/fetchRates";
import { useAPI } from "../../../../hooks/useAPI";
import { ExchangeRate } from "../../types";
import { useFilteredProviders } from "./useFilteredProviders";

type Props = {
fromCurrencyAccount: AccountLike | undefined;
Expand All @@ -27,19 +27,15 @@ export function useFetchRates({
? getAccountCurrency(fromCurrencyAccount).units[0]
: undefined;
const unitTo = toCurrency?.units[0];
const moonpayFF = useFeature("ptxSwapMoonpayProvider");
const removeProviders: string[] = [];
const formattedCurrencyAmount =
(unitFrom && `${fromCurrencyAmount.shiftedBy(-unitFrom.magnitude)}`) ?? "0";
const { providers, loading, error } = useFilteredProviders();

if (!moonpayFF?.enabled) {
removeProviders.push("moonpay");
}
const toCurrencyId = toCurrency?.id;
return useAPI({
queryFn: fetchRates,
queryProps: {
removeProviders: [],
providers,
unitTo: unitTo!,
unitFrom: unitFrom!,
currencyFrom,
Expand All @@ -53,7 +49,9 @@ export function useFetchRates({
fromCurrencyAmount.gt(0) &&
!!unitFrom &&
!!unitTo &&
isEnabled,
!loading &&
isEnabled &&
!error,
onSuccess,
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useState, useEffect, useCallback } from "react";
import { useFeature } from "../../../../featureFlags";
import { fetchAndMergeProviderData } from "../../../providers/swap";

export const useFilteredProviders = () => {
const [providers, setProviders] = useState<string[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<unknown>(null);

const ptxSwapMoonpayProviderFlag = useFeature("ptxSwapMoonpayProvider");
const ptxSwapExodusProviderFlag = useFeature("ptxSwapExodusProvider");
const ptxSwapThorswapProviderFlag = useFeature("ptxSwapThorswapProvider");

const fetchProviders = useCallback(async () => {
try {
const data = await fetchAndMergeProviderData();

let filteredProviders = Object.keys(data);
if (!ptxSwapMoonpayProviderFlag?.enabled) {
filteredProviders = filteredProviders.filter(provider => provider !== "moonpay");
}
if (!ptxSwapExodusProviderFlag?.enabled) {
filteredProviders = filteredProviders.filter(provider => provider !== "exodus");
}
if (!ptxSwapThorswapProviderFlag?.enabled) {
filteredProviders = filteredProviders.filter(provider => provider !== "thorswap");
}

setProviders(filteredProviders);
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
}, [ptxSwapMoonpayProviderFlag, ptxSwapExodusProviderFlag, ptxSwapThorswapProviderFlag]);

useEffect(() => {
fetchProviders();
}, [fetchProviders]);

return { providers, loading, error };
};
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import BigNumber from "bignumber.js";
import { useCallback, useEffect } from "react";
import { useCountdown } from "usehooks-ts";
import { useFeature } from "../../../../featureFlags";
import { DEFAULT_SWAP_RATES_INTERVAL_MS } from "../../const/timeout";
import { OnNoRatesCallback, RatesReducerState, SwapSelectorStateType } from "../../types";
import { SetExchangeRateCallback } from "../useSwapTransaction";
import { useFetchRates } from "./useFetchRates";
import { SetExchangeRateCallback } from "../useSwapTransaction";
import { useEffect } from "react";
import { useCountdown } from "usehooks-ts";
import { DEFAULT_SWAP_RATES_INTERVAL_MS } from "../../const/timeout";

type Props = {
fromState: SwapSelectorStateType;
Expand Down Expand Up @@ -37,28 +36,19 @@ export function useProviderRates({
countStart: props.countdown ?? DEFAULT_SWAP_RATES_INTERVAL_MS / 1000,
countStop: 0,
});
const ptxSwapMoonpayProviderFlag = useFeature("ptxSwapMoonpayProvider");
const filterMoonpay = useCallback(
rates => {
if (!rates || ptxSwapMoonpayProviderFlag?.enabled) return rates;
return rates.filter(r => r.provider !== "moonpay");
},
[ptxSwapMoonpayProviderFlag?.enabled],
);

const { data, isLoading, error, refetch } = useFetchRates({
fromCurrencyAccount: fromState.account,
toCurrency: toState.currency,
fromCurrencyAmount: fromState.amount ?? BigNumber(0),
onSuccess(data) {
resetCountdown();
const rates = filterMoonpay(data);
if (rates.length === 0) {
if (data.length === 0) {
stopCountdown();
onNoRates?.({ fromState, toState });
} else {
startCountdown();
setExchangeRate?.(rates[0]);
setExchangeRate?.(data[0]);
}
},
isEnabled,
Expand Down Expand Up @@ -115,7 +105,7 @@ export function useProviderRates({
return {
rates: {
status: "success",
value: filterMoonpay(data),
value: data,
error: undefined,
},
refetchRates: refetch,
Expand Down
2 changes: 2 additions & 0 deletions libs/ledger-live-common/src/featureFlags/defaultFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,8 @@ export const DEFAULT_FEATURES: Features = {
},

ptxSwapMoonpayProvider: DEFAULT_FEATURE,
ptxSwapExodusProvider: DEFAULT_FEATURE,
ptxSwapThorswapProvider: DEFAULT_FEATURE,

llmAnalyticsOptInPrompt: {
enabled: false,
Expand Down
4 changes: 4 additions & 0 deletions libs/ledgerjs/packages/types-live/src/feature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ export type Features = CurrencyFeatures & {
ptxSwapLiveAppDemoZero: Feature_PtxSwapLiveAppDemoZero;
ptxSwapLiveAppDemoOne: Feature_PtxSwapLiveAppDemoZero;
ptxSwapMoonpayProvider: Feature_PtxSwapMoonpayProvider;
ptxSwapExodusProvider: Feature_PtxSwapExodusProvider;
ptxSwapThorswapProvider: Feature_PtxSwapThorswapProvider;
flexibleContentCards: Feature_FlexibleContentCards;
llmAnalyticsOptInPrompt: Feature_LlmAnalyticsOptInPrompt;
lldAnalyticsOptInPrompt: Feature_LldAnalyticsOptInPrompt;
Expand Down Expand Up @@ -488,6 +490,8 @@ export type Feature_Objkt = DefaultFeature;
export type Feature_ListAppsV2minor1 = DefaultFeature;
export type Feature_BrazeLearn = DefaultFeature;
export type Feature_PtxSwapMoonpayProvider = DefaultFeature;
export type Feature_PtxSwapExodusProvider = DefaultFeature;
export type Feature_PtxSwapThorswapProvider = DefaultFeature;
export type Feature_FlexibleContentCards = DefaultFeature;
export type Feature_MyLedgerDisplayAppDeveloperName = DefaultFeature;
export type Feature_SupportDeviceStax = DefaultFeature;
Expand Down

0 comments on commit 6eec3f9

Please sign in to comment.