Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(LIVE-9638): change provider info from static object to API call #6853

Merged
merged 6 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/cool-pots-film.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"ledger-live-desktop": patch
"live-mobile": patch
"@ledgerhq/live-common": patch
---

Replace static provider info to API
11 changes: 9 additions & 2 deletions apps/ledger-live-mobile/src/screens/Swap/Form/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export function SwapForm({
const exchangeRate = useSelector(rateSelector);
// mobile specific
const [confirmed, setConfirmed] = useState(false);
const [swapAcceptedProviders, setSwapAcceptedProviders] = useState<string[]>([]);

const setExchangeRate = useCallback(
(rate?: ExchangeRate) => {
Expand Down Expand Up @@ -314,7 +315,13 @@ export function SwapForm({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [params]);

const swapAcceptedProviders = getAvailableProviders();
useEffect(() => {
const fetchProviders = async () => {
setSwapAcceptedProviders(await getAvailableProviders());
};
fetchProviders();
}, []);

const termsAccepted = (swapAcceptedProviders || []).includes(provider ?? "");
const [deviceMeta, setDeviceMeta] = useState<DeviceMeta>();

Expand All @@ -323,7 +330,7 @@ export function SwapForm({
return <Connect provider={provider} setResult={setDeviceMeta} />;
}

if (getAvailableProviders().length) {
if (swapAcceptedProviders.length) {
return (
<KeyboardAwareScrollView testID="exchange-scrollView">
<Flex flex={1} justifyContent="space-between" padding={6}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const startExchange = (input: StartExchangeInput): Observable<ExchangeRequestEve
if (provider) {
switch (exchangeType) {
case ExchangeTypes.Swap: {
const providerConfig = getSwapProvider(provider);
const providerConfig = await getSwapProvider(provider);
if (providerConfig.type !== "CEX") {
throw new Error(`Unsupported provider type ${providerConfig.type}`);
}
Expand Down
80 changes: 78 additions & 2 deletions libs/ledger-live-common/src/exchange/providers/swap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,38 @@ export type SwapProviderConfig = {

type CEXProviderConfig = ExchangeProviderNameAndSignature & SwapProviderConfig & { type: "CEX" };
type DEXProviderConfig = SwapProviderConfig & { type: "DEX" };
type AdditionalProviderConfig = SwapProviderConfig & { type: "DEX" | "CEX" } & { version?: number };
export type ProviderConfig = CEXProviderConfig | DEXProviderConfig;

const swapAdditionData: Record<string, AdditionalProviderConfig> = {
changelly: {
needsKYC: false,
needsBearerToken: false,
type: "CEX",
},
cic: {
needsKYC: false,
needsBearerToken: false,
type: "CEX",
},
moonpay: {
needsKYC: true,
needsBearerToken: false,
type: "CEX",
version: 2,
},
oneinch: {
type: "DEX",
needsKYC: false,
needsBearerToken: false,
},
paraswap: {
type: "DEX",
needsKYC: false,
needsBearerToken: false,
},
};

const swapProviders: Record<string, ProviderConfig> = {
changelly: {
name: "Changelly",
Expand Down Expand Up @@ -75,8 +105,54 @@ const swapProviders: Record<string, ProviderConfig> = {
},
};

export const getSwapProvider = (providerName: string): ProviderConfig => {
const res = swapProviders[providerName.toLowerCase()];
export const getSwapProvider = async (providerName: string): Promise<ProviderConfig> => {
const res = getProvidersData()[providerName.toLowerCase()];

if (!res) {
throw new Error(`Unknown partner ${providerName}`);
}

return res;
};

function transformData(inputArray) {
const transformedObject = {};

inputArray.forEach(item => {
const key = item.name.toLowerCase();
transformedObject[key] = {
name: item.name,
publicKey: {
curve: item.public_key_curve,
data: Buffer.from(item.public_key, "hex"),
},
signature: item.signature,
...(swapProviders[key] && {
needsKYC: swapProviders[key].needsKYC,
needsBearerToken: swapProviders[key].needsBearerToken,
type: swapProviders[key].type,
}),
};
});

return transformedObject;
}

export const getProvidersData = async () => {
try {
const providersData = await (
await fetch(
"https://crypto-assets-service.api.aws.prd.ldg-tech.com/v1/partners?output=name,payload_signature_computed_format,signature,public_key,public_key_curve",
)
).json();
return transformData(providersData);
} catch {
return swapProviders;
}
};

export const getProvidersAdditionalData = (providerName: string): AdditionalProviderConfig => {
const res = swapAdditionData[providerName.toLowerCase()];

if (!res) {
throw new Error(`Unknown partner ${providerName}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,13 @@ describe("fetchCurrencyAll", () => {
data: fetchCurrencyAllMock,
}));

const result = await fetchCurrencyAll({
providers: ["oneinch"],
});
const result = await fetchCurrencyAll({});

expect(result).toStrictEqual(flattenV5CurrenciesAll(fetchCurrencyAllMock));
expect(network as jest.Mock).toHaveBeenCalledWith({
method: "GET",
timeout: DEFAULT_SWAP_TIMEOUT_MS,
url: "https://swap.ledger.com/v5/currencies/all?providers-whitelist=oneinch&additional-coins-flag=false",
url: "https://swap.ledger.com/v5/currencies/all?providers-whitelist=changelly%2Ccic%2Cmoonpay%2Coneinch%2Cparaswap&additional-coins-flag=false",
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,14 @@ describe("fetchCurrencyFrom", () => {
}));

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

expect(result).toStrictEqual(flattenV5CurrenciesToAndFrom(fetchCurrencyFromMock));
expect(network as jest.Mock).toHaveBeenCalledWith({
method: "GET",
timeout: DEFAULT_SWAP_TIMEOUT_MS,
url: "https://swap.ledger.com/v5/currencies/from?providers-whitelist=changelly%2Ccic%2Coneinch&additional-coins-flag=false&currency-to=bitcoin",
url: "https://swap.ledger.com/v5/currencies/from?providers-whitelist=changelly%2Ccic%2Cmoonpay%2Coneinch%2Cparaswap&additional-coins-flag=false&currency-to=bitcoin",
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,14 @@ describe("fetchCurrencyFrom", () => {
});

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

expect(result).toStrictEqual(flattenV5CurrenciesToAndFrom(fetchCurrencyToMock));
expect(mockNetwork).toHaveBeenCalledWith({
method: "GET",
timeout: DEFAULT_SWAP_TIMEOUT_MS,
url: "https://swap.ledger.com/v5/currencies/to?providers-whitelist=changelly%2Ccic%2Coneinch&additional-coins-flag=false&currency-from=bitcoin",
url: "https://swap.ledger.com/v5/currencies/to?providers-whitelist=changelly%2Ccic%2Cmoonpay%2Coneinch%2Cparaswap&additional-coins-flag=false&currency-from=bitcoin",
});
});

Expand All @@ -34,7 +33,6 @@ describe("fetchCurrencyFrom", () => {

try {
await fetchCurrencyTo({
providers: ["changelly", "cic", "oneinch"],
currencyFromId: "bitcoin",
});
} catch (e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ 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 } from "../..";
import { getAvailableProviders, getSwapAPIBaseURL } from "../..";
import { getEnv } from "@ledgerhq/live-env";

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

Expand All @@ -19,10 +18,11 @@ export type ResponseDataAll = {
to: ResponseDataTo["currencyGroups"];
};

export async function fetchCurrencyAll({ providers, additionalCoinsFlag = false }: Props) {
export async function fetchCurrencyAll({ 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,10 @@ 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 { getSwapAPIBaseURL } from "../..";
import { getAvailableProviders, getSwapAPIBaseURL } from "../..";
import { getEnv } from "@ledgerhq/live-env";

type Props = {
providers: Array<string>;
currencyTo?: string;
additionalCoinsFlag?: boolean;
};
Expand All @@ -18,17 +17,15 @@ export type ResponseData = {
}>;
};

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

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());

if (currencyTo) {
url.searchParams.append("currency-to", currencyTo);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ import { DEFAULT_SWAP_TIMEOUT_MS } from "../../const/timeout";
import axios from "axios";
import { LedgerAPI4xx } from "@ledgerhq/errors";
import { flattenV5CurrenciesToAndFrom } from "../../utils/flattenV5CurrenciesToAndFrom";
import { getSwapAPIBaseURL } from "../..";
import { getAvailableProviders, getSwapAPIBaseURL } from "../..";

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

Expand All @@ -21,16 +20,13 @@ type CurrencyGroup = {
supportedCurrencies: string[];
};

export async function fetchCurrencyTo({
currencyFromId,
providers,
additionalCoinsFlag = false,
}: Props) {
export async function fetchCurrencyTo({ currencyFromId, additionalCoinsFlag = false }: Props) {
if (isIntegrationTestEnv())
return Promise.resolve(flattenV5CurrenciesToAndFrom(fetchCurrencyToMock));

const url = new URL(`${getSwapAPIBaseURL()}/currencies/to`);

const providers = getAvailableProviders();
url.searchParams.append("providers-whitelist", providers.join(","));
url.searchParams.append("additional-coins-flag", additionalCoinsFlag.toString());
url.searchParams.append("currency-from", currencyFromId!);
Expand Down
12 changes: 8 additions & 4 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 { getSwapAPIBaseURL } from "../..";
import { getAvailableProviders, getSwapAPIBaseURL } from "../..";

type Props = {
providers: Array<string>;
removeProviders: 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({
providers,
removeProviders,
currencyFrom,
toCurrencyId,
unitTo,
Expand All @@ -90,11 +90,15 @@ 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,
amountFrom: fromCurrencyAmount, // not sure why amountFrom thinks it can be undefined here
providers,
providers: providers,
};

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const completeExchange = (

const confirmExchange = async () => {
await withDevicePromise(deviceId, async transport => {
const providerConfig = getSwapProvider(provider);
const providerConfig = await getSwapProvider(provider);
if (providerConfig.type !== "CEX") {
throw new Error(`Unsupported provider type ${providerConfig.type}`);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { getSwapAPIBaseURL, getSwapAPIError } from "./";
import { mockGetExchangeRates } from "./mock";
import type { CustomMinOrMaxError, GetExchangeRates } from "./types";
import { isIntegrationTestEnv } from "./utils/isIntegrationTestEnv";
import { getSwapProvider } from "../providers";
import { getProvidersAdditionalData } from "../providers/swap";

const getExchangeRates: GetExchangeRates = async ({
exchange,
Expand Down Expand Up @@ -139,7 +139,7 @@ const inferError = (
const tenPowMagnitude = new BigNumber(10).pow(unitFrom.magnitude);
const { amountTo, minAmountFrom, maxAmountFrom, errorCode, errorMessage, provider, status } =
responseData;
const isDex = getSwapProvider(provider).type === "DEX";
const isDex = getProvidersAdditionalData(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) {
Expand Down
Loading
Loading