cilantro-react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +647 -0
- package/dist/index.d.mts +616 -0
- package/dist/index.d.ts +616 -0
- package/dist/index.js +2867 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2837 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
# cilantro-react
|
|
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`.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install cilantro-react cilantro-sdk react
|
|
9
|
+
```
|
|
10
|
+
|
|
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).
|
|
12
|
+
|
|
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.
|
|
14
|
+
|
|
15
|
+
## Polyfills
|
|
16
|
+
|
|
17
|
+
Import the SDK polyfills once before any SDK usage (e.g. in your app entry or root layout):
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import 'cilantro-sdk/polyfills'
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Setup
|
|
24
|
+
|
|
25
|
+
Wrap your app with `CilantroProvider` and pass your platform API key:
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import { CilantroProvider } from 'cilantro-react'
|
|
29
|
+
|
|
30
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
31
|
+
return (
|
|
32
|
+
<CilantroProvider platformApiKey="your-platform-api-key">
|
|
33
|
+
{children}
|
|
34
|
+
</CilantroProvider>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**CilantroProvider props:**
|
|
40
|
+
|
|
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. |
|
|
50
|
+
|
|
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.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## API reference
|
|
56
|
+
|
|
57
|
+
### Providers
|
|
58
|
+
|
|
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
|
+
|
|
63
|
+
**CilantroAuthProvider props:** `platformApiKey` (required), `apiUrl`, `jwtStorageKey`, `onLoginSuccess`, `onLogout`, `onRegisterSuccess`.
|
|
64
|
+
|
|
65
|
+
**WalletProvider props:** `storageKey` (default: `cilantro_selected_wallet_id`).
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
### Custom hooks (detailed)
|
|
70
|
+
|
|
71
|
+
All hooks must be used within the appropriate provider (see each hook below).
|
|
72
|
+
|
|
73
|
+
#### useCilantroAuth
|
|
74
|
+
|
|
75
|
+
Auth state and actions. Use inside `CilantroAuthProvider` or `CilantroProvider`.
|
|
76
|
+
|
|
77
|
+
**Returns:** `CilantroAuthContextType`
|
|
78
|
+
|
|
79
|
+
| Property | Type | Description |
|
|
80
|
+
|----------|------|-------------|
|
|
81
|
+
| `token` | `string \| null` | JWT token (null when not authenticated). |
|
|
82
|
+
| `user` | `User \| null` | User object: `{ username?, email?, userType? }`. |
|
|
83
|
+
| `isAuthenticated` | `boolean` | True when `token` is set. |
|
|
84
|
+
| `isLoading` | `boolean` | True while restoring session from storage. |
|
|
85
|
+
| `login` | `(usernameOrEmail: string, password: string) => Promise<void>` | Log in; throws on error. |
|
|
86
|
+
| `register` | `(username: string, email: string, password: string, isActive?: boolean) => Promise<void>` | Register and log in; throws on error. |
|
|
87
|
+
| `logout` | `() => void` | Clear token and user; calls `onLogout` if provided. |
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
#### useWallets
|
|
92
|
+
|
|
93
|
+
Wallet list and selection. Use inside `CilantroProvider` (which includes `WalletProvider`).
|
|
94
|
+
|
|
95
|
+
**Returns:** `WalletContextType`
|
|
96
|
+
|
|
97
|
+
| Property | Type | Description |
|
|
98
|
+
|----------|------|-------------|
|
|
99
|
+
| `wallets` | `WalletData[]` | List of wallets for the authenticated user. |
|
|
100
|
+
| `selectedWallet` | `WalletData \| null` | Currently selected wallet (persisted in localStorage). |
|
|
101
|
+
| `selectWallet` | `(walletId: string) => void` | Set the selected wallet by id. |
|
|
102
|
+
| `refreshWallets` | `() => Promise<void>` | Reload wallets from the API. |
|
|
103
|
+
| `isLoading` | `boolean` | True while loading wallets. |
|
|
104
|
+
|
|
105
|
+
**WalletData:** `{ id, walletId, walletName?, address?, walletAddress?, chain?, active?, ... }`.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
#### useSigners
|
|
110
|
+
|
|
111
|
+
Load signers for a wallet. Use inside `CilantroProvider` (auth required).
|
|
112
|
+
|
|
113
|
+
**Options:** `UseSignersOptions`
|
|
114
|
+
|
|
115
|
+
| Option | Type | Description |
|
|
116
|
+
|--------|------|-------------|
|
|
117
|
+
| `walletId` | `string \| null \| undefined` | Wallet ID to load signers for. If omitted, you can pass it from `useWallets().selectedWallet?.id`. |
|
|
118
|
+
|
|
119
|
+
**Returns:** `UseSignersResult`
|
|
120
|
+
|
|
121
|
+
| Property | Type | Description |
|
|
122
|
+
|----------|------|-------------|
|
|
123
|
+
| `signers` | `SignerData[]` | List of signers for the wallet. |
|
|
124
|
+
| `isLoading` | `boolean` | True while loading. |
|
|
125
|
+
| `error` | `string \| null` | Error message if load failed. |
|
|
126
|
+
| `refresh` | `() => Promise<void>` | Reload signers. |
|
|
127
|
+
|
|
128
|
+
When `walletId` is null/undefined, `signers` is empty and `refresh` no-ops.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
#### useSignerSelection
|
|
133
|
+
|
|
134
|
+
Wallet + signer selection state for signing flows. Uses `useWallets` and `useSigners` internally. Use inside `CilantroProvider`.
|
|
135
|
+
|
|
136
|
+
**Options:** `UseSignerSelectionOptions`
|
|
137
|
+
|
|
138
|
+
| Option | Type | Description |
|
|
139
|
+
|--------|------|-------------|
|
|
140
|
+
| `walletId` | `string \| null \| undefined` | Override wallet ID; if not set, uses `useWallets().selectedWallet?.id`. |
|
|
141
|
+
| `signingMethod` | `'wallet-adapter' \| 'sdk-signer' \| null` | Current signing method (default: `'sdk-signer'`). When `'wallet-adapter'`, signer selection is ignored. |
|
|
142
|
+
|
|
143
|
+
**Returns:** `UseSignerSelectionResult`
|
|
144
|
+
|
|
145
|
+
| Property | Type | Description |
|
|
146
|
+
|----------|------|-------------|
|
|
147
|
+
| `selectedWalletId` | `string` | Effective wallet ID (override or from context). |
|
|
148
|
+
| `setSelectedWalletId` | `(id: string) => void` | Set wallet ID (when not using context selection). |
|
|
149
|
+
| `availableSigners` | `SignerData[]` | Signers for the selected wallet (from `useSigners`). |
|
|
150
|
+
| `selectedSigner` | `SignerData \| null` | Currently selected signer. |
|
|
151
|
+
| `setSelectedSigner` | `(signer: SignerData \| null) => void` | Set selected signer. |
|
|
152
|
+
| `isLoadingSigners` | `boolean` | True while signers are loading. |
|
|
153
|
+
| `reset` | `() => void` | Clear `selectedWalletId` and `selectedSigner`. |
|
|
154
|
+
|
|
155
|
+
**SigningMethod:** `'wallet-adapter'` = use wallet adapter for signing; `'sdk-signer'` = use a Cilantro signer (email, phone, passkey, external, etc.).
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
#### useMessageSigning
|
|
160
|
+
|
|
161
|
+
Sign a message with the selected signer or wallet-adapter. Use inside `CilantroProvider`.
|
|
162
|
+
|
|
163
|
+
**Options:** `UseMessageSigningOptions`
|
|
164
|
+
|
|
165
|
+
| Option | Type | Description |
|
|
166
|
+
|--------|------|-------------|
|
|
167
|
+
| `token` | `string \| null` | JWT (usually from `useCilantroAuth().token`). |
|
|
168
|
+
| `signingMethod` | `SigningMethod \| null` | `'sdk-signer'` or `'wallet-adapter'`. |
|
|
169
|
+
| `selectedSigner` | `SignerData \| null` | Signer to use when `signingMethod === 'sdk-signer'`. |
|
|
170
|
+
| `selectedWalletId` | `string` | Wallet ID when using SDK signer. |
|
|
171
|
+
| `walletAdapterSignMessage` | `(message: Uint8Array) => Promise<Uint8Array>` | Optional; required when `signingMethod === 'wallet-adapter'`. |
|
|
172
|
+
| `walletAdapterPublicKey` | `string \| null` | Optional; included in success detail when using wallet-adapter. |
|
|
173
|
+
|
|
174
|
+
**Returns:** `UseMessageSigningResult`
|
|
175
|
+
|
|
176
|
+
| Property | Type | Description |
|
|
177
|
+
|----------|------|-------------|
|
|
178
|
+
| `messageText` | `string` | Current message (default: `"Hello, Solana!"`). |
|
|
179
|
+
| `setMessageText` | `(text: string) => void` | Update message. |
|
|
180
|
+
| `signResultState` | `ActionState` | `{ status, message?, detail? }` – idle, loading, success, or error. |
|
|
181
|
+
| `isSigning` | `boolean` | True while signing. |
|
|
182
|
+
| `handleSign` | `() => Promise<void>` | Sign the current message; updates `signResultState`. |
|
|
183
|
+
| `reset` | `() => void` | Reset message to default and clear result state. |
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
#### useTransactionSigning
|
|
188
|
+
|
|
189
|
+
Sign and/or send a transaction. Use inside `CilantroProvider`.
|
|
190
|
+
|
|
191
|
+
**Options:** `UseTransactionSigningOptions`
|
|
192
|
+
|
|
193
|
+
| Option | Type | Description |
|
|
194
|
+
|--------|------|-------------|
|
|
195
|
+
| `token` | `string \| null` | JWT (usually from `useCilantroAuth().token`). |
|
|
196
|
+
| `signingMethod` | `SigningMethod \| null` | `'sdk-signer'` or `'wallet-adapter'`. |
|
|
197
|
+
| `selectedSigner` | `SignerData \| null` | Signer when `signingMethod === 'sdk-signer'`. |
|
|
198
|
+
| `selectedWalletId` | `string` | Wallet ID when using SDK signer. |
|
|
199
|
+
| `walletAdapterSignTransaction` | `(transaction: Transaction) => Promise<Transaction>` | Optional; required for wallet-adapter signing. |
|
|
200
|
+
| `walletAdapterPublicKey` | `string \| null` | Optional; required for wallet-adapter send. |
|
|
201
|
+
| `connection` | `SignAndSendConnection \| null` | **Required for sign-and-send** when using wallet-adapter or passkey. Provide your Solana `Connection` (e.g. from `@solana/web3.js`). |
|
|
202
|
+
|
|
203
|
+
**Returns:** `UseTransactionSigningResult`
|
|
204
|
+
|
|
205
|
+
| Property | Type | Description |
|
|
206
|
+
|----------|------|-------------|
|
|
207
|
+
| `transactionResultState` | `ActionState` | Result of last sign/send (idle, loading, success, error). |
|
|
208
|
+
| `isSigningTransaction` | `boolean` | True while `signTransaction` is running. |
|
|
209
|
+
| `isSendingTransaction` | `boolean` | True while `signAndSendTransaction` is running. |
|
|
210
|
+
| `signTransaction` | `(transaction: Transaction) => Promise<void>` | Sign only (no send). Passkey signers cannot sign-only; use sign-and-send. |
|
|
211
|
+
| `signAndSendTransaction` | `(transaction: Transaction) => Promise<void>` | Sign and send. Requires `connection` for wallet-adapter and passkey. |
|
|
212
|
+
| `reset` | `() => void` | Set `transactionResultState` back to idle. |
|
|
213
|
+
|
|
214
|
+
**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.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
### Components (detailed)
|
|
219
|
+
|
|
220
|
+
All components accept `className`. Many accept a `classNames` object (see TypeScript types e.g. `WalletSelectorClassNames`, `MessageSigningFormClassNames`).
|
|
221
|
+
|
|
222
|
+
#### Auth components
|
|
223
|
+
|
|
224
|
+
| Component | Purpose | Key props |
|
|
225
|
+
|-----------|---------|-----------|
|
|
226
|
+
| **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). |
|
|
227
|
+
| **LoginForm** | Login (username/email + password). | `onSuccess?`, `onError?(error: string)`, `submitLabel`, `title`, `description`, `renderSwitchToRegister? () => ReactNode`, `className`, `classNames?`. |
|
|
228
|
+
| **RegisterForm** | Registration (username, email, password). | `onSuccess?`, `onError?(error: string)`, `submitLabel`, `title`, `description`, `isActive?` (default true), `renderSwitchToLogin? () => ReactNode`, `className`, `classNames?`. |
|
|
229
|
+
| **AuthGuard** | Renders children when authenticated; shows fallback otherwise. | `children`, `fallback?: ReactNode` (default: `<LoginForm />`), `showFallback?` (default true), `className`, `classNames?` (root, fallback). |
|
|
230
|
+
|
|
231
|
+
#### Wallet and signer components
|
|
232
|
+
|
|
233
|
+
| Component | Purpose | Key props |
|
|
234
|
+
|-----------|---------|-----------|
|
|
235
|
+
| **WalletSelector** | Wallet dropdown (Shadcn Select). | `value?`, `onWalletChange?(walletId, wallet)`, `placeholder`, `className`, `classNames?` (root, trigger, content, item). Headless: `children?`, `renderTrigger?`, `renderList?`. |
|
|
236
|
+
| **SignerSelector** | List/select signers (buttons). | `selectedWalletId?`, `availableSigners`, `selectedSigner`, `onSignerSelect(signer)`, `isLoadingSigners?`, `className`, `classNames?` (root, list, item, message). Headless: `children?`, `renderList?`. |
|
|
237
|
+
| **SignerList** | List signers + “Add signer” (opens AddSignerForm in dialog). | `walletId`, `onSignerAdded`, `className`, `classNames`. Headless: `children`. |
|
|
238
|
+
| **AddSignerForm** | Add signer (email, phone, passkey, external wallet). | `walletId`, `open?`, `onOpenChange?`, `onSuccess?`, `onCancel?`, `onError?(error)`, `asDialog?` (default true), `className`, `classNames?`. |
|
|
239
|
+
| **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). |
|
|
240
|
+
|
|
241
|
+
#### Signing form components
|
|
242
|
+
|
|
243
|
+
| Component | Purpose | Key props |
|
|
244
|
+
|-----------|---------|-----------|
|
|
245
|
+
| **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). |
|
|
246
|
+
| **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). |
|
|
247
|
+
|
|
248
|
+
When token/wallet/signer/signingMethod are omitted, MessageSigningForm and TransactionSigningForm use `useCilantroAuth().token` and `useSignerSelection()` internally.
|
|
249
|
+
|
|
250
|
+
### Types (detailed)
|
|
251
|
+
|
|
252
|
+
#### ActionState
|
|
253
|
+
|
|
254
|
+
Used by signing hooks and forms for result state.
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
type ActionState<T = unknown> = {
|
|
258
|
+
status: 'idle' | 'loading' | 'success' | 'error';
|
|
259
|
+
message?: string;
|
|
260
|
+
detail?: T;
|
|
261
|
+
};
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
- **idle** – No operation yet.
|
|
265
|
+
- **loading** – Sign/send in progress.
|
|
266
|
+
- **success** – Done; `message` and optional `detail` (e.g. signature, explorer URL).
|
|
267
|
+
- **error** – Failed; `message` (and optional `detail`).
|
|
268
|
+
|
|
269
|
+
#### User
|
|
270
|
+
|
|
271
|
+
From `useCilantroAuth().user`:
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
interface User {
|
|
275
|
+
username?: string;
|
|
276
|
+
email?: string;
|
|
277
|
+
userType?: string;
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
#### WalletData
|
|
282
|
+
|
|
283
|
+
From `useWallets().wallets` / `selectedWallet`:
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
interface WalletData {
|
|
287
|
+
id: string;
|
|
288
|
+
walletId: string;
|
|
289
|
+
walletName?: string;
|
|
290
|
+
address?: string;
|
|
291
|
+
walletAddress?: string;
|
|
292
|
+
chain?: string;
|
|
293
|
+
active?: boolean;
|
|
294
|
+
[key: string]: unknown;
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
#### SignerData
|
|
299
|
+
|
|
300
|
+
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.
|
|
301
|
+
|
|
302
|
+
#### SignAndSendConnection
|
|
303
|
+
|
|
304
|
+
Type for the `connection` prop when using sign-and-send (passkey or wallet-adapter). Your `Connection` from `@solana/web3.js` is compatible. Must have:
|
|
305
|
+
|
|
306
|
+
- `getLatestBlockhash(commitment?)` → `Promise<{ blockhash, lastValidBlockHeight }>`
|
|
307
|
+
- `confirmTransaction(opts: { signature, blockhash, lastValidBlockHeight })` → `Promise<void>`
|
|
308
|
+
|
|
309
|
+
For wallet-adapter send, the hook also uses `sendRawTransaction(buf)` (standard Solana Connection has this).
|
|
310
|
+
|
|
311
|
+
#### Other types
|
|
312
|
+
|
|
313
|
+
- **ErrorResponse** – `{ message?, error?, code?, status?, statusText?, data?, ... }` for API error shapes.
|
|
314
|
+
- **ApiResponse\<T\>** – SDK response shape; use `extractResponseData(response)` to unwrap.
|
|
315
|
+
- **DelegatedKeyData** – From DelegatedKeySelector: `id`, `walletId`, `name?`, `publicKey`, `permissions`, `isActive?`, `createdAt?`, `expiresAt?`, etc.
|
|
316
|
+
- **SigningMethod** – `'wallet-adapter' | 'sdk-signer'`.
|
|
317
|
+
- **AddSignerType** – `'email' | 'phone' | 'passkey' | 'external'`.
|
|
318
|
+
- **AuthFormMode** – `'login' | 'register'`.
|
|
319
|
+
|
|
320
|
+
#### Utilities
|
|
321
|
+
|
|
322
|
+
- **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.
|
|
323
|
+
- **extractResponseData\<T\>(response: unknown): T | null** – Unwraps SDK response shapes (`{ data }`, `{ success, data }`, signer arrays). Use after cilantro-sdk calls that return wrapped payloads.
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Examples
|
|
328
|
+
|
|
329
|
+
### Auth – single form (AuthForm)
|
|
330
|
+
|
|
331
|
+
One form that switches between sign-in and create-account:
|
|
332
|
+
|
|
333
|
+
```tsx
|
|
334
|
+
import { AuthForm } from 'cilantro-react'
|
|
335
|
+
|
|
336
|
+
function LoginPage() {
|
|
337
|
+
return (
|
|
338
|
+
<AuthForm
|
|
339
|
+
defaultMode="login"
|
|
340
|
+
onSuccess={() => window.location.href = '/dashboard'}
|
|
341
|
+
onError={(err) => console.error(err)}
|
|
342
|
+
loginTitle="Welcome back"
|
|
343
|
+
registerTitle="Create an account"
|
|
344
|
+
switchToRegisterText="Don't have an account? Create one"
|
|
345
|
+
switchToLoginText="Already have an account? Sign in"
|
|
346
|
+
/>
|
|
347
|
+
)
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Auth – separate Login and Register
|
|
352
|
+
|
|
353
|
+
Use `LoginForm` and `RegisterForm` with links to switch pages:
|
|
354
|
+
|
|
355
|
+
```tsx
|
|
356
|
+
import { LoginForm, RegisterForm } from 'cilantro-react'
|
|
357
|
+
import { Link } from 'react-router-dom'
|
|
358
|
+
|
|
359
|
+
function LoginPage() {
|
|
360
|
+
return (
|
|
361
|
+
<LoginForm
|
|
362
|
+
onSuccess={() => navigate('/dashboard')}
|
|
363
|
+
renderSwitchToRegister={() => (
|
|
364
|
+
<Link to="/register">Create account</Link>
|
|
365
|
+
)}
|
|
366
|
+
/>
|
|
367
|
+
)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function RegisterPage() {
|
|
371
|
+
return (
|
|
372
|
+
<RegisterForm
|
|
373
|
+
onSuccess={() => navigate('/dashboard')}
|
|
374
|
+
renderSwitchToLogin={() => (
|
|
375
|
+
<Link to="/login">Already have an account? Sign in</Link>
|
|
376
|
+
)}
|
|
377
|
+
/>
|
|
378
|
+
)
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Auth – protected route (AuthGuard)
|
|
383
|
+
|
|
384
|
+
Show login when not authenticated, otherwise render children:
|
|
385
|
+
|
|
386
|
+
```tsx
|
|
387
|
+
import { AuthGuard, LoginForm } from 'cilantro-react'
|
|
388
|
+
|
|
389
|
+
function DashboardRoute() {
|
|
390
|
+
return (
|
|
391
|
+
<AuthGuard fallback={<LoginForm />}>
|
|
392
|
+
<Dashboard />
|
|
393
|
+
</AuthGuard>
|
|
394
|
+
)
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Or use default fallback (LoginForm)
|
|
398
|
+
<AuthGuard>
|
|
399
|
+
<Dashboard />
|
|
400
|
+
</AuthGuard>
|
|
401
|
+
|
|
402
|
+
// Hide content when unauthenticated (no fallback UI)
|
|
403
|
+
<AuthGuard showFallback={false}>
|
|
404
|
+
<Dashboard />
|
|
405
|
+
</AuthGuard>
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Wallets
|
|
409
|
+
|
|
410
|
+
Wallet dropdown with optional customization:
|
|
411
|
+
|
|
412
|
+
```tsx
|
|
413
|
+
import { useWallets, WalletSelector } from 'cilantro-react'
|
|
414
|
+
|
|
415
|
+
function Wallets() {
|
|
416
|
+
const { wallets, selectedWallet } = useWallets()
|
|
417
|
+
|
|
418
|
+
return (
|
|
419
|
+
<div>
|
|
420
|
+
<WalletSelector
|
|
421
|
+
placeholder="Choose a wallet"
|
|
422
|
+
onWalletChange={(id, wallet) => console.log('Selected', id, wallet)}
|
|
423
|
+
classNames={{
|
|
424
|
+
root: 'w-full max-w-xs',
|
|
425
|
+
trigger: 'border-2',
|
|
426
|
+
}}
|
|
427
|
+
/>
|
|
428
|
+
{selectedWallet && (
|
|
429
|
+
<p>Selected: {selectedWallet.walletName ?? selectedWallet.id}</p>
|
|
430
|
+
)}
|
|
431
|
+
</div>
|
|
432
|
+
)
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
### Signers – list and select
|
|
437
|
+
|
|
438
|
+
Load signers for the selected wallet and let the user pick one:
|
|
439
|
+
|
|
440
|
+
```tsx
|
|
441
|
+
import { useSigners, useSignerSelection, SignerSelector } from 'cilantro-react'
|
|
442
|
+
|
|
443
|
+
function SignerPick() {
|
|
444
|
+
const { walletId } = useSignerSelection({ walletId: selectedWalletId })
|
|
445
|
+
const { signers, isLoading, refresh } = useSigners({ walletId: walletId ?? undefined })
|
|
446
|
+
|
|
447
|
+
return (
|
|
448
|
+
<SignerSelector
|
|
449
|
+
selectedWalletId={walletId ?? undefined}
|
|
450
|
+
availableSigners={signers}
|
|
451
|
+
selectedSigner={selectedSigner}
|
|
452
|
+
onSignerSelect={setSelectedSigner}
|
|
453
|
+
isLoadingSigners={isLoading}
|
|
454
|
+
className="w-full"
|
|
455
|
+
/>
|
|
456
|
+
)
|
|
457
|
+
}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
Headless: use `children` or `renderList` to render the list yourself.
|
|
461
|
+
|
|
462
|
+
### Signers – add signer (AddSignerForm + SignerList)
|
|
463
|
+
|
|
464
|
+
List signers and open a dialog to add a new one (email, phone, passkey, external wallet):
|
|
465
|
+
|
|
466
|
+
```tsx
|
|
467
|
+
import { SignerList } from 'cilantro-react'
|
|
468
|
+
|
|
469
|
+
function SignersPage() {
|
|
470
|
+
const walletId = 'your-wallet-id'
|
|
471
|
+
|
|
472
|
+
return (
|
|
473
|
+
<SignerList
|
|
474
|
+
walletId={walletId}
|
|
475
|
+
onSignerAdded={() => console.log('Signer added')}
|
|
476
|
+
/>
|
|
477
|
+
)
|
|
478
|
+
}
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
AddSignerForm can also be used standalone (e.g. inline or in your own dialog):
|
|
482
|
+
|
|
483
|
+
```tsx
|
|
484
|
+
import { AddSignerForm } from 'cilantro-react'
|
|
485
|
+
|
|
486
|
+
<AddSignerForm
|
|
487
|
+
walletId={walletId}
|
|
488
|
+
open={dialogOpen}
|
|
489
|
+
onOpenChange={setDialogOpen}
|
|
490
|
+
onSuccess={() => { refreshSigners(); setDialogOpen(false) }}
|
|
491
|
+
asDialog={true}
|
|
492
|
+
/>
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
### Delegated keys
|
|
496
|
+
|
|
497
|
+
Select a delegated key for a wallet:
|
|
498
|
+
|
|
499
|
+
```tsx
|
|
500
|
+
import { DelegatedKeySelector } from 'cilantro-react'
|
|
501
|
+
|
|
502
|
+
function DelegatedKeys() {
|
|
503
|
+
const [keyId, setKeyId] = useState<string>('')
|
|
504
|
+
|
|
505
|
+
return (
|
|
506
|
+
<DelegatedKeySelector
|
|
507
|
+
walletId={walletId}
|
|
508
|
+
value={keyId}
|
|
509
|
+
onChange={(id, key) => setKeyId(id)}
|
|
510
|
+
placeholder="Select a delegated key"
|
|
511
|
+
filterActive={true}
|
|
512
|
+
/>
|
|
513
|
+
)
|
|
514
|
+
}
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
Use `children` for full control over the list UI.
|
|
518
|
+
|
|
519
|
+
### Message signing
|
|
520
|
+
|
|
521
|
+
Default UI with message textarea, sign button, and wallet/signer context:
|
|
522
|
+
|
|
523
|
+
```tsx
|
|
524
|
+
import { MessageSigningForm } from 'cilantro-react'
|
|
525
|
+
|
|
526
|
+
function SignMessage() {
|
|
527
|
+
return (
|
|
528
|
+
<MessageSigningForm
|
|
529
|
+
showContext={true}
|
|
530
|
+
showCharCount={true}
|
|
531
|
+
/>
|
|
532
|
+
)
|
|
533
|
+
}
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
Headless: use `children` to get `messageText`, `setMessageText`, `handleSign`, `signResultState`, `isSigning`, `reset`:
|
|
537
|
+
|
|
538
|
+
```tsx
|
|
539
|
+
<MessageSigningForm>
|
|
540
|
+
{({ messageText, setMessageText, handleSign, signResultState, isSigning, reset }) => (
|
|
541
|
+
<div>
|
|
542
|
+
<textarea value={messageText} onChange={e => setMessageText(e.target.value)} />
|
|
543
|
+
<button onClick={handleSign} disabled={isSigning}>Sign</button>
|
|
544
|
+
{signResultState.status !== 'idle' && <p>{signResultState.message}</p>}
|
|
545
|
+
<button onClick={reset}>Clear</button>
|
|
546
|
+
</div>
|
|
547
|
+
)}
|
|
548
|
+
</MessageSigningForm>
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### Transaction signing
|
|
552
|
+
|
|
553
|
+
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`):
|
|
554
|
+
|
|
555
|
+
```tsx
|
|
556
|
+
import { Connection } from '@solana/web3.js'
|
|
557
|
+
import { TransactionSigningForm } from 'cilantro-react'
|
|
558
|
+
|
|
559
|
+
function SendTx() {
|
|
560
|
+
const connection = new Connection('https://api.devnet.solana.com')
|
|
561
|
+
|
|
562
|
+
return (
|
|
563
|
+
<TransactionSigningForm
|
|
564
|
+
connection={connection}
|
|
565
|
+
showContext={true}
|
|
566
|
+
>
|
|
567
|
+
{({ signTransaction, signAndSendTransaction, transactionResultState, isSigningTransaction, isSendingTransaction, reset }) => (
|
|
568
|
+
<div>
|
|
569
|
+
<button
|
|
570
|
+
onClick={() => {
|
|
571
|
+
const tx = buildMyTransaction() // your code
|
|
572
|
+
signAndSendTransaction(tx)
|
|
573
|
+
}}
|
|
574
|
+
disabled={isSendingTransaction}
|
|
575
|
+
>
|
|
576
|
+
{isSendingTransaction ? 'Sending...' : 'Sign and send'}
|
|
577
|
+
</button>
|
|
578
|
+
{transactionResultState.status !== 'idle' && (
|
|
579
|
+
<p>{transactionResultState.message}</p>
|
|
580
|
+
)}
|
|
581
|
+
<button onClick={reset}>Reset status</button>
|
|
582
|
+
</div>
|
|
583
|
+
)}
|
|
584
|
+
</TransactionSigningForm>
|
|
585
|
+
)
|
|
586
|
+
}
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
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.
|
|
590
|
+
|
|
591
|
+
### Full flow example
|
|
592
|
+
|
|
593
|
+
Typical app flow:
|
|
594
|
+
|
|
595
|
+
1. Wrap app with `CilantroProvider` (see [Setup](#setup)).
|
|
596
|
+
2. **Auth:** Use `AuthForm` on a login page, or `AuthGuard` around protected routes with `LoginForm` as fallback.
|
|
597
|
+
3. **Wallets:** Use `WalletSelector` so the user picks a wallet; get `selectedWallet` from `useWallets()`.
|
|
598
|
+
4. **Signers:** Use `SignerSelector` with `useSigners({ walletId })` and `useSignerSelection`, or use `SignerList` to list + add signers.
|
|
599
|
+
5. **Message signing:** Use `MessageSigningForm` (optionally with `showContext` and `showCharCount`).
|
|
600
|
+
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.
|
|
601
|
+
|
|
602
|
+
---
|
|
603
|
+
|
|
604
|
+
## Customization
|
|
605
|
+
|
|
606
|
+
- **className** – All components accept `className` on the root element. Apply your own Tailwind or CSS.
|
|
607
|
+
- **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.
|
|
608
|
+
- **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`).
|
|
609
|
+
|
|
610
|
+
---
|
|
611
|
+
|
|
612
|
+
## Core types and utilities
|
|
613
|
+
|
|
614
|
+
- **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.
|
|
615
|
+
- **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.
|
|
616
|
+
- **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).
|
|
617
|
+
- **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`.
|
|
618
|
+
|
|
619
|
+
---
|
|
620
|
+
|
|
621
|
+
## Advanced / low-level
|
|
622
|
+
|
|
623
|
+
**Signer signing (without React hooks):** For advanced usage you can call the core signer APIs directly:
|
|
624
|
+
|
|
625
|
+
- `signMessageWithSigner(walletId, signer, message)` – Sign a message with a given signer.
|
|
626
|
+
- `signTransactionWithSigner(walletId, signer, transactionBuffer)` – Sign a transaction (no send).
|
|
627
|
+
- `signAndSendTransactionWithSigner(walletId, signer, transaction, connection?)` – Sign and send; **connection is required for passkey**.
|
|
628
|
+
- `getWalletData(walletId)`, `getSignerPublicKey(...)` – Helpers for wallet/signer data.
|
|
629
|
+
- `SIGNER_TYPES`, `SignerType` – Signer type constants.
|
|
630
|
+
|
|
631
|
+
See TypeScript types and [cilantro-sdk](https://www.npmjs.com/package/cilantro-sdk) for full behavior.
|
|
632
|
+
|
|
633
|
+
**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.
|
|
634
|
+
|
|
635
|
+
---
|
|
636
|
+
|
|
637
|
+
## Theming
|
|
638
|
+
|
|
639
|
+
Components use Shadcn-style class names and CSS variables (e.g. `border-input`, `bg-primary`, `text-muted-foreground`). Ensure your app has Tailwind and, if you use Shadcn, your theme or `globals.css` defines those variables so colors and spacing match your design. You can override any part via `className` or `classNames`.
|
|
640
|
+
|
|
641
|
+
For Shadcn theming, see [Theming - shadcn/ui](https://ui.shadcn.com/docs/theming).
|
|
642
|
+
|
|
643
|
+
---
|
|
644
|
+
|
|
645
|
+
## Low-level SDK
|
|
646
|
+
|
|
647
|
+
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.
|