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