@lifi/sdk 4.0.0-alpha.1 → 4.0.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/LICENSE +201 -165
  2. package/dist/cjs/version.d.ts +1 -1
  3. package/dist/cjs/version.js +1 -1
  4. package/dist/esm/version.d.ts +1 -1
  5. package/dist/esm/version.js +1 -1
  6. package/dist/types/version.d.ts +1 -1
  7. package/package.json +16 -1
  8. package/src/version.ts +1 -1
  9. package/CHANGELOG.md +0 -1272
  10. package/package.json.tmp +0 -103
  11. package/src/actions/actions.unit.handlers.ts +0 -78
  12. package/src/actions/getChains.unit.spec.ts +0 -19
  13. package/src/actions/getConnections.unit.spec.ts +0 -45
  14. package/src/actions/getContractCallsQuote.unit.spec.ts +0 -323
  15. package/src/actions/getGasRecommendation.unit.spec.ts +0 -40
  16. package/src/actions/getNameServiceAddress.unit.spec.ts +0 -169
  17. package/src/actions/getQuote.int.spec.ts +0 -18
  18. package/src/actions/getQuote.unit.spec.ts +0 -154
  19. package/src/actions/getRelayedTransactionStatus.unit.spec.ts +0 -243
  20. package/src/actions/getRelayerQuote.unit.spec.ts +0 -220
  21. package/src/actions/getRoutes.unit.spec.ts +0 -112
  22. package/src/actions/getStatus.unit.spec.ts +0 -53
  23. package/src/actions/getStepTransaction.unit.spec.ts +0 -140
  24. package/src/actions/getToken.unit.spec.ts +0 -45
  25. package/src/actions/getTokenBalance.unit.spec.ts +0 -61
  26. package/src/actions/getTokenBalances.unit.spec.ts +0 -68
  27. package/src/actions/getTokenBalancesByChain.unit.spec.ts +0 -108
  28. package/src/actions/getTokens.unit.spec.ts +0 -16
  29. package/src/actions/getTools.unit.spec.ts +0 -20
  30. package/src/actions/getTransactionHistory.unit.spec.ts +0 -36
  31. package/src/actions/getWalletBalances.unit.spec.ts +0 -90
  32. package/src/actions/relayTransaction.unit.spec.ts +0 -229
  33. package/src/client/createClient.unit.spec.ts +0 -274
  34. package/src/client/getClientStorage.unit.spec.ts +0 -382
  35. package/src/core/StatusManager.unit.spec.ts +0 -298
  36. package/src/core/execution.unit.handlers.ts +0 -32
  37. package/src/core/execution.unit.mock.ts +0 -252
  38. package/src/core/execution.unit.spec.ts +0 -86
  39. package/src/core/stepComparison.unit.spec.ts +0 -89
  40. package/src/errors/SDKError.unit.spec.ts +0 -160
  41. package/src/errors/baseError.unit.spec.ts +0 -22
  42. package/src/errors/httpError.unit.spec.ts +0 -125
  43. package/src/errors/utils/baseErrorRootCause.unit.spec.ts +0 -89
  44. package/src/errors/utils/rootCause.unit.spec.ts +0 -36
  45. package/src/utils/checkPackageUpdates.unit.spec.ts +0 -71
  46. package/src/utils/convertQuoteToRoute.unit.spec.ts +0 -56
  47. package/src/utils/fetchTxErrorDetails.unit.spec.ts +0 -42
  48. package/src/utils/getTransactionMessage.unit.spec.ts +0 -38
  49. package/src/utils/isRoutesRequest.unit.spec.ts +0 -46
  50. package/src/utils/isStep.unit.spec.ts +0 -55
  51. package/src/utils/isToken.unit.spec.ts +0 -49
  52. package/src/utils/request.unit.spec.ts +0 -159
  53. package/src/utils/sleep.unit.spec.ts +0 -17
  54. package/src/utils/waitForResult.unit.spec.ts +0 -75
  55. package/src/utils/withDedupe.unit.spec.ts +0 -26
  56. package/tsconfig.json +0 -18
@@ -1,274 +0,0 @@
1
- import { ChainId, ChainType } from '@lifi/types'
2
- import { describe, expect, it, vi } from 'vitest'
3
- import type { SDKConfig } from '../types/core.js'
4
- import { createClient } from './createClient.js'
5
-
6
- // Mock providers locally
7
- const createMockProvider = (type: ChainType) => ({
8
- type,
9
- resolveAddress: vi.fn(),
10
- isAddress: vi.fn(),
11
- getBalance: vi.fn(),
12
- getStepExecutor: vi.fn(),
13
- setOptions: vi.fn(),
14
- })
15
-
16
- const EVM = (_options?: any) => createMockProvider('EVM' as ChainType)
17
- const Solana = (_options?: any) => createMockProvider('SVM' as ChainType)
18
- const UTXO = (_options?: any) => createMockProvider('UTXO' as ChainType)
19
-
20
- // Mock the version check
21
- vi.mock('../utils/checkPackageUpdates.js', () => ({
22
- checkPackageUpdates: vi.fn(),
23
- }))
24
-
25
- // Mock the client storage
26
- vi.mock('./getClientStorage.js', () => ({
27
- getClientStorage: vi.fn(() => ({
28
- getChains: vi.fn().mockResolvedValue([
29
- { id: 1, name: 'Ethereum', type: ChainType.EVM },
30
- { id: 137, name: 'Polygon', type: ChainType.EVM },
31
- ]),
32
- getRpcUrls: vi.fn().mockResolvedValue({
33
- [ChainId.ETH]: ['https://eth-mainnet.alchemyapi.io/v2/test'],
34
- [ChainId.POL]: ['https://polygon-rpc.com'],
35
- }),
36
- })),
37
- }))
38
-
39
- describe('createClient', () => {
40
- describe('basic functionality', () => {
41
- it('should create a client with minimal config', () => {
42
- const client = createClient({
43
- integrator: 'test-app',
44
- })
45
-
46
- expect(client).toBeDefined()
47
- expect(client.config.integrator).toBe('test-app')
48
- expect(client.config.apiUrl).toBe('https://li.quest/v1')
49
- expect(client.config.debug).toBe(false)
50
- expect(client.providers).toEqual([])
51
- })
52
-
53
- it('should create a client with full config', () => {
54
- const config: SDKConfig = {
55
- integrator: 'test-app',
56
- apiKey: 'test-api-key',
57
- apiUrl: 'https://custom-api.com',
58
- userId: 'user-123',
59
- debug: true,
60
- disableVersionCheck: true,
61
- widgetVersion: '1.0.0',
62
- rpcUrls: {
63
- [ChainId.ETH]: ['https://eth-mainnet.alchemyapi.io/v2/test'],
64
- },
65
- }
66
-
67
- const client = createClient(config)
68
-
69
- expect(client.config).toEqual({
70
- integrator: 'test-app',
71
- apiKey: 'test-api-key',
72
- apiUrl: 'https://custom-api.com',
73
- userId: 'user-123',
74
- debug: true,
75
- disableVersionCheck: true,
76
- widgetVersion: '1.0.0',
77
- rpcUrls: {
78
- [ChainId.ETH]: ['https://eth-mainnet.alchemyapi.io/v2/test'],
79
- },
80
- })
81
- })
82
-
83
- it('should throw error when integrator is missing', () => {
84
- expect(() => {
85
- createClient({} as SDKConfig)
86
- }).toThrow(
87
- 'Integrator not found. Please see documentation https://docs.li.fi/integrate-li.fi-js-sdk/set-up-the-sdk'
88
- )
89
- })
90
-
91
- it('should throw error when integrator is empty string', () => {
92
- expect(() => {
93
- createClient({ integrator: '' })
94
- }).toThrow(
95
- 'Integrator not found. Please see documentation https://docs.li.fi/integrate-li.fi-js-sdk/set-up-the-sdk'
96
- )
97
- })
98
- })
99
-
100
- describe('provider management', () => {
101
- it('should handle empty providers list', () => {
102
- const client = createClient({ integrator: 'test-app' })
103
- expect(client.providers).toEqual([])
104
- expect(client.getProvider(ChainType.EVM)).toBeUndefined()
105
- })
106
-
107
- it('should set and get providers', () => {
108
- const client = createClient({ integrator: 'test-app' })
109
- const evmProvider = EVM()
110
- const solanaProvider = Solana()
111
-
112
- client.setProviders([evmProvider, solanaProvider])
113
-
114
- expect(client.providers).toHaveLength(2)
115
- expect(client.getProvider(ChainType.EVM)).toBe(evmProvider)
116
- expect(client.getProvider(ChainType.SVM)).toBe(solanaProvider)
117
- expect(client.getProvider(ChainType.UTXO)).toBeUndefined()
118
- })
119
-
120
- it('should merge providers when setting new ones', () => {
121
- const client = createClient({ integrator: 'test-app' })
122
- const evmProvider = EVM()
123
- const utxoProvider = UTXO()
124
-
125
- client.setProviders([evmProvider])
126
- expect(client.providers).toHaveLength(1)
127
-
128
- client.setProviders([utxoProvider])
129
- expect(client.providers).toHaveLength(2)
130
- expect(client.getProvider(ChainType.EVM)).toBe(evmProvider)
131
- expect(client.getProvider(ChainType.UTXO)).toBe(utxoProvider)
132
- })
133
-
134
- it('should merge providers when setting overlapping types', () => {
135
- const client = createClient({ integrator: 'test-app' })
136
- const evmProvider1 = EVM()
137
- const evmProvider2 = EVM()
138
- const solanaProvider = Solana()
139
-
140
- client.setProviders([evmProvider1])
141
- client.setProviders([evmProvider2, solanaProvider])
142
-
143
- expect(client.providers).toHaveLength(2)
144
- expect(client.getProvider(ChainType.EVM)).toBe(evmProvider2)
145
- expect(client.getProvider(ChainType.SVM)).toBe(solanaProvider)
146
- })
147
- })
148
-
149
- describe('chain management', () => {
150
- it('should get chains from storage', async () => {
151
- const client = createClient({ integrator: 'test-app' })
152
- const chains = await client.getChains()
153
-
154
- expect(chains).toEqual([
155
- { id: 1, name: 'Ethereum', type: ChainType.EVM },
156
- { id: 137, name: 'Polygon', type: ChainType.EVM },
157
- ])
158
- })
159
-
160
- it('should get chain by id', async () => {
161
- const client = createClient({ integrator: 'test-app' })
162
- const chain = await client.getChainById(1)
163
-
164
- expect(chain).toEqual({ id: 1, name: 'Ethereum', type: ChainType.EVM })
165
- })
166
-
167
- it('should throw error when chain not found', async () => {
168
- const client = createClient({ integrator: 'test-app' })
169
-
170
- await expect(client.getChainById(999)).rejects.toThrow(
171
- 'ChainId 999 not found'
172
- )
173
- })
174
- })
175
-
176
- describe('RPC URL management', () => {
177
- it('should get RPC URLs from storage', async () => {
178
- const client = createClient({ integrator: 'test-app' })
179
- const rpcUrls = await client.getRpcUrls()
180
-
181
- expect(rpcUrls).toEqual({
182
- [ChainId.ETH]: ['https://eth-mainnet.alchemyapi.io/v2/test'],
183
- [ChainId.POL]: ['https://polygon-rpc.com'],
184
- })
185
- })
186
-
187
- it('should get RPC URLs by chain id', async () => {
188
- const client = createClient({ integrator: 'test-app' })
189
- const ethUrls = await client.getRpcUrlsByChainId(ChainId.ETH)
190
- const polUrls = await client.getRpcUrlsByChainId(ChainId.POL)
191
-
192
- expect(ethUrls).toEqual(['https://eth-mainnet.alchemyapi.io/v2/test'])
193
- expect(polUrls).toEqual(['https://polygon-rpc.com'])
194
- })
195
-
196
- it('should throw error when RPC URLs not found for chain', async () => {
197
- const client = createClient({ integrator: 'test-app' })
198
-
199
- await expect(client.getRpcUrlsByChainId(999)).rejects.toThrow(
200
- 'RPC URL not found for chainId: 999'
201
- )
202
- })
203
- })
204
-
205
- describe('extend functionality', () => {
206
- it('should extend client with additional functionality', () => {
207
- const client = createClient({ integrator: 'test-app' })
208
-
209
- const extendedClient = (client as any).extend((_baseClient: any) => ({
210
- customMethod: () => 'custom-value',
211
- anotherMethod: (value: string) => `processed-${value}`,
212
- }))
213
-
214
- expect(extendedClient.customMethod()).toBe('custom-value')
215
- expect(extendedClient.anotherMethod('test')).toBe('processed-test')
216
-
217
- // Should preserve original functionality
218
- expect(extendedClient.config.integrator).toBe('test-app')
219
- expect(extendedClient.providers).toEqual([])
220
- })
221
-
222
- it('should allow chaining multiple extensions', () => {
223
- const client = createClient({ integrator: 'test-app' })
224
-
225
- const extendedClient = (client as any)
226
- .extend((_baseClient: any) => ({
227
- firstExtension: () => 'first',
228
- }))
229
- .extend((_baseClient: any) => ({
230
- secondExtension: () => 'second',
231
- }))
232
-
233
- expect(extendedClient.firstExtension()).toBe('first')
234
- expect(extendedClient.secondExtension()).toBe('second')
235
- expect(extendedClient.config.integrator).toBe('test-app')
236
- })
237
-
238
- it('should preserve extend function after extension', () => {
239
- const client = createClient({ integrator: 'test-app' })
240
-
241
- const extendedClient = (client as any).extend((_baseClient: any) => ({
242
- customMethod: () => 'custom-value',
243
- }))
244
-
245
- expect(typeof extendedClient.extend).toBe('function')
246
-
247
- const doubleExtendedClient = extendedClient.extend(
248
- (_baseClient: any) => ({
249
- anotherMethod: () => 'another-value',
250
- })
251
- )
252
-
253
- expect(doubleExtendedClient.customMethod()).toBe('custom-value')
254
- expect(doubleExtendedClient.anotherMethod()).toBe('another-value')
255
- })
256
- })
257
-
258
- describe('error handling', () => {
259
- it('should handle storage errors gracefully', async () => {
260
- // Mock storage to throw error
261
- const { getClientStorage } = await import('./getClientStorage.js')
262
- vi.mocked(getClientStorage).mockReturnValueOnce({
263
- needReset: false,
264
- getChains: vi.fn().mockRejectedValue(new Error('Storage error')),
265
- getRpcUrls: vi.fn().mockRejectedValue(new Error('Storage error')),
266
- })
267
-
268
- const newClient = createClient({ integrator: 'test-app' })
269
-
270
- await expect(newClient.getChains()).rejects.toThrow('Storage error')
271
- await expect(newClient.getRpcUrls()).rejects.toThrow('Storage error')
272
- })
273
- })
274
- })
@@ -1,382 +0,0 @@
1
- import {
2
- ChainId,
3
- ChainKey,
4
- ChainType,
5
- CoinKey,
6
- type ExtendedChain,
7
- } from '@lifi/types'
8
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
9
- import type { RPCUrls, SDKBaseConfig } from '../types/core.js'
10
- import { getClientStorage } from './getClientStorage.js'
11
-
12
- // Mock the dependencies
13
- vi.mock('../actions/getChains.js', () => ({
14
- getChainsFromConfig: vi.fn(),
15
- }))
16
-
17
- vi.mock('../core/utils.js', () => ({
18
- getRpcUrlsFromChains: vi.fn(),
19
- }))
20
-
21
- describe('getClientStorage', () => {
22
- let mockConfig: SDKBaseConfig
23
- let mockChains: ExtendedChain[]
24
- let mockRpcUrls: RPCUrls
25
-
26
- beforeEach(() => {
27
- mockConfig = {
28
- integrator: 'test-app',
29
- apiUrl: 'https://li.quest/v1',
30
- debug: false,
31
- rpcUrls: {
32
- [ChainId.ETH]: ['https://eth-mainnet.alchemyapi.io/v2/test'],
33
- },
34
- }
35
-
36
- mockChains = [
37
- {
38
- id: ChainId.ETH,
39
- name: 'Ethereum',
40
- chainType: ChainType.EVM,
41
- key: ChainKey.ETH,
42
- coin: CoinKey.ETH,
43
- mainnet: true,
44
- nativeToken: {
45
- address: '0x0000000000000000000000000000000000000000',
46
- symbol: 'ETH',
47
- decimals: 18,
48
- name: 'Ethereum',
49
- chainId: ChainId.ETH,
50
- priceUSD: '0',
51
- },
52
- metamask: {
53
- chainId: '0x1',
54
- chainName: 'Ethereum',
55
- nativeCurrency: {
56
- name: 'Ethereum',
57
- symbol: 'ETH',
58
- decimals: 18,
59
- },
60
- rpcUrls: ['https://eth-mainnet.alchemyapi.io/v2/test'],
61
- blockExplorerUrls: ['https://etherscan.io'],
62
- },
63
- },
64
- {
65
- id: ChainId.POL,
66
- name: 'Polygon',
67
- chainType: ChainType.EVM,
68
- key: ChainKey.POL,
69
- coin: CoinKey.MATIC,
70
- mainnet: true,
71
- nativeToken: {
72
- address: '0x0000000000000000000000000000000000000000',
73
- symbol: 'MATIC',
74
- decimals: 18,
75
- name: 'Polygon',
76
- chainId: ChainId.POL,
77
- priceUSD: '0',
78
- },
79
- metamask: {
80
- chainId: '0x89',
81
- chainName: 'Polygon',
82
- nativeCurrency: {
83
- name: 'Polygon',
84
- symbol: 'MATIC',
85
- decimals: 18,
86
- },
87
- rpcUrls: ['https://polygon-rpc.com'],
88
- blockExplorerUrls: ['https://polygonscan.com'],
89
- },
90
- },
91
- ]
92
-
93
- mockRpcUrls = {
94
- [ChainId.ETH]: ['https://eth-mainnet.alchemyapi.io/v2/test'],
95
- [ChainId.POL]: ['https://polygon-rpc.com'],
96
- }
97
-
98
- // Reset mocks
99
- vi.clearAllMocks()
100
- })
101
-
102
- afterEach(() => {
103
- vi.restoreAllMocks()
104
- })
105
-
106
- describe('needReset property', () => {
107
- it('should return true when chainsUpdatedAt is undefined', () => {
108
- const storage = getClientStorage(mockConfig)
109
- expect(storage.needReset).toBe(true) // Because _chainsUpdatedAt is undefined
110
- })
111
-
112
- it('should return true when chains are older than 24 hours', async () => {
113
- const { getChainsFromConfig } = await import('../actions/getChains.js')
114
- const { getRpcUrlsFromChains } = await import('../core/utils.js')
115
-
116
- vi.mocked(getChainsFromConfig).mockResolvedValue(mockChains)
117
- vi.mocked(getRpcUrlsFromChains).mockReturnValue(mockRpcUrls)
118
-
119
- const storage = getClientStorage(mockConfig)
120
-
121
- // First call to getChains sets the timestamp
122
- await storage.getChains()
123
-
124
- // Mock Date.now to return a time 25 hours later
125
- const originalDateNow = Date.now
126
- vi.spyOn(Date, 'now').mockReturnValue(Date.now() + 25 * 60 * 60 * 1000)
127
-
128
- expect(storage.needReset).toBe(true)
129
-
130
- // Restore Date.now
131
- Date.now = originalDateNow
132
- })
133
-
134
- it('should return false when chains are fresh', async () => {
135
- const { getChainsFromConfig } = await import('../actions/getChains.js')
136
- const { getRpcUrlsFromChains } = await import('../core/utils.js')
137
-
138
- vi.mocked(getChainsFromConfig).mockResolvedValue(mockChains)
139
- vi.mocked(getRpcUrlsFromChains).mockReturnValue(mockRpcUrls)
140
-
141
- const storage = getClientStorage(mockConfig)
142
-
143
- // First call to getChains sets the timestamp
144
- await storage.getChains()
145
-
146
- // Should not need reset immediately after
147
- expect(storage.needReset).toBe(false)
148
- })
149
- })
150
-
151
- describe('getChains method', () => {
152
- it('should fetch chains when needReset is true', async () => {
153
- const { getChainsFromConfig } = await import('../actions/getChains.js')
154
- const { getRpcUrlsFromChains } = await import('../core/utils.js')
155
-
156
- vi.mocked(getChainsFromConfig).mockResolvedValue(mockChains)
157
- vi.mocked(getRpcUrlsFromChains).mockReturnValue(mockRpcUrls)
158
-
159
- const storage = getClientStorage(mockConfig)
160
- const chains = await storage.getChains()
161
-
162
- expect(getChainsFromConfig).toHaveBeenCalledWith(mockConfig, {
163
- chainTypes: [
164
- ChainType.EVM,
165
- ChainType.SVM,
166
- ChainType.UTXO,
167
- ChainType.MVM,
168
- ],
169
- })
170
- expect(chains).toEqual(mockChains)
171
- })
172
-
173
- it('should return cached chains when not needReset and chains exist', async () => {
174
- const { getChainsFromConfig } = await import('../actions/getChains.js')
175
- const { getRpcUrlsFromChains } = await import('../core/utils.js')
176
-
177
- vi.mocked(getChainsFromConfig).mockResolvedValue(mockChains)
178
- vi.mocked(getRpcUrlsFromChains).mockReturnValue(mockRpcUrls)
179
-
180
- const storage = getClientStorage(mockConfig)
181
-
182
- // First call fetches chains
183
- const chains1 = await storage.getChains()
184
- expect(getChainsFromConfig).toHaveBeenCalledTimes(1)
185
-
186
- // Second call should return cached chains
187
- const chains2 = await storage.getChains()
188
- expect(getChainsFromConfig).toHaveBeenCalledTimes(1)
189
- expect(chains1).toBe(chains2) // Same reference
190
- })
191
-
192
- it('should handle errors from getChainsFromConfig', async () => {
193
- const { getChainsFromConfig } = await import('../actions/getChains.js')
194
-
195
- const error = new Error('Failed to fetch chains')
196
- vi.mocked(getChainsFromConfig).mockRejectedValue(error)
197
-
198
- const configWithoutRpcUrls = {
199
- ...mockConfig,
200
- rpcUrls: {},
201
- }
202
- const storage = getClientStorage(configWithoutRpcUrls)
203
-
204
- await expect(storage.getChains()).rejects.toThrow(
205
- 'Failed to fetch chains'
206
- )
207
- })
208
- })
209
-
210
- describe('getRpcUrls method', () => {
211
- it('should fetch RPC URLs when needReset is true', async () => {
212
- const { getChainsFromConfig } = await import('../actions/getChains.js')
213
- const { getRpcUrlsFromChains } = await import('../core/utils.js')
214
-
215
- vi.mocked(getChainsFromConfig).mockResolvedValue(mockChains)
216
- vi.mocked(getRpcUrlsFromChains).mockReturnValue(mockRpcUrls)
217
-
218
- const configWithoutRpcUrls = {
219
- ...mockConfig,
220
- rpcUrls: {},
221
- }
222
- const storage = getClientStorage(configWithoutRpcUrls)
223
- const rpcUrls = await storage.getRpcUrls()
224
-
225
- expect(getRpcUrlsFromChains).toHaveBeenCalledWith({}, mockChains, [
226
- ChainId.SOL,
227
- ])
228
- expect(rpcUrls).toEqual(mockRpcUrls)
229
- })
230
-
231
- it('should return cached RPC URLs when not needReset and RPC URLs exist', async () => {
232
- const { getChainsFromConfig } = await import('../actions/getChains.js')
233
- const { getRpcUrlsFromChains } = await import('../core/utils.js')
234
-
235
- vi.mocked(getChainsFromConfig).mockResolvedValue(mockChains)
236
- vi.mocked(getRpcUrlsFromChains).mockReturnValue(mockRpcUrls)
237
-
238
- const storage = getClientStorage(mockConfig)
239
-
240
- // First call getChains to set the timestamp and make needReset false
241
- await storage.getChains()
242
-
243
- // Now getRpcUrls should use existing RPC URLs without calling getRpcUrlsFromChains
244
- const rpcUrls1 = await storage.getRpcUrls()
245
- expect(getRpcUrlsFromChains).toHaveBeenCalledTimes(0) // Should use existing RPC URLs
246
-
247
- // Second call should return cached RPC URLs
248
- const rpcUrls2 = await storage.getRpcUrls()
249
- expect(getRpcUrlsFromChains).toHaveBeenCalledTimes(0)
250
- expect(rpcUrls1).toBe(rpcUrls2) // Same reference
251
- })
252
-
253
- it('should handle errors from getRpcUrlsFromChains', async () => {
254
- const { getChainsFromConfig } = await import('../actions/getChains.js')
255
- const { getRpcUrlsFromChains } = await import('../core/utils.js')
256
-
257
- vi.mocked(getChainsFromConfig).mockResolvedValue(mockChains)
258
- const error = new Error('Failed to process RPC URLs')
259
- vi.mocked(getRpcUrlsFromChains).mockImplementation(() => {
260
- throw error
261
- })
262
-
263
- const configWithoutRpcUrls = {
264
- ...mockConfig,
265
- rpcUrls: {},
266
- }
267
- const storage = getClientStorage(configWithoutRpcUrls)
268
-
269
- await expect(storage.getRpcUrls()).rejects.toThrow(
270
- 'Failed to process RPC URLs'
271
- )
272
- })
273
- })
274
-
275
- describe('caching behavior', () => {
276
- it('should reset cache when needReset becomes true', async () => {
277
- const { getChainsFromConfig } = await import('../actions/getChains.js')
278
- const { getRpcUrlsFromChains } = await import('../core/utils.js')
279
-
280
- vi.mocked(getChainsFromConfig).mockResolvedValue(mockChains)
281
- vi.mocked(getRpcUrlsFromChains).mockReturnValue(mockRpcUrls)
282
-
283
- const configWithoutRpcUrls = {
284
- ...mockConfig,
285
- rpcUrls: {},
286
- }
287
- const storage = getClientStorage(configWithoutRpcUrls)
288
-
289
- // First call
290
- await storage.getChains()
291
- await storage.getRpcUrls()
292
-
293
- // Mock Date.now to return a time 25 hours later
294
- const originalDateNow = Date.now
295
- vi.spyOn(Date, 'now').mockReturnValue(Date.now() + 25 * 60 * 60 * 1000)
296
-
297
- // Should refetch when needReset is true
298
- await storage.getChains()
299
- await storage.getRpcUrls()
300
-
301
- expect(getChainsFromConfig).toHaveBeenCalledTimes(2)
302
- expect(getRpcUrlsFromChains).toHaveBeenCalledTimes(1) // Only called once because we have existing RPC URLs
303
-
304
- // Restore Date.now
305
- Date.now = originalDateNow
306
- })
307
- })
308
-
309
- describe('edge cases', () => {
310
- it('should handle chains without metamask RPC URLs', async () => {
311
- const { getChainsFromConfig } = await import('../actions/getChains.js')
312
- const { getRpcUrlsFromChains } = await import('../core/utils.js')
313
-
314
- const chainsWithoutRpcUrls = [
315
- {
316
- id: ChainId.ETH,
317
- name: 'Ethereum',
318
- key: ChainKey.ETH,
319
- chainType: ChainType.EVM,
320
- coin: CoinKey.ETH,
321
- mainnet: true,
322
- nativeToken: {
323
- address: '0x0000000000000000000000000000000000000000',
324
- symbol: 'ETH',
325
- decimals: 18,
326
- name: 'Ethereum',
327
- chainId: ChainId.ETH,
328
- priceUSD: '0',
329
- },
330
- metamask: {
331
- chainId: '0x1',
332
- chainName: 'Ethereum',
333
- nativeCurrency: {
334
- name: 'Ethereum',
335
- symbol: 'ETH',
336
- decimals: 18,
337
- },
338
- rpcUrls: [],
339
- blockExplorerUrls: ['https://etherscan.io'],
340
- },
341
- },
342
- ]
343
-
344
- vi.mocked(getChainsFromConfig).mockResolvedValue(chainsWithoutRpcUrls)
345
- vi.mocked(getRpcUrlsFromChains).mockReturnValue({})
346
-
347
- const configWithoutRpcUrls = {
348
- ...mockConfig,
349
- rpcUrls: {},
350
- }
351
- const storage = getClientStorage(configWithoutRpcUrls)
352
-
353
- const rpcUrls = await storage.getRpcUrls()
354
-
355
- expect(getRpcUrlsFromChains).toHaveBeenCalledWith(
356
- {},
357
- chainsWithoutRpcUrls,
358
- [ChainId.SOL]
359
- )
360
- expect(rpcUrls).toEqual({})
361
- })
362
-
363
- it('should preserve existing RPC URLs from config', async () => {
364
- const { getChainsFromConfig } = await import('../actions/getChains.js')
365
- const { getRpcUrlsFromChains } = await import('../core/utils.js')
366
-
367
- vi.mocked(getChainsFromConfig).mockResolvedValue(mockChains)
368
- vi.mocked(getRpcUrlsFromChains).mockReturnValue(mockRpcUrls)
369
-
370
- const storage = getClientStorage(mockConfig)
371
-
372
- // First call getChains to set the timestamp and make needReset false
373
- await storage.getChains()
374
-
375
- const rpcUrls = await storage.getRpcUrls()
376
-
377
- // Should not call getRpcUrlsFromChains because we already have RPC URLs
378
- expect(getRpcUrlsFromChains).not.toHaveBeenCalled()
379
- expect(rpcUrls).toEqual(mockConfig.rpcUrls)
380
- })
381
- })
382
- })