@lumiapassport/ui-kit 1.6.3 → 1.8.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.
- package/README.md +92 -0
- package/dist/iframe/index.html +220 -1
- package/dist/iframe/main.js +227 -8
- package/dist/iframe/main.js.map +1 -1
- package/dist/index.cjs +155 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +61 -1
- package/dist/index.d.ts +61 -1
- package/dist/index.js +155 -16
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -340,6 +340,98 @@ function DirectTransactionExample() {
|
|
|
340
340
|
- ✅ Use `useSendTransaction()` hook for React components (automatic session management)
|
|
341
341
|
- ✅ Use `sendUserOperation()` function for custom logic, utility functions, or non-React code
|
|
342
342
|
|
|
343
|
+
### signTypedData - Sign EIP712 Structured Messages
|
|
344
|
+
|
|
345
|
+
Sign structured data according to [EIP-712](https://eips.ethereum.org/EIPS/eip-712) standard. This is commonly used for off-chain signatures in dApps (e.g., NFT marketplace orders, gasless transactions, permit signatures).
|
|
346
|
+
|
|
347
|
+
```tsx
|
|
348
|
+
import { signTypedData, useLumiaPassportSession } from '@lumiapassport/ui-kit';
|
|
349
|
+
|
|
350
|
+
function SignatureExample() {
|
|
351
|
+
const { session } = useLumiaPassportSession();
|
|
352
|
+
|
|
353
|
+
const handleSign = async () => {
|
|
354
|
+
if (!session) return;
|
|
355
|
+
|
|
356
|
+
try {
|
|
357
|
+
// Define EIP712 typed data
|
|
358
|
+
const signature = await signTypedData(session, {
|
|
359
|
+
domain: {
|
|
360
|
+
name: 'MyDApp', // Your dApp name (must match contract)
|
|
361
|
+
version: '1', // Contract version
|
|
362
|
+
chainId: 994, // Lumia Prism Testnet
|
|
363
|
+
verifyingContract: '0x...', // Your contract address (REQUIRED in production!)
|
|
364
|
+
},
|
|
365
|
+
types: {
|
|
366
|
+
Order: [
|
|
367
|
+
{ name: 'tokenIds', type: 'uint256[]' },
|
|
368
|
+
{ name: 'price', type: 'uint256' },
|
|
369
|
+
{ name: 'deadline', type: 'uint256' },
|
|
370
|
+
],
|
|
371
|
+
},
|
|
372
|
+
primaryType: 'Order',
|
|
373
|
+
message: {
|
|
374
|
+
tokenIds: [1n, 2n, 3n],
|
|
375
|
+
price: 1000000000000000000n, // 1 token in wei
|
|
376
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour from now
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
console.log('Signature:', signature);
|
|
381
|
+
|
|
382
|
+
// Verify signature (optional)
|
|
383
|
+
const { recoverTypedDataAddress } = await import('viem');
|
|
384
|
+
const recoveredAddress = await recoverTypedDataAddress({
|
|
385
|
+
domain: { /* same domain */ },
|
|
386
|
+
types: { /* same types */ },
|
|
387
|
+
primaryType: 'Order',
|
|
388
|
+
message: { /* same message */ },
|
|
389
|
+
signature,
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
console.log('Signer:', recoveredAddress); // Should match session.ownerAddress
|
|
393
|
+
} catch (error) {
|
|
394
|
+
console.error('Signing failed:', error);
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
return <button onClick={handleSign}>Sign Message</button>;
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
**Important Notes about ERC-4337 Smart Accounts:**
|
|
403
|
+
|
|
404
|
+
In Account Abstraction (ERC-4337), there are **two addresses**:
|
|
405
|
+
1. **Owner Address (EOA)** - The address that signs messages/transactions
|
|
406
|
+
2. **Smart Account Address** - The contract wallet address
|
|
407
|
+
|
|
408
|
+
⚠️ **Critical:** The signature is created by the **owner address** (EOA), NOT the smart account address!
|
|
409
|
+
|
|
410
|
+
**Compatibility with existing protocols:**
|
|
411
|
+
- ✅ **Works:** Protocols that verify signatures off-chain (e.g., your backend verifies the owner EOA signature)
|
|
412
|
+
- ⚠️ **May not work:** Protocols designed for EOA wallets that store and verify against `msg.sender` or wallet address
|
|
413
|
+
- Example: Uniswap Permit2, some NFT marketplaces
|
|
414
|
+
- These protocols expect the signer address to match the wallet address
|
|
415
|
+
- With smart accounts: signer = owner EOA, wallet = smart account contract
|
|
416
|
+
- **Solution:** Use ERC-1271 signature validation in your smart contracts (allows contracts to validate signatures)
|
|
417
|
+
|
|
418
|
+
**Domain Configuration:**
|
|
419
|
+
- In production, use your actual `verifyingContract` address (not zero address!)
|
|
420
|
+
- The `domain` parameters must match exactly between frontend and smart contract
|
|
421
|
+
- The `chainId` should match the network you're deploying to
|
|
422
|
+
|
|
423
|
+
**Technical Details:**
|
|
424
|
+
- Shows a MetaMask-like confirmation modal with structured message preview
|
|
425
|
+
- All BigInt values are supported in the message
|
|
426
|
+
- Signature can be verified using `viem.recoverTypedDataAddress()` - will return owner EOA address
|
|
427
|
+
|
|
428
|
+
**When to use signTypedData:**
|
|
429
|
+
- ✅ Custom backend signature verification (you control the verification logic)
|
|
430
|
+
- ✅ Gasless transactions with meta-transaction relayers
|
|
431
|
+
- ✅ DAO voting and governance (off-chain signatures)
|
|
432
|
+
- ✅ Custom smart contracts with ERC-1271 support
|
|
433
|
+
- ⚠️ Be cautious with protocols designed exclusively for EOA wallets
|
|
434
|
+
|
|
343
435
|
### prepareUserOperation - Prepare for Backend Submission
|
|
344
436
|
|
|
345
437
|
```tsx
|
package/dist/iframe/index.html
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
<meta http-equiv="X-Content-Type-Options" content="nosniff" />
|
|
16
16
|
<meta http-equiv="Referrer-Policy" content="strict-origin-when-cross-origin" />
|
|
17
17
|
|
|
18
|
-
<title>Lumia Passport Secure Wallet - iframe version 1.
|
|
18
|
+
<title>Lumia Passport Secure Wallet - iframe version 1.8.0</title>
|
|
19
19
|
|
|
20
20
|
<!-- Styles will be injected by build process -->
|
|
21
21
|
<style>
|
|
@@ -28,6 +28,10 @@
|
|
|
28
28
|
--iframe-modal-bg: white;
|
|
29
29
|
--iframe-button-bg: #667eea;
|
|
30
30
|
--iframe-button-text: white;
|
|
31
|
+
--iframe-section-bg: #f9fafb;
|
|
32
|
+
--iframe-section-border: #e5e7eb;
|
|
33
|
+
--iframe-section-text: #374151;
|
|
34
|
+
--iframe-field-label: #6b7280;
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
* {
|
|
@@ -542,6 +546,221 @@
|
|
|
542
546
|
.trust-app-label:hover {
|
|
543
547
|
color: #111827;
|
|
544
548
|
}
|
|
549
|
+
|
|
550
|
+
/* EIP712 Signature Request Modal Styles */
|
|
551
|
+
.eip712-confirmation-modal {
|
|
552
|
+
position: fixed;
|
|
553
|
+
top: 0;
|
|
554
|
+
left: 0;
|
|
555
|
+
right: 0;
|
|
556
|
+
bottom: 0;
|
|
557
|
+
z-index: 10000;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
.eip712-modal {
|
|
561
|
+
max-width: 480px;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
.eip712-content {
|
|
565
|
+
padding: 1.5rem 2rem;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
.section-title {
|
|
569
|
+
font-size: 0.875rem;
|
|
570
|
+
font-weight: 600;
|
|
571
|
+
color: var(--iframe-section-text);
|
|
572
|
+
margin-bottom: 0.75rem;
|
|
573
|
+
text-transform: uppercase;
|
|
574
|
+
letter-spacing: 0.05em;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
.eip712-section {
|
|
578
|
+
background: var(--iframe-section-bg);
|
|
579
|
+
border: 1px solid var(--iframe-section-border);
|
|
580
|
+
border-radius: 8px;
|
|
581
|
+
padding: 1rem;
|
|
582
|
+
margin-bottom: 1rem;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
.section-subtitle {
|
|
586
|
+
font-size: 1rem;
|
|
587
|
+
font-weight: 700;
|
|
588
|
+
color: var(--iframe-section-text);
|
|
589
|
+
margin-bottom: 0.75rem;
|
|
590
|
+
padding-bottom: 0.5rem;
|
|
591
|
+
border-bottom: 1px solid var(--iframe-section-border);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
.eip712-field {
|
|
595
|
+
display: grid;
|
|
596
|
+
grid-template-columns: 120px 1fr;
|
|
597
|
+
gap: 0.75rem;
|
|
598
|
+
padding: 0.5rem 0;
|
|
599
|
+
align-items: start;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
.eip712-field:not(:last-child) {
|
|
603
|
+
border-bottom: 1px solid var(--iframe-section-border);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
.field-name {
|
|
607
|
+
font-size: 0.8125rem;
|
|
608
|
+
font-weight: 500;
|
|
609
|
+
color: var(--iframe-field-label);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
.field-value {
|
|
613
|
+
font-size: 0.8125rem;
|
|
614
|
+
color: var(--iframe-section-text);
|
|
615
|
+
word-break: break-word;
|
|
616
|
+
font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
.eip712-details {
|
|
620
|
+
margin-bottom: 1rem;
|
|
621
|
+
border: 1px solid var(--iframe-section-border);
|
|
622
|
+
border-radius: 6px;
|
|
623
|
+
overflow: hidden;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
.eip712-details summary {
|
|
627
|
+
padding: 0.75rem 1rem;
|
|
628
|
+
cursor: pointer;
|
|
629
|
+
background: var(--iframe-section-bg);
|
|
630
|
+
font-size: 0.875rem;
|
|
631
|
+
font-weight: 600;
|
|
632
|
+
color: var(--iframe-section-text);
|
|
633
|
+
user-select: none;
|
|
634
|
+
list-style: none;
|
|
635
|
+
display: flex;
|
|
636
|
+
align-items: center;
|
|
637
|
+
justify-content: space-between;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
.eip712-details summary::-webkit-details-marker {
|
|
641
|
+
display: none;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
.eip712-details summary:after {
|
|
645
|
+
content: '▼';
|
|
646
|
+
font-size: 0.7rem;
|
|
647
|
+
transition: transform 0.2s;
|
|
648
|
+
color: var(--iframe-section-text);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
.eip712-details[open] summary:after {
|
|
652
|
+
transform: rotate(180deg);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
.eip712-details summary:hover {
|
|
656
|
+
background: var(--iframe-modal-bg);
|
|
657
|
+
opacity: 0.9;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
.eip712-raw {
|
|
661
|
+
margin: 0;
|
|
662
|
+
padding: 1rem;
|
|
663
|
+
background: var(--iframe-modal-bg);
|
|
664
|
+
border-top: 1px solid var(--iframe-section-border);
|
|
665
|
+
max-height: 200px;
|
|
666
|
+
overflow-y: auto;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
.eip712-raw code {
|
|
670
|
+
font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
|
|
671
|
+
font-size: 0.75rem;
|
|
672
|
+
color: var(--iframe-section-text);
|
|
673
|
+
line-height: 1.5;
|
|
674
|
+
white-space: pre-wrap;
|
|
675
|
+
word-break: break-all;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
.security-info {
|
|
679
|
+
background: var(--iframe-section-bg);
|
|
680
|
+
border-radius: 6px;
|
|
681
|
+
padding: 0.75rem 1rem;
|
|
682
|
+
margin-bottom: 1rem;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
.info-item {
|
|
686
|
+
display: flex;
|
|
687
|
+
justify-content: space-between;
|
|
688
|
+
align-items: center;
|
|
689
|
+
font-size: 0.8125rem;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
.info-label {
|
|
693
|
+
font-weight: 500;
|
|
694
|
+
color: var(--iframe-field-label);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
.info-value {
|
|
698
|
+
font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
|
|
699
|
+
color: var(--iframe-section-text);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
.footer-notice {
|
|
703
|
+
padding: 0 2rem 2rem;
|
|
704
|
+
text-align: center;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
.modal-title {
|
|
708
|
+
font-size: 1.25rem;
|
|
709
|
+
font-weight: 600;
|
|
710
|
+
color: var(--iframe-text);
|
|
711
|
+
margin: 0.5rem 0;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
.origin-text {
|
|
715
|
+
font-size: 0.875rem;
|
|
716
|
+
color: var(--iframe-text-secondary);
|
|
717
|
+
margin: 0;
|
|
718
|
+
display: flex;
|
|
719
|
+
align-items: center;
|
|
720
|
+
justify-content: center;
|
|
721
|
+
gap: 0.5rem;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
.verified-badge {
|
|
725
|
+
color: #10b981;
|
|
726
|
+
font-weight: 600;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
.project-logo {
|
|
730
|
+
width: 48px;
|
|
731
|
+
height: 48px;
|
|
732
|
+
border-radius: 8px;
|
|
733
|
+
object-fit: cover;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
.project-logo-placeholder {
|
|
737
|
+
width: 48px;
|
|
738
|
+
height: 48px;
|
|
739
|
+
border-radius: 8px;
|
|
740
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
741
|
+
display: flex;
|
|
742
|
+
align-items: center;
|
|
743
|
+
justify-content: center;
|
|
744
|
+
font-size: 1.5rem;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
.arrow-icon {
|
|
748
|
+
font-size: 1.25rem;
|
|
749
|
+
color: #9ca3af;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
.lumia-logo {
|
|
753
|
+
width: 40px;
|
|
754
|
+
height: 40px;
|
|
755
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
756
|
+
border-radius: 8px;
|
|
757
|
+
display: flex;
|
|
758
|
+
align-items: center;
|
|
759
|
+
justify-content: center;
|
|
760
|
+
color: white;
|
|
761
|
+
font-weight: 700;
|
|
762
|
+
font-size: 1.25rem;
|
|
763
|
+
}
|
|
545
764
|
</style>
|
|
546
765
|
</head>
|
|
547
766
|
<body>
|
package/dist/iframe/main.js
CHANGED
|
@@ -1085,6 +1085,7 @@ async function uploadShareToVault(encryptedShare, accessToken) {
|
|
|
1085
1085
|
"Authorization": `Bearer ${token}`,
|
|
1086
1086
|
"Idempotency-Key": idempotencyKey
|
|
1087
1087
|
},
|
|
1088
|
+
credentials: "include",
|
|
1088
1089
|
body: JSON.stringify(encryptedShare)
|
|
1089
1090
|
});
|
|
1090
1091
|
if (!response.ok) {
|
|
@@ -1109,7 +1110,8 @@ async function downloadShareFromVault(accessToken) {
|
|
|
1109
1110
|
"Authorization": `Bearer ${token}`,
|
|
1110
1111
|
"X-Client-Device-Id": "lumia-ui-kit",
|
|
1111
1112
|
"X-Client-Device-Name": "Lumia UI Kit"
|
|
1112
|
-
}
|
|
1113
|
+
},
|
|
1114
|
+
credentials: "include"
|
|
1113
1115
|
});
|
|
1114
1116
|
if (!response.ok) {
|
|
1115
1117
|
if (response.status === 404) {
|
|
@@ -1379,6 +1381,7 @@ var SecureMessenger = class {
|
|
|
1379
1381
|
"AUTHENTICATE",
|
|
1380
1382
|
"START_DKG",
|
|
1381
1383
|
"SIGN_TRANSACTION",
|
|
1384
|
+
"SIGN_TYPED_DATA",
|
|
1382
1385
|
"GET_ADDRESS",
|
|
1383
1386
|
"CHECK_KEYSHARE",
|
|
1384
1387
|
"GET_TRUSTED_APPS",
|
|
@@ -2628,7 +2631,9 @@ var SigningManager = class extends TokenRefreshApiClient {
|
|
|
2628
2631
|
return cached.data;
|
|
2629
2632
|
}
|
|
2630
2633
|
try {
|
|
2631
|
-
const response = await fetch(`${this.METADATA_API_URL}/${projectId}/metadata
|
|
2634
|
+
const response = await fetch(`${this.METADATA_API_URL}/${projectId}/metadata`, {
|
|
2635
|
+
credentials: "include"
|
|
2636
|
+
});
|
|
2632
2637
|
if (!response.ok) {
|
|
2633
2638
|
console.warn(`[iframe][Sign] Failed to fetch project metadata: ${response.status}`);
|
|
2634
2639
|
return null;
|
|
@@ -2911,14 +2916,22 @@ var SigningManager = class extends TokenRefreshApiClient {
|
|
|
2911
2916
|
}
|
|
2912
2917
|
/**
|
|
2913
2918
|
* Assess transaction risk
|
|
2919
|
+
*
|
|
2920
|
+
* TODO: Implement backend-based risk assessment with real-time price conversion
|
|
2921
|
+
* - Get current LUMIA/USD price from API
|
|
2922
|
+
* - Calculate transaction value in USD
|
|
2923
|
+
* - Use dynamic thresholds based on USD value (e.g., >$100 = HIGH)
|
|
2924
|
+
* - Consider additional factors: recipient reputation, contract verification, etc.
|
|
2914
2925
|
*/
|
|
2915
2926
|
async assessRisk(transaction) {
|
|
2916
2927
|
const reasons = [];
|
|
2917
2928
|
let score = 0;
|
|
2918
|
-
const
|
|
2919
|
-
|
|
2929
|
+
const valueWei = BigInt(transaction.value || "0");
|
|
2930
|
+
const valueLumia = Number(valueWei) / 1e18;
|
|
2931
|
+
if (valueLumia > 10) {
|
|
2920
2932
|
score += 20;
|
|
2921
|
-
|
|
2933
|
+
const formattedValue = valueLumia >= 1 ? valueLumia.toFixed(4).replace(/\.?0+$/, "") : valueLumia.toFixed(8).replace(/\.?0+$/, "");
|
|
2934
|
+
reasons.push(`High value transaction (${formattedValue} LUMIA)`);
|
|
2922
2935
|
}
|
|
2923
2936
|
if (transaction.data && transaction.data !== "0x" && transaction.data.length > 2) {
|
|
2924
2937
|
score += 10;
|
|
@@ -2975,6 +2988,170 @@ var SigningManager = class extends TokenRefreshApiClient {
|
|
|
2975
2988
|
extractServerMessage(response) {
|
|
2976
2989
|
return response?.msgNext || response?.msg_next || response?.msg || null;
|
|
2977
2990
|
}
|
|
2991
|
+
/**
|
|
2992
|
+
* Sign EIP712 typed data with user confirmation
|
|
2993
|
+
*/
|
|
2994
|
+
async signTypedData(userId, projectId, origin, typedData, digest32, accessToken) {
|
|
2995
|
+
console.log("[iframe][Sign] EIP712 signing request:", { userId, projectId, origin, primaryType: typedData.primaryType });
|
|
2996
|
+
await this.initialize();
|
|
2997
|
+
const projectInfo = {
|
|
2998
|
+
id: projectId,
|
|
2999
|
+
name: "Application",
|
|
3000
|
+
logoUrl: "",
|
|
3001
|
+
website: origin,
|
|
3002
|
+
domains: [origin]
|
|
3003
|
+
};
|
|
3004
|
+
const isTrusted = this.trustedApps.isTrusted(userId, projectId, origin);
|
|
3005
|
+
if (!isTrusted) {
|
|
3006
|
+
const confirmResult = await this.showEIP712ConfirmationDialog(
|
|
3007
|
+
userId,
|
|
3008
|
+
projectId,
|
|
3009
|
+
projectInfo,
|
|
3010
|
+
origin,
|
|
3011
|
+
typedData
|
|
3012
|
+
);
|
|
3013
|
+
if (!confirmResult.confirmed) {
|
|
3014
|
+
throw new Error("User rejected signature request");
|
|
3015
|
+
}
|
|
3016
|
+
if (confirmResult.trustApp) {
|
|
3017
|
+
this.trustedApps.addTrustedApp(userId, projectId, origin);
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
const keyshareData = this.storage.loadKeyshare(userId);
|
|
3021
|
+
if (!keyshareData) {
|
|
3022
|
+
throw new Error("No keyshare found. Please complete DKG first.");
|
|
3023
|
+
}
|
|
3024
|
+
const signature = await this.performMPCSigning(userId, keyshareData, digest32, projectId, accessToken);
|
|
3025
|
+
console.log("[iframe][Sign] EIP712 signature generated");
|
|
3026
|
+
return signature;
|
|
3027
|
+
}
|
|
3028
|
+
/**
|
|
3029
|
+
* Show EIP712 confirmation dialog
|
|
3030
|
+
*/
|
|
3031
|
+
async showEIP712ConfirmationDialog(userId, projectId, project, origin, typedData) {
|
|
3032
|
+
this.showIframe();
|
|
3033
|
+
const metadata = await this.fetchProjectMetadata(projectId);
|
|
3034
|
+
return new Promise((resolve) => {
|
|
3035
|
+
const modal = this.createEIP712ConfirmationModal(userId, projectId, project, origin, typedData, metadata);
|
|
3036
|
+
const confirmBtn = modal.querySelector(".confirm-btn");
|
|
3037
|
+
const cancelBtn = modal.querySelector(".cancel-btn");
|
|
3038
|
+
const trustCheckbox = modal.querySelector(".trust-app-checkbox");
|
|
3039
|
+
confirmBtn?.addEventListener("click", (e) => {
|
|
3040
|
+
if (e.isTrusted) {
|
|
3041
|
+
const trustApp = trustCheckbox?.checked || false;
|
|
3042
|
+
modal.remove();
|
|
3043
|
+
this.hideIframe();
|
|
3044
|
+
resolve({ confirmed: true, trustApp });
|
|
3045
|
+
}
|
|
3046
|
+
});
|
|
3047
|
+
cancelBtn?.addEventListener("click", () => {
|
|
3048
|
+
modal.remove();
|
|
3049
|
+
this.hideIframe();
|
|
3050
|
+
resolve({ confirmed: false, trustApp: false });
|
|
3051
|
+
});
|
|
3052
|
+
document.body.appendChild(modal);
|
|
3053
|
+
});
|
|
3054
|
+
}
|
|
3055
|
+
/**
|
|
3056
|
+
* Create EIP712 confirmation modal UI (similar to MetaMask)
|
|
3057
|
+
*/
|
|
3058
|
+
createEIP712ConfirmationModal(userId, projectId, project, origin, typedData, metadata) {
|
|
3059
|
+
const modal = document.createElement("div");
|
|
3060
|
+
modal.className = "eip712-confirmation-modal";
|
|
3061
|
+
const isVerifiedOrigin = project.domains.includes(origin);
|
|
3062
|
+
const formatFieldValue = (value) => {
|
|
3063
|
+
if (Array.isArray(value)) {
|
|
3064
|
+
return `[${value.map((v) => formatFieldValue(v)).join(", ")}]`;
|
|
3065
|
+
}
|
|
3066
|
+
if (typeof value === "bigint") {
|
|
3067
|
+
return value.toString();
|
|
3068
|
+
}
|
|
3069
|
+
if (typeof value === "object" && value !== null) {
|
|
3070
|
+
return JSON.stringify(value, null, 2);
|
|
3071
|
+
}
|
|
3072
|
+
return String(value);
|
|
3073
|
+
};
|
|
3074
|
+
const messageFields = Object.entries(typedData.message).map(([key, value]) => `
|
|
3075
|
+
<div class="eip712-field">
|
|
3076
|
+
<div class="field-name">${key}:</div>
|
|
3077
|
+
<div class="field-value">${formatFieldValue(value)}</div>
|
|
3078
|
+
</div>
|
|
3079
|
+
`).join("");
|
|
3080
|
+
const domainFields = Object.entries(typedData.domain).filter(([_, value]) => value !== void 0).map(([key, value]) => `
|
|
3081
|
+
<div class="eip712-field">
|
|
3082
|
+
<div class="field-name">${key}:</div>
|
|
3083
|
+
<div class="field-value">${formatFieldValue(value)}</div>
|
|
3084
|
+
</div>
|
|
3085
|
+
`).join("");
|
|
3086
|
+
modal.innerHTML = `
|
|
3087
|
+
<div class="modal-overlay">
|
|
3088
|
+
<div class="modal-content eip712-modal">
|
|
3089
|
+
<!-- Header -->
|
|
3090
|
+
<div class="auth-header">
|
|
3091
|
+
<div class="logo-container">
|
|
3092
|
+
${metadata?.logo ? `<img src="${metadata.logo}" alt="${metadata.name}" class="project-logo" />` : '<div class="project-logo-placeholder">\u{1F510}</div>'}
|
|
3093
|
+
<span class="arrow-icon">\u2192</span>
|
|
3094
|
+
<div class="lumia-logo">L</div>
|
|
3095
|
+
</div>
|
|
3096
|
+
<h2 class="modal-title">Signature Request</h2>
|
|
3097
|
+
<p class="origin-text">
|
|
3098
|
+
${isVerifiedOrigin ? '<span class="verified-badge">\u2713</span>' : ""}
|
|
3099
|
+
${origin}
|
|
3100
|
+
</p>
|
|
3101
|
+
</div>
|
|
3102
|
+
|
|
3103
|
+
<!-- EIP712 Message Content -->
|
|
3104
|
+
<div class="eip712-content">
|
|
3105
|
+
<div class="section-title">\u{1F4DD} Message</div>
|
|
3106
|
+
<div class="eip712-section">
|
|
3107
|
+
<div class="section-subtitle">${typedData.primaryType}</div>
|
|
3108
|
+
${messageFields}
|
|
3109
|
+
</div>
|
|
3110
|
+
|
|
3111
|
+
<details class="eip712-details">
|
|
3112
|
+
<summary>\u{1F50D} Domain</summary>
|
|
3113
|
+
<div class="eip712-section">
|
|
3114
|
+
${domainFields}
|
|
3115
|
+
</div>
|
|
3116
|
+
</details>
|
|
3117
|
+
|
|
3118
|
+
<details class="eip712-details">
|
|
3119
|
+
<summary>\u{1F4CB} Full Message</summary>
|
|
3120
|
+
<pre class="eip712-raw"><code>${JSON.stringify(typedData.message, null, 2)}</code></pre>
|
|
3121
|
+
</details>
|
|
3122
|
+
</div>
|
|
3123
|
+
|
|
3124
|
+
<!-- Security Info -->
|
|
3125
|
+
<div class="security-info">
|
|
3126
|
+
<div class="info-item">
|
|
3127
|
+
<span class="info-label">Signing with:</span>
|
|
3128
|
+
<span class="info-value">${this.storage.getOwnerAddress(userId) || userId.substring(0, 20) + "..."}</span>
|
|
3129
|
+
</div>
|
|
3130
|
+
</div>
|
|
3131
|
+
|
|
3132
|
+
<!-- Trust App Option -->
|
|
3133
|
+
<div class="trust-app-section">
|
|
3134
|
+
<label class="trust-app-label">
|
|
3135
|
+
<input type="checkbox" class="trust-app-checkbox" />
|
|
3136
|
+
<span>Trust this application and skip confirmation for future signatures</span>
|
|
3137
|
+
</label>
|
|
3138
|
+
</div>
|
|
3139
|
+
|
|
3140
|
+
<!-- Action Buttons -->
|
|
3141
|
+
<div class="actions">
|
|
3142
|
+
<button class="cancel-btn">Reject</button>
|
|
3143
|
+
<button class="confirm-btn">Sign</button>
|
|
3144
|
+
</div>
|
|
3145
|
+
|
|
3146
|
+
<!-- Footer Notice -->
|
|
3147
|
+
<div class="footer-notice">
|
|
3148
|
+
<p class="footer-note">Only sign messages from applications you trust.</p>
|
|
3149
|
+
</div>
|
|
3150
|
+
</div>
|
|
3151
|
+
</div>
|
|
3152
|
+
`;
|
|
3153
|
+
return modal;
|
|
3154
|
+
}
|
|
2978
3155
|
/**
|
|
2979
3156
|
* Show iframe (notify parent)
|
|
2980
3157
|
*/
|
|
@@ -3014,7 +3191,9 @@ var AuthorizationManager = class {
|
|
|
3014
3191
|
return cached.data;
|
|
3015
3192
|
}
|
|
3016
3193
|
try {
|
|
3017
|
-
const response = await fetch(`${this.METADATA_API_URL}/${projectId}/metadata
|
|
3194
|
+
const response = await fetch(`${this.METADATA_API_URL}/${projectId}/metadata`, {
|
|
3195
|
+
credentials: "include"
|
|
3196
|
+
});
|
|
3018
3197
|
if (!response.ok) {
|
|
3019
3198
|
console.warn(`[iframe][Auth] Failed to fetch project metadata: ${response.status}`);
|
|
3020
3199
|
return null;
|
|
@@ -3434,7 +3613,8 @@ var GoogleDriveProvider = class {
|
|
|
3434
3613
|
const searchResponse = await fetch(
|
|
3435
3614
|
`https://www.googleapis.com/drive/v3/files?q=name='${folderName}' and mimeType='application/vnd.google-apps.folder' and trashed=false`,
|
|
3436
3615
|
{
|
|
3437
|
-
headers: { Authorization: `Bearer ${this.accessToken}` }
|
|
3616
|
+
headers: { Authorization: `Bearer ${this.accessToken}` },
|
|
3617
|
+
credentials: "include"
|
|
3438
3618
|
}
|
|
3439
3619
|
);
|
|
3440
3620
|
if (!searchResponse.ok) {
|
|
@@ -3450,6 +3630,7 @@ var GoogleDriveProvider = class {
|
|
|
3450
3630
|
Authorization: `Bearer ${this.accessToken}`,
|
|
3451
3631
|
"Content-Type": "application/json"
|
|
3452
3632
|
},
|
|
3633
|
+
credentials: "include",
|
|
3453
3634
|
body: JSON.stringify({
|
|
3454
3635
|
name: folderName,
|
|
3455
3636
|
mimeType: "application/vnd.google-apps.folder"
|
|
@@ -3473,6 +3654,7 @@ var GoogleDriveProvider = class {
|
|
|
3473
3654
|
headers: {
|
|
3474
3655
|
Authorization: `Bearer ${this.accessToken}`
|
|
3475
3656
|
},
|
|
3657
|
+
credentials: "include",
|
|
3476
3658
|
body: form
|
|
3477
3659
|
}
|
|
3478
3660
|
);
|
|
@@ -3748,7 +3930,7 @@ var BackupManager = class {
|
|
|
3748
3930
|
};
|
|
3749
3931
|
|
|
3750
3932
|
// src/iframe/main.ts
|
|
3751
|
-
var IFRAME_VERSION = "1.
|
|
3933
|
+
var IFRAME_VERSION = "1.8.0";
|
|
3752
3934
|
var IframeWallet = class {
|
|
3753
3935
|
constructor() {
|
|
3754
3936
|
console.log("=".repeat(60));
|
|
@@ -3831,6 +4013,9 @@ var IframeWallet = class {
|
|
|
3831
4013
|
case "SIGN_TRANSACTION":
|
|
3832
4014
|
await this.handleSignTransaction(message, origin);
|
|
3833
4015
|
break;
|
|
4016
|
+
case "SIGN_TYPED_DATA":
|
|
4017
|
+
await this.handleSignTypedData(message, origin);
|
|
4018
|
+
break;
|
|
3834
4019
|
case "GET_ADDRESS":
|
|
3835
4020
|
await this.handleGetAddress(message, origin);
|
|
3836
4021
|
break;
|
|
@@ -4005,6 +4190,40 @@ var IframeWallet = class {
|
|
|
4005
4190
|
throw error;
|
|
4006
4191
|
}
|
|
4007
4192
|
}
|
|
4193
|
+
async handleSignTypedData(message, origin) {
|
|
4194
|
+
const { sessionToken, userId, projectId, typedData, digest32, accessToken } = message.data;
|
|
4195
|
+
const { messageId } = message;
|
|
4196
|
+
if (!this.sessionManager.validateSession(sessionToken, origin)) {
|
|
4197
|
+
throw new Error("Invalid session");
|
|
4198
|
+
}
|
|
4199
|
+
const isAuthorized = await this.authManager.checkAuthorization(userId, projectId);
|
|
4200
|
+
if (!isAuthorized) {
|
|
4201
|
+
throw new Error("User has not authorized this application");
|
|
4202
|
+
}
|
|
4203
|
+
console.log(`[iframe] SIGN_TYPED_DATA: userId=${userId}, primaryType=${typedData?.primaryType}`);
|
|
4204
|
+
try {
|
|
4205
|
+
const signature = await this.signingManager.signTypedData(
|
|
4206
|
+
userId,
|
|
4207
|
+
projectId,
|
|
4208
|
+
origin,
|
|
4209
|
+
typedData,
|
|
4210
|
+
digest32,
|
|
4211
|
+
accessToken
|
|
4212
|
+
);
|
|
4213
|
+
this.messenger.sendResponse(
|
|
4214
|
+
messageId,
|
|
4215
|
+
{
|
|
4216
|
+
type: "LUMIA_PASSPORT_EIP712_SIGNATURE",
|
|
4217
|
+
signature
|
|
4218
|
+
},
|
|
4219
|
+
origin
|
|
4220
|
+
);
|
|
4221
|
+
console.log(`[iframe] \u2705 EIP712 message signed`);
|
|
4222
|
+
} catch (error) {
|
|
4223
|
+
console.error("[iframe] EIP712 signing failed:", error);
|
|
4224
|
+
throw error;
|
|
4225
|
+
}
|
|
4226
|
+
}
|
|
4008
4227
|
async handleGetAddress(message, origin) {
|
|
4009
4228
|
const { sessionToken, userId } = message.data;
|
|
4010
4229
|
const { messageId } = message;
|