cilantro-react 0.1.5 → 0.1.6

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
@@ -1,8 +1,64 @@
1
1
  # cilantro-react
2
2
 
3
- React SDK/UI for [Cilantro Smart Wallet](https://www.npmjs.com/package/cilantro-sdk). Provides a single context provider, hooks, and UI components for auth, wallets, signers, and transactions. Install as **cilantro-react**; documentation may show `@cilantro/react-sdk` for illustration—use `cilantro-react` in install and imports.
3
+ React SDK/UI for [Cilantro Smart Wallet](https://www.npmjs.com/package/cilantro-sdk). Provides a single context provider, hooks, and UI components for authentication, wallets, signers, and transactions.
4
+
5
+ ## Table of Contents
6
+
7
+ - [cilantro-react](#cilantro-react)
8
+ - [Table of Contents](#table-of-contents)
9
+ - [Installation](#installation)
10
+ - [Polyfills (Browser)](#polyfills-browser)
11
+ - [Quick Start](#quick-start)
12
+ - [CilantroProvider](#cilantroprovider)
13
+ - [Props](#props)
14
+ - [Storage Adapters](#storage-adapters)
15
+ - [Hooks](#hooks)
16
+ - [useAuth](#useauth)
17
+ - [useWallet](#usewallet)
18
+ - [useSigners](#usesigners)
19
+ - [usePasskey](#usepasskey)
20
+ - [useExternalWallet](#useexternalwallet)
21
+ - [useSendTransaction](#usesendtransaction)
22
+ - [useCilantroConfig](#usecilantroconfig)
23
+ - [Components](#components)
24
+ - [Auth Components](#auth-components)
25
+ - [LoginForm](#loginform)
26
+ - [LogoutButton](#logoutbutton)
27
+ - [AuthGuard](#authguard)
28
+ - [Wallet Components](#wallet-components)
29
+ - [WalletSelector](#walletselector)
30
+ - [CreateWalletForm](#createwalletform)
31
+ - [WalletAddress](#walletaddress)
32
+ - [WalletBalance](#walletbalance)
33
+ - [ConnectWalletButton](#connectwalletbutton)
34
+ - [Signer Components](#signer-components)
35
+ - [SignerSelector](#signerselector)
36
+ - [SignerList](#signerlist)
37
+ - [CreateEmailSignerForm](#createemailsignerform)
38
+ - [CreatePhoneSignerForm](#createphonesignerform)
39
+ - [AddPasskeyButton](#addpasskeybutton)
40
+ - [Transaction Components](#transaction-components)
41
+ - [SendSOLForm](#sendsolform)
42
+ - [SendSPLForm](#sendsplform)
43
+ - [TransactionStatus](#transactionstatus)
44
+ - [TransactionHistory](#transactionhistory)
45
+ - [Layout Components](#layout-components)
46
+ - [CilantroConnect](#cilantroconnect)
47
+ - [LoadingOverlay](#loadingoverlay)
48
+ - [ErrorBoundary](#errorboundary)
49
+ - [Theming](#theming)
50
+ - [Built-in Theme (ThemeProvider)](#built-in-theme-themeprovider)
51
+ - [Import Theme CSS Directly](#import-theme-css-directly)
52
+ - [Advanced Usage](#advanced-usage)
53
+ - [Low-Level Signer Signing](#low-level-signer-signing)
54
+ - [Solana Connection Adapter](#solana-connection-adapter)
55
+ - [Utilities](#utilities)
56
+ - [Types](#types)
57
+ - [License](#license)
4
58
 
5
- ## Install
59
+ ---
60
+
61
+ ## Installation
6
62
 
7
63
  ```bash
8
64
  npm install cilantro-react cilantro-sdk
@@ -12,907 +68,1180 @@ yarn add cilantro-react cilantro-sdk
12
68
  pnpm add cilantro-react cilantro-sdk
13
69
  ```
14
70
 
15
- **Peer dependencies:** React 18+, `cilantro-sdk`. Optionally `@solana/web3.js` for transaction/signing. Components use Tailwind-compatible class names; ensure your app has [Tailwind CSS](https://tailwindcss.com) configured.
71
+ **Requirements:**
72
+
73
+ - React 18+
74
+ - `cilantro-sdk` (peer dependency)
75
+ - [Tailwind CSS](https://tailwindcss.com) for component styling (components use Tailwind-compatible class names)
16
76
 
17
- ## Polyfills (browser)
77
+ ### Polyfills (Browser)
18
78
 
19
- Import at the very top of your entry file (e.g. `main.tsx`, `App.tsx`):
79
+ Import polyfills at the very top of your entry file (before any other imports):
20
80
 
21
81
  ```ts
22
- import 'cilantro-sdk/polyfills'
82
+ import 'cilantro-sdk/polyfills';
23
83
  ```
24
84
 
25
- ## Wrap your app
85
+ ---
86
+
87
+ ## Quick Start
26
88
 
27
89
  ```tsx
28
- import { CilantroProvider } from 'cilantro-react'
90
+ import 'cilantro-sdk/polyfills';
91
+ import { CilantroProvider, LoginForm, AuthGuard, useAuth, useWallet } from 'cilantro-react';
29
92
 
30
- function Root() {
93
+ function App() {
31
94
  return (
32
95
  <CilantroProvider
33
96
  apiKey={import.meta.env.VITE_API_KEY}
34
97
  baseURL="https://api.cilantro.gg"
35
98
  >
36
- <App />
99
+ <AuthGuard>
100
+ <Dashboard />
101
+ </AuthGuard>
37
102
  </CilantroProvider>
38
- )
103
+ );
104
+ }
105
+
106
+ function Dashboard() {
107
+ const { user, logout } = useAuth();
108
+ const { wallet, wallets } = useWallet();
109
+
110
+ return (
111
+ <div>
112
+ <p>Welcome, {user?.username}</p>
113
+ <p>Wallet: {wallet?.walletName}</p>
114
+ <button onClick={logout}>Logout</button>
115
+ </div>
116
+ );
39
117
  }
40
118
  ```
41
119
 
42
- ### Storage adapter (device keys)
120
+ ---
121
+
122
+ ## CilantroProvider
43
123
 
44
- For email and phone signers, device keys must be stored. Configure a storage adapter:
124
+ The root provider that initializes configuration, storage, authentication, and wallet state.
45
125
 
46
126
  ```tsx
47
- import { CilantroProvider, createIndexedDBAdapter } from 'cilantro-react'
127
+ import { CilantroProvider, createIndexedDBAdapter } from 'cilantro-react';
48
128
 
49
- const storage = createIndexedDBAdapter()
129
+ const storage = createIndexedDBAdapter();
50
130
 
51
- <CilantroProvider apiKey={API_KEY} storageAdapter={storage}>
131
+ <CilantroProvider
132
+ apiKey="your-platform-api-key"
133
+ baseURL="https://api.cilantro.gg"
134
+ storageAdapter={storage}
135
+ >
52
136
  <App />
53
137
  </CilantroProvider>
54
138
  ```
55
139
 
56
- | Adapter | Use case |
140
+ ### Props
141
+
142
+ | Prop | Type | Required | Default | Description |
143
+ |------|------|----------|---------|-------------|
144
+ | `apiKey` | `string` | No* | — | Platform API key for server-side operations |
145
+ | `baseURL` | `string` | No | `https://api.cilantro.gg` | API base URL |
146
+ | `storageAdapter` | `DeviceKeyStorage` | No | IndexedDB | Storage adapter for device keys |
147
+ | `children` | `ReactNode` | Yes | — | App content |
148
+
149
+ *Either `apiKey` or JWT (obtained via login) is required for authenticated requests.
150
+
151
+ ### Storage Adapters
152
+
153
+ For email and phone signers, device keys must be stored. Choose an adapter:
154
+
155
+ | Adapter | Use Case |
57
156
  |---------|----------|
58
- | `createIndexedDBAdapter()` | Production – persistent, large capacity |
157
+ | `createIndexedDBAdapter()` | **Production** – persistent, large capacity |
59
158
  | `createLocalStorageAdapter()` | Development – simple, limited capacity |
60
159
  | `createMemoryAdapter()` | Testing – not persisted |
61
160
 
62
- **CilantroProvider props (guide API):**
161
+ ```tsx
162
+ import { createIndexedDBAdapter, createLocalStorageAdapter, createMemoryAdapter } from 'cilantro-react';
63
163
 
64
- | Prop | Type | Description |
65
- |------|------|-------------|
66
- | `apiKey` | `string` | Platform API key (optional if using JWT via login). |
67
- | `baseURL` | `string` | API base URL (default: `https://api.cilantro.gg`). |
68
- | `storageAdapter` | `DeviceKeyStorage` | Storage for device keys (default: IndexedDB in browser). |
69
- | `children` | `ReactNode` | App content. |
164
+ // Production
165
+ const storage = createIndexedDBAdapter();
70
166
 
71
- Backward compatibility: `platformApiKey` / `apiUrl` are still supported (deprecated). See [API reference](#api-reference) for full props.
167
+ // Development
168
+ const storage = createLocalStorageAdapter();
72
169
 
73
- **Connection:** The library does not create a Solana RPC connection. For transaction sign-and-send, pass your own `connection` from `@solana/web3.js` into `useTransactionSigning` / `TransactionSigningForm`. See [Connection setup](#connection-setup-solana) below.
170
+ // Testing
171
+ const storage = createMemoryAdapter();
172
+ ```
74
173
 
75
174
  ---
76
175
 
77
- ## Display logic and form visibility
176
+ ## Hooks
78
177
 
79
- Message and transaction signing forms only show the actual form when **both** a wallet and a signer are selected:
178
+ ### useAuth
80
179
 
81
- - **Wallet selected:** Use `useWallets().selectedWallet` as the single source of truth. Derive `walletId` and `hasWallet` from it:
82
- `walletId = selectedWallet?.id ?? selectedWallet?.walletId ?? ""`,
83
- `hasWallet = !!walletId`.
84
- - **Signer selected:** Use `useSignerSelection().selectedSigner`; `hasSigner = !!selectedSigner`.
180
+ Authentication state and actions.
181
+
182
+ ```tsx
183
+ import { useAuth } from 'cilantro-react';
85
184
 
86
- Show the form only when `hasWallet && hasSigner`. Do not rely only on `useSignerSelection().selectedWalletId` for “wallet selected” to avoid sync issues between the two hooks. When building custom pages, pass `selectedWalletId={walletId}` and `selectedSigner={selectedSigner}` explicitly into `MessageSigningForm` / `TransactionSigningForm` so the forms use the same wallet/signer state as your UI.
185
+ function MyComponent() {
186
+ const { user, jwt, isLoading, isAuthenticated, login, logout } = useAuth();
187
+
188
+ const handleLogin = async () => {
189
+ await login({ usernameOrEmail: 'user@example.com', password: 'password123' });
190
+ };
191
+
192
+ return (
193
+ <div>
194
+ {isLoading && <p>Loading...</p>}
195
+ {isAuthenticated ? (
196
+ <>
197
+ <p>Hello, {user?.username}</p>
198
+ <button onClick={logout}>Logout</button>
199
+ </>
200
+ ) : (
201
+ <button onClick={handleLogin}>Login</button>
202
+ )}
203
+ </div>
204
+ );
205
+ }
206
+ ```
207
+
208
+ **Returns: `UseAuthResult`**
209
+
210
+ | Property | Type | Description |
211
+ |----------|------|-------------|
212
+ | `user` | `User \| null` | Current user object (`{ username?, email?, userType? }`) |
213
+ | `jwt` | `string \| null` | JWT token (null when not authenticated) |
214
+ | `isLoading` | `boolean` | True while restoring session from storage |
215
+ | `isAuthenticated` | `boolean` | True when user has valid JWT |
216
+ | `login` | `(params: { usernameOrEmail: string; password: string }) => Promise<void>` | Log in; throws on error |
217
+ | `logout` | `() => void` | Clear session and token |
87
218
 
88
219
  ---
89
220
 
90
- ## Connection setup (Solana)
221
+ ### useWallet
91
222
 
92
- For transaction sign-and-send (wallet-adapter or passkey), the library expects a connection object with `sendRawTransaction`, `getLatestBlockhash`, and `confirmTransaction`. If you use `@solana/web3.js`, use the provided adapter so you don’t need type assertions:
223
+ Wallet state and actions.
93
224
 
94
225
  ```tsx
95
- import { Connection } from '@solana/web3.js'
96
- import { adaptSolanaConnection, TransactionSigningForm } from 'cilantro-react'
226
+ import { useWallet } from 'cilantro-react';
97
227
 
98
- const connection = new Connection('https://api.mainnet-beta.solana.com')
228
+ function WalletManager() {
229
+ const { wallet, wallets, createWallet, selectWallet, isLoading } = useWallet();
99
230
 
100
- <TransactionSigningForm
101
- connection={adaptSolanaConnection(connection)}
102
- showContext
103
- />
104
- ```
231
+ const handleCreate = async () => {
232
+ const result = await createWallet({ name: 'My New Wallet' });
233
+ console.log('Created:', result.data.id);
234
+ };
105
235
 
106
- `adaptSolanaConnection(connection)` returns an object that matches the library’s expected connection shape. You can pass it to `TransactionSigningForm` or `useTransactionSigning({ connection: adaptSolanaConnection(solanaConnection), ... })`.
236
+ return (
237
+ <div>
238
+ {isLoading && <p>Loading wallets...</p>}
239
+ <p>Current wallet: {wallet?.walletName ?? 'None selected'}</p>
240
+ <ul>
241
+ {wallets.map((w) => (
242
+ <li key={w.id} onClick={() => selectWallet(w.id)}>
243
+ {w.walletName}
244
+ </li>
245
+ ))}
246
+ </ul>
247
+ <button onClick={handleCreate}>Create Wallet</button>
248
+ </div>
249
+ );
250
+ }
251
+ ```
107
252
 
108
- ---
253
+ **Returns: `UseWalletResult`**
109
254
 
110
- ## Headless usage and custom selectors
255
+ | Property | Type | Description |
256
+ |----------|------|-------------|
257
+ | `wallet` | `WalletData \| null` | Currently selected wallet |
258
+ | `wallets` | `WalletData[]` | All wallets for the authenticated user |
259
+ | `createWallet` | `(params: { name: string; userId?: string }) => Promise<WalletControllerCreateResult>` | Create a new wallet |
260
+ | `selectWallet` | `(walletId: string) => void` | Select a wallet by ID |
261
+ | `isLoading` | `boolean` | True while loading wallets |
111
262
 
112
- You can build your own wallet/signer UI with the hooks and render props instead of the built-in selectors:
263
+ ---
113
264
 
114
- - **Wallet:** Use `useWallets()` for `wallets`, `selectedWallet`, `selectWallet`, and `refreshWallets`. Use the `WalletSelector` `children` render prop to render your own list/dropdown; you get `{ wallets, selectedWallet, selectWallet, isLoading, refreshWallets }`.
115
- - **Signer:** Use `useSignerSelection()` for `availableSigners`, `selectedSigner`, `setSelectedSigner`, and `isLoadingSigners`. Use the `SignerSelector` `children` or `renderList` prop to render your own list (e.g. Shadcn Select or custom buttons).
265
+ ### useSigners
116
266
 
117
- Example: custom wallet + signer selection and conditional form:
267
+ Signers for a specific wallet.
118
268
 
119
269
  ```tsx
120
- const { selectedWallet } = useWallets()
121
- const { availableSigners, selectedSigner, setSelectedSigner, isLoadingSigners } = useSignerSelection()
122
- const walletId = selectedWallet?.id ?? selectedWallet?.walletId ?? ''
123
- const hasWallet = !!walletId
124
- const hasSigner = !!selectedSigner
270
+ import { useSigners } from 'cilantro-react';
271
+
272
+ function SignerManager({ walletId }: { walletId: string }) {
273
+ const {
274
+ signers,
275
+ isLoading,
276
+ error,
277
+ refresh,
278
+ createEmailSigner,
279
+ createPhoneSigner,
280
+ revokeSigner,
281
+ } = useSigners(walletId);
282
+
283
+ const handleCreateEmail = async () => {
284
+ const signer = await createEmailSigner({ email: 'user@example.com' });
285
+ console.log('Created signer:', signer.signerId);
286
+ };
287
+
288
+ const handleCreatePhone = async () => {
289
+ const signer = await createPhoneSigner({ phone: '+1234567890' });
290
+ console.log('Created signer:', signer.signerId);
291
+ };
125
292
 
126
- return (
127
- <>
128
- <WalletSelector>{({ wallets, selectedWallet, selectWallet, isLoading }) => (
129
- /* your custom wallet dropdown */
130
- )}</WalletSelector>
131
- <SignerSelector
132
- selectedWalletId={walletId}
133
- availableSigners={availableSigners}
134
- selectedSigner={selectedSigner}
135
- onSignerSelect={setSelectedSigner}
136
- isLoadingSigners={isLoadingSigners}
137
- >
138
- {({ signers, selectedSigner, onSignerSelect, isLoading }) => (
139
- /* your custom signer list */
140
- )}
141
- </SignerSelector>
142
- {hasWallet && hasSigner && (
143
- <MessageSigningForm
144
- selectedWalletId={walletId}
145
- selectedSigner={selectedSigner}
146
- showContext={false}
147
- />
148
- )}
149
- </>
150
- )
293
+ return (
294
+ <div>
295
+ {isLoading && <p>Loading signers...</p>}
296
+ {error && <p className="text-red-500">{error}</p>}
297
+ <ul>
298
+ {signers.map((s) => (
299
+ <li key={s.id}>
300
+ {s.email || s.phone || s.id} ({s.signerType})
301
+ <button onClick={() => revokeSigner(s.id)}>Revoke</button>
302
+ </li>
303
+ ))}
304
+ </ul>
305
+ <button onClick={handleCreateEmail}>Add Email Signer</button>
306
+ <button onClick={handleCreatePhone}>Add Phone Signer</button>
307
+ <button onClick={refresh}>Refresh</button>
308
+ </div>
309
+ );
310
+ }
151
311
  ```
152
312
 
153
- ---
154
-
155
- ## Property names (SDK → library)
313
+ **Parameters:**
156
314
 
157
- The library normalizes wallet and signer data at the boundary. You can rely on these fields in UI and hooks:
315
+ - `walletId: string | null | { walletId?: string | null }` Wallet ID to load signers for
158
316
 
159
- | SDK / API field | Normalized / library field | Notes |
160
- |-------------------|----------------------------|--------------------------------|
161
- | `walletId` | `id` | WalletData always has `id`. |
162
- | `walletAddress` | `address` | WalletData always has `address`. |
163
- | `isActive` | `active` | WalletData alias. |
164
- | `signerId` | `id` | SignerData always has `id` and `signerId`. |
165
- | `signerPubkey` / `publicKey` | `signerPubkey` and `publicKey` | SignerData has both. |
166
- | `signerType` / `type` | `signerType` and `type` | SignerData has both. |
317
+ **Returns: `UseSignersResult`**
167
318
 
168
- Use `normalizeWallet(dto)` and `normalizeSigner(dto, category)` at API boundaries if you ingest raw SDK responses; the provider and `loadSigners` already use normalized shapes.
319
+ | Property | Type | Description |
320
+ |----------|------|-------------|
321
+ | `signers` | `SignerData[]` | List of signers for the wallet |
322
+ | `isLoading` | `boolean` | True while loading |
323
+ | `error` | `string \| null` | Error message if load failed |
324
+ | `refresh` | `() => Promise<void>` | Reload signers |
325
+ | `createEmailSigner` | `(params: { email: string }) => Promise<SignerInfo>` | Create email signer |
326
+ | `createPhoneSigner` | `(params: { phone: string }) => Promise<SignerInfo>` | Create phone signer |
327
+ | `revokeSigner` | `(signerId: string) => Promise<void>` | Revoke a signer |
169
328
 
170
329
  ---
171
330
 
172
- ## API reference
331
+ ### usePasskey
173
332
 
174
- ### Providers
333
+ Passkey (WebAuthn) registration and authentication.
175
334
 
176
- - **CilantroProvider** – Root provider: initializes storage, auth, and wallet context. Requires `platformApiKey`; optional `apiUrl`, storage keys, and callbacks. Use this unless you only need auth.
177
- - **CilantroAuthProvider** Auth only (token, user, login, register, logout). Use when you don’t need the full provider stack (e.g. auth-only pages).
178
- - **WalletProvider** – Wallet list and selection. Depends on auth; usually used inside `CilantroProvider`.
179
- - **ThemeProvider** – Optional: sets `data-theme="light"` or `"dark"` on a wrapper and injects default CSS variables for light/dark. Props: `theme` (`"light"` | `"dark"` | `"system"`), `defaultTheme`, `storageKey`, `className`, `injectStyles`. See [Themes](#built-in-theme-themeprovider).
335
+ ```tsx
336
+ import { usePasskey } from 'cilantro-react';
180
337
 
181
- **CilantroAuthProvider props:** `platformApiKey` (required), `apiUrl`, `jwtStorageKey`, `onLoginSuccess`, `onLogout`, `onRegisterSuccess`.
338
+ function PasskeyManager({ walletId }: { walletId: string }) {
339
+ const { isSupported, register, authenticate, isLoading } = usePasskey(walletId);
182
340
 
183
- **WalletProvider props:** `storageKey` (default: `cilantro_selected_wallet_id`).
341
+ if (!isSupported) {
342
+ return <p>Passkeys not supported in this browser.</p>;
343
+ }
184
344
 
185
- ---
345
+ const handleRegister = async () => {
346
+ const result = await register();
347
+ console.log('Passkey registered:', result.signerId);
348
+ };
186
349
 
187
- ### Custom hooks (detailed)
350
+ const handleAuthenticate = async () => {
351
+ const result = await authenticate();
352
+ console.log('Authenticated with passkey');
353
+ };
188
354
 
189
- All hooks must be used within the appropriate provider (see each hook below).
355
+ return (
356
+ <div>
357
+ <button onClick={handleRegister} disabled={isLoading}>
358
+ {isLoading ? 'Registering...' : 'Register Passkey'}
359
+ </button>
360
+ <button onClick={handleAuthenticate} disabled={isLoading}>
361
+ {isLoading ? 'Authenticating...' : 'Authenticate'}
362
+ </button>
363
+ </div>
364
+ );
365
+ }
366
+ ```
190
367
 
191
- #### useCilantroAuth
368
+ **Parameters:**
192
369
 
193
- Auth state and actions. Use inside `CilantroAuthProvider` or `CilantroProvider`.
370
+ - `walletId: string | undefined` Wallet ID to register passkey for
194
371
 
195
- **Returns:** `CilantroAuthContextType`
372
+ **Returns: `UsePasskeyResult`**
196
373
 
197
374
  | Property | Type | Description |
198
375
  |----------|------|-------------|
199
- | `token` | `string \| null` | JWT token (null when not authenticated). |
200
- | `user` | `User \| null` | User object: `{ username?, email?, userType? }`. |
201
- | `isAuthenticated` | `boolean` | True when `token` is set. |
202
- | `isLoading` | `boolean` | True while restoring session from storage. |
203
- | `login` | `(usernameOrEmail: string, password: string) => Promise<void>` | Log in; throws on error. |
204
- | `register` | `(username: string, email: string, password: string, isActive?: boolean) => Promise<void>` | Register and log in; throws on error. |
205
- | `logout` | `() => void` | Clear token and user; calls `onLogout` if provided. |
376
+ | `isSupported` | `boolean` | True if WebAuthn is available |
377
+ | `register` | `() => Promise<{ signerId: string; isNew: boolean }>` | Register a new passkey |
378
+ | `authenticate` | `(signerId?: string) => Promise<{ credential: unknown }>` | Authenticate with passkey |
379
+ | `isLoading` | `boolean` | True during operation |
206
380
 
207
381
  ---
208
382
 
209
- #### useWallets
383
+ ### useExternalWallet
210
384
 
211
- Wallet list and selection. Use inside `CilantroProvider` (which includes `WalletProvider`).
385
+ Connect to external Solana wallets (Phantom, Solflare, etc.).
212
386
 
213
- **Returns:** `WalletContextType`
387
+ ```tsx
388
+ import { useExternalWallet } from 'cilantro-react';
214
389
 
215
- | Property | Type | Description |
216
- |----------|------|-------------|
217
- | `wallets` | `WalletData[]` | List of wallets for the authenticated user. |
218
- | `selectedWallet` | `WalletData \| null` | Currently selected wallet (persisted in localStorage). |
219
- | `selectWallet` | `(walletId: string) => void` | Set the selected wallet by id. |
220
- | `refreshWallets` | `() => Promise<void>` | Reload wallets from the API. |
221
- | `isLoading` | `boolean` | True while loading wallets. |
390
+ function ExternalWalletConnect() {
391
+ const { wallets, connectedWallet, connect, disconnect } = useExternalWallet();
222
392
 
223
- **WalletData:** `{ id, walletId, walletName?, address?, walletAddress?, chain?, active?, ... }`.
393
+ return (
394
+ <div>
395
+ {connectedWallet ? (
396
+ <div>
397
+ <p>Connected: {connectedWallet.name}</p>
398
+ <p>Public Key: {connectedWallet.publicKey?.toBase58()}</p>
399
+ <button onClick={disconnect}>Disconnect</button>
400
+ </div>
401
+ ) : (
402
+ <div>
403
+ {wallets.map((wallet) => (
404
+ <button key={wallet.name} onClick={() => connect(wallet)}>
405
+ Connect {wallet.name}
406
+ </button>
407
+ ))}
408
+ </div>
409
+ )}
410
+ </div>
411
+ );
412
+ }
413
+ ```
224
414
 
225
- ---
415
+ **Returns: `UseExternalWalletResult`**
226
416
 
227
- #### useSelectedWallet
417
+ | Property | Type | Description |
418
+ |----------|------|-------------|
419
+ | `wallets` | `SolanaWallet[]` | Detected Solana wallets (Phantom, Solflare, etc.) |
420
+ | `connectedWallet` | `SolanaWallet \| null` | Currently connected wallet |
421
+ | `connect` | `(wallet: SolanaWallet) => Promise<void>` | Connect to a wallet |
422
+ | `disconnect` | `() => void` | Disconnect current wallet |
228
423
 
229
- Convenience hook when you only need the currently selected wallet (and loading/refresh), not the full list. Use inside `CilantroProvider`.
424
+ ---
230
425
 
231
- **Returns:** `UseSelectedWalletResult`
426
+ ### useSendTransaction
232
427
 
233
- | Property | Type | Description |
234
- |----------|------|-------------|
235
- | `selectedWallet` | `WalletData \| null` | Currently selected wallet. |
236
- | `isLoading` | `boolean` | True while loading wallets. |
237
- | `refreshWallets` | `() => Promise<void>` | Reload wallets from the API. |
428
+ Send SOL and SPL tokens.
238
429
 
239
- **Use when:** You need the current wallet without the full wallet list or `selectWallet`.
430
+ ```tsx
431
+ import { useSendTransaction } from 'cilantro-react';
432
+
433
+ function SendTransaction({ walletId, signerId }: { walletId: string; signerId: string }) {
434
+ const { sendSOL, sendSPL, isPending, error } = useSendTransaction(walletId, signerId, 'email');
435
+
436
+ const handleSendSOL = async () => {
437
+ const result = await sendSOL({
438
+ recipientAddress: '7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU',
439
+ amountLamports: 100000000, // 0.1 SOL
440
+ });
441
+ console.log('Transaction signature:', result.data.signature);
442
+ };
443
+
444
+ const handleSendSPL = async () => {
445
+ const result = await sendSPL({
446
+ mintAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC
447
+ recipientAddress: '7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU',
448
+ amount: 1000000, // 1 USDC (6 decimals)
449
+ });
450
+ console.log('Transaction signature:', result.data.signature);
451
+ };
240
452
 
241
- ---
453
+ return (
454
+ <div>
455
+ {error && <p className="text-red-500">{error}</p>}
456
+ <button onClick={handleSendSOL} disabled={isPending}>
457
+ {isPending ? 'Sending...' : 'Send SOL'}
458
+ </button>
459
+ <button onClick={handleSendSPL} disabled={isPending}>
460
+ {isPending ? 'Sending...' : 'Send USDC'}
461
+ </button>
462
+ </div>
463
+ );
464
+ }
465
+ ```
242
466
 
243
- #### useWalletAddress
467
+ **Parameters:**
244
468
 
245
- Returns the address of the currently selected wallet (handles both `address` and `walletAddress` on WalletData). Use inside `CilantroProvider`.
469
+ - `walletId: string | undefined` Wallet to send from
470
+ - `signerId?: string` — Signer ID (optional)
471
+ - `signerType?: 'email' | 'phone' | 'passkey' | 'external'` — Signer type (optional)
246
472
 
247
- **Returns:** `string | null`
473
+ **Returns: `UseSendTransactionResult`**
248
474
 
249
- **Use when:** You need the selected wallet's address for display or building transactions.
475
+ | Property | Type | Description |
476
+ |----------|------|-------------|
477
+ | `sendSOL` | `(params: { recipientAddress: string; amountLamports: number }) => Promise<SubmitTransactionResult>` | Send SOL |
478
+ | `sendSPL` | `(params: { mintAddress: string; recipientAddress: string; amount: number }) => Promise<SubmitTransactionResult>` | Send SPL tokens |
479
+ | `isPending` | `boolean` | True while sending |
480
+ | `error` | `string \| null` | Error message if send failed |
250
481
 
251
482
  ---
252
483
 
253
- #### useSigners
484
+ ### useCilantroConfig
254
485
 
255
- Load signers for a wallet. Use inside `CilantroProvider` (auth required).
486
+ Access SDK configuration and storage adapter.
256
487
 
257
- **Options:** `UseSignersOptions`
488
+ ```tsx
489
+ import { useCilantroConfig } from 'cilantro-react';
258
490
 
259
- | Option | Type | Description |
260
- |--------|------|-------------|
261
- | `walletId` | `string \| null \| undefined` | Wallet ID to load signers for. If omitted, you can pass it from `useWallets().selectedWallet?.id`. |
491
+ function ConfigDisplay() {
492
+ const { config, storage } = useCilantroConfig();
262
493
 
263
- **Returns:** `UseSignersResult`
494
+ return (
495
+ <div>
496
+ <p>API Key: {config.apiKey ? '***' : 'Not set'}</p>
497
+ <p>Base URL: {config.baseURL}</p>
498
+ <p>Storage: {storage ? 'Configured' : 'Not configured'}</p>
499
+ </div>
500
+ );
501
+ }
502
+ ```
503
+
504
+ **Returns: `UseCilantroConfigResult`**
264
505
 
265
506
  | Property | Type | Description |
266
507
  |----------|------|-------------|
267
- | `signers` | `SignerData[]` | List of signers for the wallet. |
268
- | `isLoading` | `boolean` | True while loading. |
269
- | `error` | `string \| null` | Error message if load failed. |
270
- | `refresh` | `() => Promise<void>` | Reload signers. |
271
-
272
- When `walletId` is null/undefined, `signers` is empty and `refresh` no-ops.
508
+ | `config` | `{ apiKey?: string; baseURL?: string; jwt?: string }` | Current configuration |
509
+ | `storage` | `DeviceKeyStorage \| null` | Storage adapter instance |
273
510
 
274
511
  ---
275
512
 
276
- #### useSignersForSelectedWallet
513
+ ## Components
277
514
 
278
- Signers for the currently selected wallet (or a `walletId` override). Composes `useWallets` + `useSigners` so you don't pass `walletId` yourself. Use inside `CilantroProvider`.
515
+ ### Auth Components
279
516
 
280
- **Options:** `UseSignersForSelectedWalletOptions`
517
+ #### LoginForm
281
518
 
282
- | Option | Type | Description |
283
- |--------|------|-------------|
284
- | `walletId` | `string \| null \| undefined` | Override; when omitted, uses the selected wallet from context. |
519
+ Username/email + password login form with loading and error states.
520
+
521
+ ```tsx
522
+ import { LoginForm } from 'cilantro-react';
523
+
524
+ <LoginForm
525
+ onSuccess={() => console.log('Logged in!')}
526
+ onError={(err) => console.error(err.message)}
527
+ title="Welcome back"
528
+ description="Sign in to your account"
529
+ submitLabel="Sign in"
530
+ renderSwitchToRegister={() => <a href="/register">Create account</a>}
531
+ className="max-w-sm"
532
+ />
533
+ ```
285
534
 
286
- **Returns:** Same as `useSigners` – `UseSignersResult` (`signers`, `isLoading`, `error`, `refresh`).
535
+ **Props:**
287
536
 
288
- **Use when:** You need signers for the current wallet without calling `useWallets()` and `useSigners({ walletId })` in every component (e.g. SignerList-style UIs).
537
+ | Prop | Type | Default | Description |
538
+ |------|------|---------|-------------|
539
+ | `onSuccess` | `() => void` | — | Called after successful login |
540
+ | `onError` | `(error: Error) => void` | — | Called on login error |
541
+ | `title` | `string` | `"Sign in"` | Form title |
542
+ | `description` | `string` | — | Form description |
543
+ | `submitLabel` | `string` | `"Sign in"` | Submit button label |
544
+ | `rememberMe` | `boolean` | — | Show remember me option |
545
+ | `renderSwitchToRegister` | `() => ReactNode` | — | Render link to register page |
546
+ | `className` | `string` | — | Root element class |
547
+ | `classNames` | `LoginFormClassNames` | — | Class names for inner elements |
289
548
 
290
549
  ---
291
550
 
292
- #### useSignerSelection
551
+ #### LogoutButton
293
552
 
294
- Wallet + signer selection state for signing flows. Uses `useWallets` and `useSigners` internally. Use inside `CilantroProvider`.
553
+ Logout button with optional confirmation dialog.
295
554
 
296
- **Options:** `UseSignerSelectionOptions`
555
+ ```tsx
556
+ import { LogoutButton } from 'cilantro-react';
557
+
558
+ <LogoutButton
559
+ onLogout={() => console.log('Logged out')}
560
+ confirmBeforeLogout={true}
561
+ >
562
+ Sign out
563
+ </LogoutButton>
564
+ ```
297
565
 
298
- | Option | Type | Description |
299
- |--------|------|-------------|
300
- | `walletId` | `string \| null \| undefined` | Override wallet ID; if not set, uses `useWallets().selectedWallet?.id`. |
301
- | `signingMethod` | `'wallet-adapter' \| 'sdk-signer' \| null` | Current signing method (default: `'sdk-signer'`). When `'wallet-adapter'`, signer selection is ignored. |
566
+ **Props:**
302
567
 
303
- **Returns:** `UseSignerSelectionResult`
568
+ | Prop | Type | Default | Description |
569
+ |------|------|---------|-------------|
570
+ | `onLogout` | `() => void` | — | Called after logout |
571
+ | `confirmBeforeLogout` | `boolean` | `false` | Show confirmation dialog |
572
+ | `className` | `string` | — | Button class |
573
+ | `children` | `ReactNode` | `"Log out"` | Button content |
304
574
 
305
- | Property | Type | Description |
306
- |----------|------|-------------|
307
- | `selectedWalletId` | `string` | Effective wallet ID (override or from context). |
308
- | `setSelectedWalletId` | `(id: string) => void` | Set wallet ID (when not using context selection). |
309
- | `availableSigners` | `SignerData[]` | Signers for the selected wallet (from `useSigners`). |
310
- | `selectedSigner` | `SignerData \| null` | Currently selected signer. |
311
- | `setSelectedSigner` | `(signer: SignerData \| null) => void` | Set selected signer. |
312
- | `isLoadingSigners` | `boolean` | True while signers are loading. |
313
- | `reset` | `() => void` | Clear `selectedWalletId` and `selectedSigner`. |
575
+ ---
314
576
 
315
- **SigningMethod:** `'wallet-adapter'` = use wallet adapter for signing; `'sdk-signer'` = use a Cilantro signer (email, phone, passkey, external, etc.).
577
+ #### AuthGuard
316
578
 
317
- ---
579
+ Protect content behind authentication. Shows fallback (default: LoginForm) when not authenticated.
318
580
 
319
- #### useDelegatedKeys
581
+ ```tsx
582
+ import { AuthGuard, LoginForm } from 'cilantro-react';
320
583
 
321
- Load delegated keys for a wallet (same data as DelegatedKeySelector). Use for custom delegated-key UI or logic. Use inside `CilantroProvider` (auth required).
584
+ // Basic usage - shows LoginForm when not authenticated
585
+ <AuthGuard>
586
+ <ProtectedContent />
587
+ </AuthGuard>
322
588
 
323
- **Options:** `UseDelegatedKeysOptions`
589
+ // Custom fallback
590
+ <AuthGuard fallback={<CustomLoginPage />}>
591
+ <ProtectedContent />
592
+ </AuthGuard>
324
593
 
325
- | Option | Type | Description |
326
- |--------|------|-------------|
327
- | `walletId` | `string \| null \| undefined` | Wallet ID to load delegated keys for. When null/undefined, keys are empty. |
328
- | `filterActive` | `boolean` | When true (default), filter to active and non-expired keys only. |
594
+ // Redirect instead of showing fallback
595
+ <AuthGuard redirectTo="/login">
596
+ <ProtectedContent />
597
+ </AuthGuard>
329
598
 
330
- **Returns:** `UseDelegatedKeysResult`
599
+ // Hide content when not authenticated (no fallback)
600
+ <AuthGuard showFallback={false}>
601
+ <ProtectedContent />
602
+ </AuthGuard>
331
603
 
332
- | Property | Type | Description |
333
- |----------|------|-------------|
334
- | `keys` | `DelegatedKeyData[]` | List of delegated keys. |
335
- | `isLoading` | `boolean` | True while loading. |
336
- | `error` | `string \| null` | Error message if load failed. |
337
- | `refresh` | `() => Promise<void>` | Reload delegated keys. |
604
+ // Show skeleton while loading
605
+ <AuthGuard useSkeleton={true}>
606
+ <ProtectedContent />
607
+ </AuthGuard>
608
+ ```
338
609
 
339
- **Use when:** You need the list of delegated keys in custom UI or logic without using the DelegatedKeySelector component.
610
+ **Props:**
611
+
612
+ | Prop | Type | Default | Description |
613
+ |------|------|---------|-------------|
614
+ | `children` | `ReactNode` | — | Content to show when authenticated |
615
+ | `fallback` | `ReactNode` | `<LoginForm />` | Content when not authenticated |
616
+ | `redirectTo` | `string` | — | Redirect URL when not authenticated |
617
+ | `showFallback` | `boolean` | `true` | Whether to show fallback or null |
618
+ | `useSkeleton` | `boolean` | `false` | Show skeleton while loading auth state |
619
+ | `className` | `string` | — | Root element class |
620
+ | `classNames` | `AuthGuardClassNames` | — | Class names for inner elements |
340
621
 
341
622
  ---
342
623
 
343
- #### useCanSign
624
+ ### Wallet Components
344
625
 
345
- Derived "is the user ready to sign?" for disabling sign buttons or showing prompts. Use inside `CilantroProvider`.
626
+ #### WalletSelector
346
627
 
347
- **Options:** `UseCanSignOptions`
628
+ Dropdown to select from available wallets.
348
629
 
349
- | Option | Type | Description |
350
- |--------|------|-------------|
351
- | `signingMethod` | `SigningMethod \| null` | Override; default `'sdk-signer'`. |
352
- | `requireSigner` | `boolean` | When true (default), require a selected signer for sdk-signer. |
353
- | `walletAdapterConnected` | `boolean` | When using wallet-adapter, pass true when the adapter is connected so `canSign*` reflects it. |
630
+ ```tsx
631
+ import { WalletSelector } from 'cilantro-react';
354
632
 
355
- **Returns:** `UseCanSignResult`
633
+ // Controlled
634
+ const [walletId, setWalletId] = useState('');
635
+ <WalletSelector value={walletId} onChange={setWalletId} />
356
636
 
357
- | Property | Type | Description |
358
- |----------|------|-------------|
359
- | `hasToken` | `boolean` | True when the user has a valid token. |
360
- | `hasWallet` | `boolean` | True when a wallet is selected. |
361
- | `hasSigner` | `boolean` | True when a signer is selected (sdk-signer) or wallet-adapter is connected (when `walletAdapterConnected` is passed). |
362
- | `canSignMessage` | `boolean` | True when ready to sign a message. |
363
- | `canSignTransaction` | `boolean` | True when ready to sign/send a transaction (connection still required for send). |
637
+ // With callback
638
+ <WalletSelector
639
+ onWalletChange={(id, wallet) => console.log('Selected:', wallet?.walletName)}
640
+ placeholder="Choose a wallet"
641
+ />
364
642
 
365
- **Use when:** You need to disable sign buttons or show "Select wallet" / "Select signer" prompts without reimplementing token/wallet/signer checks. For wallet-adapter, pass `walletAdapterConnected` from your adapter state.
643
+ // Headless (custom rendering)
644
+ <WalletSelector>
645
+ {({ wallets, selectedWallet, selectWallet, isLoading }) => (
646
+ <div>
647
+ {wallets.map((w) => (
648
+ <button key={w.id} onClick={() => selectWallet(w.id)}>
649
+ {w.walletName}
650
+ </button>
651
+ ))}
652
+ </div>
653
+ )}
654
+ </WalletSelector>
655
+ ```
656
+
657
+ **Props:**
658
+
659
+ | Prop | Type | Default | Description |
660
+ |------|------|---------|-------------|
661
+ | `value` | `string` | — | Controlled wallet ID |
662
+ | `onChange` | `(walletId: string) => void` | — | Called when wallet changes |
663
+ | `onWalletChange` | `(walletId: string, wallet: WalletData \| null) => void` | — | Called with wallet object |
664
+ | `placeholder` | `string` | `"Select a wallet"` | Placeholder text |
665
+ | `useSkeleton` | `boolean` | `true` | Show skeleton while loading |
666
+ | `className` | `string` | — | Root element class |
667
+ | `classNames` | `WalletSelectorClassNames` | — | Class names for inner elements |
668
+ | `children` | `(props) => ReactNode` | — | Headless render function |
669
+ | `renderTrigger` | `(props) => ReactNode` | — | Custom trigger render |
670
+ | `renderList` | `(props) => ReactNode` | — | Custom list render |
366
671
 
367
672
  ---
368
673
 
369
- #### useMessageSigning
674
+ #### CreateWalletForm
370
675
 
371
- Sign a message with the selected signer or wallet-adapter. Use inside `CilantroProvider`.
676
+ Form to create a new wallet.
372
677
 
373
- **Options:** `UseMessageSigningOptions`
678
+ ```tsx
679
+ import { CreateWalletForm } from 'cilantro-react';
374
680
 
375
- | Option | Type | Description |
376
- |--------|------|-------------|
377
- | `token` | `string \| null` | JWT (usually from `useCilantroAuth().token`). |
378
- | `signingMethod` | `SigningMethod \| null` | `'sdk-signer'` or `'wallet-adapter'`. |
379
- | `selectedSigner` | `SignerData \| null` | Signer to use when `signingMethod === 'sdk-signer'`. |
380
- | `selectedWalletId` | `string` | Wallet ID when using SDK signer. |
381
- | `walletAdapterSignMessage` | `(message: Uint8Array) => Promise<Uint8Array>` | Optional; required when `signingMethod === 'wallet-adapter'`. |
382
- | `walletAdapterPublicKey` | `string \| null` | Optional; included in success detail when using wallet-adapter. |
681
+ <CreateWalletForm
682
+ onCreated={(result) => console.log('Created wallet:', result.data.id)}
683
+ onError={(err) => console.error(err.message)}
684
+ userId="optional-user-id"
685
+ />
686
+ ```
383
687
 
384
- **Returns:** `UseMessageSigningResult`
688
+ **Props:**
385
689
 
386
- | Property | Type | Description |
387
- |----------|------|-------------|
388
- | `messageText` | `string` | Current message (default: `"Hello, Solana!"`). |
389
- | `setMessageText` | `(text: string) => void` | Update message. |
390
- | `signResultState` | `ActionState` | `{ status, message?, detail? }` idle, loading, success, or error. |
391
- | `isSigning` | `boolean` | True while signing. |
392
- | `handleSign` | `() => Promise<void>` | Sign the current message; updates `signResultState`. |
393
- | `reset` | `() => void` | Reset message to default and clear result state. |
690
+ | Prop | Type | Default | Description |
691
+ |------|------|---------|-------------|
692
+ | `userId` | `string` | | Optional user ID to associate wallet with |
693
+ | `onCreated` | `(wallet: WalletControllerCreateResult) => void` | | Called after wallet is created |
694
+ | `onError` | `(error: Error) => void` | | Called on error |
695
+ | `className` | `string` | | Form class |
394
696
 
395
697
  ---
396
698
 
397
- #### useTransactionSigning
699
+ #### WalletAddress
398
700
 
399
- Sign and/or send a transaction. Use inside `CilantroProvider`.
701
+ Display a wallet address with copy functionality.
400
702
 
401
- **Options:** `UseTransactionSigningOptions`
402
-
403
- | Option | Type | Description |
404
- |--------|------|-------------|
405
- | `token` | `string \| null` | JWT (usually from `useCilantroAuth().token`). |
406
- | `signingMethod` | `SigningMethod \| null` | `'sdk-signer'` or `'wallet-adapter'`. |
407
- | `selectedSigner` | `SignerData \| null` | Signer when `signingMethod === 'sdk-signer'`. |
408
- | `selectedWalletId` | `string` | Wallet ID when using SDK signer. |
409
- | `walletAdapterSignTransaction` | `(transaction: Transaction) => Promise<Transaction>` | Optional; required for wallet-adapter signing. |
410
- | `walletAdapterPublicKey` | `string \| null` | Optional; required for wallet-adapter send. |
411
- | `connection` | `SignAndSendConnection \| null` | **Required for sign-and-send** when using wallet-adapter or passkey. Use `adaptSolanaConnection(connection)` when you have a `@solana/web3.js` Connection; see [Connection setup](#connection-setup-solana). |
703
+ ```tsx
704
+ import { WalletAddress } from 'cilantro-react';
412
705
 
413
- **Returns:** `UseTransactionSigningResult`
706
+ <WalletAddress
707
+ address="7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU"
708
+ short={true}
709
+ copyable={true}
710
+ />
711
+ ```
414
712
 
415
- | Property | Type | Description |
416
- |----------|------|-------------|
417
- | `transactionResultState` | `ActionState` | Result of last sign/send (idle, loading, success, error). |
418
- | `isSigningTransaction` | `boolean` | True while `signTransaction` is running. |
419
- | `isSendingTransaction` | `boolean` | True while `signAndSendTransaction` is running. |
420
- | `signTransaction` | `(transaction: Transaction) => Promise<void>` | Sign only (no send). Passkey signers cannot sign-only; use sign-and-send. |
421
- | `signAndSendTransaction` | `(transaction: Transaction) => Promise<void>` | Sign and send. Requires `connection` for wallet-adapter and passkey. |
422
- | `reset` | `() => void` | Set `transactionResultState` back to idle. |
713
+ **Props:**
423
714
 
424
- **Connection:** For passkey or wallet-adapter sign-and-send, pass a `connection` that has `getLatestBlockhash`, `sendRawTransaction`, and `confirmTransaction`. Use `adaptSolanaConnection(connection)` when you have a `@solana/web3.js` Connection; see [Connection setup](#connection-setup-solana).
715
+ | Prop | Type | Default | Description |
716
+ |------|------|---------|-------------|
717
+ | `address` | `string` | — | Wallet address to display |
718
+ | `short` | `boolean` | `false` | Truncate address for display |
719
+ | `copyable` | `boolean` | `false` | Show copy button |
720
+ | `className` | `string` | — | Element class |
425
721
 
426
722
  ---
427
723
 
428
- ### Components (detailed)
429
-
430
- All components accept `className`. Many accept a `classNames` object (see TypeScript types e.g. `WalletSelectorClassNames`, `MessageSigningFormClassNames`).
724
+ #### WalletBalance
431
725
 
432
- #### Auth components
726
+ Display wallet SOL and token balances.
433
727
 
434
- | Component | Purpose | Key props |
435
- |-----------|---------|-----------|
436
- | **AuthForm** | Single form that toggles login/register. | `defaultMode?: 'login' \| 'register'`, `onSuccess?`, `onError?(error: string)`, `loginTitle`, `registerTitle`, `loginDescription`, `registerDescription`, `loginSubmitLabel`, `registerSubmitLabel`, `switchToRegisterText`, `switchToLoginText`, `isActive?`, `className`, `classNames?` (root, header, title, description, form, label, input, submitButton, toggle, toggleLink, error). |
437
- | **LoginForm** | Login (username/email + password). | `onSuccess?`, `onError?(error: string)`, `submitLabel`, `title`, `description`, `renderSwitchToRegister? () => ReactNode`, `className`, `classNames?`. |
438
- | **RegisterForm** | Registration (username, email, password). | `onSuccess?`, `onError?(error: string)`, `submitLabel`, `title`, `description`, `isActive?` (default true), `renderSwitchToLogin? () => ReactNode`, `className`, `classNames?`. |
439
- | **AuthGuard** | Renders children when authenticated; shows fallback otherwise. | `children`, `fallback?: ReactNode` (default: `<LoginForm />`), `showFallback?` (default true), `className`, `classNames?` (root, fallback). |
440
-
441
- #### Wallet and signer components
728
+ ```tsx
729
+ import { WalletBalance } from 'cilantro-react';
442
730
 
443
- | Component | Purpose | Key props |
444
- |-----------|---------|-----------|
445
- | **WalletSelector** | Wallet dropdown (Shadcn Select). | `value?`, `onWalletChange?(walletId, wallet)`, `placeholder`, `className`, `classNames?` (root, trigger, content, item). Headless: `children?`, `renderTrigger?`, `renderList?`. |
446
- | **SignerSelector** | List/select signers (buttons). | `selectedWalletId?`, `availableSigners`, `selectedSigner`, `onSignerSelect(signer)`, `isLoadingSigners?`, `className`, `classNames?` (root, list, item, message). Headless: `children?`, `renderList?`. |
447
- | **SignerList** | List signers + “Add signer” (opens AddSignerForm in dialog). | `walletId`, `onSignerAdded`, `className`, `classNames`. Headless: `children`. |
448
- | **AddSignerForm** | Add signer (email, phone, passkey, external wallet). | `walletId`, `open?`, `onOpenChange?`, `onSuccess?`, `onCancel?`, `onError?(error)`, `asDialog?` (default true), `className`, `classNames?`. |
449
- | **DelegatedKeySelector** | Select delegated key for a wallet. | `walletId`, `value?`, `onChange?(keyId, key)`, `filterActive?` (default true), `placeholder`, `className`, `classNames?`. Headless: `children?` (keys, selectedKeyId, onSelect, isLoading, error, refresh). |
731
+ <WalletBalance walletId={walletId} showTokens={true} />
732
+ ```
450
733
 
451
- #### UI primitives
734
+ **Props:**
452
735
 
453
- - **Skeleton** Shadcn-style skeleton placeholder (`rounded-md bg-muted animate-pulse`). Use for loading states or custom UI. Exported for headless use.
454
- - **ThemeProvider** – See [Themes](#built-in-theme-themeprovider).
736
+ | Prop | Type | Default | Description |
737
+ |------|------|---------|-------------|
738
+ | `walletId` | `string` | — | Wallet ID to show balance for |
739
+ | `showTokens` | `boolean` | `false` | Show SPL token balances |
740
+ | `className` | `string` | — | Element class |
455
741
 
456
- #### Signing form components
742
+ ---
457
743
 
458
- | Component | Purpose | Key props |
459
- |-----------|---------|-----------|
460
- | **MessageSigningForm** | Message input + sign button; shows wallet/signer context. | `token?`, `selectedWalletId?`, `selectedSigner?`, `signingMethod?`, `walletAdapterSignMessage?`, `walletAdapterPublicKey?`, `showContext?`, `showCharCount?`, `className`, `classNames?`. Headless: `children?` (messageText, setMessageText, signResultState, isSigning, handleSign, reset). |
461
- | **TransactionSigningForm** | Sign/send transaction; pass `connection` for sign-and-send. | `token?`, `selectedWalletId?`, `selectedSigner?`, `signingMethod?`, `walletAdapterSignTransaction?`, `walletAdapterPublicKey?`, **`connection?`** (required for wallet-adapter or passkey sign-and-send), `showContext?`, `className`, `classNames?`. Headless: `children?` (transactionResultState, isSigningTransaction, isSendingTransaction, signTransaction, signAndSendTransaction, reset). |
744
+ #### ConnectWalletButton
462
745
 
463
- When token/wallet/signer/signingMethod are omitted, MessageSigningForm and TransactionSigningForm use `useCilantroAuth().token` and `useSignerSelection()` internally.
746
+ Connect to external Solana wallets (Phantom, Solflare).
464
747
 
465
- ### Types (detailed)
748
+ ```tsx
749
+ import { ConnectWalletButton } from 'cilantro-react';
750
+
751
+ <ConnectWalletButton
752
+ onConnect={(publicKey, wallet) => console.log('Connected:', publicKey)}
753
+ onDisconnect={() => console.log('Disconnected')}
754
+ >
755
+ Connect External Wallet
756
+ </ConnectWalletButton>
757
+ ```
466
758
 
467
- #### ActionState
759
+ **Props:**
468
760
 
469
- Used by signing hooks and forms for result state.
761
+ | Prop | Type | Default | Description |
762
+ |------|------|---------|-------------|
763
+ | `onConnect` | `(publicKey: string, wallet: SolanaWallet) => void` | — | Called when connected |
764
+ | `onDisconnect` | `() => void` | — | Called when disconnected |
765
+ | `className` | `string` | — | Button class |
766
+ | `children` | `ReactNode` | `"Connect Wallet"` | Button content |
470
767
 
471
- ```ts
472
- type ActionState<T = unknown> = {
473
- status: 'idle' | 'loading' | 'success' | 'error';
474
- message?: string;
475
- detail?: T;
476
- };
477
- ```
768
+ ---
478
769
 
479
- - **idle** – No operation yet.
480
- - **loading** – Sign/send in progress.
481
- - **success** – Done; `message` and optional `detail` (e.g. signature, explorer URL).
482
- - **error** – Failed; `message` (and optional `detail`).
770
+ ### Signer Components
483
771
 
484
- #### User
772
+ #### SignerSelector
485
773
 
486
- From `useCilantroAuth().user`:
774
+ Select a signer from the wallet's signers.
487
775
 
488
- ```ts
489
- interface User {
490
- username?: string;
491
- email?: string;
492
- userType?: string;
493
- }
494
- ```
776
+ ```tsx
777
+ import { SignerSelector } from 'cilantro-react';
495
778
 
496
- #### WalletData
779
+ const [signerId, setSignerId] = useState('');
780
+ const [signerType, setSignerType] = useState<'email' | 'phone'>('email');
497
781
 
498
- From `useWallets().wallets` / `selectedWallet`:
782
+ <SignerSelector
783
+ walletId={walletId}
784
+ value={signerId}
785
+ onChange={(id, type) => {
786
+ setSignerId(id);
787
+ setSignerType(type);
788
+ }}
789
+ />
499
790
 
500
- ```ts
501
- interface WalletData {
502
- id: string;
503
- walletId: string;
504
- walletName?: string;
505
- address?: string;
506
- walletAddress?: string;
507
- chain?: string;
508
- active?: boolean;
509
- [key: string]: unknown;
510
- }
791
+ // Headless
792
+ <SignerSelector walletId={walletId} onChange={handleChange}>
793
+ {({ signers, selectedSigner, onSignerSelect, isLoading }) => (
794
+ <div>
795
+ {signers.map((s) => (
796
+ <button key={s.id} onClick={() => onSignerSelect(s)}>
797
+ {s.email || s.phone}
798
+ </button>
799
+ ))}
800
+ </div>
801
+ )}
802
+ </SignerSelector>
511
803
  ```
512
804
 
513
- #### SignerData
805
+ **Props:**
514
806
 
515
- From `useSigners().signers` or `useSignerSelection().availableSigners` / `selectedSigner`. Used by SignerSelector, MessageSigningForm, TransactionSigningForm. Contains at least: `id`, `signerId`, `type` or `signerType`, `walletId`, `publicKey` / `signerPubkey`, and optional `email`, `phone`, `isActive`, `signerConfig`, etc. See TypeScript type `SignerData` for full shape.
807
+ | Prop | Type | Default | Description |
808
+ |------|------|---------|-------------|
809
+ | `walletId` | `string` | — | Wallet ID to load signers for |
810
+ | `value` | `string` | — | Selected signer ID |
811
+ | `onChange` | `(signerId: string, signerType: SignerType) => void` | — | Called when signer changes |
812
+ | `useSkeleton` | `boolean` | `true` | Show skeleton while loading |
813
+ | `className` | `string` | — | Root element class |
814
+ | `classNames` | `SignerSelectorClassNames` | — | Class names for inner elements |
815
+ | `children` | `(props) => ReactNode` | — | Headless render function |
816
+ | `renderList` | `(props) => ReactNode` | — | Custom list render |
516
817
 
517
- #### SignAndSendConnection / adaptSolanaConnection
818
+ ---
518
819
 
519
- Type for the `connection` prop when using sign-and-send (passkey or wallet-adapter). Must have:
820
+ #### SignerList
520
821
 
521
- - `sendRawTransaction(buf: Buffer)` `Promise<string>`
522
- - `getLatestBlockhash()` → `Promise<{ blockhash, lastValidBlockHeight }>`
523
- - `confirmTransaction(opts: { signature, blockhash, lastValidBlockHeight })` → `Promise<void>`
822
+ List signers with "Add signer" dialog (includes email, phone, passkey forms).
524
823
 
525
- If you use `@solana/web3.js`, call `adaptSolanaConnection(connection)` and pass the result; see [Connection setup](#connection-setup-solana).
824
+ ```tsx
825
+ import { SignerList } from 'cilantro-react';
526
826
 
527
- #### Other types
827
+ <SignerList
828
+ walletId={walletId}
829
+ onSignerAdded={() => console.log('Signer added')}
830
+ onRevoke={(signerId) => console.log('Revoked:', signerId)}
831
+ />
528
832
 
529
- - **ErrorResponse** – `{ message?, error?, code?, status?, statusText?, data?, ... }` for API error shapes.
530
- - **ApiResponse\<T\>** – SDK response shape; use `extractResponseData(response)` to unwrap.
531
- - **DelegatedKeyData** From DelegatedKeySelector: `id`, `walletId`, `name?`, `publicKey`, `permissions`, `isActive?`, `createdAt?`, `expiresAt?`, etc.
532
- - **SigningMethod** – `'wallet-adapter' | 'sdk-signer'`.
533
- - **AddSignerType** `'email' | 'phone' | 'passkey' | 'external'`.
534
- - **AuthFormMode** – `'login' | 'register'`.
833
+ // Headless
834
+ <SignerList walletId={walletId}>
835
+ {({ signers, isLoading, openAddSigner }) => (
836
+ <div>
837
+ {signers.map((s) => <div key={s.id}>{s.email}</div>)}
838
+ <button onClick={openAddSigner}>Add Signer</button>
839
+ </div>
840
+ )}
841
+ </SignerList>
842
+ ```
535
843
 
536
- #### Utilities
844
+ **Props:**
537
845
 
538
- - **extractErrorMessage(error: unknown): string** Returns a string from any thrown value (Error.message, object.message, response.data.message, etc.). Use in catch blocks when calling auth, signer, or SDK methods.
539
- - **extractResponseData\<T\>(response: unknown): T | null** – Unwraps SDK response shapes (`{ data }`, `{ success, data }`, signer arrays). Use after cilantro-sdk calls that return wrapped payloads.
846
+ | Prop | Type | Default | Description |
847
+ |------|------|---------|-------------|
848
+ | `walletId` | `string` | — | Wallet ID |
849
+ | `onSignerAdded` | `() => void` | — | Called after signer is added |
850
+ | `onRevoke` | `(signerId: string) => void` | — | Called when signer is revoked |
851
+ | `useSkeleton` | `boolean` | `true` | Show skeleton while loading |
852
+ | `className` | `string` | — | Root element class |
853
+ | `classNames` | `SignerListClassNames` | — | Class names for inner elements |
854
+ | `children` | `(props) => ReactNode` | — | Headless render function |
540
855
 
541
856
  ---
542
857
 
543
- ## Examples
544
-
545
- ### Auth – single form (AuthForm)
858
+ #### CreateEmailSignerForm
546
859
 
547
- One form that switches between sign-in and create-account:
860
+ Form to create an email signer.
548
861
 
549
862
  ```tsx
550
- import { AuthForm } from 'cilantro-react'
863
+ import { CreateEmailSignerForm } from 'cilantro-react';
551
864
 
552
- function LoginPage() {
553
- return (
554
- <AuthForm
555
- defaultMode="login"
556
- onSuccess={() => window.location.href = '/dashboard'}
557
- onError={(err) => console.error(err)}
558
- loginTitle="Welcome back"
559
- registerTitle="Create an account"
560
- switchToRegisterText="Don't have an account? Create one"
561
- switchToLoginText="Already have an account? Sign in"
562
- />
563
- )
564
- }
865
+ <CreateEmailSignerForm
866
+ walletId={walletId}
867
+ onCreated={(signer) => console.log('Created:', signer.signerId)}
868
+ onError={(err) => console.error(err.message)}
869
+ />
565
870
  ```
566
871
 
567
- ### Auth – separate Login and Register
872
+ **Props:**
568
873
 
569
- Use `LoginForm` and `RegisterForm` with links to switch pages:
874
+ | Prop | Type | Default | Description |
875
+ |------|------|---------|-------------|
876
+ | `walletId` | `string` | — | Wallet ID |
877
+ | `onCreated` | `(signer: SignerInfo) => void` | — | Called after signer is created |
878
+ | `onError` | `(error: Error) => void` | — | Called on error |
879
+ | `className` | `string` | — | Form class |
570
880
 
571
- ```tsx
572
- import { LoginForm, RegisterForm } from 'cilantro-react'
573
- import { Link } from 'react-router-dom'
574
-
575
- function LoginPage() {
576
- return (
577
- <LoginForm
578
- onSuccess={() => navigate('/dashboard')}
579
- renderSwitchToRegister={() => (
580
- <Link to="/register">Create account</Link>
581
- )}
582
- />
583
- )
584
- }
585
-
586
- function RegisterPage() {
587
- return (
588
- <RegisterForm
589
- onSuccess={() => navigate('/dashboard')}
590
- renderSwitchToLogin={() => (
591
- <Link to="/login">Already have an account? Sign in</Link>
592
- )}
593
- />
594
- )
595
- }
596
- ```
881
+ ---
597
882
 
598
- ### Auth – protected route (AuthGuard)
883
+ #### CreatePhoneSignerForm
599
884
 
600
- Show login when not authenticated, otherwise render children:
885
+ Form to create a phone signer.
601
886
 
602
887
  ```tsx
603
- import { AuthGuard, LoginForm } from 'cilantro-react'
888
+ import { CreatePhoneSignerForm } from 'cilantro-react';
604
889
 
605
- function DashboardRoute() {
606
- return (
607
- <AuthGuard fallback={<LoginForm />}>
608
- <Dashboard />
609
- </AuthGuard>
610
- )
611
- }
890
+ <CreatePhoneSignerForm
891
+ walletId={walletId}
892
+ onCreated={(signer) => console.log('Created:', signer.signerId)}
893
+ onError={(err) => console.error(err.message)}
894
+ />
895
+ ```
612
896
 
613
- // Or use default fallback (LoginForm)
614
- <AuthGuard>
615
- <Dashboard />
616
- </AuthGuard>
897
+ **Props:**
617
898
 
618
- // Hide content when unauthenticated (no fallback UI)
619
- <AuthGuard showFallback={false}>
620
- <Dashboard />
621
- </AuthGuard>
622
- ```
899
+ | Prop | Type | Default | Description |
900
+ |------|------|---------|-------------|
901
+ | `walletId` | `string` | — | Wallet ID |
902
+ | `onCreated` | `(signer: SignerInfo) => void` | — | Called after signer is created |
903
+ | `onError` | `(error: Error) => void` | — | Called on error |
904
+ | `className` | `string` | — | Form class |
623
905
 
624
- ### Wallets
906
+ ---
625
907
 
626
- Wallet dropdown with optional customization:
908
+ #### AddPasskeyButton
627
909
 
628
- ```tsx
629
- import { useWallets, WalletSelector } from 'cilantro-react'
910
+ Button to register a passkey signer. Only renders if WebAuthn is supported.
630
911
 
631
- function Wallets() {
632
- const { wallets, selectedWallet } = useWallets()
912
+ ```tsx
913
+ import { AddPasskeyButton } from 'cilantro-react';
633
914
 
634
- return (
635
- <div>
636
- <WalletSelector
637
- placeholder="Choose a wallet"
638
- onWalletChange={(id, wallet) => console.log('Selected', id, wallet)}
639
- classNames={{
640
- root: 'w-full max-w-xs',
641
- trigger: 'border-2',
642
- }}
643
- />
644
- {selectedWallet && (
645
- <p>Selected: {selectedWallet.walletName ?? selectedWallet.id}</p>
646
- )}
647
- </div>
648
- )
649
- }
915
+ <AddPasskeyButton
916
+ walletId={walletId}
917
+ onRegistered={(signer) => console.log('Registered:', signer.signerId)}
918
+ onError={(err) => console.error(err.message)}
919
+ >
920
+ Add Passkey
921
+ </AddPasskeyButton>
650
922
  ```
651
923
 
652
- ### Signers – list and select
924
+ **Props:**
653
925
 
654
- Load signers for the selected wallet and let the user pick one:
926
+ | Prop | Type | Default | Description |
927
+ |------|------|---------|-------------|
928
+ | `walletId` | `string` | — | Wallet ID |
929
+ | `onRegistered` | `(signer: SignerInfo) => void` | — | Called after passkey is registered |
930
+ | `onError` | `(error: Error) => void` | — | Called on error |
931
+ | `className` | `string` | — | Container class |
932
+ | `children` | `ReactNode` | `"Add Passkey"` | Button content |
655
933
 
656
- ```tsx
657
- import { useSigners, useSignerSelection, SignerSelector } from 'cilantro-react'
934
+ ---
658
935
 
659
- function SignerPick() {
660
- const { walletId } = useSignerSelection({ walletId: selectedWalletId })
661
- const { signers, isLoading, refresh } = useSigners({ walletId: walletId ?? undefined })
936
+ ### Transaction Components
662
937
 
663
- return (
664
- <SignerSelector
665
- selectedWalletId={walletId ?? undefined}
666
- availableSigners={signers}
667
- selectedSigner={selectedSigner}
668
- onSignerSelect={setSelectedSigner}
669
- isLoadingSigners={isLoading}
670
- className="w-full"
671
- />
672
- )
673
- }
674
- ```
938
+ #### SendSOLForm
675
939
 
676
- Headless: use `children` or `renderList` to render the list yourself.
940
+ Form to send SOL.
677
941
 
678
- ### Signers – add signer (AddSignerForm + SignerList)
942
+ ```tsx
943
+ import { SendSOLForm } from 'cilantro-react';
679
944
 
680
- List signers and open a dialog to add a new one (email, phone, passkey, external wallet):
945
+ <SendSOLForm
946
+ walletId={walletId}
947
+ signerId={signerId}
948
+ signerType="email"
949
+ onSuccess={(result) => console.log('Sent! Signature:', result.data.signature)}
950
+ onError={(err) => console.error(err.message)}
951
+ />
952
+ ```
681
953
 
682
- ```tsx
683
- import { SignerList } from 'cilantro-react'
954
+ **Props:**
684
955
 
685
- function SignersPage() {
686
- const walletId = 'your-wallet-id'
956
+ | Prop | Type | Default | Description |
957
+ |------|------|---------|-------------|
958
+ | `walletId` | `string` | — | Wallet to send from |
959
+ | `signerId` | `string` | — | Signer ID to authorize transaction |
960
+ | `signerType` | `'email' \| 'phone' \| 'passkey' \| 'external'` | — | Signer type |
961
+ | `onSuccess` | `(result: SubmitTransactionResult) => void` | — | Called after successful send |
962
+ | `onError` | `(error: Error) => void` | — | Called on error |
963
+ | `className` | `string` | — | Form class |
687
964
 
688
- return (
689
- <SignerList
690
- walletId={walletId}
691
- onSignerAdded={() => console.log('Signer added')}
692
- />
693
- )
694
- }
695
- ```
965
+ ---
966
+
967
+ #### SendSPLForm
696
968
 
697
- AddSignerForm can also be used standalone (e.g. inline or in your own dialog):
969
+ Form to send SPL tokens.
698
970
 
699
971
  ```tsx
700
- import { AddSignerForm } from 'cilantro-react'
972
+ import { SendSPLForm } from 'cilantro-react';
701
973
 
702
- <AddSignerForm
974
+ <SendSPLForm
703
975
  walletId={walletId}
704
- open={dialogOpen}
705
- onOpenChange={setDialogOpen}
706
- onSuccess={() => { refreshSigners(); setDialogOpen(false) }}
707
- asDialog={true}
976
+ signerId={signerId}
977
+ signerType="email"
978
+ mintAddress="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" // USDC
979
+ onSuccess={(result) => console.log('Sent! Signature:', result.data.signature)}
980
+ onError={(err) => console.error(err.message)}
708
981
  />
709
982
  ```
710
983
 
711
- ### Delegated keys
984
+ **Props:**
712
985
 
713
- Select a delegated key for a wallet:
986
+ | Prop | Type | Default | Description |
987
+ |------|------|---------|-------------|
988
+ | `walletId` | `string` | — | Wallet to send from |
989
+ | `signerId` | `string` | — | Signer ID |
990
+ | `signerType` | `SignerType` | — | Signer type |
991
+ | `mintAddress` | `string` | — | Pre-fill token mint address |
992
+ | `onSuccess` | `(result: SubmitTransactionResult) => void` | — | Called after successful send |
993
+ | `onError` | `(error: Error) => void` | — | Called on error |
994
+ | `className` | `string` | — | Form class |
714
995
 
715
- ```tsx
716
- import { DelegatedKeySelector } from 'cilantro-react'
996
+ ---
717
997
 
718
- function DelegatedKeys() {
719
- const [keyId, setKeyId] = useState<string>('')
998
+ #### TransactionStatus
720
999
 
721
- return (
722
- <DelegatedKeySelector
723
- walletId={walletId}
724
- value={keyId}
725
- onChange={(id, key) => setKeyId(id)}
726
- placeholder="Select a delegated key"
727
- filterActive={true}
728
- />
729
- )
730
- }
731
- ```
1000
+ Display transaction status with explorer link.
732
1001
 
733
- Use `children` for full control over the list UI.
1002
+ ```tsx
1003
+ import { TransactionStatus } from 'cilantro-react';
734
1004
 
735
- ### Message signing
1005
+ <TransactionStatus
1006
+ signature="5wHu1qwD7q9..."
1007
+ cluster="mainnet-beta"
1008
+ />
1009
+ ```
736
1010
 
737
- Default UI with message textarea, sign button, and wallet/signer context:
1011
+ **Props:**
738
1012
 
739
- ```tsx
740
- import { MessageSigningForm } from 'cilantro-react'
1013
+ | Prop | Type | Default | Description |
1014
+ |------|------|---------|-------------|
1015
+ | `signature` | `string` | — | Transaction signature |
1016
+ | `cluster` | `'mainnet-beta' \| 'devnet' \| 'testnet'` | `'mainnet-beta'` | Solana cluster |
1017
+ | `className` | `string` | — | Element class |
741
1018
 
742
- function SignMessage() {
743
- return (
744
- <MessageSigningForm
745
- showContext={true}
746
- showCharCount={true}
747
- />
748
- )
749
- }
750
- ```
1019
+ ---
751
1020
 
752
- Headless: use `children` to get `messageText`, `setMessageText`, `handleSign`, `signResultState`, `isSigning`, `reset`:
1021
+ #### TransactionHistory
1022
+
1023
+ Paginated list of recent transactions.
753
1024
 
754
1025
  ```tsx
755
- <MessageSigningForm>
756
- {({ messageText, setMessageText, handleSign, signResultState, isSigning, reset }) => (
757
- <div>
758
- <textarea value={messageText} onChange={e => setMessageText(e.target.value)} />
759
- <button onClick={handleSign} disabled={isSigning}>Sign</button>
760
- {signResultState.status !== 'idle' && <p>{signResultState.message}</p>}
761
- <button onClick={reset}>Clear</button>
762
- </div>
763
- )}
764
- </MessageSigningForm>
1026
+ import { TransactionHistory } from 'cilantro-react';
1027
+
1028
+ <TransactionHistory walletId={walletId} pageSize={10} />
765
1029
  ```
766
1030
 
767
- ### Transaction signing
1031
+ **Props:**
768
1032
 
769
- Build a transaction in your app, then call `signTransaction(tx)` to sign only, or `signAndSendTransaction(tx)` to sign and send. **For wallet-adapter or passkey sign-and-send, pass `connection`** from your Solana config (e.g. `@solana/web3.js`):
1033
+ | Prop | Type | Default | Description |
1034
+ |------|------|---------|-------------|
1035
+ | `walletId` | `string` | — | Wallet ID |
1036
+ | `pageSize` | `number` | `10` | Transactions per page |
1037
+ | `className` | `string` | — | Element class |
770
1038
 
771
- ```tsx
772
- import { Connection } from '@solana/web3.js'
773
- import { TransactionSigningForm } from 'cilantro-react'
1039
+ ---
774
1040
 
775
- function SendTx() {
776
- const connection = new Connection('https://api.devnet.solana.com')
1041
+ ### Layout Components
777
1042
 
778
- return (
779
- <TransactionSigningForm
780
- connection={connection}
781
- showContext={true}
782
- >
783
- {({ signTransaction, signAndSendTransaction, transactionResultState, isSigningTransaction, isSendingTransaction, reset }) => (
784
- <div>
785
- <button
786
- onClick={() => {
787
- const tx = buildMyTransaction() // your code
788
- signAndSendTransaction(tx)
789
- }}
790
- disabled={isSendingTransaction}
791
- >
792
- {isSendingTransaction ? 'Sending...' : 'Sign and send'}
793
- </button>
794
- {transactionResultState.status !== 'idle' && (
795
- <p>{transactionResultState.message}</p>
796
- )}
797
- <button onClick={reset}>Reset status</button>
798
- </div>
799
- )}
800
- </TransactionSigningForm>
801
- )
802
- }
803
- ```
1043
+ #### CilantroConnect
804
1044
 
805
- If you use passkey or wallet-adapter for sign-and-send, **connection is required**; otherwise the library will throw. The library does not create a Solana connection—you pass it in.
1045
+ All-in-one connect component combining wallet, signers, and auth.
806
1046
 
807
- ### Full flow example
1047
+ ```tsx
1048
+ import { CilantroConnect } from 'cilantro-react';
808
1049
 
809
- Typical app flow:
1050
+ <CilantroConnect
1051
+ onConnect={(wallet, signers) => console.log('Connected:', wallet, signers)}
1052
+ onDisconnect={() => console.log('Disconnected')}
1053
+ />
1054
+ ```
810
1055
 
811
- 1. Wrap app with `CilantroProvider` (see [Setup](#setup)).
812
- 2. **Auth:** Use `AuthForm` on a login page, or `AuthGuard` around protected routes with `LoginForm` as fallback.
813
- 3. **Wallets:** Use `WalletSelector` so the user picks a wallet; get `selectedWallet` from `useWallets()`.
814
- 4. **Signers:** Use `SignerSelector` with `useSigners({ walletId })` and `useSignerSelection`, or use `SignerList` to list + add signers.
815
- 5. **Message signing:** Use `MessageSigningForm` (optionally with `showContext` and `showCharCount`).
816
- 6. **Transaction signing:** Use `TransactionSigningForm` with your `connection` and build the transaction in your app; call `signTransaction(tx)` or `signAndSendTransaction(tx)` from the render props.
1056
+ **Props:**
1057
+
1058
+ | Prop | Type | Default | Description |
1059
+ |------|------|---------|-------------|
1060
+ | `onConnect` | `(wallet: WalletInfo, signers: SignerInfo[]) => void` | — | Called when connected |
1061
+ | `onDisconnect` | `() => void` | | Called when disconnected |
1062
+ | `className` | `string` | — | Element class |
817
1063
 
818
1064
  ---
819
1065
 
820
- ## Customization
1066
+ #### LoadingOverlay
821
1067
 
822
- - **className** All components accept `className` on the root element. Apply your own Tailwind or CSS.
823
- - **classNames** – Many components accept a `classNames` object to style inner parts (e.g. `root`, `trigger`, `input`, `button`, `label`). See TypeScript types: `WalletSelectorClassNames`, `SignerSelectorClassNames`, `MessageSigningFormClassNames`, `TransactionSigningFormClassNames`, `LoginFormClassNames`, `AuthFormClassNames`, etc.
824
- - **Headless** – Where supported, use `children` render props or `renderList` / `renderTrigger` to render the UI yourself and still use the component’s logic (e.g. `WalletSelector`, `SignerSelector`, `MessageSigningForm`, `TransactionSigningForm`, `SignerList`, `DelegatedKeySelector`).
1068
+ Overlay with loading spinner for async operations.
825
1069
 
826
- ---
1070
+ ```tsx
1071
+ import { LoadingOverlay } from 'cilantro-react';
1072
+
1073
+ <LoadingOverlay isLoading={isPending} message="Sending transaction...">
1074
+ <MyContent />
1075
+ </LoadingOverlay>
1076
+ ```
827
1077
 
828
- ## Core types and utilities
1078
+ **Props:**
829
1079
 
830
- - **ActionState** `{ status: 'idle' | 'loading' | 'success' | 'error', message?: string, detail?: unknown }`. Returned by signing hooks and used in signing form result UI. See [Types (detailed)](#types-detailed) for full shape.
831
- - **extractErrorMessage(error: unknown): string** – Normalizes unknown errors to a string. Handles `Error`, `{ message }`, `{ response: { data } }`, etc. Use when catching SDK or API errors to show a consistent message.
832
- - **extractResponseData\<T\>(response: unknown): T | null** – Extracts the data payload from SDK API responses. Handles `{ data }`, `{ success, data }`, and signer list shapes. Use when you need to unwrap the response shape (e.g. after calling cilantro-sdk).
833
- - **SignAndSendConnection** – Type for the `connection` prop when using sign-and-send. Your `Connection` from `@solana/web3.js` is compatible. Must provide `getLatestBlockhash`, `confirmTransaction`, and (for wallet-adapter send) `sendRawTransaction`.
1080
+ | Prop | Type | Default | Description |
1081
+ |------|------|---------|-------------|
1082
+ | `isLoading` | `boolean` | | Show overlay when true |
1083
+ | `message` | `string` | | Loading message |
1084
+ | `children` | `ReactNode` | — | Content to wrap |
1085
+ | `className` | `string` | — | Overlay class |
834
1086
 
835
1087
  ---
836
1088
 
837
- ## Advanced / low-level
1089
+ #### ErrorBoundary
1090
+
1091
+ Error boundary with retry functionality.
838
1092
 
839
- **Signer signing (without React hooks):** For advanced usage you can call the core signer APIs directly:
1093
+ ```tsx
1094
+ import { ErrorBoundary } from 'cilantro-react';
840
1095
 
841
- - `signMessageWithSigner(walletId, signer, message)` – Sign a message with a given signer.
842
- - `signTransactionWithSigner(walletId, signer, transactionBuffer)` – Sign a transaction (no send).
843
- - `signAndSendTransactionWithSigner(walletId, signer, transaction, connection?)` – Sign and send; **connection is required for passkey**.
844
- - `getWalletData(walletId)`, `getSignerPublicKey(...)` – Helpers for wallet/signer data.
845
- - `SIGNER_TYPES`, `SignerType` – Signer type constants.
1096
+ <ErrorBoundary
1097
+ fallback={<div>Something went wrong</div>}
1098
+ onRetry={() => window.location.reload()}
1099
+ >
1100
+ <App />
1101
+ </ErrorBoundary>
1102
+ ```
846
1103
 
847
- See TypeScript types and [cilantro-sdk](https://www.npmjs.com/package/cilantro-sdk) for full behavior.
1104
+ **Props:**
848
1105
 
849
- **Connection:** The library never creates a Solana RPC connection. Your app creates it (e.g. `new Connection(rpcUrl)`) and passes it into `useTransactionSigning` or `TransactionSigningForm` when using wallet-adapter or passkey sign-and-send.
1106
+ | Prop | Type | Default | Description |
1107
+ |------|------|---------|-------------|
1108
+ | `fallback` | `ReactNode` | — | Content to show on error |
1109
+ | `onRetry` | `() => void` | — | Retry callback |
1110
+ | `children` | `ReactNode` | — | Content to wrap |
850
1111
 
851
1112
  ---
852
1113
 
853
1114
  ## Theming
854
1115
 
855
- Components use Shadcn-style class names and CSS variables (e.g. `border-input`, `bg-primary`, `text-muted-foreground`). Ensure your app has **Tailwind CSS** so utility classes work; the library does not bundle Tailwind.
1116
+ Components use Shadcn-style class names and CSS variables. Ensure your app has **Tailwind CSS** configured.
856
1117
 
857
- ### Built-in theme (ThemeProvider)
858
-
859
- You can opt into a built-in light/dark theme without bringing your own Shadcn setup. Wrap your app (or just the Cilantro UI) with `ThemeProvider`:
1118
+ ### Built-in Theme (ThemeProvider)
860
1119
 
861
1120
  ```tsx
862
- import { ThemeProvider } from 'cilantro-react'
863
-
864
- <ThemeProvider theme="system" defaultTheme="dark" className="min-h-screen">
865
- <CilantroProvider platformApiKey="...">
866
- {children}
1121
+ import { ThemeProvider } from 'cilantro-react';
1122
+
1123
+ <ThemeProvider
1124
+ theme="system"
1125
+ defaultTheme="dark"
1126
+ injectStyles={true}
1127
+ className="min-h-screen"
1128
+ >
1129
+ <CilantroProvider apiKey="...">
1130
+ <App />
867
1131
  </CilantroProvider>
868
1132
  </ThemeProvider>
869
1133
  ```
870
1134
 
871
- - **theme** – `"light"` | `"dark"` | `"system"`. `"system"` follows `prefers-color-scheme`.
872
- - **defaultTheme** – Initial theme when using `"system"` (default: `"dark"`).
873
- - **storageKey** – Optional localStorage key to persist the resolved theme when using `"system"`.
874
- - **injectStyles** – When `true` (default), the provider injects a minimal set of CSS variables (`--background`, `--foreground`, `--primary`, `--muted`, `--border`, `--input`, `--ring`, etc.) for light and `[data-theme="dark"]`. You can set `injectStyles={false}` and import the theme file yourself: `import 'cilantro-react/themes/default.css'`.
1135
+ **Props:**
875
1136
 
876
- Tailwind is still required so utility classes apply; ThemeProvider only sets the variables.
1137
+ | Prop | Type | Default | Description |
1138
+ |------|------|---------|-------------|
1139
+ | `theme` | `'light' \| 'dark' \| 'system'` | `'system'` | Theme mode |
1140
+ | `defaultTheme` | `'light' \| 'dark'` | `'dark'` | Default when using system |
1141
+ | `storageKey` | `string` | — | localStorage key for persistence |
1142
+ | `injectStyles` | `boolean` | `true` | Inject CSS variables |
1143
+ | `className` | `string` | — | Wrapper class |
877
1144
 
878
- For full Shadcn theming, see [Theming - shadcn/ui](https://ui.shadcn.com/docs/theming).
1145
+ ### Import Theme CSS Directly
1146
+
1147
+ ```ts
1148
+ import 'cilantro-react/themes/default.css';
1149
+ ```
879
1150
 
880
1151
  ---
881
1152
 
882
- ## Accessibility
1153
+ ## Advanced Usage
883
1154
 
884
- Components are built with screen readers and keyboard use in mind:
1155
+ ### Low-Level Signer Signing
885
1156
 
886
- - **Live regions** – Signing result messages (MessageSigningForm, TransactionSigningForm) use `role="status"` and `aria-live="polite"` (or `assertive` for errors). Loading states use `aria-busy`.
887
- - **Form labels** – Auth, login, register, message signing, and add-signer forms use visible labels with correct `id` / `htmlFor`. Error blocks use `role="alert"` so errors are announced immediately.
888
- - **Selects and dialogs** – WalletSelector and DelegatedKeySelector triggers have `aria-label`. Radix Dialog handles focus trap and restore in AddSignerForm.
889
- - **Loading** – WalletSelector, SignerSelector, SignerList, DelegatedKeySelector, and AuthGuard use `aria-busy` and `aria-live="polite"` when loading so screen readers announce loading state.
1157
+ For advanced use cases without React hooks:
890
1158
 
891
- Buttons use `focus-visible:ring-2` for focus indication; icon-only or custom buttons can receive `aria-label` via standard HTML attributes.
1159
+ ```tsx
1160
+ import {
1161
+ signMessageWithSigner,
1162
+ signTransactionWithSigner,
1163
+ signAndSendTransactionWithSigner,
1164
+ getWalletData,
1165
+ getSignerPublicKey,
1166
+ SIGNER_TYPES,
1167
+ } from 'cilantro-react';
1168
+
1169
+ // Sign a message
1170
+ const result = await signMessageWithSigner(walletId, signer, 'Hello, Solana!');
1171
+
1172
+ // Sign and send a transaction
1173
+ const txResult = await signAndSendTransactionWithSigner(walletId, signer, transaction, connection);
1174
+ ```
892
1175
 
893
- ---
1176
+ ### Solana Connection Adapter
1177
+
1178
+ For `@solana/web3.js` connection compatibility:
1179
+
1180
+ ```tsx
1181
+ import { Connection } from '@solana/web3.js';
1182
+ import { adaptSolanaConnection } from 'cilantro-react';
894
1183
 
895
- ## Mobile
1184
+ const connection = new Connection('https://api.mainnet-beta.solana.com');
1185
+ const adapted = adaptSolanaConnection(connection);
1186
+ ```
896
1187
 
897
- Key actions use touch-friendly targets and dialogs are responsive:
1188
+ ### Utilities
898
1189
 
899
- - **Touch targets** The Button component has a `touch` size (`min-h-[44px] min-w-[44px]`) used on primary actions: AuthForm/LoginForm/RegisterForm submit, MessageSigningForm sign button, and SignerList “Add signer”. Use `size="touch"` on your own primary buttons when needed.
900
- - **Responsive layout** – Auth forms use `w-full max-w-sm`. MessageSigningForm and TransactionSigningForm use responsive button groups (`flex-col` on small screens). Result blocks use `overflow-auto` on `<pre>` so content doesn’t overflow.
901
- - **Dialog** – DialogContent has `max-h-[100dvh] sm:max-h-[90vh]` and `overflow-y-auto` so dialogs (e.g. AddSignerForm) are scrollable on small screens. You can add `className="rounded-none sm:rounded-lg"` or full-height classes for a more mobile-friendly dialog.
1190
+ | Function | Description |
1191
+ |----------|-------------|
1192
+ | `extractErrorMessage(error)` | Normalize unknown errors to string |
1193
+ | `extractResponseData(response)` | Unwrap SDK response shapes |
1194
+ | `normalizeWallet(dto)` | Normalize wallet data from SDK |
1195
+ | `normalizeSigner(dto)` | Normalize signer data from SDK |
1196
+ | `isJwtExpired(jwt)` | Check if JWT is expired |
1197
+ | `isAuthError(error)` | Check if error is an auth error |
902
1198
 
903
1199
  ---
904
1200
 
905
- ## Loading states
1201
+ ## Types
906
1202
 
907
- Loading is shown with skeleton placeholders where it improves perceived performance:
1203
+ Key exported types:
908
1204
 
909
- - **Skeleton** – A `Skeleton` UI primitive is available (`import { Skeleton } from 'cilantro-react'`). Components use it when loading: WalletSelector and DelegatedKeySelector show a skeleton trigger; SignerSelector and SignerList show 2–3 skeleton rows.
910
- - **useSkeleton** – WalletSelector, SignerSelector, SignerList, and DelegatedKeySelector accept an optional **useSkeleton** prop (default: `true`). Set `useSkeleton={false}` to show plain “Loading…” text instead.
911
- - **classNames** – These components support `classNames.skeleton` and `classNames.loading` so you can style the skeleton or the loading container.
912
- - **AuthGuard** – Optional skeleton while checking auth: set **useSkeleton={true}** to show a small skeleton card instead of “Loading…” text.
1205
+ ```ts
1206
+ import type {
1207
+ // Config
1208
+ CilantroConfig,
1209
+ CilantroContextValue,
1210
+ DeviceKeyStorage,
1211
+
1212
+ // User/Auth
1213
+ User,
1214
+ UseAuthResult,
1215
+
1216
+ // Wallet
1217
+ WalletData,
1218
+ UseWalletResult,
1219
+ WalletControllerCreateResult,
1220
+
1221
+ // Signer
1222
+ SignerData,
1223
+ SignerInfo,
1224
+ SignerType,
1225
+ UseSignersResult,
1226
+
1227
+ // Transaction
1228
+ SubmitTransactionResult,
1229
+ UseSendTransactionResult,
1230
+
1231
+ // External Wallet
1232
+ SolanaWallet,
1233
+ UseExternalWalletResult,
1234
+
1235
+ // Passkey
1236
+ UsePasskeyResult,
1237
+
1238
+ // UI
1239
+ ActionState,
1240
+ } from 'cilantro-react';
1241
+ ```
913
1242
 
914
1243
  ---
915
1244
 
916
- ## Low-level SDK
1245
+ ## License
917
1246
 
918
- For direct cilantro-sdk usage (auth, storage, wallets, signers, delegated keys, message/transaction signing), see the [cilantro-sdk](https://www.npmjs.com/package/cilantro-sdk) package.
1247
+ MIT