cilantro-react 0.1.4 → 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,800 +1,1247 @@
1
1
  # cilantro-react
2
2
 
3
- React SDK/UI for [Cilantro Smart Wallet](https://www.npmjs.com/package/cilantro-sdk). Provides providers, hooks, and UI components so you can add auth, wallets, signers, and signing flows without wiring the low-level SDK directly. Components use [Shadcn/UI](https://ui.shadcn.com)-style primitives and are customizable via `className` and `classNames`.
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
- npm install cilantro-react cilantro-sdk react
64
+ npm install cilantro-react cilantro-sdk
65
+ # or
66
+ yarn add cilantro-react cilantro-sdk
67
+ # or
68
+ pnpm add cilantro-react cilantro-sdk
9
69
  ```
10
70
 
11
- **Peer dependencies:** `react` (^18.0.0 || ^19.0.0), `cilantro-sdk` (^0.0.40). For transaction signing and Solana helpers, optionally install `@solana/web3.js` (^1.98.0).
71
+ **Requirements:**
12
72
 
13
- **Styling:** Components use Tailwind CSS and Shadcn-compatible class names. Ensure your app has [Tailwind CSS](https://tailwindcss.com) configured; for theming, use your own Shadcn theme or CSS variables so styles apply correctly.
73
+ - React 18+
74
+ - `cilantro-sdk` (peer dependency)
75
+ - [Tailwind CSS](https://tailwindcss.com) for component styling (components use Tailwind-compatible class names)
14
76
 
15
- ## Polyfills
77
+ ### Polyfills (Browser)
16
78
 
17
- Import the SDK polyfills once before any SDK usage (e.g. in your app entry or root layout):
79
+ Import polyfills at the very top of your entry file (before any other imports):
18
80
 
19
81
  ```ts
20
- import 'cilantro-sdk/polyfills'
82
+ import 'cilantro-sdk/polyfills';
21
83
  ```
22
84
 
23
- ## Setup
85
+ ---
24
86
 
25
- Wrap your app with `CilantroProvider` and pass your platform API key:
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
- export default function RootLayout({ children }: { children: React.ReactNode }) {
93
+ function App() {
31
94
  return (
32
- <CilantroProvider platformApiKey="your-platform-api-key">
33
- {children}
95
+ <CilantroProvider
96
+ apiKey={import.meta.env.VITE_API_KEY}
97
+ baseURL="https://api.cilantro.gg"
98
+ >
99
+ <AuthGuard>
100
+ <Dashboard />
101
+ </AuthGuard>
34
102
  </CilantroProvider>
35
- )
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
+ );
36
117
  }
37
118
  ```
38
119
 
39
- **CilantroProvider props:**
120
+ ---
40
121
 
41
- | Prop | Type | Description |
42
- |------|------|-------------|
43
- | `platformApiKey` | `string` | **Required.** Your Cilantro platform API key. |
44
- | `apiUrl` | `string` | Optional API base URL. |
45
- | `jwtStorageKey` | `string` | localStorage key for JWT (default: `cilantro_jwt`). |
46
- | `walletStorageKey` | `string` | localStorage key for selected wallet id (default: `cilantro_selected_wallet_id`). |
47
- | `onLoginSuccess` | `() => void` | Callback after successful login. |
48
- | `onLogout` | `() => void` | Callback after logout. |
49
- | `onRegisterSuccess` | `() => void` | Callback after successful registration. |
122
+ ## CilantroProvider
50
123
 
51
- **Connection:** The library does not create a Solana RPC connection. For transaction sign-and-send (wallet-adapter or passkey), pass your own `connection` from `@solana/web3.js` (or your Solana config) into `useTransactionSigning` / `TransactionSigningForm`. See [Transaction signing](#transaction-signing) below.
124
+ The root provider that initializes configuration, storage, authentication, and wallet state.
52
125
 
53
- ---
126
+ ```tsx
127
+ import { CilantroProvider, createIndexedDBAdapter } from 'cilantro-react';
54
128
 
55
- ## API reference
129
+ const storage = createIndexedDBAdapter();
56
130
 
57
- ### Providers
131
+ <CilantroProvider
132
+ apiKey="your-platform-api-key"
133
+ baseURL="https://api.cilantro.gg"
134
+ storageAdapter={storage}
135
+ >
136
+ <App />
137
+ </CilantroProvider>
138
+ ```
58
139
 
59
- - **CilantroProvider** – Root provider: initializes storage, auth, and wallet context. Requires `platformApiKey`; optional `apiUrl`, storage keys, and callbacks. Use this unless you only need auth.
60
- - **CilantroAuthProvider** – Auth only (token, user, login, register, logout). Use when you don’t need the full provider stack (e.g. auth-only pages).
61
- - **WalletProvider** – Wallet list and selection. Depends on auth; usually used inside `CilantroProvider`.
62
- - **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).
140
+ ### Props
63
141
 
64
- **CilantroAuthProvider props:** `platformApiKey` (required), `apiUrl`, `jwtStorageKey`, `onLoginSuccess`, `onLogout`, `onRegisterSuccess`.
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 |
65
148
 
66
- **WalletProvider props:** `storageKey` (default: `cilantro_selected_wallet_id`).
149
+ *Either `apiKey` or JWT (obtained via login) is required for authenticated requests.
67
150
 
68
- ---
151
+ ### Storage Adapters
69
152
 
70
- ### Custom hooks (detailed)
153
+ For email and phone signers, device keys must be stored. Choose an adapter:
71
154
 
72
- All hooks must be used within the appropriate provider (see each hook below).
155
+ | Adapter | Use Case |
156
+ |---------|----------|
157
+ | `createIndexedDBAdapter()` | **Production** – persistent, large capacity |
158
+ | `createLocalStorageAdapter()` | Development – simple, limited capacity |
159
+ | `createMemoryAdapter()` | Testing – not persisted |
73
160
 
74
- #### useCilantroAuth
161
+ ```tsx
162
+ import { createIndexedDBAdapter, createLocalStorageAdapter, createMemoryAdapter } from 'cilantro-react';
75
163
 
76
- Auth state and actions. Use inside `CilantroAuthProvider` or `CilantroProvider`.
164
+ // Production
165
+ const storage = createIndexedDBAdapter();
77
166
 
78
- **Returns:** `CilantroAuthContextType`
167
+ // Development
168
+ const storage = createLocalStorageAdapter();
79
169
 
80
- | Property | Type | Description |
81
- |----------|------|-------------|
82
- | `token` | `string \| null` | JWT token (null when not authenticated). |
83
- | `user` | `User \| null` | User object: `{ username?, email?, userType? }`. |
84
- | `isAuthenticated` | `boolean` | True when `token` is set. |
85
- | `isLoading` | `boolean` | True while restoring session from storage. |
86
- | `login` | `(usernameOrEmail: string, password: string) => Promise<void>` | Log in; throws on error. |
87
- | `register` | `(username: string, email: string, password: string, isActive?: boolean) => Promise<void>` | Register and log in; throws on error. |
88
- | `logout` | `() => void` | Clear token and user; calls `onLogout` if provided. |
170
+ // Testing
171
+ const storage = createMemoryAdapter();
172
+ ```
89
173
 
90
174
  ---
91
175
 
92
- #### useWallets
176
+ ## Hooks
93
177
 
94
- Wallet list and selection. Use inside `CilantroProvider` (which includes `WalletProvider`).
178
+ ### useAuth
95
179
 
96
- **Returns:** `WalletContextType`
97
-
98
- | Property | Type | Description |
99
- |----------|------|-------------|
100
- | `wallets` | `WalletData[]` | List of wallets for the authenticated user. |
101
- | `selectedWallet` | `WalletData \| null` | Currently selected wallet (persisted in localStorage). |
102
- | `selectWallet` | `(walletId: string) => void` | Set the selected wallet by id. |
103
- | `refreshWallets` | `() => Promise<void>` | Reload wallets from the API. |
104
- | `isLoading` | `boolean` | True while loading wallets. |
180
+ Authentication state and actions.
105
181
 
106
- **WalletData:** `{ id, walletId, walletName?, address?, walletAddress?, chain?, active?, ... }`.
182
+ ```tsx
183
+ import { useAuth } from 'cilantro-react';
107
184
 
108
- ---
185
+ function MyComponent() {
186
+ const { user, jwt, isLoading, isAuthenticated, login, logout } = useAuth();
109
187
 
110
- #### useSelectedWallet
188
+ const handleLogin = async () => {
189
+ await login({ usernameOrEmail: 'user@example.com', password: 'password123' });
190
+ };
111
191
 
112
- Convenience hook when you only need the currently selected wallet (and loading/refresh), not the full list. Use inside `CilantroProvider`.
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
+ ```
113
207
 
114
- **Returns:** `UseSelectedWalletResult`
208
+ **Returns: `UseAuthResult`**
115
209
 
116
210
  | Property | Type | Description |
117
211
  |----------|------|-------------|
118
- | `selectedWallet` | `WalletData \| null` | Currently selected wallet. |
119
- | `isLoading` | `boolean` | True while loading wallets. |
120
- | `refreshWallets` | `() => Promise<void>` | Reload wallets from the API. |
121
-
122
- **Use when:** You need the current wallet without the full wallet list or `selectWallet`.
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 |
123
218
 
124
219
  ---
125
220
 
126
- #### useWalletAddress
221
+ ### useWallet
222
+
223
+ Wallet state and actions.
224
+
225
+ ```tsx
226
+ import { useWallet } from 'cilantro-react';
127
227
 
128
- Returns the address of the currently selected wallet (handles both `address` and `walletAddress` on WalletData). Use inside `CilantroProvider`.
228
+ function WalletManager() {
229
+ const { wallet, wallets, createWallet, selectWallet, isLoading } = useWallet();
129
230
 
130
- **Returns:** `string | null`
231
+ const handleCreate = async () => {
232
+ const result = await createWallet({ name: 'My New Wallet' });
233
+ console.log('Created:', result.data.id);
234
+ };
131
235
 
132
- **Use when:** You need the selected wallet's address for display or building transactions.
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
+ ```
252
+
253
+ **Returns: `UseWalletResult`**
254
+
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 |
133
262
 
134
263
  ---
135
264
 
136
- #### useSigners
265
+ ### useSigners
266
+
267
+ Signers for a specific wallet.
137
268
 
138
- Load signers for a wallet. Use inside `CilantroProvider` (auth required).
269
+ ```tsx
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
+ };
139
292
 
140
- **Options:** `UseSignersOptions`
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
+ }
311
+ ```
141
312
 
142
- | Option | Type | Description |
143
- |--------|------|-------------|
144
- | `walletId` | `string \| null \| undefined` | Wallet ID to load signers for. If omitted, you can pass it from `useWallets().selectedWallet?.id`. |
313
+ **Parameters:**
145
314
 
146
- **Returns:** `UseSignersResult`
315
+ - `walletId: string | null | { walletId?: string | null }` — Wallet ID to load signers for
316
+
317
+ **Returns: `UseSignersResult`**
147
318
 
148
319
  | Property | Type | Description |
149
320
  |----------|------|-------------|
150
- | `signers` | `SignerData[]` | List of signers for the wallet. |
151
- | `isLoading` | `boolean` | True while loading. |
152
- | `error` | `string \| null` | Error message if load failed. |
153
- | `refresh` | `() => Promise<void>` | Reload signers. |
154
-
155
- When `walletId` is null/undefined, `signers` is empty and `refresh` no-ops.
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 |
156
328
 
157
329
  ---
158
330
 
159
- #### useSignersForSelectedWallet
331
+ ### usePasskey
160
332
 
161
- Signers for the currently selected wallet (or a `walletId` override). Composes `useWallets` + `useSigners` so you don't pass `walletId` yourself. Use inside `CilantroProvider`.
333
+ Passkey (WebAuthn) registration and authentication.
162
334
 
163
- **Options:** `UseSignersForSelectedWalletOptions`
164
-
165
- | Option | Type | Description |
166
- |--------|------|-------------|
167
- | `walletId` | `string \| null \| undefined` | Override; when omitted, uses the selected wallet from context. |
335
+ ```tsx
336
+ import { usePasskey } from 'cilantro-react';
168
337
 
169
- **Returns:** Same as `useSigners` `UseSignersResult` (`signers`, `isLoading`, `error`, `refresh`).
338
+ function PasskeyManager({ walletId }: { walletId: string }) {
339
+ const { isSupported, register, authenticate, isLoading } = usePasskey(walletId);
170
340
 
171
- **Use when:** You need signers for the current wallet without calling `useWallets()` and `useSigners({ walletId })` in every component (e.g. SignerList-style UIs).
341
+ if (!isSupported) {
342
+ return <p>Passkeys not supported in this browser.</p>;
343
+ }
172
344
 
173
- ---
345
+ const handleRegister = async () => {
346
+ const result = await register();
347
+ console.log('Passkey registered:', result.signerId);
348
+ };
174
349
 
175
- #### useSignerSelection
350
+ const handleAuthenticate = async () => {
351
+ const result = await authenticate();
352
+ console.log('Authenticated with passkey');
353
+ };
176
354
 
177
- Wallet + signer selection state for signing flows. Uses `useWallets` and `useSigners` internally. Use inside `CilantroProvider`.
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
+ ```
178
367
 
179
- **Options:** `UseSignerSelectionOptions`
368
+ **Parameters:**
180
369
 
181
- | Option | Type | Description |
182
- |--------|------|-------------|
183
- | `walletId` | `string \| null \| undefined` | Override wallet ID; if not set, uses `useWallets().selectedWallet?.id`. |
184
- | `signingMethod` | `'wallet-adapter' \| 'sdk-signer' \| null` | Current signing method (default: `'sdk-signer'`). When `'wallet-adapter'`, signer selection is ignored. |
370
+ - `walletId: string | undefined` Wallet ID to register passkey for
185
371
 
186
- **Returns:** `UseSignerSelectionResult`
372
+ **Returns: `UsePasskeyResult`**
187
373
 
188
374
  | Property | Type | Description |
189
375
  |----------|------|-------------|
190
- | `selectedWalletId` | `string` | Effective wallet ID (override or from context). |
191
- | `setSelectedWalletId` | `(id: string) => void` | Set wallet ID (when not using context selection). |
192
- | `availableSigners` | `SignerData[]` | Signers for the selected wallet (from `useSigners`). |
193
- | `selectedSigner` | `SignerData \| null` | Currently selected signer. |
194
- | `setSelectedSigner` | `(signer: SignerData \| null) => void` | Set selected signer. |
195
- | `isLoadingSigners` | `boolean` | True while signers are loading. |
196
- | `reset` | `() => void` | Clear `selectedWalletId` and `selectedSigner`. |
197
-
198
- **SigningMethod:** `'wallet-adapter'` = use wallet adapter for signing; `'sdk-signer'` = use a Cilantro signer (email, phone, passkey, external, etc.).
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 |
199
380
 
200
381
  ---
201
382
 
202
- #### useDelegatedKeys
383
+ ### useExternalWallet
384
+
385
+ Connect to external Solana wallets (Phantom, Solflare, etc.).
203
386
 
204
- Load delegated keys for a wallet (same data as DelegatedKeySelector). Use for custom delegated-key UI or logic. Use inside `CilantroProvider` (auth required).
387
+ ```tsx
388
+ import { useExternalWallet } from 'cilantro-react';
205
389
 
206
- **Options:** `UseDelegatedKeysOptions`
390
+ function ExternalWalletConnect() {
391
+ const { wallets, connectedWallet, connect, disconnect } = useExternalWallet();
207
392
 
208
- | Option | Type | Description |
209
- |--------|------|-------------|
210
- | `walletId` | `string \| null \| undefined` | Wallet ID to load delegated keys for. When null/undefined, keys are empty. |
211
- | `filterActive` | `boolean` | When true (default), filter to active and non-expired keys only. |
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
+ ```
212
414
 
213
- **Returns:** `UseDelegatedKeysResult`
415
+ **Returns: `UseExternalWalletResult`**
214
416
 
215
417
  | Property | Type | Description |
216
418
  |----------|------|-------------|
217
- | `keys` | `DelegatedKeyData[]` | List of delegated keys. |
218
- | `isLoading` | `boolean` | True while loading. |
219
- | `error` | `string \| null` | Error message if load failed. |
220
- | `refresh` | `() => Promise<void>` | Reload delegated keys. |
221
-
222
- **Use when:** You need the list of delegated keys in custom UI or logic without using the DelegatedKeySelector component.
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 |
223
423
 
224
424
  ---
225
425
 
226
- #### useCanSign
426
+ ### useSendTransaction
227
427
 
228
- Derived "is the user ready to sign?" for disabling sign buttons or showing prompts. Use inside `CilantroProvider`.
428
+ Send SOL and SPL tokens.
229
429
 
230
- **Options:** `UseCanSignOptions`
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
+ };
231
452
 
232
- | Option | Type | Description |
233
- |--------|------|-------------|
234
- | `signingMethod` | `SigningMethod \| null` | Override; default `'sdk-signer'`. |
235
- | `requireSigner` | `boolean` | When true (default), require a selected signer for sdk-signer. |
236
- | `walletAdapterConnected` | `boolean` | When using wallet-adapter, pass true when the adapter is connected so `canSign*` reflects it. |
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
+ ```
466
+
467
+ **Parameters:**
237
468
 
238
- **Returns:** `UseCanSignResult`
469
+ - `walletId: string | undefined` — Wallet to send from
470
+ - `signerId?: string` — Signer ID (optional)
471
+ - `signerType?: 'email' | 'phone' | 'passkey' | 'external'` — Signer type (optional)
472
+
473
+ **Returns: `UseSendTransactionResult`**
239
474
 
240
475
  | Property | Type | Description |
241
476
  |----------|------|-------------|
242
- | `hasToken` | `boolean` | True when the user has a valid token. |
243
- | `hasWallet` | `boolean` | True when a wallet is selected. |
244
- | `hasSigner` | `boolean` | True when a signer is selected (sdk-signer) or wallet-adapter is connected (when `walletAdapterConnected` is passed). |
245
- | `canSignMessage` | `boolean` | True when ready to sign a message. |
246
- | `canSignTransaction` | `boolean` | True when ready to sign/send a transaction (connection still required for send). |
247
-
248
- **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.
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 |
249
481
 
250
482
  ---
251
483
 
252
- #### useMessageSigning
484
+ ### useCilantroConfig
253
485
 
254
- Sign a message with the selected signer or wallet-adapter. Use inside `CilantroProvider`.
486
+ Access SDK configuration and storage adapter.
255
487
 
256
- **Options:** `UseMessageSigningOptions`
488
+ ```tsx
489
+ import { useCilantroConfig } from 'cilantro-react';
490
+
491
+ function ConfigDisplay() {
492
+ const { config, storage } = useCilantroConfig();
257
493
 
258
- | Option | Type | Description |
259
- |--------|------|-------------|
260
- | `token` | `string \| null` | JWT (usually from `useCilantroAuth().token`). |
261
- | `signingMethod` | `SigningMethod \| null` | `'sdk-signer'` or `'wallet-adapter'`. |
262
- | `selectedSigner` | `SignerData \| null` | Signer to use when `signingMethod === 'sdk-signer'`. |
263
- | `selectedWalletId` | `string` | Wallet ID when using SDK signer. |
264
- | `walletAdapterSignMessage` | `(message: Uint8Array) => Promise<Uint8Array>` | Optional; required when `signingMethod === 'wallet-adapter'`. |
265
- | `walletAdapterPublicKey` | `string \| null` | Optional; included in success detail when using wallet-adapter. |
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
+ ```
266
503
 
267
- **Returns:** `UseMessageSigningResult`
504
+ **Returns: `UseCilantroConfigResult`**
268
505
 
269
506
  | Property | Type | Description |
270
507
  |----------|------|-------------|
271
- | `messageText` | `string` | Current message (default: `"Hello, Solana!"`). |
272
- | `setMessageText` | `(text: string) => void` | Update message. |
273
- | `signResultState` | `ActionState` | `{ status, message?, detail? }` – idle, loading, success, or error. |
274
- | `isSigning` | `boolean` | True while signing. |
275
- | `handleSign` | `() => Promise<void>` | Sign the current message; updates `signResultState`. |
276
- | `reset` | `() => void` | Reset message to default and clear result state. |
508
+ | `config` | `{ apiKey?: string; baseURL?: string; jwt?: string }` | Current configuration |
509
+ | `storage` | `DeviceKeyStorage \| null` | Storage adapter instance |
277
510
 
278
511
  ---
279
512
 
280
- #### useTransactionSigning
513
+ ## Components
281
514
 
282
- Sign and/or send a transaction. Use inside `CilantroProvider`.
515
+ ### Auth Components
283
516
 
284
- **Options:** `UseTransactionSigningOptions`
517
+ #### LoginForm
285
518
 
286
- | Option | Type | Description |
287
- |--------|------|-------------|
288
- | `token` | `string \| null` | JWT (usually from `useCilantroAuth().token`). |
289
- | `signingMethod` | `SigningMethod \| null` | `'sdk-signer'` or `'wallet-adapter'`. |
290
- | `selectedSigner` | `SignerData \| null` | Signer when `signingMethod === 'sdk-signer'`. |
291
- | `selectedWalletId` | `string` | Wallet ID when using SDK signer. |
292
- | `walletAdapterSignTransaction` | `(transaction: Transaction) => Promise<Transaction>` | Optional; required for wallet-adapter signing. |
293
- | `walletAdapterPublicKey` | `string \| null` | Optional; required for wallet-adapter send. |
294
- | `connection` | `SignAndSendConnection \| null` | **Required for sign-and-send** when using wallet-adapter or passkey. Provide your Solana `Connection` (e.g. from `@solana/web3.js`). |
519
+ Username/email + password login form with loading and error states.
295
520
 
296
- **Returns:** `UseTransactionSigningResult`
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
+ ```
297
534
 
298
- | Property | Type | Description |
299
- |----------|------|-------------|
300
- | `transactionResultState` | `ActionState` | Result of last sign/send (idle, loading, success, error). |
301
- | `isSigningTransaction` | `boolean` | True while `signTransaction` is running. |
302
- | `isSendingTransaction` | `boolean` | True while `signAndSendTransaction` is running. |
303
- | `signTransaction` | `(transaction: Transaction) => Promise<void>` | Sign only (no send). Passkey signers cannot sign-only; use sign-and-send. |
304
- | `signAndSendTransaction` | `(transaction: Transaction) => Promise<void>` | Sign and send. Requires `connection` for wallet-adapter and passkey. |
305
- | `reset` | `() => void` | Set `transactionResultState` back to idle. |
535
+ **Props:**
306
536
 
307
- **Connection:** For passkey or wallet-adapter sign-and-send, you must pass a `connection` that has `getLatestBlockhash`, `sendRawTransaction`, and `confirmTransaction`. The library does not create a connection.
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 |
308
548
 
309
549
  ---
310
550
 
311
- ### Components (detailed)
551
+ #### LogoutButton
312
552
 
313
- All components accept `className`. Many accept a `classNames` object (see TypeScript types e.g. `WalletSelectorClassNames`, `MessageSigningFormClassNames`).
553
+ Logout button with optional confirmation dialog.
314
554
 
315
- #### Auth components
316
-
317
- | Component | Purpose | Key props |
318
- |-----------|---------|-----------|
319
- | **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). |
320
- | **LoginForm** | Login (username/email + password). | `onSuccess?`, `onError?(error: string)`, `submitLabel`, `title`, `description`, `renderSwitchToRegister? () => ReactNode`, `className`, `classNames?`. |
321
- | **RegisterForm** | Registration (username, email, password). | `onSuccess?`, `onError?(error: string)`, `submitLabel`, `title`, `description`, `isActive?` (default true), `renderSwitchToLogin? () => ReactNode`, `className`, `classNames?`. |
322
- | **AuthGuard** | Renders children when authenticated; shows fallback otherwise. | `children`, `fallback?: ReactNode` (default: `<LoginForm />`), `showFallback?` (default true), `className`, `classNames?` (root, fallback). |
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
+ ```
323
565
 
324
- #### Wallet and signer components
566
+ **Props:**
325
567
 
326
- | Component | Purpose | Key props |
327
- |-----------|---------|-----------|
328
- | **WalletSelector** | Wallet dropdown (Shadcn Select). | `value?`, `onWalletChange?(walletId, wallet)`, `placeholder`, `className`, `classNames?` (root, trigger, content, item). Headless: `children?`, `renderTrigger?`, `renderList?`. |
329
- | **SignerSelector** | List/select signers (buttons). | `selectedWalletId?`, `availableSigners`, `selectedSigner`, `onSignerSelect(signer)`, `isLoadingSigners?`, `className`, `classNames?` (root, list, item, message). Headless: `children?`, `renderList?`. |
330
- | **SignerList** | List signers + “Add signer” (opens AddSignerForm in dialog). | `walletId`, `onSignerAdded`, `className`, `classNames`. Headless: `children`. |
331
- | **AddSignerForm** | Add signer (email, phone, passkey, external wallet). | `walletId`, `open?`, `onOpenChange?`, `onSuccess?`, `onCancel?`, `onError?(error)`, `asDialog?` (default true), `className`, `classNames?`. |
332
- | **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). |
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 |
333
574
 
334
- #### UI primitives
575
+ ---
335
576
 
336
- - **Skeleton** – Shadcn-style skeleton placeholder (`rounded-md bg-muted animate-pulse`). Use for loading states or custom UI. Exported for headless use.
337
- - **ThemeProvider** – See [Themes](#built-in-theme-themeprovider).
577
+ #### AuthGuard
338
578
 
339
- #### Signing form components
579
+ Protect content behind authentication. Shows fallback (default: LoginForm) when not authenticated.
340
580
 
341
- | Component | Purpose | Key props |
342
- |-----------|---------|-----------|
343
- | **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). |
344
- | **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). |
581
+ ```tsx
582
+ import { AuthGuard, LoginForm } from 'cilantro-react';
345
583
 
346
- When token/wallet/signer/signingMethod are omitted, MessageSigningForm and TransactionSigningForm use `useCilantroAuth().token` and `useSignerSelection()` internally.
584
+ // Basic usage - shows LoginForm when not authenticated
585
+ <AuthGuard>
586
+ <ProtectedContent />
587
+ </AuthGuard>
347
588
 
348
- ### Types (detailed)
589
+ // Custom fallback
590
+ <AuthGuard fallback={<CustomLoginPage />}>
591
+ <ProtectedContent />
592
+ </AuthGuard>
349
593
 
350
- #### ActionState
594
+ // Redirect instead of showing fallback
595
+ <AuthGuard redirectTo="/login">
596
+ <ProtectedContent />
597
+ </AuthGuard>
351
598
 
352
- Used by signing hooks and forms for result state.
599
+ // Hide content when not authenticated (no fallback)
600
+ <AuthGuard showFallback={false}>
601
+ <ProtectedContent />
602
+ </AuthGuard>
353
603
 
354
- ```ts
355
- type ActionState<T = unknown> = {
356
- status: 'idle' | 'loading' | 'success' | 'error';
357
- message?: string;
358
- detail?: T;
359
- };
604
+ // Show skeleton while loading
605
+ <AuthGuard useSkeleton={true}>
606
+ <ProtectedContent />
607
+ </AuthGuard>
360
608
  ```
361
609
 
362
- - **idle** – No operation yet.
363
- - **loading** – Sign/send in progress.
364
- - **success** – Done; `message` and optional `detail` (e.g. signature, explorer URL).
365
- - **error** – Failed; `message` (and optional `detail`).
610
+ **Props:**
366
611
 
367
- #### User
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 |
368
621
 
369
- From `useCilantroAuth().user`:
622
+ ---
370
623
 
371
- ```ts
372
- interface User {
373
- username?: string;
374
- email?: string;
375
- userType?: string;
376
- }
377
- ```
624
+ ### Wallet Components
378
625
 
379
- #### WalletData
626
+ #### WalletSelector
380
627
 
381
- From `useWallets().wallets` / `selectedWallet`:
628
+ Dropdown to select from available wallets.
382
629
 
383
- ```ts
384
- interface WalletData {
385
- id: string;
386
- walletId: string;
387
- walletName?: string;
388
- address?: string;
389
- walletAddress?: string;
390
- chain?: string;
391
- active?: boolean;
392
- [key: string]: unknown;
393
- }
394
- ```
630
+ ```tsx
631
+ import { WalletSelector } from 'cilantro-react';
395
632
 
396
- #### SignerData
633
+ // Controlled
634
+ const [walletId, setWalletId] = useState('');
635
+ <WalletSelector value={walletId} onChange={setWalletId} />
397
636
 
398
- 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.
637
+ // With callback
638
+ <WalletSelector
639
+ onWalletChange={(id, wallet) => console.log('Selected:', wallet?.walletName)}
640
+ placeholder="Choose a wallet"
641
+ />
642
+
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
+ ```
399
656
 
400
- #### SignAndSendConnection
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 |
401
671
 
402
- Type for the `connection` prop when using sign-and-send (passkey or wallet-adapter). Your `Connection` from `@solana/web3.js` is compatible. Must have:
672
+ ---
403
673
 
404
- - `getLatestBlockhash(commitment?)` → `Promise<{ blockhash, lastValidBlockHeight }>`
405
- - `confirmTransaction(opts: { signature, blockhash, lastValidBlockHeight })` → `Promise<void>`
674
+ #### CreateWalletForm
406
675
 
407
- For wallet-adapter send, the hook also uses `sendRawTransaction(buf)` (standard Solana Connection has this).
676
+ Form to create a new wallet.
408
677
 
409
- #### Other types
678
+ ```tsx
679
+ import { CreateWalletForm } from 'cilantro-react';
410
680
 
411
- - **ErrorResponse** – `{ message?, error?, code?, status?, statusText?, data?, ... }` for API error shapes.
412
- - **ApiResponse\<T\>** – SDK response shape; use `extractResponseData(response)` to unwrap.
413
- - **DelegatedKeyData** – From DelegatedKeySelector: `id`, `walletId`, `name?`, `publicKey`, `permissions`, `isActive?`, `createdAt?`, `expiresAt?`, etc.
414
- - **SigningMethod** – `'wallet-adapter' | 'sdk-signer'`.
415
- - **AddSignerType** – `'email' | 'phone' | 'passkey' | 'external'`.
416
- - **AuthFormMode** – `'login' | 'register'`.
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
+ ```
417
687
 
418
- #### Utilities
688
+ **Props:**
419
689
 
420
- - **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.
421
- - **extractResponseData\<T\>(response: unknown): T | null** – Unwraps SDK response shapes (`{ data }`, `{ success, data }`, signer arrays). Use after cilantro-sdk calls that return wrapped payloads.
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 |
422
696
 
423
697
  ---
424
698
 
425
- ## Examples
699
+ #### WalletAddress
426
700
 
427
- ### Auth single form (AuthForm)
428
-
429
- One form that switches between sign-in and create-account:
701
+ Display a wallet address with copy functionality.
430
702
 
431
703
  ```tsx
432
- import { AuthForm } from 'cilantro-react'
704
+ import { WalletAddress } from 'cilantro-react';
433
705
 
434
- function LoginPage() {
435
- return (
436
- <AuthForm
437
- defaultMode="login"
438
- onSuccess={() => window.location.href = '/dashboard'}
439
- onError={(err) => console.error(err)}
440
- loginTitle="Welcome back"
441
- registerTitle="Create an account"
442
- switchToRegisterText="Don't have an account? Create one"
443
- switchToLoginText="Already have an account? Sign in"
444
- />
445
- )
446
- }
706
+ <WalletAddress
707
+ address="7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU"
708
+ short={true}
709
+ copyable={true}
710
+ />
447
711
  ```
448
712
 
449
- ### Auth – separate Login and Register
713
+ **Props:**
450
714
 
451
- Use `LoginForm` and `RegisterForm` with links to switch pages:
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 |
452
721
 
453
- ```tsx
454
- import { LoginForm, RegisterForm } from 'cilantro-react'
455
- import { Link } from 'react-router-dom'
722
+ ---
456
723
 
457
- function LoginPage() {
458
- return (
459
- <LoginForm
460
- onSuccess={() => navigate('/dashboard')}
461
- renderSwitchToRegister={() => (
462
- <Link to="/register">Create account</Link>
463
- )}
464
- />
465
- )
466
- }
724
+ #### WalletBalance
467
725
 
468
- function RegisterPage() {
469
- return (
470
- <RegisterForm
471
- onSuccess={() => navigate('/dashboard')}
472
- renderSwitchToLogin={() => (
473
- <Link to="/login">Already have an account? Sign in</Link>
474
- )}
475
- />
476
- )
477
- }
726
+ Display wallet SOL and token balances.
727
+
728
+ ```tsx
729
+ import { WalletBalance } from 'cilantro-react';
730
+
731
+ <WalletBalance walletId={walletId} showTokens={true} />
478
732
  ```
479
733
 
480
- ### Auth – protected route (AuthGuard)
734
+ **Props:**
481
735
 
482
- Show login when not authenticated, otherwise render children:
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 |
483
741
 
484
- ```tsx
485
- import { AuthGuard, LoginForm } from 'cilantro-react'
742
+ ---
486
743
 
487
- function DashboardRoute() {
488
- return (
489
- <AuthGuard fallback={<LoginForm />}>
490
- <Dashboard />
491
- </AuthGuard>
492
- )
493
- }
744
+ #### ConnectWalletButton
494
745
 
495
- // Or use default fallback (LoginForm)
496
- <AuthGuard>
497
- <Dashboard />
498
- </AuthGuard>
746
+ Connect to external Solana wallets (Phantom, Solflare).
499
747
 
500
- // Hide content when unauthenticated (no fallback UI)
501
- <AuthGuard showFallback={false}>
502
- <Dashboard />
503
- </AuthGuard>
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>
504
757
  ```
505
758
 
506
- ### Wallets
759
+ **Props:**
760
+
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 |
767
+
768
+ ---
769
+
770
+ ### Signer Components
771
+
772
+ #### SignerSelector
507
773
 
508
- Wallet dropdown with optional customization:
774
+ Select a signer from the wallet's signers.
509
775
 
510
776
  ```tsx
511
- import { useWallets, WalletSelector } from 'cilantro-react'
777
+ import { SignerSelector } from 'cilantro-react';
512
778
 
513
- function Wallets() {
514
- const { wallets, selectedWallet } = useWallets()
779
+ const [signerId, setSignerId] = useState('');
780
+ const [signerType, setSignerType] = useState<'email' | 'phone'>('email');
515
781
 
516
- return (
782
+ <SignerSelector
783
+ walletId={walletId}
784
+ value={signerId}
785
+ onChange={(id, type) => {
786
+ setSignerId(id);
787
+ setSignerType(type);
788
+ }}
789
+ />
790
+
791
+ // Headless
792
+ <SignerSelector walletId={walletId} onChange={handleChange}>
793
+ {({ signers, selectedSigner, onSignerSelect, isLoading }) => (
517
794
  <div>
518
- <WalletSelector
519
- placeholder="Choose a wallet"
520
- onWalletChange={(id, wallet) => console.log('Selected', id, wallet)}
521
- classNames={{
522
- root: 'w-full max-w-xs',
523
- trigger: 'border-2',
524
- }}
525
- />
526
- {selectedWallet && (
527
- <p>Selected: {selectedWallet.walletName ?? selectedWallet.id}</p>
528
- )}
795
+ {signers.map((s) => (
796
+ <button key={s.id} onClick={() => onSignerSelect(s)}>
797
+ {s.email || s.phone}
798
+ </button>
799
+ ))}
529
800
  </div>
530
- )
531
- }
801
+ )}
802
+ </SignerSelector>
532
803
  ```
533
804
 
534
- ### Signers – list and select
805
+ **Props:**
806
+
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 |
817
+
818
+ ---
819
+
820
+ #### SignerList
535
821
 
536
- Load signers for the selected wallet and let the user pick one:
822
+ List signers with "Add signer" dialog (includes email, phone, passkey forms).
537
823
 
538
824
  ```tsx
539
- import { useSigners, useSignerSelection, SignerSelector } from 'cilantro-react'
825
+ import { SignerList } from 'cilantro-react';
540
826
 
541
- function SignerPick() {
542
- const { walletId } = useSignerSelection({ walletId: selectedWalletId })
543
- const { signers, isLoading, refresh } = useSigners({ walletId: walletId ?? undefined })
827
+ <SignerList
828
+ walletId={walletId}
829
+ onSignerAdded={() => console.log('Signer added')}
830
+ onRevoke={(signerId) => console.log('Revoked:', signerId)}
831
+ />
544
832
 
545
- return (
546
- <SignerSelector
547
- selectedWalletId={walletId ?? undefined}
548
- availableSigners={signers}
549
- selectedSigner={selectedSigner}
550
- onSignerSelect={setSelectedSigner}
551
- isLoadingSigners={isLoading}
552
- className="w-full"
553
- />
554
- )
555
- }
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>
556
842
  ```
557
843
 
558
- Headless: use `children` or `renderList` to render the list yourself.
844
+ **Props:**
559
845
 
560
- ### Signers add signer (AddSignerForm + SignerList)
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 |
561
855
 
562
- List signers and open a dialog to add a new one (email, phone, passkey, external wallet):
856
+ ---
563
857
 
564
- ```tsx
565
- import { SignerList } from 'cilantro-react'
858
+ #### CreateEmailSignerForm
566
859
 
567
- function SignersPage() {
568
- const walletId = 'your-wallet-id'
860
+ Form to create an email signer.
569
861
 
570
- return (
571
- <SignerList
572
- walletId={walletId}
573
- onSignerAdded={() => console.log('Signer added')}
574
- />
575
- )
576
- }
862
+ ```tsx
863
+ import { CreateEmailSignerForm } from 'cilantro-react';
864
+
865
+ <CreateEmailSignerForm
866
+ walletId={walletId}
867
+ onCreated={(signer) => console.log('Created:', signer.signerId)}
868
+ onError={(err) => console.error(err.message)}
869
+ />
577
870
  ```
578
871
 
579
- AddSignerForm can also be used standalone (e.g. inline or in your own dialog):
872
+ **Props:**
873
+
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 |
880
+
881
+ ---
882
+
883
+ #### CreatePhoneSignerForm
884
+
885
+ Form to create a phone signer.
580
886
 
581
887
  ```tsx
582
- import { AddSignerForm } from 'cilantro-react'
888
+ import { CreatePhoneSignerForm } from 'cilantro-react';
583
889
 
584
- <AddSignerForm
890
+ <CreatePhoneSignerForm
585
891
  walletId={walletId}
586
- open={dialogOpen}
587
- onOpenChange={setDialogOpen}
588
- onSuccess={() => { refreshSigners(); setDialogOpen(false) }}
589
- asDialog={true}
892
+ onCreated={(signer) => console.log('Created:', signer.signerId)}
893
+ onError={(err) => console.error(err.message)}
590
894
  />
591
895
  ```
592
896
 
593
- ### Delegated keys
897
+ **Props:**
594
898
 
595
- Select a delegated key for a wallet:
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 |
596
905
 
597
- ```tsx
598
- import { DelegatedKeySelector } from 'cilantro-react'
906
+ ---
599
907
 
600
- function DelegatedKeys() {
601
- const [keyId, setKeyId] = useState<string>('')
908
+ #### AddPasskeyButton
602
909
 
603
- return (
604
- <DelegatedKeySelector
605
- walletId={walletId}
606
- value={keyId}
607
- onChange={(id, key) => setKeyId(id)}
608
- placeholder="Select a delegated key"
609
- filterActive={true}
610
- />
611
- )
612
- }
910
+ Button to register a passkey signer. Only renders if WebAuthn is supported.
911
+
912
+ ```tsx
913
+ import { AddPasskeyButton } from 'cilantro-react';
914
+
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>
613
922
  ```
614
923
 
615
- Use `children` for full control over the list UI.
924
+ **Props:**
925
+
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 |
933
+
934
+ ---
935
+
936
+ ### Transaction Components
616
937
 
617
- ### Message signing
938
+ #### SendSOLForm
618
939
 
619
- Default UI with message textarea, sign button, and wallet/signer context:
940
+ Form to send SOL.
620
941
 
621
942
  ```tsx
622
- import { MessageSigningForm } from 'cilantro-react'
943
+ import { SendSOLForm } from 'cilantro-react';
623
944
 
624
- function SignMessage() {
625
- return (
626
- <MessageSigningForm
627
- showContext={true}
628
- showCharCount={true}
629
- />
630
- )
631
- }
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
+ />
632
952
  ```
633
953
 
634
- Headless: use `children` to get `messageText`, `setMessageText`, `handleSign`, `signResultState`, `isSigning`, `reset`:
954
+ **Props:**
955
+
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 |
964
+
965
+ ---
966
+
967
+ #### SendSPLForm
968
+
969
+ Form to send SPL tokens.
635
970
 
636
971
  ```tsx
637
- <MessageSigningForm>
638
- {({ messageText, setMessageText, handleSign, signResultState, isSigning, reset }) => (
639
- <div>
640
- <textarea value={messageText} onChange={e => setMessageText(e.target.value)} />
641
- <button onClick={handleSign} disabled={isSigning}>Sign</button>
642
- {signResultState.status !== 'idle' && <p>{signResultState.message}</p>}
643
- <button onClick={reset}>Clear</button>
644
- </div>
645
- )}
646
- </MessageSigningForm>
972
+ import { SendSPLForm } from 'cilantro-react';
973
+
974
+ <SendSPLForm
975
+ walletId={walletId}
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)}
981
+ />
647
982
  ```
648
983
 
649
- ### Transaction signing
984
+ **Props:**
650
985
 
651
- 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`):
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 |
652
995
 
653
- ```tsx
654
- import { Connection } from '@solana/web3.js'
655
- import { TransactionSigningForm } from 'cilantro-react'
996
+ ---
656
997
 
657
- function SendTx() {
658
- const connection = new Connection('https://api.devnet.solana.com')
998
+ #### TransactionStatus
659
999
 
660
- return (
661
- <TransactionSigningForm
662
- connection={connection}
663
- showContext={true}
664
- >
665
- {({ signTransaction, signAndSendTransaction, transactionResultState, isSigningTransaction, isSendingTransaction, reset }) => (
666
- <div>
667
- <button
668
- onClick={() => {
669
- const tx = buildMyTransaction() // your code
670
- signAndSendTransaction(tx)
671
- }}
672
- disabled={isSendingTransaction}
673
- >
674
- {isSendingTransaction ? 'Sending...' : 'Sign and send'}
675
- </button>
676
- {transactionResultState.status !== 'idle' && (
677
- <p>{transactionResultState.message}</p>
678
- )}
679
- <button onClick={reset}>Reset status</button>
680
- </div>
681
- )}
682
- </TransactionSigningForm>
683
- )
684
- }
1000
+ Display transaction status with explorer link.
1001
+
1002
+ ```tsx
1003
+ import { TransactionStatus } from 'cilantro-react';
1004
+
1005
+ <TransactionStatus
1006
+ signature="5wHu1qwD7q9..."
1007
+ cluster="mainnet-beta"
1008
+ />
685
1009
  ```
686
1010
 
687
- 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.
1011
+ **Props:**
1012
+
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 |
1018
+
1019
+ ---
1020
+
1021
+ #### TransactionHistory
688
1022
 
689
- ### Full flow example
1023
+ Paginated list of recent transactions.
690
1024
 
691
- Typical app flow:
1025
+ ```tsx
1026
+ import { TransactionHistory } from 'cilantro-react';
692
1027
 
693
- 1. Wrap app with `CilantroProvider` (see [Setup](#setup)).
694
- 2. **Auth:** Use `AuthForm` on a login page, or `AuthGuard` around protected routes with `LoginForm` as fallback.
695
- 3. **Wallets:** Use `WalletSelector` so the user picks a wallet; get `selectedWallet` from `useWallets()`.
696
- 4. **Signers:** Use `SignerSelector` with `useSigners({ walletId })` and `useSignerSelection`, or use `SignerList` to list + add signers.
697
- 5. **Message signing:** Use `MessageSigningForm` (optionally with `showContext` and `showCharCount`).
698
- 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.
1028
+ <TransactionHistory walletId={walletId} pageSize={10} />
1029
+ ```
1030
+
1031
+ **Props:**
1032
+
1033
+ | Prop | Type | Default | Description |
1034
+ |------|------|---------|-------------|
1035
+ | `walletId` | `string` | — | Wallet ID |
1036
+ | `pageSize` | `number` | `10` | Transactions per page |
1037
+ | `className` | `string` | — | Element class |
699
1038
 
700
1039
  ---
701
1040
 
702
- ## Customization
1041
+ ### Layout Components
1042
+
1043
+ #### CilantroConnect
1044
+
1045
+ All-in-one connect component combining wallet, signers, and auth.
1046
+
1047
+ ```tsx
1048
+ import { CilantroConnect } from 'cilantro-react';
1049
+
1050
+ <CilantroConnect
1051
+ onConnect={(wallet, signers) => console.log('Connected:', wallet, signers)}
1052
+ onDisconnect={() => console.log('Disconnected')}
1053
+ />
1054
+ ```
1055
+
1056
+ **Props:**
703
1057
 
704
- - **className** All components accept `className` on the root element. Apply your own Tailwind or CSS.
705
- - **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.
706
- - **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`).
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 |
707
1063
 
708
1064
  ---
709
1065
 
710
- ## Core types and utilities
1066
+ #### LoadingOverlay
1067
+
1068
+ Overlay with loading spinner for async operations.
1069
+
1070
+ ```tsx
1071
+ import { LoadingOverlay } from 'cilantro-react';
1072
+
1073
+ <LoadingOverlay isLoading={isPending} message="Sending transaction...">
1074
+ <MyContent />
1075
+ </LoadingOverlay>
1076
+ ```
1077
+
1078
+ **Props:**
711
1079
 
712
- - **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.
713
- - **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.
714
- - **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).
715
- - **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 |
716
1086
 
717
1087
  ---
718
1088
 
719
- ## Advanced / low-level
1089
+ #### ErrorBoundary
720
1090
 
721
- **Signer signing (without React hooks):** For advanced usage you can call the core signer APIs directly:
1091
+ Error boundary with retry functionality.
722
1092
 
723
- - `signMessageWithSigner(walletId, signer, message)` – Sign a message with a given signer.
724
- - `signTransactionWithSigner(walletId, signer, transactionBuffer)` Sign a transaction (no send).
725
- - `signAndSendTransactionWithSigner(walletId, signer, transaction, connection?)` – Sign and send; **connection is required for passkey**.
726
- - `getWalletData(walletId)`, `getSignerPublicKey(...)` – Helpers for wallet/signer data.
727
- - `SIGNER_TYPES`, `SignerType` – Signer type constants.
1093
+ ```tsx
1094
+ import { ErrorBoundary } from 'cilantro-react';
1095
+
1096
+ <ErrorBoundary
1097
+ fallback={<div>Something went wrong</div>}
1098
+ onRetry={() => window.location.reload()}
1099
+ >
1100
+ <App />
1101
+ </ErrorBoundary>
1102
+ ```
728
1103
 
729
- See TypeScript types and [cilantro-sdk](https://www.npmjs.com/package/cilantro-sdk) for full behavior.
1104
+ **Props:**
730
1105
 
731
- **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 |
732
1111
 
733
1112
  ---
734
1113
 
735
1114
  ## Theming
736
1115
 
737
- 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.
738
1117
 
739
- ### Built-in theme (ThemeProvider)
740
-
741
- 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)
742
1119
 
743
1120
  ```tsx
744
- import { ThemeProvider } from 'cilantro-react'
745
-
746
- <ThemeProvider theme="system" defaultTheme="dark" className="min-h-screen">
747
- <CilantroProvider platformApiKey="...">
748
- {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 />
749
1131
  </CilantroProvider>
750
1132
  </ThemeProvider>
751
1133
  ```
752
1134
 
753
- - **theme** – `"light"` | `"dark"` | `"system"`. `"system"` follows `prefers-color-scheme`.
754
- - **defaultTheme** – Initial theme when using `"system"` (default: `"dark"`).
755
- - **storageKey** – Optional localStorage key to persist the resolved theme when using `"system"`.
756
- - **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:**
757
1136
 
758
- 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 |
759
1144
 
760
- 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
+ ```
761
1150
 
762
1151
  ---
763
1152
 
764
- ## Accessibility
1153
+ ## Advanced Usage
1154
+
1155
+ ### Low-Level Signer Signing
765
1156
 
766
- Components are built with screen readers and keyboard use in mind:
1157
+ For advanced use cases without React hooks:
767
1158
 
768
- - **Live regions** – Signing result messages (MessageSigningForm, TransactionSigningForm) use `role="status"` and `aria-live="polite"` (or `assertive` for errors). Loading states use `aria-busy`.
769
- - **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.
770
- - **Selects and dialogs** – WalletSelector and DelegatedKeySelector triggers have `aria-label`. Radix Dialog handles focus trap and restore in AddSignerForm.
771
- - **Loading** – WalletSelector, SignerSelector, SignerList, DelegatedKeySelector, and AuthGuard use `aria-busy` and `aria-live="polite"` when loading so screen readers announce loading state.
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
+ ```
772
1175
 
773
- Buttons use `focus-visible:ring-2` for focus indication; icon-only or custom buttons can receive `aria-label` via standard HTML attributes.
1176
+ ### Solana Connection Adapter
774
1177
 
775
- ---
1178
+ For `@solana/web3.js` connection compatibility:
1179
+
1180
+ ```tsx
1181
+ import { Connection } from '@solana/web3.js';
1182
+ import { adaptSolanaConnection } from 'cilantro-react';
776
1183
 
777
- ## Mobile
1184
+ const connection = new Connection('https://api.mainnet-beta.solana.com');
1185
+ const adapted = adaptSolanaConnection(connection);
1186
+ ```
778
1187
 
779
- Key actions use touch-friendly targets and dialogs are responsive:
1188
+ ### Utilities
780
1189
 
781
- - **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.
782
- - **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.
783
- - **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 |
784
1198
 
785
1199
  ---
786
1200
 
787
- ## Loading states
1201
+ ## Types
788
1202
 
789
- Loading is shown with skeleton placeholders where it improves perceived performance:
1203
+ Key exported types:
790
1204
 
791
- - **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.
792
- - **useSkeleton** – WalletSelector, SignerSelector, SignerList, and DelegatedKeySelector accept an optional **useSkeleton** prop (default: `true`). Set `useSkeleton={false}` to show plain “Loading…” text instead.
793
- - **classNames** – These components support `classNames.skeleton` and `classNames.loading` so you can style the skeleton or the loading container.
794
- - **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
+ ```
795
1242
 
796
1243
  ---
797
1244
 
798
- ## Low-level SDK
1245
+ ## License
799
1246
 
800
- 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