@portal-hq/web 3.13.2 → 3.14.0-alpha.0

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 (96) hide show
  1. package/lib/commonjs/index.js +127 -9
  2. package/lib/commonjs/index.test.js +13 -0
  3. package/lib/commonjs/integrations/delegations/index.js +109 -2
  4. package/lib/commonjs/integrations/delegations/index.test.js +171 -0
  5. package/lib/commonjs/integrations/ramps/noah/index.test.js +18 -5
  6. package/lib/commonjs/integrations/trading/index.js +16 -5
  7. package/lib/commonjs/integrations/trading/lifi/index.js +297 -25
  8. package/lib/commonjs/integrations/trading/lifi/lifi.tradeAsset.test.js +360 -0
  9. package/lib/commonjs/integrations/trading/lifi/lifiStatusPoll.js +118 -0
  10. package/lib/commonjs/integrations/trading/lifi/lifiStatusPoll.test.js +66 -0
  11. package/lib/commonjs/integrations/trading/zero-x/index.js +129 -26
  12. package/lib/commonjs/integrations/trading/zero-x/index.test.js +163 -1
  13. package/lib/commonjs/integrations/yield/index.js +18 -4
  14. package/lib/commonjs/integrations/yield/yieldxyz.getValidators.test.js +71 -0
  15. package/lib/commonjs/integrations/yield/yieldxyz.highLevel.test.js +330 -0
  16. package/lib/commonjs/integrations/yield/yieldxyz.js +517 -1
  17. package/lib/commonjs/internal/pollLoop.js +64 -0
  18. package/lib/commonjs/internal/pollLoop.test.js +100 -0
  19. package/lib/commonjs/internal/stripStalePlanningNonce.js +65 -0
  20. package/lib/commonjs/internal/stripStalePlanningNonce.test.js +35 -0
  21. package/lib/commonjs/internal/waitForEvmOrUserOpConfirmation.js +155 -0
  22. package/lib/commonjs/internal/waitForEvmOrUserOpConfirmation.test.js +33 -0
  23. package/lib/commonjs/internal/waitForEvmTxConfirmation.js +104 -0
  24. package/lib/commonjs/internal/waitForSolanaTxConfirmation.js +106 -0
  25. package/lib/commonjs/internal/yieldEvmNetwork.js +60 -0
  26. package/lib/commonjs/mpc/index.js +116 -1
  27. package/lib/commonjs/provider/index.js +17 -0
  28. package/lib/commonjs/shared/trace/index.js +0 -1
  29. package/lib/esm/index.js +127 -9
  30. package/lib/esm/index.test.js +13 -0
  31. package/lib/esm/integrations/delegations/index.js +109 -2
  32. package/lib/esm/integrations/delegations/index.test.js +171 -0
  33. package/lib/esm/integrations/ramps/noah/index.test.js +18 -5
  34. package/lib/esm/integrations/trading/index.js +16 -5
  35. package/lib/esm/integrations/trading/lifi/index.js +292 -25
  36. package/lib/esm/integrations/trading/lifi/lifi.tradeAsset.test.js +332 -0
  37. package/lib/esm/integrations/trading/lifi/lifiStatusPoll.js +113 -0
  38. package/lib/esm/integrations/trading/lifi/lifiStatusPoll.test.js +64 -0
  39. package/lib/esm/integrations/trading/zero-x/index.js +129 -26
  40. package/lib/esm/integrations/trading/zero-x/index.test.js +141 -2
  41. package/lib/esm/integrations/yield/index.js +18 -4
  42. package/lib/esm/integrations/yield/yieldxyz.getValidators.test.js +66 -0
  43. package/lib/esm/integrations/yield/yieldxyz.highLevel.test.js +325 -0
  44. package/lib/esm/integrations/yield/yieldxyz.js +517 -1
  45. package/lib/esm/internal/pollLoop.js +59 -0
  46. package/lib/esm/internal/pollLoop.test.js +98 -0
  47. package/lib/esm/internal/stripStalePlanningNonce.js +61 -0
  48. package/lib/esm/internal/stripStalePlanningNonce.test.js +33 -0
  49. package/lib/esm/internal/waitForEvmOrUserOpConfirmation.js +151 -0
  50. package/lib/esm/internal/waitForEvmOrUserOpConfirmation.test.js +31 -0
  51. package/lib/esm/internal/waitForEvmTxConfirmation.js +100 -0
  52. package/lib/esm/internal/waitForSolanaTxConfirmation.js +102 -0
  53. package/lib/esm/internal/yieldEvmNetwork.js +55 -0
  54. package/lib/esm/mpc/index.js +116 -1
  55. package/lib/esm/provider/index.js +17 -0
  56. package/lib/esm/shared/trace/index.js +0 -1
  57. package/noah-types.d.ts +16 -2
  58. package/package.json +3 -2
  59. package/src/index.test.ts +15 -0
  60. package/src/index.ts +203 -14
  61. package/src/integrations/delegations/index.test.ts +251 -0
  62. package/src/integrations/delegations/index.ts +202 -4
  63. package/src/integrations/ramps/noah/index.test.ts +18 -5
  64. package/src/integrations/trading/index.ts +10 -7
  65. package/src/integrations/trading/lifi/index.ts +388 -28
  66. package/src/integrations/trading/lifi/lifi.tradeAsset.test.ts +436 -0
  67. package/src/integrations/trading/lifi/lifiStatusPoll.test.ts +74 -0
  68. package/src/integrations/trading/lifi/lifiStatusPoll.ts +158 -0
  69. package/src/integrations/trading/zero-x/index.test.ts +297 -1
  70. package/src/integrations/trading/zero-x/index.ts +181 -27
  71. package/src/integrations/yield/index.ts +24 -4
  72. package/src/integrations/yield/yieldxyz.getValidators.test.ts +70 -0
  73. package/src/integrations/yield/yieldxyz.highLevel.test.ts +403 -0
  74. package/src/integrations/yield/yieldxyz.ts +740 -8
  75. package/src/internal/pollLoop.test.ts +109 -0
  76. package/src/internal/pollLoop.ts +87 -0
  77. package/src/internal/stripStalePlanningNonce.test.ts +38 -0
  78. package/src/internal/stripStalePlanningNonce.ts +66 -0
  79. package/src/internal/waitForEvmOrUserOpConfirmation.test.ts +31 -0
  80. package/src/internal/waitForEvmOrUserOpConfirmation.ts +194 -0
  81. package/src/internal/waitForEvmTxConfirmation.ts +155 -0
  82. package/src/internal/waitForSolanaTxConfirmation.ts +135 -0
  83. package/src/internal/yieldEvmNetwork.ts +57 -0
  84. package/src/mpc/index.ts +142 -1
  85. package/src/provider/index.ts +25 -0
  86. package/src/shared/trace/index.ts +0 -1
  87. package/src/shared/types/README.md +6 -0
  88. package/src/shared/types/api.ts +12 -1
  89. package/src/shared/types/common.ts +332 -20
  90. package/src/shared/types/delegations.ts +10 -0
  91. package/src/shared/types/index.ts +1 -0
  92. package/src/shared/types/lifi.ts +82 -0
  93. package/src/shared/types/noah.ts +124 -33
  94. package/src/shared/types/yieldxyz.ts +186 -0
  95. package/src/shared/types/zero-x.ts +66 -0
  96. package/types.d.ts +6 -0
@@ -0,0 +1,332 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
5
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
6
+ return new (P || (P = Promise))(function (resolve, reject) {
7
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
8
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
9
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
10
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
11
+ });
12
+ };
13
+ import LiFi, { normalizeChainToNetwork } from './index';
14
+ import Mpc from '../../../mpc';
15
+ import portalMock from '../../../__mocks/portal/portal';
16
+ import { mockEip155Address, mockHost, mockLifiAction, mockLifiGetRouteStepResponse, mockLifiGetRoutesResponse, mockLifiGetStatusResponse, mockLifiRoute, mockLifiStep, } from '../../../__mocks/constants';
17
+ describe('normalizeChainToNetwork', () => {
18
+ it('maps hex chain ids to eip155', () => {
19
+ expect(normalizeChainToNetwork('0x1')).toBe('eip155:1');
20
+ expect(normalizeChainToNetwork('0xa')).toBe('eip155:10');
21
+ expect(normalizeChainToNetwork('0x89')).toBe('eip155:137');
22
+ });
23
+ it('preserves existing eip155 and numeric decimal strings', () => {
24
+ expect(normalizeChainToNetwork('eip155:42161')).toBe('eip155:42161');
25
+ expect(normalizeChainToNetwork('42161')).toBe('eip155:42161');
26
+ });
27
+ });
28
+ describe('LiFi tradeAsset', () => {
29
+ let mpc;
30
+ let lifi;
31
+ beforeEach(() => {
32
+ jest.clearAllMocks();
33
+ portalMock.host = mockHost;
34
+ mpc = new Mpc({ portal: portalMock });
35
+ lifi = new LiFi({
36
+ mpc,
37
+ signAndSendTransaction: () => __awaiter(void 0, void 0, void 0, function* () { return '0xsent'; }),
38
+ waitForConfirmation: () => __awaiter(void 0, void 0, void 0, function* () { return false; }),
39
+ });
40
+ });
41
+ it('throws when waitForConfirmation returns false and does not poll bridge status', () => __awaiter(void 0, void 0, void 0, function* () {
42
+ jest
43
+ .spyOn(mpc, 'getLifiRoutes')
44
+ .mockResolvedValue(mockLifiGetRoutesResponse);
45
+ jest
46
+ .spyOn(mpc, 'getLifiRouteStep')
47
+ .mockResolvedValue(mockLifiGetRouteStepResponse);
48
+ const pollSpy = jest
49
+ .spyOn(lifi, 'pollStatus')
50
+ .mockRejectedValue(new Error('pollStatus should not run'));
51
+ yield expect(lifi.tradeAsset({
52
+ fromChain: 'eip155:8453',
53
+ toChain: 'eip155:42161',
54
+ fromToken: 'ETH',
55
+ toToken: 'USDC',
56
+ amount: '1000000000000000',
57
+ fromAddress: mockEip155Address,
58
+ })).rejects.toThrow(/on-chain confirmation did not complete/);
59
+ expect(pollSpy).not.toHaveBeenCalled();
60
+ }));
61
+ it('strips nonce from EVM transaction request before sign', () => __awaiter(void 0, void 0, void 0, function* () {
62
+ const swapStep = Object.assign(Object.assign({}, mockLifiStep), { type: 'swap', action: Object.assign(Object.assign({}, mockLifiAction), { fromChainId: 'eip155:1', toChainId: 'eip155:1' }) });
63
+ const route = Object.assign(Object.assign({}, mockLifiRoute), { fromChainId: 'eip155:1', toChainId: 'eip155:1', steps: [swapStep] });
64
+ const routesRes = {
65
+ data: {
66
+ rawResponse: {
67
+ routes: [route],
68
+ unavailableRoutes: { filteredOut: [], failed: [] },
69
+ },
70
+ },
71
+ };
72
+ const stepRes = {
73
+ data: {
74
+ rawResponse: Object.assign(Object.assign({}, swapStep), { transactionRequest: Object.assign(Object.assign({}, swapStep.transactionRequest), { nonce: '0xdeadbeef' }) }),
75
+ },
76
+ };
77
+ jest.spyOn(mpc, 'getLifiRoutes').mockResolvedValue(routesRes);
78
+ jest.spyOn(mpc, 'getLifiRouteStep').mockResolvedValue(stepRes);
79
+ const signer = jest.fn().mockResolvedValue('0xsigned');
80
+ const waiter = jest.fn().mockResolvedValue(true);
81
+ const lifiOk = new LiFi({
82
+ mpc,
83
+ signAndSendTransaction: signer,
84
+ waitForConfirmation: waiter,
85
+ });
86
+ yield lifiOk.tradeAsset({
87
+ fromChain: 'eip155:1',
88
+ toChain: 'eip155:1',
89
+ fromToken: 'ETH',
90
+ toToken: 'USDC',
91
+ amount: '1000000000000000',
92
+ fromAddress: mockEip155Address,
93
+ });
94
+ expect(signer).toHaveBeenCalledTimes(1);
95
+ const sent = signer.mock.calls[0][0];
96
+ expect(sent.nonce).toBeUndefined();
97
+ }));
98
+ it('emits onProgress failed on unexpected errors (outer catch)', () => __awaiter(void 0, void 0, void 0, function* () {
99
+ const onProgress = jest.fn();
100
+ jest.spyOn(mpc, 'getLifiRoutes').mockRejectedValue(new Error('network down'));
101
+ yield expect(lifi.tradeAsset({
102
+ fromChain: 'eip155:8453',
103
+ toChain: 'eip155:42161',
104
+ fromToken: 'ETH',
105
+ toToken: 'USDC',
106
+ amount: '1000000000000000',
107
+ fromAddress: mockEip155Address,
108
+ onProgress,
109
+ }, {
110
+ signAndSendTransaction: () => __awaiter(void 0, void 0, void 0, function* () { return '0x'; }),
111
+ waitForConfirmation: () => __awaiter(void 0, void 0, void 0, function* () { return true; }),
112
+ })).rejects.toThrow('network down');
113
+ expect(onProgress).toHaveBeenCalledWith('failed', expect.objectContaining({ errorMessage: 'network down' }));
114
+ }));
115
+ it('throws when routeIndex is out of bounds', () => __awaiter(void 0, void 0, void 0, function* () {
116
+ jest
117
+ .spyOn(mpc, 'getLifiRoutes')
118
+ .mockResolvedValue(mockLifiGetRoutesResponse);
119
+ const lifiOk = new LiFi({
120
+ mpc,
121
+ signAndSendTransaction: () => __awaiter(void 0, void 0, void 0, function* () { return '0x'; }),
122
+ waitForConfirmation: () => __awaiter(void 0, void 0, void 0, function* () { return true; }),
123
+ });
124
+ yield expect(lifiOk.tradeAsset({
125
+ fromChain: 'eip155:8453',
126
+ toChain: 'eip155:42161',
127
+ fromToken: 'ETH',
128
+ toToken: 'USDC',
129
+ amount: '1000000000000000',
130
+ fromAddress: mockEip155Address,
131
+ routeIndex: 99,
132
+ })).rejects.toThrow(/no route available at index 99/);
133
+ }));
134
+ it('throws when getRouteStep returns error', () => __awaiter(void 0, void 0, void 0, function* () {
135
+ jest
136
+ .spyOn(mpc, 'getLifiRoutes')
137
+ .mockResolvedValue(mockLifiGetRoutesResponse);
138
+ jest.spyOn(mpc, 'getLifiRouteStep').mockResolvedValue({
139
+ error: 'step build failed',
140
+ });
141
+ const lifiOk = new LiFi({
142
+ mpc,
143
+ signAndSendTransaction: () => __awaiter(void 0, void 0, void 0, function* () { return '0x'; }),
144
+ waitForConfirmation: () => __awaiter(void 0, void 0, void 0, function* () { return true; }),
145
+ });
146
+ yield expect(lifiOk.tradeAsset({
147
+ fromChain: 'eip155:8453',
148
+ toChain: 'eip155:42161',
149
+ fromToken: 'ETH',
150
+ toToken: 'USDC',
151
+ amount: '1000000000000000',
152
+ fromAddress: mockEip155Address,
153
+ })).rejects.toThrow('getRouteStep: step build failed');
154
+ }));
155
+ it('cross-chain step calls pollStatus with fast statusPoll options', () => __awaiter(void 0, void 0, void 0, function* () {
156
+ jest
157
+ .spyOn(mpc, 'getLifiRoutes')
158
+ .mockResolvedValue(mockLifiGetRoutesResponse);
159
+ jest
160
+ .spyOn(mpc, 'getLifiRouteStep')
161
+ .mockResolvedValue(mockLifiGetRouteStepResponse);
162
+ jest
163
+ .spyOn(mpc, 'getLifiStatus')
164
+ .mockResolvedValue(mockLifiGetStatusResponse);
165
+ const pollSpy = jest.spyOn(LiFi.prototype, 'pollStatus');
166
+ const signer = jest.fn().mockResolvedValue('0xstep1');
167
+ const lifiOk = new LiFi({
168
+ mpc,
169
+ signAndSendTransaction: signer,
170
+ waitForConfirmation: () => __awaiter(void 0, void 0, void 0, function* () { return true; }),
171
+ });
172
+ yield lifiOk.tradeAsset({
173
+ fromChain: 'eip155:8453',
174
+ toChain: 'eip155:42161',
175
+ fromToken: 'ETH',
176
+ toToken: 'USDC',
177
+ amount: '1000000000000000',
178
+ fromAddress: mockEip155Address,
179
+ statusPoll: {
180
+ initialDelayMs: 0,
181
+ everyMs: 5,
182
+ timeoutMs: 20000,
183
+ },
184
+ });
185
+ expect(signer).toHaveBeenCalled();
186
+ expect(pollSpy).toHaveBeenCalled();
187
+ pollSpy.mockRestore();
188
+ }));
189
+ it('same-chain swap does not call pollStatus', () => __awaiter(void 0, void 0, void 0, function* () {
190
+ const swapStep = Object.assign(Object.assign({}, mockLifiStep), { type: 'swap', action: Object.assign(Object.assign({}, mockLifiAction), { fromChainId: 'eip155:1', toChainId: 'eip155:1' }) });
191
+ const route = Object.assign(Object.assign({}, mockLifiRoute), { fromChainId: 'eip155:1', toChainId: 'eip155:1', steps: [swapStep] });
192
+ const routesRes = {
193
+ data: {
194
+ rawResponse: {
195
+ routes: [route],
196
+ unavailableRoutes: { filteredOut: [], failed: [] },
197
+ },
198
+ },
199
+ };
200
+ const stepRes = {
201
+ data: {
202
+ rawResponse: Object.assign(Object.assign({}, swapStep), { transactionRequest: Object.assign(Object.assign({}, swapStep.transactionRequest), { chainId: 'eip155:1' }) }),
203
+ },
204
+ };
205
+ jest.spyOn(mpc, 'getLifiRoutes').mockResolvedValue(routesRes);
206
+ jest.spyOn(mpc, 'getLifiRouteStep').mockResolvedValue(stepRes);
207
+ const pollSpy = jest.spyOn(LiFi.prototype, 'pollStatus');
208
+ const lifiOk = new LiFi({
209
+ mpc,
210
+ signAndSendTransaction: () => __awaiter(void 0, void 0, void 0, function* () { return '0xswap'; }),
211
+ waitForConfirmation: () => __awaiter(void 0, void 0, void 0, function* () { return true; }),
212
+ });
213
+ yield lifiOk.tradeAsset({
214
+ fromChain: 'eip155:1',
215
+ toChain: 'eip155:1',
216
+ fromToken: 'ETH',
217
+ toToken: 'USDC',
218
+ amount: '1000000000000000',
219
+ fromAddress: mockEip155Address,
220
+ });
221
+ expect(pollSpy).not.toHaveBeenCalled();
222
+ pollSpy.mockRestore();
223
+ }));
224
+ it('throws when signer returns empty hash', () => __awaiter(void 0, void 0, void 0, function* () {
225
+ jest
226
+ .spyOn(mpc, 'getLifiRoutes')
227
+ .mockResolvedValue(mockLifiGetRoutesResponse);
228
+ jest
229
+ .spyOn(mpc, 'getLifiRouteStep')
230
+ .mockResolvedValue(mockLifiGetRouteStepResponse);
231
+ const onProgress = jest.fn();
232
+ const lifiOk = new LiFi({
233
+ mpc,
234
+ signAndSendTransaction: () => __awaiter(void 0, void 0, void 0, function* () { return ' '; }),
235
+ waitForConfirmation: () => __awaiter(void 0, void 0, void 0, function* () { return true; }),
236
+ });
237
+ yield expect(lifiOk.tradeAsset({
238
+ fromChain: 'eip155:8453',
239
+ toChain: 'eip155:42161',
240
+ fromToken: 'ETH',
241
+ toToken: 'USDC',
242
+ amount: '1000000000000000',
243
+ fromAddress: mockEip155Address,
244
+ onProgress,
245
+ })).rejects.toThrow(/Invalid transaction hash/);
246
+ expect(onProgress).toHaveBeenCalledWith('failed', expect.objectContaining({ errorMessage: expect.stringContaining('Invalid') }));
247
+ }));
248
+ it('emits failed only ONCE when signer returns empty hash (no duplicate)', () => __awaiter(void 0, void 0, void 0, function* () {
249
+ jest
250
+ .spyOn(mpc, 'getLifiRoutes')
251
+ .mockResolvedValue(mockLifiGetRoutesResponse);
252
+ jest
253
+ .spyOn(mpc, 'getLifiRouteStep')
254
+ .mockResolvedValue(mockLifiGetRouteStepResponse);
255
+ const onProgress = jest.fn();
256
+ const lifiOk = new LiFi({
257
+ mpc,
258
+ signAndSendTransaction: () => __awaiter(void 0, void 0, void 0, function* () { return ''; }),
259
+ waitForConfirmation: () => __awaiter(void 0, void 0, void 0, function* () { return true; }),
260
+ });
261
+ yield expect(lifiOk.tradeAsset({
262
+ fromChain: 'eip155:8453',
263
+ toChain: 'eip155:42161',
264
+ fromToken: 'ETH',
265
+ toToken: 'USDC',
266
+ amount: '1000000000000000',
267
+ fromAddress: mockEip155Address,
268
+ onProgress,
269
+ })).rejects.toThrow(/Invalid transaction hash/);
270
+ const failedCalls = onProgress.mock.calls.filter((call) => call[0] === 'failed');
271
+ expect(failedCalls).toHaveLength(1);
272
+ expect(failedCalls[0][1]).toMatchObject({
273
+ errorMessage: expect.stringContaining('Invalid transaction hash'),
274
+ stepIndex: 0,
275
+ totalSteps: 1,
276
+ });
277
+ }));
278
+ it('emits failed only ONCE when waitForConfirmation returns false (no duplicate)', () => __awaiter(void 0, void 0, void 0, function* () {
279
+ jest
280
+ .spyOn(mpc, 'getLifiRoutes')
281
+ .mockResolvedValue(mockLifiGetRoutesResponse);
282
+ jest
283
+ .spyOn(mpc, 'getLifiRouteStep')
284
+ .mockResolvedValue(mockLifiGetRouteStepResponse);
285
+ const onProgress = jest.fn();
286
+ const lifiOk = new LiFi({
287
+ mpc,
288
+ signAndSendTransaction: () => __awaiter(void 0, void 0, void 0, function* () { return '0xhash'; }),
289
+ waitForConfirmation: () => __awaiter(void 0, void 0, void 0, function* () { return false; }),
290
+ });
291
+ yield expect(lifiOk.tradeAsset({
292
+ fromChain: 'eip155:8453',
293
+ toChain: 'eip155:42161',
294
+ fromToken: 'ETH',
295
+ toToken: 'USDC',
296
+ amount: '1000000000000000',
297
+ fromAddress: mockEip155Address,
298
+ onProgress,
299
+ })).rejects.toThrow(/on-chain confirmation did not complete/);
300
+ const failedCalls = onProgress.mock.calls.filter((call) => call[0] === 'failed');
301
+ expect(failedCalls).toHaveLength(1);
302
+ expect(failedCalls[0][1]).toMatchObject({
303
+ errorMessage: expect.stringContaining('on-chain confirmation'),
304
+ txHash: '0xhash',
305
+ stepIndex: 0,
306
+ totalSteps: 1,
307
+ });
308
+ }));
309
+ it('throws when waitForConfirmation throws', () => __awaiter(void 0, void 0, void 0, function* () {
310
+ jest
311
+ .spyOn(mpc, 'getLifiRoutes')
312
+ .mockResolvedValue(mockLifiGetRoutesResponse);
313
+ jest
314
+ .spyOn(mpc, 'getLifiRouteStep')
315
+ .mockResolvedValue(mockLifiGetRouteStepResponse);
316
+ const lifiOk = new LiFi({
317
+ mpc,
318
+ signAndSendTransaction: () => __awaiter(void 0, void 0, void 0, function* () { return '0xabc'; }),
319
+ waitForConfirmation: () => __awaiter(void 0, void 0, void 0, function* () {
320
+ throw new Error('waiter boom');
321
+ }),
322
+ });
323
+ yield expect(lifiOk.tradeAsset({
324
+ fromChain: 'eip155:8453',
325
+ toChain: 'eip155:42161',
326
+ fromToken: 'ETH',
327
+ toToken: 'USDC',
328
+ amount: '1000000000000000',
329
+ fromAddress: mockEip155Address,
330
+ })).rejects.toThrow('waiter boom');
331
+ }));
332
+ });
@@ -0,0 +1,113 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ /**
11
+ * LiFi status polling: domain tick (DONE / FAILED / indexer lag) over shared {@link pollLoop}.
12
+ */
13
+ import { sdkLogger } from '../../../logger';
14
+ import { pollLoop, PollLoopTimeoutError, } from '../../../internal/pollLoop';
15
+ const LOG_PREFIX = '[LiFi.pollStatus]';
16
+ const INDEXER_LAG_MESSAGE_SNIPPETS = [
17
+ 'not an evm transaction',
18
+ 'no transfer information found',
19
+ 'transfer not found',
20
+ ];
21
+ export function isLikelyLifiIndexerNotReadyMessage(message) {
22
+ const m = message.toLowerCase();
23
+ return INDEXER_LAG_MESSAGE_SNIPPETS.some((s) => m.includes(s));
24
+ }
25
+ export function pollLifiStatusUntilTerminal(params) {
26
+ return __awaiter(this, void 0, void 0, function* () {
27
+ let consecutiveHardErrors = 0;
28
+ try {
29
+ return yield pollLoop({
30
+ tick: () => __awaiter(this, void 0, void 0, function* () {
31
+ return lifiStatusPollTick(params, {
32
+ get: () => consecutiveHardErrors,
33
+ set: (n) => {
34
+ consecutiveHardErrors = n;
35
+ },
36
+ });
37
+ }),
38
+ intervalMs: params.everyMs,
39
+ initialDelayMs: params.initialDelayMs,
40
+ backoff: params.backoff,
41
+ timeoutMs: params.timeoutMs,
42
+ });
43
+ }
44
+ catch (error) {
45
+ if (error instanceof PollLoopTimeoutError) {
46
+ throw new Error(`${LOG_PREFIX} timed out after ${params.timeoutMs}ms waiting for LiFi terminal status`);
47
+ }
48
+ throw error;
49
+ }
50
+ });
51
+ }
52
+ function lifiStatusPollTick(params, counter) {
53
+ var _a, _b, _c;
54
+ return __awaiter(this, void 0, void 0, function* () {
55
+ try {
56
+ const res = yield params.getStatus(params.request);
57
+ if (res.error) {
58
+ sdkLogger.warn(`${LOG_PREFIX} response error field`, res.error);
59
+ return bumpHardError(counter, params.maxConsecutiveErrors);
60
+ }
61
+ counter.set(0);
62
+ const raw = (_a = res.data) === null || _a === void 0 ? void 0 : _a.rawResponse;
63
+ if (!raw) {
64
+ return { kind: 'continue' };
65
+ }
66
+ const { status } = raw;
67
+ if (status === 'INVALID') {
68
+ return {
69
+ kind: 'throw',
70
+ error: new Error(`${LOG_PREFIX} LiFi status INVALID for ${params.request.txHash}`),
71
+ };
72
+ }
73
+ if (status === 'DONE') {
74
+ return { kind: 'stop', value: raw };
75
+ }
76
+ if (status === 'FAILED') {
77
+ const detail = (_c = (_b = raw.substatusMessage) !== null && _b !== void 0 ? _b : raw.substatus) !== null && _c !== void 0 ? _c : 'LiFi transfer FAILED';
78
+ return {
79
+ kind: 'throw',
80
+ error: new Error(`${LOG_PREFIX} ${detail}`),
81
+ };
82
+ }
83
+ if (params.onUpdate) {
84
+ const carryOn = params.onUpdate(raw);
85
+ if (carryOn === false) {
86
+ return { kind: 'stop', value: raw };
87
+ }
88
+ }
89
+ return { kind: 'continue' };
90
+ }
91
+ catch (error) {
92
+ const message = error instanceof Error ? error.message : String(error);
93
+ if (isLikelyLifiIndexerNotReadyMessage(message)) {
94
+ sdkLogger.debug(`${LOG_PREFIX} indexer not ready, will retry`, message);
95
+ counter.set(0);
96
+ return { kind: 'continue' };
97
+ }
98
+ sdkLogger.warn(`${LOG_PREFIX} getStatus error`, error);
99
+ return bumpHardError(counter, params.maxConsecutiveErrors);
100
+ }
101
+ });
102
+ }
103
+ function bumpHardError(counter, max) {
104
+ const next = counter.get() + 1;
105
+ counter.set(next);
106
+ if (next >= max) {
107
+ return {
108
+ kind: 'throw',
109
+ error: new Error(`${LOG_PREFIX} too many consecutive getStatus failures (${max})`),
110
+ };
111
+ }
112
+ return { kind: 'continue' };
113
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
5
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
6
+ return new (P || (P = Promise))(function (resolve, reject) {
7
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
8
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
9
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
10
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
11
+ });
12
+ };
13
+ import { pollLifiStatusUntilTerminal, isLikelyLifiIndexerNotReadyMessage, } from './lifiStatusPoll';
14
+ describe('lifiStatusPoll', () => {
15
+ describe('isLikelyLifiIndexerNotReadyMessage', () => {
16
+ it('returns true for known indexer lag phrases', () => {
17
+ expect(isLikelyLifiIndexerNotReadyMessage('No transfer information found')).toBe(true);
18
+ expect(isLikelyLifiIndexerNotReadyMessage('transfer not found')).toBe(true);
19
+ });
20
+ it('returns false for unrelated errors', () => {
21
+ expect(isLikelyLifiIndexerNotReadyMessage('Network error 400')).toBe(false);
22
+ });
23
+ });
24
+ describe('pollLifiStatusUntilTerminal', () => {
25
+ it('treats response error field as hard error without resetting counter incorrectly', () => __awaiter(void 0, void 0, void 0, function* () {
26
+ const getStatus = jest
27
+ .fn()
28
+ .mockResolvedValueOnce({
29
+ error: 'temporary',
30
+ data: undefined,
31
+ })
32
+ .mockResolvedValueOnce({
33
+ data: { rawResponse: { status: 'DONE' } },
34
+ });
35
+ const result = yield pollLifiStatusUntilTerminal({
36
+ getStatus,
37
+ request: { txHash: '0x1' },
38
+ everyMs: 5,
39
+ initialDelayMs: 0,
40
+ timeoutMs: 5000,
41
+ maxConsecutiveErrors: 10,
42
+ });
43
+ expect(result.status).toBe('DONE');
44
+ expect(getStatus).toHaveBeenCalledTimes(2);
45
+ }));
46
+ it('retries on indexer lag message without counting as hard error', () => __awaiter(void 0, void 0, void 0, function* () {
47
+ const getStatus = jest
48
+ .fn()
49
+ .mockRejectedValueOnce(new Error('transfer not found'))
50
+ .mockResolvedValue({
51
+ data: { rawResponse: { status: 'DONE' } },
52
+ });
53
+ const result = yield pollLifiStatusUntilTerminal({
54
+ getStatus,
55
+ request: { txHash: '0x1' },
56
+ everyMs: 5,
57
+ initialDelayMs: 0,
58
+ timeoutMs: 5000,
59
+ maxConsecutiveErrors: 2,
60
+ });
61
+ expect(result.status).toBe('DONE');
62
+ }));
63
+ });
64
+ });