@embarkai/ui-kit 0.1.1
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/LICENSE +21 -0
- package/README.md +977 -0
- package/dist/iframe/_headers +49 -0
- package/dist/iframe/dkls23_wasm_bg.wasm +0 -0
- package/dist/iframe/index.html +881 -0
- package/dist/iframe/kyc/sumsub.html +102 -0
- package/dist/iframe/kyc/sumsub.js +237 -0
- package/dist/iframe/lumia-logo.svg +1 -0
- package/dist/iframe/main.js +5180 -0
- package/dist/iframe/main.js.map +1 -0
- package/dist/iframe/oauth/telegram.html +129 -0
- package/dist/iframe/oauth/telegram.js +112 -0
- package/dist/iframe/oauth/x.html +162 -0
- package/dist/iframe/oauth/x.js +436 -0
- package/dist/index.cjs +21115 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2719 -0
- package/dist/index.d.ts +2719 -0
- package/dist/index.js +20978 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +1 -0
- package/package.json +96 -0
package/README.md
ADDED
|
@@ -0,0 +1,977 @@
|
|
|
1
|
+
# @embarkai/ui-kit
|
|
2
|
+
|
|
3
|
+
React UI components and hooks for EmbarkAI - a secure, user-friendly authentication and Account Abstraction wallet solution with MPC (Multi-Party Computation) key management.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔐 **Secure Authentication** - Multiple auth methods: Email, Passkey, Telegram, Wallet connect
|
|
8
|
+
- 🔑 **MPC Key Management** - Distributed key generation with iframe isolation
|
|
9
|
+
- 💼 **Account Abstraction** - ERC-4337 compliant smart contract wallets
|
|
10
|
+
- 🎨 **Pre-built UI Components** - Ready-to-use React components with customizable themes
|
|
11
|
+
- ⚡ **Easy Integration** - Just wrap your app with `LumiaPassportProvider`
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @embarkai/ui-kit
|
|
17
|
+
# or
|
|
18
|
+
pnpm add @embarkai/ui-kit
|
|
19
|
+
# or
|
|
20
|
+
yarn add @embarkai/ui-kit
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
The UI Kit requires `@tanstack/react-query` for query management & `i18next` + `react-i18next` for multilanguages support.
|
|
26
|
+
|
|
27
|
+
### 1. QueryClient Setup (Required)
|
|
28
|
+
|
|
29
|
+
First, create a query client:
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
// queryClient.ts
|
|
33
|
+
import { QueryClient } from '@tanstack/react-query'
|
|
34
|
+
|
|
35
|
+
export const queryClient = new QueryClient({
|
|
36
|
+
defaultOptions: {}
|
|
37
|
+
})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 2. I18n config (Required) & typing (Optional)
|
|
41
|
+
|
|
42
|
+
i18next lib must be initiated for both passport & your app (namespace concept used to manage translations), so your app's language can be switched by Passport's lang selector.
|
|
43
|
+
|
|
44
|
+
There is no need to provide any translations for Passport ( has built-in default lang-set ), unless it's not required to expand supported languages by custom langs, but it is required to init i18next inside DApp.
|
|
45
|
+
|
|
46
|
+
First, create the following folder structure for your dapp's translations (or update existant, if needed):
|
|
47
|
+
|
|
48
|
+
example: common.json
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"appname": "My App",
|
|
53
|
+
"signin": "Sign IN"
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
src/
|
|
59
|
+
└── i18n/
|
|
60
|
+
├── locales/
|
|
61
|
+
│ ├── en/
|
|
62
|
+
│ ├── ├── common.json
|
|
63
|
+
│ ├── ├── header.json
|
|
64
|
+
│ ├── ├── page1.json
|
|
65
|
+
│ ├── ├── page2.json
|
|
66
|
+
│ ├── └── ...restTranslationsJsons
|
|
67
|
+
│ ├── /...restLocales/...
|
|
68
|
+
└── index.ts
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Where:
|
|
72
|
+
|
|
73
|
+
- `locales/**/*.json` files contain i18next translation maps. OPTIONAL Use dedicated files for easier translations maintainance.
|
|
74
|
+
- `index.ts` exports translation resources
|
|
75
|
+
|
|
76
|
+
> **IMPORTANT:** YOUR_APP_TRANSLATION_RESOURSES must be structured with namespaces (as shown), where "app" is example namespace
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
import commonEn from './i18n/locales/en/common.json'
|
|
80
|
+
import headerEn from './i18n/locales/en/header.json'
|
|
81
|
+
import page1en from './locales/en/page1.json'
|
|
82
|
+
import page2en from './locales/en/page2.json'
|
|
83
|
+
|
|
84
|
+
export const YOUR_APP_TRANSLATION_RESOURSES = {
|
|
85
|
+
// language: { namespace: jsonLocale }
|
|
86
|
+
en: {
|
|
87
|
+
app: {
|
|
88
|
+
common: commonEn,
|
|
89
|
+
header: headerEn,
|
|
90
|
+
page1: page1en,
|
|
91
|
+
page2: page2en
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// ...
|
|
95
|
+
} as const
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
> **Note:** If you don't need to translate your app then leave YOUR_APP_TRANSLATION_RESOURSES empty with no locales folder.
|
|
99
|
+
|
|
100
|
+
Decalre types (usualy at `src/i18next.d.ts`) so t-method provides intellisence typings for your translations. Default locale is recomended to be used for typing as shown
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
import { YOUR_APP_TRANSLATION_RESOURSES } from './i18n'
|
|
104
|
+
|
|
105
|
+
declare module 'i18next' {
|
|
106
|
+
interface CustomTypeOptions {
|
|
107
|
+
defaultNS: 'app' //
|
|
108
|
+
resources: {
|
|
109
|
+
app: typeof YOUR_APP_TRANSLATION_RESOURSES.en.app
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 3. Wrap your app with providers & init i18n
|
|
116
|
+
|
|
117
|
+
> **Note:** Declare your specific dapp's high order useTranslation hook re-expoting namespaced react-i18next useTranslation, since now t-method is typed by your default locale.
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
import { useTranslation } from 'react-i18next'
|
|
121
|
+
|
|
122
|
+
export function useT() {
|
|
123
|
+
return useTranslation('app') // this will make t-method typed accordingly
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
import {
|
|
129
|
+
combineI18NResources,
|
|
130
|
+
LOCAL_STORAGE_I18N_KEY,
|
|
131
|
+
//
|
|
132
|
+
LumiaPassportProvider,
|
|
133
|
+
LumiaPassportSessionProvider,
|
|
134
|
+
LumiaRainbowKitProvider
|
|
135
|
+
} from '@embarkai/ui-kit'
|
|
136
|
+
//
|
|
137
|
+
import i18n from 'i18next'
|
|
138
|
+
import { initReactI18next, useTranslation } from 'react-i18next'
|
|
139
|
+
|
|
140
|
+
import { YOUR_APP_TRANSLATION_RESOURSES } from './i18n'
|
|
141
|
+
import { queryClient } from './queryClient'
|
|
142
|
+
|
|
143
|
+
i18n.use(initReactI18next).init({
|
|
144
|
+
resources: combineI18NResources(YOUR_APP_TRANSLATION_RESOURSES),
|
|
145
|
+
lng: localStorage.getItem(LOCAL_STORAGE_I18N_KEY) || 'en', // passport saves language setup on change
|
|
146
|
+
fallbackLng: 'en', // default
|
|
147
|
+
defaultNS: 'app', // your app i18n-namespace, example: app
|
|
148
|
+
namespace: 'app', // your app i18n-namespace, example: app
|
|
149
|
+
debug: false
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
function Root() {
|
|
153
|
+
const { t } = useT()
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<QueryClientProvider client={queryClient}>
|
|
157
|
+
<LumiaPassportProvider
|
|
158
|
+
projectId="your-project-id" // Get from Lumia Passport Dashboard
|
|
159
|
+
>
|
|
160
|
+
<LumiaRainbowKitProvider>
|
|
161
|
+
<LumiaPassportSessionProvider>
|
|
162
|
+
<header>
|
|
163
|
+
<h1>{t('common.appname')}</h1>
|
|
164
|
+
</header>
|
|
165
|
+
|
|
166
|
+
<main>
|
|
167
|
+
<span>{t('page1.title')}</span>
|
|
168
|
+
</main>
|
|
169
|
+
</LumiaPassportSessionProvider>
|
|
170
|
+
</LumiaRainbowKitProvider>
|
|
171
|
+
</LumiaPassportProvider>
|
|
172
|
+
</QueryClientProvider>
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### 4. Add the Connect Button
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
import { ConnectWalletButton } from '@embarkai/ui-kit'
|
|
181
|
+
|
|
182
|
+
function YourApp() {
|
|
183
|
+
const { t } = useT()
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
<div>
|
|
187
|
+
<h1>{t('common.appname')}</h1>
|
|
188
|
+
<ConnectWalletButton label={t('common.signin')} />
|
|
189
|
+
</div>
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### 5. (Optional)
|
|
195
|
+
|
|
196
|
+
Custom unconnected button can be provided via ConnectButton prop.
|
|
197
|
+
Prop consumes standart HTMLButton component and will provide required onClick to it
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
import { ConnectWalletButton } from '@embarkai/ui-kit'
|
|
201
|
+
|
|
202
|
+
function CustomButtonComponent(props: HTMLAttributes<HTMLButtonElement>) => {
|
|
203
|
+
return (<button {...props}/>)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function YourApp() {
|
|
207
|
+
const { t } = useT()
|
|
208
|
+
|
|
209
|
+
return (
|
|
210
|
+
<div>
|
|
211
|
+
<h1>My App</h1>
|
|
212
|
+
<ConnectWalletButton label={t('common.signin')} ConnectButton={CustomButtonComponent} />
|
|
213
|
+
</div>
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
That's it! The `ConnectWalletButton` provides a complete authentication UI with wallet management.
|
|
219
|
+
|
|
220
|
+
> **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"
|
|
221
|
+
|
|
222
|
+
## Configuration Options
|
|
223
|
+
|
|
224
|
+
### Basic Configuration
|
|
225
|
+
|
|
226
|
+
```tsx
|
|
227
|
+
<LumiaPassportProvider
|
|
228
|
+
projectId="your-project-id" // Required
|
|
229
|
+
initialConfig={{
|
|
230
|
+
network: {
|
|
231
|
+
name: 'Lumia Beam',
|
|
232
|
+
symbol: 'LUMIA',
|
|
233
|
+
chainId: 2030232745, // Default chain for your dApp
|
|
234
|
+
rpcUrl: 'https://beam-rpc.lumia.org',
|
|
235
|
+
explorerUrl: 'https://beam-explorer.lumia.org',
|
|
236
|
+
testnet: true,
|
|
237
|
+
},
|
|
238
|
+
}}
|
|
239
|
+
>
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**Network Chain Priority:**
|
|
243
|
+
|
|
244
|
+
The SDK uses the following priority for determining the active chain:
|
|
245
|
+
|
|
246
|
+
1. **User's explicit selection** - If user manually switched chains in the UI, their choice is preserved (stored in localStorage)
|
|
247
|
+
2. **dApp config `network.chainId`** - Your configured default chain for first-time users
|
|
248
|
+
3. **SDK default** - Lumia Testnet (fallback if no config provided)
|
|
249
|
+
|
|
250
|
+
This ensures returning users keep their preferred chain while new users start on your configured network.
|
|
251
|
+
|
|
252
|
+
### Advanced Configuration
|
|
253
|
+
|
|
254
|
+
```tsx
|
|
255
|
+
<LumiaPassportProvider
|
|
256
|
+
projectId="your-project-id"
|
|
257
|
+
initialConfig={{
|
|
258
|
+
// UI customization
|
|
259
|
+
preferedColorMode?: 'light', // 'light' | 'dark'
|
|
260
|
+
|
|
261
|
+
ui: {
|
|
262
|
+
title: 'Welcome to MyApp',
|
|
263
|
+
subtitle: 'Sign in to continue',
|
|
264
|
+
|
|
265
|
+
dialogClassName: 'string', // beta
|
|
266
|
+
|
|
267
|
+
authOrder: ['email', 'passkey', 'social'],
|
|
268
|
+
|
|
269
|
+
branding: {
|
|
270
|
+
tagline: 'Powered by MPC',
|
|
271
|
+
link: { text: 'Learn More', url: \'https\:\/\/example.com/docs\' },
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
// Authentication providers
|
|
276
|
+
email: {
|
|
277
|
+
enabled: true,
|
|
278
|
+
placeholder: 'Enter your email',
|
|
279
|
+
buttonText: 'Continue',
|
|
280
|
+
verificationTitle: 'Check your email',
|
|
281
|
+
},
|
|
282
|
+
passkey: {
|
|
283
|
+
enabled: true,
|
|
284
|
+
showCreateButton: true,
|
|
285
|
+
primaryButtonText: 'Sign in with Passkey',
|
|
286
|
+
},
|
|
287
|
+
social: {
|
|
288
|
+
enabled: true,
|
|
289
|
+
providers: [
|
|
290
|
+
{ id: 'Discord', name: 'Discord', enabled: true, comingSoon: false },
|
|
291
|
+
{ id: 'telegram', name: 'Telegram', enabled: true, comingSoon: false },
|
|
292
|
+
],
|
|
293
|
+
},
|
|
294
|
+
wallet: {
|
|
295
|
+
enabled: true,
|
|
296
|
+
supportedChains: [994873017, 2030232745],
|
|
297
|
+
requireSignature: true,
|
|
298
|
+
walletConnectProjectId: 'your-walletconnect-id',
|
|
299
|
+
},
|
|
300
|
+
|
|
301
|
+
// Features
|
|
302
|
+
features: {
|
|
303
|
+
mpcSecurity: true,
|
|
304
|
+
strictMode: false,
|
|
305
|
+
requestDeduplication: true,
|
|
306
|
+
kycNeeded: false,
|
|
307
|
+
displayNameNeeded: false,
|
|
308
|
+
showActiveBalanceAsFiat: false,
|
|
309
|
+
},
|
|
310
|
+
|
|
311
|
+
// KYC configuration (if needed)
|
|
312
|
+
kyc: {
|
|
313
|
+
provider: 'sumsub',
|
|
314
|
+
options: { levelName: 'basic-kyc', flowName: 'default' }
|
|
315
|
+
},
|
|
316
|
+
|
|
317
|
+
// Warnings
|
|
318
|
+
warnings: {
|
|
319
|
+
backupWarning: true,
|
|
320
|
+
emailNotConnectedWarning: true,
|
|
321
|
+
},
|
|
322
|
+
|
|
323
|
+
// Network configuration
|
|
324
|
+
// chainId sets default chain for new users (see "Network Chain Priority" above)
|
|
325
|
+
network: {
|
|
326
|
+
name: 'Lumia Beam',
|
|
327
|
+
symbol: 'LUMIA',
|
|
328
|
+
chainId: 2030232745, // Your dApp's default chain
|
|
329
|
+
rpcUrl: 'https\:\/\/beam-rpc.lumia.org',
|
|
330
|
+
explorerUrl: 'https\:\/\/beam-explorer.lumia.org',
|
|
331
|
+
testnet: true,
|
|
332
|
+
forceChain: false, // if true, passport is immediatly forced to switch itto provided chainId, chain selector via SeetingsMenu becomes anavailable
|
|
333
|
+
},
|
|
334
|
+
}}
|
|
335
|
+
callbacks={{
|
|
336
|
+
onLumiaPassportConnecting: ({ method, provider }) => {
|
|
337
|
+
console.log('Connecting with:', method, provider);
|
|
338
|
+
},
|
|
339
|
+
onLumiaPassportConnect: ({ address, session }) => {
|
|
340
|
+
console.log('Connected:', address);
|
|
341
|
+
},
|
|
342
|
+
onLumiaPassportAccount: ({ userId, address, session, hasKeyshare }) => {
|
|
343
|
+
console.log('Account ready:', userId);
|
|
344
|
+
},
|
|
345
|
+
onLumiaPassportUpdate: ({ providers }) => {
|
|
346
|
+
console.log('Profile updated:', providers);
|
|
347
|
+
},
|
|
348
|
+
onLumiaPassportDisconnect: ({ address, userId }) => {
|
|
349
|
+
console.log('Disconnected:', address);
|
|
350
|
+
},
|
|
351
|
+
onLumiaPassportError: ({ error, message }) => {
|
|
352
|
+
console.error('Error:', message);
|
|
353
|
+
},
|
|
354
|
+
onWalletReady: (status) => {
|
|
355
|
+
console.log('Wallet ready:', status.ready);
|
|
356
|
+
},
|
|
357
|
+
onLumiaPassportChainChange: ({ chainId, previousChainId }) => {
|
|
358
|
+
console.log('Chain Changed:', { previousChainId, chainId })
|
|
359
|
+
},
|
|
360
|
+
}}
|
|
361
|
+
>
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## Using Hooks
|
|
365
|
+
|
|
366
|
+
> **Note:** The `useLumiaPassportSession` hook is based on pure Zustand store so if you're already using useLumiaPassportSession hook please consider 2 options: 1) refactor state extarction so it uses zustand state extraction feature. 2) consider using dedicated LumiaPassport shared store values hooks: `useLumiaPassportAccountSession`, `useLumiaPassportAddress` etc. Otherwise you might experience excessive re-rendering issues as LumiaPassport shares its internal store and might update some state values which should not affect app render.
|
|
367
|
+
|
|
368
|
+
```tsx
|
|
369
|
+
import { useLumiaPassportAccountSession, useLumiaPassportLoadingStatus } from '@embarkai/ui-kit'
|
|
370
|
+
|
|
371
|
+
function MyComponent() {
|
|
372
|
+
// const session = useLumiaPassportSession(s => s.session) - with prev hook & Zustand state extraction feature, please prefer this instead:
|
|
373
|
+
const session = useLumiaPassportAccountSession()
|
|
374
|
+
const { isSessionLoading } = useLumiaPassportLoadingStatus()
|
|
375
|
+
|
|
376
|
+
if (isSessionLoading) return <div>Loading...</div>
|
|
377
|
+
|
|
378
|
+
if (!session) {
|
|
379
|
+
return <div>Not authenticated. Please connect your wallet.</div>
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return (
|
|
383
|
+
<div>
|
|
384
|
+
<p>Welcome!</p>
|
|
385
|
+
<p>User ID: {session.userId}</p>
|
|
386
|
+
<p>Wallet Address: {session.ownerAddress}</p>
|
|
387
|
+
<p>Smart Account: {session.accountAddress}</p>
|
|
388
|
+
</div>
|
|
389
|
+
)
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### Lumia Passport shared store values hooks
|
|
394
|
+
|
|
395
|
+
- **useLumiaPassportActiveChainId** - Returns current ChainID
|
|
396
|
+
- **useLumiaPassportIsMobileView** - Returns boolean indicating if UI is in mobile view mode
|
|
397
|
+
- **useLumiaPassportAccountSession** - Returns current user session object with userId, addresses, and auth info
|
|
398
|
+
- **useLumiaPassportLoadingStatus** - Returns `{ isSessionLoading, sessionStatus }` for tracking authentication state
|
|
399
|
+
- **useLumiaPassportBalance** - Returns wallet balance data: `{ walletBalance, fiatBalance, cryptoRate, fiatSymbol, cryptoSymbol }`
|
|
400
|
+
- **useLumiaPassportIFrameReady** - Returns boolean indicating if the MPC iframe is ready for operations
|
|
401
|
+
- **useLumiaPassportAddress** - Returns the current user's wallet address
|
|
402
|
+
- **useLumiaPassportError** - Returns any error that occurred during authentication or operations
|
|
403
|
+
- **useLumiaPassportRecoveryUserId** - Returns userId for account recovery flow
|
|
404
|
+
- **useLumiaPassportHasServerVault** - Returns boolean indicating if user has server-side keyshare backup
|
|
405
|
+
|
|
406
|
+
### useSendTransaction - Send Transactions
|
|
407
|
+
|
|
408
|
+
```tsx
|
|
409
|
+
import { useSendTransaction } from '@embarkai/ui-kit'
|
|
410
|
+
|
|
411
|
+
function TransactionExample() {
|
|
412
|
+
const { sendTransaction, isPending } = useSendTransaction()
|
|
413
|
+
|
|
414
|
+
const handleSend = async () => {
|
|
415
|
+
try {
|
|
416
|
+
const userOpHash = await sendTransaction({
|
|
417
|
+
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
|
|
418
|
+
value: '1000000000000000000', // 1 ETH in wei
|
|
419
|
+
data: '0x' // Optional contract call data
|
|
420
|
+
})
|
|
421
|
+
console.log('UserOp hash:', userOpHash)
|
|
422
|
+
} catch (error) {
|
|
423
|
+
console.error('Transaction failed:', error)
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return (
|
|
428
|
+
<div>
|
|
429
|
+
<button onClick={handleSend} disabled={isPending}>
|
|
430
|
+
{isPending ? 'Sending...' : 'Send Transaction'}
|
|
431
|
+
</button>
|
|
432
|
+
</div>
|
|
433
|
+
)
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### sendUserOperation - Direct Transaction Submission
|
|
438
|
+
|
|
439
|
+
For direct control without using the React hook, you can use `sendUserOperation` function:
|
|
440
|
+
|
|
441
|
+
```tsx
|
|
442
|
+
import { sendUserOperation, useLumiaPassportAccountSession } from '@embarkai/ui-kit'
|
|
443
|
+
|
|
444
|
+
function DirectTransactionExample() {
|
|
445
|
+
const session = useLumiaPassportAccountSession()
|
|
446
|
+
|
|
447
|
+
const handleSend = async () => {
|
|
448
|
+
if (!session) {
|
|
449
|
+
console.error('No active session')
|
|
450
|
+
return
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
try {
|
|
454
|
+
// Send transaction directly with full control
|
|
455
|
+
const userOpHash = await sendUserOperation(
|
|
456
|
+
session, // Required: session from useLumiaPassportAccountSession
|
|
457
|
+
'0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', // to address
|
|
458
|
+
'1000000000000000000', // value in wei (1 ETH)
|
|
459
|
+
'0x', // data (optional contract call)
|
|
460
|
+
'standard', // fee type: 'economy' | 'standard' | 'fast'
|
|
461
|
+
'v0.7' // EntryPoint version
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
console.log('Transaction submitted:', userOpHash)
|
|
465
|
+
} catch (error) {
|
|
466
|
+
console.error('Transaction failed:', error)
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return <button onClick={handleSend}>Send Transaction</button>
|
|
471
|
+
}
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
**When to use:**
|
|
475
|
+
|
|
476
|
+
- ✅ Use `useSendTransaction()` hook for React components (automatic session management)
|
|
477
|
+
- ✅ Use `sendUserOperation()` function for custom logic, utility functions, or non-React code
|
|
478
|
+
|
|
479
|
+
### deployAccount - Deploy Smart Account (Optional)
|
|
480
|
+
|
|
481
|
+
Deploy the smart account contract immediately after registration. This is **optional** - accounts are automatically deployed on first transaction.
|
|
482
|
+
|
|
483
|
+
**Smart behavior:** Automatically checks if account is already deployed and skips transaction to save gas.
|
|
484
|
+
|
|
485
|
+
```tsx
|
|
486
|
+
import { deployAccount, useLumiaPassportAccountSession } from '@embarkai/ui-kit'
|
|
487
|
+
|
|
488
|
+
function DeployAccountExample() {
|
|
489
|
+
const session = useLumiaPassportAccountSession()
|
|
490
|
+
|
|
491
|
+
const handleDeploy = async () => {
|
|
492
|
+
if (!session) return
|
|
493
|
+
|
|
494
|
+
try {
|
|
495
|
+
// Deploy account with minimal gas cost (skips if already deployed)
|
|
496
|
+
const userOpHash = await deployAccount(session, 'economy')
|
|
497
|
+
|
|
498
|
+
if (userOpHash) {
|
|
499
|
+
console.log('Account deployed:', userOpHash)
|
|
500
|
+
} else {
|
|
501
|
+
console.log('Account already deployed, skipped')
|
|
502
|
+
}
|
|
503
|
+
} catch (error) {
|
|
504
|
+
console.error('Deployment failed:', error)
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return <button onClick={handleDeploy}>Deploy Account</button>
|
|
509
|
+
}
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
**Return values:**
|
|
513
|
+
|
|
514
|
+
- Returns `userOpHash` (string) - if deployment was needed and executed
|
|
515
|
+
- Returns `null` - if account already deployed (saves gas)
|
|
516
|
+
|
|
517
|
+
**Advanced usage:**
|
|
518
|
+
|
|
519
|
+
```tsx
|
|
520
|
+
// Force deployment even if already deployed (not recommended)
|
|
521
|
+
const hash = await deployAccount(session, 'economy', { force: true })
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
**Why use this?**
|
|
525
|
+
|
|
526
|
+
- ✅ **Pre-deploy** account before first real transaction
|
|
527
|
+
- ✅ **No user consent** required (safe minimal operation)
|
|
528
|
+
- ✅ **Cleaner UX** - separate deployment from first payment
|
|
529
|
+
- ✅ **Smart gas savings** - auto-skips if already deployed
|
|
530
|
+
- ⚠️ **Optional** - accounts auto-deploy on first transaction anyway
|
|
531
|
+
|
|
532
|
+
**How it works:**
|
|
533
|
+
|
|
534
|
+
1. Checks if smart account contract exists on-chain
|
|
535
|
+
2. If exists: returns `null` immediately (no gas cost)
|
|
536
|
+
3. If not exists: sends minimal UserOperation (`to=0x0, value=0, data=0x`)
|
|
537
|
+
4. Factory deploys contract without user confirmation
|
|
538
|
+
|
|
539
|
+
### signTypedData - Sign EIP712 Structured Messages
|
|
540
|
+
|
|
541
|
+
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).
|
|
542
|
+
|
|
543
|
+
```tsx
|
|
544
|
+
import { signTypedData, useLumiaPassportAccountSession } from '@embarkai/ui-kit'
|
|
545
|
+
|
|
546
|
+
function SignatureExample() {
|
|
547
|
+
const session = useLumiaPassportAccountSession()
|
|
548
|
+
|
|
549
|
+
const handleSign = async () => {
|
|
550
|
+
if (!session) return
|
|
551
|
+
|
|
552
|
+
try {
|
|
553
|
+
// Define EIP712 typed data
|
|
554
|
+
const signature = await signTypedData(session, {
|
|
555
|
+
domain: {
|
|
556
|
+
name: 'MyDApp', // Your dApp name (must match contract)
|
|
557
|
+
version: '1', // Contract version
|
|
558
|
+
chainId: 994, // Lumia Prism Testnet
|
|
559
|
+
verifyingContract: '0x...' // Your contract address (REQUIRED in production!)
|
|
560
|
+
},
|
|
561
|
+
types: {
|
|
562
|
+
Order: [
|
|
563
|
+
{ name: 'tokenIds', type: 'uint256[]' },
|
|
564
|
+
{ name: 'price', type: 'uint256' },
|
|
565
|
+
{ name: 'deadline', type: 'uint256' }
|
|
566
|
+
]
|
|
567
|
+
},
|
|
568
|
+
primaryType: 'Order',
|
|
569
|
+
message: {
|
|
570
|
+
tokenIds: [1n, 2n, 3n],
|
|
571
|
+
price: 1000000000000000000n, // 1 token in wei
|
|
572
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600) // 1 hour from now
|
|
573
|
+
}
|
|
574
|
+
})
|
|
575
|
+
|
|
576
|
+
console.log('Signature:', signature)
|
|
577
|
+
|
|
578
|
+
// Verify signature (optional)
|
|
579
|
+
const { recoverTypedDataAddress } = await import('viem')
|
|
580
|
+
const recoveredAddress = await recoverTypedDataAddress({
|
|
581
|
+
domain: {
|
|
582
|
+
/* same domain */
|
|
583
|
+
},
|
|
584
|
+
types: {
|
|
585
|
+
/* same types */
|
|
586
|
+
},
|
|
587
|
+
primaryType: 'Order',
|
|
588
|
+
message: {
|
|
589
|
+
/* same message */
|
|
590
|
+
},
|
|
591
|
+
signature
|
|
592
|
+
})
|
|
593
|
+
|
|
594
|
+
console.log('Signer:', recoveredAddress) // Should match session.ownerAddress
|
|
595
|
+
} catch (error) {
|
|
596
|
+
console.error('Signing failed:', error)
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
return <button onClick={handleSign}>Sign Message</button>
|
|
601
|
+
}
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
**Important Notes about ERC-4337 Smart Accounts:**
|
|
605
|
+
|
|
606
|
+
In Account Abstraction (ERC-4337), there are **two addresses**:
|
|
607
|
+
|
|
608
|
+
1. **Owner Address (EOA)** - The address that signs messages/transactions
|
|
609
|
+
2. **Smart Account Address** - The contract wallet address
|
|
610
|
+
|
|
611
|
+
⚠️ **Critical:** The signature is created by the **owner address** (EOA), NOT the smart account address!
|
|
612
|
+
|
|
613
|
+
**Compatibility with existing protocols:**
|
|
614
|
+
|
|
615
|
+
- ✅ **Works:** Protocols that verify signatures off-chain (e.g., your backend verifies the owner EOA signature)
|
|
616
|
+
- ⚠️ **May not work:** Protocols designed for EOA wallets that store and verify against `msg.sender` or wallet address
|
|
617
|
+
- Example: Uniswap Permit2, some NFT marketplaces
|
|
618
|
+
- These protocols expect the signer address to match the wallet address
|
|
619
|
+
- With smart accounts: signer = owner EOA, wallet = smart account contract
|
|
620
|
+
- **Solution:** Use ERC-1271 signature validation in your smart contracts (allows contracts to validate signatures)
|
|
621
|
+
|
|
622
|
+
**Domain Configuration:**
|
|
623
|
+
|
|
624
|
+
- In production, use your actual `verifyingContract` address (not zero address!)
|
|
625
|
+
- The `domain` parameters must match exactly between frontend and smart contract
|
|
626
|
+
- The `chainId` should match the network you're deploying to
|
|
627
|
+
|
|
628
|
+
**Technical Details:**
|
|
629
|
+
|
|
630
|
+
- Shows a MetaMask-like confirmation modal with structured message preview
|
|
631
|
+
- All BigInt values are supported in the message
|
|
632
|
+
- Signature can be verified using `viem.recoverTypedDataAddress()` - will return owner EOA address
|
|
633
|
+
|
|
634
|
+
**When to use signTypedData:**
|
|
635
|
+
|
|
636
|
+
- ✅ Custom backend signature verification (you control the verification logic)
|
|
637
|
+
- ✅ Gasless transactions with meta-transaction relayers
|
|
638
|
+
- ✅ DAO voting and governance (off-chain signatures)
|
|
639
|
+
- ✅ Custom smart contracts with ERC-1271 support
|
|
640
|
+
- ⚠️ Be cautious with protocols designed exclusively for EOA wallets
|
|
641
|
+
|
|
642
|
+
### prepareUserOperation - Prepare for Backend Submission
|
|
643
|
+
|
|
644
|
+
```tsx
|
|
645
|
+
import { prepareUserOperation, useLumiaPassportAccountSession } from '@embarkai/ui-kit'
|
|
646
|
+
|
|
647
|
+
function BackendSubmissionExample() {
|
|
648
|
+
const session = useLumiaPassportAccountSession()
|
|
649
|
+
|
|
650
|
+
const handlePrepare = async () => {
|
|
651
|
+
if (!session) return
|
|
652
|
+
|
|
653
|
+
// Prepare and sign UserOp without sending to bundler
|
|
654
|
+
const { userOp, userOpHash } = await prepareUserOperation(
|
|
655
|
+
session,
|
|
656
|
+
'0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', // to
|
|
657
|
+
'1000000000000000000', // 1 ETH in wei
|
|
658
|
+
'0x', // data
|
|
659
|
+
'standard', // fee type: 'economy' | 'standard' | 'fast'
|
|
660
|
+
'v0.7' // EntryPoint version
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
// Send to backend for validation and submission
|
|
664
|
+
await fetch('/api/submit-transaction', {
|
|
665
|
+
method: 'POST',
|
|
666
|
+
headers: { 'Content-Type': 'application/json' },
|
|
667
|
+
body: JSON.stringify({
|
|
668
|
+
userOp,
|
|
669
|
+
userOpHash,
|
|
670
|
+
ownerAddress: session.ownerAddress // for signature verification
|
|
671
|
+
})
|
|
672
|
+
})
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
return <button onClick={handlePrepare}>Prepare & Send to Backend</button>
|
|
676
|
+
}
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
**Backend example (using @embarkai/core):**
|
|
680
|
+
|
|
681
|
+
```typescript
|
|
682
|
+
import { getUserOperationReceipt, sendUserOperationRaw } from '@embarkai/core'
|
|
683
|
+
import { getChainConfig } from '@embarkai/core/read'
|
|
684
|
+
import { recoverAddress } from 'viem'
|
|
685
|
+
|
|
686
|
+
const CHAIN_ID = 2030232745 // Lumia Testnet
|
|
687
|
+
|
|
688
|
+
// Get chain config for bundler URL
|
|
689
|
+
const chainConfig = getChainConfig(CHAIN_ID)
|
|
690
|
+
if (!chainConfig) throw new Error('Unsupported chain')
|
|
691
|
+
|
|
692
|
+
// Receive from frontend
|
|
693
|
+
const { userOp, userOpHash, ownerAddress } = await request.json()
|
|
694
|
+
|
|
695
|
+
// Verify signature
|
|
696
|
+
const recoveredAddress = await recoverAddress({
|
|
697
|
+
hash: userOpHash,
|
|
698
|
+
signature: userOp.signature
|
|
699
|
+
})
|
|
700
|
+
|
|
701
|
+
if (recoveredAddress.toLowerCase() !== ownerAddress.toLowerCase()) {
|
|
702
|
+
throw new Error('Invalid signature')
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// Submit to bundler - REQUIRES chainId parameter
|
|
706
|
+
const submittedUserOpHash = await sendUserOperationRaw(userOp, { chainId: CHAIN_ID })
|
|
707
|
+
|
|
708
|
+
// Poll for receipt to get transaction hash and status
|
|
709
|
+
const waitForReceipt = async (hash: string, maxAttempts = 60, delayMs = 1000) => {
|
|
710
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
711
|
+
// REQUIRES bundlerUrl parameter
|
|
712
|
+
const receipt = await getUserOperationReceipt(
|
|
713
|
+
hash as `0x${string}`,
|
|
714
|
+
chainConfig.bundlerUrl
|
|
715
|
+
)
|
|
716
|
+
if (receipt) {
|
|
717
|
+
return {
|
|
718
|
+
success: receipt.success,
|
|
719
|
+
transactionHash: receipt.receipt?.transactionHash,
|
|
720
|
+
blockNumber: receipt.receipt?.blockNumber
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs))
|
|
724
|
+
}
|
|
725
|
+
throw new Error('Transaction timeout')
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
const result = await waitForReceipt(submittedUserOpHash)
|
|
729
|
+
return {
|
|
730
|
+
success: result.success,
|
|
731
|
+
transactionHash: result.transactionHash,
|
|
732
|
+
blockNumber: result.blockNumber
|
|
733
|
+
}
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
**Network Configuration:**
|
|
737
|
+
|
|
738
|
+
| Network | Chain ID | Bundler URL |
|
|
739
|
+
|---------|----------|-------------|
|
|
740
|
+
| Lumia Testnet (Beam) | 2030232745 | https://api.lumiapassport.com/rundler |
|
|
741
|
+
| Lumia Mainnet (Prism) | 994873017 | https://api.lumiapassport.com/rundler-prism |
|
|
742
|
+
|
|
743
|
+
### useLumiaPassportOpen - Programmatic LumiaPassport Dialog Control
|
|
744
|
+
|
|
745
|
+
Control the Lumia Passport dialog programmatically.
|
|
746
|
+
|
|
747
|
+
```tsx
|
|
748
|
+
import { PageKey, useLumiaPassportOpen } from '@embarkai/ui-kit'
|
|
749
|
+
|
|
750
|
+
function CustomAuthButton() {
|
|
751
|
+
const { isOpen, open: openLumiaPassport, close } = useLumiaPassportOpen()
|
|
752
|
+
|
|
753
|
+
return (
|
|
754
|
+
<div>
|
|
755
|
+
<button onClick={() => openLumiaPassport(PageKey.AUTH)}>Sign In</button>
|
|
756
|
+
<button onClick={() => openLumiaPassport(PageKey.RECEIVE)}>Receive LUMIA</button>
|
|
757
|
+
|
|
758
|
+
<button onClick={close}>Close Dialog</button>
|
|
759
|
+
</div>
|
|
760
|
+
)
|
|
761
|
+
}
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
### ThemeToggle - Quick Theme Switcher
|
|
765
|
+
|
|
766
|
+
Pre-built theme toggle button component to use in combo with useLumiaPassportColorMode.
|
|
767
|
+
|
|
768
|
+
```tsx
|
|
769
|
+
import { ThemeToggle } from '@embarkai/ui-kit'
|
|
770
|
+
|
|
771
|
+
function AppHeader() {
|
|
772
|
+
return (
|
|
773
|
+
<header>
|
|
774
|
+
<h1>My App</h1>
|
|
775
|
+
<ThemeToggle />
|
|
776
|
+
</header>
|
|
777
|
+
)
|
|
778
|
+
}
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
## Authentication Methods
|
|
782
|
+
|
|
783
|
+
### Email OTP
|
|
784
|
+
|
|
785
|
+
Users receive a one-time code via email.
|
|
786
|
+
|
|
787
|
+
```tsx
|
|
788
|
+
// Configured by default, no additional setup needed
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
### Passkey (WebAuthn)
|
|
792
|
+
|
|
793
|
+
Secure biometric authentication with device passkeys.
|
|
794
|
+
|
|
795
|
+
```tsx
|
|
796
|
+
// Configured by default
|
|
797
|
+
// Users can register passkey after initial login
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
### Telegram Mini App
|
|
801
|
+
|
|
802
|
+
Authentication via Telegram for mini apps.
|
|
803
|
+
|
|
804
|
+
```tsx
|
|
805
|
+
// Configure via social providers:
|
|
806
|
+
initialConfig={{
|
|
807
|
+
social: {
|
|
808
|
+
enabled: true,
|
|
809
|
+
providers: [
|
|
810
|
+
{ id: 'telegram', name: 'Telegram', enabled: true },
|
|
811
|
+
],
|
|
812
|
+
},
|
|
813
|
+
}}
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
### External Wallet
|
|
817
|
+
|
|
818
|
+
Connect existing wallets (MetaMask, WalletConnect, etc.).
|
|
819
|
+
|
|
820
|
+
```tsx
|
|
821
|
+
// Configured by default
|
|
822
|
+
// Uses RainbowKit for wallet connections
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
## Styling
|
|
826
|
+
|
|
827
|
+
### Passport Dialog Window
|
|
828
|
+
|
|
829
|
+
Global Passport window view can be redefined via config prop by providing specific dialogClassName.
|
|
830
|
+
|
|
831
|
+
> **Note** Providing "dialogClassName" will disable default dialog window styles
|
|
832
|
+
|
|
833
|
+
```css
|
|
834
|
+
.passport-dialog-classname {
|
|
835
|
+
background-color: rgba(14, 14, 14, 0.7);
|
|
836
|
+
backdrop-filter: blur(10px);
|
|
837
|
+
|
|
838
|
+
box-shadow:
|
|
839
|
+
0 0 8px rgba(14, 14, 14, 0.1),
|
|
840
|
+
inset 0 0 0 1px var(--lumia-passport-bd);
|
|
841
|
+
}
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
```tsx
|
|
845
|
+
import 'index.css'
|
|
846
|
+
|
|
847
|
+
<LumiaPassportProvider
|
|
848
|
+
...
|
|
849
|
+
initialConfig={{
|
|
850
|
+
...
|
|
851
|
+
ui: {
|
|
852
|
+
...
|
|
853
|
+
dialogClassName: 'passport-dialog-classname',
|
|
854
|
+
...
|
|
855
|
+
},
|
|
856
|
+
...
|
|
857
|
+
}}/>
|
|
858
|
+
```
|
|
859
|
+
|
|
860
|
+
### Passport styles
|
|
861
|
+
|
|
862
|
+
Passport provides global hook for Light/Dark modes - useLumiaPassportColorMode - use it within yor application instead of any local mode declaration.
|
|
863
|
+
|
|
864
|
+
```tsx
|
|
865
|
+
import { useLumiaPassportColorMode } from '@embarkai/ui-kit'
|
|
866
|
+
|
|
867
|
+
function YourAnyComponent() {
|
|
868
|
+
const { colorMode, setColorMode } = useLumiaPassportColorMode()
|
|
869
|
+
|
|
870
|
+
return (
|
|
871
|
+
<div>
|
|
872
|
+
<p>Current theme: {colorMode}</p>
|
|
873
|
+
<button onClick={() => setColorMode('light')}>Light</button>
|
|
874
|
+
<button onClick={() => setColorMode('dark')}>Dark</button>
|
|
875
|
+
</div>
|
|
876
|
+
)
|
|
877
|
+
}
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
Passport styles are possible to match with your project via css-variables set (use separate for dark & light).
|
|
881
|
+
Values will be automatically applied according to selected colorMode.
|
|
882
|
+
The simpliest way to customize cssv is via app index.css file:
|
|
883
|
+
|
|
884
|
+
```css
|
|
885
|
+
.lumia-scope[data-lumia-passport-mode='light'],
|
|
886
|
+
.lumia-scope[data-lumia-passport-mode='dark'] {
|
|
887
|
+
/* fixed cssv */
|
|
888
|
+
--lumia-passport-maw: 384px;
|
|
889
|
+
--lumia-passport-pd: 12px;
|
|
890
|
+
--lumia-passport-gap: 10px;
|
|
891
|
+
--lumia-passport-bdrs: 20px;
|
|
892
|
+
--lumia-passport-element-bdrs: 10px;
|
|
893
|
+
|
|
894
|
+
/** overlay */
|
|
895
|
+
--lumia-passport-overlay: rgba(255, 255, 255, 0.8);
|
|
896
|
+
--lumia-passport-backdrop-blur: 10px;
|
|
897
|
+
|
|
898
|
+
/** surface backgrounds */
|
|
899
|
+
--lumia-passport-bg: #ffffff;
|
|
900
|
+
|
|
901
|
+
/** text */
|
|
902
|
+
--lumia-passport-fg: #000000;
|
|
903
|
+
--lumia-passport-fg-h: rgba(0, 0, 0, 0.6);
|
|
904
|
+
--lumia-passport-fg-a: rgba(0, 0, 0, 0.4);
|
|
905
|
+
--lumia-passport-fg-inverted: #ffffff;
|
|
906
|
+
--lumia-passport-fg-muted: rgba(0, 0, 0, 0.6);
|
|
907
|
+
|
|
908
|
+
/** backgrounds i.e. buttons bg etc */
|
|
909
|
+
--lumia-passport-primary: #000000;
|
|
910
|
+
--lumia-passport-primary-h: rgba(0, 0, 0, 0.8);
|
|
911
|
+
--lumia-passport-primary-a: rgba(0, 0, 0, 0.6);
|
|
912
|
+
|
|
913
|
+
--lumia-passport-secondary: #e4e4e4;
|
|
914
|
+
--lumia-passport-secondary-h: rgba(228, 228, 228, 0.8);
|
|
915
|
+
--lumia-passport-secondary-a: rgba(228, 228, 228, 0.6);
|
|
916
|
+
|
|
917
|
+
/** borders */
|
|
918
|
+
--lumia-passport-bd: #ebebeb;
|
|
919
|
+
--lumia-passport-bd-intense: rgb(169, 169, 169);
|
|
920
|
+
|
|
921
|
+
/** shadows */
|
|
922
|
+
--lumia-passport-shadow-c: rgba(0, 0, 0, 0.1);
|
|
923
|
+
|
|
924
|
+
/** highlight colors */
|
|
925
|
+
--lumia-passport-info: #000000;
|
|
926
|
+
--lumia-passport-bg-info: #e4e4e4;
|
|
927
|
+
|
|
928
|
+
--lumia-passport-success: #000000;
|
|
929
|
+
--lumia-passport-bg-success: #21ff51;
|
|
930
|
+
|
|
931
|
+
--lumia-passport-warning: #000000;
|
|
932
|
+
--lumia-passport-bg-warning: #e9fa00;
|
|
933
|
+
|
|
934
|
+
--lumia-passport-error: #ffffff;
|
|
935
|
+
--lumia-passport-bg-error: #d6204e;
|
|
936
|
+
}
|
|
937
|
+
```
|
|
938
|
+
|
|
939
|
+
## TypeScript Support
|
|
940
|
+
|
|
941
|
+
Full TypeScript support with exported types:
|
|
942
|
+
|
|
943
|
+
```tsx
|
|
944
|
+
import type { AuthProvider, LumiaPassportConfig, User, WalletInfo } from '@embarkai/ui-kit'
|
|
945
|
+
```
|
|
946
|
+
|
|
947
|
+
## Examples
|
|
948
|
+
|
|
949
|
+
Check out the `/examples` directory for complete working examples:
|
|
950
|
+
|
|
951
|
+
- **React + Vite** - Modern React setup with Vite
|
|
952
|
+
- **Next.js** - Next.js App Router integration
|
|
953
|
+
- **React + TypeScript** - Full TypeScript example
|
|
954
|
+
|
|
955
|
+
## Security
|
|
956
|
+
|
|
957
|
+
- 🔒 **MPC Key Management** - Keys are split between client and server using threshold cryptography
|
|
958
|
+
- 🏝️ **Iframe Isolation** - Sensitive operations run in isolated iframe context
|
|
959
|
+
- 🔐 **No Private Key Exposure** - Private keys never exist in complete form on client
|
|
960
|
+
- ✅ **Non-custodial** - Users maintain full control of their accounts
|
|
961
|
+
|
|
962
|
+
## Need Help?
|
|
963
|
+
|
|
964
|
+
- 📖 [Documentation](https://docs.lumiapassport.com)
|
|
965
|
+
- 💬 [Discord Community](https://discord.gg/lumia)
|
|
966
|
+
- 🐛 [Report Issues](https://github.com/lumiachain/lumia-passport-sdk/issues)
|
|
967
|
+
- 📧 [Email Support](mailto:support@lumia.org)
|
|
968
|
+
|
|
969
|
+
## License
|
|
970
|
+
|
|
971
|
+
MIT License - see LICENSE file for details.
|
|
972
|
+
|
|
973
|
+
## Links
|
|
974
|
+
|
|
975
|
+
- [Website](https://lumiapassport.com)
|
|
976
|
+
- [GitHub](https://github.com/lumiachain/lumia-passport-sdk)
|
|
977
|
+
- [NPM Package](https://www.npmjs.com/package/@embarkai/ui-kit)
|