Skip to content
78 changes: 78 additions & 0 deletions src/actions/actions.unit.handlers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { findDefaultToken } from '@lifi/data-types'
import { ChainId, CoinKey } from '@lifi/types'
import { HttpResponse, http } from 'msw'
import { setupServer } from 'msw/node'
import { afterAll, afterEach, beforeAll, beforeEach, vi } from 'vitest'
import { createClient } from '../client/createClient.js'
import { requestSettings } from '../request.js'

const client = createClient({
integrator: 'lifi-sdk',
})

export const handlers = [
http.post(`${client.config.apiUrl}/advanced/routes`, async () => {
return HttpResponse.json({})
}),
http.post(`${client.config.apiUrl}/advanced/possibilities`, async () =>
HttpResponse.json({})
),
http.get(`${client.config.apiUrl}/token`, async () => HttpResponse.json({})),
http.get(`${client.config.apiUrl}/quote`, async () => HttpResponse.json({})),
http.get(`${client.config.apiUrl}/status`, async () => HttpResponse.json({})),
http.get(`${client.config.apiUrl}/chains`, async () =>
HttpResponse.json({ chains: [{ id: 1 }] })
),
http.get(`${client.config.apiUrl}/tools`, async () =>
HttpResponse.json({ bridges: [], exchanges: [] })
),
http.get(`${client.config.apiUrl}/tokens`, async () =>
HttpResponse.json({
tokens: {
[ChainId.ETH]: [findDefaultToken(CoinKey.ETH, ChainId.ETH)],
},
})
),
http.post(`${client.config.apiUrl}/advanced/stepTransaction`, async () =>
HttpResponse.json({})
),
http.get(`${client.config.apiUrl}/gas/suggestion/${ChainId.OPT}`, async () =>
HttpResponse.json({})
),
http.get(`${client.config.apiUrl}/connections`, async () =>
HttpResponse.json({ connections: [] })
),
http.get(`${client.config.apiUrl}/analytics/transfers`, async () =>
HttpResponse.json({})
),
]

/**
* Sets up MSW server with common handlers for HTTP-based tests
* Call this function at the top level of your test file
*/
export const setupTestServer = () => {
const server = setupServer(...handlers)

beforeAll(() => {
server.listen({
onUnhandledRequest: 'warn',
})
requestSettings.retries = 0
})

beforeEach(() => {
vi.clearAllMocks()
})

afterEach(() => server.resetHandlers())

afterAll(() => {
requestSettings.retries = 1
server.close()
})

return server
}

export { client }
54 changes: 54 additions & 0 deletions src/actions/getChains.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type {
ChainsRequest,
ChainsResponse,
ExtendedChain,
RequestOptions,
} from '@lifi/types'
import { request } from '../request.js'
import type { SDKBaseConfig, SDKClient } from '../types/core.js'
import { withDedupe } from '../utils/withDedupe.js'

/**
* Get all available chains
* @param client - The SDK client
* @param params - The configuration of the requested chains
* @param options - Request options
* @returns A list of all available chains
* @throws {LiFiError} Throws a LiFiError if request fails.
*/
export const getChains = async (
client: SDKClient,
params?: ChainsRequest,
options?: RequestOptions
): Promise<ExtendedChain[]> => {
return await getChainsFromConfig(client.config, params, options)
}

export const getChainsFromConfig = async (
config: SDKBaseConfig,
params?: ChainsRequest,
options?: RequestOptions
): Promise<ExtendedChain[]> => {
if (params) {
for (const key of Object.keys(params)) {
if (!params[key as keyof ChainsRequest]) {
delete params[key as keyof ChainsRequest]
}
}
}
const urlSearchParams = new URLSearchParams(
params as Record<string, string>
).toString()
const response = await withDedupe(
() =>
request<ChainsResponse>(
config,
`${config.apiUrl}/chains?${urlSearchParams}`,
{
signal: options?.signal,
}
),
{ id: `${getChains.name}.${urlSearchParams}` }
)
return response.chains
}
19 changes: 19 additions & 0 deletions src/actions/getChains.unit.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { describe, expect, it, vi } from 'vitest'
import * as request from '../request.js'
import { client, setupTestServer } from './actions.unit.handlers.js'
import { getChains } from './getChains.js'

const mockedFetch = vi.spyOn(request, 'request')

describe('getChains', () => {
setupTestServer()

describe('and the backend call is successful', () => {
it('call the server once', async () => {
const chains = await getChains(client)

expect(chains[0]?.id).toEqual(1)
expect(mockedFetch).toHaveBeenCalledTimes(1)
})
})
})
55 changes: 55 additions & 0 deletions src/actions/getConnections.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type {
ConnectionsRequest,
ConnectionsResponse,
RequestOptions,
} from '@lifi/types'
import { request } from '../request.js'
import type { SDKClient } from '../types/core.js'

/**
* Get all the available connections for swap/bridging tokens
* @param client - The SDK client
* @param connectionRequest ConnectionsRequest
* @param options - Request options
* @returns ConnectionsResponse
*/
export const getConnections = async (
client: SDKClient,
connectionRequest: ConnectionsRequest,
options?: RequestOptions
): Promise<ConnectionsResponse> => {
const url = new URL(`${client.config.apiUrl}/connections`)

const { fromChain, fromToken, toChain, toToken } = connectionRequest

if (fromChain) {
url.searchParams.append('fromChain', fromChain as unknown as string)
}
if (fromToken) {
url.searchParams.append('fromToken', fromToken)
}
if (toChain) {
url.searchParams.append('toChain', toChain as unknown as string)
}
if (toToken) {
url.searchParams.append('toToken', toToken)
}
const connectionRequestArrayParams: Array<keyof ConnectionsRequest> = [
'allowBridges',
'denyBridges',
'preferBridges',
'allowExchanges',
'denyExchanges',
'preferExchanges',
]
for (const parameter of connectionRequestArrayParams) {
const connectionRequestArrayParam = connectionRequest[parameter] as string[]

if (connectionRequestArrayParam?.length) {
for (const value of connectionRequestArrayParam) {
url.searchParams.append(parameter, value)
}
}
}
return await request<ConnectionsResponse>(client.config, url, options)
}
45 changes: 45 additions & 0 deletions src/actions/getConnections.unit.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { findDefaultToken } from '@lifi/data-types'
import type { ConnectionsRequest } from '@lifi/types'
import { ChainId, CoinKey } from '@lifi/types'
import { HttpResponse, http } from 'msw'
import { describe, expect, it, vi } from 'vitest'
import * as request from '../request.js'
import { client, setupTestServer } from './actions.unit.handlers.js'
import { getConnections } from './getConnections.js'

const mockedFetch = vi.spyOn(request, 'request')

describe('getConnections', () => {
const server = setupTestServer()

it('returns empty array in response', async () => {
server.use(
http.get(`${client.config.apiUrl}/connections`, async () =>
HttpResponse.json({ connections: [] })
)
)

const connectionRequest: ConnectionsRequest = {
fromChain: ChainId.BSC,
toChain: ChainId.OPT,
fromToken: findDefaultToken(CoinKey.USDC, ChainId.BSC).address,
toToken: findDefaultToken(CoinKey.USDC, 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 expect(getConnections(client, connectionRequest)).resolves.toEqual({
connections: [],
})

expect((mockedFetch.mock.calls[0][1] as URL).href).toEqual(generatedURL)
expect(mockedFetch).toHaveBeenCalledOnce()
})
})
81 changes: 81 additions & 0 deletions src/actions/getContractCallsQuote.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import type {
ContractCallsQuoteRequest,
LiFiStep,
RequestOptions,
} from '@lifi/types'
import {
isContractCallsRequestWithFromAmount,
isContractCallsRequestWithToAmount,
} from '@lifi/types'
import { ValidationError } from '../errors/errors.js'
import { SDKError } from '../errors/SDKError.js'
import { request } from '../request.js'
import type { SDKClient } from '../types/core.js'

/**
* Get a quote for a destination contract call
* @param client - The SDK client
* @param params - The configuration of the requested destination call
* @param options - Request options
* @throws {LiFiError} - Throws a LiFiError if request fails
* @returns - Returns step.
*/
export const getContractCallsQuote = async (
client: SDKClient,
params: ContractCallsQuoteRequest,
options?: RequestOptions
): Promise<LiFiStep> => {
// validation
const requiredParameters: Array<keyof ContractCallsQuoteRequest> = [
'fromChain',
'fromToken',
'fromAddress',
'toChain',
'toToken',
'contractCalls',
]
for (const requiredParameter of requiredParameters) {
if (!params[requiredParameter]) {
throw new SDKError(
new ValidationError(
`Required parameter "${requiredParameter}" is missing.`
)
)
}
}
if (
!isContractCallsRequestWithFromAmount(params) &&
!isContractCallsRequestWithToAmount(params)
) {
throw new SDKError(
new ValidationError(
`Required parameter "fromAmount" or "toAmount" is missing.`
)
)
}
// apply defaults
// option.order is not used in this endpoint
params.integrator ??= client.config.integrator
params.slippage ??= client.config.routeOptions?.slippage
params.referrer ??= client.config.routeOptions?.referrer
params.fee ??= client.config.routeOptions?.fee
params.allowBridges ??= client.config.routeOptions?.bridges?.allow
params.denyBridges ??= client.config.routeOptions?.bridges?.deny
params.preferBridges ??= client.config.routeOptions?.bridges?.prefer
params.allowExchanges ??= client.config.routeOptions?.exchanges?.allow
params.denyExchanges ??= client.config.routeOptions?.exchanges?.deny
params.preferExchanges ??= client.config.routeOptions?.exchanges?.prefer
// send request
return await request<LiFiStep>(
client.config,
`${client.config.apiUrl}/quote/contractCalls`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
signal: options?.signal,
}
)
}
Loading