@solana/connector 0.1.3 → 0.1.5
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/LICENSE +21 -0
- package/README.md +685 -1071
- package/dist/chunk-3STZXVXD.mjs +2185 -0
- package/dist/chunk-3STZXVXD.mjs.map +1 -0
- package/dist/chunk-I64FD2EH.js +312 -0
- package/dist/chunk-I64FD2EH.js.map +1 -0
- package/dist/{chunk-TIW3EQPC.js → chunk-JUZVCBAI.js} +127 -104
- package/dist/chunk-JUZVCBAI.js.map +1 -0
- package/dist/{chunk-7CKCRY25.js → chunk-NQXK7PGX.js} +75 -79
- package/dist/chunk-NQXK7PGX.js.map +1 -0
- package/dist/{chunk-HPENTIPE.mjs → chunk-QKVL45F6.mjs} +57 -57
- package/dist/chunk-QKVL45F6.mjs.map +1 -0
- package/dist/chunk-QL3IT3TS.mjs +299 -0
- package/dist/chunk-QL3IT3TS.mjs.map +1 -0
- package/dist/chunk-ULUYX23Q.js +2213 -0
- package/dist/chunk-ULUYX23Q.js.map +1 -0
- package/dist/{chunk-TKJSKXSA.mjs → chunk-VMSZJPR5.mjs} +42 -20
- package/dist/chunk-VMSZJPR5.mjs.map +1 -0
- package/dist/compat.d.mts +4 -2
- package/dist/compat.d.ts +4 -2
- package/dist/compat.js +3 -3
- package/dist/compat.mjs +1 -1
- package/dist/headless.d.mts +146 -18
- package/dist/headless.d.ts +146 -18
- package/dist/headless.js +144 -111
- package/dist/headless.mjs +3 -2
- package/dist/index.d.mts +6 -5
- package/dist/index.d.ts +6 -5
- package/dist/index.js +207 -126
- package/dist/index.mjs +4 -3
- package/dist/react.d.mts +707 -67
- package/dist/react.d.ts +707 -67
- package/dist/react.js +64 -16
- package/dist/react.mjs +2 -2
- package/dist/{transaction-signer-D3csM_Mf.d.mts → transaction-signer-D9d8nxwb.d.mts} +3 -1
- package/dist/{transaction-signer-D3csM_Mf.d.ts → transaction-signer-D9d8nxwb.d.ts} +3 -1
- package/dist/{wallet-standard-shim-Cg0GVGwu.d.mts → wallet-standard-shim--YcrQNRt.d.ts} +216 -6
- package/dist/{wallet-standard-shim-C1tisl9S.d.ts → wallet-standard-shim-Dx7H8Ctf.d.mts} +216 -6
- package/package.json +16 -12
- package/dist/chunk-5ZUVZZWU.mjs +0 -180
- package/dist/chunk-5ZUVZZWU.mjs.map +0 -1
- package/dist/chunk-7CKCRY25.js.map +0 -1
- package/dist/chunk-FTD7F7CS.js +0 -314
- package/dist/chunk-FTD7F7CS.js.map +0 -1
- package/dist/chunk-HPENTIPE.mjs.map +0 -1
- package/dist/chunk-MPZFJEJK.mjs +0 -298
- package/dist/chunk-MPZFJEJK.mjs.map +0 -1
- package/dist/chunk-SMUUAKC3.js +0 -186
- package/dist/chunk-SMUUAKC3.js.map +0 -1
- package/dist/chunk-TIW3EQPC.js.map +0 -1
- package/dist/chunk-TKJSKXSA.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -3,255 +3,139 @@ title: @solana/connector
|
|
|
3
3
|
description: Production-ready wallet connector for Solana applications
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
**ConnectorKit is your Solana wallet infrastructure.** A headless, framework-agnostic wallet connector built on Wallet Standard
|
|
6
|
+
**ConnectorKit is your Solana wallet infrastructure.** A headless, framework-agnostic wallet connector built on Wallet Standard that just work.
|
|
7
7
|
|
|
8
8
|

|
|
9
|
-

|
|
10
10
|

|
|
11
|
-

|
|
12
11
|
|
|
13
12
|
### Why ConnectorKit?
|
|
14
13
|
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
18
|
-
- **
|
|
19
|
-
- **
|
|
20
|
-
|
|
21
|
-
### Architecture
|
|
22
|
-
|
|
23
|
-
- **Language**: TypeScript with full type safety
|
|
24
|
-
- **Standard**: Built on Wallet Standard protocol
|
|
25
|
-
- **Frameworks**: React hooks + headless core for any framework
|
|
26
|
-
- **Mobile**: Solana Mobile Wallet Adapter integration
|
|
27
|
-
- **Storage**: Enhanced persistence with SSR support
|
|
28
|
-
|
|
29
|
-
### Features
|
|
30
|
-
|
|
31
|
-
- Wallet Standard compliance with multi-wallet support
|
|
32
|
-
- Clean transaction signer abstraction
|
|
33
|
-
- Comprehensive event system for analytics
|
|
34
|
-
- Connection pooling for better performance
|
|
35
|
-
- Health checks and diagnostics
|
|
36
|
-
- Auto-connect and account management
|
|
37
|
-
- Network switching (mainnet, devnet, testnet, custom)
|
|
38
|
-
- Browser polyfills for universal compatibility
|
|
39
|
-
- Debug panel for development
|
|
40
|
-
- Wallet Adapter compatibility bridge
|
|
41
|
-
- 80%+ test coverage
|
|
14
|
+
- **Wallet Standard First**: Built on the official Wallet Standard protocol for universal wallet compatibility
|
|
15
|
+
- **Modern & Legacy Support**: Works with both `@solana/kit` (web3.js 2.0) and `@solana/web3.js` (legacy)
|
|
16
|
+
- **Framework Agnostic**: React hooks + headless core for Vue, Svelte, or vanilla JavaScript
|
|
17
|
+
- **Production Ready**: Event system for analytics, health checks for diagnostics, error boundaries for React apps
|
|
18
|
+
- **Enhanced Storage**: Automatic validation, SSR fallback, and error handling out of the box
|
|
19
|
+
- **Mobile Support**: Built-in Solana Mobile Wallet Adapter integration
|
|
42
20
|
|
|
43
21
|
---
|
|
44
22
|
|
|
45
23
|
## Quick Start
|
|
46
24
|
|
|
47
|
-
Install
|
|
25
|
+
### 1. Install
|
|
48
26
|
|
|
49
27
|
```bash
|
|
50
|
-
pnpm add @solana/connector
|
|
51
|
-
# or
|
|
52
28
|
npm install @solana/connector
|
|
53
29
|
# or
|
|
30
|
+
pnpm add @solana/connector
|
|
31
|
+
# or
|
|
54
32
|
yarn add @solana/connector
|
|
33
|
+
# or
|
|
34
|
+
bun add @solana/connector
|
|
55
35
|
```
|
|
56
36
|
|
|
57
|
-
|
|
37
|
+
### 2. Setup Provider (once in your app root)
|
|
58
38
|
|
|
59
39
|
```typescript
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// 3. Sign transactions
|
|
99
|
-
function SendTransaction() {
|
|
100
|
-
const { signer, ready } = useTransactionSigner();
|
|
101
|
-
|
|
102
|
-
const handleSend = async () => {
|
|
103
|
-
const signature = await signer.signAndSendTransaction(transaction);
|
|
104
|
-
};
|
|
40
|
+
'use client';
|
|
41
|
+
|
|
42
|
+
import { useMemo } from 'react';
|
|
43
|
+
import { AppProvider } from '@solana/connector/react';
|
|
44
|
+
import { getDefaultConfig, getDefaultMobileConfig } from '@solana/connector/headless';
|
|
45
|
+
|
|
46
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
47
|
+
const connectorConfig = useMemo(() => {
|
|
48
|
+
// Optional: Get custom RPC URL from environment variable
|
|
49
|
+
const customRpcUrl = process.env.SOLANA_RPC_URL;
|
|
50
|
+
|
|
51
|
+
// Optional: Create custom cluster configuration
|
|
52
|
+
const clusters = customRpcUrl
|
|
53
|
+
? [
|
|
54
|
+
{
|
|
55
|
+
id: 'solana:mainnet' as const,
|
|
56
|
+
label: 'Mainnet (Custom RPC)',
|
|
57
|
+
name: 'mainnet-beta' as const,
|
|
58
|
+
url: customRpcUrl,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: 'solana:devnet' as const,
|
|
62
|
+
label: 'Devnet',
|
|
63
|
+
name: 'devnet' as const,
|
|
64
|
+
url: 'https://api.devnet.solana.com',
|
|
65
|
+
},
|
|
66
|
+
]
|
|
67
|
+
: undefined;
|
|
68
|
+
|
|
69
|
+
return getDefaultConfig({
|
|
70
|
+
appName: 'My App',
|
|
71
|
+
appUrl: 'https://myapp.com',
|
|
72
|
+
autoConnect: true,
|
|
73
|
+
enableMobile: true,
|
|
74
|
+
clusters,
|
|
75
|
+
});
|
|
76
|
+
}, []);
|
|
105
77
|
|
|
106
|
-
|
|
78
|
+
const mobile = useMemo(
|
|
79
|
+
() =>
|
|
80
|
+
getDefaultMobileConfig({
|
|
81
|
+
appName: 'My App',
|
|
82
|
+
appUrl: 'https://myapp.com',
|
|
83
|
+
}),
|
|
84
|
+
[],
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<AppProvider connectorConfig={connectorConfig} mobile={mobile}>
|
|
89
|
+
{children}
|
|
90
|
+
</AppProvider>
|
|
91
|
+
);
|
|
107
92
|
}
|
|
108
93
|
```
|
|
109
94
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
**[→ Transaction Signer](#transaction-signer)** - Sign and send transactions
|
|
113
|
-
|
|
114
|
-
**[→ Event System](#event-system)** - Track wallet events for analytics
|
|
115
|
-
|
|
116
|
-
**[→ Framework Guides](#headless-client-vue-svelte-vanilla-js)** - Use with Vue, Svelte, or vanilla JS
|
|
117
|
-
|
|
118
|
-
---
|
|
119
|
-
|
|
120
|
-
## Documentation
|
|
121
|
-
|
|
122
|
-
### Getting Started
|
|
123
|
-
|
|
124
|
-
- [Installation](#quick-start)
|
|
125
|
-
- [Quick Start Guide](#quick-start)
|
|
126
|
-
- [TypeScript SDK](#typescript-sdk)
|
|
127
|
-
- [Core Hooks](#core-hooks)
|
|
128
|
-
- [Local Development](#local-development)
|
|
129
|
-
|
|
130
|
-
### Production Features
|
|
131
|
-
|
|
132
|
-
- [Transaction Signer](#transaction-signer) - Clean signing API
|
|
133
|
-
- [Event System](#event-system) - Analytics and monitoring
|
|
134
|
-
- [Debug Panel](#debug-panel) - Development tools
|
|
135
|
-
- [Connection Pooling](#connection-pooling) - Performance optimization
|
|
136
|
-
- [Health Checks](#health-checks) - Diagnostics
|
|
137
|
-
- [Wallet Adapter Compatibility](#wallet-adapter-compatibility) - Drop-in replacement
|
|
138
|
-
|
|
139
|
-
### Configuration & Usage
|
|
140
|
-
|
|
141
|
-
- [Configuration](#configuration) - Setup options
|
|
142
|
-
- [Network Selection](#network-selection) - Cluster management
|
|
143
|
-
- [Custom Storage](#custom-storage) - Persistence options
|
|
144
|
-
- [Mobile Wallet Adapter](#mobile-wallet-adapter) - Mobile support
|
|
145
|
-
- [Advanced Usage](#advanced-usage) - Framework-specific guides
|
|
146
|
-
|
|
147
|
-
### Reference
|
|
148
|
-
|
|
149
|
-
- [Complete API Reference](#complete-api-reference)
|
|
150
|
-
- [Configuration Functions](#configuration-functions)
|
|
151
|
-
- [Transaction Signing API](#transaction-signing-api-new)
|
|
152
|
-
- [Event System API](#event-system-api-new)
|
|
153
|
-
- [Health Check API](#health-check-api-new)
|
|
154
|
-
- [Connection Pool API](#connection-pool-api-new)
|
|
155
|
-
- [Utility Functions](#utility-functions)
|
|
156
|
-
- [TypeScript Types](#types)
|
|
157
|
-
|
|
158
|
-
### Testing & Performance
|
|
159
|
-
|
|
160
|
-
- [Testing](#testing) - Test suite and utilities
|
|
161
|
-
- [Performance](#performance) - Bundle size and optimization
|
|
162
|
-
- [Browser Compatibility](#browser-compatibility)
|
|
163
|
-
|
|
164
|
-
### Migration & Examples
|
|
165
|
-
|
|
166
|
-
- [Migration from wallet-adapter](#migration-from-solanawallet-adapter)
|
|
167
|
-
- [Examples](#examples)
|
|
168
|
-
- [Supported Wallets](#supported-wallets)
|
|
169
|
-
|
|
170
|
-
---
|
|
171
|
-
|
|
172
|
-
## TypeScript SDK
|
|
173
|
-
|
|
174
|
-
ConnectorKit provides React hooks and a headless core for any framework:
|
|
95
|
+
### 3. Use Hooks (in any component)
|
|
175
96
|
|
|
176
97
|
```typescript
|
|
177
|
-
|
|
178
|
-
import { useConnector, useTransactionSigner, useAccount } from '@solana/connector';
|
|
179
|
-
|
|
180
|
-
// Headless (Vue, Svelte, vanilla JS)
|
|
181
|
-
import { ConnectorClient, getDefaultConfig } from '@solana/connector/headless';
|
|
182
|
-
|
|
183
|
-
const client = new ConnectorClient(getDefaultConfig({ appName: 'My App' }));
|
|
184
|
-
await client.select('Phantom');
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
**[→ API Reference](#complete-api-reference)** - Full TypeScript API documentation
|
|
188
|
-
|
|
189
|
-
**[→ Core Hooks](#core-hooks)** - React hooks reference
|
|
98
|
+
'use client';
|
|
190
99
|
|
|
191
|
-
|
|
100
|
+
import { useConnector, useAccount } from '@solana/connector';
|
|
192
101
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
### Prerequisites
|
|
198
|
-
|
|
199
|
-
- Node.js 20+ (or 18+)
|
|
200
|
-
- pnpm, npm, or yarn
|
|
201
|
-
- TypeScript 5.0+
|
|
202
|
-
|
|
203
|
-
### Installation
|
|
204
|
-
|
|
205
|
-
```bash
|
|
206
|
-
git clone https://github.com/your-org/connectorkit.git
|
|
207
|
-
cd connectorkit
|
|
208
|
-
pnpm install
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
### Build
|
|
212
|
-
|
|
213
|
-
```bash
|
|
214
|
-
pnpm build
|
|
215
|
-
```
|
|
102
|
+
export function ConnectButton() {
|
|
103
|
+
const { wallets, select, disconnect, connected, connecting, selectedWallet, selectedAccount } = useConnector();
|
|
104
|
+
const { address, formatted, copy } = useAccount();
|
|
216
105
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
cd examples/react
|
|
221
|
-
pnpm dev
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
Or run the Vite example:
|
|
225
|
-
|
|
226
|
-
```bash
|
|
227
|
-
cd examples/vite
|
|
228
|
-
pnpm dev
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
### Local Testing
|
|
232
|
-
|
|
233
|
-
Run all tests:
|
|
234
|
-
|
|
235
|
-
```bash
|
|
236
|
-
pnpm test
|
|
237
|
-
```
|
|
106
|
+
if (connecting) {
|
|
107
|
+
return <button disabled>Connecting...</button>;
|
|
108
|
+
}
|
|
238
109
|
|
|
239
|
-
|
|
110
|
+
if (!connected) {
|
|
111
|
+
return (
|
|
112
|
+
<div>
|
|
113
|
+
{wallets.map(w => (
|
|
114
|
+
<button key={w.wallet.name} onClick={() => select(w.wallet.name)}>
|
|
115
|
+
Connect {w.wallet.name}
|
|
116
|
+
</button>
|
|
117
|
+
))}
|
|
118
|
+
</div>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
240
121
|
|
|
241
|
-
|
|
242
|
-
|
|
122
|
+
return (
|
|
123
|
+
<div>
|
|
124
|
+
<button onClick={copy}>{formatted}</button>
|
|
125
|
+
<button onClick={disconnect}>Disconnect</button>
|
|
126
|
+
</div>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
243
129
|
```
|
|
244
130
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
```bash
|
|
248
|
-
pnpm test:coverage
|
|
249
|
-
```
|
|
131
|
+
**That's it!** You're ready to go. Everything else below is optional.
|
|
250
132
|
|
|
251
133
|
---
|
|
252
134
|
|
|
253
135
|
## Core Hooks
|
|
254
136
|
|
|
137
|
+
These are the main hooks you'll use in your components.
|
|
138
|
+
|
|
255
139
|
### `useConnector()`
|
|
256
140
|
|
|
257
141
|
Main hook for wallet connection and state.
|
|
@@ -264,6 +148,7 @@ function Component() {
|
|
|
264
148
|
// State
|
|
265
149
|
wallets, // WalletInfo[] - All available wallets
|
|
266
150
|
selectedWallet, // Wallet | null - Currently connected wallet
|
|
151
|
+
selectedAccount, // string | null - Currently selected account address
|
|
267
152
|
accounts, // AccountInfo[] - Connected accounts
|
|
268
153
|
connected, // boolean - Connection status
|
|
269
154
|
connecting, // boolean - Connecting in progress
|
|
@@ -275,6 +160,41 @@ function Component() {
|
|
|
275
160
|
}
|
|
276
161
|
```
|
|
277
162
|
|
|
163
|
+
**Real Example** - Connect Button with wallet selection:
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
'use client';
|
|
167
|
+
|
|
168
|
+
import { useConnector } from '@solana/connector';
|
|
169
|
+
import { useState } from 'react';
|
|
170
|
+
|
|
171
|
+
export function ConnectButton() {
|
|
172
|
+
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
173
|
+
const { connected, connecting, selectedWallet, selectedAccount, wallets, select } = useConnector();
|
|
174
|
+
|
|
175
|
+
if (connecting) {
|
|
176
|
+
return <button disabled>Connecting...</button>;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (connected && selectedAccount && selectedWallet) {
|
|
180
|
+
const shortAddress = `${selectedAccount.slice(0, 4)}...${selectedAccount.slice(-4)}`;
|
|
181
|
+
return (
|
|
182
|
+
<div>
|
|
183
|
+
<span>{shortAddress}</span>
|
|
184
|
+
<button onClick={() => select(selectedWallet.name)}>Switch Wallet</button>
|
|
185
|
+
</div>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<>
|
|
191
|
+
<button onClick={() => setIsModalOpen(true)}>Connect Wallet</button>
|
|
192
|
+
{/* Wallet selection modal */}
|
|
193
|
+
</>
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
278
198
|
### `useAccount()`
|
|
279
199
|
|
|
280
200
|
Hook for working with the connected account.
|
|
@@ -295,6 +215,35 @@ function Component() {
|
|
|
295
215
|
}
|
|
296
216
|
```
|
|
297
217
|
|
|
218
|
+
**Real Example** - Account Switcher for multi-account wallets:
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
'use client';
|
|
222
|
+
|
|
223
|
+
import { useAccount } from '@solana/connector';
|
|
224
|
+
|
|
225
|
+
export function AccountSwitcher() {
|
|
226
|
+
const { accounts, address, selectAccount, connected } = useAccount();
|
|
227
|
+
|
|
228
|
+
if (!connected || accounts.length <= 1) {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return (
|
|
233
|
+
<select
|
|
234
|
+
value={address || ''}
|
|
235
|
+
onChange={e => selectAccount(e.target.value)}
|
|
236
|
+
>
|
|
237
|
+
{accounts.map(account => (
|
|
238
|
+
<option key={account.address} value={account.address}>
|
|
239
|
+
{account.address.slice(0, 6)}...{account.address.slice(-6)}
|
|
240
|
+
</option>
|
|
241
|
+
))}
|
|
242
|
+
</select>
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
298
247
|
### `useCluster()`
|
|
299
248
|
|
|
300
249
|
Hook for managing Solana network/cluster.
|
|
@@ -315,359 +264,583 @@ function Component() {
|
|
|
315
264
|
}
|
|
316
265
|
```
|
|
317
266
|
|
|
267
|
+
**Real Example** - Network Selector:
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
'use client';
|
|
271
|
+
|
|
272
|
+
import { useCluster } from '@solana/connector';
|
|
273
|
+
|
|
274
|
+
export function ClusterSelector() {
|
|
275
|
+
const { cluster, clusters, setCluster } = useCluster();
|
|
276
|
+
|
|
277
|
+
return (
|
|
278
|
+
<select
|
|
279
|
+
value={cluster?.id || ''}
|
|
280
|
+
onChange={e => setCluster(e.target.value as SolanaClusterId)}
|
|
281
|
+
>
|
|
282
|
+
{clusters.map(c => (
|
|
283
|
+
<option key={c.id} value={c.id}>
|
|
284
|
+
{c.label}
|
|
285
|
+
</option>
|
|
286
|
+
))}
|
|
287
|
+
</select>
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### `useWalletInfo()`
|
|
293
|
+
|
|
294
|
+
Hook for accessing current wallet metadata.
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
import { useWalletInfo } from '@solana/connector';
|
|
298
|
+
|
|
299
|
+
function Component() {
|
|
300
|
+
const {
|
|
301
|
+
name, // string | null - Wallet name
|
|
302
|
+
icon, // string | undefined - Wallet icon URL
|
|
303
|
+
wallet, // WalletInfo | null - Full wallet info
|
|
304
|
+
connecting, // boolean - Connection in progress
|
|
305
|
+
} = useWalletInfo();
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
318
309
|
---
|
|
319
310
|
|
|
320
|
-
##
|
|
311
|
+
## Transaction Signing
|
|
312
|
+
|
|
313
|
+
ConnectorKit provides powerful transaction signing capabilities with support for both legacy `@solana/web3.js` and modern `@solana/kit` APIs.
|
|
321
314
|
|
|
322
|
-
###
|
|
315
|
+
### Modern API (`@solana/kit`)
|
|
323
316
|
|
|
324
|
-
|
|
317
|
+
Use `useKitTransactionSigner()` for modern, type-safe transaction building:
|
|
325
318
|
|
|
326
319
|
```typescript
|
|
327
|
-
|
|
320
|
+
'use client';
|
|
328
321
|
|
|
329
|
-
|
|
330
|
-
|
|
322
|
+
import { useState } from 'react';
|
|
323
|
+
import {
|
|
324
|
+
address,
|
|
325
|
+
createSolanaRpc,
|
|
326
|
+
pipe,
|
|
327
|
+
createTransactionMessage,
|
|
328
|
+
setTransactionMessageFeePayerSigner,
|
|
329
|
+
setTransactionMessageLifetimeUsingBlockhash,
|
|
330
|
+
appendTransactionMessageInstructions,
|
|
331
|
+
sendAndConfirmTransactionFactory,
|
|
332
|
+
getSignatureFromTransaction,
|
|
333
|
+
signTransactionMessageWithSigners,
|
|
334
|
+
createSolanaRpcSubscriptions,
|
|
335
|
+
lamports,
|
|
336
|
+
assertIsTransactionWithBlockhashLifetime,
|
|
337
|
+
} from '@solana/kit';
|
|
338
|
+
import { getTransferSolInstruction } from '@solana-program/system';
|
|
339
|
+
import { useKitTransactionSigner, useCluster, useConnectorClient, LAMPORTS_PER_SOL } from '@solana/connector';
|
|
340
|
+
|
|
341
|
+
export function ModernSolTransfer() {
|
|
342
|
+
const { signer, ready } = useKitTransactionSigner();
|
|
343
|
+
const { cluster } = useCluster();
|
|
344
|
+
const client = useConnectorClient();
|
|
345
|
+
const [signature, setSignature] = useState<string | null>(null);
|
|
331
346
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
console.log('Batch support:', capabilities.supportsBatchSigning);
|
|
347
|
+
async function handleTransfer(recipientAddress: string, amount: number) {
|
|
348
|
+
if (!signer || !client) {
|
|
349
|
+
throw new Error('Wallet not connected');
|
|
350
|
+
}
|
|
337
351
|
|
|
338
|
-
|
|
339
|
-
|
|
352
|
+
// Get RPC URL from connector client
|
|
353
|
+
const rpcUrl = client.getRpcUrl();
|
|
354
|
+
if (!rpcUrl) {
|
|
355
|
+
throw new Error('No RPC endpoint configured');
|
|
356
|
+
}
|
|
340
357
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
console.log('Success:', signature);
|
|
358
|
+
// Create RPC client using web3.js 2.0
|
|
359
|
+
const rpc = createSolanaRpc(rpcUrl);
|
|
360
|
+
const rpcSubscriptions = createSolanaRpcSubscriptions(rpcUrl.replace('http', 'ws'));
|
|
345
361
|
|
|
346
|
-
|
|
347
|
-
|
|
362
|
+
// Get recent blockhash
|
|
363
|
+
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
|
|
348
364
|
|
|
349
|
-
|
|
350
|
-
|
|
365
|
+
// Convert SOL to lamports
|
|
366
|
+
const amountInLamports = lamports(BigInt(Math.floor(amount * Number(LAMPORTS_PER_SOL))));
|
|
351
367
|
|
|
352
|
-
|
|
353
|
-
|
|
368
|
+
// Create transfer instruction
|
|
369
|
+
const transferInstruction = getTransferSolInstruction({
|
|
370
|
+
source: signer,
|
|
371
|
+
destination: address(recipientAddress),
|
|
372
|
+
amount: amountInLamports,
|
|
373
|
+
});
|
|
354
374
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
375
|
+
// Build transaction message
|
|
376
|
+
const transactionMessage = pipe(
|
|
377
|
+
createTransactionMessage({ version: 0 }),
|
|
378
|
+
tx => setTransactionMessageFeePayerSigner(signer, tx),
|
|
379
|
+
tx => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
|
|
380
|
+
tx => appendTransactionMessageInstructions([transferInstruction], tx),
|
|
381
|
+
);
|
|
361
382
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
Send Transaction
|
|
365
|
-
</button>
|
|
366
|
-
);
|
|
367
|
-
}
|
|
368
|
-
```
|
|
383
|
+
// Sign transaction
|
|
384
|
+
const signedTransaction = await signTransactionMessageWithSigners(transactionMessage);
|
|
369
385
|
|
|
370
|
-
|
|
386
|
+
// Send and confirm
|
|
387
|
+
assertIsTransactionWithBlockhashLifetime(signedTransaction);
|
|
388
|
+
await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedTransaction, {
|
|
389
|
+
commitment: 'confirmed',
|
|
390
|
+
});
|
|
371
391
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
try {
|
|
376
|
-
await signer.signAndSendTransaction(tx);
|
|
377
|
-
} catch (error) {
|
|
378
|
-
if (isTransactionSignerError(error)) {
|
|
379
|
-
switch (error.code) {
|
|
380
|
-
case 'WALLET_NOT_CONNECTED':
|
|
381
|
-
// Show connect prompt
|
|
382
|
-
break;
|
|
383
|
-
case 'FEATURE_NOT_SUPPORTED':
|
|
384
|
-
// Show unsupported feature message
|
|
385
|
-
break;
|
|
386
|
-
case 'SIGNING_FAILED':
|
|
387
|
-
// User rejected or signing failed
|
|
388
|
-
break;
|
|
389
|
-
case 'SEND_FAILED':
|
|
390
|
-
// Transaction broadcast failed
|
|
391
|
-
break;
|
|
392
|
-
}
|
|
392
|
+
const transactionSignature = getSignatureFromTransaction(signedTransaction);
|
|
393
|
+
setSignature(transactionSignature);
|
|
393
394
|
}
|
|
395
|
+
|
|
396
|
+
return (
|
|
397
|
+
<div>
|
|
398
|
+
{/* Your form UI */}
|
|
399
|
+
<button onClick={() => handleTransfer('...', 0.1)} disabled={!ready}>
|
|
400
|
+
Send SOL
|
|
401
|
+
</button>
|
|
402
|
+
{signature && <div>Transaction: {signature}</div>}
|
|
403
|
+
</div>
|
|
404
|
+
);
|
|
394
405
|
}
|
|
395
406
|
```
|
|
396
407
|
|
|
397
|
-
###
|
|
408
|
+
### Legacy API (`@solana/web3.js`)
|
|
398
409
|
|
|
399
|
-
|
|
410
|
+
Use `useTransactionSigner()` for legacy web3.js compatibility:
|
|
400
411
|
|
|
401
412
|
```typescript
|
|
402
|
-
import {
|
|
413
|
+
import { useTransactionSigner } from '@solana/connector';
|
|
414
|
+
import { Transaction, SystemProgram } from '@solana/web3.js';
|
|
403
415
|
|
|
404
|
-
function
|
|
405
|
-
const
|
|
416
|
+
function SendTransaction() {
|
|
417
|
+
const { signer, ready } = useTransactionSigner();
|
|
406
418
|
|
|
407
|
-
|
|
408
|
-
if (!
|
|
409
|
-
|
|
410
|
-
// Subscribe to events
|
|
411
|
-
const unsubscribe = client.on(event => {
|
|
412
|
-
switch (event.type) {
|
|
413
|
-
case 'wallet:connected':
|
|
414
|
-
analytics.track('Wallet Connected', {
|
|
415
|
-
wallet: event.wallet,
|
|
416
|
-
account: event.account,
|
|
417
|
-
timestamp: event.timestamp,
|
|
418
|
-
});
|
|
419
|
-
break;
|
|
420
|
-
|
|
421
|
-
case 'wallet:disconnected':
|
|
422
|
-
analytics.track('Wallet Disconnected');
|
|
423
|
-
break;
|
|
424
|
-
|
|
425
|
-
case 'cluster:changed':
|
|
426
|
-
analytics.track('Network Changed', {
|
|
427
|
-
from: event.previousCluster,
|
|
428
|
-
to: event.cluster,
|
|
429
|
-
});
|
|
430
|
-
break;
|
|
431
|
-
|
|
432
|
-
case 'error':
|
|
433
|
-
errorTracker.captureException(event.error, {
|
|
434
|
-
context: event.context,
|
|
435
|
-
});
|
|
436
|
-
break;
|
|
437
|
-
}
|
|
438
|
-
});
|
|
419
|
+
const handleSend = async () => {
|
|
420
|
+
if (!signer) return;
|
|
439
421
|
|
|
440
|
-
|
|
441
|
-
|
|
422
|
+
const transaction = new Transaction().add(
|
|
423
|
+
SystemProgram.transfer({
|
|
424
|
+
fromPubkey: signer.address,
|
|
425
|
+
toPubkey: recipientPubkey,
|
|
426
|
+
lamports: 1000000,
|
|
427
|
+
})
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
const signature = await signer.signAndSendTransaction(transaction);
|
|
431
|
+
console.log('Transaction sent:', signature);
|
|
432
|
+
};
|
|
442
433
|
|
|
443
|
-
return
|
|
434
|
+
return <button onClick={handleSend} disabled={!ready}>Send</button>;
|
|
444
435
|
}
|
|
445
436
|
```
|
|
446
437
|
|
|
447
|
-
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
## UI Elements
|
|
448
441
|
|
|
449
|
-
|
|
450
|
-
- `wallet:disconnected` - Wallet disconnected
|
|
451
|
-
- `wallet:changed` - Selected wallet changed
|
|
452
|
-
- `account:changed` - Selected account changed
|
|
453
|
-
- `cluster:changed` - Network/cluster changed
|
|
454
|
-
- `wallets:detected` - New wallets detected
|
|
455
|
-
- `connecting` - Connection attempt started
|
|
456
|
-
- `connection:failed` - Connection attempt failed
|
|
457
|
-
- `error` - Error occurred
|
|
442
|
+
ConnectorKit provides composable UI elements that handle data fetching and state management for you. Use the render prop pattern to customize the UI.
|
|
458
443
|
|
|
459
|
-
###
|
|
444
|
+
### Available Elements
|
|
460
445
|
|
|
461
|
-
|
|
446
|
+
- `BalanceElement` - Display SOL balance with refresh
|
|
447
|
+
- `ClusterElement` - Network/cluster selector
|
|
448
|
+
- `TokenListElement` - List of SPL tokens
|
|
449
|
+
- `TransactionHistoryElement` - Recent transaction history
|
|
450
|
+
- `DisconnectElement` - Disconnect button
|
|
451
|
+
- `AccountElement` - Account display and switcher
|
|
452
|
+
- `WalletListElement` - List of available wallets
|
|
453
|
+
|
|
454
|
+
### Example: Wallet Dropdown
|
|
462
455
|
|
|
463
456
|
```typescript
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
457
|
+
'use client';
|
|
458
|
+
|
|
459
|
+
import {
|
|
460
|
+
BalanceElement,
|
|
461
|
+
ClusterElement,
|
|
462
|
+
TokenListElement,
|
|
463
|
+
TransactionHistoryElement,
|
|
464
|
+
DisconnectElement,
|
|
465
|
+
} from '@solana/connector/react';
|
|
466
|
+
|
|
467
|
+
export function WalletDropdown() {
|
|
468
|
+
return (
|
|
469
|
+
<div className="wallet-dropdown">
|
|
470
|
+
{/* Balance */}
|
|
471
|
+
<BalanceElement
|
|
472
|
+
render={({ solBalance, isLoading, refetch }) => (
|
|
473
|
+
<div>
|
|
474
|
+
<div>Balance: {isLoading ? '...' : `${solBalance?.toFixed(4)} SOL`}</div>
|
|
475
|
+
<button onClick={refetch}>Refresh</button>
|
|
476
|
+
</div>
|
|
477
|
+
)}
|
|
478
|
+
/>
|
|
479
|
+
|
|
480
|
+
{/* Network Selector */}
|
|
481
|
+
<ClusterElement
|
|
482
|
+
render={({ cluster, clusters, setCluster }) => (
|
|
483
|
+
<select value={cluster?.id} onChange={e => setCluster(e.target.value)}>
|
|
484
|
+
{clusters.map(c => (
|
|
485
|
+
<option key={c.id} value={c.id}>
|
|
486
|
+
{c.label}
|
|
487
|
+
</option>
|
|
488
|
+
))}
|
|
489
|
+
</select>
|
|
490
|
+
)}
|
|
491
|
+
/>
|
|
492
|
+
|
|
493
|
+
{/* Tokens */}
|
|
494
|
+
<TokenListElement
|
|
495
|
+
limit={5}
|
|
496
|
+
render={({ tokens, isLoading }) => (
|
|
497
|
+
<div>
|
|
498
|
+
{isLoading ? (
|
|
499
|
+
<div>Loading tokens...</div>
|
|
500
|
+
) : (
|
|
501
|
+
tokens.map(token => (
|
|
502
|
+
<div key={token.mint}>
|
|
503
|
+
{token.symbol}: {token.formatted}
|
|
504
|
+
</div>
|
|
505
|
+
))
|
|
506
|
+
)}
|
|
507
|
+
</div>
|
|
508
|
+
)}
|
|
509
|
+
/>
|
|
510
|
+
|
|
511
|
+
{/* Transaction History */}
|
|
512
|
+
<TransactionHistoryElement
|
|
513
|
+
limit={5}
|
|
514
|
+
render={({ transactions, isLoading }) => (
|
|
515
|
+
<div>
|
|
516
|
+
{isLoading ? (
|
|
517
|
+
<div>Loading transactions...</div>
|
|
518
|
+
) : (
|
|
519
|
+
transactions.map(tx => (
|
|
520
|
+
<a key={tx.signature} href={tx.explorerUrl} target="_blank">
|
|
521
|
+
{tx.type} - {tx.formattedTime}
|
|
522
|
+
</a>
|
|
523
|
+
))
|
|
524
|
+
)}
|
|
525
|
+
</div>
|
|
526
|
+
)}
|
|
527
|
+
/>
|
|
528
|
+
|
|
529
|
+
{/* Disconnect */}
|
|
530
|
+
<DisconnectElement
|
|
531
|
+
render={({ disconnect, disconnecting }) => (
|
|
532
|
+
<button onClick={disconnect} disabled={disconnecting}>
|
|
533
|
+
{disconnecting ? 'Disconnecting...' : 'Disconnect'}
|
|
534
|
+
</button>
|
|
535
|
+
)}
|
|
536
|
+
/>
|
|
537
|
+
</div>
|
|
538
|
+
);
|
|
474
539
|
}
|
|
475
540
|
```
|
|
476
541
|
|
|
477
|
-
|
|
542
|
+
---
|
|
478
543
|
|
|
479
|
-
|
|
480
|
-
- Displays current account and wallet
|
|
481
|
-
- Shows active cluster and RPC URL
|
|
482
|
-
- Lists detected wallets
|
|
483
|
-
- Health check information
|
|
484
|
-
- Click to expand/collapse
|
|
485
|
-
- Automatically excluded in production builds
|
|
544
|
+
## Configuration
|
|
486
545
|
|
|
487
|
-
|
|
546
|
+
### Basic Configuration
|
|
488
547
|
|
|
489
548
|
```typescript
|
|
490
|
-
|
|
491
|
-
position="top-left" // 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
|
|
492
|
-
defaultOpen={true}
|
|
493
|
-
zIndex={10000}
|
|
494
|
-
/>
|
|
495
|
-
```
|
|
549
|
+
import { getDefaultConfig } from '@solana/connector';
|
|
496
550
|
|
|
497
|
-
|
|
551
|
+
const config = getDefaultConfig({
|
|
552
|
+
appName: 'My App', // Required
|
|
553
|
+
appUrl: 'https://myapp.com', // Optional: for mobile wallet adapter
|
|
554
|
+
autoConnect: true, // Auto-reconnect (default: true)
|
|
555
|
+
network: 'mainnet-beta', // Initial network (default: 'mainnet-beta')
|
|
556
|
+
enableMobile: true, // Mobile Wallet Adapter (default: true)
|
|
557
|
+
debug: false, // Debug logging
|
|
558
|
+
});
|
|
559
|
+
```
|
|
498
560
|
|
|
499
|
-
|
|
561
|
+
### Network Selection
|
|
500
562
|
|
|
501
563
|
```typescript
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
// Create custom pool
|
|
506
|
-
const pool = createConnectionPool({
|
|
507
|
-
maxSize: 10,
|
|
508
|
-
createConnection: cluster => {
|
|
509
|
-
return new Connection(cluster.endpoint, {
|
|
510
|
-
commitment: 'confirmed',
|
|
511
|
-
});
|
|
512
|
-
},
|
|
564
|
+
const config = getDefaultConfig({
|
|
565
|
+
appName: 'My App',
|
|
566
|
+
network: 'devnet', // 'mainnet-beta' | 'devnet' | 'testnet' | 'localnet'
|
|
513
567
|
});
|
|
568
|
+
```
|
|
514
569
|
|
|
515
|
-
|
|
516
|
-
const connection = pool.get(currentCluster);
|
|
517
|
-
const balance = await connection.getBalance(publicKey);
|
|
570
|
+
### Custom RPC Endpoints
|
|
518
571
|
|
|
519
|
-
|
|
520
|
-
|
|
572
|
+
```typescript
|
|
573
|
+
import { getDefaultConfig } from '@solana/connector';
|
|
521
574
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
575
|
+
const config = getDefaultConfig({
|
|
576
|
+
appName: 'My App',
|
|
577
|
+
clusters: [
|
|
578
|
+
{
|
|
579
|
+
id: 'solana:mainnet' as const,
|
|
580
|
+
label: 'Mainnet (Custom RPC)',
|
|
581
|
+
name: 'mainnet-beta' as const,
|
|
582
|
+
url: 'https://my-custom-rpc.com',
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
id: 'solana:devnet' as const,
|
|
586
|
+
label: 'Devnet',
|
|
587
|
+
name: 'devnet' as const,
|
|
588
|
+
url: 'https://api.devnet.solana.com',
|
|
589
|
+
},
|
|
590
|
+
],
|
|
591
|
+
});
|
|
526
592
|
```
|
|
527
593
|
|
|
528
|
-
|
|
594
|
+
### Mobile Wallet Adapter
|
|
529
595
|
|
|
530
596
|
```typescript
|
|
531
|
-
import {
|
|
597
|
+
import { getDefaultMobileConfig } from '@solana/connector/headless';
|
|
598
|
+
|
|
599
|
+
const mobile = getDefaultMobileConfig({
|
|
600
|
+
appName: 'My App',
|
|
601
|
+
appUrl: 'https://myapp.com',
|
|
602
|
+
});
|
|
532
603
|
|
|
533
|
-
|
|
534
|
-
|
|
604
|
+
<AppProvider connectorConfig={config} mobile={mobile}>
|
|
605
|
+
{children}
|
|
606
|
+
</AppProvider>
|
|
535
607
|
```
|
|
536
608
|
|
|
537
|
-
|
|
609
|
+
---
|
|
538
610
|
|
|
539
|
-
|
|
611
|
+
## Security Considerations
|
|
540
612
|
|
|
541
|
-
|
|
542
|
-
import { useConnectorClient } from '@solana/connector';
|
|
613
|
+
### Token Image Privacy
|
|
543
614
|
|
|
544
|
-
|
|
545
|
-
const client = useConnectorClient();
|
|
615
|
+
When using `useTokens()` or `useTransactions()`, token metadata (including logo URLs) is fetched from external APIs. By default, these image URLs are returned directly, which means when your users' browsers fetch these images, the image host can see:
|
|
546
616
|
|
|
547
|
-
|
|
617
|
+
- User IP addresses
|
|
618
|
+
- Request timing (when users viewed their tokens)
|
|
619
|
+
- User agent and browser information
|
|
548
620
|
|
|
549
|
-
|
|
621
|
+
This could potentially be exploited by malicious token creators who set tracking URLs in their token metadata.
|
|
550
622
|
|
|
551
|
-
|
|
552
|
-
<div>
|
|
553
|
-
<h3>Connector Health</h3>
|
|
554
|
-
<div>Initialized: {health.initialized ? '✓' : '✗'}</div>
|
|
555
|
-
<div>Wallet Standard: {health.walletStandardAvailable ? '✓' : '✗'}</div>
|
|
556
|
-
<div>Storage: {health.storageAvailable ? '✓' : '✗'}</div>
|
|
557
|
-
<div>Wallets Detected: {health.walletsDetected}</div>
|
|
623
|
+
### Image Proxy Configuration
|
|
558
624
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
625
|
+
To protect user privacy, you can configure an image proxy that fetches images on behalf of your users:
|
|
626
|
+
|
|
627
|
+
```typescript
|
|
628
|
+
const config = getDefaultConfig({
|
|
629
|
+
appName: 'My App',
|
|
630
|
+
imageProxy: '/_next/image?w=64&q=75&url=', // Next.js Image Optimization
|
|
631
|
+
});
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
When `imageProxy` is set, all token image URLs returned by `useTokens()` and `useTransactions()` will be automatically transformed:
|
|
635
|
+
|
|
636
|
+
```
|
|
637
|
+
// Original URL from token metadata
|
|
638
|
+
https://raw.githubusercontent.com/.../token-logo.png
|
|
639
|
+
|
|
640
|
+
// Transformed URL (when imageProxy is set)
|
|
641
|
+
/_next/image?w=64&q=75&url=https%3A%2F%2Fraw.githubusercontent.com%2F...%2Ftoken-logo.png
|
|
567
642
|
```
|
|
568
643
|
|
|
569
|
-
###
|
|
644
|
+
### Common Proxy Options
|
|
570
645
|
|
|
571
|
-
|
|
646
|
+
| Service | Configuration |
|
|
647
|
+
|---------|---------------|
|
|
648
|
+
| **Next.js Image** | `imageProxy: '/_next/image?w=64&q=75&url='` |
|
|
649
|
+
| **Cloudflare** | `imageProxy: '/cdn-cgi/image/width=64,quality=75/'` |
|
|
650
|
+
| **imgproxy** | `imageProxy: 'https://imgproxy.example.com/insecure/fill/64/64/'` |
|
|
651
|
+
| **Custom API** | `imageProxy: '/api/image-proxy?url='` |
|
|
652
|
+
|
|
653
|
+
### Custom Proxy API Route (Next.js Example)
|
|
572
654
|
|
|
573
655
|
```typescript
|
|
574
|
-
|
|
575
|
-
import {
|
|
656
|
+
// app/api/image-proxy/route.ts
|
|
657
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
658
|
+
import dns from 'dns/promises';
|
|
659
|
+
|
|
660
|
+
// Allowlist of permitted domains for image fetching
|
|
661
|
+
const ALLOWED_DOMAINS = [
|
|
662
|
+
'raw.githubusercontent.com',
|
|
663
|
+
'arweave.net',
|
|
664
|
+
'ipfs.io',
|
|
665
|
+
'cloudflare-ipfs.com',
|
|
666
|
+
'nftstorage.link',
|
|
667
|
+
// Add other trusted image domains as needed
|
|
668
|
+
];
|
|
669
|
+
|
|
670
|
+
// Check if an IP address falls within private/reserved ranges
|
|
671
|
+
function isPrivateOrReservedIP(ip: string): boolean {
|
|
672
|
+
// IPv4 private/reserved ranges
|
|
673
|
+
const ipv4PrivateRanges = [
|
|
674
|
+
/^127\./, // 127.0.0.0/8 (loopback)
|
|
675
|
+
/^10\./, // 10.0.0.0/8 (private)
|
|
676
|
+
/^172\.(1[6-9]|2[0-9]|3[0-1])\./, // 172.16.0.0/12 (private)
|
|
677
|
+
/^192\.168\./, // 192.168.0.0/16 (private)
|
|
678
|
+
/^169\.254\./, // 169.254.0.0/16 (link-local/metadata)
|
|
679
|
+
/^0\./, // 0.0.0.0/8 (current network)
|
|
680
|
+
/^100\.(6[4-9]|[7-9][0-9]|1[0-1][0-9]|12[0-7])\./, // 100.64.0.0/10 (CGNAT)
|
|
681
|
+
/^192\.0\.0\./, // 192.0.0.0/24 (IETF protocol assignments)
|
|
682
|
+
/^192\.0\.2\./, // 192.0.2.0/24 (TEST-NET-1)
|
|
683
|
+
/^198\.51\.100\./, // 198.51.100.0/24 (TEST-NET-2)
|
|
684
|
+
/^203\.0\.113\./, // 203.0.113.0/24 (TEST-NET-3)
|
|
685
|
+
/^224\./, // 224.0.0.0/4 (multicast)
|
|
686
|
+
/^240\./, // 240.0.0.0/4 (reserved)
|
|
687
|
+
/^255\.255\.255\.255$/, // broadcast
|
|
688
|
+
];
|
|
689
|
+
|
|
690
|
+
// IPv6 private/reserved ranges
|
|
691
|
+
const ipv6PrivatePatterns = [
|
|
692
|
+
/^::1$/, // loopback
|
|
693
|
+
/^fe80:/i, // link-local
|
|
694
|
+
/^fc00:/i, // unique local (fc00::/7)
|
|
695
|
+
/^fd/i, // unique local (fd00::/8)
|
|
696
|
+
/^::ffff:(127\.|10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|192\.168\.|169\.254\.)/i, // IPv4-mapped
|
|
697
|
+
];
|
|
698
|
+
|
|
699
|
+
// Check IPv4
|
|
700
|
+
for (const range of ipv4PrivateRanges) {
|
|
701
|
+
if (range.test(ip)) return true;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// Check IPv6
|
|
705
|
+
for (const pattern of ipv6PrivatePatterns) {
|
|
706
|
+
if (pattern.test(ip)) return true;
|
|
707
|
+
}
|
|
576
708
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
const { disconnect } = useConnector();
|
|
709
|
+
return false;
|
|
710
|
+
}
|
|
580
711
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
712
|
+
// Validate and parse the URL
|
|
713
|
+
function validateUrl(urlString: string): URL | null {
|
|
714
|
+
try {
|
|
715
|
+
const parsed = new URL(urlString);
|
|
716
|
+
// Only allow http and https protocols
|
|
717
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
718
|
+
return null;
|
|
719
|
+
}
|
|
720
|
+
return parsed;
|
|
721
|
+
} catch {
|
|
722
|
+
return null;
|
|
588
723
|
}
|
|
589
|
-
|
|
724
|
+
}
|
|
590
725
|
|
|
591
|
-
|
|
592
|
-
|
|
726
|
+
// Check if hostname is in the allowlist
|
|
727
|
+
function isAllowedDomain(hostname: string): boolean {
|
|
728
|
+
return ALLOWED_DOMAINS.some(
|
|
729
|
+
(domain) => hostname === domain || hostname.endsWith(`.${domain}`)
|
|
730
|
+
);
|
|
593
731
|
}
|
|
594
|
-
```
|
|
595
732
|
|
|
596
|
-
|
|
733
|
+
export async function GET(request: NextRequest) {
|
|
734
|
+
const urlParam = request.nextUrl.searchParams.get('url');
|
|
735
|
+
|
|
736
|
+
// (1) Ensure URL exists and parses correctly with http/https
|
|
737
|
+
if (!urlParam) {
|
|
738
|
+
return new NextResponse('Missing URL parameter', { status: 400 });
|
|
739
|
+
}
|
|
597
740
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
741
|
+
const parsedUrl = validateUrl(urlParam);
|
|
742
|
+
if (!parsedUrl) {
|
|
743
|
+
return new NextResponse('Invalid URL or protocol', { status: 400 });
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// (2) Enforce allowlist of permitted domains
|
|
747
|
+
if (!isAllowedDomain(parsedUrl.hostname)) {
|
|
748
|
+
return new NextResponse('Domain not allowed', { status: 403 });
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// (3) Resolve hostname and check for private/reserved IPs
|
|
752
|
+
try {
|
|
753
|
+
const addresses = await dns.resolve(parsedUrl.hostname);
|
|
754
|
+
for (const ip of addresses) {
|
|
755
|
+
if (isPrivateOrReservedIP(ip)) {
|
|
756
|
+
return new NextResponse('Resolved IP is not allowed', { status: 403 });
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
} catch {
|
|
760
|
+
return new NextResponse('Failed to resolve hostname', { status: 400 });
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// (4) All checks passed - perform the fetch
|
|
764
|
+
try {
|
|
765
|
+
const response = await fetch(parsedUrl.toString());
|
|
766
|
+
const buffer = await response.arrayBuffer();
|
|
767
|
+
|
|
768
|
+
return new NextResponse(buffer, {
|
|
769
|
+
headers: {
|
|
770
|
+
'Content-Type': response.headers.get('Content-Type') || 'image/png',
|
|
771
|
+
'Cache-Control': 'public, max-age=86400',
|
|
772
|
+
},
|
|
773
|
+
});
|
|
774
|
+
} catch {
|
|
775
|
+
return new NextResponse('Failed to fetch image', { status: 500 });
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
```
|
|
603
779
|
|
|
604
780
|
---
|
|
605
781
|
|
|
606
|
-
##
|
|
782
|
+
## CoinGecko API & Rate Limits
|
|
607
783
|
|
|
608
|
-
|
|
784
|
+
The `useTokens()` hook fetches token prices from CoinGecko. CoinGecko has rate limits that may affect your application:
|
|
609
785
|
|
|
610
|
-
|
|
611
|
-
import { getDefaultConfig } from '@solana/connector';
|
|
786
|
+
### Rate Limits (as of 2024)
|
|
612
787
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
788
|
+
| Tier | Rate Limit | API Key Required |
|
|
789
|
+
|------|------------|------------------|
|
|
790
|
+
| **Free (Public)** | 10-30 requests/minute | No |
|
|
791
|
+
| **Demo** | 30 requests/minute | Yes (free) |
|
|
792
|
+
| **Analyst** | 500 requests/minute | Yes (paid) |
|
|
793
|
+
| **Pro** | 1000+ requests/minute | Yes (paid) |
|
|
619
794
|
|
|
620
|
-
|
|
621
|
-
onError: (error, errorInfo) => {
|
|
622
|
-
console.error('Connector error:', error);
|
|
623
|
-
errorTracker.captureException(error, errorInfo);
|
|
624
|
-
},
|
|
625
|
-
});
|
|
626
|
-
```
|
|
795
|
+
### Handling Rate Limits
|
|
627
796
|
|
|
628
|
-
|
|
797
|
+
ConnectorKit automatically handles rate limits with:
|
|
798
|
+
- **Exponential backoff**: Retries with increasing delays
|
|
799
|
+
- **Jitter**: Random delay added to prevent thundering herd
|
|
800
|
+
- **Retry-After header**: Honors server-specified wait times
|
|
801
|
+
- **Bounded timeout**: Won't block forever (default 30s max)
|
|
802
|
+
|
|
803
|
+
### Adding a CoinGecko API Key
|
|
804
|
+
|
|
805
|
+
For higher rate limits, add a free Demo API key from [CoinGecko](https://www.coingecko.com/en/api/pricing):
|
|
629
806
|
|
|
630
807
|
```typescript
|
|
631
808
|
const config = getDefaultConfig({
|
|
632
809
|
appName: 'My App',
|
|
633
|
-
|
|
810
|
+
coingecko: {
|
|
811
|
+
apiKey: process.env.COINGECKO_API_KEY, // Demo or Pro API key
|
|
812
|
+
isPro: false, // Set to true for Pro API keys
|
|
813
|
+
},
|
|
634
814
|
});
|
|
635
815
|
```
|
|
636
816
|
|
|
637
|
-
###
|
|
817
|
+
### Advanced Configuration
|
|
638
818
|
|
|
639
819
|
```typescript
|
|
640
|
-
import { getDefaultConfig, createSolanaMainnet } from '@solana/connector';
|
|
641
|
-
|
|
642
820
|
const config = getDefaultConfig({
|
|
643
821
|
appName: 'My App',
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
822
|
+
coingecko: {
|
|
823
|
+
// API key for higher rate limits (optional)
|
|
824
|
+
apiKey: process.env.COINGECKO_API_KEY,
|
|
825
|
+
|
|
826
|
+
// Set to true if using a Pro API key (default: false for Demo keys)
|
|
827
|
+
isPro: false,
|
|
828
|
+
|
|
829
|
+
// Maximum retry attempts on 429 (default: 3)
|
|
830
|
+
maxRetries: 3,
|
|
831
|
+
|
|
832
|
+
// Base delay for exponential backoff in ms (default: 1000)
|
|
833
|
+
baseDelay: 1000,
|
|
834
|
+
|
|
835
|
+
// Maximum total timeout in ms (default: 30000)
|
|
836
|
+
maxTimeout: 30000,
|
|
837
|
+
},
|
|
652
838
|
});
|
|
653
839
|
```
|
|
654
840
|
|
|
655
|
-
###
|
|
841
|
+
### Caching
|
|
656
842
|
|
|
657
|
-
|
|
658
|
-
<ConnectorProvider
|
|
659
|
-
config={config}
|
|
660
|
-
mobile={{
|
|
661
|
-
appIdentity: {
|
|
662
|
-
name: 'My App',
|
|
663
|
-
uri: 'https://myapp.com',
|
|
664
|
-
icon: 'https://myapp.com/icon.png'
|
|
665
|
-
}
|
|
666
|
-
}}
|
|
667
|
-
>
|
|
668
|
-
<App />
|
|
669
|
-
</ConnectorProvider>
|
|
670
|
-
```
|
|
843
|
+
Token prices are cached for 60 seconds to minimize API calls. The retry logic only applies to uncached token IDs, so frequently-viewed tokens won't trigger additional API calls.
|
|
671
844
|
|
|
672
845
|
---
|
|
673
846
|
|
|
@@ -675,7 +848,7 @@ const config = getDefaultConfig({
|
|
|
675
848
|
|
|
676
849
|
### Headless Client (Vue, Svelte, Vanilla JS)
|
|
677
850
|
|
|
678
|
-
Use `ConnectorClient` for non-React frameworks
|
|
851
|
+
Use `ConnectorClient` for non-React frameworks:
|
|
679
852
|
|
|
680
853
|
```typescript
|
|
681
854
|
import { ConnectorClient, getDefaultConfig } from '@solana/connector/headless';
|
|
@@ -695,15 +868,6 @@ const unsubscribe = client.subscribe(state => {
|
|
|
695
868
|
console.log('State updated:', state);
|
|
696
869
|
});
|
|
697
870
|
|
|
698
|
-
// Subscribe to events (NEW!)
|
|
699
|
-
const unsubEvents = client.on(event => {
|
|
700
|
-
console.log('Event:', event.type, event);
|
|
701
|
-
});
|
|
702
|
-
|
|
703
|
-
// Check health (NEW!)
|
|
704
|
-
const health = client.getHealth();
|
|
705
|
-
console.log('Health:', health);
|
|
706
|
-
|
|
707
871
|
// Disconnect
|
|
708
872
|
await client.disconnect();
|
|
709
873
|
|
|
@@ -711,44 +875,15 @@ await client.disconnect();
|
|
|
711
875
|
client.destroy();
|
|
712
876
|
```
|
|
713
877
|
|
|
714
|
-
###
|
|
715
|
-
|
|
716
|
-
If you're using both ConnectorKit and Armadura:
|
|
717
|
-
|
|
718
|
-
```typescript
|
|
719
|
-
import { createConfig, AppProvider } from '@solana/connector';
|
|
720
|
-
import { ArmaProvider } from '@armadura/sdk';
|
|
721
|
-
|
|
722
|
-
const config = createConfig({
|
|
723
|
-
appName: 'My App',
|
|
724
|
-
network: 'mainnet',
|
|
725
|
-
rpcUrl: 'https://my-custom-rpc.com',
|
|
726
|
-
autoConnect: true
|
|
727
|
-
});
|
|
728
|
-
|
|
729
|
-
function App() {
|
|
730
|
-
return (
|
|
731
|
-
<AppProvider config={config.connectorConfig}>
|
|
732
|
-
<ArmaProvider
|
|
733
|
-
config={{
|
|
734
|
-
network: config.network,
|
|
735
|
-
rpcUrl: config.rpcUrl,
|
|
736
|
-
providers: [/* ... */]
|
|
737
|
-
}}
|
|
738
|
-
useConnector="auto"
|
|
739
|
-
>
|
|
740
|
-
{children}
|
|
741
|
-
</ArmaProvider>
|
|
742
|
-
</AppProvider>
|
|
743
|
-
);
|
|
744
|
-
}
|
|
745
|
-
```
|
|
878
|
+
### Custom Storage (React Native, SSR)
|
|
746
879
|
|
|
747
|
-
|
|
880
|
+
Storage uses nanostores with built-in enhancements that are **automatically applied**:
|
|
748
881
|
|
|
749
|
-
|
|
882
|
+
- Validation (Solana address format checking)
|
|
883
|
+
- Error handling (catches localStorage quota errors, private browsing)
|
|
884
|
+
- SSR fallback (uses memory storage when localStorage unavailable)
|
|
750
885
|
|
|
751
|
-
Only customize for:
|
|
886
|
+
**Most users don't need to configure storage.** Only customize for:
|
|
752
887
|
|
|
753
888
|
- React Native (custom storage backend)
|
|
754
889
|
- Additional validation rules
|
|
@@ -759,7 +894,6 @@ import { getDefaultConfig, createEnhancedStorageWallet, EnhancedStorageAdapter }
|
|
|
759
894
|
|
|
760
895
|
const config = getDefaultConfig({
|
|
761
896
|
appName: 'My App',
|
|
762
|
-
|
|
763
897
|
storage: {
|
|
764
898
|
wallet: new EnhancedStorageAdapter(
|
|
765
899
|
createEnhancedStorageWallet({
|
|
@@ -768,11 +902,11 @@ const config = getDefaultConfig({
|
|
|
768
902
|
return walletName !== null && walletName.length > 0;
|
|
769
903
|
},
|
|
770
904
|
onError: error => {
|
|
905
|
+
// Custom error tracking
|
|
771
906
|
Sentry.captureException(error);
|
|
772
907
|
},
|
|
773
908
|
}),
|
|
774
909
|
),
|
|
775
|
-
// account and cluster use defaults if not specified
|
|
776
910
|
},
|
|
777
911
|
});
|
|
778
912
|
```
|
|
@@ -785,7 +919,7 @@ const config = getDefaultConfig({
|
|
|
785
919
|
|
|
786
920
|
```typescript
|
|
787
921
|
// Full library - includes React and headless
|
|
788
|
-
import { ConnectorProvider, useConnector } from '@solana/connector';
|
|
922
|
+
import { ConnectorProvider, useConnector, useAccount } from '@solana/connector';
|
|
789
923
|
```
|
|
790
924
|
|
|
791
925
|
### Headless Export (Framework Agnostic)
|
|
@@ -802,427 +936,58 @@ import { ConnectorClient, getDefaultConfig } from '@solana/connector/headless';
|
|
|
802
936
|
import { useConnector, useAccount } from '@solana/connector/react';
|
|
803
937
|
```
|
|
804
938
|
|
|
805
|
-
### Compatibility Layer (NEW!)
|
|
806
|
-
|
|
807
|
-
```typescript
|
|
808
|
-
// Wallet adapter compatibility bridge
|
|
809
|
-
import { createWalletAdapterCompat } from '@solana/connector/compat';
|
|
810
|
-
```
|
|
811
|
-
|
|
812
939
|
---
|
|
813
940
|
|
|
814
|
-
##
|
|
815
|
-
|
|
816
|
-
The connector package includes a comprehensive test suite built with Vitest. All tests are located in `src/__tests__/` and co-located with source files.
|
|
817
|
-
|
|
818
|
-
### Running Tests
|
|
819
|
-
|
|
820
|
-
```bash
|
|
821
|
-
# Run all tests
|
|
822
|
-
pnpm test
|
|
823
|
-
|
|
824
|
-
# Run tests in watch mode
|
|
825
|
-
pnpm test:watch
|
|
826
|
-
|
|
827
|
-
# Run tests with UI
|
|
828
|
-
pnpm test:ui
|
|
829
|
-
|
|
830
|
-
# Generate coverage report
|
|
831
|
-
pnpm test:coverage
|
|
832
|
-
```
|
|
833
|
-
|
|
834
|
-
### Test Coverage
|
|
835
|
-
|
|
836
|
-
The package maintains high test coverage:
|
|
837
|
-
|
|
838
|
-
- **Lines**: 80%+
|
|
839
|
-
- **Functions**: 80%+
|
|
840
|
-
- **Branches**: 75%+
|
|
841
|
-
- **Statements**: 80%+
|
|
842
|
-
|
|
843
|
-
### Test Structure
|
|
844
|
-
|
|
845
|
-
```
|
|
846
|
-
src/
|
|
847
|
-
├── lib/
|
|
848
|
-
│ ├── core/
|
|
849
|
-
│ │ ├── state-manager.ts
|
|
850
|
-
│ │ └── state-manager.test.ts # Unit tests
|
|
851
|
-
│ └── connection/
|
|
852
|
-
│ ├── connection-manager.ts
|
|
853
|
-
│ └── connection-manager.test.ts # Unit tests
|
|
854
|
-
├── hooks/
|
|
855
|
-
│ ├── use-account.ts
|
|
856
|
-
│ └── use-account.test.tsx # React hook tests
|
|
857
|
-
└── __tests__/
|
|
858
|
-
├── setup.ts # Global test setup
|
|
859
|
-
├── mocks/ # Mock implementations
|
|
860
|
-
│ ├── wallet-standard-mock.ts # Mock wallets
|
|
861
|
-
│ ├── storage-mock.ts # Mock storage
|
|
862
|
-
│ └── window-mock.ts # Mock browser APIs
|
|
863
|
-
├── fixtures/ # Test data
|
|
864
|
-
│ ├── wallets.ts # Wallet fixtures
|
|
865
|
-
│ ├── accounts.ts # Account fixtures
|
|
866
|
-
│ └── transactions.ts # Transaction fixtures
|
|
867
|
-
├── utils/ # Test helpers
|
|
868
|
-
│ ├── test-helpers.ts # Common utilities
|
|
869
|
-
│ ├── react-helpers.tsx # React test utils
|
|
870
|
-
│ └── wait-for-state.ts # State helpers
|
|
871
|
-
└── integration/ # Integration tests
|
|
872
|
-
└── connector-flow.test.ts # Full workflows
|
|
873
|
-
```
|
|
874
|
-
|
|
875
|
-
### Writing Tests
|
|
876
|
-
|
|
877
|
-
Example unit test:
|
|
878
|
-
|
|
879
|
-
```typescript
|
|
880
|
-
import { describe, it, expect } from 'vitest';
|
|
881
|
-
import { StateManager } from './state-manager';
|
|
882
|
-
|
|
883
|
-
describe('StateManager', () => {
|
|
884
|
-
it('should update state correctly', () => {
|
|
885
|
-
const manager = new StateManager(initialState);
|
|
886
|
-
manager.updateState({ connected: true });
|
|
887
|
-
|
|
888
|
-
expect(manager.getSnapshot().connected).toBe(true);
|
|
889
|
-
});
|
|
890
|
-
});
|
|
891
|
-
```
|
|
892
|
-
|
|
893
|
-
Example React hook test:
|
|
894
|
-
|
|
895
|
-
```typescript
|
|
896
|
-
import { renderHook } from '@testing-library/react';
|
|
897
|
-
import { useAccount } from './use-account';
|
|
898
|
-
import { createHookWrapper } from '../__tests__/utils/react-helpers';
|
|
899
|
-
|
|
900
|
-
describe('useAccount', () => {
|
|
901
|
-
it('should return account information', () => {
|
|
902
|
-
const { result } = renderHook(() => useAccount(), {
|
|
903
|
-
wrapper: createHookWrapper(),
|
|
904
|
-
});
|
|
905
|
-
|
|
906
|
-
expect(result.current.address).toBeDefined();
|
|
907
|
-
});
|
|
908
|
-
});
|
|
909
|
-
```
|
|
910
|
-
|
|
911
|
-
### Test Utilities
|
|
912
|
-
|
|
913
|
-
The package provides comprehensive test utilities:
|
|
914
|
-
|
|
915
|
-
**Mock Wallets:**
|
|
916
|
-
|
|
917
|
-
```typescript
|
|
918
|
-
import { createMockPhantomWallet, createMockSolflareWallet } from '../mocks/wallet-standard-mock';
|
|
919
|
-
|
|
920
|
-
const wallet = createMockPhantomWallet({
|
|
921
|
-
connectBehavior: 'success', // or 'error', 'timeout'
|
|
922
|
-
});
|
|
923
|
-
```
|
|
924
|
-
|
|
925
|
-
**Test Fixtures:**
|
|
926
|
-
|
|
927
|
-
```typescript
|
|
928
|
-
import { createTestAccounts, TEST_ADDRESSES } from '../fixtures/accounts';
|
|
929
|
-
import { createTestWallets } from '../fixtures/wallets';
|
|
930
|
-
|
|
931
|
-
const accounts = createTestAccounts(3);
|
|
932
|
-
const wallets = createTestWallets();
|
|
933
|
-
```
|
|
934
|
-
|
|
935
|
-
**Test Helpers:**
|
|
936
|
-
|
|
937
|
-
```typescript
|
|
938
|
-
import { waitForCondition, createEventCollector } from '../utils/test-helpers';
|
|
939
|
-
|
|
940
|
-
// Wait for a condition
|
|
941
|
-
await waitForCondition(() => state.connected, { timeout: 5000 });
|
|
942
|
-
|
|
943
|
-
// Collect events
|
|
944
|
-
const collector = createEventCollector();
|
|
945
|
-
client.on(collector.collect);
|
|
946
|
-
collector.assertEventEmitted('connected');
|
|
947
|
-
```
|
|
948
|
-
|
|
949
|
-
### Contributing Tests
|
|
941
|
+
## API Reference
|
|
950
942
|
|
|
951
|
-
|
|
943
|
+
### Hooks
|
|
952
944
|
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
945
|
+
| Hook | Description | Returns |
|
|
946
|
+
|------|-------------|---------|
|
|
947
|
+
| `useConnector()` | Main wallet connection hook | `{ wallets, selectedWallet, accounts, connected, connecting, select, disconnect }` |
|
|
948
|
+
| `useAccount()` | Account management hook | `{ address, formatted, copy, copied, accounts, selectAccount }` |
|
|
949
|
+
| `useCluster()` | Network/cluster management hook | `{ cluster, clusters, setCluster, isMainnet, isDevnet, rpcUrl }` |
|
|
950
|
+
| `useWalletInfo()` | Wallet metadata hook | `{ name, icon, wallet, connecting }` |
|
|
951
|
+
| `useTransactionSigner()` | Legacy transaction signer (web3.js) | `{ signer, ready, address, capabilities }` |
|
|
952
|
+
| `useKitTransactionSigner()` | Modern transaction signer (@solana/kit) | `{ signer, ready, address }` |
|
|
953
|
+
| `useBalance()` | SOL balance hook | `{ solBalance, isLoading, refetch }` |
|
|
954
|
+
| `useTokens()` | SPL tokens hook | `{ tokens, isLoading, refetch }` |
|
|
955
|
+
| `useTransactions()` | Transaction history hook | `{ transactions, isLoading, refetch }` |
|
|
963
956
|
|
|
964
957
|
### Configuration Functions
|
|
965
958
|
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
appUrl?: string, // App URL for metadata
|
|
972
|
-
autoConnect?: boolean, // Auto-reconnect (default: true)
|
|
973
|
-
debug?: boolean, // Debug logging
|
|
974
|
-
network?: 'mainnet' | 'mainnet-beta' // Initial network (default: mainnet-beta)
|
|
975
|
-
| 'devnet' | 'testnet' | 'localnet',
|
|
976
|
-
enableMobile?: boolean, // Mobile Wallet Adapter (default: true)
|
|
977
|
-
storage?: ConnectorConfig['storage'], // Custom storage adapters
|
|
978
|
-
clusters?: SolanaCluster[], // Override default clusters
|
|
979
|
-
customClusters?: SolanaCluster[], // Add custom clusters
|
|
980
|
-
persistClusterSelection?: boolean, // Persist cluster (default: true)
|
|
981
|
-
enableErrorBoundary?: boolean, // Error boundaries (default: true)
|
|
982
|
-
maxRetries?: number, // Retry attempts (default: 3)
|
|
983
|
-
onError?: (error, errorInfo) => void // Error handler (NEW!)
|
|
984
|
-
});
|
|
985
|
-
```
|
|
986
|
-
|
|
987
|
-
### Transaction Signing API (NEW!)
|
|
988
|
-
|
|
989
|
-
#### `useTransactionSigner()`
|
|
990
|
-
|
|
991
|
-
React hook for transaction operations.
|
|
992
|
-
|
|
993
|
-
```typescript
|
|
994
|
-
interface UseTransactionSignerReturn {
|
|
995
|
-
signer: TransactionSigner | null; // Signer instance
|
|
996
|
-
ready: boolean; // Whether signer is ready
|
|
997
|
-
address: string | null; // Current address
|
|
998
|
-
capabilities: TransactionSignerCapabilities; // What signer can do
|
|
999
|
-
}
|
|
1000
|
-
```
|
|
1001
|
-
|
|
1002
|
-
#### `createTransactionSigner(config)`
|
|
1003
|
-
|
|
1004
|
-
Create a transaction signer (headless).
|
|
1005
|
-
|
|
1006
|
-
```typescript
|
|
1007
|
-
import { createTransactionSigner } from '@solana/connector/headless';
|
|
1008
|
-
|
|
1009
|
-
const signer = createTransactionSigner({
|
|
1010
|
-
wallet: connectedWallet,
|
|
1011
|
-
account: selectedAccount,
|
|
1012
|
-
cluster: currentCluster, // Optional
|
|
1013
|
-
});
|
|
1014
|
-
```
|
|
1015
|
-
|
|
1016
|
-
#### `TransactionSigner` Interface
|
|
1017
|
-
|
|
1018
|
-
```typescript
|
|
1019
|
-
interface TransactionSigner {
|
|
1020
|
-
readonly address: string;
|
|
1021
|
-
|
|
1022
|
-
signTransaction(tx: any): Promise<any>;
|
|
1023
|
-
signAllTransactions(txs: any[]): Promise<any[]>;
|
|
1024
|
-
signAndSendTransaction(tx: any, options?: SendOptions): Promise<string>;
|
|
1025
|
-
signAndSendTransactions(txs: any[], options?: SendOptions): Promise<string[]>;
|
|
1026
|
-
signMessage?(message: Uint8Array): Promise<Uint8Array>;
|
|
1027
|
-
|
|
1028
|
-
getCapabilities(): TransactionSignerCapabilities;
|
|
1029
|
-
}
|
|
1030
|
-
```
|
|
1031
|
-
|
|
1032
|
-
### Event System API (NEW!)
|
|
1033
|
-
|
|
1034
|
-
#### Event Types
|
|
1035
|
-
|
|
1036
|
-
```typescript
|
|
1037
|
-
type ConnectorEvent =
|
|
1038
|
-
| { type: 'wallet:connected'; wallet: string; account: string; timestamp: string }
|
|
1039
|
-
| { type: 'wallet:disconnected'; timestamp: string }
|
|
1040
|
-
| { type: 'cluster:changed'; cluster: string; previousCluster: string | null; timestamp: string }
|
|
1041
|
-
| { type: 'wallets:detected'; count: number; timestamp: string }
|
|
1042
|
-
| { type: 'connecting'; wallet: string; timestamp: string }
|
|
1043
|
-
| { type: 'connection:failed'; wallet: string; error: string; timestamp: string }
|
|
1044
|
-
| { type: 'error'; error: Error; context: string; timestamp: string };
|
|
1045
|
-
```
|
|
1046
|
-
|
|
1047
|
-
#### Methods
|
|
1048
|
-
|
|
1049
|
-
```typescript
|
|
1050
|
-
// Subscribe to events
|
|
1051
|
-
const unsubscribe = client.on(event => {
|
|
1052
|
-
console.log('Event:', event.type, event);
|
|
1053
|
-
});
|
|
1054
|
-
|
|
1055
|
-
// Unsubscribe
|
|
1056
|
-
client.off(listener);
|
|
1057
|
-
|
|
1058
|
-
// Unsubscribe all
|
|
1059
|
-
client.offAll();
|
|
1060
|
-
```
|
|
1061
|
-
|
|
1062
|
-
### Health Check API (NEW!)
|
|
1063
|
-
|
|
1064
|
-
```typescript
|
|
1065
|
-
interface ConnectorHealth {
|
|
1066
|
-
initialized: boolean;
|
|
1067
|
-
walletStandardAvailable: boolean;
|
|
1068
|
-
storageAvailable: boolean;
|
|
1069
|
-
walletsDetected: number;
|
|
1070
|
-
errors: string[];
|
|
1071
|
-
connectionState: {
|
|
1072
|
-
connected: boolean;
|
|
1073
|
-
connecting: boolean;
|
|
1074
|
-
hasSelectedWallet: boolean;
|
|
1075
|
-
hasSelectedAccount: boolean;
|
|
1076
|
-
};
|
|
1077
|
-
timestamp: string;
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
// Get health status
|
|
1081
|
-
const health = client.getHealth();
|
|
1082
|
-
```
|
|
1083
|
-
|
|
1084
|
-
### Connection Pool API (NEW!)
|
|
1085
|
-
|
|
1086
|
-
```typescript
|
|
1087
|
-
class ConnectionPool {
|
|
1088
|
-
get(cluster: SolanaCluster): ConnectionLike;
|
|
1089
|
-
has(clusterId: string): boolean;
|
|
1090
|
-
clear(clusterId: string): void;
|
|
1091
|
-
clearAll(): void;
|
|
1092
|
-
getStats(): ConnectionPoolStats;
|
|
1093
|
-
resetStats(): void;
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
// Create pool
|
|
1097
|
-
const pool = createConnectionPool(options);
|
|
1098
|
-
|
|
1099
|
-
// Get global pool
|
|
1100
|
-
const pool = getConnectionPool();
|
|
1101
|
-
```
|
|
1102
|
-
|
|
1103
|
-
### Wallet Adapter Compat API (NEW!)
|
|
1104
|
-
|
|
1105
|
-
```typescript
|
|
1106
|
-
// Create wallet-adapter compatible interface
|
|
1107
|
-
createWalletAdapterCompat(
|
|
1108
|
-
signer: TransactionSigner | null,
|
|
1109
|
-
options: {
|
|
1110
|
-
disconnect: () => Promise<void>;
|
|
1111
|
-
transformTransaction?: (tx: any) => any;
|
|
1112
|
-
onError?: (error: Error, operation: string) => void;
|
|
1113
|
-
}
|
|
1114
|
-
): WalletAdapterCompatible
|
|
1115
|
-
|
|
1116
|
-
// React hook version
|
|
1117
|
-
useWalletAdapterCompat(
|
|
1118
|
-
signer: TransactionSigner | null,
|
|
1119
|
-
disconnect: () => Promise<void>,
|
|
1120
|
-
options?: Omit<WalletAdapterCompatOptions, 'disconnect'>
|
|
1121
|
-
): WalletAdapterCompatible
|
|
1122
|
-
|
|
1123
|
-
// Type guard
|
|
1124
|
-
isWalletAdapterCompatible(obj: any): obj is WalletAdapterCompatible
|
|
1125
|
-
```
|
|
1126
|
-
|
|
1127
|
-
### Polyfill API (NEW!)
|
|
1128
|
-
|
|
1129
|
-
```typescript
|
|
1130
|
-
import {
|
|
1131
|
-
installPolyfills,
|
|
1132
|
-
isPolyfillInstalled,
|
|
1133
|
-
isCryptoAvailable,
|
|
1134
|
-
getPolyfillStatus,
|
|
1135
|
-
} from '@solana/connector/headless';
|
|
1136
|
-
|
|
1137
|
-
// Install browser polyfills (automatic in React provider)
|
|
1138
|
-
installPolyfills();
|
|
1139
|
-
|
|
1140
|
-
// Check status
|
|
1141
|
-
const installed = isPolyfillInstalled();
|
|
1142
|
-
const cryptoAvailable = isCryptoAvailable();
|
|
1143
|
-
const status = getPolyfillStatus();
|
|
1144
|
-
```
|
|
959
|
+
| Function | Description |
|
|
960
|
+
|----------|-------------|
|
|
961
|
+
| `getDefaultConfig(options)` | Create default connector configuration |
|
|
962
|
+
| `getDefaultMobileConfig(options)` | Create mobile wallet adapter configuration |
|
|
963
|
+
| `createConfig(options)` | Create unified config for ConnectorKit + Armadura |
|
|
1145
964
|
|
|
1146
965
|
### Utility Functions
|
|
1147
966
|
|
|
1148
|
-
|
|
967
|
+
| Function | Description |
|
|
968
|
+
|----------|-------------|
|
|
969
|
+
| `formatAddress(address, options?)` | Format Solana address |
|
|
970
|
+
| `formatSOL(lamports, options?)` | Format SOL amount |
|
|
971
|
+
| `copyAddressToClipboard(address)` | Copy address to clipboard |
|
|
972
|
+
| `getTransactionUrl(cluster, signature)` | Get Solana Explorer transaction URL |
|
|
973
|
+
| `getAddressUrl(cluster, address)` | Get Solana Explorer address URL |
|
|
1149
974
|
|
|
1150
|
-
|
|
1151
|
-
import { formatSOL, formatAddress } from '@solana/connector';
|
|
1152
|
-
|
|
1153
|
-
// Format SOL amounts
|
|
1154
|
-
formatSOL(1500000000, { decimals: 4 }); // "1.5000 SOL"
|
|
1155
|
-
|
|
1156
|
-
// Format addresses
|
|
1157
|
-
formatAddress(address, { length: 6 }); // "5Gv8yU...8x3kF"
|
|
1158
|
-
|
|
1159
|
-
// Lightweight versions (smaller bundle, no Intl)
|
|
1160
|
-
import { formatSOLSimple, formatAddressSimple } from '@solana/connector';
|
|
1161
|
-
```
|
|
1162
|
-
|
|
1163
|
-
#### Clipboard
|
|
1164
|
-
|
|
1165
|
-
```typescript
|
|
1166
|
-
import { copyAddressToClipboard, copyToClipboard } from '@solana/connector';
|
|
1167
|
-
|
|
1168
|
-
await copyAddressToClipboard(address);
|
|
1169
|
-
await copyToClipboard(text);
|
|
1170
|
-
```
|
|
1171
|
-
|
|
1172
|
-
#### Cluster Utilities
|
|
1173
|
-
|
|
1174
|
-
```typescript
|
|
1175
|
-
import {
|
|
1176
|
-
getClusterRpcUrl,
|
|
1177
|
-
getClusterExplorerUrl,
|
|
1178
|
-
getTransactionUrl,
|
|
1179
|
-
getAddressUrl,
|
|
1180
|
-
isMainnetCluster,
|
|
1181
|
-
isDevnetCluster,
|
|
1182
|
-
} from '@solana/connector';
|
|
1183
|
-
|
|
1184
|
-
const rpcUrl = getClusterRpcUrl(cluster);
|
|
1185
|
-
const explorerUrl = getClusterExplorerUrl(cluster);
|
|
1186
|
-
const txUrl = getTransactionUrl(cluster, signature);
|
|
1187
|
-
const addrUrl = getAddressUrl(cluster, address);
|
|
1188
|
-
```
|
|
975
|
+
---
|
|
1189
976
|
|
|
1190
|
-
|
|
977
|
+
## Types
|
|
1191
978
|
|
|
1192
979
|
```typescript
|
|
1193
980
|
import type {
|
|
1194
981
|
// Configuration
|
|
1195
982
|
ConnectorConfig,
|
|
1196
983
|
DefaultConfigOptions,
|
|
1197
|
-
ExtendedConnectorConfig,
|
|
1198
984
|
UnifiedConfig,
|
|
1199
|
-
MobileWalletAdapterConfig,
|
|
1200
985
|
|
|
1201
986
|
// State & Info
|
|
1202
987
|
ConnectorState,
|
|
1203
988
|
ConnectorSnapshot,
|
|
1204
989
|
WalletInfo,
|
|
1205
990
|
AccountInfo,
|
|
1206
|
-
ConnectorHealth, // NEW!
|
|
1207
|
-
|
|
1208
|
-
// Events
|
|
1209
|
-
ConnectorEvent, // NEW!
|
|
1210
|
-
ConnectorEventListener, // NEW!
|
|
1211
|
-
|
|
1212
|
-
// Transaction Signing
|
|
1213
|
-
TransactionSigner, // NEW!
|
|
1214
|
-
TransactionSignerConfig, // NEW!
|
|
1215
|
-
TransactionSignerCapabilities, // NEW!
|
|
1216
|
-
SignedTransaction, // NEW!
|
|
1217
|
-
|
|
1218
|
-
// Connection Pooling
|
|
1219
|
-
ConnectionLike, // NEW!
|
|
1220
|
-
ConnectionPoolOptions, // NEW!
|
|
1221
|
-
ConnectionPoolStats, // NEW!
|
|
1222
|
-
|
|
1223
|
-
// Wallet Adapter Compat
|
|
1224
|
-
WalletAdapterCompatible, // NEW!
|
|
1225
|
-
WalletAdapterCompatOptions, // NEW!
|
|
1226
991
|
|
|
1227
992
|
// Wallet Standard
|
|
1228
993
|
Wallet,
|
|
@@ -1232,101 +997,17 @@ import type {
|
|
|
1232
997
|
SolanaCluster,
|
|
1233
998
|
SolanaClusterId,
|
|
1234
999
|
|
|
1235
|
-
// Storage
|
|
1236
|
-
StorageAdapter,
|
|
1237
|
-
|
|
1238
1000
|
// Hook Returns
|
|
1239
1001
|
UseClusterReturn,
|
|
1240
1002
|
UseAccountReturn,
|
|
1241
1003
|
UseWalletInfoReturn,
|
|
1242
|
-
UseTransactionSignerReturn,
|
|
1243
|
-
|
|
1244
|
-
// Errors
|
|
1245
|
-
WalletError,
|
|
1004
|
+
UseTransactionSignerReturn,
|
|
1005
|
+
UseKitTransactionSignerReturn,
|
|
1246
1006
|
} from '@solana/connector';
|
|
1247
1007
|
```
|
|
1248
1008
|
|
|
1249
1009
|
---
|
|
1250
1010
|
|
|
1251
|
-
## Performance
|
|
1252
|
-
|
|
1253
|
-
### Bundle Size
|
|
1254
|
-
|
|
1255
|
-
| Component | Size (gzipped) | Tree-Shakeable |
|
|
1256
|
-
| -------------------- | -------------- | ------------------ |
|
|
1257
|
-
| **Base Connector** | ~45KB | ✅ |
|
|
1258
|
-
| + Polyfills | +2KB | ❌ (auto-included) |
|
|
1259
|
-
| + Transaction Signer | +3KB | ✅ |
|
|
1260
|
-
| + Connection Pool | +1.5KB | ✅ |
|
|
1261
|
-
| + Debug Panel | +2KB | ✅ (dev-only) |
|
|
1262
|
-
| + Event System | +0.5KB | ✅ |
|
|
1263
|
-
| + Compat Layer | +2KB | ✅ |
|
|
1264
|
-
|
|
1265
|
-
**Total**: ~48-53KB for typical production usage
|
|
1266
|
-
|
|
1267
|
-
### Runtime Performance
|
|
1268
|
-
|
|
1269
|
-
- **40-60% fewer re-renders** via optimized state updates
|
|
1270
|
-
- **Connection pooling** reduces memory usage and initialization overhead
|
|
1271
|
-
- **Automatic tree-shaking** excludes unused features
|
|
1272
|
-
- **Debug panel** automatically excluded in production builds
|
|
1273
|
-
|
|
1274
|
-
---
|
|
1275
|
-
|
|
1276
|
-
## Browser Compatibility
|
|
1277
|
-
|
|
1278
|
-
Enhanced support for:
|
|
1279
|
-
|
|
1280
|
-
- ✅ Chrome/Edge 90+
|
|
1281
|
-
- ✅ Firefox 88+
|
|
1282
|
-
- ✅ Safari 14+
|
|
1283
|
-
- ✅ iOS Safari 14+
|
|
1284
|
-
- ✅ Chrome Mobile 90+
|
|
1285
|
-
- ✅ Samsung Internet 15+
|
|
1286
|
-
|
|
1287
|
-
Automatic polyfills ensure compatibility across all environments.
|
|
1288
|
-
|
|
1289
|
-
---
|
|
1290
|
-
|
|
1291
|
-
## Migration from @solana/wallet-adapter
|
|
1292
|
-
|
|
1293
|
-
Using the wallet-adapter compatibility bridge for gradual migration:
|
|
1294
|
-
|
|
1295
|
-
```typescript
|
|
1296
|
-
// Before (wallet-adapter)
|
|
1297
|
-
import { useWallet } from '@solana/wallet-adapter-react';
|
|
1298
|
-
|
|
1299
|
-
const { publicKey, sendTransaction } = useWallet();
|
|
1300
|
-
await sendTransaction(tx, connection);
|
|
1301
|
-
|
|
1302
|
-
// After (connector-kit)
|
|
1303
|
-
import { useTransactionSigner } from '@solana/connector';
|
|
1304
|
-
|
|
1305
|
-
const { signer } = useTransactionSigner();
|
|
1306
|
-
await signer.signAndSendTransaction(tx);
|
|
1307
|
-
|
|
1308
|
-
// Or use compatibility bridge for existing integrations
|
|
1309
|
-
import { createWalletAdapterCompat } from '@solana/connector/compat';
|
|
1310
|
-
|
|
1311
|
-
const walletAdapter = createWalletAdapterCompat(signer, { disconnect });
|
|
1312
|
-
// Pass walletAdapter to existing wallet-adapter code
|
|
1313
|
-
```
|
|
1314
|
-
|
|
1315
|
-
---
|
|
1316
|
-
|
|
1317
|
-
## Examples
|
|
1318
|
-
|
|
1319
|
-
Check out the [examples directory](../../examples/react) for:
|
|
1320
|
-
|
|
1321
|
-
- **React Example** - Complete wallet connection UI with shadcn/ui
|
|
1322
|
-
- **Vite Example** - Lightweight setup with Vite
|
|
1323
|
-
- **Transaction Signing** - Full transaction demos
|
|
1324
|
-
- **Network Switching** - Cluster/network management
|
|
1325
|
-
- **Account Management** - Multi-account support
|
|
1326
|
-
- **Mobile Support** - Solana Mobile Wallet Adapter
|
|
1327
|
-
|
|
1328
|
-
---
|
|
1329
|
-
|
|
1330
1011
|
## Development
|
|
1331
1012
|
|
|
1332
1013
|
### Commands
|
|
@@ -1353,9 +1034,6 @@ pnpm test
|
|
|
1353
1034
|
# Test in watch mode
|
|
1354
1035
|
pnpm test:watch
|
|
1355
1036
|
|
|
1356
|
-
# Test with UI
|
|
1357
|
-
pnpm test:ui
|
|
1358
|
-
|
|
1359
1037
|
# Coverage report
|
|
1360
1038
|
pnpm test:coverage
|
|
1361
1039
|
|
|
@@ -1363,65 +1041,17 @@ pnpm test:coverage
|
|
|
1363
1041
|
pnpm size
|
|
1364
1042
|
```
|
|
1365
1043
|
|
|
1366
|
-
### Project Structure
|
|
1367
|
-
|
|
1368
|
-
```
|
|
1369
|
-
packages/connector/
|
|
1370
|
-
├── src/
|
|
1371
|
-
│ ├── lib/
|
|
1372
|
-
│ │ ├── core/ # Core functionality
|
|
1373
|
-
│ │ │ ├── state-manager.ts
|
|
1374
|
-
│ │ │ ├── event-emitter.ts
|
|
1375
|
-
│ │ │ └── debug-metrics.ts
|
|
1376
|
-
│ │ ├── connection/ # Connection management
|
|
1377
|
-
│ │ │ ├── connection-manager.ts
|
|
1378
|
-
│ │ │ └── connection-pool.ts
|
|
1379
|
-
│ │ ├── transaction/ # Transaction signing
|
|
1380
|
-
│ │ │ ├── transaction-signer.ts
|
|
1381
|
-
│ │ │ └── signer-factory.ts
|
|
1382
|
-
│ │ └── storage/ # Persistence layer
|
|
1383
|
-
│ │ └── enhanced-storage.ts
|
|
1384
|
-
│ ├── hooks/ # React hooks
|
|
1385
|
-
│ │ ├── use-connector.ts
|
|
1386
|
-
│ │ ├── use-account.ts
|
|
1387
|
-
│ │ ├── use-cluster.ts
|
|
1388
|
-
│ │ └── use-transaction-signer.ts
|
|
1389
|
-
│ ├── components/ # React components
|
|
1390
|
-
│ │ └── debug-panel.tsx
|
|
1391
|
-
│ ├── compat/ # Wallet adapter compatibility
|
|
1392
|
-
│ │ └── wallet-adapter-compat.ts
|
|
1393
|
-
│ ├── __tests__/ # Test utilities
|
|
1394
|
-
│ │ ├── mocks/
|
|
1395
|
-
│ │ ├── fixtures/
|
|
1396
|
-
│ │ └── utils/
|
|
1397
|
-
│ └── index.ts # Main exports
|
|
1398
|
-
├── tsconfig.json
|
|
1399
|
-
├── tsup.config.ts
|
|
1400
|
-
├── vitest.config.ts
|
|
1401
|
-
└── package.json
|
|
1402
|
-
```
|
|
1403
|
-
|
|
1404
|
-
### Contributing
|
|
1405
|
-
|
|
1406
|
-
Contributions are welcome! Please:
|
|
1407
|
-
|
|
1408
|
-
1. Fork the repository
|
|
1409
|
-
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
|
1410
|
-
3. Make your changes
|
|
1411
|
-
4. Add tests for new functionality
|
|
1412
|
-
5. Ensure tests pass (`pnpm test`)
|
|
1413
|
-
6. Commit with conventional commits (`feat:`, `fix:`, `docs:`, etc.)
|
|
1414
|
-
7. Push to your fork and submit a pull request
|
|
1415
|
-
|
|
1416
|
-
For detailed testing guidelines, see [Testing Guide](src/__tests__/README.md).
|
|
1417
|
-
|
|
1418
1044
|
---
|
|
1419
1045
|
|
|
1420
|
-
##
|
|
1046
|
+
## Examples
|
|
1421
1047
|
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
- **
|
|
1048
|
+
Check out the [examples directory](../../examples/next-js) for complete working examples:
|
|
1049
|
+
|
|
1050
|
+
- **Next.js Example** - Full-featured wallet connection UI with shadcn/ui
|
|
1051
|
+
- **Transaction Signing** - Modern and legacy transaction examples
|
|
1052
|
+
- **Network Switching** - Cluster/network management
|
|
1053
|
+
- **Account Management** - Multi-account support
|
|
1054
|
+
- **Mobile Support** - Solana Mobile Wallet Adapter
|
|
1425
1055
|
|
|
1426
1056
|
---
|
|
1427
1057
|
|
|
@@ -1439,22 +1069,6 @@ Compatible with all [Wallet Standard](https://github.com/wallet-standard/wallet-
|
|
|
1439
1069
|
|
|
1440
1070
|
---
|
|
1441
1071
|
|
|
1442
|
-
##
|
|
1443
|
-
|
|
1444
|
-
- [ConnectorKit Documentation](https://connectorkit.dev) - Full documentation site
|
|
1445
|
-
- [API Reference](#complete-api-reference) - Complete TypeScript API
|
|
1446
|
-
- [Examples](../../examples/react) - Working examples
|
|
1447
|
-
- [Wallet Standard Spec](https://github.com/wallet-standard/wallet-standard) - Protocol specification
|
|
1448
|
-
- [@solana/connector on NPM](https://www.npmjs.com/package/@solana/connector) - Package page
|
|
1449
|
-
|
|
1450
|
-
---
|
|
1451
|
-
|
|
1452
|
-
## Source
|
|
1453
|
-
|
|
1454
|
-
- [GitHub Repository](https://github.com/your-org/connectorkit)
|
|
1455
|
-
- [Examples Directory](../../examples)
|
|
1456
|
-
- [Package Directory](../../packages/connector)
|
|
1457
|
-
|
|
1458
|
-
Built with ❤️ for the Solana ecosystem.
|
|
1072
|
+
## License
|
|
1459
1073
|
|
|
1460
|
-
|
|
1074
|
+
MIT
|