@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
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.6.2-alpha",
6
+ "version": "3.7.0",
7
7
  "license": "MIT",
8
8
  "main": "lib/commonjs/index",
9
9
  "module": "lib/esm/index",
@@ -13,6 +13,10 @@
13
13
  "src",
14
14
  "lib",
15
15
  "tsconfig.json",
16
+ "hypernative.d.ts",
17
+ "lifi-types.d.ts",
18
+ "yieldxyz-types.d.ts",
19
+ "zero-x.d.ts",
16
20
  "types.d.ts",
17
21
  "!src/iframe",
18
22
  "!dist"
@@ -49,6 +53,5 @@
49
53
  },
50
54
  "dependencies": {
51
55
  "@solana/web3.js": "^1.91.8"
52
- },
53
- "stableVersion": "3.5.2"
56
+ }
54
57
  }
@@ -730,11 +730,17 @@ export const mockQuoteRes = {
730
730
  }
731
731
 
732
732
  export const mockSourcesRes = {
733
- '0x': '0xE41d2489571d322189246DaFA5ebDe1F4699F498',
734
- SushiSwap: '0x6B3595068778DD592e39A122f4f5a5cF09C90fE2',
735
- Uniswap: '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984',
736
- Uniswap_V2: '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984',
737
- Uniswap_V3: '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984',
733
+ data: {
734
+ rawResponse: {
735
+ sources: ['0x', 'SushiSwap', 'Uniswap', 'Uniswap_V2', 'Uniswap_V3'],
736
+ zid: 'test-zid',
737
+ },
738
+ },
739
+ metadata: {
740
+ chainId: 'eip155:1',
741
+ clientId: 'test',
742
+ clientEip155Address: '0x1234567890123456789012345678901234567890',
743
+ },
738
744
  }
739
745
 
740
746
  export const mockZeroXQuoteResponse = {
@@ -1312,3 +1318,414 @@ export const mockLifiGetRouteStepResponse = {
1312
1318
  },
1313
1319
  },
1314
1320
  }
1321
+
1322
+ // ZeroEx v2 mock data
1323
+ export const mockZeroExQuoteV2Request = {
1324
+ chainId: 'eip155:1',
1325
+ buyToken: 'UNI',
1326
+ sellToken: 'WETH',
1327
+ sellAmount: '1000000000000000000',
1328
+ }
1329
+
1330
+ export const mockZeroExQuoteV2Response = {
1331
+ data: {
1332
+ rawResponse: {
1333
+ blockNumber: '18500000',
1334
+ buyAmount: '100000000000000000000',
1335
+ buyToken: '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984',
1336
+ fees: {
1337
+ integratorFee: null,
1338
+ zeroExFee: null,
1339
+ gasFee: {
1340
+ amount: '21000000000000',
1341
+ token: '0x0000000000000000000000000000000000000000',
1342
+ type: 'gas',
1343
+ },
1344
+ },
1345
+ issues: {
1346
+ allowance: {
1347
+ actual: '0',
1348
+ spender: '0x1234567890123456789012345678901234567890',
1349
+ },
1350
+ balance: {
1351
+ token: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
1352
+ actual: '1000000000000000000',
1353
+ expected: '1000000000000000000',
1354
+ },
1355
+ simulationIncomplete: false,
1356
+ invalidSourcesPassed: [],
1357
+ },
1358
+ liquidityAvailable: true,
1359
+ minBuyAmount: '99000000000000000000',
1360
+ route: {
1361
+ fills: [
1362
+ {
1363
+ from: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
1364
+ to: '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984',
1365
+ source: 'Uniswap_V3',
1366
+ proportionBps: '10000',
1367
+ },
1368
+ ],
1369
+ tokens: [
1370
+ {
1371
+ address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
1372
+ symbol: 'WETH',
1373
+ },
1374
+ {
1375
+ address: '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984',
1376
+ symbol: 'UNI',
1377
+ },
1378
+ ],
1379
+ },
1380
+ sellAmount: '1000000000000000000',
1381
+ sellToken: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
1382
+ tokenMetadata: {
1383
+ buyToken: {
1384
+ buyTaxBps: '0',
1385
+ sellTaxBps: '0',
1386
+ },
1387
+ sellToken: {
1388
+ buyTaxBps: '0',
1389
+ sellTaxBps: '0',
1390
+ },
1391
+ },
1392
+ totalNetworkFee: '21000000000000',
1393
+ transaction: {
1394
+ to: '0x1234567890123456789012345678901234567890',
1395
+ data: '0xabcdef',
1396
+ gas: '200000',
1397
+ gasPrice: '30000000000',
1398
+ value: '0',
1399
+ },
1400
+ },
1401
+ },
1402
+ metadata: {
1403
+ buyToken: '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984',
1404
+ sellToken: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
1405
+ sellAmount: '1000000000000000000',
1406
+ chainId: 'eip155:1',
1407
+ clientId: 'test-client-id',
1408
+ clientEip155Address: mockEip155Address,
1409
+ },
1410
+ }
1411
+
1412
+ export const mockZeroExSourcesV2Request = {
1413
+ chainId: 'eip155:1',
1414
+ }
1415
+
1416
+ export const mockZeroExSourcesV2Response = {
1417
+ data: {
1418
+ rawResponse: {
1419
+ sources: ['Uniswap_V2', 'Uniswap_V3', 'SushiSwap', '0x'],
1420
+ zid: 'test-zid',
1421
+ },
1422
+ },
1423
+ metadata: {
1424
+ chainId: 'eip155:1',
1425
+ clientId: 'test-client-id',
1426
+ },
1427
+ }
1428
+
1429
+ export const mockZeroExPriceRequest = {
1430
+ chainId: 'eip155:1',
1431
+ buyToken: 'UNI',
1432
+ sellToken: 'WETH',
1433
+ sellAmount: '1000000000000000000',
1434
+ }
1435
+
1436
+ export const mockZeroExPriceResponse = {
1437
+ data: {
1438
+ rawResponse: {
1439
+ blockNumber: '18500000',
1440
+ buyAmount: '100000000000000000000',
1441
+ buyToken: '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984',
1442
+ fees: {
1443
+ integratorFee: null,
1444
+ zeroExFee: null,
1445
+ gasFee: {
1446
+ amount: '21000000000000',
1447
+ token: '0x0000000000000000000000000000000000000000',
1448
+ type: 'gas',
1449
+ },
1450
+ },
1451
+ gas: '200000',
1452
+ gasPrice: '30000000000',
1453
+ issues: {
1454
+ allowance: {
1455
+ actual: '0',
1456
+ spender: '0x1234567890123456789012345678901234567890',
1457
+ },
1458
+ balance: {
1459
+ token: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
1460
+ actual: '1000000000000000000',
1461
+ expected: '1000000000000000000',
1462
+ },
1463
+ simulationIncomplete: false,
1464
+ invalidSourcesPassed: [],
1465
+ },
1466
+ liquidityAvailable: true,
1467
+ minBuyAmount: '99000000000000000000',
1468
+ route: {
1469
+ fills: [
1470
+ {
1471
+ from: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
1472
+ to: '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984',
1473
+ source: 'Uniswap_V3',
1474
+ proportionBps: '10000',
1475
+ },
1476
+ ],
1477
+ tokens: [
1478
+ {
1479
+ address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
1480
+ symbol: 'WETH',
1481
+ },
1482
+ {
1483
+ address: '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984',
1484
+ symbol: 'UNI',
1485
+ },
1486
+ ],
1487
+ },
1488
+ sellAmount: '1000000000000000000',
1489
+ sellToken: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
1490
+ tokenMetadata: {
1491
+ buyToken: {
1492
+ buyTaxBps: '0',
1493
+ sellTaxBps: '0',
1494
+ },
1495
+ sellToken: {
1496
+ buyTaxBps: '0',
1497
+ sellTaxBps: '0',
1498
+ },
1499
+ },
1500
+ totalNetworkFee: '21000000000000',
1501
+ },
1502
+ },
1503
+ metadata: {
1504
+ buyToken: '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984',
1505
+ sellToken: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
1506
+ sellAmount: '1000000000000000000',
1507
+ chainId: 'eip155:1',
1508
+ clientId: 'test-client-id',
1509
+ clientEip155Address: mockEip155Address,
1510
+ },
1511
+ }
1512
+
1513
+ export const mockZeroExOptions = {
1514
+ zeroXApiKey: 'test-0x-api-key',
1515
+ }
1516
+
1517
+ // Hypernative Security mock data
1518
+ export const mockScanAddressesRequest = {
1519
+ addresses: ['0x1234567890123456789012345678901234567890'],
1520
+ screenerPolicyId: 'test-policy-id',
1521
+ }
1522
+
1523
+ export const mockScanAddressesResponse = {
1524
+ data: {
1525
+ rawResponse: [
1526
+ {
1527
+ address: '0x1234567890123456789012345678901234567890',
1528
+ recommendation: 'accept',
1529
+ severity: 'low',
1530
+ totalIncomingUsd: 1000,
1531
+ policyId: 'test-policy-id',
1532
+ timestamp: '2024-01-01T00:00:00Z',
1533
+ flags: [],
1534
+ },
1535
+ ],
1536
+ },
1537
+ }
1538
+
1539
+ export const mockScanEVMTxRequest = {
1540
+ transaction: {
1541
+ chain: 'ethereum',
1542
+ fromAddress: '0x1234567890123456789012345678901234567890',
1543
+ toAddress: '0x0987654321098765432109876543210987654321',
1544
+ input: '0xabcdef',
1545
+ value: '1000000000000000000',
1546
+ },
1547
+ url: 'https://example.com',
1548
+ validateNonce: true,
1549
+ showFullFindings: true,
1550
+ }
1551
+
1552
+ export const mockScanEVMTxResponse = {
1553
+ data: {
1554
+ rawResponse: {
1555
+ success: true,
1556
+ data: {
1557
+ assessmentId: 'test-assessment-id',
1558
+ assessmentTimestamp: '2024-01-01T00:00:00Z',
1559
+ recommendation: 'accept' as const,
1560
+ expectedStatus: 'success' as const,
1561
+ findings: [],
1562
+ involvedAssets: [],
1563
+ balanceChanges: null,
1564
+ },
1565
+ error: null,
1566
+ version: '1.0.0',
1567
+ service: 'hypernative',
1568
+ },
1569
+ },
1570
+ }
1571
+
1572
+ export const mockScanEip712TxRequest = {
1573
+ walletAddress: '0x1234567890123456789012345678901234567890',
1574
+ chainId: '1',
1575
+ eip712Message: {
1576
+ primaryType: 'Transfer',
1577
+ types: {
1578
+ Transfer: [
1579
+ { name: 'to', type: 'address' },
1580
+ { name: 'amount', type: 'uint256' },
1581
+ ],
1582
+ },
1583
+ domain: {
1584
+ name: 'Test',
1585
+ version: '1',
1586
+ chainId: 1,
1587
+ },
1588
+ message: {
1589
+ to: '0x0987654321098765432109876543210987654321',
1590
+ amount: '1000000000000000000',
1591
+ },
1592
+ },
1593
+ showFullFindings: true,
1594
+ }
1595
+
1596
+ export const mockScanEip712TxResponse = {
1597
+ data: {
1598
+ rawResponse: {
1599
+ success: true,
1600
+ data: {
1601
+ assessmentId: 'test-assessment-id',
1602
+ assessmentTimestamp: '2024-01-01T00:00:00Z',
1603
+ recommendation: 'accept' as const,
1604
+ findings: [],
1605
+ involvedAssets: [],
1606
+ },
1607
+ error: null,
1608
+ version: '1.0.0',
1609
+ service: 'hypernative',
1610
+ },
1611
+ },
1612
+ }
1613
+
1614
+ export const mockScanSolanaTxRequest = {
1615
+ transaction: {
1616
+ message: {
1617
+ accountKeys: ['test-account-key'],
1618
+ header: {
1619
+ numRequiredSignatures: 1,
1620
+ numReadonlySignedAccounts: 0,
1621
+ numReadonlyUnsignedAccounts: 0,
1622
+ },
1623
+ instructions: [
1624
+ {
1625
+ accounts: [0],
1626
+ data: 'test-data',
1627
+ programIdIndex: 0,
1628
+ },
1629
+ ],
1630
+ recentBlockhash: 'test-blockhash',
1631
+ },
1632
+ signatures: ['test-signature'],
1633
+ },
1634
+ url: 'https://example.com',
1635
+ validateRecentBlockHash: true,
1636
+ showFullFindings: true,
1637
+ }
1638
+
1639
+ export const mockScanSolanaTxResponse = {
1640
+ data: {
1641
+ rawResponse: {
1642
+ success: true,
1643
+ data: {
1644
+ recommendation: 'accept' as const,
1645
+ expectedStatus: 'success' as const,
1646
+ findings: [],
1647
+ involvedAssets: [],
1648
+ balanceChanges: null,
1649
+ },
1650
+ error: null,
1651
+ version: '1.0.0',
1652
+ service: 'hypernative',
1653
+ },
1654
+ },
1655
+ }
1656
+
1657
+ export const mockScanNftRequest = [
1658
+ {
1659
+ address: '0x1234567890123456789012345678901234567890',
1660
+ chain: 'ethereum',
1661
+ evmChainId: 1,
1662
+ },
1663
+ ]
1664
+
1665
+ export const mockScanNftResponse = {
1666
+ data: {
1667
+ rawResponse: {
1668
+ success: true,
1669
+ data: {
1670
+ nfts: [
1671
+ {
1672
+ address: '0x1234567890123456789012345678901234567890',
1673
+ chain: 'ethereum',
1674
+ evmChainId: '1',
1675
+ accept: true,
1676
+ },
1677
+ ],
1678
+ },
1679
+ error: null,
1680
+ version: '1.0.0',
1681
+ service: 'hypernative',
1682
+ },
1683
+ },
1684
+ }
1685
+
1686
+ export const mockScanTokenRequest = [
1687
+ {
1688
+ address: '0x1234567890123456789012345678901234567890',
1689
+ chain: 'ethereum',
1690
+ evmChainId: 1,
1691
+ },
1692
+ ]
1693
+
1694
+ export const mockScanTokenResponse = {
1695
+ data: {
1696
+ rawResponse: {
1697
+ success: true,
1698
+ data: {
1699
+ tokens: [
1700
+ {
1701
+ address: '0x1234567890123456789012345678901234567890',
1702
+ chain: 'ethereum',
1703
+ reputation: {
1704
+ recommendation: 'accept' as const,
1705
+ },
1706
+ },
1707
+ ],
1708
+ },
1709
+ error: null,
1710
+ version: '1.0.0',
1711
+ service: 'hypernative',
1712
+ },
1713
+ },
1714
+ }
1715
+
1716
+ export const mockScanUrlRequest = 'https://example.com'
1717
+
1718
+ export const mockScanUrlResponse = {
1719
+ data: {
1720
+ rawResponse: {
1721
+ success: true,
1722
+ data: {
1723
+ isMalicious: false,
1724
+ deepScanTriggered: false,
1725
+ },
1726
+ error: null,
1727
+ version: '1.0.0',
1728
+ service: 'hypernative',
1729
+ },
1730
+ },
1731
+ }
@@ -27,5 +27,6 @@ mpcMock.ejectPrivateKeys = jest
27
27
  .fn()
28
28
  .mockResolvedValue(mockEjectPrivateKeysResult)
29
29
  mpcMock.checkSharesOnDevice = jest.fn().mockResolvedValue(mockSharesOnDevice)
30
+ mpcMock.setBackupStatus = jest.fn().mockResolvedValue(true)
30
31
 
31
32
  export default mpcMock as jest.Mocked<Mpc>
package/src/index.test.ts CHANGED
@@ -173,6 +173,182 @@ describe('Portal', () => {
173
173
  })
174
174
  })
175
175
 
176
+ describe('generateBackupShare', () => {
177
+ it('should request a custom backup and return cipherText and encryptionKey', async () => {
178
+ const storageCallback = jest.fn().mockResolvedValue(undefined)
179
+ ;(portal.mpc.backup as jest.Mock).mockResolvedValueOnce({
180
+ cipherText: mockCipherText,
181
+ encryptionKey: 'manual-key',
182
+ storageCallback,
183
+ })
184
+
185
+ const result = await portal.generateBackupShare()
186
+
187
+ expect(result).toEqual({
188
+ cipherText: mockCipherText,
189
+ encryptionKey: 'manual-key',
190
+ })
191
+
192
+ expect(portal.mpc.backup).toHaveBeenCalledWith(
193
+ {
194
+ backupMethod: BackupMethods.custom,
195
+ backupConfigs: {},
196
+ host: 'web.portalhq.io',
197
+ mpcVersion: 'v6',
198
+ featureFlags: {},
199
+ },
200
+ expect.any(Function),
201
+ )
202
+ })
203
+
204
+ it('should throw if the iframe response does not contain an encryption key', async () => {
205
+ ;(portal.mpc.backup as jest.Mock).mockResolvedValueOnce({
206
+ cipherText: mockCipherText,
207
+ storageCallback: jest.fn(),
208
+ })
209
+
210
+ await expect(portal.generateBackupShare()).rejects.toThrow(
211
+ 'Custom backup did not return an encryption key',
212
+ )
213
+ })
214
+ })
215
+
216
+ describe('registerPasskeyAndStoreEncryptionKey', () => {
217
+ it('should delegate to the passkey service with computed relying party data', async () => {
218
+ const passkeyServiceMock = {
219
+ registerPasskeyAndStoreKey: jest.fn().mockResolvedValue(undefined),
220
+ }
221
+ ;(portal as any).passkeyService = passkeyServiceMock
222
+ ;(portal as any).passkeyServiceDefaultDomain = 'backup.web.portalhq.io'
223
+ ;(portal as any).passkeyServiceApiKey = portal.apiKey
224
+
225
+ await portal.registerPasskeyAndStoreEncryptionKey(
226
+ mockCipherText,
227
+ 'manual-key',
228
+ )
229
+
230
+ expect(
231
+ passkeyServiceMock.registerPasskeyAndStoreKey,
232
+ ).toHaveBeenCalledTimes(1)
233
+ expect(
234
+ passkeyServiceMock.registerPasskeyAndStoreKey,
235
+ ).toHaveBeenCalledWith(
236
+ expect.objectContaining({
237
+ customDomain: undefined,
238
+ encryptionKey: 'manual-key',
239
+ relyingPartyId: 'backup.web.portalhq.io',
240
+ relyingPartyName: 'Portal',
241
+ cipherText: mockCipherText,
242
+ }),
243
+ )
244
+ })
245
+
246
+ it('should throw when usePopup is requested', async () => {
247
+ await expect(
248
+ portal.registerPasskeyAndStoreEncryptionKey(
249
+ mockCipherText,
250
+ 'manual-key',
251
+ { usePopup: true },
252
+ ),
253
+ ).rejects.toThrow('does not support the popup flow')
254
+ })
255
+ })
256
+
257
+ describe('authenticatePasskeyAndRetrieveKey', () => {
258
+ it('should invoke the passkey service for direct authentication', async () => {
259
+ const passkeyServiceMock = {
260
+ authenticatePasskeyAndRetrieveKey: jest
261
+ .fn()
262
+ .mockResolvedValue('retrieved-key'),
263
+ }
264
+ ;(portal as any).passkeyService = passkeyServiceMock
265
+ ;(portal as any).passkeyServiceDefaultDomain = 'backup.web.portalhq.io'
266
+ ;(portal as any).passkeyServiceApiKey = portal.apiKey
267
+
268
+ const key = await portal.authenticatePasskeyAndRetrieveKey()
269
+
270
+ expect(key).toEqual('retrieved-key')
271
+ expect(
272
+ passkeyServiceMock.authenticatePasskeyAndRetrieveKey,
273
+ ).toHaveBeenCalledWith(
274
+ expect.objectContaining({
275
+ customDomain: undefined,
276
+ relyingPartyId: 'backup.web.portalhq.io',
277
+ relyingPartyName: 'Portal',
278
+ }),
279
+ )
280
+ })
281
+
282
+ it('should throw when usePopup is true', async () => {
283
+ await expect(
284
+ portal.authenticatePasskeyAndRetrieveKey({ usePopup: true }),
285
+ ).rejects.toThrow('does not support the popup flow')
286
+ })
287
+ })
288
+
289
+ describe('backupWithPasskey', () => {
290
+ it('should orchestrate the direct passkey flow when usePopup is false', async () => {
291
+ const directShare = {
292
+ cipherText: mockCipherText,
293
+ encryptionKey: 'manual-key',
294
+ }
295
+ const generateSpy = jest
296
+ .spyOn(portal, 'generateBackupShare')
297
+ .mockResolvedValue(directShare)
298
+ const registerSpy = jest
299
+ .spyOn(portal, 'registerPasskeyAndStoreEncryptionKey')
300
+ .mockResolvedValue(undefined)
301
+ const storedClientBackupShareSpy = jest
302
+ .spyOn(portal, 'storedClientBackupShare')
303
+ .mockResolvedValue(undefined)
304
+
305
+ await portal.backupWithPasskey(
306
+ {
307
+ usePopup: false,
308
+ customDomain: 'passkeys.wigwam.app',
309
+ relyingPartyName: 'Wigwam',
310
+ },
311
+ undefined,
312
+ )
313
+
314
+ expect(generateSpy).toHaveBeenCalledTimes(1)
315
+ expect(registerSpy).toHaveBeenCalledWith(
316
+ directShare.cipherText,
317
+ directShare.encryptionKey,
318
+ expect.objectContaining({
319
+ customDomain: 'passkeys.wigwam.app',
320
+ relyingPartyName: 'Wigwam',
321
+ usePopup: false,
322
+ }),
323
+ )
324
+ expect(storedClientBackupShareSpy).toHaveBeenCalledWith(
325
+ true,
326
+ BackupMethods.passkey,
327
+ )
328
+
329
+ generateSpy.mockRestore()
330
+ registerSpy.mockRestore()
331
+ storedClientBackupShareSpy.mockRestore()
332
+ })
333
+
334
+ it('should fall back to the legacy popup flow when usePopup is true', async () => {
335
+ const progress = jest.fn()
336
+
337
+ await portal.backupWithPasskey({}, progress)
338
+
339
+ expect(portal.mpc.backup).toHaveBeenCalledWith(
340
+ {
341
+ backupMethod: BackupMethods.passkey,
342
+ backupConfigs: {},
343
+ host: 'web.portalhq.io',
344
+ mpcVersion: 'v6',
345
+ featureFlags: {},
346
+ },
347
+ progress,
348
+ )
349
+ })
350
+ })
351
+
176
352
  describe('recoverWallet', () => {
177
353
  it('should successfully recover a wallet and call mpc.recover correctly', async () => {
178
354
  const mockProgressFn = jest.fn()
@@ -883,9 +1059,9 @@ describe('Portal', () => {
883
1059
 
884
1060
  expect(portal.mpc.getQuote).toHaveBeenCalledTimes(1)
885
1061
  expect(portal.mpc.getQuote).toHaveBeenCalledWith(
886
- 'test',
887
- mockQuoteArgs,
888
1062
  'eip155:1',
1063
+ mockQuoteArgs,
1064
+ 'test',
889
1065
  )
890
1066
  })
891
1067
  })
@@ -895,7 +1071,7 @@ describe('Portal', () => {
895
1071
  await portal.getSources('test', 'eip155:1')
896
1072
 
897
1073
  expect(portal.mpc.getSources).toHaveBeenCalledTimes(1)
898
- expect(portal.mpc.getSources).toHaveBeenCalledWith('test', 'eip155:1')
1074
+ expect(portal.mpc.getSources).toHaveBeenCalledWith('eip155:1', 'test')
899
1075
  })
900
1076
  })
901
1077