Skip to content

Commit

Permalink
feat: use cal as sell provider source of true (#7886)
Browse files Browse the repository at this point in the history
* feat: use cal as sell provider source of true

* test: add getProvidesData tests
  • Loading branch information
sarneijim authored Sep 27, 2024
1 parent 1751a82 commit 1f55fa5
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const startExchange = (input: StartExchangeInput): Observable<ExchangeRequestEve
break;
}
default: {
const providerConfig = getProviderConfig(exchangeType, provider);
const providerConfig = await getProviderConfig(exchangeType, provider);
version = providerConfig.version;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const completeExchange = (

const confirmExchange = async () => {
await withDevicePromise(deviceId, async transport => {
const providerNameAndSignature = getProviderConfig(exchangeType, provider);
const providerNameAndSignature = await getProviderConfig(exchangeType, provider);

if (!providerNameAndSignature) throw new Error("Could not get provider infos");

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { transformData, getProvidersData, ProvidersDataResponse } from "./getProvidersData";
import network from "@ledgerhq/live-network";

type TransformedProviderData = {
name: string;
publicKey: {
curve: string;
data: Buffer;
};
signature: Buffer;
};

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

describe("transformData", () => {
it("should transform providers data correctly", () => {
const providersData: ProvidersDataResponse = [
{
name: "ProviderA",
signature: "a1b2c3",
public_key: "1234567890abcdef",
public_key_curve: "secp256k1",
},
{
name: "ProviderB",
signature: "d4e5f6",
public_key: "abcdef1234567890",
public_key_curve: "secp256r1",
},
];

const expected: Record<string, TransformedProviderData> = {
providera: {
name: "ProviderA",
publicKey: {
curve: "secp256k1",
data: Buffer.from("1234567890abcdef", "hex"),
},
signature: Buffer.from("a1b2c3", "hex"),
},
providerb: {
name: "ProviderB",
publicKey: {
curve: "secp256r1",
data: Buffer.from("abcdef1234567890", "hex"),
},
signature: Buffer.from("d4e5f6", "hex"),
},
};

const result = transformData(providersData);
expect(result).toEqual(expected);
});

it("should handle empty providers data", () => {
const providersData: ProvidersDataResponse = [];

const expected: Record<string, TransformedProviderData> = {};

const result = transformData(providersData);
expect(result).toEqual(expected);
});
});

describe("getProvidersData", () => {
it("should fetch and transform providers data", async () => {
const mockProvidersData: ProvidersDataResponse = [
{
name: "ProviderA",
signature: "a1b2c3",
public_key: "1234567890abcdef",
public_key_curve: "secp256k1",
},
];

(network as jest.Mock).mockResolvedValue({ data: mockProvidersData });

const result = await getProvidersData("someType");

expect(network).toHaveBeenCalledWith({
method: "GET",
url: "https://crypto-assets-service.api.ledger.com/v1/partners",
params: {
output: "name,signature,public_key,public_key_curve",
service_name: "someType",
},
});

expect(result).toEqual({
providera: {
name: "ProviderA",
publicKey: {
curve: "secp256k1",
data: Buffer.from("1234567890abcdef", "hex"),
},
signature: Buffer.from("a1b2c3", "hex"),
},
});
});

it("should handle errors when fetching data", async () => {
(network as jest.Mock).mockRejectedValue(new Error("Network error"));

await expect(getProvidersData("someType")).rejects.toThrow("Network error");
});
});
42 changes: 42 additions & 0 deletions libs/ledger-live-common/src/exchange/providers/getProvidersData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { ExchangeProviderNameAndSignature } from ".";
import network from "@ledgerhq/live-network";

export type ProvidersDataResponse = {
name: string;
signature: string;
public_key: string;
public_key_curve: string;
}[];

export function transformData(
providersData: ProvidersDataResponse,
): Record<string, ExchangeProviderNameAndSignature> {
const transformed = {};
providersData.forEach(provider => {
const key = provider.name.toLowerCase();
transformed[key] = {
name: provider.name,
publicKey: {
curve: provider.public_key_curve,
data: Buffer.from(provider.public_key, "hex"),
},
signature: Buffer.from(provider.signature, "hex"),
};
});
return transformed;
}

export const getProvidersData = async (
type,
): Promise<Record<string, ExchangeProviderNameAndSignature>> => {
const { data: providersData } = await network<ProvidersDataResponse>({
method: "GET",
url: "https://crypto-assets-service.api.ledger.com/v1/partners",
params: {
output: "name,signature,public_key,public_key_curve",
service_name: type,
},
});

return transformData(providersData);
};
6 changes: 3 additions & 3 deletions libs/ledger-live-common/src/exchange/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ export function convertToAppExchangePartnerKey(
};
}

export const getProviderConfig = (
export const getProviderConfig = async (
exchangeType: ExchangeTypes,
providerName: string,
): ExchangeProviderNameAndSignature => {
): Promise<ExchangeProviderNameAndSignature> => {
if (getEnv("MOCK_EXCHANGE_TEST_CONFIG") && testProvider) {
return testProvider;
}
Expand All @@ -41,7 +41,7 @@ export const getProviderConfig = (

case ExchangeTypes.Sell:
case ExchangeTypes.SellNg:
return getSellProvider(providerName.toLowerCase());
return await getSellProvider(providerName.toLowerCase());

default:
throw new Error(`Unknown partner ${providerName} type`);
Expand Down
51 changes: 32 additions & 19 deletions libs/ledger-live-common/src/exchange/providers/sell.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getEnv } from "@ledgerhq/live-env";
import { ExchangeProviderNameAndSignature } from ".";
import { getProvidersData } from "./getProvidersData";

const testSellProvider: ExchangeProviderNameAndSignature = {
name: "SELL_TEST",
Expand All @@ -17,33 +18,45 @@ const testSellProvider: ExchangeProviderNameAndSignature = {
version: 2,
};

const sellProviders: Record<string, ExchangeProviderNameAndSignature> = {
coinify: {
name: "Coinify",
publicKey: {
curve: "secp256r1",
data: Buffer.from(
"04CEA7DC8B189FA0D7A5A97530B50556CB0C14079C39CD44532D7037F2B96F0FA9C2DE588E1840B351B71114EE4021FC260F790A6F2D0CDF1C3E1899CCF97D3CCB",
"hex",
),
},
signature: Buffer.from(
"3043021f023ecbbb1dfd44f390944bd1f6c039942943009a51ca4f134589441476651a02200cbfdf2ebe32eb0b0a88be9b1fec343ed5b230a69e65a1d15b4e34ef4206a9dd",
"hex",
),
},
let providerDataCache: Record<string, ExchangeProviderNameAndSignature> | null = null;

/**
* The result is cached after the first successful fetch to avoid redundant network calls.
* - "fund" providers currently include Mercuryo, which is stored as a fund provider.
* If this behavior changes, this logic should be revisited.
* Reference: https://github.dev/LedgerHQ/crypto-assets/blob/main/assets/partners/mercuryo/common.json
*/
export const fetchAndMergeProviderData = async () => {
if (providerDataCache) {
return providerDataCache;
}
try {
const [sellProvidersData, fundProviderData] = await Promise.all([
getProvidersData("sell"),
getProvidersData("fund"), // Mercuryo is currently treated as a fund provider
]);
providerDataCache = { ...sellProvidersData, ...fundProviderData };
return providerDataCache;
} catch (error) {
console.error("Error fetching or processing provider data:", error);
}
};

export const getSellProvider = (providerName: string): ExchangeProviderNameAndSignature => {
export const getSellProvider = async (
providerName: string,
): Promise<ExchangeProviderNameAndSignature> => {
if (getEnv("MOCK_EXCHANGE_TEST_CONFIG")) {
return testSellProvider;
}

const res = sellProviders[providerName.toLowerCase()];
const res = await fetchAndMergeProviderData();

if (!res) {
throw new Error("Failed to fetch provider data");
}
const provider = res[providerName.toLowerCase()];
if (!provider) {
throw new Error(`Unknown partner ${providerName}`);
}

return res;
return provider;
};
48 changes: 2 additions & 46 deletions libs/ledger-live-common/src/exchange/providers/swap.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ExchangeProviderNameAndSignature } from ".";
import { isIntegrationTestEnv } from "../swap/utils/isIntegrationTestEnv";
import { getProvidersData } from "./getProvidersData";
import network from "@ledgerhq/live-network";

export type SwapProviderConfig = {
Expand Down Expand Up @@ -185,22 +186,6 @@ type CurrencyData = {
signature: string;
};

type ProvidersDataResponse = {
name: string;
signature: string;
public_key: string;
public_key_curve: string;
}[];

type ProviderData = {
name: string;
publicKey: {
curve: string;
data: Buffer;
};
signature: Buffer;
};

let providerDataCache: Record<string, ProviderConfig & AdditionalProviderConfig> | null = null;

export const getSwapProvider = async (
Expand All @@ -215,35 +200,6 @@ export const getSwapProvider = async (
return res[providerName.toLowerCase()];
};

function transformData(providersData: ProvidersDataResponse): Record<string, ProviderData> {
const transformed = {};
providersData.forEach(provider => {
const key = provider.name.toLowerCase();
transformed[key] = {
name: provider.name,
publicKey: {
curve: provider.public_key_curve,
data: Buffer.from(provider.public_key, "hex"),
},
signature: Buffer.from(provider.signature, "hex"),
};
});
return transformed;
}

export const getProvidersData = async (): Promise<Record<string, ProviderData>> => {
const { data: providersData } = await network<ProvidersDataResponse>({
method: "GET",
url: "https://crypto-assets-service.api.ledger.com/v1/partners",
params: {
output: "name,signature,public_key,public_key_curve",
service_name: "swap",
},
});

return transformData(providersData);
};

/**
* Retrieves the currency data for a given ID
* @param currencyId The unique identifier for the currency.
Expand Down Expand Up @@ -285,7 +241,7 @@ export const fetchAndMergeProviderData = async () => {

try {
const [providersData, providersExtraData] = await Promise.all([
getProvidersData(),
getProvidersData("swap"),
getProvidersCDNData(),
]);

Expand Down

0 comments on commit 1f55fa5

Please sign in to comment.