Skip to content
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
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
Loading