@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.
- package/lib/commonjs/index.js +4 -0
- package/lib/commonjs/integrations/trading/lifi/index.js +22 -1
- package/lib/commonjs/integrations/yield/yieldxyz.highLevel.test.js +108 -0
- package/lib/commonjs/integrations/yield/yieldxyz.js +27 -3
- package/lib/commonjs/mpc/index.js +53 -3
- package/lib/commonjs/mpc/index.test.js +48 -1
- package/lib/commonjs/provider/index.js +3 -0
- package/lib/commonjs/provider/index.test.js +110 -0
- package/lib/esm/index.js +4 -0
- package/lib/esm/integrations/trading/lifi/index.js +22 -1
- package/lib/esm/integrations/yield/yieldxyz.highLevel.test.js +108 -0
- package/lib/esm/integrations/yield/yieldxyz.js +27 -3
- package/lib/esm/mpc/index.js +53 -3
- package/lib/esm/mpc/index.test.js +48 -1
- package/lib/esm/provider/index.js +3 -0
- package/lib/esm/provider/index.test.js +110 -0
- package/package.json +2 -2
- package/src/index.ts +9 -0
- package/src/integrations/trading/lifi/index.ts +34 -1
- package/src/integrations/yield/yieldxyz.highLevel.test.ts +133 -0
- package/src/integrations/yield/yieldxyz.ts +27 -5
- package/src/mpc/index.test.ts +70 -1
- package/src/mpc/index.ts +62 -2
- package/src/provider/index.test.ts +137 -0
- package/src/provider/index.ts +3 -0
- package/src/shared/types/common.ts +7 -1
- package/src/shared/types/yieldxyz.ts +7 -0
- package/src/shared/types/zero-x.ts +18 -2
- package/types.d.ts +5 -0
package/lib/commonjs/index.js
CHANGED
|
@@ -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:
|
|
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:
|
|
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
|
|
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.
|
|
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 =
|
|
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,
|