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