From b0d9b3530907df427c14dd08c62cf7deed8a87e1 Mon Sep 17 00:00:00 2001 From: Lizaveta Miasayedava Date: Fri, 10 Oct 2025 12:16:14 +0100 Subject: [PATCH 01/13] feat: pass config to actions --- src/config.ts | 100 --------------- src/core/BaseStepExecutor.ts | 6 +- src/core/EVM/EVMStepExecutor.ts | 63 ++++++---- src/core/EVM/checkAllowance.ts | 11 ++ src/core/EVM/checkPermitSupport.ts | 32 +++-- src/core/EVM/getActionWithFallback.ts | 4 +- src/core/EVM/getAllowance.int.spec.ts | 17 ++- src/core/EVM/getAllowance.ts | 28 +++-- src/core/EVM/getEVMBalance.int.spec.ts | 7 +- src/core/EVM/getEVMBalance.ts | 13 +- src/core/EVM/isBatchingSupported.ts | 23 ++-- src/core/EVM/permits/getNativePermit.ts | 69 +++++++++-- .../permits/getPermitTransferFromValues.ts | 3 + src/core/EVM/permits/signPermit2Message.ts | 3 + src/core/EVM/publicClient.ts | 9 +- src/core/EVM/resolveENSAddress.ts | 4 +- src/core/EVM/resolveEVMAddress.ts | 6 +- src/core/EVM/setAllowance.int.spec.ts | 4 + src/core/EVM/setAllowance.ts | 15 ++- src/core/EVM/types.ts | 8 +- src/core/EVM/uns/resolveUNSAddress.ts | 7 +- src/core/EVM/utils.ts | 18 ++- .../EVM/waitForRelayedTransactionReceipt.ts | 4 +- src/core/EVM/waitForTransactionReceipt.ts | 5 +- src/core/Solana/SolanaStepExecutor.ts | 35 ++++-- src/core/Solana/connection.ts | 14 ++- src/core/Solana/getSolanaBalance.int.spec.ts | 6 +- src/core/Solana/getSolanaBalance.ts | 15 ++- src/core/Solana/sendAndConfirmTransaction.ts | 4 +- src/core/Sui/SuiStepExecutor.ts | 19 ++- src/core/Sui/getSuiBalance.int.spec.ts | 10 +- src/core/Sui/getSuiBalance.ts | 9 +- src/core/Sui/suiClient.ts | 8 +- src/core/UTXO/UTXOStepExecutor.ts | 14 ++- src/core/UTXO/getUTXOBalance.int.spec.ts | 13 +- src/core/UTXO/getUTXOBalance.ts | 4 +- src/core/UTXO/getUTXOPublicClient.ts | 6 +- src/core/checkBalance.ts | 10 +- src/core/execution.ts | 22 ++-- src/core/execution.unit.handlers.ts | 3 +- src/core/execution.unit.spec.ts | 4 +- src/core/rpc.ts | 7 +- src/core/types.ts | 27 +++- .../waitForDestinationChainTransaction.ts | 5 +- src/core/waitForTransactionStatus.ts | 5 +- src/createConfig.ts | 115 ++++++++++++++++-- src/index.ts | 1 - src/request.ts | 8 +- src/request.unit.spec.ts | 18 +-- src/services/api.int.spec.ts | 5 +- src/services/api.ts | 78 ++++++++---- src/services/api.unit.handlers.ts | 3 +- src/services/api.unit.spec.ts | 102 +++++++++------- src/services/balance.ts | 15 ++- src/services/balance.unit.spec.ts | 48 +++++--- src/services/getNameServiceAddress.ts | 11 +- src/utils/getTransactionMessage.ts | 3 +- 57 files changed, 733 insertions(+), 373 deletions(-) delete mode 100644 src/config.ts diff --git a/src/config.ts b/src/config.ts deleted file mode 100644 index 9c322fc1..00000000 --- a/src/config.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { ChainId, type ChainType, type ExtendedChain } from '@lifi/types' -import type { SDKProvider } from './core/types.js' -import type { RPCUrls, SDKBaseConfig, SDKConfig } from './types/internal.js' - -export const config = (() => { - const _config: SDKBaseConfig = { - integrator: 'lifi-sdk', - apiUrl: 'https://li.quest/v1', - rpcUrls: {}, - chains: [], - providers: [], - preloadChains: true, - debug: false, - } - let _loading: Promise | undefined - return { - set loading(loading: Promise) { - _loading = loading - }, - get() { - return _config - }, - set(options: SDKConfig) { - const { chains, providers, rpcUrls, ...otherOptions } = options - Object.assign(_config, otherOptions) - if (chains) { - this.setChains(chains) - } - if (providers) { - this.setProviders(providers) - } - if (rpcUrls) { - this.setRPCUrls(rpcUrls) - } - return _config - }, - getProvider(type: ChainType) { - return _config.providers.find((provider) => provider.type === type) - }, - setProviders(providers: SDKProvider[]) { - const providerMap = new Map( - _config.providers.map((provider) => [provider.type, provider]) - ) - for (const provider of providers) { - providerMap.set(provider.type, provider) - } - _config.providers = Array.from(providerMap.values()) - }, - setChains(chains: ExtendedChain[]) { - const rpcUrls = chains.reduce((rpcUrls, chain) => { - if (chain.metamask?.rpcUrls?.length) { - rpcUrls[chain.id as ChainId] = chain.metamask.rpcUrls - } - return rpcUrls - }, {} as RPCUrls) - this.setRPCUrls(rpcUrls, [ChainId.SOL]) - _config.chains = chains - _loading = undefined - }, - async getChains() { - if (_loading) { - await _loading - } - return _config.chains - }, - async getChainById(chainId: ChainId) { - if (_loading) { - await _loading - } - const chain = _config.chains?.find((chain) => chain.id === chainId) - if (!chain) { - throw new Error(`ChainId ${chainId} not found`) - } - return chain - }, - setRPCUrls(rpcUrls: RPCUrls, skipChains?: ChainId[]) { - for (const rpcUrlsKey in rpcUrls) { - const chainId = Number(rpcUrlsKey) as ChainId - const urls = rpcUrls[chainId] - if (!urls?.length) { - continue - } - if (!_config.rpcUrls[chainId]?.length) { - _config.rpcUrls[chainId] = Array.from(urls) - } else if (!skipChains?.includes(chainId)) { - const filteredUrls = urls.filter( - (url) => !_config.rpcUrls[chainId]?.includes(url) - ) - _config.rpcUrls[chainId].push(...filteredUrls) - } - } - }, - async getRPCUrls() { - if (_loading) { - await _loading - } - return _config.rpcUrls - }, - } -})() diff --git a/src/core/BaseStepExecutor.ts b/src/core/BaseStepExecutor.ts index 854f15a7..e4f24c20 100644 --- a/src/core/BaseStepExecutor.ts +++ b/src/core/BaseStepExecutor.ts @@ -3,6 +3,7 @@ import { StatusManager } from './StatusManager.js' import type { ExecutionOptions, InteractionSettings, + SDKProviderConfig, StepExecutor, StepExecutorOptions, } from './types.js' @@ -36,5 +37,8 @@ export abstract class BaseStepExecutor implements StepExecutor { this.allowExecution = interactionSettings.allowExecution } - abstract executeStep(step: LiFiStep): Promise + abstract executeStep( + config: SDKProviderConfig, + step: LiFiStep + ): Promise } diff --git a/src/core/EVM/EVMStepExecutor.ts b/src/core/EVM/EVMStepExecutor.ts index 77bc382c..35da4b2e 100644 --- a/src/core/EVM/EVMStepExecutor.ts +++ b/src/core/EVM/EVMStepExecutor.ts @@ -16,7 +16,6 @@ import { signTypedData, } from 'viem/actions' import { getAction, isHex } from 'viem/utils' -import { config } from '../../config.js' import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' import { @@ -31,6 +30,7 @@ import { stepComparison } from '../stepComparison.js' import type { LiFiStepExtended, Process, + SDKProviderConfig, StepExecutorOptions, TransactionMethodType, TransactionParameters, @@ -124,19 +124,22 @@ export class EVMStepExecutor extends BaseStepExecutor { return updatedClient } - waitForTransaction = async ({ - step, - process, - fromChain, - toChain, - isBridgeExecution, - }: { - step: LiFiStepExtended - process: Process - fromChain: ExtendedChain - toChain: ExtendedChain - isBridgeExecution: boolean - }) => { + waitForTransaction = async ( + config: SDKProviderConfig, + { + step, + process, + fromChain, + toChain, + isBridgeExecution, + }: { + step: LiFiStepExtended + process: Process + fromChain: ExtendedChain + toChain: ExtendedChain + isBridgeExecution: boolean + } + ) => { const updateProcessWithReceipt = ( transactionReceipt: TransactionReceipt | WalletCallReceipt | undefined ) => { @@ -187,12 +190,14 @@ export class EVMStepExecutor extends BaseStepExecutor { break case 'relayed': transactionReceipt = await waitForRelayedTransactionReceipt( + config, process.taskId as Hash, step ) break default: transactionReceipt = await waitForTransactionReceipt({ + config, client: this.client, chainId: fromChain.id, txHash: process.txHash as Hash, @@ -212,6 +217,7 @@ export class EVMStepExecutor extends BaseStepExecutor { } await waitForDestinationChainTransaction( + config, step, process, fromChain, @@ -221,6 +227,7 @@ export class EVMStepExecutor extends BaseStepExecutor { } private prepareUpdatedStep = async ( + config: SDKProviderConfig, step: LiFiStepExtended, signedTypedData?: SignedTypedData[] ) => { @@ -230,7 +237,7 @@ export class EVMStepExecutor extends BaseStepExecutor { const gaslessStep = isGaslessStep(step) let updatedStep: LiFiStep if (relayerStep && gaslessStep) { - const updatedRelayedStep = await getRelayerQuote({ + const updatedRelayedStep = await getRelayerQuote(config, { fromChain: stepBase.action.fromChainId, fromToken: stepBase.action.fromToken.address, fromAddress: stepBase.action.fromAddress!, @@ -253,7 +260,7 @@ export class EVMStepExecutor extends BaseStepExecutor { const params = filteredSignedTypedData?.length ? { ...restStepBase, typedData: filteredSignedTypedData } : restStepBase - updatedStep = await getStepTransaction(params) + updatedStep = await getStepTransaction(config, params) } const comparedStep = await stepComparison( @@ -296,7 +303,7 @@ export class EVMStepExecutor extends BaseStepExecutor { // : undefined, maxPriorityFeePerGas: this.client.account?.type === 'local' - ? await getMaxPriorityFeePerGas(this.client) + ? await getMaxPriorityFeePerGas(config, this.client) : step.transactionRequest.maxPriorityFeePerGas ? BigInt(step.transactionRequest.maxPriorityFeePerGas) : undefined, @@ -327,6 +334,7 @@ export class EVMStepExecutor extends BaseStepExecutor { } private estimateTransactionRequest = async ( + config: SDKProviderConfig, transactionRequest: TransactionParameters, fromChain: ExtendedChain ) => { @@ -335,6 +343,7 @@ export class EVMStepExecutor extends BaseStepExecutor { try { // Try to re-estimate the gas due to additional Permit data const estimatedGas = await getActionWithFallback( + config, this.client, estimateGas, 'estimateGas', @@ -360,6 +369,7 @@ export class EVMStepExecutor extends BaseStepExecutor { } executeStep = async ( + config: SDKProviderConfig, step: LiFiStepExtended, // Explicitly set to true if the wallet rejected the upgrade to 7702 account, based on the EIP-5792 capabilities atomicityNotReady = false @@ -403,7 +413,7 @@ export class EVMStepExecutor extends BaseStepExecutor { const batchingSupported = atomicityNotReady || step.tool === 'thorswap' || isRelayerStep(step) ? false - : await isBatchingSupported({ + : await isBatchingSupported(config, { client: this.client, chainId: fromChain.id, }) @@ -448,6 +458,7 @@ export class EVMStepExecutor extends BaseStepExecutor { if (checkForAllowance) { // Check if token needs approval and get approval transaction or message data when available const allowanceResult = await checkAllowance({ + config, checkClient: this.checkClient, chain: fromChain, step, @@ -482,6 +493,7 @@ export class EVMStepExecutor extends BaseStepExecutor { try { if (process?.status === 'DONE') { await waitForDestinationChainTransaction( + config, step, process, fromChain, @@ -499,7 +511,7 @@ export class EVMStepExecutor extends BaseStepExecutor { return step } - await this.waitForTransaction({ + await this.waitForTransaction(config, { step, process, fromChain, @@ -517,11 +529,11 @@ export class EVMStepExecutor extends BaseStepExecutor { chainId: fromChain.id, }) - await checkBalance(this.client.account!.address, step) + await checkBalance(config, this.client.account!.address, step) // Try to prepare a new transaction request and update the step with typed data let { transactionRequest, isRelayerTransaction } = - await this.prepareUpdatedStep(step, signedTypedData) + await this.prepareUpdatedStep(config, step, signedTypedData) // Make sure that the chain is still correct const updatedClient = await this.checkClient(step, process) @@ -614,7 +626,7 @@ export class EVMStepExecutor extends BaseStepExecutor { // biome-ignore lint/correctness/noUnusedVariables: destructuring const { execution, ...stepBase } = step - const relayedTransaction = await relayTransaction({ + const relayedTransaction = await relayTransaction(config, { ...stepBase, typedData: signedTypedData, }) @@ -647,7 +659,7 @@ export class EVMStepExecutor extends BaseStepExecutor { process.type, 'MESSAGE_REQUIRED' ) - const permit2Signature = await signPermit2Message({ + const permit2Signature = await signPermit2Message(config, { client: this.client, chain: fromChain, tokenAddress: step.action.fromToken.address as Address, @@ -671,6 +683,7 @@ export class EVMStepExecutor extends BaseStepExecutor { if (signedNativePermitTypedData || permit2Supported) { transactionRequest = await this.estimateTransactionRequest( + config, transactionRequest, fromChain ) @@ -709,7 +722,7 @@ export class EVMStepExecutor extends BaseStepExecutor { } ) - await this.waitForTransaction({ + await this.waitForTransaction(config, { step, process, fromChain, @@ -723,7 +736,7 @@ export class EVMStepExecutor extends BaseStepExecutor { // If the wallet rejected the upgrade to 7702 account, we need to try again with the standard flow if (isAtomicReadyWalletRejectedUpgradeError(e) && !atomicityNotReady) { step.execution = undefined - return this.executeStep(step, true) + return this.executeStep(config, step, true) } const error = await parseEVMErrors(e, step, process) process = this.statusManager.updateProcess( diff --git a/src/core/EVM/checkAllowance.ts b/src/core/EVM/checkAllowance.ts index 2154f82d..647a53db 100644 --- a/src/core/EVM/checkAllowance.ts +++ b/src/core/EVM/checkAllowance.ts @@ -9,6 +9,7 @@ import type { LiFiStepExtended, Process, ProcessType, + SDKProviderConfig, } from '../types.js' import { getActionWithFallback } from './getActionWithFallback.js' import { getAllowance } from './getAllowance.js' @@ -22,6 +23,7 @@ import { getDomainChainId } from './utils.js' import { waitForTransactionReceipt } from './waitForTransactionReceipt.js' type CheckAllowanceParams = { + config: SDKProviderConfig checkClient( step: LiFiStepExtended, process: Process, @@ -51,6 +53,7 @@ type AllowanceResult = } export const checkAllowance = async ({ + config, checkClient, chain, step, @@ -164,6 +167,7 @@ export const checkAllowance = async ({ // Handle existing pending transaction if (sharedProcess.txHash && sharedProcess.status !== 'DONE') { await waitForApprovalTransaction( + config, updatedClient, sharedProcess.txHash as Address, sharedProcess.type, @@ -184,6 +188,7 @@ export const checkAllowance = async ({ const fromAmount = BigInt(step.action.fromAmount) const approved = await getAllowance( + config, updatedClient, step.action.fromToken.address as Address, updatedClient.account!.address, @@ -203,10 +208,12 @@ export const checkAllowance = async ({ let nativePermitData: NativePermitData | undefined if (isNativePermitAvailable) { nativePermitData = await getActionWithFallback( + config, updatedClient, getNativePermit, 'getNativePermit', { + config, chainId: chain.id, tokenAddress: step.action.fromToken.address as Address, spenderAddress: chain.permit2Proxy as Address, @@ -274,6 +281,7 @@ export const checkAllowance = async ({ // Set new allowance const approveAmount = permit2Supported ? MaxUint256 : fromAmount const approveTxHash = await setAllowance( + config, updatedClient, step.action.fromToken.address as Address, spenderAddress as Address, @@ -302,6 +310,7 @@ export const checkAllowance = async ({ } await waitForApprovalTransaction( + config, updatedClient, approveTxHash, sharedProcess.type, @@ -332,6 +341,7 @@ export const checkAllowance = async ({ } const waitForApprovalTransaction = async ( + config: SDKProviderConfig, client: Client, txHash: Hash, processType: ProcessType, @@ -348,6 +358,7 @@ const waitForApprovalTransaction = async ( }) const transactionReceipt = await waitForTransactionReceipt({ + config, client, chainId: chain.id, txHash, diff --git a/src/core/EVM/checkPermitSupport.ts b/src/core/EVM/checkPermitSupport.ts index 9be2bb6d..c38ef648 100644 --- a/src/core/EVM/checkPermitSupport.ts +++ b/src/core/EVM/checkPermitSupport.ts @@ -1,7 +1,7 @@ import type { ExtendedChain } from '@lifi/types' import { ChainType } from '@lifi/types' import type { Address } from 'viem' -import { config } from '../../config.js' +import type { SDKProviderConfig } from '../types.js' import { getActionWithFallback } from './getActionWithFallback.js' import { getAllowance } from './getAllowance.js' import { getNativePermit } from './permits/getNativePermit.js' @@ -27,30 +27,35 @@ type PermitSupport = { * @param amount - The amount to check allowance against for Permit2 * @returns Object indicating which permit types are supported */ -export const checkPermitSupport = async ({ - chain, - tokenAddress, - ownerAddress, - amount, -}: { - chain: ExtendedChain - tokenAddress: Address - ownerAddress: Address - amount: bigint -}): Promise => { +export const checkPermitSupport = async ( + config: SDKProviderConfig, + { + chain, + tokenAddress, + ownerAddress, + amount, + }: { + chain: ExtendedChain + tokenAddress: Address + ownerAddress: Address + amount: bigint + } +): Promise => { const provider = config.getProvider(ChainType.EVM) as EVMProvider | undefined let client = await provider?.getWalletClient?.() if (!client) { - client = await getPublicClient(chain.id) + client = await getPublicClient(config, chain.id) } const nativePermit = await getActionWithFallback( + config, client, getNativePermit, 'getNativePermit', { + config, chainId: chain.id, tokenAddress, spenderAddress: chain.permit2Proxy as Address, @@ -62,6 +67,7 @@ export const checkPermitSupport = async ({ // Check Permit2 allowance if available on chain if (chain.permit2) { permit2Allowance = await getAllowance( + config, client, tokenAddress, ownerAddress, diff --git a/src/core/EVM/getActionWithFallback.ts b/src/core/EVM/getActionWithFallback.ts index f54e4816..212060ee 100644 --- a/src/core/EVM/getActionWithFallback.ts +++ b/src/core/EVM/getActionWithFallback.ts @@ -8,6 +8,7 @@ import type { WalletActions, } from 'viem' import { getAction } from 'viem/utils' +import type { SDKProviderConfig } from '../types.js' import { getPublicClient } from './publicClient.js' /** @@ -33,6 +34,7 @@ export const getActionWithFallback = async < parameters, returnType, >( + config: SDKProviderConfig, walletClient: client, actionFn: (_: client, parameters: parameters) => returnType, name: keyof PublicActions | keyof WalletActions | (string & {}), @@ -52,7 +54,7 @@ export const getActionWithFallback = async < throw error } - const publicClient = await getPublicClient(chainId) + const publicClient = await getPublicClient(config, chainId) return await getAction(publicClient, actionFn, name)(params) } } diff --git a/src/core/EVM/getAllowance.int.spec.ts b/src/core/EVM/getAllowance.int.spec.ts index 776ef466..49ff7c08 100644 --- a/src/core/EVM/getAllowance.int.spec.ts +++ b/src/core/EVM/getAllowance.int.spec.ts @@ -3,6 +3,7 @@ import { ChainId, CoinKey } from '@lifi/types' import type { Address } from 'viem' import { beforeAll, describe, expect, it } from 'vitest' import { setupTestEnvironment } from '../../../tests/setup.js' +import { createConfig } from '../../createConfig.js' import { getTokens } from '../../services/api.js' import { getAllowance, @@ -12,6 +13,7 @@ import { import { getPublicClient } from './publicClient.js' import type { TokenSpender } from './types.js' +const config = createConfig({ integrator: 'lifi-sdk' }) const defaultWalletAddress = '0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0' const defaultSpenderAddress = '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE' const memeToken = { @@ -31,8 +33,9 @@ beforeAll(setupTestEnvironment) describe('allowance integration tests', { retry: retryTimes, timeout }, () => { it('should work for ERC20 on POL', async () => { - const client = await getPublicClient(memeToken.chainId) + const client = await getPublicClient(config, memeToken.chainId) const allowance = await getAllowance( + config, client, memeToken.address as Address, defaultWalletAddress, @@ -47,8 +50,9 @@ describe('allowance integration tests', { retry: retryTimes, timeout }, () => { { retry: retryTimes, timeout }, async () => { const token = findDefaultToken(CoinKey.POL, ChainId.POL) - const client = await getPublicClient(token.chainId) + const client = await getPublicClient(config, token.chainId) const allowance = await getAllowance( + config, client, token.address as Address, defaultWalletAddress, @@ -65,8 +69,9 @@ describe('allowance integration tests', { retry: retryTimes, timeout }, () => { async () => { const invalidToken = findDefaultToken(CoinKey.POL, ChainId.POL) invalidToken.address = '0x2170ed0880ac9a755fd29b2688956bd959f933f8' - const client = await getPublicClient(invalidToken.chainId) + const client = await getPublicClient(config, invalidToken.chainId) const allowance = await getAllowance( + config, client, invalidToken.address as Address, defaultWalletAddress, @@ -80,8 +85,9 @@ describe('allowance integration tests', { retry: retryTimes, timeout }, () => { 'should handle empty lists with multicall', { retry: retryTimes, timeout }, async () => { - const client = await getPublicClient(137) + const client = await getPublicClient(config, 137) const allowances = await getAllowanceMulticall( + config, client, 137, [], @@ -95,7 +101,7 @@ describe('allowance integration tests', { retry: retryTimes, timeout }, () => { 'should handle token lists with more than 10 tokens', { retry: retryTimes, timeout }, async () => { - const { tokens } = await getTokens({ + const { tokens } = await getTokens(config, { chains: [ChainId.POL], }) const filteredTokens = tokens[ChainId.POL] @@ -111,6 +117,7 @@ describe('allowance integration tests', { retry: retryTimes, timeout }, () => { if (tokenSpenders?.length) { const tokens = await getTokenAllowanceMulticall( + config, defaultWalletAddress, tokenSpenders.slice(0, 10) ) diff --git a/src/core/EVM/getAllowance.ts b/src/core/EVM/getAllowance.ts index b32a15e8..fc833134 100644 --- a/src/core/EVM/getAllowance.ts +++ b/src/core/EVM/getAllowance.ts @@ -2,6 +2,7 @@ import type { BaseToken, ChainId } from '@lifi/types' import type { Address, Client } from 'viem' import { multicall, readContract } from 'viem/actions' import { isZeroAddress } from '../../utils/isZeroAddress.js' +import type { SDKProviderConfig } from '../types.js' import { allowanceAbi } from './abi.js' import { getActionWithFallback } from './getActionWithFallback.js' import { getPublicClient } from './publicClient.js' @@ -13,6 +14,7 @@ import type { import { getMulticallAddress } from './utils.js' export const getAllowance = async ( + config: SDKProviderConfig, client: Client, tokenAddress: Address, ownerAddress: Address, @@ -20,6 +22,7 @@ export const getAllowance = async ( ): Promise => { try { const approved = await getActionWithFallback( + config, client, readContract, 'readContract', @@ -37,6 +40,7 @@ export const getAllowance = async ( } export const getAllowanceMulticall = async ( + config: SDKProviderConfig, client: Client, chainId: ChainId, tokens: TokenSpender[], @@ -45,7 +49,7 @@ export const getAllowanceMulticall = async ( if (!tokens.length) { return [] } - const multicallAddress = await getMulticallAddress(chainId) + const multicallAddress = await getMulticallAddress(config, chainId) if (!multicallAddress) { throw new Error(`No multicall address configured for chainId ${chainId}.`) } @@ -57,10 +61,16 @@ export const getAllowanceMulticall = async ( args: [ownerAddress, token.spenderAddress], })) - const results = await getActionWithFallback(client, multicall, 'multicall', { - contracts, - multicallAddress: multicallAddress as Address, - }) + const results = await getActionWithFallback( + config, + client, + multicall, + 'multicall', + { + contracts, + multicallAddress: multicallAddress as Address, + } + ) if (!results.length) { throw new Error( @@ -83,6 +93,7 @@ export const getAllowanceMulticall = async ( * @returns Returns allowance */ export const getTokenAllowance = async ( + config: SDKProviderConfig, token: BaseToken, ownerAddress: Address, spenderAddress: Address @@ -92,9 +103,10 @@ export const getTokenAllowance = async ( return } - const client = await getPublicClient(token.chainId) + const client = await getPublicClient(config, token.chainId) const approved = await getAllowance( + config, client, token.address as Address, ownerAddress, @@ -110,6 +122,7 @@ export const getTokenAllowance = async ( * @returns Returns array of tokens and their allowance */ export const getTokenAllowanceMulticall = async ( + config: SDKProviderConfig, ownerAddress: Address, tokens: TokenSpender[] ): Promise => { @@ -132,9 +145,10 @@ export const getTokenAllowanceMulticall = async ( const allowances = ( await Promise.all( chainKeys.map(async (chainId) => { - const client = await getPublicClient(chainId) + const client = await getPublicClient(config, chainId) // get allowances for current chain and token list return getAllowanceMulticall( + config, client, chainId, tokenDataByChain[chainId], diff --git a/src/core/EVM/getEVMBalance.int.spec.ts b/src/core/EVM/getEVMBalance.int.spec.ts index f1f5ab11..754c743d 100644 --- a/src/core/EVM/getEVMBalance.int.spec.ts +++ b/src/core/EVM/getEVMBalance.int.spec.ts @@ -4,9 +4,12 @@ import { ChainId, CoinKey } from '@lifi/types' import type { Address } from 'viem' import { beforeAll, describe, expect, it } from 'vitest' import { setupTestEnvironment } from '../../../tests/setup.js' +import { createConfig } from '../../createConfig.js' import { getTokens } from '../../services/api.js' import { getEVMBalance } from './getEVMBalance.js' +const config = createConfig({ integrator: 'lifi-sdk' }) + const defaultWalletAddress = '0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0' const retryTimes = 2 @@ -20,6 +23,7 @@ describe('getBalances integration tests', () => { tokens: StaticToken[] ) => { const tokenBalances = await getEVMBalance( + config, walletAddress as Address, tokens as Token[] ) @@ -81,6 +85,7 @@ describe('getBalances integration tests', () => { const tokens = [findDefaultToken(CoinKey.USDC, ChainId.POL), invalidToken] const tokenBalances = await getEVMBalance( + config, walletAddress, tokens as Token[] ) @@ -116,7 +121,7 @@ describe('getBalances integration tests', () => { { retry: retryTimes, timeout }, async () => { const walletAddress = defaultWalletAddress - const { tokens } = await getTokens({ + const { tokens } = await getTokens(config, { chains: [ChainId.OPT], }) expect(tokens[ChainId.OPT]?.length).toBeGreaterThan(100) diff --git a/src/core/EVM/getEVMBalance.ts b/src/core/EVM/getEVMBalance.ts index b6da8a42..9ac24124 100644 --- a/src/core/EVM/getEVMBalance.ts +++ b/src/core/EVM/getEVMBalance.ts @@ -7,11 +7,13 @@ import { readContract, } from 'viem/actions' import { isZeroAddress } from '../../utils/isZeroAddress.js' +import type { SDKProviderConfig } from '../types.js' import { balanceOfAbi, getEthBalanceAbi } from './abi.js' import { getPublicClient } from './publicClient.js' import { getMulticallAddress } from './utils.js' export const getEVMBalance = async ( + config: SDKProviderConfig, walletAddress: Address, tokens: Token[] ): Promise => { @@ -25,26 +27,28 @@ export const getEVMBalance = async ( } } - const multicallAddress = await getMulticallAddress(chainId) + const multicallAddress = await getMulticallAddress(config, chainId) if (multicallAddress && tokens.length > 1) { return getEVMBalanceMulticall( + config, chainId, tokens, walletAddress, multicallAddress ) } - return getEVMBalanceDefault(chainId, tokens, walletAddress) + return getEVMBalanceDefault(config, chainId, tokens, walletAddress) } const getEVMBalanceMulticall = async ( + config: SDKProviderConfig, chainId: ChainId, tokens: Token[], walletAddress: string, multicallAddress: string ): Promise => { - const client = await getPublicClient(chainId) + const client = await getPublicClient(config, chainId) const contracts = tokens.map((token) => { if (isZeroAddress(token.address)) { @@ -85,11 +89,12 @@ const getEVMBalanceMulticall = async ( } const getEVMBalanceDefault = async ( + config: SDKProviderConfig, chainId: ChainId, tokens: Token[], walletAddress: Address ): Promise => { - const client = await getPublicClient(chainId) + const client = await getPublicClient(config, chainId) const queue: Promise[] = tokens.map((token) => { if (isZeroAddress(token.address)) { diff --git a/src/core/EVM/isBatchingSupported.ts b/src/core/EVM/isBatchingSupported.ts index 90c70ac6..8ae70b31 100644 --- a/src/core/EVM/isBatchingSupported.ts +++ b/src/core/EVM/isBatchingSupported.ts @@ -2,19 +2,22 @@ import { ChainType } from '@lifi/types' import type { Client } from 'viem' import { getCapabilities } from 'viem/actions' import { getAction } from 'viem/utils' -import { config } from '../../config.js' import { sleep } from '../../utils/sleep.js' +import type { SDKProviderConfig } from '../types.js' import type { EVMProvider } from './types.js' -export async function isBatchingSupported({ - client, - chainId, - skipReady = false, -}: { - client?: Client - chainId: number - skipReady?: boolean -}): Promise { +export async function isBatchingSupported( + config: SDKProviderConfig, + { + client, + chainId, + skipReady = false, + }: { + client?: Client + chainId: number + skipReady?: boolean + } +): Promise { const _client = client ?? (await ( diff --git a/src/core/EVM/permits/getNativePermit.ts b/src/core/EVM/permits/getNativePermit.ts index 3b272393..0cfe2302 100644 --- a/src/core/EVM/permits/getNativePermit.ts +++ b/src/core/EVM/permits/getNativePermit.ts @@ -9,6 +9,7 @@ import { zeroHash, } from 'viem' import { getCode, multicall, readContract } from 'viem/actions' +import type { SDKProviderConfig } from '../../types.js' import { eip2612Abi } from '../abi.js' import { getActionWithFallback } from '../getActionWithFallback.js' import { getMulticallAddress, isDelegationDesignatorCode } from '../utils.js' @@ -21,6 +22,7 @@ import { import type { NativePermitData } from './types.js' type GetNativePermitParams = { + config: SDKProviderConfig chainId: number tokenAddress: Address spenderAddress: Address @@ -131,9 +133,13 @@ function validateDomainSeparator({ * @param client - The Viem client instance * @returns Promise - Whether the account can use native permits */ -const canAccountUseNativePermits = async (client: Client): Promise => { +const canAccountUseNativePermits = async ( + config: SDKProviderConfig, + client: Client +): Promise => { try { const accountCode = await getActionWithFallback( + config, client, getCode, 'getCode', @@ -170,12 +176,13 @@ const canAccountUseNativePermits = async (client: Client): Promise => { * @returns Contract data if EIP-5267 is supported, undefined otherwise */ const getEIP712DomainData = async ( + config: SDKProviderConfig, client: Client, chainId: number, tokenAddress: Address ) => { try { - const multicallAddress = await getMulticallAddress(chainId) + const multicallAddress = await getMulticallAddress(config, chainId) const contractCalls = [ { @@ -194,6 +201,7 @@ const getEIP712DomainData = async ( if (multicallAddress) { try { const [eip712DomainResult, noncesResult] = await getActionWithFallback( + config, client, multicall, 'multicall', @@ -255,7 +263,13 @@ const getEIP712DomainData = async ( // Fallback to individual contract calls const [eip712DomainResult, noncesResult] = (await Promise.allSettled( contractCalls.map((call) => - getActionWithFallback(client, readContract, 'readContract', call) + getActionWithFallback( + config, + client, + readContract, + 'readContract', + call + ) ) )) as [ PromiseSettledResult< @@ -311,19 +325,25 @@ const getEIP712DomainData = async ( } const getContractData = async ( + config: SDKProviderConfig, client: Client, chainId: number, tokenAddress: Address ) => { try { // First try EIP-5267 approach - returns domain object directly - const eip5267Data = await getEIP712DomainData(client, chainId, tokenAddress) + const eip5267Data = await getEIP712DomainData( + config, + client, + chainId, + tokenAddress + ) if (eip5267Data) { return eip5267Data } // Fallback to legacy approach - validates and returns domain object - const multicallAddress = await getMulticallAddress(chainId) + const multicallAddress = await getMulticallAddress(config, chainId) const contractCalls = [ { @@ -362,10 +382,16 @@ const getContractData = async ( permitTypehashResult, noncesResult, versionResult, - ] = await getActionWithFallback(client, multicall, 'multicall', { - contracts: contractCalls, - multicallAddress, - }) + ] = await getActionWithFallback( + config, + client, + multicall, + 'multicall', + { + contracts: contractCalls, + multicallAddress, + } + ) if ( nameResult.status !== 'success' || @@ -412,7 +438,13 @@ const getContractData = async ( versionResult, ] = (await Promise.allSettled( contractCalls.map((call) => - getActionWithFallback(client, readContract, 'readContract', call) + getActionWithFallback( + config, + client, + readContract, + 'readContract', + call + ) ) )) as [ PromiseSettledResult, @@ -472,15 +504,26 @@ const getContractData = async ( */ export const getNativePermit = async ( client: Client, - { chainId, tokenAddress, spenderAddress, amount }: GetNativePermitParams + { + config, + chainId, + tokenAddress, + spenderAddress, + amount, + }: GetNativePermitParams ): Promise => { // Check if the account can use native permits (EOA or EIP-7702 delegated account) - const canUsePermits = await canAccountUseNativePermits(client) + const canUsePermits = await canAccountUseNativePermits(config, client) if (!canUsePermits) { return undefined } - const contractData = await getContractData(client, chainId, tokenAddress) + const contractData = await getContractData( + config, + client, + chainId, + tokenAddress + ) if (!contractData) { return undefined } diff --git a/src/core/EVM/permits/getPermitTransferFromValues.ts b/src/core/EVM/permits/getPermitTransferFromValues.ts index 93de5d6d..ec20a3fb 100644 --- a/src/core/EVM/permits/getPermitTransferFromValues.ts +++ b/src/core/EVM/permits/getPermitTransferFromValues.ts @@ -1,17 +1,20 @@ import type { ExtendedChain } from '@lifi/types' import type { Address, Client } from 'viem' import { readContract } from 'viem/actions' +import type { SDKProviderConfig } from '../../types.js' import { permit2ProxyAbi } from '../abi.js' import { getActionWithFallback } from '../getActionWithFallback.js' import type { PermitTransferFrom } from './signatureTransfer.js' export const getPermitTransferFromValues = async ( + config: SDKProviderConfig, client: Client, chain: ExtendedChain, tokenAddress: Address, amount: bigint ): Promise => { const nonce = await getActionWithFallback( + config, client, readContract, 'readContract', diff --git a/src/core/EVM/permits/signPermit2Message.ts b/src/core/EVM/permits/signPermit2Message.ts index 6918ede4..40f3020a 100644 --- a/src/core/EVM/permits/signPermit2Message.ts +++ b/src/core/EVM/permits/signPermit2Message.ts @@ -3,6 +3,7 @@ import type { Address, Client, Hex } from 'viem' import { keccak256 } from 'viem' import { signTypedData } from 'viem/actions' import { getAction } from 'viem/utils' +import type { SDKProviderConfig } from '../../types.js' import { getPermitTransferFromValues } from './getPermitTransferFromValues.js' import { getPermitData } from './signatureTransfer.js' @@ -16,11 +17,13 @@ interface SignPermit2MessageParams { } export async function signPermit2Message( + config: SDKProviderConfig, params: SignPermit2MessageParams ): Promise { const { client, chain, tokenAddress, amount, data, witness } = params const permitTransferFrom = await getPermitTransferFromValues( + config, client, chain, tokenAddress, diff --git a/src/core/EVM/publicClient.ts b/src/core/EVM/publicClient.ts index df2ae7fd..ca10dae9 100644 --- a/src/core/EVM/publicClient.ts +++ b/src/core/EVM/publicClient.ts @@ -2,8 +2,8 @@ import { ChainId, ChainType } from '@lifi/types' import type { Client } from 'viem' import { type Address, createClient, fallback, http, webSocket } from 'viem' import { type Chain, mainnet } from 'viem/chains' -import { config } from '../../config.js' import { getRpcUrls } from '../rpc.js' +import type { SDKProviderConfig } from '../types.js' import type { EVMProvider } from './types.js' import { UNS_PROXY_READER_ADDRESSES } from './uns/constants.js' @@ -15,12 +15,15 @@ const publicClients: Record = {} * @param chainId - Id of the chain the provider is for * @returns The public client for the given chain */ -export const getPublicClient = async (chainId: number): Promise => { +export const getPublicClient = async ( + config: SDKProviderConfig, + chainId: number +): Promise => { if (publicClients[chainId]) { return publicClients[chainId] } - const urls = await getRpcUrls(chainId) + const urls = await getRpcUrls(config, chainId) const fallbackTransports = urls.map((url) => url.startsWith('wss') ? webSocket(url) diff --git a/src/core/EVM/resolveENSAddress.ts b/src/core/EVM/resolveENSAddress.ts index 0b5b1164..5ee89161 100644 --- a/src/core/EVM/resolveENSAddress.ts +++ b/src/core/EVM/resolveENSAddress.ts @@ -1,12 +1,14 @@ import { ChainId } from '@lifi/types' import { getEnsAddress, normalize } from 'viem/ens' +import type { SDKProviderConfig } from '../types.js' import { getPublicClient } from './publicClient.js' export const resolveENSAddress = async ( + config: SDKProviderConfig, name: string ): Promise => { try { - const client = await getPublicClient(ChainId.ETH) + const client = await getPublicClient(config, ChainId.ETH) const address = await getEnsAddress(client, { name: normalize(name), }) diff --git a/src/core/EVM/resolveEVMAddress.ts b/src/core/EVM/resolveEVMAddress.ts index 4296dcee..938eacea 100644 --- a/src/core/EVM/resolveEVMAddress.ts +++ b/src/core/EVM/resolveEVMAddress.ts @@ -1,15 +1,17 @@ import type { ChainId, CoinKey } from '@lifi/types' import { ChainType } from '@lifi/types' +import type { SDKProviderConfig } from '../types.js' import { resolveENSAddress } from './resolveENSAddress.js' import { resolveUNSAddress } from './uns/resolveUNSAddress.js' export async function resolveEVMAddress( name: string, + config: SDKProviderConfig, chainId?: ChainId, token?: CoinKey ): Promise { return ( - (await resolveENSAddress(name)) || - (await resolveUNSAddress(name, ChainType.EVM, chainId, token)) + (await resolveENSAddress(config, name)) || + (await resolveUNSAddress(config, name, ChainType.EVM, chainId, token)) ) } diff --git a/src/core/EVM/setAllowance.int.spec.ts b/src/core/EVM/setAllowance.int.spec.ts index 92f36031..14aaa4c8 100644 --- a/src/core/EVM/setAllowance.int.spec.ts +++ b/src/core/EVM/setAllowance.int.spec.ts @@ -5,9 +5,11 @@ import { waitForTransactionReceipt } from 'viem/actions' import { polygon } from 'viem/chains' import { beforeAll, describe, expect, it } from 'vitest' import { setupTestEnvironment } from '../../../tests/setup.js' +import { createConfig } from '../../createConfig.js' import { revokeTokenApproval, setTokenAllowance } from './setAllowance.js' import { retryCount, retryDelay } from './utils.js' +const config = createConfig({ integrator: 'lifi-sdk' }) const defaultSpenderAddress = '0x9b11bc9FAc17c058CAB6286b0c785bE6a65492EF' const testToken = { name: 'USDT', @@ -41,6 +43,7 @@ describe.skipIf(!MNEMONIC)('Approval integration tests', () => { 'should revoke allowance for ERC20 on POL', async () => { const revokeTxHash = await revokeTokenApproval({ + config, walletClient: client, token: testToken, spenderAddress: defaultSpenderAddress, @@ -63,6 +66,7 @@ describe.skipIf(!MNEMONIC)('Approval integration tests', () => { 'should set allowance ERC20 on POL', async () => { const approvalTxHash = await setTokenAllowance({ + config, walletClient: client, token: testToken, spenderAddress: defaultSpenderAddress, diff --git a/src/core/EVM/setAllowance.ts b/src/core/EVM/setAllowance.ts index 7f4be5f4..c5bac780 100644 --- a/src/core/EVM/setAllowance.ts +++ b/src/core/EVM/setAllowance.ts @@ -3,13 +3,18 @@ import { encodeFunctionData } from 'viem' import { sendTransaction } from 'viem/actions' import { getAction } from 'viem/utils' import { isZeroAddress } from '../../utils/isZeroAddress.js' -import type { ExecutionOptions, TransactionParameters } from '../types.js' +import type { + ExecutionOptions, + SDKProviderConfig, + TransactionParameters, +} from '../types.js' import { approveAbi } from './abi.js' import { getAllowance } from './getAllowance.js' import type { ApproveTokenRequest, RevokeApprovalRequest } from './types.js' import { getMaxPriorityFeePerGas } from './utils.js' export const setAllowance = async ( + config: SDKProviderConfig, client: Client, tokenAddress: Address, contractAddress: Address, @@ -32,7 +37,7 @@ export const setAllowance = async ( data, maxPriorityFeePerGas: client.account?.type === 'local' - ? await getMaxPriorityFeePerGas(client) + ? await getMaxPriorityFeePerGas(config, client) : undefined, } @@ -74,6 +79,7 @@ export const setAllowance = async ( * @returns Returns Hash or nothing */ export const setTokenAllowance = async ({ + config, walletClient, token, spenderAddress, @@ -84,6 +90,7 @@ export const setTokenAllowance = async ({ return } const approvedAmount = await getAllowance( + config, walletClient, token.address as Address, walletClient.account!.address, @@ -92,6 +99,7 @@ export const setTokenAllowance = async ({ if (amount > approvedAmount) { const approveTx = await setAllowance( + config, walletClient, token.address as Address, spenderAddress as Address, @@ -111,6 +119,7 @@ export const setTokenAllowance = async ({ * @returns Returns Hash or nothing */ export const revokeTokenApproval = async ({ + config, walletClient, token, spenderAddress, @@ -120,6 +129,7 @@ export const revokeTokenApproval = async ({ return } const approvedAmount = await getAllowance( + config, walletClient, token.address as Address, walletClient.account!.address, @@ -127,6 +137,7 @@ export const revokeTokenApproval = async ({ ) if (approvedAmount > 0) { const approveTx = await setAllowance( + config, walletClient, token.address as Address, spenderAddress as Address, diff --git a/src/core/EVM/types.ts b/src/core/EVM/types.ts index 906976d0..a0ee7708 100644 --- a/src/core/EVM/types.ts +++ b/src/core/EVM/types.ts @@ -6,7 +6,11 @@ import type { FallbackTransportConfig, Hex, } from 'viem' -import type { SDKProvider, SwitchChainHook } from '../types.js' +import type { + SDKProvider, + SDKProviderConfig, + SwitchChainHook, +} from '../types.js' export interface EVMProviderOptions { getWalletClient?: () => Promise @@ -41,6 +45,7 @@ export type TokenSpenderAllowance = { } export interface ApproveTokenRequest { + config: SDKProviderConfig walletClient: Client token: BaseToken spenderAddress: string @@ -52,6 +57,7 @@ export interface ApproveTokenRequest { } export interface RevokeApprovalRequest { + config: SDKProviderConfig walletClient: Client token: BaseToken spenderAddress: string diff --git a/src/core/EVM/uns/resolveUNSAddress.ts b/src/core/EVM/uns/resolveUNSAddress.ts index 7b0ca177..754395f5 100644 --- a/src/core/EVM/uns/resolveUNSAddress.ts +++ b/src/core/EVM/uns/resolveUNSAddress.ts @@ -3,8 +3,8 @@ import type { Address, Client } from 'viem' import { readContract } from 'viem/actions' import { namehash } from 'viem/ens' import { getAction, trim } from 'viem/utils' +import type { SDKProviderConfig } from '../../types.js' import { getPublicClient } from '../publicClient.js' - import { CHAIN_ID_UNS_CHAIN_MAP, CHAIN_TYPE_FAMILY_MAP, @@ -14,14 +14,15 @@ import { } from './constants.js' export const resolveUNSAddress = async ( + config: SDKProviderConfig, name: string, chainType: ChainType, chain?: ChainId, token?: CoinKey ): Promise => { try { - const L1Client = await getPublicClient(ChainId.ETH) - const L2Client = await getPublicClient(ChainId.POL) + const L1Client = await getPublicClient(config, ChainId.ETH) + const L2Client = await getPublicClient(config, ChainId.POL) const nameHash = namehash(name) const keys: string[] = [] diff --git a/src/core/EVM/utils.ts b/src/core/EVM/utils.ts index 02e01a59..684ec176 100644 --- a/src/core/EVM/utils.ts +++ b/src/core/EVM/utils.ts @@ -1,8 +1,8 @@ import type { ChainId, ExtendedChain } from '@lifi/types' import type { Address, Chain, Client, Transaction, TypedDataDomain } from 'viem' import { getBlock } from 'viem/actions' -import { config } from '../../config.js' import { median } from '../../utils/median.js' +import type { SDKProviderConfig } from '../types.js' import { getActionWithFallback } from './getActionWithFallback.js' type ChainBlockExplorer = { @@ -57,11 +57,18 @@ export function isExtendedChain(chain: any): chain is ExtendedChain { } export const getMaxPriorityFeePerGas = async ( + config: SDKProviderConfig, client: Client ): Promise => { - const block = await getActionWithFallback(client, getBlock, 'getBlock', { - includeTransactions: true, - }) + const block = await getActionWithFallback( + config, + client, + getBlock, + 'getBlock', + { + includeTransactions: true, + } + ) const maxPriorityFeePerGasList = (block.transactions as Transaction[]) .filter((tx) => tx.maxPriorityFeePerGas) @@ -88,10 +95,11 @@ export const getMaxPriorityFeePerGas = async ( // Multicall export const getMulticallAddress = async ( + config: SDKProviderConfig, chainId: ChainId ): Promise
=> { const chains = await config.getChains() - return chains.find((chain) => chain.id === chainId) + return chains.find((chain: any) => chain.id === chainId) ?.multicallAddress as Address } diff --git a/src/core/EVM/waitForRelayedTransactionReceipt.ts b/src/core/EVM/waitForRelayedTransactionReceipt.ts index edc18c07..0a81372f 100644 --- a/src/core/EVM/waitForRelayedTransactionReceipt.ts +++ b/src/core/EVM/waitForRelayedTransactionReceipt.ts @@ -4,15 +4,17 @@ import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' import { getRelayedTransactionStatus } from '../../services/api.js' import { waitForResult } from '../../utils/waitForResult.js' +import type { SDKProviderConfig } from '../types.js' import type { WalletCallReceipt } from './types.js' export const waitForRelayedTransactionReceipt = async ( + config: SDKProviderConfig, taskId: Hash, step: LiFiStep ): Promise => { return waitForResult( async () => { - const result = await getRelayedTransactionStatus({ + const result = await getRelayedTransactionStatus(config, { taskId, fromChain: step.action.fromChainId, toChain: step.action.toChainId, diff --git a/src/core/EVM/waitForTransactionReceipt.ts b/src/core/EVM/waitForTransactionReceipt.ts index 59f5f0e2..3b19ca50 100644 --- a/src/core/EVM/waitForTransactionReceipt.ts +++ b/src/core/EVM/waitForTransactionReceipt.ts @@ -10,9 +10,11 @@ import type { import { waitForTransactionReceipt as waitForTransactionReceiptInternal } from 'viem/actions' import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' +import type { SDKProviderConfig } from '../types.js' import { getPublicClient } from './publicClient.js' interface WaitForTransactionReceiptProps { + config: SDKProviderConfig client: Client chainId: ChainId txHash: Hash @@ -20,6 +22,7 @@ interface WaitForTransactionReceiptProps { } export async function waitForTransactionReceipt({ + config, client, chainId, txHash, @@ -32,7 +35,7 @@ export async function waitForTransactionReceipt({ ) if (!transactionReceipt?.status) { - const publicClient = await getPublicClient(chainId) + const publicClient = await getPublicClient(config, chainId) const result = await waitForReceipt(publicClient, txHash, onReplaced) transactionReceipt = result.transactionReceipt replacementReason = result.replacementReason diff --git a/src/core/Solana/SolanaStepExecutor.ts b/src/core/Solana/SolanaStepExecutor.ts index 502204c2..a057c067 100644 --- a/src/core/Solana/SolanaStepExecutor.ts +++ b/src/core/Solana/SolanaStepExecutor.ts @@ -1,7 +1,6 @@ import type { SignerWalletAdapter } from '@solana/wallet-adapter-base' import { VersionedTransaction } from '@solana/web3.js' import { withTimeout } from 'viem' -import { config } from '../../config.js' import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' import { getStepTransaction } from '../../services/api.js' @@ -9,7 +8,11 @@ import { base64ToUint8Array } from '../../utils/base64ToUint8Array.js' import { BaseStepExecutor } from '../BaseStepExecutor.js' import { checkBalance } from '../checkBalance.js' import { stepComparison } from '../stepComparison.js' -import type { LiFiStepExtended, TransactionParameters } from '../types.js' +import type { + LiFiStepExtended, + SDKProviderConfig, + TransactionParameters, +} from '../types.js' import { waitForDestinationChainTransaction } from '../waitForDestinationChainTransaction.js' import { callSolanaWithRetry } from './connection.js' import { parseSolanaErrors } from './parseSolanaErrors.js' @@ -34,7 +37,10 @@ export class SolanaStepExecutor extends BaseStepExecutor { } } - executeStep = async (step: LiFiStepExtended): Promise => { + executeStep = async ( + config: SDKProviderConfig, + step: LiFiStepExtended + ): Promise => { step.execution = this.statusManager.initExecutionObject(step) const fromChain = await config.getChainById(step.action.fromChainId) @@ -58,13 +64,17 @@ export class SolanaStepExecutor extends BaseStepExecutor { ) // Check balance - await checkBalance(this.walletAdapter.publicKey!.toString(), step) + await checkBalance( + config, + this.walletAdapter.publicKey!.toString(), + step + ) // Create new transaction if (!step.transactionRequest) { // biome-ignore lint/correctness/noUnusedVariables: destructuring const { execution, ...stepBase } = step - const updatedStep = await getStepTransaction(stepBase) + const updatedStep = await getStepTransaction(config, stepBase) const comparedStep = await stepComparison( this.statusManager, step, @@ -145,11 +155,13 @@ export class SolanaStepExecutor extends BaseStepExecutor { 'PENDING' ) - const simulationResult = await callSolanaWithRetry((connection) => - connection.simulateTransaction(signedTx, { - commitment: 'confirmed', - replaceRecentBlockhash: true, - }) + const simulationResult = await callSolanaWithRetry( + config, + (connection) => + connection.simulateTransaction(signedTx, { + commitment: 'confirmed', + replaceRecentBlockhash: true, + }) ) if (simulationResult.value.err) { @@ -159,7 +171,7 @@ export class SolanaStepExecutor extends BaseStepExecutor { ) } - const confirmedTx = await sendAndConfirmTransaction(signedTx) + const confirmedTx = await sendAndConfirmTransaction(config, signedTx) if (!confirmedTx.signatureResult) { throw new TransactionError( @@ -212,6 +224,7 @@ export class SolanaStepExecutor extends BaseStepExecutor { } await waitForDestinationChainTransaction( + config, step, process, fromChain, diff --git a/src/core/Solana/connection.ts b/src/core/Solana/connection.ts index 062e1a4f..40ce0c61 100644 --- a/src/core/Solana/connection.ts +++ b/src/core/Solana/connection.ts @@ -1,6 +1,7 @@ import { ChainId } from '@lifi/types' import { Connection } from '@solana/web3.js' import { getRpcUrls } from '../rpc.js' +import type { SDKProviderConfig } from '../types.js' const connections = new Map() @@ -8,8 +9,8 @@ const connections = new Map() * Initializes the Solana connections if they haven't been initialized yet. * @returns - Promise that resolves when connections are initialized. */ -const ensureConnections = async (): Promise => { - const rpcUrls = await getRpcUrls(ChainId.SOL) +const ensureConnections = async (config: SDKProviderConfig): Promise => { + const rpcUrls = await getRpcUrls(config, ChainId.SOL) for (const rpcUrl of rpcUrls) { if (!connections.get(rpcUrl)) { const connection = new Connection(rpcUrl) @@ -22,8 +23,10 @@ const ensureConnections = async (): Promise => { * Wrapper around getting the connection (RPC provider) for Solana * @returns - Solana RPC connections */ -export const getSolanaConnections = async (): Promise => { - await ensureConnections() +export const getSolanaConnections = async ( + config: SDKProviderConfig +): Promise => { + await ensureConnections(config) return Array.from(connections.values()) } @@ -33,10 +36,11 @@ export const getSolanaConnections = async (): Promise => { * @returns - The result of the function call. */ export async function callSolanaWithRetry( + config: SDKProviderConfig, fn: (connection: Connection) => Promise ): Promise { // Ensure connections are initialized - await ensureConnections() + await ensureConnections(config) let lastError: any = null for (const connection of connections.values()) { try { diff --git a/src/core/Solana/getSolanaBalance.int.spec.ts b/src/core/Solana/getSolanaBalance.int.spec.ts index 68083420..2bccd723 100644 --- a/src/core/Solana/getSolanaBalance.int.spec.ts +++ b/src/core/Solana/getSolanaBalance.int.spec.ts @@ -3,8 +3,10 @@ import type { StaticToken, Token } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' import { beforeAll, describe, expect, it } from 'vitest' import { setupTestEnvironment } from '../../../tests/setup.js' +import { createConfig } from '../../createConfig.js' import { getSolanaBalance } from './getSolanaBalance.js' +const config = createConfig({ integrator: 'lifi-sdk' }) const defaultWalletAddress = '9T655zHa6bYrTHWdy59NFqkjwoaSwfMat2yzixE1nb56' const retryTimes = 2 @@ -18,6 +20,7 @@ describe.sequential('Solana token balance', async () => { tokens: StaticToken[] ) => { const tokenBalances = await getSolanaBalance( + config, walletAddress, tokens as Token[] ) @@ -71,6 +74,7 @@ describe.sequential('Solana token balance', async () => { const tokens = [findDefaultToken(CoinKey.USDC, ChainId.SOL), invalidToken] const tokenBalances = await getSolanaBalance( + config, walletAddress, tokens as Token[] ) @@ -100,7 +104,7 @@ describe.sequential('Solana token balance', async () => { // console.log(quote) - // await executeRoute(convertQuoteToRoute(quote), { + // await executeRoute(config, convertQuoteToRoute(quote), { // updateRouteHook: (route) => { // console.log(route.steps?.[0].execution) // }, diff --git a/src/core/Solana/getSolanaBalance.ts b/src/core/Solana/getSolanaBalance.ts index 78c80946..82d188b2 100644 --- a/src/core/Solana/getSolanaBalance.ts +++ b/src/core/Solana/getSolanaBalance.ts @@ -2,10 +2,12 @@ import type { ChainId, Token, TokenAmount } from '@lifi/types' import { PublicKey } from '@solana/web3.js' import { SolSystemProgram } from '../../constants.js' import { withDedupe } from '../../utils/withDedupe.js' +import type { SDKProviderConfig } from '../types.js' import { callSolanaWithRetry } from './connection.js' import { Token2022ProgramId, TokenProgramId } from './types.js' export const getSolanaBalance = async ( + config: SDKProviderConfig, walletAddress: string, tokens: Token[] ): Promise => { @@ -19,10 +21,11 @@ export const getSolanaBalance = async ( } } - return getSolanaBalanceDefault(chainId, tokens, walletAddress) + return getSolanaBalanceDefault(config, chainId, tokens, walletAddress) } const getSolanaBalanceDefault = async ( + config: SDKProviderConfig, _chainId: ChainId, tokens: Token[], walletAddress: string @@ -34,19 +37,21 @@ const getSolanaBalanceDefault = async ( await Promise.allSettled([ withDedupe( () => - callSolanaWithRetry((connection) => connection.getSlot('confirmed')), + callSolanaWithRetry(config, (connection) => + connection.getSlot('confirmed') + ), { id: `${getSolanaBalanceDefault.name}.getSlot` } ), withDedupe( () => - callSolanaWithRetry((connection) => + callSolanaWithRetry(config, (connection) => connection.getBalance(accountPublicKey, 'confirmed') ), { id: `${getSolanaBalanceDefault.name}.getBalance` } ), withDedupe( () => - callSolanaWithRetry((connection) => + callSolanaWithRetry(config, (connection) => connection.getParsedTokenAccountsByOwner( accountPublicKey, { @@ -61,7 +66,7 @@ const getSolanaBalanceDefault = async ( ), withDedupe( () => - callSolanaWithRetry((connection) => + callSolanaWithRetry(config, (connection) => connection.getParsedTokenAccountsByOwner( accountPublicKey, { diff --git a/src/core/Solana/sendAndConfirmTransaction.ts b/src/core/Solana/sendAndConfirmTransaction.ts index 37d450ac..73278de4 100644 --- a/src/core/Solana/sendAndConfirmTransaction.ts +++ b/src/core/Solana/sendAndConfirmTransaction.ts @@ -5,6 +5,7 @@ import type { } from '@solana/web3.js' import bs58 from 'bs58' import { sleep } from '../../utils/sleep.js' +import type { SDKProviderConfig } from '../types.js' import { getSolanaConnections } from './connection.js' type ConfirmedTransactionResult = { @@ -19,9 +20,10 @@ type ConfirmedTransactionResult = { * @returns - The confirmation result of the transaction. */ export async function sendAndConfirmTransaction( + config: SDKProviderConfig, signedTx: VersionedTransaction ): Promise { - const connections = await getSolanaConnections() + const connections = await getSolanaConnections(config) const signedTxSerialized = signedTx.serialize() // Create transaction hash (signature) diff --git a/src/core/Sui/SuiStepExecutor.ts b/src/core/Sui/SuiStepExecutor.ts index eb7b0877..438a48a3 100644 --- a/src/core/Sui/SuiStepExecutor.ts +++ b/src/core/Sui/SuiStepExecutor.ts @@ -2,14 +2,17 @@ import { signAndExecuteTransaction, type WalletWithRequiredFeatures, } from '@mysten/wallet-standard' -import { config } from '../../config.js' import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' import { getStepTransaction } from '../../services/api.js' import { BaseStepExecutor } from '../BaseStepExecutor.js' import { checkBalance } from '../checkBalance.js' import { stepComparison } from '../stepComparison.js' -import type { LiFiStepExtended, TransactionParameters } from '../types.js' +import type { + LiFiStepExtended, + SDKProviderConfig, + TransactionParameters, +} from '../types.js' import { waitForDestinationChainTransaction } from '../waitForDestinationChainTransaction.js' import { parseSuiErrors } from './parseSuiErrors.js' import { callSuiWithRetry } from './suiClient.js' @@ -37,7 +40,10 @@ export class SuiStepExecutor extends BaseStepExecutor { } } - executeStep = async (step: LiFiStepExtended): Promise => { + executeStep = async ( + config: SDKProviderConfig, + step: LiFiStepExtended + ): Promise => { step.execution = this.statusManager.initExecutionObject(step) const fromChain = await config.getChainById(step.action.fromChainId) @@ -61,13 +67,13 @@ export class SuiStepExecutor extends BaseStepExecutor { ) // Check balance - await checkBalance(step.action.fromAddress!, step) + await checkBalance(config, step.action.fromAddress!, step) // Create new transaction if (!step.transactionRequest) { // biome-ignore lint/correctness/noUnusedVariables: destructuring const { execution, ...stepBase } = step - const updatedStep = await getStepTransaction(stepBase) + const updatedStep = await getStepTransaction(config, stepBase) const comparedStep = await stepComparison( this.statusManager, step, @@ -143,7 +149,7 @@ export class SuiStepExecutor extends BaseStepExecutor { 'PENDING' ) - const result = await callSuiWithRetry((client) => + const result = await callSuiWithRetry(config, (client) => client.waitForTransaction({ digest: signedTx.digest, options: { @@ -192,6 +198,7 @@ export class SuiStepExecutor extends BaseStepExecutor { } await waitForDestinationChainTransaction( + config, step, process, fromChain, diff --git a/src/core/Sui/getSuiBalance.int.spec.ts b/src/core/Sui/getSuiBalance.int.spec.ts index e2e06a37..92a5a641 100644 --- a/src/core/Sui/getSuiBalance.int.spec.ts +++ b/src/core/Sui/getSuiBalance.int.spec.ts @@ -3,8 +3,11 @@ import type { StaticToken, Token } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' import { beforeAll, describe, expect, it } from 'vitest' import { setupTestEnvironment } from '../../../tests/setup.js' +import { createConfig } from '../../createConfig.js' import { getSuiBalance } from './getSuiBalance.js' +const config = createConfig({ integrator: 'lifi-sdk' }) + const defaultWalletAddress = '0xd2fdd62880764fa73b895a9824ecec255a4bd9d654a125e58de33088cbf5eb67' @@ -18,7 +21,11 @@ describe.sequential('Sui token balance', async () => { walletAddress: string, tokens: StaticToken[] ) => { - const tokenBalances = await getSuiBalance(walletAddress, tokens as Token[]) + const tokenBalances = await getSuiBalance( + config, + walletAddress, + tokens as Token[] + ) expect(tokenBalances.length).toEqual(tokens.length) @@ -69,6 +76,7 @@ describe.sequential('Sui token balance', async () => { const tokens = [findDefaultToken(CoinKey.SUI, ChainId.SUI), invalidToken] const tokenBalances = await getSuiBalance( + config, walletAddress, tokens as Token[] ) diff --git a/src/core/Sui/getSuiBalance.ts b/src/core/Sui/getSuiBalance.ts index 3bd4649c..02ba6d84 100644 --- a/src/core/Sui/getSuiBalance.ts +++ b/src/core/Sui/getSuiBalance.ts @@ -1,9 +1,11 @@ import type { Token, TokenAmount } from '@lifi/types' import { withDedupe } from '../../utils/withDedupe.js' +import type { SDKProviderConfig } from '../types.js' import { callSuiWithRetry } from './suiClient.js' import { SuiTokenLongAddress, SuiTokenShortAddress } from './types.js' export async function getSuiBalance( + config: SDKProviderConfig, walletAddress: string, tokens: Token[] ): Promise { @@ -18,10 +20,11 @@ export async function getSuiBalance( } } - return getSuiBalanceDefault(chainId, tokens, walletAddress) + return getSuiBalanceDefault(config, chainId, tokens, walletAddress) } const getSuiBalanceDefault = async ( + config: SDKProviderConfig, _chainId: number, tokens: Token[], walletAddress: string @@ -29,7 +32,7 @@ const getSuiBalanceDefault = async ( const [coins, checkpoint] = await Promise.allSettled([ withDedupe( () => - callSuiWithRetry((client) => + callSuiWithRetry(config, (client) => client.getAllBalances({ owner: walletAddress, }) @@ -38,7 +41,7 @@ const getSuiBalanceDefault = async ( ), withDedupe( () => - callSuiWithRetry((client) => + callSuiWithRetry(config, (client) => client.getLatestCheckpointSequenceNumber() ), { id: `${getSuiBalanceDefault.name}.getLatestCheckpointSequenceNumber` } diff --git a/src/core/Sui/suiClient.ts b/src/core/Sui/suiClient.ts index 0fa186c2..3e1e5b2e 100644 --- a/src/core/Sui/suiClient.ts +++ b/src/core/Sui/suiClient.ts @@ -1,6 +1,7 @@ import { ChainId } from '@lifi/types' import { SuiClient } from '@mysten/sui/client' import { getRpcUrls } from '../rpc.js' +import type { SDKProviderConfig } from '../types.js' const clients = new Map() @@ -8,8 +9,8 @@ const clients = new Map() * Initializes the Sui clients if they haven't been initialized yet. * @returns - Promise that resolves when clients are initialized. */ -const ensureClients = async (): Promise => { - const rpcUrls = await getRpcUrls(ChainId.SUI) +const ensureClients = async (config: SDKProviderConfig): Promise => { + const rpcUrls = await getRpcUrls(config, ChainId.SUI) for (const rpcUrl of rpcUrls) { if (!clients.get(rpcUrl)) { const client = new SuiClient({ url: rpcUrl }) @@ -24,10 +25,11 @@ const ensureClients = async (): Promise => { * @returns - The result of the function call. */ export async function callSuiWithRetry( + config: SDKProviderConfig, fn: (client: SuiClient) => Promise ): Promise { // Ensure clients are initialized - await ensureClients() + await ensureClients(config) let lastError: any = null for (const client of clients.values()) { try { diff --git a/src/core/UTXO/UTXOStepExecutor.ts b/src/core/UTXO/UTXOStepExecutor.ts index 5788b34e..f050e485 100644 --- a/src/core/UTXO/UTXOStepExecutor.ts +++ b/src/core/UTXO/UTXOStepExecutor.ts @@ -10,7 +10,6 @@ import { import * as ecc from '@bitcoinerlab/secp256k1' import { ChainId } from '@lifi/types' import { address, initEccLib, networks, Psbt } from 'bitcoinjs-lib' -import { config } from '../../config.js' import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' import { getStepTransaction } from '../../services/api.js' @@ -19,6 +18,7 @@ import { checkBalance } from '../checkBalance.js' import { stepComparison } from '../stepComparison.js' import type { LiFiStepExtended, + SDKProviderConfig, StepExecutorOptions, TransactionParameters, } from '../types.js' @@ -50,7 +50,10 @@ export class UTXOStepExecutor extends BaseStepExecutor { } } - executeStep = async (step: LiFiStepExtended): Promise => { + executeStep = async ( + config: SDKProviderConfig, + step: LiFiStepExtended + ): Promise => { step.execution = this.statusManager.initExecutionObject(step) const fromChain = await config.getChainById(step.action.fromChainId) @@ -65,7 +68,7 @@ export class UTXOStepExecutor extends BaseStepExecutor { chainId: fromChain.id, }) - const publicClient = await getUTXOPublicClient(ChainId.BTC) + const publicClient = await getUTXOPublicClient(config, ChainId.BTC) if (process.status !== 'DONE') { try { @@ -86,13 +89,13 @@ export class UTXOStepExecutor extends BaseStepExecutor { ) // Check balance - await checkBalance(this.client.account!.address, step) + await checkBalance(config, this.client.account!.address, step) // Create new transaction if (!step.transactionRequest) { // biome-ignore lint/correctness/noUnusedVariables: destructuring const { execution, ...stepBase } = step - const updatedStep = await getStepTransaction(stepBase) + const updatedStep = await getStepTransaction(config, stepBase) const comparedStep = await stepComparison( this.statusManager, step, @@ -324,6 +327,7 @@ export class UTXOStepExecutor extends BaseStepExecutor { } await waitForDestinationChainTransaction( + config, step, process, fromChain, diff --git a/src/core/UTXO/getUTXOBalance.int.spec.ts b/src/core/UTXO/getUTXOBalance.int.spec.ts index 1ebff64b..2b3efe05 100644 --- a/src/core/UTXO/getUTXOBalance.int.spec.ts +++ b/src/core/UTXO/getUTXOBalance.int.spec.ts @@ -3,8 +3,12 @@ import type { StaticToken, Token } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' import { beforeAll, describe, expect, it } from 'vitest' import { setupTestEnvironment } from '../../../tests/setup.js' +import { createConfig } from '../../createConfig.js' +import type { SDKProviderConfig } from '../types.js' import { getUTXOBalance } from './getUTXOBalance.js' +const config = createConfig({ integrator: 'lifi-sdk' }) + const defaultWalletAddress = 'bc1q5hx26klsnyqqc9255vuh0s96guz79x0cc54896' const retryTimes = 2 @@ -14,10 +18,15 @@ beforeAll(setupTestEnvironment) describe('getBalances integration tests', () => { const loadAndCompareTokenAmounts = async ( + config: SDKProviderConfig, walletAddress: string, tokens: StaticToken[] ) => { - const tokenBalances = await getUTXOBalance(walletAddress, tokens as Token[]) + const tokenBalances = await getUTXOBalance( + config, + walletAddress, + tokens as Token[] + ) expect(tokenBalances.length).toEqual(tokens.length) @@ -48,7 +57,7 @@ describe('getBalances integration tests', () => { findDefaultToken(CoinKey.USDT, ChainId.POL), ] - await loadAndCompareTokenAmounts(walletAddress, tokens) + await loadAndCompareTokenAmounts(config, walletAddress, tokens) } ) }) diff --git a/src/core/UTXO/getUTXOBalance.ts b/src/core/UTXO/getUTXOBalance.ts index 59a63d28..0d347443 100644 --- a/src/core/UTXO/getUTXOBalance.ts +++ b/src/core/UTXO/getUTXOBalance.ts @@ -1,7 +1,9 @@ import { ChainId, type Token, type TokenAmount } from '@lifi/types' +import type { SDKProviderConfig } from '../types.js' import { getUTXOPublicClient } from './getUTXOPublicClient.js' export const getUTXOBalance = async ( + config: SDKProviderConfig, walletAddress: string, tokens: Token[] ): Promise => { @@ -14,7 +16,7 @@ export const getUTXOBalance = async ( console.warn('Requested tokens have to be on the same chain.') } } - const client = await getUTXOPublicClient(ChainId.BTC) + const client = await getUTXOPublicClient(config, ChainId.BTC) const [balance, blockCount] = await Promise.all([ client.getBalance({ address: walletAddress }), client.getBlockCount(), diff --git a/src/core/UTXO/getUTXOPublicClient.ts b/src/core/UTXO/getUTXOPublicClient.ts index 4b114706..e84f73c9 100644 --- a/src/core/UTXO/getUTXOPublicClient.ts +++ b/src/core/UTXO/getUTXOPublicClient.ts @@ -17,9 +17,8 @@ import { type WalletActions, walletActions, } from '@bigmi/core' - -import { config } from '../../config.js' import { getRpcUrls } from '../rpc.js' +import type { SDKProviderConfig } from '../types.js' import { toBigmiChainId } from './utils.js' type PublicClient = Client< @@ -39,10 +38,11 @@ const publicClients: Record = {} * @returns The public client for the given chain */ export const getUTXOPublicClient = async ( + config: SDKProviderConfig, chainId: number ): Promise => { if (!publicClients[chainId]) { - const urls = await getRpcUrls(chainId) + const urls = await getRpcUrls(config, chainId) const fallbackTransports = urls.map((url) => http(url, { fetchOptions: { diff --git a/src/core/checkBalance.ts b/src/core/checkBalance.ts index c8d413d6..ee341d60 100644 --- a/src/core/checkBalance.ts +++ b/src/core/checkBalance.ts @@ -3,13 +3,19 @@ import { formatUnits } from 'viem' import { BalanceError } from '../errors/errors.js' import { getTokenBalance } from '../services/balance.js' import { sleep } from '../utils/sleep.js' +import type { SDKProviderConfig } from './types.js' export const checkBalance = async ( + config: SDKProviderConfig, walletAddress: string, step: LiFiStep, depth = 0 ): Promise => { - const token = await getTokenBalance(walletAddress, step.action.fromToken) + const token = await getTokenBalance( + config, + walletAddress, + step.action.fromToken + ) if (token) { const currentBalance = token.amount ?? 0n const neededBalance = BigInt(step.action.fromAmount) @@ -17,7 +23,7 @@ export const checkBalance = async ( if (currentBalance < neededBalance) { if (depth <= 3) { await sleep(200) - await checkBalance(walletAddress, step, depth + 1) + await checkBalance(config, walletAddress, step, depth + 1) } else if ( (neededBalance * BigInt((1 - (step.action.slippage ?? 0)) * 1_000_000_000)) / diff --git a/src/core/execution.ts b/src/core/execution.ts index 82a2620a..ec3dc07c 100644 --- a/src/core/execution.ts +++ b/src/core/execution.ts @@ -1,10 +1,13 @@ import type { Route } from '@lifi/types' -import { config } from '../config.js' import { LiFiErrorCode } from '../errors/constants.js' import { ProviderError } from '../errors/errors.js' import { executionState } from './executionState.js' import { prepareRestart } from './prepareRestart.js' -import type { ExecutionOptions, RouteExtended } from './types.js' +import type { + ExecutionOptions, + RouteExtended, + SDKProviderConfig, +} from './types.js' /** * Execute a route. @@ -14,6 +17,7 @@ import type { ExecutionOptions, RouteExtended } from './types.js' * @throws {LiFiError} Throws a LiFiError if the execution fails. */ export const executeRoute = async ( + config: SDKProviderConfig, route: Route, executionOptions?: ExecutionOptions ): Promise => { @@ -27,7 +31,7 @@ export const executeRoute = async ( } executionState.create({ route: clonedRoute, executionOptions }) - executionPromise = executeSteps(clonedRoute) + executionPromise = executeSteps(config, clonedRoute) executionState.update({ route: clonedRoute, promise: executionPromise, @@ -44,6 +48,7 @@ export const executeRoute = async ( * @throws {LiFiError} Throws a LiFiError if the execution fails. */ export const resumeRoute = async ( + config: SDKProviderConfig, route: Route, executionOptions?: ExecutionOptions ): Promise => { @@ -68,10 +73,13 @@ export const resumeRoute = async ( prepareRestart(route) - return executeRoute(route, executionOptions) + return executeRoute(config, route, executionOptions) } -const executeSteps = async (route: RouteExtended): Promise => { +const executeSteps = async ( + config: SDKProviderConfig, + route: RouteExtended +): Promise => { // Loop over steps and execute them for (let index = 0; index < route.steps.length; index++) { const execution = executionState.get(route.id) @@ -105,7 +113,7 @@ const executeSteps = async (route: RouteExtended): Promise => { const provider = config .get() - .providers.find((provider) => provider.isAddress(fromAddress)) + .providers.find((provider: any) => provider.isAddress(fromAddress)) if (!provider) { throw new ProviderError( @@ -125,7 +133,7 @@ const executeSteps = async (route: RouteExtended): Promise => { updateRouteExecution(route, execution.executionOptions) } - const executedStep = await stepExecutor.executeStep(step) + const executedStep = await stepExecutor.executeStep(config, step) // We may reach this point if user interaction isn't allowed. We want to stop execution until we resume it if (executedStep.execution?.status !== 'DONE') { diff --git a/src/core/execution.unit.handlers.ts b/src/core/execution.unit.handlers.ts index 1431cde3..9819ef70 100644 --- a/src/core/execution.unit.handlers.ts +++ b/src/core/execution.unit.handlers.ts @@ -1,12 +1,13 @@ import { HttpResponse, http } from 'msw' import { buildStepObject } from '../../tests/fixtures.js' -import { config } from '../config.js' +import { createConfig } from '../createConfig.js' import { mockChainsResponse, mockStatus, mockStepTransactionWithTxRequest, } from './execution.unit.mock.js' +const config = createConfig({ integrator: 'lifi-sdk' }) const _config = config.get() export const lifiHandlers = [ diff --git a/src/core/execution.unit.spec.ts b/src/core/execution.unit.spec.ts index bbf1845f..433f3d4f 100644 --- a/src/core/execution.unit.spec.ts +++ b/src/core/execution.unit.spec.ts @@ -12,10 +12,12 @@ import { vi, } from 'vitest' import { buildRouteObject, buildStepObject } from '../../tests/fixtures.js' +import { createConfig } from '../createConfig.js' import { requestSettings } from '../request.js' import { executeRoute } from './execution.js' import { lifiHandlers } from './execution.unit.handlers.js' +const config = createConfig({ integrator: 'lifi-sdk' }) let client: Partial vi.mock('../balance', () => ({ @@ -61,7 +63,7 @@ describe.skip('Should pick up gas from wallet client estimation', () => { step, }) - await executeRoute(route) + await executeRoute(config, route) expect(sendTransaction).toHaveBeenCalledWith(client, { gasLimit: 125000n, diff --git a/src/core/rpc.ts b/src/core/rpc.ts index 035d1b36..a36b9d97 100644 --- a/src/core/rpc.ts +++ b/src/core/rpc.ts @@ -1,7 +1,10 @@ import type { ChainId } from '@lifi/types' -import { config } from '../config.js' +import type { SDKProviderConfig } from './types.js' -export const getRpcUrls = async (chainId: ChainId): Promise => { +export const getRpcUrls = async ( + config: SDKProviderConfig, + chainId: ChainId +): Promise => { const rpcUrls = (await config.getRPCUrls())[chainId] if (!rpcUrls?.length) { throw new Error(`RPC URL not found for chainId: ${chainId}`) diff --git a/src/core/types.ts b/src/core/types.ts index 8b60e35a..ec7b5fcb 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -12,17 +12,37 @@ import type { TokenAmount, } from '@lifi/types' import type { Client } from 'viem' +import type { ExtendedChain } from '../index.js' +import type { RPCUrls, SDKBaseConfig, SDKConfig } from '../types/internal.js' + +export interface SDKProviderConfig { + loading: Promise + get(): SDKBaseConfig + set(options: SDKConfig): SDKBaseConfig + getProvider(type: ChainType): SDKProvider | undefined + setProviders(providers: SDKProvider[]): void + setChains(chains: ExtendedChain[]): void + getChains(): Promise + getChainById(chainId: ChainId): Promise + setRPCUrls(rpcUrls: RPCUrls, skipChains?: ChainId[]): void + getRPCUrls(): Promise>> +} export interface SDKProvider { readonly type: ChainType isAddress(address: string): boolean resolveAddress( name: string, + config?: SDKProviderConfig, chainId?: ChainId, token?: CoinKey ): Promise getStepExecutor(options: StepExecutorOptions): Promise - getBalance(walletAddress: string, tokens: Token[]): Promise + getBalance( + config: SDKProviderConfig, + walletAddress: string, + tokens: Token[] + ): Promise } export interface StepExecutorOptions { @@ -40,7 +60,10 @@ export interface StepExecutor { allowUserInteraction: boolean allowExecution: boolean setInteraction(settings?: InteractionSettings): void - executeStep(step: LiFiStepExtended): Promise + executeStep( + config: SDKProviderConfig, + step: LiFiStepExtended + ): Promise } export interface RouteExtended extends Omit { diff --git a/src/core/waitForDestinationChainTransaction.ts b/src/core/waitForDestinationChainTransaction.ts index 0dba81b6..c1d37f85 100644 --- a/src/core/waitForDestinationChainTransaction.ts +++ b/src/core/waitForDestinationChainTransaction.ts @@ -6,10 +6,11 @@ import type { import { LiFiErrorCode } from '../errors/constants.js' import { getTransactionFailedMessage } from '../utils/getTransactionMessage.js' import type { StatusManager } from './StatusManager.js' -import type { LiFiStepExtended, Process } from './types.js' +import type { LiFiStepExtended, Process, SDKProviderConfig } from './types.js' import { waitForTransactionStatus } from './waitForTransactionStatus.js' export async function waitForDestinationChainTransaction( + config: SDKProviderConfig, step: LiFiStepExtended, process: Process, fromChain: ExtendedChain, @@ -40,6 +41,7 @@ export async function waitForDestinationChainTransaction( } const statusResponse = (await waitForTransactionStatus( + config, statusManager, transactionHash, step, @@ -84,6 +86,7 @@ export async function waitForDestinationChainTransaction( return step } catch (e: unknown) { const htmlMessage = await getTransactionFailedMessage( + config, step, `${toChain.metamask.blockExplorerUrls[0]}tx/${transactionHash}` ) diff --git a/src/core/waitForTransactionStatus.ts b/src/core/waitForTransactionStatus.ts index 8661c80e..2a812e12 100644 --- a/src/core/waitForTransactionStatus.ts +++ b/src/core/waitForTransactionStatus.ts @@ -4,11 +4,12 @@ import { getStatus } from '../services/api.js' import { waitForResult } from '../utils/waitForResult.js' import { getSubstatusMessage } from './processMessages.js' import type { StatusManager } from './StatusManager.js' -import type { ProcessType } from './types.js' +import type { ProcessType, SDKProviderConfig } from './types.js' const TRANSACTION_HASH_OBSERVERS: Record> = {} export async function waitForTransactionStatus( + config: SDKProviderConfig, statusManager: StatusManager, txHash: string, step: LiFiStep, @@ -16,7 +17,7 @@ export async function waitForTransactionStatus( interval = 5_000 ): Promise { const _getStatus = (): Promise => { - return getStatus({ + return getStatus(config, { fromChain: step.action.fromChainId, fromAddress: step.action.fromAddress, toChain: step.action.toChainId, diff --git a/src/createConfig.ts b/src/createConfig.ts index 0452c1fd..0e89228c 100644 --- a/src/createConfig.ts +++ b/src/createConfig.ts @@ -1,25 +1,124 @@ -import { ChainType } from '@lifi/types' -import { config } from './config.js' +import { ChainId, ChainType, type ExtendedChain } from '@lifi/types' +import type { SDKProvider, SDKProviderConfig } from './core/types.js' import { getChains } from './services/api.js' -import type { SDKConfig } from './types/internal.js' +import type { RPCUrls, SDKBaseConfig, SDKConfig } from './types/internal.js' import { checkPackageUpdates } from './utils/checkPackageUpdates.js' import { name, version } from './version.js' +function initializeConfig(options: SDKConfig) { + const _config: SDKBaseConfig = { + integrator: 'lifi-sdk', + apiUrl: 'https://li.quest/v1', + rpcUrls: {}, + chains: [], + providers: [], + preloadChains: true, + debug: false, + } + let _loading: Promise | undefined + const config = { + set loading(loading: Promise) { + _loading = loading + }, + get() { + return _config + }, + set(options: SDKConfig) { + const { chains, providers, rpcUrls, ...otherOptions } = options + Object.assign(_config, otherOptions) + if (chains) { + this.setChains(chains) + } + if (providers) { + this.setProviders(providers) + } + if (rpcUrls) { + this.setRPCUrls(rpcUrls) + } + return _config + }, + getProvider(type: ChainType) { + return _config.providers.find((provider) => provider.type === type) + }, + setProviders(providers: SDKProvider[]) { + const providerMap = new Map( + _config.providers.map((provider) => [provider.type, provider]) + ) + for (const provider of providers) { + providerMap.set(provider.type, provider) + } + _config.providers = Array.from(providerMap.values()) + }, + setChains(chains: ExtendedChain[]) { + const rpcUrls = chains.reduce((rpcUrls, chain) => { + if (chain.metamask?.rpcUrls?.length) { + rpcUrls[chain.id as ChainId] = chain.metamask.rpcUrls + } + return rpcUrls + }, {} as RPCUrls) + this.setRPCUrls(rpcUrls, [ChainId.SOL]) + _config.chains = chains + _loading = undefined + }, + async getChains() { + if (_loading) { + await _loading + } + return _config.chains + }, + async getChainById(chainId: ChainId) { + if (_loading) { + await _loading + } + const chain = _config.chains?.find((chain) => chain.id === chainId) + if (!chain) { + throw new Error(`ChainId ${chainId} not found`) + } + return chain + }, + setRPCUrls(rpcUrls: RPCUrls, skipChains?: ChainId[]) { + for (const rpcUrlsKey in rpcUrls) { + const chainId = Number(rpcUrlsKey) as ChainId + const urls = rpcUrls[chainId] + if (!urls?.length) { + continue + } + if (!_config.rpcUrls[chainId]?.length) { + _config.rpcUrls[chainId] = Array.from(urls) + } else if (!skipChains?.includes(chainId)) { + const filteredUrls = urls.filter( + (url) => !_config.rpcUrls[chainId]?.includes(url) + ) + _config.rpcUrls[chainId].push(...filteredUrls) + } + } + }, + async getRPCUrls() { + if (_loading) { + await _loading + } + return _config.rpcUrls + }, + } + config.set(options) + return config +} + function createBaseConfig(options: SDKConfig) { if (!options.integrator) { throw new Error( 'Integrator not found. Please see documentation https://docs.li.fi/integrate-li.fi-js-sdk/set-up-the-sdk' ) } - const _config = config.set(options) + const _config = initializeConfig(options) if (!options.disableVersionCheck && process.env.NODE_ENV === 'development') { checkPackageUpdates(name, version) } return _config } -async function createChainsConfig() { - config.loading = getChains({ +async function createChainsConfig(config: SDKProviderConfig) { + config.loading = getChains(config, { chainTypes: [ChainType.EVM, ChainType.SVM, ChainType.UTXO, ChainType.MVM], }) .then((chains) => config.setChains(chains)) @@ -29,8 +128,8 @@ async function createChainsConfig() { export function createConfig(options: SDKConfig) { const _config = createBaseConfig(options) - if (_config.preloadChains) { - createChainsConfig() + if (_config.get().preloadChains) { + createChainsConfig(_config) } return _config } diff --git a/src/index.ts b/src/index.ts index 2d23255e..5cdf966e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,6 @@ // biome-ignore lint/performance/noBarrelFile: module entrypoint // biome-ignore lint/performance/noReExportAll: types export * from '@lifi/types' -export { config } from './config.js' export { checkPermitSupport } from './core/EVM/checkPermitSupport.js' export { EVM } from './core/EVM/EVM.js' export { diff --git a/src/request.ts b/src/request.ts index 0483d93a..91f9d0b4 100644 --- a/src/request.ts +++ b/src/request.ts @@ -1,4 +1,4 @@ -import { config } from './config.js' +import type { SDKProviderConfig } from './core/types.js' import { ValidationError } from './errors/errors.js' import { HTTPError } from './errors/httpError.js' import { SDKError } from './errors/SDKError.js' @@ -18,6 +18,7 @@ const stripExtendRequestInitProperties = ({ }) export const request = async ( + config: SDKProviderConfig, url: RequestInfo | URL, options: ExtendedRequestInit = { retries: requestSettings.retries, @@ -83,7 +84,10 @@ export const request = async ( } catch (error) { if (options.retries > 0 && (error as HTTPError).status === 500) { await sleep(500) - return request(url, { ...options, retries: options.retries - 1 }) + return request(config, url, { + ...options, + retries: options.retries - 1, + }) } await (error as HTTPError).buildAdditionalDetails?.() diff --git a/src/request.unit.spec.ts b/src/request.unit.spec.ts index 67618c7b..72c9d32a 100644 --- a/src/request.unit.spec.ts +++ b/src/request.unit.spec.ts @@ -11,7 +11,7 @@ import { vi, } from 'vitest' import { setupTestEnvironment } from '../tests/setup.js' -import { config } from './config.js' +import { createConfig } from './createConfig.js' import { ValidationError } from './errors/errors.js' import type { HTTPError } from './errors/httpError.js' import { SDKError } from './errors/SDKError.js' @@ -21,6 +21,7 @@ import type { SDKBaseConfig } from './types/internal.js' import type { ExtendedRequestInit } from './types/request.js' import { version } from './version.js' +const config = createConfig({ integrator: 'lifi-sdk' }) const apiUrl = config.get().apiUrl describe('request new', () => { @@ -50,7 +51,7 @@ describe('request new', () => { it('should be able to successfully make a fetch request', async () => { const url = `${apiUrl}/advanced/routes` - const response = await request<{ message: string }>(url, { + const response = await request<{ message: string }>(config, url, { method: 'POST', retries: 0, }) @@ -79,7 +80,7 @@ describe('request new', () => { retries: 0, } - const response = await request<{ message: string }>(url, options) + const response = await request<{ message: string }>(config, url, options) expect(response).toEqual(successResponse) }) @@ -112,7 +113,7 @@ describe('request new', () => { }, } - const response = await request<{ message: string }>(url, options) + const response = await request<{ message: string }>(config, url, options) expect(response).toEqual(successResponse) }) @@ -125,7 +126,7 @@ describe('request new', () => { const url = `${apiUrl}/advanced/routes` await expect( - request<{ message: string }>(url, { + request<{ message: string }>(config, url, { method: 'POST', retries: 0, }) @@ -152,7 +153,10 @@ describe('request new', () => { ) try { - await request<{ message: string }>(url, { method: 'POST', retries: 0 }) + await request<{ message: string }>(config, url, { + method: 'POST', + retries: 0, + }) } catch (e) { expect((e as SDKError).name).toEqual('SDKError') expect(((e as SDKError).cause as HTTPError).status).toEqual(400) @@ -171,7 +175,7 @@ describe('request new', () => { ) try { - await request<{ message: string }>(url, { + await request<{ message: string }>(config, url, { method: 'POST', retries: 0, }) diff --git a/src/services/api.int.spec.ts b/src/services/api.int.spec.ts index ff3b56aa..eef8e819 100644 --- a/src/services/api.int.spec.ts +++ b/src/services/api.int.spec.ts @@ -1,9 +1,12 @@ import { describe, expect, it } from 'vitest' +import { createConfig } from '../createConfig.js' import { getQuote } from './api.js' +const config = createConfig({ integrator: 'lifi-sdk' }) + describe('ApiService Integration Tests', () => { it('should successfully request a quote', async () => { - const quote = await getQuote({ + const quote = await getQuote(config, { fromChain: '1', fromToken: '0x0000000000000000000000000000000000000000', fromAddress: '0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0', diff --git a/src/services/api.ts b/src/services/api.ts index 4a4ae98f..ea2824fb 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -33,7 +33,7 @@ import { type TransactionAnalyticsRequest, type TransactionAnalyticsResponse, } from '@lifi/types' -import { config } from '../config.js' +import type { SDKProviderConfig } from '../core/types.js' import { BaseError } from '../errors/baseError.js' import { ErrorName } from '../errors/constants.js' import { ValidationError } from '../errors/errors.js' @@ -56,14 +56,17 @@ import type { * @returns Quote for a token transfer */ export async function getQuote( + config: SDKProviderConfig, params: QuoteRequestFromAmount, options?: RequestOptions ): Promise export async function getQuote( + config: SDKProviderConfig, params: QuoteRequestToAmount, options?: RequestOptions ): Promise export async function getQuote( + config: SDKProviderConfig, params: QuoteRequest, options?: RequestOptions ): Promise { @@ -126,6 +129,7 @@ export async function getQuote( } return await request( + config, `${_config.apiUrl}/${isFromAmountRequest ? 'quote' : 'quote/toAmount'}?${new URLSearchParams( params as unknown as Record )}`, @@ -143,6 +147,7 @@ export async function getQuote( * @throws {LiFiError} Throws a LiFiError if request fails. */ export const getRoutes = async ( + config: SDKProviderConfig, params: RoutesRequest, options?: RequestOptions ): Promise => { @@ -157,14 +162,18 @@ export const getRoutes = async ( ...params.options, } - return await request(`${_config.apiUrl}/advanced/routes`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(params), - signal: options?.signal, - }) + return await request( + config, + `${_config.apiUrl}/advanced/routes`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(params), + signal: options?.signal, + } + ) } /** @@ -175,6 +184,7 @@ export const getRoutes = async ( * @returns - Returns step. */ export const getContractCallsQuote = async ( + config: SDKProviderConfig, params: ContractCallsQuoteRequest, options?: RequestOptions ): Promise => { @@ -220,14 +230,18 @@ export const getContractCallsQuote = async ( params.denyExchanges ??= _config.routeOptions?.exchanges?.deny params.preferExchanges ??= _config.routeOptions?.exchanges?.prefer // send request - return await request(`${_config.apiUrl}/quote/contractCalls`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(params), - signal: options?.signal, - }) + return await request( + config, + `${_config.apiUrl}/quote/contractCalls`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(params), + signal: options?.signal, + } + ) } /** @@ -238,6 +252,7 @@ export const getContractCallsQuote = async ( * @throws {LiFiError} Throws a LiFiError if request fails. */ export const getStepTransaction = async ( + config: SDKProviderConfig, step: LiFiStep | SignedLiFiStep, options?: RequestOptions ): Promise => { @@ -247,6 +262,7 @@ export const getStepTransaction = async ( } return await request( + config, `${config.get().apiUrl}/advanced/stepTransaction`, { method: 'POST', @@ -267,6 +283,7 @@ export const getStepTransaction = async ( * @returns Returns status response. */ export const getStatus = async ( + config: SDKProviderConfig, params: GetStatusRequestExtended, options?: RequestOptions ): Promise => { @@ -279,6 +296,7 @@ export const getStatus = async ( params as unknown as Record ) return await request( + config, `${config.get().apiUrl}/status?${queryParams}`, { signal: options?.signal, @@ -294,6 +312,7 @@ export const getStatus = async ( * @returns Relayer quote for a token transfer */ export const getRelayerQuote = async ( + config: SDKProviderConfig, params: QuoteRequestFromAmount, options?: RequestOptions ): Promise => { @@ -335,6 +354,7 @@ export const getRelayerQuote = async ( } const result = await request( + config, `${config.get().apiUrl}/relayer/quote?${new URLSearchParams( params as unknown as Record )}`, @@ -362,6 +382,7 @@ export const getRelayerQuote = async ( * @returns Task ID for the relayed transaction */ export const relayTransaction = async ( + config: SDKProviderConfig, params: RelayRequest, options?: RequestOptions ): Promise => { @@ -386,6 +407,7 @@ export const relayTransaction = async ( : '/advanced/relay' const result = await request( + config, `${config.get().apiUrl}${relayerPath}`, { method: 'POST', @@ -421,6 +443,7 @@ export const relayTransaction = async ( * @returns Status of the relayed transaction */ export const getRelayedTransactionStatus = async ( + config: SDKProviderConfig, params: RelayStatusRequest, options?: RequestOptions ): Promise => { @@ -435,6 +458,7 @@ export const getRelayedTransactionStatus = async ( otherParams as unknown as Record ) const result = await request( + config, `${config.get().apiUrl}/relayer/status/${taskId}?${queryParams}`, { signal: options?.signal, @@ -460,6 +484,7 @@ export const getRelayedTransactionStatus = async ( * @throws {LiFiError} Throws a LiFiError if request fails. */ export const getChains = async ( + config: SDKProviderConfig, params?: ChainsRequest, options?: RequestOptions ): Promise => { @@ -476,6 +501,7 @@ export const getChains = async ( const response = await withDedupe( () => request( + config, `${config.get().apiUrl}/chains?${urlSearchParams}`, { signal: options?.signal, @@ -493,14 +519,17 @@ export const getChains = async ( * @returns The tokens that are available on the requested chains */ export async function getTokens( + config: SDKProviderConfig, params?: TokensRequest & { extended?: false | undefined }, options?: RequestOptions ): Promise export async function getTokens( + config: SDKProviderConfig, params: TokensRequest & { extended: true }, options?: RequestOptions ): Promise export async function getTokens( + config: SDKProviderConfig, params?: TokensRequest, options?: RequestOptions ): Promise { @@ -519,7 +548,7 @@ export async function getTokens( () => request< typeof isExtended extends true ? TokensExtendedResponse : TokensResponse - >(`${config.get().apiUrl}/tokens?${urlSearchParams}`, { + >(config, `${config.get().apiUrl}/tokens?${urlSearchParams}`, { signal: options?.signal, }), { id: `${getTokens.name}.${urlSearchParams}` } @@ -536,6 +565,7 @@ export async function getTokens( * @returns Token information */ export const getToken = async ( + config: SDKProviderConfig, chain: ChainKey | ChainId, token: string, options?: RequestOptions @@ -551,6 +581,7 @@ export const getToken = async ( ) } return await request( + config, `${config.get().apiUrl}/token?${new URLSearchParams({ chain, token, @@ -568,6 +599,7 @@ export const getToken = async ( * @returns The tools that are available on the requested chains */ export const getTools = async ( + config: SDKProviderConfig, params?: ToolsRequest, options?: RequestOptions ): Promise => { @@ -579,6 +611,7 @@ export const getTools = async ( } } return await request( + config, `${config.get().apiUrl}/tools?${new URLSearchParams( params as Record )}`, @@ -596,6 +629,7 @@ export const getTools = async ( * @returns Gas recommendation response. */ export const getGasRecommendation = async ( + config: SDKProviderConfig, params: GasRecommendationRequest, options?: RequestOptions ): Promise => { @@ -613,7 +647,7 @@ export const getGasRecommendation = async ( url.searchParams.append('fromToken', params.fromToken) } - return await request(url.toString(), { + return await request(config, url.toString(), { signal: options?.signal, }) } @@ -625,6 +659,7 @@ export const getGasRecommendation = async ( * @returns ConnectionsResponse */ export const getConnections = async ( + config: SDKProviderConfig, connectionRequest: ConnectionsRequest, options?: RequestOptions ): Promise => { @@ -661,10 +696,11 @@ export const getConnections = async ( } } } - return await request(url, options) + return await request(config, url, options) } export const getTransactionHistory = async ( + config: SDKProviderConfig, { wallet, status, fromTimestamp, toTimestamp }: TransactionAnalyticsRequest, options?: RequestOptions ): Promise => { @@ -693,5 +729,5 @@ export const getTransactionHistory = async ( url.searchParams.append('toTimestamp', toTimestamp.toString()) } - return await request(url, options) + return await request(config, url, options) } diff --git a/src/services/api.unit.handlers.ts b/src/services/api.unit.handlers.ts index 6149b608..378538df 100644 --- a/src/services/api.unit.handlers.ts +++ b/src/services/api.unit.handlers.ts @@ -1,8 +1,9 @@ import { findDefaultToken } from '@lifi/data-types' import { ChainId, CoinKey } from '@lifi/types' import { HttpResponse, http } from 'msw' -import { config } from '../config.js' +import { createConfig } from '../createConfig.js' +const config = createConfig({ integrator: 'lifi-sdk' }) const _config = config.get() export const handlers = [ diff --git a/src/services/api.unit.spec.ts b/src/services/api.unit.spec.ts index 109368b5..b06e5ead 100644 --- a/src/services/api.unit.spec.ts +++ b/src/services/api.unit.spec.ts @@ -23,7 +23,7 @@ import { vi, } from 'vitest' import { setupTestEnvironment } from '../../tests/setup.js' -import { config } from '../config.js' +import { createConfig } from '../createConfig.js' import { ValidationError } from '../errors/errors.js' import { SDKError } from '../errors/SDKError.js' import * as request from '../request.js' @@ -31,6 +31,7 @@ import { requestSettings } from '../request.js' import * as ApiService from './api.js' import { handlers } from './api.unit.handlers.js' +const config = createConfig({ integrator: 'lifi-sdk' }) const mockedFetch = vi.spyOn(request, 'request') describe('ApiService', () => { @@ -83,7 +84,7 @@ describe('ApiService', () => { fromChainId: 'xxx' as unknown as ChainId, }) - await expect(ApiService.getRoutes(request)).rejects.toThrow( + await expect(ApiService.getRoutes(config, request)).rejects.toThrow( 'Invalid routes request.' ) expect(mockedFetch).toHaveBeenCalledTimes(0) @@ -94,7 +95,7 @@ describe('ApiService', () => { fromAmount: 10000000000000 as unknown as string, }) - await expect(ApiService.getRoutes(request)).rejects.toThrow( + await expect(ApiService.getRoutes(config, request)).rejects.toThrow( 'Invalid routes request.' ) expect(mockedFetch).toHaveBeenCalledTimes(0) @@ -105,7 +106,7 @@ describe('ApiService', () => { fromTokenAddress: 1234 as unknown as string, }) - await expect(ApiService.getRoutes(request)).rejects.toThrow( + await expect(ApiService.getRoutes(config, request)).rejects.toThrow( 'Invalid routes request.' ) expect(mockedFetch).toHaveBeenCalledTimes(0) @@ -116,7 +117,7 @@ describe('ApiService', () => { toChainId: 'xxx' as unknown as ChainId, }) - await expect(ApiService.getRoutes(request)).rejects.toThrow( + await expect(ApiService.getRoutes(config, request)).rejects.toThrow( 'Invalid routes request.' ) expect(mockedFetch).toHaveBeenCalledTimes(0) @@ -125,7 +126,7 @@ describe('ApiService', () => { it('should throw Error because of invalid toTokenAddress type', async () => { const request = getRoutesRequest({ toTokenAddress: '' }) - await expect(ApiService.getRoutes(request)).rejects.toThrow( + await expect(ApiService.getRoutes(config, request)).rejects.toThrow( 'Invalid routes request.' ) expect(mockedFetch).toHaveBeenCalledTimes(0) @@ -136,7 +137,7 @@ describe('ApiService', () => { options: { slippage: 'not a number' as unknown as number }, }) - await expect(ApiService.getRoutes(request)).rejects.toThrow( + await expect(ApiService.getRoutes(config, request)).rejects.toThrow( 'Invalid routes request.' ) expect(mockedFetch).toHaveBeenCalledTimes(0) @@ -147,7 +148,7 @@ describe('ApiService', () => { describe('and the backend call is successful', () => { it('call the server once', async () => { const request = getRoutesRequest({}) - await ApiService.getRoutes(request) + await ApiService.getRoutes(config, request) expect(mockedFetch).toHaveBeenCalledTimes(1) }) }) @@ -158,7 +159,7 @@ describe('ApiService', () => { describe('user input is invalid', () => { it('throw an error', async () => { await expect( - ApiService.getToken(undefined as unknown as ChainId, 'DAI') + ApiService.getToken(config, undefined as unknown as ChainId, 'DAI') ).rejects.toThrowError( new SDKError( new ValidationError('Required parameter "chain" is missing.') @@ -167,7 +168,11 @@ describe('ApiService', () => { expect(mockedFetch).toHaveBeenCalledTimes(0) await expect( - ApiService.getToken(ChainId.ETH, undefined as unknown as string) + ApiService.getToken( + config, + ChainId.ETH, + undefined as unknown as string + ) ).rejects.toThrowError( new SDKError( new ValidationError('Required parameter "token" is missing.') @@ -180,7 +185,7 @@ describe('ApiService', () => { describe('user input is valid', () => { describe('and the backend call is successful', () => { it('call the server once', async () => { - await ApiService.getToken(ChainId.DAI, 'DAI') + await ApiService.getToken(config, ChainId.DAI, 'DAI') expect(mockedFetch).toHaveBeenCalledTimes(1) }) @@ -200,7 +205,7 @@ describe('ApiService', () => { describe('user input is invalid', () => { it('throw an error', async () => { await expect( - ApiService.getQuote({ + ApiService.getQuote(config, { fromChain: undefined as unknown as ChainId, fromToken, fromAddress, @@ -215,7 +220,7 @@ describe('ApiService', () => { ) await expect( - ApiService.getQuote({ + ApiService.getQuote(config, { fromChain, fromToken: undefined as unknown as string, fromAddress, @@ -230,7 +235,7 @@ describe('ApiService', () => { ) await expect( - ApiService.getQuote({ + ApiService.getQuote(config, { fromChain, fromToken, fromAddress: undefined as unknown as string, @@ -245,7 +250,7 @@ describe('ApiService', () => { ) await expect( - ApiService.getQuote({ + ApiService.getQuote(config, { fromChain, fromToken, fromAddress, @@ -262,7 +267,7 @@ describe('ApiService', () => { ) await expect( - ApiService.getQuote({ + ApiService.getQuote(config, { fromChain, fromToken, fromAddress, @@ -280,7 +285,7 @@ describe('ApiService', () => { ) await expect( - ApiService.getQuote({ + ApiService.getQuote(config, { fromChain, fromToken, fromAddress, @@ -295,7 +300,7 @@ describe('ApiService', () => { ) await expect( - ApiService.getQuote({ + ApiService.getQuote(config, { fromChain, fromToken, fromAddress, @@ -316,7 +321,7 @@ describe('ApiService', () => { describe('user input is valid', () => { describe('and the backend call is successful', () => { it('call the server once', async () => { - await ApiService.getQuote({ + await ApiService.getQuote(config, { fromChain, fromToken, fromAddress, @@ -340,7 +345,7 @@ describe('ApiService', () => { describe('user input is invalid', () => { it('throw an error', async () => { await expect( - ApiService.getStatus({ + ApiService.getStatus(config, { bridge, fromChain, toChain, @@ -359,7 +364,12 @@ describe('ApiService', () => { describe('user input is valid', () => { describe('and the backend call is successful', () => { it('call the server once', async () => { - await ApiService.getStatus({ bridge, fromChain, toChain, txHash }) + await ApiService.getStatus(config, { + bridge, + fromChain, + toChain, + txHash, + }) expect(mockedFetch).toHaveBeenCalledTimes(1) }) }) @@ -369,7 +379,7 @@ describe('ApiService', () => { describe('getChains', () => { describe('and the backend call is successful', () => { it('call the server once', async () => { - const chains = await ApiService.getChains() + const chains = await ApiService.getChains(config) expect(chains[0]?.id).toEqual(1) expect(mockedFetch).toHaveBeenCalledTimes(1) @@ -380,7 +390,7 @@ describe('ApiService', () => { describe('getTools', () => { describe('and the backend succeeds', () => { it('returns the tools', async () => { - const tools = await ApiService.getTools({ + const tools = await ApiService.getTools(config, { chains: [ChainId.ETH, ChainId.POL], }) @@ -393,7 +403,7 @@ describe('ApiService', () => { describe('getTokens', () => { it('return the tokens', async () => { - const result = await ApiService.getTokens({ + const result = await ApiService.getTokens(config, { chains: [ChainId.ETH, ChainId.POL], }) expect(result).toBeDefined() @@ -470,27 +480,27 @@ describe('ApiService', () => { it('should throw Error because of invalid id', async () => { const step = getStep({ id: null as unknown as string }) - await expect(ApiService.getStepTransaction(step)).rejects.toThrow( - 'Invalid step.' - ) + await expect( + ApiService.getStepTransaction(config, step) + ).rejects.toThrow('Invalid step.') expect(mockedFetch).toHaveBeenCalledTimes(0) }) it('should throw Error because of invalid type', async () => { const step = getStep({ type: 42 as unknown as 'lifi' }) - await expect(ApiService.getStepTransaction(step)).rejects.toThrow( - 'Invalid Step' - ) + await expect( + ApiService.getStepTransaction(config, step) + ).rejects.toThrow('Invalid Step') expect(mockedFetch).toHaveBeenCalledTimes(0) }) it('should throw Error because of invalid tool', async () => { const step = getStep({ tool: null as unknown as StepTool }) - await expect(ApiService.getStepTransaction(step)).rejects.toThrow( - 'Invalid step.' - ) + await expect( + ApiService.getStepTransaction(config, step) + ).rejects.toThrow('Invalid step.') expect(mockedFetch).toHaveBeenCalledTimes(0) }) @@ -498,9 +508,9 @@ describe('ApiService', () => { it('should throw Error because of invalid action', async () => { const step = getStep({ action: 'xxx' as unknown as Action }) - await expect(ApiService.getStepTransaction(step)).rejects.toThrow( - 'Invalid step.' - ) + await expect( + ApiService.getStepTransaction(config, step) + ).rejects.toThrow('Invalid step.') expect(mockedFetch).toHaveBeenCalledTimes(0) }) @@ -510,9 +520,9 @@ describe('ApiService', () => { estimate: 'Is this really an estimate?' as unknown as Estimate, }) - await expect(ApiService.getStepTransaction(step)).rejects.toThrow( - 'Invalid step.' - ) + await expect( + ApiService.getStepTransaction(config, step) + ).rejects.toThrow('Invalid step.') expect(mockedFetch).toHaveBeenCalledTimes(0) }) }) @@ -522,7 +532,7 @@ describe('ApiService', () => { it('call the server once', async () => { const step = getStep({}) - await ApiService.getStepTransaction(step) + await ApiService.getStepTransaction(config, step) expect(mockedFetch).toHaveBeenCalledTimes(1) }) }) @@ -534,7 +544,7 @@ describe('ApiService', () => { describe('user input is invalid', () => { it('throw an error', async () => { await expect( - ApiService.getGasRecommendation({ + ApiService.getGasRecommendation(config, { chainId: undefined as unknown as number, }) ).rejects.toThrowError( @@ -549,7 +559,7 @@ describe('ApiService', () => { describe('user input is valid', () => { describe('and the backend call is successful', () => { it('call the server once', async () => { - await ApiService.getGasRecommendation({ + await ApiService.getGasRecommendation(config, { chainId: ChainId.OPT, }) @@ -583,12 +593,12 @@ describe('ApiService', () => { '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( - ApiService.getConnections(connectionRequest) + ApiService.getConnections(config, connectionRequest) ).resolves.toEqual({ connections: [], }) - expect((mockedFetch.mock.calls[0][0] as URL).href).toEqual(generatedURL) + expect((mockedFetch.mock.calls[0][1] as URL).href).toEqual(generatedURL) expect(mockedFetch).toHaveBeenCalledOnce() }) }) @@ -610,10 +620,10 @@ describe('ApiService', () => { 'https://li.quest/v1/analytics/transfers?integrator=lifi-sdk&wallet=0x5520abcd&fromTimestamp=1696326609361&toTimestamp=1696326609362' await expect( - ApiService.getTransactionHistory(walletAnalyticsRequest) + ApiService.getTransactionHistory(config, walletAnalyticsRequest) ).resolves.toEqual({}) - expect((mockedFetch.mock.calls[0][0] as URL).href).toEqual(generatedURL) + expect((mockedFetch.mock.calls[0][1] as URL).href).toEqual(generatedURL) expect(mockedFetch).toHaveBeenCalledOnce() }) }) diff --git a/src/services/balance.ts b/src/services/balance.ts index 103fc774..b158becf 100644 --- a/src/services/balance.ts +++ b/src/services/balance.ts @@ -7,7 +7,7 @@ import type { TokenExtended, WalletTokenExtended, } from '@lifi/types' -import { config } from '../config.js' +import type { SDKProviderConfig } from '../core/types.js' import { ValidationError } from '../errors/errors.js' import { request } from '../request.js' import { isToken } from '../typeguards.js' @@ -20,10 +20,11 @@ import { isToken } from '../typeguards.js' * @throws {BaseError} Throws a ValidationError if parameters are invalid. */ export const getTokenBalance = async ( + config: SDKProviderConfig, walletAddress: string, token: Token ): Promise => { - const tokenAmounts = await getTokenBalances(walletAddress, [token]) + const tokenAmounts = await getTokenBalances(config, walletAddress, [token]) return tokenAmounts.length ? tokenAmounts[0] : null } @@ -35,10 +36,12 @@ export const getTokenBalance = async ( * @throws {BaseError} Throws a ValidationError if parameters are invalid. */ export async function getTokenBalances( + config: SDKProviderConfig, walletAddress: string, tokens: Token[] ): Promise export async function getTokenBalances( + config: SDKProviderConfig, walletAddress: string, tokens: TokenExtended[] ): Promise { @@ -55,6 +58,7 @@ export async function getTokenBalances( ) const tokenAmountsByChain = await getTokenBalancesByChain( + config, walletAddress, tokensByChain ) @@ -69,10 +73,12 @@ export async function getTokenBalances( * @throws {BaseError} Throws a ValidationError if parameters are invalid. */ export async function getTokenBalancesByChain( + config: SDKProviderConfig, walletAddress: string, tokensByChain: { [chainId: number]: Token[] } ): Promise<{ [chainId: number]: TokenAmount[] }> export async function getTokenBalancesByChain( + config: SDKProviderConfig, walletAddress: string, tokensByChain: { [chainId: number]: TokenExtended[] } ): Promise<{ [chainId: number]: TokenAmountExtended[] }> { @@ -88,7 +94,7 @@ export async function getTokenBalancesByChain( const provider = config .get() - .providers.find((provider) => provider.isAddress(walletAddress)) + .providers.find((provider: any) => provider.isAddress(walletAddress)) if (!provider) { throw new Error(`SDK Token Provider for ${walletAddress} is not found.`) } @@ -102,6 +108,7 @@ export async function getTokenBalancesByChain( const chain = await config.getChainById(chainId) if (provider.type === chain.chainType) { const tokenAmounts = await provider.getBalance( + config, walletAddress, tokensByChain[chainId] ) @@ -131,6 +138,7 @@ export async function getTokenBalancesByChain( * @throws {BaseError} Throws a ValidationError if parameters are invalid. */ export const getWalletBalances = async ( + config: SDKProviderConfig, walletAddress: string, options?: RequestOptions ): Promise> => { @@ -139,6 +147,7 @@ export const getWalletBalances = async ( } const response = await request( + config, `${config.get().apiUrl}/wallets/${walletAddress}/balances?extended=true`, { signal: options?.signal, diff --git a/src/services/balance.unit.spec.ts b/src/services/balance.unit.spec.ts index ca915f81..664db91c 100644 --- a/src/services/balance.unit.spec.ts +++ b/src/services/balance.unit.spec.ts @@ -2,8 +2,10 @@ import { findDefaultToken } from '@lifi/data-types' import type { Token, WalletTokenExtended } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { createConfig } from '../createConfig.js' import * as balance from './balance.js' +const config = createConfig({ integrator: 'lifi-sdk' }) const mockedGetTokenBalance = vi.spyOn(balance, 'getTokenBalance') const mockedGetTokenBalances = vi.spyOn(balance, 'getTokenBalances') const mockedGetTokenBalancesForChains = vi.spyOn( @@ -25,14 +27,14 @@ describe('Balance service tests', () => { describe('getTokenBalance', () => { describe('user input is invalid', () => { it('should throw Error because of missing walletAddress', async () => { - await expect(balance.getTokenBalance('', SOME_TOKEN)).rejects.toThrow( - 'Missing walletAddress.' - ) + await expect( + balance.getTokenBalance(config, '', SOME_TOKEN) + ).rejects.toThrow('Missing walletAddress.') }) it('should throw Error because of invalid token', async () => { await expect( - balance.getTokenBalance(SOME_WALLET_ADDRESS, { + balance.getTokenBalance(config, SOME_WALLET_ADDRESS, { address: 'some wrong stuff', chainId: 'not a chain Id', } as unknown as Token) @@ -51,6 +53,7 @@ describe('Balance service tests', () => { mockedGetTokenBalance.mockReturnValue(Promise.resolve(balanceResponse)) const result = await balance.getTokenBalance( + config, SOME_WALLET_ADDRESS, SOME_TOKEN ) @@ -65,13 +68,13 @@ describe('Balance service tests', () => { describe('user input is invalid', () => { it('should throw Error because of missing walletAddress', async () => { await expect( - balance.getTokenBalances('', [SOME_TOKEN]) + balance.getTokenBalances(config, '', [SOME_TOKEN]) ).rejects.toThrow('Missing walletAddress.') }) it('should throw Error because of an invalid token', async () => { await expect( - balance.getTokenBalances(SOME_WALLET_ADDRESS, [ + balance.getTokenBalances(config, SOME_WALLET_ADDRESS, [ SOME_TOKEN, { not: 'a token' } as unknown as Token, ]) @@ -80,7 +83,11 @@ describe('Balance service tests', () => { it('should return empty token list as it is', async () => { mockedGetTokenBalances.mockReturnValue(Promise.resolve([])) - const result = await balance.getTokenBalances(SOME_WALLET_ADDRESS, []) + const result = await balance.getTokenBalances( + config, + SOME_WALLET_ADDRESS, + [] + ) expect(result).toEqual([]) expect(mockedGetTokenBalances).toHaveBeenCalledTimes(1) }) @@ -98,9 +105,11 @@ describe('Balance service tests', () => { mockedGetTokenBalances.mockReturnValue(Promise.resolve(balanceResponse)) - const result = await balance.getTokenBalances(SOME_WALLET_ADDRESS, [ - SOME_TOKEN, - ]) + const result = await balance.getTokenBalances( + config, + SOME_WALLET_ADDRESS, + [SOME_TOKEN] + ) expect(mockedGetTokenBalances).toHaveBeenCalledTimes(1) expect(result).toEqual(balanceResponse) @@ -112,13 +121,15 @@ describe('Balance service tests', () => { describe('user input is invalid', () => { it('should throw Error because of missing walletAddress', async () => { await expect( - balance.getTokenBalancesByChain('', { [ChainId.DAI]: [SOME_TOKEN] }) + balance.getTokenBalancesByChain(config, '', { + [ChainId.DAI]: [SOME_TOKEN], + }) ).rejects.toThrow('Missing walletAddress.') }) it('should throw Error because of an invalid token', async () => { await expect( - balance.getTokenBalancesByChain(SOME_WALLET_ADDRESS, { + balance.getTokenBalancesByChain(config, SOME_WALLET_ADDRESS, { [ChainId.DAI]: [{ not: 'a token' } as unknown as Token], }) ).rejects.toThrow('Invalid tokens passed.') @@ -128,6 +139,7 @@ describe('Balance service tests', () => { mockedGetTokenBalancesForChains.mockReturnValue(Promise.resolve([])) const result = await balance.getTokenBalancesByChain( + config, SOME_WALLET_ADDRESS, { [ChainId.DAI]: [], @@ -156,6 +168,7 @@ describe('Balance service tests', () => { ) const result = await balance.getTokenBalancesByChain( + config, SOME_WALLET_ADDRESS, { [ChainId.DAI]: [SOME_TOKEN], @@ -178,6 +191,7 @@ describe('Balance service tests', () => { ) const result = await balance.getTokenBalancesByChain( + config, SOME_WALLET_ADDRESS, { [ChainId.DAI]: [SOME_TOKEN], @@ -193,7 +207,7 @@ describe('Balance service tests', () => { describe('getWalletBalances', () => { describe('user input is invalid', () => { it('should throw Error because of missing walletAddress', async () => { - await expect(balance.getWalletBalances('')).rejects.toThrow( + await expect(balance.getWalletBalances(config, '')).rejects.toThrow( 'Missing walletAddress.' ) }) @@ -217,10 +231,14 @@ describe('Balance service tests', () => { Promise.resolve(balanceResponse) ) - const result = await balance.getWalletBalances(SOME_WALLET_ADDRESS) + const result = await balance.getWalletBalances( + config, + SOME_WALLET_ADDRESS + ) expect(mockedGetWalletBalances).toHaveBeenCalledTimes(1) expect(mockedGetWalletBalances).toHaveBeenCalledWith( + config, SOME_WALLET_ADDRESS ) expect(result).toEqual(balanceResponse) @@ -246,12 +264,14 @@ describe('Balance service tests', () => { ) const result = await balance.getWalletBalances( + config, SOME_WALLET_ADDRESS, options ) expect(mockedGetWalletBalances).toHaveBeenCalledTimes(1) expect(mockedGetWalletBalances).toHaveBeenCalledWith( + config, SOME_WALLET_ADDRESS, options ) diff --git a/src/services/getNameServiceAddress.ts b/src/services/getNameServiceAddress.ts index d8cb1e7a..521ea566 100644 --- a/src/services/getNameServiceAddress.ts +++ b/src/services/getNameServiceAddress.ts @@ -1,21 +1,24 @@ import type { ChainType } from '@lifi/types' -import { config } from '../config.js' +import type { SDKProviderConfig } from '../core/types.js' export const getNameServiceAddress = async ( + config: SDKProviderConfig, name: string, chainType?: ChainType ): Promise => { try { let providers = config.get().providers if (chainType) { - providers = providers.filter((provider) => provider.type === chainType) + providers = providers.filter( + (provider: any) => provider.type === chainType + ) } - const resolvers = providers.map((provider) => provider.resolveAddress) + const resolvers = providers.map((provider: any) => provider.resolveAddress) if (!resolvers.length) { return } const result = await Promise.any( - resolvers.map(async (resolve) => { + resolvers.map(async (resolve: any) => { const address = await resolve(name) if (!address) { throw undefined diff --git a/src/utils/getTransactionMessage.ts b/src/utils/getTransactionMessage.ts index 8e5a764a..c7d9765c 100644 --- a/src/utils/getTransactionMessage.ts +++ b/src/utils/getTransactionMessage.ts @@ -1,7 +1,8 @@ import type { LiFiStep } from '@lifi/types' -import { config } from '../config.js' +import type { SDKProviderConfig } from '../core/types.js' export const getTransactionFailedMessage = async ( + config: SDKProviderConfig, step: LiFiStep, txLink?: string ): Promise => { From e2c8d3a57754ce2a0c4e45453402b8cbcf8e5db9 Mon Sep 17 00:00:00 2001 From: Lizaveta Miasayedava Date: Mon, 13 Oct 2025 14:25:08 +0100 Subject: [PATCH 02/13] fix: config type --- src/core/BaseStepExecutor.ts | 7 +- src/core/EVM/EVMStepExecutor.ts | 15 +- src/core/EVM/checkAllowance.ts | 6 +- src/core/EVM/checkPermitSupport.ts | 7 +- src/core/EVM/getActionWithFallback.ts | 4 +- src/core/EVM/getAllowance.int.spec.ts | 3 +- src/core/EVM/getAllowance.ts | 10 +- src/core/EVM/getEVMBalance.int.spec.ts | 3 +- src/core/EVM/getEVMBalance.ts | 8 +- src/core/EVM/isBatchingSupported.ts | 7 +- src/core/EVM/permits/getNativePermit.ts | 10 +- .../permits/getPermitTransferFromValues.ts | 4 +- src/core/EVM/permits/signPermit2Message.ts | 4 +- src/core/EVM/publicClient.ts | 11 +- src/core/EVM/resolveENSAddress.ts | 4 +- src/core/EVM/resolveEVMAddress.ts | 4 +- src/core/EVM/setAllowance.int.spec.ts | 3 +- src/core/EVM/setAllowance.ts | 9 +- src/core/EVM/types.ts | 11 +- src/core/EVM/uns/resolveUNSAddress.ts | 4 +- src/core/EVM/utils.ts | 8 +- .../EVM/waitForRelayedTransactionReceipt.ts | 4 +- src/core/EVM/waitForTransactionReceipt.ts | 4 +- src/core/Solana/SolanaStepExecutor.ts | 14 +- src/core/Solana/connection.ts | 10 +- src/core/Solana/getSolanaBalance.int.spec.ts | 3 +- src/core/Solana/getSolanaBalance.ts | 6 +- src/core/Solana/sendAndConfirmTransaction.ts | 4 +- src/core/Sui/SuiStepExecutor.ts | 14 +- src/core/Sui/getSuiBalance.int.spec.ts | 3 +- src/core/Sui/getSuiBalance.ts | 6 +- src/core/Sui/suiClient.ts | 8 +- src/core/UTXO/UTXOStepExecutor.ts | 9 +- src/core/UTXO/getUTXOBalance.int.spec.ts | 7 +- src/core/UTXO/getUTXOBalance.ts | 4 +- src/core/UTXO/getUTXOPublicClient.ts | 9 +- src/core/checkBalance.ts | 4 +- src/core/configProvider.ts | 20 ++ src/core/execution.ts | 19 +- src/core/execution.unit.handlers.ts | 11 +- src/core/execution.unit.spec.ts | 4 +- src/core/rpc.ts | 10 +- src/core/types.ts | 22 +-- .../waitForDestinationChainTransaction.ts | 5 +- src/core/waitForTransactionStatus.ts | 5 +- src/createConfig.ts | 180 ++++++++---------- src/request.ts | 6 +- src/request.unit.spec.ts | 12 +- src/services/api.int.spec.ts | 4 +- src/services/api.ts | 76 ++++---- src/services/api.unit.handlers.ts | 6 +- src/services/api.unit.spec.ts | 5 +- src/services/balance.ts | 27 +-- src/services/balance.unit.spec.ts | 4 +- src/services/getNameServiceAddress.ts | 6 +- src/utils/getTransactionMessage.ts | 7 +- tests/setup.ts | 4 +- 57 files changed, 327 insertions(+), 357 deletions(-) create mode 100644 src/core/configProvider.ts diff --git a/src/core/BaseStepExecutor.ts b/src/core/BaseStepExecutor.ts index e4f24c20..937dd27d 100644 --- a/src/core/BaseStepExecutor.ts +++ b/src/core/BaseStepExecutor.ts @@ -1,9 +1,9 @@ import type { LiFiStep } from '@lifi/types' +import type { SDKBaseConfig } from '../types/internal.js' import { StatusManager } from './StatusManager.js' import type { ExecutionOptions, InteractionSettings, - SDKProviderConfig, StepExecutor, StepExecutorOptions, } from './types.js' @@ -37,8 +37,5 @@ export abstract class BaseStepExecutor implements StepExecutor { this.allowExecution = interactionSettings.allowExecution } - abstract executeStep( - config: SDKProviderConfig, - step: LiFiStep - ): Promise + abstract executeStep(config: SDKBaseConfig, step: LiFiStep): Promise } diff --git a/src/core/EVM/EVMStepExecutor.ts b/src/core/EVM/EVMStepExecutor.ts index 35da4b2e..285436d6 100644 --- a/src/core/EVM/EVMStepExecutor.ts +++ b/src/core/EVM/EVMStepExecutor.ts @@ -23,14 +23,15 @@ import { getStepTransaction, relayTransaction, } from '../../services/api.js' +import type { SDKBaseConfig } from '../../types/internal.js' import { isZeroAddress } from '../../utils/isZeroAddress.js' import { BaseStepExecutor } from '../BaseStepExecutor.js' import { checkBalance } from '../checkBalance.js' +import { getChainById } from '../configProvider.js' import { stepComparison } from '../stepComparison.js' import type { LiFiStepExtended, Process, - SDKProviderConfig, StepExecutorOptions, TransactionMethodType, TransactionParameters, @@ -125,7 +126,7 @@ export class EVMStepExecutor extends BaseStepExecutor { } waitForTransaction = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, { step, process, @@ -227,7 +228,7 @@ export class EVMStepExecutor extends BaseStepExecutor { } private prepareUpdatedStep = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, step: LiFiStepExtended, signedTypedData?: SignedTypedData[] ) => { @@ -334,7 +335,7 @@ export class EVMStepExecutor extends BaseStepExecutor { } private estimateTransactionRequest = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, transactionRequest: TransactionParameters, fromChain: ExtendedChain ) => { @@ -369,7 +370,7 @@ export class EVMStepExecutor extends BaseStepExecutor { } executeStep = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, step: LiFiStepExtended, // Explicitly set to true if the wallet rejected the upgrade to 7702 account, based on the EIP-5792 capabilities atomicityNotReady = false @@ -398,8 +399,8 @@ export class EVMStepExecutor extends BaseStepExecutor { } } - const fromChain = await config.getChainById(step.action.fromChainId) - const toChain = await config.getChainById(step.action.toChainId) + const fromChain = await getChainById(config, step.action.fromChainId) + const toChain = await getChainById(config, step.action.toChainId) // Check if the wallet supports atomic batch transactions (EIP-5792) const calls: Call[] = [] diff --git a/src/core/EVM/checkAllowance.ts b/src/core/EVM/checkAllowance.ts index 647a53db..17e79900 100644 --- a/src/core/EVM/checkAllowance.ts +++ b/src/core/EVM/checkAllowance.ts @@ -3,13 +3,13 @@ import type { Address, Client, Hash } from 'viem' import { signTypedData } from 'viem/actions' import { getAction } from 'viem/utils' import { MaxUint256 } from '../../constants.js' +import type { SDKBaseConfig } from '../../types/internal.js' import type { StatusManager } from '../StatusManager.js' import type { ExecutionOptions, LiFiStepExtended, Process, ProcessType, - SDKProviderConfig, } from '../types.js' import { getActionWithFallback } from './getActionWithFallback.js' import { getAllowance } from './getAllowance.js' @@ -23,7 +23,7 @@ import { getDomainChainId } from './utils.js' import { waitForTransactionReceipt } from './waitForTransactionReceipt.js' type CheckAllowanceParams = { - config: SDKProviderConfig + config: SDKBaseConfig checkClient( step: LiFiStepExtended, process: Process, @@ -341,7 +341,7 @@ export const checkAllowance = async ({ } const waitForApprovalTransaction = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, client: Client, txHash: Hash, processType: ProcessType, diff --git a/src/core/EVM/checkPermitSupport.ts b/src/core/EVM/checkPermitSupport.ts index c38ef648..12aaf43f 100644 --- a/src/core/EVM/checkPermitSupport.ts +++ b/src/core/EVM/checkPermitSupport.ts @@ -1,7 +1,8 @@ import type { ExtendedChain } from '@lifi/types' import { ChainType } from '@lifi/types' import type { Address } from 'viem' -import type { SDKProviderConfig } from '../types.js' +import type { SDKBaseConfig } from '../../types/internal.js' +import { getProvider } from '../configProvider.js' import { getActionWithFallback } from './getActionWithFallback.js' import { getAllowance } from './getAllowance.js' import { getNativePermit } from './permits/getNativePermit.js' @@ -28,7 +29,7 @@ type PermitSupport = { * @returns Object indicating which permit types are supported */ export const checkPermitSupport = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, { chain, tokenAddress, @@ -41,7 +42,7 @@ export const checkPermitSupport = async ( amount: bigint } ): Promise => { - const provider = config.getProvider(ChainType.EVM) as EVMProvider | undefined + const provider = getProvider(config, ChainType.EVM) as EVMProvider | undefined let client = await provider?.getWalletClient?.() diff --git a/src/core/EVM/getActionWithFallback.ts b/src/core/EVM/getActionWithFallback.ts index 212060ee..249469e0 100644 --- a/src/core/EVM/getActionWithFallback.ts +++ b/src/core/EVM/getActionWithFallback.ts @@ -8,7 +8,7 @@ import type { WalletActions, } from 'viem' import { getAction } from 'viem/utils' -import type { SDKProviderConfig } from '../types.js' +import type { SDKBaseConfig } from '../../types/internal.js' import { getPublicClient } from './publicClient.js' /** @@ -34,7 +34,7 @@ export const getActionWithFallback = async < parameters, returnType, >( - config: SDKProviderConfig, + config: SDKBaseConfig, walletClient: client, actionFn: (_: client, parameters: parameters) => returnType, name: keyof PublicActions | keyof WalletActions | (string & {}), diff --git a/src/core/EVM/getAllowance.int.spec.ts b/src/core/EVM/getAllowance.int.spec.ts index 49ff7c08..75e2a82b 100644 --- a/src/core/EVM/getAllowance.int.spec.ts +++ b/src/core/EVM/getAllowance.int.spec.ts @@ -3,7 +3,6 @@ import { ChainId, CoinKey } from '@lifi/types' import type { Address } from 'viem' import { beforeAll, describe, expect, it } from 'vitest' import { setupTestEnvironment } from '../../../tests/setup.js' -import { createConfig } from '../../createConfig.js' import { getTokens } from '../../services/api.js' import { getAllowance, @@ -13,7 +12,7 @@ import { import { getPublicClient } from './publicClient.js' import type { TokenSpender } from './types.js' -const config = createConfig({ integrator: 'lifi-sdk' }) +const config = await setupTestEnvironment() const defaultWalletAddress = '0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0' const defaultSpenderAddress = '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE' const memeToken = { diff --git a/src/core/EVM/getAllowance.ts b/src/core/EVM/getAllowance.ts index fc833134..0f3ede66 100644 --- a/src/core/EVM/getAllowance.ts +++ b/src/core/EVM/getAllowance.ts @@ -1,8 +1,8 @@ import type { BaseToken, ChainId } from '@lifi/types' import type { Address, Client } from 'viem' import { multicall, readContract } from 'viem/actions' +import type { SDKBaseConfig } from '../../types/internal.js' import { isZeroAddress } from '../../utils/isZeroAddress.js' -import type { SDKProviderConfig } from '../types.js' import { allowanceAbi } from './abi.js' import { getActionWithFallback } from './getActionWithFallback.js' import { getPublicClient } from './publicClient.js' @@ -14,7 +14,7 @@ import type { import { getMulticallAddress } from './utils.js' export const getAllowance = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, client: Client, tokenAddress: Address, ownerAddress: Address, @@ -40,7 +40,7 @@ export const getAllowance = async ( } export const getAllowanceMulticall = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, client: Client, chainId: ChainId, tokens: TokenSpender[], @@ -93,7 +93,7 @@ export const getAllowanceMulticall = async ( * @returns Returns allowance */ export const getTokenAllowance = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, token: BaseToken, ownerAddress: Address, spenderAddress: Address @@ -122,7 +122,7 @@ export const getTokenAllowance = async ( * @returns Returns array of tokens and their allowance */ export const getTokenAllowanceMulticall = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, ownerAddress: Address, tokens: TokenSpender[] ): Promise => { diff --git a/src/core/EVM/getEVMBalance.int.spec.ts b/src/core/EVM/getEVMBalance.int.spec.ts index 754c743d..7a4a6e17 100644 --- a/src/core/EVM/getEVMBalance.int.spec.ts +++ b/src/core/EVM/getEVMBalance.int.spec.ts @@ -4,11 +4,10 @@ import { ChainId, CoinKey } from '@lifi/types' import type { Address } from 'viem' import { beforeAll, describe, expect, it } from 'vitest' import { setupTestEnvironment } from '../../../tests/setup.js' -import { createConfig } from '../../createConfig.js' import { getTokens } from '../../services/api.js' import { getEVMBalance } from './getEVMBalance.js' -const config = createConfig({ integrator: 'lifi-sdk' }) +const config = await setupTestEnvironment() const defaultWalletAddress = '0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0' diff --git a/src/core/EVM/getEVMBalance.ts b/src/core/EVM/getEVMBalance.ts index 9ac24124..e660c0df 100644 --- a/src/core/EVM/getEVMBalance.ts +++ b/src/core/EVM/getEVMBalance.ts @@ -6,14 +6,14 @@ import { multicall, readContract, } from 'viem/actions' +import type { SDKBaseConfig } from '../../types/internal.js' import { isZeroAddress } from '../../utils/isZeroAddress.js' -import type { SDKProviderConfig } from '../types.js' import { balanceOfAbi, getEthBalanceAbi } from './abi.js' import { getPublicClient } from './publicClient.js' import { getMulticallAddress } from './utils.js' export const getEVMBalance = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, walletAddress: Address, tokens: Token[] ): Promise => { @@ -42,7 +42,7 @@ export const getEVMBalance = async ( } const getEVMBalanceMulticall = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, chainId: ChainId, tokens: Token[], walletAddress: string, @@ -89,7 +89,7 @@ const getEVMBalanceMulticall = async ( } const getEVMBalanceDefault = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, chainId: ChainId, tokens: Token[], walletAddress: Address diff --git a/src/core/EVM/isBatchingSupported.ts b/src/core/EVM/isBatchingSupported.ts index 8ae70b31..83cc3569 100644 --- a/src/core/EVM/isBatchingSupported.ts +++ b/src/core/EVM/isBatchingSupported.ts @@ -2,12 +2,13 @@ import { ChainType } from '@lifi/types' import type { Client } from 'viem' import { getCapabilities } from 'viem/actions' import { getAction } from 'viem/utils' +import type { SDKBaseConfig } from '../../types/internal.js' import { sleep } from '../../utils/sleep.js' -import type { SDKProviderConfig } from '../types.js' +import { getProvider } from '../configProvider.js' import type { EVMProvider } from './types.js' export async function isBatchingSupported( - config: SDKProviderConfig, + config: SDKBaseConfig, { client, chainId, @@ -21,7 +22,7 @@ export async function isBatchingSupported( const _client = client ?? (await ( - config.getProvider(ChainType.EVM) as EVMProvider + getProvider(config, ChainType.EVM) as EVMProvider )?.getWalletClient?.()) if (!_client) { diff --git a/src/core/EVM/permits/getNativePermit.ts b/src/core/EVM/permits/getNativePermit.ts index 0cfe2302..50a0630b 100644 --- a/src/core/EVM/permits/getNativePermit.ts +++ b/src/core/EVM/permits/getNativePermit.ts @@ -9,7 +9,7 @@ import { zeroHash, } from 'viem' import { getCode, multicall, readContract } from 'viem/actions' -import type { SDKProviderConfig } from '../../types.js' +import type { SDKBaseConfig } from '../../../types/internal.js' import { eip2612Abi } from '../abi.js' import { getActionWithFallback } from '../getActionWithFallback.js' import { getMulticallAddress, isDelegationDesignatorCode } from '../utils.js' @@ -22,7 +22,7 @@ import { import type { NativePermitData } from './types.js' type GetNativePermitParams = { - config: SDKProviderConfig + config: SDKBaseConfig chainId: number tokenAddress: Address spenderAddress: Address @@ -134,7 +134,7 @@ function validateDomainSeparator({ * @returns Promise - Whether the account can use native permits */ const canAccountUseNativePermits = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, client: Client ): Promise => { try { @@ -176,7 +176,7 @@ const canAccountUseNativePermits = async ( * @returns Contract data if EIP-5267 is supported, undefined otherwise */ const getEIP712DomainData = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, client: Client, chainId: number, tokenAddress: Address @@ -325,7 +325,7 @@ const getEIP712DomainData = async ( } const getContractData = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, client: Client, chainId: number, tokenAddress: Address diff --git a/src/core/EVM/permits/getPermitTransferFromValues.ts b/src/core/EVM/permits/getPermitTransferFromValues.ts index ec20a3fb..4676cbae 100644 --- a/src/core/EVM/permits/getPermitTransferFromValues.ts +++ b/src/core/EVM/permits/getPermitTransferFromValues.ts @@ -1,13 +1,13 @@ import type { ExtendedChain } from '@lifi/types' import type { Address, Client } from 'viem' import { readContract } from 'viem/actions' -import type { SDKProviderConfig } from '../../types.js' +import type { SDKBaseConfig } from '../../../types/internal.js' import { permit2ProxyAbi } from '../abi.js' import { getActionWithFallback } from '../getActionWithFallback.js' import type { PermitTransferFrom } from './signatureTransfer.js' export const getPermitTransferFromValues = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, client: Client, chain: ExtendedChain, tokenAddress: Address, diff --git a/src/core/EVM/permits/signPermit2Message.ts b/src/core/EVM/permits/signPermit2Message.ts index 40f3020a..007746ed 100644 --- a/src/core/EVM/permits/signPermit2Message.ts +++ b/src/core/EVM/permits/signPermit2Message.ts @@ -3,7 +3,7 @@ import type { Address, Client, Hex } from 'viem' import { keccak256 } from 'viem' import { signTypedData } from 'viem/actions' import { getAction } from 'viem/utils' -import type { SDKProviderConfig } from '../../types.js' +import type { SDKBaseConfig } from '../../../types/internal.js' import { getPermitTransferFromValues } from './getPermitTransferFromValues.js' import { getPermitData } from './signatureTransfer.js' @@ -17,7 +17,7 @@ interface SignPermit2MessageParams { } export async function signPermit2Message( - config: SDKProviderConfig, + config: SDKBaseConfig, params: SignPermit2MessageParams ): Promise { const { client, chain, tokenAddress, amount, data, witness } = params diff --git a/src/core/EVM/publicClient.ts b/src/core/EVM/publicClient.ts index ca10dae9..a2f7b9aa 100644 --- a/src/core/EVM/publicClient.ts +++ b/src/core/EVM/publicClient.ts @@ -2,8 +2,9 @@ import { ChainId, ChainType } from '@lifi/types' import type { Client } from 'viem' import { type Address, createClient, fallback, http, webSocket } from 'viem' import { type Chain, mainnet } from 'viem/chains' +import type { SDKBaseConfig } from '../../types/internal.js' +import { getChainById, getProvider } from '../configProvider.js' import { getRpcUrls } from '../rpc.js' -import type { SDKProviderConfig } from '../types.js' import type { EVMProvider } from './types.js' import { UNS_PROXY_READER_ADDRESSES } from './uns/constants.js' @@ -16,14 +17,14 @@ const publicClients: Record = {} * @returns The public client for the given chain */ export const getPublicClient = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, chainId: number ): Promise => { if (publicClients[chainId]) { return publicClients[chainId] } - const urls = await getRpcUrls(config, chainId) + const urls = getRpcUrls(config, chainId) const fallbackTransports = urls.map((url) => url.startsWith('wss') ? webSocket(url) @@ -33,7 +34,7 @@ export const getPublicClient = async ( }, }) ) - const _chain = await config.getChainById(chainId) + const _chain = await getChainById(config, chainId) const chain: Chain = { ..._chain, ..._chain.metamask, @@ -61,7 +62,7 @@ export const getPublicClient = async ( } } - const provider = config.getProvider(ChainType.EVM) as EVMProvider | undefined + const provider = getProvider(config, ChainType.EVM) as EVMProvider | undefined publicClients[chainId] = createClient({ chain: chain, transport: fallback( diff --git a/src/core/EVM/resolveENSAddress.ts b/src/core/EVM/resolveENSAddress.ts index 5ee89161..683330ea 100644 --- a/src/core/EVM/resolveENSAddress.ts +++ b/src/core/EVM/resolveENSAddress.ts @@ -1,10 +1,10 @@ import { ChainId } from '@lifi/types' import { getEnsAddress, normalize } from 'viem/ens' -import type { SDKProviderConfig } from '../types.js' +import type { SDKBaseConfig } from '../../types/internal.js' import { getPublicClient } from './publicClient.js' export const resolveENSAddress = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, name: string ): Promise => { try { diff --git a/src/core/EVM/resolveEVMAddress.ts b/src/core/EVM/resolveEVMAddress.ts index 938eacea..d5523982 100644 --- a/src/core/EVM/resolveEVMAddress.ts +++ b/src/core/EVM/resolveEVMAddress.ts @@ -1,12 +1,12 @@ import type { ChainId, CoinKey } from '@lifi/types' import { ChainType } from '@lifi/types' -import type { SDKProviderConfig } from '../types.js' +import type { SDKBaseConfig } from '../../types/internal.js' import { resolveENSAddress } from './resolveENSAddress.js' import { resolveUNSAddress } from './uns/resolveUNSAddress.js' export async function resolveEVMAddress( name: string, - config: SDKProviderConfig, + config: SDKBaseConfig, chainId?: ChainId, token?: CoinKey ): Promise { diff --git a/src/core/EVM/setAllowance.int.spec.ts b/src/core/EVM/setAllowance.int.spec.ts index 14aaa4c8..66301bca 100644 --- a/src/core/EVM/setAllowance.int.spec.ts +++ b/src/core/EVM/setAllowance.int.spec.ts @@ -5,11 +5,10 @@ import { waitForTransactionReceipt } from 'viem/actions' import { polygon } from 'viem/chains' import { beforeAll, describe, expect, it } from 'vitest' import { setupTestEnvironment } from '../../../tests/setup.js' -import { createConfig } from '../../createConfig.js' import { revokeTokenApproval, setTokenAllowance } from './setAllowance.js' import { retryCount, retryDelay } from './utils.js' -const config = createConfig({ integrator: 'lifi-sdk' }) +const config = await setupTestEnvironment() const defaultSpenderAddress = '0x9b11bc9FAc17c058CAB6286b0c785bE6a65492EF' const testToken = { name: 'USDT', diff --git a/src/core/EVM/setAllowance.ts b/src/core/EVM/setAllowance.ts index c5bac780..2360f57d 100644 --- a/src/core/EVM/setAllowance.ts +++ b/src/core/EVM/setAllowance.ts @@ -2,19 +2,16 @@ import type { Address, Client, Hash, SendTransactionParameters } from 'viem' import { encodeFunctionData } from 'viem' import { sendTransaction } from 'viem/actions' import { getAction } from 'viem/utils' +import type { SDKBaseConfig } from '../../types/internal.js' import { isZeroAddress } from '../../utils/isZeroAddress.js' -import type { - ExecutionOptions, - SDKProviderConfig, - TransactionParameters, -} from '../types.js' +import type { ExecutionOptions, TransactionParameters } from '../types.js' import { approveAbi } from './abi.js' import { getAllowance } from './getAllowance.js' import type { ApproveTokenRequest, RevokeApprovalRequest } from './types.js' import { getMaxPriorityFeePerGas } from './utils.js' export const setAllowance = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, client: Client, tokenAddress: Address, contractAddress: Address, diff --git a/src/core/EVM/types.ts b/src/core/EVM/types.ts index a0ee7708..b3800d70 100644 --- a/src/core/EVM/types.ts +++ b/src/core/EVM/types.ts @@ -6,11 +6,8 @@ import type { FallbackTransportConfig, Hex, } from 'viem' -import type { - SDKProvider, - SDKProviderConfig, - SwitchChainHook, -} from '../types.js' +import type { SDKBaseConfig } from '../../types/internal.js' +import type { SDKProvider, SwitchChainHook } from '../types.js' export interface EVMProviderOptions { getWalletClient?: () => Promise @@ -45,7 +42,7 @@ export type TokenSpenderAllowance = { } export interface ApproveTokenRequest { - config: SDKProviderConfig + config: SDKBaseConfig walletClient: Client token: BaseToken spenderAddress: string @@ -57,7 +54,7 @@ export interface ApproveTokenRequest { } export interface RevokeApprovalRequest { - config: SDKProviderConfig + config: SDKBaseConfig walletClient: Client token: BaseToken spenderAddress: string diff --git a/src/core/EVM/uns/resolveUNSAddress.ts b/src/core/EVM/uns/resolveUNSAddress.ts index 754395f5..0d7e5770 100644 --- a/src/core/EVM/uns/resolveUNSAddress.ts +++ b/src/core/EVM/uns/resolveUNSAddress.ts @@ -3,7 +3,7 @@ import type { Address, Client } from 'viem' import { readContract } from 'viem/actions' import { namehash } from 'viem/ens' import { getAction, trim } from 'viem/utils' -import type { SDKProviderConfig } from '../../types.js' +import type { SDKBaseConfig } from '../../../types/internal.js' import { getPublicClient } from '../publicClient.js' import { CHAIN_ID_UNS_CHAIN_MAP, @@ -14,7 +14,7 @@ import { } from './constants.js' export const resolveUNSAddress = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, name: string, chainType: ChainType, chain?: ChainId, diff --git a/src/core/EVM/utils.ts b/src/core/EVM/utils.ts index 684ec176..c79fc952 100644 --- a/src/core/EVM/utils.ts +++ b/src/core/EVM/utils.ts @@ -1,8 +1,8 @@ import type { ChainId, ExtendedChain } from '@lifi/types' import type { Address, Chain, Client, Transaction, TypedDataDomain } from 'viem' import { getBlock } from 'viem/actions' +import type { SDKBaseConfig } from '../../types/internal.js' import { median } from '../../utils/median.js' -import type { SDKProviderConfig } from '../types.js' import { getActionWithFallback } from './getActionWithFallback.js' type ChainBlockExplorer = { @@ -57,7 +57,7 @@ export function isExtendedChain(chain: any): chain is ExtendedChain { } export const getMaxPriorityFeePerGas = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, client: Client ): Promise => { const block = await getActionWithFallback( @@ -95,10 +95,10 @@ export const getMaxPriorityFeePerGas = async ( // Multicall export const getMulticallAddress = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, chainId: ChainId ): Promise
=> { - const chains = await config.getChains() + const chains = config.chains return chains.find((chain: any) => chain.id === chainId) ?.multicallAddress as Address } diff --git a/src/core/EVM/waitForRelayedTransactionReceipt.ts b/src/core/EVM/waitForRelayedTransactionReceipt.ts index 0a81372f..c7ad3e20 100644 --- a/src/core/EVM/waitForRelayedTransactionReceipt.ts +++ b/src/core/EVM/waitForRelayedTransactionReceipt.ts @@ -3,12 +3,12 @@ import type { Hash } from 'viem' import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' import { getRelayedTransactionStatus } from '../../services/api.js' +import type { SDKBaseConfig } from '../../types/internal.js' import { waitForResult } from '../../utils/waitForResult.js' -import type { SDKProviderConfig } from '../types.js' import type { WalletCallReceipt } from './types.js' export const waitForRelayedTransactionReceipt = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, taskId: Hash, step: LiFiStep ): Promise => { diff --git a/src/core/EVM/waitForTransactionReceipt.ts b/src/core/EVM/waitForTransactionReceipt.ts index 3b19ca50..27f8d28d 100644 --- a/src/core/EVM/waitForTransactionReceipt.ts +++ b/src/core/EVM/waitForTransactionReceipt.ts @@ -10,11 +10,11 @@ import type { import { waitForTransactionReceipt as waitForTransactionReceiptInternal } from 'viem/actions' import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' -import type { SDKProviderConfig } from '../types.js' +import type { SDKBaseConfig } from '../../types/internal.js' import { getPublicClient } from './publicClient.js' interface WaitForTransactionReceiptProps { - config: SDKProviderConfig + config: SDKBaseConfig client: Client chainId: ChainId txHash: Hash diff --git a/src/core/Solana/SolanaStepExecutor.ts b/src/core/Solana/SolanaStepExecutor.ts index a057c067..18cf834c 100644 --- a/src/core/Solana/SolanaStepExecutor.ts +++ b/src/core/Solana/SolanaStepExecutor.ts @@ -4,15 +4,13 @@ import { withTimeout } from 'viem' import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' import { getStepTransaction } from '../../services/api.js' +import type { SDKBaseConfig } from '../../types/internal.js' import { base64ToUint8Array } from '../../utils/base64ToUint8Array.js' import { BaseStepExecutor } from '../BaseStepExecutor.js' import { checkBalance } from '../checkBalance.js' +import { getChainById } from '../configProvider.js' import { stepComparison } from '../stepComparison.js' -import type { - LiFiStepExtended, - SDKProviderConfig, - TransactionParameters, -} from '../types.js' +import type { LiFiStepExtended, TransactionParameters } from '../types.js' import { waitForDestinationChainTransaction } from '../waitForDestinationChainTransaction.js' import { callSolanaWithRetry } from './connection.js' import { parseSolanaErrors } from './parseSolanaErrors.js' @@ -38,13 +36,13 @@ export class SolanaStepExecutor extends BaseStepExecutor { } executeStep = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, step: LiFiStepExtended ): Promise => { step.execution = this.statusManager.initExecutionObject(step) - const fromChain = await config.getChainById(step.action.fromChainId) - const toChain = await config.getChainById(step.action.toChainId) + const fromChain = await getChainById(config, step.action.fromChainId) + const toChain = await getChainById(config, step.action.toChainId) const isBridgeExecution = fromChain.id !== toChain.id const currentProcessType = isBridgeExecution ? 'CROSS_CHAIN' : 'SWAP' diff --git a/src/core/Solana/connection.ts b/src/core/Solana/connection.ts index 40ce0c61..b44b3195 100644 --- a/src/core/Solana/connection.ts +++ b/src/core/Solana/connection.ts @@ -1,7 +1,7 @@ import { ChainId } from '@lifi/types' import { Connection } from '@solana/web3.js' +import type { SDKBaseConfig } from '../../types/internal.js' import { getRpcUrls } from '../rpc.js' -import type { SDKProviderConfig } from '../types.js' const connections = new Map() @@ -9,8 +9,8 @@ const connections = new Map() * Initializes the Solana connections if they haven't been initialized yet. * @returns - Promise that resolves when connections are initialized. */ -const ensureConnections = async (config: SDKProviderConfig): Promise => { - const rpcUrls = await getRpcUrls(config, ChainId.SOL) +const ensureConnections = async (config: SDKBaseConfig): Promise => { + const rpcUrls = getRpcUrls(config, ChainId.SOL) for (const rpcUrl of rpcUrls) { if (!connections.get(rpcUrl)) { const connection = new Connection(rpcUrl) @@ -24,7 +24,7 @@ const ensureConnections = async (config: SDKProviderConfig): Promise => { * @returns - Solana RPC connections */ export const getSolanaConnections = async ( - config: SDKProviderConfig + config: SDKBaseConfig ): Promise => { await ensureConnections(config) return Array.from(connections.values()) @@ -36,7 +36,7 @@ export const getSolanaConnections = async ( * @returns - The result of the function call. */ export async function callSolanaWithRetry( - config: SDKProviderConfig, + config: SDKBaseConfig, fn: (connection: Connection) => Promise ): Promise { // Ensure connections are initialized diff --git a/src/core/Solana/getSolanaBalance.int.spec.ts b/src/core/Solana/getSolanaBalance.int.spec.ts index 2bccd723..35d493e5 100644 --- a/src/core/Solana/getSolanaBalance.int.spec.ts +++ b/src/core/Solana/getSolanaBalance.int.spec.ts @@ -3,10 +3,9 @@ import type { StaticToken, Token } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' import { beforeAll, describe, expect, it } from 'vitest' import { setupTestEnvironment } from '../../../tests/setup.js' -import { createConfig } from '../../createConfig.js' import { getSolanaBalance } from './getSolanaBalance.js' -const config = createConfig({ integrator: 'lifi-sdk' }) +const config = await setupTestEnvironment() const defaultWalletAddress = '9T655zHa6bYrTHWdy59NFqkjwoaSwfMat2yzixE1nb56' const retryTimes = 2 diff --git a/src/core/Solana/getSolanaBalance.ts b/src/core/Solana/getSolanaBalance.ts index 82d188b2..3fe2d031 100644 --- a/src/core/Solana/getSolanaBalance.ts +++ b/src/core/Solana/getSolanaBalance.ts @@ -1,13 +1,13 @@ import type { ChainId, Token, TokenAmount } from '@lifi/types' import { PublicKey } from '@solana/web3.js' import { SolSystemProgram } from '../../constants.js' +import type { SDKBaseConfig } from '../../types/internal.js' import { withDedupe } from '../../utils/withDedupe.js' -import type { SDKProviderConfig } from '../types.js' import { callSolanaWithRetry } from './connection.js' import { Token2022ProgramId, TokenProgramId } from './types.js' export const getSolanaBalance = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, walletAddress: string, tokens: Token[] ): Promise => { @@ -25,7 +25,7 @@ export const getSolanaBalance = async ( } const getSolanaBalanceDefault = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, _chainId: ChainId, tokens: Token[], walletAddress: string diff --git a/src/core/Solana/sendAndConfirmTransaction.ts b/src/core/Solana/sendAndConfirmTransaction.ts index 73278de4..326b1567 100644 --- a/src/core/Solana/sendAndConfirmTransaction.ts +++ b/src/core/Solana/sendAndConfirmTransaction.ts @@ -4,8 +4,8 @@ import type { VersionedTransaction, } from '@solana/web3.js' import bs58 from 'bs58' +import type { SDKBaseConfig } from '../../types/internal.js' import { sleep } from '../../utils/sleep.js' -import type { SDKProviderConfig } from '../types.js' import { getSolanaConnections } from './connection.js' type ConfirmedTransactionResult = { @@ -20,7 +20,7 @@ type ConfirmedTransactionResult = { * @returns - The confirmation result of the transaction. */ export async function sendAndConfirmTransaction( - config: SDKProviderConfig, + config: SDKBaseConfig, signedTx: VersionedTransaction ): Promise { const connections = await getSolanaConnections(config) diff --git a/src/core/Sui/SuiStepExecutor.ts b/src/core/Sui/SuiStepExecutor.ts index 438a48a3..2a5fcbd4 100644 --- a/src/core/Sui/SuiStepExecutor.ts +++ b/src/core/Sui/SuiStepExecutor.ts @@ -5,14 +5,12 @@ import { import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' import { getStepTransaction } from '../../services/api.js' +import type { SDKBaseConfig } from '../../types/internal.js' import { BaseStepExecutor } from '../BaseStepExecutor.js' import { checkBalance } from '../checkBalance.js' +import { getChainById } from '../configProvider.js' import { stepComparison } from '../stepComparison.js' -import type { - LiFiStepExtended, - SDKProviderConfig, - TransactionParameters, -} from '../types.js' +import type { LiFiStepExtended, TransactionParameters } from '../types.js' import { waitForDestinationChainTransaction } from '../waitForDestinationChainTransaction.js' import { parseSuiErrors } from './parseSuiErrors.js' import { callSuiWithRetry } from './suiClient.js' @@ -41,13 +39,13 @@ export class SuiStepExecutor extends BaseStepExecutor { } executeStep = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, step: LiFiStepExtended ): Promise => { step.execution = this.statusManager.initExecutionObject(step) - const fromChain = await config.getChainById(step.action.fromChainId) - const toChain = await config.getChainById(step.action.toChainId) + const fromChain = await getChainById(config, step.action.fromChainId) + const toChain = await getChainById(config, step.action.toChainId) const isBridgeExecution = fromChain.id !== toChain.id const currentProcessType = isBridgeExecution ? 'CROSS_CHAIN' : 'SWAP' diff --git a/src/core/Sui/getSuiBalance.int.spec.ts b/src/core/Sui/getSuiBalance.int.spec.ts index 92a5a641..32795946 100644 --- a/src/core/Sui/getSuiBalance.int.spec.ts +++ b/src/core/Sui/getSuiBalance.int.spec.ts @@ -3,10 +3,9 @@ import type { StaticToken, Token } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' import { beforeAll, describe, expect, it } from 'vitest' import { setupTestEnvironment } from '../../../tests/setup.js' -import { createConfig } from '../../createConfig.js' import { getSuiBalance } from './getSuiBalance.js' -const config = createConfig({ integrator: 'lifi-sdk' }) +const config = await setupTestEnvironment() const defaultWalletAddress = '0xd2fdd62880764fa73b895a9824ecec255a4bd9d654a125e58de33088cbf5eb67' diff --git a/src/core/Sui/getSuiBalance.ts b/src/core/Sui/getSuiBalance.ts index 02ba6d84..c004d404 100644 --- a/src/core/Sui/getSuiBalance.ts +++ b/src/core/Sui/getSuiBalance.ts @@ -1,11 +1,11 @@ import type { Token, TokenAmount } from '@lifi/types' +import type { SDKBaseConfig } from '../../types/internal.js' import { withDedupe } from '../../utils/withDedupe.js' -import type { SDKProviderConfig } from '../types.js' import { callSuiWithRetry } from './suiClient.js' import { SuiTokenLongAddress, SuiTokenShortAddress } from './types.js' export async function getSuiBalance( - config: SDKProviderConfig, + config: SDKBaseConfig, walletAddress: string, tokens: Token[] ): Promise { @@ -24,7 +24,7 @@ export async function getSuiBalance( } const getSuiBalanceDefault = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, _chainId: number, tokens: Token[], walletAddress: string diff --git a/src/core/Sui/suiClient.ts b/src/core/Sui/suiClient.ts index 3e1e5b2e..e19382f9 100644 --- a/src/core/Sui/suiClient.ts +++ b/src/core/Sui/suiClient.ts @@ -1,7 +1,7 @@ import { ChainId } from '@lifi/types' import { SuiClient } from '@mysten/sui/client' +import type { SDKBaseConfig } from '../../types/internal.js' import { getRpcUrls } from '../rpc.js' -import type { SDKProviderConfig } from '../types.js' const clients = new Map() @@ -9,8 +9,8 @@ const clients = new Map() * Initializes the Sui clients if they haven't been initialized yet. * @returns - Promise that resolves when clients are initialized. */ -const ensureClients = async (config: SDKProviderConfig): Promise => { - const rpcUrls = await getRpcUrls(config, ChainId.SUI) +const ensureClients = async (config: SDKBaseConfig): Promise => { + const rpcUrls = getRpcUrls(config, ChainId.SUI) for (const rpcUrl of rpcUrls) { if (!clients.get(rpcUrl)) { const client = new SuiClient({ url: rpcUrl }) @@ -25,7 +25,7 @@ const ensureClients = async (config: SDKProviderConfig): Promise => { * @returns - The result of the function call. */ export async function callSuiWithRetry( - config: SDKProviderConfig, + config: SDKBaseConfig, fn: (client: SuiClient) => Promise ): Promise { // Ensure clients are initialized diff --git a/src/core/UTXO/UTXOStepExecutor.ts b/src/core/UTXO/UTXOStepExecutor.ts index f050e485..4597c49d 100644 --- a/src/core/UTXO/UTXOStepExecutor.ts +++ b/src/core/UTXO/UTXOStepExecutor.ts @@ -13,12 +13,13 @@ import { address, initEccLib, networks, Psbt } from 'bitcoinjs-lib' import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' import { getStepTransaction } from '../../services/api.js' +import type { SDKBaseConfig } from '../../types/internal.js' import { BaseStepExecutor } from '../BaseStepExecutor.js' import { checkBalance } from '../checkBalance.js' +import { getChainById } from '../configProvider.js' import { stepComparison } from '../stepComparison.js' import type { LiFiStepExtended, - SDKProviderConfig, StepExecutorOptions, TransactionParameters, } from '../types.js' @@ -51,13 +52,13 @@ export class UTXOStepExecutor extends BaseStepExecutor { } executeStep = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, step: LiFiStepExtended ): Promise => { step.execution = this.statusManager.initExecutionObject(step) - const fromChain = await config.getChainById(step.action.fromChainId) - const toChain = await config.getChainById(step.action.toChainId) + const fromChain = await getChainById(config, step.action.fromChainId) + const toChain = await getChainById(config, step.action.toChainId) const isBridgeExecution = fromChain.id !== toChain.id const currentProcessType = isBridgeExecution ? 'CROSS_CHAIN' : 'SWAP' diff --git a/src/core/UTXO/getUTXOBalance.int.spec.ts b/src/core/UTXO/getUTXOBalance.int.spec.ts index 2b3efe05..f2edc62f 100644 --- a/src/core/UTXO/getUTXOBalance.int.spec.ts +++ b/src/core/UTXO/getUTXOBalance.int.spec.ts @@ -3,11 +3,10 @@ import type { StaticToken, Token } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' import { beforeAll, describe, expect, it } from 'vitest' import { setupTestEnvironment } from '../../../tests/setup.js' -import { createConfig } from '../../createConfig.js' -import type { SDKProviderConfig } from '../types.js' +import type { SDKBaseConfig } from '../../types/internal.js' import { getUTXOBalance } from './getUTXOBalance.js' -const config = createConfig({ integrator: 'lifi-sdk' }) +const config = await setupTestEnvironment() const defaultWalletAddress = 'bc1q5hx26klsnyqqc9255vuh0s96guz79x0cc54896' @@ -18,7 +17,7 @@ beforeAll(setupTestEnvironment) describe('getBalances integration tests', () => { const loadAndCompareTokenAmounts = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, walletAddress: string, tokens: StaticToken[] ) => { diff --git a/src/core/UTXO/getUTXOBalance.ts b/src/core/UTXO/getUTXOBalance.ts index 0d347443..7425190e 100644 --- a/src/core/UTXO/getUTXOBalance.ts +++ b/src/core/UTXO/getUTXOBalance.ts @@ -1,9 +1,9 @@ import { ChainId, type Token, type TokenAmount } from '@lifi/types' -import type { SDKProviderConfig } from '../types.js' +import type { SDKBaseConfig } from '../../types/internal.js' import { getUTXOPublicClient } from './getUTXOPublicClient.js' export const getUTXOBalance = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, walletAddress: string, tokens: Token[] ): Promise => { diff --git a/src/core/UTXO/getUTXOPublicClient.ts b/src/core/UTXO/getUTXOPublicClient.ts index e84f73c9..3b26b744 100644 --- a/src/core/UTXO/getUTXOPublicClient.ts +++ b/src/core/UTXO/getUTXOPublicClient.ts @@ -17,8 +17,9 @@ import { type WalletActions, walletActions, } from '@bigmi/core' +import type { SDKBaseConfig } from '../../types/internal.js' +import { getChainById } from '../configProvider.js' import { getRpcUrls } from '../rpc.js' -import type { SDKProviderConfig } from '../types.js' import { toBigmiChainId } from './utils.js' type PublicClient = Client< @@ -38,11 +39,11 @@ const publicClients: Record = {} * @returns The public client for the given chain */ export const getUTXOPublicClient = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, chainId: number ): Promise => { if (!publicClients[chainId]) { - const urls = await getRpcUrls(config, chainId) + const urls = getRpcUrls(config, chainId) const fallbackTransports = urls.map((url) => http(url, { fetchOptions: { @@ -50,7 +51,7 @@ export const getUTXOPublicClient = async ( }, }) ) - const _chain = await config.getChainById(chainId) + const _chain = await getChainById(config, chainId) const chain: Chain = { ..._chain, ..._chain.metamask, diff --git a/src/core/checkBalance.ts b/src/core/checkBalance.ts index ee341d60..a4c9f859 100644 --- a/src/core/checkBalance.ts +++ b/src/core/checkBalance.ts @@ -2,11 +2,11 @@ import type { LiFiStep } from '@lifi/types' import { formatUnits } from 'viem' import { BalanceError } from '../errors/errors.js' import { getTokenBalance } from '../services/balance.js' +import type { SDKBaseConfig } from '../types/internal.js' import { sleep } from '../utils/sleep.js' -import type { SDKProviderConfig } from './types.js' export const checkBalance = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, walletAddress: string, step: LiFiStep, depth = 0 diff --git a/src/core/configProvider.ts b/src/core/configProvider.ts new file mode 100644 index 00000000..0ac8a98a --- /dev/null +++ b/src/core/configProvider.ts @@ -0,0 +1,20 @@ +import type { ChainId, ChainType } from '@lifi/types' +import type { SDKProvider } from '../core/types.js' +import type { SDKBaseConfig } from '../types/internal.js' + +export const getProvider = ( + config: SDKBaseConfig, + type: ChainType +): SDKProvider | undefined => { + return config.providers.find( + (provider: SDKProvider) => provider.type === type + ) +} + +export async function getChainById(config: SDKBaseConfig, chainId: ChainId) { + const chain = config.chains?.find((chain) => chain.id === chainId) + if (!chain) { + throw new Error(`ChainId ${chainId} not found`) + } + return chain +} diff --git a/src/core/execution.ts b/src/core/execution.ts index ec3dc07c..5c938c00 100644 --- a/src/core/execution.ts +++ b/src/core/execution.ts @@ -1,13 +1,10 @@ import type { Route } from '@lifi/types' import { LiFiErrorCode } from '../errors/constants.js' import { ProviderError } from '../errors/errors.js' +import type { SDKBaseConfig } from '../types/internal.js' import { executionState } from './executionState.js' import { prepareRestart } from './prepareRestart.js' -import type { - ExecutionOptions, - RouteExtended, - SDKProviderConfig, -} from './types.js' +import type { ExecutionOptions, RouteExtended } from './types.js' /** * Execute a route. @@ -17,7 +14,7 @@ import type { * @throws {LiFiError} Throws a LiFiError if the execution fails. */ export const executeRoute = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, route: Route, executionOptions?: ExecutionOptions ): Promise => { @@ -48,7 +45,7 @@ export const executeRoute = async ( * @throws {LiFiError} Throws a LiFiError if the execution fails. */ export const resumeRoute = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, route: Route, executionOptions?: ExecutionOptions ): Promise => { @@ -77,7 +74,7 @@ export const resumeRoute = async ( } const executeSteps = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, route: RouteExtended ): Promise => { // Loop over steps and execute them @@ -111,9 +108,9 @@ const executeSteps = async ( throw new Error('Action fromAddress is not specified.') } - const provider = config - .get() - .providers.find((provider: any) => provider.isAddress(fromAddress)) + const provider = config.providers.find((provider) => + provider.isAddress(fromAddress) + ) if (!provider) { throw new ProviderError( diff --git a/src/core/execution.unit.handlers.ts b/src/core/execution.unit.handlers.ts index 9819ef70..2cdd4789 100644 --- a/src/core/execution.unit.handlers.ts +++ b/src/core/execution.unit.handlers.ts @@ -1,17 +1,16 @@ import { HttpResponse, http } from 'msw' import { buildStepObject } from '../../tests/fixtures.js' -import { createConfig } from '../createConfig.js' +import { setupTestEnvironment } from '../../tests/setup.js' import { mockChainsResponse, mockStatus, mockStepTransactionWithTxRequest, } from './execution.unit.mock.js' -const config = createConfig({ integrator: 'lifi-sdk' }) -const _config = config.get() +const config = await setupTestEnvironment() export const lifiHandlers = [ - http.post(`${_config.apiUrl}/advanced/stepTransaction`, async () => + http.post(`${config.apiUrl}/advanced/stepTransaction`, async () => HttpResponse.json( mockStepTransactionWithTxRequest( buildStepObject({ @@ -20,12 +19,12 @@ export const lifiHandlers = [ ) ) ), - http.get(`${_config.apiUrl}/chains`, async () => + http.get(`${config.apiUrl}/chains`, async () => HttpResponse.json({ chains: mockChainsResponse, }) ), - http.get(`${_config.apiUrl}/status`, async () => + http.get(`${config.apiUrl}/status`, async () => HttpResponse.json(mockStatus) ), ] diff --git a/src/core/execution.unit.spec.ts b/src/core/execution.unit.spec.ts index 433f3d4f..1f9f2436 100644 --- a/src/core/execution.unit.spec.ts +++ b/src/core/execution.unit.spec.ts @@ -12,12 +12,12 @@ import { vi, } from 'vitest' import { buildRouteObject, buildStepObject } from '../../tests/fixtures.js' -import { createConfig } from '../createConfig.js' +import { setupTestEnvironment } from '../../tests/setup.js' import { requestSettings } from '../request.js' import { executeRoute } from './execution.js' import { lifiHandlers } from './execution.unit.handlers.js' -const config = createConfig({ integrator: 'lifi-sdk' }) +const config = await setupTestEnvironment() let client: Partial vi.mock('../balance', () => ({ diff --git a/src/core/rpc.ts b/src/core/rpc.ts index a36b9d97..d42e4eff 100644 --- a/src/core/rpc.ts +++ b/src/core/rpc.ts @@ -1,11 +1,11 @@ import type { ChainId } from '@lifi/types' -import type { SDKProviderConfig } from './types.js' +import type { SDKBaseConfig } from '../types/internal.js' -export const getRpcUrls = async ( - config: SDKProviderConfig, +export const getRpcUrls = ( + config: SDKBaseConfig, chainId: ChainId -): Promise => { - const rpcUrls = (await config.getRPCUrls())[chainId] +): string[] => { + const rpcUrls = config.rpcUrls[chainId] if (!rpcUrls?.length) { throw new Error(`RPC URL not found for chainId: ${chainId}`) } diff --git a/src/core/types.ts b/src/core/types.ts index ec7b5fcb..526a00d6 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -12,34 +12,20 @@ import type { TokenAmount, } from '@lifi/types' import type { Client } from 'viem' -import type { ExtendedChain } from '../index.js' -import type { RPCUrls, SDKBaseConfig, SDKConfig } from '../types/internal.js' - -export interface SDKProviderConfig { - loading: Promise - get(): SDKBaseConfig - set(options: SDKConfig): SDKBaseConfig - getProvider(type: ChainType): SDKProvider | undefined - setProviders(providers: SDKProvider[]): void - setChains(chains: ExtendedChain[]): void - getChains(): Promise - getChainById(chainId: ChainId): Promise - setRPCUrls(rpcUrls: RPCUrls, skipChains?: ChainId[]): void - getRPCUrls(): Promise>> -} +import type { SDKBaseConfig } from '../types/internal.js' export interface SDKProvider { readonly type: ChainType isAddress(address: string): boolean resolveAddress( name: string, - config?: SDKProviderConfig, + config?: SDKBaseConfig, chainId?: ChainId, token?: CoinKey ): Promise getStepExecutor(options: StepExecutorOptions): Promise getBalance( - config: SDKProviderConfig, + config: SDKBaseConfig, walletAddress: string, tokens: Token[] ): Promise @@ -61,7 +47,7 @@ export interface StepExecutor { allowExecution: boolean setInteraction(settings?: InteractionSettings): void executeStep( - config: SDKProviderConfig, + config: SDKBaseConfig, step: LiFiStepExtended ): Promise } diff --git a/src/core/waitForDestinationChainTransaction.ts b/src/core/waitForDestinationChainTransaction.ts index c1d37f85..4c060a1e 100644 --- a/src/core/waitForDestinationChainTransaction.ts +++ b/src/core/waitForDestinationChainTransaction.ts @@ -4,13 +4,14 @@ import type { FullStatusData, } from '@lifi/types' import { LiFiErrorCode } from '../errors/constants.js' +import type { SDKBaseConfig } from '../types/internal.js' import { getTransactionFailedMessage } from '../utils/getTransactionMessage.js' import type { StatusManager } from './StatusManager.js' -import type { LiFiStepExtended, Process, SDKProviderConfig } from './types.js' +import type { LiFiStepExtended, Process } from './types.js' import { waitForTransactionStatus } from './waitForTransactionStatus.js' export async function waitForDestinationChainTransaction( - config: SDKProviderConfig, + config: SDKBaseConfig, step: LiFiStepExtended, process: Process, fromChain: ExtendedChain, diff --git a/src/core/waitForTransactionStatus.ts b/src/core/waitForTransactionStatus.ts index 2a812e12..d664fdcf 100644 --- a/src/core/waitForTransactionStatus.ts +++ b/src/core/waitForTransactionStatus.ts @@ -1,15 +1,16 @@ import type { FullStatusData, LiFiStep, StatusResponse } from '@lifi/types' import { ServerError } from '../errors/errors.js' import { getStatus } from '../services/api.js' +import type { SDKBaseConfig } from '../types/internal.js' import { waitForResult } from '../utils/waitForResult.js' import { getSubstatusMessage } from './processMessages.js' import type { StatusManager } from './StatusManager.js' -import type { ProcessType, SDKProviderConfig } from './types.js' +import type { ProcessType } from './types.js' const TRANSACTION_HASH_OBSERVERS: Record> = {} export async function waitForTransactionStatus( - config: SDKProviderConfig, + config: SDKBaseConfig, statusManager: StatusManager, txHash: string, step: LiFiStep, diff --git a/src/createConfig.ts b/src/createConfig.ts index 0e89228c..2efd3200 100644 --- a/src/createConfig.ts +++ b/src/createConfig.ts @@ -1,10 +1,48 @@ -import { ChainId, ChainType, type ExtendedChain } from '@lifi/types' -import type { SDKProvider, SDKProviderConfig } from './core/types.js' +import { ChainId, ChainType } from '@lifi/types' +import type { SDKProvider } from './core/types.js' import { getChains } from './services/api.js' import type { RPCUrls, SDKBaseConfig, SDKConfig } from './types/internal.js' import { checkPackageUpdates } from './utils/checkPackageUpdates.js' import { name, version } from './version.js' +function setProviders( + configProviders: SDKProvider[], + providers: SDKProvider[] +) { + const providerMap = new Map( + configProviders.map((provider) => [provider.type, provider]) + ) + for (const provider of providers) { + providerMap.set(provider.type, provider) + } + return Array.from(providerMap.values()) +} + +function setRPCUrls( + configRPCUrls: RPCUrls, + rpcUrls: RPCUrls, + skipChains?: ChainId[] +) { + const newRPCUrls = { ...configRPCUrls } + for (const rpcUrlsKey in rpcUrls) { + const chainId = Number(rpcUrlsKey) as ChainId + const urls = rpcUrls[chainId] + if (!urls?.length) { + continue + } + if (!newRPCUrls[chainId]?.length) { + newRPCUrls[chainId] = Array.from(urls) + } else if (!skipChains?.includes(chainId)) { + const filteredUrls = urls.filter( + (url) => !newRPCUrls[chainId]?.includes(url) + ) + newRPCUrls[chainId].push(...filteredUrls) + } + } + + return newRPCUrls +} + function initializeConfig(options: SDKConfig) { const _config: SDKBaseConfig = { integrator: 'lifi-sdk', @@ -15,93 +53,33 @@ function initializeConfig(options: SDKConfig) { preloadChains: true, debug: false, } - let _loading: Promise | undefined - const config = { - set loading(loading: Promise) { - _loading = loading - }, - get() { - return _config - }, - set(options: SDKConfig) { - const { chains, providers, rpcUrls, ...otherOptions } = options - Object.assign(_config, otherOptions) - if (chains) { - this.setChains(chains) - } - if (providers) { - this.setProviders(providers) - } - if (rpcUrls) { - this.setRPCUrls(rpcUrls) - } - return _config - }, - getProvider(type: ChainType) { - return _config.providers.find((provider) => provider.type === type) - }, - setProviders(providers: SDKProvider[]) { - const providerMap = new Map( - _config.providers.map((provider) => [provider.type, provider]) - ) - for (const provider of providers) { - providerMap.set(provider.type, provider) - } - _config.providers = Array.from(providerMap.values()) - }, - setChains(chains: ExtendedChain[]) { - const rpcUrls = chains.reduce((rpcUrls, chain) => { - if (chain.metamask?.rpcUrls?.length) { - rpcUrls[chain.id as ChainId] = chain.metamask.rpcUrls - } - return rpcUrls - }, {} as RPCUrls) - this.setRPCUrls(rpcUrls, [ChainId.SOL]) - _config.chains = chains - _loading = undefined - }, - async getChains() { - if (_loading) { - await _loading - } - return _config.chains - }, - async getChainById(chainId: ChainId) { - if (_loading) { - await _loading - } - const chain = _config.chains?.find((chain) => chain.id === chainId) - if (!chain) { - throw new Error(`ChainId ${chainId} not found`) - } - return chain - }, - setRPCUrls(rpcUrls: RPCUrls, skipChains?: ChainId[]) { - for (const rpcUrlsKey in rpcUrls) { - const chainId = Number(rpcUrlsKey) as ChainId - const urls = rpcUrls[chainId] - if (!urls?.length) { - continue - } - if (!_config.rpcUrls[chainId]?.length) { - _config.rpcUrls[chainId] = Array.from(urls) - } else if (!skipChains?.includes(chainId)) { - const filteredUrls = urls.filter( - (url) => !_config.rpcUrls[chainId]?.includes(url) - ) - _config.rpcUrls[chainId].push(...filteredUrls) - } - } - }, - async getRPCUrls() { - if (_loading) { - await _loading + + const { chains, providers, rpcUrls, ...otherOptions } = options + Object.assign(_config, otherOptions) + + // Set chains + if (chains) { + _config.chains = chains + const rpcUrls = chains.reduce((rpcUrls, chain) => { + if (chain.metamask?.rpcUrls?.length) { + rpcUrls[chain.id as ChainId] = chain.metamask.rpcUrls } - return _config.rpcUrls - }, + return rpcUrls + }, {} as RPCUrls) + _config.rpcUrls = setRPCUrls(_config.rpcUrls, rpcUrls, [ChainId.SOL]) + } + + // Set providers + if (providers) { + _config.providers = setProviders(_config.providers, providers) } - config.set(options) - return config + + // Set RPC URLs + if (rpcUrls) { + _config.rpcUrls = setRPCUrls(_config.rpcUrls, rpcUrls) + } + + return _config } function createBaseConfig(options: SDKConfig) { @@ -117,19 +95,23 @@ function createBaseConfig(options: SDKConfig) { return _config } -async function createChainsConfig(config: SDKProviderConfig) { - config.loading = getChains(config, { - chainTypes: [ChainType.EVM, ChainType.SVM, ChainType.UTXO, ChainType.MVM], - }) - .then((chains) => config.setChains(chains)) - .catch() - await config.loading -} - -export function createConfig(options: SDKConfig) { +export async function createConfig(options: SDKConfig) { const _config = createBaseConfig(options) - if (_config.get().preloadChains) { - createChainsConfig(_config) + if (_config.preloadChains) { + await getChains(_config, { + chainTypes: [ChainType.EVM, ChainType.SVM, ChainType.UTXO, ChainType.MVM], + }) + .then((chains) => { + _config.chains = chains + const rpcUrls = chains.reduce((rpcUrls, chain) => { + if (chain.metamask?.rpcUrls?.length) { + rpcUrls[chain.id as ChainId] = chain.metamask.rpcUrls + } + return rpcUrls + }, {} as RPCUrls) + _config.rpcUrls = setRPCUrls(_config.rpcUrls, rpcUrls, [ChainId.SOL]) + }) + .catch() } return _config } diff --git a/src/request.ts b/src/request.ts index 91f9d0b4..afb61f8a 100644 --- a/src/request.ts +++ b/src/request.ts @@ -1,7 +1,7 @@ -import type { SDKProviderConfig } from './core/types.js' import { ValidationError } from './errors/errors.js' import { HTTPError } from './errors/httpError.js' import { SDKError } from './errors/SDKError.js' +import type { SDKBaseConfig } from './types/internal.js' import type { ExtendedRequestInit } from './types/request.js' import { sleep } from './utils/sleep.js' import { version } from './version.js' @@ -18,13 +18,13 @@ const stripExtendRequestInitProperties = ({ }) export const request = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, url: RequestInfo | URL, options: ExtendedRequestInit = { retries: requestSettings.retries, } ): Promise => { - const { userId, integrator, widgetVersion, apiKey } = config.get() + const { userId, integrator, widgetVersion, apiKey } = config if (!integrator) { throw new SDKError( diff --git a/src/request.unit.spec.ts b/src/request.unit.spec.ts index 72c9d32a..30edf19e 100644 --- a/src/request.unit.spec.ts +++ b/src/request.unit.spec.ts @@ -11,18 +11,16 @@ import { vi, } from 'vitest' import { setupTestEnvironment } from '../tests/setup.js' -import { createConfig } from './createConfig.js' import { ValidationError } from './errors/errors.js' import type { HTTPError } from './errors/httpError.js' import { SDKError } from './errors/SDKError.js' import { request } from './request.js' import { handlers } from './services/api.unit.handlers.js' -import type { SDKBaseConfig } from './types/internal.js' import type { ExtendedRequestInit } from './types/request.js' import { version } from './version.js' -const config = createConfig({ integrator: 'lifi-sdk' }) -const apiUrl = config.get().apiUrl +const config = await setupTestEnvironment() +const apiUrl = config.apiUrl describe('request new', () => { const server = setupServer(...handlers) @@ -120,8 +118,8 @@ describe('request new', () => { describe('when dealing with errors', () => { it('should throw an error if the Integrator property is missing from the config', async () => { - const originalIntegrator = config.get().integrator - config.set({ integrator: '' } as SDKBaseConfig) + const originalIntegrator = config.integrator + config.integrator = '' const url = `${apiUrl}/advanced/routes` @@ -138,7 +136,7 @@ describe('request new', () => { ) ) - config.set({ integrator: originalIntegrator } as SDKBaseConfig) + config.integrator = originalIntegrator }) it('should throw a error with when the request fails', async () => { expect.assertions(2) diff --git a/src/services/api.int.spec.ts b/src/services/api.int.spec.ts index eef8e819..f4dfce9a 100644 --- a/src/services/api.int.spec.ts +++ b/src/services/api.int.spec.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from 'vitest' -import { createConfig } from '../createConfig.js' +import { setupTestEnvironment } from '../../tests/setup.js' import { getQuote } from './api.js' -const config = createConfig({ integrator: 'lifi-sdk' }) +const config = await setupTestEnvironment() describe('ApiService Integration Tests', () => { it('should successfully request a quote', async () => { diff --git a/src/services/api.ts b/src/services/api.ts index ea2824fb..4c09d18a 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -33,13 +33,13 @@ import { type TransactionAnalyticsRequest, type TransactionAnalyticsResponse, } from '@lifi/types' -import type { SDKProviderConfig } from '../core/types.js' import { BaseError } from '../errors/baseError.js' import { ErrorName } from '../errors/constants.js' import { ValidationError } from '../errors/errors.js' import { SDKError } from '../errors/SDKError.js' import { request } from '../request.js' import { isRoutesRequest, isStep } from '../typeguards.js' +import type { SDKBaseConfig } from '../types/internal.js' import { withDedupe } from '../utils/withDedupe.js' import type { GetStatusRequestExtended, @@ -56,17 +56,17 @@ import type { * @returns Quote for a token transfer */ export async function getQuote( - config: SDKProviderConfig, + config: SDKBaseConfig, params: QuoteRequestFromAmount, options?: RequestOptions ): Promise export async function getQuote( - config: SDKProviderConfig, + config: SDKBaseConfig, params: QuoteRequestToAmount, options?: RequestOptions ): Promise export async function getQuote( - config: SDKProviderConfig, + config: SDKBaseConfig, params: QuoteRequest, options?: RequestOptions ): Promise { @@ -108,7 +108,7 @@ export async function getQuote( ) ) } - const _config = config.get() + const _config = config // apply defaults params.integrator ??= _config.integrator params.order ??= _config.routeOptions?.order @@ -147,14 +147,14 @@ export async function getQuote( * @throws {LiFiError} Throws a LiFiError if request fails. */ export const getRoutes = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, params: RoutesRequest, options?: RequestOptions ): Promise => { if (!isRoutesRequest(params)) { throw new SDKError(new ValidationError('Invalid routes request.')) } - const _config = config.get() + const _config = config // apply defaults params.options = { integrator: _config.integrator, @@ -184,7 +184,7 @@ export const getRoutes = async ( * @returns - Returns step. */ export const getContractCallsQuote = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, params: ContractCallsQuoteRequest, options?: RequestOptions ): Promise => { @@ -216,7 +216,7 @@ export const getContractCallsQuote = async ( ) ) } - const _config = config.get() + const _config = config // apply defaults // option.order is not used in this endpoint params.integrator ??= _config.integrator @@ -252,7 +252,7 @@ export const getContractCallsQuote = async ( * @throws {LiFiError} Throws a LiFiError if request fails. */ export const getStepTransaction = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, step: LiFiStep | SignedLiFiStep, options?: RequestOptions ): Promise => { @@ -263,7 +263,7 @@ export const getStepTransaction = async ( return await request( config, - `${config.get().apiUrl}/advanced/stepTransaction`, + `${config.apiUrl}/advanced/stepTransaction`, { method: 'POST', headers: { @@ -283,7 +283,7 @@ export const getStepTransaction = async ( * @returns Returns status response. */ export const getStatus = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, params: GetStatusRequestExtended, options?: RequestOptions ): Promise => { @@ -297,7 +297,7 @@ export const getStatus = async ( ) return await request( config, - `${config.get().apiUrl}/status?${queryParams}`, + `${config.apiUrl}/status?${queryParams}`, { signal: options?.signal, } @@ -312,7 +312,7 @@ export const getStatus = async ( * @returns Relayer quote for a token transfer */ export const getRelayerQuote = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, params: QuoteRequestFromAmount, options?: RequestOptions ): Promise => { @@ -333,7 +333,7 @@ export const getRelayerQuote = async ( ) } } - const _config = config.get() + const _config = config // apply defaults params.integrator ??= _config.integrator params.order ??= _config.routeOptions?.order @@ -355,7 +355,7 @@ export const getRelayerQuote = async ( const result = await request( config, - `${config.get().apiUrl}/relayer/quote?${new URLSearchParams( + `${config.apiUrl}/relayer/quote?${new URLSearchParams( params as unknown as Record )}`, { @@ -382,7 +382,7 @@ export const getRelayerQuote = async ( * @returns Task ID for the relayed transaction */ export const relayTransaction = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, params: RelayRequest, options?: RequestOptions ): Promise => { @@ -408,7 +408,7 @@ export const relayTransaction = async ( const result = await request( config, - `${config.get().apiUrl}${relayerPath}`, + `${config.apiUrl}${relayerPath}`, { method: 'POST', headers: { @@ -443,7 +443,7 @@ export const relayTransaction = async ( * @returns Status of the relayed transaction */ export const getRelayedTransactionStatus = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, params: RelayStatusRequest, options?: RequestOptions ): Promise => { @@ -459,7 +459,7 @@ export const getRelayedTransactionStatus = async ( ) const result = await request( config, - `${config.get().apiUrl}/relayer/status/${taskId}?${queryParams}`, + `${config.apiUrl}/relayer/status/${taskId}?${queryParams}`, { signal: options?.signal, } @@ -484,7 +484,7 @@ export const getRelayedTransactionStatus = async ( * @throws {LiFiError} Throws a LiFiError if request fails. */ export const getChains = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, params?: ChainsRequest, options?: RequestOptions ): Promise => { @@ -502,7 +502,7 @@ export const getChains = async ( () => request( config, - `${config.get().apiUrl}/chains?${urlSearchParams}`, + `${config.apiUrl}/chains?${urlSearchParams}`, { signal: options?.signal, } @@ -519,17 +519,17 @@ export const getChains = async ( * @returns The tokens that are available on the requested chains */ export async function getTokens( - config: SDKProviderConfig, + config: SDKBaseConfig, params?: TokensRequest & { extended?: false | undefined }, options?: RequestOptions ): Promise export async function getTokens( - config: SDKProviderConfig, + config: SDKBaseConfig, params: TokensRequest & { extended: true }, options?: RequestOptions ): Promise export async function getTokens( - config: SDKProviderConfig, + config: SDKBaseConfig, params?: TokensRequest, options?: RequestOptions ): Promise { @@ -548,7 +548,7 @@ export async function getTokens( () => request< typeof isExtended extends true ? TokensExtendedResponse : TokensResponse - >(config, `${config.get().apiUrl}/tokens?${urlSearchParams}`, { + >(config, `${config.apiUrl}/tokens?${urlSearchParams}`, { signal: options?.signal, }), { id: `${getTokens.name}.${urlSearchParams}` } @@ -565,7 +565,7 @@ export async function getTokens( * @returns Token information */ export const getToken = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, chain: ChainKey | ChainId, token: string, options?: RequestOptions @@ -582,7 +582,7 @@ export const getToken = async ( } return await request( config, - `${config.get().apiUrl}/token?${new URLSearchParams({ + `${config.apiUrl}/token?${new URLSearchParams({ chain, token, } as Record)}`, @@ -599,7 +599,7 @@ export const getToken = async ( * @returns The tools that are available on the requested chains */ export const getTools = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, params?: ToolsRequest, options?: RequestOptions ): Promise => { @@ -612,7 +612,7 @@ export const getTools = async ( } return await request( config, - `${config.get().apiUrl}/tools?${new URLSearchParams( + `${config.apiUrl}/tools?${new URLSearchParams( params as Record )}`, { @@ -629,7 +629,7 @@ export const getTools = async ( * @returns Gas recommendation response. */ export const getGasRecommendation = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, params: GasRecommendationRequest, options?: RequestOptions ): Promise => { @@ -639,7 +639,7 @@ export const getGasRecommendation = async ( ) } - const url = new URL(`${config.get().apiUrl}/gas/suggestion/${params.chainId}`) + const url = new URL(`${config.apiUrl}/gas/suggestion/${params.chainId}`) if (params.fromChain) { url.searchParams.append('fromChain', params.fromChain as unknown as string) } @@ -659,11 +659,11 @@ export const getGasRecommendation = async ( * @returns ConnectionsResponse */ export const getConnections = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, connectionRequest: ConnectionsRequest, options?: RequestOptions ): Promise => { - const url = new URL(`${config.get().apiUrl}/connections`) + const url = new URL(`${config.apiUrl}/connections`) const { fromChain, fromToken, toChain, toToken } = connectionRequest @@ -700,7 +700,7 @@ export const getConnections = async ( } export const getTransactionHistory = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, { wallet, status, fromTimestamp, toTimestamp }: TransactionAnalyticsRequest, options?: RequestOptions ): Promise => { @@ -710,11 +710,9 @@ export const getTransactionHistory = async ( ) } - const _config = config.get() + const url = new URL(`${config.apiUrl}/analytics/transfers`) - const url = new URL(`${_config.apiUrl}/analytics/transfers`) - - url.searchParams.append('integrator', _config.integrator) + url.searchParams.append('integrator', config.integrator) url.searchParams.append('wallet', wallet) if (status) { diff --git a/src/services/api.unit.handlers.ts b/src/services/api.unit.handlers.ts index 378538df..5abd1976 100644 --- a/src/services/api.unit.handlers.ts +++ b/src/services/api.unit.handlers.ts @@ -1,10 +1,10 @@ import { findDefaultToken } from '@lifi/data-types' import { ChainId, CoinKey } from '@lifi/types' import { HttpResponse, http } from 'msw' -import { createConfig } from '../createConfig.js' +import { setupTestEnvironment } from '../../tests/setup.js' -const config = createConfig({ integrator: 'lifi-sdk' }) -const _config = config.get() +const config = await setupTestEnvironment() +const _config = config export const handlers = [ http.post(`${_config.apiUrl}/advanced/routes`, async () => { diff --git a/src/services/api.unit.spec.ts b/src/services/api.unit.spec.ts index b06e5ead..142763f2 100644 --- a/src/services/api.unit.spec.ts +++ b/src/services/api.unit.spec.ts @@ -23,7 +23,6 @@ import { vi, } from 'vitest' import { setupTestEnvironment } from '../../tests/setup.js' -import { createConfig } from '../createConfig.js' import { ValidationError } from '../errors/errors.js' import { SDKError } from '../errors/SDKError.js' import * as request from '../request.js' @@ -31,11 +30,11 @@ import { requestSettings } from '../request.js' import * as ApiService from './api.js' import { handlers } from './api.unit.handlers.js' -const config = createConfig({ integrator: 'lifi-sdk' }) +const config = await setupTestEnvironment() const mockedFetch = vi.spyOn(request, 'request') describe('ApiService', () => { - const _config = config.get() + const _config = config const server = setupServer(...handlers) beforeAll(() => { setupTestEnvironment() diff --git a/src/services/balance.ts b/src/services/balance.ts index b158becf..9e4a702b 100644 --- a/src/services/balance.ts +++ b/src/services/balance.ts @@ -7,10 +7,11 @@ import type { TokenExtended, WalletTokenExtended, } from '@lifi/types' -import type { SDKProviderConfig } from '../core/types.js' +import { getChainById } from '../core/configProvider.js' import { ValidationError } from '../errors/errors.js' import { request } from '../request.js' import { isToken } from '../typeguards.js' +import type { SDKBaseConfig } from '../types/internal.js' /** * Returns the balances of a specific token a wallet holds across all aggregated chains. @@ -20,7 +21,7 @@ import { isToken } from '../typeguards.js' * @throws {BaseError} Throws a ValidationError if parameters are invalid. */ export const getTokenBalance = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, walletAddress: string, token: Token ): Promise => { @@ -36,12 +37,12 @@ export const getTokenBalance = async ( * @throws {BaseError} Throws a ValidationError if parameters are invalid. */ export async function getTokenBalances( - config: SDKProviderConfig, + config: SDKBaseConfig, walletAddress: string, tokens: Token[] ): Promise export async function getTokenBalances( - config: SDKProviderConfig, + config: SDKBaseConfig, walletAddress: string, tokens: TokenExtended[] ): Promise { @@ -73,12 +74,12 @@ export async function getTokenBalances( * @throws {BaseError} Throws a ValidationError if parameters are invalid. */ export async function getTokenBalancesByChain( - config: SDKProviderConfig, + config: SDKBaseConfig, walletAddress: string, tokensByChain: { [chainId: number]: Token[] } ): Promise<{ [chainId: number]: TokenAmount[] }> export async function getTokenBalancesByChain( - config: SDKProviderConfig, + config: SDKBaseConfig, walletAddress: string, tokensByChain: { [chainId: number]: TokenExtended[] } ): Promise<{ [chainId: number]: TokenAmountExtended[] }> { @@ -92,9 +93,9 @@ export async function getTokenBalancesByChain( throw new ValidationError('Invalid tokens passed.') } - const provider = config - .get() - .providers.find((provider: any) => provider.isAddress(walletAddress)) + const provider = config.providers.find((provider) => + provider.isAddress(walletAddress) + ) if (!provider) { throw new Error(`SDK Token Provider for ${walletAddress} is not found.`) } @@ -105,7 +106,7 @@ export async function getTokenBalancesByChain( const tokenAmountsSettled = await Promise.allSettled( Object.keys(tokensByChain).map(async (chainIdStr) => { const chainId = Number.parseInt(chainIdStr, 10) - const chain = await config.getChainById(chainId) + const chain = await getChainById(config, chainId) if (provider.type === chain.chainType) { const tokenAmounts = await provider.getBalance( config, @@ -120,7 +121,7 @@ export async function getTokenBalancesByChain( } }) ) - if (config.get().debug) { + if (config.debug) { for (const result of tokenAmountsSettled) { if (result.status === 'rejected') { console.warn("Couldn't fetch token balance.", result.reason) @@ -138,7 +139,7 @@ export async function getTokenBalancesByChain( * @throws {BaseError} Throws a ValidationError if parameters are invalid. */ export const getWalletBalances = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, walletAddress: string, options?: RequestOptions ): Promise> => { @@ -148,7 +149,7 @@ export const getWalletBalances = async ( const response = await request( config, - `${config.get().apiUrl}/wallets/${walletAddress}/balances?extended=true`, + `${config.apiUrl}/wallets/${walletAddress}/balances?extended=true`, { signal: options?.signal, } diff --git a/src/services/balance.unit.spec.ts b/src/services/balance.unit.spec.ts index 664db91c..c2b5e340 100644 --- a/src/services/balance.unit.spec.ts +++ b/src/services/balance.unit.spec.ts @@ -2,10 +2,10 @@ import { findDefaultToken } from '@lifi/data-types' import type { Token, WalletTokenExtended } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { createConfig } from '../createConfig.js' +import { setupTestEnvironment } from '../../tests/setup.js' import * as balance from './balance.js' -const config = createConfig({ integrator: 'lifi-sdk' }) +const config = await setupTestEnvironment() const mockedGetTokenBalance = vi.spyOn(balance, 'getTokenBalance') const mockedGetTokenBalances = vi.spyOn(balance, 'getTokenBalances') const mockedGetTokenBalancesForChains = vi.spyOn( diff --git a/src/services/getNameServiceAddress.ts b/src/services/getNameServiceAddress.ts index 521ea566..4cf6ba65 100644 --- a/src/services/getNameServiceAddress.ts +++ b/src/services/getNameServiceAddress.ts @@ -1,13 +1,13 @@ import type { ChainType } from '@lifi/types' -import type { SDKProviderConfig } from '../core/types.js' +import type { SDKBaseConfig } from '../types/internal.js' export const getNameServiceAddress = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, name: string, chainType?: ChainType ): Promise => { try { - let providers = config.get().providers + let providers = config.providers if (chainType) { providers = providers.filter( (provider: any) => provider.type === chainType diff --git a/src/utils/getTransactionMessage.ts b/src/utils/getTransactionMessage.ts index c7d9765c..22ffa6c3 100644 --- a/src/utils/getTransactionMessage.ts +++ b/src/utils/getTransactionMessage.ts @@ -1,12 +1,13 @@ import type { LiFiStep } from '@lifi/types' -import type { SDKProviderConfig } from '../core/types.js' +import { getChainById } from '../core/configProvider.js' +import type { SDKBaseConfig } from '../types/internal.js' export const getTransactionFailedMessage = async ( - config: SDKProviderConfig, + config: SDKBaseConfig, step: LiFiStep, txLink?: string ): Promise => { - const chain = await config.getChainById(step.action.toChainId) + const chain = await getChainById(config, step.action.toChainId) const baseString = `It appears that your transaction may not have been successful. However, to confirm this, please check your ${chain.name} wallet for ${step.action.toToken.symbol}.` diff --git a/tests/setup.ts b/tests/setup.ts index b192c791..033d0686 100644 --- a/tests/setup.ts +++ b/tests/setup.ts @@ -1,8 +1,8 @@ import { createConfig } from '../src/createConfig.js' import { EVM, Solana, Sui, UTXO } from '../src/index.js' -export const setupTestEnvironment = () => { - createConfig({ +export const setupTestEnvironment = async () => { + return await createConfig({ integrator: 'lifi-sdk', providers: [EVM(), Solana(), UTXO(), Sui()], }) From 23128536087e1885ad4f773f5e3003cae5c80cc5 Mon Sep 17 00:00:00 2001 From: Lizaveta Miasayedava Date: Mon, 13 Oct 2025 14:38:38 +0100 Subject: [PATCH 03/13] fix: config type --- src/core/BaseStepExecutor.ts | 2 +- src/core/EVM/EVMStepExecutor.ts | 2 +- src/core/EVM/checkAllowance.ts | 2 +- src/core/EVM/checkPermitSupport.ts | 2 +- src/core/EVM/getActionWithFallback.ts | 2 +- src/core/EVM/getAllowance.ts | 2 +- src/core/EVM/getEVMBalance.ts | 2 +- src/core/EVM/isBatchingSupported.ts | 2 +- src/core/EVM/permits/getNativePermit.ts | 2 +- .../permits/getPermitTransferFromValues.ts | 2 +- src/core/EVM/permits/signPermit2Message.ts | 2 +- src/core/EVM/publicClient.ts | 2 +- src/core/EVM/resolveENSAddress.ts | 2 +- src/core/EVM/resolveEVMAddress.ts | 2 +- src/core/EVM/setAllowance.ts | 7 ++++-- src/core/EVM/types.ts | 3 +-- src/core/EVM/uns/resolveUNSAddress.ts | 2 +- src/core/EVM/utils.ts | 2 +- .../EVM/waitForRelayedTransactionReceipt.ts | 2 +- src/core/EVM/waitForTransactionReceipt.ts | 2 +- src/core/Solana/SolanaStepExecutor.ts | 7 ++++-- src/core/Solana/connection.ts | 2 +- src/core/Solana/getSolanaBalance.ts | 2 +- src/core/Solana/sendAndConfirmTransaction.ts | 2 +- src/core/Sui/SuiStepExecutor.ts | 7 ++++-- src/core/Sui/getSuiBalance.ts | 2 +- src/core/Sui/suiClient.ts | 2 +- src/core/UTXO/UTXOStepExecutor.ts | 2 +- src/core/UTXO/getUTXOBalance.int.spec.ts | 2 +- src/core/UTXO/getUTXOBalance.ts | 2 +- src/core/UTXO/getUTXOPublicClient.ts | 2 +- src/core/checkBalance.ts | 2 +- src/core/configProvider.ts | 3 +-- src/core/execution.ts | 3 +-- src/core/rpc.ts | 2 +- src/core/types.ts | 24 ++++++++++++++++++- .../waitForDestinationChainTransaction.ts | 3 +-- src/core/waitForTransactionStatus.ts | 3 +-- src/createConfig.ts | 8 +++++-- src/index.ts | 4 +++- src/request.ts | 2 +- src/services/api.ts | 2 +- src/services/balance.ts | 2 +- src/services/getNameServiceAddress.ts | 2 +- src/types/internal.ts | 23 ------------------ src/utils/getTransactionMessage.ts | 2 +- 46 files changed, 86 insertions(+), 77 deletions(-) delete mode 100644 src/types/internal.ts diff --git a/src/core/BaseStepExecutor.ts b/src/core/BaseStepExecutor.ts index 937dd27d..df28c09a 100644 --- a/src/core/BaseStepExecutor.ts +++ b/src/core/BaseStepExecutor.ts @@ -1,9 +1,9 @@ import type { LiFiStep } from '@lifi/types' -import type { SDKBaseConfig } from '../types/internal.js' import { StatusManager } from './StatusManager.js' import type { ExecutionOptions, InteractionSettings, + SDKBaseConfig, StepExecutor, StepExecutorOptions, } from './types.js' diff --git a/src/core/EVM/EVMStepExecutor.ts b/src/core/EVM/EVMStepExecutor.ts index 285436d6..9567b796 100644 --- a/src/core/EVM/EVMStepExecutor.ts +++ b/src/core/EVM/EVMStepExecutor.ts @@ -23,7 +23,6 @@ import { getStepTransaction, relayTransaction, } from '../../services/api.js' -import type { SDKBaseConfig } from '../../types/internal.js' import { isZeroAddress } from '../../utils/isZeroAddress.js' import { BaseStepExecutor } from '../BaseStepExecutor.js' import { checkBalance } from '../checkBalance.js' @@ -32,6 +31,7 @@ import { stepComparison } from '../stepComparison.js' import type { LiFiStepExtended, Process, + SDKBaseConfig, StepExecutorOptions, TransactionMethodType, TransactionParameters, diff --git a/src/core/EVM/checkAllowance.ts b/src/core/EVM/checkAllowance.ts index 17e79900..ac8bfa7d 100644 --- a/src/core/EVM/checkAllowance.ts +++ b/src/core/EVM/checkAllowance.ts @@ -3,13 +3,13 @@ import type { Address, Client, Hash } from 'viem' import { signTypedData } from 'viem/actions' import { getAction } from 'viem/utils' import { MaxUint256 } from '../../constants.js' -import type { SDKBaseConfig } from '../../types/internal.js' import type { StatusManager } from '../StatusManager.js' import type { ExecutionOptions, LiFiStepExtended, Process, ProcessType, + SDKBaseConfig, } from '../types.js' import { getActionWithFallback } from './getActionWithFallback.js' import { getAllowance } from './getAllowance.js' diff --git a/src/core/EVM/checkPermitSupport.ts b/src/core/EVM/checkPermitSupport.ts index 12aaf43f..52c27edb 100644 --- a/src/core/EVM/checkPermitSupport.ts +++ b/src/core/EVM/checkPermitSupport.ts @@ -1,8 +1,8 @@ import type { ExtendedChain } from '@lifi/types' import { ChainType } from '@lifi/types' import type { Address } from 'viem' -import type { SDKBaseConfig } from '../../types/internal.js' import { getProvider } from '../configProvider.js' +import type { SDKBaseConfig } from '../types.js' import { getActionWithFallback } from './getActionWithFallback.js' import { getAllowance } from './getAllowance.js' import { getNativePermit } from './permits/getNativePermit.js' diff --git a/src/core/EVM/getActionWithFallback.ts b/src/core/EVM/getActionWithFallback.ts index 249469e0..121800b3 100644 --- a/src/core/EVM/getActionWithFallback.ts +++ b/src/core/EVM/getActionWithFallback.ts @@ -8,7 +8,7 @@ import type { WalletActions, } from 'viem' import { getAction } from 'viem/utils' -import type { SDKBaseConfig } from '../../types/internal.js' +import type { SDKBaseConfig } from '../types.js' import { getPublicClient } from './publicClient.js' /** diff --git a/src/core/EVM/getAllowance.ts b/src/core/EVM/getAllowance.ts index 0f3ede66..e290fc06 100644 --- a/src/core/EVM/getAllowance.ts +++ b/src/core/EVM/getAllowance.ts @@ -1,8 +1,8 @@ import type { BaseToken, ChainId } from '@lifi/types' import type { Address, Client } from 'viem' import { multicall, readContract } from 'viem/actions' -import type { SDKBaseConfig } from '../../types/internal.js' import { isZeroAddress } from '../../utils/isZeroAddress.js' +import type { SDKBaseConfig } from '../types.js' import { allowanceAbi } from './abi.js' import { getActionWithFallback } from './getActionWithFallback.js' import { getPublicClient } from './publicClient.js' diff --git a/src/core/EVM/getEVMBalance.ts b/src/core/EVM/getEVMBalance.ts index e660c0df..4bd7a127 100644 --- a/src/core/EVM/getEVMBalance.ts +++ b/src/core/EVM/getEVMBalance.ts @@ -6,8 +6,8 @@ import { multicall, readContract, } from 'viem/actions' -import type { SDKBaseConfig } from '../../types/internal.js' import { isZeroAddress } from '../../utils/isZeroAddress.js' +import type { SDKBaseConfig } from '../types.js' import { balanceOfAbi, getEthBalanceAbi } from './abi.js' import { getPublicClient } from './publicClient.js' import { getMulticallAddress } from './utils.js' diff --git a/src/core/EVM/isBatchingSupported.ts b/src/core/EVM/isBatchingSupported.ts index 83cc3569..e3468755 100644 --- a/src/core/EVM/isBatchingSupported.ts +++ b/src/core/EVM/isBatchingSupported.ts @@ -2,9 +2,9 @@ import { ChainType } from '@lifi/types' import type { Client } from 'viem' import { getCapabilities } from 'viem/actions' import { getAction } from 'viem/utils' -import type { SDKBaseConfig } from '../../types/internal.js' import { sleep } from '../../utils/sleep.js' import { getProvider } from '../configProvider.js' +import type { SDKBaseConfig } from '../types.js' import type { EVMProvider } from './types.js' export async function isBatchingSupported( diff --git a/src/core/EVM/permits/getNativePermit.ts b/src/core/EVM/permits/getNativePermit.ts index 50a0630b..90c1dd6f 100644 --- a/src/core/EVM/permits/getNativePermit.ts +++ b/src/core/EVM/permits/getNativePermit.ts @@ -9,7 +9,7 @@ import { zeroHash, } from 'viem' import { getCode, multicall, readContract } from 'viem/actions' -import type { SDKBaseConfig } from '../../../types/internal.js' +import type { SDKBaseConfig } from '../../../core/types.js' import { eip2612Abi } from '../abi.js' import { getActionWithFallback } from '../getActionWithFallback.js' import { getMulticallAddress, isDelegationDesignatorCode } from '../utils.js' diff --git a/src/core/EVM/permits/getPermitTransferFromValues.ts b/src/core/EVM/permits/getPermitTransferFromValues.ts index 4676cbae..5d2e57fe 100644 --- a/src/core/EVM/permits/getPermitTransferFromValues.ts +++ b/src/core/EVM/permits/getPermitTransferFromValues.ts @@ -1,7 +1,7 @@ import type { ExtendedChain } from '@lifi/types' import type { Address, Client } from 'viem' import { readContract } from 'viem/actions' -import type { SDKBaseConfig } from '../../../types/internal.js' +import type { SDKBaseConfig } from '../../../core/types.js' import { permit2ProxyAbi } from '../abi.js' import { getActionWithFallback } from '../getActionWithFallback.js' import type { PermitTransferFrom } from './signatureTransfer.js' diff --git a/src/core/EVM/permits/signPermit2Message.ts b/src/core/EVM/permits/signPermit2Message.ts index 007746ed..abe2ff64 100644 --- a/src/core/EVM/permits/signPermit2Message.ts +++ b/src/core/EVM/permits/signPermit2Message.ts @@ -3,7 +3,7 @@ import type { Address, Client, Hex } from 'viem' import { keccak256 } from 'viem' import { signTypedData } from 'viem/actions' import { getAction } from 'viem/utils' -import type { SDKBaseConfig } from '../../../types/internal.js' +import type { SDKBaseConfig } from '../../../core/types.js' import { getPermitTransferFromValues } from './getPermitTransferFromValues.js' import { getPermitData } from './signatureTransfer.js' diff --git a/src/core/EVM/publicClient.ts b/src/core/EVM/publicClient.ts index a2f7b9aa..dce46f2f 100644 --- a/src/core/EVM/publicClient.ts +++ b/src/core/EVM/publicClient.ts @@ -2,9 +2,9 @@ import { ChainId, ChainType } from '@lifi/types' import type { Client } from 'viem' import { type Address, createClient, fallback, http, webSocket } from 'viem' import { type Chain, mainnet } from 'viem/chains' -import type { SDKBaseConfig } from '../../types/internal.js' import { getChainById, getProvider } from '../configProvider.js' import { getRpcUrls } from '../rpc.js' +import type { SDKBaseConfig } from '../types.js' import type { EVMProvider } from './types.js' import { UNS_PROXY_READER_ADDRESSES } from './uns/constants.js' diff --git a/src/core/EVM/resolveENSAddress.ts b/src/core/EVM/resolveENSAddress.ts index 683330ea..a5a71d47 100644 --- a/src/core/EVM/resolveENSAddress.ts +++ b/src/core/EVM/resolveENSAddress.ts @@ -1,6 +1,6 @@ import { ChainId } from '@lifi/types' import { getEnsAddress, normalize } from 'viem/ens' -import type { SDKBaseConfig } from '../../types/internal.js' +import type { SDKBaseConfig } from '../types.js' import { getPublicClient } from './publicClient.js' export const resolveENSAddress = async ( diff --git a/src/core/EVM/resolveEVMAddress.ts b/src/core/EVM/resolveEVMAddress.ts index d5523982..aa597131 100644 --- a/src/core/EVM/resolveEVMAddress.ts +++ b/src/core/EVM/resolveEVMAddress.ts @@ -1,6 +1,6 @@ import type { ChainId, CoinKey } from '@lifi/types' import { ChainType } from '@lifi/types' -import type { SDKBaseConfig } from '../../types/internal.js' +import type { SDKBaseConfig } from '../types.js' import { resolveENSAddress } from './resolveENSAddress.js' import { resolveUNSAddress } from './uns/resolveUNSAddress.js' diff --git a/src/core/EVM/setAllowance.ts b/src/core/EVM/setAllowance.ts index 2360f57d..ca1a88a7 100644 --- a/src/core/EVM/setAllowance.ts +++ b/src/core/EVM/setAllowance.ts @@ -2,9 +2,12 @@ import type { Address, Client, Hash, SendTransactionParameters } from 'viem' import { encodeFunctionData } from 'viem' import { sendTransaction } from 'viem/actions' import { getAction } from 'viem/utils' -import type { SDKBaseConfig } from '../../types/internal.js' import { isZeroAddress } from '../../utils/isZeroAddress.js' -import type { ExecutionOptions, TransactionParameters } from '../types.js' +import type { + ExecutionOptions, + SDKBaseConfig, + TransactionParameters, +} from '../types.js' import { approveAbi } from './abi.js' import { getAllowance } from './getAllowance.js' import type { ApproveTokenRequest, RevokeApprovalRequest } from './types.js' diff --git a/src/core/EVM/types.ts b/src/core/EVM/types.ts index b3800d70..fcea855c 100644 --- a/src/core/EVM/types.ts +++ b/src/core/EVM/types.ts @@ -6,8 +6,7 @@ import type { FallbackTransportConfig, Hex, } from 'viem' -import type { SDKBaseConfig } from '../../types/internal.js' -import type { SDKProvider, SwitchChainHook } from '../types.js' +import type { SDKBaseConfig, SDKProvider, SwitchChainHook } from '../types.js' export interface EVMProviderOptions { getWalletClient?: () => Promise diff --git a/src/core/EVM/uns/resolveUNSAddress.ts b/src/core/EVM/uns/resolveUNSAddress.ts index 0d7e5770..85f2986e 100644 --- a/src/core/EVM/uns/resolveUNSAddress.ts +++ b/src/core/EVM/uns/resolveUNSAddress.ts @@ -3,7 +3,7 @@ import type { Address, Client } from 'viem' import { readContract } from 'viem/actions' import { namehash } from 'viem/ens' import { getAction, trim } from 'viem/utils' -import type { SDKBaseConfig } from '../../../types/internal.js' +import type { SDKBaseConfig } from '../../../core/types.js' import { getPublicClient } from '../publicClient.js' import { CHAIN_ID_UNS_CHAIN_MAP, diff --git a/src/core/EVM/utils.ts b/src/core/EVM/utils.ts index c79fc952..b83ccc99 100644 --- a/src/core/EVM/utils.ts +++ b/src/core/EVM/utils.ts @@ -1,8 +1,8 @@ import type { ChainId, ExtendedChain } from '@lifi/types' import type { Address, Chain, Client, Transaction, TypedDataDomain } from 'viem' import { getBlock } from 'viem/actions' -import type { SDKBaseConfig } from '../../types/internal.js' import { median } from '../../utils/median.js' +import type { SDKBaseConfig } from '../types.js' import { getActionWithFallback } from './getActionWithFallback.js' type ChainBlockExplorer = { diff --git a/src/core/EVM/waitForRelayedTransactionReceipt.ts b/src/core/EVM/waitForRelayedTransactionReceipt.ts index c7ad3e20..697d4b7a 100644 --- a/src/core/EVM/waitForRelayedTransactionReceipt.ts +++ b/src/core/EVM/waitForRelayedTransactionReceipt.ts @@ -3,8 +3,8 @@ import type { Hash } from 'viem' import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' import { getRelayedTransactionStatus } from '../../services/api.js' -import type { SDKBaseConfig } from '../../types/internal.js' import { waitForResult } from '../../utils/waitForResult.js' +import type { SDKBaseConfig } from '../types.js' import type { WalletCallReceipt } from './types.js' export const waitForRelayedTransactionReceipt = async ( diff --git a/src/core/EVM/waitForTransactionReceipt.ts b/src/core/EVM/waitForTransactionReceipt.ts index 27f8d28d..04000476 100644 --- a/src/core/EVM/waitForTransactionReceipt.ts +++ b/src/core/EVM/waitForTransactionReceipt.ts @@ -10,7 +10,7 @@ import type { import { waitForTransactionReceipt as waitForTransactionReceiptInternal } from 'viem/actions' import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' -import type { SDKBaseConfig } from '../../types/internal.js' +import type { SDKBaseConfig } from '../types.js' import { getPublicClient } from './publicClient.js' interface WaitForTransactionReceiptProps { diff --git a/src/core/Solana/SolanaStepExecutor.ts b/src/core/Solana/SolanaStepExecutor.ts index 18cf834c..8c345da8 100644 --- a/src/core/Solana/SolanaStepExecutor.ts +++ b/src/core/Solana/SolanaStepExecutor.ts @@ -4,13 +4,16 @@ import { withTimeout } from 'viem' import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' import { getStepTransaction } from '../../services/api.js' -import type { SDKBaseConfig } from '../../types/internal.js' import { base64ToUint8Array } from '../../utils/base64ToUint8Array.js' import { BaseStepExecutor } from '../BaseStepExecutor.js' import { checkBalance } from '../checkBalance.js' import { getChainById } from '../configProvider.js' import { stepComparison } from '../stepComparison.js' -import type { LiFiStepExtended, TransactionParameters } from '../types.js' +import type { + LiFiStepExtended, + SDKBaseConfig, + TransactionParameters, +} from '../types.js' import { waitForDestinationChainTransaction } from '../waitForDestinationChainTransaction.js' import { callSolanaWithRetry } from './connection.js' import { parseSolanaErrors } from './parseSolanaErrors.js' diff --git a/src/core/Solana/connection.ts b/src/core/Solana/connection.ts index b44b3195..dad939f9 100644 --- a/src/core/Solana/connection.ts +++ b/src/core/Solana/connection.ts @@ -1,7 +1,7 @@ import { ChainId } from '@lifi/types' import { Connection } from '@solana/web3.js' -import type { SDKBaseConfig } from '../../types/internal.js' import { getRpcUrls } from '../rpc.js' +import type { SDKBaseConfig } from '../types.js' const connections = new Map() diff --git a/src/core/Solana/getSolanaBalance.ts b/src/core/Solana/getSolanaBalance.ts index 3fe2d031..4cf61fef 100644 --- a/src/core/Solana/getSolanaBalance.ts +++ b/src/core/Solana/getSolanaBalance.ts @@ -1,8 +1,8 @@ import type { ChainId, Token, TokenAmount } from '@lifi/types' import { PublicKey } from '@solana/web3.js' import { SolSystemProgram } from '../../constants.js' -import type { SDKBaseConfig } from '../../types/internal.js' import { withDedupe } from '../../utils/withDedupe.js' +import type { SDKBaseConfig } from '../types.js' import { callSolanaWithRetry } from './connection.js' import { Token2022ProgramId, TokenProgramId } from './types.js' diff --git a/src/core/Solana/sendAndConfirmTransaction.ts b/src/core/Solana/sendAndConfirmTransaction.ts index 326b1567..8530d310 100644 --- a/src/core/Solana/sendAndConfirmTransaction.ts +++ b/src/core/Solana/sendAndConfirmTransaction.ts @@ -4,8 +4,8 @@ import type { VersionedTransaction, } from '@solana/web3.js' import bs58 from 'bs58' -import type { SDKBaseConfig } from '../../types/internal.js' import { sleep } from '../../utils/sleep.js' +import type { SDKBaseConfig } from '../types.js' import { getSolanaConnections } from './connection.js' type ConfirmedTransactionResult = { diff --git a/src/core/Sui/SuiStepExecutor.ts b/src/core/Sui/SuiStepExecutor.ts index 2a5fcbd4..d781835c 100644 --- a/src/core/Sui/SuiStepExecutor.ts +++ b/src/core/Sui/SuiStepExecutor.ts @@ -5,12 +5,15 @@ import { import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' import { getStepTransaction } from '../../services/api.js' -import type { SDKBaseConfig } from '../../types/internal.js' import { BaseStepExecutor } from '../BaseStepExecutor.js' import { checkBalance } from '../checkBalance.js' import { getChainById } from '../configProvider.js' import { stepComparison } from '../stepComparison.js' -import type { LiFiStepExtended, TransactionParameters } from '../types.js' +import type { + LiFiStepExtended, + SDKBaseConfig, + TransactionParameters, +} from '../types.js' import { waitForDestinationChainTransaction } from '../waitForDestinationChainTransaction.js' import { parseSuiErrors } from './parseSuiErrors.js' import { callSuiWithRetry } from './suiClient.js' diff --git a/src/core/Sui/getSuiBalance.ts b/src/core/Sui/getSuiBalance.ts index c004d404..d54baac3 100644 --- a/src/core/Sui/getSuiBalance.ts +++ b/src/core/Sui/getSuiBalance.ts @@ -1,6 +1,6 @@ import type { Token, TokenAmount } from '@lifi/types' -import type { SDKBaseConfig } from '../../types/internal.js' import { withDedupe } from '../../utils/withDedupe.js' +import type { SDKBaseConfig } from '../types.js' import { callSuiWithRetry } from './suiClient.js' import { SuiTokenLongAddress, SuiTokenShortAddress } from './types.js' diff --git a/src/core/Sui/suiClient.ts b/src/core/Sui/suiClient.ts index e19382f9..d0bf40e3 100644 --- a/src/core/Sui/suiClient.ts +++ b/src/core/Sui/suiClient.ts @@ -1,7 +1,7 @@ import { ChainId } from '@lifi/types' import { SuiClient } from '@mysten/sui/client' -import type { SDKBaseConfig } from '../../types/internal.js' import { getRpcUrls } from '../rpc.js' +import type { SDKBaseConfig } from '../types.js' const clients = new Map() diff --git a/src/core/UTXO/UTXOStepExecutor.ts b/src/core/UTXO/UTXOStepExecutor.ts index 4597c49d..2166cd33 100644 --- a/src/core/UTXO/UTXOStepExecutor.ts +++ b/src/core/UTXO/UTXOStepExecutor.ts @@ -13,13 +13,13 @@ import { address, initEccLib, networks, Psbt } from 'bitcoinjs-lib' import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' import { getStepTransaction } from '../../services/api.js' -import type { SDKBaseConfig } from '../../types/internal.js' import { BaseStepExecutor } from '../BaseStepExecutor.js' import { checkBalance } from '../checkBalance.js' import { getChainById } from '../configProvider.js' import { stepComparison } from '../stepComparison.js' import type { LiFiStepExtended, + SDKBaseConfig, StepExecutorOptions, TransactionParameters, } from '../types.js' diff --git a/src/core/UTXO/getUTXOBalance.int.spec.ts b/src/core/UTXO/getUTXOBalance.int.spec.ts index f2edc62f..5778edf3 100644 --- a/src/core/UTXO/getUTXOBalance.int.spec.ts +++ b/src/core/UTXO/getUTXOBalance.int.spec.ts @@ -3,7 +3,7 @@ import type { StaticToken, Token } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' import { beforeAll, describe, expect, it } from 'vitest' import { setupTestEnvironment } from '../../../tests/setup.js' -import type { SDKBaseConfig } from '../../types/internal.js' +import type { SDKBaseConfig } from '../types.js' import { getUTXOBalance } from './getUTXOBalance.js' const config = await setupTestEnvironment() diff --git a/src/core/UTXO/getUTXOBalance.ts b/src/core/UTXO/getUTXOBalance.ts index 7425190e..11e3c7ff 100644 --- a/src/core/UTXO/getUTXOBalance.ts +++ b/src/core/UTXO/getUTXOBalance.ts @@ -1,5 +1,5 @@ import { ChainId, type Token, type TokenAmount } from '@lifi/types' -import type { SDKBaseConfig } from '../../types/internal.js' +import type { SDKBaseConfig } from '../types.js' import { getUTXOPublicClient } from './getUTXOPublicClient.js' export const getUTXOBalance = async ( diff --git a/src/core/UTXO/getUTXOPublicClient.ts b/src/core/UTXO/getUTXOPublicClient.ts index 3b26b744..ea5cd36c 100644 --- a/src/core/UTXO/getUTXOPublicClient.ts +++ b/src/core/UTXO/getUTXOPublicClient.ts @@ -17,9 +17,9 @@ import { type WalletActions, walletActions, } from '@bigmi/core' -import type { SDKBaseConfig } from '../../types/internal.js' import { getChainById } from '../configProvider.js' import { getRpcUrls } from '../rpc.js' +import type { SDKBaseConfig } from '../types.js' import { toBigmiChainId } from './utils.js' type PublicClient = Client< diff --git a/src/core/checkBalance.ts b/src/core/checkBalance.ts index a4c9f859..81586599 100644 --- a/src/core/checkBalance.ts +++ b/src/core/checkBalance.ts @@ -2,8 +2,8 @@ import type { LiFiStep } from '@lifi/types' import { formatUnits } from 'viem' import { BalanceError } from '../errors/errors.js' import { getTokenBalance } from '../services/balance.js' -import type { SDKBaseConfig } from '../types/internal.js' import { sleep } from '../utils/sleep.js' +import type { SDKBaseConfig } from './types.js' export const checkBalance = async ( config: SDKBaseConfig, diff --git a/src/core/configProvider.ts b/src/core/configProvider.ts index 0ac8a98a..a24235c9 100644 --- a/src/core/configProvider.ts +++ b/src/core/configProvider.ts @@ -1,6 +1,5 @@ import type { ChainId, ChainType } from '@lifi/types' -import type { SDKProvider } from '../core/types.js' -import type { SDKBaseConfig } from '../types/internal.js' +import type { SDKBaseConfig, SDKProvider } from './types.js' export const getProvider = ( config: SDKBaseConfig, diff --git a/src/core/execution.ts b/src/core/execution.ts index 5c938c00..0b1bfb2d 100644 --- a/src/core/execution.ts +++ b/src/core/execution.ts @@ -1,10 +1,9 @@ import type { Route } from '@lifi/types' import { LiFiErrorCode } from '../errors/constants.js' import { ProviderError } from '../errors/errors.js' -import type { SDKBaseConfig } from '../types/internal.js' import { executionState } from './executionState.js' import { prepareRestart } from './prepareRestart.js' -import type { ExecutionOptions, RouteExtended } from './types.js' +import type { ExecutionOptions, RouteExtended, SDKBaseConfig } from './types.js' /** * Execute a route. diff --git a/src/core/rpc.ts b/src/core/rpc.ts index d42e4eff..fffbb362 100644 --- a/src/core/rpc.ts +++ b/src/core/rpc.ts @@ -1,5 +1,5 @@ import type { ChainId } from '@lifi/types' -import type { SDKBaseConfig } from '../types/internal.js' +import type { SDKBaseConfig } from './types.js' export const getRpcUrls = ( config: SDKBaseConfig, diff --git a/src/core/types.ts b/src/core/types.ts index 526a00d6..96c186aa 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -2,17 +2,39 @@ import type { ChainId, ChainType, CoinKey, + ExtendedChain, FeeCost, GasCost, LiFiStep, Route, + RouteOptions, Step, Substatus, Token, TokenAmount, } from '@lifi/types' import type { Client } from 'viem' -import type { SDKBaseConfig } from '../types/internal.js' + +export interface SDKBaseConfig { + apiKey?: string + apiUrl: string + integrator: string + userId?: string + providers: SDKProvider[] + routeOptions?: RouteOptions + rpcUrls: RPCUrls + chains: ExtendedChain[] + disableVersionCheck?: boolean + widgetVersion?: string + preloadChains: boolean + debug: boolean +} + +export interface SDKConfig extends Partial> { + integrator: string +} + +export type RPCUrls = Partial> export interface SDKProvider { readonly type: ChainType diff --git a/src/core/waitForDestinationChainTransaction.ts b/src/core/waitForDestinationChainTransaction.ts index 4c060a1e..3b16776b 100644 --- a/src/core/waitForDestinationChainTransaction.ts +++ b/src/core/waitForDestinationChainTransaction.ts @@ -4,10 +4,9 @@ import type { FullStatusData, } from '@lifi/types' import { LiFiErrorCode } from '../errors/constants.js' -import type { SDKBaseConfig } from '../types/internal.js' import { getTransactionFailedMessage } from '../utils/getTransactionMessage.js' import type { StatusManager } from './StatusManager.js' -import type { LiFiStepExtended, Process } from './types.js' +import type { LiFiStepExtended, Process, SDKBaseConfig } from './types.js' import { waitForTransactionStatus } from './waitForTransactionStatus.js' export async function waitForDestinationChainTransaction( diff --git a/src/core/waitForTransactionStatus.ts b/src/core/waitForTransactionStatus.ts index d664fdcf..9efe3f8c 100644 --- a/src/core/waitForTransactionStatus.ts +++ b/src/core/waitForTransactionStatus.ts @@ -1,11 +1,10 @@ import type { FullStatusData, LiFiStep, StatusResponse } from '@lifi/types' import { ServerError } from '../errors/errors.js' import { getStatus } from '../services/api.js' -import type { SDKBaseConfig } from '../types/internal.js' import { waitForResult } from '../utils/waitForResult.js' import { getSubstatusMessage } from './processMessages.js' import type { StatusManager } from './StatusManager.js' -import type { ProcessType } from './types.js' +import type { ProcessType, SDKBaseConfig } from './types.js' const TRANSACTION_HASH_OBSERVERS: Record> = {} diff --git a/src/createConfig.ts b/src/createConfig.ts index 2efd3200..ad844535 100644 --- a/src/createConfig.ts +++ b/src/createConfig.ts @@ -1,7 +1,11 @@ import { ChainId, ChainType } from '@lifi/types' -import type { SDKProvider } from './core/types.js' +import type { + RPCUrls, + SDKBaseConfig, + SDKConfig, + SDKProvider, +} from './core/types.js' import { getChains } from './services/api.js' -import type { RPCUrls, SDKBaseConfig, SDKConfig } from './types/internal.js' import { checkPackageUpdates } from './utils/checkPackageUpdates.js' import { name, version } from './version.js' diff --git a/src/index.ts b/src/index.ts index 5cdf966e..4e315ad2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -69,6 +69,9 @@ export type { RouteExecutionDataDictionary, RouteExecutionDictionary, RouteExtended, + RPCUrls, + SDKBaseConfig, + SDKConfig, SDKProvider, StepExecutor, StepExecutorOptions, @@ -121,7 +124,6 @@ export { getWalletBalances, } from './services/balance.js' export { getNameServiceAddress } from './services/getNameServiceAddress.js' -export type { RPCUrls, SDKBaseConfig, SDKConfig } from './types/internal.js' export { checkPackageUpdates } from './utils/checkPackageUpdates.js' export { convertQuoteToRoute } from './utils/convertQuoteToRoute.js' export { fetchTxErrorDetails } from './utils/fetchTxErrorDetails.js' diff --git a/src/request.ts b/src/request.ts index afb61f8a..19f9702e 100644 --- a/src/request.ts +++ b/src/request.ts @@ -1,7 +1,7 @@ +import type { SDKBaseConfig } from './core/types.js' import { ValidationError } from './errors/errors.js' import { HTTPError } from './errors/httpError.js' import { SDKError } from './errors/SDKError.js' -import type { SDKBaseConfig } from './types/internal.js' import type { ExtendedRequestInit } from './types/request.js' import { sleep } from './utils/sleep.js' import { version } from './version.js' diff --git a/src/services/api.ts b/src/services/api.ts index 4c09d18a..bbd778d5 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -33,13 +33,13 @@ import { type TransactionAnalyticsRequest, type TransactionAnalyticsResponse, } from '@lifi/types' +import type { SDKBaseConfig } from '../core/types.js' import { BaseError } from '../errors/baseError.js' import { ErrorName } from '../errors/constants.js' import { ValidationError } from '../errors/errors.js' import { SDKError } from '../errors/SDKError.js' import { request } from '../request.js' import { isRoutesRequest, isStep } from '../typeguards.js' -import type { SDKBaseConfig } from '../types/internal.js' import { withDedupe } from '../utils/withDedupe.js' import type { GetStatusRequestExtended, diff --git a/src/services/balance.ts b/src/services/balance.ts index 9e4a702b..35f10861 100644 --- a/src/services/balance.ts +++ b/src/services/balance.ts @@ -8,10 +8,10 @@ import type { WalletTokenExtended, } from '@lifi/types' import { getChainById } from '../core/configProvider.js' +import type { SDKBaseConfig } from '../core/types.js' import { ValidationError } from '../errors/errors.js' import { request } from '../request.js' import { isToken } from '../typeguards.js' -import type { SDKBaseConfig } from '../types/internal.js' /** * Returns the balances of a specific token a wallet holds across all aggregated chains. diff --git a/src/services/getNameServiceAddress.ts b/src/services/getNameServiceAddress.ts index 4cf6ba65..45706fac 100644 --- a/src/services/getNameServiceAddress.ts +++ b/src/services/getNameServiceAddress.ts @@ -1,5 +1,5 @@ import type { ChainType } from '@lifi/types' -import type { SDKBaseConfig } from '../types/internal.js' +import type { SDKBaseConfig } from '../core/types.js' export const getNameServiceAddress = async ( config: SDKBaseConfig, diff --git a/src/types/internal.ts b/src/types/internal.ts deleted file mode 100644 index fb5527ef..00000000 --- a/src/types/internal.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { ChainId, ExtendedChain, RouteOptions } from '@lifi/types' -import type { SDKProvider } from '../core/types.js' - -export interface SDKBaseConfig { - apiKey?: string - apiUrl: string - integrator: string - userId?: string - providers: SDKProvider[] - routeOptions?: RouteOptions - rpcUrls: RPCUrls - chains: ExtendedChain[] - disableVersionCheck?: boolean - widgetVersion?: string - preloadChains: boolean - debug: boolean -} - -export interface SDKConfig extends Partial> { - integrator: string -} - -export type RPCUrls = Partial> diff --git a/src/utils/getTransactionMessage.ts b/src/utils/getTransactionMessage.ts index 22ffa6c3..edc581de 100644 --- a/src/utils/getTransactionMessage.ts +++ b/src/utils/getTransactionMessage.ts @@ -1,6 +1,6 @@ import type { LiFiStep } from '@lifi/types' import { getChainById } from '../core/configProvider.js' -import type { SDKBaseConfig } from '../types/internal.js' +import type { SDKBaseConfig } from '../core/types.js' export const getTransactionFailedMessage = async ( config: SDKBaseConfig, From dfb70723bd67089ca9108066c206cfc0fd0bb19f Mon Sep 17 00:00:00 2001 From: Lizaveta Miasayedava Date: Tue, 14 Oct 2025 13:45:22 +0100 Subject: [PATCH 04/13] refactor: config creation funcs --- src/core/EVM/EVMStepExecutor.ts | 4 +- src/core/EVM/checkPermitSupport.ts | 2 +- src/core/EVM/getActionWithFallback.ts | 2 +- src/core/EVM/getAllowance.int.spec.ts | 8 +-- src/core/EVM/getAllowance.ts | 4 +- src/core/EVM/getEVMBalance.ts | 4 +- src/core/EVM/publicClient.ts | 6 +- src/core/EVM/resolveENSAddress.ts | 2 +- src/core/EVM/uns/resolveUNSAddress.ts | 4 +- src/core/EVM/utils.ts | 2 +- src/core/EVM/waitForTransactionReceipt.ts | 2 +- src/core/Solana/SolanaStepExecutor.ts | 4 +- src/core/Sui/SuiStepExecutor.ts | 4 +- src/core/UTXO/UTXOStepExecutor.ts | 4 +- src/core/UTXO/getUTXOPublicClient.ts | 6 +- src/core/configProvider.ts | 57 ++++++++++++++-- src/createConfig.ts | 75 +++++---------------- src/services/api.ts | 82 +++++++++++------------ src/services/api.unit.handlers.ts | 21 +++--- src/services/api.unit.spec.ts | 5 +- src/services/balance.ts | 2 +- src/services/getNameServiceAddress.ts | 8 +-- src/utils/getTransactionMessage.ts | 6 +- 23 files changed, 156 insertions(+), 158 deletions(-) diff --git a/src/core/EVM/EVMStepExecutor.ts b/src/core/EVM/EVMStepExecutor.ts index 9567b796..04ec69c8 100644 --- a/src/core/EVM/EVMStepExecutor.ts +++ b/src/core/EVM/EVMStepExecutor.ts @@ -399,8 +399,8 @@ export class EVMStepExecutor extends BaseStepExecutor { } } - const fromChain = await getChainById(config, step.action.fromChainId) - const toChain = await getChainById(config, step.action.toChainId) + const fromChain = getChainById(config, step.action.fromChainId) + const toChain = getChainById(config, step.action.toChainId) // Check if the wallet supports atomic batch transactions (EIP-5792) const calls: Call[] = [] diff --git a/src/core/EVM/checkPermitSupport.ts b/src/core/EVM/checkPermitSupport.ts index 52c27edb..18adb957 100644 --- a/src/core/EVM/checkPermitSupport.ts +++ b/src/core/EVM/checkPermitSupport.ts @@ -47,7 +47,7 @@ export const checkPermitSupport = async ( let client = await provider?.getWalletClient?.() if (!client) { - client = await getPublicClient(config, chain.id) + client = getPublicClient(config, chain.id) } const nativePermit = await getActionWithFallback( diff --git a/src/core/EVM/getActionWithFallback.ts b/src/core/EVM/getActionWithFallback.ts index 121800b3..11218120 100644 --- a/src/core/EVM/getActionWithFallback.ts +++ b/src/core/EVM/getActionWithFallback.ts @@ -54,7 +54,7 @@ export const getActionWithFallback = async < throw error } - const publicClient = await getPublicClient(config, chainId) + const publicClient = getPublicClient(config, chainId) return await getAction(publicClient, actionFn, name)(params) } } diff --git a/src/core/EVM/getAllowance.int.spec.ts b/src/core/EVM/getAllowance.int.spec.ts index 75e2a82b..67233676 100644 --- a/src/core/EVM/getAllowance.int.spec.ts +++ b/src/core/EVM/getAllowance.int.spec.ts @@ -32,7 +32,7 @@ beforeAll(setupTestEnvironment) describe('allowance integration tests', { retry: retryTimes, timeout }, () => { it('should work for ERC20 on POL', async () => { - const client = await getPublicClient(config, memeToken.chainId) + const client = getPublicClient(config, memeToken.chainId) const allowance = await getAllowance( config, client, @@ -49,7 +49,7 @@ describe('allowance integration tests', { retry: retryTimes, timeout }, () => { { retry: retryTimes, timeout }, async () => { const token = findDefaultToken(CoinKey.POL, ChainId.POL) - const client = await getPublicClient(config, token.chainId) + const client = getPublicClient(config, token.chainId) const allowance = await getAllowance( config, client, @@ -68,7 +68,7 @@ describe('allowance integration tests', { retry: retryTimes, timeout }, () => { async () => { const invalidToken = findDefaultToken(CoinKey.POL, ChainId.POL) invalidToken.address = '0x2170ed0880ac9a755fd29b2688956bd959f933f8' - const client = await getPublicClient(config, invalidToken.chainId) + const client = getPublicClient(config, invalidToken.chainId) const allowance = await getAllowance( config, client, @@ -84,7 +84,7 @@ describe('allowance integration tests', { retry: retryTimes, timeout }, () => { 'should handle empty lists with multicall', { retry: retryTimes, timeout }, async () => { - const client = await getPublicClient(config, 137) + const client = getPublicClient(config, 137) const allowances = await getAllowanceMulticall( config, client, diff --git a/src/core/EVM/getAllowance.ts b/src/core/EVM/getAllowance.ts index e290fc06..decc8581 100644 --- a/src/core/EVM/getAllowance.ts +++ b/src/core/EVM/getAllowance.ts @@ -103,7 +103,7 @@ export const getTokenAllowance = async ( return } - const client = await getPublicClient(config, token.chainId) + const client = getPublicClient(config, token.chainId) const approved = await getAllowance( config, @@ -145,7 +145,7 @@ export const getTokenAllowanceMulticall = async ( const allowances = ( await Promise.all( chainKeys.map(async (chainId) => { - const client = await getPublicClient(config, chainId) + const client = getPublicClient(config, chainId) // get allowances for current chain and token list return getAllowanceMulticall( config, diff --git a/src/core/EVM/getEVMBalance.ts b/src/core/EVM/getEVMBalance.ts index 4bd7a127..18bf5c73 100644 --- a/src/core/EVM/getEVMBalance.ts +++ b/src/core/EVM/getEVMBalance.ts @@ -48,7 +48,7 @@ const getEVMBalanceMulticall = async ( walletAddress: string, multicallAddress: string ): Promise => { - const client = await getPublicClient(config, chainId) + const client = getPublicClient(config, chainId) const contracts = tokens.map((token) => { if (isZeroAddress(token.address)) { @@ -94,7 +94,7 @@ const getEVMBalanceDefault = async ( tokens: Token[], walletAddress: Address ): Promise => { - const client = await getPublicClient(config, chainId) + const client = getPublicClient(config, chainId) const queue: Promise[] = tokens.map((token) => { if (isZeroAddress(token.address)) { diff --git a/src/core/EVM/publicClient.ts b/src/core/EVM/publicClient.ts index dce46f2f..2dd9bbf6 100644 --- a/src/core/EVM/publicClient.ts +++ b/src/core/EVM/publicClient.ts @@ -16,10 +16,10 @@ const publicClients: Record = {} * @param chainId - Id of the chain the provider is for * @returns The public client for the given chain */ -export const getPublicClient = async ( +export const getPublicClient = ( config: SDKBaseConfig, chainId: number -): Promise => { +): Client => { if (publicClients[chainId]) { return publicClients[chainId] } @@ -34,7 +34,7 @@ export const getPublicClient = async ( }, }) ) - const _chain = await getChainById(config, chainId) + const _chain = getChainById(config, chainId) const chain: Chain = { ..._chain, ..._chain.metamask, diff --git a/src/core/EVM/resolveENSAddress.ts b/src/core/EVM/resolveENSAddress.ts index a5a71d47..1d72c4b5 100644 --- a/src/core/EVM/resolveENSAddress.ts +++ b/src/core/EVM/resolveENSAddress.ts @@ -8,7 +8,7 @@ export const resolveENSAddress = async ( name: string ): Promise => { try { - const client = await getPublicClient(config, ChainId.ETH) + const client = getPublicClient(config, ChainId.ETH) const address = await getEnsAddress(client, { name: normalize(name), }) diff --git a/src/core/EVM/uns/resolveUNSAddress.ts b/src/core/EVM/uns/resolveUNSAddress.ts index 85f2986e..0df39b6b 100644 --- a/src/core/EVM/uns/resolveUNSAddress.ts +++ b/src/core/EVM/uns/resolveUNSAddress.ts @@ -21,8 +21,8 @@ export const resolveUNSAddress = async ( token?: CoinKey ): Promise => { try { - const L1Client = await getPublicClient(config, ChainId.ETH) - const L2Client = await getPublicClient(config, ChainId.POL) + const L1Client = getPublicClient(config, ChainId.ETH) + const L2Client = getPublicClient(config, ChainId.POL) const nameHash = namehash(name) const keys: string[] = [] diff --git a/src/core/EVM/utils.ts b/src/core/EVM/utils.ts index b83ccc99..56386edc 100644 --- a/src/core/EVM/utils.ts +++ b/src/core/EVM/utils.ts @@ -99,7 +99,7 @@ export const getMulticallAddress = async ( chainId: ChainId ): Promise
=> { const chains = config.chains - return chains.find((chain: any) => chain.id === chainId) + return chains.find((chain) => chain.id === chainId) ?.multicallAddress as Address } diff --git a/src/core/EVM/waitForTransactionReceipt.ts b/src/core/EVM/waitForTransactionReceipt.ts index 04000476..0db1d0ac 100644 --- a/src/core/EVM/waitForTransactionReceipt.ts +++ b/src/core/EVM/waitForTransactionReceipt.ts @@ -35,7 +35,7 @@ export async function waitForTransactionReceipt({ ) if (!transactionReceipt?.status) { - const publicClient = await getPublicClient(config, chainId) + const publicClient = getPublicClient(config, chainId) const result = await waitForReceipt(publicClient, txHash, onReplaced) transactionReceipt = result.transactionReceipt replacementReason = result.replacementReason diff --git a/src/core/Solana/SolanaStepExecutor.ts b/src/core/Solana/SolanaStepExecutor.ts index 8c345da8..ca6254d7 100644 --- a/src/core/Solana/SolanaStepExecutor.ts +++ b/src/core/Solana/SolanaStepExecutor.ts @@ -44,8 +44,8 @@ export class SolanaStepExecutor extends BaseStepExecutor { ): Promise => { step.execution = this.statusManager.initExecutionObject(step) - const fromChain = await getChainById(config, step.action.fromChainId) - const toChain = await getChainById(config, step.action.toChainId) + const fromChain = getChainById(config, step.action.fromChainId) + const toChain = getChainById(config, step.action.toChainId) const isBridgeExecution = fromChain.id !== toChain.id const currentProcessType = isBridgeExecution ? 'CROSS_CHAIN' : 'SWAP' diff --git a/src/core/Sui/SuiStepExecutor.ts b/src/core/Sui/SuiStepExecutor.ts index d781835c..dabf4e79 100644 --- a/src/core/Sui/SuiStepExecutor.ts +++ b/src/core/Sui/SuiStepExecutor.ts @@ -47,8 +47,8 @@ export class SuiStepExecutor extends BaseStepExecutor { ): Promise => { step.execution = this.statusManager.initExecutionObject(step) - const fromChain = await getChainById(config, step.action.fromChainId) - const toChain = await getChainById(config, step.action.toChainId) + const fromChain = getChainById(config, step.action.fromChainId) + const toChain = getChainById(config, step.action.toChainId) const isBridgeExecution = fromChain.id !== toChain.id const currentProcessType = isBridgeExecution ? 'CROSS_CHAIN' : 'SWAP' diff --git a/src/core/UTXO/UTXOStepExecutor.ts b/src/core/UTXO/UTXOStepExecutor.ts index 2166cd33..246b42f5 100644 --- a/src/core/UTXO/UTXOStepExecutor.ts +++ b/src/core/UTXO/UTXOStepExecutor.ts @@ -57,8 +57,8 @@ export class UTXOStepExecutor extends BaseStepExecutor { ): Promise => { step.execution = this.statusManager.initExecutionObject(step) - const fromChain = await getChainById(config, step.action.fromChainId) - const toChain = await getChainById(config, step.action.toChainId) + const fromChain = getChainById(config, step.action.fromChainId) + const toChain = getChainById(config, step.action.toChainId) const isBridgeExecution = fromChain.id !== toChain.id const currentProcessType = isBridgeExecution ? 'CROSS_CHAIN' : 'SWAP' diff --git a/src/core/UTXO/getUTXOPublicClient.ts b/src/core/UTXO/getUTXOPublicClient.ts index ea5cd36c..939a6fd5 100644 --- a/src/core/UTXO/getUTXOPublicClient.ts +++ b/src/core/UTXO/getUTXOPublicClient.ts @@ -38,10 +38,10 @@ const publicClients: Record = {} * @param chainId - Id of the chain the provider is for * @returns The public client for the given chain */ -export const getUTXOPublicClient = async ( +export const getUTXOPublicClient = ( config: SDKBaseConfig, chainId: number -): Promise => { +): PublicClient => { if (!publicClients[chainId]) { const urls = getRpcUrls(config, chainId) const fallbackTransports = urls.map((url) => @@ -51,7 +51,7 @@ export const getUTXOPublicClient = async ( }, }) ) - const _chain = await getChainById(config, chainId) + const _chain = getChainById(config, chainId) const chain: Chain = { ..._chain, ..._chain.metamask, diff --git a/src/core/configProvider.ts b/src/core/configProvider.ts index a24235c9..f1902889 100644 --- a/src/core/configProvider.ts +++ b/src/core/configProvider.ts @@ -1,19 +1,66 @@ -import type { ChainId, ChainType } from '@lifi/types' -import type { SDKBaseConfig, SDKProvider } from './types.js' +import type { ChainId, ChainType, ExtendedChain } from '@lifi/types' +import type { RPCUrls, SDKBaseConfig, SDKProvider } from './types.js' -export const getProvider = ( +export function getProvider( config: SDKBaseConfig, type: ChainType -): SDKProvider | undefined => { +): SDKProvider | undefined { return config.providers.find( (provider: SDKProvider) => provider.type === type ) } -export async function getChainById(config: SDKBaseConfig, chainId: ChainId) { +export function getMergedProviders( + configProviders: SDKProvider[], + providers: SDKProvider[] +) { + const providerMap = new Map( + configProviders.map((provider) => [provider.type, provider]) + ) + for (const provider of providers) { + providerMap.set(provider.type, provider) + } + return Array.from(providerMap.values()) +} + +export function getChainById(config: SDKBaseConfig, chainId: ChainId) { const chain = config.chains?.find((chain) => chain.id === chainId) if (!chain) { throw new Error(`ChainId ${chainId} not found`) } return chain } + +export function getMergedRPCUrls( + configRPCUrls: RPCUrls, + rpcUrls: RPCUrls, + skipChains?: ChainId[] +) { + const newRPCUrls = { ...configRPCUrls } + for (const rpcUrlsKey in rpcUrls) { + const chainId = Number(rpcUrlsKey) as ChainId + const urls = rpcUrls[chainId] + if (!urls?.length) { + continue + } + if (!newRPCUrls[chainId]?.length) { + newRPCUrls[chainId] = Array.from(urls) + } else if (!skipChains?.includes(chainId)) { + const filteredUrls = urls.filter( + (url) => !newRPCUrls[chainId]?.includes(url) + ) + newRPCUrls[chainId].push(...filteredUrls) + } + } + + return newRPCUrls +} + +export function getMetamaskRPCUrls(chains: ExtendedChain[]) { + return chains.reduce((rpcUrls, chain) => { + if (chain.metamask?.rpcUrls?.length) { + rpcUrls[chain.id as ChainId] = chain.metamask.rpcUrls + } + return rpcUrls + }, {} as RPCUrls) +} diff --git a/src/createConfig.ts b/src/createConfig.ts index ad844535..c045c50c 100644 --- a/src/createConfig.ts +++ b/src/createConfig.ts @@ -1,52 +1,14 @@ import { ChainId, ChainType } from '@lifi/types' -import type { - RPCUrls, - SDKBaseConfig, - SDKConfig, - SDKProvider, -} from './core/types.js' +import { + getMergedProviders, + getMergedRPCUrls, + getMetamaskRPCUrls, +} from './core/configProvider.js' +import type { SDKBaseConfig, SDKConfig } from './core/types.js' import { getChains } from './services/api.js' import { checkPackageUpdates } from './utils/checkPackageUpdates.js' import { name, version } from './version.js' -function setProviders( - configProviders: SDKProvider[], - providers: SDKProvider[] -) { - const providerMap = new Map( - configProviders.map((provider) => [provider.type, provider]) - ) - for (const provider of providers) { - providerMap.set(provider.type, provider) - } - return Array.from(providerMap.values()) -} - -function setRPCUrls( - configRPCUrls: RPCUrls, - rpcUrls: RPCUrls, - skipChains?: ChainId[] -) { - const newRPCUrls = { ...configRPCUrls } - for (const rpcUrlsKey in rpcUrls) { - const chainId = Number(rpcUrlsKey) as ChainId - const urls = rpcUrls[chainId] - if (!urls?.length) { - continue - } - if (!newRPCUrls[chainId]?.length) { - newRPCUrls[chainId] = Array.from(urls) - } else if (!skipChains?.includes(chainId)) { - const filteredUrls = urls.filter( - (url) => !newRPCUrls[chainId]?.includes(url) - ) - newRPCUrls[chainId].push(...filteredUrls) - } - } - - return newRPCUrls -} - function initializeConfig(options: SDKConfig) { const _config: SDKBaseConfig = { integrator: 'lifi-sdk', @@ -64,23 +26,18 @@ function initializeConfig(options: SDKConfig) { // Set chains if (chains) { _config.chains = chains - const rpcUrls = chains.reduce((rpcUrls, chain) => { - if (chain.metamask?.rpcUrls?.length) { - rpcUrls[chain.id as ChainId] = chain.metamask.rpcUrls - } - return rpcUrls - }, {} as RPCUrls) - _config.rpcUrls = setRPCUrls(_config.rpcUrls, rpcUrls, [ChainId.SOL]) + const rpcUrls = getMetamaskRPCUrls(chains) + _config.rpcUrls = getMergedRPCUrls(_config.rpcUrls, rpcUrls, [ChainId.SOL]) } // Set providers if (providers) { - _config.providers = setProviders(_config.providers, providers) + _config.providers = getMergedProviders(_config.providers, providers) } // Set RPC URLs if (rpcUrls) { - _config.rpcUrls = setRPCUrls(_config.rpcUrls, rpcUrls) + _config.rpcUrls = getMergedRPCUrls(_config.rpcUrls, rpcUrls) } return _config @@ -106,14 +63,12 @@ export async function createConfig(options: SDKConfig) { chainTypes: [ChainType.EVM, ChainType.SVM, ChainType.UTXO, ChainType.MVM], }) .then((chains) => { + // Set chains _config.chains = chains - const rpcUrls = chains.reduce((rpcUrls, chain) => { - if (chain.metamask?.rpcUrls?.length) { - rpcUrls[chain.id as ChainId] = chain.metamask.rpcUrls - } - return rpcUrls - }, {} as RPCUrls) - _config.rpcUrls = setRPCUrls(_config.rpcUrls, rpcUrls, [ChainId.SOL]) + const rpcUrls = getMetamaskRPCUrls(chains) + _config.rpcUrls = getMergedRPCUrls(_config.rpcUrls, rpcUrls, [ + ChainId.SOL, + ]) }) .catch() } diff --git a/src/services/api.ts b/src/services/api.ts index bbd778d5..757978b0 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -108,19 +108,19 @@ export async function getQuote( ) ) } - const _config = config + // apply defaults - params.integrator ??= _config.integrator - params.order ??= _config.routeOptions?.order - params.slippage ??= _config.routeOptions?.slippage - params.referrer ??= _config.routeOptions?.referrer - params.fee ??= _config.routeOptions?.fee - params.allowBridges ??= _config.routeOptions?.bridges?.allow - params.denyBridges ??= _config.routeOptions?.bridges?.deny - params.preferBridges ??= _config.routeOptions?.bridges?.prefer - params.allowExchanges ??= _config.routeOptions?.exchanges?.allow - params.denyExchanges ??= _config.routeOptions?.exchanges?.deny - params.preferExchanges ??= _config.routeOptions?.exchanges?.prefer + params.integrator ??= config.integrator + params.order ??= config.routeOptions?.order + params.slippage ??= config.routeOptions?.slippage + params.referrer ??= config.routeOptions?.referrer + params.fee ??= config.routeOptions?.fee + params.allowBridges ??= config.routeOptions?.bridges?.allow + params.denyBridges ??= config.routeOptions?.bridges?.deny + params.preferBridges ??= config.routeOptions?.bridges?.prefer + params.allowExchanges ??= config.routeOptions?.exchanges?.allow + params.denyExchanges ??= config.routeOptions?.exchanges?.deny + params.preferExchanges ??= config.routeOptions?.exchanges?.prefer for (const key of Object.keys(params)) { if (!params[key as keyof QuoteRequest]) { @@ -130,7 +130,7 @@ export async function getQuote( return await request( config, - `${_config.apiUrl}/${isFromAmountRequest ? 'quote' : 'quote/toAmount'}?${new URLSearchParams( + `${config.apiUrl}/${isFromAmountRequest ? 'quote' : 'quote/toAmount'}?${new URLSearchParams( params as unknown as Record )}`, { @@ -154,17 +154,17 @@ export const getRoutes = async ( if (!isRoutesRequest(params)) { throw new SDKError(new ValidationError('Invalid routes request.')) } - const _config = config + // apply defaults params.options = { - integrator: _config.integrator, - ..._config.routeOptions, + integrator: config.integrator, + ...config.routeOptions, ...params.options, } return await request( config, - `${_config.apiUrl}/advanced/routes`, + `${config.apiUrl}/advanced/routes`, { method: 'POST', headers: { @@ -216,23 +216,23 @@ export const getContractCallsQuote = async ( ) ) } - const _config = config + // apply defaults // option.order is not used in this endpoint - params.integrator ??= _config.integrator - params.slippage ??= _config.routeOptions?.slippage - params.referrer ??= _config.routeOptions?.referrer - params.fee ??= _config.routeOptions?.fee - params.allowBridges ??= _config.routeOptions?.bridges?.allow - params.denyBridges ??= _config.routeOptions?.bridges?.deny - params.preferBridges ??= _config.routeOptions?.bridges?.prefer - params.allowExchanges ??= _config.routeOptions?.exchanges?.allow - params.denyExchanges ??= _config.routeOptions?.exchanges?.deny - params.preferExchanges ??= _config.routeOptions?.exchanges?.prefer + params.integrator ??= config.integrator + params.slippage ??= config.routeOptions?.slippage + params.referrer ??= config.routeOptions?.referrer + params.fee ??= config.routeOptions?.fee + params.allowBridges ??= config.routeOptions?.bridges?.allow + params.denyBridges ??= config.routeOptions?.bridges?.deny + params.preferBridges ??= config.routeOptions?.bridges?.prefer + params.allowExchanges ??= config.routeOptions?.exchanges?.allow + params.denyExchanges ??= config.routeOptions?.exchanges?.deny + params.preferExchanges ??= config.routeOptions?.exchanges?.prefer // send request return await request( config, - `${_config.apiUrl}/quote/contractCalls`, + `${config.apiUrl}/quote/contractCalls`, { method: 'POST', headers: { @@ -333,19 +333,19 @@ export const getRelayerQuote = async ( ) } } - const _config = config + // apply defaults - params.integrator ??= _config.integrator - params.order ??= _config.routeOptions?.order - params.slippage ??= _config.routeOptions?.slippage - params.referrer ??= _config.routeOptions?.referrer - params.fee ??= _config.routeOptions?.fee - params.allowBridges ??= _config.routeOptions?.bridges?.allow - params.denyBridges ??= _config.routeOptions?.bridges?.deny - params.preferBridges ??= _config.routeOptions?.bridges?.prefer - params.allowExchanges ??= _config.routeOptions?.exchanges?.allow - params.denyExchanges ??= _config.routeOptions?.exchanges?.deny - params.preferExchanges ??= _config.routeOptions?.exchanges?.prefer + params.integrator ??= config.integrator + params.order ??= config.routeOptions?.order + params.slippage ??= config.routeOptions?.slippage + params.referrer ??= config.routeOptions?.referrer + params.fee ??= config.routeOptions?.fee + params.allowBridges ??= config.routeOptions?.bridges?.allow + params.denyBridges ??= config.routeOptions?.bridges?.deny + params.preferBridges ??= config.routeOptions?.bridges?.prefer + params.allowExchanges ??= config.routeOptions?.exchanges?.allow + params.denyExchanges ??= config.routeOptions?.exchanges?.deny + params.preferExchanges ??= config.routeOptions?.exchanges?.prefer for (const key of Object.keys(params)) { if (!params[key as keyof QuoteRequest]) { diff --git a/src/services/api.unit.handlers.ts b/src/services/api.unit.handlers.ts index 5abd1976..79a6b4c7 100644 --- a/src/services/api.unit.handlers.ts +++ b/src/services/api.unit.handlers.ts @@ -4,35 +4,34 @@ import { HttpResponse, http } from 'msw' import { setupTestEnvironment } from '../../tests/setup.js' const config = await setupTestEnvironment() -const _config = config export const handlers = [ - http.post(`${_config.apiUrl}/advanced/routes`, async () => { + http.post(`${config.apiUrl}/advanced/routes`, async () => { return HttpResponse.json({}) }), - http.post(`${_config.apiUrl}/advanced/possibilities`, async () => + http.post(`${config.apiUrl}/advanced/possibilities`, async () => HttpResponse.json({}) ), - http.get(`${_config.apiUrl}/token`, async () => HttpResponse.json({})), - http.get(`${_config.apiUrl}/quote`, async () => HttpResponse.json({})), - http.get(`${_config.apiUrl}/status`, async () => HttpResponse.json({})), - http.get(`${_config.apiUrl}/chains`, async () => + http.get(`${config.apiUrl}/token`, async () => HttpResponse.json({})), + http.get(`${config.apiUrl}/quote`, async () => HttpResponse.json({})), + http.get(`${config.apiUrl}/status`, async () => HttpResponse.json({})), + http.get(`${config.apiUrl}/chains`, async () => HttpResponse.json({ chains: [{ id: 1 }] }) ), - http.get(`${_config.apiUrl}/tools`, async () => + http.get(`${config.apiUrl}/tools`, async () => HttpResponse.json({ bridges: [], exchanges: [] }) ), - http.get(`${_config.apiUrl}/tokens`, async () => + http.get(`${config.apiUrl}/tokens`, async () => HttpResponse.json({ tokens: { [ChainId.ETH]: [findDefaultToken(CoinKey.ETH, ChainId.ETH)], }, }) ), - http.post(`${_config.apiUrl}/advanced/stepTransaction`, async () => + http.post(`${config.apiUrl}/advanced/stepTransaction`, async () => HttpResponse.json({}) ), - http.get(`${_config.apiUrl}/gas/suggestion/${ChainId.OPT}`, async () => + http.get(`${config.apiUrl}/gas/suggestion/${ChainId.OPT}`, async () => HttpResponse.json({}) ), ] diff --git a/src/services/api.unit.spec.ts b/src/services/api.unit.spec.ts index 142763f2..65235ff5 100644 --- a/src/services/api.unit.spec.ts +++ b/src/services/api.unit.spec.ts @@ -34,7 +34,6 @@ const config = await setupTestEnvironment() const mockedFetch = vi.spyOn(request, 'request') describe('ApiService', () => { - const _config = config const server = setupServer(...handlers) beforeAll(() => { setupTestEnvironment() @@ -570,7 +569,7 @@ describe('ApiService', () => { describe('getAvailableConnections', () => { it('returns empty array in response', async () => { server.use( - http.get(`${_config.apiUrl}/connections`, async () => + http.get(`${config.apiUrl}/connections`, async () => HttpResponse.json({ connections: [] }) ) ) @@ -604,7 +603,7 @@ describe('ApiService', () => { describe('getTransactionHistory', () => { it('returns empty array in response', async () => { server.use( - http.get(`${_config.apiUrl}/analytics/transfers`, async () => + http.get(`${config.apiUrl}/analytics/transfers`, async () => HttpResponse.json({}) ) ) diff --git a/src/services/balance.ts b/src/services/balance.ts index 35f10861..36b1efc8 100644 --- a/src/services/balance.ts +++ b/src/services/balance.ts @@ -106,7 +106,7 @@ export async function getTokenBalancesByChain( const tokenAmountsSettled = await Promise.allSettled( Object.keys(tokensByChain).map(async (chainIdStr) => { const chainId = Number.parseInt(chainIdStr, 10) - const chain = await getChainById(config, chainId) + const chain = getChainById(config, chainId) if (provider.type === chain.chainType) { const tokenAmounts = await provider.getBalance( config, diff --git a/src/services/getNameServiceAddress.ts b/src/services/getNameServiceAddress.ts index 45706fac..4dcd0f23 100644 --- a/src/services/getNameServiceAddress.ts +++ b/src/services/getNameServiceAddress.ts @@ -9,16 +9,14 @@ export const getNameServiceAddress = async ( try { let providers = config.providers if (chainType) { - providers = providers.filter( - (provider: any) => provider.type === chainType - ) + providers = providers.filter((provider) => provider.type === chainType) } - const resolvers = providers.map((provider: any) => provider.resolveAddress) + const resolvers = providers.map((provider) => provider.resolveAddress) if (!resolvers.length) { return } const result = await Promise.any( - resolvers.map(async (resolve: any) => { + resolvers.map(async (resolve) => { const address = await resolve(name) if (!address) { throw undefined diff --git a/src/utils/getTransactionMessage.ts b/src/utils/getTransactionMessage.ts index edc581de..1773fa13 100644 --- a/src/utils/getTransactionMessage.ts +++ b/src/utils/getTransactionMessage.ts @@ -2,12 +2,12 @@ import type { LiFiStep } from '@lifi/types' import { getChainById } from '../core/configProvider.js' import type { SDKBaseConfig } from '../core/types.js' -export const getTransactionFailedMessage = async ( +export const getTransactionFailedMessage = ( config: SDKBaseConfig, step: LiFiStep, txLink?: string -): Promise => { - const chain = await getChainById(config, step.action.toChainId) +): string => { + const chain = getChainById(config, step.action.toChainId) const baseString = `It appears that your transaction may not have been successful. However, to confirm this, please check your ${chain.name} wallet for ${step.action.toToken.symbol}.` From d33806b5f8be861f3314f0614acb9af275021105 Mon Sep 17 00:00:00 2001 From: Lizaveta Miasayedava Date: Tue, 14 Oct 2025 16:32:11 +0100 Subject: [PATCH 05/13] refactor: update exports --- package.json | 2 +- src/createConfig.ts | 2 +- src/index.ts | 2 +- src/version.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 1e25052c..d2c29e49 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lifi/sdk", - "version": "3.12.14", + "version": "4.0.0", "description": "LI.FI Any-to-Any Cross-Chain-Swap SDK", "keywords": [ "bridge", diff --git a/src/createConfig.ts b/src/createConfig.ts index c045c50c..989cde58 100644 --- a/src/createConfig.ts +++ b/src/createConfig.ts @@ -43,7 +43,7 @@ function initializeConfig(options: SDKConfig) { return _config } -function createBaseConfig(options: SDKConfig) { +export function createBaseConfig(options: SDKConfig) { if (!options.integrator) { throw new Error( 'Integrator not found. Please see documentation https://docs.li.fi/integrate-li.fi-js-sdk/set-up-the-sdk' diff --git a/src/index.ts b/src/index.ts index 4e315ad2..647b408c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -85,7 +85,7 @@ export type { export type { UTXOProvider, UTXOProviderOptions } from './core/UTXO/types.js' export { isUTXO } from './core/UTXO/types.js' export { UTXO } from './core/UTXO/UTXO.js' -export { createConfig } from './createConfig.js' +export { createBaseConfig, createConfig } from './createConfig.js' export { BaseError } from './errors/baseError.js' export type { ErrorCode } from './errors/constants.js' export { ErrorMessage, ErrorName, LiFiErrorCode } from './errors/constants.js' diff --git a/src/version.ts b/src/version.ts index 44280bf7..57a76f39 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1,2 +1,2 @@ export const name = '@lifi/sdk' -export const version = '3.12.14' +export const version = '4.0.0' From 929c1517a31bd67e00ab5b5eabc2e2c1b0b70d4d Mon Sep 17 00:00:00 2001 From: Lizaveta Miasayedava Date: Mon, 20 Oct 2025 14:05:51 +0100 Subject: [PATCH 06/13] refactor: update config --- src/core/types.ts | 2 +- src/createConfig.ts | 25 ++----------------------- src/index.ts | 2 +- 3 files changed, 4 insertions(+), 25 deletions(-) diff --git a/src/core/types.ts b/src/core/types.ts index 96c186aa..15585d63 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -22,11 +22,11 @@ export interface SDKBaseConfig { userId?: string providers: SDKProvider[] routeOptions?: RouteOptions + executionOptions?: ExecutionOptions rpcUrls: RPCUrls chains: ExtendedChain[] disableVersionCheck?: boolean widgetVersion?: string - preloadChains: boolean debug: boolean } diff --git a/src/createConfig.ts b/src/createConfig.ts index 989cde58..ccb1499b 100644 --- a/src/createConfig.ts +++ b/src/createConfig.ts @@ -1,11 +1,10 @@ -import { ChainId, ChainType } from '@lifi/types' +import { ChainId } from '@lifi/types' import { getMergedProviders, getMergedRPCUrls, getMetamaskRPCUrls, } from './core/configProvider.js' import type { SDKBaseConfig, SDKConfig } from './core/types.js' -import { getChains } from './services/api.js' import { checkPackageUpdates } from './utils/checkPackageUpdates.js' import { name, version } from './version.js' @@ -16,7 +15,6 @@ function initializeConfig(options: SDKConfig) { rpcUrls: {}, chains: [], providers: [], - preloadChains: true, debug: false, } @@ -43,7 +41,7 @@ function initializeConfig(options: SDKConfig) { return _config } -export function createBaseConfig(options: SDKConfig) { +export function createConfig(options: SDKConfig) { if (!options.integrator) { throw new Error( 'Integrator not found. Please see documentation https://docs.li.fi/integrate-li.fi-js-sdk/set-up-the-sdk' @@ -55,22 +53,3 @@ export function createBaseConfig(options: SDKConfig) { } return _config } - -export async function createConfig(options: SDKConfig) { - const _config = createBaseConfig(options) - if (_config.preloadChains) { - await getChains(_config, { - chainTypes: [ChainType.EVM, ChainType.SVM, ChainType.UTXO, ChainType.MVM], - }) - .then((chains) => { - // Set chains - _config.chains = chains - const rpcUrls = getMetamaskRPCUrls(chains) - _config.rpcUrls = getMergedRPCUrls(_config.rpcUrls, rpcUrls, [ - ChainId.SOL, - ]) - }) - .catch() - } - return _config -} diff --git a/src/index.ts b/src/index.ts index 647b408c..4e315ad2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -85,7 +85,7 @@ export type { export type { UTXOProvider, UTXOProviderOptions } from './core/UTXO/types.js' export { isUTXO } from './core/UTXO/types.js' export { UTXO } from './core/UTXO/UTXO.js' -export { createBaseConfig, createConfig } from './createConfig.js' +export { createConfig } from './createConfig.js' export { BaseError } from './errors/baseError.js' export type { ErrorCode } from './errors/constants.js' export { ErrorMessage, ErrorName, LiFiErrorCode } from './errors/constants.js' From b33f6419e3ba349580246ad24f1bb29a4bd7dac1 Mon Sep 17 00:00:00 2001 From: Lizaveta Miasayedava Date: Mon, 20 Oct 2025 15:52:53 +0100 Subject: [PATCH 07/13] fix: remove chains from config to resolve circular dep --- src/core/EVM/EVMStepExecutor.ts | 4 ++-- src/core/EVM/checkPermitSupport.ts | 2 +- src/core/EVM/getActionWithFallback.ts | 2 +- src/core/EVM/getAllowance.int.spec.ts | 8 +++---- src/core/EVM/getAllowance.ts | 4 ++-- src/core/EVM/getEVMBalance.ts | 4 ++-- src/core/EVM/publicClient.ts | 8 +++---- src/core/EVM/resolveENSAddress.ts | 2 +- src/core/EVM/uns/resolveUNSAddress.ts | 4 ++-- src/core/EVM/utils.ts | 6 +++-- src/core/EVM/waitForTransactionReceipt.ts | 2 +- src/core/Solana/SolanaStepExecutor.ts | 4 ++-- src/core/Solana/connection.ts | 2 +- src/core/Sui/SuiStepExecutor.ts | 4 ++-- src/core/Sui/suiClient.ts | 2 +- src/core/UTXO/UTXOStepExecutor.ts | 4 ++-- src/core/UTXO/getUTXOPublicClient.ts | 8 +++---- src/core/configProvider.ts | 24 +++++++------------ src/core/rpc.ts | 21 +++++++++++----- src/core/types.ts | 2 -- src/createConfig.ts | 29 +---------------------- src/services/balance.ts | 2 +- src/utils/getTransactionMessage.ts | 6 ++--- tests/setup.ts | 2 +- 24 files changed, 65 insertions(+), 91 deletions(-) diff --git a/src/core/EVM/EVMStepExecutor.ts b/src/core/EVM/EVMStepExecutor.ts index 04ec69c8..9567b796 100644 --- a/src/core/EVM/EVMStepExecutor.ts +++ b/src/core/EVM/EVMStepExecutor.ts @@ -399,8 +399,8 @@ export class EVMStepExecutor extends BaseStepExecutor { } } - const fromChain = getChainById(config, step.action.fromChainId) - const toChain = getChainById(config, step.action.toChainId) + const fromChain = await getChainById(config, step.action.fromChainId) + const toChain = await getChainById(config, step.action.toChainId) // Check if the wallet supports atomic batch transactions (EIP-5792) const calls: Call[] = [] diff --git a/src/core/EVM/checkPermitSupport.ts b/src/core/EVM/checkPermitSupport.ts index 18adb957..52c27edb 100644 --- a/src/core/EVM/checkPermitSupport.ts +++ b/src/core/EVM/checkPermitSupport.ts @@ -47,7 +47,7 @@ export const checkPermitSupport = async ( let client = await provider?.getWalletClient?.() if (!client) { - client = getPublicClient(config, chain.id) + client = await getPublicClient(config, chain.id) } const nativePermit = await getActionWithFallback( diff --git a/src/core/EVM/getActionWithFallback.ts b/src/core/EVM/getActionWithFallback.ts index 11218120..121800b3 100644 --- a/src/core/EVM/getActionWithFallback.ts +++ b/src/core/EVM/getActionWithFallback.ts @@ -54,7 +54,7 @@ export const getActionWithFallback = async < throw error } - const publicClient = getPublicClient(config, chainId) + const publicClient = await getPublicClient(config, chainId) return await getAction(publicClient, actionFn, name)(params) } } diff --git a/src/core/EVM/getAllowance.int.spec.ts b/src/core/EVM/getAllowance.int.spec.ts index 67233676..75e2a82b 100644 --- a/src/core/EVM/getAllowance.int.spec.ts +++ b/src/core/EVM/getAllowance.int.spec.ts @@ -32,7 +32,7 @@ beforeAll(setupTestEnvironment) describe('allowance integration tests', { retry: retryTimes, timeout }, () => { it('should work for ERC20 on POL', async () => { - const client = getPublicClient(config, memeToken.chainId) + const client = await getPublicClient(config, memeToken.chainId) const allowance = await getAllowance( config, client, @@ -49,7 +49,7 @@ describe('allowance integration tests', { retry: retryTimes, timeout }, () => { { retry: retryTimes, timeout }, async () => { const token = findDefaultToken(CoinKey.POL, ChainId.POL) - const client = getPublicClient(config, token.chainId) + const client = await getPublicClient(config, token.chainId) const allowance = await getAllowance( config, client, @@ -68,7 +68,7 @@ describe('allowance integration tests', { retry: retryTimes, timeout }, () => { async () => { const invalidToken = findDefaultToken(CoinKey.POL, ChainId.POL) invalidToken.address = '0x2170ed0880ac9a755fd29b2688956bd959f933f8' - const client = getPublicClient(config, invalidToken.chainId) + const client = await getPublicClient(config, invalidToken.chainId) const allowance = await getAllowance( config, client, @@ -84,7 +84,7 @@ describe('allowance integration tests', { retry: retryTimes, timeout }, () => { 'should handle empty lists with multicall', { retry: retryTimes, timeout }, async () => { - const client = getPublicClient(config, 137) + const client = await getPublicClient(config, 137) const allowances = await getAllowanceMulticall( config, client, diff --git a/src/core/EVM/getAllowance.ts b/src/core/EVM/getAllowance.ts index decc8581..e290fc06 100644 --- a/src/core/EVM/getAllowance.ts +++ b/src/core/EVM/getAllowance.ts @@ -103,7 +103,7 @@ export const getTokenAllowance = async ( return } - const client = getPublicClient(config, token.chainId) + const client = await getPublicClient(config, token.chainId) const approved = await getAllowance( config, @@ -145,7 +145,7 @@ export const getTokenAllowanceMulticall = async ( const allowances = ( await Promise.all( chainKeys.map(async (chainId) => { - const client = getPublicClient(config, chainId) + const client = await getPublicClient(config, chainId) // get allowances for current chain and token list return getAllowanceMulticall( config, diff --git a/src/core/EVM/getEVMBalance.ts b/src/core/EVM/getEVMBalance.ts index 18bf5c73..4bd7a127 100644 --- a/src/core/EVM/getEVMBalance.ts +++ b/src/core/EVM/getEVMBalance.ts @@ -48,7 +48,7 @@ const getEVMBalanceMulticall = async ( walletAddress: string, multicallAddress: string ): Promise => { - const client = getPublicClient(config, chainId) + const client = await getPublicClient(config, chainId) const contracts = tokens.map((token) => { if (isZeroAddress(token.address)) { @@ -94,7 +94,7 @@ const getEVMBalanceDefault = async ( tokens: Token[], walletAddress: Address ): Promise => { - const client = getPublicClient(config, chainId) + const client = await getPublicClient(config, chainId) const queue: Promise[] = tokens.map((token) => { if (isZeroAddress(token.address)) { diff --git a/src/core/EVM/publicClient.ts b/src/core/EVM/publicClient.ts index 2dd9bbf6..e2938fa9 100644 --- a/src/core/EVM/publicClient.ts +++ b/src/core/EVM/publicClient.ts @@ -16,15 +16,15 @@ const publicClients: Record = {} * @param chainId - Id of the chain the provider is for * @returns The public client for the given chain */ -export const getPublicClient = ( +export const getPublicClient = async ( config: SDKBaseConfig, chainId: number -): Client => { +): Promise => { if (publicClients[chainId]) { return publicClients[chainId] } - const urls = getRpcUrls(config, chainId) + const urls = await getRpcUrls(config, chainId) const fallbackTransports = urls.map((url) => url.startsWith('wss') ? webSocket(url) @@ -34,7 +34,7 @@ export const getPublicClient = ( }, }) ) - const _chain = getChainById(config, chainId) + const _chain = await getChainById(config, chainId) const chain: Chain = { ..._chain, ..._chain.metamask, diff --git a/src/core/EVM/resolveENSAddress.ts b/src/core/EVM/resolveENSAddress.ts index 1d72c4b5..a5a71d47 100644 --- a/src/core/EVM/resolveENSAddress.ts +++ b/src/core/EVM/resolveENSAddress.ts @@ -8,7 +8,7 @@ export const resolveENSAddress = async ( name: string ): Promise => { try { - const client = getPublicClient(config, ChainId.ETH) + const client = await getPublicClient(config, ChainId.ETH) const address = await getEnsAddress(client, { name: normalize(name), }) diff --git a/src/core/EVM/uns/resolveUNSAddress.ts b/src/core/EVM/uns/resolveUNSAddress.ts index 0df39b6b..85f2986e 100644 --- a/src/core/EVM/uns/resolveUNSAddress.ts +++ b/src/core/EVM/uns/resolveUNSAddress.ts @@ -21,8 +21,8 @@ export const resolveUNSAddress = async ( token?: CoinKey ): Promise => { try { - const L1Client = getPublicClient(config, ChainId.ETH) - const L2Client = getPublicClient(config, ChainId.POL) + const L1Client = await getPublicClient(config, ChainId.ETH) + const L2Client = await getPublicClient(config, ChainId.POL) const nameHash = namehash(name) const keys: string[] = [] diff --git a/src/core/EVM/utils.ts b/src/core/EVM/utils.ts index 56386edc..8c0e156f 100644 --- a/src/core/EVM/utils.ts +++ b/src/core/EVM/utils.ts @@ -1,6 +1,8 @@ import type { ChainId, ExtendedChain } from '@lifi/types' +import { ChainType } from '@lifi/types' import type { Address, Chain, Client, Transaction, TypedDataDomain } from 'viem' import { getBlock } from 'viem/actions' +import { getChains } from '../../services/api.js' import { median } from '../../utils/median.js' import type { SDKBaseConfig } from '../types.js' import { getActionWithFallback } from './getActionWithFallback.js' @@ -98,8 +100,8 @@ export const getMulticallAddress = async ( config: SDKBaseConfig, chainId: ChainId ): Promise
=> { - const chains = config.chains - return chains.find((chain) => chain.id === chainId) + const chains = await getChains(config, { chainTypes: [ChainType.EVM] }) + return chains?.find((chain) => chain.id === chainId) ?.multicallAddress as Address } diff --git a/src/core/EVM/waitForTransactionReceipt.ts b/src/core/EVM/waitForTransactionReceipt.ts index 0db1d0ac..04000476 100644 --- a/src/core/EVM/waitForTransactionReceipt.ts +++ b/src/core/EVM/waitForTransactionReceipt.ts @@ -35,7 +35,7 @@ export async function waitForTransactionReceipt({ ) if (!transactionReceipt?.status) { - const publicClient = getPublicClient(config, chainId) + const publicClient = await getPublicClient(config, chainId) const result = await waitForReceipt(publicClient, txHash, onReplaced) transactionReceipt = result.transactionReceipt replacementReason = result.replacementReason diff --git a/src/core/Solana/SolanaStepExecutor.ts b/src/core/Solana/SolanaStepExecutor.ts index ca6254d7..8c345da8 100644 --- a/src/core/Solana/SolanaStepExecutor.ts +++ b/src/core/Solana/SolanaStepExecutor.ts @@ -44,8 +44,8 @@ export class SolanaStepExecutor extends BaseStepExecutor { ): Promise => { step.execution = this.statusManager.initExecutionObject(step) - const fromChain = getChainById(config, step.action.fromChainId) - const toChain = getChainById(config, step.action.toChainId) + const fromChain = await getChainById(config, step.action.fromChainId) + const toChain = await getChainById(config, step.action.toChainId) const isBridgeExecution = fromChain.id !== toChain.id const currentProcessType = isBridgeExecution ? 'CROSS_CHAIN' : 'SWAP' diff --git a/src/core/Solana/connection.ts b/src/core/Solana/connection.ts index dad939f9..7b1b7f0b 100644 --- a/src/core/Solana/connection.ts +++ b/src/core/Solana/connection.ts @@ -10,7 +10,7 @@ const connections = new Map() * @returns - Promise that resolves when connections are initialized. */ const ensureConnections = async (config: SDKBaseConfig): Promise => { - const rpcUrls = getRpcUrls(config, ChainId.SOL) + const rpcUrls = await getRpcUrls(config, ChainId.SOL) for (const rpcUrl of rpcUrls) { if (!connections.get(rpcUrl)) { const connection = new Connection(rpcUrl) diff --git a/src/core/Sui/SuiStepExecutor.ts b/src/core/Sui/SuiStepExecutor.ts index dabf4e79..d781835c 100644 --- a/src/core/Sui/SuiStepExecutor.ts +++ b/src/core/Sui/SuiStepExecutor.ts @@ -47,8 +47,8 @@ export class SuiStepExecutor extends BaseStepExecutor { ): Promise => { step.execution = this.statusManager.initExecutionObject(step) - const fromChain = getChainById(config, step.action.fromChainId) - const toChain = getChainById(config, step.action.toChainId) + const fromChain = await getChainById(config, step.action.fromChainId) + const toChain = await getChainById(config, step.action.toChainId) const isBridgeExecution = fromChain.id !== toChain.id const currentProcessType = isBridgeExecution ? 'CROSS_CHAIN' : 'SWAP' diff --git a/src/core/Sui/suiClient.ts b/src/core/Sui/suiClient.ts index d0bf40e3..9f124640 100644 --- a/src/core/Sui/suiClient.ts +++ b/src/core/Sui/suiClient.ts @@ -10,7 +10,7 @@ const clients = new Map() * @returns - Promise that resolves when clients are initialized. */ const ensureClients = async (config: SDKBaseConfig): Promise => { - const rpcUrls = getRpcUrls(config, ChainId.SUI) + const rpcUrls = await getRpcUrls(config, ChainId.SUI) for (const rpcUrl of rpcUrls) { if (!clients.get(rpcUrl)) { const client = new SuiClient({ url: rpcUrl }) diff --git a/src/core/UTXO/UTXOStepExecutor.ts b/src/core/UTXO/UTXOStepExecutor.ts index 246b42f5..2166cd33 100644 --- a/src/core/UTXO/UTXOStepExecutor.ts +++ b/src/core/UTXO/UTXOStepExecutor.ts @@ -57,8 +57,8 @@ export class UTXOStepExecutor extends BaseStepExecutor { ): Promise => { step.execution = this.statusManager.initExecutionObject(step) - const fromChain = getChainById(config, step.action.fromChainId) - const toChain = getChainById(config, step.action.toChainId) + const fromChain = await getChainById(config, step.action.fromChainId) + const toChain = await getChainById(config, step.action.toChainId) const isBridgeExecution = fromChain.id !== toChain.id const currentProcessType = isBridgeExecution ? 'CROSS_CHAIN' : 'SWAP' diff --git a/src/core/UTXO/getUTXOPublicClient.ts b/src/core/UTXO/getUTXOPublicClient.ts index 939a6fd5..6fe1883f 100644 --- a/src/core/UTXO/getUTXOPublicClient.ts +++ b/src/core/UTXO/getUTXOPublicClient.ts @@ -38,12 +38,12 @@ const publicClients: Record = {} * @param chainId - Id of the chain the provider is for * @returns The public client for the given chain */ -export const getUTXOPublicClient = ( +export const getUTXOPublicClient = async ( config: SDKBaseConfig, chainId: number -): PublicClient => { +): Promise => { if (!publicClients[chainId]) { - const urls = getRpcUrls(config, chainId) + const urls = await getRpcUrls(config, chainId) const fallbackTransports = urls.map((url) => http(url, { fetchOptions: { @@ -51,7 +51,7 @@ export const getUTXOPublicClient = ( }, }) ) - const _chain = getChainById(config, chainId) + const _chain = await getChainById(config, chainId) const chain: Chain = { ..._chain, ..._chain.metamask, diff --git a/src/core/configProvider.ts b/src/core/configProvider.ts index f1902889..a9d7685b 100644 --- a/src/core/configProvider.ts +++ b/src/core/configProvider.ts @@ -1,4 +1,6 @@ -import type { ChainId, ChainType, ExtendedChain } from '@lifi/types' +import type { ChainId, ExtendedChain } from '@lifi/types' +import { ChainType } from '@lifi/types' +import { getChains } from '../services/api.js' import type { RPCUrls, SDKBaseConfig, SDKProvider } from './types.js' export function getProvider( @@ -10,21 +12,11 @@ export function getProvider( ) } -export function getMergedProviders( - configProviders: SDKProvider[], - providers: SDKProvider[] -) { - const providerMap = new Map( - configProviders.map((provider) => [provider.type, provider]) - ) - for (const provider of providers) { - providerMap.set(provider.type, provider) - } - return Array.from(providerMap.values()) -} - -export function getChainById(config: SDKBaseConfig, chainId: ChainId) { - const chain = config.chains?.find((chain) => chain.id === chainId) +export async function getChainById(config: SDKBaseConfig, chainId: ChainId) { + const chains = await getChains(config, { + chainTypes: [ChainType.EVM, ChainType.SVM, ChainType.UTXO, ChainType.MVM], + }) + const chain = chains?.find((chain) => chain.id === chainId) if (!chain) { throw new Error(`ChainId ${chainId} not found`) } diff --git a/src/core/rpc.ts b/src/core/rpc.ts index fffbb362..2df5cdfc 100644 --- a/src/core/rpc.ts +++ b/src/core/rpc.ts @@ -1,13 +1,22 @@ -import type { ChainId } from '@lifi/types' +import { ChainId, ChainType } from '@lifi/types' +import { getChains } from '../services/api.js' +import { getMergedRPCUrls, getMetamaskRPCUrls } from './configProvider.js' import type { SDKBaseConfig } from './types.js' -export const getRpcUrls = ( +export async function getRpcUrls( config: SDKBaseConfig, chainId: ChainId -): string[] => { - const rpcUrls = config.rpcUrls[chainId] - if (!rpcUrls?.length) { +): Promise { + const chains = await getChains(config, { + chainTypes: [ChainType.EVM, ChainType.SVM, ChainType.UTXO, ChainType.MVM], + }) + const metamaskRpcUrls = getMetamaskRPCUrls(chains) + const rpcUrls = getMergedRPCUrls(config.rpcUrls, metamaskRpcUrls, [ + ChainId.SOL, + ]) + const chainRpcUrls = rpcUrls[chainId] + if (!chainRpcUrls?.length) { throw new Error(`RPC URL not found for chainId: ${chainId}`) } - return rpcUrls + return chainRpcUrls } diff --git a/src/core/types.ts b/src/core/types.ts index 15585d63..5005a83d 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -2,7 +2,6 @@ import type { ChainId, ChainType, CoinKey, - ExtendedChain, FeeCost, GasCost, LiFiStep, @@ -24,7 +23,6 @@ export interface SDKBaseConfig { routeOptions?: RouteOptions executionOptions?: ExecutionOptions rpcUrls: RPCUrls - chains: ExtendedChain[] disableVersionCheck?: boolean widgetVersion?: string debug: boolean diff --git a/src/createConfig.ts b/src/createConfig.ts index ccb1499b..2651ae3c 100644 --- a/src/createConfig.ts +++ b/src/createConfig.ts @@ -1,9 +1,3 @@ -import { ChainId } from '@lifi/types' -import { - getMergedProviders, - getMergedRPCUrls, - getMetamaskRPCUrls, -} from './core/configProvider.js' import type { SDKBaseConfig, SDKConfig } from './core/types.js' import { checkPackageUpdates } from './utils/checkPackageUpdates.js' import { name, version } from './version.js' @@ -13,31 +7,10 @@ function initializeConfig(options: SDKConfig) { integrator: 'lifi-sdk', apiUrl: 'https://li.quest/v1', rpcUrls: {}, - chains: [], providers: [], debug: false, } - - const { chains, providers, rpcUrls, ...otherOptions } = options - Object.assign(_config, otherOptions) - - // Set chains - if (chains) { - _config.chains = chains - const rpcUrls = getMetamaskRPCUrls(chains) - _config.rpcUrls = getMergedRPCUrls(_config.rpcUrls, rpcUrls, [ChainId.SOL]) - } - - // Set providers - if (providers) { - _config.providers = getMergedProviders(_config.providers, providers) - } - - // Set RPC URLs - if (rpcUrls) { - _config.rpcUrls = getMergedRPCUrls(_config.rpcUrls, rpcUrls) - } - + Object.assign(_config, options) return _config } diff --git a/src/services/balance.ts b/src/services/balance.ts index 36b1efc8..35f10861 100644 --- a/src/services/balance.ts +++ b/src/services/balance.ts @@ -106,7 +106,7 @@ export async function getTokenBalancesByChain( const tokenAmountsSettled = await Promise.allSettled( Object.keys(tokensByChain).map(async (chainIdStr) => { const chainId = Number.parseInt(chainIdStr, 10) - const chain = getChainById(config, chainId) + const chain = await getChainById(config, chainId) if (provider.type === chain.chainType) { const tokenAmounts = await provider.getBalance( config, diff --git a/src/utils/getTransactionMessage.ts b/src/utils/getTransactionMessage.ts index 1773fa13..edc581de 100644 --- a/src/utils/getTransactionMessage.ts +++ b/src/utils/getTransactionMessage.ts @@ -2,12 +2,12 @@ import type { LiFiStep } from '@lifi/types' import { getChainById } from '../core/configProvider.js' import type { SDKBaseConfig } from '../core/types.js' -export const getTransactionFailedMessage = ( +export const getTransactionFailedMessage = async ( config: SDKBaseConfig, step: LiFiStep, txLink?: string -): string => { - const chain = getChainById(config, step.action.toChainId) +): Promise => { + const chain = await getChainById(config, step.action.toChainId) const baseString = `It appears that your transaction may not have been successful. However, to confirm this, please check your ${chain.name} wallet for ${step.action.toToken.symbol}.` diff --git a/tests/setup.ts b/tests/setup.ts index 033d0686..c5a1e3ac 100644 --- a/tests/setup.ts +++ b/tests/setup.ts @@ -2,7 +2,7 @@ import { createConfig } from '../src/createConfig.js' import { EVM, Solana, Sui, UTXO } from '../src/index.js' export const setupTestEnvironment = async () => { - return await createConfig({ + return createConfig({ integrator: 'lifi-sdk', providers: [EVM(), Solana(), UTXO(), Sui()], }) From 1d357b901af5b017bf8a7a891786ce91be6420c5 Mon Sep 17 00:00:00 2001 From: Lizaveta Miasayedava Date: Mon, 20 Oct 2025 16:15:12 +0100 Subject: [PATCH 08/13] refactor: tests --- src/core/EVM/getAllowance.int.spec.ts | 12 +++++++----- src/core/EVM/getEVMBalance.int.spec.ts | 12 +++++++----- src/core/EVM/parseEVMErrors.unit.spec.ts | 5 +---- src/core/EVM/setAllowance.int.spec.ts | 12 +++++++----- src/core/Solana/getSolanaBalance.int.spec.ts | 12 +++++++----- src/core/Solana/parseSolanaError.unit.spec.ts | 5 +---- src/core/StatusManager.unit.spec.ts | 5 +---- src/core/Sui/getSuiBalance.int.spec.ts | 12 +++++++----- src/core/UTXO/getUTXOBalance.int.spec.ts | 12 +++++++----- src/core/execution.unit.handlers.ts | 6 ++++-- src/core/execution.unit.spec.ts | 6 ++++-- src/request.unit.spec.ts | 7 ++++--- src/services/api.int.spec.ts | 6 ++++-- src/services/api.unit.handlers.ts | 6 ++++-- src/services/api.unit.spec.ts | 7 ++++--- src/services/balance.unit.spec.ts | 6 ++++-- tests/setup.ts | 9 --------- 17 files changed, 73 insertions(+), 67 deletions(-) delete mode 100644 tests/setup.ts diff --git a/src/core/EVM/getAllowance.int.spec.ts b/src/core/EVM/getAllowance.int.spec.ts index 75e2a82b..c2522a69 100644 --- a/src/core/EVM/getAllowance.int.spec.ts +++ b/src/core/EVM/getAllowance.int.spec.ts @@ -1,9 +1,10 @@ import { findDefaultToken } from '@lifi/data-types' import { ChainId, CoinKey } from '@lifi/types' import type { Address } from 'viem' -import { beforeAll, describe, expect, it } from 'vitest' -import { setupTestEnvironment } from '../../../tests/setup.js' +import { describe, expect, it } from 'vitest' +import { createConfig } from '../../createConfig.js' import { getTokens } from '../../services/api.js' +import { EVM } from './EVM.js' import { getAllowance, getAllowanceMulticall, @@ -12,7 +13,10 @@ import { import { getPublicClient } from './publicClient.js' import type { TokenSpender } from './types.js' -const config = await setupTestEnvironment() +const config = createConfig({ + integrator: 'lifi-sdk', + providers: [EVM()], +}) const defaultWalletAddress = '0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0' const defaultSpenderAddress = '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE' const memeToken = { @@ -28,8 +32,6 @@ const defaultMemeAllowance = 123000000000000000000n const retryTimes = 2 const timeout = 10000 -beforeAll(setupTestEnvironment) - describe('allowance integration tests', { retry: retryTimes, timeout }, () => { it('should work for ERC20 on POL', async () => { const client = await getPublicClient(config, memeToken.chainId) diff --git a/src/core/EVM/getEVMBalance.int.spec.ts b/src/core/EVM/getEVMBalance.int.spec.ts index 7a4a6e17..f8118c11 100644 --- a/src/core/EVM/getEVMBalance.int.spec.ts +++ b/src/core/EVM/getEVMBalance.int.spec.ts @@ -2,20 +2,22 @@ import { findDefaultToken } from '@lifi/data-types' import type { StaticToken, Token } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' import type { Address } from 'viem' -import { beforeAll, describe, expect, it } from 'vitest' -import { setupTestEnvironment } from '../../../tests/setup.js' +import { describe, expect, it } from 'vitest' +import { createConfig } from '../../createConfig.js' import { getTokens } from '../../services/api.js' +import { EVM } from './EVM.js' import { getEVMBalance } from './getEVMBalance.js' -const config = await setupTestEnvironment() +const config = createConfig({ + integrator: 'lifi-sdk', + providers: [EVM()], +}) const defaultWalletAddress = '0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0' const retryTimes = 2 const timeout = 10000 -beforeAll(setupTestEnvironment) - describe('getBalances integration tests', () => { const loadAndCompareTokenAmounts = async ( walletAddress: string, diff --git a/src/core/EVM/parseEVMErrors.unit.spec.ts b/src/core/EVM/parseEVMErrors.unit.spec.ts index 81c4c21e..51029397 100644 --- a/src/core/EVM/parseEVMErrors.unit.spec.ts +++ b/src/core/EVM/parseEVMErrors.unit.spec.ts @@ -1,7 +1,6 @@ import type { LiFiStep } from '@lifi/types' -import { beforeAll, describe, expect, it, vi } from 'vitest' +import { describe, expect, it, vi } from 'vitest' import { buildStepObject } from '../../../tests/fixtures.js' -import { setupTestEnvironment } from '../../../tests/setup.js' import { BaseError } from '../../errors/baseError.js' import { ErrorMessage, @@ -14,8 +13,6 @@ import * as helpers from '../../utils/fetchTxErrorDetails.js' import type { Process } from '../types.js' import { parseEVMErrors } from './parseEVMErrors.js' -beforeAll(setupTestEnvironment) - describe('parseEVMStepErrors', () => { describe('when a SDKError is passed', async () => { it('should return the original error', async () => { diff --git a/src/core/EVM/setAllowance.int.spec.ts b/src/core/EVM/setAllowance.int.spec.ts index 66301bca..bad2cda6 100644 --- a/src/core/EVM/setAllowance.int.spec.ts +++ b/src/core/EVM/setAllowance.int.spec.ts @@ -3,12 +3,16 @@ import { createClient, http } from 'viem' import { mnemonicToAccount } from 'viem/accounts' import { waitForTransactionReceipt } from 'viem/actions' import { polygon } from 'viem/chains' -import { beforeAll, describe, expect, it } from 'vitest' -import { setupTestEnvironment } from '../../../tests/setup.js' +import { describe, expect, it } from 'vitest' +import { createConfig } from '../../createConfig.js' +import { EVM } from './EVM.js' import { revokeTokenApproval, setTokenAllowance } from './setAllowance.js' import { retryCount, retryDelay } from './utils.js' -const config = await setupTestEnvironment() +const config = createConfig({ + integrator: 'lifi-sdk', + providers: [EVM()], +}) const defaultSpenderAddress = '0x9b11bc9FAc17c058CAB6286b0c785bE6a65492EF' const testToken = { name: 'USDT', @@ -36,8 +40,6 @@ describe.skipIf(!MNEMONIC)('Approval integration tests', () => { transport: http(), }) - beforeAll(setupTestEnvironment) - it( 'should revoke allowance for ERC20 on POL', async () => { diff --git a/src/core/Solana/getSolanaBalance.int.spec.ts b/src/core/Solana/getSolanaBalance.int.spec.ts index 35d493e5..6b9d8586 100644 --- a/src/core/Solana/getSolanaBalance.int.spec.ts +++ b/src/core/Solana/getSolanaBalance.int.spec.ts @@ -1,18 +1,20 @@ import { findDefaultToken } from '@lifi/data-types' import type { StaticToken, Token } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' -import { beforeAll, describe, expect, it } from 'vitest' -import { setupTestEnvironment } from '../../../tests/setup.js' +import { describe, expect, it } from 'vitest' +import { createConfig } from '../../createConfig.js' import { getSolanaBalance } from './getSolanaBalance.js' +import { Solana } from './Solana.js' -const config = await setupTestEnvironment() +const config = createConfig({ + integrator: 'lifi-sdk', + providers: [Solana()], +}) const defaultWalletAddress = '9T655zHa6bYrTHWdy59NFqkjwoaSwfMat2yzixE1nb56' const retryTimes = 2 const timeout = 10000 -beforeAll(setupTestEnvironment) - describe.sequential('Solana token balance', async () => { const loadAndCompareTokenAmounts = async ( walletAddress: string, diff --git a/src/core/Solana/parseSolanaError.unit.spec.ts b/src/core/Solana/parseSolanaError.unit.spec.ts index 264fa51f..80f880ff 100644 --- a/src/core/Solana/parseSolanaError.unit.spec.ts +++ b/src/core/Solana/parseSolanaError.unit.spec.ts @@ -1,14 +1,11 @@ -import { beforeAll, describe, expect, it } from 'vitest' +import { describe, expect, it } from 'vitest' import { buildStepObject } from '../../../tests/fixtures.js' -import { setupTestEnvironment } from '../../../tests/setup.js' import { BaseError } from '../../errors/baseError.js' import { ErrorName, LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' import { SDKError } from '../../errors/SDKError.js' import { parseSolanaErrors } from './parseSolanaErrors.js' -beforeAll(setupTestEnvironment) - describe('parseSolanaStepError', () => { describe('when a SDKError is passed', () => { it('should return the original error', async () => { diff --git a/src/core/StatusManager.unit.spec.ts b/src/core/StatusManager.unit.spec.ts index 804ac729..01e3889a 100644 --- a/src/core/StatusManager.unit.spec.ts +++ b/src/core/StatusManager.unit.spec.ts @@ -1,12 +1,11 @@ import type { Route } from '@lifi/types' import type { Mock } from 'vitest' -import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' +import { beforeEach, describe, expect, it, vi } from 'vitest' import { buildRouteObject, buildStepObject, SOME_DATE, } from '../../tests/fixtures.js' -import { setupTestEnvironment } from '../../tests/setup.js' import { executionState } from './executionState.js' import { StatusManager } from './StatusManager.js' import type { @@ -17,8 +16,6 @@ import type { // Note: using structuredClone when passing objects to the StatusManager shall make sure that we are not facing any unknown call-by-reference-issues anymore -beforeAll(setupTestEnvironment) - describe('StatusManager', () => { let statusManager: StatusManager let updateRouteHookMock: Mock diff --git a/src/core/Sui/getSuiBalance.int.spec.ts b/src/core/Sui/getSuiBalance.int.spec.ts index 32795946..f8809b2d 100644 --- a/src/core/Sui/getSuiBalance.int.spec.ts +++ b/src/core/Sui/getSuiBalance.int.spec.ts @@ -1,11 +1,15 @@ import { findDefaultToken } from '@lifi/data-types' import type { StaticToken, Token } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' -import { beforeAll, describe, expect, it } from 'vitest' -import { setupTestEnvironment } from '../../../tests/setup.js' +import { describe, expect, it } from 'vitest' +import { createConfig } from '../../createConfig.js' import { getSuiBalance } from './getSuiBalance.js' +import { Sui } from './Sui.js' -const config = await setupTestEnvironment() +const config = createConfig({ + integrator: 'lifi-sdk', + providers: [Sui()], +}) const defaultWalletAddress = '0xd2fdd62880764fa73b895a9824ecec255a4bd9d654a125e58de33088cbf5eb67' @@ -13,8 +17,6 @@ const defaultWalletAddress = const retryTimes = 2 const timeout = 10000 -beforeAll(setupTestEnvironment) - describe.sequential('Sui token balance', async () => { const loadAndCompareTokenAmounts = async ( walletAddress: string, diff --git a/src/core/UTXO/getUTXOBalance.int.spec.ts b/src/core/UTXO/getUTXOBalance.int.spec.ts index 5778edf3..472adb0d 100644 --- a/src/core/UTXO/getUTXOBalance.int.spec.ts +++ b/src/core/UTXO/getUTXOBalance.int.spec.ts @@ -1,20 +1,22 @@ import { findDefaultToken } from '@lifi/data-types' import type { StaticToken, Token } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' -import { beforeAll, describe, expect, it } from 'vitest' -import { setupTestEnvironment } from '../../../tests/setup.js' +import { describe, expect, it } from 'vitest' +import { createConfig } from '../../createConfig.js' import type { SDKBaseConfig } from '../types.js' import { getUTXOBalance } from './getUTXOBalance.js' +import { UTXO } from './UTXO.js' -const config = await setupTestEnvironment() +const config = createConfig({ + integrator: 'lifi-sdk', + providers: [UTXO()], +}) const defaultWalletAddress = 'bc1q5hx26klsnyqqc9255vuh0s96guz79x0cc54896' const retryTimes = 2 const timeout = 10000 -beforeAll(setupTestEnvironment) - describe('getBalances integration tests', () => { const loadAndCompareTokenAmounts = async ( config: SDKBaseConfig, diff --git a/src/core/execution.unit.handlers.ts b/src/core/execution.unit.handlers.ts index 2cdd4789..ba229387 100644 --- a/src/core/execution.unit.handlers.ts +++ b/src/core/execution.unit.handlers.ts @@ -1,13 +1,15 @@ import { HttpResponse, http } from 'msw' import { buildStepObject } from '../../tests/fixtures.js' -import { setupTestEnvironment } from '../../tests/setup.js' +import { createConfig } from '../createConfig.js' import { mockChainsResponse, mockStatus, mockStepTransactionWithTxRequest, } from './execution.unit.mock.js' -const config = await setupTestEnvironment() +const config = createConfig({ + integrator: 'lifi-sdk', +}) export const lifiHandlers = [ http.post(`${config.apiUrl}/advanced/stepTransaction`, async () => diff --git a/src/core/execution.unit.spec.ts b/src/core/execution.unit.spec.ts index 1f9f2436..bf648da4 100644 --- a/src/core/execution.unit.spec.ts +++ b/src/core/execution.unit.spec.ts @@ -12,12 +12,14 @@ import { vi, } from 'vitest' import { buildRouteObject, buildStepObject } from '../../tests/fixtures.js' -import { setupTestEnvironment } from '../../tests/setup.js' +import { createConfig } from '../createConfig.js' import { requestSettings } from '../request.js' import { executeRoute } from './execution.js' import { lifiHandlers } from './execution.unit.handlers.js' -const config = await setupTestEnvironment() +const config = createConfig({ + integrator: 'lifi-sdk', +}) let client: Partial vi.mock('../balance', () => ({ diff --git a/src/request.unit.spec.ts b/src/request.unit.spec.ts index 30edf19e..5aa40fef 100644 --- a/src/request.unit.spec.ts +++ b/src/request.unit.spec.ts @@ -10,7 +10,7 @@ import { it, vi, } from 'vitest' -import { setupTestEnvironment } from '../tests/setup.js' +import { createConfig } from './createConfig.js' import { ValidationError } from './errors/errors.js' import type { HTTPError } from './errors/httpError.js' import { SDKError } from './errors/SDKError.js' @@ -19,14 +19,15 @@ import { handlers } from './services/api.unit.handlers.js' import type { ExtendedRequestInit } from './types/request.js' import { version } from './version.js' -const config = await setupTestEnvironment() +const config = createConfig({ + integrator: 'lifi-sdk', +}) const apiUrl = config.apiUrl describe('request new', () => { const server = setupServer(...handlers) beforeAll(() => { - setupTestEnvironment() server.listen({ onUnhandledRequest: 'warn', }) diff --git a/src/services/api.int.spec.ts b/src/services/api.int.spec.ts index f4dfce9a..570a65cd 100644 --- a/src/services/api.int.spec.ts +++ b/src/services/api.int.spec.ts @@ -1,8 +1,10 @@ import { describe, expect, it } from 'vitest' -import { setupTestEnvironment } from '../../tests/setup.js' +import { createConfig } from '../createConfig.js' import { getQuote } from './api.js' -const config = await setupTestEnvironment() +const config = createConfig({ + integrator: 'lifi-sdk', +}) describe('ApiService Integration Tests', () => { it('should successfully request a quote', async () => { diff --git a/src/services/api.unit.handlers.ts b/src/services/api.unit.handlers.ts index 79a6b4c7..992a932d 100644 --- a/src/services/api.unit.handlers.ts +++ b/src/services/api.unit.handlers.ts @@ -1,9 +1,11 @@ import { findDefaultToken } from '@lifi/data-types' import { ChainId, CoinKey } from '@lifi/types' import { HttpResponse, http } from 'msw' -import { setupTestEnvironment } from '../../tests/setup.js' +import { createConfig } from '../createConfig.js' -const config = await setupTestEnvironment() +const config = createConfig({ + integrator: 'lifi-sdk', +}) export const handlers = [ http.post(`${config.apiUrl}/advanced/routes`, async () => { diff --git a/src/services/api.unit.spec.ts b/src/services/api.unit.spec.ts index 65235ff5..445a7050 100644 --- a/src/services/api.unit.spec.ts +++ b/src/services/api.unit.spec.ts @@ -22,7 +22,7 @@ import { it, vi, } from 'vitest' -import { setupTestEnvironment } from '../../tests/setup.js' +import { createConfig } from '../createConfig.js' import { ValidationError } from '../errors/errors.js' import { SDKError } from '../errors/SDKError.js' import * as request from '../request.js' @@ -30,13 +30,14 @@ import { requestSettings } from '../request.js' import * as ApiService from './api.js' import { handlers } from './api.unit.handlers.js' -const config = await setupTestEnvironment() +const config = createConfig({ + integrator: 'lifi-sdk', +}) const mockedFetch = vi.spyOn(request, 'request') describe('ApiService', () => { const server = setupServer(...handlers) beforeAll(() => { - setupTestEnvironment() server.listen({ onUnhandledRequest: 'warn', }) diff --git a/src/services/balance.unit.spec.ts b/src/services/balance.unit.spec.ts index c2b5e340..163f3de8 100644 --- a/src/services/balance.unit.spec.ts +++ b/src/services/balance.unit.spec.ts @@ -2,10 +2,12 @@ import { findDefaultToken } from '@lifi/data-types' import type { Token, WalletTokenExtended } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { setupTestEnvironment } from '../../tests/setup.js' +import { createConfig } from '../createConfig.js' import * as balance from './balance.js' -const config = await setupTestEnvironment() +const config = createConfig({ + integrator: 'lifi-sdk', +}) const mockedGetTokenBalance = vi.spyOn(balance, 'getTokenBalance') const mockedGetTokenBalances = vi.spyOn(balance, 'getTokenBalances') const mockedGetTokenBalancesForChains = vi.spyOn( diff --git a/tests/setup.ts b/tests/setup.ts deleted file mode 100644 index c5a1e3ac..00000000 --- a/tests/setup.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { createConfig } from '../src/createConfig.js' -import { EVM, Solana, Sui, UTXO } from '../src/index.js' - -export const setupTestEnvironment = async () => { - return createConfig({ - integrator: 'lifi-sdk', - providers: [EVM(), Solana(), UTXO(), Sui()], - }) -} From 56ffbdb03dda4ad8d1104dc3c6aabf691ed6c220 Mon Sep 17 00:00:00 2001 From: Lizaveta Miasayedava Date: Tue, 21 Oct 2025 09:40:10 +0100 Subject: [PATCH 09/13] refactor: remove providers from config --- src/core/EVM/EVM.ts | 19 ++++-- src/core/EVM/EVMStepExecutor.ts | 41 +++++++++---- src/core/EVM/checkAllowance.ts | 8 ++- src/core/EVM/checkPermitSupport.ts | 16 +++-- src/core/EVM/getActionWithFallback.ts | 8 ++- src/core/EVM/getAllowance.int.spec.ts | 8 ++- src/core/EVM/getAllowance.ts | 15 ++++- src/core/EVM/getEVMBalance.int.spec.ts | 2 - src/core/EVM/getEVMBalance.ts | 25 ++++---- src/core/EVM/isBatchingSupported.ts | 32 ++++------ src/core/EVM/permits/getNativePermit.ts | 19 +++++- .../permits/getPermitTransferFromValues.ts | 3 + src/core/EVM/permits/signPermit2Message.ts | 3 + src/core/EVM/publicClient.ts | 16 ++--- src/core/EVM/setAllowance.int.spec.ts | 4 +- src/core/EVM/setAllowance.ts | 15 ++++- src/core/EVM/types.ts | 2 + src/core/EVM/utils.ts | 3 + src/core/Solana/Solana.ts | 1 + src/core/Solana/SolanaStepExecutor.ts | 13 ++++- src/core/Solana/getSolanaBalance.int.spec.ts | 5 +- src/core/Sui/Sui.ts | 1 + src/core/Sui/SuiStepExecutor.ts | 19 +++++- src/core/Sui/getSuiBalance.int.spec.ts | 2 - src/core/UTXO/UTXO.ts | 1 + src/core/UTXO/UTXOStepExecutor.ts | 13 ++++- src/core/UTXO/getUTXOBalance.int.spec.ts | 2 - src/core/UTXO/getUTXOPublicClient.ts | 2 +- src/core/checkBalance.ts | 6 +- src/core/configProvider.ts | 58 ------------------- src/core/execution.ts | 16 +++-- src/core/execution.unit.spec.ts | 10 +++- src/core/getChainById.ts | 15 +++++ src/core/rpc.ts | 39 ++++++++++++- src/core/types.ts | 1 - src/createConfig.ts | 1 - src/services/api.unit.spec.ts | 1 + src/services/balance.ts | 19 ++++-- src/services/balance.unit.spec.ts | 33 ++++++++--- src/services/getNameServiceAddress.ts | 6 +- src/utils/getTransactionMessage.ts | 2 +- 41 files changed, 328 insertions(+), 177 deletions(-) delete mode 100644 src/core/configProvider.ts create mode 100644 src/core/getChainById.ts diff --git a/src/core/EVM/EVM.ts b/src/core/EVM/EVM.ts index 763e0775..dfb4de80 100644 --- a/src/core/EVM/EVM.ts +++ b/src/core/EVM/EVM.ts @@ -1,6 +1,6 @@ -import { ChainType } from '@lifi/types' -import { isAddress } from 'viem' -import type { StepExecutorOptions } from '../types.js' +import { ChainType, type Token } from '@lifi/types' +import { type Address, type FallbackTransportConfig, isAddress } from 'viem' +import type { SDKBaseConfig, StepExecutorOptions } from '../types.js' import { EVMStepExecutor } from './EVMStepExecutor.js' import { getEVMBalance } from './getEVMBalance.js' import { resolveEVMAddress } from './resolveEVMAddress.js' @@ -17,7 +17,17 @@ export function EVM(options?: EVMProviderOptions): EVMProvider { }, isAddress, resolveAddress: resolveEVMAddress, - getBalance: getEVMBalance, + getBalance: ( + config: SDKBaseConfig, + walletAddress: Address, + tokens: Token[] + ) => + getEVMBalance( + config, + walletAddress, + tokens, + _options.fallbackTransportConfig as FallbackTransportConfig + ), getWalletClient: _options.getWalletClient, async getStepExecutor( options: StepExecutorOptions @@ -36,6 +46,7 @@ export function EVM(options?: EVMProviderOptions): EVMProvider { switchChainHook: _options.switchChain ?? options.executionOptions?.switchChainHook, }, + provider: this, }) return executor diff --git a/src/core/EVM/EVMStepExecutor.ts b/src/core/EVM/EVMStepExecutor.ts index 9567b796..5cb254b0 100644 --- a/src/core/EVM/EVMStepExecutor.ts +++ b/src/core/EVM/EVMStepExecutor.ts @@ -26,7 +26,7 @@ import { import { isZeroAddress } from '../../utils/isZeroAddress.js' import { BaseStepExecutor } from '../BaseStepExecutor.js' import { checkBalance } from '../checkBalance.js' -import { getChainById } from '../configProvider.js' +import { getChainById } from '../getChainById.js' import { stepComparison } from '../stepComparison.js' import type { LiFiStepExtended, @@ -50,7 +50,7 @@ import { isNativePermitValid } from './permits/isNativePermitValid.js' import { signPermit2Message } from './permits/signPermit2Message.js' import { switchChain } from './switchChain.js' import { isGaslessStep, isRelayerStep } from './typeguards.js' -import type { Call, WalletCallReceipt } from './types.js' +import type { Call, EVMProvider, WalletCallReceipt } from './types.js' import { convertExtendedChain, getDomainChainId, @@ -62,14 +62,17 @@ import { waitForTransactionReceipt } from './waitForTransactionReceipt.js' interface EVMStepExecutorOptions extends StepExecutorOptions { client: Client + provider: EVMProvider } export class EVMStepExecutor extends BaseStepExecutor { private client: Client + private provider: EVMProvider constructor(options: EVMStepExecutorOptions) { super(options) this.client = options.client + this.provider = options.provider } // Ensure that we are using the right chain and wallet when executing transactions. @@ -304,7 +307,7 @@ export class EVMStepExecutor extends BaseStepExecutor { // : undefined, maxPriorityFeePerGas: this.client.account?.type === 'local' - ? await getMaxPriorityFeePerGas(config, this.client) + ? await getMaxPriorityFeePerGas(config, this.provider, this.client) : step.transactionRequest.maxPriorityFeePerGas ? BigInt(step.transactionRequest.maxPriorityFeePerGas) : undefined, @@ -336,6 +339,7 @@ export class EVMStepExecutor extends BaseStepExecutor { private estimateTransactionRequest = async ( config: SDKBaseConfig, + provider: EVMProvider, transactionRequest: TransactionParameters, fromChain: ExtendedChain ) => { @@ -345,6 +349,7 @@ export class EVMStepExecutor extends BaseStepExecutor { // Try to re-estimate the gas due to additional Permit data const estimatedGas = await getActionWithFallback( config, + provider, this.client, estimateGas, 'estimateGas', @@ -414,8 +419,9 @@ export class EVMStepExecutor extends BaseStepExecutor { const batchingSupported = atomicityNotReady || step.tool === 'thorswap' || isRelayerStep(step) ? false - : await isBatchingSupported(config, { + : await isBatchingSupported({ client: this.client, + provider: this.provider, chainId: fromChain.id, }) @@ -460,6 +466,7 @@ export class EVMStepExecutor extends BaseStepExecutor { // Check if token needs approval and get approval transaction or message data when available const allowanceResult = await checkAllowance({ config, + provider: this.provider, checkClient: this.checkClient, chain: fromChain, step, @@ -530,7 +537,12 @@ export class EVMStepExecutor extends BaseStepExecutor { chainId: fromChain.id, }) - await checkBalance(config, this.client.account!.address, step) + await checkBalance( + config, + [this.provider], + this.client.account!.address, + step + ) // Try to prepare a new transaction request and update the step with typed data let { transactionRequest, isRelayerTransaction } = @@ -660,13 +672,17 @@ export class EVMStepExecutor extends BaseStepExecutor { process.type, 'MESSAGE_REQUIRED' ) - const permit2Signature = await signPermit2Message(config, { - client: this.client, - chain: fromChain, - tokenAddress: step.action.fromToken.address as Address, - amount: BigInt(step.action.fromAmount), - data: transactionRequest.data as Hex, - }) + const permit2Signature = await signPermit2Message( + config, + this.provider, + { + client: this.client, + chain: fromChain, + tokenAddress: step.action.fromToken.address as Address, + amount: BigInt(step.action.fromAmount), + data: transactionRequest.data as Hex, + } + ) transactionRequest.data = encodePermit2Data( step.action.fromToken.address as Address, BigInt(step.action.fromAmount), @@ -685,6 +701,7 @@ export class EVMStepExecutor extends BaseStepExecutor { if (signedNativePermitTypedData || permit2Supported) { transactionRequest = await this.estimateTransactionRequest( config, + this.provider, transactionRequest, fromChain ) diff --git a/src/core/EVM/checkAllowance.ts b/src/core/EVM/checkAllowance.ts index ac8bfa7d..03d9dd4f 100644 --- a/src/core/EVM/checkAllowance.ts +++ b/src/core/EVM/checkAllowance.ts @@ -18,12 +18,13 @@ import { getNativePermit } from './permits/getNativePermit.js' import { isNativePermitValid } from './permits/isNativePermitValid.js' import type { NativePermitData } from './permits/types.js' import { setAllowance } from './setAllowance.js' -import type { Call } from './types.js' +import type { Call, EVMProvider } from './types.js' import { getDomainChainId } from './utils.js' import { waitForTransactionReceipt } from './waitForTransactionReceipt.js' type CheckAllowanceParams = { config: SDKBaseConfig + provider: EVMProvider checkClient( step: LiFiStepExtended, process: Process, @@ -54,6 +55,7 @@ type AllowanceResult = export const checkAllowance = async ({ config, + provider, checkClient, chain, step, @@ -189,6 +191,7 @@ export const checkAllowance = async ({ const approved = await getAllowance( config, + provider, updatedClient, step.action.fromToken.address as Address, updatedClient.account!.address, @@ -209,11 +212,13 @@ export const checkAllowance = async ({ if (isNativePermitAvailable) { nativePermitData = await getActionWithFallback( config, + provider, updatedClient, getNativePermit, 'getNativePermit', { config, + provider, chainId: chain.id, tokenAddress: step.action.fromToken.address as Address, spenderAddress: chain.permit2Proxy as Address, @@ -282,6 +287,7 @@ export const checkAllowance = async ({ const approveAmount = permit2Supported ? MaxUint256 : fromAmount const approveTxHash = await setAllowance( config, + provider, updatedClient, step.action.fromToken.address as Address, spenderAddress as Address, diff --git a/src/core/EVM/checkPermitSupport.ts b/src/core/EVM/checkPermitSupport.ts index 52c27edb..61dfc877 100644 --- a/src/core/EVM/checkPermitSupport.ts +++ b/src/core/EVM/checkPermitSupport.ts @@ -1,7 +1,5 @@ import type { ExtendedChain } from '@lifi/types' -import { ChainType } from '@lifi/types' import type { Address } from 'viem' -import { getProvider } from '../configProvider.js' import type { SDKBaseConfig } from '../types.js' import { getActionWithFallback } from './getActionWithFallback.js' import { getAllowance } from './getAllowance.js' @@ -22,6 +20,8 @@ type PermitSupport = { * 1. Native permit (EIP-2612) support * 2. Permit2 availability and allowance * + * @param config - The SDK base config + * @param provider - The EVM SDK provider * @param chain - The chain to check permit support on * @param tokenAddress - The token address to check * @param ownerAddress - The address that would sign the permit @@ -30,6 +30,7 @@ type PermitSupport = { */ export const checkPermitSupport = async ( config: SDKBaseConfig, + provider: EVMProvider, { chain, tokenAddress, @@ -42,21 +43,25 @@ export const checkPermitSupport = async ( amount: bigint } ): Promise => { - const provider = getProvider(config, ChainType.EVM) as EVMProvider | undefined - let client = await provider?.getWalletClient?.() if (!client) { - client = await getPublicClient(config, chain.id) + client = await getPublicClient( + config, + chain.id, + provider?.options?.fallbackTransportConfig + ) } const nativePermit = await getActionWithFallback( config, + provider, client, getNativePermit, 'getNativePermit', { config, + provider, chainId: chain.id, tokenAddress, spenderAddress: chain.permit2Proxy as Address, @@ -69,6 +74,7 @@ export const checkPermitSupport = async ( if (chain.permit2) { permit2Allowance = await getAllowance( config, + provider, client, tokenAddress, ownerAddress, diff --git a/src/core/EVM/getActionWithFallback.ts b/src/core/EVM/getActionWithFallback.ts index 121800b3..fb539403 100644 --- a/src/core/EVM/getActionWithFallback.ts +++ b/src/core/EVM/getActionWithFallback.ts @@ -10,6 +10,7 @@ import type { import { getAction } from 'viem/utils' import type { SDKBaseConfig } from '../types.js' import { getPublicClient } from './publicClient.js' +import type { EVMProvider } from './types.js' /** * Executes an action with a fallback to public client if the wallet client fails due to rate limiting @@ -35,6 +36,7 @@ export const getActionWithFallback = async < returnType, >( config: SDKBaseConfig, + provider: EVMProvider, walletClient: client, actionFn: (_: client, parameters: parameters) => returnType, name: keyof PublicActions | keyof WalletActions | (string & {}), @@ -54,7 +56,11 @@ export const getActionWithFallback = async < throw error } - const publicClient = await getPublicClient(config, chainId) + const publicClient = await getPublicClient( + config, + chainId, + provider?.options?.fallbackTransportConfig + ) return await getAction(publicClient, actionFn, name)(params) } } diff --git a/src/core/EVM/getAllowance.int.spec.ts b/src/core/EVM/getAllowance.int.spec.ts index c2522a69..e141ab58 100644 --- a/src/core/EVM/getAllowance.int.spec.ts +++ b/src/core/EVM/getAllowance.int.spec.ts @@ -15,8 +15,9 @@ import type { TokenSpender } from './types.js' const config = createConfig({ integrator: 'lifi-sdk', - providers: [EVM()], }) +const provider = EVM() + const defaultWalletAddress = '0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0' const defaultSpenderAddress = '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE' const memeToken = { @@ -37,6 +38,7 @@ describe('allowance integration tests', { retry: retryTimes, timeout }, () => { const client = await getPublicClient(config, memeToken.chainId) const allowance = await getAllowance( config, + provider, client, memeToken.address as Address, defaultWalletAddress, @@ -54,6 +56,7 @@ describe('allowance integration tests', { retry: retryTimes, timeout }, () => { const client = await getPublicClient(config, token.chainId) const allowance = await getAllowance( config, + provider, client, token.address as Address, defaultWalletAddress, @@ -73,6 +76,7 @@ describe('allowance integration tests', { retry: retryTimes, timeout }, () => { const client = await getPublicClient(config, invalidToken.chainId) const allowance = await getAllowance( config, + provider, client, invalidToken.address as Address, defaultWalletAddress, @@ -89,6 +93,7 @@ describe('allowance integration tests', { retry: retryTimes, timeout }, () => { const client = await getPublicClient(config, 137) const allowances = await getAllowanceMulticall( config, + provider, client, 137, [], @@ -119,6 +124,7 @@ describe('allowance integration tests', { retry: retryTimes, timeout }, () => { if (tokenSpenders?.length) { const tokens = await getTokenAllowanceMulticall( config, + provider, defaultWalletAddress, tokenSpenders.slice(0, 10) ) diff --git a/src/core/EVM/getAllowance.ts b/src/core/EVM/getAllowance.ts index e290fc06..a6c1efdc 100644 --- a/src/core/EVM/getAllowance.ts +++ b/src/core/EVM/getAllowance.ts @@ -7,6 +7,7 @@ import { allowanceAbi } from './abi.js' import { getActionWithFallback } from './getActionWithFallback.js' import { getPublicClient } from './publicClient.js' import type { + EVMProvider, TokenAllowance, TokenSpender, TokenSpenderAllowance, @@ -15,6 +16,7 @@ import { getMulticallAddress } from './utils.js' export const getAllowance = async ( config: SDKBaseConfig, + provider: EVMProvider, client: Client, tokenAddress: Address, ownerAddress: Address, @@ -23,6 +25,7 @@ export const getAllowance = async ( try { const approved = await getActionWithFallback( config, + provider, client, readContract, 'readContract', @@ -41,6 +44,7 @@ export const getAllowance = async ( export const getAllowanceMulticall = async ( config: SDKBaseConfig, + provider: EVMProvider, client: Client, chainId: ChainId, tokens: TokenSpender[], @@ -63,6 +67,7 @@ export const getAllowanceMulticall = async ( const results = await getActionWithFallback( config, + provider, client, multicall, 'multicall', @@ -94,6 +99,7 @@ export const getAllowanceMulticall = async ( */ export const getTokenAllowance = async ( config: SDKBaseConfig, + provider: EVMProvider, token: BaseToken, ownerAddress: Address, spenderAddress: Address @@ -107,6 +113,7 @@ export const getTokenAllowance = async ( const approved = await getAllowance( config, + provider, client, token.address as Address, ownerAddress, @@ -123,6 +130,7 @@ export const getTokenAllowance = async ( */ export const getTokenAllowanceMulticall = async ( config: SDKBaseConfig, + provider: EVMProvider, ownerAddress: Address, tokens: TokenSpender[] ): Promise => { @@ -145,10 +153,15 @@ export const getTokenAllowanceMulticall = async ( const allowances = ( await Promise.all( chainKeys.map(async (chainId) => { - const client = await getPublicClient(config, chainId) + const client = await getPublicClient( + config, + chainId, + provider?.options?.fallbackTransportConfig + ) // get allowances for current chain and token list return getAllowanceMulticall( config, + provider, client, chainId, tokenDataByChain[chainId], diff --git a/src/core/EVM/getEVMBalance.int.spec.ts b/src/core/EVM/getEVMBalance.int.spec.ts index f8118c11..7684d8ab 100644 --- a/src/core/EVM/getEVMBalance.int.spec.ts +++ b/src/core/EVM/getEVMBalance.int.spec.ts @@ -5,12 +5,10 @@ import type { Address } from 'viem' import { describe, expect, it } from 'vitest' import { createConfig } from '../../createConfig.js' import { getTokens } from '../../services/api.js' -import { EVM } from './EVM.js' import { getEVMBalance } from './getEVMBalance.js' const config = createConfig({ integrator: 'lifi-sdk', - providers: [EVM()], }) const defaultWalletAddress = '0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0' diff --git a/src/core/EVM/getEVMBalance.ts b/src/core/EVM/getEVMBalance.ts index 4bd7a127..82ebdaa5 100644 --- a/src/core/EVM/getEVMBalance.ts +++ b/src/core/EVM/getEVMBalance.ts @@ -1,5 +1,5 @@ -import type { ChainId, Token, TokenAmount } from '@lifi/types' -import type { Address } from 'viem' +import type { Token, TokenAmount } from '@lifi/types' +import type { Address, Client, FallbackTransportConfig } from 'viem' import { getBalance, getBlockNumber, @@ -15,8 +15,9 @@ import { getMulticallAddress } from './utils.js' export const getEVMBalance = async ( config: SDKBaseConfig, walletAddress: Address, - tokens: Token[] -): Promise => { + tokens: Token[], + fallbackTransportConfig?: FallbackTransportConfig +) => { if (tokens.length === 0) { return [] } @@ -29,27 +30,24 @@ export const getEVMBalance = async ( const multicallAddress = await getMulticallAddress(config, chainId) + const client = await getPublicClient(config, chainId, fallbackTransportConfig) if (multicallAddress && tokens.length > 1) { return getEVMBalanceMulticall( - config, - chainId, + client, tokens, walletAddress, multicallAddress ) } - return getEVMBalanceDefault(config, chainId, tokens, walletAddress) + return getEVMBalanceDefault(client, tokens, walletAddress) } const getEVMBalanceMulticall = async ( - config: SDKBaseConfig, - chainId: ChainId, + client: Client, tokens: Token[], walletAddress: string, multicallAddress: string ): Promise => { - const client = await getPublicClient(config, chainId) - const contracts = tokens.map((token) => { if (isZeroAddress(token.address)) { return { @@ -89,13 +87,10 @@ const getEVMBalanceMulticall = async ( } const getEVMBalanceDefault = async ( - config: SDKBaseConfig, - chainId: ChainId, + client: Client, tokens: Token[], walletAddress: Address ): Promise => { - const client = await getPublicClient(config, chainId) - const queue: Promise[] = tokens.map((token) => { if (isZeroAddress(token.address)) { return getBalance(client, { diff --git a/src/core/EVM/isBatchingSupported.ts b/src/core/EVM/isBatchingSupported.ts index e3468755..91219161 100644 --- a/src/core/EVM/isBatchingSupported.ts +++ b/src/core/EVM/isBatchingSupported.ts @@ -1,29 +1,21 @@ -import { ChainType } from '@lifi/types' import type { Client } from 'viem' import { getCapabilities } from 'viem/actions' import { getAction } from 'viem/utils' import { sleep } from '../../utils/sleep.js' -import { getProvider } from '../configProvider.js' -import type { SDKBaseConfig } from '../types.js' import type { EVMProvider } from './types.js' -export async function isBatchingSupported( - config: SDKBaseConfig, - { - client, - chainId, - skipReady = false, - }: { - client?: Client - chainId: number - skipReady?: boolean - } -): Promise { - const _client = - client ?? - (await ( - getProvider(config, ChainType.EVM) as EVMProvider - )?.getWalletClient?.()) +export async function isBatchingSupported({ + client, + provider, + chainId, + skipReady = false, +}: { + client?: Client + provider: EVMProvider + chainId: number + skipReady?: boolean +}): Promise { + const _client = client ?? (await provider?.getWalletClient?.()) if (!_client) { throw new Error('WalletClient is not provided.') diff --git a/src/core/EVM/permits/getNativePermit.ts b/src/core/EVM/permits/getNativePermit.ts index 90c1dd6f..e5182f73 100644 --- a/src/core/EVM/permits/getNativePermit.ts +++ b/src/core/EVM/permits/getNativePermit.ts @@ -12,6 +12,7 @@ import { getCode, multicall, readContract } from 'viem/actions' import type { SDKBaseConfig } from '../../../core/types.js' import { eip2612Abi } from '../abi.js' import { getActionWithFallback } from '../getActionWithFallback.js' +import type { EVMProvider } from '../types.js' import { getMulticallAddress, isDelegationDesignatorCode } from '../utils.js' import { DAI_LIKE_PERMIT_TYPEHASH, @@ -23,6 +24,7 @@ import type { NativePermitData } from './types.js' type GetNativePermitParams = { config: SDKBaseConfig + provider: EVMProvider chainId: number tokenAddress: Address spenderAddress: Address @@ -135,11 +137,13 @@ function validateDomainSeparator({ */ const canAccountUseNativePermits = async ( config: SDKBaseConfig, + provider: EVMProvider, client: Client ): Promise => { try { const accountCode = await getActionWithFallback( config, + provider, client, getCode, 'getCode', @@ -177,6 +181,7 @@ const canAccountUseNativePermits = async ( */ const getEIP712DomainData = async ( config: SDKBaseConfig, + provider: EVMProvider, client: Client, chainId: number, tokenAddress: Address @@ -202,6 +207,7 @@ const getEIP712DomainData = async ( try { const [eip712DomainResult, noncesResult] = await getActionWithFallback( config, + provider, client, multicall, 'multicall', @@ -265,6 +271,7 @@ const getEIP712DomainData = async ( contractCalls.map((call) => getActionWithFallback( config, + provider, client, readContract, 'readContract', @@ -326,6 +333,7 @@ const getEIP712DomainData = async ( const getContractData = async ( config: SDKBaseConfig, + provider: EVMProvider, client: Client, chainId: number, tokenAddress: Address @@ -334,6 +342,7 @@ const getContractData = async ( // First try EIP-5267 approach - returns domain object directly const eip5267Data = await getEIP712DomainData( config, + provider, client, chainId, tokenAddress @@ -384,6 +393,7 @@ const getContractData = async ( versionResult, ] = await getActionWithFallback( config, + provider, client, multicall, 'multicall', @@ -440,6 +450,7 @@ const getContractData = async ( contractCalls.map((call) => getActionWithFallback( config, + provider, client, readContract, 'readContract', @@ -506,6 +517,7 @@ export const getNativePermit = async ( client: Client, { config, + provider, chainId, tokenAddress, spenderAddress, @@ -513,13 +525,18 @@ export const getNativePermit = async ( }: GetNativePermitParams ): Promise => { // Check if the account can use native permits (EOA or EIP-7702 delegated account) - const canUsePermits = await canAccountUseNativePermits(config, client) + const canUsePermits = await canAccountUseNativePermits( + config, + provider, + client + ) if (!canUsePermits) { return undefined } const contractData = await getContractData( config, + provider, client, chainId, tokenAddress diff --git a/src/core/EVM/permits/getPermitTransferFromValues.ts b/src/core/EVM/permits/getPermitTransferFromValues.ts index 5d2e57fe..41ab4dcd 100644 --- a/src/core/EVM/permits/getPermitTransferFromValues.ts +++ b/src/core/EVM/permits/getPermitTransferFromValues.ts @@ -4,10 +4,12 @@ import { readContract } from 'viem/actions' import type { SDKBaseConfig } from '../../../core/types.js' import { permit2ProxyAbi } from '../abi.js' import { getActionWithFallback } from '../getActionWithFallback.js' +import type { EVMProvider } from '../types.js' import type { PermitTransferFrom } from './signatureTransfer.js' export const getPermitTransferFromValues = async ( config: SDKBaseConfig, + provider: EVMProvider, client: Client, chain: ExtendedChain, tokenAddress: Address, @@ -15,6 +17,7 @@ export const getPermitTransferFromValues = async ( ): Promise => { const nonce = await getActionWithFallback( config, + provider, client, readContract, 'readContract', diff --git a/src/core/EVM/permits/signPermit2Message.ts b/src/core/EVM/permits/signPermit2Message.ts index abe2ff64..7eab6ed0 100644 --- a/src/core/EVM/permits/signPermit2Message.ts +++ b/src/core/EVM/permits/signPermit2Message.ts @@ -4,6 +4,7 @@ import { keccak256 } from 'viem' import { signTypedData } from 'viem/actions' import { getAction } from 'viem/utils' import type { SDKBaseConfig } from '../../../core/types.js' +import type { EVMProvider } from '../types.js' import { getPermitTransferFromValues } from './getPermitTransferFromValues.js' import { getPermitData } from './signatureTransfer.js' @@ -18,12 +19,14 @@ interface SignPermit2MessageParams { export async function signPermit2Message( config: SDKBaseConfig, + provider: EVMProvider, params: SignPermit2MessageParams ): Promise { const { client, chain, tokenAddress, amount, data, witness } = params const permitTransferFrom = await getPermitTransferFromValues( config, + provider, client, chain, tokenAddress, diff --git a/src/core/EVM/publicClient.ts b/src/core/EVM/publicClient.ts index e2938fa9..46b9f053 100644 --- a/src/core/EVM/publicClient.ts +++ b/src/core/EVM/publicClient.ts @@ -1,11 +1,10 @@ -import { ChainId, ChainType } from '@lifi/types' -import type { Client } from 'viem' +import { ChainId } from '@lifi/types' +import type { Client, FallbackTransportConfig } from 'viem' import { type Address, createClient, fallback, http, webSocket } from 'viem' import { type Chain, mainnet } from 'viem/chains' -import { getChainById, getProvider } from '../configProvider.js' +import { getChainById } from '../getChainById.js' import { getRpcUrls } from '../rpc.js' import type { SDKBaseConfig } from '../types.js' -import type { EVMProvider } from './types.js' import { UNS_PROXY_READER_ADDRESSES } from './uns/constants.js' // cached providers @@ -18,7 +17,8 @@ const publicClients: Record = {} */ export const getPublicClient = async ( config: SDKBaseConfig, - chainId: number + chainId: number, + fallbackTransportConfig?: FallbackTransportConfig ): Promise => { if (publicClients[chainId]) { return publicClients[chainId] @@ -62,13 +62,9 @@ export const getPublicClient = async ( } } - const provider = getProvider(config, ChainType.EVM) as EVMProvider | undefined publicClients[chainId] = createClient({ chain: chain, - transport: fallback( - fallbackTransports, - provider?.options?.fallbackTransportConfig - ), + transport: fallback(fallbackTransports, fallbackTransportConfig), batch: { multicall: true, }, diff --git a/src/core/EVM/setAllowance.int.spec.ts b/src/core/EVM/setAllowance.int.spec.ts index bad2cda6..a487c0a3 100644 --- a/src/core/EVM/setAllowance.int.spec.ts +++ b/src/core/EVM/setAllowance.int.spec.ts @@ -11,8 +11,8 @@ import { retryCount, retryDelay } from './utils.js' const config = createConfig({ integrator: 'lifi-sdk', - providers: [EVM()], }) +const provider = EVM() const defaultSpenderAddress = '0x9b11bc9FAc17c058CAB6286b0c785bE6a65492EF' const testToken = { name: 'USDT', @@ -45,6 +45,7 @@ describe.skipIf(!MNEMONIC)('Approval integration tests', () => { async () => { const revokeTxHash = await revokeTokenApproval({ config, + provider, walletClient: client, token: testToken, spenderAddress: defaultSpenderAddress, @@ -68,6 +69,7 @@ describe.skipIf(!MNEMONIC)('Approval integration tests', () => { async () => { const approvalTxHash = await setTokenAllowance({ config, + provider, walletClient: client, token: testToken, spenderAddress: defaultSpenderAddress, diff --git a/src/core/EVM/setAllowance.ts b/src/core/EVM/setAllowance.ts index ca1a88a7..706bf805 100644 --- a/src/core/EVM/setAllowance.ts +++ b/src/core/EVM/setAllowance.ts @@ -10,11 +10,16 @@ import type { } from '../types.js' import { approveAbi } from './abi.js' import { getAllowance } from './getAllowance.js' -import type { ApproveTokenRequest, RevokeApprovalRequest } from './types.js' +import type { + ApproveTokenRequest, + EVMProvider, + RevokeApprovalRequest, +} from './types.js' import { getMaxPriorityFeePerGas } from './utils.js' export const setAllowance = async ( config: SDKBaseConfig, + provider: EVMProvider, client: Client, tokenAddress: Address, contractAddress: Address, @@ -37,7 +42,7 @@ export const setAllowance = async ( data, maxPriorityFeePerGas: client.account?.type === 'local' - ? await getMaxPriorityFeePerGas(config, client) + ? await getMaxPriorityFeePerGas(config, provider, client) : undefined, } @@ -80,6 +85,7 @@ export const setAllowance = async ( */ export const setTokenAllowance = async ({ config, + provider, walletClient, token, spenderAddress, @@ -91,6 +97,7 @@ export const setTokenAllowance = async ({ } const approvedAmount = await getAllowance( config, + provider, walletClient, token.address as Address, walletClient.account!.address, @@ -100,6 +107,7 @@ export const setTokenAllowance = async ({ if (amount > approvedAmount) { const approveTx = await setAllowance( config, + provider, walletClient, token.address as Address, spenderAddress as Address, @@ -120,6 +128,7 @@ export const setTokenAllowance = async ({ */ export const revokeTokenApproval = async ({ config, + provider, walletClient, token, spenderAddress, @@ -130,6 +139,7 @@ export const revokeTokenApproval = async ({ } const approvedAmount = await getAllowance( config, + provider, walletClient, token.address as Address, walletClient.account!.address, @@ -138,6 +148,7 @@ export const revokeTokenApproval = async ({ if (approvedAmount > 0) { const approveTx = await setAllowance( config, + provider, walletClient, token.address as Address, spenderAddress as Address, diff --git a/src/core/EVM/types.ts b/src/core/EVM/types.ts index fcea855c..34ee4692 100644 --- a/src/core/EVM/types.ts +++ b/src/core/EVM/types.ts @@ -42,6 +42,7 @@ export type TokenSpenderAllowance = { export interface ApproveTokenRequest { config: SDKBaseConfig + provider: EVMProvider walletClient: Client token: BaseToken spenderAddress: string @@ -54,6 +55,7 @@ export interface ApproveTokenRequest { export interface RevokeApprovalRequest { config: SDKBaseConfig + provider: EVMProvider walletClient: Client token: BaseToken spenderAddress: string diff --git a/src/core/EVM/utils.ts b/src/core/EVM/utils.ts index 8c0e156f..c61102b1 100644 --- a/src/core/EVM/utils.ts +++ b/src/core/EVM/utils.ts @@ -6,6 +6,7 @@ import { getChains } from '../../services/api.js' import { median } from '../../utils/median.js' import type { SDKBaseConfig } from '../types.js' import { getActionWithFallback } from './getActionWithFallback.js' +import type { EVMProvider } from './types.js' type ChainBlockExplorer = { name: string @@ -60,10 +61,12 @@ export function isExtendedChain(chain: any): chain is ExtendedChain { export const getMaxPriorityFeePerGas = async ( config: SDKBaseConfig, + provider: EVMProvider, client: Client ): Promise => { const block = await getActionWithFallback( config, + provider, client, getBlock, 'getBlock', diff --git a/src/core/Solana/Solana.ts b/src/core/Solana/Solana.ts index 49ac9a4e..de46b003 100644 --- a/src/core/Solana/Solana.ts +++ b/src/core/Solana/Solana.ts @@ -30,6 +30,7 @@ export function Solana(options?: SolanaProviderOptions): SolanaProvider { executionOptions: { ...options.executionOptions, }, + provider: this, }) return executor diff --git a/src/core/Solana/SolanaStepExecutor.ts b/src/core/Solana/SolanaStepExecutor.ts index 8c345da8..8e91375a 100644 --- a/src/core/Solana/SolanaStepExecutor.ts +++ b/src/core/Solana/SolanaStepExecutor.ts @@ -7,25 +7,33 @@ import { getStepTransaction } from '../../services/api.js' import { base64ToUint8Array } from '../../utils/base64ToUint8Array.js' import { BaseStepExecutor } from '../BaseStepExecutor.js' import { checkBalance } from '../checkBalance.js' -import { getChainById } from '../configProvider.js' +import { getChainById } from '../getChainById.js' import { stepComparison } from '../stepComparison.js' import type { LiFiStepExtended, SDKBaseConfig, + StepExecutorOptions, TransactionParameters, } from '../types.js' import { waitForDestinationChainTransaction } from '../waitForDestinationChainTransaction.js' import { callSolanaWithRetry } from './connection.js' import { parseSolanaErrors } from './parseSolanaErrors.js' import { sendAndConfirmTransaction } from './sendAndConfirmTransaction.js' -import type { SolanaStepExecutorOptions } from './types.js' +import type { SolanaProvider } from './types.js' + +interface SolanaStepExecutorOptions extends StepExecutorOptions { + walletAdapter: SignerWalletAdapter + provider: SolanaProvider +} export class SolanaStepExecutor extends BaseStepExecutor { private walletAdapter: SignerWalletAdapter + private provider: SolanaProvider constructor(options: SolanaStepExecutorOptions) { super(options) this.walletAdapter = options.walletAdapter + this.provider = options.provider } checkWalletAdapter = (step: LiFiStepExtended) => { @@ -67,6 +75,7 @@ export class SolanaStepExecutor extends BaseStepExecutor { // Check balance await checkBalance( config, + [this.provider], this.walletAdapter.publicKey!.toString(), step ) diff --git a/src/core/Solana/getSolanaBalance.int.spec.ts b/src/core/Solana/getSolanaBalance.int.spec.ts index 6b9d8586..4be3d34d 100644 --- a/src/core/Solana/getSolanaBalance.int.spec.ts +++ b/src/core/Solana/getSolanaBalance.int.spec.ts @@ -4,12 +4,11 @@ import { ChainId, CoinKey } from '@lifi/types' import { describe, expect, it } from 'vitest' import { createConfig } from '../../createConfig.js' import { getSolanaBalance } from './getSolanaBalance.js' -import { Solana } from './Solana.js' const config = createConfig({ integrator: 'lifi-sdk', - providers: [Solana()], }) + const defaultWalletAddress = '9T655zHa6bYrTHWdy59NFqkjwoaSwfMat2yzixE1nb56' const retryTimes = 2 @@ -105,7 +104,7 @@ describe.sequential('Solana token balance', async () => { // console.log(quote) - // await executeRoute(config, convertQuoteToRoute(quote), { + // await executeRoute(config, providers, convertQuoteToRoute(quote), { // updateRouteHook: (route) => { // console.log(route.steps?.[0].execution) // }, diff --git a/src/core/Sui/Sui.ts b/src/core/Sui/Sui.ts index b416010f..b4379f3e 100644 --- a/src/core/Sui/Sui.ts +++ b/src/core/Sui/Sui.ts @@ -30,6 +30,7 @@ export function Sui(options?: SuiProviderOptions): SuiProvider { executionOptions: { ...options.executionOptions, }, + provider: this, }) return executor diff --git a/src/core/Sui/SuiStepExecutor.ts b/src/core/Sui/SuiStepExecutor.ts index d781835c..d627ebd4 100644 --- a/src/core/Sui/SuiStepExecutor.ts +++ b/src/core/Sui/SuiStepExecutor.ts @@ -7,24 +7,32 @@ import { TransactionError } from '../../errors/errors.js' import { getStepTransaction } from '../../services/api.js' import { BaseStepExecutor } from '../BaseStepExecutor.js' import { checkBalance } from '../checkBalance.js' -import { getChainById } from '../configProvider.js' +import { getChainById } from '../getChainById.js' import { stepComparison } from '../stepComparison.js' import type { LiFiStepExtended, SDKBaseConfig, + StepExecutorOptions, TransactionParameters, } from '../types.js' import { waitForDestinationChainTransaction } from '../waitForDestinationChainTransaction.js' import { parseSuiErrors } from './parseSuiErrors.js' import { callSuiWithRetry } from './suiClient.js' -import type { SuiStepExecutorOptions } from './types.js' +import type { SuiProvider } from './types.js' + +interface SuiStepExecutorOptions extends StepExecutorOptions { + wallet: WalletWithRequiredFeatures + provider: SuiProvider +} export class SuiStepExecutor extends BaseStepExecutor { private wallet: WalletWithRequiredFeatures + private provider: SuiProvider constructor(options: SuiStepExecutorOptions) { super(options) this.wallet = options.wallet + this.provider = options.provider } checkWallet = (step: LiFiStepExtended) => { @@ -68,7 +76,12 @@ export class SuiStepExecutor extends BaseStepExecutor { ) // Check balance - await checkBalance(config, step.action.fromAddress!, step) + await checkBalance( + config, + [this.provider], + step.action.fromAddress!, + step + ) // Create new transaction if (!step.transactionRequest) { diff --git a/src/core/Sui/getSuiBalance.int.spec.ts b/src/core/Sui/getSuiBalance.int.spec.ts index f8809b2d..eacdc8b1 100644 --- a/src/core/Sui/getSuiBalance.int.spec.ts +++ b/src/core/Sui/getSuiBalance.int.spec.ts @@ -4,11 +4,9 @@ import { ChainId, CoinKey } from '@lifi/types' import { describe, expect, it } from 'vitest' import { createConfig } from '../../createConfig.js' import { getSuiBalance } from './getSuiBalance.js' -import { Sui } from './Sui.js' const config = createConfig({ integrator: 'lifi-sdk', - providers: [Sui()], }) const defaultWalletAddress = diff --git a/src/core/UTXO/UTXO.ts b/src/core/UTXO/UTXO.ts index 3174ecde..a3baee54 100644 --- a/src/core/UTXO/UTXO.ts +++ b/src/core/UTXO/UTXO.ts @@ -30,6 +30,7 @@ export function UTXO(options?: UTXOProviderOptions): UTXOProvider { executionOptions: { ...options.executionOptions, }, + provider: this, }) return executor diff --git a/src/core/UTXO/UTXOStepExecutor.ts b/src/core/UTXO/UTXOStepExecutor.ts index 2166cd33..0ec0433d 100644 --- a/src/core/UTXO/UTXOStepExecutor.ts +++ b/src/core/UTXO/UTXOStepExecutor.ts @@ -15,7 +15,7 @@ import { TransactionError } from '../../errors/errors.js' import { getStepTransaction } from '../../services/api.js' import { BaseStepExecutor } from '../BaseStepExecutor.js' import { checkBalance } from '../checkBalance.js' -import { getChainById } from '../configProvider.js' +import { getChainById } from '../getChainById.js' import { stepComparison } from '../stepComparison.js' import type { LiFiStepExtended, @@ -26,18 +26,22 @@ import type { import { waitForDestinationChainTransaction } from '../waitForDestinationChainTransaction.js' import { getUTXOPublicClient } from './getUTXOPublicClient.js' import { parseUTXOErrors } from './parseUTXOErrors.js' +import type { UTXOProvider } from './types.js' import { generateRedeemScript, isPsbtFinalized, toXOnly } from './utils.js' interface UTXOStepExecutorOptions extends StepExecutorOptions { client: Client + provider: UTXOProvider } export class UTXOStepExecutor extends BaseStepExecutor { private client: Client + private provider: UTXOProvider constructor(options: UTXOStepExecutorOptions) { super(options) this.client = options.client + this.provider = options.provider } checkClient = (step: LiFiStepExtended) => { @@ -90,7 +94,12 @@ export class UTXOStepExecutor extends BaseStepExecutor { ) // Check balance - await checkBalance(config, this.client.account!.address, step) + await checkBalance( + config, + [this.provider], + this.client.account!.address, + step + ) // Create new transaction if (!step.transactionRequest) { diff --git a/src/core/UTXO/getUTXOBalance.int.spec.ts b/src/core/UTXO/getUTXOBalance.int.spec.ts index 472adb0d..20866c6a 100644 --- a/src/core/UTXO/getUTXOBalance.int.spec.ts +++ b/src/core/UTXO/getUTXOBalance.int.spec.ts @@ -5,11 +5,9 @@ import { describe, expect, it } from 'vitest' import { createConfig } from '../../createConfig.js' import type { SDKBaseConfig } from '../types.js' import { getUTXOBalance } from './getUTXOBalance.js' -import { UTXO } from './UTXO.js' const config = createConfig({ integrator: 'lifi-sdk', - providers: [UTXO()], }) const defaultWalletAddress = 'bc1q5hx26klsnyqqc9255vuh0s96guz79x0cc54896' diff --git a/src/core/UTXO/getUTXOPublicClient.ts b/src/core/UTXO/getUTXOPublicClient.ts index 6fe1883f..e1b81ca1 100644 --- a/src/core/UTXO/getUTXOPublicClient.ts +++ b/src/core/UTXO/getUTXOPublicClient.ts @@ -17,7 +17,7 @@ import { type WalletActions, walletActions, } from '@bigmi/core' -import { getChainById } from '../configProvider.js' +import { getChainById } from '../getChainById.js' import { getRpcUrls } from '../rpc.js' import type { SDKBaseConfig } from '../types.js' import { toBigmiChainId } from './utils.js' diff --git a/src/core/checkBalance.ts b/src/core/checkBalance.ts index 81586599..b55ef36c 100644 --- a/src/core/checkBalance.ts +++ b/src/core/checkBalance.ts @@ -3,16 +3,18 @@ import { formatUnits } from 'viem' import { BalanceError } from '../errors/errors.js' import { getTokenBalance } from '../services/balance.js' import { sleep } from '../utils/sleep.js' -import type { SDKBaseConfig } from './types.js' +import type { SDKBaseConfig, SDKProvider } from './types.js' export const checkBalance = async ( config: SDKBaseConfig, + providers: SDKProvider[], walletAddress: string, step: LiFiStep, depth = 0 ): Promise => { const token = await getTokenBalance( config, + providers, walletAddress, step.action.fromToken ) @@ -23,7 +25,7 @@ export const checkBalance = async ( if (currentBalance < neededBalance) { if (depth <= 3) { await sleep(200) - await checkBalance(config, walletAddress, step, depth + 1) + await checkBalance(config, providers, walletAddress, step, depth + 1) } else if ( (neededBalance * BigInt((1 - (step.action.slippage ?? 0)) * 1_000_000_000)) / diff --git a/src/core/configProvider.ts b/src/core/configProvider.ts deleted file mode 100644 index a9d7685b..00000000 --- a/src/core/configProvider.ts +++ /dev/null @@ -1,58 +0,0 @@ -import type { ChainId, ExtendedChain } from '@lifi/types' -import { ChainType } from '@lifi/types' -import { getChains } from '../services/api.js' -import type { RPCUrls, SDKBaseConfig, SDKProvider } from './types.js' - -export function getProvider( - config: SDKBaseConfig, - type: ChainType -): SDKProvider | undefined { - return config.providers.find( - (provider: SDKProvider) => provider.type === type - ) -} - -export async function getChainById(config: SDKBaseConfig, chainId: ChainId) { - const chains = await getChains(config, { - chainTypes: [ChainType.EVM, ChainType.SVM, ChainType.UTXO, ChainType.MVM], - }) - const chain = chains?.find((chain) => chain.id === chainId) - if (!chain) { - throw new Error(`ChainId ${chainId} not found`) - } - return chain -} - -export function getMergedRPCUrls( - configRPCUrls: RPCUrls, - rpcUrls: RPCUrls, - skipChains?: ChainId[] -) { - const newRPCUrls = { ...configRPCUrls } - for (const rpcUrlsKey in rpcUrls) { - const chainId = Number(rpcUrlsKey) as ChainId - const urls = rpcUrls[chainId] - if (!urls?.length) { - continue - } - if (!newRPCUrls[chainId]?.length) { - newRPCUrls[chainId] = Array.from(urls) - } else if (!skipChains?.includes(chainId)) { - const filteredUrls = urls.filter( - (url) => !newRPCUrls[chainId]?.includes(url) - ) - newRPCUrls[chainId].push(...filteredUrls) - } - } - - return newRPCUrls -} - -export function getMetamaskRPCUrls(chains: ExtendedChain[]) { - return chains.reduce((rpcUrls, chain) => { - if (chain.metamask?.rpcUrls?.length) { - rpcUrls[chain.id as ChainId] = chain.metamask.rpcUrls - } - return rpcUrls - }, {} as RPCUrls) -} diff --git a/src/core/execution.ts b/src/core/execution.ts index 0b1bfb2d..79a62fc9 100644 --- a/src/core/execution.ts +++ b/src/core/execution.ts @@ -3,7 +3,12 @@ import { LiFiErrorCode } from '../errors/constants.js' import { ProviderError } from '../errors/errors.js' import { executionState } from './executionState.js' import { prepareRestart } from './prepareRestart.js' -import type { ExecutionOptions, RouteExtended, SDKBaseConfig } from './types.js' +import type { + ExecutionOptions, + RouteExtended, + SDKBaseConfig, + SDKProvider, +} from './types.js' /** * Execute a route. @@ -14,6 +19,7 @@ import type { ExecutionOptions, RouteExtended, SDKBaseConfig } from './types.js' */ export const executeRoute = async ( config: SDKBaseConfig, + providers: SDKProvider[], route: Route, executionOptions?: ExecutionOptions ): Promise => { @@ -27,7 +33,7 @@ export const executeRoute = async ( } executionState.create({ route: clonedRoute, executionOptions }) - executionPromise = executeSteps(config, clonedRoute) + executionPromise = executeSteps(config, providers, clonedRoute) executionState.update({ route: clonedRoute, promise: executionPromise, @@ -45,6 +51,7 @@ export const executeRoute = async ( */ export const resumeRoute = async ( config: SDKBaseConfig, + providers: SDKProvider[], route: Route, executionOptions?: ExecutionOptions ): Promise => { @@ -69,11 +76,12 @@ export const resumeRoute = async ( prepareRestart(route) - return executeRoute(config, route, executionOptions) + return executeRoute(config, providers, route, executionOptions) } const executeSteps = async ( config: SDKBaseConfig, + providers: SDKProvider[], route: RouteExtended ): Promise => { // Loop over steps and execute them @@ -107,7 +115,7 @@ const executeSteps = async ( throw new Error('Action fromAddress is not specified.') } - const provider = config.providers.find((provider) => + const provider = providers.find((provider) => provider.isAddress(fromAddress) ) diff --git a/src/core/execution.unit.spec.ts b/src/core/execution.unit.spec.ts index bf648da4..3cd7e4c8 100644 --- a/src/core/execution.unit.spec.ts +++ b/src/core/execution.unit.spec.ts @@ -14,12 +14,20 @@ import { import { buildRouteObject, buildStepObject } from '../../tests/fixtures.js' import { createConfig } from '../createConfig.js' import { requestSettings } from '../request.js' +import { EVM } from './EVM/EVM.js' import { executeRoute } from './execution.js' import { lifiHandlers } from './execution.unit.handlers.js' +import { Solana } from './Solana/Solana.js' +import { Sui } from './Sui/Sui.js' +import type { SDKProvider } from './types.js' +import { UTXO } from './UTXO/UTXO.js' const config = createConfig({ integrator: 'lifi-sdk', }) + +const providers: SDKProvider[] = [EVM(), UTXO(), Solana(), Sui()] + let client: Partial vi.mock('../balance', () => ({ @@ -65,7 +73,7 @@ describe.skip('Should pick up gas from wallet client estimation', () => { step, }) - await executeRoute(config, route) + await executeRoute(config, providers, route) expect(sendTransaction).toHaveBeenCalledWith(client, { gasLimit: 125000n, diff --git a/src/core/getChainById.ts b/src/core/getChainById.ts new file mode 100644 index 00000000..33dd9b23 --- /dev/null +++ b/src/core/getChainById.ts @@ -0,0 +1,15 @@ +import type { ChainId } from '@lifi/types' +import { ChainType } from '@lifi/types' +import { getChains } from '../services/api.js' +import type { SDKBaseConfig } from './types.js' + +export async function getChainById(config: SDKBaseConfig, chainId: ChainId) { + const chains = await getChains(config, { + chainTypes: [ChainType.EVM, ChainType.SVM, ChainType.UTXO, ChainType.MVM], + }) + const chain = chains?.find((chain) => chain.id === chainId) + if (!chain) { + throw new Error(`ChainId ${chainId} not found`) + } + return chain +} diff --git a/src/core/rpc.ts b/src/core/rpc.ts index 2df5cdfc..f66bed03 100644 --- a/src/core/rpc.ts +++ b/src/core/rpc.ts @@ -1,7 +1,6 @@ -import { ChainId, ChainType } from '@lifi/types' +import { ChainId, ChainType, type ExtendedChain } from '@lifi/types' import { getChains } from '../services/api.js' -import { getMergedRPCUrls, getMetamaskRPCUrls } from './configProvider.js' -import type { SDKBaseConfig } from './types.js' +import type { RPCUrls, SDKBaseConfig } from './types.js' export async function getRpcUrls( config: SDKBaseConfig, @@ -20,3 +19,37 @@ export async function getRpcUrls( } return chainRpcUrls } + +function getMergedRPCUrls( + configRPCUrls: RPCUrls, + rpcUrls: RPCUrls, + skipChains?: ChainId[] +) { + const newRPCUrls = { ...configRPCUrls } + for (const rpcUrlsKey in rpcUrls) { + const chainId = Number(rpcUrlsKey) as ChainId + const urls = rpcUrls[chainId] + if (!urls?.length) { + continue + } + if (!newRPCUrls[chainId]?.length) { + newRPCUrls[chainId] = Array.from(urls) + } else if (!skipChains?.includes(chainId)) { + const filteredUrls = urls.filter( + (url) => !newRPCUrls[chainId]?.includes(url) + ) + newRPCUrls[chainId].push(...filteredUrls) + } + } + + return newRPCUrls +} + +function getMetamaskRPCUrls(chains: ExtendedChain[]) { + return chains.reduce((rpcUrls, chain) => { + if (chain.metamask?.rpcUrls?.length) { + rpcUrls[chain.id as ChainId] = chain.metamask.rpcUrls + } + return rpcUrls + }, {} as RPCUrls) +} diff --git a/src/core/types.ts b/src/core/types.ts index 5005a83d..066af137 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -19,7 +19,6 @@ export interface SDKBaseConfig { apiUrl: string integrator: string userId?: string - providers: SDKProvider[] routeOptions?: RouteOptions executionOptions?: ExecutionOptions rpcUrls: RPCUrls diff --git a/src/createConfig.ts b/src/createConfig.ts index 2651ae3c..bb22ab00 100644 --- a/src/createConfig.ts +++ b/src/createConfig.ts @@ -7,7 +7,6 @@ function initializeConfig(options: SDKConfig) { integrator: 'lifi-sdk', apiUrl: 'https://li.quest/v1', rpcUrls: {}, - providers: [], debug: false, } Object.assign(_config, options) diff --git a/src/services/api.unit.spec.ts b/src/services/api.unit.spec.ts index 445a7050..8ae3fa25 100644 --- a/src/services/api.unit.spec.ts +++ b/src/services/api.unit.spec.ts @@ -33,6 +33,7 @@ import { handlers } from './api.unit.handlers.js' const config = createConfig({ integrator: 'lifi-sdk', }) + const mockedFetch = vi.spyOn(request, 'request') describe('ApiService', () => { diff --git a/src/services/balance.ts b/src/services/balance.ts index 35f10861..29330379 100644 --- a/src/services/balance.ts +++ b/src/services/balance.ts @@ -7,8 +7,8 @@ import type { TokenExtended, WalletTokenExtended, } from '@lifi/types' -import { getChainById } from '../core/configProvider.js' -import type { SDKBaseConfig } from '../core/types.js' +import { getChainById } from '../core/getChainById.js' +import type { SDKBaseConfig, SDKProvider } from '../core/types.js' import { ValidationError } from '../errors/errors.js' import { request } from '../request.js' import { isToken } from '../typeguards.js' @@ -22,10 +22,16 @@ import { isToken } from '../typeguards.js' */ export const getTokenBalance = async ( config: SDKBaseConfig, + providers: SDKProvider[], walletAddress: string, token: Token ): Promise => { - const tokenAmounts = await getTokenBalances(config, walletAddress, [token]) + const tokenAmounts = await getTokenBalances( + config, + providers, + walletAddress, + [token] + ) return tokenAmounts.length ? tokenAmounts[0] : null } @@ -38,11 +44,13 @@ export const getTokenBalance = async ( */ export async function getTokenBalances( config: SDKBaseConfig, + providers: SDKProvider[], walletAddress: string, tokens: Token[] ): Promise export async function getTokenBalances( config: SDKBaseConfig, + providers: SDKProvider[], walletAddress: string, tokens: TokenExtended[] ): Promise { @@ -60,6 +68,7 @@ export async function getTokenBalances( const tokenAmountsByChain = await getTokenBalancesByChain( config, + providers, walletAddress, tokensByChain ) @@ -75,11 +84,13 @@ export async function getTokenBalances( */ export async function getTokenBalancesByChain( config: SDKBaseConfig, + providers: SDKProvider[], walletAddress: string, tokensByChain: { [chainId: number]: Token[] } ): Promise<{ [chainId: number]: TokenAmount[] }> export async function getTokenBalancesByChain( config: SDKBaseConfig, + providers: SDKProvider[], walletAddress: string, tokensByChain: { [chainId: number]: TokenExtended[] } ): Promise<{ [chainId: number]: TokenAmountExtended[] }> { @@ -93,7 +104,7 @@ export async function getTokenBalancesByChain( throw new ValidationError('Invalid tokens passed.') } - const provider = config.providers.find((provider) => + const provider = providers.find((provider) => provider.isAddress(walletAddress) ) if (!provider) { diff --git a/src/services/balance.unit.spec.ts b/src/services/balance.unit.spec.ts index 163f3de8..dd3e5437 100644 --- a/src/services/balance.unit.spec.ts +++ b/src/services/balance.unit.spec.ts @@ -2,12 +2,18 @@ import { findDefaultToken } from '@lifi/data-types' import type { Token, WalletTokenExtended } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { EVM } from '../core/EVM/EVM.js' +import { Solana } from '../core/Solana/Solana.js' +import { Sui } from '../core/Sui/Sui.js' +import type { SDKProvider } from '../core/types.js' +import { UTXO } from '../core/UTXO/UTXO.js' import { createConfig } from '../createConfig.js' import * as balance from './balance.js' const config = createConfig({ integrator: 'lifi-sdk', }) +const providers: SDKProvider[] = [EVM(), UTXO(), Solana(), Sui()] const mockedGetTokenBalance = vi.spyOn(balance, 'getTokenBalance') const mockedGetTokenBalances = vi.spyOn(balance, 'getTokenBalances') const mockedGetTokenBalancesForChains = vi.spyOn( @@ -30,13 +36,13 @@ describe('Balance service tests', () => { describe('user input is invalid', () => { it('should throw Error because of missing walletAddress', async () => { await expect( - balance.getTokenBalance(config, '', SOME_TOKEN) + balance.getTokenBalance(config, providers, '', SOME_TOKEN) ).rejects.toThrow('Missing walletAddress.') }) it('should throw Error because of invalid token', async () => { await expect( - balance.getTokenBalance(config, SOME_WALLET_ADDRESS, { + balance.getTokenBalance(config, providers, SOME_WALLET_ADDRESS, { address: 'some wrong stuff', chainId: 'not a chain Id', } as unknown as Token) @@ -56,6 +62,7 @@ describe('Balance service tests', () => { const result = await balance.getTokenBalance( config, + providers, SOME_WALLET_ADDRESS, SOME_TOKEN ) @@ -70,13 +77,13 @@ describe('Balance service tests', () => { describe('user input is invalid', () => { it('should throw Error because of missing walletAddress', async () => { await expect( - balance.getTokenBalances(config, '', [SOME_TOKEN]) + balance.getTokenBalances(config, providers, '', [SOME_TOKEN]) ).rejects.toThrow('Missing walletAddress.') }) it('should throw Error because of an invalid token', async () => { await expect( - balance.getTokenBalances(config, SOME_WALLET_ADDRESS, [ + balance.getTokenBalances(config, providers, SOME_WALLET_ADDRESS, [ SOME_TOKEN, { not: 'a token' } as unknown as Token, ]) @@ -87,6 +94,7 @@ describe('Balance service tests', () => { mockedGetTokenBalances.mockReturnValue(Promise.resolve([])) const result = await balance.getTokenBalances( config, + providers, SOME_WALLET_ADDRESS, [] ) @@ -109,6 +117,7 @@ describe('Balance service tests', () => { const result = await balance.getTokenBalances( config, + providers, SOME_WALLET_ADDRESS, [SOME_TOKEN] ) @@ -123,7 +132,7 @@ describe('Balance service tests', () => { describe('user input is invalid', () => { it('should throw Error because of missing walletAddress', async () => { await expect( - balance.getTokenBalancesByChain(config, '', { + balance.getTokenBalancesByChain(config, providers, '', { [ChainId.DAI]: [SOME_TOKEN], }) ).rejects.toThrow('Missing walletAddress.') @@ -131,9 +140,14 @@ describe('Balance service tests', () => { it('should throw Error because of an invalid token', async () => { await expect( - balance.getTokenBalancesByChain(config, SOME_WALLET_ADDRESS, { - [ChainId.DAI]: [{ not: 'a token' } as unknown as Token], - }) + balance.getTokenBalancesByChain( + config, + providers, + SOME_WALLET_ADDRESS, + { + [ChainId.DAI]: [{ not: 'a token' } as unknown as Token], + } + ) ).rejects.toThrow('Invalid tokens passed.') }) @@ -142,6 +156,7 @@ describe('Balance service tests', () => { const result = await balance.getTokenBalancesByChain( config, + providers, SOME_WALLET_ADDRESS, { [ChainId.DAI]: [], @@ -171,6 +186,7 @@ describe('Balance service tests', () => { const result = await balance.getTokenBalancesByChain( config, + providers, SOME_WALLET_ADDRESS, { [ChainId.DAI]: [SOME_TOKEN], @@ -194,6 +210,7 @@ describe('Balance service tests', () => { const result = await balance.getTokenBalancesByChain( config, + providers, SOME_WALLET_ADDRESS, { [ChainId.DAI]: [SOME_TOKEN], diff --git a/src/services/getNameServiceAddress.ts b/src/services/getNameServiceAddress.ts index 4dcd0f23..595752f0 100644 --- a/src/services/getNameServiceAddress.ts +++ b/src/services/getNameServiceAddress.ts @@ -1,13 +1,13 @@ import type { ChainType } from '@lifi/types' -import type { SDKBaseConfig } from '../core/types.js' +import type { SDKProvider } from '../core/types.js' export const getNameServiceAddress = async ( - config: SDKBaseConfig, + allProviders: SDKProvider[], name: string, chainType?: ChainType ): Promise => { try { - let providers = config.providers + let providers = [...allProviders] if (chainType) { providers = providers.filter((provider) => provider.type === chainType) } diff --git a/src/utils/getTransactionMessage.ts b/src/utils/getTransactionMessage.ts index edc581de..13a45f78 100644 --- a/src/utils/getTransactionMessage.ts +++ b/src/utils/getTransactionMessage.ts @@ -1,5 +1,5 @@ import type { LiFiStep } from '@lifi/types' -import { getChainById } from '../core/configProvider.js' +import { getChainById } from '../core/getChainById.js' import type { SDKBaseConfig } from '../core/types.js' export const getTransactionFailedMessage = async ( From ae6ff82fe12d1d2580fb4b916f7d796cf3dbb980 Mon Sep 17 00:00:00 2001 From: Lizaveta Miasayedava Date: Wed, 22 Oct 2025 10:12:54 +0100 Subject: [PATCH 10/13] feat: sdk client --- package.json | 2 +- src/client/createClient.ts | 84 ++++++++++++++++++ src/client/getChainById.ts | 12 +++ src/client/getChainsFromCache.ts | 18 ++++ src/client/getRpcUrls.ts | 17 ++++ src/core/BaseStepExecutor.ts | 4 +- src/core/EVM/EVM.ts | 19 +---- src/core/EVM/EVMStepExecutor.ts | 85 +++++++------------ src/core/EVM/checkAllowance.ts | 75 +++++++--------- src/core/EVM/checkPermitSupport.ts | 44 ++++------ src/core/EVM/getActionWithFallback.ts | 12 +-- src/core/EVM/getAllowance.int.spec.ts | 31 +++---- src/core/EVM/getAllowance.ts | 42 +++------ src/core/EVM/getEVMBalance.int.spec.ts | 12 +-- src/core/EVM/getEVMBalance.ts | 17 ++-- src/core/EVM/isBatchingSupported.ts | 31 ++++--- src/core/EVM/permits/getNativePermit.ts | 65 ++++++-------- .../permits/getPermitTransferFromValues.ts | 13 ++- src/core/EVM/permits/signPermit2Message.ts | 22 +++-- src/core/EVM/publicClient.ts | 26 +++--- src/core/EVM/resolveENSAddress.ts | 8 +- src/core/EVM/resolveEVMAddress.ts | 8 +- src/core/EVM/setAllowance.int.spec.ts | 49 ++++++----- src/core/EVM/setAllowance.ts | 56 +++++------- src/core/EVM/types.ts | 6 +- src/core/EVM/uns/resolveUNSAddress.ts | 8 +- src/core/EVM/utils.ts | 11 +-- src/core/EVM/waitForTransactionReceipt.ts | 23 ++--- src/core/Solana/Solana.ts | 1 - src/core/Solana/SolanaStepExecutor.ts | 25 +++--- src/core/Solana/connection.ts | 16 ++-- src/core/Solana/getSolanaBalance.int.spec.ts | 10 ++- src/core/Solana/getSolanaBalance.ts | 16 ++-- src/core/Solana/sendAndConfirmTransaction.ts | 6 +- src/core/Sui/Sui.ts | 1 - src/core/Sui/SuiStepExecutor.ts | 27 ++---- src/core/Sui/getSuiBalance.int.spec.ts | 8 +- src/core/Sui/getSuiBalance.ts | 12 +-- src/core/Sui/suiClient.ts | 12 +-- src/core/UTXO/UTXO.ts | 1 - src/core/UTXO/UTXOStepExecutor.ts | 30 +++---- src/core/UTXO/getUTXOBalance.int.spec.ts | 14 +-- src/core/UTXO/getUTXOBalance.ts | 10 +-- src/core/UTXO/getUTXOPublicClient.ts | 16 ++-- src/core/checkBalance.ts | 10 +-- src/core/execution.ts | 19 ++--- src/core/execution.unit.handlers.ts | 10 +-- src/core/execution.unit.spec.ts | 18 ++-- src/core/getChainById.ts | 15 ---- src/core/rpc.ts | 55 ------------ src/core/types.ts | 21 ++++- .../waitForDestinationChainTransaction.ts | 8 +- src/createConfig.ts | 27 ------ src/index.ts | 3 +- src/request.unit.spec.ts | 7 +- src/services/api.int.spec.ts | 6 +- src/services/api.unit.handlers.ts | 5 +- src/services/api.unit.spec.ts | 5 +- src/services/balance.ts | 42 ++++----- src/services/balance.unit.spec.ts | 57 +++++-------- src/services/getNameServiceAddress.ts | 8 +- src/utils/getTransactionMessage.ts | 8 +- src/version.ts | 2 +- 63 files changed, 616 insertions(+), 715 deletions(-) create mode 100644 src/client/createClient.ts create mode 100644 src/client/getChainById.ts create mode 100644 src/client/getChainsFromCache.ts create mode 100644 src/client/getRpcUrls.ts delete mode 100644 src/core/getChainById.ts delete mode 100644 src/core/rpc.ts delete mode 100644 src/createConfig.ts diff --git a/package.json b/package.json index d2c29e49..1e25052c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lifi/sdk", - "version": "4.0.0", + "version": "3.12.14", "description": "LI.FI Any-to-Any Cross-Chain-Swap SDK", "keywords": [ "bridge", diff --git a/src/client/createClient.ts b/src/client/createClient.ts new file mode 100644 index 00000000..f495351a --- /dev/null +++ b/src/client/createClient.ts @@ -0,0 +1,84 @@ +import { ChainId, type ChainType, type ExtendedChain } from '@lifi/types' +import type { + RPCUrls, + SDKBaseConfig, + SDKClient, + SDKConfig, + SDKProvider, +} from '../core/types.js' +import { checkPackageUpdates } from '../utils/checkPackageUpdates.js' +import { name, version } from '../version.js' + +export function createClient(options: SDKConfig): SDKClient { + if (!options.integrator) { + throw new Error( + 'Integrator not found. Please see documentation https://docs.li.fi/integrate-li.fi-js-sdk/set-up-the-sdk' + ) + } + + if (!options.disableVersionCheck && process.env.NODE_ENV === 'development') { + checkPackageUpdates(name, version) + } + + const config: SDKBaseConfig = { + ...options, + apiUrl: options?.apiUrl ?? 'https://li.quest/v1', + rpcUrls: options?.rpcUrls ?? {}, + debug: options?.debug ?? false, + integrator: options?.integrator ?? 'lifi-sdk', + } + + let _providers: SDKProvider[] = [] + let _chains: ExtendedChain[] = [] + const _rpcUrls: RPCUrls = { ...config.rpcUrls } + + return { + config, + providers: _providers, + getProvider(type: ChainType) { + return _providers.find((provider) => provider.type === type) + }, + setProviders(newProviders: SDKProvider[]) { + const providerMap = new Map( + _providers.map((provider) => [provider.type, provider]) + ) + for (const provider of newProviders) { + providerMap.set(provider.type, provider) + } + _providers = Array.from(providerMap.values()) + }, + // Config cache + _storage: { + chainsUpdatedAt: undefined, + chains: _chains, + rpcUrls: _rpcUrls, + setChains(chains: ExtendedChain[]) { + const rpcUrls = chains.reduce((rpcUrls, chain) => { + if (chain.metamask?.rpcUrls?.length) { + _rpcUrls[chain.id as ChainId] = chain.metamask.rpcUrls + } + return rpcUrls + }, {} as RPCUrls) + this.setRPCUrls(rpcUrls, [ChainId.SOL]) + _chains = chains + }, + setRPCUrls(rpcUrls: RPCUrls, skipChains?: ChainId[]) { + for (const rpcUrlsKey in rpcUrls) { + const chainId = Number(rpcUrlsKey) as ChainId + const urls = rpcUrls[chainId] + if (!urls?.length) { + continue + } + if (!_rpcUrls[chainId]?.length) { + _rpcUrls[chainId] = Array.from(urls) + } else if (!skipChains?.includes(chainId)) { + const filteredUrls = urls.filter( + (url) => !_rpcUrls[chainId]?.includes(url) + ) + _rpcUrls[chainId].push(...filteredUrls) + } + } + }, + }, + } as SDKClient +} diff --git a/src/client/getChainById.ts b/src/client/getChainById.ts new file mode 100644 index 00000000..c5e47ad9 --- /dev/null +++ b/src/client/getChainById.ts @@ -0,0 +1,12 @@ +import type { ChainId } from '@lifi/types' +import type { SDKClient } from '../core/types.js' +import { getChainsFromCache } from './getChainsFromCache.js' + +export async function getChainById(client: SDKClient, chainId: ChainId) { + const chains = await getChainsFromCache(client) + const chain = chains?.find((chain) => chain.id === chainId) + if (!chain) { + throw new Error(`ChainId ${chainId} not found`) + } + return chain +} diff --git a/src/client/getChainsFromCache.ts b/src/client/getChainsFromCache.ts new file mode 100644 index 00000000..dc54df98 --- /dev/null +++ b/src/client/getChainsFromCache.ts @@ -0,0 +1,18 @@ +import { ChainType } from '@lifi/types' +import type { SDKClient } from '../core/types.js' +import { getChains } from '../services/api.js' + +export async function getChainsFromCache(client: SDKClient) { + let chains = client._storage?.chains + // Update chains in config cache every 24 hours + if ( + !client._storage.chainsUpdatedAt || + Date.now() - client._storage.chainsUpdatedAt >= 1000 * 60 * 60 * 24 + ) { + chains = await getChains(client.config, { + chainTypes: [ChainType.EVM, ChainType.SVM, ChainType.UTXO, ChainType.MVM], + }) + client._storage.setChains(chains) + } + return chains +} diff --git a/src/client/getRpcUrls.ts b/src/client/getRpcUrls.ts new file mode 100644 index 00000000..423d14ce --- /dev/null +++ b/src/client/getRpcUrls.ts @@ -0,0 +1,17 @@ +import type { ChainId } from '@lifi/types' +import type { SDKClient } from '../core/types.js' +import { getChainsFromCache } from './getChainsFromCache.js' + +export async function getRpcUrls( + client: SDKClient, + chainId: ChainId +): Promise { + // Make sure chains are up to date so we can get fresh RPC URLs + await getChainsFromCache(client) + + const chainRpcUrls = client._storage.rpcUrls[chainId] + if (!chainRpcUrls?.length) { + throw new Error(`RPC URL not found for chainId: ${chainId}`) + } + return chainRpcUrls +} diff --git a/src/core/BaseStepExecutor.ts b/src/core/BaseStepExecutor.ts index df28c09a..72074867 100644 --- a/src/core/BaseStepExecutor.ts +++ b/src/core/BaseStepExecutor.ts @@ -3,7 +3,7 @@ import { StatusManager } from './StatusManager.js' import type { ExecutionOptions, InteractionSettings, - SDKBaseConfig, + SDKClient, StepExecutor, StepExecutorOptions, } from './types.js' @@ -37,5 +37,5 @@ export abstract class BaseStepExecutor implements StepExecutor { this.allowExecution = interactionSettings.allowExecution } - abstract executeStep(config: SDKBaseConfig, step: LiFiStep): Promise + abstract executeStep(client: SDKClient, step: LiFiStep): Promise } diff --git a/src/core/EVM/EVM.ts b/src/core/EVM/EVM.ts index dfb4de80..763e0775 100644 --- a/src/core/EVM/EVM.ts +++ b/src/core/EVM/EVM.ts @@ -1,6 +1,6 @@ -import { ChainType, type Token } from '@lifi/types' -import { type Address, type FallbackTransportConfig, isAddress } from 'viem' -import type { SDKBaseConfig, StepExecutorOptions } from '../types.js' +import { ChainType } from '@lifi/types' +import { isAddress } from 'viem' +import type { StepExecutorOptions } from '../types.js' import { EVMStepExecutor } from './EVMStepExecutor.js' import { getEVMBalance } from './getEVMBalance.js' import { resolveEVMAddress } from './resolveEVMAddress.js' @@ -17,17 +17,7 @@ export function EVM(options?: EVMProviderOptions): EVMProvider { }, isAddress, resolveAddress: resolveEVMAddress, - getBalance: ( - config: SDKBaseConfig, - walletAddress: Address, - tokens: Token[] - ) => - getEVMBalance( - config, - walletAddress, - tokens, - _options.fallbackTransportConfig as FallbackTransportConfig - ), + getBalance: getEVMBalance, getWalletClient: _options.getWalletClient, async getStepExecutor( options: StepExecutorOptions @@ -46,7 +36,6 @@ export function EVM(options?: EVMProviderOptions): EVMProvider { switchChainHook: _options.switchChain ?? options.executionOptions?.switchChainHook, }, - provider: this, }) return executor diff --git a/src/core/EVM/EVMStepExecutor.ts b/src/core/EVM/EVMStepExecutor.ts index 5cb254b0..8968313f 100644 --- a/src/core/EVM/EVMStepExecutor.ts +++ b/src/core/EVM/EVMStepExecutor.ts @@ -16,6 +16,7 @@ import { signTypedData, } from 'viem/actions' import { getAction, isHex } from 'viem/utils' +import { getChainById } from '../../client/getChainById.js' import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' import { @@ -26,12 +27,11 @@ import { import { isZeroAddress } from '../../utils/isZeroAddress.js' import { BaseStepExecutor } from '../BaseStepExecutor.js' import { checkBalance } from '../checkBalance.js' -import { getChainById } from '../getChainById.js' import { stepComparison } from '../stepComparison.js' import type { LiFiStepExtended, Process, - SDKBaseConfig, + SDKClient, StepExecutorOptions, TransactionMethodType, TransactionParameters, @@ -50,7 +50,7 @@ import { isNativePermitValid } from './permits/isNativePermitValid.js' import { signPermit2Message } from './permits/signPermit2Message.js' import { switchChain } from './switchChain.js' import { isGaslessStep, isRelayerStep } from './typeguards.js' -import type { Call, EVMProvider, WalletCallReceipt } from './types.js' +import type { Call, WalletCallReceipt } from './types.js' import { convertExtendedChain, getDomainChainId, @@ -62,17 +62,14 @@ import { waitForTransactionReceipt } from './waitForTransactionReceipt.js' interface EVMStepExecutorOptions extends StepExecutorOptions { client: Client - provider: EVMProvider } export class EVMStepExecutor extends BaseStepExecutor { private client: Client - private provider: EVMProvider constructor(options: EVMStepExecutorOptions) { super(options) this.client = options.client - this.provider = options.provider } // Ensure that we are using the right chain and wallet when executing transactions. @@ -129,7 +126,7 @@ export class EVMStepExecutor extends BaseStepExecutor { } waitForTransaction = async ( - config: SDKBaseConfig, + client: SDKClient, { step, process, @@ -194,14 +191,13 @@ export class EVMStepExecutor extends BaseStepExecutor { break case 'relayed': transactionReceipt = await waitForRelayedTransactionReceipt( - config, + client.config, process.taskId as Hash, step ) break default: - transactionReceipt = await waitForTransactionReceipt({ - config, + transactionReceipt = await waitForTransactionReceipt(client, { client: this.client, chainId: fromChain.id, txHash: process.txHash as Hash, @@ -221,7 +217,7 @@ export class EVMStepExecutor extends BaseStepExecutor { } await waitForDestinationChainTransaction( - config, + client, step, process, fromChain, @@ -231,7 +227,7 @@ export class EVMStepExecutor extends BaseStepExecutor { } private prepareUpdatedStep = async ( - config: SDKBaseConfig, + client: SDKClient, step: LiFiStepExtended, signedTypedData?: SignedTypedData[] ) => { @@ -241,7 +237,7 @@ export class EVMStepExecutor extends BaseStepExecutor { const gaslessStep = isGaslessStep(step) let updatedStep: LiFiStep if (relayerStep && gaslessStep) { - const updatedRelayedStep = await getRelayerQuote(config, { + const updatedRelayedStep = await getRelayerQuote(client.config, { fromChain: stepBase.action.fromChainId, fromToken: stepBase.action.fromToken.address, fromAddress: stepBase.action.fromAddress!, @@ -264,7 +260,7 @@ export class EVMStepExecutor extends BaseStepExecutor { const params = filteredSignedTypedData?.length ? { ...restStepBase, typedData: filteredSignedTypedData } : restStepBase - updatedStep = await getStepTransaction(config, params) + updatedStep = await getStepTransaction(client.config, params) } const comparedStep = await stepComparison( @@ -307,7 +303,7 @@ export class EVMStepExecutor extends BaseStepExecutor { // : undefined, maxPriorityFeePerGas: this.client.account?.type === 'local' - ? await getMaxPriorityFeePerGas(config, this.provider, this.client) + ? await getMaxPriorityFeePerGas(client, this.client) : step.transactionRequest.maxPriorityFeePerGas ? BigInt(step.transactionRequest.maxPriorityFeePerGas) : undefined, @@ -338,8 +334,7 @@ export class EVMStepExecutor extends BaseStepExecutor { } private estimateTransactionRequest = async ( - config: SDKBaseConfig, - provider: EVMProvider, + client: SDKClient, transactionRequest: TransactionParameters, fromChain: ExtendedChain ) => { @@ -348,8 +343,7 @@ export class EVMStepExecutor extends BaseStepExecutor { try { // Try to re-estimate the gas due to additional Permit data const estimatedGas = await getActionWithFallback( - config, - provider, + client, this.client, estimateGas, 'estimateGas', @@ -375,7 +369,7 @@ export class EVMStepExecutor extends BaseStepExecutor { } executeStep = async ( - config: SDKBaseConfig, + client: SDKClient, step: LiFiStepExtended, // Explicitly set to true if the wallet rejected the upgrade to 7702 account, based on the EIP-5792 capabilities atomicityNotReady = false @@ -404,8 +398,8 @@ export class EVMStepExecutor extends BaseStepExecutor { } } - const fromChain = await getChainById(config, step.action.fromChainId) - const toChain = await getChainById(config, step.action.toChainId) + const fromChain = await getChainById(client, step.action.fromChainId) + const toChain = await getChainById(client, step.action.toChainId) // Check if the wallet supports atomic batch transactions (EIP-5792) const calls: Call[] = [] @@ -419,9 +413,8 @@ export class EVMStepExecutor extends BaseStepExecutor { const batchingSupported = atomicityNotReady || step.tool === 'thorswap' || isRelayerStep(step) ? false - : await isBatchingSupported({ + : await isBatchingSupported(client, { client: this.client, - provider: this.provider, chainId: fromChain.id, }) @@ -464,9 +457,7 @@ export class EVMStepExecutor extends BaseStepExecutor { if (checkForAllowance) { // Check if token needs approval and get approval transaction or message data when available - const allowanceResult = await checkAllowance({ - config, - provider: this.provider, + const allowanceResult = await checkAllowance(client, { checkClient: this.checkClient, chain: fromChain, step, @@ -501,7 +492,7 @@ export class EVMStepExecutor extends BaseStepExecutor { try { if (process?.status === 'DONE') { await waitForDestinationChainTransaction( - config, + client, step, process, fromChain, @@ -519,7 +510,7 @@ export class EVMStepExecutor extends BaseStepExecutor { return step } - await this.waitForTransaction(config, { + await this.waitForTransaction(client, { step, process, fromChain, @@ -537,16 +528,11 @@ export class EVMStepExecutor extends BaseStepExecutor { chainId: fromChain.id, }) - await checkBalance( - config, - [this.provider], - this.client.account!.address, - step - ) + await checkBalance(client, this.client.account!.address, step) // Try to prepare a new transaction request and update the step with typed data let { transactionRequest, isRelayerTransaction } = - await this.prepareUpdatedStep(config, step, signedTypedData) + await this.prepareUpdatedStep(client, step, signedTypedData) // Make sure that the chain is still correct const updatedClient = await this.checkClient(step, process) @@ -639,7 +625,7 @@ export class EVMStepExecutor extends BaseStepExecutor { // biome-ignore lint/correctness/noUnusedVariables: destructuring const { execution, ...stepBase } = step - const relayedTransaction = await relayTransaction(config, { + const relayedTransaction = await relayTransaction(client.config, { ...stepBase, typedData: signedTypedData, }) @@ -672,17 +658,13 @@ export class EVMStepExecutor extends BaseStepExecutor { process.type, 'MESSAGE_REQUIRED' ) - const permit2Signature = await signPermit2Message( - config, - this.provider, - { - client: this.client, - chain: fromChain, - tokenAddress: step.action.fromToken.address as Address, - amount: BigInt(step.action.fromAmount), - data: transactionRequest.data as Hex, - } - ) + const permit2Signature = await signPermit2Message(client, { + client: this.client, + chain: fromChain, + tokenAddress: step.action.fromToken.address as Address, + amount: BigInt(step.action.fromAmount), + data: transactionRequest.data as Hex, + }) transactionRequest.data = encodePermit2Data( step.action.fromToken.address as Address, BigInt(step.action.fromAmount), @@ -700,8 +682,7 @@ export class EVMStepExecutor extends BaseStepExecutor { if (signedNativePermitTypedData || permit2Supported) { transactionRequest = await this.estimateTransactionRequest( - config, - this.provider, + client, transactionRequest, fromChain ) @@ -740,7 +721,7 @@ export class EVMStepExecutor extends BaseStepExecutor { } ) - await this.waitForTransaction(config, { + await this.waitForTransaction(client, { step, process, fromChain, @@ -754,7 +735,7 @@ export class EVMStepExecutor extends BaseStepExecutor { // If the wallet rejected the upgrade to 7702 account, we need to try again with the standard flow if (isAtomicReadyWalletRejectedUpgradeError(e) && !atomicityNotReady) { step.execution = undefined - return this.executeStep(config, step, true) + return this.executeStep(client, step, true) } const error = await parseEVMErrors(e, step, process) process = this.statusManager.updateProcess( diff --git a/src/core/EVM/checkAllowance.ts b/src/core/EVM/checkAllowance.ts index 03d9dd4f..5f647620 100644 --- a/src/core/EVM/checkAllowance.ts +++ b/src/core/EVM/checkAllowance.ts @@ -9,22 +9,19 @@ import type { LiFiStepExtended, Process, ProcessType, - SDKBaseConfig, + SDKClient, } from '../types.js' -import { getActionWithFallback } from './getActionWithFallback.js' import { getAllowance } from './getAllowance.js' import { parseEVMErrors } from './parseEVMErrors.js' import { getNativePermit } from './permits/getNativePermit.js' import { isNativePermitValid } from './permits/isNativePermitValid.js' import type { NativePermitData } from './permits/types.js' import { setAllowance } from './setAllowance.js' -import type { Call, EVMProvider } from './types.js' +import type { Call } from './types.js' import { getDomainChainId } from './utils.js' import { waitForTransactionReceipt } from './waitForTransactionReceipt.js' type CheckAllowanceParams = { - config: SDKBaseConfig - provider: EVMProvider checkClient( step: LiFiStepExtended, process: Process, @@ -53,19 +50,20 @@ type AllowanceResult = data: SignedTypedData[] } -export const checkAllowance = async ({ - config, - provider, - checkClient, - chain, - step, - statusManager, - executionOptions, - allowUserInteraction = false, - batchingSupported = false, - permit2Supported = false, - disableMessageSigning = false, -}: CheckAllowanceParams): Promise => { +export const checkAllowance = async ( + client: SDKClient, + { + checkClient, + chain, + step, + statusManager, + executionOptions, + allowUserInteraction = false, + batchingSupported = false, + permit2Supported = false, + disableMessageSigning = false, + }: CheckAllowanceParams +): Promise => { let sharedProcess: Process | undefined let signedTypedData: SignedTypedData[] = [] try { @@ -169,7 +167,7 @@ export const checkAllowance = async ({ // Handle existing pending transaction if (sharedProcess.txHash && sharedProcess.status !== 'DONE') { await waitForApprovalTransaction( - config, + client, updatedClient, sharedProcess.txHash as Address, sharedProcess.type, @@ -190,8 +188,7 @@ export const checkAllowance = async ({ const fromAmount = BigInt(step.action.fromAmount) const approved = await getAllowance( - config, - provider, + client, updatedClient, step.action.fromToken.address as Address, updatedClient.account!.address, @@ -210,21 +207,13 @@ export const checkAllowance = async ({ let nativePermitData: NativePermitData | undefined if (isNativePermitAvailable) { - nativePermitData = await getActionWithFallback( - config, - provider, - updatedClient, - getNativePermit, - 'getNativePermit', - { - config, - provider, - chainId: chain.id, - tokenAddress: step.action.fromToken.address as Address, - spenderAddress: chain.permit2Proxy as Address, - amount: fromAmount, - } - ) + nativePermitData = await getNativePermit(client, { + client: updatedClient, + chainId: chain.id, + tokenAddress: step.action.fromToken.address as Address, + spenderAddress: chain.permit2Proxy as Address, + amount: fromAmount, + }) } if (isNativePermitAvailable && nativePermitData) { @@ -286,8 +275,7 @@ export const checkAllowance = async ({ // Set new allowance const approveAmount = permit2Supported ? MaxUint256 : fromAmount const approveTxHash = await setAllowance( - config, - provider, + client, updatedClient, step.action.fromToken.address as Address, spenderAddress as Address, @@ -316,7 +304,7 @@ export const checkAllowance = async ({ } await waitForApprovalTransaction( - config, + client, updatedClient, approveTxHash, sharedProcess.type, @@ -347,8 +335,8 @@ export const checkAllowance = async ({ } const waitForApprovalTransaction = async ( - config: SDKBaseConfig, - client: Client, + client: SDKClient, + viemClient: Client, txHash: Hash, processType: ProcessType, step: LiFiStep, @@ -363,9 +351,8 @@ const waitForApprovalTransaction = async ( txLink: getTxLink(txHash), }) - const transactionReceipt = await waitForTransactionReceipt({ - config, - client, + const transactionReceipt = await waitForTransactionReceipt(client, { + client: viemClient, chainId: chain.id, txHash, onReplaced(response) { diff --git a/src/core/EVM/checkPermitSupport.ts b/src/core/EVM/checkPermitSupport.ts index 61dfc877..c81da408 100644 --- a/src/core/EVM/checkPermitSupport.ts +++ b/src/core/EVM/checkPermitSupport.ts @@ -1,7 +1,6 @@ -import type { ExtendedChain } from '@lifi/types' +import { ChainType, type ExtendedChain } from '@lifi/types' import type { Address } from 'viem' -import type { SDKBaseConfig } from '../types.js' -import { getActionWithFallback } from './getActionWithFallback.js' +import type { SDKClient } from '../types.js' import { getAllowance } from './getAllowance.js' import { getNativePermit } from './permits/getNativePermit.js' import { getPublicClient } from './publicClient.js' @@ -29,8 +28,7 @@ type PermitSupport = { * @returns Object indicating which permit types are supported */ export const checkPermitSupport = async ( - config: SDKBaseConfig, - provider: EVMProvider, + client: SDKClient, { chain, tokenAddress, @@ -43,39 +41,27 @@ export const checkPermitSupport = async ( amount: bigint } ): Promise => { - let client = await provider?.getWalletClient?.() + const provider = client.getProvider(ChainType.EVM) as EVMProvider | undefined + let viemClient = await provider?.getWalletClient?.() - if (!client) { - client = await getPublicClient( - config, - chain.id, - provider?.options?.fallbackTransportConfig - ) + if (!viemClient) { + viemClient = await getPublicClient(client, chain.id) } - const nativePermit = await getActionWithFallback( - config, - provider, - client, - getNativePermit, - 'getNativePermit', - { - config, - provider, - chainId: chain.id, - tokenAddress, - spenderAddress: chain.permit2Proxy as Address, - amount, - } - ) + const nativePermit = await getNativePermit(client, { + client: viemClient, + chainId: chain.id, + tokenAddress, + spenderAddress: chain.permit2Proxy as Address, + amount, + }) let permit2Allowance: bigint | undefined // Check Permit2 allowance if available on chain if (chain.permit2) { permit2Allowance = await getAllowance( - config, - provider, client, + viemClient, tokenAddress, ownerAddress, chain.permit2 as Address diff --git a/src/core/EVM/getActionWithFallback.ts b/src/core/EVM/getActionWithFallback.ts index fb539403..3f37d973 100644 --- a/src/core/EVM/getActionWithFallback.ts +++ b/src/core/EVM/getActionWithFallback.ts @@ -8,9 +8,8 @@ import type { WalletActions, } from 'viem' import { getAction } from 'viem/utils' -import type { SDKBaseConfig } from '../types.js' +import type { SDKClient } from '../types.js' import { getPublicClient } from './publicClient.js' -import type { EVMProvider } from './types.js' /** * Executes an action with a fallback to public client if the wallet client fails due to rate limiting @@ -35,8 +34,7 @@ export const getActionWithFallback = async < parameters, returnType, >( - config: SDKBaseConfig, - provider: EVMProvider, + client: SDKClient, walletClient: client, actionFn: (_: client, parameters: parameters) => returnType, name: keyof PublicActions | keyof WalletActions | (string & {}), @@ -56,11 +54,7 @@ export const getActionWithFallback = async < throw error } - const publicClient = await getPublicClient( - config, - chainId, - provider?.options?.fallbackTransportConfig - ) + const publicClient = await getPublicClient(client, chainId) return await getAction(publicClient, actionFn, name)(params) } } diff --git a/src/core/EVM/getAllowance.int.spec.ts b/src/core/EVM/getAllowance.int.spec.ts index e141ab58..48a396e1 100644 --- a/src/core/EVM/getAllowance.int.spec.ts +++ b/src/core/EVM/getAllowance.int.spec.ts @@ -2,7 +2,7 @@ import { findDefaultToken } from '@lifi/data-types' import { ChainId, CoinKey } from '@lifi/types' import type { Address } from 'viem' import { describe, expect, it } from 'vitest' -import { createConfig } from '../../createConfig.js' +import { createClient } from '../../client/createClient.js' import { getTokens } from '../../services/api.js' import { EVM } from './EVM.js' import { @@ -13,10 +13,10 @@ import { import { getPublicClient } from './publicClient.js' import type { TokenSpender } from './types.js' -const config = createConfig({ +const client = createClient({ integrator: 'lifi-sdk', }) -const provider = EVM() +client.setProviders([EVM()]) const defaultWalletAddress = '0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0' const defaultSpenderAddress = '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE' @@ -35,11 +35,10 @@ const timeout = 10000 describe('allowance integration tests', { retry: retryTimes, timeout }, () => { it('should work for ERC20 on POL', async () => { - const client = await getPublicClient(config, memeToken.chainId) + const viemClient = await getPublicClient(client, memeToken.chainId) const allowance = await getAllowance( - config, - provider, client, + viemClient, memeToken.address as Address, defaultWalletAddress, defaultSpenderAddress @@ -53,11 +52,10 @@ describe('allowance integration tests', { retry: retryTimes, timeout }, () => { { retry: retryTimes, timeout }, async () => { const token = findDefaultToken(CoinKey.POL, ChainId.POL) - const client = await getPublicClient(config, token.chainId) + const viemClient = await getPublicClient(client, token.chainId) const allowance = await getAllowance( - config, - provider, client, + viemClient, token.address as Address, defaultWalletAddress, defaultSpenderAddress @@ -73,11 +71,10 @@ describe('allowance integration tests', { retry: retryTimes, timeout }, () => { async () => { const invalidToken = findDefaultToken(CoinKey.POL, ChainId.POL) invalidToken.address = '0x2170ed0880ac9a755fd29b2688956bd959f933f8' - const client = await getPublicClient(config, invalidToken.chainId) + const viemClient = await getPublicClient(client, invalidToken.chainId) const allowance = await getAllowance( - config, - provider, client, + viemClient, invalidToken.address as Address, defaultWalletAddress, defaultSpenderAddress @@ -90,11 +87,10 @@ describe('allowance integration tests', { retry: retryTimes, timeout }, () => { 'should handle empty lists with multicall', { retry: retryTimes, timeout }, async () => { - const client = await getPublicClient(config, 137) + const viemClient = await getPublicClient(client, 137) const allowances = await getAllowanceMulticall( - config, - provider, client, + viemClient, 137, [], defaultWalletAddress @@ -107,7 +103,7 @@ describe('allowance integration tests', { retry: retryTimes, timeout }, () => { 'should handle token lists with more than 10 tokens', { retry: retryTimes, timeout }, async () => { - const { tokens } = await getTokens(config, { + const { tokens } = await getTokens(client.config, { chains: [ChainId.POL], }) const filteredTokens = tokens[ChainId.POL] @@ -123,8 +119,7 @@ describe('allowance integration tests', { retry: retryTimes, timeout }, () => { if (tokenSpenders?.length) { const tokens = await getTokenAllowanceMulticall( - config, - provider, + client, defaultWalletAddress, tokenSpenders.slice(0, 10) ) diff --git a/src/core/EVM/getAllowance.ts b/src/core/EVM/getAllowance.ts index a6c1efdc..db66c645 100644 --- a/src/core/EVM/getAllowance.ts +++ b/src/core/EVM/getAllowance.ts @@ -2,12 +2,11 @@ import type { BaseToken, ChainId } from '@lifi/types' import type { Address, Client } from 'viem' import { multicall, readContract } from 'viem/actions' import { isZeroAddress } from '../../utils/isZeroAddress.js' -import type { SDKBaseConfig } from '../types.js' +import type { SDKClient } from '../types.js' import { allowanceAbi } from './abi.js' import { getActionWithFallback } from './getActionWithFallback.js' import { getPublicClient } from './publicClient.js' import type { - EVMProvider, TokenAllowance, TokenSpender, TokenSpenderAllowance, @@ -15,18 +14,16 @@ import type { import { getMulticallAddress } from './utils.js' export const getAllowance = async ( - config: SDKBaseConfig, - provider: EVMProvider, - client: Client, + client: SDKClient, + viemClient: Client, tokenAddress: Address, ownerAddress: Address, spenderAddress: Address ): Promise => { try { const approved = await getActionWithFallback( - config, - provider, client, + viemClient, readContract, 'readContract', { @@ -43,9 +40,8 @@ export const getAllowance = async ( } export const getAllowanceMulticall = async ( - config: SDKBaseConfig, - provider: EVMProvider, - client: Client, + client: SDKClient, + viemClient: Client, chainId: ChainId, tokens: TokenSpender[], ownerAddress: Address @@ -53,11 +49,10 @@ export const getAllowanceMulticall = async ( if (!tokens.length) { return [] } - const multicallAddress = await getMulticallAddress(config, chainId) + const multicallAddress = await getMulticallAddress(client.config, chainId) if (!multicallAddress) { throw new Error(`No multicall address configured for chainId ${chainId}.`) } - const contracts = tokens.map((token) => ({ address: token.token.address as Address, abi: allowanceAbi, @@ -66,9 +61,8 @@ export const getAllowanceMulticall = async ( })) const results = await getActionWithFallback( - config, - provider, client, + viemClient, multicall, 'multicall', { @@ -98,8 +92,7 @@ export const getAllowanceMulticall = async ( * @returns Returns allowance */ export const getTokenAllowance = async ( - config: SDKBaseConfig, - provider: EVMProvider, + client: SDKClient, token: BaseToken, ownerAddress: Address, spenderAddress: Address @@ -109,12 +102,11 @@ export const getTokenAllowance = async ( return } - const client = await getPublicClient(config, token.chainId) + const viemClient = await getPublicClient(client, token.chainId) const approved = await getAllowance( - config, - provider, client, + viemClient, token.address as Address, ownerAddress, spenderAddress @@ -129,8 +121,7 @@ export const getTokenAllowance = async ( * @returns Returns array of tokens and their allowance */ export const getTokenAllowanceMulticall = async ( - config: SDKBaseConfig, - provider: EVMProvider, + client: SDKClient, ownerAddress: Address, tokens: TokenSpender[] ): Promise => { @@ -153,16 +144,11 @@ export const getTokenAllowanceMulticall = async ( const allowances = ( await Promise.all( chainKeys.map(async (chainId) => { - const client = await getPublicClient( - config, - chainId, - provider?.options?.fallbackTransportConfig - ) + const viemClient = await getPublicClient(client, chainId) // get allowances for current chain and token list return getAllowanceMulticall( - config, - provider, client, + viemClient, chainId, tokenDataByChain[chainId], ownerAddress diff --git a/src/core/EVM/getEVMBalance.int.spec.ts b/src/core/EVM/getEVMBalance.int.spec.ts index 7684d8ab..7aa9672c 100644 --- a/src/core/EVM/getEVMBalance.int.spec.ts +++ b/src/core/EVM/getEVMBalance.int.spec.ts @@ -3,13 +3,15 @@ import type { StaticToken, Token } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' import type { Address } from 'viem' import { describe, expect, it } from 'vitest' -import { createConfig } from '../../createConfig.js' +import { createClient } from '../../client/createClient.js' import { getTokens } from '../../services/api.js' +import { EVM } from './EVM.js' import { getEVMBalance } from './getEVMBalance.js' -const config = createConfig({ +const client = createClient({ integrator: 'lifi-sdk', }) +client.setProviders([EVM()]) const defaultWalletAddress = '0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0' @@ -22,7 +24,7 @@ describe('getBalances integration tests', () => { tokens: StaticToken[] ) => { const tokenBalances = await getEVMBalance( - config, + client, walletAddress as Address, tokens as Token[] ) @@ -84,7 +86,7 @@ describe('getBalances integration tests', () => { const tokens = [findDefaultToken(CoinKey.USDC, ChainId.POL), invalidToken] const tokenBalances = await getEVMBalance( - config, + client, walletAddress, tokens as Token[] ) @@ -120,7 +122,7 @@ describe('getBalances integration tests', () => { { retry: retryTimes, timeout }, async () => { const walletAddress = defaultWalletAddress - const { tokens } = await getTokens(config, { + const { tokens } = await getTokens(client.config, { chains: [ChainId.OPT], }) expect(tokens[ChainId.OPT]?.length).toBeGreaterThan(100) diff --git a/src/core/EVM/getEVMBalance.ts b/src/core/EVM/getEVMBalance.ts index 82ebdaa5..9c777b2c 100644 --- a/src/core/EVM/getEVMBalance.ts +++ b/src/core/EVM/getEVMBalance.ts @@ -1,5 +1,5 @@ import type { Token, TokenAmount } from '@lifi/types' -import type { Address, Client, FallbackTransportConfig } from 'viem' +import type { Address, Client } from 'viem' import { getBalance, getBlockNumber, @@ -7,16 +7,15 @@ import { readContract, } from 'viem/actions' import { isZeroAddress } from '../../utils/isZeroAddress.js' -import type { SDKBaseConfig } from '../types.js' +import type { SDKClient } from '../types.js' import { balanceOfAbi, getEthBalanceAbi } from './abi.js' import { getPublicClient } from './publicClient.js' import { getMulticallAddress } from './utils.js' export const getEVMBalance = async ( - config: SDKBaseConfig, + client: SDKClient, walletAddress: Address, - tokens: Token[], - fallbackTransportConfig?: FallbackTransportConfig + tokens: Token[] ) => { if (tokens.length === 0) { return [] @@ -28,18 +27,18 @@ export const getEVMBalance = async ( } } - const multicallAddress = await getMulticallAddress(config, chainId) + const multicallAddress = await getMulticallAddress(client.config, chainId) - const client = await getPublicClient(config, chainId, fallbackTransportConfig) + const viemClient = await getPublicClient(client, chainId) if (multicallAddress && tokens.length > 1) { return getEVMBalanceMulticall( - client, + viemClient, tokens, walletAddress, multicallAddress ) } - return getEVMBalanceDefault(client, tokens, walletAddress) + return getEVMBalanceDefault(viemClient, tokens, walletAddress) } const getEVMBalanceMulticall = async ( diff --git a/src/core/EVM/isBatchingSupported.ts b/src/core/EVM/isBatchingSupported.ts index 91219161..1139e8ba 100644 --- a/src/core/EVM/isBatchingSupported.ts +++ b/src/core/EVM/isBatchingSupported.ts @@ -1,21 +1,28 @@ +import { ChainType } from '@lifi/types' import type { Client } from 'viem' import { getCapabilities } from 'viem/actions' import { getAction } from 'viem/utils' import { sleep } from '../../utils/sleep.js' +import type { SDKClient } from '../types.js' import type { EVMProvider } from './types.js' -export async function isBatchingSupported({ - client, - provider, - chainId, - skipReady = false, -}: { - client?: Client - provider: EVMProvider - chainId: number - skipReady?: boolean -}): Promise { - const _client = client ?? (await provider?.getWalletClient?.()) +export async function isBatchingSupported( + client: SDKClient, + { + client: viemClient, + chainId, + skipReady = false, + }: { + client?: Client + chainId: number + skipReady?: boolean + } +): Promise { + const _client = + viemClient ?? + (await ( + client.getProvider(ChainType.EVM) as EVMProvider + )?.getWalletClient?.()) if (!_client) { throw new Error('WalletClient is not provided.') diff --git a/src/core/EVM/permits/getNativePermit.ts b/src/core/EVM/permits/getNativePermit.ts index e5182f73..d4ef742d 100644 --- a/src/core/EVM/permits/getNativePermit.ts +++ b/src/core/EVM/permits/getNativePermit.ts @@ -9,10 +9,9 @@ import { zeroHash, } from 'viem' import { getCode, multicall, readContract } from 'viem/actions' -import type { SDKBaseConfig } from '../../../core/types.js' +import type { SDKClient } from '../../types.js' import { eip2612Abi } from '../abi.js' import { getActionWithFallback } from '../getActionWithFallback.js' -import type { EVMProvider } from '../types.js' import { getMulticallAddress, isDelegationDesignatorCode } from '../utils.js' import { DAI_LIKE_PERMIT_TYPEHASH, @@ -23,8 +22,7 @@ import { import type { NativePermitData } from './types.js' type GetNativePermitParams = { - config: SDKBaseConfig - provider: EVMProvider + client: Client chainId: number tokenAddress: Address spenderAddress: Address @@ -136,19 +134,17 @@ function validateDomainSeparator({ * @returns Promise - Whether the account can use native permits */ const canAccountUseNativePermits = async ( - config: SDKBaseConfig, - provider: EVMProvider, - client: Client + client: SDKClient, + viemClient: Client ): Promise => { try { const accountCode = await getActionWithFallback( - config, - provider, client, + viemClient, getCode, 'getCode', { - address: client.account!.address, + address: viemClient.account!.address, } ) @@ -180,14 +176,13 @@ const canAccountUseNativePermits = async ( * @returns Contract data if EIP-5267 is supported, undefined otherwise */ const getEIP712DomainData = async ( - config: SDKBaseConfig, - provider: EVMProvider, - client: Client, + client: SDKClient, + viemClient: Client, chainId: number, tokenAddress: Address ) => { try { - const multicallAddress = await getMulticallAddress(config, chainId) + const multicallAddress = await getMulticallAddress(client.config, chainId) const contractCalls = [ { @@ -199,16 +194,15 @@ const getEIP712DomainData = async ( address: tokenAddress, abi: eip2612Abi, functionName: 'nonces', - args: [client.account!.address], + args: [viemClient.account!.address], }, ] as const if (multicallAddress) { try { const [eip712DomainResult, noncesResult] = await getActionWithFallback( - config, - provider, client, + viemClient, multicall, 'multicall', { @@ -270,9 +264,8 @@ const getEIP712DomainData = async ( const [eip712DomainResult, noncesResult] = (await Promise.allSettled( contractCalls.map((call) => getActionWithFallback( - config, - provider, client, + viemClient, readContract, 'readContract', call @@ -332,18 +325,16 @@ const getEIP712DomainData = async ( } const getContractData = async ( - config: SDKBaseConfig, - provider: EVMProvider, - client: Client, + client: SDKClient, + viemClient: Client, chainId: number, tokenAddress: Address ) => { try { // First try EIP-5267 approach - returns domain object directly const eip5267Data = await getEIP712DomainData( - config, - provider, client, + viemClient, chainId, tokenAddress ) @@ -352,7 +343,7 @@ const getContractData = async ( } // Fallback to legacy approach - validates and returns domain object - const multicallAddress = await getMulticallAddress(config, chainId) + const multicallAddress = await getMulticallAddress(client.config, chainId) const contractCalls = [ { @@ -374,7 +365,7 @@ const getContractData = async ( address: tokenAddress, abi: eip2612Abi, functionName: 'nonces', - args: [client.account!.address], + args: [viemClient.account!.address], }, { address: tokenAddress, @@ -392,9 +383,8 @@ const getContractData = async ( noncesResult, versionResult, ] = await getActionWithFallback( - config, - provider, client, + viemClient, multicall, 'multicall', { @@ -449,9 +439,8 @@ const getContractData = async ( ] = (await Promise.allSettled( contractCalls.map((call) => getActionWithFallback( - config, - provider, client, + viemClient, readContract, 'readContract', call @@ -514,10 +503,9 @@ const getContractData = async ( * @returns {Promise} Object containing permit data including name, version, nonce and support status */ export const getNativePermit = async ( - client: Client, + client: SDKClient, { - config, - provider, + client: viemClient, chainId, tokenAddress, spenderAddress, @@ -525,19 +513,14 @@ export const getNativePermit = async ( }: GetNativePermitParams ): Promise => { // Check if the account can use native permits (EOA or EIP-7702 delegated account) - const canUsePermits = await canAccountUseNativePermits( - config, - provider, - client - ) + const canUsePermits = await canAccountUseNativePermits(client, viemClient) if (!canUsePermits) { return undefined } const contractData = await getContractData( - config, - provider, client, + viemClient, chainId, tokenAddress ) @@ -554,7 +537,7 @@ export const getNativePermit = async ( const deadline = BigInt(Math.floor(Date.now() / 1000) + 30 * 60).toString() // 30 minutes const message = { - owner: client.account!.address, + owner: viemClient.account!.address, spender: spenderAddress, value: amount.toString(), nonce: contractData.nonce.toString(), diff --git a/src/core/EVM/permits/getPermitTransferFromValues.ts b/src/core/EVM/permits/getPermitTransferFromValues.ts index 41ab4dcd..7d279c57 100644 --- a/src/core/EVM/permits/getPermitTransferFromValues.ts +++ b/src/core/EVM/permits/getPermitTransferFromValues.ts @@ -1,31 +1,28 @@ import type { ExtendedChain } from '@lifi/types' import type { Address, Client } from 'viem' import { readContract } from 'viem/actions' -import type { SDKBaseConfig } from '../../../core/types.js' +import type { SDKClient } from '../../types.js' import { permit2ProxyAbi } from '../abi.js' import { getActionWithFallback } from '../getActionWithFallback.js' -import type { EVMProvider } from '../types.js' import type { PermitTransferFrom } from './signatureTransfer.js' export const getPermitTransferFromValues = async ( - config: SDKBaseConfig, - provider: EVMProvider, - client: Client, + client: SDKClient, + viemClient: Client, chain: ExtendedChain, tokenAddress: Address, amount: bigint ): Promise => { const nonce = await getActionWithFallback( - config, - provider, client, + viemClient, readContract, 'readContract', { address: chain.permit2Proxy as Address, abi: permit2ProxyAbi, functionName: 'nextNonce' as const, - args: [client.account!.address] as const, + args: [viemClient.account!.address] as const, } ) diff --git a/src/core/EVM/permits/signPermit2Message.ts b/src/core/EVM/permits/signPermit2Message.ts index 7eab6ed0..9b839f90 100644 --- a/src/core/EVM/permits/signPermit2Message.ts +++ b/src/core/EVM/permits/signPermit2Message.ts @@ -3,8 +3,7 @@ import type { Address, Client, Hex } from 'viem' import { keccak256 } from 'viem' import { signTypedData } from 'viem/actions' import { getAction } from 'viem/utils' -import type { SDKBaseConfig } from '../../../core/types.js' -import type { EVMProvider } from '../types.js' +import type { SDKClient } from '../../types.js' import { getPermitTransferFromValues } from './getPermitTransferFromValues.js' import { getPermitData } from './signatureTransfer.js' @@ -18,16 +17,21 @@ interface SignPermit2MessageParams { } export async function signPermit2Message( - config: SDKBaseConfig, - provider: EVMProvider, + client: SDKClient, params: SignPermit2MessageParams ): Promise { - const { client, chain, tokenAddress, amount, data, witness } = params + const { + client: viemClient, + chain, + tokenAddress, + amount, + data, + witness, + } = params const permitTransferFrom = await getPermitTransferFromValues( - config, - provider, client, + viemClient, chain, tokenAddress, amount @@ -62,11 +66,11 @@ export async function signPermit2Message( : 'PermitTransferFrom' const signature = await getAction( - client, + viemClient, signTypedData, 'signTypedData' )({ - account: client.account!, + account: viemClient.account!, primaryType, domain: permitData.domain, types: permitData.types, diff --git a/src/core/EVM/publicClient.ts b/src/core/EVM/publicClient.ts index 46b9f053..1029a751 100644 --- a/src/core/EVM/publicClient.ts +++ b/src/core/EVM/publicClient.ts @@ -1,10 +1,11 @@ -import { ChainId } from '@lifi/types' -import type { Client, FallbackTransportConfig } from 'viem' +import { ChainId, ChainType } from '@lifi/types' +import type { Client } from 'viem' import { type Address, createClient, fallback, http, webSocket } from 'viem' import { type Chain, mainnet } from 'viem/chains' -import { getChainById } from '../getChainById.js' -import { getRpcUrls } from '../rpc.js' -import type { SDKBaseConfig } from '../types.js' +import { getChainById } from '../../client/getChainById.js' +import { getRpcUrls } from '../../client/getRpcUrls.js' +import type { SDKClient } from '../types.js' +import type { EVMProvider } from './types.js' import { UNS_PROXY_READER_ADDRESSES } from './uns/constants.js' // cached providers @@ -16,15 +17,14 @@ const publicClients: Record = {} * @returns The public client for the given chain */ export const getPublicClient = async ( - config: SDKBaseConfig, - chainId: number, - fallbackTransportConfig?: FallbackTransportConfig + client: SDKClient, + chainId: number ): Promise => { if (publicClients[chainId]) { return publicClients[chainId] } - const urls = await getRpcUrls(config, chainId) + const urls = await getRpcUrls(client, chainId) const fallbackTransports = urls.map((url) => url.startsWith('wss') ? webSocket(url) @@ -34,7 +34,7 @@ export const getPublicClient = async ( }, }) ) - const _chain = await getChainById(config, chainId) + const _chain = await getChainById(client, chainId) const chain: Chain = { ..._chain, ..._chain.metamask, @@ -62,9 +62,13 @@ export const getPublicClient = async ( } } + const provider = client.getProvider(ChainType.EVM) as EVMProvider | undefined publicClients[chainId] = createClient({ chain: chain, - transport: fallback(fallbackTransports, fallbackTransportConfig), + transport: fallback( + fallbackTransports, + provider?.options?.fallbackTransportConfig + ), batch: { multicall: true, }, diff --git a/src/core/EVM/resolveENSAddress.ts b/src/core/EVM/resolveENSAddress.ts index a5a71d47..b5a1490f 100644 --- a/src/core/EVM/resolveENSAddress.ts +++ b/src/core/EVM/resolveENSAddress.ts @@ -1,15 +1,15 @@ import { ChainId } from '@lifi/types' import { getEnsAddress, normalize } from 'viem/ens' -import type { SDKBaseConfig } from '../types.js' +import type { SDKClient } from '../types.js' import { getPublicClient } from './publicClient.js' export const resolveENSAddress = async ( - config: SDKBaseConfig, + client: SDKClient, name: string ): Promise => { try { - const client = await getPublicClient(config, ChainId.ETH) - const address = await getEnsAddress(client, { + const viemClient = await getPublicClient(client, ChainId.ETH) + const address = await getEnsAddress(viemClient, { name: normalize(name), }) return address as string | undefined diff --git a/src/core/EVM/resolveEVMAddress.ts b/src/core/EVM/resolveEVMAddress.ts index aa597131..3da29251 100644 --- a/src/core/EVM/resolveEVMAddress.ts +++ b/src/core/EVM/resolveEVMAddress.ts @@ -1,17 +1,17 @@ import type { ChainId, CoinKey } from '@lifi/types' import { ChainType } from '@lifi/types' -import type { SDKBaseConfig } from '../types.js' +import type { SDKClient } from '../types.js' import { resolveENSAddress } from './resolveENSAddress.js' import { resolveUNSAddress } from './uns/resolveUNSAddress.js' export async function resolveEVMAddress( name: string, - config: SDKBaseConfig, + client: SDKClient, chainId?: ChainId, token?: CoinKey ): Promise { return ( - (await resolveENSAddress(config, name)) || - (await resolveUNSAddress(config, name, ChainType.EVM, chainId, token)) + (await resolveENSAddress(client, name)) || + (await resolveUNSAddress(client, name, ChainType.EVM, chainId, token)) ) } diff --git a/src/core/EVM/setAllowance.int.spec.ts b/src/core/EVM/setAllowance.int.spec.ts index a487c0a3..466efdc3 100644 --- a/src/core/EVM/setAllowance.int.spec.ts +++ b/src/core/EVM/setAllowance.int.spec.ts @@ -1,18 +1,19 @@ import type { Address, Client } from 'viem' -import { createClient, http } from 'viem' +import { createClient as createViemClient, http } from 'viem' import { mnemonicToAccount } from 'viem/accounts' import { waitForTransactionReceipt } from 'viem/actions' import { polygon } from 'viem/chains' import { describe, expect, it } from 'vitest' -import { createConfig } from '../../createConfig.js' +import { createClient } from '../../client/createClient.js' import { EVM } from './EVM.js' import { revokeTokenApproval, setTokenAllowance } from './setAllowance.js' import { retryCount, retryDelay } from './utils.js' -const config = createConfig({ +const client = createClient({ integrator: 'lifi-sdk', }) -const provider = EVM() +client.setProviders([EVM()]) + const defaultSpenderAddress = '0x9b11bc9FAc17c058CAB6286b0c785bE6a65492EF' const testToken = { name: 'USDT', @@ -34,7 +35,7 @@ describe.skipIf(!MNEMONIC)('Approval integration tests', () => { } const account = mnemonicToAccount(MNEMONIC as Address) - const client: Client = createClient({ + const walletClient: Client = createViemClient({ account, chain: polygon, transport: http(), @@ -43,20 +44,21 @@ describe.skipIf(!MNEMONIC)('Approval integration tests', () => { it( 'should revoke allowance for ERC20 on POL', async () => { - const revokeTxHash = await revokeTokenApproval({ - config, - provider, - walletClient: client, + const revokeTxHash = await revokeTokenApproval(client, { + walletClient, token: testToken, spenderAddress: defaultSpenderAddress, }) if (revokeTxHash) { - const transactionReceipt = await waitForTransactionReceipt(client, { - hash: revokeTxHash!, - retryCount, - retryDelay, - }) + const transactionReceipt = await waitForTransactionReceipt( + walletClient, + { + hash: revokeTxHash!, + retryCount, + retryDelay, + } + ) expect(transactionReceipt.status).toBe('success') } @@ -67,21 +69,22 @@ describe.skipIf(!MNEMONIC)('Approval integration tests', () => { it( 'should set allowance ERC20 on POL', async () => { - const approvalTxHash = await setTokenAllowance({ - config, - provider, - walletClient: client, + const approvalTxHash = await setTokenAllowance(client, { + walletClient: walletClient, token: testToken, spenderAddress: defaultSpenderAddress, amount: defaultAllowance, }) if (approvalTxHash) { - const transactionReceipt = await waitForTransactionReceipt(client, { - hash: approvalTxHash!, - retryCount, - retryDelay, - }) + const transactionReceipt = await waitForTransactionReceipt( + walletClient, + { + hash: approvalTxHash!, + retryCount, + retryDelay, + } + ) expect(transactionReceipt.status).toBe('success') } diff --git a/src/core/EVM/setAllowance.ts b/src/core/EVM/setAllowance.ts index 706bf805..deadb97d 100644 --- a/src/core/EVM/setAllowance.ts +++ b/src/core/EVM/setAllowance.ts @@ -5,22 +5,17 @@ import { getAction } from 'viem/utils' import { isZeroAddress } from '../../utils/isZeroAddress.js' import type { ExecutionOptions, - SDKBaseConfig, + SDKClient, TransactionParameters, } from '../types.js' import { approveAbi } from './abi.js' import { getAllowance } from './getAllowance.js' -import type { - ApproveTokenRequest, - EVMProvider, - RevokeApprovalRequest, -} from './types.js' +import type { ApproveTokenRequest, RevokeApprovalRequest } from './types.js' import { getMaxPriorityFeePerGas } from './utils.js' export const setAllowance = async ( - config: SDKBaseConfig, - provider: EVMProvider, - client: Client, + client: SDKClient, + viemClient: Client, tokenAddress: Address, contractAddress: Address, amount: bigint, @@ -41,8 +36,8 @@ export const setAllowance = async ( to: tokenAddress, data, maxPriorityFeePerGas: - client.account?.type === 'local' - ? await getMaxPriorityFeePerGas(config, provider, client) + viemClient.account?.type === 'local' + ? await getMaxPriorityFeePerGas(client, viemClient) : undefined, } @@ -60,12 +55,12 @@ export const setAllowance = async ( } return getAction( - client, + viemClient, sendTransaction, 'sendTransaction' )({ to: transactionRequest.to, - account: client.account!, + account: viemClient.account!, data: transactionRequest.data, gas: transactionRequest.gas, gasPrice: transactionRequest.gasPrice, @@ -83,21 +78,16 @@ export const setAllowance = async ( * @param request.amount - The amount of tokens to approve * @returns Returns Hash or nothing */ -export const setTokenAllowance = async ({ - config, - provider, - walletClient, - token, - spenderAddress, - amount, -}: ApproveTokenRequest): Promise => { +export const setTokenAllowance = async ( + client: SDKClient, + { walletClient, token, spenderAddress, amount }: ApproveTokenRequest +): Promise => { // native token don't need approval if (isZeroAddress(token.address)) { return } const approvedAmount = await getAllowance( - config, - provider, + client, walletClient, token.address as Address, walletClient.account!.address, @@ -106,8 +96,7 @@ export const setTokenAllowance = async ({ if (amount > approvedAmount) { const approveTx = await setAllowance( - config, - provider, + client, walletClient, token.address as Address, spenderAddress as Address, @@ -126,20 +115,16 @@ export const setTokenAllowance = async ({ * @param request.spenderAddress - The address of the spender * @returns Returns Hash or nothing */ -export const revokeTokenApproval = async ({ - config, - provider, - walletClient, - token, - spenderAddress, -}: RevokeApprovalRequest): Promise => { +export const revokeTokenApproval = async ( + client: SDKClient, + { walletClient, token, spenderAddress }: RevokeApprovalRequest +): Promise => { // native token don't need approval if (isZeroAddress(token.address)) { return } const approvedAmount = await getAllowance( - config, - provider, + client, walletClient, token.address as Address, walletClient.account!.address, @@ -147,8 +132,7 @@ export const revokeTokenApproval = async ({ ) if (approvedAmount > 0) { const approveTx = await setAllowance( - config, - provider, + client, walletClient, token.address as Address, spenderAddress as Address, diff --git a/src/core/EVM/types.ts b/src/core/EVM/types.ts index 34ee4692..906976d0 100644 --- a/src/core/EVM/types.ts +++ b/src/core/EVM/types.ts @@ -6,7 +6,7 @@ import type { FallbackTransportConfig, Hex, } from 'viem' -import type { SDKBaseConfig, SDKProvider, SwitchChainHook } from '../types.js' +import type { SDKProvider, SwitchChainHook } from '../types.js' export interface EVMProviderOptions { getWalletClient?: () => Promise @@ -41,8 +41,6 @@ export type TokenSpenderAllowance = { } export interface ApproveTokenRequest { - config: SDKBaseConfig - provider: EVMProvider walletClient: Client token: BaseToken spenderAddress: string @@ -54,8 +52,6 @@ export interface ApproveTokenRequest { } export interface RevokeApprovalRequest { - config: SDKBaseConfig - provider: EVMProvider walletClient: Client token: BaseToken spenderAddress: string diff --git a/src/core/EVM/uns/resolveUNSAddress.ts b/src/core/EVM/uns/resolveUNSAddress.ts index 85f2986e..ae751ae9 100644 --- a/src/core/EVM/uns/resolveUNSAddress.ts +++ b/src/core/EVM/uns/resolveUNSAddress.ts @@ -3,7 +3,7 @@ import type { Address, Client } from 'viem' import { readContract } from 'viem/actions' import { namehash } from 'viem/ens' import { getAction, trim } from 'viem/utils' -import type { SDKBaseConfig } from '../../../core/types.js' +import type { SDKClient } from '../../types.js' import { getPublicClient } from '../publicClient.js' import { CHAIN_ID_UNS_CHAIN_MAP, @@ -14,15 +14,15 @@ import { } from './constants.js' export const resolveUNSAddress = async ( - config: SDKBaseConfig, + client: SDKClient, name: string, chainType: ChainType, chain?: ChainId, token?: CoinKey ): Promise => { try { - const L1Client = await getPublicClient(config, ChainId.ETH) - const L2Client = await getPublicClient(config, ChainId.POL) + const L1Client = await getPublicClient(client, ChainId.ETH) + const L2Client = await getPublicClient(client, ChainId.POL) const nameHash = namehash(name) const keys: string[] = [] diff --git a/src/core/EVM/utils.ts b/src/core/EVM/utils.ts index c61102b1..d483f24b 100644 --- a/src/core/EVM/utils.ts +++ b/src/core/EVM/utils.ts @@ -4,9 +4,8 @@ import type { Address, Chain, Client, Transaction, TypedDataDomain } from 'viem' import { getBlock } from 'viem/actions' import { getChains } from '../../services/api.js' import { median } from '../../utils/median.js' -import type { SDKBaseConfig } from '../types.js' +import type { SDKBaseConfig, SDKClient } from '../types.js' import { getActionWithFallback } from './getActionWithFallback.js' -import type { EVMProvider } from './types.js' type ChainBlockExplorer = { name: string @@ -60,14 +59,12 @@ export function isExtendedChain(chain: any): chain is ExtendedChain { } export const getMaxPriorityFeePerGas = async ( - config: SDKBaseConfig, - provider: EVMProvider, - client: Client + client: SDKClient, + viemClient: Client ): Promise => { const block = await getActionWithFallback( - config, - provider, client, + viemClient, getBlock, 'getBlock', { diff --git a/src/core/EVM/waitForTransactionReceipt.ts b/src/core/EVM/waitForTransactionReceipt.ts index 04000476..2dd058a6 100644 --- a/src/core/EVM/waitForTransactionReceipt.ts +++ b/src/core/EVM/waitForTransactionReceipt.ts @@ -10,32 +10,33 @@ import type { import { waitForTransactionReceipt as waitForTransactionReceiptInternal } from 'viem/actions' import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' -import type { SDKBaseConfig } from '../types.js' +import type { SDKClient } from '../types.js' import { getPublicClient } from './publicClient.js' interface WaitForTransactionReceiptProps { - config: SDKBaseConfig client: Client chainId: ChainId txHash: Hash onReplaced?: (response: ReplacementReturnType) => void } -export async function waitForTransactionReceipt({ - config, - client, - chainId, - txHash, - onReplaced, -}: WaitForTransactionReceiptProps): Promise { +export async function waitForTransactionReceipt( + client: SDKClient, + { + client: viemClient, + chainId, + txHash, + onReplaced, + }: WaitForTransactionReceiptProps +): Promise { let { transactionReceipt, replacementReason } = await waitForReceipt( - client, + viemClient, txHash, onReplaced ) if (!transactionReceipt?.status) { - const publicClient = await getPublicClient(config, chainId) + const publicClient = await getPublicClient(client, chainId) const result = await waitForReceipt(publicClient, txHash, onReplaced) transactionReceipt = result.transactionReceipt replacementReason = result.replacementReason diff --git a/src/core/Solana/Solana.ts b/src/core/Solana/Solana.ts index de46b003..49ac9a4e 100644 --- a/src/core/Solana/Solana.ts +++ b/src/core/Solana/Solana.ts @@ -30,7 +30,6 @@ export function Solana(options?: SolanaProviderOptions): SolanaProvider { executionOptions: { ...options.executionOptions, }, - provider: this, }) return executor diff --git a/src/core/Solana/SolanaStepExecutor.ts b/src/core/Solana/SolanaStepExecutor.ts index 8e91375a..4a8bdc2d 100644 --- a/src/core/Solana/SolanaStepExecutor.ts +++ b/src/core/Solana/SolanaStepExecutor.ts @@ -1,17 +1,17 @@ import type { SignerWalletAdapter } from '@solana/wallet-adapter-base' import { VersionedTransaction } from '@solana/web3.js' import { withTimeout } from 'viem' +import { getChainById } from '../../client/getChainById.js' import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' import { getStepTransaction } from '../../services/api.js' import { base64ToUint8Array } from '../../utils/base64ToUint8Array.js' import { BaseStepExecutor } from '../BaseStepExecutor.js' import { checkBalance } from '../checkBalance.js' -import { getChainById } from '../getChainById.js' import { stepComparison } from '../stepComparison.js' import type { LiFiStepExtended, - SDKBaseConfig, + SDKClient, StepExecutorOptions, TransactionParameters, } from '../types.js' @@ -19,21 +19,17 @@ import { waitForDestinationChainTransaction } from '../waitForDestinationChainTr import { callSolanaWithRetry } from './connection.js' import { parseSolanaErrors } from './parseSolanaErrors.js' import { sendAndConfirmTransaction } from './sendAndConfirmTransaction.js' -import type { SolanaProvider } from './types.js' interface SolanaStepExecutorOptions extends StepExecutorOptions { walletAdapter: SignerWalletAdapter - provider: SolanaProvider } export class SolanaStepExecutor extends BaseStepExecutor { private walletAdapter: SignerWalletAdapter - private provider: SolanaProvider constructor(options: SolanaStepExecutorOptions) { super(options) this.walletAdapter = options.walletAdapter - this.provider = options.provider } checkWalletAdapter = (step: LiFiStepExtended) => { @@ -47,13 +43,13 @@ export class SolanaStepExecutor extends BaseStepExecutor { } executeStep = async ( - config: SDKBaseConfig, + client: SDKClient, step: LiFiStepExtended ): Promise => { step.execution = this.statusManager.initExecutionObject(step) - const fromChain = await getChainById(config, step.action.fromChainId) - const toChain = await getChainById(config, step.action.toChainId) + const fromChain = await getChainById(client, step.action.fromChainId) + const toChain = await getChainById(client, step.action.toChainId) const isBridgeExecution = fromChain.id !== toChain.id const currentProcessType = isBridgeExecution ? 'CROSS_CHAIN' : 'SWAP' @@ -74,8 +70,7 @@ export class SolanaStepExecutor extends BaseStepExecutor { // Check balance await checkBalance( - config, - [this.provider], + client, this.walletAdapter.publicKey!.toString(), step ) @@ -84,7 +79,7 @@ export class SolanaStepExecutor extends BaseStepExecutor { if (!step.transactionRequest) { // biome-ignore lint/correctness/noUnusedVariables: destructuring const { execution, ...stepBase } = step - const updatedStep = await getStepTransaction(config, stepBase) + const updatedStep = await getStepTransaction(client.config, stepBase) const comparedStep = await stepComparison( this.statusManager, step, @@ -166,7 +161,7 @@ export class SolanaStepExecutor extends BaseStepExecutor { ) const simulationResult = await callSolanaWithRetry( - config, + client, (connection) => connection.simulateTransaction(signedTx, { commitment: 'confirmed', @@ -181,7 +176,7 @@ export class SolanaStepExecutor extends BaseStepExecutor { ) } - const confirmedTx = await sendAndConfirmTransaction(config, signedTx) + const confirmedTx = await sendAndConfirmTransaction(client, signedTx) if (!confirmedTx.signatureResult) { throw new TransactionError( @@ -234,7 +229,7 @@ export class SolanaStepExecutor extends BaseStepExecutor { } await waitForDestinationChainTransaction( - config, + client, step, process, fromChain, diff --git a/src/core/Solana/connection.ts b/src/core/Solana/connection.ts index 7b1b7f0b..8a76dc7b 100644 --- a/src/core/Solana/connection.ts +++ b/src/core/Solana/connection.ts @@ -1,7 +1,7 @@ import { ChainId } from '@lifi/types' import { Connection } from '@solana/web3.js' -import { getRpcUrls } from '../rpc.js' -import type { SDKBaseConfig } from '../types.js' +import { getRpcUrls } from '../../client/getRpcUrls.js' +import type { SDKClient } from '../types.js' const connections = new Map() @@ -9,8 +9,8 @@ const connections = new Map() * Initializes the Solana connections if they haven't been initialized yet. * @returns - Promise that resolves when connections are initialized. */ -const ensureConnections = async (config: SDKBaseConfig): Promise => { - const rpcUrls = await getRpcUrls(config, ChainId.SOL) +const ensureConnections = async (client: SDKClient): Promise => { + const rpcUrls = await getRpcUrls(client, ChainId.SOL) for (const rpcUrl of rpcUrls) { if (!connections.get(rpcUrl)) { const connection = new Connection(rpcUrl) @@ -24,9 +24,9 @@ const ensureConnections = async (config: SDKBaseConfig): Promise => { * @returns - Solana RPC connections */ export const getSolanaConnections = async ( - config: SDKBaseConfig + client: SDKClient ): Promise => { - await ensureConnections(config) + await ensureConnections(client) return Array.from(connections.values()) } @@ -36,11 +36,11 @@ export const getSolanaConnections = async ( * @returns - The result of the function call. */ export async function callSolanaWithRetry( - config: SDKBaseConfig, + client: SDKClient, fn: (connection: Connection) => Promise ): Promise { // Ensure connections are initialized - await ensureConnections(config) + await ensureConnections(client) let lastError: any = null for (const connection of connections.values()) { try { diff --git a/src/core/Solana/getSolanaBalance.int.spec.ts b/src/core/Solana/getSolanaBalance.int.spec.ts index 4be3d34d..6aea0ae1 100644 --- a/src/core/Solana/getSolanaBalance.int.spec.ts +++ b/src/core/Solana/getSolanaBalance.int.spec.ts @@ -2,12 +2,14 @@ import { findDefaultToken } from '@lifi/data-types' import type { StaticToken, Token } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' import { describe, expect, it } from 'vitest' -import { createConfig } from '../../createConfig.js' +import { createClient } from '../../client/createClient.js' import { getSolanaBalance } from './getSolanaBalance.js' +import { Solana } from './Solana.js' -const config = createConfig({ +const client = createClient({ integrator: 'lifi-sdk', }) +client.setProviders([Solana()]) const defaultWalletAddress = '9T655zHa6bYrTHWdy59NFqkjwoaSwfMat2yzixE1nb56' @@ -20,7 +22,7 @@ describe.sequential('Solana token balance', async () => { tokens: StaticToken[] ) => { const tokenBalances = await getSolanaBalance( - config, + client, walletAddress, tokens as Token[] ) @@ -74,7 +76,7 @@ describe.sequential('Solana token balance', async () => { const tokens = [findDefaultToken(CoinKey.USDC, ChainId.SOL), invalidToken] const tokenBalances = await getSolanaBalance( - config, + client, walletAddress, tokens as Token[] ) diff --git a/src/core/Solana/getSolanaBalance.ts b/src/core/Solana/getSolanaBalance.ts index 4cf61fef..0ff59c9b 100644 --- a/src/core/Solana/getSolanaBalance.ts +++ b/src/core/Solana/getSolanaBalance.ts @@ -2,12 +2,12 @@ import type { ChainId, Token, TokenAmount } from '@lifi/types' import { PublicKey } from '@solana/web3.js' import { SolSystemProgram } from '../../constants.js' import { withDedupe } from '../../utils/withDedupe.js' -import type { SDKBaseConfig } from '../types.js' +import type { SDKClient } from '../types.js' import { callSolanaWithRetry } from './connection.js' import { Token2022ProgramId, TokenProgramId } from './types.js' export const getSolanaBalance = async ( - config: SDKBaseConfig, + client: SDKClient, walletAddress: string, tokens: Token[] ): Promise => { @@ -21,11 +21,11 @@ export const getSolanaBalance = async ( } } - return getSolanaBalanceDefault(config, chainId, tokens, walletAddress) + return getSolanaBalanceDefault(client, chainId, tokens, walletAddress) } const getSolanaBalanceDefault = async ( - config: SDKBaseConfig, + client: SDKClient, _chainId: ChainId, tokens: Token[], walletAddress: string @@ -37,21 +37,21 @@ const getSolanaBalanceDefault = async ( await Promise.allSettled([ withDedupe( () => - callSolanaWithRetry(config, (connection) => + callSolanaWithRetry(client, (connection) => connection.getSlot('confirmed') ), { id: `${getSolanaBalanceDefault.name}.getSlot` } ), withDedupe( () => - callSolanaWithRetry(config, (connection) => + callSolanaWithRetry(client, (connection) => connection.getBalance(accountPublicKey, 'confirmed') ), { id: `${getSolanaBalanceDefault.name}.getBalance` } ), withDedupe( () => - callSolanaWithRetry(config, (connection) => + callSolanaWithRetry(client, (connection) => connection.getParsedTokenAccountsByOwner( accountPublicKey, { @@ -66,7 +66,7 @@ const getSolanaBalanceDefault = async ( ), withDedupe( () => - callSolanaWithRetry(config, (connection) => + callSolanaWithRetry(client, (connection) => connection.getParsedTokenAccountsByOwner( accountPublicKey, { diff --git a/src/core/Solana/sendAndConfirmTransaction.ts b/src/core/Solana/sendAndConfirmTransaction.ts index 8530d310..cb19190c 100644 --- a/src/core/Solana/sendAndConfirmTransaction.ts +++ b/src/core/Solana/sendAndConfirmTransaction.ts @@ -5,7 +5,7 @@ import type { } from '@solana/web3.js' import bs58 from 'bs58' import { sleep } from '../../utils/sleep.js' -import type { SDKBaseConfig } from '../types.js' +import type { SDKClient } from '../types.js' import { getSolanaConnections } from './connection.js' type ConfirmedTransactionResult = { @@ -20,10 +20,10 @@ type ConfirmedTransactionResult = { * @returns - The confirmation result of the transaction. */ export async function sendAndConfirmTransaction( - config: SDKBaseConfig, + client: SDKClient, signedTx: VersionedTransaction ): Promise { - const connections = await getSolanaConnections(config) + const connections = await getSolanaConnections(client) const signedTxSerialized = signedTx.serialize() // Create transaction hash (signature) diff --git a/src/core/Sui/Sui.ts b/src/core/Sui/Sui.ts index b4379f3e..b416010f 100644 --- a/src/core/Sui/Sui.ts +++ b/src/core/Sui/Sui.ts @@ -30,7 +30,6 @@ export function Sui(options?: SuiProviderOptions): SuiProvider { executionOptions: { ...options.executionOptions, }, - provider: this, }) return executor diff --git a/src/core/Sui/SuiStepExecutor.ts b/src/core/Sui/SuiStepExecutor.ts index d627ebd4..f5ddc285 100644 --- a/src/core/Sui/SuiStepExecutor.ts +++ b/src/core/Sui/SuiStepExecutor.ts @@ -2,37 +2,33 @@ import { signAndExecuteTransaction, type WalletWithRequiredFeatures, } from '@mysten/wallet-standard' +import { getChainById } from '../../client/getChainById.js' import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' import { getStepTransaction } from '../../services/api.js' import { BaseStepExecutor } from '../BaseStepExecutor.js' import { checkBalance } from '../checkBalance.js' -import { getChainById } from '../getChainById.js' import { stepComparison } from '../stepComparison.js' import type { LiFiStepExtended, - SDKBaseConfig, + SDKClient, StepExecutorOptions, TransactionParameters, } from '../types.js' import { waitForDestinationChainTransaction } from '../waitForDestinationChainTransaction.js' import { parseSuiErrors } from './parseSuiErrors.js' import { callSuiWithRetry } from './suiClient.js' -import type { SuiProvider } from './types.js' interface SuiStepExecutorOptions extends StepExecutorOptions { wallet: WalletWithRequiredFeatures - provider: SuiProvider } export class SuiStepExecutor extends BaseStepExecutor { private wallet: WalletWithRequiredFeatures - private provider: SuiProvider constructor(options: SuiStepExecutorOptions) { super(options) this.wallet = options.wallet - this.provider = options.provider } checkWallet = (step: LiFiStepExtended) => { @@ -50,13 +46,13 @@ export class SuiStepExecutor extends BaseStepExecutor { } executeStep = async ( - config: SDKBaseConfig, + client: SDKClient, step: LiFiStepExtended ): Promise => { step.execution = this.statusManager.initExecutionObject(step) - const fromChain = await getChainById(config, step.action.fromChainId) - const toChain = await getChainById(config, step.action.toChainId) + const fromChain = await getChainById(client, step.action.fromChainId) + const toChain = await getChainById(client, step.action.toChainId) const isBridgeExecution = fromChain.id !== toChain.id const currentProcessType = isBridgeExecution ? 'CROSS_CHAIN' : 'SWAP' @@ -76,18 +72,13 @@ export class SuiStepExecutor extends BaseStepExecutor { ) // Check balance - await checkBalance( - config, - [this.provider], - step.action.fromAddress!, - step - ) + await checkBalance(client, step.action.fromAddress!, step) // Create new transaction if (!step.transactionRequest) { // biome-ignore lint/correctness/noUnusedVariables: destructuring const { execution, ...stepBase } = step - const updatedStep = await getStepTransaction(config, stepBase) + const updatedStep = await getStepTransaction(client.config, stepBase) const comparedStep = await stepComparison( this.statusManager, step, @@ -163,7 +154,7 @@ export class SuiStepExecutor extends BaseStepExecutor { 'PENDING' ) - const result = await callSuiWithRetry(config, (client) => + const result = await callSuiWithRetry(client, (client) => client.waitForTransaction({ digest: signedTx.digest, options: { @@ -212,7 +203,7 @@ export class SuiStepExecutor extends BaseStepExecutor { } await waitForDestinationChainTransaction( - config, + client, step, process, fromChain, diff --git a/src/core/Sui/getSuiBalance.int.spec.ts b/src/core/Sui/getSuiBalance.int.spec.ts index eacdc8b1..8f081e4f 100644 --- a/src/core/Sui/getSuiBalance.int.spec.ts +++ b/src/core/Sui/getSuiBalance.int.spec.ts @@ -2,10 +2,10 @@ import { findDefaultToken } from '@lifi/data-types' import type { StaticToken, Token } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' import { describe, expect, it } from 'vitest' -import { createConfig } from '../../createConfig.js' +import { createClient } from '../../client/createClient.js' import { getSuiBalance } from './getSuiBalance.js' -const config = createConfig({ +const client = createClient({ integrator: 'lifi-sdk', }) @@ -21,7 +21,7 @@ describe.sequential('Sui token balance', async () => { tokens: StaticToken[] ) => { const tokenBalances = await getSuiBalance( - config, + client, walletAddress, tokens as Token[] ) @@ -75,7 +75,7 @@ describe.sequential('Sui token balance', async () => { const tokens = [findDefaultToken(CoinKey.SUI, ChainId.SUI), invalidToken] const tokenBalances = await getSuiBalance( - config, + client, walletAddress, tokens as Token[] ) diff --git a/src/core/Sui/getSuiBalance.ts b/src/core/Sui/getSuiBalance.ts index d54baac3..bf65dd97 100644 --- a/src/core/Sui/getSuiBalance.ts +++ b/src/core/Sui/getSuiBalance.ts @@ -1,11 +1,11 @@ import type { Token, TokenAmount } from '@lifi/types' import { withDedupe } from '../../utils/withDedupe.js' -import type { SDKBaseConfig } from '../types.js' +import type { SDKClient } from '../types.js' import { callSuiWithRetry } from './suiClient.js' import { SuiTokenLongAddress, SuiTokenShortAddress } from './types.js' export async function getSuiBalance( - config: SDKBaseConfig, + client: SDKClient, walletAddress: string, tokens: Token[] ): Promise { @@ -20,11 +20,11 @@ export async function getSuiBalance( } } - return getSuiBalanceDefault(config, chainId, tokens, walletAddress) + return getSuiBalanceDefault(client, chainId, tokens, walletAddress) } const getSuiBalanceDefault = async ( - config: SDKBaseConfig, + client: SDKClient, _chainId: number, tokens: Token[], walletAddress: string @@ -32,7 +32,7 @@ const getSuiBalanceDefault = async ( const [coins, checkpoint] = await Promise.allSettled([ withDedupe( () => - callSuiWithRetry(config, (client) => + callSuiWithRetry(client, (client) => client.getAllBalances({ owner: walletAddress, }) @@ -41,7 +41,7 @@ const getSuiBalanceDefault = async ( ), withDedupe( () => - callSuiWithRetry(config, (client) => + callSuiWithRetry(client, (client) => client.getLatestCheckpointSequenceNumber() ), { id: `${getSuiBalanceDefault.name}.getLatestCheckpointSequenceNumber` } diff --git a/src/core/Sui/suiClient.ts b/src/core/Sui/suiClient.ts index 9f124640..dc1dbcf8 100644 --- a/src/core/Sui/suiClient.ts +++ b/src/core/Sui/suiClient.ts @@ -1,7 +1,7 @@ import { ChainId } from '@lifi/types' import { SuiClient } from '@mysten/sui/client' -import { getRpcUrls } from '../rpc.js' -import type { SDKBaseConfig } from '../types.js' +import { getRpcUrls } from '../../client/getRpcUrls.js' +import type { SDKClient } from '../types.js' const clients = new Map() @@ -9,8 +9,8 @@ const clients = new Map() * Initializes the Sui clients if they haven't been initialized yet. * @returns - Promise that resolves when clients are initialized. */ -const ensureClients = async (config: SDKBaseConfig): Promise => { - const rpcUrls = await getRpcUrls(config, ChainId.SUI) +const ensureClients = async (client: SDKClient): Promise => { + const rpcUrls = await getRpcUrls(client, ChainId.SUI) for (const rpcUrl of rpcUrls) { if (!clients.get(rpcUrl)) { const client = new SuiClient({ url: rpcUrl }) @@ -25,11 +25,11 @@ const ensureClients = async (config: SDKBaseConfig): Promise => { * @returns - The result of the function call. */ export async function callSuiWithRetry( - config: SDKBaseConfig, + client: SDKClient, fn: (client: SuiClient) => Promise ): Promise { // Ensure clients are initialized - await ensureClients(config) + await ensureClients(client) let lastError: any = null for (const client of clients.values()) { try { diff --git a/src/core/UTXO/UTXO.ts b/src/core/UTXO/UTXO.ts index a3baee54..3174ecde 100644 --- a/src/core/UTXO/UTXO.ts +++ b/src/core/UTXO/UTXO.ts @@ -30,7 +30,6 @@ export function UTXO(options?: UTXOProviderOptions): UTXOProvider { executionOptions: { ...options.executionOptions, }, - provider: this, }) return executor diff --git a/src/core/UTXO/UTXOStepExecutor.ts b/src/core/UTXO/UTXOStepExecutor.ts index 0ec0433d..9e9547a5 100644 --- a/src/core/UTXO/UTXOStepExecutor.ts +++ b/src/core/UTXO/UTXOStepExecutor.ts @@ -10,38 +10,34 @@ import { import * as ecc from '@bitcoinerlab/secp256k1' import { ChainId } from '@lifi/types' import { address, initEccLib, networks, Psbt } from 'bitcoinjs-lib' +import { getChainById } from '../../client/getChainById.js' import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' import { getStepTransaction } from '../../services/api.js' import { BaseStepExecutor } from '../BaseStepExecutor.js' import { checkBalance } from '../checkBalance.js' -import { getChainById } from '../getChainById.js' import { stepComparison } from '../stepComparison.js' import type { LiFiStepExtended, - SDKBaseConfig, + SDKClient, StepExecutorOptions, TransactionParameters, } from '../types.js' import { waitForDestinationChainTransaction } from '../waitForDestinationChainTransaction.js' import { getUTXOPublicClient } from './getUTXOPublicClient.js' import { parseUTXOErrors } from './parseUTXOErrors.js' -import type { UTXOProvider } from './types.js' import { generateRedeemScript, isPsbtFinalized, toXOnly } from './utils.js' interface UTXOStepExecutorOptions extends StepExecutorOptions { client: Client - provider: UTXOProvider } export class UTXOStepExecutor extends BaseStepExecutor { private client: Client - private provider: UTXOProvider constructor(options: UTXOStepExecutorOptions) { super(options) this.client = options.client - this.provider = options.provider } checkClient = (step: LiFiStepExtended) => { @@ -56,13 +52,13 @@ export class UTXOStepExecutor extends BaseStepExecutor { } executeStep = async ( - config: SDKBaseConfig, + client: SDKClient, step: LiFiStepExtended ): Promise => { step.execution = this.statusManager.initExecutionObject(step) - const fromChain = await getChainById(config, step.action.fromChainId) - const toChain = await getChainById(config, step.action.toChainId) + const fromChain = await getChainById(client, step.action.fromChainId) + const toChain = await getChainById(client, step.action.toChainId) const isBridgeExecution = fromChain.id !== toChain.id const currentProcessType = isBridgeExecution ? 'CROSS_CHAIN' : 'SWAP' @@ -73,7 +69,7 @@ export class UTXOStepExecutor extends BaseStepExecutor { chainId: fromChain.id, }) - const publicClient = await getUTXOPublicClient(config, ChainId.BTC) + const publicClient = await getUTXOPublicClient(client, ChainId.BTC) if (process.status !== 'DONE') { try { @@ -94,18 +90,16 @@ export class UTXOStepExecutor extends BaseStepExecutor { ) // Check balance - await checkBalance( - config, - [this.provider], - this.client.account!.address, - step - ) + await checkBalance(client, this.client.account!.address, step) // Create new transaction if (!step.transactionRequest) { // biome-ignore lint/correctness/noUnusedVariables: destructuring const { execution, ...stepBase } = step - const updatedStep = await getStepTransaction(config, stepBase) + const updatedStep = await getStepTransaction( + client.config, + stepBase + ) const comparedStep = await stepComparison( this.statusManager, step, @@ -337,7 +331,7 @@ export class UTXOStepExecutor extends BaseStepExecutor { } await waitForDestinationChainTransaction( - config, + client, step, process, fromChain, diff --git a/src/core/UTXO/getUTXOBalance.int.spec.ts b/src/core/UTXO/getUTXOBalance.int.spec.ts index 20866c6a..7d56179f 100644 --- a/src/core/UTXO/getUTXOBalance.int.spec.ts +++ b/src/core/UTXO/getUTXOBalance.int.spec.ts @@ -2,13 +2,15 @@ import { findDefaultToken } from '@lifi/data-types' import type { StaticToken, Token } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' import { describe, expect, it } from 'vitest' -import { createConfig } from '../../createConfig.js' -import type { SDKBaseConfig } from '../types.js' +import { createClient } from '../../client/createClient.js' +import type { SDKClient } from '../types.js' import { getUTXOBalance } from './getUTXOBalance.js' +import { UTXO } from './UTXO.js' -const config = createConfig({ +const client = createClient({ integrator: 'lifi-sdk', }) +client.setProviders([UTXO()]) const defaultWalletAddress = 'bc1q5hx26klsnyqqc9255vuh0s96guz79x0cc54896' @@ -17,12 +19,12 @@ const timeout = 10000 describe('getBalances integration tests', () => { const loadAndCompareTokenAmounts = async ( - config: SDKBaseConfig, + client: SDKClient, walletAddress: string, tokens: StaticToken[] ) => { const tokenBalances = await getUTXOBalance( - config, + client, walletAddress, tokens as Token[] ) @@ -56,7 +58,7 @@ describe('getBalances integration tests', () => { findDefaultToken(CoinKey.USDT, ChainId.POL), ] - await loadAndCompareTokenAmounts(config, walletAddress, tokens) + await loadAndCompareTokenAmounts(client, walletAddress, tokens) } ) }) diff --git a/src/core/UTXO/getUTXOBalance.ts b/src/core/UTXO/getUTXOBalance.ts index 11e3c7ff..3d9cc938 100644 --- a/src/core/UTXO/getUTXOBalance.ts +++ b/src/core/UTXO/getUTXOBalance.ts @@ -1,9 +1,9 @@ import { ChainId, type Token, type TokenAmount } from '@lifi/types' -import type { SDKBaseConfig } from '../types.js' +import type { SDKClient } from '../types.js' import { getUTXOPublicClient } from './getUTXOPublicClient.js' export const getUTXOBalance = async ( - config: SDKBaseConfig, + client: SDKClient, walletAddress: string, tokens: Token[] ): Promise => { @@ -16,10 +16,10 @@ export const getUTXOBalance = async ( console.warn('Requested tokens have to be on the same chain.') } } - const client = await getUTXOPublicClient(config, ChainId.BTC) + const bigmiClient = await getUTXOPublicClient(client, ChainId.BTC) const [balance, blockCount] = await Promise.all([ - client.getBalance({ address: walletAddress }), - client.getBlockCount(), + bigmiClient.getBalance({ address: walletAddress }), + bigmiClient.getBlockCount(), ]) return tokens.map((token) => ({ diff --git a/src/core/UTXO/getUTXOPublicClient.ts b/src/core/UTXO/getUTXOPublicClient.ts index e1b81ca1..76e6ec25 100644 --- a/src/core/UTXO/getUTXOPublicClient.ts +++ b/src/core/UTXO/getUTXOPublicClient.ts @@ -17,9 +17,9 @@ import { type WalletActions, walletActions, } from '@bigmi/core' -import { getChainById } from '../getChainById.js' -import { getRpcUrls } from '../rpc.js' -import type { SDKBaseConfig } from '../types.js' +import { getChainById } from '../../client/getChainById.js' +import { getRpcUrls } from '../../client/getRpcUrls.js' +import type { SDKClient } from '../types.js' import { toBigmiChainId } from './utils.js' type PublicClient = Client< @@ -39,11 +39,11 @@ const publicClients: Record = {} * @returns The public client for the given chain */ export const getUTXOPublicClient = async ( - config: SDKBaseConfig, + client: SDKClient, chainId: number ): Promise => { if (!publicClients[chainId]) { - const urls = await getRpcUrls(config, chainId) + const urls = await getRpcUrls(client, chainId) const fallbackTransports = urls.map((url) => http(url, { fetchOptions: { @@ -51,7 +51,7 @@ export const getUTXOPublicClient = async ( }, }) ) - const _chain = await getChainById(config, chainId) + const _chain = await getChainById(client, chainId) const chain: Chain = { ..._chain, ..._chain.metamask, @@ -62,7 +62,7 @@ export const getUTXOPublicClient = async ( public: { http: _chain.metamask.rpcUrls }, }, } - const client = createClient({ + const bigmiClient = createClient({ chain, rpcSchema: rpcSchema(), transport: fallback([ @@ -75,7 +75,7 @@ export const getUTXOPublicClient = async ( }) .extend(publicActions) .extend(walletActions) - publicClients[chainId] = client + publicClients[chainId] = bigmiClient } if (!publicClients[chainId]) { diff --git a/src/core/checkBalance.ts b/src/core/checkBalance.ts index b55ef36c..44260d08 100644 --- a/src/core/checkBalance.ts +++ b/src/core/checkBalance.ts @@ -3,18 +3,16 @@ import { formatUnits } from 'viem' import { BalanceError } from '../errors/errors.js' import { getTokenBalance } from '../services/balance.js' import { sleep } from '../utils/sleep.js' -import type { SDKBaseConfig, SDKProvider } from './types.js' +import type { SDKClient } from './types.js' export const checkBalance = async ( - config: SDKBaseConfig, - providers: SDKProvider[], + client: SDKClient, walletAddress: string, step: LiFiStep, depth = 0 ): Promise => { const token = await getTokenBalance( - config, - providers, + client, walletAddress, step.action.fromToken ) @@ -25,7 +23,7 @@ export const checkBalance = async ( if (currentBalance < neededBalance) { if (depth <= 3) { await sleep(200) - await checkBalance(config, providers, walletAddress, step, depth + 1) + await checkBalance(client, walletAddress, step, depth + 1) } else if ( (neededBalance * BigInt((1 - (step.action.slippage ?? 0)) * 1_000_000_000)) / diff --git a/src/core/execution.ts b/src/core/execution.ts index 79a62fc9..4f409029 100644 --- a/src/core/execution.ts +++ b/src/core/execution.ts @@ -6,7 +6,7 @@ import { prepareRestart } from './prepareRestart.js' import type { ExecutionOptions, RouteExtended, - SDKBaseConfig, + SDKClient, SDKProvider, } from './types.js' @@ -18,8 +18,7 @@ import type { * @throws {LiFiError} Throws a LiFiError if the execution fails. */ export const executeRoute = async ( - config: SDKBaseConfig, - providers: SDKProvider[], + client: SDKClient, route: Route, executionOptions?: ExecutionOptions ): Promise => { @@ -33,7 +32,7 @@ export const executeRoute = async ( } executionState.create({ route: clonedRoute, executionOptions }) - executionPromise = executeSteps(config, providers, clonedRoute) + executionPromise = executeSteps(client, clonedRoute) executionState.update({ route: clonedRoute, promise: executionPromise, @@ -50,8 +49,7 @@ export const executeRoute = async ( * @throws {LiFiError} Throws a LiFiError if the execution fails. */ export const resumeRoute = async ( - config: SDKBaseConfig, - providers: SDKProvider[], + client: SDKClient, route: Route, executionOptions?: ExecutionOptions ): Promise => { @@ -76,12 +74,11 @@ export const resumeRoute = async ( prepareRestart(route) - return executeRoute(config, providers, route, executionOptions) + return executeRoute(client, route, executionOptions) } const executeSteps = async ( - config: SDKBaseConfig, - providers: SDKProvider[], + client: SDKClient, route: RouteExtended ): Promise => { // Loop over steps and execute them @@ -115,7 +112,7 @@ const executeSteps = async ( throw new Error('Action fromAddress is not specified.') } - const provider = providers.find((provider) => + const provider = client.providers.find((provider: SDKProvider) => provider.isAddress(fromAddress) ) @@ -137,7 +134,7 @@ const executeSteps = async ( updateRouteExecution(route, execution.executionOptions) } - const executedStep = await stepExecutor.executeStep(config, step) + const executedStep = await stepExecutor.executeStep(client, step) // We may reach this point if user interaction isn't allowed. We want to stop execution until we resume it if (executedStep.execution?.status !== 'DONE') { diff --git a/src/core/execution.unit.handlers.ts b/src/core/execution.unit.handlers.ts index ba229387..f00b5946 100644 --- a/src/core/execution.unit.handlers.ts +++ b/src/core/execution.unit.handlers.ts @@ -1,18 +1,18 @@ import { HttpResponse, http } from 'msw' import { buildStepObject } from '../../tests/fixtures.js' -import { createConfig } from '../createConfig.js' +import { createClient } from '../client/createClient.js' import { mockChainsResponse, mockStatus, mockStepTransactionWithTxRequest, } from './execution.unit.mock.js' -const config = createConfig({ +const client = createClient({ integrator: 'lifi-sdk', }) export const lifiHandlers = [ - http.post(`${config.apiUrl}/advanced/stepTransaction`, async () => + http.post(`${client.config.apiUrl}/advanced/stepTransaction`, async () => HttpResponse.json( mockStepTransactionWithTxRequest( buildStepObject({ @@ -21,12 +21,12 @@ export const lifiHandlers = [ ) ) ), - http.get(`${config.apiUrl}/chains`, async () => + http.get(`${client.config.apiUrl}/chains`, async () => HttpResponse.json({ chains: mockChainsResponse, }) ), - http.get(`${config.apiUrl}/status`, async () => + http.get(`${client.config.apiUrl}/status`, async () => HttpResponse.json(mockStatus) ), ] diff --git a/src/core/execution.unit.spec.ts b/src/core/execution.unit.spec.ts index 3cd7e4c8..e6effb76 100644 --- a/src/core/execution.unit.spec.ts +++ b/src/core/execution.unit.spec.ts @@ -12,30 +12,28 @@ import { vi, } from 'vitest' import { buildRouteObject, buildStepObject } from '../../tests/fixtures.js' -import { createConfig } from '../createConfig.js' +import { createClient } from '../client/createClient.js' import { requestSettings } from '../request.js' import { EVM } from './EVM/EVM.js' import { executeRoute } from './execution.js' import { lifiHandlers } from './execution.unit.handlers.js' import { Solana } from './Solana/Solana.js' import { Sui } from './Sui/Sui.js' -import type { SDKProvider } from './types.js' import { UTXO } from './UTXO/UTXO.js' -const config = createConfig({ +const client = createClient({ integrator: 'lifi-sdk', }) +client.setProviders([EVM(), UTXO(), Solana(), Sui()]) -const providers: SDKProvider[] = [EVM(), UTXO(), Solana(), Sui()] - -let client: Partial +let viemClient: Partial vi.mock('../balance', () => ({ checkBalance: vi.fn(() => Promise.resolve([])), })) vi.mock('../execution/switchChain', () => ({ - switchChain: vi.fn(() => Promise.resolve(client)), + switchChain: vi.fn(() => Promise.resolve(viemClient)), })) vi.mock('../allowance/getAllowance', () => ({ @@ -55,7 +53,7 @@ describe.skip('Should pick up gas from wallet client estimation', () => { requestSettings.retries = 0 vi.clearAllMocks() - client = { + viemClient = { sendTransaction: () => Promise.resolve('0xabc'), getChainId: () => Promise.resolve(137), getAddresses: () => @@ -73,9 +71,9 @@ describe.skip('Should pick up gas from wallet client estimation', () => { step, }) - await executeRoute(config, providers, route) + await executeRoute(client, route) - expect(sendTransaction).toHaveBeenCalledWith(client, { + expect(sendTransaction).toHaveBeenCalledWith(viemClient, { gasLimit: 125000n, gasPrice: 100000n, // TODO: Check the cause for gasLimit being outside transactionRequest. Currently working as expected in widget diff --git a/src/core/getChainById.ts b/src/core/getChainById.ts deleted file mode 100644 index 33dd9b23..00000000 --- a/src/core/getChainById.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { ChainId } from '@lifi/types' -import { ChainType } from '@lifi/types' -import { getChains } from '../services/api.js' -import type { SDKBaseConfig } from './types.js' - -export async function getChainById(config: SDKBaseConfig, chainId: ChainId) { - const chains = await getChains(config, { - chainTypes: [ChainType.EVM, ChainType.SVM, ChainType.UTXO, ChainType.MVM], - }) - const chain = chains?.find((chain) => chain.id === chainId) - if (!chain) { - throw new Error(`ChainId ${chainId} not found`) - } - return chain -} diff --git a/src/core/rpc.ts b/src/core/rpc.ts deleted file mode 100644 index f66bed03..00000000 --- a/src/core/rpc.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { ChainId, ChainType, type ExtendedChain } from '@lifi/types' -import { getChains } from '../services/api.js' -import type { RPCUrls, SDKBaseConfig } from './types.js' - -export async function getRpcUrls( - config: SDKBaseConfig, - chainId: ChainId -): Promise { - const chains = await getChains(config, { - chainTypes: [ChainType.EVM, ChainType.SVM, ChainType.UTXO, ChainType.MVM], - }) - const metamaskRpcUrls = getMetamaskRPCUrls(chains) - const rpcUrls = getMergedRPCUrls(config.rpcUrls, metamaskRpcUrls, [ - ChainId.SOL, - ]) - const chainRpcUrls = rpcUrls[chainId] - if (!chainRpcUrls?.length) { - throw new Error(`RPC URL not found for chainId: ${chainId}`) - } - return chainRpcUrls -} - -function getMergedRPCUrls( - configRPCUrls: RPCUrls, - rpcUrls: RPCUrls, - skipChains?: ChainId[] -) { - const newRPCUrls = { ...configRPCUrls } - for (const rpcUrlsKey in rpcUrls) { - const chainId = Number(rpcUrlsKey) as ChainId - const urls = rpcUrls[chainId] - if (!urls?.length) { - continue - } - if (!newRPCUrls[chainId]?.length) { - newRPCUrls[chainId] = Array.from(urls) - } else if (!skipChains?.includes(chainId)) { - const filteredUrls = urls.filter( - (url) => !newRPCUrls[chainId]?.includes(url) - ) - newRPCUrls[chainId].push(...filteredUrls) - } - } - - return newRPCUrls -} - -function getMetamaskRPCUrls(chains: ExtendedChain[]) { - return chains.reduce((rpcUrls, chain) => { - if (chain.metamask?.rpcUrls?.length) { - rpcUrls[chain.id as ChainId] = chain.metamask.rpcUrls - } - return rpcUrls - }, {} as RPCUrls) -} diff --git a/src/core/types.ts b/src/core/types.ts index 066af137..e641d3e0 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -2,6 +2,7 @@ import type { ChainId, ChainType, CoinKey, + ExtendedChain, FeeCost, GasCost, LiFiStep, @@ -38,18 +39,32 @@ export interface SDKProvider { isAddress(address: string): boolean resolveAddress( name: string, - config?: SDKBaseConfig, + client: SDKClient, chainId?: ChainId, token?: CoinKey ): Promise getStepExecutor(options: StepExecutorOptions): Promise getBalance( - config: SDKBaseConfig, + client: SDKClient, walletAddress: string, tokens: Token[] ): Promise } +export interface SDKClient { + config: SDKBaseConfig + providers: SDKProvider[] + getProvider(type: ChainType): SDKProvider | undefined + setProviders(providers: SDKProvider[]): void + _storage: { + chainsUpdatedAt?: number + chains: ExtendedChain[] + rpcUrls: RPCUrls + setChains(chains: ExtendedChain[]): void + setRPCUrls(rpcUrls: RPCUrls, skipChains?: ChainId[]): void + } +} + export interface StepExecutorOptions { routeId: string executionOptions?: ExecutionOptions @@ -66,7 +81,7 @@ export interface StepExecutor { allowExecution: boolean setInteraction(settings?: InteractionSettings): void executeStep( - config: SDKBaseConfig, + client: SDKClient, step: LiFiStepExtended ): Promise } diff --git a/src/core/waitForDestinationChainTransaction.ts b/src/core/waitForDestinationChainTransaction.ts index 3b16776b..27c26f28 100644 --- a/src/core/waitForDestinationChainTransaction.ts +++ b/src/core/waitForDestinationChainTransaction.ts @@ -6,11 +6,11 @@ import type { import { LiFiErrorCode } from '../errors/constants.js' import { getTransactionFailedMessage } from '../utils/getTransactionMessage.js' import type { StatusManager } from './StatusManager.js' -import type { LiFiStepExtended, Process, SDKBaseConfig } from './types.js' +import type { LiFiStepExtended, Process, SDKClient } from './types.js' import { waitForTransactionStatus } from './waitForTransactionStatus.js' export async function waitForDestinationChainTransaction( - config: SDKBaseConfig, + client: SDKClient, step: LiFiStepExtended, process: Process, fromChain: ExtendedChain, @@ -41,7 +41,7 @@ export async function waitForDestinationChainTransaction( } const statusResponse = (await waitForTransactionStatus( - config, + client.config, statusManager, transactionHash, step, @@ -86,7 +86,7 @@ export async function waitForDestinationChainTransaction( return step } catch (e: unknown) { const htmlMessage = await getTransactionFailedMessage( - config, + client, step, `${toChain.metamask.blockExplorerUrls[0]}tx/${transactionHash}` ) diff --git a/src/createConfig.ts b/src/createConfig.ts deleted file mode 100644 index bb22ab00..00000000 --- a/src/createConfig.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { SDKBaseConfig, SDKConfig } from './core/types.js' -import { checkPackageUpdates } from './utils/checkPackageUpdates.js' -import { name, version } from './version.js' - -function initializeConfig(options: SDKConfig) { - const _config: SDKBaseConfig = { - integrator: 'lifi-sdk', - apiUrl: 'https://li.quest/v1', - rpcUrls: {}, - debug: false, - } - Object.assign(_config, options) - return _config -} - -export function createConfig(options: SDKConfig) { - if (!options.integrator) { - throw new Error( - 'Integrator not found. Please see documentation https://docs.li.fi/integrate-li.fi-js-sdk/set-up-the-sdk' - ) - } - const _config = initializeConfig(options) - if (!options.disableVersionCheck && process.env.NODE_ENV === 'development') { - checkPackageUpdates(name, version) - } - return _config -} diff --git a/src/index.ts b/src/index.ts index 4e315ad2..b8ce3f5f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ // biome-ignore lint/performance/noBarrelFile: module entrypoint // biome-ignore lint/performance/noReExportAll: types export * from '@lifi/types' +export { createClient } from './client/createClient.js' export { checkPermitSupport } from './core/EVM/checkPermitSupport.js' export { EVM } from './core/EVM/EVM.js' export { @@ -71,6 +72,7 @@ export type { RouteExtended, RPCUrls, SDKBaseConfig, + SDKClient, SDKConfig, SDKProvider, StepExecutor, @@ -85,7 +87,6 @@ export type { export type { UTXOProvider, UTXOProviderOptions } from './core/UTXO/types.js' export { isUTXO } from './core/UTXO/types.js' export { UTXO } from './core/UTXO/UTXO.js' -export { createConfig } from './createConfig.js' export { BaseError } from './errors/baseError.js' export type { ErrorCode } from './errors/constants.js' export { ErrorMessage, ErrorName, LiFiErrorCode } from './errors/constants.js' diff --git a/src/request.unit.spec.ts b/src/request.unit.spec.ts index 5aa40fef..4910c24a 100644 --- a/src/request.unit.spec.ts +++ b/src/request.unit.spec.ts @@ -10,7 +10,7 @@ import { it, vi, } from 'vitest' -import { createConfig } from './createConfig.js' +import { createClient } from './client/createClient.js' import { ValidationError } from './errors/errors.js' import type { HTTPError } from './errors/httpError.js' import { SDKError } from './errors/SDKError.js' @@ -19,10 +19,11 @@ import { handlers } from './services/api.unit.handlers.js' import type { ExtendedRequestInit } from './types/request.js' import { version } from './version.js' -const config = createConfig({ +const client = createClient({ integrator: 'lifi-sdk', }) -const apiUrl = config.apiUrl +const config = client.config +const apiUrl = client.config.apiUrl describe('request new', () => { const server = setupServer(...handlers) diff --git a/src/services/api.int.spec.ts b/src/services/api.int.spec.ts index 570a65cd..27fe4baf 100644 --- a/src/services/api.int.spec.ts +++ b/src/services/api.int.spec.ts @@ -1,14 +1,14 @@ import { describe, expect, it } from 'vitest' -import { createConfig } from '../createConfig.js' +import { createClient } from '../client/createClient.js' import { getQuote } from './api.js' -const config = createConfig({ +const client = createClient({ integrator: 'lifi-sdk', }) describe('ApiService Integration Tests', () => { it('should successfully request a quote', async () => { - const quote = await getQuote(config, { + const quote = await getQuote(client.config, { fromChain: '1', fromToken: '0x0000000000000000000000000000000000000000', fromAddress: '0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0', diff --git a/src/services/api.unit.handlers.ts b/src/services/api.unit.handlers.ts index 992a932d..3d34bd08 100644 --- a/src/services/api.unit.handlers.ts +++ b/src/services/api.unit.handlers.ts @@ -1,11 +1,12 @@ import { findDefaultToken } from '@lifi/data-types' import { ChainId, CoinKey } from '@lifi/types' import { HttpResponse, http } from 'msw' -import { createConfig } from '../createConfig.js' +import { createClient } from '../client/createClient.js' -const config = createConfig({ +const client = createClient({ integrator: 'lifi-sdk', }) +const config = client.config export const handlers = [ http.post(`${config.apiUrl}/advanced/routes`, async () => { diff --git a/src/services/api.unit.spec.ts b/src/services/api.unit.spec.ts index 8ae3fa25..311d471a 100644 --- a/src/services/api.unit.spec.ts +++ b/src/services/api.unit.spec.ts @@ -22,7 +22,7 @@ import { it, vi, } from 'vitest' -import { createConfig } from '../createConfig.js' +import { createClient } from '../client/createClient.js' import { ValidationError } from '../errors/errors.js' import { SDKError } from '../errors/SDKError.js' import * as request from '../request.js' @@ -30,9 +30,10 @@ import { requestSettings } from '../request.js' import * as ApiService from './api.js' import { handlers } from './api.unit.handlers.js' -const config = createConfig({ +const client = createClient({ integrator: 'lifi-sdk', }) +const config = client.config const mockedFetch = vi.spyOn(request, 'request') diff --git a/src/services/balance.ts b/src/services/balance.ts index 29330379..1d377478 100644 --- a/src/services/balance.ts +++ b/src/services/balance.ts @@ -7,8 +7,8 @@ import type { TokenExtended, WalletTokenExtended, } from '@lifi/types' -import { getChainById } from '../core/getChainById.js' -import type { SDKBaseConfig, SDKProvider } from '../core/types.js' +import { getChainById } from '../client/getChainById.js' +import type { SDKClient, SDKProvider } from '../core/types.js' import { ValidationError } from '../errors/errors.js' import { request } from '../request.js' import { isToken } from '../typeguards.js' @@ -21,17 +21,11 @@ import { isToken } from '../typeguards.js' * @throws {BaseError} Throws a ValidationError if parameters are invalid. */ export const getTokenBalance = async ( - config: SDKBaseConfig, - providers: SDKProvider[], + client: SDKClient, walletAddress: string, token: Token ): Promise => { - const tokenAmounts = await getTokenBalances( - config, - providers, - walletAddress, - [token] - ) + const tokenAmounts = await getTokenBalances(client, walletAddress, [token]) return tokenAmounts.length ? tokenAmounts[0] : null } @@ -43,14 +37,12 @@ export const getTokenBalance = async ( * @throws {BaseError} Throws a ValidationError if parameters are invalid. */ export async function getTokenBalances( - config: SDKBaseConfig, - providers: SDKProvider[], + client: SDKClient, walletAddress: string, tokens: Token[] ): Promise export async function getTokenBalances( - config: SDKBaseConfig, - providers: SDKProvider[], + client: SDKClient, walletAddress: string, tokens: TokenExtended[] ): Promise { @@ -67,8 +59,7 @@ export async function getTokenBalances( ) const tokenAmountsByChain = await getTokenBalancesByChain( - config, - providers, + client, walletAddress, tokensByChain ) @@ -83,14 +74,12 @@ export async function getTokenBalances( * @throws {BaseError} Throws a ValidationError if parameters are invalid. */ export async function getTokenBalancesByChain( - config: SDKBaseConfig, - providers: SDKProvider[], + client: SDKClient, walletAddress: string, tokensByChain: { [chainId: number]: Token[] } ): Promise<{ [chainId: number]: TokenAmount[] }> export async function getTokenBalancesByChain( - config: SDKBaseConfig, - providers: SDKProvider[], + client: SDKClient, walletAddress: string, tokensByChain: { [chainId: number]: TokenExtended[] } ): Promise<{ [chainId: number]: TokenAmountExtended[] }> { @@ -98,13 +87,14 @@ export async function getTokenBalancesByChain( throw new ValidationError('Missing walletAddress.') } + const config = client.config const tokenList = Object.values(tokensByChain).flat() const invalidTokens = tokenList.filter((token) => !isToken(token)) if (invalidTokens.length) { throw new ValidationError('Invalid tokens passed.') } - const provider = providers.find((provider) => + const provider = client.providers.find((provider: SDKProvider) => provider.isAddress(walletAddress) ) if (!provider) { @@ -117,10 +107,10 @@ export async function getTokenBalancesByChain( const tokenAmountsSettled = await Promise.allSettled( Object.keys(tokensByChain).map(async (chainIdStr) => { const chainId = Number.parseInt(chainIdStr, 10) - const chain = await getChainById(config, chainId) + const chain = await getChainById(client, chainId) if (provider.type === chain.chainType) { const tokenAmounts = await provider.getBalance( - config, + client, walletAddress, tokensByChain[chainId] ) @@ -150,7 +140,7 @@ export async function getTokenBalancesByChain( * @throws {BaseError} Throws a ValidationError if parameters are invalid. */ export const getWalletBalances = async ( - config: SDKBaseConfig, + client: SDKClient, walletAddress: string, options?: RequestOptions ): Promise> => { @@ -159,8 +149,8 @@ export const getWalletBalances = async ( } const response = await request( - config, - `${config.apiUrl}/wallets/${walletAddress}/balances?extended=true`, + client.config, + `${client.config.apiUrl}/wallets/${walletAddress}/balances?extended=true`, { signal: options?.signal, } diff --git a/src/services/balance.unit.spec.ts b/src/services/balance.unit.spec.ts index dd3e5437..4fc70655 100644 --- a/src/services/balance.unit.spec.ts +++ b/src/services/balance.unit.spec.ts @@ -2,18 +2,18 @@ import { findDefaultToken } from '@lifi/data-types' import type { Token, WalletTokenExtended } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { createClient } from '../client/createClient.js' import { EVM } from '../core/EVM/EVM.js' import { Solana } from '../core/Solana/Solana.js' import { Sui } from '../core/Sui/Sui.js' -import type { SDKProvider } from '../core/types.js' import { UTXO } from '../core/UTXO/UTXO.js' -import { createConfig } from '../createConfig.js' import * as balance from './balance.js' -const config = createConfig({ +const client = createClient({ integrator: 'lifi-sdk', }) -const providers: SDKProvider[] = [EVM(), UTXO(), Solana(), Sui()] +client.setProviders([EVM(), UTXO(), Solana(), Sui()]) + const mockedGetTokenBalance = vi.spyOn(balance, 'getTokenBalance') const mockedGetTokenBalances = vi.spyOn(balance, 'getTokenBalances') const mockedGetTokenBalancesForChains = vi.spyOn( @@ -36,13 +36,13 @@ describe('Balance service tests', () => { describe('user input is invalid', () => { it('should throw Error because of missing walletAddress', async () => { await expect( - balance.getTokenBalance(config, providers, '', SOME_TOKEN) + balance.getTokenBalance(client, '', SOME_TOKEN) ).rejects.toThrow('Missing walletAddress.') }) it('should throw Error because of invalid token', async () => { await expect( - balance.getTokenBalance(config, providers, SOME_WALLET_ADDRESS, { + balance.getTokenBalance(client, SOME_WALLET_ADDRESS, { address: 'some wrong stuff', chainId: 'not a chain Id', } as unknown as Token) @@ -61,8 +61,7 @@ describe('Balance service tests', () => { mockedGetTokenBalance.mockReturnValue(Promise.resolve(balanceResponse)) const result = await balance.getTokenBalance( - config, - providers, + client, SOME_WALLET_ADDRESS, SOME_TOKEN ) @@ -77,13 +76,13 @@ describe('Balance service tests', () => { describe('user input is invalid', () => { it('should throw Error because of missing walletAddress', async () => { await expect( - balance.getTokenBalances(config, providers, '', [SOME_TOKEN]) + balance.getTokenBalances(client, '', [SOME_TOKEN]) ).rejects.toThrow('Missing walletAddress.') }) it('should throw Error because of an invalid token', async () => { await expect( - balance.getTokenBalances(config, providers, SOME_WALLET_ADDRESS, [ + balance.getTokenBalances(client, SOME_WALLET_ADDRESS, [ SOME_TOKEN, { not: 'a token' } as unknown as Token, ]) @@ -93,8 +92,7 @@ describe('Balance service tests', () => { it('should return empty token list as it is', async () => { mockedGetTokenBalances.mockReturnValue(Promise.resolve([])) const result = await balance.getTokenBalances( - config, - providers, + client, SOME_WALLET_ADDRESS, [] ) @@ -116,8 +114,7 @@ describe('Balance service tests', () => { mockedGetTokenBalances.mockReturnValue(Promise.resolve(balanceResponse)) const result = await balance.getTokenBalances( - config, - providers, + client, SOME_WALLET_ADDRESS, [SOME_TOKEN] ) @@ -132,7 +129,7 @@ describe('Balance service tests', () => { describe('user input is invalid', () => { it('should throw Error because of missing walletAddress', async () => { await expect( - balance.getTokenBalancesByChain(config, providers, '', { + balance.getTokenBalancesByChain(client, '', { [ChainId.DAI]: [SOME_TOKEN], }) ).rejects.toThrow('Missing walletAddress.') @@ -140,14 +137,9 @@ describe('Balance service tests', () => { it('should throw Error because of an invalid token', async () => { await expect( - balance.getTokenBalancesByChain( - config, - providers, - SOME_WALLET_ADDRESS, - { - [ChainId.DAI]: [{ not: 'a token' } as unknown as Token], - } - ) + balance.getTokenBalancesByChain(client, SOME_WALLET_ADDRESS, { + [ChainId.DAI]: [{ not: 'a token' } as unknown as Token], + }) ).rejects.toThrow('Invalid tokens passed.') }) @@ -155,8 +147,7 @@ describe('Balance service tests', () => { mockedGetTokenBalancesForChains.mockReturnValue(Promise.resolve([])) const result = await balance.getTokenBalancesByChain( - config, - providers, + client, SOME_WALLET_ADDRESS, { [ChainId.DAI]: [], @@ -185,8 +176,7 @@ describe('Balance service tests', () => { ) const result = await balance.getTokenBalancesByChain( - config, - providers, + client, SOME_WALLET_ADDRESS, { [ChainId.DAI]: [SOME_TOKEN], @@ -209,8 +199,7 @@ describe('Balance service tests', () => { ) const result = await balance.getTokenBalancesByChain( - config, - providers, + client, SOME_WALLET_ADDRESS, { [ChainId.DAI]: [SOME_TOKEN], @@ -226,7 +215,7 @@ describe('Balance service tests', () => { describe('getWalletBalances', () => { describe('user input is invalid', () => { it('should throw Error because of missing walletAddress', async () => { - await expect(balance.getWalletBalances(config, '')).rejects.toThrow( + await expect(balance.getWalletBalances(client, '')).rejects.toThrow( 'Missing walletAddress.' ) }) @@ -251,13 +240,13 @@ describe('Balance service tests', () => { ) const result = await balance.getWalletBalances( - config, + client, SOME_WALLET_ADDRESS ) expect(mockedGetWalletBalances).toHaveBeenCalledTimes(1) expect(mockedGetWalletBalances).toHaveBeenCalledWith( - config, + client, SOME_WALLET_ADDRESS ) expect(result).toEqual(balanceResponse) @@ -283,14 +272,14 @@ describe('Balance service tests', () => { ) const result = await balance.getWalletBalances( - config, + client, SOME_WALLET_ADDRESS, options ) expect(mockedGetWalletBalances).toHaveBeenCalledTimes(1) expect(mockedGetWalletBalances).toHaveBeenCalledWith( - config, + client, SOME_WALLET_ADDRESS, options ) diff --git a/src/services/getNameServiceAddress.ts b/src/services/getNameServiceAddress.ts index 595752f0..cecc24de 100644 --- a/src/services/getNameServiceAddress.ts +++ b/src/services/getNameServiceAddress.ts @@ -1,13 +1,13 @@ import type { ChainType } from '@lifi/types' -import type { SDKProvider } from '../core/types.js' +import type { SDKClient } from '../core/types.js' export const getNameServiceAddress = async ( - allProviders: SDKProvider[], + client: SDKClient, name: string, chainType?: ChainType ): Promise => { try { - let providers = [...allProviders] + let providers = client.providers if (chainType) { providers = providers.filter((provider) => provider.type === chainType) } @@ -17,7 +17,7 @@ export const getNameServiceAddress = async ( } const result = await Promise.any( resolvers.map(async (resolve) => { - const address = await resolve(name) + const address = await resolve(name, client) if (!address) { throw undefined } diff --git a/src/utils/getTransactionMessage.ts b/src/utils/getTransactionMessage.ts index 13a45f78..b74eeda9 100644 --- a/src/utils/getTransactionMessage.ts +++ b/src/utils/getTransactionMessage.ts @@ -1,13 +1,13 @@ import type { LiFiStep } from '@lifi/types' -import { getChainById } from '../core/getChainById.js' -import type { SDKBaseConfig } from '../core/types.js' +import { getChainById } from '../client/getChainById.js' +import type { SDKClient } from '../core/types.js' export const getTransactionFailedMessage = async ( - config: SDKBaseConfig, + client: SDKClient, step: LiFiStep, txLink?: string ): Promise => { - const chain = await getChainById(config, step.action.toChainId) + const chain = await getChainById(client, step.action.toChainId) const baseString = `It appears that your transaction may not have been successful. However, to confirm this, please check your ${chain.name} wallet for ${step.action.toToken.symbol}.` diff --git a/src/version.ts b/src/version.ts index 57a76f39..44280bf7 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1,2 +1,2 @@ export const name = '@lifi/sdk' -export const version = '4.0.0' +export const version = '3.12.14' From 4462792d0807df9b78b51b020f4a4a2605051750 Mon Sep 17 00:00:00 2001 From: Lizaveta Miasayedava Date: Wed, 22 Oct 2025 12:35:44 +0100 Subject: [PATCH 11/13] refactor: revamp client structure --- src/client/createClient.ts | 84 -------------------- src/client/getChainById.ts | 12 --- src/client/getChainsFromCache.ts | 18 ----- src/client/getRpcUrls.ts | 17 ---- src/core/EVM/EVMStepExecutor.ts | 5 +- src/core/EVM/getAllowance.int.spec.ts | 2 +- src/core/EVM/getEVMBalance.int.spec.ts | 2 +- src/core/EVM/publicClient.ts | 6 +- src/core/EVM/setAllowance.int.spec.ts | 2 +- src/core/Solana/SolanaStepExecutor.ts | 5 +- src/core/Solana/connection.ts | 3 +- src/core/Solana/getSolanaBalance.int.spec.ts | 2 +- src/core/Sui/SuiStepExecutor.ts | 5 +- src/core/Sui/getSuiBalance.int.spec.ts | 2 +- src/core/Sui/suiClient.ts | 3 +- src/core/UTXO/UTXOStepExecutor.ts | 5 +- src/core/UTXO/getUTXOBalance.int.spec.ts | 2 +- src/core/UTXO/getUTXOPublicClient.ts | 6 +- src/core/client/createClient.ts | 73 +++++++++++++++++ src/core/client/getClientStorage.ts | 40 ++++++++++ src/core/execution.unit.handlers.ts | 2 +- src/core/execution.unit.spec.ts | 2 +- src/core/types.ts | 11 +-- src/core/utils.ts | 31 +++++++- src/index.ts | 2 +- src/request.unit.spec.ts | 2 +- src/services/api.int.spec.ts | 2 +- src/services/api.unit.handlers.ts | 2 +- src/services/api.unit.spec.ts | 2 +- src/services/balance.ts | 3 +- src/services/balance.unit.spec.ts | 2 +- src/utils/getTransactionMessage.ts | 3 +- 32 files changed, 177 insertions(+), 181 deletions(-) delete mode 100644 src/client/createClient.ts delete mode 100644 src/client/getChainById.ts delete mode 100644 src/client/getChainsFromCache.ts delete mode 100644 src/client/getRpcUrls.ts create mode 100644 src/core/client/createClient.ts create mode 100644 src/core/client/getClientStorage.ts diff --git a/src/client/createClient.ts b/src/client/createClient.ts deleted file mode 100644 index f495351a..00000000 --- a/src/client/createClient.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { ChainId, type ChainType, type ExtendedChain } from '@lifi/types' -import type { - RPCUrls, - SDKBaseConfig, - SDKClient, - SDKConfig, - SDKProvider, -} from '../core/types.js' -import { checkPackageUpdates } from '../utils/checkPackageUpdates.js' -import { name, version } from '../version.js' - -export function createClient(options: SDKConfig): SDKClient { - if (!options.integrator) { - throw new Error( - 'Integrator not found. Please see documentation https://docs.li.fi/integrate-li.fi-js-sdk/set-up-the-sdk' - ) - } - - if (!options.disableVersionCheck && process.env.NODE_ENV === 'development') { - checkPackageUpdates(name, version) - } - - const config: SDKBaseConfig = { - ...options, - apiUrl: options?.apiUrl ?? 'https://li.quest/v1', - rpcUrls: options?.rpcUrls ?? {}, - debug: options?.debug ?? false, - integrator: options?.integrator ?? 'lifi-sdk', - } - - let _providers: SDKProvider[] = [] - let _chains: ExtendedChain[] = [] - const _rpcUrls: RPCUrls = { ...config.rpcUrls } - - return { - config, - providers: _providers, - getProvider(type: ChainType) { - return _providers.find((provider) => provider.type === type) - }, - setProviders(newProviders: SDKProvider[]) { - const providerMap = new Map( - _providers.map((provider) => [provider.type, provider]) - ) - for (const provider of newProviders) { - providerMap.set(provider.type, provider) - } - _providers = Array.from(providerMap.values()) - }, - // Config cache - _storage: { - chainsUpdatedAt: undefined, - chains: _chains, - rpcUrls: _rpcUrls, - setChains(chains: ExtendedChain[]) { - const rpcUrls = chains.reduce((rpcUrls, chain) => { - if (chain.metamask?.rpcUrls?.length) { - _rpcUrls[chain.id as ChainId] = chain.metamask.rpcUrls - } - return rpcUrls - }, {} as RPCUrls) - this.setRPCUrls(rpcUrls, [ChainId.SOL]) - _chains = chains - }, - setRPCUrls(rpcUrls: RPCUrls, skipChains?: ChainId[]) { - for (const rpcUrlsKey in rpcUrls) { - const chainId = Number(rpcUrlsKey) as ChainId - const urls = rpcUrls[chainId] - if (!urls?.length) { - continue - } - if (!_rpcUrls[chainId]?.length) { - _rpcUrls[chainId] = Array.from(urls) - } else if (!skipChains?.includes(chainId)) { - const filteredUrls = urls.filter( - (url) => !_rpcUrls[chainId]?.includes(url) - ) - _rpcUrls[chainId].push(...filteredUrls) - } - } - }, - }, - } as SDKClient -} diff --git a/src/client/getChainById.ts b/src/client/getChainById.ts deleted file mode 100644 index c5e47ad9..00000000 --- a/src/client/getChainById.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { ChainId } from '@lifi/types' -import type { SDKClient } from '../core/types.js' -import { getChainsFromCache } from './getChainsFromCache.js' - -export async function getChainById(client: SDKClient, chainId: ChainId) { - const chains = await getChainsFromCache(client) - const chain = chains?.find((chain) => chain.id === chainId) - if (!chain) { - throw new Error(`ChainId ${chainId} not found`) - } - return chain -} diff --git a/src/client/getChainsFromCache.ts b/src/client/getChainsFromCache.ts deleted file mode 100644 index dc54df98..00000000 --- a/src/client/getChainsFromCache.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ChainType } from '@lifi/types' -import type { SDKClient } from '../core/types.js' -import { getChains } from '../services/api.js' - -export async function getChainsFromCache(client: SDKClient) { - let chains = client._storage?.chains - // Update chains in config cache every 24 hours - if ( - !client._storage.chainsUpdatedAt || - Date.now() - client._storage.chainsUpdatedAt >= 1000 * 60 * 60 * 24 - ) { - chains = await getChains(client.config, { - chainTypes: [ChainType.EVM, ChainType.SVM, ChainType.UTXO, ChainType.MVM], - }) - client._storage.setChains(chains) - } - return chains -} diff --git a/src/client/getRpcUrls.ts b/src/client/getRpcUrls.ts deleted file mode 100644 index 423d14ce..00000000 --- a/src/client/getRpcUrls.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { ChainId } from '@lifi/types' -import type { SDKClient } from '../core/types.js' -import { getChainsFromCache } from './getChainsFromCache.js' - -export async function getRpcUrls( - client: SDKClient, - chainId: ChainId -): Promise { - // Make sure chains are up to date so we can get fresh RPC URLs - await getChainsFromCache(client) - - const chainRpcUrls = client._storage.rpcUrls[chainId] - if (!chainRpcUrls?.length) { - throw new Error(`RPC URL not found for chainId: ${chainId}`) - } - return chainRpcUrls -} diff --git a/src/core/EVM/EVMStepExecutor.ts b/src/core/EVM/EVMStepExecutor.ts index 8968313f..8ceff7dd 100644 --- a/src/core/EVM/EVMStepExecutor.ts +++ b/src/core/EVM/EVMStepExecutor.ts @@ -16,7 +16,6 @@ import { signTypedData, } from 'viem/actions' import { getAction, isHex } from 'viem/utils' -import { getChainById } from '../../client/getChainById.js' import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' import { @@ -398,8 +397,8 @@ export class EVMStepExecutor extends BaseStepExecutor { } } - const fromChain = await getChainById(client, step.action.fromChainId) - const toChain = await getChainById(client, step.action.toChainId) + const fromChain = await client.getChainById(step.action.fromChainId) + const toChain = await client.getChainById(step.action.toChainId) // Check if the wallet supports atomic batch transactions (EIP-5792) const calls: Call[] = [] diff --git a/src/core/EVM/getAllowance.int.spec.ts b/src/core/EVM/getAllowance.int.spec.ts index 48a396e1..16e773dc 100644 --- a/src/core/EVM/getAllowance.int.spec.ts +++ b/src/core/EVM/getAllowance.int.spec.ts @@ -2,8 +2,8 @@ import { findDefaultToken } from '@lifi/data-types' import { ChainId, CoinKey } from '@lifi/types' import type { Address } from 'viem' import { describe, expect, it } from 'vitest' -import { createClient } from '../../client/createClient.js' import { getTokens } from '../../services/api.js' +import { createClient } from '../client/createClient.js' import { EVM } from './EVM.js' import { getAllowance, diff --git a/src/core/EVM/getEVMBalance.int.spec.ts b/src/core/EVM/getEVMBalance.int.spec.ts index 7aa9672c..eb740fbb 100644 --- a/src/core/EVM/getEVMBalance.int.spec.ts +++ b/src/core/EVM/getEVMBalance.int.spec.ts @@ -3,8 +3,8 @@ import type { StaticToken, Token } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' import type { Address } from 'viem' import { describe, expect, it } from 'vitest' -import { createClient } from '../../client/createClient.js' import { getTokens } from '../../services/api.js' +import { createClient } from '../client/createClient.js' import { EVM } from './EVM.js' import { getEVMBalance } from './getEVMBalance.js' diff --git a/src/core/EVM/publicClient.ts b/src/core/EVM/publicClient.ts index 1029a751..889ab0be 100644 --- a/src/core/EVM/publicClient.ts +++ b/src/core/EVM/publicClient.ts @@ -2,8 +2,6 @@ import { ChainId, ChainType } from '@lifi/types' import type { Client } from 'viem' import { type Address, createClient, fallback, http, webSocket } from 'viem' import { type Chain, mainnet } from 'viem/chains' -import { getChainById } from '../../client/getChainById.js' -import { getRpcUrls } from '../../client/getRpcUrls.js' import type { SDKClient } from '../types.js' import type { EVMProvider } from './types.js' import { UNS_PROXY_READER_ADDRESSES } from './uns/constants.js' @@ -24,7 +22,7 @@ export const getPublicClient = async ( return publicClients[chainId] } - const urls = await getRpcUrls(client, chainId) + const urls = await client.getRpcUrlsByChainId(chainId) const fallbackTransports = urls.map((url) => url.startsWith('wss') ? webSocket(url) @@ -34,7 +32,7 @@ export const getPublicClient = async ( }, }) ) - const _chain = await getChainById(client, chainId) + const _chain = await client.getChainById(chainId) const chain: Chain = { ..._chain, ..._chain.metamask, diff --git a/src/core/EVM/setAllowance.int.spec.ts b/src/core/EVM/setAllowance.int.spec.ts index 466efdc3..e5abdadb 100644 --- a/src/core/EVM/setAllowance.int.spec.ts +++ b/src/core/EVM/setAllowance.int.spec.ts @@ -4,7 +4,7 @@ import { mnemonicToAccount } from 'viem/accounts' import { waitForTransactionReceipt } from 'viem/actions' import { polygon } from 'viem/chains' import { describe, expect, it } from 'vitest' -import { createClient } from '../../client/createClient.js' +import { createClient } from '../client/createClient.js' import { EVM } from './EVM.js' import { revokeTokenApproval, setTokenAllowance } from './setAllowance.js' import { retryCount, retryDelay } from './utils.js' diff --git a/src/core/Solana/SolanaStepExecutor.ts b/src/core/Solana/SolanaStepExecutor.ts index 4a8bdc2d..72fe52a8 100644 --- a/src/core/Solana/SolanaStepExecutor.ts +++ b/src/core/Solana/SolanaStepExecutor.ts @@ -1,7 +1,6 @@ import type { SignerWalletAdapter } from '@solana/wallet-adapter-base' import { VersionedTransaction } from '@solana/web3.js' import { withTimeout } from 'viem' -import { getChainById } from '../../client/getChainById.js' import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' import { getStepTransaction } from '../../services/api.js' @@ -48,8 +47,8 @@ export class SolanaStepExecutor extends BaseStepExecutor { ): Promise => { step.execution = this.statusManager.initExecutionObject(step) - const fromChain = await getChainById(client, step.action.fromChainId) - const toChain = await getChainById(client, step.action.toChainId) + const fromChain = await client.getChainById(step.action.fromChainId) + const toChain = await client.getChainById(step.action.toChainId) const isBridgeExecution = fromChain.id !== toChain.id const currentProcessType = isBridgeExecution ? 'CROSS_CHAIN' : 'SWAP' diff --git a/src/core/Solana/connection.ts b/src/core/Solana/connection.ts index 8a76dc7b..c75d0267 100644 --- a/src/core/Solana/connection.ts +++ b/src/core/Solana/connection.ts @@ -1,6 +1,5 @@ import { ChainId } from '@lifi/types' import { Connection } from '@solana/web3.js' -import { getRpcUrls } from '../../client/getRpcUrls.js' import type { SDKClient } from '../types.js' const connections = new Map() @@ -10,7 +9,7 @@ const connections = new Map() * @returns - Promise that resolves when connections are initialized. */ const ensureConnections = async (client: SDKClient): Promise => { - const rpcUrls = await getRpcUrls(client, ChainId.SOL) + const rpcUrls = await client.getRpcUrlsByChainId(ChainId.SOL) for (const rpcUrl of rpcUrls) { if (!connections.get(rpcUrl)) { const connection = new Connection(rpcUrl) diff --git a/src/core/Solana/getSolanaBalance.int.spec.ts b/src/core/Solana/getSolanaBalance.int.spec.ts index 6aea0ae1..22a2bafb 100644 --- a/src/core/Solana/getSolanaBalance.int.spec.ts +++ b/src/core/Solana/getSolanaBalance.int.spec.ts @@ -2,7 +2,7 @@ import { findDefaultToken } from '@lifi/data-types' import type { StaticToken, Token } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' import { describe, expect, it } from 'vitest' -import { createClient } from '../../client/createClient.js' +import { createClient } from '../client/createClient.js' import { getSolanaBalance } from './getSolanaBalance.js' import { Solana } from './Solana.js' diff --git a/src/core/Sui/SuiStepExecutor.ts b/src/core/Sui/SuiStepExecutor.ts index f5ddc285..90af12f8 100644 --- a/src/core/Sui/SuiStepExecutor.ts +++ b/src/core/Sui/SuiStepExecutor.ts @@ -2,7 +2,6 @@ import { signAndExecuteTransaction, type WalletWithRequiredFeatures, } from '@mysten/wallet-standard' -import { getChainById } from '../../client/getChainById.js' import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' import { getStepTransaction } from '../../services/api.js' @@ -51,8 +50,8 @@ export class SuiStepExecutor extends BaseStepExecutor { ): Promise => { step.execution = this.statusManager.initExecutionObject(step) - const fromChain = await getChainById(client, step.action.fromChainId) - const toChain = await getChainById(client, step.action.toChainId) + const fromChain = await client.getChainById(step.action.fromChainId) + const toChain = await client.getChainById(step.action.toChainId) const isBridgeExecution = fromChain.id !== toChain.id const currentProcessType = isBridgeExecution ? 'CROSS_CHAIN' : 'SWAP' diff --git a/src/core/Sui/getSuiBalance.int.spec.ts b/src/core/Sui/getSuiBalance.int.spec.ts index 8f081e4f..c4383726 100644 --- a/src/core/Sui/getSuiBalance.int.spec.ts +++ b/src/core/Sui/getSuiBalance.int.spec.ts @@ -2,7 +2,7 @@ import { findDefaultToken } from '@lifi/data-types' import type { StaticToken, Token } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' import { describe, expect, it } from 'vitest' -import { createClient } from '../../client/createClient.js' +import { createClient } from '../client/createClient.js' import { getSuiBalance } from './getSuiBalance.js' const client = createClient({ diff --git a/src/core/Sui/suiClient.ts b/src/core/Sui/suiClient.ts index dc1dbcf8..7de988e4 100644 --- a/src/core/Sui/suiClient.ts +++ b/src/core/Sui/suiClient.ts @@ -1,6 +1,5 @@ import { ChainId } from '@lifi/types' import { SuiClient } from '@mysten/sui/client' -import { getRpcUrls } from '../../client/getRpcUrls.js' import type { SDKClient } from '../types.js' const clients = new Map() @@ -10,7 +9,7 @@ const clients = new Map() * @returns - Promise that resolves when clients are initialized. */ const ensureClients = async (client: SDKClient): Promise => { - const rpcUrls = await getRpcUrls(client, ChainId.SUI) + const rpcUrls = await client.getRpcUrlsByChainId(ChainId.SUI) for (const rpcUrl of rpcUrls) { if (!clients.get(rpcUrl)) { const client = new SuiClient({ url: rpcUrl }) diff --git a/src/core/UTXO/UTXOStepExecutor.ts b/src/core/UTXO/UTXOStepExecutor.ts index 9e9547a5..75213123 100644 --- a/src/core/UTXO/UTXOStepExecutor.ts +++ b/src/core/UTXO/UTXOStepExecutor.ts @@ -10,7 +10,6 @@ import { import * as ecc from '@bitcoinerlab/secp256k1' import { ChainId } from '@lifi/types' import { address, initEccLib, networks, Psbt } from 'bitcoinjs-lib' -import { getChainById } from '../../client/getChainById.js' import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' import { getStepTransaction } from '../../services/api.js' @@ -57,8 +56,8 @@ export class UTXOStepExecutor extends BaseStepExecutor { ): Promise => { step.execution = this.statusManager.initExecutionObject(step) - const fromChain = await getChainById(client, step.action.fromChainId) - const toChain = await getChainById(client, step.action.toChainId) + const fromChain = await client.getChainById(step.action.fromChainId) + const toChain = await client.getChainById(step.action.toChainId) const isBridgeExecution = fromChain.id !== toChain.id const currentProcessType = isBridgeExecution ? 'CROSS_CHAIN' : 'SWAP' diff --git a/src/core/UTXO/getUTXOBalance.int.spec.ts b/src/core/UTXO/getUTXOBalance.int.spec.ts index 7d56179f..9548f12d 100644 --- a/src/core/UTXO/getUTXOBalance.int.spec.ts +++ b/src/core/UTXO/getUTXOBalance.int.spec.ts @@ -2,7 +2,7 @@ import { findDefaultToken } from '@lifi/data-types' import type { StaticToken, Token } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' import { describe, expect, it } from 'vitest' -import { createClient } from '../../client/createClient.js' +import { createClient } from '../client/createClient.js' import type { SDKClient } from '../types.js' import { getUTXOBalance } from './getUTXOBalance.js' import { UTXO } from './UTXO.js' diff --git a/src/core/UTXO/getUTXOPublicClient.ts b/src/core/UTXO/getUTXOPublicClient.ts index 76e6ec25..e78c0ce9 100644 --- a/src/core/UTXO/getUTXOPublicClient.ts +++ b/src/core/UTXO/getUTXOPublicClient.ts @@ -17,8 +17,6 @@ import { type WalletActions, walletActions, } from '@bigmi/core' -import { getChainById } from '../../client/getChainById.js' -import { getRpcUrls } from '../../client/getRpcUrls.js' import type { SDKClient } from '../types.js' import { toBigmiChainId } from './utils.js' @@ -43,7 +41,7 @@ export const getUTXOPublicClient = async ( chainId: number ): Promise => { if (!publicClients[chainId]) { - const urls = await getRpcUrls(client, chainId) + const urls = await client.getRpcUrlsByChainId(chainId) const fallbackTransports = urls.map((url) => http(url, { fetchOptions: { @@ -51,7 +49,7 @@ export const getUTXOPublicClient = async ( }, }) ) - const _chain = await getChainById(client, chainId) + const _chain = await client.getChainById(chainId) const chain: Chain = { ..._chain, ..._chain.metamask, diff --git a/src/core/client/createClient.ts b/src/core/client/createClient.ts new file mode 100644 index 00000000..54a0b232 --- /dev/null +++ b/src/core/client/createClient.ts @@ -0,0 +1,73 @@ +import type { ChainId, ChainType } from '@lifi/types' +import { checkPackageUpdates } from '../../utils/checkPackageUpdates.js' +import { name, version } from '../../version.js' +import type { + SDKBaseConfig, + SDKClient, + SDKConfig, + SDKProvider, +} from '../types.js' +import { getClientStorage } from './getClientStorage.js' + +export function createClient(options: SDKConfig): SDKClient { + if (!options.integrator) { + throw new Error( + 'Integrator not found. Please see documentation https://docs.li.fi/integrate-li.fi-js-sdk/set-up-the-sdk' + ) + } + + if (!options.disableVersionCheck && process.env.NODE_ENV === 'development') { + checkPackageUpdates(name, version) + } + + const config: SDKBaseConfig = { + ...options, + apiUrl: options?.apiUrl ?? 'https://li.quest/v1', + rpcUrls: options?.rpcUrls ?? {}, + debug: options?.debug ?? false, + integrator: options?.integrator ?? 'lifi-sdk', + } + + let _providers: SDKProvider[] = [] + + const _storage = getClientStorage(config) + + return { + config, + providers: _providers, + getProvider(type: ChainType) { + return _providers.find((provider) => provider.type === type) + }, + setProviders(newProviders: SDKProvider[]) { + const providerMap = new Map( + _providers.map((provider) => [provider.type, provider]) + ) + for (const provider of newProviders) { + providerMap.set(provider.type, provider) + } + _providers = Array.from(providerMap.values()) + }, + async getChains() { + return await _storage.getChains() + }, + async getChainById(chainId: ChainId) { + const chains = await this.getChains() + const chain = chains?.find((chain) => chain.id === chainId) + if (!chain) { + throw new Error(`ChainId ${chainId} not found`) + } + return chain + }, + async getRpcUrls() { + return await _storage.getRpcUrls() + }, + async getRpcUrlsByChainId(chainId: ChainId) { + const rpcUrls = await this.getRpcUrls() + const chainRpcUrls = rpcUrls[chainId] + if (!chainRpcUrls?.length) { + throw new Error(`RPC URL not found for chainId: ${chainId}`) + } + return chainRpcUrls + }, + } as SDKClient +} diff --git a/src/core/client/getClientStorage.ts b/src/core/client/getClientStorage.ts new file mode 100644 index 00000000..36d36a27 --- /dev/null +++ b/src/core/client/getClientStorage.ts @@ -0,0 +1,40 @@ +import { ChainId, ChainType, type ExtendedChain } from '@lifi/types' +import { getChains } from '../../services/api.js' +import type { RPCUrls, SDKBaseConfig } from '../types.js' +import { getRpcUrlsFromChains } from '../utils.js' + +export const getClientStorage = (config: SDKBaseConfig) => { + let _chains = [] as ExtendedChain[] + let _rpcUrls = { ...config.rpcUrls } as RPCUrls + let _chainsUpdatedAt: number | undefined + + return { + get needReset() { + return ( + !_chainsUpdatedAt || + Date.now() - _chainsUpdatedAt >= 1000 * 60 * 60 * 24 + ) + }, + async getChains() { + if (this.needReset || !_chains.length) { + _chains = await getChains(config, { + chainTypes: [ + ChainType.EVM, + ChainType.SVM, + ChainType.UTXO, + ChainType.MVM, + ], + }) + _chainsUpdatedAt = Date.now() + } + return _chains + }, + async getRpcUrls() { + if (this.needReset || !Object.keys(_rpcUrls).length) { + const chains = await this.getChains() + _rpcUrls = getRpcUrlsFromChains(_rpcUrls, chains, [ChainId.SOL]) + } + return _rpcUrls + }, + } +} diff --git a/src/core/execution.unit.handlers.ts b/src/core/execution.unit.handlers.ts index f00b5946..034355a2 100644 --- a/src/core/execution.unit.handlers.ts +++ b/src/core/execution.unit.handlers.ts @@ -1,6 +1,6 @@ import { HttpResponse, http } from 'msw' import { buildStepObject } from '../../tests/fixtures.js' -import { createClient } from '../client/createClient.js' +import { createClient } from './client/createClient.js' import { mockChainsResponse, mockStatus, diff --git a/src/core/execution.unit.spec.ts b/src/core/execution.unit.spec.ts index e6effb76..38f64279 100644 --- a/src/core/execution.unit.spec.ts +++ b/src/core/execution.unit.spec.ts @@ -12,8 +12,8 @@ import { vi, } from 'vitest' import { buildRouteObject, buildStepObject } from '../../tests/fixtures.js' -import { createClient } from '../client/createClient.js' import { requestSettings } from '../request.js' +import { createClient } from './client/createClient.js' import { EVM } from './EVM/EVM.js' import { executeRoute } from './execution.js' import { lifiHandlers } from './execution.unit.handlers.js' diff --git a/src/core/types.ts b/src/core/types.ts index e641d3e0..7635fde8 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -56,13 +56,10 @@ export interface SDKClient { providers: SDKProvider[] getProvider(type: ChainType): SDKProvider | undefined setProviders(providers: SDKProvider[]): void - _storage: { - chainsUpdatedAt?: number - chains: ExtendedChain[] - rpcUrls: RPCUrls - setChains(chains: ExtendedChain[]): void - setRPCUrls(rpcUrls: RPCUrls, skipChains?: ChainId[]): void - } + getChains(): Promise + getChainById(chainId: ChainId): Promise + getRpcUrls(): Promise + getRpcUrlsByChainId(chainId: ChainId): Promise } export interface StepExecutorOptions { diff --git a/src/core/utils.ts b/src/core/utils.ts index c9b5f5a4..dcab6e1b 100644 --- a/src/core/utils.ts +++ b/src/core/utils.ts @@ -1,4 +1,5 @@ -import type { LiFiStep } from '@lifi/types' +import type { ChainId, ExtendedChain, LiFiStep } from '@lifi/types' +import type { RPCUrls } from '../core/types.js' // Standard threshold for destination amount difference (0.5%) const standardThreshold = 0.005 @@ -28,3 +29,31 @@ export function checkStepSlippageThreshold( } return actualSlippage <= setSlippage } + +export function getRpcUrlsFromChains( + existingRpcUrls: RPCUrls, + chains: ExtendedChain[], + skipChains?: ChainId[] +) { + const rpcUrlsFromChains = chains.reduce((rpcUrls, chain) => { + if (chain.metamask?.rpcUrls?.length) { + rpcUrls[chain.id as ChainId] = chain.metamask.rpcUrls + } + return rpcUrls + }, {} as RPCUrls) + const result = { ...existingRpcUrls } + for (const rpcUrlsKey in rpcUrlsFromChains) { + const chainId = Number(rpcUrlsKey) as ChainId + const urls = rpcUrlsFromChains[chainId] + if (!urls?.length) { + continue + } + if (!result[chainId]?.length) { + result[chainId] = Array.from(urls) + } else if (!skipChains?.includes(chainId)) { + const filteredUrls = urls.filter((url) => !result[chainId]?.includes(url)) + result[chainId].push(...filteredUrls) + } + } + return result +} diff --git a/src/index.ts b/src/index.ts index b8ce3f5f..0e21b221 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ // biome-ignore lint/performance/noBarrelFile: module entrypoint // biome-ignore lint/performance/noReExportAll: types export * from '@lifi/types' -export { createClient } from './client/createClient.js' +export { createClient } from './core/client/createClient.js' export { checkPermitSupport } from './core/EVM/checkPermitSupport.js' export { EVM } from './core/EVM/EVM.js' export { diff --git a/src/request.unit.spec.ts b/src/request.unit.spec.ts index 4910c24a..bda41369 100644 --- a/src/request.unit.spec.ts +++ b/src/request.unit.spec.ts @@ -10,7 +10,7 @@ import { it, vi, } from 'vitest' -import { createClient } from './client/createClient.js' +import { createClient } from './core/client/createClient.js' import { ValidationError } from './errors/errors.js' import type { HTTPError } from './errors/httpError.js' import { SDKError } from './errors/SDKError.js' diff --git a/src/services/api.int.spec.ts b/src/services/api.int.spec.ts index 27fe4baf..4965e29b 100644 --- a/src/services/api.int.spec.ts +++ b/src/services/api.int.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { createClient } from '../client/createClient.js' +import { createClient } from '../core/client/createClient.js' import { getQuote } from './api.js' const client = createClient({ diff --git a/src/services/api.unit.handlers.ts b/src/services/api.unit.handlers.ts index 3d34bd08..4bc0ed1d 100644 --- a/src/services/api.unit.handlers.ts +++ b/src/services/api.unit.handlers.ts @@ -1,7 +1,7 @@ import { findDefaultToken } from '@lifi/data-types' import { ChainId, CoinKey } from '@lifi/types' import { HttpResponse, http } from 'msw' -import { createClient } from '../client/createClient.js' +import { createClient } from '../core/client/createClient.js' const client = createClient({ integrator: 'lifi-sdk', diff --git a/src/services/api.unit.spec.ts b/src/services/api.unit.spec.ts index 311d471a..9fe94ac7 100644 --- a/src/services/api.unit.spec.ts +++ b/src/services/api.unit.spec.ts @@ -22,7 +22,7 @@ import { it, vi, } from 'vitest' -import { createClient } from '../client/createClient.js' +import { createClient } from '../core/client/createClient.js' import { ValidationError } from '../errors/errors.js' import { SDKError } from '../errors/SDKError.js' import * as request from '../request.js' diff --git a/src/services/balance.ts b/src/services/balance.ts index 1d377478..15e6f7f3 100644 --- a/src/services/balance.ts +++ b/src/services/balance.ts @@ -7,7 +7,6 @@ import type { TokenExtended, WalletTokenExtended, } from '@lifi/types' -import { getChainById } from '../client/getChainById.js' import type { SDKClient, SDKProvider } from '../core/types.js' import { ValidationError } from '../errors/errors.js' import { request } from '../request.js' @@ -107,7 +106,7 @@ export async function getTokenBalancesByChain( const tokenAmountsSettled = await Promise.allSettled( Object.keys(tokensByChain).map(async (chainIdStr) => { const chainId = Number.parseInt(chainIdStr, 10) - const chain = await getChainById(client, chainId) + const chain = await client.getChainById(chainId) if (provider.type === chain.chainType) { const tokenAmounts = await provider.getBalance( client, diff --git a/src/services/balance.unit.spec.ts b/src/services/balance.unit.spec.ts index 4fc70655..23d366b2 100644 --- a/src/services/balance.unit.spec.ts +++ b/src/services/balance.unit.spec.ts @@ -2,7 +2,7 @@ import { findDefaultToken } from '@lifi/data-types' import type { Token, WalletTokenExtended } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { createClient } from '../client/createClient.js' +import { createClient } from '../core/client/createClient.js' import { EVM } from '../core/EVM/EVM.js' import { Solana } from '../core/Solana/Solana.js' import { Sui } from '../core/Sui/Sui.js' diff --git a/src/utils/getTransactionMessage.ts b/src/utils/getTransactionMessage.ts index b74eeda9..11fd00fa 100644 --- a/src/utils/getTransactionMessage.ts +++ b/src/utils/getTransactionMessage.ts @@ -1,5 +1,4 @@ import type { LiFiStep } from '@lifi/types' -import { getChainById } from '../client/getChainById.js' import type { SDKClient } from '../core/types.js' export const getTransactionFailedMessage = async ( @@ -7,7 +6,7 @@ export const getTransactionFailedMessage = async ( step: LiFiStep, txLink?: string ): Promise => { - const chain = await getChainById(client, step.action.toChainId) + const chain = await client.getChainById(step.action.toChainId) const baseString = `It appears that your transaction may not have been successful. However, to confirm this, please check your ${chain.name} wallet for ${step.action.toToken.symbol}.` From 4894d050d22e5eaa06a76ec62992d6e837354ade Mon Sep 17 00:00:00 2001 From: Lizaveta Miasayedava Date: Wed, 22 Oct 2025 15:12:49 +0100 Subject: [PATCH 12/13] refactor: add missing doc strings, cleanup --- src/core/EVM/checkAllowance.ts | 22 +++++++++++------ src/core/EVM/checkPermitSupport.ts | 25 +++++++++++++------- src/core/EVM/getActionWithFallback.ts | 1 + src/core/EVM/getAllowance.ts | 2 ++ src/core/EVM/permits/getNativePermit.ts | 16 ++++++++----- src/core/EVM/publicClient.ts | 1 + src/core/EVM/setAllowance.ts | 2 ++ src/core/Solana/SolanaStepExecutor.ts | 6 +---- src/core/Solana/connection.ts | 1 + src/core/Solana/getSolanaBalance.int.spec.ts | 2 +- src/core/Solana/sendAndConfirmTransaction.ts | 1 + src/core/Sui/SuiStepExecutor.ts | 6 +---- src/core/Sui/suiClient.ts | 1 + src/core/UTXO/getUTXOPublicClient.ts | 1 + src/core/client/createClient.ts | 17 +++++++------ src/core/execution.ts | 2 ++ src/services/api.ts | 13 ++++++++++ src/services/balance.ts | 3 +++ src/services/getNameServiceAddress.ts | 8 +++++-- 19 files changed, 88 insertions(+), 42 deletions(-) diff --git a/src/core/EVM/checkAllowance.ts b/src/core/EVM/checkAllowance.ts index 5f647620..9089229c 100644 --- a/src/core/EVM/checkAllowance.ts +++ b/src/core/EVM/checkAllowance.ts @@ -11,6 +11,7 @@ import type { ProcessType, SDKClient, } from '../types.js' +import { getActionWithFallback } from './getActionWithFallback.js' import { getAllowance } from './getAllowance.js' import { parseEVMErrors } from './parseEVMErrors.js' import { getNativePermit } from './permits/getNativePermit.js' @@ -207,13 +208,20 @@ export const checkAllowance = async ( let nativePermitData: NativePermitData | undefined if (isNativePermitAvailable) { - nativePermitData = await getNativePermit(client, { - client: updatedClient, - chainId: chain.id, - tokenAddress: step.action.fromToken.address as Address, - spenderAddress: chain.permit2Proxy as Address, - amount: fromAmount, - }) + nativePermitData = await getActionWithFallback( + client, + updatedClient, + getNativePermit, + 'getNativePermit', + { + client, + viemClient: updatedClient, + chainId: chain.id, + tokenAddress: step.action.fromToken.address as Address, + spenderAddress: chain.permit2Proxy as Address, + amount: fromAmount, + } + ) } if (isNativePermitAvailable && nativePermitData) { diff --git a/src/core/EVM/checkPermitSupport.ts b/src/core/EVM/checkPermitSupport.ts index c81da408..60abcb54 100644 --- a/src/core/EVM/checkPermitSupport.ts +++ b/src/core/EVM/checkPermitSupport.ts @@ -1,6 +1,7 @@ import { ChainType, type ExtendedChain } from '@lifi/types' import type { Address } from 'viem' import type { SDKClient } from '../types.js' +import { getActionWithFallback } from './getActionWithFallback.js' import { getAllowance } from './getAllowance.js' import { getNativePermit } from './permits/getNativePermit.js' import { getPublicClient } from './publicClient.js' @@ -19,8 +20,7 @@ type PermitSupport = { * 1. Native permit (EIP-2612) support * 2. Permit2 availability and allowance * - * @param config - The SDK base config - * @param provider - The EVM SDK provider + * @param client - The SDK client * @param chain - The chain to check permit support on * @param tokenAddress - The token address to check * @param ownerAddress - The address that would sign the permit @@ -48,13 +48,20 @@ export const checkPermitSupport = async ( viemClient = await getPublicClient(client, chain.id) } - const nativePermit = await getNativePermit(client, { - client: viemClient, - chainId: chain.id, - tokenAddress, - spenderAddress: chain.permit2Proxy as Address, - amount, - }) + const nativePermit = await getActionWithFallback( + client, + viemClient, + getNativePermit, + 'getNativePermit', + { + client, + viemClient, + chainId: chain.id, + tokenAddress, + spenderAddress: chain.permit2Proxy as Address, + amount, + } + ) let permit2Allowance: bigint | undefined // Check Permit2 allowance if available on chain diff --git a/src/core/EVM/getActionWithFallback.ts b/src/core/EVM/getActionWithFallback.ts index 3f37d973..33944f6d 100644 --- a/src/core/EVM/getActionWithFallback.ts +++ b/src/core/EVM/getActionWithFallback.ts @@ -18,6 +18,7 @@ import { getPublicClient } from './publicClient.js' * Note: Only falls back to public client if the initial client was a wallet client (has an account address). * If the initial client was already a public client, no fallback will occur. * + * @param client - The SDK client * @param walletClient - The wallet client to use primarily * @param action - The function or method to execute * @param actionName - The name of the action (used for error handling) diff --git a/src/core/EVM/getAllowance.ts b/src/core/EVM/getAllowance.ts index db66c645..84525d6e 100644 --- a/src/core/EVM/getAllowance.ts +++ b/src/core/EVM/getAllowance.ts @@ -86,6 +86,7 @@ export const getAllowanceMulticall = async ( /** * Get the current allowance for a certain token. + * @param client - The SDK client * @param token - The token that should be checked * @param ownerAddress - The owner of the token * @param spenderAddress - The spender address that has to be approved @@ -116,6 +117,7 @@ export const getTokenAllowance = async ( /** * Get the current allowance for a list of token/spender address pairs. + * @param client - The SDK client * @param ownerAddress - The owner of the tokens * @param tokens - A list of token and spender address pairs * @returns Returns array of tokens and their allowance diff --git a/src/core/EVM/permits/getNativePermit.ts b/src/core/EVM/permits/getNativePermit.ts index d4ef742d..9449423c 100644 --- a/src/core/EVM/permits/getNativePermit.ts +++ b/src/core/EVM/permits/getNativePermit.ts @@ -22,7 +22,8 @@ import { import type { NativePermitData } from './types.js' type GetNativePermitParams = { - client: Client + client: SDKClient + viemClient: Client chainId: number tokenAddress: Address spenderAddress: Address @@ -130,7 +131,8 @@ function validateDomainSeparator({ * 1. Account has no code (EOA) * 2. Account is EOA and has EIP-7702 delegation designator code * - * @param client - The Viem client instance + * @param client - The SDK client + * @param viemClient - The Viem client instance * @returns Promise - Whether the account can use native permits */ const canAccountUseNativePermits = async ( @@ -170,7 +172,8 @@ const canAccountUseNativePermits = async ( /** * Attempts to retrieve contract data using EIP-5267 eip712Domain() function * @link https://eips.ethereum.org/EIPS/eip-5267 - * @param client - The Viem client instance + * @param client - The SDK client + * @param viemClient - The Viem client instance * @param chainId - The chain ID * @param tokenAddress - The token contract address * @returns Contract data if EIP-5267 is supported, undefined otherwise @@ -497,15 +500,16 @@ const getContractData = async ( /** * Retrieves native permit data (EIP-2612) for a token on a specific chain * @link https://eips.ethereum.org/EIPS/eip-2612 - * @param client - The Viem client instance + @param client - The SDK client + * @param viemClient - The Viem client instance * @param chain - The extended chain object containing chain details * @param tokenAddress - The address of the token to check for permit support * @returns {Promise} Object containing permit data including name, version, nonce and support status */ export const getNativePermit = async ( - client: SDKClient, + viemClient: Client, { - client: viemClient, + client, chainId, tokenAddress, spenderAddress, diff --git a/src/core/EVM/publicClient.ts b/src/core/EVM/publicClient.ts index 889ab0be..ec0d806e 100644 --- a/src/core/EVM/publicClient.ts +++ b/src/core/EVM/publicClient.ts @@ -11,6 +11,7 @@ const publicClients: Record = {} /** * Get an instance of a provider for a specific chain + * @param client - The SDK client * @param chainId - Id of the chain the provider is for * @returns The public client for the given chain */ diff --git a/src/core/EVM/setAllowance.ts b/src/core/EVM/setAllowance.ts index deadb97d..60d8faea 100644 --- a/src/core/EVM/setAllowance.ts +++ b/src/core/EVM/setAllowance.ts @@ -71,6 +71,7 @@ export const setAllowance = async ( /** * Set approval for a certain token and amount. + * @param client - The SDK client * @param request - The approval request * @param request.walletClient - The Viem wallet client used to send the transaction * @param request.token - The token for which to set the allowance @@ -109,6 +110,7 @@ export const setTokenAllowance = async ( /** * Revoke approval for a certain token. + * @param client - The SDK client * @param request - The revoke request * @param request.walletClient - The Viem wallet client used to send the transaction * @param request.token - The token for which to revoke the allowance diff --git a/src/core/Solana/SolanaStepExecutor.ts b/src/core/Solana/SolanaStepExecutor.ts index 72fe52a8..4c119633 100644 --- a/src/core/Solana/SolanaStepExecutor.ts +++ b/src/core/Solana/SolanaStepExecutor.ts @@ -11,17 +11,13 @@ import { stepComparison } from '../stepComparison.js' import type { LiFiStepExtended, SDKClient, - StepExecutorOptions, TransactionParameters, } from '../types.js' import { waitForDestinationChainTransaction } from '../waitForDestinationChainTransaction.js' import { callSolanaWithRetry } from './connection.js' import { parseSolanaErrors } from './parseSolanaErrors.js' import { sendAndConfirmTransaction } from './sendAndConfirmTransaction.js' - -interface SolanaStepExecutorOptions extends StepExecutorOptions { - walletAdapter: SignerWalletAdapter -} +import type { SolanaStepExecutorOptions } from './types.js' export class SolanaStepExecutor extends BaseStepExecutor { private walletAdapter: SignerWalletAdapter diff --git a/src/core/Solana/connection.ts b/src/core/Solana/connection.ts index c75d0267..7edfc9fa 100644 --- a/src/core/Solana/connection.ts +++ b/src/core/Solana/connection.ts @@ -31,6 +31,7 @@ export const getSolanaConnections = async ( /** * Calls a function on the Connection instances with retry logic. + * @param client - The SDK client * @param fn - The function to call, which receives a Connection instance. * @returns - The result of the function call. */ diff --git a/src/core/Solana/getSolanaBalance.int.spec.ts b/src/core/Solana/getSolanaBalance.int.spec.ts index 22a2bafb..071d0dd8 100644 --- a/src/core/Solana/getSolanaBalance.int.spec.ts +++ b/src/core/Solana/getSolanaBalance.int.spec.ts @@ -106,7 +106,7 @@ describe.sequential('Solana token balance', async () => { // console.log(quote) - // await executeRoute(config, providers, convertQuoteToRoute(quote), { + // await executeRoute(client, convertQuoteToRoute(quote), { // updateRouteHook: (route) => { // console.log(route.steps?.[0].execution) // }, diff --git a/src/core/Solana/sendAndConfirmTransaction.ts b/src/core/Solana/sendAndConfirmTransaction.ts index cb19190c..d59a6839 100644 --- a/src/core/Solana/sendAndConfirmTransaction.ts +++ b/src/core/Solana/sendAndConfirmTransaction.ts @@ -16,6 +16,7 @@ type ConfirmedTransactionResult = { /** * Sends a Solana transaction to multiple RPC endpoints and returns the confirmation * as soon as any of them confirm the transaction. + * @param client - The SDK client. * @param signedTx - The signed transaction to send. * @returns - The confirmation result of the transaction. */ diff --git a/src/core/Sui/SuiStepExecutor.ts b/src/core/Sui/SuiStepExecutor.ts index 90af12f8..28e917dd 100644 --- a/src/core/Sui/SuiStepExecutor.ts +++ b/src/core/Sui/SuiStepExecutor.ts @@ -11,16 +11,12 @@ import { stepComparison } from '../stepComparison.js' import type { LiFiStepExtended, SDKClient, - StepExecutorOptions, TransactionParameters, } from '../types.js' import { waitForDestinationChainTransaction } from '../waitForDestinationChainTransaction.js' import { parseSuiErrors } from './parseSuiErrors.js' import { callSuiWithRetry } from './suiClient.js' - -interface SuiStepExecutorOptions extends StepExecutorOptions { - wallet: WalletWithRequiredFeatures -} +import type { SuiStepExecutorOptions } from './types.js' export class SuiStepExecutor extends BaseStepExecutor { private wallet: WalletWithRequiredFeatures diff --git a/src/core/Sui/suiClient.ts b/src/core/Sui/suiClient.ts index 7de988e4..5e04e3ff 100644 --- a/src/core/Sui/suiClient.ts +++ b/src/core/Sui/suiClient.ts @@ -20,6 +20,7 @@ const ensureClients = async (client: SDKClient): Promise => { /** * Calls a function on the SuiClient instances with retry logic. + * @param client - The SDK client * @param fn - The function to call, which receives a SuiClient instance. * @returns - The result of the function call. */ diff --git a/src/core/UTXO/getUTXOPublicClient.ts b/src/core/UTXO/getUTXOPublicClient.ts index e78c0ce9..f4f2926c 100644 --- a/src/core/UTXO/getUTXOPublicClient.ts +++ b/src/core/UTXO/getUTXOPublicClient.ts @@ -33,6 +33,7 @@ const publicClients: Record = {} /** * Get an instance of a provider for a specific chain + * @param client - The SDK client * @param chainId - Id of the chain the provider is for * @returns The public client for the given chain */ diff --git a/src/core/client/createClient.ts b/src/core/client/createClient.ts index 54a0b232..ef69d36d 100644 --- a/src/core/client/createClient.ts +++ b/src/core/client/createClient.ts @@ -20,7 +20,7 @@ export function createClient(options: SDKConfig): SDKClient { checkPackageUpdates(name, version) } - const config: SDKBaseConfig = { + const _config: SDKBaseConfig = { ...options, apiUrl: options?.apiUrl ?? 'https://li.quest/v1', rpcUrls: options?.rpcUrls ?? {}, @@ -29,18 +29,21 @@ export function createClient(options: SDKConfig): SDKClient { } let _providers: SDKProvider[] = [] - - const _storage = getClientStorage(config) + const _storage = getClientStorage(_config) return { - config, - providers: _providers, + get config() { + return _config + }, + get providers() { + return _providers + }, getProvider(type: ChainType) { - return _providers.find((provider) => provider.type === type) + return this.providers.find((provider) => provider.type === type) }, setProviders(newProviders: SDKProvider[]) { const providerMap = new Map( - _providers.map((provider) => [provider.type, provider]) + this.providers.map((provider) => [provider.type, provider]) ) for (const provider of newProviders) { providerMap.set(provider.type, provider) diff --git a/src/core/execution.ts b/src/core/execution.ts index 4f409029..de858532 100644 --- a/src/core/execution.ts +++ b/src/core/execution.ts @@ -12,6 +12,7 @@ import type { /** * Execute a route. + * @param client - The SDK client. * @param route - The route that should be executed. Cannot be an active route. * @param executionOptions - An object containing settings and callbacks. * @returns The executed route. @@ -43,6 +44,7 @@ export const executeRoute = async ( /** * Resume the execution of a route that has been stopped or had an error while executing. + * @param client - The SDK client. * @param route - The route that is to be executed. Cannot be an active route. * @param executionOptions - An object containing settings and callbacks. * @returns The executed route. diff --git a/src/services/api.ts b/src/services/api.ts index 757978b0..407b867f 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -50,6 +50,7 @@ import type { /** * Get a quote for a token transfer + * @param config - The SDK client configuration * @param params - The configuration of the requested quote * @param options - Request options * @throws {LiFiError} - Throws a LiFiError if request fails @@ -141,6 +142,7 @@ export async function getQuote( /** * Get a set of routes for a request that describes a transfer of tokens. + * @param config - The SDK client configuration * @param params - A description of the transfer. * @param options - Request options * @returns The resulting routes that can be used to realize the described transfer of tokens. @@ -178,6 +180,7 @@ export const getRoutes = async ( /** * Get a quote for a destination contract call + * @param config - The SDK client configuration * @param params - The configuration of the requested destination call * @param options - Request options * @throws {LiFiError} - Throws a LiFiError if request fails @@ -246,6 +249,7 @@ export const getContractCallsQuote = async ( /** * Get the transaction data for a single step of a route + * @param config - The SDK client configuration * @param step - The step object. * @param options - Request options * @returns The step populated with the transaction data. @@ -277,6 +281,7 @@ export const getStepTransaction = async ( /** * Check the status of a transfer. For cross chain transfers, the "bridge" parameter is required. + * @param config - The SDK client configuration * @param params - Configuration of the requested status * @param options - Request options. * @throws {LiFiError} - Throws a LiFiError if request fails @@ -306,6 +311,7 @@ export const getStatus = async ( /** * Get a relayer quote for a token transfer + * @param config - The SDK client configuration * @param params - The configuration of the requested quote * @param options - Request options * @throws {LiFiError} - Throws a LiFiError if request fails @@ -376,6 +382,7 @@ export const getRelayerQuote = async ( /** * Relay a transaction through the relayer service + * @param config - The SDK client configuration * @param params - The configuration for the relay request * @param options - Request options * @throws {LiFiError} - Throws a LiFiError if request fails @@ -478,6 +485,7 @@ export const getRelayedTransactionStatus = async ( /** * Get all available chains + * @param config - The SDK client configuration * @param params - The configuration of the requested chains * @param options - Request options * @returns A list of all available chains @@ -514,6 +522,7 @@ export const getChains = async ( /** * Get all known tokens. + * @param config - The SDK client configuration * @param params - The configuration of the requested tokens * @param options - Request options * @returns The tokens that are available on the requested chains @@ -558,6 +567,7 @@ export async function getTokens( /** * Fetch information about a Token + * @param config - The SDK client configuration * @param chain - Id or key of the chain that contains the token * @param token - Address or symbol of the token on the requested chain * @param options - Request options @@ -594,6 +604,7 @@ export const getToken = async ( /** * Get the available tools to bridge and swap tokens. + * @param config - The SDK client configuration * @param params - The configuration of the requested tools * @param options - Request options * @returns The tools that are available on the requested chains @@ -623,6 +634,7 @@ export const getTools = async ( /** * Get gas recommendation for a certain chain + * @param config - The SDK client configuration * @param params - Configuration of the requested gas recommendation. * @param options - Request options * @throws {LiFiError} Throws a LiFiError if request fails. @@ -654,6 +666,7 @@ export const getGasRecommendation = async ( /** * Get all the available connections for swap/bridging tokens + * @param config - The SDK client configuration * @param connectionRequest ConnectionsRequest * @param options - Request options * @returns ConnectionsResponse diff --git a/src/services/balance.ts b/src/services/balance.ts index 15e6f7f3..ad71c2d8 100644 --- a/src/services/balance.ts +++ b/src/services/balance.ts @@ -30,6 +30,7 @@ export const getTokenBalance = async ( /** * Returns the balances for a list tokens a wallet holds across all aggregated chains. + * @param client - The SDK client * @param walletAddress - A wallet address. * @param tokens - A list of Token (or TokenExtended) objects. * @returns A list of objects containing the tokens and the amounts on different chains. @@ -67,6 +68,7 @@ export async function getTokenBalances( /** * This method queries the balances of tokens for a specific list of chains for a given wallet. + * @param client - The SDK client * @param walletAddress - A wallet address. * @param tokensByChain - A list of token objects organized by chain ids. * @returns A list of objects containing the tokens and the amounts on different chains organized by the chosen chains. @@ -133,6 +135,7 @@ export async function getTokenBalancesByChain( /** * Returns the balances of tokens a wallet holds across EVM chains. + * @param client - The SDK client * @param walletAddress - A wallet address. * @param options - Optional request options. * @returns An object containing the tokens and the amounts organized by chain ids. diff --git a/src/services/getNameServiceAddress.ts b/src/services/getNameServiceAddress.ts index cecc24de..641094a7 100644 --- a/src/services/getNameServiceAddress.ts +++ b/src/services/getNameServiceAddress.ts @@ -7,9 +7,13 @@ export const getNameServiceAddress = async ( chainType?: ChainType ): Promise => { try { - let providers = client.providers + let providers = [] if (chainType) { - providers = providers.filter((provider) => provider.type === chainType) + providers = client.providers.filter( + (provider) => provider.type === chainType + ) + } else { + providers = client.providers } const resolvers = providers.map((provider) => provider.resolveAddress) if (!resolvers.length) { From e98c16d92bf18c1f125424a3850abb4d4ce16eee Mon Sep 17 00:00:00 2001 From: Lizaveta Miasayedava <68655719+effie-ms@users.noreply.github.com> Date: Fri, 24 Oct 2025 10:03:50 +0100 Subject: [PATCH 13/13] refactor: separate service actions (#316) --- src/actions/actions.unit.handlers.ts | 78 ++ src/actions/getChains.ts | 54 ++ src/actions/getChains.unit.spec.ts | 19 + src/actions/getConnections.ts | 55 ++ src/actions/getConnections.unit.spec.ts | 45 ++ src/actions/getContractCallsQuote.ts | 81 ++ .../getContractCallsQuote.unit.spec.ts | 323 ++++++++ src/actions/getGasRecommendation.ts | 47 ++ src/actions/getGasRecommendation.unit.spec.ts | 40 + .../getNameServiceAddress.ts | 9 +- .../getNameServiceAddress.unit.spec.ts | 157 ++++ .../getQuote.int.spec.ts} | 10 +- src/actions/getQuote.ts | 102 +++ src/actions/getQuote.unit.spec.ts | 154 ++++ src/actions/getRelayedTransactionStatus.ts | 54 ++ .../getRelayedTransactionStatus.unit.spec.ts | 243 ++++++ src/actions/getRelayerQuote.ts | 83 ++ src/actions/getRelayerQuote.unit.spec.ts | 220 ++++++ src/actions/getRoutes.ts | 43 + src/actions/getRoutes.unit.spec.ts | 112 +++ src/actions/getStatus.ts | 36 + src/actions/getStatus.unit.spec.ts | 51 ++ src/actions/getStepTransaction.ts | 36 + src/actions/getStepTransaction.unit.spec.ts | 140 ++++ src/actions/getToken.ts | 47 ++ src/actions/getToken.unit.spec.ts | 45 ++ src/actions/getTokenBalance.ts | 21 + src/actions/getTokenBalance.unit.spec.ts | 61 ++ src/actions/getTokenBalances.ts | 47 ++ src/actions/getTokenBalances.unit.spec.ts | 68 ++ src/actions/getTokenBalancesByChain.ts | 76 ++ .../getTokenBalancesByChain.unit.spec.ts | 108 +++ src/actions/getTokens.ts | 54 ++ src/actions/getTokens.unit.spec.ts | 16 + src/actions/getTools.ts | 33 + src/actions/getTools.unit.spec.ts | 20 + src/actions/getTransactionHistory.ts | 54 ++ .../getTransactionHistory.unit.spec.ts | 36 + src/actions/getWalletBalances.ts | 36 + src/actions/getWalletBalances.unit.spec.ts | 90 +++ src/actions/index.ts | 329 ++++++++ src/actions/relayTransaction.ts | 74 ++ src/actions/relayTransaction.unit.spec.ts | 229 ++++++ src/{core => }/client/createClient.ts | 28 +- src/client/createClient.unit.spec.ts | 263 +++++++ src/{core => }/client/getClientStorage.ts | 8 +- src/client/getClientStorage.unit.spec.ts | 382 +++++++++ src/core/BaseStepExecutor.ts | 4 +- src/core/EVM/EVM.ts | 2 +- src/core/EVM/EVMStepExecutor.ts | 26 +- src/core/EVM/checkAllowance.ts | 4 +- src/core/EVM/checkPermitSupport.ts | 2 +- src/core/EVM/getActionWithFallback.ts | 2 +- src/core/EVM/getAllowance.int.spec.ts | 6 +- src/core/EVM/getAllowance.ts | 2 +- src/core/EVM/getEVMBalance.int.spec.ts | 6 +- src/core/EVM/getEVMBalance.ts | 2 +- src/core/EVM/isBatchingSupported.ts | 2 +- src/core/EVM/parseEVMErrors.ts | 2 +- src/core/EVM/parseEVMErrors.unit.spec.ts | 4 +- src/core/EVM/permits/getNativePermit.ts | 2 +- .../permits/getPermitTransferFromValues.ts | 2 +- src/core/EVM/permits/signPermit2Message.ts | 2 +- src/core/EVM/publicClient.ts | 2 +- src/core/EVM/resolveENSAddress.ts | 2 +- src/core/EVM/resolveEVMAddress.ts | 2 +- src/core/EVM/setAllowance.int.spec.ts | 2 +- src/core/EVM/setAllowance.ts | 4 +- src/core/EVM/switchChain.ts | 6 +- src/core/EVM/switchChain.unit.spec.ts | 4 +- src/core/EVM/typeguards.ts | 2 +- src/core/EVM/types.ts | 2 +- src/core/EVM/uns/resolveUNSAddress.ts | 2 +- src/core/EVM/utils.ts | 8 +- .../EVM/waitForRelayedTransactionReceipt.ts | 8 +- src/core/EVM/waitForTransactionReceipt.ts | 2 +- src/core/Solana/Solana.ts | 2 +- src/core/Solana/SolanaStepExecutor.ts | 14 +- src/core/Solana/connection.ts | 2 +- src/core/Solana/getSolanaBalance.int.spec.ts | 2 +- src/core/Solana/getSolanaBalance.ts | 2 +- src/core/Solana/parseSolanaError.unit.spec.ts | 2 +- src/core/Solana/parseSolanaErrors.ts | 2 +- src/core/Solana/sendAndConfirmTransaction.ts | 2 +- src/core/Solana/types.ts | 2 +- src/core/StatusManager.ts | 6 +- src/core/StatusManager.unit.spec.ts | 8 +- src/core/Sui/Sui.ts | 2 +- src/core/Sui/SuiStepExecutor.ts | 12 +- src/core/Sui/getSuiBalance.int.spec.ts | 2 +- src/core/Sui/getSuiBalance.ts | 2 +- src/core/Sui/parseSuiErrors.ts | 2 +- src/core/Sui/suiClient.ts | 2 +- src/core/Sui/types.ts | 2 +- src/core/UTXO/UTXO.ts | 2 +- src/core/UTXO/UTXOStepExecutor.ts | 15 +- src/core/UTXO/getUTXOBalance.int.spec.ts | 4 +- src/core/UTXO/getUTXOBalance.ts | 2 +- src/core/UTXO/getUTXOPublicClient.ts | 2 +- src/core/UTXO/parseUTXOErrors.ts | 2 +- src/core/UTXO/types.ts | 2 +- src/core/checkBalance.ts | 4 +- src/core/execution.ts | 6 +- src/core/execution.unit.handlers.ts | 4 +- src/core/execution.unit.mock.ts | 2 +- src/core/execution.unit.spec.ts | 4 +- src/core/executionState.ts | 6 +- src/core/prepareRestart.ts | 2 +- src/core/processMessages.ts | 2 +- src/core/stepComparison.ts | 2 +- src/core/utils.ts | 2 +- .../waitForDestinationChainTransaction.ts | 4 +- src/core/waitForTransactionStatus.ts | 8 +- src/errors/SDKError.ts | 2 +- src/index.ts | 83 +- src/request.ts | 2 +- src/request.unit.spec.ts | 4 +- src/services/api.ts | 744 ------------------ src/services/api.unit.handlers.ts | 40 - src/services/api.unit.spec.ts | 631 --------------- src/services/balance.ts | 162 ---- src/services/balance.unit.spec.ts | 290 ------- {tests => src/tests}/fixtures.ts | 2 +- src/{services/types.ts => types/actions.ts} | 0 src/{core/types.ts => types/core.ts} | 20 +- src/utils/getTransactionMessage.ts | 2 +- 126 files changed, 4488 insertions(+), 2070 deletions(-) create mode 100644 src/actions/actions.unit.handlers.ts create mode 100644 src/actions/getChains.ts create mode 100644 src/actions/getChains.unit.spec.ts create mode 100644 src/actions/getConnections.ts create mode 100644 src/actions/getConnections.unit.spec.ts create mode 100644 src/actions/getContractCallsQuote.ts create mode 100644 src/actions/getContractCallsQuote.unit.spec.ts create mode 100644 src/actions/getGasRecommendation.ts create mode 100644 src/actions/getGasRecommendation.unit.spec.ts rename src/{services => actions}/getNameServiceAddress.ts (74%) create mode 100644 src/actions/getNameServiceAddress.unit.spec.ts rename src/{services/api.int.spec.ts => actions/getQuote.int.spec.ts} (71%) create mode 100644 src/actions/getQuote.ts create mode 100644 src/actions/getQuote.unit.spec.ts create mode 100644 src/actions/getRelayedTransactionStatus.ts create mode 100644 src/actions/getRelayedTransactionStatus.unit.spec.ts create mode 100644 src/actions/getRelayerQuote.ts create mode 100644 src/actions/getRelayerQuote.unit.spec.ts create mode 100644 src/actions/getRoutes.ts create mode 100644 src/actions/getRoutes.unit.spec.ts create mode 100644 src/actions/getStatus.ts create mode 100644 src/actions/getStatus.unit.spec.ts create mode 100644 src/actions/getStepTransaction.ts create mode 100644 src/actions/getStepTransaction.unit.spec.ts create mode 100644 src/actions/getToken.ts create mode 100644 src/actions/getToken.unit.spec.ts create mode 100644 src/actions/getTokenBalance.ts create mode 100644 src/actions/getTokenBalance.unit.spec.ts create mode 100644 src/actions/getTokenBalances.ts create mode 100644 src/actions/getTokenBalances.unit.spec.ts create mode 100644 src/actions/getTokenBalancesByChain.ts create mode 100644 src/actions/getTokenBalancesByChain.unit.spec.ts create mode 100644 src/actions/getTokens.ts create mode 100644 src/actions/getTokens.unit.spec.ts create mode 100644 src/actions/getTools.ts create mode 100644 src/actions/getTools.unit.spec.ts create mode 100644 src/actions/getTransactionHistory.ts create mode 100644 src/actions/getTransactionHistory.unit.spec.ts create mode 100644 src/actions/getWalletBalances.ts create mode 100644 src/actions/getWalletBalances.unit.spec.ts create mode 100644 src/actions/index.ts create mode 100644 src/actions/relayTransaction.ts create mode 100644 src/actions/relayTransaction.unit.spec.ts rename src/{core => }/client/createClient.ts (74%) create mode 100644 src/client/createClient.unit.spec.ts rename src/{core => }/client/getClientStorage.ts (80%) create mode 100644 src/client/getClientStorage.unit.spec.ts delete mode 100644 src/services/api.ts delete mode 100644 src/services/api.unit.handlers.ts delete mode 100644 src/services/api.unit.spec.ts delete mode 100644 src/services/balance.ts delete mode 100644 src/services/balance.unit.spec.ts rename {tests => src/tests}/fixtures.ts (98%) rename src/{services/types.ts => types/actions.ts} (100%) rename src/{core/types.ts => types/core.ts} (100%) diff --git a/src/actions/actions.unit.handlers.ts b/src/actions/actions.unit.handlers.ts new file mode 100644 index 00000000..67e17d30 --- /dev/null +++ b/src/actions/actions.unit.handlers.ts @@ -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 } diff --git a/src/actions/getChains.ts b/src/actions/getChains.ts new file mode 100644 index 00000000..9cca561e --- /dev/null +++ b/src/actions/getChains.ts @@ -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 => { + return await getChainsFromConfig(client.config, params, options) +} + +export const getChainsFromConfig = async ( + config: SDKBaseConfig, + params?: ChainsRequest, + options?: RequestOptions +): Promise => { + 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 + ).toString() + const response = await withDedupe( + () => + request( + config, + `${config.apiUrl}/chains?${urlSearchParams}`, + { + signal: options?.signal, + } + ), + { id: `${getChains.name}.${urlSearchParams}` } + ) + return response.chains +} diff --git a/src/actions/getChains.unit.spec.ts b/src/actions/getChains.unit.spec.ts new file mode 100644 index 00000000..7c202e85 --- /dev/null +++ b/src/actions/getChains.unit.spec.ts @@ -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) + }) + }) +}) diff --git a/src/actions/getConnections.ts b/src/actions/getConnections.ts new file mode 100644 index 00000000..079d9c80 --- /dev/null +++ b/src/actions/getConnections.ts @@ -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 => { + 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 = [ + '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(client.config, url, options) +} diff --git a/src/actions/getConnections.unit.spec.ts b/src/actions/getConnections.unit.spec.ts new file mode 100644 index 00000000..de9815c3 --- /dev/null +++ b/src/actions/getConnections.unit.spec.ts @@ -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() + }) +}) diff --git a/src/actions/getContractCallsQuote.ts b/src/actions/getContractCallsQuote.ts new file mode 100644 index 00000000..bd9109c0 --- /dev/null +++ b/src/actions/getContractCallsQuote.ts @@ -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 => { + // validation + const requiredParameters: Array = [ + '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( + client.config, + `${client.config.apiUrl}/quote/contractCalls`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(params), + signal: options?.signal, + } + ) +} diff --git a/src/actions/getContractCallsQuote.unit.spec.ts b/src/actions/getContractCallsQuote.unit.spec.ts new file mode 100644 index 00000000..b3d4d2d8 --- /dev/null +++ b/src/actions/getContractCallsQuote.unit.spec.ts @@ -0,0 +1,323 @@ +import type { + ContractCallsQuoteRequest, + LiFiStep, + RequestOptions, +} from '@lifi/types' +import { ChainId } from '@lifi/types' +import { HttpResponse, http } from 'msw' +import { describe, expect, it } from 'vitest' +import { ValidationError } from '../errors/errors.js' +import { SDKError } from '../errors/SDKError.js' +import { client, setupTestServer } from './actions.unit.handlers.js' +import { getContractCallsQuote } from './getContractCallsQuote.js' + +describe('getContractCallsQuote', () => { + const server = setupTestServer() + + const createMockContractCallsRequest = ( + overrides: Partial = {} + ): ContractCallsQuoteRequest => ({ + fromChain: ChainId.ETH, + fromToken: '0xA0b86a33E6441c8C06DDD4f36e4C4C5B4c3B4c3B', + fromAddress: '0x1234567890123456789012345678901234567890', + toChain: ChainId.POL, + toToken: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', + contractCalls: [ + { + fromAmount: '1000000', + fromTokenAddress: '0xA0b86a33E6441c8C06DDD4f36e4C4C5B4c3B4c3B', + toContractAddress: '0x1234567890123456789012345678901234567890', + toContractCallData: '0x1234567890abcdef', + toContractGasLimit: '100000', + }, + ], + fromAmount: '1000000', + ...overrides, + }) + + const mockLiFiStep: LiFiStep = { + id: 'test-step-id', + type: 'lifi', + includedSteps: [], + tool: 'test-tool', + toolDetails: { + key: 'test-tool', + name: 'Test Tool', + logoURI: 'https://example.com/logo.png', + }, + action: { + fromChainId: ChainId.ETH, + toChainId: ChainId.POL, + fromToken: { + address: '0xA0b86a33E6441c8C06DDD4f36e4C4C5B4c3B4c3B', + symbol: 'USDC', + decimals: 6, + chainId: ChainId.ETH, + name: 'USD Coin', + priceUSD: '1.00', + }, + toToken: { + address: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', + symbol: 'USDC', + decimals: 6, + chainId: ChainId.POL, + name: 'USD Coin', + priceUSD: '1.00', + }, + fromAmount: '1000000', + fromAddress: '0x1234567890123456789012345678901234567890', + toAddress: '0x1234567890123456789012345678901234567890', + }, + estimate: { + fromAmount: '1000000', + toAmount: '1000000', + toAmountMin: '970000', + approvalAddress: '0x1234567890123456789012345678901234567890', + tool: 'test-tool', + executionDuration: 30000, + }, + transactionRequest: { + to: '0x1234567890123456789012345678901234567890', + data: '0x', + value: '0', + gasLimit: '100000', + }, + } + + describe('success scenarios', () => { + it('should get contract calls quote successfully with fromAmount', async () => { + server.use( + http.post(`${client.config.apiUrl}/quote/contractCalls`, async () => { + return HttpResponse.json(mockLiFiStep) + }) + ) + + const request = createMockContractCallsRequest({ + fromAmount: '1000000', + }) + + const result = await getContractCallsQuote(client, request) + + expect(result).toEqual(mockLiFiStep) + }) + + it('should get contract calls quote successfully with toAmount', async () => { + server.use( + http.post(`${client.config.apiUrl}/quote/contractCalls`, async () => { + return HttpResponse.json(mockLiFiStep) + }) + ) + + const request = createMockContractCallsRequest({ + toAmount: '1000000', + fromAmount: undefined, + }) + + const result = await getContractCallsQuote(client, request) + + expect(result).toEqual(mockLiFiStep) + }) + + it('should pass request options correctly', async () => { + const mockAbortController = new AbortController() + const options: RequestOptions = { + signal: mockAbortController.signal, + } + + let capturedOptions: any + server.use( + http.post( + `${client.config.apiUrl}/quote/contractCalls`, + async ({ request }) => { + capturedOptions = request + return HttpResponse.json(mockLiFiStep) + } + ) + ) + + const request = createMockContractCallsRequest() + + await getContractCallsQuote(client, request, options) + + expect(capturedOptions.signal).toBeDefined() + expect(capturedOptions.signal).toBeInstanceOf(AbortSignal) + }) + }) + + describe('validation scenarios', () => { + it('should throw SDKError when fromChain is missing', async () => { + const invalidRequest = createMockContractCallsRequest({ + fromChain: undefined as any, + }) + + await expect( + getContractCallsQuote(client, invalidRequest) + ).rejects.toThrow(SDKError) + + try { + await getContractCallsQuote(client, invalidRequest) + } catch (error) { + expect(error).toBeInstanceOf(SDKError) + expect((error as SDKError).cause).toBeInstanceOf(ValidationError) + expect((error as SDKError).cause.message).toBe( + 'Required parameter "fromChain" is missing.' + ) + } + }) + + it('should throw SDKError when fromToken is missing', async () => { + const invalidRequest = createMockContractCallsRequest({ + fromToken: undefined as any, + }) + + await expect( + getContractCallsQuote(client, invalidRequest) + ).rejects.toThrow(SDKError) + + try { + await getContractCallsQuote(client, invalidRequest) + } catch (error) { + expect(error).toBeInstanceOf(SDKError) + expect((error as SDKError).cause).toBeInstanceOf(ValidationError) + expect((error as SDKError).cause.message).toBe( + 'Required parameter "fromToken" is missing.' + ) + } + }) + + it('should throw SDKError when fromAddress is missing', async () => { + const invalidRequest = createMockContractCallsRequest({ + fromAddress: undefined as any, + }) + + await expect( + getContractCallsQuote(client, invalidRequest) + ).rejects.toThrow(SDKError) + + try { + await getContractCallsQuote(client, invalidRequest) + } catch (error) { + expect(error).toBeInstanceOf(SDKError) + expect((error as SDKError).cause).toBeInstanceOf(ValidationError) + expect((error as SDKError).cause.message).toBe( + 'Required parameter "fromAddress" is missing.' + ) + } + }) + + it('should throw SDKError when toChain is missing', async () => { + const invalidRequest = createMockContractCallsRequest({ + toChain: undefined as any, + }) + + await expect( + getContractCallsQuote(client, invalidRequest) + ).rejects.toThrow(SDKError) + + try { + await getContractCallsQuote(client, invalidRequest) + } catch (error) { + expect(error).toBeInstanceOf(SDKError) + expect((error as SDKError).cause).toBeInstanceOf(ValidationError) + expect((error as SDKError).cause.message).toBe( + 'Required parameter "toChain" is missing.' + ) + } + }) + + it('should throw SDKError when toToken is missing', async () => { + const invalidRequest = createMockContractCallsRequest({ + toToken: undefined as any, + }) + + await expect( + getContractCallsQuote(client, invalidRequest) + ).rejects.toThrow(SDKError) + + try { + await getContractCallsQuote(client, invalidRequest) + } catch (error) { + expect(error).toBeInstanceOf(SDKError) + expect((error as SDKError).cause).toBeInstanceOf(ValidationError) + expect((error as SDKError).cause.message).toBe( + 'Required parameter "toToken" is missing.' + ) + } + }) + + it('should throw SDKError when contractCalls is missing', async () => { + const invalidRequest = createMockContractCallsRequest({ + contractCalls: undefined as any, + }) + + await expect( + getContractCallsQuote(client, invalidRequest) + ).rejects.toThrow(SDKError) + + try { + await getContractCallsQuote(client, invalidRequest) + } catch (error) { + expect(error).toBeInstanceOf(SDKError) + expect((error as SDKError).cause).toBeInstanceOf(ValidationError) + expect((error as SDKError).cause.message).toBe( + 'Required parameter "contractCalls" is missing.' + ) + } + }) + + it('should throw SDKError when both fromAmount and toAmount are missing', async () => { + const invalidRequest = createMockContractCallsRequest() + // Remove both fromAmount and toAmount to test validation + delete (invalidRequest as any).fromAmount + delete (invalidRequest as any).toAmount + + await expect( + getContractCallsQuote(client, invalidRequest) + ).rejects.toThrow(SDKError) + + try { + await getContractCallsQuote(client, invalidRequest) + } catch (error) { + expect(error).toBeInstanceOf(SDKError) + expect((error as SDKError).cause).toBeInstanceOf(ValidationError) + expect((error as SDKError).cause.message).toBe( + 'Required parameter "fromAmount" or "toAmount" is missing.' + ) + } + }) + }) + + describe('error scenarios', () => { + it('should throw SDKError when network request fails', async () => { + server.use( + http.post(`${client.config.apiUrl}/quote/contractCalls`, async () => { + return HttpResponse.error() + }) + ) + + const request = createMockContractCallsRequest() + + await expect(getContractCallsQuote(client, request)).rejects.toThrow( + SDKError + ) + }) + + it('should throw SDKError when request times out', async () => { + server.use( + http.post(`${client.config.apiUrl}/quote/contractCalls`, async () => { + // Simulate timeout by not responding + await new Promise(() => {}) // Never resolves + }) + ) + + const request = createMockContractCallsRequest() + const timeoutOptions: RequestOptions = { + signal: AbortSignal.timeout(100), // 100ms timeout + } + + await expect( + getContractCallsQuote(client, request, timeoutOptions) + ).rejects.toThrow() + }) + }) +}) diff --git a/src/actions/getGasRecommendation.ts b/src/actions/getGasRecommendation.ts new file mode 100644 index 00000000..47412fd9 --- /dev/null +++ b/src/actions/getGasRecommendation.ts @@ -0,0 +1,47 @@ +import type { + GasRecommendationRequest, + GasRecommendationResponse, + RequestOptions, +} 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 gas recommendation for a certain chain + * @param client - The SDK client + * @param params - Configuration of the requested gas recommendation. + * @param options - Request options + * @throws {LiFiError} Throws a LiFiError if request fails. + * @returns Gas recommendation response. + */ +export const getGasRecommendation = async ( + client: SDKClient, + params: GasRecommendationRequest, + options?: RequestOptions +): Promise => { + if (!params.chainId) { + throw new SDKError( + new ValidationError('Required parameter "chainId" is missing.') + ) + } + + const url = new URL( + `${client.config.apiUrl}/gas/suggestion/${params.chainId}` + ) + if (params.fromChain) { + url.searchParams.append('fromChain', params.fromChain as unknown as string) + } + if (params.fromToken) { + url.searchParams.append('fromToken', params.fromToken) + } + + return await request( + client.config, + url.toString(), + { + signal: options?.signal, + } + ) +} diff --git a/src/actions/getGasRecommendation.unit.spec.ts b/src/actions/getGasRecommendation.unit.spec.ts new file mode 100644 index 00000000..29369248 --- /dev/null +++ b/src/actions/getGasRecommendation.unit.spec.ts @@ -0,0 +1,40 @@ +import { ChainId } from '@lifi/types' +import { describe, expect, it, vi } from 'vitest' +import { ValidationError } from '../errors/errors.js' +import { SDKError } from '../errors/SDKError.js' +import * as request from '../request.js' +import { client, setupTestServer } from './actions.unit.handlers.js' +import { getGasRecommendation } from './getGasRecommendation.js' + +const mockedFetch = vi.spyOn(request, 'request') + +describe('getGasRecommendation', () => { + setupTestServer() + + describe('user input is invalid', () => { + it('throw an error', async () => { + await expect( + getGasRecommendation(client, { + chainId: undefined as unknown as number, + }) + ).rejects.toThrowError( + new SDKError( + new ValidationError('Required parameter "chainId" is missing.') + ) + ) + expect(mockedFetch).toHaveBeenCalledTimes(0) + }) + }) + + describe('user input is valid', () => { + describe('and the backend call is successful', () => { + it('call the server once', async () => { + await getGasRecommendation(client, { + chainId: ChainId.OPT, + }) + + expect(mockedFetch).toHaveBeenCalledTimes(1) + }) + }) + }) +}) diff --git a/src/services/getNameServiceAddress.ts b/src/actions/getNameServiceAddress.ts similarity index 74% rename from src/services/getNameServiceAddress.ts rename to src/actions/getNameServiceAddress.ts index 641094a7..a2bc3422 100644 --- a/src/services/getNameServiceAddress.ts +++ b/src/actions/getNameServiceAddress.ts @@ -1,6 +1,13 @@ import type { ChainType } from '@lifi/types' -import type { SDKClient } from '../core/types.js' +import type { SDKClient } from '../types/core.js' +/** + * Get the address of a name service + * @param client - The SDK client + * @param name - The name to resolve + * @param chainType - The chain type to resolve the name on + * @returns The address of the name service + */ export const getNameServiceAddress = async ( client: SDKClient, name: string, diff --git a/src/actions/getNameServiceAddress.unit.spec.ts b/src/actions/getNameServiceAddress.unit.spec.ts new file mode 100644 index 00000000..a6e42787 --- /dev/null +++ b/src/actions/getNameServiceAddress.unit.spec.ts @@ -0,0 +1,157 @@ +import type { ChainType } from '@lifi/types' +import { describe, expect, it, vi } from 'vitest' +import { EVM } from '../core/EVM/EVM.js' +import { Solana } from '../core/Solana/Solana.js' +import { UTXO } from '../core/UTXO/UTXO.js' +import { client } from './actions.unit.handlers.js' +import { getNameServiceAddress } from './getNameServiceAddress.js' + +describe('getNameServiceAddress', () => { + describe('success scenarios', () => { + it('should resolve address successfully with single provider', async () => { + const mockResolveAddress = vi + .fn() + .mockResolvedValue('0x1234567890123456789012345678901234567890') + + const provider = EVM({ getWalletClient: vi.fn() }) + vi.spyOn(provider, 'resolveAddress').mockImplementation( + mockResolveAddress + ) + + client.setProviders([provider]) + + const result = await getNameServiceAddress(client, 'test.eth') + + expect(result).toBe('0x1234567890123456789012345678901234567890') + expect(mockResolveAddress).toHaveBeenCalledWith('test.eth', client) + }) + + it('should resolve address successfully with multiple providers', async () => { + const mockResolveAddress1 = vi + .fn() + .mockResolvedValue('0x1111111111111111111111111111111111111111') + const mockResolveAddress2 = vi + .fn() + .mockResolvedValue('0x2222222222222222222222222222222222222222') + + const provider1 = EVM({ getWalletClient: vi.fn() }) + const provider2 = Solana({ getWalletAdapter: vi.fn() }) + + vi.spyOn(provider1, 'resolveAddress').mockImplementation( + mockResolveAddress1 + ) + vi.spyOn(provider2, 'resolveAddress').mockImplementation( + mockResolveAddress2 + ) + + client.setProviders([provider1, provider2]) + + const result = await getNameServiceAddress(client, 'test.sol') + + // Should return the first successful result + expect(result).toBe('0x1111111111111111111111111111111111111111') + expect(mockResolveAddress1).toHaveBeenCalledWith('test.sol', client) + }) + + it('should resolve address with specific chain type', async () => { + const mockResolveAddress = vi + .fn() + .mockResolvedValue('0x1234567890123456789012345678901234567890') + + const evmProvider = EVM({ getWalletClient: vi.fn() }) + const svmProvider = Solana({ getWalletAdapter: vi.fn() }) + + vi.spyOn(evmProvider, 'resolveAddress').mockImplementation( + mockResolveAddress + ) + + client.setProviders([evmProvider, svmProvider]) + + const result = await getNameServiceAddress( + client, + 'test.eth', + 'EVM' as ChainType + ) + + expect(result).toBe('0x1234567890123456789012345678901234567890') + expect(mockResolveAddress).toHaveBeenCalledWith('test.eth', client) + }) + }) + + describe('chain type filtering', () => { + it('should filter providers by chain type', async () => { + const mockResolveAddress = vi + .fn() + .mockResolvedValue('0x1234567890123456789012345678901234567890') + + const evmProvider = EVM({ getWalletClient: vi.fn() }) + const svmProvider = Solana({ getWalletAdapter: vi.fn() }) + const utxoProvider = UTXO({ getWalletClient: vi.fn() }) + + vi.spyOn(evmProvider, 'resolveAddress').mockImplementation( + mockResolveAddress + ) + + client.setProviders([evmProvider, svmProvider, utxoProvider]) + + const result = await getNameServiceAddress( + client, + 'test.eth', + 'EVM' as ChainType + ) + + expect(result).toBe('0x1234567890123456789012345678901234567890') + expect(mockResolveAddress).toHaveBeenCalledWith('test.eth', client) + }) + + it('should return undefined when no providers match chain type', async () => { + const mockResolveAddress = vi + .fn() + .mockResolvedValue('0x1234567890123456789012345678901234567890') + const evmProvider = EVM({ getWalletClient: vi.fn() }) + client.setProviders([evmProvider]) + + const result = await getNameServiceAddress( + client, + 'test.name', + 'SVM' as ChainType + ) + + expect(result).toBeUndefined() + expect(mockResolveAddress).not.toHaveBeenCalled() + }) + }) + + describe('error scenarios', () => { + it('should handle mixed success and failure scenarios', async () => { + const mockResolveAddress1 = vi + .fn() + .mockRejectedValue(new Error('Provider 1 failed')) + const mockResolveAddress2 = vi + .fn() + .mockResolvedValue('0x2222222222222222222222222222222222222222') + const mockResolveAddress3 = vi.fn().mockResolvedValue(undefined) + + const provider1 = EVM({ getWalletClient: vi.fn() }) + const provider2 = Solana({ getWalletAdapter: vi.fn() }) + const provider3 = UTXO({ getWalletClient: vi.fn() }) + + vi.spyOn(provider1, 'resolveAddress').mockImplementation( + mockResolveAddress1 + ) + vi.spyOn(provider2, 'resolveAddress').mockImplementation( + mockResolveAddress2 + ) + vi.spyOn(provider3, 'resolveAddress').mockImplementation( + mockResolveAddress3 + ) + + client.setProviders([provider1, provider2, provider3]) + + const result = await getNameServiceAddress(client, 'test.name') + + // Should return the first successful result (provider2) + expect(result).toBe('0x2222222222222222222222222222222222222222') + }) + }) +}) diff --git a/src/services/api.int.spec.ts b/src/actions/getQuote.int.spec.ts similarity index 71% rename from src/services/api.int.spec.ts rename to src/actions/getQuote.int.spec.ts index 4965e29b..3c31d3aa 100644 --- a/src/services/api.int.spec.ts +++ b/src/actions/getQuote.int.spec.ts @@ -1,14 +1,10 @@ import { describe, expect, it } from 'vitest' -import { createClient } from '../core/client/createClient.js' -import { getQuote } from './api.js' - -const client = createClient({ - integrator: 'lifi-sdk', -}) +import { client } from './actions.unit.handlers.js' +import { getQuote } from './getQuote.js' describe('ApiService Integration Tests', () => { it('should successfully request a quote', async () => { - const quote = await getQuote(client.config, { + const quote = await getQuote(client, { fromChain: '1', fromToken: '0x0000000000000000000000000000000000000000', fromAddress: '0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0', diff --git a/src/actions/getQuote.ts b/src/actions/getQuote.ts new file mode 100644 index 00000000..4cd23ae1 --- /dev/null +++ b/src/actions/getQuote.ts @@ -0,0 +1,102 @@ +import type { LiFiStep, RequestOptions } from '@lifi/types' +import { ValidationError } from '../errors/errors.js' +import { SDKError } from '../errors/SDKError.js' +import { request } from '../request.js' +import type { + QuoteRequest, + QuoteRequestFromAmount, + QuoteRequestToAmount, +} from '../types/actions.js' +import type { SDKClient } from '../types/core.js' + +/** + * Get a quote for a token transfer + * @param client - The SDK client + * @param params - The configuration of the requested quote + * @param options - Request options + * @throws {LiFiError} - Throws a LiFiError if request fails + * @returns Quote for a token transfer + */ +export async function getQuote( + client: SDKClient, + params: QuoteRequestFromAmount, + options?: RequestOptions +): Promise +export async function getQuote( + client: SDKClient, + params: QuoteRequestToAmount, + options?: RequestOptions +): Promise +export async function getQuote( + client: SDKClient, + params: QuoteRequest, + options?: RequestOptions +): Promise { + const requiredParameters: Array = [ + 'fromChain', + 'fromToken', + 'fromAddress', + 'toChain', + 'toToken', + ] + + for (const requiredParameter of requiredParameters) { + if (!params[requiredParameter]) { + throw new SDKError( + new ValidationError( + `Required parameter "${requiredParameter}" is missing.` + ) + ) + } + } + + const isFromAmountRequest = + 'fromAmount' in params && params.fromAmount !== undefined + const isToAmountRequest = + 'toAmount' in params && params.toAmount !== undefined + + if (!isFromAmountRequest && !isToAmountRequest) { + throw new SDKError( + new ValidationError( + 'Required parameter "fromAmount" or "toAmount" is missing.' + ) + ) + } + + if (isFromAmountRequest && isToAmountRequest) { + throw new SDKError( + new ValidationError( + 'Cannot provide both "fromAmount" and "toAmount" parameters.' + ) + ) + } + + // apply defaults + params.integrator ??= client.config.integrator + params.order ??= client.config.routeOptions?.order + 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 + + for (const key of Object.keys(params)) { + if (!params[key as keyof QuoteRequest]) { + delete params[key as keyof QuoteRequest] + } + } + + return await request( + client.config, + `${client.config.apiUrl}/${isFromAmountRequest ? 'quote' : 'quote/toAmount'}?${new URLSearchParams( + params as unknown as Record + )}`, + { + signal: options?.signal, + } + ) +} diff --git a/src/actions/getQuote.unit.spec.ts b/src/actions/getQuote.unit.spec.ts new file mode 100644 index 00000000..f2fe2cc9 --- /dev/null +++ b/src/actions/getQuote.unit.spec.ts @@ -0,0 +1,154 @@ +import { ChainId } from '@lifi/types' +import { describe, expect, it, vi } from 'vitest' +import { ValidationError } from '../errors/errors.js' +import { SDKError } from '../errors/SDKError.js' +import * as request from '../request.js' +import { client, setupTestServer } from './actions.unit.handlers.js' +import { getQuote } from './getQuote.js' + +const mockedFetch = vi.spyOn(request, 'request') + +describe('getQuote', () => { + setupTestServer() + + const fromChain = ChainId.DAI + const fromToken = 'DAI' + const fromAddress = 'Some wallet address' + const fromAmount = '1000' + const toChain = ChainId.POL + const toToken = 'MATIC' + const toAmount = '1000' + + describe('user input is invalid', () => { + it('throw an error', async () => { + await expect( + getQuote(client, { + fromChain: undefined as unknown as ChainId, + fromToken, + fromAddress, + fromAmount, + toChain, + toToken, + }) + ).rejects.toThrowError( + new SDKError( + new ValidationError('Required parameter "fromChain" is missing.') + ) + ) + + await expect( + getQuote(client, { + fromChain, + fromToken: undefined as unknown as string, + fromAddress, + fromAmount, + toChain, + toToken, + }) + ).rejects.toThrowError( + new SDKError( + new ValidationError('Required parameter "fromToken" is missing.') + ) + ) + + await expect( + getQuote(client, { + fromChain, + fromToken, + fromAddress: undefined as unknown as string, + fromAmount, + toChain, + toToken, + }) + ).rejects.toThrowError( + new SDKError( + new ValidationError('Required parameter "fromAddress" is missing.') + ) + ) + + await expect( + getQuote(client, { + fromChain, + fromToken, + fromAddress, + fromAmount: undefined as unknown as string, + toChain, + toToken, + }) + ).rejects.toThrowError( + new SDKError( + new ValidationError( + 'Required parameter "fromAmount" or "toAmount" is missing.' + ) + ) + ) + + await expect( + getQuote(client, { + fromChain, + fromToken, + fromAddress, + fromAmount, + toChain, + toToken, + toAmount, + } as any) + ).rejects.toThrowError( + new SDKError( + new ValidationError( + 'Cannot provide both "fromAmount" and "toAmount" parameters.' + ) + ) + ) + + await expect( + getQuote(client, { + fromChain, + fromToken, + fromAddress, + fromAmount, + toChain: undefined as unknown as ChainId, + toToken, + }) + ).rejects.toThrowError( + new SDKError( + new ValidationError('Required parameter "toChain" is missing.') + ) + ) + + await expect( + getQuote(client, { + fromChain, + fromToken, + fromAddress, + fromAmount, + toChain, + toToken: undefined as unknown as string, + }) + ).rejects.toThrowError( + new SDKError( + new ValidationError('Required parameter "toToken" is missing.') + ) + ) + + expect(mockedFetch).toHaveBeenCalledTimes(0) + }) + }) + + describe('user input is valid', () => { + describe('and the backend call is successful', () => { + it('call the server once', async () => { + await getQuote(client, { + fromChain, + fromToken, + fromAddress, + fromAmount, + toChain, + toToken, + }) + + expect(mockedFetch).toHaveBeenCalledTimes(1) + }) + }) + }) +}) diff --git a/src/actions/getRelayedTransactionStatus.ts b/src/actions/getRelayedTransactionStatus.ts new file mode 100644 index 00000000..a066fe2b --- /dev/null +++ b/src/actions/getRelayedTransactionStatus.ts @@ -0,0 +1,54 @@ +import type { + RelayStatusRequest, + RelayStatusResponse, + RelayStatusResponseData, + RequestOptions, +} from '@lifi/types' +import { BaseError } from '../errors/baseError.js' +import { ErrorName } from '../errors/constants.js' +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 the status of a relayed transaction + * @param client - The SDK client + * @param params - Parameters for the relay status request + * @param options - Request options + * @throws {LiFiError} - Throws a LiFiError if request fails + * @returns Status of the relayed transaction + */ +export const getRelayedTransactionStatus = async ( + client: SDKClient, + params: RelayStatusRequest, + options?: RequestOptions +): Promise => { + if (!params.taskId) { + throw new SDKError( + new ValidationError('Required parameter "taskId" is missing.') + ) + } + + const { taskId, ...otherParams } = params + const queryParams = new URLSearchParams( + otherParams as unknown as Record + ) + const result = await request( + client.config, + `${client.config.apiUrl}/relayer/status/${taskId}?${queryParams}`, + { + signal: options?.signal, + } + ) + + if (result.status === 'error') { + throw new BaseError( + ErrorName.ServerError, + result.data.code, + result.data.message + ) + } + + return result.data +} diff --git a/src/actions/getRelayedTransactionStatus.unit.spec.ts b/src/actions/getRelayedTransactionStatus.unit.spec.ts new file mode 100644 index 00000000..09988291 --- /dev/null +++ b/src/actions/getRelayedTransactionStatus.unit.spec.ts @@ -0,0 +1,243 @@ +import type { + RelayStatusRequest, + RelayStatusResponse, + RelayStatusResponseData, + RequestOptions, +} from '@lifi/types' +import { HttpResponse, http } from 'msw' +import { describe, expect, it } from 'vitest' +import { BaseError } from '../errors/baseError.js' +import { ErrorName } from '../errors/constants.js' +import { ValidationError } from '../errors/errors.js' +import { SDKError } from '../errors/SDKError.js' +import { client, setupTestServer } from './actions.unit.handlers.js' +import { getRelayedTransactionStatus } from './getRelayedTransactionStatus.js' + +describe('getRelayedTransactionStatus', () => { + const server = setupTestServer() + + const createMockRelayStatusRequest = ( + overrides: Partial = {} + ): RelayStatusRequest => ({ + taskId: + '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', + ...overrides, + }) + + const mockStatusResponseData: RelayStatusResponseData = { + status: 'PENDING', + metadata: { + chainId: 1, + }, + } + + const mockSuccessResponse: RelayStatusResponse = { + status: 'ok', + data: mockStatusResponseData, + } + + const mockErrorResponse: RelayStatusResponse = { + status: 'error', + data: { + code: 404, + message: 'Task not found', + }, + } + + describe('success scenarios', () => { + it('should get relayed transaction status successfully', async () => { + server.use( + http.get( + `${client.config.apiUrl}/relayer/status/0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef`, + async () => { + return HttpResponse.json(mockSuccessResponse) + } + ) + ) + + const request = createMockRelayStatusRequest() + + const result = await getRelayedTransactionStatus(client, request) + + expect(result).toEqual(mockStatusResponseData) + }) + + it('should pass request options correctly', async () => { + const mockAbortController = new AbortController() + const options: RequestOptions = { + signal: mockAbortController.signal, + } + + let capturedOptions: any + server.use( + http.get( + `${client.config.apiUrl}/relayer/status/0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef`, + async ({ request }) => { + capturedOptions = request + return HttpResponse.json(mockSuccessResponse) + } + ) + ) + + const request = createMockRelayStatusRequest() + + await getRelayedTransactionStatus(client, request, options) + + expect(capturedOptions.signal).toBeDefined() + expect(capturedOptions.signal).toBeInstanceOf(AbortSignal) + }) + + it('should handle different task statuses', async () => { + const pendingResponse = { + ...mockSuccessResponse, + data: { ...mockStatusResponseData, status: 'PENDING' }, + } + const completedResponse = { + ...mockSuccessResponse, + data: { ...mockStatusResponseData, status: 'COMPLETED' }, + } + const failedResponse = { + ...mockSuccessResponse, + data: { ...mockStatusResponseData, status: 'FAILED' }, + } + + // Test PENDING status + server.use( + http.get( + `${client.config.apiUrl}/relayer/status/0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef`, + async () => { + return HttpResponse.json(pendingResponse) + } + ) + ) + + let result = await getRelayedTransactionStatus( + client, + createMockRelayStatusRequest() + ) + expect(result.status).toBe('PENDING') + + // Test COMPLETED status + server.resetHandlers() + server.use( + http.get( + `${client.config.apiUrl}/relayer/status/0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef`, + async () => { + return HttpResponse.json(completedResponse) + } + ) + ) + + result = await getRelayedTransactionStatus( + client, + createMockRelayStatusRequest() + ) + expect(result.status).toBe('COMPLETED') + + // Test FAILED status + server.resetHandlers() + server.use( + http.get( + `${client.config.apiUrl}/relayer/status/0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef`, + async () => { + return HttpResponse.json(failedResponse) + } + ) + ) + + result = await getRelayedTransactionStatus( + client, + createMockRelayStatusRequest() + ) + expect(result.status).toBe('FAILED') + }) + }) + + describe('validation scenarios', () => { + it('should throw SDKError when taskId is missing', async () => { + const invalidRequest = createMockRelayStatusRequest({ + taskId: undefined as any, + }) + + await expect( + getRelayedTransactionStatus(client, invalidRequest) + ).rejects.toThrow(SDKError) + + try { + await getRelayedTransactionStatus(client, invalidRequest) + } catch (error) { + expect(error).toBeInstanceOf(SDKError) + expect((error as SDKError).cause).toBeInstanceOf(ValidationError) + expect((error as SDKError).cause.message).toBe( + 'Required parameter "taskId" is missing.' + ) + } + }) + }) + + describe('error scenarios', () => { + it('should throw BaseError when server returns error status', async () => { + server.use( + http.get( + `${client.config.apiUrl}/relayer/status/0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef`, + async () => { + return HttpResponse.json(mockErrorResponse) + } + ) + ) + + const request = createMockRelayStatusRequest() + + await expect( + getRelayedTransactionStatus(client, request) + ).rejects.toThrow(BaseError) + + try { + await getRelayedTransactionStatus(client, request) + } catch (error) { + expect(error).toBeInstanceOf(BaseError) + expect((error as BaseError).name).toBe(ErrorName.ServerError) + expect((error as BaseError).code).toBe(404) + expect((error as BaseError).message).toBe('Task not found') + } + }) + + it('should throw SDKError when network request fails', async () => { + server.use( + http.get( + `${client.config.apiUrl}/relayer/status/0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef`, + async () => { + return HttpResponse.error() + } + ) + ) + + const request = createMockRelayStatusRequest() + + await expect( + getRelayedTransactionStatus(client, request) + ).rejects.toThrow(SDKError) + }) + + it('should throw SDKError when request times out', async () => { + server.use( + http.get( + `${client.config.apiUrl}/relayer/status/0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef`, + async () => { + // Simulate timeout by not responding + await new Promise(() => {}) // Never resolves + } + ) + ) + + const request = createMockRelayStatusRequest() + const timeoutOptions: RequestOptions = { + signal: AbortSignal.timeout(100), // 100ms timeout + } + + await expect( + getRelayedTransactionStatus(client, request, timeoutOptions) + ).rejects.toThrow() + }) + }) +}) diff --git a/src/actions/getRelayerQuote.ts b/src/actions/getRelayerQuote.ts new file mode 100644 index 00000000..fae19e30 --- /dev/null +++ b/src/actions/getRelayerQuote.ts @@ -0,0 +1,83 @@ +import type { + LiFiStep, + RelayerQuoteResponse, + RequestOptions, +} from '@lifi/types' +import { BaseError } from '../errors/baseError.js' +import { ErrorName } from '../errors/constants.js' +import { ValidationError } from '../errors/errors.js' +import { SDKError } from '../errors/SDKError.js' +import { request } from '../request.js' +import type { QuoteRequest, QuoteRequestFromAmount } from '../types/actions.js' +import type { SDKClient } from '../types/core.js' + +/** + * Get a relayer quote for a token transfer + * @param client - The SDK client + * @param params - The configuration of the requested quote + * @param options - Request options + * @throws {LiFiError} - Throws a LiFiError if request fails + * @returns Relayer quote for a token transfer + */ +export const getRelayerQuote = async ( + client: SDKClient, + params: QuoteRequestFromAmount, + options?: RequestOptions +): Promise => { + const requiredParameters: Array = [ + 'fromChain', + 'fromToken', + 'fromAddress', + 'fromAmount', + 'toChain', + 'toToken', + ] + for (const requiredParameter of requiredParameters) { + if (!params[requiredParameter]) { + throw new SDKError( + new ValidationError( + `Required parameter "${requiredParameter}" is missing.` + ) + ) + } + } + + // apply defaults + params.integrator ??= client.config.integrator + params.order ??= client.config.routeOptions?.order + 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 + + for (const key of Object.keys(params)) { + if (!params[key as keyof QuoteRequest]) { + delete params[key as keyof QuoteRequest] + } + } + + const result = await request( + client.config, + `${client.config.apiUrl}/relayer/quote?${new URLSearchParams( + params as unknown as Record + )}`, + { + signal: options?.signal, + } + ) + + if (result.status === 'error') { + throw new BaseError( + ErrorName.ServerError, + result.data.code, + result.data.message + ) + } + + return result.data +} diff --git a/src/actions/getRelayerQuote.unit.spec.ts b/src/actions/getRelayerQuote.unit.spec.ts new file mode 100644 index 00000000..6b410162 --- /dev/null +++ b/src/actions/getRelayerQuote.unit.spec.ts @@ -0,0 +1,220 @@ +import type { + LiFiStep, + RelayerQuoteResponse, + RequestOptions, +} from '@lifi/types' +import { ChainId } from '@lifi/types' +import { HttpResponse, http } from 'msw' +import { describe, expect, it } from 'vitest' +import { BaseError } from '../errors/baseError.js' +import { ErrorName } from '../errors/constants.js' +import { ValidationError } from '../errors/errors.js' +import { SDKError } from '../errors/SDKError.js' +import type { QuoteRequestFromAmount } from '../types/actions.js' +import { client, setupTestServer } from './actions.unit.handlers.js' +import { getRelayerQuote } from './getRelayerQuote.js' + +describe('getRelayerQuote', () => { + const server = setupTestServer() + + const createMockQuoteRequest = ( + overrides: Partial = {} + ): QuoteRequestFromAmount => ({ + fromChain: ChainId.ETH, + fromToken: '0xA0b86a33E6441c8C06DDD4f36e4C4C5B4c3B4c3B', + fromAddress: '0x1234567890123456789012345678901234567890', + fromAmount: '1000000', + toChain: ChainId.POL, + toToken: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', + ...overrides, + }) + + const mockLiFiStep: LiFiStep = { + id: 'test-step-id', + type: 'lifi', + includedSteps: [], + tool: 'test-tool', + toolDetails: { + key: 'test-tool', + name: 'Test Tool', + logoURI: 'https://example.com/logo.png', + }, + action: { + fromChainId: ChainId.ETH, + toChainId: ChainId.POL, + fromToken: { + address: '0xA0b86a33E6441c8C06DDD4f36e4C4C5B4c3B4c3B', + symbol: 'USDC', + decimals: 6, + chainId: ChainId.ETH, + name: 'USD Coin', + priceUSD: '1.00', + }, + toToken: { + address: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', + symbol: 'USDC', + decimals: 6, + chainId: ChainId.POL, + name: 'USD Coin', + priceUSD: '1.00', + }, + fromAmount: '1000000', + fromAddress: '0x1234567890123456789012345678901234567890', + toAddress: '0x1234567890123456789012345678901234567890', + }, + estimate: { + fromAmount: '1000000', + toAmount: '1000000', + toAmountMin: '970000', + approvalAddress: '0x1234567890123456789012345678901234567890', + tool: 'test-tool', + executionDuration: 30000, + }, + transactionRequest: { + to: '0x1234567890123456789012345678901234567890', + data: '0x', + value: '0', + gasLimit: '100000', + }, + } + + const mockSuccessResponse: RelayerQuoteResponse = { + status: 'ok', + data: mockLiFiStep, + } + + const mockErrorResponse: RelayerQuoteResponse = { + status: 'error', + data: { + code: 400, + message: 'Invalid request parameters', + }, + } + + describe('success scenarios', () => { + it('should get relayer quote successfully', async () => { + server.use( + http.get(`${client.config.apiUrl}/relayer/quote`, async () => { + return HttpResponse.json(mockSuccessResponse) + }) + ) + + const request = createMockQuoteRequest() + + const result = await getRelayerQuote(client, request) + + expect(result).toEqual(mockLiFiStep) + }) + + it('should pass request options correctly', async () => { + const mockAbortController = new AbortController() + const options: RequestOptions = { + signal: mockAbortController.signal, + } + + let capturedOptions: any + server.use( + http.get( + `${client.config.apiUrl}/relayer/quote`, + async ({ request }) => { + capturedOptions = request + return HttpResponse.json(mockSuccessResponse) + } + ) + ) + + const request = createMockQuoteRequest() + + await getRelayerQuote(client, request, options) + + expect(capturedOptions.signal).toBeDefined() + expect(capturedOptions.signal).toBeInstanceOf(AbortSignal) + }) + }) + + describe('validation scenarios', () => { + it('should throw SDKError when required parameters are missing', async () => { + const testCases = [ + { param: 'fromChain', value: undefined }, + { param: 'fromToken', value: undefined }, + { param: 'fromAddress', value: undefined }, + { param: 'fromAmount', value: undefined }, + { param: 'toChain', value: undefined }, + { param: 'toToken', value: undefined }, + ] + + for (const testCase of testCases) { + const invalidRequest = createMockQuoteRequest({ + [testCase.param]: testCase.value, + }) + + await expect(getRelayerQuote(client, invalidRequest)).rejects.toThrow( + SDKError + ) + + try { + await getRelayerQuote(client, invalidRequest) + } catch (error) { + expect(error).toBeInstanceOf(SDKError) + expect((error as SDKError).cause).toBeInstanceOf(ValidationError) + expect((error as SDKError).cause.message).toBe( + `Required parameter "${testCase.param}" is missing.` + ) + } + } + }) + }) + + describe('error scenarios', () => { + it('should throw BaseError when server returns error status', async () => { + server.use( + http.get(`${client.config.apiUrl}/relayer/quote`, async () => { + return HttpResponse.json(mockErrorResponse) + }) + ) + + const request = createMockQuoteRequest() + + await expect(getRelayerQuote(client, request)).rejects.toThrow(BaseError) + + try { + await getRelayerQuote(client, request) + } catch (error) { + expect(error).toBeInstanceOf(BaseError) + expect((error as BaseError).name).toBe(ErrorName.ServerError) + expect((error as BaseError).code).toBe(400) + expect((error as BaseError).message).toBe('Invalid request parameters') + } + }) + + it('should throw SDKError when network request fails', async () => { + server.use( + http.get(`${client.config.apiUrl}/relayer/quote`, async () => { + return HttpResponse.error() + }) + ) + + const request = createMockQuoteRequest() + + await expect(getRelayerQuote(client, request)).rejects.toThrow(SDKError) + }) + + it('should throw SDKError when request times out', async () => { + server.use( + http.get(`${client.config.apiUrl}/relayer/quote`, async () => { + // Simulate timeout by not responding + await new Promise(() => {}) // Never resolves + }) + ) + + const request = createMockQuoteRequest() + const timeoutOptions: RequestOptions = { + signal: AbortSignal.timeout(100), // 100ms timeout + } + + await expect( + getRelayerQuote(client, request, timeoutOptions) + ).rejects.toThrow() + }) + }) +}) diff --git a/src/actions/getRoutes.ts b/src/actions/getRoutes.ts new file mode 100644 index 00000000..3c07331e --- /dev/null +++ b/src/actions/getRoutes.ts @@ -0,0 +1,43 @@ +import type { RequestOptions, RoutesRequest, RoutesResponse } from '@lifi/types' +import { ValidationError } from '../errors/errors.js' +import { SDKError } from '../errors/SDKError.js' +import { request } from '../request.js' +import { isRoutesRequest } from '../typeguards.js' +import type { SDKClient } from '../types/core.js' + +/** + * Get a set of routes for a request that describes a transfer of tokens. + * @param client - The SDK client. + * @param params - A description of the transfer. + * @param options - Request options + * @returns The resulting routes that can be used to realize the described transfer of tokens. + * @throws {LiFiError} Throws a LiFiError if request fails. + */ +export const getRoutes = async ( + client: SDKClient, + params: RoutesRequest, + options?: RequestOptions +): Promise => { + if (!isRoutesRequest(params)) { + throw new SDKError(new ValidationError('Invalid routes request.')) + } + // apply defaults + params.options = { + integrator: client.config.integrator, + ...client.config.routeOptions, + ...params.options, + } + + return await request( + client.config, + `${client.config.apiUrl}/advanced/routes`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(params), + signal: options?.signal, + } + ) +} diff --git a/src/actions/getRoutes.unit.spec.ts b/src/actions/getRoutes.unit.spec.ts new file mode 100644 index 00000000..2dce9534 --- /dev/null +++ b/src/actions/getRoutes.unit.spec.ts @@ -0,0 +1,112 @@ +import { findDefaultToken } from '@lifi/data-types' +import type { RoutesRequest } from '@lifi/types' +import { ChainId, CoinKey } from '@lifi/types' +import { describe, expect, it, vi } from 'vitest' +import * as request from '../request.js' +import { client, setupTestServer } from './actions.unit.handlers.js' +import { getRoutes } from './getRoutes.js' + +const mockedFetch = vi.spyOn(request, 'request') + +describe('getRoutes', () => { + setupTestServer() + + const getRoutesRequest = ({ + fromChainId = ChainId.BSC, + fromAmount = '10000000000000', + fromTokenAddress = findDefaultToken(CoinKey.USDC, ChainId.BSC).address, + toChainId = ChainId.DAI, + toTokenAddress = findDefaultToken(CoinKey.USDC, ChainId.DAI).address, + options = { slippage: 0.03 }, + }: { + fromChainId?: ChainId + fromAmount?: string + fromTokenAddress?: string + toChainId?: ChainId + toTokenAddress?: string + options?: { slippage: number } + }): RoutesRequest => ({ + fromChainId, + fromAmount, + fromTokenAddress, + toChainId, + toTokenAddress, + options, + }) + + describe('user input is invalid', () => { + it('should throw Error because of invalid fromChainId type', async () => { + const request = getRoutesRequest({ + fromChainId: 'xxx' as unknown as ChainId, + }) + + await expect(getRoutes(client, request)).rejects.toThrow( + 'Invalid routes request.' + ) + expect(mockedFetch).toHaveBeenCalledTimes(0) + }) + + it('should throw Error because of invalid fromAmount type', async () => { + const request = getRoutesRequest({ + fromAmount: 10000000000000 as unknown as string, + }) + + await expect(getRoutes(client, request)).rejects.toThrow( + 'Invalid routes request.' + ) + expect(mockedFetch).toHaveBeenCalledTimes(0) + }) + + it('should throw Error because of invalid fromTokenAddress type', async () => { + const request = getRoutesRequest({ + fromTokenAddress: 1234 as unknown as string, + }) + + await expect(getRoutes(client, request)).rejects.toThrow( + 'Invalid routes request.' + ) + expect(mockedFetch).toHaveBeenCalledTimes(0) + }) + + it('should throw Error because of invalid toChainId type', async () => { + const request = getRoutesRequest({ + toChainId: 'xxx' as unknown as ChainId, + }) + + await expect(getRoutes(client, request)).rejects.toThrow( + 'Invalid routes request.' + ) + expect(mockedFetch).toHaveBeenCalledTimes(0) + }) + + it('should throw Error because of invalid toTokenAddress type', async () => { + const request = getRoutesRequest({ toTokenAddress: '' }) + + await expect(getRoutes(client, request)).rejects.toThrow( + 'Invalid routes request.' + ) + expect(mockedFetch).toHaveBeenCalledTimes(0) + }) + + it('should throw Error because of invalid options type', async () => { + const request = getRoutesRequest({ + options: { slippage: 'not a number' as unknown as number }, + }) + + await expect(getRoutes(client, request)).rejects.toThrow( + 'Invalid routes request.' + ) + expect(mockedFetch).toHaveBeenCalledTimes(0) + }) + }) + + describe('user input is valid', () => { + describe('and the backend call is successful', () => { + it('call the server once', async () => { + const request = getRoutesRequest({}) + await getRoutes(client, request) + expect(mockedFetch).toHaveBeenCalledTimes(1) + }) + }) + }) +}) diff --git a/src/actions/getStatus.ts b/src/actions/getStatus.ts new file mode 100644 index 00000000..5921cc3e --- /dev/null +++ b/src/actions/getStatus.ts @@ -0,0 +1,36 @@ +import type { RequestOptions, StatusResponse } from '@lifi/types' +import { ValidationError } from '../errors/errors.js' +import { SDKError } from '../errors/SDKError.js' +import { request } from '../request.js' +import type { GetStatusRequestExtended } from '../types/actions.js' +import type { SDKClient } from '../types/core.js' + +/** + * Check the status of a transfer. For cross chain transfers, the "bridge" parameter is required. + * @param client - The SDK client + * @param params - Configuration of the requested status + * @param options - Request options. + * @throws {LiFiError} - Throws a LiFiError if request fails + * @returns Returns status response. + */ +export const getStatus = async ( + client: SDKClient, + params: GetStatusRequestExtended, + options?: RequestOptions +): Promise => { + if (!params.txHash) { + throw new SDKError( + new ValidationError('Required parameter "txHash" is missing.') + ) + } + const queryParams = new URLSearchParams( + params as unknown as Record + ) + return await request( + client.config, + `${client.config.apiUrl}/status?${queryParams}`, + { + signal: options?.signal, + } + ) +} diff --git a/src/actions/getStatus.unit.spec.ts b/src/actions/getStatus.unit.spec.ts new file mode 100644 index 00000000..5fb9ade6 --- /dev/null +++ b/src/actions/getStatus.unit.spec.ts @@ -0,0 +1,51 @@ +import { ChainId } from '@lifi/types' +import { describe, expect, it, vi } from 'vitest' +import { ValidationError } from '../errors/errors.js' +import { SDKError } from '../errors/SDKError.js' +import * as request from '../request.js' +import { client, setupTestServer } from './actions.unit.handlers.js' +import { getStatus } from './getStatus.js' + +const mockedFetch = vi.spyOn(request, 'request') + +describe('getStatus', () => { + setupTestServer() + + const fromChain = ChainId.DAI + const toChain = ChainId.POL + const txHash = 'some tx hash' + const bridge = 'some bridge tool' + + describe('user input is invalid', () => { + it('throw an error', async () => { + await expect( + getStatus(client, { + bridge, + fromChain, + toChain, + txHash: undefined as unknown as string, + }) + ).rejects.toThrowError( + new SDKError( + new ValidationError('Required parameter "txHash" is missing.') + ) + ) + + expect(mockedFetch).toHaveBeenCalledTimes(0) + }) + }) + + describe('user input is valid', () => { + describe('and the backend call is successful', () => { + it('call the server once', async () => { + await getStatus(client, { + bridge, + fromChain, + toChain, + txHash, + }) + expect(mockedFetch).toHaveBeenCalledTimes(1) + }) + }) + }) +}) diff --git a/src/actions/getStepTransaction.ts b/src/actions/getStepTransaction.ts new file mode 100644 index 00000000..9e34ec4d --- /dev/null +++ b/src/actions/getStepTransaction.ts @@ -0,0 +1,36 @@ +import type { LiFiStep, RequestOptions, SignedLiFiStep } from '@lifi/types' +import { request } from '../request.js' +import { isStep } from '../typeguards.js' +import type { SDKClient } from '../types/core.js' + +/** + * Get the transaction data for a single step of a route + * @param client - The SDK client + * @param step - The step object. + * @param options - Request options + * @returns The step populated with the transaction data. + * @throws {LiFiError} Throws a LiFiError if request fails. + */ +export const getStepTransaction = async ( + client: SDKClient, + step: LiFiStep | SignedLiFiStep, + options?: RequestOptions +): Promise => { + if (!isStep(step)) { + // While the validation fails for some users we should not enforce it + console.warn('SDK Validation: Invalid Step', step) + } + + return await request( + client.config, + `${client.config.apiUrl}/advanced/stepTransaction`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(step), + signal: options?.signal, + } + ) +} diff --git a/src/actions/getStepTransaction.unit.spec.ts b/src/actions/getStepTransaction.unit.spec.ts new file mode 100644 index 00000000..7e89e37a --- /dev/null +++ b/src/actions/getStepTransaction.unit.spec.ts @@ -0,0 +1,140 @@ +import { findDefaultToken } from '@lifi/data-types' +import type { Action, Estimate, LiFiStep, StepTool, Token } from '@lifi/types' +import { ChainId, CoinKey } from '@lifi/types' +import { describe, expect, it, vi } from 'vitest' +import * as request from '../request.js' +import { client, setupTestServer } from './actions.unit.handlers.js' +import { getStepTransaction } from './getStepTransaction.js' + +const mockedFetch = vi.spyOn(request, 'request') + +describe('getStepTransaction', () => { + setupTestServer() + + const getAction = ({ + fromChainId = ChainId.BSC, + fromAmount = '10000000000000', + fromToken = findDefaultToken(CoinKey.USDC, ChainId.BSC), + fromAddress = 'some from address', // we don't validate the format of addresses atm + toChainId = ChainId.DAI, + toToken = findDefaultToken(CoinKey.USDC, ChainId.DAI), + toAddress = 'some to address', + slippage = 0.03, + }): Action => ({ + fromChainId, + fromAmount, + fromToken: fromToken as Token, + fromAddress, + toChainId, + toToken: toToken as Token, + 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', + }): Estimate => ({ + fromAmount, + toAmount, + toAmountMin, + approvalAddress, + executionDuration, + tool, + }) + + const getStep = ({ + id = 'some random id', + type = 'lifi', + tool = 'some swap tool', + action = getAction({}), + estimate = getEstimate({}), + }: { + id?: string + type?: 'lifi' + tool?: StepTool + action?: Action + estimate?: Estimate + }): LiFiStep => ({ + id, + type, + tool, + toolDetails: { + key: tool, + name: tool, + logoURI: '', + }, + action, + estimate, + includedSteps: [], + }) + + describe('with a swap step', () => { + // While the validation fails for some users we should not enforce it + describe.skip('user input is invalid', () => { + it('should throw Error because of invalid id', async () => { + const step = getStep({ id: null as unknown as string }) + + await expect(getStepTransaction(client, step)).rejects.toThrow( + 'Invalid step.' + ) + expect(mockedFetch).toHaveBeenCalledTimes(0) + }) + + it('should throw Error because of invalid type', async () => { + const step = getStep({ type: 42 as unknown as 'lifi' }) + + await expect(getStepTransaction(client, step)).rejects.toThrow( + 'Invalid Step' + ) + expect(mockedFetch).toHaveBeenCalledTimes(0) + }) + + it('should throw Error because of invalid tool', async () => { + const step = getStep({ tool: null as unknown as StepTool }) + + await expect(getStepTransaction(client, step)).rejects.toThrow( + 'Invalid step.' + ) + expect(mockedFetch).toHaveBeenCalledTimes(0) + }) + + // more indepth checks for the action type should be done once we have real schema validation + it('should throw Error because of invalid action', async () => { + const step = getStep({ action: 'xxx' as unknown as Action }) + + await expect(getStepTransaction(client, step)).rejects.toThrow( + 'Invalid step.' + ) + expect(mockedFetch).toHaveBeenCalledTimes(0) + }) + + // more indepth checks for the estimate type should be done once we have real schema validation + it('should throw Error because of invalid estimate', async () => { + const step = getStep({ + estimate: 'Is this really an estimate?' as unknown as Estimate, + }) + + await expect(getStepTransaction(client, step)).rejects.toThrow( + 'Invalid step.' + ) + expect(mockedFetch).toHaveBeenCalledTimes(0) + }) + }) + + describe('user input is valid', () => { + describe('and the backend call is successful', () => { + it('call the server once', async () => { + const step = getStep({}) + + await getStepTransaction(client, step) + expect(mockedFetch).toHaveBeenCalledTimes(1) + }) + }) + }) + }) +}) diff --git a/src/actions/getToken.ts b/src/actions/getToken.ts new file mode 100644 index 00000000..c2acfd0b --- /dev/null +++ b/src/actions/getToken.ts @@ -0,0 +1,47 @@ +import type { + ChainId, + ChainKey, + RequestOptions, + TokenExtended, +} 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' + +/** + * Fetch information about a Token + * @param client - The SDK client + * @param chain - Id or key of the chain that contains the token + * @param token - Address or symbol of the token on the requested chain + * @param options - Request options + * @throws {LiFiError} - Throws a LiFiError if request fails + * @returns Token information + */ +export const getToken = async ( + client: SDKClient, + chain: ChainKey | ChainId, + token: string, + options?: RequestOptions +): Promise => { + if (!chain) { + throw new SDKError( + new ValidationError('Required parameter "chain" is missing.') + ) + } + if (!token) { + throw new SDKError( + new ValidationError('Required parameter "token" is missing.') + ) + } + return await request( + client.config, + `${client.config.apiUrl}/token?${new URLSearchParams({ + chain, + token, + } as Record)}`, + { + signal: options?.signal, + } + ) +} diff --git a/src/actions/getToken.unit.spec.ts b/src/actions/getToken.unit.spec.ts new file mode 100644 index 00000000..04f5d054 --- /dev/null +++ b/src/actions/getToken.unit.spec.ts @@ -0,0 +1,45 @@ +import { ChainId } from '@lifi/types' +import { describe, expect, it, vi } from 'vitest' +import { ValidationError } from '../errors/errors.js' +import { SDKError } from '../errors/SDKError.js' +import * as request from '../request.js' +import { client, setupTestServer } from './actions.unit.handlers.js' +import { getToken } from './getToken.js' + +const mockedFetch = vi.spyOn(request, 'request') + +describe('getToken', () => { + setupTestServer() + + describe('user input is invalid', () => { + it('throw an error', async () => { + await expect( + getToken(client, undefined as unknown as ChainId, 'DAI') + ).rejects.toThrowError( + new SDKError( + new ValidationError('Required parameter "chain" is missing.') + ) + ) + expect(mockedFetch).toHaveBeenCalledTimes(0) + + await expect( + getToken(client, ChainId.ETH, undefined as unknown as string) + ).rejects.toThrowError( + new SDKError( + new ValidationError('Required parameter "token" is missing.') + ) + ) + expect(mockedFetch).toHaveBeenCalledTimes(0) + }) + }) + + describe('user input is valid', () => { + describe('and the backend call is successful', () => { + it('call the server once', async () => { + await getToken(client, ChainId.DAI, 'DAI') + + expect(mockedFetch).toHaveBeenCalledTimes(1) + }) + }) + }) +}) diff --git a/src/actions/getTokenBalance.ts b/src/actions/getTokenBalance.ts new file mode 100644 index 00000000..ef473f4f --- /dev/null +++ b/src/actions/getTokenBalance.ts @@ -0,0 +1,21 @@ +import type { Token, TokenAmount } from '@lifi/types' +import type { SDKClient } from '../types/core.js' +import { getTokenBalances } from './getTokenBalances.js' + +/** + * Returns the balances of a specific token a wallet holds across all aggregated chains. + * @param client - The SDK client. + * @param walletAddress - A wallet address. + * @param token - A Token object. + * @returns An object containing the token and the amounts on different chains. + * @throws {ValidationError} Throws a ValidationError if validation fails. + * @throws {Error} Throws an Error if the SDK Provider for the wallet address is not found. + */ +export const getTokenBalance = async ( + client: SDKClient, + walletAddress: string, + token: Token +): Promise => { + const tokenAmounts = await getTokenBalances(client, walletAddress, [token]) + return tokenAmounts.length ? tokenAmounts[0] : null +} diff --git a/src/actions/getTokenBalance.unit.spec.ts b/src/actions/getTokenBalance.unit.spec.ts new file mode 100644 index 00000000..951e1c2b --- /dev/null +++ b/src/actions/getTokenBalance.unit.spec.ts @@ -0,0 +1,61 @@ +import { findDefaultToken } from '@lifi/data-types' +import type { Token } from '@lifi/types' +import { ChainId, CoinKey } from '@lifi/types' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { client } from './actions.unit.handlers.js' +import { getTokenBalance } from './getTokenBalance.js' + +const mockedGetTokenBalance = vi.spyOn( + await import('./getTokenBalance.js'), + 'getTokenBalance' +) + +describe('getTokenBalance', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + const SOME_TOKEN = { + ...findDefaultToken(CoinKey.USDC, ChainId.DAI), + priceUSD: '', + } + const SOME_WALLET_ADDRESS = 'some wallet address' + + describe('user input is invalid', () => { + it('should throw Error because of missing walletAddress', async () => { + await expect(getTokenBalance(client, '', SOME_TOKEN)).rejects.toThrow( + 'Missing walletAddress.' + ) + }) + + it('should throw Error because of invalid token', async () => { + await expect( + getTokenBalance(client, SOME_WALLET_ADDRESS, { + address: 'some wrong stuff', + chainId: 'not a chain Id', + } as unknown as Token) + ).rejects.toThrow('Invalid tokens passed.') + }) + }) + + describe('user input is valid', () => { + it('should call the balance service', async () => { + const balanceResponse = { + ...SOME_TOKEN, + amount: 123n, + blockNumber: 1n, + } + + mockedGetTokenBalance.mockReturnValue(Promise.resolve(balanceResponse)) + + const result = await getTokenBalance( + client, + SOME_WALLET_ADDRESS, + SOME_TOKEN + ) + + expect(mockedGetTokenBalance).toHaveBeenCalledTimes(1) + expect(result).toEqual(balanceResponse) + }) + }) +}) diff --git a/src/actions/getTokenBalances.ts b/src/actions/getTokenBalances.ts new file mode 100644 index 00000000..d5fe6fbc --- /dev/null +++ b/src/actions/getTokenBalances.ts @@ -0,0 +1,47 @@ +import type { + Token, + TokenAmount, + TokenAmountExtended, + TokenExtended, +} from '@lifi/types' +import type { SDKClient } from '../types/core.js' +import { getTokenBalancesByChain } from './getTokenBalancesByChain.js' + +/** + * Returns the balances for a list tokens a wallet holds across all aggregated chains. + * @param client - The SDK client. + * @param walletAddress - A wallet address. + * @param tokens - A list of Token (or TokenExtended) objects. + * @returns A list of objects containing the tokens and the amounts on different chains. + * @throws {ValidationError} Throws a ValidationError if validation fails. + * @throws {Error} Throws an Error if the SDK Provider for the wallet address is not found. + */ +export async function getTokenBalances( + client: SDKClient, + walletAddress: string, + tokens: Token[] +): Promise +export async function getTokenBalances( + client: SDKClient, + walletAddress: string, + tokens: TokenExtended[] +): Promise { + // split by chain + const tokensByChain = tokens.reduce( + (tokens, token) => { + if (!tokens[token.chainId]) { + tokens[token.chainId] = [] + } + tokens[token.chainId].push(token) + return tokens + }, + {} as { [chainId: number]: Token[] | TokenExtended[] } + ) + + const tokenAmountsByChain = await getTokenBalancesByChain( + client, + walletAddress, + tokensByChain + ) + return Object.values(tokenAmountsByChain).flat() +} diff --git a/src/actions/getTokenBalances.unit.spec.ts b/src/actions/getTokenBalances.unit.spec.ts new file mode 100644 index 00000000..5dcc05fa --- /dev/null +++ b/src/actions/getTokenBalances.unit.spec.ts @@ -0,0 +1,68 @@ +import { findDefaultToken } from '@lifi/data-types' +import type { Token } from '@lifi/types' +import { ChainId, CoinKey } from '@lifi/types' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { client } from './actions.unit.handlers.js' +import { getTokenBalances } from './getTokenBalances.js' + +const mockedGetTokenBalances = vi.spyOn( + await import('./getTokenBalances.js'), + 'getTokenBalances' +) + +describe('getTokenBalances', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + const SOME_TOKEN = { + ...findDefaultToken(CoinKey.USDC, ChainId.DAI), + priceUSD: '', + } + const SOME_WALLET_ADDRESS = 'some wallet address' + + describe('user input is invalid', () => { + it('should throw Error because of missing walletAddress', async () => { + await expect(getTokenBalances(client, '', [SOME_TOKEN])).rejects.toThrow( + 'Missing walletAddress.' + ) + }) + + it('should throw Error because of an invalid token', async () => { + await expect( + getTokenBalances(client, SOME_WALLET_ADDRESS, [ + SOME_TOKEN, + { not: 'a token' } as unknown as Token, + ]) + ).rejects.toThrow('Invalid tokens passed.') + }) + + it('should return empty token list as it is', async () => { + mockedGetTokenBalances.mockReturnValue(Promise.resolve([])) + const result = await getTokenBalances(client, SOME_WALLET_ADDRESS, []) + expect(result).toEqual([]) + expect(mockedGetTokenBalances).toHaveBeenCalledTimes(1) + }) + }) + + describe('user input is valid', () => { + it('should call the balance service', async () => { + const balanceResponse = [ + { + ...SOME_TOKEN, + amount: 123n, + blockNumber: 1n, + }, + ] + + mockedGetTokenBalances.mockReturnValue(Promise.resolve(balanceResponse)) + + const result = await getTokenBalances(client, SOME_WALLET_ADDRESS, [ + SOME_TOKEN, + ]) + + expect(mockedGetTokenBalances).toHaveBeenCalledTimes(1) + expect(result).toEqual(balanceResponse) + }) + }) +}) diff --git a/src/actions/getTokenBalancesByChain.ts b/src/actions/getTokenBalancesByChain.ts new file mode 100644 index 00000000..e591f498 --- /dev/null +++ b/src/actions/getTokenBalancesByChain.ts @@ -0,0 +1,76 @@ +import type { + Token, + TokenAmount, + TokenAmountExtended, + TokenExtended, +} from '@lifi/types' +import { ValidationError } from '../errors/errors.js' +import { isToken } from '../typeguards.js' +import type { SDKClient } from '../types/core.js' + +/** + * This method queries the balances of tokens for a specific list of chains for a given wallet. + * @param client - The SDK client. + * @param walletAddress - A wallet address. + * @param tokensByChain - A list of token objects organized by chain ids. + * @returns A list of objects containing the tokens and the amounts on different chains organized by the chosen chains. + * @throws {ValidationError} Throws a ValidationError if validation fails. + * @throws {Error} Throws an Error if the SDK Provider for the wallet address is not found. + */ +export async function getTokenBalancesByChain( + client: SDKClient, + walletAddress: string, + tokensByChain: { [chainId: number]: Token[] } +): Promise<{ [chainId: number]: TokenAmount[] }> +export async function getTokenBalancesByChain( + client: SDKClient, + walletAddress: string, + tokensByChain: { [chainId: number]: TokenExtended[] } +): Promise<{ [chainId: number]: TokenAmountExtended[] }> { + if (!walletAddress) { + throw new ValidationError('Missing walletAddress.') + } + + const tokenList = Object.values(tokensByChain).flat() + const invalidTokens = tokenList.filter((token) => !isToken(token)) + if (invalidTokens.length) { + throw new ValidationError('Invalid tokens passed.') + } + + const provider = client.providers.find((provider) => + provider.isAddress(walletAddress) + ) + if (!provider) { + throw new Error(`SDK Token Provider for ${walletAddress} is not found.`) + } + + const tokenAmountsByChain: { + [chainId: number]: TokenAmount[] | TokenAmountExtended[] + } = {} + const tokenAmountsSettled = await Promise.allSettled( + Object.keys(tokensByChain).map(async (chainIdStr) => { + const chainId = Number.parseInt(chainIdStr, 10) + const chain = await client.getChainById(chainId) + if (provider.type === chain.chainType) { + const tokenAmounts = await provider.getBalance( + client, + walletAddress, + tokensByChain[chainId] + ) + tokenAmountsByChain[chainId] = tokenAmounts + } else { + // if the provider is not the same as the chain type, + // return the tokens as is + tokenAmountsByChain[chainId] = tokensByChain[chainId] + } + }) + ) + if (client.config.debug) { + for (const result of tokenAmountsSettled) { + if (result.status === 'rejected') { + console.warn("Couldn't fetch token balance.", result.reason) + } + } + } + return tokenAmountsByChain +} diff --git a/src/actions/getTokenBalancesByChain.unit.spec.ts b/src/actions/getTokenBalancesByChain.unit.spec.ts new file mode 100644 index 00000000..d8d0aabb --- /dev/null +++ b/src/actions/getTokenBalancesByChain.unit.spec.ts @@ -0,0 +1,108 @@ +import { findDefaultToken } from '@lifi/data-types' +import type { Token } from '@lifi/types' +import { ChainId, CoinKey } from '@lifi/types' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { client } from './actions.unit.handlers.js' +import { getTokenBalancesByChain } from './getTokenBalancesByChain.js' + +const mockedGetTokenBalancesForChains = vi.spyOn( + await import('./getTokenBalancesByChain.js'), + 'getTokenBalancesByChain' +) + +describe('getTokenBalancesByChain', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + const SOME_TOKEN = { + ...findDefaultToken(CoinKey.USDC, ChainId.DAI), + priceUSD: '', + } + const SOME_WALLET_ADDRESS = 'some wallet address' + + describe('user input is invalid', () => { + it('should throw Error because of missing walletAddress', async () => { + await expect( + getTokenBalancesByChain(client, '', { + [ChainId.DAI]: [SOME_TOKEN], + }) + ).rejects.toThrow('Missing walletAddress.') + }) + + it('should throw Error because of an invalid token', async () => { + await expect( + getTokenBalancesByChain(client, SOME_WALLET_ADDRESS, { + [ChainId.DAI]: [{ not: 'a token' } as unknown as Token], + }) + ).rejects.toThrow('Invalid tokens passed.') + }) + + it('should return empty token list as it is', async () => { + mockedGetTokenBalancesForChains.mockReturnValue(Promise.resolve([])) + + const result = await getTokenBalancesByChain( + client, + SOME_WALLET_ADDRESS, + { + [ChainId.DAI]: [], + } + ) + + expect(result).toEqual([]) + expect(mockedGetTokenBalancesForChains).toHaveBeenCalledTimes(1) + }) + }) + + describe('user input is valid', () => { + it('should call the balance service', async () => { + const balanceResponse = { + [ChainId.DAI]: [ + { + ...SOME_TOKEN, + amount: 123n, + blockNumber: 1n, + }, + ], + } + + mockedGetTokenBalancesForChains.mockReturnValue( + Promise.resolve(balanceResponse) + ) + + const result = await getTokenBalancesByChain( + client, + SOME_WALLET_ADDRESS, + { + [ChainId.DAI]: [SOME_TOKEN], + } + ) + + expect(mockedGetTokenBalancesForChains).toHaveBeenCalledTimes(1) + expect(result).toEqual(balanceResponse) + }) + }) + + describe('provider is not the same as the chain type', () => { + it('should return the tokens as is', async () => { + const balanceResponse = { + [ChainId.DAI]: [SOME_TOKEN], + } + + mockedGetTokenBalancesForChains.mockReturnValue( + Promise.resolve(balanceResponse) + ) + + const result = await getTokenBalancesByChain( + client, + SOME_WALLET_ADDRESS, + { + [ChainId.DAI]: [SOME_TOKEN], + } + ) + + expect(mockedGetTokenBalancesForChains).toHaveBeenCalledTimes(1) + expect(result).toEqual(balanceResponse) + }) + }) +}) diff --git a/src/actions/getTokens.ts b/src/actions/getTokens.ts new file mode 100644 index 00000000..4690818a --- /dev/null +++ b/src/actions/getTokens.ts @@ -0,0 +1,54 @@ +import type { + RequestOptions, + TokensExtendedResponse, + TokensRequest, + TokensResponse, +} from '@lifi/types' +import { request } from '../request.js' +import type { SDKClient } from '../types/core.js' +import { withDedupe } from '../utils/withDedupe.js' + +/** + * Get all known tokens. + * @param client - The SDK client + * @param params - The configuration of the requested tokens + * @param options - Request options + * @returns The tokens that are available on the requested chains + */ +export async function getTokens( + client: SDKClient, + params?: TokensRequest & { extended?: false | undefined }, + options?: RequestOptions +): Promise +export async function getTokens( + client: SDKClient, + params: TokensRequest & { extended: true }, + options?: RequestOptions +): Promise +export async function getTokens( + client: SDKClient, + params?: TokensRequest, + options?: RequestOptions +): Promise { + if (params) { + for (const key of Object.keys(params)) { + if (!params[key as keyof TokensRequest]) { + delete params[key as keyof TokensRequest] + } + } + } + const urlSearchParams = new URLSearchParams( + params as Record + ).toString() + const isExtended = params?.extended === true + const response = await withDedupe( + () => + request< + typeof isExtended extends true ? TokensExtendedResponse : TokensResponse + >(client.config, `${client.config.apiUrl}/tokens?${urlSearchParams}`, { + signal: options?.signal, + }), + { id: `${getTokens.name}.${urlSearchParams}` } + ) + return response +} diff --git a/src/actions/getTokens.unit.spec.ts b/src/actions/getTokens.unit.spec.ts new file mode 100644 index 00000000..2a022d02 --- /dev/null +++ b/src/actions/getTokens.unit.spec.ts @@ -0,0 +1,16 @@ +import { ChainId } from '@lifi/types' +import { describe, expect, it } from 'vitest' +import { client, setupTestServer } from './actions.unit.handlers.js' +import { getTokens } from './getTokens.js' + +describe('getTokens', () => { + setupTestServer() + + it('return the tokens', async () => { + const result = await getTokens(client, { + chains: [ChainId.ETH, ChainId.POL], + }) + expect(result).toBeDefined() + expect(result.tokens[ChainId.ETH]).toBeDefined() + }) +}) diff --git a/src/actions/getTools.ts b/src/actions/getTools.ts new file mode 100644 index 00000000..f2a87fa3 --- /dev/null +++ b/src/actions/getTools.ts @@ -0,0 +1,33 @@ +import type { RequestOptions, ToolsRequest, ToolsResponse } from '@lifi/types' +import { request } from '../request.js' +import type { SDKClient } from '../types/core.js' + +/** + * Get the available tools to bridge and swap tokens. + * @param client - The SDK client + * @param params - The configuration of the requested tools + * @param options - Request options + * @returns The tools that are available on the requested chains + */ +export const getTools = async ( + client: SDKClient, + params?: ToolsRequest, + options?: RequestOptions +): Promise => { + if (params) { + for (const key of Object.keys(params)) { + if (!params[key as keyof ToolsRequest]) { + delete params[key as keyof ToolsRequest] + } + } + } + return await request( + client.config, + `${client.config.apiUrl}/tools?${new URLSearchParams( + params as Record + )}`, + { + signal: options?.signal, + } + ) +} diff --git a/src/actions/getTools.unit.spec.ts b/src/actions/getTools.unit.spec.ts new file mode 100644 index 00000000..99aa7a16 --- /dev/null +++ b/src/actions/getTools.unit.spec.ts @@ -0,0 +1,20 @@ +import { ChainId } from '@lifi/types' +import { describe, expect, it } from 'vitest' +import { client, setupTestServer } from './actions.unit.handlers.js' +import { getTools } from './getTools.js' + +describe('getTools', () => { + setupTestServer() + + describe('and the backend succeeds', () => { + it('returns the tools', async () => { + const tools = await getTools(client, { + chains: [ChainId.ETH, ChainId.POL], + }) + + expect(tools).toBeDefined() + expect(tools.bridges).toBeDefined() + expect(tools.exchanges).toBeDefined() + }) + }) +}) diff --git a/src/actions/getTransactionHistory.ts b/src/actions/getTransactionHistory.ts new file mode 100644 index 00000000..5b7539e6 --- /dev/null +++ b/src/actions/getTransactionHistory.ts @@ -0,0 +1,54 @@ +import type { + RequestOptions, + TransactionAnalyticsRequest, + TransactionAnalyticsResponse, +} from '@lifi/types' +import { ValidationError } from '../errors/errors.js' +import { request } from '../request.js' +import type { SDKClient } from '../types/core.js' + +/** + * Get the transaction history for a wallet + * @param client - The SDK client + * @param params - The parameters for the transaction history request + * @param params.wallet - The wallet address + * @param params.status - The status of the transactions + * @param params.fromTimestamp - The start timestamp for the transactions + * @param params.toTimestamp - The end timestamp for the transactions + * @param options - Request options + * @throws {ValidationError} - Throws a ValidationError if parameters are invalid + * @throws {LiFiError} - Throws a LiFiError if request fails. + * @returns The transaction history response + */ +export const getTransactionHistory = async ( + client: SDKClient, + { wallet, status, fromTimestamp, toTimestamp }: TransactionAnalyticsRequest, + options?: RequestOptions +): Promise => { + if (!wallet) { + throw new ValidationError('Required parameter "wallet" is missing.') + } + + const url = new URL(`${client.config.apiUrl}/analytics/transfers`) + + url.searchParams.append('integrator', client.config.integrator) + url.searchParams.append('wallet', wallet) + + if (status) { + url.searchParams.append('status', status) + } + + if (fromTimestamp) { + url.searchParams.append('fromTimestamp', fromTimestamp.toString()) + } + + if (toTimestamp) { + url.searchParams.append('toTimestamp', toTimestamp.toString()) + } + + return await request( + client.config, + url, + options + ) +} diff --git a/src/actions/getTransactionHistory.unit.spec.ts b/src/actions/getTransactionHistory.unit.spec.ts new file mode 100644 index 00000000..c303709f --- /dev/null +++ b/src/actions/getTransactionHistory.unit.spec.ts @@ -0,0 +1,36 @@ +import type { TransactionAnalyticsRequest } 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 { getTransactionHistory } from './getTransactionHistory.js' + +const mockedFetch = vi.spyOn(request, 'request') + +describe('getTransactionHistory', () => { + const server = setupTestServer() + + it('returns empty array in response', async () => { + server.use( + http.get(`${client.config.apiUrl}/analytics/transfers`, async () => + HttpResponse.json({}) + ) + ) + + const walletAnalyticsRequest: TransactionAnalyticsRequest = { + fromTimestamp: 1696326609361, + toTimestamp: 1696326609362, + wallet: '0x5520abcd', + } + + const generatedURL = + 'https://li.quest/v1/analytics/transfers?integrator=lifi-sdk&wallet=0x5520abcd&fromTimestamp=1696326609361&toTimestamp=1696326609362' + + await expect( + getTransactionHistory(client, walletAnalyticsRequest) + ).resolves.toEqual({}) + + expect((mockedFetch.mock.calls[0][1] as URL).href).toEqual(generatedURL) + expect(mockedFetch).toHaveBeenCalledOnce() + }) +}) diff --git a/src/actions/getWalletBalances.ts b/src/actions/getWalletBalances.ts new file mode 100644 index 00000000..59ad54ed --- /dev/null +++ b/src/actions/getWalletBalances.ts @@ -0,0 +1,36 @@ +import type { + GetWalletBalanceExtendedResponse, + RequestOptions, + WalletTokenExtended, +} from '@lifi/types' +import { ValidationError } from '../errors/errors.js' +import { request } from '../request.js' +import type { SDKClient } from '../types/core.js' + +/** + * Returns the balances of tokens a wallet holds across EVM chains. + * @param client - The SDK client. + * @param walletAddress - A wallet address. + * @param options - Optional request options. + * @returns An object containing the tokens and the amounts organized by chain ids. + * @throws {ValidationError} Throws a ValidationError if parameters are invalid. + */ +export const getWalletBalances = async ( + client: SDKClient, + walletAddress: string, + options?: RequestOptions +): Promise> => { + if (!walletAddress) { + throw new ValidationError('Missing walletAddress.') + } + + const response = await request( + client.config, + `${client.config.apiUrl}/wallets/${walletAddress}/balances?extended=true`, + { + signal: options?.signal, + } + ) + + return (response?.balances || {}) as Record +} diff --git a/src/actions/getWalletBalances.unit.spec.ts b/src/actions/getWalletBalances.unit.spec.ts new file mode 100644 index 00000000..05a9c635 --- /dev/null +++ b/src/actions/getWalletBalances.unit.spec.ts @@ -0,0 +1,90 @@ +import { findDefaultToken } from '@lifi/data-types' +import type { WalletTokenExtended } from '@lifi/types' +import { ChainId, CoinKey } from '@lifi/types' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { client } from './actions.unit.handlers.js' +import { getWalletBalances } from './getWalletBalances.js' + +const mockedGetWalletBalances = vi.spyOn( + await import('./getWalletBalances.js'), + 'getWalletBalances' +) + +describe('getWalletBalances', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + const SOME_TOKEN = { + ...findDefaultToken(CoinKey.USDC, ChainId.DAI), + priceUSD: '', + } + const SOME_WALLET_ADDRESS = 'some wallet address' + + describe('user input is invalid', () => { + it('should throw Error because of missing walletAddress', async () => { + await expect(getWalletBalances(client, '')).rejects.toThrow( + 'Missing walletAddress.' + ) + }) + }) + + describe('user input is valid', () => { + it('should call the balance service without options', async () => { + const balanceResponse: Record = { + [ChainId.DAI]: [ + { + ...SOME_TOKEN, + amount: '123', + marketCapUSD: 1000000, + volumeUSD24H: 50000, + fdvUSD: 2000000, + }, + ], + } + + mockedGetWalletBalances.mockReturnValue(Promise.resolve(balanceResponse)) + + const result = await getWalletBalances(client, SOME_WALLET_ADDRESS) + + expect(mockedGetWalletBalances).toHaveBeenCalledTimes(1) + expect(mockedGetWalletBalances).toHaveBeenCalledWith( + client, + SOME_WALLET_ADDRESS + ) + expect(result).toEqual(balanceResponse) + }) + + it('should call the balance service with options', async () => { + const balanceResponse: Record = { + [ChainId.DAI]: [ + { + ...SOME_TOKEN, + amount: '123', + marketCapUSD: 1000000, + volumeUSD24H: 50000, + fdvUSD: 2000000, + }, + ], + } + + const options = { signal: new AbortController().signal } + + mockedGetWalletBalances.mockReturnValue(Promise.resolve(balanceResponse)) + + const result = await getWalletBalances( + client, + SOME_WALLET_ADDRESS, + options + ) + + expect(mockedGetWalletBalances).toHaveBeenCalledTimes(1) + expect(mockedGetWalletBalances).toHaveBeenCalledWith( + client, + SOME_WALLET_ADDRESS, + options + ) + expect(result).toEqual(balanceResponse) + }) + }) +}) diff --git a/src/actions/index.ts b/src/actions/index.ts new file mode 100644 index 00000000..1d2e0fae --- /dev/null +++ b/src/actions/index.ts @@ -0,0 +1,329 @@ +import type { + ChainId, + ChainKey, + ChainsRequest, + ChainType, + ConnectionsRequest, + ConnectionsResponse, + ContractCallsQuoteRequest, + ExtendedChain, + GasRecommendationRequest, + GasRecommendationResponse, + LiFiStep, + RelayRequest, + RelayResponseData, + RelayStatusRequest, + RelayStatusResponseData, + RequestOptions, + RoutesRequest, + RoutesResponse, + SignedLiFiStep, + StatusResponse, + Token, + TokenAmount, + TokenExtended, + TokensExtendedResponse, + TokensRequest, + TokensResponse, + ToolsRequest, + ToolsResponse, + TransactionAnalyticsRequest, + TransactionAnalyticsResponse, + WalletTokenExtended, +} from '@lifi/types' +import type { + GetStatusRequestExtended, + QuoteRequestFromAmount, +} from '../types/actions.js' +import type { SDKClient } from '../types/core.js' +import { getChains } from './getChains.js' +import { getConnections } from './getConnections.js' +import { getContractCallsQuote } from './getContractCallsQuote.js' +import { getGasRecommendation } from './getGasRecommendation.js' +import { getNameServiceAddress } from './getNameServiceAddress.js' +import { getQuote } from './getQuote.js' +import { getRelayedTransactionStatus } from './getRelayedTransactionStatus.js' +import { getRelayerQuote } from './getRelayerQuote.js' +import { getRoutes } from './getRoutes.js' +import { getStatus } from './getStatus.js' +import { getStepTransaction } from './getStepTransaction.js' +import { getToken } from './getToken.js' +import { getTokenBalance } from './getTokenBalance.js' +import { getTokenBalances } from './getTokenBalances.js' +import { getTokenBalancesByChain } from './getTokenBalancesByChain.js' +import { getTokens } from './getTokens.js' +import { getTools } from './getTools.js' +import { getTransactionHistory } from './getTransactionHistory.js' +import { getWalletBalances } from './getWalletBalances.js' +import { relayTransaction } from './relayTransaction.js' + +export type Actions = { + /** + * Get all available chains + * @param params - The configuration of the requested chains + * @param options - Request options + * @returns A list of all available chains + */ + getChains: ( + params?: ChainsRequest, + options?: RequestOptions + ) => Promise + + /** + * Get connections between chains + * @param params - The configuration of the requested connections + * @param options - Request options + * @returns A list of connections + */ + getConnections: ( + params: ConnectionsRequest, + options?: RequestOptions + ) => Promise + + /** + * Get a quote for contract calls + * @param params - The configuration of the requested contract calls quote + * @param options - Request options + * @returns Quote for contract calls + */ + getContractCallsQuote: ( + params: ContractCallsQuoteRequest, + options?: RequestOptions + ) => Promise + + /** + * Get gas recommendation for a chain + * @param params - The configuration of the requested gas recommendation + * @param options - Request options + * @returns Gas recommendation + */ + getGasRecommendation: ( + params: GasRecommendationRequest, + options?: RequestOptions + ) => Promise + + /** + * Get the address of a name service + * @param name - The name to resolve + * @param chainType - The chain type to resolve the name on + * @returns The address of the name service + */ + getNameServiceAddress: ( + name: string, + chainType?: ChainType + ) => Promise + + /** + * Get a quote for a token transfer + * @param params - The configuration of the requested quote + * @param options - Request options + * @returns Quote for a token transfer + */ + getQuote: ( + params: Parameters[1], + options?: RequestOptions + ) => Promise + + /** + * Get the status of a relayed transaction + * @param params - The configuration of the requested relay status + * @param options - Request options + * @returns Status of the relayed transaction + */ + getRelayedTransactionStatus: ( + params: RelayStatusRequest, + options?: RequestOptions + ) => Promise + + /** + * Get a quote from a relayer + * @param params - The configuration of the requested relayer quote + * @param options - Request options + * @returns Quote from a relayer + */ + getRelayerQuote: ( + params: QuoteRequestFromAmount, + options?: RequestOptions + ) => Promise + + /** + * Get a set of routes for a request that describes a transfer of tokens + * @param params - A description of the transfer + * @param options - Request options + * @returns The resulting routes that can be used to realize the described transfer + */ + getRoutes: ( + params: RoutesRequest, + options?: RequestOptions + ) => Promise + + /** + * Get the status of a transaction + * @param params - The configuration of the requested status + * @param options - Request options + * @returns Status of the transaction + */ + getStatus: ( + params: GetStatusRequestExtended, + options?: RequestOptions + ) => Promise + + /** + * Get a step transaction + * @param params - The configuration of the requested step transaction + * @param options - Request options + * @returns Step transaction + */ + getStepTransaction: ( + params: LiFiStep | SignedLiFiStep, + options?: RequestOptions + ) => Promise + + /** + * Get a specific token + * @param chain - Id or key of the chain that contains the token + * @param token - Address or symbol of the token on the requested chain + * @param options - Request options + * @returns Token information + */ + getToken: ( + chain: ChainKey | ChainId, + token: string, + options?: RequestOptions + ) => Promise + + /** + * Get token balance for a specific token + * @param walletAddress - A wallet address + * @param token - A Token object + * @returns Token balance + */ + getTokenBalance: ( + walletAddress: string, + token: Token + ) => Promise + + /** + * Get token balances for multiple tokens + * @param walletAddress - A wallet address + * @param tokens - A list of Token objects + * @returns Token balances + */ + getTokenBalances: ( + walletAddress: string, + tokens: Token[] + ) => Promise + + /** + * Get token balances by chain + * @param walletAddress - A wallet address + * @param tokensByChain - A list of token objects organized by chain ids + * @returns Token balances by chain + */ + getTokenBalancesByChain: ( + walletAddress: string, + tokensByChain: { [chainId: number]: Token[] } + ) => Promise<{ + [chainId: number]: TokenAmount[] + }> + + /** + * Get all available tokens + * @param params - The configuration of the requested tokens + * @param options - Request options + * @returns A list of all available tokens + */ + getTokens: { + ( + params?: TokensRequest & { extended?: false | undefined }, + options?: RequestOptions + ): Promise + ( + params: TokensRequest & { extended: true }, + options?: RequestOptions + ): Promise + } + + /** + * Get all available tools (bridges and exchanges) + * @param params - The configuration of the requested tools + * @param options - Request options + * @returns A list of all available tools + */ + getTools: ( + params?: ToolsRequest, + options?: RequestOptions + ) => Promise + + /** + * Get transaction history + * @param params - The configuration of the requested transaction history + * @param options - Request options + * @returns Transaction history + */ + getTransactionHistory: ( + params: TransactionAnalyticsRequest, + options?: RequestOptions + ) => Promise + + /** + * Get wallet balances + * @param params - The configuration of the requested wallet balances + * @param options - Request options + * @returns Wallet balances + */ + getWalletBalances: ( + walletAddress: string, + options?: RequestOptions + ) => Promise> + + /** + * Relay a transaction through the relayer service + * @param params - The configuration for the relay request + * @param options - Request options + * @returns Task ID and transaction link for the relayed transaction + */ + relayTransaction: ( + params: RelayRequest, + options?: RequestOptions + ) => Promise +} + +export function actions(client: SDKClient): Actions { + return { + getChains: (params, options) => getChains(client, params, options), + getConnections: (params, options) => + getConnections(client, params, options), + getContractCallsQuote: (params, options) => + getContractCallsQuote(client, params, options), + getGasRecommendation: (params, options) => + getGasRecommendation(client, params, options), + getNameServiceAddress: (name, chainType) => + getNameServiceAddress(client, name, chainType), + getTokens: (params, options) => getTokens(client, params as any, options), + getTools: (params, options) => getTools(client, params, options), + getQuote: (params, options) => getQuote(client, params, options), + getRelayedTransactionStatus: (params, options) => + getRelayedTransactionStatus(client, params, options), + getRelayerQuote: (params, options) => + getRelayerQuote(client, params, options), + getRoutes: (params, options) => getRoutes(client, params, options), + getStatus: (params, options) => getStatus(client, params, options), + getStepTransaction: (params, options) => + getStepTransaction(client, params, options), + getToken: (chain, token, options) => + getToken(client, chain, token, options), + getTokenBalance: (walletAddress, token) => + getTokenBalance(client, walletAddress, token), + getTokenBalances: (walletAddress, tokens) => + getTokenBalances(client, walletAddress, tokens), + getTokenBalancesByChain: (walletAddress, tokensByChain) => + getTokenBalancesByChain(client, walletAddress, tokensByChain), + getTransactionHistory: (params, options) => + getTransactionHistory(client, params, options), + getWalletBalances: (walletAddress, options) => + getWalletBalances(client, walletAddress, options), + relayTransaction: (params, options) => + relayTransaction(client, params, options), + } +} diff --git a/src/actions/relayTransaction.ts b/src/actions/relayTransaction.ts new file mode 100644 index 00000000..aee89f9e --- /dev/null +++ b/src/actions/relayTransaction.ts @@ -0,0 +1,74 @@ +import type { + RelayRequest, + RelayResponse, + RelayResponseData, + RequestOptions, +} from '@lifi/types' +import { BaseError } from '../errors/baseError.js' +import { ErrorName } from '../errors/constants.js' +import { ValidationError } from '../errors/errors.js' +import { SDKError } from '../errors/SDKError.js' +import { request } from '../request.js' +import type { SDKClient } from '../types/core.js' + +/** + * Relay a transaction through the relayer service + * @param client - The SDK client + * @param params - The configuration for the relay request + * @param options - Request options + * @throws {LiFiError} - Throws a LiFiError if request fails + * @returns Task ID and transaction link for the relayed transaction + */ +export const relayTransaction = async ( + client: SDKClient, + params: RelayRequest, + options?: RequestOptions +): Promise => { + const requiredParameters: Array = ['typedData'] + + for (const requiredParameter of requiredParameters) { + if (!params[requiredParameter]) { + throw new SDKError( + new ValidationError( + `Required parameter "${requiredParameter}" is missing.` + ) + ) + } + } + + // Determine if the request is for a gasless relayer service or advanced relayer service + // We will use the same endpoint for both after the gasless relayer service is deprecated + const relayerPath = params.typedData.some( + (t) => t.primaryType === 'PermitWitnessTransferFrom' + ) + ? '/relayer/relay' + : '/advanced/relay' + + const result = await request( + client.config, + `${client.config.apiUrl}${relayerPath}`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(params, (_, value) => { + if (typeof value === 'bigint') { + return value.toString() + } + return value + }), + signal: options?.signal, + } + ) + + if (result.status === 'error') { + throw new BaseError( + ErrorName.ServerError, + result.data.code, + result.data.message + ) + } + + return result.data +} diff --git a/src/actions/relayTransaction.unit.spec.ts b/src/actions/relayTransaction.unit.spec.ts new file mode 100644 index 00000000..6f054110 --- /dev/null +++ b/src/actions/relayTransaction.unit.spec.ts @@ -0,0 +1,229 @@ +import type { RelayRequest, RelayResponse, RequestOptions } from '@lifi/types' +import { HttpResponse, http } from 'msw' +import { describe, expect, it } from 'vitest' +import { BaseError } from '../errors/baseError.js' +import { ErrorName } from '../errors/constants.js' +import { SDKError } from '../errors/SDKError.js' +import { client, setupTestServer } from './actions.unit.handlers.js' +import { relayTransaction } from './relayTransaction.js' + +describe('relayTransaction', () => { + const server = setupTestServer() + + const createMockRelayRequest = (typedData: any[]): RelayRequest => ({ + type: 'lifi', + id: 'test-step-id', + includedSteps: [], + tool: 'test-tool', + toolDetails: { + key: 'test-tool', + name: 'Test Tool', + logoURI: 'https://example.com/logo.png', + }, + action: { + fromChainId: 1, + toChainId: 1, + fromToken: { + address: '0x1234567890123456789012345678901234567890', + symbol: 'TEST', + decimals: 18, + chainId: 1, + name: 'Test Token', + priceUSD: '1.00', + }, + toToken: { + address: '0x0987654321098765432109876543210987654321', + symbol: 'TEST2', + decimals: 18, + chainId: 1, + name: 'Test Token 2', + priceUSD: '1.00', + }, + fromAmount: '1000000000000000000', + }, + estimate: { + fromAmount: '1000000000000000000', + toAmount: '1000000000000000000', + toAmountMin: '1000000000000000000', + approvalAddress: '0x1234567890123456789012345678901234567890', + tool: 'test-tool', + executionDuration: 30000, + }, + transactionRequest: { + to: '0x1234567890123456789012345678901234567890', + data: '0x', + value: '0', + gasLimit: '100000', + }, + typedData, + }) + + const mockRelayRequest: RelayRequest = createMockRelayRequest([ + { + domain: { + name: 'Test Token', + version: '1', + chainId: 1, + verifyingContract: '0x1234567890123456789012345678901234567890', + }, + types: { + Permit: [ + { name: 'owner', type: 'address' }, + { name: 'spender', type: 'address' }, + { name: 'value', type: 'uint256' }, + { name: 'nonce', type: 'uint256' }, + { name: 'deadline', type: 'uint256' }, + ], + }, + primaryType: 'Permit', + message: { + owner: '0x1234567890123456789012345678901234567890', + spender: '0x0987654321098765432109876543210987654321', + value: '1000000000000000000', + nonce: 0, + deadline: 1234567890, + }, + signature: + '0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12', + }, + ]) + + const mockRelayRequestWithPermitWitness: RelayRequest = + createMockRelayRequest([ + { + domain: { + name: 'Test Token', + version: '1', + chainId: 1, + verifyingContract: '0x1234567890123456789012345678901234567890', + }, + types: { + PermitWitnessTransferFrom: [ + { name: 'owner', type: 'address' }, + { name: 'spender', type: 'address' }, + { name: 'value', type: 'uint256' }, + { name: 'nonce', type: 'uint256' }, + { name: 'deadline', type: 'uint256' }, + ], + }, + primaryType: 'PermitWitnessTransferFrom', + message: { + owner: '0x1234567890123456789012345678901234567890', + spender: '0x0987654321098765432109876543210987654321', + value: '1000000000000000000', + nonce: 0, + deadline: 1234567890, + }, + signature: + '0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12', + }, + ]) + + const mockSuccessResponse: RelayResponse = { + status: 'ok', + data: { + taskId: 'test-task-id-123', + }, + } + + const mockErrorResponse: RelayResponse = { + status: 'error', + data: { + code: 400, + message: 'Invalid request parameters', + }, + } + + describe('success scenarios', () => { + it('should relay transaction successfully for advanced relayer', async () => { + server.use( + http.post(`${client.config.apiUrl}/advanced/relay`, async () => { + return HttpResponse.json(mockSuccessResponse) + }) + ) + + const result = await relayTransaction(client, mockRelayRequest) + + expect(result).toEqual(mockSuccessResponse.data) + }) + + it('should relay transaction successfully for gasless relayer', async () => { + server.use( + http.post(`${client.config.apiUrl}/relayer/relay`, async () => { + return HttpResponse.json(mockSuccessResponse) + }) + ) + + const result = await relayTransaction( + client, + mockRelayRequestWithPermitWitness + ) + + expect(result).toEqual(mockSuccessResponse.data) + }) + }) + + describe('error scenarios', () => { + it('should throw BaseError when server returns error status', async () => { + server.use( + http.post(`${client.config.apiUrl}/advanced/relay`, async () => { + return HttpResponse.json(mockErrorResponse) + }) + ) + + await expect(relayTransaction(client, mockRelayRequest)).rejects.toThrow( + BaseError + ) + + try { + await relayTransaction(client, mockRelayRequest) + } catch (error) { + expect(error).toBeInstanceOf(BaseError) + expect((error as BaseError).name).toBe(ErrorName.ServerError) + expect((error as BaseError).code).toBe(400) + expect((error as BaseError).message).toBe('Invalid request parameters') + } + }) + + it('should throw SDKError when network request fails', async () => { + server.use( + http.post(`${client.config.apiUrl}/advanced/relay`, async () => { + return HttpResponse.error() + }) + ) + + await expect(relayTransaction(client, mockRelayRequest)).rejects.toThrow( + SDKError + ) + }) + + it('should throw SDKError when request times out', async () => { + server.use( + http.post(`${client.config.apiUrl}/advanced/relay`, async () => { + // Simulate timeout by not responding + await new Promise(() => {}) // Never resolves + }) + ) + + const timeoutOptions: RequestOptions = { + signal: AbortSignal.timeout(100), // 100ms timeout + } + + await expect( + relayTransaction(client, mockRelayRequest, timeoutOptions) + ).rejects.toThrow() + }) + }) + + describe('validation scenarios', () => { + it('should throw SDKError when typedData is missing', async () => { + const invalidRequest = createMockRelayRequest([]) + // Remove typedData to test validation + delete (invalidRequest as any).typedData + + await expect(relayTransaction(client, invalidRequest)).rejects.toThrow( + SDKError + ) + }) + }) +}) diff --git a/src/core/client/createClient.ts b/src/client/createClient.ts similarity index 74% rename from src/core/client/createClient.ts rename to src/client/createClient.ts index ef69d36d..afc4f196 100644 --- a/src/core/client/createClient.ts +++ b/src/client/createClient.ts @@ -1,12 +1,12 @@ import type { ChainId, ChainType } from '@lifi/types' -import { checkPackageUpdates } from '../../utils/checkPackageUpdates.js' -import { name, version } from '../../version.js' import type { SDKBaseConfig, SDKClient, SDKConfig, SDKProvider, -} from '../types.js' +} from '../types/core.js' +import { checkPackageUpdates } from '../utils/checkPackageUpdates.js' +import { name, version } from '../version.js' import { getClientStorage } from './getClientStorage.js' export function createClient(options: SDKConfig): SDKClient { @@ -31,7 +31,7 @@ export function createClient(options: SDKConfig): SDKClient { let _providers: SDKProvider[] = [] const _storage = getClientStorage(_config) - return { + const client: SDKClient = { get config() { return _config }, @@ -72,5 +72,23 @@ export function createClient(options: SDKConfig): SDKClient { } return chainRpcUrls }, - } as SDKClient + } + + function extend( + base: TClient + ): >( + extendFn: (client: TClient) => TExtensions + ) => TClient & TExtensions { + return (extendFn) => { + const extensions = extendFn(base) + const extended = { ...base, ...extensions } as TClient & typeof extensions + + // Preserve the extend function for further extensions + return Object.assign(extended, { + extend: extend(extended), + }) + } + } + + return Object.assign(client, { extend: extend(client) }) } diff --git a/src/client/createClient.unit.spec.ts b/src/client/createClient.unit.spec.ts new file mode 100644 index 00000000..c212c9c4 --- /dev/null +++ b/src/client/createClient.unit.spec.ts @@ -0,0 +1,263 @@ +import { ChainId, ChainType } from '@lifi/types' +import { describe, expect, it, vi } from 'vitest' +import { EVM } from '../core/EVM/EVM.js' +import { Solana } from '../core/Solana/Solana.js' +import { UTXO } from '../core/UTXO/UTXO.js' +import type { SDKConfig } from '../types/core.js' +import { createClient } from './createClient.js' + +// Mock the version check +vi.mock('../utils/checkPackageUpdates.js', () => ({ + checkPackageUpdates: vi.fn(), +})) + +// Mock the client storage +vi.mock('./getClientStorage.js', () => ({ + getClientStorage: vi.fn(() => ({ + getChains: vi.fn().mockResolvedValue([ + { id: 1, name: 'Ethereum', type: ChainType.EVM }, + { id: 137, name: 'Polygon', type: ChainType.EVM }, + ]), + getRpcUrls: vi.fn().mockResolvedValue({ + [ChainId.ETH]: ['https://eth-mainnet.alchemyapi.io/v2/test'], + [ChainId.POL]: ['https://polygon-rpc.com'], + }), + })), +})) + +describe('createClient', () => { + describe('basic functionality', () => { + it('should create a client with minimal config', () => { + const client = createClient({ + integrator: 'test-app', + }) + + expect(client).toBeDefined() + expect(client.config.integrator).toBe('test-app') + expect(client.config.apiUrl).toBe('https://li.quest/v1') + expect(client.config.debug).toBe(false) + expect(client.providers).toEqual([]) + }) + + it('should create a client with full config', () => { + const config: SDKConfig = { + integrator: 'test-app', + apiKey: 'test-api-key', + apiUrl: 'https://custom-api.com', + userId: 'user-123', + debug: true, + disableVersionCheck: true, + widgetVersion: '1.0.0', + rpcUrls: { + [ChainId.ETH]: ['https://eth-mainnet.alchemyapi.io/v2/test'], + }, + } + + const client = createClient(config) + + expect(client.config).toEqual({ + integrator: 'test-app', + apiKey: 'test-api-key', + apiUrl: 'https://custom-api.com', + userId: 'user-123', + debug: true, + disableVersionCheck: true, + widgetVersion: '1.0.0', + rpcUrls: { + [ChainId.ETH]: ['https://eth-mainnet.alchemyapi.io/v2/test'], + }, + }) + }) + + it('should throw error when integrator is missing', () => { + expect(() => { + createClient({} as SDKConfig) + }).toThrow( + 'Integrator not found. Please see documentation https://docs.li.fi/integrate-li.fi-js-sdk/set-up-the-sdk' + ) + }) + + it('should throw error when integrator is empty string', () => { + expect(() => { + createClient({ integrator: '' }) + }).toThrow( + 'Integrator not found. Please see documentation https://docs.li.fi/integrate-li.fi-js-sdk/set-up-the-sdk' + ) + }) + }) + + describe('provider management', () => { + it('should handle empty providers list', () => { + const client = createClient({ integrator: 'test-app' }) + expect(client.providers).toEqual([]) + expect(client.getProvider(ChainType.EVM)).toBeUndefined() + }) + + it('should set and get providers', () => { + const client = createClient({ integrator: 'test-app' }) + const evmProvider = EVM() + const solanaProvider = Solana() + + client.setProviders([evmProvider, solanaProvider]) + + expect(client.providers).toHaveLength(2) + expect(client.getProvider(ChainType.EVM)).toBe(evmProvider) + expect(client.getProvider(ChainType.SVM)).toBe(solanaProvider) + expect(client.getProvider(ChainType.UTXO)).toBeUndefined() + }) + + it('should merge providers when setting new ones', () => { + const client = createClient({ integrator: 'test-app' }) + const evmProvider = EVM() + const utxoProvider = UTXO() + + client.setProviders([evmProvider]) + expect(client.providers).toHaveLength(1) + + client.setProviders([utxoProvider]) + expect(client.providers).toHaveLength(2) + expect(client.getProvider(ChainType.EVM)).toBe(evmProvider) + expect(client.getProvider(ChainType.UTXO)).toBe(utxoProvider) + }) + + it('should merge providers when setting overlapping types', () => { + const client = createClient({ integrator: 'test-app' }) + const evmProvider1 = EVM() + const evmProvider2 = EVM() + const solanaProvider = Solana() + + client.setProviders([evmProvider1]) + client.setProviders([evmProvider2, solanaProvider]) + + expect(client.providers).toHaveLength(2) + expect(client.getProvider(ChainType.EVM)).toBe(evmProvider2) + expect(client.getProvider(ChainType.SVM)).toBe(solanaProvider) + }) + }) + + describe('chain management', () => { + it('should get chains from storage', async () => { + const client = createClient({ integrator: 'test-app' }) + const chains = await client.getChains() + + expect(chains).toEqual([ + { id: 1, name: 'Ethereum', type: ChainType.EVM }, + { id: 137, name: 'Polygon', type: ChainType.EVM }, + ]) + }) + + it('should get chain by id', async () => { + const client = createClient({ integrator: 'test-app' }) + const chain = await client.getChainById(1) + + expect(chain).toEqual({ id: 1, name: 'Ethereum', type: ChainType.EVM }) + }) + + it('should throw error when chain not found', async () => { + const client = createClient({ integrator: 'test-app' }) + + await expect(client.getChainById(999)).rejects.toThrow( + 'ChainId 999 not found' + ) + }) + }) + + describe('RPC URL management', () => { + it('should get RPC URLs from storage', async () => { + const client = createClient({ integrator: 'test-app' }) + const rpcUrls = await client.getRpcUrls() + + expect(rpcUrls).toEqual({ + [ChainId.ETH]: ['https://eth-mainnet.alchemyapi.io/v2/test'], + [ChainId.POL]: ['https://polygon-rpc.com'], + }) + }) + + it('should get RPC URLs by chain id', async () => { + const client = createClient({ integrator: 'test-app' }) + const ethUrls = await client.getRpcUrlsByChainId(ChainId.ETH) + const polUrls = await client.getRpcUrlsByChainId(ChainId.POL) + + expect(ethUrls).toEqual(['https://eth-mainnet.alchemyapi.io/v2/test']) + expect(polUrls).toEqual(['https://polygon-rpc.com']) + }) + + it('should throw error when RPC URLs not found for chain', async () => { + const client = createClient({ integrator: 'test-app' }) + + await expect(client.getRpcUrlsByChainId(999)).rejects.toThrow( + 'RPC URL not found for chainId: 999' + ) + }) + }) + + describe('extend functionality', () => { + it('should extend client with additional functionality', () => { + const client = createClient({ integrator: 'test-app' }) + + const extendedClient = (client as any).extend((_baseClient: any) => ({ + customMethod: () => 'custom-value', + anotherMethod: (value: string) => `processed-${value}`, + })) + + expect(extendedClient.customMethod()).toBe('custom-value') + expect(extendedClient.anotherMethod('test')).toBe('processed-test') + + // Should preserve original functionality + expect(extendedClient.config.integrator).toBe('test-app') + expect(extendedClient.providers).toEqual([]) + }) + + it('should allow chaining multiple extensions', () => { + const client = createClient({ integrator: 'test-app' }) + + const extendedClient = (client as any) + .extend((_baseClient: any) => ({ + firstExtension: () => 'first', + })) + .extend((_baseClient: any) => ({ + secondExtension: () => 'second', + })) + + expect(extendedClient.firstExtension()).toBe('first') + expect(extendedClient.secondExtension()).toBe('second') + expect(extendedClient.config.integrator).toBe('test-app') + }) + + it('should preserve extend function after extension', () => { + const client = createClient({ integrator: 'test-app' }) + + const extendedClient = (client as any).extend((_baseClient: any) => ({ + customMethod: () => 'custom-value', + })) + + expect(typeof extendedClient.extend).toBe('function') + + const doubleExtendedClient = extendedClient.extend( + (_baseClient: any) => ({ + anotherMethod: () => 'another-value', + }) + ) + + expect(doubleExtendedClient.customMethod()).toBe('custom-value') + expect(doubleExtendedClient.anotherMethod()).toBe('another-value') + }) + }) + + describe('error handling', () => { + it('should handle storage errors gracefully', async () => { + // Mock storage to throw error + const { getClientStorage } = await import('./getClientStorage.js') + vi.mocked(getClientStorage).mockReturnValueOnce({ + needReset: false, + getChains: vi.fn().mockRejectedValue(new Error('Storage error')), + getRpcUrls: vi.fn().mockRejectedValue(new Error('Storage error')), + }) + + const newClient = createClient({ integrator: 'test-app' }) + + await expect(newClient.getChains()).rejects.toThrow('Storage error') + await expect(newClient.getRpcUrls()).rejects.toThrow('Storage error') + }) + }) +}) diff --git a/src/core/client/getClientStorage.ts b/src/client/getClientStorage.ts similarity index 80% rename from src/core/client/getClientStorage.ts rename to src/client/getClientStorage.ts index 36d36a27..52e66bfb 100644 --- a/src/core/client/getClientStorage.ts +++ b/src/client/getClientStorage.ts @@ -1,7 +1,7 @@ import { ChainId, ChainType, type ExtendedChain } from '@lifi/types' -import { getChains } from '../../services/api.js' -import type { RPCUrls, SDKBaseConfig } from '../types.js' -import { getRpcUrlsFromChains } from '../utils.js' +import { getChainsFromConfig } from '../actions/getChains.js' +import { getRpcUrlsFromChains } from '../core/utils.js' +import type { RPCUrls, SDKBaseConfig } from '../types/core.js' export const getClientStorage = (config: SDKBaseConfig) => { let _chains = [] as ExtendedChain[] @@ -17,7 +17,7 @@ export const getClientStorage = (config: SDKBaseConfig) => { }, async getChains() { if (this.needReset || !_chains.length) { - _chains = await getChains(config, { + _chains = await getChainsFromConfig(config, { chainTypes: [ ChainType.EVM, ChainType.SVM, diff --git a/src/client/getClientStorage.unit.spec.ts b/src/client/getClientStorage.unit.spec.ts new file mode 100644 index 00000000..321e3d53 --- /dev/null +++ b/src/client/getClientStorage.unit.spec.ts @@ -0,0 +1,382 @@ +import { + ChainId, + ChainKey, + ChainType, + CoinKey, + type ExtendedChain, +} from '@lifi/types' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import type { RPCUrls, SDKBaseConfig } from '../types/core.js' +import { getClientStorage } from './getClientStorage.js' + +// Mock the dependencies +vi.mock('../actions/getChains.js', () => ({ + getChainsFromConfig: vi.fn(), +})) + +vi.mock('../core/utils.js', () => ({ + getRpcUrlsFromChains: vi.fn(), +})) + +describe('getClientStorage', () => { + let mockConfig: SDKBaseConfig + let mockChains: ExtendedChain[] + let mockRpcUrls: RPCUrls + + beforeEach(() => { + mockConfig = { + integrator: 'test-app', + apiUrl: 'https://li.quest/v1', + debug: false, + rpcUrls: { + [ChainId.ETH]: ['https://eth-mainnet.alchemyapi.io/v2/test'], + }, + } + + mockChains = [ + { + id: ChainId.ETH, + name: 'Ethereum', + chainType: ChainType.EVM, + key: ChainKey.ETH, + coin: CoinKey.ETH, + mainnet: true, + nativeToken: { + address: '0x0000000000000000000000000000000000000000', + symbol: 'ETH', + decimals: 18, + name: 'Ethereum', + chainId: ChainId.ETH, + priceUSD: '0', + }, + metamask: { + chainId: '0x1', + chainName: 'Ethereum', + nativeCurrency: { + name: 'Ethereum', + symbol: 'ETH', + decimals: 18, + }, + rpcUrls: ['https://eth-mainnet.alchemyapi.io/v2/test'], + blockExplorerUrls: ['https://etherscan.io'], + }, + }, + { + id: ChainId.POL, + name: 'Polygon', + chainType: ChainType.EVM, + key: ChainKey.POL, + coin: CoinKey.MATIC, + mainnet: true, + nativeToken: { + address: '0x0000000000000000000000000000000000000000', + symbol: 'MATIC', + decimals: 18, + name: 'Polygon', + chainId: ChainId.POL, + priceUSD: '0', + }, + metamask: { + chainId: '0x89', + chainName: 'Polygon', + nativeCurrency: { + name: 'Polygon', + symbol: 'MATIC', + decimals: 18, + }, + rpcUrls: ['https://polygon-rpc.com'], + blockExplorerUrls: ['https://polygonscan.com'], + }, + }, + ] + + mockRpcUrls = { + [ChainId.ETH]: ['https://eth-mainnet.alchemyapi.io/v2/test'], + [ChainId.POL]: ['https://polygon-rpc.com'], + } + + // Reset mocks + vi.clearAllMocks() + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + describe('needReset property', () => { + it('should return true when chainsUpdatedAt is undefined', () => { + const storage = getClientStorage(mockConfig) + expect(storage.needReset).toBe(true) // Because _chainsUpdatedAt is undefined + }) + + it('should return true when chains are older than 24 hours', async () => { + const { getChainsFromConfig } = await import('../actions/getChains.js') + const { getRpcUrlsFromChains } = await import('../core/utils.js') + + vi.mocked(getChainsFromConfig).mockResolvedValue(mockChains) + vi.mocked(getRpcUrlsFromChains).mockReturnValue(mockRpcUrls) + + const storage = getClientStorage(mockConfig) + + // First call to getChains sets the timestamp + await storage.getChains() + + // Mock Date.now to return a time 25 hours later + const originalDateNow = Date.now + vi.spyOn(Date, 'now').mockReturnValue(Date.now() + 25 * 60 * 60 * 1000) + + expect(storage.needReset).toBe(true) + + // Restore Date.now + Date.now = originalDateNow + }) + + it('should return false when chains are fresh', async () => { + const { getChainsFromConfig } = await import('../actions/getChains.js') + const { getRpcUrlsFromChains } = await import('../core/utils.js') + + vi.mocked(getChainsFromConfig).mockResolvedValue(mockChains) + vi.mocked(getRpcUrlsFromChains).mockReturnValue(mockRpcUrls) + + const storage = getClientStorage(mockConfig) + + // First call to getChains sets the timestamp + await storage.getChains() + + // Should not need reset immediately after + expect(storage.needReset).toBe(false) + }) + }) + + describe('getChains method', () => { + it('should fetch chains when needReset is true', async () => { + const { getChainsFromConfig } = await import('../actions/getChains.js') + const { getRpcUrlsFromChains } = await import('../core/utils.js') + + vi.mocked(getChainsFromConfig).mockResolvedValue(mockChains) + vi.mocked(getRpcUrlsFromChains).mockReturnValue(mockRpcUrls) + + const storage = getClientStorage(mockConfig) + const chains = await storage.getChains() + + expect(getChainsFromConfig).toHaveBeenCalledWith(mockConfig, { + chainTypes: [ + ChainType.EVM, + ChainType.SVM, + ChainType.UTXO, + ChainType.MVM, + ], + }) + expect(chains).toEqual(mockChains) + }) + + it('should return cached chains when not needReset and chains exist', async () => { + const { getChainsFromConfig } = await import('../actions/getChains.js') + const { getRpcUrlsFromChains } = await import('../core/utils.js') + + vi.mocked(getChainsFromConfig).mockResolvedValue(mockChains) + vi.mocked(getRpcUrlsFromChains).mockReturnValue(mockRpcUrls) + + const storage = getClientStorage(mockConfig) + + // First call fetches chains + const chains1 = await storage.getChains() + expect(getChainsFromConfig).toHaveBeenCalledTimes(1) + + // Second call should return cached chains + const chains2 = await storage.getChains() + expect(getChainsFromConfig).toHaveBeenCalledTimes(1) + expect(chains1).toBe(chains2) // Same reference + }) + + it('should handle errors from getChainsFromConfig', async () => { + const { getChainsFromConfig } = await import('../actions/getChains.js') + + const error = new Error('Failed to fetch chains') + vi.mocked(getChainsFromConfig).mockRejectedValue(error) + + const configWithoutRpcUrls = { + ...mockConfig, + rpcUrls: {}, + } + const storage = getClientStorage(configWithoutRpcUrls) + + await expect(storage.getChains()).rejects.toThrow( + 'Failed to fetch chains' + ) + }) + }) + + describe('getRpcUrls method', () => { + it('should fetch RPC URLs when needReset is true', async () => { + const { getChainsFromConfig } = await import('../actions/getChains.js') + const { getRpcUrlsFromChains } = await import('../core/utils.js') + + vi.mocked(getChainsFromConfig).mockResolvedValue(mockChains) + vi.mocked(getRpcUrlsFromChains).mockReturnValue(mockRpcUrls) + + const configWithoutRpcUrls = { + ...mockConfig, + rpcUrls: {}, + } + const storage = getClientStorage(configWithoutRpcUrls) + const rpcUrls = await storage.getRpcUrls() + + expect(getRpcUrlsFromChains).toHaveBeenCalledWith({}, mockChains, [ + ChainId.SOL, + ]) + expect(rpcUrls).toEqual(mockRpcUrls) + }) + + it('should return cached RPC URLs when not needReset and RPC URLs exist', async () => { + const { getChainsFromConfig } = await import('../actions/getChains.js') + const { getRpcUrlsFromChains } = await import('../core/utils.js') + + vi.mocked(getChainsFromConfig).mockResolvedValue(mockChains) + vi.mocked(getRpcUrlsFromChains).mockReturnValue(mockRpcUrls) + + const storage = getClientStorage(mockConfig) + + // First call getChains to set the timestamp and make needReset false + await storage.getChains() + + // Now getRpcUrls should use existing RPC URLs without calling getRpcUrlsFromChains + const rpcUrls1 = await storage.getRpcUrls() + expect(getRpcUrlsFromChains).toHaveBeenCalledTimes(0) // Should use existing RPC URLs + + // Second call should return cached RPC URLs + const rpcUrls2 = await storage.getRpcUrls() + expect(getRpcUrlsFromChains).toHaveBeenCalledTimes(0) + expect(rpcUrls1).toBe(rpcUrls2) // Same reference + }) + + it('should handle errors from getRpcUrlsFromChains', async () => { + const { getChainsFromConfig } = await import('../actions/getChains.js') + const { getRpcUrlsFromChains } = await import('../core/utils.js') + + vi.mocked(getChainsFromConfig).mockResolvedValue(mockChains) + const error = new Error('Failed to process RPC URLs') + vi.mocked(getRpcUrlsFromChains).mockImplementation(() => { + throw error + }) + + const configWithoutRpcUrls = { + ...mockConfig, + rpcUrls: {}, + } + const storage = getClientStorage(configWithoutRpcUrls) + + await expect(storage.getRpcUrls()).rejects.toThrow( + 'Failed to process RPC URLs' + ) + }) + }) + + describe('caching behavior', () => { + it('should reset cache when needReset becomes true', async () => { + const { getChainsFromConfig } = await import('../actions/getChains.js') + const { getRpcUrlsFromChains } = await import('../core/utils.js') + + vi.mocked(getChainsFromConfig).mockResolvedValue(mockChains) + vi.mocked(getRpcUrlsFromChains).mockReturnValue(mockRpcUrls) + + const configWithoutRpcUrls = { + ...mockConfig, + rpcUrls: {}, + } + const storage = getClientStorage(configWithoutRpcUrls) + + // First call + await storage.getChains() + await storage.getRpcUrls() + + // Mock Date.now to return a time 25 hours later + const originalDateNow = Date.now + vi.spyOn(Date, 'now').mockReturnValue(Date.now() + 25 * 60 * 60 * 1000) + + // Should refetch when needReset is true + await storage.getChains() + await storage.getRpcUrls() + + expect(getChainsFromConfig).toHaveBeenCalledTimes(2) + expect(getRpcUrlsFromChains).toHaveBeenCalledTimes(1) // Only called once because we have existing RPC URLs + + // Restore Date.now + Date.now = originalDateNow + }) + }) + + describe('edge cases', () => { + it('should handle chains without metamask RPC URLs', async () => { + const { getChainsFromConfig } = await import('../actions/getChains.js') + const { getRpcUrlsFromChains } = await import('../core/utils.js') + + const chainsWithoutRpcUrls = [ + { + id: ChainId.ETH, + name: 'Ethereum', + key: ChainKey.ETH, + chainType: ChainType.EVM, + coin: CoinKey.ETH, + mainnet: true, + nativeToken: { + address: '0x0000000000000000000000000000000000000000', + symbol: 'ETH', + decimals: 18, + name: 'Ethereum', + chainId: ChainId.ETH, + priceUSD: '0', + }, + metamask: { + chainId: '0x1', + chainName: 'Ethereum', + nativeCurrency: { + name: 'Ethereum', + symbol: 'ETH', + decimals: 18, + }, + rpcUrls: [], + blockExplorerUrls: ['https://etherscan.io'], + }, + }, + ] + + vi.mocked(getChainsFromConfig).mockResolvedValue(chainsWithoutRpcUrls) + vi.mocked(getRpcUrlsFromChains).mockReturnValue({}) + + const configWithoutRpcUrls = { + ...mockConfig, + rpcUrls: {}, + } + const storage = getClientStorage(configWithoutRpcUrls) + + const rpcUrls = await storage.getRpcUrls() + + expect(getRpcUrlsFromChains).toHaveBeenCalledWith( + {}, + chainsWithoutRpcUrls, + [ChainId.SOL] + ) + expect(rpcUrls).toEqual({}) + }) + + it('should preserve existing RPC URLs from config', async () => { + const { getChainsFromConfig } = await import('../actions/getChains.js') + const { getRpcUrlsFromChains } = await import('../core/utils.js') + + vi.mocked(getChainsFromConfig).mockResolvedValue(mockChains) + vi.mocked(getRpcUrlsFromChains).mockReturnValue(mockRpcUrls) + + const storage = getClientStorage(mockConfig) + + // First call getChains to set the timestamp and make needReset false + await storage.getChains() + + const rpcUrls = await storage.getRpcUrls() + + // Should not call getRpcUrlsFromChains because we already have RPC URLs + expect(getRpcUrlsFromChains).not.toHaveBeenCalled() + expect(rpcUrls).toEqual(mockConfig.rpcUrls) + }) + }) +}) diff --git a/src/core/BaseStepExecutor.ts b/src/core/BaseStepExecutor.ts index 72074867..d4a6c636 100644 --- a/src/core/BaseStepExecutor.ts +++ b/src/core/BaseStepExecutor.ts @@ -1,12 +1,12 @@ import type { LiFiStep } from '@lifi/types' -import { StatusManager } from './StatusManager.js' import type { ExecutionOptions, InteractionSettings, SDKClient, StepExecutor, StepExecutorOptions, -} from './types.js' +} from '../types/core.js' +import { StatusManager } from './StatusManager.js' // Please be careful when changing the defaults as it may break the behavior (e.g., background execution) const defaultInteractionSettings = { diff --git a/src/core/EVM/EVM.ts b/src/core/EVM/EVM.ts index 763e0775..f20b0012 100644 --- a/src/core/EVM/EVM.ts +++ b/src/core/EVM/EVM.ts @@ -1,6 +1,6 @@ import { ChainType } from '@lifi/types' import { isAddress } from 'viem' -import type { StepExecutorOptions } from '../types.js' +import type { StepExecutorOptions } from '../../types/core.js' import { EVMStepExecutor } from './EVMStepExecutor.js' import { getEVMBalance } from './getEVMBalance.js' import { resolveEVMAddress } from './resolveEVMAddress.js' diff --git a/src/core/EVM/EVMStepExecutor.ts b/src/core/EVM/EVMStepExecutor.ts index 8ceff7dd..35386804 100644 --- a/src/core/EVM/EVMStepExecutor.ts +++ b/src/core/EVM/EVMStepExecutor.ts @@ -16,17 +16,11 @@ import { signTypedData, } from 'viem/actions' import { getAction, isHex } from 'viem/utils' +import { getRelayerQuote } from '../../actions/getRelayerQuote.js' +import { getStepTransaction } from '../../actions/getStepTransaction.js' +import { relayTransaction } from '../../actions/relayTransaction.js' import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' -import { - getRelayerQuote, - getStepTransaction, - relayTransaction, -} from '../../services/api.js' -import { isZeroAddress } from '../../utils/isZeroAddress.js' -import { BaseStepExecutor } from '../BaseStepExecutor.js' -import { checkBalance } from '../checkBalance.js' -import { stepComparison } from '../stepComparison.js' import type { LiFiStepExtended, Process, @@ -34,7 +28,11 @@ import type { StepExecutorOptions, TransactionMethodType, TransactionParameters, -} from '../types.js' +} from '../../types/core.js' +import { isZeroAddress } from '../../utils/isZeroAddress.js' +import { BaseStepExecutor } from '../BaseStepExecutor.js' +import { checkBalance } from '../checkBalance.js' +import { stepComparison } from '../stepComparison.js' import { waitForDestinationChainTransaction } from '../waitForDestinationChainTransaction.js' import { checkAllowance } from './checkAllowance.js' import { getActionWithFallback } from './getActionWithFallback.js' @@ -190,7 +188,7 @@ export class EVMStepExecutor extends BaseStepExecutor { break case 'relayed': transactionReceipt = await waitForRelayedTransactionReceipt( - client.config, + client, process.taskId as Hash, step ) @@ -236,7 +234,7 @@ export class EVMStepExecutor extends BaseStepExecutor { const gaslessStep = isGaslessStep(step) let updatedStep: LiFiStep if (relayerStep && gaslessStep) { - const updatedRelayedStep = await getRelayerQuote(client.config, { + const updatedRelayedStep = await getRelayerQuote(client, { fromChain: stepBase.action.fromChainId, fromToken: stepBase.action.fromToken.address, fromAddress: stepBase.action.fromAddress!, @@ -259,7 +257,7 @@ export class EVMStepExecutor extends BaseStepExecutor { const params = filteredSignedTypedData?.length ? { ...restStepBase, typedData: filteredSignedTypedData } : restStepBase - updatedStep = await getStepTransaction(client.config, params) + updatedStep = await getStepTransaction(client, params) } const comparedStep = await stepComparison( @@ -624,7 +622,7 @@ export class EVMStepExecutor extends BaseStepExecutor { // biome-ignore lint/correctness/noUnusedVariables: destructuring const { execution, ...stepBase } = step - const relayedTransaction = await relayTransaction(client.config, { + const relayedTransaction = await relayTransaction(client, { ...stepBase, typedData: signedTypedData, }) diff --git a/src/core/EVM/checkAllowance.ts b/src/core/EVM/checkAllowance.ts index 9089229c..14674933 100644 --- a/src/core/EVM/checkAllowance.ts +++ b/src/core/EVM/checkAllowance.ts @@ -3,14 +3,14 @@ import type { Address, Client, Hash } from 'viem' import { signTypedData } from 'viem/actions' import { getAction } from 'viem/utils' import { MaxUint256 } from '../../constants.js' -import type { StatusManager } from '../StatusManager.js' import type { ExecutionOptions, LiFiStepExtended, Process, ProcessType, SDKClient, -} from '../types.js' +} from '../../types/core.js' +import type { StatusManager } from '../StatusManager.js' import { getActionWithFallback } from './getActionWithFallback.js' import { getAllowance } from './getAllowance.js' import { parseEVMErrors } from './parseEVMErrors.js' diff --git a/src/core/EVM/checkPermitSupport.ts b/src/core/EVM/checkPermitSupport.ts index 60abcb54..09f5316b 100644 --- a/src/core/EVM/checkPermitSupport.ts +++ b/src/core/EVM/checkPermitSupport.ts @@ -1,6 +1,6 @@ import { ChainType, type ExtendedChain } from '@lifi/types' import type { Address } from 'viem' -import type { SDKClient } from '../types.js' +import type { SDKClient } from '../../types/core.js' import { getActionWithFallback } from './getActionWithFallback.js' import { getAllowance } from './getAllowance.js' import { getNativePermit } from './permits/getNativePermit.js' diff --git a/src/core/EVM/getActionWithFallback.ts b/src/core/EVM/getActionWithFallback.ts index 33944f6d..8a75cfc9 100644 --- a/src/core/EVM/getActionWithFallback.ts +++ b/src/core/EVM/getActionWithFallback.ts @@ -8,7 +8,7 @@ import type { WalletActions, } from 'viem' import { getAction } from 'viem/utils' -import type { SDKClient } from '../types.js' +import type { SDKClient } from '../../types/core.js' import { getPublicClient } from './publicClient.js' /** diff --git a/src/core/EVM/getAllowance.int.spec.ts b/src/core/EVM/getAllowance.int.spec.ts index 16e773dc..cd65e2fd 100644 --- a/src/core/EVM/getAllowance.int.spec.ts +++ b/src/core/EVM/getAllowance.int.spec.ts @@ -2,8 +2,8 @@ import { findDefaultToken } from '@lifi/data-types' import { ChainId, CoinKey } from '@lifi/types' import type { Address } from 'viem' import { describe, expect, it } from 'vitest' -import { getTokens } from '../../services/api.js' -import { createClient } from '../client/createClient.js' +import { getTokens } from '../../actions/getTokens.js' +import { createClient } from '../../client/createClient.js' import { EVM } from './EVM.js' import { getAllowance, @@ -103,7 +103,7 @@ describe('allowance integration tests', { retry: retryTimes, timeout }, () => { 'should handle token lists with more than 10 tokens', { retry: retryTimes, timeout }, async () => { - const { tokens } = await getTokens(client.config, { + const { tokens } = await getTokens(client, { chains: [ChainId.POL], }) const filteredTokens = tokens[ChainId.POL] diff --git a/src/core/EVM/getAllowance.ts b/src/core/EVM/getAllowance.ts index 84525d6e..e6d2d5ee 100644 --- a/src/core/EVM/getAllowance.ts +++ b/src/core/EVM/getAllowance.ts @@ -1,8 +1,8 @@ import type { BaseToken, ChainId } from '@lifi/types' import type { Address, Client } from 'viem' import { multicall, readContract } from 'viem/actions' +import type { SDKClient } from '../../types/core.js' import { isZeroAddress } from '../../utils/isZeroAddress.js' -import type { SDKClient } from '../types.js' import { allowanceAbi } from './abi.js' import { getActionWithFallback } from './getActionWithFallback.js' import { getPublicClient } from './publicClient.js' diff --git a/src/core/EVM/getEVMBalance.int.spec.ts b/src/core/EVM/getEVMBalance.int.spec.ts index eb740fbb..280edbc4 100644 --- a/src/core/EVM/getEVMBalance.int.spec.ts +++ b/src/core/EVM/getEVMBalance.int.spec.ts @@ -3,8 +3,8 @@ import type { StaticToken, Token } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' import type { Address } from 'viem' import { describe, expect, it } from 'vitest' -import { getTokens } from '../../services/api.js' -import { createClient } from '../client/createClient.js' +import { getTokens } from '../../actions/getTokens.js' +import { createClient } from '../../client/createClient.js' import { EVM } from './EVM.js' import { getEVMBalance } from './getEVMBalance.js' @@ -122,7 +122,7 @@ describe('getBalances integration tests', () => { { retry: retryTimes, timeout }, async () => { const walletAddress = defaultWalletAddress - const { tokens } = await getTokens(client.config, { + const { tokens } = await getTokens(client, { chains: [ChainId.OPT], }) expect(tokens[ChainId.OPT]?.length).toBeGreaterThan(100) diff --git a/src/core/EVM/getEVMBalance.ts b/src/core/EVM/getEVMBalance.ts index 9c777b2c..cf9e20c5 100644 --- a/src/core/EVM/getEVMBalance.ts +++ b/src/core/EVM/getEVMBalance.ts @@ -6,8 +6,8 @@ import { multicall, readContract, } from 'viem/actions' +import type { SDKClient } from '../../types/core.js' import { isZeroAddress } from '../../utils/isZeroAddress.js' -import type { SDKClient } from '../types.js' import { balanceOfAbi, getEthBalanceAbi } from './abi.js' import { getPublicClient } from './publicClient.js' import { getMulticallAddress } from './utils.js' diff --git a/src/core/EVM/isBatchingSupported.ts b/src/core/EVM/isBatchingSupported.ts index 1139e8ba..642b80d6 100644 --- a/src/core/EVM/isBatchingSupported.ts +++ b/src/core/EVM/isBatchingSupported.ts @@ -2,8 +2,8 @@ import { ChainType } from '@lifi/types' import type { Client } from 'viem' import { getCapabilities } from 'viem/actions' import { getAction } from 'viem/utils' +import type { SDKClient } from '../../types/core.js' import { sleep } from '../../utils/sleep.js' -import type { SDKClient } from '../types.js' import type { EVMProvider } from './types.js' export async function isBatchingSupported( diff --git a/src/core/EVM/parseEVMErrors.ts b/src/core/EVM/parseEVMErrors.ts index 7065c3b1..ffc8d5b3 100644 --- a/src/core/EVM/parseEVMErrors.ts +++ b/src/core/EVM/parseEVMErrors.ts @@ -4,8 +4,8 @@ import { BaseError } from '../../errors/baseError.js' import { ErrorMessage, LiFiErrorCode } from '../../errors/constants.js' import { TransactionError, UnknownError } from '../../errors/errors.js' import { SDKError } from '../../errors/SDKError.js' +import type { Process } from '../../types/core.js' import { fetchTxErrorDetails } from '../../utils/fetchTxErrorDetails.js' -import type { Process } from '../types.js' export const parseEVMErrors = async ( e: Error, diff --git a/src/core/EVM/parseEVMErrors.unit.spec.ts b/src/core/EVM/parseEVMErrors.unit.spec.ts index 51029397..3bec4bc0 100644 --- a/src/core/EVM/parseEVMErrors.unit.spec.ts +++ b/src/core/EVM/parseEVMErrors.unit.spec.ts @@ -1,6 +1,5 @@ import type { LiFiStep } from '@lifi/types' import { describe, expect, it, vi } from 'vitest' -import { buildStepObject } from '../../../tests/fixtures.js' import { BaseError } from '../../errors/baseError.js' import { ErrorMessage, @@ -9,8 +8,9 @@ import { } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' import { SDKError } from '../../errors/SDKError.js' +import { buildStepObject } from '../../tests/fixtures.js' +import type { Process } from '../../types/core.js' import * as helpers from '../../utils/fetchTxErrorDetails.js' -import type { Process } from '../types.js' import { parseEVMErrors } from './parseEVMErrors.js' describe('parseEVMStepErrors', () => { diff --git a/src/core/EVM/permits/getNativePermit.ts b/src/core/EVM/permits/getNativePermit.ts index 9449423c..886269b3 100644 --- a/src/core/EVM/permits/getNativePermit.ts +++ b/src/core/EVM/permits/getNativePermit.ts @@ -9,7 +9,7 @@ import { zeroHash, } from 'viem' import { getCode, multicall, readContract } from 'viem/actions' -import type { SDKClient } from '../../types.js' +import type { SDKClient } from '../../../types/core.js' import { eip2612Abi } from '../abi.js' import { getActionWithFallback } from '../getActionWithFallback.js' import { getMulticallAddress, isDelegationDesignatorCode } from '../utils.js' diff --git a/src/core/EVM/permits/getPermitTransferFromValues.ts b/src/core/EVM/permits/getPermitTransferFromValues.ts index 7d279c57..5ecd7742 100644 --- a/src/core/EVM/permits/getPermitTransferFromValues.ts +++ b/src/core/EVM/permits/getPermitTransferFromValues.ts @@ -1,7 +1,7 @@ import type { ExtendedChain } from '@lifi/types' import type { Address, Client } from 'viem' import { readContract } from 'viem/actions' -import type { SDKClient } from '../../types.js' +import type { SDKClient } from '../../../types/core.js' import { permit2ProxyAbi } from '../abi.js' import { getActionWithFallback } from '../getActionWithFallback.js' import type { PermitTransferFrom } from './signatureTransfer.js' diff --git a/src/core/EVM/permits/signPermit2Message.ts b/src/core/EVM/permits/signPermit2Message.ts index 9b839f90..9ec981de 100644 --- a/src/core/EVM/permits/signPermit2Message.ts +++ b/src/core/EVM/permits/signPermit2Message.ts @@ -3,7 +3,7 @@ import type { Address, Client, Hex } from 'viem' import { keccak256 } from 'viem' import { signTypedData } from 'viem/actions' import { getAction } from 'viem/utils' -import type { SDKClient } from '../../types.js' +import type { SDKClient } from '../../../types/core.js' import { getPermitTransferFromValues } from './getPermitTransferFromValues.js' import { getPermitData } from './signatureTransfer.js' diff --git a/src/core/EVM/publicClient.ts b/src/core/EVM/publicClient.ts index ec0d806e..694715ad 100644 --- a/src/core/EVM/publicClient.ts +++ b/src/core/EVM/publicClient.ts @@ -2,7 +2,7 @@ import { ChainId, ChainType } from '@lifi/types' import type { Client } from 'viem' import { type Address, createClient, fallback, http, webSocket } from 'viem' import { type Chain, mainnet } from 'viem/chains' -import type { SDKClient } from '../types.js' +import type { SDKClient } from '../../types/core.js' import type { EVMProvider } from './types.js' import { UNS_PROXY_READER_ADDRESSES } from './uns/constants.js' diff --git a/src/core/EVM/resolveENSAddress.ts b/src/core/EVM/resolveENSAddress.ts index b5a1490f..03f4cfea 100644 --- a/src/core/EVM/resolveENSAddress.ts +++ b/src/core/EVM/resolveENSAddress.ts @@ -1,6 +1,6 @@ import { ChainId } from '@lifi/types' import { getEnsAddress, normalize } from 'viem/ens' -import type { SDKClient } from '../types.js' +import type { SDKClient } from '../../types/core.js' import { getPublicClient } from './publicClient.js' export const resolveENSAddress = async ( diff --git a/src/core/EVM/resolveEVMAddress.ts b/src/core/EVM/resolveEVMAddress.ts index 3da29251..a98675b9 100644 --- a/src/core/EVM/resolveEVMAddress.ts +++ b/src/core/EVM/resolveEVMAddress.ts @@ -1,6 +1,6 @@ import type { ChainId, CoinKey } from '@lifi/types' import { ChainType } from '@lifi/types' -import type { SDKClient } from '../types.js' +import type { SDKClient } from '../../types/core.js' import { resolveENSAddress } from './resolveENSAddress.js' import { resolveUNSAddress } from './uns/resolveUNSAddress.js' diff --git a/src/core/EVM/setAllowance.int.spec.ts b/src/core/EVM/setAllowance.int.spec.ts index e5abdadb..466efdc3 100644 --- a/src/core/EVM/setAllowance.int.spec.ts +++ b/src/core/EVM/setAllowance.int.spec.ts @@ -4,7 +4,7 @@ import { mnemonicToAccount } from 'viem/accounts' import { waitForTransactionReceipt } from 'viem/actions' import { polygon } from 'viem/chains' import { describe, expect, it } from 'vitest' -import { createClient } from '../client/createClient.js' +import { createClient } from '../../client/createClient.js' import { EVM } from './EVM.js' import { revokeTokenApproval, setTokenAllowance } from './setAllowance.js' import { retryCount, retryDelay } from './utils.js' diff --git a/src/core/EVM/setAllowance.ts b/src/core/EVM/setAllowance.ts index 60d8faea..04a91e7b 100644 --- a/src/core/EVM/setAllowance.ts +++ b/src/core/EVM/setAllowance.ts @@ -2,12 +2,12 @@ import type { Address, Client, Hash, SendTransactionParameters } from 'viem' import { encodeFunctionData } from 'viem' import { sendTransaction } from 'viem/actions' import { getAction } from 'viem/utils' -import { isZeroAddress } from '../../utils/isZeroAddress.js' import type { ExecutionOptions, SDKClient, TransactionParameters, -} from '../types.js' +} from '../../types/core.js' +import { isZeroAddress } from '../../utils/isZeroAddress.js' import { approveAbi } from './abi.js' import { getAllowance } from './getAllowance.js' import type { ApproveTokenRequest, RevokeApprovalRequest } from './types.js' diff --git a/src/core/EVM/switchChain.ts b/src/core/EVM/switchChain.ts index c8aae985..3c17d226 100644 --- a/src/core/EVM/switchChain.ts +++ b/src/core/EVM/switchChain.ts @@ -3,8 +3,12 @@ import { getChainId } from 'viem/actions' import { getAction } from 'viem/utils' import { LiFiErrorCode } from '../../errors/constants.js' import { ProviderError } from '../../errors/errors.js' +import type { + ExecutionOptions, + LiFiStepExtended, + Process, +} from '../../types/core.js' import type { StatusManager } from '../StatusManager.js' -import type { ExecutionOptions, LiFiStepExtended, Process } from '../types.js' /** * This method checks whether the wallet client is configured for the correct chain. diff --git a/src/core/EVM/switchChain.unit.spec.ts b/src/core/EVM/switchChain.unit.spec.ts index 12e031f4..08472d29 100644 --- a/src/core/EVM/switchChain.unit.spec.ts +++ b/src/core/EVM/switchChain.unit.spec.ts @@ -2,11 +2,11 @@ import type { LiFiStep } from '@lifi/types' import type { Client } from 'viem' import type { Mock } from 'vitest' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { buildStepObject } from '../../../tests/fixtures.js' import { LiFiErrorCode } from '../../errors/constants.js' import { ProviderError } from '../../errors/errors.js' +import { buildStepObject } from '../../tests/fixtures.js' +import type { ExecutionOptions, Process } from '../../types/core.js' import type { StatusManager } from '../StatusManager.js' -import type { ExecutionOptions, Process } from '../types.js' import { switchChain } from './switchChain.js' let client: Client diff --git a/src/core/EVM/typeguards.ts b/src/core/EVM/typeguards.ts index 3dc1614e..bc1f58b1 100644 --- a/src/core/EVM/typeguards.ts +++ b/src/core/EVM/typeguards.ts @@ -1,5 +1,5 @@ import type { ExtendedChain, LiFiStep } from '@lifi/types' -import type { LiFiStepExtended } from '../types.js' +import type { LiFiStepExtended } from '../../types/core.js' type RelayerStep = (LiFiStepExtended | LiFiStep) & { typedData: NonNullable<(LiFiStepExtended | LiFiStep)['typedData']> diff --git a/src/core/EVM/types.ts b/src/core/EVM/types.ts index 906976d0..2d630bdf 100644 --- a/src/core/EVM/types.ts +++ b/src/core/EVM/types.ts @@ -6,7 +6,7 @@ import type { FallbackTransportConfig, Hex, } from 'viem' -import type { SDKProvider, SwitchChainHook } from '../types.js' +import type { SDKProvider, SwitchChainHook } from '../../types/core.js' export interface EVMProviderOptions { getWalletClient?: () => Promise diff --git a/src/core/EVM/uns/resolveUNSAddress.ts b/src/core/EVM/uns/resolveUNSAddress.ts index ae751ae9..826f698e 100644 --- a/src/core/EVM/uns/resolveUNSAddress.ts +++ b/src/core/EVM/uns/resolveUNSAddress.ts @@ -3,7 +3,7 @@ import type { Address, Client } from 'viem' import { readContract } from 'viem/actions' import { namehash } from 'viem/ens' import { getAction, trim } from 'viem/utils' -import type { SDKClient } from '../../types.js' +import type { SDKClient } from '../../../types/core.js' import { getPublicClient } from '../publicClient.js' import { CHAIN_ID_UNS_CHAIN_MAP, diff --git a/src/core/EVM/utils.ts b/src/core/EVM/utils.ts index d483f24b..cfeec582 100644 --- a/src/core/EVM/utils.ts +++ b/src/core/EVM/utils.ts @@ -2,9 +2,9 @@ import type { ChainId, ExtendedChain } from '@lifi/types' import { ChainType } from '@lifi/types' import type { Address, Chain, Client, Transaction, TypedDataDomain } from 'viem' import { getBlock } from 'viem/actions' -import { getChains } from '../../services/api.js' +import { getChainsFromConfig } from '../../actions/getChains.js' +import type { SDKBaseConfig, SDKClient } from '../../types/core.js' import { median } from '../../utils/median.js' -import type { SDKBaseConfig, SDKClient } from '../types.js' import { getActionWithFallback } from './getActionWithFallback.js' type ChainBlockExplorer = { @@ -100,7 +100,9 @@ export const getMulticallAddress = async ( config: SDKBaseConfig, chainId: ChainId ): Promise
=> { - const chains = await getChains(config, { chainTypes: [ChainType.EVM] }) + const chains = await getChainsFromConfig(config, { + chainTypes: [ChainType.EVM], + }) return chains?.find((chain) => chain.id === chainId) ?.multicallAddress as Address } diff --git a/src/core/EVM/waitForRelayedTransactionReceipt.ts b/src/core/EVM/waitForRelayedTransactionReceipt.ts index 697d4b7a..d33cd5d3 100644 --- a/src/core/EVM/waitForRelayedTransactionReceipt.ts +++ b/src/core/EVM/waitForRelayedTransactionReceipt.ts @@ -1,20 +1,20 @@ import type { ExtendedTransactionInfo, LiFiStep } from '@lifi/types' import type { Hash } from 'viem' +import { getRelayedTransactionStatus } from '../../actions/getRelayedTransactionStatus.js' import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' -import { getRelayedTransactionStatus } from '../../services/api.js' +import type { SDKClient } from '../../types/core.js' import { waitForResult } from '../../utils/waitForResult.js' -import type { SDKBaseConfig } from '../types.js' import type { WalletCallReceipt } from './types.js' export const waitForRelayedTransactionReceipt = async ( - config: SDKBaseConfig, + client: SDKClient, taskId: Hash, step: LiFiStep ): Promise => { return waitForResult( async () => { - const result = await getRelayedTransactionStatus(config, { + const result = await getRelayedTransactionStatus(client, { taskId, fromChain: step.action.fromChainId, toChain: step.action.toChainId, diff --git a/src/core/EVM/waitForTransactionReceipt.ts b/src/core/EVM/waitForTransactionReceipt.ts index 2dd058a6..c4294ca1 100644 --- a/src/core/EVM/waitForTransactionReceipt.ts +++ b/src/core/EVM/waitForTransactionReceipt.ts @@ -10,7 +10,7 @@ import type { import { waitForTransactionReceipt as waitForTransactionReceiptInternal } from 'viem/actions' import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' -import type { SDKClient } from '../types.js' +import type { SDKClient } from '../../types/core.js' import { getPublicClient } from './publicClient.js' interface WaitForTransactionReceiptProps { diff --git a/src/core/Solana/Solana.ts b/src/core/Solana/Solana.ts index 49ac9a4e..f306a576 100644 --- a/src/core/Solana/Solana.ts +++ b/src/core/Solana/Solana.ts @@ -1,5 +1,5 @@ import { ChainType } from '@lifi/types' -import type { StepExecutorOptions } from '../types.js' +import type { StepExecutorOptions } from '../../types/core.js' import { getSolanaBalance } from './getSolanaBalance.js' import { isSVMAddress } from './isSVMAddress.js' import { resolveSolanaAddress } from './resolveSolanaAddress.js' diff --git a/src/core/Solana/SolanaStepExecutor.ts b/src/core/Solana/SolanaStepExecutor.ts index 4c119633..49efb8fe 100644 --- a/src/core/Solana/SolanaStepExecutor.ts +++ b/src/core/Solana/SolanaStepExecutor.ts @@ -1,18 +1,18 @@ import type { SignerWalletAdapter } from '@solana/wallet-adapter-base' import { VersionedTransaction } from '@solana/web3.js' import { withTimeout } from 'viem' +import { getStepTransaction } from '../../actions/getStepTransaction.js' import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' -import { getStepTransaction } from '../../services/api.js' -import { base64ToUint8Array } from '../../utils/base64ToUint8Array.js' -import { BaseStepExecutor } from '../BaseStepExecutor.js' -import { checkBalance } from '../checkBalance.js' -import { stepComparison } from '../stepComparison.js' import type { LiFiStepExtended, SDKClient, TransactionParameters, -} from '../types.js' +} from '../../types/core.js' +import { base64ToUint8Array } from '../../utils/base64ToUint8Array.js' +import { BaseStepExecutor } from '../BaseStepExecutor.js' +import { checkBalance } from '../checkBalance.js' +import { stepComparison } from '../stepComparison.js' import { waitForDestinationChainTransaction } from '../waitForDestinationChainTransaction.js' import { callSolanaWithRetry } from './connection.js' import { parseSolanaErrors } from './parseSolanaErrors.js' @@ -74,7 +74,7 @@ export class SolanaStepExecutor extends BaseStepExecutor { if (!step.transactionRequest) { // biome-ignore lint/correctness/noUnusedVariables: destructuring const { execution, ...stepBase } = step - const updatedStep = await getStepTransaction(client.config, stepBase) + const updatedStep = await getStepTransaction(client, stepBase) const comparedStep = await stepComparison( this.statusManager, step, diff --git a/src/core/Solana/connection.ts b/src/core/Solana/connection.ts index 7edfc9fa..d470a126 100644 --- a/src/core/Solana/connection.ts +++ b/src/core/Solana/connection.ts @@ -1,6 +1,6 @@ import { ChainId } from '@lifi/types' import { Connection } from '@solana/web3.js' -import type { SDKClient } from '../types.js' +import type { SDKClient } from '../../types/core.js' const connections = new Map() diff --git a/src/core/Solana/getSolanaBalance.int.spec.ts b/src/core/Solana/getSolanaBalance.int.spec.ts index 071d0dd8..8701ea86 100644 --- a/src/core/Solana/getSolanaBalance.int.spec.ts +++ b/src/core/Solana/getSolanaBalance.int.spec.ts @@ -2,7 +2,7 @@ import { findDefaultToken } from '@lifi/data-types' import type { StaticToken, Token } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' import { describe, expect, it } from 'vitest' -import { createClient } from '../client/createClient.js' +import { createClient } from '../../client/createClient.js' import { getSolanaBalance } from './getSolanaBalance.js' import { Solana } from './Solana.js' diff --git a/src/core/Solana/getSolanaBalance.ts b/src/core/Solana/getSolanaBalance.ts index 0ff59c9b..760f2071 100644 --- a/src/core/Solana/getSolanaBalance.ts +++ b/src/core/Solana/getSolanaBalance.ts @@ -1,8 +1,8 @@ import type { ChainId, Token, TokenAmount } from '@lifi/types' import { PublicKey } from '@solana/web3.js' import { SolSystemProgram } from '../../constants.js' +import type { SDKClient } from '../../types/core.js' import { withDedupe } from '../../utils/withDedupe.js' -import type { SDKClient } from '../types.js' import { callSolanaWithRetry } from './connection.js' import { Token2022ProgramId, TokenProgramId } from './types.js' diff --git a/src/core/Solana/parseSolanaError.unit.spec.ts b/src/core/Solana/parseSolanaError.unit.spec.ts index 80f880ff..e6898ed0 100644 --- a/src/core/Solana/parseSolanaError.unit.spec.ts +++ b/src/core/Solana/parseSolanaError.unit.spec.ts @@ -1,9 +1,9 @@ import { describe, expect, it } from 'vitest' -import { buildStepObject } from '../../../tests/fixtures.js' import { BaseError } from '../../errors/baseError.js' import { ErrorName, LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' import { SDKError } from '../../errors/SDKError.js' +import { buildStepObject } from '../../tests/fixtures.js' import { parseSolanaErrors } from './parseSolanaErrors.js' describe('parseSolanaStepError', () => { diff --git a/src/core/Solana/parseSolanaErrors.ts b/src/core/Solana/parseSolanaErrors.ts index 31853b6a..ab9645de 100644 --- a/src/core/Solana/parseSolanaErrors.ts +++ b/src/core/Solana/parseSolanaErrors.ts @@ -3,7 +3,7 @@ import { BaseError } from '../../errors/baseError.js' import { ErrorMessage, LiFiErrorCode } from '../../errors/constants.js' import { TransactionError, UnknownError } from '../../errors/errors.js' import { SDKError } from '../../errors/SDKError.js' -import type { Process } from '../types.js' +import type { Process } from '../../types/core.js' export const parseSolanaErrors = async ( e: Error, diff --git a/src/core/Solana/sendAndConfirmTransaction.ts b/src/core/Solana/sendAndConfirmTransaction.ts index d59a6839..4bb3f8cd 100644 --- a/src/core/Solana/sendAndConfirmTransaction.ts +++ b/src/core/Solana/sendAndConfirmTransaction.ts @@ -4,8 +4,8 @@ import type { VersionedTransaction, } from '@solana/web3.js' import bs58 from 'bs58' +import type { SDKClient } from '../../types/core.js' import { sleep } from '../../utils/sleep.js' -import type { SDKClient } from '../types.js' import { getSolanaConnections } from './connection.js' type ConfirmedTransactionResult = { diff --git a/src/core/Solana/types.ts b/src/core/Solana/types.ts index 0a784f22..6f39c9d4 100644 --- a/src/core/Solana/types.ts +++ b/src/core/Solana/types.ts @@ -1,6 +1,6 @@ import { ChainType } from '@lifi/types' import type { SignerWalletAdapter } from '@solana/wallet-adapter-base' -import type { SDKProvider, StepExecutorOptions } from '../types.js' +import type { SDKProvider, StepExecutorOptions } from '../../types/core.js' export interface SolanaProviderOptions { getWalletAdapter?: () => Promise diff --git a/src/core/StatusManager.ts b/src/core/StatusManager.ts index 0f8c400a..4c4d630e 100644 --- a/src/core/StatusManager.ts +++ b/src/core/StatusManager.ts @@ -1,6 +1,4 @@ import type { ChainId, LiFiStep } from '@lifi/types' -import { executionState } from './executionState.js' -import { getProcessMessage } from './processMessages.js' import type { Execution, ExecutionStatus, @@ -8,7 +6,9 @@ import type { Process, ProcessStatus, ProcessType, -} from './types.js' +} from '../types/core.js' +import { executionState } from './executionState.js' +import { getProcessMessage } from './processMessages.js' type FindOrCreateProcessProps = { step: LiFiStepExtended diff --git a/src/core/StatusManager.unit.spec.ts b/src/core/StatusManager.unit.spec.ts index 01e3889a..4bebb6f1 100644 --- a/src/core/StatusManager.unit.spec.ts +++ b/src/core/StatusManager.unit.spec.ts @@ -5,14 +5,14 @@ import { buildRouteObject, buildStepObject, SOME_DATE, -} from '../../tests/fixtures.js' -import { executionState } from './executionState.js' -import { StatusManager } from './StatusManager.js' +} from '../tests/fixtures.js' import type { ExecutionStatus, LiFiStepExtended, ProcessStatus, -} from './types.js' +} from '../types/core.js' +import { executionState } from './executionState.js' +import { StatusManager } from './StatusManager.js' // Note: using structuredClone when passing objects to the StatusManager shall make sure that we are not facing any unknown call-by-reference-issues anymore diff --git a/src/core/Sui/Sui.ts b/src/core/Sui/Sui.ts index b416010f..cf565184 100644 --- a/src/core/Sui/Sui.ts +++ b/src/core/Sui/Sui.ts @@ -1,6 +1,6 @@ import { ChainType } from '@lifi/types' import { isValidSuiAddress } from '@mysten/sui/utils' -import type { StepExecutorOptions } from '../types.js' +import type { StepExecutorOptions } from '../../types/core.js' import { getSuiBalance } from './getSuiBalance.js' import { resolveSuiAddress } from './resolveSuiAddress.js' import { SuiStepExecutor } from './SuiStepExecutor.js' diff --git a/src/core/Sui/SuiStepExecutor.ts b/src/core/Sui/SuiStepExecutor.ts index 28e917dd..b3af15f4 100644 --- a/src/core/Sui/SuiStepExecutor.ts +++ b/src/core/Sui/SuiStepExecutor.ts @@ -2,17 +2,17 @@ import { signAndExecuteTransaction, type WalletWithRequiredFeatures, } from '@mysten/wallet-standard' +import { getStepTransaction } from '../../actions/getStepTransaction.js' import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' -import { getStepTransaction } from '../../services/api.js' -import { BaseStepExecutor } from '../BaseStepExecutor.js' -import { checkBalance } from '../checkBalance.js' -import { stepComparison } from '../stepComparison.js' import type { LiFiStepExtended, SDKClient, TransactionParameters, -} from '../types.js' +} from '../../types/core.js' +import { BaseStepExecutor } from '../BaseStepExecutor.js' +import { checkBalance } from '../checkBalance.js' +import { stepComparison } from '../stepComparison.js' import { waitForDestinationChainTransaction } from '../waitForDestinationChainTransaction.js' import { parseSuiErrors } from './parseSuiErrors.js' import { callSuiWithRetry } from './suiClient.js' @@ -73,7 +73,7 @@ export class SuiStepExecutor extends BaseStepExecutor { if (!step.transactionRequest) { // biome-ignore lint/correctness/noUnusedVariables: destructuring const { execution, ...stepBase } = step - const updatedStep = await getStepTransaction(client.config, stepBase) + const updatedStep = await getStepTransaction(client, stepBase) const comparedStep = await stepComparison( this.statusManager, step, diff --git a/src/core/Sui/getSuiBalance.int.spec.ts b/src/core/Sui/getSuiBalance.int.spec.ts index c4383726..8f081e4f 100644 --- a/src/core/Sui/getSuiBalance.int.spec.ts +++ b/src/core/Sui/getSuiBalance.int.spec.ts @@ -2,7 +2,7 @@ import { findDefaultToken } from '@lifi/data-types' import type { StaticToken, Token } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' import { describe, expect, it } from 'vitest' -import { createClient } from '../client/createClient.js' +import { createClient } from '../../client/createClient.js' import { getSuiBalance } from './getSuiBalance.js' const client = createClient({ diff --git a/src/core/Sui/getSuiBalance.ts b/src/core/Sui/getSuiBalance.ts index bf65dd97..f02be4d5 100644 --- a/src/core/Sui/getSuiBalance.ts +++ b/src/core/Sui/getSuiBalance.ts @@ -1,6 +1,6 @@ import type { Token, TokenAmount } from '@lifi/types' +import type { SDKClient } from '../../types/core.js' import { withDedupe } from '../../utils/withDedupe.js' -import type { SDKClient } from '../types.js' import { callSuiWithRetry } from './suiClient.js' import { SuiTokenLongAddress, SuiTokenShortAddress } from './types.js' diff --git a/src/core/Sui/parseSuiErrors.ts b/src/core/Sui/parseSuiErrors.ts index 75c59fe3..b7e7574a 100644 --- a/src/core/Sui/parseSuiErrors.ts +++ b/src/core/Sui/parseSuiErrors.ts @@ -3,7 +3,7 @@ import { BaseError } from '../../errors/baseError.js' import { ErrorMessage, LiFiErrorCode } from '../../errors/constants.js' import { TransactionError, UnknownError } from '../../errors/errors.js' import { SDKError } from '../../errors/SDKError.js' -import type { Process } from '../types.js' +import type { Process } from '../../types/core.js' export const parseSuiErrors = async ( e: Error, diff --git a/src/core/Sui/suiClient.ts b/src/core/Sui/suiClient.ts index 5e04e3ff..7af46177 100644 --- a/src/core/Sui/suiClient.ts +++ b/src/core/Sui/suiClient.ts @@ -1,6 +1,6 @@ import { ChainId } from '@lifi/types' import { SuiClient } from '@mysten/sui/client' -import type { SDKClient } from '../types.js' +import type { SDKClient } from '../../types/core.js' const clients = new Map() diff --git a/src/core/Sui/types.ts b/src/core/Sui/types.ts index 47aaf3a0..d82ca6e6 100644 --- a/src/core/Sui/types.ts +++ b/src/core/Sui/types.ts @@ -1,6 +1,6 @@ import { ChainType } from '@lifi/types' import type { WalletWithRequiredFeatures } from '@mysten/wallet-standard' -import type { SDKProvider, StepExecutorOptions } from '../types.js' +import type { SDKProvider, StepExecutorOptions } from '../../types/core.js' export interface SuiProviderOptions { getWallet?: () => Promise diff --git a/src/core/UTXO/UTXO.ts b/src/core/UTXO/UTXO.ts index 3174ecde..9f213115 100644 --- a/src/core/UTXO/UTXO.ts +++ b/src/core/UTXO/UTXO.ts @@ -1,6 +1,6 @@ import { isUTXOAddress } from '@bigmi/core' import { ChainType } from '@lifi/types' -import type { StepExecutorOptions } from '../types.js' +import type { StepExecutorOptions } from '../../types/core.js' import { getUTXOBalance } from './getUTXOBalance.js' import { resolveUTXOAddress } from './resolveUTXOAddress.js' import type { UTXOProvider, UTXOProviderOptions } from './types.js' diff --git a/src/core/UTXO/UTXOStepExecutor.ts b/src/core/UTXO/UTXOStepExecutor.ts index 75213123..f78cb7c9 100644 --- a/src/core/UTXO/UTXOStepExecutor.ts +++ b/src/core/UTXO/UTXOStepExecutor.ts @@ -10,18 +10,18 @@ import { import * as ecc from '@bitcoinerlab/secp256k1' import { ChainId } from '@lifi/types' import { address, initEccLib, networks, Psbt } from 'bitcoinjs-lib' +import { getStepTransaction } from '../../actions/getStepTransaction.js' import { LiFiErrorCode } from '../../errors/constants.js' import { TransactionError } from '../../errors/errors.js' -import { getStepTransaction } from '../../services/api.js' -import { BaseStepExecutor } from '../BaseStepExecutor.js' -import { checkBalance } from '../checkBalance.js' -import { stepComparison } from '../stepComparison.js' import type { LiFiStepExtended, SDKClient, StepExecutorOptions, TransactionParameters, -} from '../types.js' +} from '../../types/core.js' +import { BaseStepExecutor } from '../BaseStepExecutor.js' +import { checkBalance } from '../checkBalance.js' +import { stepComparison } from '../stepComparison.js' import { waitForDestinationChainTransaction } from '../waitForDestinationChainTransaction.js' import { getUTXOPublicClient } from './getUTXOPublicClient.js' import { parseUTXOErrors } from './parseUTXOErrors.js' @@ -95,10 +95,7 @@ export class UTXOStepExecutor extends BaseStepExecutor { if (!step.transactionRequest) { // biome-ignore lint/correctness/noUnusedVariables: destructuring const { execution, ...stepBase } = step - const updatedStep = await getStepTransaction( - client.config, - stepBase - ) + const updatedStep = await getStepTransaction(client, stepBase) const comparedStep = await stepComparison( this.statusManager, step, diff --git a/src/core/UTXO/getUTXOBalance.int.spec.ts b/src/core/UTXO/getUTXOBalance.int.spec.ts index 9548f12d..7aa088c4 100644 --- a/src/core/UTXO/getUTXOBalance.int.spec.ts +++ b/src/core/UTXO/getUTXOBalance.int.spec.ts @@ -2,8 +2,8 @@ import { findDefaultToken } from '@lifi/data-types' import type { StaticToken, Token } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' import { describe, expect, it } from 'vitest' -import { createClient } from '../client/createClient.js' -import type { SDKClient } from '../types.js' +import { createClient } from '../../client/createClient.js' +import type { SDKClient } from '../../types/core.js' import { getUTXOBalance } from './getUTXOBalance.js' import { UTXO } from './UTXO.js' diff --git a/src/core/UTXO/getUTXOBalance.ts b/src/core/UTXO/getUTXOBalance.ts index 3d9cc938..515b7931 100644 --- a/src/core/UTXO/getUTXOBalance.ts +++ b/src/core/UTXO/getUTXOBalance.ts @@ -1,5 +1,5 @@ import { ChainId, type Token, type TokenAmount } from '@lifi/types' -import type { SDKClient } from '../types.js' +import type { SDKClient } from '../../types/core.js' import { getUTXOPublicClient } from './getUTXOPublicClient.js' export const getUTXOBalance = async ( diff --git a/src/core/UTXO/getUTXOPublicClient.ts b/src/core/UTXO/getUTXOPublicClient.ts index f4f2926c..6b6040f7 100644 --- a/src/core/UTXO/getUTXOPublicClient.ts +++ b/src/core/UTXO/getUTXOPublicClient.ts @@ -17,7 +17,7 @@ import { type WalletActions, walletActions, } from '@bigmi/core' -import type { SDKClient } from '../types.js' +import type { SDKClient } from '../../types/core.js' import { toBigmiChainId } from './utils.js' type PublicClient = Client< diff --git a/src/core/UTXO/parseUTXOErrors.ts b/src/core/UTXO/parseUTXOErrors.ts index 83a78790..ae7a7a41 100644 --- a/src/core/UTXO/parseUTXOErrors.ts +++ b/src/core/UTXO/parseUTXOErrors.ts @@ -3,7 +3,7 @@ import { BaseError } from '../../errors/baseError.js' import { ErrorMessage, LiFiErrorCode } from '../../errors/constants.js' import { TransactionError, UnknownError } from '../../errors/errors.js' import { SDKError } from '../../errors/SDKError.js' -import type { Process } from '../types.js' +import type { Process } from '../../types/core.js' export const parseUTXOErrors = async ( e: Error, diff --git a/src/core/UTXO/types.ts b/src/core/UTXO/types.ts index 754a2d95..0100f22d 100644 --- a/src/core/UTXO/types.ts +++ b/src/core/UTXO/types.ts @@ -1,7 +1,7 @@ import type { Client } from '@bigmi/core' import { ChainType } from '@lifi/types' -import type { SDKProvider } from '../types.js' +import type { SDKProvider } from '../../types/core.js' export interface UTXOProviderOptions { getWalletClient?: () => Promise diff --git a/src/core/checkBalance.ts b/src/core/checkBalance.ts index 44260d08..90133a8a 100644 --- a/src/core/checkBalance.ts +++ b/src/core/checkBalance.ts @@ -1,9 +1,9 @@ import type { LiFiStep } from '@lifi/types' import { formatUnits } from 'viem' +import { getTokenBalance } from '../actions/getTokenBalance.js' import { BalanceError } from '../errors/errors.js' -import { getTokenBalance } from '../services/balance.js' +import type { SDKClient } from '../types/core.js' import { sleep } from '../utils/sleep.js' -import type { SDKClient } from './types.js' export const checkBalance = async ( client: SDKClient, diff --git a/src/core/execution.ts b/src/core/execution.ts index de858532..d1653e7d 100644 --- a/src/core/execution.ts +++ b/src/core/execution.ts @@ -1,14 +1,14 @@ import type { Route } from '@lifi/types' import { LiFiErrorCode } from '../errors/constants.js' import { ProviderError } from '../errors/errors.js' -import { executionState } from './executionState.js' -import { prepareRestart } from './prepareRestart.js' import type { ExecutionOptions, RouteExtended, SDKClient, SDKProvider, -} from './types.js' +} from '../types/core.js' +import { executionState } from './executionState.js' +import { prepareRestart } from './prepareRestart.js' /** * Execute a route. diff --git a/src/core/execution.unit.handlers.ts b/src/core/execution.unit.handlers.ts index 034355a2..123c6336 100644 --- a/src/core/execution.unit.handlers.ts +++ b/src/core/execution.unit.handlers.ts @@ -1,6 +1,6 @@ import { HttpResponse, http } from 'msw' -import { buildStepObject } from '../../tests/fixtures.js' -import { createClient } from './client/createClient.js' +import { createClient } from '../client/createClient.js' +import { buildStepObject } from '../tests/fixtures.js' import { mockChainsResponse, mockStatus, diff --git a/src/core/execution.unit.mock.ts b/src/core/execution.unit.mock.ts index 3fd2665b..9d93c3de 100644 --- a/src/core/execution.unit.mock.ts +++ b/src/core/execution.unit.mock.ts @@ -1,5 +1,5 @@ import type { LiFiStep } from '@lifi/types' -import { buildStepObject } from '../../tests/fixtures.js' +import { buildStepObject } from '../tests/fixtures.js' export const mockChainsResponse = [ { diff --git a/src/core/execution.unit.spec.ts b/src/core/execution.unit.spec.ts index 38f64279..d3f3f51f 100644 --- a/src/core/execution.unit.spec.ts +++ b/src/core/execution.unit.spec.ts @@ -11,9 +11,9 @@ import { it, vi, } from 'vitest' -import { buildRouteObject, buildStepObject } from '../../tests/fixtures.js' +import { createClient } from '../client/createClient.js' import { requestSettings } from '../request.js' -import { createClient } from './client/createClient.js' +import { buildRouteObject, buildStepObject } from '../tests/fixtures.js' import { EVM } from './EVM/EVM.js' import { executeRoute } from './execution.js' import { lifiHandlers } from './execution.unit.handlers.js' diff --git a/src/core/executionState.ts b/src/core/executionState.ts index aae66930..fc9c7961 100644 --- a/src/core/executionState.ts +++ b/src/core/executionState.ts @@ -1,4 +1,8 @@ -import type { ExecutionOptions, RouteExtended, StepExecutor } from './types.js' +import type { + ExecutionOptions, + RouteExtended, + StepExecutor, +} from '../types/core.js' interface ExecutionData { route: RouteExtended diff --git a/src/core/prepareRestart.ts b/src/core/prepareRestart.ts index a012b902..c8fb8f20 100644 --- a/src/core/prepareRestart.ts +++ b/src/core/prepareRestart.ts @@ -1,4 +1,4 @@ -import type { RouteExtended } from './types.js' +import type { RouteExtended } from '../types/core.js' export const prepareRestart = (route: RouteExtended) => { for (let index = 0; index < route.steps.length; index++) { diff --git a/src/core/processMessages.ts b/src/core/processMessages.ts index db0cc7ee..d7fdb142 100644 --- a/src/core/processMessages.ts +++ b/src/core/processMessages.ts @@ -1,5 +1,5 @@ import type { StatusMessage, Substatus } from '@lifi/types' -import type { ProcessStatus, ProcessType } from './types.js' +import type { ProcessStatus, ProcessType } from '../types/core.js' const processMessages: Record< ProcessType, diff --git a/src/core/stepComparison.ts b/src/core/stepComparison.ts index cd3135c3..3a64445a 100644 --- a/src/core/stepComparison.ts +++ b/src/core/stepComparison.ts @@ -1,8 +1,8 @@ import type { LiFiStep } from '@lifi/types' import { LiFiErrorCode } from '../errors/constants.js' import { TransactionError } from '../errors/errors.js' +import type { ExecutionOptions } from '../types/core.js' import type { StatusManager } from './StatusManager.js' -import type { ExecutionOptions } from './types.js' import { checkStepSlippageThreshold } from './utils.js' /** diff --git a/src/core/utils.ts b/src/core/utils.ts index dcab6e1b..66a83740 100644 --- a/src/core/utils.ts +++ b/src/core/utils.ts @@ -1,5 +1,5 @@ import type { ChainId, ExtendedChain, LiFiStep } from '@lifi/types' -import type { RPCUrls } from '../core/types.js' +import type { RPCUrls } from '../types/core.js' // Standard threshold for destination amount difference (0.5%) const standardThreshold = 0.005 diff --git a/src/core/waitForDestinationChainTransaction.ts b/src/core/waitForDestinationChainTransaction.ts index 27c26f28..28ab5734 100644 --- a/src/core/waitForDestinationChainTransaction.ts +++ b/src/core/waitForDestinationChainTransaction.ts @@ -4,9 +4,9 @@ import type { FullStatusData, } from '@lifi/types' import { LiFiErrorCode } from '../errors/constants.js' +import type { LiFiStepExtended, Process, SDKClient } from '../types/core.js' import { getTransactionFailedMessage } from '../utils/getTransactionMessage.js' import type { StatusManager } from './StatusManager.js' -import type { LiFiStepExtended, Process, SDKClient } from './types.js' import { waitForTransactionStatus } from './waitForTransactionStatus.js' export async function waitForDestinationChainTransaction( @@ -41,7 +41,7 @@ export async function waitForDestinationChainTransaction( } const statusResponse = (await waitForTransactionStatus( - client.config, + client, statusManager, transactionHash, step, diff --git a/src/core/waitForTransactionStatus.ts b/src/core/waitForTransactionStatus.ts index 9efe3f8c..8b8a2191 100644 --- a/src/core/waitForTransactionStatus.ts +++ b/src/core/waitForTransactionStatus.ts @@ -1,15 +1,15 @@ import type { FullStatusData, LiFiStep, StatusResponse } from '@lifi/types' +import { getStatus } from '../actions/getStatus.js' import { ServerError } from '../errors/errors.js' -import { getStatus } from '../services/api.js' +import type { ProcessType, SDKClient } from '../types/core.js' import { waitForResult } from '../utils/waitForResult.js' import { getSubstatusMessage } from './processMessages.js' import type { StatusManager } from './StatusManager.js' -import type { ProcessType, SDKBaseConfig } from './types.js' const TRANSACTION_HASH_OBSERVERS: Record> = {} export async function waitForTransactionStatus( - config: SDKBaseConfig, + client: SDKClient, statusManager: StatusManager, txHash: string, step: LiFiStep, @@ -17,7 +17,7 @@ export async function waitForTransactionStatus( interval = 5_000 ): Promise { const _getStatus = (): Promise => { - return getStatus(config, { + return getStatus(client, { fromChain: step.action.fromChainId, fromAddress: step.action.fromAddress, toChain: step.action.toChainId, diff --git a/src/errors/SDKError.ts b/src/errors/SDKError.ts index 2dc29fb8..c06bd507 100644 --- a/src/errors/SDKError.ts +++ b/src/errors/SDKError.ts @@ -1,5 +1,5 @@ import type { LiFiStep } from '@lifi/types' -import type { Process } from '../core/types.js' +import type { Process } from '../types/core.js' import { version } from '../version.js' import type { BaseError } from './baseError.js' import type { ErrorCode } from './constants.js' diff --git a/src/index.ts b/src/index.ts index 0e21b221..f316bc4f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,28 @@ // biome-ignore lint/performance/noBarrelFile: module entrypoint // biome-ignore lint/performance/noReExportAll: types export * from '@lifi/types' -export { createClient } from './core/client/createClient.js' +export { getChains } from './actions/getChains.js' +export { getConnections } from './actions/getConnections.js' +export { getContractCallsQuote } from './actions/getContractCallsQuote.js' +export { getGasRecommendation } from './actions/getGasRecommendation.js' +export { getNameServiceAddress } from './actions/getNameServiceAddress.js' +export { getQuote } from './actions/getQuote.js' +export { getRelayedTransactionStatus } from './actions/getRelayedTransactionStatus.js' +export { getRelayerQuote } from './actions/getRelayerQuote.js' +export { getRoutes } from './actions/getRoutes.js' +export { getStatus } from './actions/getStatus.js' +export { getStepTransaction } from './actions/getStepTransaction.js' +export { getToken } from './actions/getToken.js' +export { getTokenBalance } from './actions/getTokenBalance.js' +export { getTokenBalances } from './actions/getTokenBalances.js' +export { getTokenBalancesByChain } from './actions/getTokenBalancesByChain.js' +export { getTokens } from './actions/getTokens.js' +export { getTools } from './actions/getTools.js' +export { getTransactionHistory } from './actions/getTransactionHistory.js' +export { getWalletBalances } from './actions/getWalletBalances.js' +export { actions } from './actions/index.js' +export { relayTransaction } from './actions/relayTransaction.js' +export { createClient } from './client/createClient.js' export { checkPermitSupport } from './core/EVM/checkPermitSupport.js' export { EVM } from './core/EVM/EVM.js' export { @@ -53,6 +74,23 @@ export { StatusManager } from './core/StatusManager.js' export { Sui } from './core/Sui/Sui.js' export type { SuiProvider, SuiProviderOptions } from './core/Sui/types.js' export { isSui } from './core/Sui/types.js' +export type { UTXOProvider, UTXOProviderOptions } from './core/UTXO/types.js' +export { isUTXO } from './core/UTXO/types.js' +export { UTXO } from './core/UTXO/UTXO.js' +export { BaseError } from './errors/baseError.js' +export type { ErrorCode } from './errors/constants.js' +export { ErrorMessage, ErrorName, LiFiErrorCode } from './errors/constants.js' +export { + BalanceError, + ProviderError, + RPCError, + ServerError, + TransactionError, + UnknownError, + ValidationError, +} from './errors/errors.js' +export { HTTPError } from './errors/httpError.js' +export { SDKError } from './errors/SDKError.js' export type { AcceptExchangeRateUpdateHook, AcceptSlippageUpdateHook, @@ -83,48 +121,7 @@ export type { TransactionRequestParameters, TransactionRequestUpdateHook, UpdateRouteHook, -} from './core/types.js' -export type { UTXOProvider, UTXOProviderOptions } from './core/UTXO/types.js' -export { isUTXO } from './core/UTXO/types.js' -export { UTXO } from './core/UTXO/UTXO.js' -export { BaseError } from './errors/baseError.js' -export type { ErrorCode } from './errors/constants.js' -export { ErrorMessage, ErrorName, LiFiErrorCode } from './errors/constants.js' -export { - BalanceError, - ProviderError, - RPCError, - ServerError, - TransactionError, - UnknownError, - ValidationError, -} from './errors/errors.js' -export { HTTPError } from './errors/httpError.js' -export { SDKError } from './errors/SDKError.js' -export { - getChains, - getConnections, - getContractCallsQuote, - getGasRecommendation, - getQuote, - getRelayedTransactionStatus, - getRelayerQuote, - getRoutes, - getStatus, - getStepTransaction, - getToken, - getTokens, - getTools, - getTransactionHistory, - relayTransaction, -} from './services/api.js' -export { - getTokenBalance, - getTokenBalances, - getTokenBalancesByChain, - getWalletBalances, -} from './services/balance.js' -export { getNameServiceAddress } from './services/getNameServiceAddress.js' +} from './types/core.js' export { checkPackageUpdates } from './utils/checkPackageUpdates.js' export { convertQuoteToRoute } from './utils/convertQuoteToRoute.js' export { fetchTxErrorDetails } from './utils/fetchTxErrorDetails.js' diff --git a/src/request.ts b/src/request.ts index 19f9702e..b2ef9234 100644 --- a/src/request.ts +++ b/src/request.ts @@ -1,7 +1,7 @@ -import type { SDKBaseConfig } from './core/types.js' import { ValidationError } from './errors/errors.js' import { HTTPError } from './errors/httpError.js' import { SDKError } from './errors/SDKError.js' +import type { SDKBaseConfig } from './types/core.js' import type { ExtendedRequestInit } from './types/request.js' import { sleep } from './utils/sleep.js' import { version } from './version.js' diff --git a/src/request.unit.spec.ts b/src/request.unit.spec.ts index bda41369..0e30b207 100644 --- a/src/request.unit.spec.ts +++ b/src/request.unit.spec.ts @@ -10,12 +10,12 @@ import { it, vi, } from 'vitest' -import { createClient } from './core/client/createClient.js' +import { handlers } from './actions/actions.unit.handlers.js' +import { createClient } from './client/createClient.js' import { ValidationError } from './errors/errors.js' import type { HTTPError } from './errors/httpError.js' import { SDKError } from './errors/SDKError.js' import { request } from './request.js' -import { handlers } from './services/api.unit.handlers.js' import type { ExtendedRequestInit } from './types/request.js' import { version } from './version.js' diff --git a/src/services/api.ts b/src/services/api.ts deleted file mode 100644 index 407b867f..00000000 --- a/src/services/api.ts +++ /dev/null @@ -1,744 +0,0 @@ -import { - type ChainId, - type ChainKey, - type ChainsRequest, - type ChainsResponse, - type ConnectionsRequest, - type ConnectionsResponse, - type ContractCallsQuoteRequest, - type ExtendedChain, - type GasRecommendationRequest, - type GasRecommendationResponse, - isContractCallsRequestWithFromAmount, - isContractCallsRequestWithToAmount, - type LiFiStep, - type RelayerQuoteResponse, - type RelayRequest, - type RelayResponse, - type RelayResponseData, - type RelayStatusRequest, - type RelayStatusResponse, - type RelayStatusResponseData, - type RequestOptions, - type RoutesRequest, - type RoutesResponse, - type SignedLiFiStep, - type StatusResponse, - type TokenExtended, - type TokensExtendedResponse, - type TokensRequest, - type TokensResponse, - type ToolsRequest, - type ToolsResponse, - type TransactionAnalyticsRequest, - type TransactionAnalyticsResponse, -} from '@lifi/types' -import type { SDKBaseConfig } from '../core/types.js' -import { BaseError } from '../errors/baseError.js' -import { ErrorName } from '../errors/constants.js' -import { ValidationError } from '../errors/errors.js' -import { SDKError } from '../errors/SDKError.js' -import { request } from '../request.js' -import { isRoutesRequest, isStep } from '../typeguards.js' -import { withDedupe } from '../utils/withDedupe.js' -import type { - GetStatusRequestExtended, - QuoteRequest, - QuoteRequestFromAmount, - QuoteRequestToAmount, -} from './types.js' - -/** - * Get a quote for a token transfer - * @param config - The SDK client configuration - * @param params - The configuration of the requested quote - * @param options - Request options - * @throws {LiFiError} - Throws a LiFiError if request fails - * @returns Quote for a token transfer - */ -export async function getQuote( - config: SDKBaseConfig, - params: QuoteRequestFromAmount, - options?: RequestOptions -): Promise -export async function getQuote( - config: SDKBaseConfig, - params: QuoteRequestToAmount, - options?: RequestOptions -): Promise -export async function getQuote( - config: SDKBaseConfig, - params: QuoteRequest, - options?: RequestOptions -): Promise { - const requiredParameters: Array = [ - 'fromChain', - 'fromToken', - 'fromAddress', - 'toChain', - 'toToken', - ] - - for (const requiredParameter of requiredParameters) { - if (!params[requiredParameter]) { - throw new SDKError( - new ValidationError( - `Required parameter "${requiredParameter}" is missing.` - ) - ) - } - } - - const isFromAmountRequest = - 'fromAmount' in params && params.fromAmount !== undefined - const isToAmountRequest = - 'toAmount' in params && params.toAmount !== undefined - - if (!isFromAmountRequest && !isToAmountRequest) { - throw new SDKError( - new ValidationError( - 'Required parameter "fromAmount" or "toAmount" is missing.' - ) - ) - } - - if (isFromAmountRequest && isToAmountRequest) { - throw new SDKError( - new ValidationError( - 'Cannot provide both "fromAmount" and "toAmount" parameters.' - ) - ) - } - - // apply defaults - params.integrator ??= config.integrator - params.order ??= config.routeOptions?.order - params.slippage ??= config.routeOptions?.slippage - params.referrer ??= config.routeOptions?.referrer - params.fee ??= config.routeOptions?.fee - params.allowBridges ??= config.routeOptions?.bridges?.allow - params.denyBridges ??= config.routeOptions?.bridges?.deny - params.preferBridges ??= config.routeOptions?.bridges?.prefer - params.allowExchanges ??= config.routeOptions?.exchanges?.allow - params.denyExchanges ??= config.routeOptions?.exchanges?.deny - params.preferExchanges ??= config.routeOptions?.exchanges?.prefer - - for (const key of Object.keys(params)) { - if (!params[key as keyof QuoteRequest]) { - delete params[key as keyof QuoteRequest] - } - } - - return await request( - config, - `${config.apiUrl}/${isFromAmountRequest ? 'quote' : 'quote/toAmount'}?${new URLSearchParams( - params as unknown as Record - )}`, - { - signal: options?.signal, - } - ) -} - -/** - * Get a set of routes for a request that describes a transfer of tokens. - * @param config - The SDK client configuration - * @param params - A description of the transfer. - * @param options - Request options - * @returns The resulting routes that can be used to realize the described transfer of tokens. - * @throws {LiFiError} Throws a LiFiError if request fails. - */ -export const getRoutes = async ( - config: SDKBaseConfig, - params: RoutesRequest, - options?: RequestOptions -): Promise => { - if (!isRoutesRequest(params)) { - throw new SDKError(new ValidationError('Invalid routes request.')) - } - - // apply defaults - params.options = { - integrator: config.integrator, - ...config.routeOptions, - ...params.options, - } - - return await request( - config, - `${config.apiUrl}/advanced/routes`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(params), - signal: options?.signal, - } - ) -} - -/** - * Get a quote for a destination contract call - * @param config - The SDK client configuration - * @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 ( - config: SDKBaseConfig, - params: ContractCallsQuoteRequest, - options?: RequestOptions -): Promise => { - // validation - const requiredParameters: Array = [ - '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 ??= config.integrator - params.slippage ??= config.routeOptions?.slippage - params.referrer ??= config.routeOptions?.referrer - params.fee ??= config.routeOptions?.fee - params.allowBridges ??= config.routeOptions?.bridges?.allow - params.denyBridges ??= config.routeOptions?.bridges?.deny - params.preferBridges ??= config.routeOptions?.bridges?.prefer - params.allowExchanges ??= config.routeOptions?.exchanges?.allow - params.denyExchanges ??= config.routeOptions?.exchanges?.deny - params.preferExchanges ??= config.routeOptions?.exchanges?.prefer - // send request - return await request( - config, - `${config.apiUrl}/quote/contractCalls`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(params), - signal: options?.signal, - } - ) -} - -/** - * Get the transaction data for a single step of a route - * @param config - The SDK client configuration - * @param step - The step object. - * @param options - Request options - * @returns The step populated with the transaction data. - * @throws {LiFiError} Throws a LiFiError if request fails. - */ -export const getStepTransaction = async ( - config: SDKBaseConfig, - step: LiFiStep | SignedLiFiStep, - options?: RequestOptions -): Promise => { - if (!isStep(step)) { - // While the validation fails for some users we should not enforce it - console.warn('SDK Validation: Invalid Step', step) - } - - return await request( - config, - `${config.apiUrl}/advanced/stepTransaction`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(step), - signal: options?.signal, - } - ) -} - -/** - * Check the status of a transfer. For cross chain transfers, the "bridge" parameter is required. - * @param config - The SDK client configuration - * @param params - Configuration of the requested status - * @param options - Request options. - * @throws {LiFiError} - Throws a LiFiError if request fails - * @returns Returns status response. - */ -export const getStatus = async ( - config: SDKBaseConfig, - params: GetStatusRequestExtended, - options?: RequestOptions -): Promise => { - if (!params.txHash) { - throw new SDKError( - new ValidationError('Required parameter "txHash" is missing.') - ) - } - const queryParams = new URLSearchParams( - params as unknown as Record - ) - return await request( - config, - `${config.apiUrl}/status?${queryParams}`, - { - signal: options?.signal, - } - ) -} - -/** - * Get a relayer quote for a token transfer - * @param config - The SDK client configuration - * @param params - The configuration of the requested quote - * @param options - Request options - * @throws {LiFiError} - Throws a LiFiError if request fails - * @returns Relayer quote for a token transfer - */ -export const getRelayerQuote = async ( - config: SDKBaseConfig, - params: QuoteRequestFromAmount, - options?: RequestOptions -): Promise => { - const requiredParameters: Array = [ - 'fromChain', - 'fromToken', - 'fromAddress', - 'fromAmount', - 'toChain', - 'toToken', - ] - for (const requiredParameter of requiredParameters) { - if (!params[requiredParameter]) { - throw new SDKError( - new ValidationError( - `Required parameter "${requiredParameter}" is missing.` - ) - ) - } - } - - // apply defaults - params.integrator ??= config.integrator - params.order ??= config.routeOptions?.order - params.slippage ??= config.routeOptions?.slippage - params.referrer ??= config.routeOptions?.referrer - params.fee ??= config.routeOptions?.fee - params.allowBridges ??= config.routeOptions?.bridges?.allow - params.denyBridges ??= config.routeOptions?.bridges?.deny - params.preferBridges ??= config.routeOptions?.bridges?.prefer - params.allowExchanges ??= config.routeOptions?.exchanges?.allow - params.denyExchanges ??= config.routeOptions?.exchanges?.deny - params.preferExchanges ??= config.routeOptions?.exchanges?.prefer - - for (const key of Object.keys(params)) { - if (!params[key as keyof QuoteRequest]) { - delete params[key as keyof QuoteRequest] - } - } - - const result = await request( - config, - `${config.apiUrl}/relayer/quote?${new URLSearchParams( - params as unknown as Record - )}`, - { - signal: options?.signal, - } - ) - - if (result.status === 'error') { - throw new BaseError( - ErrorName.ServerError, - result.data.code, - result.data.message - ) - } - - return result.data -} - -/** - * Relay a transaction through the relayer service - * @param config - The SDK client configuration - * @param params - The configuration for the relay request - * @param options - Request options - * @throws {LiFiError} - Throws a LiFiError if request fails - * @returns Task ID for the relayed transaction - */ -export const relayTransaction = async ( - config: SDKBaseConfig, - params: RelayRequest, - options?: RequestOptions -): Promise => { - const requiredParameters: Array = ['typedData'] - - for (const requiredParameter of requiredParameters) { - if (!params[requiredParameter]) { - throw new SDKError( - new ValidationError( - `Required parameter "${requiredParameter}" is missing.` - ) - ) - } - } - - // Determine if the request is for a gasless relayer service or advanced relayer service - // We will use the same endpoint for both after the gasless relayer service is deprecated - const relayerPath = params.typedData.some( - (t) => t.primaryType === 'PermitWitnessTransferFrom' - ) - ? '/relayer/relay' - : '/advanced/relay' - - const result = await request( - config, - `${config.apiUrl}${relayerPath}`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(params, (_, value) => { - if (typeof value === 'bigint') { - return value.toString() - } - return value - }), - signal: options?.signal, - } - ) - - if (result.status === 'error') { - throw new BaseError( - ErrorName.ServerError, - result.data.code, - result.data.message - ) - } - - return result.data -} - -/** - * Get the status of a relayed transaction - * @param params - Parameters for the relay status request - * @param options - Request options - * @throws {LiFiError} - Throws a LiFiError if request fails - * @returns Status of the relayed transaction - */ -export const getRelayedTransactionStatus = async ( - config: SDKBaseConfig, - params: RelayStatusRequest, - options?: RequestOptions -): Promise => { - if (!params.taskId) { - throw new SDKError( - new ValidationError('Required parameter "taskId" is missing.') - ) - } - - const { taskId, ...otherParams } = params - const queryParams = new URLSearchParams( - otherParams as unknown as Record - ) - const result = await request( - config, - `${config.apiUrl}/relayer/status/${taskId}?${queryParams}`, - { - signal: options?.signal, - } - ) - - if (result.status === 'error') { - throw new BaseError( - ErrorName.ServerError, - result.data.code, - result.data.message - ) - } - - return result.data -} - -/** - * Get all available chains - * @param config - The SDK client configuration - * @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 ( - config: SDKBaseConfig, - params?: ChainsRequest, - options?: RequestOptions -): Promise => { - 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 - ).toString() - const response = await withDedupe( - () => - request( - config, - `${config.apiUrl}/chains?${urlSearchParams}`, - { - signal: options?.signal, - } - ), - { id: `${getChains.name}.${urlSearchParams}` } - ) - return response.chains -} - -/** - * Get all known tokens. - * @param config - The SDK client configuration - * @param params - The configuration of the requested tokens - * @param options - Request options - * @returns The tokens that are available on the requested chains - */ -export async function getTokens( - config: SDKBaseConfig, - params?: TokensRequest & { extended?: false | undefined }, - options?: RequestOptions -): Promise -export async function getTokens( - config: SDKBaseConfig, - params: TokensRequest & { extended: true }, - options?: RequestOptions -): Promise -export async function getTokens( - config: SDKBaseConfig, - params?: TokensRequest, - options?: RequestOptions -): Promise { - if (params) { - for (const key of Object.keys(params)) { - if (!params[key as keyof TokensRequest]) { - delete params[key as keyof TokensRequest] - } - } - } - const urlSearchParams = new URLSearchParams( - params as Record - ).toString() - const isExtended = params?.extended === true - const response = await withDedupe( - () => - request< - typeof isExtended extends true ? TokensExtendedResponse : TokensResponse - >(config, `${config.apiUrl}/tokens?${urlSearchParams}`, { - signal: options?.signal, - }), - { id: `${getTokens.name}.${urlSearchParams}` } - ) - return response -} - -/** - * Fetch information about a Token - * @param config - The SDK client configuration - * @param chain - Id or key of the chain that contains the token - * @param token - Address or symbol of the token on the requested chain - * @param options - Request options - * @throws {LiFiError} - Throws a LiFiError if request fails - * @returns Token information - */ -export const getToken = async ( - config: SDKBaseConfig, - chain: ChainKey | ChainId, - token: string, - options?: RequestOptions -): Promise => { - if (!chain) { - throw new SDKError( - new ValidationError('Required parameter "chain" is missing.') - ) - } - if (!token) { - throw new SDKError( - new ValidationError('Required parameter "token" is missing.') - ) - } - return await request( - config, - `${config.apiUrl}/token?${new URLSearchParams({ - chain, - token, - } as Record)}`, - { - signal: options?.signal, - } - ) -} - -/** - * Get the available tools to bridge and swap tokens. - * @param config - The SDK client configuration - * @param params - The configuration of the requested tools - * @param options - Request options - * @returns The tools that are available on the requested chains - */ -export const getTools = async ( - config: SDKBaseConfig, - params?: ToolsRequest, - options?: RequestOptions -): Promise => { - if (params) { - for (const key of Object.keys(params)) { - if (!params[key as keyof ToolsRequest]) { - delete params[key as keyof ToolsRequest] - } - } - } - return await request( - config, - `${config.apiUrl}/tools?${new URLSearchParams( - params as Record - )}`, - { - signal: options?.signal, - } - ) -} - -/** - * Get gas recommendation for a certain chain - * @param config - The SDK client configuration - * @param params - Configuration of the requested gas recommendation. - * @param options - Request options - * @throws {LiFiError} Throws a LiFiError if request fails. - * @returns Gas recommendation response. - */ -export const getGasRecommendation = async ( - config: SDKBaseConfig, - params: GasRecommendationRequest, - options?: RequestOptions -): Promise => { - if (!params.chainId) { - throw new SDKError( - new ValidationError('Required parameter "chainId" is missing.') - ) - } - - const url = new URL(`${config.apiUrl}/gas/suggestion/${params.chainId}`) - if (params.fromChain) { - url.searchParams.append('fromChain', params.fromChain as unknown as string) - } - if (params.fromToken) { - url.searchParams.append('fromToken', params.fromToken) - } - - return await request(config, url.toString(), { - signal: options?.signal, - }) -} - -/** - * Get all the available connections for swap/bridging tokens - * @param config - The SDK client configuration - * @param connectionRequest ConnectionsRequest - * @param options - Request options - * @returns ConnectionsResponse - */ -export const getConnections = async ( - config: SDKBaseConfig, - connectionRequest: ConnectionsRequest, - options?: RequestOptions -): Promise => { - const url = new URL(`${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 = [ - '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(config, url, options) -} - -export const getTransactionHistory = async ( - config: SDKBaseConfig, - { wallet, status, fromTimestamp, toTimestamp }: TransactionAnalyticsRequest, - options?: RequestOptions -): Promise => { - if (!wallet) { - throw new SDKError( - new ValidationError('Required parameter "wallet" is missing.') - ) - } - - const url = new URL(`${config.apiUrl}/analytics/transfers`) - - url.searchParams.append('integrator', config.integrator) - url.searchParams.append('wallet', wallet) - - if (status) { - url.searchParams.append('status', status) - } - - if (fromTimestamp) { - url.searchParams.append('fromTimestamp', fromTimestamp.toString()) - } - - if (toTimestamp) { - url.searchParams.append('toTimestamp', toTimestamp.toString()) - } - - return await request(config, url, options) -} diff --git a/src/services/api.unit.handlers.ts b/src/services/api.unit.handlers.ts deleted file mode 100644 index 4bc0ed1d..00000000 --- a/src/services/api.unit.handlers.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { findDefaultToken } from '@lifi/data-types' -import { ChainId, CoinKey } from '@lifi/types' -import { HttpResponse, http } from 'msw' -import { createClient } from '../core/client/createClient.js' - -const client = createClient({ - integrator: 'lifi-sdk', -}) -const config = client.config - -export const handlers = [ - http.post(`${config.apiUrl}/advanced/routes`, async () => { - return HttpResponse.json({}) - }), - http.post(`${config.apiUrl}/advanced/possibilities`, async () => - HttpResponse.json({}) - ), - http.get(`${config.apiUrl}/token`, async () => HttpResponse.json({})), - http.get(`${config.apiUrl}/quote`, async () => HttpResponse.json({})), - http.get(`${config.apiUrl}/status`, async () => HttpResponse.json({})), - http.get(`${config.apiUrl}/chains`, async () => - HttpResponse.json({ chains: [{ id: 1 }] }) - ), - http.get(`${config.apiUrl}/tools`, async () => - HttpResponse.json({ bridges: [], exchanges: [] }) - ), - http.get(`${config.apiUrl}/tokens`, async () => - HttpResponse.json({ - tokens: { - [ChainId.ETH]: [findDefaultToken(CoinKey.ETH, ChainId.ETH)], - }, - }) - ), - http.post(`${config.apiUrl}/advanced/stepTransaction`, async () => - HttpResponse.json({}) - ), - http.get(`${config.apiUrl}/gas/suggestion/${ChainId.OPT}`, async () => - HttpResponse.json({}) - ), -] diff --git a/src/services/api.unit.spec.ts b/src/services/api.unit.spec.ts deleted file mode 100644 index 9fe94ac7..00000000 --- a/src/services/api.unit.spec.ts +++ /dev/null @@ -1,631 +0,0 @@ -import { findDefaultToken } from '@lifi/data-types' -import type { - Action, - ConnectionsRequest, - Estimate, - LiFiStep, - RoutesRequest, - StepTool, - Token, - TransactionAnalyticsRequest, -} from '@lifi/types' -import { ChainId, CoinKey } from '@lifi/types' -import { HttpResponse, http } from 'msw' -import { setupServer } from 'msw/node' -import { - afterAll, - afterEach, - beforeAll, - beforeEach, - describe, - expect, - it, - vi, -} from 'vitest' -import { createClient } from '../core/client/createClient.js' -import { ValidationError } from '../errors/errors.js' -import { SDKError } from '../errors/SDKError.js' -import * as request from '../request.js' -import { requestSettings } from '../request.js' -import * as ApiService from './api.js' -import { handlers } from './api.unit.handlers.js' - -const client = createClient({ - integrator: 'lifi-sdk', -}) -const config = client.config - -const mockedFetch = vi.spyOn(request, 'request') - -describe('ApiService', () => { - const server = setupServer(...handlers) - beforeAll(() => { - server.listen({ - onUnhandledRequest: 'warn', - }) - requestSettings.retries = 0 - // server.use(...handlers) - }) - beforeEach(() => { - vi.clearAllMocks() - }) - afterEach(() => server.resetHandlers()) - afterAll(() => { - requestSettings.retries = 1 - server.close() - }) - - describe('getRoutes', () => { - const getRoutesRequest = ({ - fromChainId = ChainId.BSC, - fromAmount = '10000000000000', - fromTokenAddress = findDefaultToken(CoinKey.USDC, ChainId.BSC).address, - toChainId = ChainId.DAI, - toTokenAddress = findDefaultToken(CoinKey.USDC, ChainId.DAI).address, - options = { slippage: 0.03 }, - }: { - fromChainId?: ChainId - fromAmount?: string - fromTokenAddress?: string - toChainId?: ChainId - toTokenAddress?: string - options?: { slippage: number } - }): RoutesRequest => ({ - fromChainId, - fromAmount, - fromTokenAddress, - toChainId, - toTokenAddress, - options, - }) - - describe('user input is invalid', () => { - it('should throw Error because of invalid fromChainId type', async () => { - const request = getRoutesRequest({ - fromChainId: 'xxx' as unknown as ChainId, - }) - - await expect(ApiService.getRoutes(config, request)).rejects.toThrow( - 'Invalid routes request.' - ) - expect(mockedFetch).toHaveBeenCalledTimes(0) - }) - - it('should throw Error because of invalid fromAmount type', async () => { - const request = getRoutesRequest({ - fromAmount: 10000000000000 as unknown as string, - }) - - await expect(ApiService.getRoutes(config, request)).rejects.toThrow( - 'Invalid routes request.' - ) - expect(mockedFetch).toHaveBeenCalledTimes(0) - }) - - it('should throw Error because of invalid fromTokenAddress type', async () => { - const request = getRoutesRequest({ - fromTokenAddress: 1234 as unknown as string, - }) - - await expect(ApiService.getRoutes(config, request)).rejects.toThrow( - 'Invalid routes request.' - ) - expect(mockedFetch).toHaveBeenCalledTimes(0) - }) - - it('should throw Error because of invalid toChainId type', async () => { - const request = getRoutesRequest({ - toChainId: 'xxx' as unknown as ChainId, - }) - - await expect(ApiService.getRoutes(config, request)).rejects.toThrow( - 'Invalid routes request.' - ) - expect(mockedFetch).toHaveBeenCalledTimes(0) - }) - - it('should throw Error because of invalid toTokenAddress type', async () => { - const request = getRoutesRequest({ toTokenAddress: '' }) - - await expect(ApiService.getRoutes(config, request)).rejects.toThrow( - 'Invalid routes request.' - ) - expect(mockedFetch).toHaveBeenCalledTimes(0) - }) - - it('should throw Error because of invalid options type', async () => { - const request = getRoutesRequest({ - options: { slippage: 'not a number' as unknown as number }, - }) - - await expect(ApiService.getRoutes(config, request)).rejects.toThrow( - 'Invalid routes request.' - ) - expect(mockedFetch).toHaveBeenCalledTimes(0) - }) - }) - - describe('user input is valid', () => { - describe('and the backend call is successful', () => { - it('call the server once', async () => { - const request = getRoutesRequest({}) - await ApiService.getRoutes(config, request) - expect(mockedFetch).toHaveBeenCalledTimes(1) - }) - }) - }) - }) - - describe('getToken', () => { - describe('user input is invalid', () => { - it('throw an error', async () => { - await expect( - ApiService.getToken(config, undefined as unknown as ChainId, 'DAI') - ).rejects.toThrowError( - new SDKError( - new ValidationError('Required parameter "chain" is missing.') - ) - ) - expect(mockedFetch).toHaveBeenCalledTimes(0) - - await expect( - ApiService.getToken( - config, - ChainId.ETH, - undefined as unknown as string - ) - ).rejects.toThrowError( - new SDKError( - new ValidationError('Required parameter "token" is missing.') - ) - ) - expect(mockedFetch).toHaveBeenCalledTimes(0) - }) - }) - - describe('user input is valid', () => { - describe('and the backend call is successful', () => { - it('call the server once', async () => { - await ApiService.getToken(config, ChainId.DAI, 'DAI') - - expect(mockedFetch).toHaveBeenCalledTimes(1) - }) - }) - }) - }) - - describe('getQuote', () => { - const fromChain = ChainId.DAI - const fromToken = 'DAI' - const fromAddress = 'Some wallet address' - const fromAmount = '1000' - const toChain = ChainId.POL - const toToken = 'MATIC' - const toAmount = '1000' - - describe('user input is invalid', () => { - it('throw an error', async () => { - await expect( - ApiService.getQuote(config, { - fromChain: undefined as unknown as ChainId, - fromToken, - fromAddress, - fromAmount, - toChain, - toToken, - }) - ).rejects.toThrowError( - new SDKError( - new ValidationError('Required parameter "fromChain" is missing.') - ) - ) - - await expect( - ApiService.getQuote(config, { - fromChain, - fromToken: undefined as unknown as string, - fromAddress, - fromAmount, - toChain, - toToken, - }) - ).rejects.toThrowError( - new SDKError( - new ValidationError('Required parameter "fromToken" is missing.') - ) - ) - - await expect( - ApiService.getQuote(config, { - fromChain, - fromToken, - fromAddress: undefined as unknown as string, - fromAmount, - toChain, - toToken, - }) - ).rejects.toThrowError( - new SDKError( - new ValidationError('Required parameter "fromAddress" is missing.') - ) - ) - - await expect( - ApiService.getQuote(config, { - fromChain, - fromToken, - fromAddress, - fromAmount: undefined as unknown as string, - toChain, - toToken, - }) - ).rejects.toThrowError( - new SDKError( - new ValidationError( - 'Required parameter "fromAmount" or "toAmount" is missing.' - ) - ) - ) - - await expect( - ApiService.getQuote(config, { - fromChain, - fromToken, - fromAddress, - fromAmount, - toChain, - toToken, - toAmount, - } as any) - ).rejects.toThrowError( - new SDKError( - new ValidationError( - 'Cannot provide both "fromAmount" and "toAmount" parameters.' - ) - ) - ) - - await expect( - ApiService.getQuote(config, { - fromChain, - fromToken, - fromAddress, - fromAmount, - toChain: undefined as unknown as ChainId, - toToken, - }) - ).rejects.toThrowError( - new SDKError( - new ValidationError('Required parameter "toChain" is missing.') - ) - ) - - await expect( - ApiService.getQuote(config, { - fromChain, - fromToken, - fromAddress, - fromAmount, - toChain, - toToken: undefined as unknown as string, - }) - ).rejects.toThrowError( - new SDKError( - new ValidationError('Required parameter "toToken" is missing.') - ) - ) - - expect(mockedFetch).toHaveBeenCalledTimes(0) - }) - }) - - describe('user input is valid', () => { - describe('and the backend call is successful', () => { - it('call the server once', async () => { - await ApiService.getQuote(config, { - fromChain, - fromToken, - fromAddress, - fromAmount, - toChain, - toToken, - }) - - expect(mockedFetch).toHaveBeenCalledTimes(1) - }) - }) - }) - }) - - describe('getStatus', () => { - const fromChain = ChainId.DAI - const toChain = ChainId.POL - const txHash = 'some tx hash' - const bridge = 'some bridge tool' - - describe('user input is invalid', () => { - it('throw an error', async () => { - await expect( - ApiService.getStatus(config, { - bridge, - fromChain, - toChain, - txHash: undefined as unknown as string, - }) - ).rejects.toThrowError( - new SDKError( - new ValidationError('Required parameter "txHash" is missing.') - ) - ) - - expect(mockedFetch).toHaveBeenCalledTimes(0) - }) - }) - - describe('user input is valid', () => { - describe('and the backend call is successful', () => { - it('call the server once', async () => { - await ApiService.getStatus(config, { - bridge, - fromChain, - toChain, - txHash, - }) - expect(mockedFetch).toHaveBeenCalledTimes(1) - }) - }) - }) - }) - - describe('getChains', () => { - describe('and the backend call is successful', () => { - it('call the server once', async () => { - const chains = await ApiService.getChains(config) - - expect(chains[0]?.id).toEqual(1) - expect(mockedFetch).toHaveBeenCalledTimes(1) - }) - }) - }) - - describe('getTools', () => { - describe('and the backend succeeds', () => { - it('returns the tools', async () => { - const tools = await ApiService.getTools(config, { - chains: [ChainId.ETH, ChainId.POL], - }) - - expect(tools).toBeDefined() - expect(tools.bridges).toBeDefined() - expect(tools.exchanges).toBeDefined() - }) - }) - }) - - describe('getTokens', () => { - it('return the tokens', async () => { - const result = await ApiService.getTokens(config, { - chains: [ChainId.ETH, ChainId.POL], - }) - expect(result).toBeDefined() - expect(result.tokens[ChainId.ETH]).toBeDefined() - }) - }) - - describe('getStepTransaction', () => { - const getAction = ({ - fromChainId = ChainId.BSC, - fromAmount = '10000000000000', - fromToken = findDefaultToken(CoinKey.USDC, ChainId.BSC), - fromAddress = 'some from address', // we don't validate the format of addresses atm - toChainId = ChainId.DAI, - toToken = findDefaultToken(CoinKey.USDC, ChainId.DAI), - toAddress = 'some to address', - slippage = 0.03, - }): Action => ({ - fromChainId, - fromAmount, - fromToken: fromToken as Token, - fromAddress, - toChainId, - toToken: toToken as Token, - 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', - }): Estimate => ({ - fromAmount, - toAmount, - toAmountMin, - approvalAddress, - executionDuration, - tool, - }) - - const getStep = ({ - id = 'some random id', - type = 'lifi', - tool = 'some swap tool', - action = getAction({}), - estimate = getEstimate({}), - }: { - id?: string - type?: 'lifi' - tool?: StepTool - action?: Action - estimate?: Estimate - }): LiFiStep => ({ - id, - type, - tool, - toolDetails: { - key: tool, - name: tool, - logoURI: '', - }, - action, - estimate, - includedSteps: [], - }) - - describe('with a swap step', () => { - // While the validation fails for some users we should not enforce it - describe.skip('user input is invalid', () => { - it('should throw Error because of invalid id', async () => { - const step = getStep({ id: null as unknown as string }) - - await expect( - ApiService.getStepTransaction(config, step) - ).rejects.toThrow('Invalid step.') - expect(mockedFetch).toHaveBeenCalledTimes(0) - }) - - it('should throw Error because of invalid type', async () => { - const step = getStep({ type: 42 as unknown as 'lifi' }) - - await expect( - ApiService.getStepTransaction(config, step) - ).rejects.toThrow('Invalid Step') - expect(mockedFetch).toHaveBeenCalledTimes(0) - }) - - it('should throw Error because of invalid tool', async () => { - const step = getStep({ tool: null as unknown as StepTool }) - - await expect( - ApiService.getStepTransaction(config, step) - ).rejects.toThrow('Invalid step.') - expect(mockedFetch).toHaveBeenCalledTimes(0) - }) - - // more indepth checks for the action type should be done once we have real schema validation - it('should throw Error because of invalid action', async () => { - const step = getStep({ action: 'xxx' as unknown as Action }) - - await expect( - ApiService.getStepTransaction(config, step) - ).rejects.toThrow('Invalid step.') - expect(mockedFetch).toHaveBeenCalledTimes(0) - }) - - // more indepth checks for the estimate type should be done once we have real schema validation - it('should throw Error because of invalid estimate', async () => { - const step = getStep({ - estimate: 'Is this really an estimate?' as unknown as Estimate, - }) - - await expect( - ApiService.getStepTransaction(config, step) - ).rejects.toThrow('Invalid step.') - expect(mockedFetch).toHaveBeenCalledTimes(0) - }) - }) - - describe('user input is valid', () => { - describe('and the backend call is successful', () => { - it('call the server once', async () => { - const step = getStep({}) - - await ApiService.getStepTransaction(config, step) - expect(mockedFetch).toHaveBeenCalledTimes(1) - }) - }) - }) - }) - }) - - describe('getGasRecommendation', () => { - describe('user input is invalid', () => { - it('throw an error', async () => { - await expect( - ApiService.getGasRecommendation(config, { - chainId: undefined as unknown as number, - }) - ).rejects.toThrowError( - new SDKError( - new ValidationError('Required parameter "chainId" is missing.') - ) - ) - expect(mockedFetch).toHaveBeenCalledTimes(0) - }) - }) - - describe('user input is valid', () => { - describe('and the backend call is successful', () => { - it('call the server once', async () => { - await ApiService.getGasRecommendation(config, { - chainId: ChainId.OPT, - }) - - expect(mockedFetch).toHaveBeenCalledTimes(1) - }) - }) - }) - }) - describe('getAvailableConnections', () => { - it('returns empty array in response', async () => { - server.use( - http.get(`${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( - ApiService.getConnections(config, connectionRequest) - ).resolves.toEqual({ - connections: [], - }) - - expect((mockedFetch.mock.calls[0][1] as URL).href).toEqual(generatedURL) - expect(mockedFetch).toHaveBeenCalledOnce() - }) - }) - describe('getTransactionHistory', () => { - it('returns empty array in response', async () => { - server.use( - http.get(`${config.apiUrl}/analytics/transfers`, async () => - HttpResponse.json({}) - ) - ) - - const walletAnalyticsRequest: TransactionAnalyticsRequest = { - fromTimestamp: 1696326609361, - toTimestamp: 1696326609362, - wallet: '0x5520abcd', - } - - const generatedURL = - 'https://li.quest/v1/analytics/transfers?integrator=lifi-sdk&wallet=0x5520abcd&fromTimestamp=1696326609361&toTimestamp=1696326609362' - - await expect( - ApiService.getTransactionHistory(config, walletAnalyticsRequest) - ).resolves.toEqual({}) - - expect((mockedFetch.mock.calls[0][1] as URL).href).toEqual(generatedURL) - expect(mockedFetch).toHaveBeenCalledOnce() - }) - }) -}) diff --git a/src/services/balance.ts b/src/services/balance.ts deleted file mode 100644 index ad71c2d8..00000000 --- a/src/services/balance.ts +++ /dev/null @@ -1,162 +0,0 @@ -import type { - GetWalletBalanceExtendedResponse, - RequestOptions, - Token, - TokenAmount, - TokenAmountExtended, - TokenExtended, - WalletTokenExtended, -} from '@lifi/types' -import type { SDKClient, SDKProvider } from '../core/types.js' -import { ValidationError } from '../errors/errors.js' -import { request } from '../request.js' -import { isToken } from '../typeguards.js' - -/** - * Returns the balances of a specific token a wallet holds across all aggregated chains. - * @param walletAddress - A wallet address. - * @param token - A Token object. - * @returns An object containing the token and the amounts on different chains. - * @throws {BaseError} Throws a ValidationError if parameters are invalid. - */ -export const getTokenBalance = async ( - client: SDKClient, - walletAddress: string, - token: Token -): Promise => { - const tokenAmounts = await getTokenBalances(client, walletAddress, [token]) - return tokenAmounts.length ? tokenAmounts[0] : null -} - -/** - * Returns the balances for a list tokens a wallet holds across all aggregated chains. - * @param client - The SDK client - * @param walletAddress - A wallet address. - * @param tokens - A list of Token (or TokenExtended) objects. - * @returns A list of objects containing the tokens and the amounts on different chains. - * @throws {BaseError} Throws a ValidationError if parameters are invalid. - */ -export async function getTokenBalances( - client: SDKClient, - walletAddress: string, - tokens: Token[] -): Promise -export async function getTokenBalances( - client: SDKClient, - walletAddress: string, - tokens: TokenExtended[] -): Promise { - // split by chain - const tokensByChain = tokens.reduce( - (tokens, token) => { - if (!tokens[token.chainId]) { - tokens[token.chainId] = [] - } - tokens[token.chainId].push(token) - return tokens - }, - {} as { [chainId: number]: Token[] | TokenExtended[] } - ) - - const tokenAmountsByChain = await getTokenBalancesByChain( - client, - walletAddress, - tokensByChain - ) - return Object.values(tokenAmountsByChain).flat() -} - -/** - * This method queries the balances of tokens for a specific list of chains for a given wallet. - * @param client - The SDK client - * @param walletAddress - A wallet address. - * @param tokensByChain - A list of token objects organized by chain ids. - * @returns A list of objects containing the tokens and the amounts on different chains organized by the chosen chains. - * @throws {BaseError} Throws a ValidationError if parameters are invalid. - */ -export async function getTokenBalancesByChain( - client: SDKClient, - walletAddress: string, - tokensByChain: { [chainId: number]: Token[] } -): Promise<{ [chainId: number]: TokenAmount[] }> -export async function getTokenBalancesByChain( - client: SDKClient, - walletAddress: string, - tokensByChain: { [chainId: number]: TokenExtended[] } -): Promise<{ [chainId: number]: TokenAmountExtended[] }> { - if (!walletAddress) { - throw new ValidationError('Missing walletAddress.') - } - - const config = client.config - const tokenList = Object.values(tokensByChain).flat() - const invalidTokens = tokenList.filter((token) => !isToken(token)) - if (invalidTokens.length) { - throw new ValidationError('Invalid tokens passed.') - } - - const provider = client.providers.find((provider: SDKProvider) => - provider.isAddress(walletAddress) - ) - if (!provider) { - throw new Error(`SDK Token Provider for ${walletAddress} is not found.`) - } - - const tokenAmountsByChain: { - [chainId: number]: TokenAmount[] | TokenAmountExtended[] - } = {} - const tokenAmountsSettled = await Promise.allSettled( - Object.keys(tokensByChain).map(async (chainIdStr) => { - const chainId = Number.parseInt(chainIdStr, 10) - const chain = await client.getChainById(chainId) - if (provider.type === chain.chainType) { - const tokenAmounts = await provider.getBalance( - client, - walletAddress, - tokensByChain[chainId] - ) - tokenAmountsByChain[chainId] = tokenAmounts - } else { - // if the provider is not the same as the chain type, - // return the tokens as is - tokenAmountsByChain[chainId] = tokensByChain[chainId] - } - }) - ) - if (config.debug) { - for (const result of tokenAmountsSettled) { - if (result.status === 'rejected') { - console.warn("Couldn't fetch token balance.", result.reason) - } - } - } - return tokenAmountsByChain -} - -/** - * Returns the balances of tokens a wallet holds across EVM chains. - * @param client - The SDK client - * @param walletAddress - A wallet address. - * @param options - Optional request options. - * @returns An object containing the tokens and the amounts organized by chain ids. - * @throws {BaseError} Throws a ValidationError if parameters are invalid. - */ -export const getWalletBalances = async ( - client: SDKClient, - walletAddress: string, - options?: RequestOptions -): Promise> => { - if (!walletAddress) { - throw new ValidationError('Missing walletAddress.') - } - - const response = await request( - client.config, - `${client.config.apiUrl}/wallets/${walletAddress}/balances?extended=true`, - { - signal: options?.signal, - } - ) - - return (response?.balances || {}) as Record -} diff --git a/src/services/balance.unit.spec.ts b/src/services/balance.unit.spec.ts deleted file mode 100644 index 23d366b2..00000000 --- a/src/services/balance.unit.spec.ts +++ /dev/null @@ -1,290 +0,0 @@ -import { findDefaultToken } from '@lifi/data-types' -import type { Token, WalletTokenExtended } from '@lifi/types' -import { ChainId, CoinKey } from '@lifi/types' -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { createClient } from '../core/client/createClient.js' -import { EVM } from '../core/EVM/EVM.js' -import { Solana } from '../core/Solana/Solana.js' -import { Sui } from '../core/Sui/Sui.js' -import { UTXO } from '../core/UTXO/UTXO.js' -import * as balance from './balance.js' - -const client = createClient({ - integrator: 'lifi-sdk', -}) -client.setProviders([EVM(), UTXO(), Solana(), Sui()]) - -const mockedGetTokenBalance = vi.spyOn(balance, 'getTokenBalance') -const mockedGetTokenBalances = vi.spyOn(balance, 'getTokenBalances') -const mockedGetTokenBalancesForChains = vi.spyOn( - balance, - 'getTokenBalancesByChain' -) -const mockedGetWalletBalances = vi.spyOn(balance, 'getWalletBalances') - -describe('Balance service tests', () => { - beforeEach(() => { - vi.clearAllMocks() - }) - const SOME_TOKEN = { - ...findDefaultToken(CoinKey.USDC, ChainId.DAI), - priceUSD: '', - } - const SOME_WALLET_ADDRESS = 'some wallet address' - - describe('getTokenBalance', () => { - describe('user input is invalid', () => { - it('should throw Error because of missing walletAddress', async () => { - await expect( - balance.getTokenBalance(client, '', SOME_TOKEN) - ).rejects.toThrow('Missing walletAddress.') - }) - - it('should throw Error because of invalid token', async () => { - await expect( - balance.getTokenBalance(client, SOME_WALLET_ADDRESS, { - address: 'some wrong stuff', - chainId: 'not a chain Id', - } as unknown as Token) - ).rejects.toThrow('Invalid tokens passed.') - }) - }) - - describe('user input is valid', () => { - it('should call the balance service', async () => { - const balanceResponse = { - ...SOME_TOKEN, - amount: 123n, - blockNumber: 1n, - } - - mockedGetTokenBalance.mockReturnValue(Promise.resolve(balanceResponse)) - - const result = await balance.getTokenBalance( - client, - SOME_WALLET_ADDRESS, - SOME_TOKEN - ) - - expect(mockedGetTokenBalance).toHaveBeenCalledTimes(1) - expect(result).toEqual(balanceResponse) - }) - }) - }) - - describe('getTokenBalances', () => { - describe('user input is invalid', () => { - it('should throw Error because of missing walletAddress', async () => { - await expect( - balance.getTokenBalances(client, '', [SOME_TOKEN]) - ).rejects.toThrow('Missing walletAddress.') - }) - - it('should throw Error because of an invalid token', async () => { - await expect( - balance.getTokenBalances(client, SOME_WALLET_ADDRESS, [ - SOME_TOKEN, - { not: 'a token' } as unknown as Token, - ]) - ).rejects.toThrow('Invalid tokens passed.') - }) - - it('should return empty token list as it is', async () => { - mockedGetTokenBalances.mockReturnValue(Promise.resolve([])) - const result = await balance.getTokenBalances( - client, - SOME_WALLET_ADDRESS, - [] - ) - expect(result).toEqual([]) - expect(mockedGetTokenBalances).toHaveBeenCalledTimes(1) - }) - }) - - describe('user input is valid', () => { - it('should call the balance service', async () => { - const balanceResponse = [ - { - ...SOME_TOKEN, - amount: 123n, - blockNumber: 1n, - }, - ] - - mockedGetTokenBalances.mockReturnValue(Promise.resolve(balanceResponse)) - - const result = await balance.getTokenBalances( - client, - SOME_WALLET_ADDRESS, - [SOME_TOKEN] - ) - - expect(mockedGetTokenBalances).toHaveBeenCalledTimes(1) - expect(result).toEqual(balanceResponse) - }) - }) - }) - - describe('getTokenBalancesForChains', () => { - describe('user input is invalid', () => { - it('should throw Error because of missing walletAddress', async () => { - await expect( - balance.getTokenBalancesByChain(client, '', { - [ChainId.DAI]: [SOME_TOKEN], - }) - ).rejects.toThrow('Missing walletAddress.') - }) - - it('should throw Error because of an invalid token', async () => { - await expect( - balance.getTokenBalancesByChain(client, SOME_WALLET_ADDRESS, { - [ChainId.DAI]: [{ not: 'a token' } as unknown as Token], - }) - ).rejects.toThrow('Invalid tokens passed.') - }) - - it('should return empty token list as it is', async () => { - mockedGetTokenBalancesForChains.mockReturnValue(Promise.resolve([])) - - const result = await balance.getTokenBalancesByChain( - client, - SOME_WALLET_ADDRESS, - { - [ChainId.DAI]: [], - } - ) - - expect(result).toEqual([]) - expect(mockedGetTokenBalancesForChains).toHaveBeenCalledTimes(1) - }) - }) - - describe('user input is valid', () => { - it('should call the balance service', async () => { - const balanceResponse = { - [ChainId.DAI]: [ - { - ...SOME_TOKEN, - amount: 123n, - blockNumber: 1n, - }, - ], - } - - mockedGetTokenBalancesForChains.mockReturnValue( - Promise.resolve(balanceResponse) - ) - - const result = await balance.getTokenBalancesByChain( - client, - SOME_WALLET_ADDRESS, - { - [ChainId.DAI]: [SOME_TOKEN], - } - ) - - expect(mockedGetTokenBalancesForChains).toHaveBeenCalledTimes(1) - expect(result).toEqual(balanceResponse) - }) - }) - - describe('provider is not the same as the chain type', () => { - it('should return the tokens as is', async () => { - const balanceResponse = { - [ChainId.DAI]: [SOME_TOKEN], - } - - mockedGetTokenBalancesForChains.mockReturnValue( - Promise.resolve(balanceResponse) - ) - - const result = await balance.getTokenBalancesByChain( - client, - SOME_WALLET_ADDRESS, - { - [ChainId.DAI]: [SOME_TOKEN], - } - ) - - expect(mockedGetTokenBalancesForChains).toHaveBeenCalledTimes(1) - expect(result).toEqual(balanceResponse) - }) - }) - }) - - describe('getWalletBalances', () => { - describe('user input is invalid', () => { - it('should throw Error because of missing walletAddress', async () => { - await expect(balance.getWalletBalances(client, '')).rejects.toThrow( - 'Missing walletAddress.' - ) - }) - }) - - describe('user input is valid', () => { - it('should call the balance service without options', async () => { - const balanceResponse: Record = { - [ChainId.DAI]: [ - { - ...SOME_TOKEN, - amount: '123', - marketCapUSD: 1000000, - volumeUSD24H: 50000, - fdvUSD: 2000000, - }, - ], - } - - mockedGetWalletBalances.mockReturnValue( - Promise.resolve(balanceResponse) - ) - - const result = await balance.getWalletBalances( - client, - SOME_WALLET_ADDRESS - ) - - expect(mockedGetWalletBalances).toHaveBeenCalledTimes(1) - expect(mockedGetWalletBalances).toHaveBeenCalledWith( - client, - SOME_WALLET_ADDRESS - ) - expect(result).toEqual(balanceResponse) - }) - - it('should call the balance service with options', async () => { - const balanceResponse: Record = { - [ChainId.DAI]: [ - { - ...SOME_TOKEN, - amount: '123', - marketCapUSD: 1000000, - volumeUSD24H: 50000, - fdvUSD: 2000000, - }, - ], - } - - const options = { signal: new AbortController().signal } - - mockedGetWalletBalances.mockReturnValue( - Promise.resolve(balanceResponse) - ) - - const result = await balance.getWalletBalances( - client, - SOME_WALLET_ADDRESS, - options - ) - - expect(mockedGetWalletBalances).toHaveBeenCalledTimes(1) - expect(mockedGetWalletBalances).toHaveBeenCalledWith( - client, - SOME_WALLET_ADDRESS, - options - ) - expect(result).toEqual(balanceResponse) - }) - }) - }) -}) diff --git a/tests/fixtures.ts b/src/tests/fixtures.ts similarity index 98% rename from tests/fixtures.ts rename to src/tests/fixtures.ts index 4718bea9..1fa8938d 100644 --- a/tests/fixtures.ts +++ b/src/tests/fixtures.ts @@ -3,7 +3,7 @@ import { findDefaultToken } from '@lifi/data-types' import type { LiFiStep, Route, Token } from '@lifi/types' import { ChainId, CoinKey } from '@lifi/types' -import type { LiFiStepExtended } from '../src/index.js' +import type { LiFiStepExtended } from '../index.js' const SOME_TOKEN: Token = { ...findDefaultToken(CoinKey.USDC, ChainId.DAI), diff --git a/src/services/types.ts b/src/types/actions.ts similarity index 100% rename from src/services/types.ts rename to src/types/actions.ts diff --git a/src/core/types.ts b/src/types/core.ts similarity index 100% rename from src/core/types.ts rename to src/types/core.ts index 7635fde8..b20c768b 100644 --- a/src/core/types.ts +++ b/src/types/core.ts @@ -83,6 +83,16 @@ export interface StepExecutor { ): Promise } +export interface RouteExecutionData { + route: Route + executors: StepExecutor[] + executionOptions?: ExecutionOptions +} + +export type RouteExecutionDataDictionary = Partial< + Record +> + export interface RouteExtended extends Omit { steps: LiFiStepExtended[] } @@ -108,16 +118,6 @@ export type TransactionParameters = { maxPriorityFeePerGas?: bigint } -export interface RouteExecutionData { - route: Route - executors: StepExecutor[] - executionOptions?: ExecutionOptions -} - -export type RouteExecutionDataDictionary = Partial< - Record -> - export type RouteExecutionDictionary = Partial>> export type UpdateRouteHook = (updatedRoute: RouteExtended) => void diff --git a/src/utils/getTransactionMessage.ts b/src/utils/getTransactionMessage.ts index 11fd00fa..e1717d4a 100644 --- a/src/utils/getTransactionMessage.ts +++ b/src/utils/getTransactionMessage.ts @@ -1,5 +1,5 @@ import type { LiFiStep } from '@lifi/types' -import type { SDKClient } from '../core/types.js' +import type { SDKClient } from '../types/core.js' export const getTransactionFailedMessage = async ( client: SDKClient,