decane-connect-kit 0.0.1

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 ADDED
@@ -0,0 +1,583 @@
1
+ # decane-connect-kit
2
+
3
+ Wallet connection for EVM and Solana dApps. Detects installed wallets via EIP-6963 and the Solana Wallet Standard, manages sessions with CAIP-25, and ships a polished wallet selector UI. Works with React 18+ in any framework.
4
+
5
+ ---
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install decane-connect-kit
11
+ ```
12
+
13
+ React 18 or later is required as a peer dependency.
14
+
15
+ ---
16
+
17
+ ## Quick start
18
+
19
+ ### 1. Wrap your app
20
+
21
+ ```tsx
22
+ // main.tsx / _app.tsx / layout.tsx
23
+ import { DecaneKit } from "decane-connect-kit";
24
+
25
+ <DecaneKit config={{ theme: "auto" }}>
26
+ <App />
27
+ </DecaneKit>
28
+ ```
29
+
30
+ That's the entire setup. Wallet discovery starts immediately. If the user was previously connected, the session is restored from `localStorage` automatically.
31
+
32
+ ### 2. Add a connect button
33
+
34
+ ```tsx
35
+ import { useDecane, useWalletSelector } from "decane-connect-kit";
36
+
37
+ function ConnectButton() {
38
+ const { isConnected, connectedWallet, disconnect } = useDecane();
39
+ const { open } = useWalletSelector();
40
+
41
+ if (isConnected) {
42
+ return (
43
+ <>
44
+ <span>{connectedWallet!.name}</span>
45
+ <button onClick={disconnect}>Disconnect</button>
46
+ </>
47
+ );
48
+ }
49
+
50
+ return <button onClick={open}>Connect Wallet</button>;
51
+ }
52
+ ```
53
+
54
+ The modal renders itself — there is no `<WalletSelector>` component to place anywhere.
55
+
56
+ ### 3. Sign a message
57
+
58
+ ```tsx
59
+ import { useSignMessage } from "decane-connect-kit";
60
+
61
+ function SignMessage() {
62
+ const { signMessage, signature, loading, error } = useSignMessage();
63
+
64
+ return (
65
+ <>
66
+ <button onClick={() => signMessage("Hello world")} disabled={loading}>
67
+ {loading ? "Signing…" : "Sign"}
68
+ </button>
69
+ {signature && <p>{signature}</p>}
70
+ {error && <p style={{ color: "red" }}>{error.message}</p>}
71
+ </>
72
+ );
73
+ }
74
+ ```
75
+
76
+ Chain and account are resolved automatically from the active session. Pass `options.chainId` to target a specific chain explicitly.
77
+
78
+ ---
79
+
80
+ ## Framework setup
81
+
82
+ ### Next.js (App Router)
83
+
84
+ Client components are required for all hooks. Wrap `DecaneKit` in a `"use client"` boundary so the server layout stays a server component.
85
+
86
+ ```tsx
87
+ // src/components/Providers.tsx
88
+ "use client";
89
+
90
+ import { DecaneKit } from "decane-connect-kit";
91
+ import type { ReactNode } from "react";
92
+
93
+ export function Providers({ children }: { children: ReactNode }) {
94
+ return (
95
+ <DecaneKit config={{ theme: "auto" }}>
96
+ {children}
97
+ </DecaneKit>
98
+ );
99
+ }
100
+ ```
101
+
102
+ ```tsx
103
+ // src/app/layout.tsx
104
+ import { Providers } from "@/components/Providers";
105
+
106
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
107
+ return (
108
+ <html lang="en">
109
+ <body>
110
+ <Providers>{children}</Providers>
111
+ </body>
112
+ </html>
113
+ );
114
+ }
115
+ ```
116
+
117
+ Add `"use client"` to every component that uses a decane hook.
118
+
119
+ Add `transpilePackages` to `next.config.ts`:
120
+
121
+ ```ts
122
+ const nextConfig = {
123
+ transpilePackages: ["decane-connect-kit"],
124
+ };
125
+ ```
126
+
127
+ ### Vite / Create React App
128
+
129
+ No extra config needed. Wrap your root component with `<DecaneKit>` and use hooks anywhere inside it.
130
+
131
+ ---
132
+
133
+ ## Configuration
134
+
135
+ All options are optional.
136
+
137
+ ```tsx
138
+ <DecaneKit config={{
139
+ // Which wallet types to show and connect
140
+ // "evm" | "solana" | "all" — default: "all"
141
+ networks: "all",
142
+
143
+ // Restrict which built-in chains are available
144
+ // Uses CAIP-2 IDs. Custom chains (via `chains`) are always included.
145
+ supportedChainIds: [CHAINS.ETHEREUM, CHAINS.POLYGON, CHAINS.ARBITRUM],
146
+
147
+ // Custom chains to register at startup
148
+ chains: [
149
+ {
150
+ id: "eip155:31337",
151
+ type: "evm",
152
+ chainId: 31337,
153
+ name: "Hardhat Local",
154
+ nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
155
+ rpcUrl: "http://localhost:8545",
156
+ isCustom: true,
157
+ },
158
+ ],
159
+
160
+ // UI theme — "light" | "dark" | "auto" — default: "auto"
161
+ theme: "auto",
162
+
163
+ // Default CAIP-2 chain ID to request when connecting
164
+ defaultChain: "eip155:1",
165
+ }}>
166
+ ```
167
+
168
+ ### `networks`
169
+
170
+ Controls which wallets appear in the selector and how multi-chain wallets connect.
171
+
172
+ ```tsx
173
+ // EVM-only dApp — Solana wallets hidden, chain picker skipped
174
+ <DecaneKit config={{ networks: "evm" }}>
175
+
176
+ // Solana-only dApp
177
+ <DecaneKit config={{ networks: "solana" }}>
178
+
179
+ // Both (default) — all wallets shown, chain picker appears for Phantom/Backpack
180
+ <DecaneKit config={{ networks: "all" }}>
181
+ ```
182
+
183
+ ### `supportedChainIds`
184
+
185
+ Restricts the chain list used throughout the SDK — the wallet selector, `provider.chains`, and `wallet_requestPermissions` all respect it.
186
+
187
+ ```tsx
188
+ import { CHAINS } from "decane-connect-kit";
189
+
190
+ <DecaneKit config={{
191
+ supportedChainIds: [CHAINS.ETHEREUM, CHAINS.BASE, CHAINS.SOLANA],
192
+ }}>
193
+ ```
194
+
195
+ ---
196
+
197
+ ## Hooks
198
+
199
+ ### `useDecane()`
200
+
201
+ Reactive state hook. Re-renders on any wallet or session change.
202
+
203
+ ```tsx
204
+ const {
205
+ // Detected wallets
206
+ wallets, // all wallets
207
+ evmWallets, // EVM-only + multi-chain
208
+ solanaWallets, // Solana-only + multi-chain
209
+ multiChainWallets, // wallets with both EVM + Solana
210
+
211
+ // Connection state
212
+ isConnected,
213
+ connectedWallet, // UnifiedWalletAccount | null
214
+ activeSession, // ActiveSession | null
215
+ activeChain, // CAIP-2 string | null
216
+
217
+ // Registered chains
218
+ chains, // ChainDescriptor[]
219
+
220
+ // Theme
221
+ theme, // "light" | "dark" | "auto"
222
+ setTheme, // (t: Theme) => void
223
+
224
+ // Actions
225
+ connect, // (wallet, preference?) => Promise<void>
226
+ disconnect, // () => Promise<void>
227
+ addChain, // (chain: ChainDescriptor) => void
228
+ removeChain, // (chainId: string) => void
229
+ } = useDecane();
230
+ ```
231
+
232
+ ### `useWalletSelector()`
233
+
234
+ Controls the wallet modal.
235
+
236
+ ```tsx
237
+ const { isOpen, open, close } = useWalletSelector();
238
+
239
+ <button onClick={open}>Connect Wallet</button>
240
+ ```
241
+
242
+ ### `useSignMessage()`
243
+
244
+ ```tsx
245
+ const { signMessage, signature, loading, error, reset } = useSignMessage();
246
+
247
+ // Uses active chain and account automatically
248
+ await signMessage("Hello world");
249
+
250
+ // Override chain or account
251
+ await signMessage("Hello world", { chainId: CHAINS.POLYGON, account: "0xAbc…" });
252
+ ```
253
+
254
+ ### `useSendTransaction()`
255
+
256
+ ```tsx
257
+ const { sendTransaction, txHash, loading, error, reset } = useSendTransaction();
258
+
259
+ await sendTransaction({
260
+ to: "0xRecipient",
261
+ value: "1000000000000000", // wei as decimal string or hex
262
+ data: "0x", // optional calldata
263
+ gas: "21000", // optional
264
+ });
265
+
266
+ // Override chain
267
+ await sendTransaction({ to: "0xRecipient" }, { chainId: CHAINS.ARBITRUM });
268
+ ```
269
+
270
+ ### `useSignTypedData()`
271
+
272
+ EVM only. Throws `DecaneErrorCode.EVM_ONLY_METHOD` if called on a Solana chain.
273
+
274
+ ```tsx
275
+ const { signTypedData, signature, loading, error, reset } = useSignTypedData();
276
+
277
+ await signTypedData({
278
+ domain: { name: "MyApp", version: "1", chainId: 1 },
279
+ types: { Permit: [{ name: "owner", type: "address" }, …] },
280
+ message: { owner: "0xAbc…", spender: "0xDef…", value: "1000" },
281
+ });
282
+ ```
283
+
284
+ ### `useChainSwitch()`
285
+
286
+ Switch the active chain within the current session.
287
+
288
+ ```tsx
289
+ const { switchChain, activeChain, grantedChains, loading, error } = useChainSwitch();
290
+
291
+ // grantedChains — CAIP-2 IDs granted in the current session
292
+ // switchChain requests permissions for the new chain
293
+ await switchChain(CHAINS.POLYGON);
294
+ ```
295
+
296
+ ---
297
+
298
+ ## Chain constants
299
+
300
+ Use `CHAINS` instead of bare strings to get autocomplete and catch typos at compile time.
301
+
302
+ ```tsx
303
+ import { CHAINS, type ChainId } from "decane-connect-kit";
304
+
305
+ // Tier-1
306
+ CHAINS.ETHEREUM // "eip155:1"
307
+ CHAINS.BASE // "eip155:8453"
308
+ CHAINS.ARBITRUM // "eip155:42161"
309
+ CHAINS.OPTIMISM // "eip155:10"
310
+ CHAINS.BNB // "eip155:56"
311
+ CHAINS.POLYGON // "eip155:137"
312
+
313
+ // Extended EVM (selected)
314
+ CHAINS.ABSTRACT // "eip155:2741"
315
+ CHAINS.APE_CHAIN // "eip155:33139"
316
+ CHAINS.AVALANCHE // "eip155:43114"
317
+ CHAINS.BERACHAIN // "eip155:80094"
318
+ CHAINS.BLAST // "eip155:81457"
319
+ CHAINS.CELO // "eip155:42220"
320
+ CHAINS.GNOSIS // "eip155:100"
321
+ CHAINS.LINEA // "eip155:59144"
322
+ CHAINS.MANTLE // "eip155:5000"
323
+ CHAINS.SCROLL // "eip155:534352"
324
+ CHAINS.SONIC // "eip155:146"
325
+ CHAINS.TAIKO // "eip155:167000"
326
+ CHAINS.UNICHAIN // "eip155:130"
327
+ CHAINS.WORLD_CHAIN // "eip155:480"
328
+ CHAINS.ZKSYNC_ERA // "eip155:324"
329
+ CHAINS.ZORA // "eip155:7777777"
330
+ // …58 chains total — see Built-in chains table below
331
+
332
+ // Dev
333
+ CHAINS.LOCALHOST // "eip155:31337"
334
+
335
+ // Solana
336
+ CHAINS.SOLANA // "solana:mainnet"
337
+ CHAINS.SOLANA_DEVNET // "solana:devnet"
338
+ ```
339
+
340
+ `ChainId` is the union type of all built-in chain ID strings.
341
+
342
+ ---
343
+
344
+ ## Custom chains
345
+
346
+ Add any EVM chain (local Hardhat, private L2) or Solana network at runtime.
347
+
348
+ ```tsx
349
+ const { addChain, removeChain } = useDecane();
350
+
351
+ // Add a local Hardhat node
352
+ addChain({
353
+ id: "eip155:31337",
354
+ type: "evm",
355
+ chainId: 31337,
356
+ name: "Hardhat Local",
357
+ nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
358
+ rpcUrl: "http://localhost:8545",
359
+ isCustom: true,
360
+ });
361
+
362
+ // Add a custom Solana network
363
+ addChain({
364
+ id: "solana:localnet",
365
+ type: "solana",
366
+ name: "Solana Localnet",
367
+ nativeCurrency: { name: "SOL", symbol: "SOL", decimals: 9 },
368
+ rpcUrl: "http://localhost:8899",
369
+ isCustom: true,
370
+ });
371
+
372
+ // Remove a custom chain (built-in chains cannot be removed)
373
+ removeChain("eip155:31337");
374
+ ```
375
+
376
+ Custom chains can also be registered at init via `config.chains`. When `addChain` is called while a wallet is connected, it automatically calls `wallet_addEthereumChain` on the wallet so it knows about the network.
377
+
378
+ ---
379
+
380
+ ## Error handling
381
+
382
+ All hooks return `error: DecaneError | null`. Branch on `error.code` instead of parsing error strings.
383
+
384
+ ```tsx
385
+ import { DecaneError, DecaneErrorCode } from "decane-connect-kit";
386
+
387
+ const { signMessage, error } = useSignMessage();
388
+
389
+ if (error) {
390
+ switch (error.code) {
391
+ case DecaneErrorCode.USER_REJECTED:
392
+ // User dismissed the wallet prompt — not an error, just reset
393
+ break;
394
+ case DecaneErrorCode.NO_ACTIVE_SESSION:
395
+ // Prompt the user to connect a wallet first
396
+ break;
397
+ case DecaneErrorCode.CHAIN_NOT_IN_SCOPE:
398
+ // Call switchChain() to add this chain to the session
399
+ break;
400
+ case DecaneErrorCode.SCOPE_MISMATCH:
401
+ // The account isn't in the current session
402
+ break;
403
+ default:
404
+ console.error(error.message);
405
+ }
406
+ }
407
+ ```
408
+
409
+ ### Error codes
410
+
411
+ | Code | Value | When thrown |
412
+ |------|-------|-------------|
413
+ | `USER_REJECTED` | 4001 | User dismissed the wallet prompt |
414
+ | `UNAUTHORIZED` | 4100 | Wallet refused the request |
415
+ | `NO_ACTIVE_SESSION` | 5001 | Signing called before connecting |
416
+ | `SCOPE_MISMATCH` | 5002 | Account not in session scope |
417
+ | `SESSION_EXPIRED` | 5003 | Session is no longer valid |
418
+ | `UNSUPPORTED_CHAIN` | 5010 | Chain ID not in registry |
419
+ | `CHAIN_NOT_IN_SCOPE` | 5011 | Chain not granted in session |
420
+ | `INVALID_CHAIN_CONFIG` | 5012 | Custom chain descriptor is malformed |
421
+ | `NO_WALLET_CONNECTED` | 5020 | Action requires a connected wallet |
422
+ | `WALLET_DISCONNECTED` | 5021 | Wallet disconnected mid-session |
423
+ | `PROVIDER_ERROR` | 5022 | Underlying wallet threw an error |
424
+ | `METHOD_NOT_SUPPORTED` | 5030 | Method not recognised |
425
+ | `EVM_ONLY_METHOD` | 5031 | `wallet_signTypedData` on Solana chain |
426
+
427
+ ---
428
+
429
+ ## Theme
430
+
431
+ The modal theme is controlled through `DecaneKit` config and toggled at runtime via `useDecane`.
432
+
433
+ ```tsx
434
+ // Follow system preference (default)
435
+ <DecaneKit config={{ theme: "auto" }}>
436
+
437
+ // Force dark
438
+ <DecaneKit config={{ theme: "dark" }}>
439
+ ```
440
+
441
+ ```tsx
442
+ // Toggle at runtime — the modal follows automatically
443
+ const { theme, setTheme } = useDecane();
444
+
445
+ <button onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>
446
+ Toggle theme
447
+ </button>
448
+ ```
449
+
450
+ To apply the same tokens to your own app UI, the SDK injects CSS custom properties into `document.head` whenever the modal opens:
451
+
452
+ ```css
453
+ var(--dc-bg)
454
+ var(--dc-surface)
455
+ var(--dc-border)
456
+ var(--dc-text)
457
+ var(--dc-text-secondary)
458
+ var(--dc-text-muted)
459
+ var(--dc-accent) /* gold */
460
+ var(--dc-accent-hover)
461
+ var(--dc-accent-text)
462
+ var(--dc-danger)
463
+ var(--dc-success)
464
+ ```
465
+
466
+ ---
467
+
468
+ ## TypeScript
469
+
470
+ The package ships full type declarations. Key types:
471
+
472
+ ```ts
473
+ import type {
474
+ DecaneProvider, // full provider interface
475
+ DecaneConfig, // config passed to <DecaneKit>
476
+ UnifiedWalletAccount, // a detected wallet entry
477
+ ChainDescriptor, // chain definition object
478
+ ActiveSession, // CAIP-25 session state
479
+ ChainScope, // methods + accounts for one chain
480
+ ChainType, // "evm" | "solana"
481
+ ChainId, // union of all built-in CAIP-2 chain IDs
482
+ Theme, // "light" | "dark" | "auto"
483
+ TxParams, // params for useSendTransaction
484
+ CAIP25Method, // all supported method strings
485
+ } from "decane-connect-kit";
486
+ ```
487
+
488
+ ---
489
+
490
+ ## What is not included
491
+
492
+ - Social sign-in or email login
493
+ - Any backend, server, or API calls
494
+ - `window.ethereum` — detection uses EIP-6963 exclusively
495
+ - Private key or seed phrase management
496
+ - State management libraries (Redux, Zustand) — pure React context
497
+
498
+ ---
499
+
500
+ ## Built-in chains
501
+
502
+ The following chains are available out of the box. Pass `supportedChainIds` in config to restrict to a subset.
503
+
504
+ ### Tier-1 EVM
505
+
506
+ | Name | CAIP-2 ID | `CHAINS` constant |
507
+ |------|-----------|-------------------|
508
+ | Ethereum | `eip155:1` | `CHAINS.ETHEREUM` |
509
+ | Base | `eip155:8453` | `CHAINS.BASE` |
510
+ | Arbitrum One | `eip155:42161` | `CHAINS.ARBITRUM` |
511
+ | Optimism | `eip155:10` | `CHAINS.OPTIMISM` |
512
+ | BNB Chain | `eip155:56` | `CHAINS.BNB` |
513
+ | Polygon | `eip155:137` | `CHAINS.POLYGON` |
514
+
515
+ ### Extended EVM
516
+
517
+ | Name | CAIP-2 ID | `CHAINS` constant |
518
+ |------|-----------|-------------------|
519
+ | Abstract | `eip155:2741` | `CHAINS.ABSTRACT` |
520
+ | Ancient8 | `eip155:888888888` | `CHAINS.ANCIENT8` |
521
+ | Animechain | `eip155:69000` | `CHAINS.ANIMECHAIN` |
522
+ | ApeChain | `eip155:33139` | `CHAINS.APE_CHAIN` |
523
+ | Arbitrum Nova | `eip155:42170` | `CHAINS.ARBITRUM_NOVA` |
524
+ | Avalanche | `eip155:43114` | `CHAINS.AVALANCHE` |
525
+ | B3 | `eip155:8333` | `CHAINS.B3` |
526
+ | Berachain | `eip155:80094` | `CHAINS.BERACHAIN` |
527
+ | Blast | `eip155:81457` | `CHAINS.BLAST` |
528
+ | BOB | `eip155:60808` | `CHAINS.BOB` |
529
+ | Boba Network | `eip155:288` | `CHAINS.BOBA` |
530
+ | Celo | `eip155:42220` | `CHAINS.CELO` |
531
+ | Corn | `eip155:21000000` | `CHAINS.CORN` |
532
+ | Cronos | `eip155:25` | `CHAINS.CRONOS` |
533
+ | Cyber | `eip155:7560` | `CHAINS.CYBER` |
534
+ | Degen | `eip155:666666666` | `CHAINS.DEGEN` |
535
+ | Flow EVM | `eip155:747` | `CHAINS.FLOW_EVM` |
536
+ | Gnosis | `eip155:100` | `CHAINS.GNOSIS` |
537
+ | Gunz | `eip155:49321` | `CHAINS.GUNZ` |
538
+ | Hemi | `eip155:43111` | `CHAINS.HEMI` |
539
+ | HyperEVM | `eip155:999` | `CHAINS.HYPER_EVM` |
540
+ | Ink | `eip155:57073` | `CHAINS.INK` |
541
+ | Linea | `eip155:59144` | `CHAINS.LINEA` |
542
+ | Lisk | `eip155:1135` | `CHAINS.LISK` |
543
+ | Manta Pacific | `eip155:169` | `CHAINS.MANTA_PACIFIC` |
544
+ | Mantle | `eip155:5000` | `CHAINS.MANTLE` |
545
+ | MegaETH | `eip155:6342` | `CHAINS.MEGA_ETH` |
546
+ | Metis | `eip155:1088` | `CHAINS.METIS` |
547
+ | Mode | `eip155:34443` | `CHAINS.MODE` |
548
+ | Monad | `eip155:10143` | `CHAINS.MONAD` |
549
+ | Morph | `eip155:2818` | `CHAINS.MORPH` |
550
+ | Plume | `eip155:98866` | `CHAINS.PLUME` |
551
+ | RARI | `eip155:1380012617` | `CHAINS.RARI` |
552
+ | Redstone | `eip155:690` | `CHAINS.REDSTONE` |
553
+ | Ronin | `eip155:2020` | `CHAINS.RONIN` |
554
+ | Scroll | `eip155:534352` | `CHAINS.SCROLL` |
555
+ | Sei | `eip155:1329` | `CHAINS.SEI` |
556
+ | Shape | `eip155:360` | `CHAINS.SHAPE` |
557
+ | Somnia | `eip155:50312` | `CHAINS.SOMNIA` |
558
+ | Soneium | `eip155:1868` | `CHAINS.SONEIUM` |
559
+ | Sonic | `eip155:146` | `CHAINS.SONIC` |
560
+ | Story | `eip155:1514` | `CHAINS.STORY` |
561
+ | Superposition | `eip155:55244` | `CHAINS.SUPERPOSITION` |
562
+ | Superseed | `eip155:5330` | `CHAINS.SUPERSEED` |
563
+ | SwellChain | `eip155:1923` | `CHAINS.SWELL_CHAIN` |
564
+ | Taiko | `eip155:167000` | `CHAINS.TAIKO` |
565
+ | Unichain | `eip155:130` | `CHAINS.UNICHAIN` |
566
+ | World Chain | `eip155:480` | `CHAINS.WORLD_CHAIN` |
567
+ | ZERO | `eip155:543210` | `CHAINS.ZERO` |
568
+ | Zircuit | `eip155:48900` | `CHAINS.ZIRCUIT` |
569
+ | zkSync Era | `eip155:324` | `CHAINS.ZKSYNC_ERA` |
570
+ | Zora | `eip155:7777777` | `CHAINS.ZORA` |
571
+
572
+ ### Dev / local
573
+
574
+ | Name | CAIP-2 ID | `CHAINS` constant |
575
+ |------|-----------|-------------------|
576
+ | Localhost | `eip155:31337` | `CHAINS.LOCALHOST` |
577
+
578
+ ### Solana
579
+
580
+ | Name | CAIP-2 ID | `CHAINS` constant |
581
+ |------|-----------|-------------------|
582
+ | Solana | `solana:mainnet` | `CHAINS.SOLANA` |
583
+ | Solana Devnet | `solana:devnet` | `CHAINS.SOLANA_DEVNET` |