@portal-hq/web 3.14.0-alpha.0 → 3.14.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.
@@ -280,6 +280,27 @@ class LiFi {
280
280
  });
281
281
  throw new LifiReportedError(msg);
282
282
  }
283
+ let lifiTxHash = txHash;
284
+ if (network.startsWith('eip155:') && o.evmRequestFn) {
285
+ try {
286
+ const userOpReceipt = yield o.evmRequestFn('eth_getUserOperationReceipt', [txHash], network);
287
+ if (userOpReceipt &&
288
+ typeof userOpReceipt === 'object' &&
289
+ 'receipt' in userOpReceipt &&
290
+ userOpReceipt.receipt &&
291
+ typeof userOpReceipt.receipt === 'object' &&
292
+ 'transactionHash' in userOpReceipt.receipt) {
293
+ const bundleHash = userOpReceipt.receipt.transactionHash;
294
+ if (bundleHash && typeof bundleHash === 'string') {
295
+ lifiTxHash = bundleHash;
296
+ logger_1.sdkLogger.debug(`${LOG_PREFIX} Resolved AA bundle tx hash for LiFi: ${lifiTxHash}`);
297
+ }
298
+ }
299
+ }
300
+ catch (error) {
301
+ logger_1.sdkLogger.debug(`${LOG_PREFIX} Could not resolve bundle hash (likely EOA tx), using original hash`);
302
+ }
303
+ }
283
304
  const bridgeTool = statusBridgeFromStepTool(populated.tool);
284
305
  const crossLike = populated.type === 'cross' ||
285
306
  populated.action.fromChainId !== populated.action.toChainId;
@@ -294,7 +315,7 @@ class LiFi {
294
315
  });
295
316
  const pollOpts = (_p = params.statusPoll) !== null && _p !== void 0 ? _p : {};
296
317
  const terminal = yield this.pollStatus({
297
- txHash,
318
+ txHash: lifiTxHash,
298
319
  fromChain: params.fromChain,
299
320
  toChain: params.toChain,
300
321
  bridge: bridgeTool,
@@ -327,4 +327,112 @@ describe('YieldXyz high-level (deposit, withdraw, defaults)', () => {
327
327
  expect(r.chain).toBe('eip155:1');
328
328
  expect(r.token).toBe('ETH');
329
329
  }));
330
+ it('status=SUCCESS when no waitForConfirmation configured', () => __awaiter(void 0, void 0, void 0, function* () {
331
+ jest.spyOn(mpc, 'enterYieldXyzYield').mockResolvedValue(enterResponseWithTxs([
332
+ {
333
+ id: 'tx1',
334
+ network: 'eip155:1',
335
+ unsignedTransaction: { to: '0x1' },
336
+ },
337
+ ]));
338
+ jest.spyOn(mpc, 'trackYieldXyzTransaction').mockResolvedValue({});
339
+ const r = yield yieldXyz.deposit({
340
+ yieldId: 'y',
341
+ amount: '1',
342
+ address: constants_1.mockAddress,
343
+ });
344
+ expect(r.status).toBe('SUCCESS');
345
+ }));
346
+ it('status=SUCCESS when all confirmations reach true', () => __awaiter(void 0, void 0, void 0, function* () {
347
+ const y = new yieldxyz_1.default({
348
+ mpc,
349
+ waitForConfirmation: jest.fn().mockResolvedValue(true),
350
+ });
351
+ y.setSignAndSendTransaction(jest.fn().mockResolvedValue('0xh'));
352
+ jest.spyOn(mpc, 'enterYieldXyzYield').mockResolvedValue(enterResponseWithTxs([
353
+ {
354
+ id: 'tx1',
355
+ network: 'eip155:1',
356
+ unsignedTransaction: { to: '0x1' },
357
+ },
358
+ {
359
+ id: 'tx2',
360
+ network: 'eip155:1',
361
+ unsignedTransaction: { to: '0x2' },
362
+ },
363
+ ]));
364
+ jest.spyOn(mpc, 'trackYieldXyzTransaction').mockResolvedValue({});
365
+ const r = yield y.deposit({ yieldId: 'y', amount: '1', address: constants_1.mockAddress });
366
+ expect(r.status).toBe('SUCCESS');
367
+ expect(r.hashes).toHaveLength(2);
368
+ }));
369
+ it('status=FAILED when first confirmation returns false', () => __awaiter(void 0, void 0, void 0, function* () {
370
+ const y = new yieldxyz_1.default({
371
+ mpc,
372
+ waitForConfirmation: jest.fn().mockResolvedValue(false),
373
+ });
374
+ y.setSignAndSendTransaction(jest.fn().mockResolvedValue('0xh'));
375
+ jest.spyOn(mpc, 'enterYieldXyzYield').mockResolvedValue(enterResponseWithTxs([
376
+ {
377
+ id: 'tx1',
378
+ network: 'eip155:1',
379
+ unsignedTransaction: { to: '0x1' },
380
+ },
381
+ ]));
382
+ jest.spyOn(mpc, 'trackYieldXyzTransaction').mockResolvedValue({});
383
+ const r = yield y.deposit({ yieldId: 'y', amount: '1', address: constants_1.mockAddress });
384
+ expect(r.status).toBe('FAILED');
385
+ expect(r.hashes).toEqual(['0xh']);
386
+ }));
387
+ it('status=PARTIAL_SUCCESS when first tx confirms but second fails', () => __awaiter(void 0, void 0, void 0, function* () {
388
+ const waiter = jest
389
+ .fn()
390
+ .mockResolvedValueOnce(true)
391
+ .mockResolvedValueOnce(false);
392
+ const y = new yieldxyz_1.default({
393
+ mpc,
394
+ waitForConfirmation: waiter,
395
+ });
396
+ const signer = jest
397
+ .fn()
398
+ .mockResolvedValueOnce('0xh1')
399
+ .mockResolvedValueOnce('0xh2');
400
+ y.setSignAndSendTransaction(signer);
401
+ jest.spyOn(mpc, 'enterYieldXyzYield').mockResolvedValue(enterResponseWithTxs([
402
+ {
403
+ id: 'tx1',
404
+ network: 'eip155:1',
405
+ unsignedTransaction: { to: '0x1' },
406
+ },
407
+ {
408
+ id: 'tx2',
409
+ network: 'eip155:1',
410
+ unsignedTransaction: { to: '0x2' },
411
+ },
412
+ ]));
413
+ jest.spyOn(mpc, 'trackYieldXyzTransaction').mockResolvedValue({});
414
+ const r = yield y.deposit({ yieldId: 'y', amount: '1', address: constants_1.mockAddress });
415
+ expect(r.status).toBe('PARTIAL_SUCCESS');
416
+ expect(r.hashes).toEqual(['0xh1', '0xh2']);
417
+ expect(signer).toHaveBeenCalledTimes(2);
418
+ expect(waiter).toHaveBeenCalledTimes(2);
419
+ }));
420
+ it('withdraw: status=FAILED when confirmation returns false', () => __awaiter(void 0, void 0, void 0, function* () {
421
+ const y = new yieldxyz_1.default({
422
+ mpc,
423
+ waitForConfirmation: jest.fn().mockResolvedValue(false),
424
+ });
425
+ y.setSignAndSendTransaction(jest.fn().mockResolvedValue('0xh'));
426
+ jest.spyOn(mpc, 'exitYieldXyzYield').mockResolvedValue(exitResponseWithTxs([
427
+ {
428
+ id: 'tx1',
429
+ network: 'eip155:1',
430
+ unsignedTransaction: { to: '0x1' },
431
+ },
432
+ ]));
433
+ jest.spyOn(mpc, 'trackYieldXyzTransaction').mockResolvedValue({});
434
+ const r = yield y.withdraw({ yieldId: 'y', amount: '1', address: constants_1.mockAddress });
435
+ expect(r.status).toBe('FAILED');
436
+ expect(r.hashes).toEqual(['0xh']);
437
+ }));
330
438
  });
@@ -263,9 +263,10 @@ class YieldXyz {
263
263
  const result = Object.assign(Object.assign({}, base), (resolvedChain !== undefined && resolvedToken !== undefined
264
264
  ? { chain: resolvedChain, token: resolvedToken }
265
265
  : {}));
266
- logger_1.sdkLogger.info(`${LOG_PREFIX} deposit: success`, {
266
+ logger_1.sdkLogger.info(`${LOG_PREFIX} deposit: complete`, {
267
267
  hashes: result.hashes,
268
268
  yieldId: result.yieldId,
269
+ status: result.status,
269
270
  yieldOpportunityDetails: result.yieldOpportunityDetails,
270
271
  });
271
272
  return result;
@@ -310,9 +311,10 @@ class YieldXyz {
310
311
  const result = Object.assign(Object.assign({}, base), (resolvedChain !== undefined && resolvedToken !== undefined
311
312
  ? { chain: resolvedChain, token: resolvedToken }
312
313
  : {}));
313
- logger_1.sdkLogger.info(`${LOG_PREFIX} withdraw: success`, {
314
+ logger_1.sdkLogger.info(`${LOG_PREFIX} withdraw: complete`, {
314
315
  hashes: result.hashes,
315
316
  yieldId: result.yieldId,
317
+ status: result.status,
316
318
  yieldOpportunityDetails: result.yieldOpportunityDetails,
317
319
  });
318
320
  return result;
@@ -444,6 +446,7 @@ class YieldXyz {
444
446
  const total = transactions.length;
445
447
  const hashes = [];
446
448
  let confirmationsReached = 0;
449
+ let confirmationsRequired = waitForConfirmation ? total : 0;
447
450
  for (let index = 0; index < transactions.length; index++) {
448
451
  const tx = transactions[index];
449
452
  // Validate transaction exists (defensive check for sparse arrays)
@@ -615,15 +618,36 @@ class YieldXyz {
615
618
  const rawResponse = (_d = response.data) === null || _d === void 0 ? void 0 : _d.rawResponse;
616
619
  const yieldId = (_e = rawResponse === null || rawResponse === void 0 ? void 0 : rawResponse.yieldId) !== null && _e !== void 0 ? _e : '';
617
620
  const yieldOpportunityDetails = this.buildYieldOpportunityDetails(response);
621
+ // Determine status based on confirmation semantics from docs:
622
+ // - If no confirmations required (no waitForConfirmation): SUCCESS
623
+ // - If all required confirmations reached: SUCCESS
624
+ // - If some confirmations reached but not all: PARTIAL_SUCCESS
625
+ // - If confirmations required but zero reached: FAILED
626
+ let status;
627
+ if (confirmationsRequired === 0) {
628
+ status = 'SUCCESS';
629
+ }
630
+ else if (confirmationsReached === confirmationsRequired) {
631
+ status = 'SUCCESS';
632
+ }
633
+ else if (confirmationsReached > 0) {
634
+ status = 'PARTIAL_SUCCESS';
635
+ }
636
+ else {
637
+ status = 'FAILED';
638
+ }
618
639
  logger_1.sdkLogger.info(`${LOG_PREFIX} ${method}: executeAndTrack complete`, {
619
640
  yieldId,
620
641
  hashCount: hashes.length,
621
- confirmationsReached: confirmationsReached > 0 ? confirmationsReached : undefined,
642
+ confirmationsReached,
643
+ confirmationsRequired,
644
+ status,
622
645
  });
623
646
  return {
624
647
  hashes,
625
648
  yieldId,
626
649
  yieldOpportunityDetails,
650
+ status,
627
651
  };
628
652
  });
629
653
  }
@@ -14,7 +14,7 @@ const errors_1 = require("./errors");
14
14
  const logger_1 = require("../logger");
15
15
  const index_1 = require("../index");
16
16
  const trace_1 = require("../shared/trace");
17
- const WEB_SDK_VERSION = '3.14.0-alpha.0';
17
+ const WEB_SDK_VERSION = '3.14.0';
18
18
  class Mpc {
19
19
  get ready() {
20
20
  return this._ready;
@@ -1011,10 +1011,11 @@ class Mpc {
1011
1011
  });
1012
1012
  }
1013
1013
  rpcRequest(data, options) {
1014
- var _a, _b;
1015
1014
  return __awaiter(this, void 0, void 0, function* () {
1016
1015
  const { timeoutMs = 30000, traceId } = options !== null && options !== void 0 ? options : {};
1017
- const requestId = (_b = (_a = crypto.randomUUID) === null || _a === void 0 ? void 0 : _a.call(crypto)) !== null && _b !== void 0 ? _b : `${Date.now()}-${Math.random()}`;
1016
+ const requestId = typeof crypto !== 'undefined' && crypto.randomUUID
1017
+ ? crypto.randomUUID()
1018
+ : `${Date.now()}-${Math.random()}`;
1018
1019
  const resolvedTraceId = traceId !== null && traceId !== void 0 ? traceId : (0, trace_1.generateTraceId)();
1019
1020
  logger_1.sdkLogger.debug('[Portal] rpcRequest', {
1020
1021
  requestId,
@@ -273,6 +273,27 @@ export default class LiFi {
273
273
  });
274
274
  throw new LifiReportedError(msg);
275
275
  }
276
+ let lifiTxHash = txHash;
277
+ if (network.startsWith('eip155:') && o.evmRequestFn) {
278
+ try {
279
+ const userOpReceipt = yield o.evmRequestFn('eth_getUserOperationReceipt', [txHash], network);
280
+ if (userOpReceipt &&
281
+ typeof userOpReceipt === 'object' &&
282
+ 'receipt' in userOpReceipt &&
283
+ userOpReceipt.receipt &&
284
+ typeof userOpReceipt.receipt === 'object' &&
285
+ 'transactionHash' in userOpReceipt.receipt) {
286
+ const bundleHash = userOpReceipt.receipt.transactionHash;
287
+ if (bundleHash && typeof bundleHash === 'string') {
288
+ lifiTxHash = bundleHash;
289
+ sdkLogger.debug(`${LOG_PREFIX} Resolved AA bundle tx hash for LiFi: ${lifiTxHash}`);
290
+ }
291
+ }
292
+ }
293
+ catch (error) {
294
+ sdkLogger.debug(`${LOG_PREFIX} Could not resolve bundle hash (likely EOA tx), using original hash`);
295
+ }
296
+ }
276
297
  const bridgeTool = statusBridgeFromStepTool(populated.tool);
277
298
  const crossLike = populated.type === 'cross' ||
278
299
  populated.action.fromChainId !== populated.action.toChainId;
@@ -287,7 +308,7 @@ export default class LiFi {
287
308
  });
288
309
  const pollOpts = (_p = params.statusPoll) !== null && _p !== void 0 ? _p : {};
289
310
  const terminal = yield this.pollStatus({
290
- txHash,
311
+ txHash: lifiTxHash,
291
312
  fromChain: params.fromChain,
292
313
  toChain: params.toChain,
293
314
  bridge: bridgeTool,
@@ -322,4 +322,112 @@ describe('YieldXyz high-level (deposit, withdraw, defaults)', () => {
322
322
  expect(r.chain).toBe('eip155:1');
323
323
  expect(r.token).toBe('ETH');
324
324
  }));
325
+ it('status=SUCCESS when no waitForConfirmation configured', () => __awaiter(void 0, void 0, void 0, function* () {
326
+ jest.spyOn(mpc, 'enterYieldXyzYield').mockResolvedValue(enterResponseWithTxs([
327
+ {
328
+ id: 'tx1',
329
+ network: 'eip155:1',
330
+ unsignedTransaction: { to: '0x1' },
331
+ },
332
+ ]));
333
+ jest.spyOn(mpc, 'trackYieldXyzTransaction').mockResolvedValue({});
334
+ const r = yield yieldXyz.deposit({
335
+ yieldId: 'y',
336
+ amount: '1',
337
+ address: mockAddress,
338
+ });
339
+ expect(r.status).toBe('SUCCESS');
340
+ }));
341
+ it('status=SUCCESS when all confirmations reach true', () => __awaiter(void 0, void 0, void 0, function* () {
342
+ const y = new YieldXyz({
343
+ mpc,
344
+ waitForConfirmation: jest.fn().mockResolvedValue(true),
345
+ });
346
+ y.setSignAndSendTransaction(jest.fn().mockResolvedValue('0xh'));
347
+ jest.spyOn(mpc, 'enterYieldXyzYield').mockResolvedValue(enterResponseWithTxs([
348
+ {
349
+ id: 'tx1',
350
+ network: 'eip155:1',
351
+ unsignedTransaction: { to: '0x1' },
352
+ },
353
+ {
354
+ id: 'tx2',
355
+ network: 'eip155:1',
356
+ unsignedTransaction: { to: '0x2' },
357
+ },
358
+ ]));
359
+ jest.spyOn(mpc, 'trackYieldXyzTransaction').mockResolvedValue({});
360
+ const r = yield y.deposit({ yieldId: 'y', amount: '1', address: mockAddress });
361
+ expect(r.status).toBe('SUCCESS');
362
+ expect(r.hashes).toHaveLength(2);
363
+ }));
364
+ it('status=FAILED when first confirmation returns false', () => __awaiter(void 0, void 0, void 0, function* () {
365
+ const y = new YieldXyz({
366
+ mpc,
367
+ waitForConfirmation: jest.fn().mockResolvedValue(false),
368
+ });
369
+ y.setSignAndSendTransaction(jest.fn().mockResolvedValue('0xh'));
370
+ jest.spyOn(mpc, 'enterYieldXyzYield').mockResolvedValue(enterResponseWithTxs([
371
+ {
372
+ id: 'tx1',
373
+ network: 'eip155:1',
374
+ unsignedTransaction: { to: '0x1' },
375
+ },
376
+ ]));
377
+ jest.spyOn(mpc, 'trackYieldXyzTransaction').mockResolvedValue({});
378
+ const r = yield y.deposit({ yieldId: 'y', amount: '1', address: mockAddress });
379
+ expect(r.status).toBe('FAILED');
380
+ expect(r.hashes).toEqual(['0xh']);
381
+ }));
382
+ it('status=PARTIAL_SUCCESS when first tx confirms but second fails', () => __awaiter(void 0, void 0, void 0, function* () {
383
+ const waiter = jest
384
+ .fn()
385
+ .mockResolvedValueOnce(true)
386
+ .mockResolvedValueOnce(false);
387
+ const y = new YieldXyz({
388
+ mpc,
389
+ waitForConfirmation: waiter,
390
+ });
391
+ const signer = jest
392
+ .fn()
393
+ .mockResolvedValueOnce('0xh1')
394
+ .mockResolvedValueOnce('0xh2');
395
+ y.setSignAndSendTransaction(signer);
396
+ jest.spyOn(mpc, 'enterYieldXyzYield').mockResolvedValue(enterResponseWithTxs([
397
+ {
398
+ id: 'tx1',
399
+ network: 'eip155:1',
400
+ unsignedTransaction: { to: '0x1' },
401
+ },
402
+ {
403
+ id: 'tx2',
404
+ network: 'eip155:1',
405
+ unsignedTransaction: { to: '0x2' },
406
+ },
407
+ ]));
408
+ jest.spyOn(mpc, 'trackYieldXyzTransaction').mockResolvedValue({});
409
+ const r = yield y.deposit({ yieldId: 'y', amount: '1', address: mockAddress });
410
+ expect(r.status).toBe('PARTIAL_SUCCESS');
411
+ expect(r.hashes).toEqual(['0xh1', '0xh2']);
412
+ expect(signer).toHaveBeenCalledTimes(2);
413
+ expect(waiter).toHaveBeenCalledTimes(2);
414
+ }));
415
+ it('withdraw: status=FAILED when confirmation returns false', () => __awaiter(void 0, void 0, void 0, function* () {
416
+ const y = new YieldXyz({
417
+ mpc,
418
+ waitForConfirmation: jest.fn().mockResolvedValue(false),
419
+ });
420
+ y.setSignAndSendTransaction(jest.fn().mockResolvedValue('0xh'));
421
+ jest.spyOn(mpc, 'exitYieldXyzYield').mockResolvedValue(exitResponseWithTxs([
422
+ {
423
+ id: 'tx1',
424
+ network: 'eip155:1',
425
+ unsignedTransaction: { to: '0x1' },
426
+ },
427
+ ]));
428
+ jest.spyOn(mpc, 'trackYieldXyzTransaction').mockResolvedValue({});
429
+ const r = yield y.withdraw({ yieldId: 'y', amount: '1', address: mockAddress });
430
+ expect(r.status).toBe('FAILED');
431
+ expect(r.hashes).toEqual(['0xh']);
432
+ }));
325
433
  });
@@ -261,9 +261,10 @@ export default class YieldXyz {
261
261
  const result = Object.assign(Object.assign({}, base), (resolvedChain !== undefined && resolvedToken !== undefined
262
262
  ? { chain: resolvedChain, token: resolvedToken }
263
263
  : {}));
264
- sdkLogger.info(`${LOG_PREFIX} deposit: success`, {
264
+ sdkLogger.info(`${LOG_PREFIX} deposit: complete`, {
265
265
  hashes: result.hashes,
266
266
  yieldId: result.yieldId,
267
+ status: result.status,
267
268
  yieldOpportunityDetails: result.yieldOpportunityDetails,
268
269
  });
269
270
  return result;
@@ -308,9 +309,10 @@ export default class YieldXyz {
308
309
  const result = Object.assign(Object.assign({}, base), (resolvedChain !== undefined && resolvedToken !== undefined
309
310
  ? { chain: resolvedChain, token: resolvedToken }
310
311
  : {}));
311
- sdkLogger.info(`${LOG_PREFIX} withdraw: success`, {
312
+ sdkLogger.info(`${LOG_PREFIX} withdraw: complete`, {
312
313
  hashes: result.hashes,
313
314
  yieldId: result.yieldId,
315
+ status: result.status,
314
316
  yieldOpportunityDetails: result.yieldOpportunityDetails,
315
317
  });
316
318
  return result;
@@ -442,6 +444,7 @@ export default class YieldXyz {
442
444
  const total = transactions.length;
443
445
  const hashes = [];
444
446
  let confirmationsReached = 0;
447
+ let confirmationsRequired = waitForConfirmation ? total : 0;
445
448
  for (let index = 0; index < transactions.length; index++) {
446
449
  const tx = transactions[index];
447
450
  // Validate transaction exists (defensive check for sparse arrays)
@@ -613,15 +616,36 @@ export default class YieldXyz {
613
616
  const rawResponse = (_d = response.data) === null || _d === void 0 ? void 0 : _d.rawResponse;
614
617
  const yieldId = (_e = rawResponse === null || rawResponse === void 0 ? void 0 : rawResponse.yieldId) !== null && _e !== void 0 ? _e : '';
615
618
  const yieldOpportunityDetails = this.buildYieldOpportunityDetails(response);
619
+ // Determine status based on confirmation semantics from docs:
620
+ // - If no confirmations required (no waitForConfirmation): SUCCESS
621
+ // - If all required confirmations reached: SUCCESS
622
+ // - If some confirmations reached but not all: PARTIAL_SUCCESS
623
+ // - If confirmations required but zero reached: FAILED
624
+ let status;
625
+ if (confirmationsRequired === 0) {
626
+ status = 'SUCCESS';
627
+ }
628
+ else if (confirmationsReached === confirmationsRequired) {
629
+ status = 'SUCCESS';
630
+ }
631
+ else if (confirmationsReached > 0) {
632
+ status = 'PARTIAL_SUCCESS';
633
+ }
634
+ else {
635
+ status = 'FAILED';
636
+ }
616
637
  sdkLogger.info(`${LOG_PREFIX} ${method}: executeAndTrack complete`, {
617
638
  yieldId,
618
639
  hashCount: hashes.length,
619
- confirmationsReached: confirmationsReached > 0 ? confirmationsReached : undefined,
640
+ confirmationsReached,
641
+ confirmationsRequired,
642
+ status,
620
643
  });
621
644
  return {
622
645
  hashes,
623
646
  yieldId,
624
647
  yieldOpportunityDetails,
648
+ status,
625
649
  };
626
650
  });
627
651
  }
@@ -11,7 +11,7 @@ import { PortalMpcError } from './errors';
11
11
  import { sdkLogger } from '../logger';
12
12
  import { BackupMethods, } from '../index';
13
13
  import { generateTraceId } from '../shared/trace';
14
- const WEB_SDK_VERSION = '3.14.0-alpha.0';
14
+ const WEB_SDK_VERSION = '3.14.0';
15
15
  class Mpc {
16
16
  get ready() {
17
17
  return this._ready;
@@ -1008,10 +1008,11 @@ class Mpc {
1008
1008
  });
1009
1009
  }
1010
1010
  rpcRequest(data, options) {
1011
- var _a, _b;
1012
1011
  return __awaiter(this, void 0, void 0, function* () {
1013
1012
  const { timeoutMs = 30000, traceId } = options !== null && options !== void 0 ? options : {};
1014
- const requestId = (_b = (_a = crypto.randomUUID) === null || _a === void 0 ? void 0 : _a.call(crypto)) !== null && _b !== void 0 ? _b : `${Date.now()}-${Math.random()}`;
1013
+ const requestId = typeof crypto !== 'undefined' && crypto.randomUUID
1014
+ ? crypto.randomUUID()
1015
+ : `${Date.now()}-${Math.random()}`;
1015
1016
  const resolvedTraceId = traceId !== null && traceId !== void 0 ? traceId : generateTraceId();
1016
1017
  sdkLogger.debug('[Portal] rpcRequest', {
1017
1018
  requestId,
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Portal MPC Support for Web",
4
4
  "author": "Portal Labs, Inc.",
5
5
  "homepage": "https://portalhq.io/",
6
- "version": "3.14.0-alpha.0",
6
+ "version": "3.14.0",
7
7
  "license": "MIT",
8
8
  "main": "lib/commonjs/index",
9
9
  "module": "lib/esm/index",
@@ -58,6 +58,5 @@
58
58
  },
59
59
  "dependencies": {
60
60
  "@solana/web3.js": "^1.91.8"
61
- },
62
- "stableVersion": "3.13.1"
61
+ }
63
62
  }
@@ -358,6 +358,39 @@ export default class LiFi implements ILiFi {
358
358
  throw new LifiReportedError(msg)
359
359
  }
360
360
 
361
+ let lifiTxHash = txHash
362
+ if (network.startsWith('eip155:') && o.evmRequestFn) {
363
+ try {
364
+ const userOpReceipt = await o.evmRequestFn(
365
+ 'eth_getUserOperationReceipt',
366
+ [txHash],
367
+ network,
368
+ )
369
+ if (
370
+ userOpReceipt &&
371
+ typeof userOpReceipt === 'object' &&
372
+ 'receipt' in userOpReceipt &&
373
+ userOpReceipt.receipt &&
374
+ typeof userOpReceipt.receipt === 'object' &&
375
+ 'transactionHash' in userOpReceipt.receipt
376
+ ) {
377
+ const bundleHash = (
378
+ userOpReceipt.receipt as { transactionHash?: string }
379
+ ).transactionHash
380
+ if (bundleHash && typeof bundleHash === 'string') {
381
+ lifiTxHash = bundleHash
382
+ sdkLogger.debug(
383
+ `${LOG_PREFIX} Resolved AA bundle tx hash for LiFi: ${lifiTxHash}`,
384
+ )
385
+ }
386
+ }
387
+ } catch (error) {
388
+ sdkLogger.debug(
389
+ `${LOG_PREFIX} Could not resolve bundle hash (likely EOA tx), using original hash`,
390
+ )
391
+ }
392
+ }
393
+
361
394
  const bridgeTool = statusBridgeFromStepTool(populated.tool)
362
395
  const crossLike =
363
396
  populated.type === 'cross' ||
@@ -376,7 +409,7 @@ export default class LiFi implements ILiFi {
376
409
  const pollOpts = params.statusPoll ?? {}
377
410
  const terminal = await this.pollStatus(
378
411
  {
379
- txHash,
412
+ txHash: lifiTxHash,
380
413
  fromChain: params.fromChain,
381
414
  toChain: params.toChain,
382
415
  bridge: bridgeTool,
@@ -400,4 +400,137 @@ describe('YieldXyz high-level (deposit, withdraw, defaults)', () => {
400
400
  expect(r.chain).toBe('eip155:1')
401
401
  expect(r.token).toBe('ETH')
402
402
  })
403
+
404
+ it('status=SUCCESS when no waitForConfirmation configured', async () => {
405
+ jest.spyOn(mpc, 'enterYieldXyzYield').mockResolvedValue(
406
+ enterResponseWithTxs([
407
+ {
408
+ id: 'tx1',
409
+ network: 'eip155:1',
410
+ unsignedTransaction: { to: '0x1' },
411
+ },
412
+ ]),
413
+ )
414
+ jest.spyOn(mpc, 'trackYieldXyzTransaction').mockResolvedValue({})
415
+
416
+ const r = await yieldXyz.deposit({
417
+ yieldId: 'y',
418
+ amount: '1',
419
+ address: mockAddress,
420
+ })
421
+
422
+ expect(r.status).toBe('SUCCESS')
423
+ })
424
+
425
+ it('status=SUCCESS when all confirmations reach true', async () => {
426
+ const y = new YieldXyz({
427
+ mpc,
428
+ waitForConfirmation: jest.fn().mockResolvedValue(true),
429
+ })
430
+ y.setSignAndSendTransaction(jest.fn().mockResolvedValue('0xh'))
431
+ jest.spyOn(mpc, 'enterYieldXyzYield').mockResolvedValue(
432
+ enterResponseWithTxs([
433
+ {
434
+ id: 'tx1',
435
+ network: 'eip155:1',
436
+ unsignedTransaction: { to: '0x1' },
437
+ },
438
+ {
439
+ id: 'tx2',
440
+ network: 'eip155:1',
441
+ unsignedTransaction: { to: '0x2' },
442
+ },
443
+ ]),
444
+ )
445
+ jest.spyOn(mpc, 'trackYieldXyzTransaction').mockResolvedValue({})
446
+
447
+ const r = await y.deposit({ yieldId: 'y', amount: '1', address: mockAddress })
448
+
449
+ expect(r.status).toBe('SUCCESS')
450
+ expect(r.hashes).toHaveLength(2)
451
+ })
452
+
453
+ it('status=FAILED when first confirmation returns false', async () => {
454
+ const y = new YieldXyz({
455
+ mpc,
456
+ waitForConfirmation: jest.fn().mockResolvedValue(false),
457
+ })
458
+ y.setSignAndSendTransaction(jest.fn().mockResolvedValue('0xh'))
459
+ jest.spyOn(mpc, 'enterYieldXyzYield').mockResolvedValue(
460
+ enterResponseWithTxs([
461
+ {
462
+ id: 'tx1',
463
+ network: 'eip155:1',
464
+ unsignedTransaction: { to: '0x1' },
465
+ },
466
+ ]),
467
+ )
468
+ jest.spyOn(mpc, 'trackYieldXyzTransaction').mockResolvedValue({})
469
+
470
+ const r = await y.deposit({ yieldId: 'y', amount: '1', address: mockAddress })
471
+
472
+ expect(r.status).toBe('FAILED')
473
+ expect(r.hashes).toEqual(['0xh'])
474
+ })
475
+
476
+ it('status=PARTIAL_SUCCESS when first tx confirms but second fails', async () => {
477
+ const waiter = jest
478
+ .fn()
479
+ .mockResolvedValueOnce(true)
480
+ .mockResolvedValueOnce(false)
481
+ const y = new YieldXyz({
482
+ mpc,
483
+ waitForConfirmation: waiter,
484
+ })
485
+ const signer = jest
486
+ .fn()
487
+ .mockResolvedValueOnce('0xh1')
488
+ .mockResolvedValueOnce('0xh2')
489
+ y.setSignAndSendTransaction(signer)
490
+ jest.spyOn(mpc, 'enterYieldXyzYield').mockResolvedValue(
491
+ enterResponseWithTxs([
492
+ {
493
+ id: 'tx1',
494
+ network: 'eip155:1',
495
+ unsignedTransaction: { to: '0x1' },
496
+ },
497
+ {
498
+ id: 'tx2',
499
+ network: 'eip155:1',
500
+ unsignedTransaction: { to: '0x2' },
501
+ },
502
+ ]),
503
+ )
504
+ jest.spyOn(mpc, 'trackYieldXyzTransaction').mockResolvedValue({})
505
+
506
+ const r = await y.deposit({ yieldId: 'y', amount: '1', address: mockAddress })
507
+
508
+ expect(r.status).toBe('PARTIAL_SUCCESS')
509
+ expect(r.hashes).toEqual(['0xh1', '0xh2'])
510
+ expect(signer).toHaveBeenCalledTimes(2)
511
+ expect(waiter).toHaveBeenCalledTimes(2)
512
+ })
513
+
514
+ it('withdraw: status=FAILED when confirmation returns false', async () => {
515
+ const y = new YieldXyz({
516
+ mpc,
517
+ waitForConfirmation: jest.fn().mockResolvedValue(false),
518
+ })
519
+ y.setSignAndSendTransaction(jest.fn().mockResolvedValue('0xh'))
520
+ jest.spyOn(mpc, 'exitYieldXyzYield').mockResolvedValue(
521
+ exitResponseWithTxs([
522
+ {
523
+ id: 'tx1',
524
+ network: 'eip155:1',
525
+ unsignedTransaction: { to: '0x1' },
526
+ },
527
+ ]),
528
+ )
529
+ jest.spyOn(mpc, 'trackYieldXyzTransaction').mockResolvedValue({})
530
+
531
+ const r = await y.withdraw({ yieldId: 'y', amount: '1', address: mockAddress })
532
+
533
+ expect(r.status).toBe('FAILED')
534
+ expect(r.hashes).toEqual(['0xh'])
535
+ })
403
536
  })
@@ -11,6 +11,7 @@ import type {
11
11
  YieldDepositResult,
12
12
  YieldSubmitOptions,
13
13
  YieldSubmitProgress,
14
+ YieldSubmitResultStatus,
14
15
  YieldWithdrawParams,
15
16
  YieldWithdrawResult,
16
17
  YieldXyzActionTransaction,
@@ -369,9 +370,10 @@ export default class YieldXyz {
369
370
  : {}),
370
371
  }
371
372
 
372
- sdkLogger.info(`${LOG_PREFIX} deposit: success`, {
373
+ sdkLogger.info(`${LOG_PREFIX} deposit: complete`, {
373
374
  hashes: result.hashes,
374
375
  yieldId: result.yieldId,
376
+ status: result.status,
375
377
  yieldOpportunityDetails: result.yieldOpportunityDetails,
376
378
  })
377
379
  return result
@@ -439,9 +441,10 @@ export default class YieldXyz {
439
441
  : {}),
440
442
  }
441
443
 
442
- sdkLogger.info(`${LOG_PREFIX} withdraw: success`, {
444
+ sdkLogger.info(`${LOG_PREFIX} withdraw: complete`, {
443
445
  hashes: result.hashes,
444
446
  yieldId: result.yieldId,
447
+ status: result.status,
445
448
  yieldOpportunityDetails: result.yieldOpportunityDetails,
446
449
  })
447
450
  return result
@@ -600,7 +603,7 @@ export default class YieldXyz {
600
603
  ): Promise<
601
604
  Pick<
602
605
  YieldDepositResult,
603
- 'hashes' | 'yieldId' | 'yieldOpportunityDetails'
606
+ 'hashes' | 'yieldId' | 'yieldOpportunityDetails' | 'status'
604
607
  >
605
608
  > {
606
609
  const transactions = this.extractTransactions(response)
@@ -617,6 +620,7 @@ export default class YieldXyz {
617
620
  const total = transactions.length
618
621
  const hashes: string[] = []
619
622
  let confirmationsReached = 0
623
+ let confirmationsRequired = waitForConfirmation ? total : 0
620
624
 
621
625
  for (let index = 0; index < transactions.length; index++) {
622
626
  const tx = transactions[index]
@@ -838,17 +842,35 @@ export default class YieldXyz {
838
842
  const yieldId = rawResponse?.yieldId ?? ''
839
843
  const yieldOpportunityDetails = this.buildYieldOpportunityDetails(response)
840
844
 
845
+ // Determine status based on confirmation semantics from docs:
846
+ // - If no confirmations required (no waitForConfirmation): SUCCESS
847
+ // - If all required confirmations reached: SUCCESS
848
+ // - If some confirmations reached but not all: PARTIAL_SUCCESS
849
+ // - If confirmations required but zero reached: FAILED
850
+ let status: YieldSubmitResultStatus
851
+ if (confirmationsRequired === 0) {
852
+ status = 'SUCCESS'
853
+ } else if (confirmationsReached === confirmationsRequired) {
854
+ status = 'SUCCESS'
855
+ } else if (confirmationsReached > 0) {
856
+ status = 'PARTIAL_SUCCESS'
857
+ } else {
858
+ status = 'FAILED'
859
+ }
860
+
841
861
  sdkLogger.info(`${LOG_PREFIX} ${method}: executeAndTrack complete`, {
842
862
  yieldId,
843
863
  hashCount: hashes.length,
844
- confirmationsReached:
845
- confirmationsReached > 0 ? confirmationsReached : undefined,
864
+ confirmationsReached,
865
+ confirmationsRequired,
866
+ status,
846
867
  })
847
868
 
848
869
  return {
849
870
  hashes,
850
871
  yieldId,
851
872
  yieldOpportunityDetails,
873
+ status,
852
874
  }
853
875
  }
854
876
  }
package/src/mpc/index.ts CHANGED
@@ -134,7 +134,7 @@ import {
134
134
  } from '../../hypernative'
135
135
  import { generateTraceId } from '../shared/trace'
136
136
 
137
- const WEB_SDK_VERSION = '3.14.0-alpha.0'
137
+ const WEB_SDK_VERSION = '3.14.0'
138
138
 
139
139
  class Mpc {
140
140
  public iframe?: HTMLIFrameElement
@@ -1288,7 +1288,9 @@ class Mpc {
1288
1288
  ): Promise<RpcProxyResponse> {
1289
1289
  const { timeoutMs = 30_000, traceId } = options ?? {}
1290
1290
  const requestId =
1291
- crypto.randomUUID?.() ?? `${Date.now()}-${Math.random()}`
1291
+ typeof crypto !== 'undefined' && crypto.randomUUID
1292
+ ? crypto.randomUUID()
1293
+ : `${Date.now()}-${Math.random()}`
1292
1294
  const resolvedTraceId = traceId ?? generateTraceId()
1293
1295
 
1294
1296
  sdkLogger.debug('[Portal] rpcRequest', {
@@ -877,6 +877,11 @@ export type YieldDepositParams =
877
877
  */
878
878
  export type YieldWithdrawParams = YieldDepositParams
879
879
 
880
+ /**
881
+ * Result status for deposit/withdraw operations
882
+ */
883
+ export type YieldSubmitResultStatus = 'SUCCESS' | 'PARTIAL_SUCCESS' | 'FAILED'
884
+
880
885
  /**
881
886
  * Options for deposit/withdraw high-level methods (e.g. onProgress).
882
887
  */
@@ -933,6 +938,7 @@ export interface YieldDepositResult {
933
938
  yieldId: string
934
939
  chain?: string
935
940
  token?: string
941
+ status: YieldSubmitResultStatus
936
942
  yieldOpportunityDetails: {
937
943
  yieldId: string
938
944
  intent?: YieldXyzActionIntent
@@ -952,6 +958,7 @@ export interface YieldWithdrawResult {
952
958
  yieldId: string
953
959
  chain?: string
954
960
  token?: string
961
+ status: YieldSubmitResultStatus
955
962
  yieldOpportunityDetails: {
956
963
  yieldId: string
957
964
  intent?: YieldXyzActionIntent
@@ -14,9 +14,9 @@ export interface ZeroExQuoteRequest {
14
14
  sellEntireBalance?: 'true' | 'false'
15
15
  }
16
16
 
17
- export interface ZeroExQuoteResponse {
17
+ interface ZeroExQuoteSuccess {
18
18
  /** When set (non-empty after trim), do not execute the quote (fail closed). */
19
- error?: string
19
+ error?: never
20
20
  data: {
21
21
  rawResponse: {
22
22
  message?: string
@@ -96,6 +96,22 @@ export interface ZeroExQuoteResponse {
96
96
  }
97
97
  }
98
98
 
99
+ interface ZeroExQuoteError {
100
+ /** When set (non-empty after trim), do not execute the quote (fail closed). */
101
+ error: string
102
+ data?: never
103
+ metadata?: {
104
+ buyToken: string
105
+ sellToken: string
106
+ sellAmount: string
107
+ chainId: string
108
+ clientId: string
109
+ clientEip155Address: string
110
+ }
111
+ }
112
+
113
+ export type ZeroExQuoteResponse = ZeroExQuoteSuccess | ZeroExQuoteError
114
+
99
115
  export interface ZeroExSourcesRequest {
100
116
  chainId: string
101
117
  }