@portal-hq/web 3.6.2-alpha → 3.7.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 (45) hide show
  1. package/hypernative.d.ts +346 -0
  2. package/lib/commonjs/index.js +144 -2
  3. package/lib/commonjs/index.test.js +119 -2
  4. package/lib/commonjs/integrations/security/hypernative/index.js +101 -0
  5. package/lib/commonjs/integrations/security/hypernative/index.test.js +151 -0
  6. package/lib/commonjs/integrations/security/index.js +16 -0
  7. package/lib/commonjs/integrations/trading/zero-x/index.js +17 -4
  8. package/lib/commonjs/integrations/trading/zero-x/index.test.js +61 -15
  9. package/lib/commonjs/mpc/index.js +156 -5
  10. package/lib/commonjs/mpc/index.test.js +794 -5
  11. package/lib/commonjs/passkeys/index.js +394 -0
  12. package/lib/commonjs/passkeys/types.js +2 -0
  13. package/lib/commonjs/provider/index.js +5 -2
  14. package/lib/esm/index.js +144 -2
  15. package/lib/esm/index.test.js +119 -2
  16. package/lib/esm/integrations/security/hypernative/index.js +98 -0
  17. package/lib/esm/integrations/security/hypernative/index.test.js +146 -0
  18. package/lib/esm/integrations/security/index.js +10 -0
  19. package/lib/esm/integrations/trading/zero-x/index.js +17 -4
  20. package/lib/esm/integrations/trading/zero-x/index.test.js +62 -16
  21. package/lib/esm/mpc/index.js +156 -5
  22. package/lib/esm/mpc/index.test.js +795 -6
  23. package/lib/esm/passkeys/index.js +390 -0
  24. package/lib/esm/passkeys/types.js +1 -0
  25. package/lib/esm/provider/index.js +5 -2
  26. package/lifi-types.d.ts +1236 -0
  27. package/package.json +6 -3
  28. package/src/__mocks/constants.ts +422 -5
  29. package/src/__mocks/portal/mpc.ts +1 -0
  30. package/src/index.test.ts +179 -3
  31. package/src/index.ts +212 -4
  32. package/src/integrations/security/hypernative/index.test.ts +196 -0
  33. package/src/integrations/security/hypernative/index.ts +106 -0
  34. package/src/integrations/security/index.ts +14 -0
  35. package/src/integrations/trading/zero-x/index.test.ts +98 -19
  36. package/src/integrations/trading/zero-x/index.ts +29 -9
  37. package/src/mpc/index.test.ts +944 -7
  38. package/src/mpc/index.ts +200 -10
  39. package/src/passkeys/index.ts +536 -0
  40. package/src/passkeys/types.ts +78 -0
  41. package/src/provider/index.ts +5 -0
  42. package/tsconfig.json +7 -1
  43. package/types.d.ts +45 -12
  44. package/yieldxyz-types.d.ts +778 -0
  45. package/zero-x.d.ts +204 -0
@@ -0,0 +1,346 @@
1
+ export type ScreenAddressRequestOptions = {
2
+ screenerPolicyId?: string
3
+ }
4
+
5
+ export interface ScreenAddressApiResponse {
6
+ data: {
7
+ rawResponse: ScreenAddressResponse[]
8
+ }
9
+ }
10
+
11
+ export interface ScanEVMRequest {
12
+ transaction: TransactionObject
13
+ url?: string
14
+ blockNumber?: number
15
+ validateNonce?: boolean
16
+ showFullFindings?: boolean
17
+ policy?: string
18
+ }
19
+
20
+ export interface ScanEVMResponse {
21
+ data: {
22
+ rawResponse: {
23
+ success: boolean
24
+ data: TransactionRiskData
25
+ error: string | null
26
+ version?: string
27
+ service?: string
28
+ }
29
+ }
30
+ }
31
+
32
+ export interface ScanEip712Request {
33
+ walletAddress: string
34
+ chainId: string
35
+ eip712Message: Eip712TypedData
36
+ showFullFindings?: boolean
37
+ policy?: string
38
+ }
39
+
40
+ export interface ScanEip712Response {
41
+ data: {
42
+ rawResponse: {
43
+ success: boolean
44
+ data: TypedMessageRiskData
45
+ error: string | null
46
+ version?: string
47
+ service?: string
48
+ }
49
+ }
50
+ }
51
+
52
+ export interface ScanSolanaRequest {
53
+ transaction: SolanaTransaction
54
+ url?: string
55
+ validateRecentBlockHash?: boolean
56
+ showFullFindings?: boolean
57
+ policy?: string
58
+ }
59
+
60
+ export interface ScanSolanaResponse {
61
+ data: {
62
+ rawResponse: {
63
+ success: boolean
64
+ data: SolanaTransactionRiskData
65
+ error: string | null
66
+ version?: string
67
+ service?: string
68
+ }
69
+ }
70
+ }
71
+
72
+ export type ScanNftsRequest = ScanNftRequestItem[]
73
+
74
+ export interface ScanNftsResponse {
75
+ data: {
76
+ rawResponse: {
77
+ success: boolean
78
+ data: {
79
+ nfts: ScanNftResponseItem[]
80
+ }
81
+ error: string | null
82
+ version?: string
83
+ service?: string
84
+ }
85
+ }
86
+ }
87
+
88
+ export type ScanTokensRequest = ScanTokenRequestItem[]
89
+
90
+ export interface ScanTokensResponse {
91
+ data: {
92
+ rawResponse: {
93
+ success: boolean
94
+ data: {
95
+ tokens: ScanTokenResponseItem[]
96
+ }
97
+ error: string | null
98
+ version?: string
99
+ service?: string
100
+ }
101
+ }
102
+ }
103
+
104
+ export type ScanUrlRequest = string
105
+
106
+ export interface ScanUrlResponse {
107
+ data: {
108
+ rawResponse: {
109
+ success: boolean
110
+ data: {
111
+ isMalicious: boolean
112
+ deepScanTriggered?: boolean
113
+ }
114
+ error: string | null
115
+ version?: string
116
+ service?: string
117
+ }
118
+ }
119
+ }
120
+
121
+ export interface ScreenAddressResponse {
122
+ address: string
123
+ recommendation: string
124
+ severity: string
125
+ totalIncomingUsd: number
126
+ policyId: string
127
+ timestamp: string
128
+ flags: Flag[]
129
+ }
130
+
131
+ export interface Flag {
132
+ title: string
133
+ flagId: string
134
+ chain: string
135
+ severity: string
136
+ lastUpdate?: string
137
+ events: HypernativeEvent[]
138
+ exposures: Exposure[]
139
+ }
140
+
141
+ export interface HypernativeEvent {
142
+ eventId: string
143
+ address: string
144
+ chain: string
145
+ flagId: string
146
+ timestampEvent: string
147
+ txHash: string
148
+ direction: string
149
+ hop: number
150
+ counterpartyAddress: string
151
+ counterpartyAlias?: string
152
+ counterpartyFlagId: string
153
+ tokenSymbol: string
154
+ tokenAmount: number
155
+ tokenUsdValue: number
156
+ reason: string
157
+ source: string
158
+ originalFlaggedAddress: string
159
+ originalFlaggedAlias?: string
160
+ originalFlaggedChain: string
161
+ }
162
+
163
+ export interface Exposure {
164
+ exposurePortion: number
165
+ exposureType?: string
166
+ totalExposureUsd: number
167
+ flaggedInteractions: FlaggedInteraction[]
168
+ }
169
+
170
+ export interface FlaggedInteraction {
171
+ address: string
172
+ chain: string
173
+ alias?: string
174
+ minHop: number
175
+ totalExposureUsd: number
176
+ }
177
+
178
+ export interface TransactionRiskData {
179
+ assessmentId?: string
180
+ assessmentTimestamp?: string
181
+ recommendation: 'accept' | 'notes' | 'warn' | 'deny' | 'autoAccept'
182
+ expectedStatus?: 'success' | 'fail'
183
+ findings?: Finding[]
184
+ involvedAssets?: Asset[]
185
+ balanceChanges?: Record<string, BalanceChange[]> | null
186
+ parsedActions?: Record<string, unknown[]>
187
+ blockNumber?: number
188
+ riIds?: string[]
189
+ signature?: string
190
+ }
191
+
192
+ export interface TypedMessageRiskData {
193
+ assessmentId?: string
194
+ assessmentTimestamp?: string
195
+ blockNumber?: number
196
+ recommendation: 'accept' | 'notes' | 'warn' | 'deny' | 'autoAccept'
197
+ findings?: Finding[]
198
+ involvedAssets?: Asset[]
199
+ parsedActions?: Record<string, unknown[]>
200
+ riIds?: string[]
201
+ }
202
+
203
+ export interface SolanaTransactionRiskData {
204
+ recommendation: 'accept' | 'notes' | 'warn' | 'deny' | 'autoAccept'
205
+ expectedStatus?: 'success' | 'fail'
206
+ findings?: Finding[]
207
+ involvedAssets?: Asset[]
208
+ balanceChanges?: Record<string, BalanceChange[]> | null
209
+ parsedActions?: Record<string, unknown[]>
210
+ blockNumber?: number
211
+ riIds?: string[]
212
+ }
213
+
214
+ export interface Finding {
215
+ typeId: string
216
+ title: string
217
+ description: string
218
+ details?: string
219
+ severity: 'Accept' | 'Notes' | 'Warn' | 'Deny' | 'AutoAccept'
220
+ relatedAssets?: Asset[]
221
+ }
222
+
223
+ export interface Asset {
224
+ chain: string
225
+ evmChainId?: string
226
+ address: string
227
+ type: 'Wallet' | 'Contract'
228
+ involvementTypes: string[]
229
+ tag: string
230
+ alias?: string
231
+ note?: string
232
+ }
233
+
234
+ export interface BalanceChange {
235
+ changeType: 'send' | 'receive'
236
+ tokenSymbol: string
237
+ tokenAddress?: string
238
+ usdValue?: string
239
+ amount: string
240
+ chain: string
241
+ evmChainId?: string
242
+ }
243
+
244
+ export interface ScanNftRequestItem {
245
+ address: string
246
+ chain?: string
247
+ evmChainId?: number | string
248
+ }
249
+
250
+ export interface ScanNftResponseItem {
251
+ address: string
252
+ chain: string
253
+ evmChainId: string
254
+ accept: boolean
255
+ }
256
+
257
+ export interface ScanTokenRequestItem {
258
+ address: string
259
+ chain?: string
260
+ evmChainId?: number | string
261
+ }
262
+
263
+ export interface ScanTokenResponseItem {
264
+ address: string
265
+ chain: string
266
+ reputation?: {
267
+ recommendation: 'accept' | 'deny'
268
+ }
269
+ }
270
+
271
+ export interface TransactionObject {
272
+ chain: string
273
+ fromAddress: string
274
+ toAddress: string
275
+ input?: string
276
+ value?: string | number
277
+ nonce?: string | number
278
+ hash?: string
279
+ gas?: string | number
280
+ gasPrice?: string | number
281
+ maxPriorityFeePerGas?: string | number
282
+ maxFeePerGas?: string | number
283
+ }
284
+
285
+ export interface Eip712TypeProperty {
286
+ name: string
287
+ type: string
288
+ }
289
+
290
+ export type Eip712Value =
291
+ | string
292
+ | number
293
+ | boolean
294
+ | null
295
+ | undefined
296
+ | Eip712Value[]
297
+ | { [key: string]: Eip712Value }
298
+
299
+ export interface Eip712Domain {
300
+ name?: string
301
+ version?: string
302
+ chainId?: number | string
303
+ verifyingContract?: string
304
+ salt?: string
305
+ [key: string]: Eip712Value
306
+ }
307
+
308
+ export interface Eip712TypedData {
309
+ primaryType: string
310
+ types: Record<string, Eip712TypeProperty[]>
311
+ domain: Eip712Domain
312
+ message: Record<string, Eip712Value>
313
+ }
314
+
315
+ export interface SolanaHeader {
316
+ numRequiredSignatures?: number
317
+ numReadonlySignedAccounts?: number
318
+ numReadonlyUnsignedAccounts?: number
319
+ }
320
+
321
+ export interface SolanaInstruction {
322
+ accounts: number[]
323
+ data: string
324
+ programIdIndex: number
325
+ }
326
+
327
+ export interface AddressTableLookup {
328
+ accountKey: string
329
+ writableIndexes: number[]
330
+ readonlyIndexes: number[]
331
+ }
332
+
333
+ export interface SolanaMessage {
334
+ accountKeys: string[]
335
+ header: SolanaHeader
336
+ instructions: SolanaInstruction[]
337
+ addressTableLookups?: AddressTableLookup[]
338
+ recentBlockhash: string
339
+ }
340
+
341
+ export interface SolanaTransaction {
342
+ message?: SolanaMessage
343
+ signatures?: string[]
344
+ rawTransaction?: string
345
+ version?: 'legacy' | '0'
346
+ }
@@ -41,6 +41,8 @@ const mpc_1 = __importDefault(require("./mpc"));
41
41
  const provider_1 = __importStar(require("./provider"));
42
42
  const yield_1 = __importDefault(require("./integrations/yield"));
43
43
  const trading_1 = __importDefault(require("./integrations/trading"));
44
+ const security_1 = __importDefault(require("./integrations/security"));
45
+ const passkeys_1 = __importDefault(require("./passkeys"));
44
46
  class Portal {
45
47
  get ready() {
46
48
  return this.mpc.ready;
@@ -85,6 +87,7 @@ class Portal {
85
87
  });
86
88
  this.yield = new yield_1.default({ mpc: this.mpc });
87
89
  this.trading = new trading_1.default({ mpc: this.mpc });
90
+ this.security = new security_1.default({ mpc: this.mpc });
88
91
  this.provider = new provider_1.default({
89
92
  portal: this,
90
93
  chainId: chainId ? Number(chainId) : undefined,
@@ -148,6 +151,98 @@ class Portal {
148
151
  return address;
149
152
  });
150
153
  }
154
+ generateBackupShare(progress = () => {
155
+ // Noop
156
+ }) {
157
+ return __awaiter(this, void 0, void 0, function* () {
158
+ const response = yield this.mpc.backup({
159
+ backupMethod: BackupMethods.custom,
160
+ backupConfigs: {},
161
+ host: this.host,
162
+ mpcVersion: this.mpcVersion,
163
+ featureFlags: this.featureFlags,
164
+ }, progress);
165
+ if (!response.encryptionKey) {
166
+ throw new Error('[Portal] Custom backup did not return an encryption key. Please ensure you are using the latest iframe bundle.');
167
+ }
168
+ return {
169
+ cipherText: response.cipherText,
170
+ encryptionKey: response.encryptionKey,
171
+ };
172
+ });
173
+ }
174
+ registerPasskeyAndStoreEncryptionKey(cipherText, encryptionKey, options = {}) {
175
+ return __awaiter(this, void 0, void 0, function* () {
176
+ const { service, customDomain, relyingPartyId, relyingPartyName } = this.resolvePasskeyOptions(options);
177
+ yield service.registerPasskeyAndStoreKey({
178
+ customDomain,
179
+ relyingPartyName,
180
+ encryptionKey,
181
+ relyingPartyId,
182
+ cipherText,
183
+ });
184
+ });
185
+ }
186
+ authenticatePasskeyAndRetrieveKey(options = {}) {
187
+ return __awaiter(this, void 0, void 0, function* () {
188
+ const { service, customDomain, relyingPartyId, relyingPartyName } = this.resolvePasskeyOptions(options);
189
+ return yield service.authenticatePasskeyAndRetrieveKey({
190
+ customDomain,
191
+ relyingPartyName,
192
+ relyingPartyId,
193
+ });
194
+ });
195
+ }
196
+ /**
197
+ * Register a passkey without tying it to an encryption key.
198
+ * The encryption key can be stored later using authenticatePasskeyAndWriteKey.
199
+ */
200
+ registerPasskey(options = {}) {
201
+ return __awaiter(this, void 0, void 0, function* () {
202
+ const { service, customDomain, relyingPartyId, relyingPartyName } = this.resolvePasskeyOptions(options);
203
+ yield service.registerPasskey({
204
+ customDomain,
205
+ relyingPartyName,
206
+ relyingPartyId,
207
+ });
208
+ });
209
+ }
210
+ /**
211
+ * Authenticate with passkey and store an encryption key.
212
+ * Used after registerPasskey to associate an encryption key with the passkey.
213
+ */
214
+ authenticatePasskeyAndWriteKey(encryptionKey, options = {}) {
215
+ return __awaiter(this, void 0, void 0, function* () {
216
+ const { service, customDomain, relyingPartyId, relyingPartyName } = this.resolvePasskeyOptions(options);
217
+ yield service.authenticatePasskeyAndWriteKey({
218
+ customDomain,
219
+ relyingPartyName,
220
+ relyingPartyId,
221
+ encryptionKey,
222
+ });
223
+ });
224
+ }
225
+ backupWithPasskey(options = {}, progress = () => {
226
+ // Noop
227
+ }) {
228
+ return __awaiter(this, void 0, void 0, function* () {
229
+ const { usePopup = true, customDomain, relyingPartyName, backupMethod = BackupMethods.passkey, } = options;
230
+ if (usePopup) {
231
+ yield this.backupWallet(backupMethod, progress, {});
232
+ return;
233
+ }
234
+ if (backupMethod !== BackupMethods.passkey) {
235
+ throw new Error(`[Portal] Direct passkey backup currently supports only BackupMethods.passkey (received ${backupMethod}).`);
236
+ }
237
+ const { cipherText, encryptionKey } = yield this.generateBackupShare(progress);
238
+ yield this.registerPasskeyAndStoreEncryptionKey(cipherText, encryptionKey, {
239
+ customDomain,
240
+ relyingPartyName,
241
+ usePopup,
242
+ });
243
+ yield this.storedClientBackupShare(true, BackupMethods.passkey);
244
+ });
245
+ }
151
246
  backupWallet(backupMethod, progress = () => {
152
247
  // Noop
153
248
  }, backupConfigs = {}) {
@@ -361,6 +456,7 @@ class Portal {
361
456
  chainId,
362
457
  method: 'eth_sendTransaction',
363
458
  params: [buildTxResponse.transaction],
459
+ sponsorGas: params.sponsorGas,
364
460
  });
365
461
  break;
366
462
  }
@@ -369,6 +465,7 @@ class Portal {
369
465
  chainId,
370
466
  method: 'sol_signAndSendTransaction',
371
467
  params: [buildTxResponse.transaction],
468
+ sponsorGas: params.sponsorGas,
372
469
  });
373
470
  break;
374
471
  }
@@ -711,7 +808,7 @@ class Portal {
711
808
  getQuote(apiKey, args, chainId) {
712
809
  var _a;
713
810
  return __awaiter(this, void 0, void 0, function* () {
714
- return (_a = this.mpc) === null || _a === void 0 ? void 0 : _a.getQuote(apiKey, args, chainId);
811
+ return (_a = this.mpc) === null || _a === void 0 ? void 0 : _a.getQuote(chainId, args, apiKey);
715
812
  });
716
813
  }
717
814
  /**
@@ -720,7 +817,7 @@ class Portal {
720
817
  getSources(apiKey, chainId) {
721
818
  var _a;
722
819
  return __awaiter(this, void 0, void 0, function* () {
723
- return (_a = this.mpc) === null || _a === void 0 ? void 0 : _a.getSources(apiKey, chainId);
820
+ return (_a = this.mpc) === null || _a === void 0 ? void 0 : _a.getSources(chainId, apiKey);
724
821
  });
725
822
  }
726
823
  /*******************************
@@ -808,6 +905,50 @@ class Portal {
808
905
  }
809
906
  return chainId;
810
907
  }
908
+ ensurePasskeyService() {
909
+ const defaultDomain = this.getDefaultPasskeyDomain();
910
+ if (!this.passkeyService ||
911
+ this.passkeyServiceDefaultDomain !== defaultDomain) {
912
+ this.passkeyService = new passkeys_1.default({
913
+ defaultDomain,
914
+ getJwt: () => this.mpc.getPasskeyJwt(),
915
+ });
916
+ this.passkeyServiceDefaultDomain = defaultDomain;
917
+ }
918
+ return this.passkeyService;
919
+ }
920
+ getDefaultPasskeyDomain() {
921
+ var _a, _b;
922
+ return (_b = (_a = this.passkeyConfig) === null || _a === void 0 ? void 0 : _a.webAuthnHost) !== null && _b !== void 0 ? _b : 'backup.web.portalhq.io';
923
+ }
924
+ extractRpId(domain) {
925
+ const targetDomain = domain !== null && domain !== void 0 ? domain : this.getDefaultPasskeyDomain();
926
+ try {
927
+ const normalized = targetDomain.startsWith('http')
928
+ ? targetDomain
929
+ : `https://${targetDomain}`;
930
+ return new URL(normalized).hostname;
931
+ }
932
+ catch (_a) {
933
+ return targetDomain;
934
+ }
935
+ }
936
+ resolvePasskeyOptions(options) {
937
+ var _a, _b, _c, _d, _e;
938
+ if (options.usePopup) {
939
+ throw new Error('[Portal] This method does not support the popup flow. Use usePopup: false.');
940
+ }
941
+ const service = this.ensurePasskeyService();
942
+ const domain = (_a = options.customDomain) !== null && _a !== void 0 ? _a : this.getDefaultPasskeyDomain();
943
+ const relyingPartyId = (_b = options.relyingPartyId) !== null && _b !== void 0 ? _b : this.extractRpId(domain);
944
+ const relyingPartyName = (_e = (_c = options.relyingPartyName) !== null && _c !== void 0 ? _c : (_d = this.passkeyConfig) === null || _d === void 0 ? void 0 : _d.relyingParty) !== null && _e !== void 0 ? _e : 'Portal';
945
+ return {
946
+ service,
947
+ customDomain: options.customDomain,
948
+ relyingPartyId,
949
+ relyingPartyName,
950
+ };
951
+ }
811
952
  }
812
953
  var mpc_2 = require("./mpc");
813
954
  Object.defineProperty(exports, "MpcError", { enumerable: true, get: function () { return mpc_2.MpcError; } });
@@ -833,6 +974,7 @@ var BackupMethods;
833
974
  BackupMethods["gdrive"] = "GDRIVE";
834
975
  BackupMethods["password"] = "PASSWORD";
835
976
  BackupMethods["passkey"] = "PASSKEY";
977
+ BackupMethods["custom"] = "CUSTOM";
836
978
  BackupMethods["unknown"] = "UNKNOWN";
837
979
  })(BackupMethods = exports.BackupMethods || (exports.BackupMethods = {}));
838
980
  var GetTransactionsOrder;
@@ -144,6 +144,123 @@ describe('Portal', () => {
144
144
  }, mockProgressFn);
145
145
  }));
146
146
  });
147
+ describe('generateBackupShare', () => {
148
+ it('should request a custom backup and return cipherText and encryptionKey', () => __awaiter(void 0, void 0, void 0, function* () {
149
+ const storageCallback = jest.fn().mockResolvedValue(undefined);
150
+ portal.mpc.backup.mockResolvedValueOnce({
151
+ cipherText: constants_1.mockCipherText,
152
+ encryptionKey: 'manual-key',
153
+ storageCallback,
154
+ });
155
+ const result = yield portal.generateBackupShare();
156
+ expect(result).toEqual({
157
+ cipherText: constants_1.mockCipherText,
158
+ encryptionKey: 'manual-key',
159
+ });
160
+ expect(portal.mpc.backup).toHaveBeenCalledWith({
161
+ backupMethod: _1.BackupMethods.custom,
162
+ backupConfigs: {},
163
+ host: 'web.portalhq.io',
164
+ mpcVersion: 'v6',
165
+ featureFlags: {},
166
+ }, expect.any(Function));
167
+ }));
168
+ it('should throw if the iframe response does not contain an encryption key', () => __awaiter(void 0, void 0, void 0, function* () {
169
+ ;
170
+ portal.mpc.backup.mockResolvedValueOnce({
171
+ cipherText: constants_1.mockCipherText,
172
+ storageCallback: jest.fn(),
173
+ });
174
+ yield expect(portal.generateBackupShare()).rejects.toThrow('Custom backup did not return an encryption key');
175
+ }));
176
+ });
177
+ describe('registerPasskeyAndStoreEncryptionKey', () => {
178
+ it('should delegate to the passkey service with computed relying party data', () => __awaiter(void 0, void 0, void 0, function* () {
179
+ const passkeyServiceMock = {
180
+ registerPasskeyAndStoreKey: jest.fn().mockResolvedValue(undefined),
181
+ };
182
+ portal.passkeyService = passkeyServiceMock;
183
+ portal.passkeyServiceDefaultDomain = 'backup.web.portalhq.io';
184
+ portal.passkeyServiceApiKey = portal.apiKey;
185
+ yield portal.registerPasskeyAndStoreEncryptionKey(constants_1.mockCipherText, 'manual-key');
186
+ expect(passkeyServiceMock.registerPasskeyAndStoreKey).toHaveBeenCalledTimes(1);
187
+ expect(passkeyServiceMock.registerPasskeyAndStoreKey).toHaveBeenCalledWith(expect.objectContaining({
188
+ customDomain: undefined,
189
+ encryptionKey: 'manual-key',
190
+ relyingPartyId: 'backup.web.portalhq.io',
191
+ relyingPartyName: 'Portal',
192
+ cipherText: constants_1.mockCipherText,
193
+ }));
194
+ }));
195
+ it('should throw when usePopup is requested', () => __awaiter(void 0, void 0, void 0, function* () {
196
+ yield expect(portal.registerPasskeyAndStoreEncryptionKey(constants_1.mockCipherText, 'manual-key', { usePopup: true })).rejects.toThrow('does not support the popup flow');
197
+ }));
198
+ });
199
+ describe('authenticatePasskeyAndRetrieveKey', () => {
200
+ it('should invoke the passkey service for direct authentication', () => __awaiter(void 0, void 0, void 0, function* () {
201
+ const passkeyServiceMock = {
202
+ authenticatePasskeyAndRetrieveKey: jest
203
+ .fn()
204
+ .mockResolvedValue('retrieved-key'),
205
+ };
206
+ portal.passkeyService = passkeyServiceMock;
207
+ portal.passkeyServiceDefaultDomain = 'backup.web.portalhq.io';
208
+ portal.passkeyServiceApiKey = portal.apiKey;
209
+ const key = yield portal.authenticatePasskeyAndRetrieveKey();
210
+ expect(key).toEqual('retrieved-key');
211
+ expect(passkeyServiceMock.authenticatePasskeyAndRetrieveKey).toHaveBeenCalledWith(expect.objectContaining({
212
+ customDomain: undefined,
213
+ relyingPartyId: 'backup.web.portalhq.io',
214
+ relyingPartyName: 'Portal',
215
+ }));
216
+ }));
217
+ it('should throw when usePopup is true', () => __awaiter(void 0, void 0, void 0, function* () {
218
+ yield expect(portal.authenticatePasskeyAndRetrieveKey({ usePopup: true })).rejects.toThrow('does not support the popup flow');
219
+ }));
220
+ });
221
+ describe('backupWithPasskey', () => {
222
+ it('should orchestrate the direct passkey flow when usePopup is false', () => __awaiter(void 0, void 0, void 0, function* () {
223
+ const directShare = {
224
+ cipherText: constants_1.mockCipherText,
225
+ encryptionKey: 'manual-key',
226
+ };
227
+ const generateSpy = jest
228
+ .spyOn(portal, 'generateBackupShare')
229
+ .mockResolvedValue(directShare);
230
+ const registerSpy = jest
231
+ .spyOn(portal, 'registerPasskeyAndStoreEncryptionKey')
232
+ .mockResolvedValue(undefined);
233
+ const storedClientBackupShareSpy = jest
234
+ .spyOn(portal, 'storedClientBackupShare')
235
+ .mockResolvedValue(undefined);
236
+ yield portal.backupWithPasskey({
237
+ usePopup: false,
238
+ customDomain: 'passkeys.wigwam.app',
239
+ relyingPartyName: 'Wigwam',
240
+ }, undefined);
241
+ expect(generateSpy).toHaveBeenCalledTimes(1);
242
+ expect(registerSpy).toHaveBeenCalledWith(directShare.cipherText, directShare.encryptionKey, expect.objectContaining({
243
+ customDomain: 'passkeys.wigwam.app',
244
+ relyingPartyName: 'Wigwam',
245
+ usePopup: false,
246
+ }));
247
+ expect(storedClientBackupShareSpy).toHaveBeenCalledWith(true, _1.BackupMethods.passkey);
248
+ generateSpy.mockRestore();
249
+ registerSpy.mockRestore();
250
+ storedClientBackupShareSpy.mockRestore();
251
+ }));
252
+ it('should fall back to the legacy popup flow when usePopup is true', () => __awaiter(void 0, void 0, void 0, function* () {
253
+ const progress = jest.fn();
254
+ yield portal.backupWithPasskey({}, progress);
255
+ expect(portal.mpc.backup).toHaveBeenCalledWith({
256
+ backupMethod: _1.BackupMethods.passkey,
257
+ backupConfigs: {},
258
+ host: 'web.portalhq.io',
259
+ mpcVersion: 'v6',
260
+ featureFlags: {},
261
+ }, progress);
262
+ }));
263
+ });
147
264
  describe('recoverWallet', () => {
148
265
  it('should successfully recover a wallet and call mpc.recover correctly', () => __awaiter(void 0, void 0, void 0, function* () {
149
266
  const mockProgressFn = jest.fn();
@@ -565,14 +682,14 @@ describe('Portal', () => {
565
682
  it('should correctly call mpc.getQuote', () => __awaiter(void 0, void 0, void 0, function* () {
566
683
  yield portal.getQuote('test', constants_1.mockQuoteArgs, 'eip155:1');
567
684
  expect(portal.mpc.getQuote).toHaveBeenCalledTimes(1);
568
- expect(portal.mpc.getQuote).toHaveBeenCalledWith('test', constants_1.mockQuoteArgs, 'eip155:1');
685
+ expect(portal.mpc.getQuote).toHaveBeenCalledWith('eip155:1', constants_1.mockQuoteArgs, 'test');
569
686
  }));
570
687
  });
571
688
  describe('getSources', () => {
572
689
  it('should correctly call mpc.getSources', () => __awaiter(void 0, void 0, void 0, function* () {
573
690
  yield portal.getSources('test', 'eip155:1');
574
691
  expect(portal.mpc.getSources).toHaveBeenCalledTimes(1);
575
- expect(portal.mpc.getSources).toHaveBeenCalledWith('test', 'eip155:1');
692
+ expect(portal.mpc.getSources).toHaveBeenCalledWith('eip155:1', 'test');
576
693
  }));
577
694
  });
578
695
  describe('storedClientBackupShare', () => {