@getclave/lifi-sdk
Version:
LI.FI Any-to-Any Cross-Chain-Swap SDK
382 lines (381 loc) • 20.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const data_types_1 = require("@lifi/data-types");
const types_1 = require("@lifi/types");
const msw_1 = require("msw");
const node_1 = require("msw/node");
const vitest_1 = require("vitest");
const config_1 = require("../config");
const SDKError_1 = require("../errors/SDKError");
const errors_1 = require("../errors/errors");
const request = require("../request");
const request_1 = require("../request");
const setup_1 = require("../tests/setup");
const ApiService = require("./api");
const api_unit_handlers_1 = require("./api.unit.handlers");
const mockedFetch = vitest_1.vi.spyOn(request, 'request');
(0, vitest_1.describe)('ApiService', () => {
const _config = config_1.config.get();
const server = (0, node_1.setupServer)(...api_unit_handlers_1.handlers);
(0, vitest_1.beforeAll)(() => {
(0, setup_1.setupTestEnvironment)();
server.listen({
onUnhandledRequest: 'warn',
});
request_1.requestSettings.retries = 0;
// server.use(...handlers)
});
(0, vitest_1.beforeEach)(() => {
vitest_1.vi.clearAllMocks();
});
(0, vitest_1.afterEach)(() => server.resetHandlers());
(0, vitest_1.afterAll)(() => {
request_1.requestSettings.retries = 1;
server.close();
});
(0, vitest_1.describe)('getRoutes', () => {
const getRoutesRequest = ({ fromChainId = types_1.ChainId.BSC, fromAmount = '10000000000000', fromTokenAddress = (0, data_types_1.findDefaultToken)(types_1.CoinKey.USDC, types_1.ChainId.BSC).address, toChainId = types_1.ChainId.DAI, toTokenAddress = (0, data_types_1.findDefaultToken)(types_1.CoinKey.USDC, types_1.ChainId.DAI).address, options = { slippage: 0.03 }, }) => ({
fromChainId,
fromAmount,
fromTokenAddress,
toChainId,
toTokenAddress,
options,
});
(0, vitest_1.describe)('user input is invalid', () => {
(0, vitest_1.it)('should throw Error because of invalid fromChainId type', async () => {
const request = getRoutesRequest({
fromChainId: 'xxx',
});
await (0, vitest_1.expect)(ApiService.getRoutes(request)).rejects.toThrow('Invalid routes request.');
(0, vitest_1.expect)(mockedFetch).toHaveBeenCalledTimes(0);
});
(0, vitest_1.it)('should throw Error because of invalid fromAmount type', async () => {
const request = getRoutesRequest({
fromAmount: 10000000000000,
});
await (0, vitest_1.expect)(ApiService.getRoutes(request)).rejects.toThrow('Invalid routes request.');
(0, vitest_1.expect)(mockedFetch).toHaveBeenCalledTimes(0);
});
(0, vitest_1.it)('should throw Error because of invalid fromTokenAddress type', async () => {
const request = getRoutesRequest({
fromTokenAddress: 1234,
});
await (0, vitest_1.expect)(ApiService.getRoutes(request)).rejects.toThrow('Invalid routes request.');
(0, vitest_1.expect)(mockedFetch).toHaveBeenCalledTimes(0);
});
(0, vitest_1.it)('should throw Error because of invalid toChainId type', async () => {
const request = getRoutesRequest({
toChainId: 'xxx',
});
await (0, vitest_1.expect)(ApiService.getRoutes(request)).rejects.toThrow('Invalid routes request.');
(0, vitest_1.expect)(mockedFetch).toHaveBeenCalledTimes(0);
});
(0, vitest_1.it)('should throw Error because of invalid toTokenAddress type', async () => {
const request = getRoutesRequest({ toTokenAddress: '' });
await (0, vitest_1.expect)(ApiService.getRoutes(request)).rejects.toThrow('Invalid routes request.');
(0, vitest_1.expect)(mockedFetch).toHaveBeenCalledTimes(0);
});
(0, vitest_1.it)('should throw Error because of invalid options type', async () => {
const request = getRoutesRequest({
options: { slippage: 'not a number' },
});
await (0, vitest_1.expect)(ApiService.getRoutes(request)).rejects.toThrow('Invalid routes request.');
(0, vitest_1.expect)(mockedFetch).toHaveBeenCalledTimes(0);
});
});
(0, vitest_1.describe)('user input is valid', () => {
(0, vitest_1.describe)('and the backend call is successful', () => {
(0, vitest_1.it)('call the server once', async () => {
const request = getRoutesRequest({});
await ApiService.getRoutes(request);
(0, vitest_1.expect)(mockedFetch).toHaveBeenCalledTimes(1);
});
});
});
});
(0, vitest_1.describe)('getToken', () => {
(0, vitest_1.describe)('user input is invalid', () => {
(0, vitest_1.it)('throw an error', async () => {
await (0, vitest_1.expect)(ApiService.getToken(undefined, 'DAI')).rejects.toThrowError(new SDKError_1.SDKError(new errors_1.ValidationError('Required parameter "chain" is missing.')));
(0, vitest_1.expect)(mockedFetch).toHaveBeenCalledTimes(0);
await (0, vitest_1.expect)(ApiService.getToken(types_1.ChainId.ETH, undefined)).rejects.toThrowError(new SDKError_1.SDKError(new errors_1.ValidationError('Required parameter "token" is missing.')));
(0, vitest_1.expect)(mockedFetch).toHaveBeenCalledTimes(0);
});
});
(0, vitest_1.describe)('user input is valid', () => {
(0, vitest_1.describe)('and the backend call is successful', () => {
(0, vitest_1.it)('call the server once', async () => {
await ApiService.getToken(types_1.ChainId.DAI, 'DAI');
(0, vitest_1.expect)(mockedFetch).toHaveBeenCalledTimes(1);
});
});
});
});
(0, vitest_1.describe)('getQuote', () => {
const fromChain = types_1.ChainId.DAI;
const fromToken = 'DAI';
const fromAddress = 'Some wallet address';
const fromAmount = '1000';
const toChain = types_1.ChainId.POL;
const toToken = 'MATIC';
(0, vitest_1.describe)('user input is invalid', () => {
(0, vitest_1.it)('throw an error', async () => {
await (0, vitest_1.expect)(ApiService.getQuote({
fromChain: undefined,
fromToken,
fromAddress,
fromAmount,
toChain,
toToken,
})).rejects.toThrowError(new SDKError_1.SDKError(new errors_1.ValidationError('Required parameter "fromChain" is missing.')));
await (0, vitest_1.expect)(ApiService.getQuote({
fromChain,
fromToken: undefined,
fromAddress,
fromAmount,
toChain,
toToken,
})).rejects.toThrowError(new SDKError_1.SDKError(new errors_1.ValidationError('Required parameter "fromToken" is missing.')));
await (0, vitest_1.expect)(ApiService.getQuote({
fromChain,
fromToken,
fromAddress: undefined,
fromAmount,
toChain,
toToken,
})).rejects.toThrowError(new SDKError_1.SDKError(new errors_1.ValidationError('Required parameter "fromAddress" is missing.')));
await (0, vitest_1.expect)(ApiService.getQuote({
fromChain,
fromToken,
fromAddress,
fromAmount: undefined,
toChain,
toToken,
})).rejects.toThrowError(new SDKError_1.SDKError(new errors_1.ValidationError('Required parameter "fromAmount" is missing.')));
await (0, vitest_1.expect)(ApiService.getQuote({
fromChain,
fromToken,
fromAddress,
fromAmount,
toChain: undefined,
toToken,
})).rejects.toThrowError(new SDKError_1.SDKError(new errors_1.ValidationError('Required parameter "toChain" is missing.')));
await (0, vitest_1.expect)(ApiService.getQuote({
fromChain,
fromToken,
fromAddress,
fromAmount,
toChain,
toToken: undefined,
})).rejects.toThrowError(new SDKError_1.SDKError(new errors_1.ValidationError('Required parameter "toToken" is missing.')));
(0, vitest_1.expect)(mockedFetch).toHaveBeenCalledTimes(0);
});
});
(0, vitest_1.describe)('user input is valid', () => {
(0, vitest_1.describe)('and the backend call is successful', () => {
(0, vitest_1.it)('call the server once', async () => {
await ApiService.getQuote({
fromChain,
fromToken,
fromAddress,
fromAmount,
toChain,
toToken,
});
(0, vitest_1.expect)(mockedFetch).toHaveBeenCalledTimes(1);
});
});
});
});
(0, vitest_1.describe)('getStatus', () => {
const fromChain = types_1.ChainId.DAI;
const toChain = types_1.ChainId.POL;
const txHash = 'some tx hash';
const bridge = 'some bridge tool';
(0, vitest_1.describe)('user input is invalid', () => {
(0, vitest_1.it)('throw an error', async () => {
await (0, vitest_1.expect)(ApiService.getStatus({
bridge,
fromChain,
toChain,
txHash: undefined,
})).rejects.toThrowError(new SDKError_1.SDKError(new errors_1.ValidationError('Required parameter "txHash" is missing.')));
(0, vitest_1.expect)(mockedFetch).toHaveBeenCalledTimes(0);
});
});
(0, vitest_1.describe)('user input is valid', () => {
(0, vitest_1.describe)('and the backend call is successful', () => {
(0, vitest_1.it)('call the server once', async () => {
await ApiService.getStatus({ bridge, fromChain, toChain, txHash });
(0, vitest_1.expect)(mockedFetch).toHaveBeenCalledTimes(1);
});
});
});
});
(0, vitest_1.describe)('getChains', () => {
(0, vitest_1.describe)('and the backend call is successful', () => {
(0, vitest_1.it)('call the server once', async () => {
const chains = await ApiService.getChains();
(0, vitest_1.expect)(chains[0]?.id).toEqual(1);
(0, vitest_1.expect)(mockedFetch).toHaveBeenCalledTimes(1);
});
});
});
(0, vitest_1.describe)('getTools', () => {
(0, vitest_1.describe)('and the backend succeeds', () => {
(0, vitest_1.it)('returns the tools', async () => {
const tools = await ApiService.getTools({
chains: [types_1.ChainId.ETH, types_1.ChainId.POL],
});
(0, vitest_1.expect)(tools).toBeDefined();
(0, vitest_1.expect)(tools.bridges).toBeDefined();
(0, vitest_1.expect)(tools.exchanges).toBeDefined();
});
});
});
(0, vitest_1.describe)('getTokens', () => {
(0, vitest_1.it)('return the tokens', async () => {
const result = await ApiService.getTokens({
chains: [types_1.ChainId.ETH, types_1.ChainId.POL],
});
(0, vitest_1.expect)(result).toBeDefined();
(0, vitest_1.expect)(result.tokens[types_1.ChainId.ETH]).toBeDefined();
});
});
(0, vitest_1.describe)('getStepTransaction', () => {
const getAction = ({ fromChainId = types_1.ChainId.BSC, fromAmount = '10000000000000', fromToken = (0, data_types_1.findDefaultToken)(types_1.CoinKey.USDC, types_1.ChainId.BSC), fromAddress = 'some from address', // we don't validate the format of addresses atm
toChainId = types_1.ChainId.DAI, toToken = (0, data_types_1.findDefaultToken)(types_1.CoinKey.USDC, types_1.ChainId.DAI), toAddress = 'some to address', slippage = 0.03, }) => ({
fromChainId,
fromAmount,
fromToken: fromToken,
fromAddress,
toChainId,
toToken: toToken,
toAddress,
slippage,
});
const getEstimate = ({ fromAmount = '10000000000000', toAmount = '10000000000000', toAmountMin = '999999999999', approvalAddress = 'some approval address', // we don't validate the format of addresses atm;
executionDuration = 300, tool = '1inch', }) => ({
fromAmount,
toAmount,
toAmountMin,
approvalAddress,
executionDuration,
tool,
});
const getStep = ({ id = 'some random id', type = 'lifi', tool = 'some swap tool', action = getAction({}), estimate = getEstimate({}), }) => ({
id,
type,
tool,
toolDetails: {
key: tool,
name: tool,
logoURI: '',
},
action,
estimate,
includedSteps: [],
});
(0, vitest_1.describe)('with a swap step', () => {
// While the validation fails for some users we should not enforce it
vitest_1.describe.skip('user input is invalid', () => {
(0, vitest_1.it)('should throw Error because of invalid id', async () => {
const step = getStep({ id: null });
await (0, vitest_1.expect)(ApiService.getStepTransaction(step)).rejects.toThrow('Invalid step.');
(0, vitest_1.expect)(mockedFetch).toHaveBeenCalledTimes(0);
});
(0, vitest_1.it)('should throw Error because of invalid type', async () => {
const step = getStep({ type: 42 });
await (0, vitest_1.expect)(ApiService.getStepTransaction(step)).rejects.toThrow('Invalid Step');
(0, vitest_1.expect)(mockedFetch).toHaveBeenCalledTimes(0);
});
(0, vitest_1.it)('should throw Error because of invalid tool', async () => {
const step = getStep({ tool: null });
await (0, vitest_1.expect)(ApiService.getStepTransaction(step)).rejects.toThrow('Invalid step.');
(0, vitest_1.expect)(mockedFetch).toHaveBeenCalledTimes(0);
});
// more indepth checks for the action type should be done once we have real schema validation
(0, vitest_1.it)('should throw Error because of invalid action', async () => {
const step = getStep({ action: 'xxx' });
await (0, vitest_1.expect)(ApiService.getStepTransaction(step)).rejects.toThrow('Invalid step.');
(0, vitest_1.expect)(mockedFetch).toHaveBeenCalledTimes(0);
});
// more indepth checks for the estimate type should be done once we have real schema validation
(0, vitest_1.it)('should throw Error because of invalid estimate', async () => {
const step = getStep({
estimate: 'Is this really an estimate?',
});
await (0, vitest_1.expect)(ApiService.getStepTransaction(step)).rejects.toThrow('Invalid step.');
(0, vitest_1.expect)(mockedFetch).toHaveBeenCalledTimes(0);
});
});
(0, vitest_1.describe)('user input is valid', () => {
(0, vitest_1.describe)('and the backend call is successful', () => {
(0, vitest_1.it)('call the server once', async () => {
const step = getStep({});
await ApiService.getStepTransaction(step);
(0, vitest_1.expect)(mockedFetch).toHaveBeenCalledTimes(1);
});
});
});
});
});
(0, vitest_1.describe)('getGasRecommendation', () => {
(0, vitest_1.describe)('user input is invalid', () => {
(0, vitest_1.it)('throw an error', async () => {
await (0, vitest_1.expect)(ApiService.getGasRecommendation({
chainId: undefined,
})).rejects.toThrowError(new SDKError_1.SDKError(new errors_1.ValidationError('Required parameter "chainId" is missing.')));
(0, vitest_1.expect)(mockedFetch).toHaveBeenCalledTimes(0);
});
});
(0, vitest_1.describe)('user input is valid', () => {
(0, vitest_1.describe)('and the backend call is successful', () => {
(0, vitest_1.it)('call the server once', async () => {
await ApiService.getGasRecommendation({
chainId: types_1.ChainId.OPT,
});
(0, vitest_1.expect)(mockedFetch).toHaveBeenCalledTimes(1);
});
});
});
});
(0, vitest_1.describe)('getAvailableConnections', () => {
(0, vitest_1.it)('returns empty array in response', async () => {
server.use(msw_1.http.get(`${_config.apiUrl}/connections`, async () => msw_1.HttpResponse.json({ connections: [] })));
const connectionRequest = {
fromChain: types_1.ChainId.BSC,
toChain: types_1.ChainId.OPT,
fromToken: (0, data_types_1.findDefaultToken)(types_1.CoinKey.USDC, types_1.ChainId.BSC).address,
toToken: (0, data_types_1.findDefaultToken)(types_1.CoinKey.USDC, types_1.ChainId.OPT).address,
allowBridges: ['connext', 'uniswap', 'polygon'],
allowExchanges: ['1inch', 'ParaSwap', 'SushiSwap'],
denyBridges: ['Hop', 'Multichain'],
preferBridges: ['Hyphen', 'Across'],
denyExchanges: ['UbeSwap', 'BeamSwap'],
preferExchanges: ['Evmoswap', 'Diffusion'],
};
const generatedURL = 'https://li.quest/v1/connections?fromChain=56&fromToken=0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d&toChain=10&toToken=0x0b2c639c533813f4aa9d7837caf62653d097ff85&allowBridges=connext&allowBridges=uniswap&allowBridges=polygon&denyBridges=Hop&denyBridges=Multichain&preferBridges=Hyphen&preferBridges=Across&allowExchanges=1inch&allowExchanges=ParaSwap&allowExchanges=SushiSwap&denyExchanges=UbeSwap&denyExchanges=BeamSwap&preferExchanges=Evmoswap&preferExchanges=Diffusion';
await (0, vitest_1.expect)(ApiService.getConnections(connectionRequest)).resolves.toEqual({
connections: [],
});
(0, vitest_1.expect)(mockedFetch.mock.calls[0][0].href).toEqual(generatedURL);
(0, vitest_1.expect)(mockedFetch).toHaveBeenCalledOnce();
});
});
(0, vitest_1.describe)('getTransactionHistory', () => {
(0, vitest_1.it)('returns empty array in response', async () => {
server.use(msw_1.http.get(`${_config.apiUrl}/analytics/transfers`, async () => msw_1.HttpResponse.json({})));
const walletAnalyticsRequest = {
fromTimestamp: 1696326609361,
toTimestamp: 1696326609362,
wallet: '0x5520abcd',
};
const generatedURL = 'https://li.quest/v1/analytics/transfers?integrator=lifi-sdk&wallet=0x5520abcd&fromTimestamp=1696326609361&toTimestamp=1696326609362';
await (0, vitest_1.expect)(ApiService.getTransactionHistory(walletAnalyticsRequest)).resolves.toEqual({});
(0, vitest_1.expect)(mockedFetch.mock.calls[0][0].href).toEqual(generatedURL);
(0, vitest_1.expect)(mockedFetch).toHaveBeenCalledOnce();
});
});
});