@lumiapassport/ui-kit 1.5.1 → 1.5.3

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 CHANGED
@@ -13,43 +13,57 @@ React UI components and hooks for Lumia Passport - a secure, user-friendly authe
13
13
  ## Installation
14
14
 
15
15
  ```bash
16
- npm install @lumiapassport/ui-kit @lumiapassport/core
16
+ npm install @lumiapassport/ui-kit
17
17
  # or
18
- pnpm add @lumiapassport/ui-kit @lumiapassport/core
18
+ pnpm add @lumiapassport/ui-kit
19
19
  # or
20
- yarn add @lumiapassport/ui-kit @lumiapassport/core
20
+ yarn add @lumiapassport/ui-kit
21
21
  ```
22
22
 
23
- ### Peer Dependencies
23
+ ## Quick Start
24
24
 
25
- The following packages are required as peer dependencies:
25
+ ### 1. Setup QueryClient (Required)
26
26
 
27
- ```bash
28
- npm install react react-dom viem wagmi @tanstack/react-query
29
- ```
27
+ The UI Kit requires `@tanstack/react-query` for state management. First, create a query client:
30
28
 
31
- ## Quick Start
29
+ ```tsx
30
+ // queryClient.ts
31
+ import { QueryClient } from '@tanstack/react-query';
32
+
33
+ export const queryClient = new QueryClient({
34
+ defaultOptions: {
35
+ queries: {
36
+ staleTime: 1000 * 60 * 5, // 5 minutes
37
+ gcTime: 1000 * 60 * 10, // 10 minutes
38
+ refetchOnWindowFocus: false,
39
+ retry: false,
40
+ },
41
+ },
42
+ });
43
+ ```
32
44
 
33
- ### 1. Wrap your app with `LumiaPassportProvider`
45
+ ### 2. Wrap your app with providers
34
46
 
35
47
  ```tsx
48
+ import { QueryClientProvider } from '@tanstack/react-query';
36
49
  import { LumiaPassportProvider } from '@lumiapassport/ui-kit';
37
50
  import '@lumiapassport/ui-kit/dist/styles.css';
51
+ import { queryClient } from './queryClient';
38
52
 
39
- function App() {
53
+ function Root() {
40
54
  return (
41
- <LumiaPassportProvider
42
- config={{
43
- projectId: 'your-project-id', // Get from Lumia Passport Dashboard
44
- }}
45
- >
46
- <YourApp />
47
- </LumiaPassportProvider>
55
+ <QueryClientProvider client={queryClient}>
56
+ <LumiaPassportProvider
57
+ projectId="your-project-id" // Get from Lumia Passport Dashboard
58
+ >
59
+ <YourApp />
60
+ </LumiaPassportProvider>
61
+ </QueryClientProvider>
48
62
  );
49
63
  }
50
64
  ```
51
65
 
52
- ### 2. Add the Connect Button
66
+ ### 3. Add the Connect Button
53
67
 
54
68
  ```tsx
55
69
  import { ConnectWalletButton } from '@lumiapassport/ui-kit';
@@ -58,7 +72,7 @@ function YourApp() {
58
72
  return (
59
73
  <div>
60
74
  <h1>My App</h1>
61
- <ConnectWalletButton />
75
+ <ConnectWalletButton label="Sign in" mode="compact"/>
62
76
  </div>
63
77
  );
64
78
  }
@@ -66,22 +80,24 @@ function YourApp() {
66
80
 
67
81
  That's it! The `ConnectWalletButton` provides a complete authentication UI with wallet management.
68
82
 
83
+ > **Note:** Don't forget to wrap your app with `QueryClientProvider` from `@tanstack/react-query` before using `LumiaPassportProvider`, otherwise you'll get an error: "No QueryClient set, use QueryClientProvider to set one"
84
+
69
85
  ## Configuration Options
70
86
 
71
87
  ### Basic Configuration
72
88
 
73
89
  ```tsx
74
90
  <LumiaPassportProvider
75
- config={{
76
- projectId: 'your-project-id', // Required
77
-
78
- // Optional: Custom RPC and network
79
- rpcUrl: 'https://beam-rpc.lumia.org',
80
- explorerUrl: 'https://beam-explorer.lumia.org',
81
-
82
- // Optional: Custom smart contract addresses
83
- aaFactoryAddress: '0x...',
84
- paymasterAddress: '0x...',
91
+ projectId="your-project-id" // Required
92
+ initialConfig={{
93
+ network: {
94
+ name: 'Lumia Beam',
95
+ symbol: 'LUMIA',
96
+ chainId: 2030232745,
97
+ rpcUrl: 'https://beam-rpc.lumia.org',
98
+ explorerUrl: 'https://beam-explorer.lumia.org',
99
+ testnet: true,
100
+ },
85
101
  }}
86
102
  >
87
103
  ```
@@ -90,27 +106,134 @@ That's it! The `ConnectWalletButton` provides a complete authentication UI with
90
106
 
91
107
  ```tsx
92
108
  <LumiaPassportProvider
93
- config={{
94
- projectId: 'your-project-id',
109
+ projectId="your-project-id"
110
+ initialConfig={{
111
+ // UI customization
112
+ ui: {
113
+ theme: 'dark', // 'light' | 'dark' | 'auto'
114
+ title: 'Welcome to MyApp',
115
+ subtitle: 'Sign in to continue',
116
+ authOrder: ['email', 'passkey', 'social'],
117
+ modal: {
118
+ width: '420px',
119
+ borderRadius: '24px'
120
+ },
121
+ branding: {
122
+ tagline: 'Powered by MPC',
123
+ link: { text: 'Learn More', url: 'https://example.com/docs' },
124
+ },
125
+ // Custom colors
126
+ colors: {
127
+ dark: {
128
+ background: '#060117',
129
+ text: '#fefdff',
130
+ textSecondary: '#c4c2c7',
131
+ textMuted: '#8a8890',
132
+ border: '#2a1a3a',
133
+ buttonBackground: '#ff6b35',
134
+ buttonBackgroundEnd: '#f7931e',
135
+ buttonText: '#ffffff',
136
+ buttonBackgroundHover: '#e55a2b',
137
+ buttonBackgroundHoverEnd: '#d67d1a',
138
+ connectedButtonBackground: 'rgba(27, 90, 226, 0.6)',
139
+ connectedButtonBorder: '#4a5568'
140
+ },
141
+ light: {
142
+ background: '#fefdff',
143
+ text: '#060117',
144
+ textSecondary: '#4a4754',
145
+ textMuted: '#6b6a70',
146
+ border: '#e0dde6',
147
+ buttonBackground: '#9333ea',
148
+ buttonBackgroundEnd: '#2563eb',
149
+ buttonText: '#fefdff',
150
+ buttonBackgroundHover: '#7e22ce',
151
+ buttonBackgroundHoverEnd: '#1d4ed8',
152
+ connectedButtonBackground: '#f3f4f6',
153
+ connectedButtonBorder: '#d1d5db'
154
+ }
155
+ },
156
+ },
95
157
 
96
158
  // Authentication providers
97
- authProviders: {
98
- email: true, // Email OTP
99
- passkey: true, // Passkey/WebAuthn
100
- telegram: true, // Telegram Mini App
101
- wallet: true, // External wallet connection
159
+ email: {
160
+ enabled: true,
161
+ placeholder: 'Enter your email',
162
+ buttonText: 'Continue',
163
+ verificationTitle: 'Check your email',
102
164
  },
103
-
104
- // UI customization
105
- theme: {
106
- mode: 'dark', // 'light' | 'dark' | 'auto'
107
- primaryColor: '#6366f1', // Custom brand color
165
+ passkey: {
166
+ enabled: true,
167
+ showCreateButton: true,
168
+ primaryButtonText: 'Sign in with Passkey',
169
+ },
170
+ social: {
171
+ enabled: true,
172
+ gridColumns: 2,
173
+ providers: [
174
+ { id: 'Discord', name: 'Discord', enabled: true, comingSoon: false },
175
+ { id: 'telegram', name: 'Telegram', enabled: true, comingSoon: false },
176
+ ],
177
+ },
178
+ wallet: {
179
+ enabled: true,
180
+ supportedChains: [994873017, 2030232745],
181
+ requireSignature: true,
182
+ walletConnectProjectId: 'your-walletconnect-id',
108
183
  },
109
184
 
110
185
  // Features
111
186
  features: {
112
- backup: true, // Enable key backup/recovery
113
- mpcSecurity: true, // Enable MPC iframe isolation
187
+ mpcSecurity: true,
188
+ strictMode: false,
189
+ requestDeduplication: true,
190
+ kycNeeded: false,
191
+ displayNameNeeded: false,
192
+ },
193
+
194
+ // KYC configuration (if needed)
195
+ kyc: {
196
+ provider: 'sumsub',
197
+ options: { levelName: 'basic-kyc', flowName: 'default' }
198
+ },
199
+
200
+ // Warnings
201
+ warnings: {
202
+ backupWarning: true,
203
+ emailNotConnectedWarning: true,
204
+ },
205
+
206
+ // Network configuration
207
+ network: {
208
+ name: 'Lumia Beam',
209
+ symbol: 'LUMIA',
210
+ chainId: 2030232745,
211
+ rpcUrl: 'https://beam-rpc.lumia.org',
212
+ explorerUrl: 'https://beam-explorer.lumia.org',
213
+ testnet: true,
214
+ },
215
+ }}
216
+ callbacks={{
217
+ onLumiaPassportConnecting: ({ method, provider }) => {
218
+ console.log('Connecting with:', method, provider);
219
+ },
220
+ onLumiaPassportConnect: ({ address, session }) => {
221
+ console.log('Connected:', address);
222
+ },
223
+ onLumiaPassportAccount: ({ userId, address, session, hasKeyshare }) => {
224
+ console.log('Account ready:', userId);
225
+ },
226
+ onLumiaPassportUpdate: ({ providers }) => {
227
+ console.log('Profile updated:', providers);
228
+ },
229
+ onLumiaPassportDisconnect: ({ address, userId }) => {
230
+ console.log('Disconnected:', address);
231
+ },
232
+ onLumiaPassportError: ({ error, message }) => {
233
+ console.error('Error:', message);
234
+ },
235
+ onWalletReady: (status) => {
236
+ console.log('Wallet ready:', status.ready);
114
237
  },
115
238
  }}
116
239
  >
@@ -348,12 +471,14 @@ Secure biometric authentication with device passkeys.
348
471
  Authentication via Telegram for mini apps.
349
472
 
350
473
  ```tsx
351
- // Requires Telegram bot configuration
352
- // Set in config:
353
- config={{
354
- telegram: {
355
- botName: 'your_bot_name',
356
- }
474
+ // Configure via social providers:
475
+ initialConfig={{
476
+ social: {
477
+ enabled: true,
478
+ providers: [
479
+ { id: 'telegram', name: 'Telegram', enabled: true },
480
+ ],
481
+ },
357
482
  }}
358
483
  ```
359
484
 
@@ -374,10 +499,10 @@ Connect existing wallets (MetaMask, WalletConnect, etc.).
374
499
  import '@lumiapassport/ui-kit/dist/styles.css';
375
500
 
376
501
  <LumiaPassportProvider
377
- config={{
378
- projectId: 'your-project-id',
379
- theme: {
380
- mode: 'dark', // or 'light', 'auto'
502
+ projectId="your-project-id"
503
+ initialConfig={{
504
+ ui: {
505
+ theme: 'dark', // or 'light', 'auto'
381
506
  }
382
507
  }}
383
508
  >
@@ -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.5.1</title>
18
+ <title>Lumia Passport Secure Wallet - iframe version 1.5.3</title>
19
19
 
20
20
  <!-- Styles will be injected by build process -->
21
21
  <style>
@@ -203,7 +203,7 @@
203
203
 
204
204
  .app-logo {
205
205
  width: 64px;
206
- height: 64px;
206
+ /* height: 64px; */
207
207
  border-radius: 12px;
208
208
  border: 1px solid var(--iframe-border);
209
209
  object-fit: cover;
@@ -284,20 +284,20 @@
284
284
  .permissions-box {
285
285
  margin: 1.5rem 2rem;
286
286
  padding: 1rem;
287
- border: 1px solid #30363d;
287
+ border: 1px solid var(--iframe-text);
288
288
  border-radius: 8px;
289
- background: #161b22;
289
+ background: transparent;
290
290
  }
291
291
 
292
292
  .permissions-header {
293
293
  font-size: 0.875rem;
294
- color: #c9d1d9;
294
+ color: var(--iframe-text);
295
295
  margin-bottom: 1rem;
296
296
  line-height: 1.5;
297
297
  }
298
298
 
299
299
  .project-owner {
300
- color: #8b949e;
300
+ color: var(--iframe-text-secondary);
301
301
  }
302
302
 
303
303
  .permission-group {
@@ -311,7 +311,7 @@
311
311
  .permission-group-title {
312
312
  font-size: 0.8125rem;
313
313
  font-weight: 600;
314
- color: #c9d1d9;
314
+ color: var(--iframe-text);
315
315
  margin-bottom: 0.5rem;
316
316
  }
317
317
 
@@ -323,7 +323,7 @@
323
323
 
324
324
  .permissions-list li {
325
325
  font-size: 0.8125rem;
326
- color: #c9d1d9;
326
+ color: var(--iframe-text);
327
327
  padding: 0.25rem 0;
328
328
  padding-left: 1.25rem;
329
329
  position: relative;
@@ -333,7 +333,7 @@
333
333
  content: "•";
334
334
  position: absolute;
335
335
  left: 0.5rem;
336
- color: #8b949e;
336
+ color: var(--iframe-text-secondary);
337
337
  }
338
338
 
339
339
  /* Actions Section */
@@ -423,19 +423,14 @@
423
423
  font-size: 0.9rem;
424
424
  }
425
425
 
426
- .detail-row:not(:last-child) {
427
- border-bottom: 1px solid #e5e7eb;
428
- }
429
-
430
426
  .detail-row code {
431
427
  font-family: 'Courier New', monospace;
432
- font-size: 0.85rem;
433
- color: #6b7280;
434
428
  }
435
429
 
430
+
436
431
  /* Security Warning */
437
432
  .security-warning {
438
- margin: 1rem 2rem;
433
+ margin: 1.5rem 2rem;
439
434
  padding: 1rem;
440
435
  background: #fff8c5;
441
436
  border: 1px solid #d29922;
@@ -516,7 +511,7 @@
516
511
 
517
512
  /* Trust app checkbox section */
518
513
  .trust-app-section {
519
- margin: 1rem 0;
514
+ margin: 1.5rem 2rem;
520
515
  padding: 0.75rem;
521
516
  background: #f9fafb;
522
517
  border-radius: 6px;
@@ -2595,6 +2595,11 @@ var SigningManager = class extends TokenRefreshApiClient {
2595
2595
  constructor() {
2596
2596
  super();
2597
2597
  this.wasmLoaded = false;
2598
+ this.explorerUrl = "https://beam-explorer.lumia.org";
2599
+ this.METADATA_API_URL = "https://dashboard.lumiapassport.com/api/public/project";
2600
+ this.METADATA_CACHE_TTL = 36e5;
2601
+ // 1 hour in milliseconds
2602
+ this.metadataCache = /* @__PURE__ */ new Map();
2598
2603
  this.storage = new StorageManager();
2599
2604
  this.trustedApps = new TrustedAppsManager();
2600
2605
  }
@@ -2612,6 +2617,34 @@ var SigningManager = class extends TokenRefreshApiClient {
2612
2617
  throw new Error("Failed to initialize signing WASM module");
2613
2618
  }
2614
2619
  }
2620
+ /**
2621
+ * Fetch project metadata from public API (with caching)
2622
+ */
2623
+ async fetchProjectMetadata(projectId) {
2624
+ const cached = this.metadataCache.get(projectId);
2625
+ const now = Date.now();
2626
+ if (cached && now - cached.timestamp < this.METADATA_CACHE_TTL) {
2627
+ console.log(`[iframe][Sign] Using cached metadata for project: ${cached.data.name}`);
2628
+ return cached.data;
2629
+ }
2630
+ try {
2631
+ const response = await fetch(`${this.METADATA_API_URL}/${projectId}/metadata`);
2632
+ if (!response.ok) {
2633
+ console.warn(`[iframe][Sign] Failed to fetch project metadata: ${response.status}`);
2634
+ return null;
2635
+ }
2636
+ const metadata = await response.json();
2637
+ console.log(`[iframe][Sign] Fetched metadata for project: ${metadata.name}`);
2638
+ this.metadataCache.set(projectId, {
2639
+ data: metadata,
2640
+ timestamp: now
2641
+ });
2642
+ return metadata;
2643
+ } catch (error) {
2644
+ console.error("[iframe][Sign] Error fetching project metadata:", error);
2645
+ return null;
2646
+ }
2647
+ }
2615
2648
  /**
2616
2649
  * Sign transaction with user confirmation
2617
2650
  */
@@ -2719,8 +2752,9 @@ var SigningManager = class extends TokenRefreshApiClient {
2719
2752
  */
2720
2753
  async showConfirmationDialog(userId, projectId, project, origin, transaction, risk) {
2721
2754
  this.showIframe();
2755
+ const metadata = await this.fetchProjectMetadata(projectId);
2722
2756
  return new Promise((resolve) => {
2723
- const modal = this.createConfirmationModal(userId, projectId, project, origin, transaction, risk);
2757
+ const modal = this.createConfirmationModal(userId, projectId, project, origin, transaction, risk, metadata);
2724
2758
  const confirmBtn = modal.querySelector(".confirm-btn");
2725
2759
  const cancelBtn = modal.querySelector(".cancel-btn");
2726
2760
  const trustCheckbox = modal.querySelector(".trust-app-checkbox");
@@ -2743,79 +2777,91 @@ var SigningManager = class extends TokenRefreshApiClient {
2743
2777
  /**
2744
2778
  * Create confirmation modal UI
2745
2779
  */
2746
- createConfirmationModal(userId, projectId, project, origin, transaction, risk) {
2780
+ createConfirmationModal(userId, projectId, project, origin, transaction, risk, metadata) {
2747
2781
  const modal = document.createElement("div");
2748
2782
  modal.className = "transaction-confirmation-modal";
2749
- const riskClass = risk.level.toLowerCase();
2783
+ const isVerifiedOrigin = project.domains.includes(origin);
2750
2784
  const isUserOp = !!transaction.userOpDetails;
2751
- const formatHex = (value, maxLength = 20) => {
2752
- if (!value) return "N/A";
2753
- if (value.length > maxLength) {
2754
- return `${value.substring(0, maxLength)}...`;
2755
- }
2756
- return value;
2757
- };
2758
- const formatGas = (gasLimit, gasPrice) => {
2759
- if (!gasLimit || !gasPrice) return "N/A";
2760
- try {
2761
- const gas = BigInt(gasLimit);
2762
- const price = BigInt(gasPrice);
2763
- const totalWei = gas * price;
2764
- const totalEth = Number(totalWei) / 1e18;
2765
- return totalEth.toFixed(9) + " ETH";
2766
- } catch {
2767
- return "N/A";
2768
- }
2769
- };
2770
2785
  const isSponsored = isUserOp && !!transaction.userOpDetails?.paymaster;
2771
2786
  const estimatedCost = isSponsored ? "0 (Sponsored)" : "Free (Sponsored Transaction)";
2787
+ const displayName = metadata?.name || project.name;
2788
+ const displayLogo = metadata?.logo || project.logoUrl;
2789
+ const fromAddress = transaction.userOpDetails?.sender;
2790
+ const toAddress = transaction.userOpDetails?.callTarget || transaction.to;
2772
2791
  modal.innerHTML = `
2773
2792
  <div class="modal-overlay">
2774
2793
  <div class="modal-content">
2775
- <div class="app-identity">
2776
- <div class="app-info">
2777
- <h3>${project.name}</h3>
2778
- <p class="origin">${origin}</p>
2794
+ <!-- Header with logos -->
2795
+ <div class="auth-header">
2796
+ <div class="logo-container">
2797
+ ${displayLogo ? `<img src="${displayLogo}" alt="${displayName}" class="app-logo" />` : '<div class="app-logo-placeholder"></div>'}
2798
+ <div class="logo-connector">
2799
+ <svg width="24" height="16" viewBox="0 0 24 16" fill="currentColor">
2800
+ <path d="M23.7071 8.70711C24.0976 8.31658 24.0976 7.68342 23.7071 7.29289L17.3431 0.928932C16.9526 0.538408 16.3195 0.538408 15.9289 0.928932C15.5384 1.31946 15.5384 1.95262 15.9289 2.34315L21.5858 8L15.9289 13.6569C15.5384 14.0474 15.5384 14.6805 15.9289 15.0711C16.3195 15.4616 16.9526 15.4616 17.3431 15.0711L23.7071 8.70711ZM0 9H23V7H0V9Z"/>
2801
+ </svg>
2802
+ </div>
2803
+ <img src="./lumia-logo.svg" alt="Lumia Passport" class="lumia-logo" />
2779
2804
  </div>
2780
2805
  </div>
2781
2806
 
2782
- <h2>Confirm Transaction</h2>
2807
+ <!-- Transaction title -->
2808
+ <h2 class="auth-title">Confirm Transaction</h2>
2783
2809
 
2784
- ${risk.level !== "LOW" ? `
2785
- <div class="risk-warning ${riskClass}">
2786
- <strong>\u26A0\uFE0F ${risk.level} RISK</strong>
2787
- ${risk.reasons.map((r) => `<p>\u2022 ${r}</p>`).join("")}
2810
+ <!-- Application info -->
2811
+ <div class="auth-description">
2812
+ <div class="domain-info ${isVerifiedOrigin ? "verified" : "unverified"}">
2813
+ <span class="domain-label">
2814
+ ${isVerifiedOrigin ? "\u2713 Verified:" : "\u26A0\uFE0F Unverified:"}
2815
+ </span>
2816
+ <span class="domain-value">${origin}</span>
2788
2817
  </div>
2789
- ` : ""}
2818
+ </div>
2790
2819
 
2791
- <div class="tx-details">
2792
- <div class="detail-row">
2793
- <span><strong>From:</strong></span>
2794
- <code>${formatHex(transaction.userOpDetails?.sender || transaction.to, 16)}</code>
2795
- </div>
2820
+ <!-- Transaction details box -->
2821
+ <div class="permissions-box">
2822
+ ${fromAddress ? `
2823
+ <div class="detail-row" style="margin-bottom: 0.5rem;">
2824
+ <div style="color: var(--iframe-text-secondary); font-size: 0.875rem; margin-bottom: 0.25rem;"><strong>From:</strong></div>
2825
+ <code style="font-size: 0.75rem; word-break: break-all; display: block; color: var(--iframe-text);">${fromAddress}</code>
2826
+ </div>
2827
+ ` : ""}
2828
+
2829
+ ${toAddress ? `
2830
+ <div class="detail-row" style="margin-bottom: 0.5rem;">
2831
+ <div style="color: var(--iframe-text-secondary); font-size: 0.875rem; margin-bottom: 0.25rem;"><strong>To:</strong></div>
2832
+ <code style="font-size: 0.75rem; word-break: break-all; display: block; color: var(--iframe-text);">${toAddress}</code>
2833
+ </div>
2834
+ ` : ""}
2796
2835
 
2797
2836
  ${isUserOp && transaction.userOpDetails?.callData && transaction.userOpDetails.callData !== "0x" ? `
2798
- <div class="detail-row">
2799
- <span><strong>Action:</strong></span>
2800
- <span>Contract Interaction</span>
2837
+ <div class="detail-row" style="margin-bottom: 0.5rem;">
2838
+ <span style="color: var(--iframe-text-secondary);"><strong>Action:</strong></span>
2839
+ <span style="color: var(--iframe-text);">Contract Interaction</span>
2801
2840
  </div>
2802
2841
  ` : ""}
2803
2842
 
2804
- <div class="detail-row">
2805
- <span><strong>Estimated Cost:</strong></span>
2843
+ <div class="detail-row" style="margin-bottom: 0.5rem;">
2844
+ <span style="color: var(--iframe-text-secondary);"><strong>Estimated Cost:</strong></span>
2806
2845
  <strong style="color: #10b981;">${estimatedCost}</strong>
2807
2846
  </div>
2808
2847
 
2809
2848
  ${isSponsored ? `
2810
- <div class="security-notice" style="background: #d1fae5; border-left-color: #10b981; margin-top: 1rem;">
2811
- <p>\u2713 Gas fees will be paid by the application (no cost to you)</p>
2849
+ <div class="detail-row" style="background: #d1fae5; border-radius: 6px; padding: 0.75rem; margin-top: 0.5rem;">
2850
+ <span style="color: #047857;">\u2713 Gas fees will be paid by the application (no cost to you)</span>
2812
2851
  </div>
2813
2852
  ` : ""}
2814
2853
  </div>
2815
2854
 
2816
- <div class="security-notice">
2817
- <p>\u26A0\uFE0F Please verify this transaction carefully before confirming.</p>
2818
- </div>
2855
+ ${risk.level !== "LOW" ? `
2856
+ <div class="security-warning">
2857
+ <strong>\u26A0\uFE0F ${risk.level} RISK</strong>
2858
+ ${risk.reasons.map((r) => `<div>\u2022 ${r}</div>`).join("")}
2859
+ </div>
2860
+ ` : `
2861
+ <div class="security-warning">
2862
+ <strong>\u26A0\uFE0F Warning:</strong> Please verify this transaction carefully before confirming.
2863
+ </div>
2864
+ `}
2819
2865
 
2820
2866
  <div class="trust-app-section">
2821
2867
  <label class="trust-app-label">
@@ -2824,10 +2870,16 @@ var SigningManager = class extends TokenRefreshApiClient {
2824
2870
  </label>
2825
2871
  </div>
2826
2872
 
2873
+ <!-- Action buttons -->
2827
2874
  <div class="actions">
2828
2875
  <button class="cancel-btn">Reject</button>
2829
2876
  <button class="confirm-btn">Confirm</button>
2830
2877
  </div>
2878
+
2879
+ <!-- Footer notice -->
2880
+ <div class="auth-footer">
2881
+ <p class="footer-note">You can manage trusted applications in your Lumia Passport settings.</p>
2882
+ </div>
2831
2883
  </div>
2832
2884
  </div>
2833
2885
  `;
@@ -2922,12 +2974,21 @@ var AuthorizationManager = class {
2922
2974
  constructor() {
2923
2975
  this.STORAGE_KEY_PREFIX = "auth";
2924
2976
  this.METADATA_API_URL = "https://dashboard.lumiapassport.com/api/public/project";
2977
+ this.METADATA_CACHE_TTL = 36e5;
2978
+ // 1 hour in milliseconds
2979
+ this.metadataCache = /* @__PURE__ */ new Map();
2925
2980
  console.log("[iframe][Auth] Initialized");
2926
2981
  }
2927
2982
  /**
2928
- * Fetch project metadata from public API
2983
+ * Fetch project metadata from public API (with caching)
2929
2984
  */
2930
2985
  async fetchProjectMetadata(projectId) {
2986
+ const cached = this.metadataCache.get(projectId);
2987
+ const now = Date.now();
2988
+ if (cached && now - cached.timestamp < this.METADATA_CACHE_TTL) {
2989
+ console.log(`[iframe][Auth] Using cached metadata for project: ${cached.data.name}`);
2990
+ return cached.data;
2991
+ }
2931
2992
  try {
2932
2993
  const response = await fetch(`${this.METADATA_API_URL}/${projectId}/metadata`);
2933
2994
  if (!response.ok) {
@@ -2936,6 +2997,10 @@ var AuthorizationManager = class {
2936
2997
  }
2937
2998
  const metadata = await response.json();
2938
2999
  console.log(`[iframe][Auth] Fetched metadata for project: ${metadata.name}`);
3000
+ this.metadataCache.set(projectId, {
3001
+ data: metadata,
3002
+ timestamp: now
3003
+ });
2939
3004
  return metadata;
2940
3005
  } catch (error) {
2941
3006
  console.error("[iframe][Auth] Error fetching project metadata:", error);
@@ -3659,7 +3724,7 @@ var BackupManager = class {
3659
3724
  };
3660
3725
 
3661
3726
  // src/iframe/main.ts
3662
- var IFRAME_VERSION = "1.5.1";
3727
+ var IFRAME_VERSION = "1.5.3";
3663
3728
  var IframeWallet = class {
3664
3729
  constructor() {
3665
3730
  console.log("=".repeat(60));