@lumiapassport/ui-kit 1.12.5 → 1.13.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 +210 -159
- package/dist/iframe/_headers +2 -2
- package/dist/iframe/index.html +1 -1
- package/dist/iframe/main.js +1 -1
- package/dist/index.cjs +2137 -2055
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -24
- package/dist/index.d.ts +24 -24
- package/dist/index.js +2027 -1937
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,9 +13,9 @@ 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
|
|
16
|
+
npm install @lumiapassport/ui-kit
|
|
17
17
|
# or
|
|
18
|
-
pnpm add @lumiapassport/ui-kit
|
|
18
|
+
pnpm add @lumiapassport/ui-kit
|
|
19
19
|
# or
|
|
20
20
|
yarn add @lumiapassport/ui-kit
|
|
21
21
|
```
|
|
@@ -24,31 +24,28 @@ yarn add @lumiapassport/ui-kit
|
|
|
24
24
|
|
|
25
25
|
### 1. Setup QueryClient (Required)
|
|
26
26
|
|
|
27
|
-
The UI Kit requires `@tanstack/react-query` for
|
|
27
|
+
The UI Kit requires `@tanstack/react-query` for query management. First, create a query client:
|
|
28
28
|
|
|
29
29
|
```tsx
|
|
30
30
|
// queryClient.ts
|
|
31
|
-
import { QueryClient } from '@tanstack/react-query'
|
|
31
|
+
import { QueryClient } from '@tanstack/react-query'
|
|
32
32
|
|
|
33
33
|
export const queryClient = new QueryClient({
|
|
34
|
-
defaultOptions: {
|
|
35
|
-
|
|
36
|
-
staleTime: 1000 * 60 * 5, // 5 minutes
|
|
37
|
-
gcTime: 1000 * 60 * 10, // 10 minutes
|
|
38
|
-
refetchOnWindowFocus: false,
|
|
39
|
-
retry: false,
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
});
|
|
34
|
+
defaultOptions: {}
|
|
35
|
+
})
|
|
43
36
|
```
|
|
44
37
|
|
|
45
38
|
### 2. Wrap your app with providers
|
|
46
39
|
|
|
47
40
|
```tsx
|
|
48
|
-
import {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
41
|
+
import {
|
|
42
|
+
//
|
|
43
|
+
LumiaPassportProvider,
|
|
44
|
+
LumiaPassportSessionProvider,
|
|
45
|
+
LumiaRainbowKitProvider
|
|
46
|
+
} from '@lumiapassport/ui-kit'
|
|
47
|
+
|
|
48
|
+
import { queryClient } from './queryClient'
|
|
52
49
|
|
|
53
50
|
function Root() {
|
|
54
51
|
return (
|
|
@@ -56,25 +53,51 @@ function Root() {
|
|
|
56
53
|
<LumiaPassportProvider
|
|
57
54
|
projectId="your-project-id" // Get from Lumia Passport Dashboard
|
|
58
55
|
>
|
|
59
|
-
<
|
|
56
|
+
<LumiaRainbowKitProvider>
|
|
57
|
+
<LumiaPassportSessionProvider>
|
|
58
|
+
<YourApp />
|
|
59
|
+
</LumiaPassportSessionProvider>
|
|
60
|
+
</LumiaRainbowKitProvider>
|
|
60
61
|
</LumiaPassportProvider>
|
|
61
62
|
</QueryClientProvider>
|
|
62
|
-
)
|
|
63
|
+
)
|
|
63
64
|
}
|
|
64
65
|
```
|
|
65
66
|
|
|
66
67
|
### 3. Add the Connect Button
|
|
67
68
|
|
|
68
69
|
```tsx
|
|
69
|
-
import { ConnectWalletButton } from '@lumiapassport/ui-kit'
|
|
70
|
+
import { ConnectWalletButton } from '@lumiapassport/ui-kit'
|
|
70
71
|
|
|
71
72
|
function YourApp() {
|
|
72
73
|
return (
|
|
73
74
|
<div>
|
|
74
75
|
<h1>My App</h1>
|
|
75
|
-
<ConnectWalletButton label="Sign in"
|
|
76
|
+
<ConnectWalletButton label="Sign in" />
|
|
76
77
|
</div>
|
|
77
|
-
)
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 3.1 (Optional)
|
|
83
|
+
|
|
84
|
+
Custom unconnected button can be provided via ConnectButton prop.
|
|
85
|
+
Prop consumes standart HTMLButton component and will provide required onClick to it
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
import { ConnectWalletButton } from '@lumiapassport/ui-kit'
|
|
89
|
+
|
|
90
|
+
function CustomButtonComponent(props: HTMLAttributes<HTMLButtonElement>) => {
|
|
91
|
+
return (<button {...props}/>)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function YourApp() {
|
|
95
|
+
return (
|
|
96
|
+
<div>
|
|
97
|
+
<h1>My App</h1>
|
|
98
|
+
<ConnectWalletButton label="Sign in" ConnectButton={CustomButtonComponent} />
|
|
99
|
+
</div>
|
|
100
|
+
)
|
|
78
101
|
}
|
|
79
102
|
```
|
|
80
103
|
|
|
@@ -109,20 +132,23 @@ That's it! The `ConnectWalletButton` provides a complete authentication UI with
|
|
|
109
132
|
projectId="your-project-id"
|
|
110
133
|
initialConfig={{
|
|
111
134
|
// UI customization
|
|
135
|
+
preferedColorMode?: 'light', // 'light' | 'dark'
|
|
136
|
+
|
|
112
137
|
ui: {
|
|
113
|
-
theme: 'dark', // 'light' | 'dark' | 'auto'
|
|
114
138
|
title: 'Welcome to MyApp',
|
|
115
139
|
subtitle: 'Sign in to continue',
|
|
140
|
+
|
|
141
|
+
// beta
|
|
142
|
+
dialogClassName: 'string',
|
|
143
|
+
|
|
116
144
|
authOrder: ['email', 'passkey', 'social'],
|
|
117
|
-
|
|
118
|
-
width: '420px',
|
|
119
|
-
borderRadius: '24px'
|
|
120
|
-
},
|
|
145
|
+
|
|
121
146
|
branding: {
|
|
122
147
|
tagline: 'Powered by MPC',
|
|
123
148
|
link: { text: 'Learn More', url: 'https://example.com/docs' },
|
|
124
149
|
},
|
|
125
|
-
|
|
150
|
+
|
|
151
|
+
// TO BE DEPRECATED, cssv is used
|
|
126
152
|
colors: {
|
|
127
153
|
dark: {
|
|
128
154
|
background: '#060117',
|
|
@@ -241,20 +267,21 @@ That's it! The `ConnectWalletButton` provides a complete authentication UI with
|
|
|
241
267
|
|
|
242
268
|
## Using Hooks
|
|
243
269
|
|
|
244
|
-
> **Note:** The
|
|
270
|
+
> **Note:** The `useLumiaPassportSession` hook is based on pure Zustand store so if you already using hook consider 2 options: 1) refactor state extarction so it uses zustand state extraction feature. 2) consider using new session hooks: `useLumiaPassportAccountSession`, `useLumiaPassportAddress` etc. Otherwise you might experience excessive re-rendering issues
|
|
245
271
|
|
|
246
|
-
###
|
|
272
|
+
### useLumiaPassportAccountSession, useLumiaPassportLoadingStatus, useLumiaPassportBalance, useLumiaPassportIFrameReady, useLumiaPassportAddress, useLumiaPassportError, useLumiaPassportLoadingStatus, useLumiaPassportRecoveryUserId, useLumiaPassportHasServerVault
|
|
247
273
|
|
|
248
274
|
```tsx
|
|
249
|
-
import {
|
|
275
|
+
import { useLumiaPassportAccountSession, useLumiaPassportLoadingStatus } from '@lumiapassport/ui-kit'
|
|
250
276
|
|
|
251
277
|
function MyComponent() {
|
|
252
|
-
const
|
|
278
|
+
const session = useLumiaPassportAccountSession() // const session = useLumiaPassportSession(s => s.session) - with prev hook & Zustand state extraction feature
|
|
279
|
+
const { isSessionLoading } = useLumiaPassportLoadingStatus()
|
|
253
280
|
|
|
254
|
-
if (
|
|
281
|
+
if (isSessionLoading) return <div>Loading...</div>
|
|
255
282
|
|
|
256
283
|
if (!session) {
|
|
257
|
-
return <div>Not authenticated. Please connect your wallet.</div
|
|
284
|
+
return <div>Not authenticated. Please connect your wallet.</div>
|
|
258
285
|
}
|
|
259
286
|
|
|
260
287
|
return (
|
|
@@ -264,30 +291,30 @@ function MyComponent() {
|
|
|
264
291
|
<p>Wallet Address: {session.ownerAddress}</p>
|
|
265
292
|
<p>Smart Account: {session.accountAddress}</p>
|
|
266
293
|
</div>
|
|
267
|
-
)
|
|
294
|
+
)
|
|
268
295
|
}
|
|
269
296
|
```
|
|
270
297
|
|
|
271
298
|
### useSendTransaction - Send Transactions
|
|
272
299
|
|
|
273
300
|
```tsx
|
|
274
|
-
import { useSendTransaction } from '@lumiapassport/ui-kit'
|
|
301
|
+
import { useSendTransaction } from '@lumiapassport/ui-kit'
|
|
275
302
|
|
|
276
303
|
function TransactionExample() {
|
|
277
|
-
const { sendTransaction, isPending } = useSendTransaction()
|
|
304
|
+
const { sendTransaction, isPending } = useSendTransaction()
|
|
278
305
|
|
|
279
306
|
const handleSend = async () => {
|
|
280
307
|
try {
|
|
281
308
|
const userOpHash = await sendTransaction({
|
|
282
309
|
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
|
|
283
310
|
value: '1000000000000000000', // 1 ETH in wei
|
|
284
|
-
data: '0x'
|
|
285
|
-
})
|
|
286
|
-
console.log('UserOp hash:', userOpHash)
|
|
311
|
+
data: '0x' // Optional contract call data
|
|
312
|
+
})
|
|
313
|
+
console.log('UserOp hash:', userOpHash)
|
|
287
314
|
} catch (error) {
|
|
288
|
-
console.error('Transaction failed:', error)
|
|
315
|
+
console.error('Transaction failed:', error)
|
|
289
316
|
}
|
|
290
|
-
}
|
|
317
|
+
}
|
|
291
318
|
|
|
292
319
|
return (
|
|
293
320
|
<div>
|
|
@@ -295,7 +322,7 @@ function TransactionExample() {
|
|
|
295
322
|
{isPending ? 'Sending...' : 'Send Transaction'}
|
|
296
323
|
</button>
|
|
297
324
|
</div>
|
|
298
|
-
)
|
|
325
|
+
)
|
|
299
326
|
}
|
|
300
327
|
```
|
|
301
328
|
|
|
@@ -304,39 +331,40 @@ function TransactionExample() {
|
|
|
304
331
|
For direct control without using the React hook, you can use `sendUserOperation` function:
|
|
305
332
|
|
|
306
333
|
```tsx
|
|
307
|
-
import { sendUserOperation,
|
|
334
|
+
import { sendUserOperation, useLumiaPassportAccountSession } from '@lumiapassport/ui-kit'
|
|
308
335
|
|
|
309
336
|
function DirectTransactionExample() {
|
|
310
|
-
const
|
|
337
|
+
const session = useLumiaPassportAccountSession()
|
|
311
338
|
|
|
312
339
|
const handleSend = async () => {
|
|
313
340
|
if (!session) {
|
|
314
|
-
console.error('No active session')
|
|
315
|
-
return
|
|
341
|
+
console.error('No active session')
|
|
342
|
+
return
|
|
316
343
|
}
|
|
317
344
|
|
|
318
345
|
try {
|
|
319
346
|
// Send transaction directly with full control
|
|
320
347
|
const userOpHash = await sendUserOperation(
|
|
321
|
-
session,
|
|
348
|
+
session, // Required: session from useLumiaPassportAccountSession
|
|
322
349
|
'0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', // to address
|
|
323
|
-
'1000000000000000000',
|
|
324
|
-
'0x',
|
|
325
|
-
'standard',
|
|
326
|
-
'v0.7'
|
|
327
|
-
)
|
|
350
|
+
'1000000000000000000', // value in wei (1 ETH)
|
|
351
|
+
'0x', // data (optional contract call)
|
|
352
|
+
'standard', // fee type: 'economy' | 'standard' | 'fast'
|
|
353
|
+
'v0.7' // EntryPoint version
|
|
354
|
+
)
|
|
328
355
|
|
|
329
|
-
console.log('Transaction submitted:', userOpHash)
|
|
356
|
+
console.log('Transaction submitted:', userOpHash)
|
|
330
357
|
} catch (error) {
|
|
331
|
-
console.error('Transaction failed:', error)
|
|
358
|
+
console.error('Transaction failed:', error)
|
|
332
359
|
}
|
|
333
|
-
}
|
|
360
|
+
}
|
|
334
361
|
|
|
335
|
-
return <button onClick={handleSend}>Send Transaction</button
|
|
362
|
+
return <button onClick={handleSend}>Send Transaction</button>
|
|
336
363
|
}
|
|
337
364
|
```
|
|
338
365
|
|
|
339
366
|
**When to use:**
|
|
367
|
+
|
|
340
368
|
- ✅ Use `useSendTransaction()` hook for React components (automatic session management)
|
|
341
369
|
- ✅ Use `sendUserOperation()` function for custom logic, utility functions, or non-React code
|
|
342
370
|
|
|
@@ -347,43 +375,46 @@ Deploy the smart account contract immediately after registration. This is **opti
|
|
|
347
375
|
**Smart behavior:** Automatically checks if account is already deployed and skips transaction to save gas.
|
|
348
376
|
|
|
349
377
|
```tsx
|
|
350
|
-
import { deployAccount,
|
|
378
|
+
import { deployAccount, useLumiaPassportAccountSession } from '@lumiapassport/ui-kit'
|
|
351
379
|
|
|
352
380
|
function DeployAccountExample() {
|
|
353
|
-
const
|
|
381
|
+
const session = useLumiaPassportAccountSession()
|
|
354
382
|
|
|
355
383
|
const handleDeploy = async () => {
|
|
356
|
-
if (!session) return
|
|
384
|
+
if (!session) return
|
|
357
385
|
|
|
358
386
|
try {
|
|
359
387
|
// Deploy account with minimal gas cost (skips if already deployed)
|
|
360
|
-
const userOpHash = await deployAccount(session, 'economy')
|
|
388
|
+
const userOpHash = await deployAccount(session, 'economy')
|
|
361
389
|
|
|
362
390
|
if (userOpHash) {
|
|
363
|
-
console.log('Account deployed:', userOpHash)
|
|
391
|
+
console.log('Account deployed:', userOpHash)
|
|
364
392
|
} else {
|
|
365
|
-
console.log('Account already deployed, skipped')
|
|
393
|
+
console.log('Account already deployed, skipped')
|
|
366
394
|
}
|
|
367
395
|
} catch (error) {
|
|
368
|
-
console.error('Deployment failed:', error)
|
|
396
|
+
console.error('Deployment failed:', error)
|
|
369
397
|
}
|
|
370
|
-
}
|
|
398
|
+
}
|
|
371
399
|
|
|
372
|
-
return <button onClick={handleDeploy}>Deploy Account</button
|
|
400
|
+
return <button onClick={handleDeploy}>Deploy Account</button>
|
|
373
401
|
}
|
|
374
402
|
```
|
|
375
403
|
|
|
376
404
|
**Return values:**
|
|
405
|
+
|
|
377
406
|
- Returns `userOpHash` (string) - if deployment was needed and executed
|
|
378
407
|
- Returns `null` - if account already deployed (saves gas)
|
|
379
408
|
|
|
380
409
|
**Advanced usage:**
|
|
410
|
+
|
|
381
411
|
```tsx
|
|
382
412
|
// Force deployment even if already deployed (not recommended)
|
|
383
|
-
const hash = await deployAccount(session, 'economy', { force: true })
|
|
413
|
+
const hash = await deployAccount(session, 'economy', { force: true })
|
|
384
414
|
```
|
|
385
415
|
|
|
386
416
|
**Why use this?**
|
|
417
|
+
|
|
387
418
|
- ✅ **Pre-deploy** account before first real transaction
|
|
388
419
|
- ✅ **No user consent** required (safe minimal operation)
|
|
389
420
|
- ✅ **Cleaner UX** - separate deployment from first payment
|
|
@@ -391,6 +422,7 @@ const hash = await deployAccount(session, 'economy', { force: true });
|
|
|
391
422
|
- ⚠️ **Optional** - accounts auto-deploy on first transaction anyway
|
|
392
423
|
|
|
393
424
|
**How it works:**
|
|
425
|
+
|
|
394
426
|
1. Checks if smart account contract exists on-chain
|
|
395
427
|
2. If exists: returns `null` immediately (no gas cost)
|
|
396
428
|
3. If not exists: sends minimal UserOperation (`to=0x0, value=0, data=0x`)
|
|
@@ -401,69 +433,77 @@ const hash = await deployAccount(session, 'economy', { force: true });
|
|
|
401
433
|
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).
|
|
402
434
|
|
|
403
435
|
```tsx
|
|
404
|
-
import { signTypedData,
|
|
436
|
+
import { signTypedData, useLumiaPassportAccountSession } from '@lumiapassport/ui-kit'
|
|
405
437
|
|
|
406
438
|
function SignatureExample() {
|
|
407
|
-
const
|
|
439
|
+
const session = useLumiaPassportAccountSession()
|
|
408
440
|
|
|
409
441
|
const handleSign = async () => {
|
|
410
|
-
if (!session) return
|
|
442
|
+
if (!session) return
|
|
411
443
|
|
|
412
444
|
try {
|
|
413
445
|
// Define EIP712 typed data
|
|
414
446
|
const signature = await signTypedData(session, {
|
|
415
447
|
domain: {
|
|
416
|
-
name: 'MyDApp',
|
|
417
|
-
version: '1',
|
|
418
|
-
chainId: 994,
|
|
419
|
-
verifyingContract: '0x...'
|
|
448
|
+
name: 'MyDApp', // Your dApp name (must match contract)
|
|
449
|
+
version: '1', // Contract version
|
|
450
|
+
chainId: 994, // Lumia Prism Testnet
|
|
451
|
+
verifyingContract: '0x...' // Your contract address (REQUIRED in production!)
|
|
420
452
|
},
|
|
421
453
|
types: {
|
|
422
454
|
Order: [
|
|
423
455
|
{ name: 'tokenIds', type: 'uint256[]' },
|
|
424
456
|
{ name: 'price', type: 'uint256' },
|
|
425
|
-
{ name: 'deadline', type: 'uint256' }
|
|
426
|
-
]
|
|
457
|
+
{ name: 'deadline', type: 'uint256' }
|
|
458
|
+
]
|
|
427
459
|
},
|
|
428
460
|
primaryType: 'Order',
|
|
429
461
|
message: {
|
|
430
462
|
tokenIds: [1n, 2n, 3n],
|
|
431
463
|
price: 1000000000000000000n, // 1 token in wei
|
|
432
|
-
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600)
|
|
433
|
-
}
|
|
434
|
-
})
|
|
464
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600) // 1 hour from now
|
|
465
|
+
}
|
|
466
|
+
})
|
|
435
467
|
|
|
436
|
-
console.log('Signature:', signature)
|
|
468
|
+
console.log('Signature:', signature)
|
|
437
469
|
|
|
438
470
|
// Verify signature (optional)
|
|
439
|
-
const { recoverTypedDataAddress } = await import('viem')
|
|
471
|
+
const { recoverTypedDataAddress } = await import('viem')
|
|
440
472
|
const recoveredAddress = await recoverTypedDataAddress({
|
|
441
|
-
domain: {
|
|
442
|
-
|
|
473
|
+
domain: {
|
|
474
|
+
/* same domain */
|
|
475
|
+
},
|
|
476
|
+
types: {
|
|
477
|
+
/* same types */
|
|
478
|
+
},
|
|
443
479
|
primaryType: 'Order',
|
|
444
|
-
message: {
|
|
445
|
-
|
|
446
|
-
|
|
480
|
+
message: {
|
|
481
|
+
/* same message */
|
|
482
|
+
},
|
|
483
|
+
signature
|
|
484
|
+
})
|
|
447
485
|
|
|
448
|
-
console.log('Signer:', recoveredAddress)
|
|
486
|
+
console.log('Signer:', recoveredAddress) // Should match session.ownerAddress
|
|
449
487
|
} catch (error) {
|
|
450
|
-
console.error('Signing failed:', error)
|
|
488
|
+
console.error('Signing failed:', error)
|
|
451
489
|
}
|
|
452
|
-
}
|
|
490
|
+
}
|
|
453
491
|
|
|
454
|
-
return <button onClick={handleSign}>Sign Message</button
|
|
492
|
+
return <button onClick={handleSign}>Sign Message</button>
|
|
455
493
|
}
|
|
456
494
|
```
|
|
457
495
|
|
|
458
496
|
**Important Notes about ERC-4337 Smart Accounts:**
|
|
459
497
|
|
|
460
498
|
In Account Abstraction (ERC-4337), there are **two addresses**:
|
|
499
|
+
|
|
461
500
|
1. **Owner Address (EOA)** - The address that signs messages/transactions
|
|
462
501
|
2. **Smart Account Address** - The contract wallet address
|
|
463
502
|
|
|
464
503
|
⚠️ **Critical:** The signature is created by the **owner address** (EOA), NOT the smart account address!
|
|
465
504
|
|
|
466
505
|
**Compatibility with existing protocols:**
|
|
506
|
+
|
|
467
507
|
- ✅ **Works:** Protocols that verify signatures off-chain (e.g., your backend verifies the owner EOA signature)
|
|
468
508
|
- ⚠️ **May not work:** Protocols designed for EOA wallets that store and verify against `msg.sender` or wallet address
|
|
469
509
|
- Example: Uniswap Permit2, some NFT marketplaces
|
|
@@ -472,16 +512,19 @@ In Account Abstraction (ERC-4337), there are **two addresses**:
|
|
|
472
512
|
- **Solution:** Use ERC-1271 signature validation in your smart contracts (allows contracts to validate signatures)
|
|
473
513
|
|
|
474
514
|
**Domain Configuration:**
|
|
515
|
+
|
|
475
516
|
- In production, use your actual `verifyingContract` address (not zero address!)
|
|
476
517
|
- The `domain` parameters must match exactly between frontend and smart contract
|
|
477
518
|
- The `chainId` should match the network you're deploying to
|
|
478
519
|
|
|
479
520
|
**Technical Details:**
|
|
521
|
+
|
|
480
522
|
- Shows a MetaMask-like confirmation modal with structured message preview
|
|
481
523
|
- All BigInt values are supported in the message
|
|
482
524
|
- Signature can be verified using `viem.recoverTypedDataAddress()` - will return owner EOA address
|
|
483
525
|
|
|
484
526
|
**When to use signTypedData:**
|
|
527
|
+
|
|
485
528
|
- ✅ Custom backend signature verification (you control the verification logic)
|
|
486
529
|
- ✅ Gasless transactions with meta-transaction relayers
|
|
487
530
|
- ✅ DAO voting and governance (off-chain signatures)
|
|
@@ -491,13 +534,13 @@ In Account Abstraction (ERC-4337), there are **two addresses**:
|
|
|
491
534
|
### prepareUserOperation - Prepare for Backend Submission
|
|
492
535
|
|
|
493
536
|
```tsx
|
|
494
|
-
import { prepareUserOperation,
|
|
537
|
+
import { prepareUserOperation, useLumiaPassportAccountSession } from '@lumiapassport/ui-kit'
|
|
495
538
|
|
|
496
539
|
function BackendSubmissionExample() {
|
|
497
|
-
const
|
|
540
|
+
const session = useLumiaPassportAccountSession()
|
|
498
541
|
|
|
499
542
|
const handlePrepare = async () => {
|
|
500
|
-
if (!session) return
|
|
543
|
+
if (!session) return
|
|
501
544
|
|
|
502
545
|
// Prepare and sign UserOp without sending to bundler
|
|
503
546
|
const { userOp, userOpHash } = await prepareUserOperation(
|
|
@@ -507,7 +550,7 @@ function BackendSubmissionExample() {
|
|
|
507
550
|
'0x', // data
|
|
508
551
|
'standard', // fee type: 'economy' | 'standard' | 'fast'
|
|
509
552
|
'v0.7' // EntryPoint version
|
|
510
|
-
)
|
|
553
|
+
)
|
|
511
554
|
|
|
512
555
|
// Send to backend for validation and submission
|
|
513
556
|
await fetch('/api/submit-transaction', {
|
|
@@ -517,76 +560,116 @@ function BackendSubmissionExample() {
|
|
|
517
560
|
userOp,
|
|
518
561
|
userOpHash,
|
|
519
562
|
ownerAddress: session.ownerAddress // for signature verification
|
|
520
|
-
})
|
|
521
|
-
})
|
|
522
|
-
}
|
|
563
|
+
})
|
|
564
|
+
})
|
|
565
|
+
}
|
|
523
566
|
|
|
524
|
-
return
|
|
525
|
-
<button onClick={handlePrepare}>
|
|
526
|
-
Prepare & Send to Backend
|
|
527
|
-
</button>
|
|
528
|
-
);
|
|
567
|
+
return <button onClick={handlePrepare}>Prepare & Send to Backend</button>
|
|
529
568
|
}
|
|
530
569
|
```
|
|
531
570
|
|
|
532
571
|
**Backend example (using @lumiapassport/core):**
|
|
533
572
|
|
|
534
573
|
```typescript
|
|
535
|
-
import {
|
|
536
|
-
import { recoverAddress } from 'viem'
|
|
574
|
+
import { getUserOperationReceipt, sendUserOperationRaw } from '@lumiapassport/core'
|
|
575
|
+
import { recoverAddress } from 'viem'
|
|
537
576
|
|
|
538
577
|
// Receive from frontend
|
|
539
|
-
const { userOp, userOpHash, ownerAddress } = await request.json()
|
|
578
|
+
const { userOp, userOpHash, ownerAddress } = await request.json()
|
|
540
579
|
|
|
541
580
|
// Verify signature
|
|
542
581
|
const recoveredAddress = await recoverAddress({
|
|
543
582
|
hash: userOpHash,
|
|
544
|
-
signature: userOp.signature
|
|
545
|
-
})
|
|
583
|
+
signature: userOp.signature
|
|
584
|
+
})
|
|
546
585
|
|
|
547
586
|
if (recoveredAddress.toLowerCase() !== ownerAddress.toLowerCase()) {
|
|
548
|
-
throw new Error('Invalid signature')
|
|
587
|
+
throw new Error('Invalid signature')
|
|
549
588
|
}
|
|
550
589
|
|
|
551
590
|
// Submit to bundler - returns userOpHash
|
|
552
|
-
const submittedUserOpHash = await sendUserOperationRaw(userOp)
|
|
591
|
+
const submittedUserOpHash = await sendUserOperationRaw(userOp)
|
|
553
592
|
|
|
554
593
|
// Poll for receipt to get transaction hash and status
|
|
555
594
|
const waitForReceipt = async (userOpHash: string, maxAttempts = 60, delayMs = 1000) => {
|
|
556
595
|
for (let i = 0; i < maxAttempts; i++) {
|
|
557
|
-
const receipt = await getUserOperationReceipt(userOpHash as `0x${string}`)
|
|
596
|
+
const receipt = await getUserOperationReceipt(userOpHash as `0x${string}`)
|
|
558
597
|
if (receipt) {
|
|
559
598
|
return {
|
|
560
599
|
success: receipt.success,
|
|
561
600
|
transactionHash: receipt.receipt?.transactionHash,
|
|
562
|
-
blockNumber: receipt.receipt?.blockNumber
|
|
563
|
-
}
|
|
601
|
+
blockNumber: receipt.receipt?.blockNumber
|
|
602
|
+
}
|
|
564
603
|
}
|
|
565
|
-
await new Promise(resolve => setTimeout(resolve, delayMs))
|
|
604
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs))
|
|
566
605
|
}
|
|
567
|
-
throw new Error('Transaction timeout')
|
|
568
|
-
}
|
|
606
|
+
throw new Error('Transaction timeout')
|
|
607
|
+
}
|
|
569
608
|
|
|
570
|
-
const result = await waitForReceipt(submittedUserOpHash)
|
|
609
|
+
const result = await waitForReceipt(submittedUserOpHash)
|
|
571
610
|
return {
|
|
572
611
|
success: result.success,
|
|
573
612
|
transactionHash: result.transactionHash,
|
|
574
613
|
blockNumber: result.blockNumber
|
|
575
|
-
}
|
|
614
|
+
}
|
|
576
615
|
```
|
|
577
616
|
|
|
578
|
-
|
|
617
|
+
### useLumiaPassportOpen - Programmatic LumiaPassport Dialog Control
|
|
618
|
+
|
|
619
|
+
Control the Lumia Passport dialog programmatically.
|
|
620
|
+
|
|
621
|
+
```tsx
|
|
622
|
+
import { PageKey, useLumiaPassportOpen } from '@lumiapassport/ui-kit'
|
|
623
|
+
|
|
624
|
+
function CustomAuthButton() {
|
|
625
|
+
const { isOpen, open: openLumiaPassport, close } = useLumiaPassportOpen()
|
|
626
|
+
|
|
627
|
+
return (
|
|
628
|
+
<div>
|
|
629
|
+
<button onClick={() => openLumiaPassport(PageKey.AUTH)}>Sign In</button>
|
|
630
|
+
<button onClick={() => openLumiaPassport(PageKey.RECEIVE)}>Receive LUMIA</button>
|
|
631
|
+
|
|
632
|
+
<button onClick={close}>Close Dialog</button>
|
|
633
|
+
</div>
|
|
634
|
+
)
|
|
635
|
+
}
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
### useLumiaPassportColorMode - Theme Control
|
|
639
|
+
|
|
640
|
+
Control light/dark mode for Lumia Passport UI. Use hook to sync colorMode inside your App instead of any local colorMode states
|
|
641
|
+
|
|
642
|
+
```tsx
|
|
643
|
+
import { useLumiaPassportColorMode } from '@lumiapassport/ui-kit'
|
|
644
|
+
|
|
645
|
+
function ThemeSelector() {
|
|
646
|
+
const { colorMode, setColorMode } = useLumiaPassportColorMode()
|
|
647
|
+
|
|
648
|
+
return (
|
|
649
|
+
<div>
|
|
650
|
+
<p>Current theme: {colorMode}</p>
|
|
651
|
+
<button onClick={() => setColorMode('light')}>Light</button>
|
|
652
|
+
<button onClick={() => setColorMode('dark')}>Dark</button>
|
|
653
|
+
</div>
|
|
654
|
+
)
|
|
655
|
+
}
|
|
656
|
+
```
|
|
579
657
|
|
|
580
|
-
###
|
|
658
|
+
### ThemeToggle - Quick Theme Switcher
|
|
581
659
|
|
|
582
|
-
Pre-built button with
|
|
660
|
+
Pre-built theme toggle button component to use in combo with useLumiaPassportColorMode.
|
|
583
661
|
|
|
584
662
|
```tsx
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
663
|
+
import { ThemeToggle } from '@lumiapassport/ui-kit'
|
|
664
|
+
|
|
665
|
+
function AppHeader() {
|
|
666
|
+
return (
|
|
667
|
+
<header>
|
|
668
|
+
<h1>My App</h1>
|
|
669
|
+
<ThemeToggle />
|
|
670
|
+
</header>
|
|
671
|
+
)
|
|
672
|
+
}
|
|
590
673
|
```
|
|
591
674
|
|
|
592
675
|
## Authentication Methods
|
|
@@ -635,46 +718,14 @@ Connect existing wallets (MetaMask, WalletConnect, etc.).
|
|
|
635
718
|
|
|
636
719
|
## Styling
|
|
637
720
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
```tsx
|
|
641
|
-
import '@lumiapassport/ui-kit/dist/styles.css';
|
|
642
|
-
|
|
643
|
-
<LumiaPassportProvider
|
|
644
|
-
projectId="your-project-id"
|
|
645
|
-
initialConfig={{
|
|
646
|
-
ui: {
|
|
647
|
-
theme: 'dark', // or 'light', 'auto'
|
|
648
|
-
}
|
|
649
|
-
}}
|
|
650
|
-
>
|
|
651
|
-
```
|
|
652
|
-
|
|
653
|
-
### Custom Styling
|
|
654
|
-
|
|
655
|
-
Override CSS variables for custom branding:
|
|
656
|
-
|
|
657
|
-
```css
|
|
658
|
-
.lumia-scope {
|
|
659
|
-
--lumia-primary: #6366f1;
|
|
660
|
-
--lumia-background: #0f172a;
|
|
661
|
-
--lumia-surface: #1e293b;
|
|
662
|
-
--lumia-text-primary: #f8fafc;
|
|
663
|
-
--lumia-text-secondary: #cbd5e1;
|
|
664
|
-
}
|
|
665
|
-
```
|
|
721
|
+
...to be updated
|
|
666
722
|
|
|
667
723
|
## TypeScript Support
|
|
668
724
|
|
|
669
725
|
Full TypeScript support with exported types:
|
|
670
726
|
|
|
671
727
|
```tsx
|
|
672
|
-
import type {
|
|
673
|
-
LumiaPassportConfig,
|
|
674
|
-
User,
|
|
675
|
-
AuthProvider,
|
|
676
|
-
WalletInfo,
|
|
677
|
-
} from '@lumiapassport/ui-kit';
|
|
728
|
+
import type { AuthProvider, LumiaPassportConfig, User, WalletInfo } from '@lumiapassport/ui-kit'
|
|
678
729
|
```
|
|
679
730
|
|
|
680
731
|
## Examples
|