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