@lumiapassport/ui-kit 1.6.3 → 1.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.
- package/README.md +73 -0
- package/dist/iframe/index.html +220 -1
- package/dist/iframe/main.js +214 -4
- package/dist/iframe/main.js.map +1 -1
- package/dist/index.cjs +130 -3
- 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 +130 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -340,6 +340,79 @@ 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:**
|
|
403
|
+
- The signature is created by the **owner address** (EOA), not the smart account address
|
|
404
|
+
- In production, always use your actual `verifyingContract` address (not zero address!)
|
|
405
|
+
- The `domain` parameters must match exactly between frontend and smart contract
|
|
406
|
+
- Shows a MetaMask-like confirmation modal with structured message preview
|
|
407
|
+
- All BigInt values are supported in the message
|
|
408
|
+
|
|
409
|
+
**Common Use Cases:**
|
|
410
|
+
- NFT marketplace orders (OpenSea-style)
|
|
411
|
+
- ERC-20 Permit signatures (gasless approvals)
|
|
412
|
+
- Meta-transactions and gasless operations
|
|
413
|
+
- DAO voting signatures
|
|
414
|
+
- Any off-chain signature verification
|
|
415
|
+
|
|
343
416
|
### prepareUserOperation - Prepare for Backend Submission
|
|
344
417
|
|
|
345
418
|
```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.7.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
|
@@ -1379,6 +1379,7 @@ var SecureMessenger = class {
|
|
|
1379
1379
|
"AUTHENTICATE",
|
|
1380
1380
|
"START_DKG",
|
|
1381
1381
|
"SIGN_TRANSACTION",
|
|
1382
|
+
"SIGN_TYPED_DATA",
|
|
1382
1383
|
"GET_ADDRESS",
|
|
1383
1384
|
"CHECK_KEYSHARE",
|
|
1384
1385
|
"GET_TRUSTED_APPS",
|
|
@@ -2911,14 +2912,22 @@ var SigningManager = class extends TokenRefreshApiClient {
|
|
|
2911
2912
|
}
|
|
2912
2913
|
/**
|
|
2913
2914
|
* Assess transaction risk
|
|
2915
|
+
*
|
|
2916
|
+
* TODO: Implement backend-based risk assessment with real-time price conversion
|
|
2917
|
+
* - Get current LUMIA/USD price from API
|
|
2918
|
+
* - Calculate transaction value in USD
|
|
2919
|
+
* - Use dynamic thresholds based on USD value (e.g., >$100 = HIGH)
|
|
2920
|
+
* - Consider additional factors: recipient reputation, contract verification, etc.
|
|
2914
2921
|
*/
|
|
2915
2922
|
async assessRisk(transaction) {
|
|
2916
2923
|
const reasons = [];
|
|
2917
2924
|
let score = 0;
|
|
2918
|
-
const
|
|
2919
|
-
|
|
2925
|
+
const valueWei = BigInt(transaction.value || "0");
|
|
2926
|
+
const valueLumia = Number(valueWei) / 1e18;
|
|
2927
|
+
if (valueLumia > 10) {
|
|
2920
2928
|
score += 20;
|
|
2921
|
-
|
|
2929
|
+
const formattedValue = valueLumia >= 1 ? valueLumia.toFixed(4).replace(/\.?0+$/, "") : valueLumia.toFixed(8).replace(/\.?0+$/, "");
|
|
2930
|
+
reasons.push(`High value transaction (${formattedValue} LUMIA)`);
|
|
2922
2931
|
}
|
|
2923
2932
|
if (transaction.data && transaction.data !== "0x" && transaction.data.length > 2) {
|
|
2924
2933
|
score += 10;
|
|
@@ -2975,6 +2984,170 @@ var SigningManager = class extends TokenRefreshApiClient {
|
|
|
2975
2984
|
extractServerMessage(response) {
|
|
2976
2985
|
return response?.msgNext || response?.msg_next || response?.msg || null;
|
|
2977
2986
|
}
|
|
2987
|
+
/**
|
|
2988
|
+
* Sign EIP712 typed data with user confirmation
|
|
2989
|
+
*/
|
|
2990
|
+
async signTypedData(userId, projectId, origin, typedData, digest32, accessToken) {
|
|
2991
|
+
console.log("[iframe][Sign] EIP712 signing request:", { userId, projectId, origin, primaryType: typedData.primaryType });
|
|
2992
|
+
await this.initialize();
|
|
2993
|
+
const projectInfo = {
|
|
2994
|
+
id: projectId,
|
|
2995
|
+
name: "Application",
|
|
2996
|
+
logoUrl: "",
|
|
2997
|
+
website: origin,
|
|
2998
|
+
domains: [origin]
|
|
2999
|
+
};
|
|
3000
|
+
const isTrusted = this.trustedApps.isTrusted(userId, projectId, origin);
|
|
3001
|
+
if (!isTrusted) {
|
|
3002
|
+
const confirmResult = await this.showEIP712ConfirmationDialog(
|
|
3003
|
+
userId,
|
|
3004
|
+
projectId,
|
|
3005
|
+
projectInfo,
|
|
3006
|
+
origin,
|
|
3007
|
+
typedData
|
|
3008
|
+
);
|
|
3009
|
+
if (!confirmResult.confirmed) {
|
|
3010
|
+
throw new Error("User rejected signature request");
|
|
3011
|
+
}
|
|
3012
|
+
if (confirmResult.trustApp) {
|
|
3013
|
+
this.trustedApps.addTrustedApp(userId, projectId, origin);
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
const keyshareData = this.storage.loadKeyshare(userId);
|
|
3017
|
+
if (!keyshareData) {
|
|
3018
|
+
throw new Error("No keyshare found. Please complete DKG first.");
|
|
3019
|
+
}
|
|
3020
|
+
const signature = await this.performMPCSigning(userId, keyshareData, digest32, projectId, accessToken);
|
|
3021
|
+
console.log("[iframe][Sign] EIP712 signature generated");
|
|
3022
|
+
return signature;
|
|
3023
|
+
}
|
|
3024
|
+
/**
|
|
3025
|
+
* Show EIP712 confirmation dialog
|
|
3026
|
+
*/
|
|
3027
|
+
async showEIP712ConfirmationDialog(userId, projectId, project, origin, typedData) {
|
|
3028
|
+
this.showIframe();
|
|
3029
|
+
const metadata = await this.fetchProjectMetadata(projectId);
|
|
3030
|
+
return new Promise((resolve) => {
|
|
3031
|
+
const modal = this.createEIP712ConfirmationModal(userId, projectId, project, origin, typedData, metadata);
|
|
3032
|
+
const confirmBtn = modal.querySelector(".confirm-btn");
|
|
3033
|
+
const cancelBtn = modal.querySelector(".cancel-btn");
|
|
3034
|
+
const trustCheckbox = modal.querySelector(".trust-app-checkbox");
|
|
3035
|
+
confirmBtn?.addEventListener("click", (e) => {
|
|
3036
|
+
if (e.isTrusted) {
|
|
3037
|
+
const trustApp = trustCheckbox?.checked || false;
|
|
3038
|
+
modal.remove();
|
|
3039
|
+
this.hideIframe();
|
|
3040
|
+
resolve({ confirmed: true, trustApp });
|
|
3041
|
+
}
|
|
3042
|
+
});
|
|
3043
|
+
cancelBtn?.addEventListener("click", () => {
|
|
3044
|
+
modal.remove();
|
|
3045
|
+
this.hideIframe();
|
|
3046
|
+
resolve({ confirmed: false, trustApp: false });
|
|
3047
|
+
});
|
|
3048
|
+
document.body.appendChild(modal);
|
|
3049
|
+
});
|
|
3050
|
+
}
|
|
3051
|
+
/**
|
|
3052
|
+
* Create EIP712 confirmation modal UI (similar to MetaMask)
|
|
3053
|
+
*/
|
|
3054
|
+
createEIP712ConfirmationModal(userId, projectId, project, origin, typedData, metadata) {
|
|
3055
|
+
const modal = document.createElement("div");
|
|
3056
|
+
modal.className = "eip712-confirmation-modal";
|
|
3057
|
+
const isVerifiedOrigin = project.domains.includes(origin);
|
|
3058
|
+
const formatFieldValue = (value) => {
|
|
3059
|
+
if (Array.isArray(value)) {
|
|
3060
|
+
return `[${value.map((v) => formatFieldValue(v)).join(", ")}]`;
|
|
3061
|
+
}
|
|
3062
|
+
if (typeof value === "bigint") {
|
|
3063
|
+
return value.toString();
|
|
3064
|
+
}
|
|
3065
|
+
if (typeof value === "object" && value !== null) {
|
|
3066
|
+
return JSON.stringify(value, null, 2);
|
|
3067
|
+
}
|
|
3068
|
+
return String(value);
|
|
3069
|
+
};
|
|
3070
|
+
const messageFields = Object.entries(typedData.message).map(([key, value]) => `
|
|
3071
|
+
<div class="eip712-field">
|
|
3072
|
+
<div class="field-name">${key}:</div>
|
|
3073
|
+
<div class="field-value">${formatFieldValue(value)}</div>
|
|
3074
|
+
</div>
|
|
3075
|
+
`).join("");
|
|
3076
|
+
const domainFields = Object.entries(typedData.domain).filter(([_, value]) => value !== void 0).map(([key, value]) => `
|
|
3077
|
+
<div class="eip712-field">
|
|
3078
|
+
<div class="field-name">${key}:</div>
|
|
3079
|
+
<div class="field-value">${formatFieldValue(value)}</div>
|
|
3080
|
+
</div>
|
|
3081
|
+
`).join("");
|
|
3082
|
+
modal.innerHTML = `
|
|
3083
|
+
<div class="modal-overlay">
|
|
3084
|
+
<div class="modal-content eip712-modal">
|
|
3085
|
+
<!-- Header -->
|
|
3086
|
+
<div class="auth-header">
|
|
3087
|
+
<div class="logo-container">
|
|
3088
|
+
${metadata?.logo ? `<img src="${metadata.logo}" alt="${metadata.name}" class="project-logo" />` : '<div class="project-logo-placeholder">\u{1F510}</div>'}
|
|
3089
|
+
<span class="arrow-icon">\u2192</span>
|
|
3090
|
+
<div class="lumia-logo">L</div>
|
|
3091
|
+
</div>
|
|
3092
|
+
<h2 class="modal-title">Signature Request</h2>
|
|
3093
|
+
<p class="origin-text">
|
|
3094
|
+
${isVerifiedOrigin ? '<span class="verified-badge">\u2713</span>' : ""}
|
|
3095
|
+
${origin}
|
|
3096
|
+
</p>
|
|
3097
|
+
</div>
|
|
3098
|
+
|
|
3099
|
+
<!-- EIP712 Message Content -->
|
|
3100
|
+
<div class="eip712-content">
|
|
3101
|
+
<div class="section-title">\u{1F4DD} Message</div>
|
|
3102
|
+
<div class="eip712-section">
|
|
3103
|
+
<div class="section-subtitle">${typedData.primaryType}</div>
|
|
3104
|
+
${messageFields}
|
|
3105
|
+
</div>
|
|
3106
|
+
|
|
3107
|
+
<details class="eip712-details">
|
|
3108
|
+
<summary>\u{1F50D} Domain</summary>
|
|
3109
|
+
<div class="eip712-section">
|
|
3110
|
+
${domainFields}
|
|
3111
|
+
</div>
|
|
3112
|
+
</details>
|
|
3113
|
+
|
|
3114
|
+
<details class="eip712-details">
|
|
3115
|
+
<summary>\u{1F4CB} Full Message</summary>
|
|
3116
|
+
<pre class="eip712-raw"><code>${JSON.stringify(typedData.message, null, 2)}</code></pre>
|
|
3117
|
+
</details>
|
|
3118
|
+
</div>
|
|
3119
|
+
|
|
3120
|
+
<!-- Security Info -->
|
|
3121
|
+
<div class="security-info">
|
|
3122
|
+
<div class="info-item">
|
|
3123
|
+
<span class="info-label">Signing with:</span>
|
|
3124
|
+
<span class="info-value">${this.storage.getOwnerAddress(userId) || userId.substring(0, 20) + "..."}</span>
|
|
3125
|
+
</div>
|
|
3126
|
+
</div>
|
|
3127
|
+
|
|
3128
|
+
<!-- Trust App Option -->
|
|
3129
|
+
<div class="trust-app-section">
|
|
3130
|
+
<label class="trust-app-label">
|
|
3131
|
+
<input type="checkbox" class="trust-app-checkbox" />
|
|
3132
|
+
<span>Trust this application and skip confirmation for future signatures</span>
|
|
3133
|
+
</label>
|
|
3134
|
+
</div>
|
|
3135
|
+
|
|
3136
|
+
<!-- Action Buttons -->
|
|
3137
|
+
<div class="actions">
|
|
3138
|
+
<button class="cancel-btn">Reject</button>
|
|
3139
|
+
<button class="confirm-btn">Sign</button>
|
|
3140
|
+
</div>
|
|
3141
|
+
|
|
3142
|
+
<!-- Footer Notice -->
|
|
3143
|
+
<div class="footer-notice">
|
|
3144
|
+
<p class="footer-note">Only sign messages from applications you trust.</p>
|
|
3145
|
+
</div>
|
|
3146
|
+
</div>
|
|
3147
|
+
</div>
|
|
3148
|
+
`;
|
|
3149
|
+
return modal;
|
|
3150
|
+
}
|
|
2978
3151
|
/**
|
|
2979
3152
|
* Show iframe (notify parent)
|
|
2980
3153
|
*/
|
|
@@ -3748,7 +3921,7 @@ var BackupManager = class {
|
|
|
3748
3921
|
};
|
|
3749
3922
|
|
|
3750
3923
|
// src/iframe/main.ts
|
|
3751
|
-
var IFRAME_VERSION = "1.
|
|
3924
|
+
var IFRAME_VERSION = "1.7.0";
|
|
3752
3925
|
var IframeWallet = class {
|
|
3753
3926
|
constructor() {
|
|
3754
3927
|
console.log("=".repeat(60));
|
|
@@ -3831,6 +4004,9 @@ var IframeWallet = class {
|
|
|
3831
4004
|
case "SIGN_TRANSACTION":
|
|
3832
4005
|
await this.handleSignTransaction(message, origin);
|
|
3833
4006
|
break;
|
|
4007
|
+
case "SIGN_TYPED_DATA":
|
|
4008
|
+
await this.handleSignTypedData(message, origin);
|
|
4009
|
+
break;
|
|
3834
4010
|
case "GET_ADDRESS":
|
|
3835
4011
|
await this.handleGetAddress(message, origin);
|
|
3836
4012
|
break;
|
|
@@ -4005,6 +4181,40 @@ var IframeWallet = class {
|
|
|
4005
4181
|
throw error;
|
|
4006
4182
|
}
|
|
4007
4183
|
}
|
|
4184
|
+
async handleSignTypedData(message, origin) {
|
|
4185
|
+
const { sessionToken, userId, projectId, typedData, digest32, accessToken } = message.data;
|
|
4186
|
+
const { messageId } = message;
|
|
4187
|
+
if (!this.sessionManager.validateSession(sessionToken, origin)) {
|
|
4188
|
+
throw new Error("Invalid session");
|
|
4189
|
+
}
|
|
4190
|
+
const isAuthorized = await this.authManager.checkAuthorization(userId, projectId);
|
|
4191
|
+
if (!isAuthorized) {
|
|
4192
|
+
throw new Error("User has not authorized this application");
|
|
4193
|
+
}
|
|
4194
|
+
console.log(`[iframe] SIGN_TYPED_DATA: userId=${userId}, primaryType=${typedData?.primaryType}`);
|
|
4195
|
+
try {
|
|
4196
|
+
const signature = await this.signingManager.signTypedData(
|
|
4197
|
+
userId,
|
|
4198
|
+
projectId,
|
|
4199
|
+
origin,
|
|
4200
|
+
typedData,
|
|
4201
|
+
digest32,
|
|
4202
|
+
accessToken
|
|
4203
|
+
);
|
|
4204
|
+
this.messenger.sendResponse(
|
|
4205
|
+
messageId,
|
|
4206
|
+
{
|
|
4207
|
+
type: "LUMIA_PASSPORT_EIP712_SIGNATURE",
|
|
4208
|
+
signature
|
|
4209
|
+
},
|
|
4210
|
+
origin
|
|
4211
|
+
);
|
|
4212
|
+
console.log(`[iframe] \u2705 EIP712 message signed`);
|
|
4213
|
+
} catch (error) {
|
|
4214
|
+
console.error("[iframe] EIP712 signing failed:", error);
|
|
4215
|
+
throw error;
|
|
4216
|
+
}
|
|
4217
|
+
}
|
|
4008
4218
|
async handleGetAddress(message, origin) {
|
|
4009
4219
|
const { sessionToken, userId } = message.data;
|
|
4010
4220
|
const { messageId } = message;
|