@portal-hq/web 3.14.0-alpha.0 → 3.15.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.
@@ -316,6 +316,9 @@ class Portal {
316
316
  yield this.storedClientBackupShare(true, BackupMethods.passkey);
317
317
  });
318
318
  }
319
+ configureFirebaseStorage(options) {
320
+ this.mpc.configureFirebaseStorage(options);
321
+ }
319
322
  backupWallet(backupMethod, progress = () => {
320
323
  // Noop
321
324
  }, backupConfigs = {}) {
@@ -1144,6 +1147,7 @@ var BackupMethods;
1144
1147
  BackupMethods["password"] = "PASSWORD";
1145
1148
  BackupMethods["passkey"] = "PASSKEY";
1146
1149
  BackupMethods["custom"] = "CUSTOM";
1150
+ BackupMethods["firebase"] = "FIREBASE";
1147
1151
  BackupMethods["unknown"] = "UNKNOWN";
1148
1152
  })(BackupMethods = exports.BackupMethods || (exports.BackupMethods = {}));
1149
1153
  var GetTransactionsOrder;
@@ -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.15.0-alpha.2';
18
18
  class Mpc {
19
19
  get ready() {
20
20
  return this._ready;
@@ -25,6 +25,47 @@ class Mpc {
25
25
  constructor({ portal }) {
26
26
  this._ready = false;
27
27
  this.presignatureLogHandler = null;
28
+ this.boundFirebaseTokenBridge = (event) => {
29
+ var _a;
30
+ const { origin } = event;
31
+ if (origin !== this.getOrigin()) {
32
+ return;
33
+ }
34
+ if (event.source !== ((_a = this.iframe) === null || _a === void 0 ? void 0 : _a.contentWindow)) {
35
+ return;
36
+ }
37
+ const { type, data } = event.data || {};
38
+ if (type !== 'portal:firebase:requestToken') {
39
+ return;
40
+ }
41
+ const requestId = data === null || data === void 0 ? void 0 : data.requestId;
42
+ if (!requestId) {
43
+ return;
44
+ }
45
+ const getter = this.firebaseGetToken;
46
+ void (() => __awaiter(this, void 0, void 0, function* () {
47
+ try {
48
+ if (!getter) {
49
+ throw new Error('Firebase storage is not configured (getToken missing)');
50
+ }
51
+ const token = yield getter({
52
+ forceRefresh: (data === null || data === void 0 ? void 0 : data.forceRefresh) === true,
53
+ });
54
+ this.postMessage({
55
+ type: 'portal:firebase:requestTokenResult',
56
+ data: { requestId, token },
57
+ });
58
+ }
59
+ catch (e) {
60
+ const message = (e instanceof Error ? e.message : String(e)) ||
61
+ 'Unknown firebase token error';
62
+ this.postMessage({
63
+ type: 'portal:firebase:requestTokenError',
64
+ data: { requestId, message },
65
+ });
66
+ }
67
+ }))();
68
+ };
28
69
  this.configureIframe = () => {
29
70
  const config = {
30
71
  apiKey: this.portal.apiKey,
@@ -54,6 +95,14 @@ class Mpc {
54
95
  this.configureIframe = this.configureIframe.bind(this);
55
96
  // Create the iFrame for MPC operations
56
97
  this.appendIframe();
98
+ window.addEventListener('message', this.boundFirebaseTokenBridge);
99
+ }
100
+ configureFirebaseStorage(options) {
101
+ this.firebaseGetToken = options.getToken;
102
+ this.postMessage({
103
+ type: 'portal:firebase:configure',
104
+ data: { tbsHost: options.tbsHost },
105
+ });
57
106
  }
58
107
  /*******************************
59
108
  * Wallet Methods
@@ -1011,10 +1060,11 @@ class Mpc {
1011
1060
  });
1012
1061
  }
1013
1062
  rpcRequest(data, options) {
1014
- var _a, _b;
1015
1063
  return __awaiter(this, void 0, void 0, function* () {
1016
1064
  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()}`;
1065
+ const requestId = typeof crypto !== 'undefined' && crypto.randomUUID
1066
+ ? crypto.randomUUID()
1067
+ : `${Date.now()}-${Math.random()}`;
1018
1068
  const resolvedTraceId = traceId !== null && traceId !== void 0 ? traceId : (0, trace_1.generateTraceId)();
1019
1069
  logger_1.sdkLogger.debug('[Portal] rpcRequest', {
1020
1070
  requestId,
@@ -30,6 +30,53 @@ describe('Mpc', () => {
30
30
  portal: portal_1.default,
31
31
  });
32
32
  });
33
+ describe('configureFirebaseStorage', () => {
34
+ it('posts configure to iframe and bridges token requests from iframe', () => __awaiter(void 0, void 0, void 0, function* () {
35
+ const postSpy = jest
36
+ .spyOn(mpc.iframe.contentWindow, 'postMessage')
37
+ .mockImplementation(() => { });
38
+ mpc.configureFirebaseStorage({
39
+ getToken: (opts) => __awaiter(void 0, void 0, void 0, function* () { return (opts === null || opts === void 0 ? void 0 : opts.forceRefresh) ? 'firebase-jwt-refreshed' : 'firebase-jwt'; }),
40
+ tbsHost: 'backup.web.portalhq.io',
41
+ });
42
+ expect(postSpy).toHaveBeenNthCalledWith(1, {
43
+ type: 'portal:firebase:configure',
44
+ data: { tbsHost: 'backup.web.portalhq.io' },
45
+ }, mockHostOrigin);
46
+ const bridge = mpc
47
+ .boundFirebaseTokenBridge;
48
+ bridge(new MessageEvent('message', {
49
+ origin: mockHostOrigin,
50
+ source: mpc.iframe.contentWindow,
51
+ data: {
52
+ type: 'portal:firebase:requestToken',
53
+ data: { requestId: 'r1' },
54
+ },
55
+ }));
56
+ yield new Promise((resolve) => {
57
+ setTimeout(resolve, 0);
58
+ });
59
+ expect(postSpy).toHaveBeenNthCalledWith(2, {
60
+ type: 'portal:firebase:requestTokenResult',
61
+ data: { requestId: 'r1', token: 'firebase-jwt' },
62
+ }, mockHostOrigin);
63
+ bridge(new MessageEvent('message', {
64
+ origin: mockHostOrigin,
65
+ source: mpc.iframe.contentWindow,
66
+ data: {
67
+ type: 'portal:firebase:requestToken',
68
+ data: { requestId: 'r2', forceRefresh: true },
69
+ },
70
+ }));
71
+ yield new Promise((resolve) => {
72
+ setTimeout(resolve, 0);
73
+ });
74
+ expect(postSpy).toHaveBeenNthCalledWith(3, {
75
+ type: 'portal:firebase:requestTokenResult',
76
+ data: { requestId: 'r2', token: 'firebase-jwt-refreshed' },
77
+ }, mockHostOrigin);
78
+ }));
79
+ });
33
80
  describe('backup', () => {
34
81
  const args = {
35
82
  backupMethod: index_1.BackupMethods.password,
@@ -267,7 +314,7 @@ describe('Mpc', () => {
267
314
  })
268
315
  .catch((e) => {
269
316
  expect(e).toBeInstanceOf(Error);
270
- expect(e.message).toEqual('Invalid backup method: INVALID_METHOD. Valid methods are: GDRIVE, PASSWORD, PASSKEY, CUSTOM, UNKNOWN');
317
+ expect(e.message).toEqual('Invalid backup method: INVALID_METHOD. Valid methods are: GDRIVE, PASSWORD, PASSKEY, CUSTOM, FIREBASE, UNKNOWN');
271
318
  done();
272
319
  });
273
320
  });
@@ -58,6 +58,7 @@ var RequestMethod;
58
58
  RequestMethod["eth_sendTransaction"] = "eth_sendTransaction";
59
59
  RequestMethod["eth_sign"] = "eth_sign";
60
60
  RequestMethod["eth_signTransaction"] = "eth_signTransaction";
61
+ RequestMethod["eth_signUserOperation"] = "eth_signUserOperation";
61
62
  RequestMethod["eth_signTypedData"] = "eth_signTypedData";
62
63
  RequestMethod["eth_signTypedData_v3"] = "eth_signTypedData_v3";
63
64
  RequestMethod["eth_signTypedData_v4"] = "eth_signTypedData_v4";
@@ -144,6 +145,7 @@ const signerMethods = [
144
145
  RequestMethod.eth_sendTransaction,
145
146
  RequestMethod.eth_sign,
146
147
  RequestMethod.eth_signTransaction,
148
+ RequestMethod.eth_signUserOperation,
147
149
  RequestMethod.eth_signTypedData_v3,
148
150
  RequestMethod.eth_signTypedData_v4,
149
151
  RequestMethod.personal_sign,
@@ -447,6 +449,7 @@ class Provider {
447
449
  case RequestMethod.eth_sendTransaction:
448
450
  case RequestMethod.eth_sign:
449
451
  case RequestMethod.eth_signTransaction:
452
+ case RequestMethod.eth_signUserOperation:
450
453
  case RequestMethod.eth_signTypedData_v3:
451
454
  case RequestMethod.eth_signTypedData_v4:
452
455
  case RequestMethod.personal_sign: {
@@ -206,6 +206,53 @@ describe('Provider', () => {
206
206
  });
207
207
  }));
208
208
  });
209
+ describe('eth_signUserOperation', () => {
210
+ const mockUserOperation = {
211
+ sender: '0x1234567890123456789012345678901234567890',
212
+ callData: '0x',
213
+ nonce: '0x0',
214
+ maxFeePerGas: '0x3B9ACA00',
215
+ maxPriorityFeePerGas: '0x3B9ACA00',
216
+ signature: '0x',
217
+ };
218
+ it('should throw an error if no chainId is provided alongside eth_signUserOperation', () => __awaiter(void 0, void 0, void 0, function* () {
219
+ expect(provider.request({
220
+ method: __1.RequestMethod.eth_signUserOperation,
221
+ params: [mockUserOperation],
222
+ })).rejects.toThrow(new Error(`[PortalProvider] Chain ID is required for the operation`));
223
+ }));
224
+ it('should throw an error if malformed chainId is provided alongside eth_signUserOperation', () => __awaiter(void 0, void 0, void 0, function* () {
225
+ const chainId = 'unsupported:chain';
226
+ expect(provider.request({
227
+ chainId,
228
+ method: __1.RequestMethod.eth_signUserOperation,
229
+ params: [mockUserOperation],
230
+ })).rejects.toThrow(new Error(`[PortalProvider] Chain ID must be prefixed with "eip155:" for the operation, got ${chainId}`));
231
+ }));
232
+ it('should successfully handle an eth_signUserOperation request', () => __awaiter(void 0, void 0, void 0, function* () {
233
+ const result = yield provider.request({
234
+ chainId: 'eip155:1',
235
+ method: __1.RequestMethod.eth_signUserOperation,
236
+ params: [mockUserOperation],
237
+ signatureApprovalMemo: 'Test eth_signUserOperation',
238
+ });
239
+ expect(result).toEqual(constants_1.mockSignedHash);
240
+ expect(portal_1.default.mpc.sign).toHaveBeenCalledWith({
241
+ chainId: 'eip155:1',
242
+ method: __1.RequestMethod.eth_signUserOperation,
243
+ params: mockUserOperation,
244
+ rpcUrl: constants_1.mockRpcUrl,
245
+ signatureApprovalMemo: 'Test eth_signUserOperation',
246
+ traceId: 'mock-trace-id-12345',
247
+ });
248
+ expect(provider.emit).toHaveBeenCalledWith('portal_signatureReceived', {
249
+ chainId: 'eip155:1',
250
+ method: __1.RequestMethod.eth_signUserOperation,
251
+ params: [mockUserOperation],
252
+ signature: result,
253
+ });
254
+ }));
255
+ });
209
256
  describe('eth_sign', () => {
210
257
  it('should throw an error if no chainId is provided alongside eth_sign', () => __awaiter(void 0, void 0, void 0, function* () {
211
258
  expect(provider.request({
@@ -674,6 +721,69 @@ describe('Provider', () => {
674
721
  expect(mockConsoleWarn).toHaveBeenCalledWith("[PortalProvider] Request for signing method 'eth_signTransaction' could not be completed because it was not approved by the user.");
675
722
  }));
676
723
  });
724
+ describe('eth_signUserOperation', () => {
725
+ const mockUserOperation = {
726
+ sender: '0x1234567890123456789012345678901234567890',
727
+ callData: '0x',
728
+ nonce: '0x0',
729
+ maxFeePerGas: '0x3B9ACA00',
730
+ maxPriorityFeePerGas: '0x3B9ACA00',
731
+ signature: '0x',
732
+ };
733
+ it('should successfully handle an approved eth_signUserOperation request', () => __awaiter(void 0, void 0, void 0, function* () {
734
+ provider.on('portal_signingRequested', () => {
735
+ provider.emit('portal_signingApproved', {
736
+ method: __1.RequestMethod.eth_signUserOperation,
737
+ params: [mockUserOperation],
738
+ });
739
+ });
740
+ const result = yield provider.request({
741
+ chainId: 'eip155:1',
742
+ method: __1.RequestMethod.eth_signUserOperation,
743
+ params: [mockUserOperation],
744
+ signatureApprovalMemo: 'Test eth_signUserOperation',
745
+ });
746
+ expect(mockSigningRequestedHandler).toHaveBeenCalledWith({
747
+ method: __1.RequestMethod.eth_signUserOperation,
748
+ params: [mockUserOperation],
749
+ signatureApprovalMemo: 'Test eth_signUserOperation',
750
+ });
751
+ expect(result).toEqual(constants_1.mockSignedHash);
752
+ expect(portal_1.default.mpc.sign).toHaveBeenCalledWith({
753
+ chainId: 'eip155:1',
754
+ method: __1.RequestMethod.eth_signUserOperation,
755
+ params: mockUserOperation,
756
+ rpcUrl: constants_1.mockRpcUrl,
757
+ signatureApprovalMemo: 'Test eth_signUserOperation',
758
+ traceId: 'mock-trace-id-12345',
759
+ });
760
+ expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({
761
+ chainId: 'eip155:1',
762
+ method: __1.RequestMethod.eth_signUserOperation,
763
+ params: [mockUserOperation],
764
+ signature: result,
765
+ });
766
+ }));
767
+ it('should successfully handle a rejected eth_signUserOperation request', () => __awaiter(void 0, void 0, void 0, function* () {
768
+ provider.on('portal_signingRequested', () => {
769
+ provider.emit('portal_signingRejected', {
770
+ method: __1.RequestMethod.eth_signUserOperation,
771
+ params: [mockUserOperation],
772
+ });
773
+ });
774
+ const result = yield provider.request({
775
+ chainId: 'eip155:1',
776
+ method: __1.RequestMethod.eth_signUserOperation,
777
+ params: [mockUserOperation],
778
+ });
779
+ expect(mockSigningRequestedHandler).toHaveBeenCalledWith({
780
+ method: __1.RequestMethod.eth_signUserOperation,
781
+ params: [mockUserOperation],
782
+ });
783
+ expect(result).toEqual(undefined);
784
+ expect(mockConsoleWarn).toHaveBeenCalledWith("[PortalProvider] Request for signing method 'eth_signUserOperation' could not be completed because it was not approved by the user.");
785
+ }));
786
+ });
677
787
  describe('eth_sign', () => {
678
788
  it('should successfully handle an approved eth_sign request', () => __awaiter(void 0, void 0, void 0, function* () {
679
789
  provider.on('portal_signingRequested', () => {
package/lib/esm/index.js CHANGED
@@ -287,6 +287,9 @@ class Portal {
287
287
  yield this.storedClientBackupShare(true, BackupMethods.passkey);
288
288
  });
289
289
  }
290
+ configureFirebaseStorage(options) {
291
+ this.mpc.configureFirebaseStorage(options);
292
+ }
290
293
  backupWallet(backupMethod, progress = () => {
291
294
  // Noop
292
295
  }, backupConfigs = {}) {
@@ -1111,6 +1114,7 @@ export var BackupMethods;
1111
1114
  BackupMethods["password"] = "PASSWORD";
1112
1115
  BackupMethods["passkey"] = "PASSKEY";
1113
1116
  BackupMethods["custom"] = "CUSTOM";
1117
+ BackupMethods["firebase"] = "FIREBASE";
1114
1118
  BackupMethods["unknown"] = "UNKNOWN";
1115
1119
  })(BackupMethods || (BackupMethods = {}));
1116
1120
  export var GetTransactionsOrder;
@@ -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,