UNPKG

@getclave/lifi-sdk

Version:

LI.FI Any-to-Any Cross-Chain-Swap SDK

382 lines (381 loc) 20.6 kB
"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(); }); }); });