@solana/react-hooks 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +316 -0
- package/package.json +85 -0
package/README.md
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# @solana/react-hooks
|
|
2
|
+
|
|
3
|
+
React hooks for `@solana/client-core`. Drop in the provider and call hooks instead of juggling RPC
|
|
4
|
+
clients, wallets, and stores yourself.
|
|
5
|
+
|
|
6
|
+
> **Status:** Experimental – breaking changes may land often.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pnpm add @solana/react-hooks
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Minimal example
|
|
15
|
+
|
|
16
|
+
Mount the provider once and call hooks anywhere in the subtree.
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
import {
|
|
20
|
+
SolanaClientProvider,
|
|
21
|
+
useBalance,
|
|
22
|
+
useConnectWallet,
|
|
23
|
+
useWallet,
|
|
24
|
+
} from '@solana/react-hooks';
|
|
25
|
+
|
|
26
|
+
function WalletButton() {
|
|
27
|
+
const connectWallet = useConnectWallet();
|
|
28
|
+
return <button onClick={() => connectWallet('phantom')}>Connect Phantom</button>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function WalletBalance() {
|
|
32
|
+
const wallet = useWallet();
|
|
33
|
+
const balance = useBalance(wallet.status === 'connected' ? wallet.session.account.address : undefined);
|
|
34
|
+
|
|
35
|
+
if (wallet.status !== 'connected') return <p>Connect a wallet</p>;
|
|
36
|
+
if (balance.fetching) return <p>Loading…</p>;
|
|
37
|
+
|
|
38
|
+
return <p>Lamports: {balance.lamports?.toString() ?? '0'}</p>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function App() {
|
|
42
|
+
return (
|
|
43
|
+
<SolanaClientProvider
|
|
44
|
+
config={{
|
|
45
|
+
endpoint: 'https://api.devnet.solana.com',
|
|
46
|
+
websocketEndpoint: 'wss://api.devnet.solana.com',
|
|
47
|
+
}}
|
|
48
|
+
>
|
|
49
|
+
<WalletButton />
|
|
50
|
+
<WalletBalance />
|
|
51
|
+
</SolanaClientProvider>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Hooks at a glance
|
|
57
|
+
|
|
58
|
+
- `useWallet`, `useConnectWallet`, `useDisconnectWallet` – read or update the current wallet session.
|
|
59
|
+
- `useBalance` / `useAccount` – fetch lamports once or keep account data in sync.
|
|
60
|
+
- `useSolTransfer`, `useSplToken`, `useTransactionPool` – helper-driven flows for SOL, SPL, and
|
|
61
|
+
general transactions.
|
|
62
|
+
- `useClientStore` – access the underlying Zustand store if you need low-level state.
|
|
63
|
+
|
|
64
|
+
### Wallet helpers
|
|
65
|
+
|
|
66
|
+
Read the current wallet session and expose connect/disconnect buttons.
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
const WalletActions = () => {
|
|
70
|
+
const wallet = useWallet();
|
|
71
|
+
const connect = useConnectWallet();
|
|
72
|
+
const disconnect = useDisconnectWallet();
|
|
73
|
+
|
|
74
|
+
if (wallet.status === 'connected') {
|
|
75
|
+
return (
|
|
76
|
+
<div>
|
|
77
|
+
<p>{wallet.session.account.address.toString()}</p>
|
|
78
|
+
<button onClick={() => disconnect()}>Disconnect</button>
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return <button onClick={() => connect('phantom')}>Connect Phantom</button>;
|
|
84
|
+
};
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Balance watcher
|
|
88
|
+
|
|
89
|
+
Read lamports (cached plus live updates) for any address.
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
import { useBalance } from '@solana/react-hooks';
|
|
93
|
+
|
|
94
|
+
function BalanceCard({ address }) {
|
|
95
|
+
const { lamports, fetching, slot } = useBalance(address);
|
|
96
|
+
if (fetching) return <p>Loading…</p>;
|
|
97
|
+
return (
|
|
98
|
+
<div>
|
|
99
|
+
<p>Lamports: {lamports?.toString() ?? '0'}</p>
|
|
100
|
+
<small>Last slot: {slot?.toString() ?? 'unknown'}</small>
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Account cache
|
|
107
|
+
|
|
108
|
+
Fetch account data and optionally keep it in sync via subscriptions.
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
import { useAccount } from '@solana/react-hooks';
|
|
112
|
+
|
|
113
|
+
function AccountInspector({ address }) {
|
|
114
|
+
const account = useAccount(address, { watch: true });
|
|
115
|
+
|
|
116
|
+
if (!account) return <p>Loading…</p>;
|
|
117
|
+
if (account.error) return <p>Error loading account</p>;
|
|
118
|
+
|
|
119
|
+
return <pre>{JSON.stringify(account.data, null, 2)}</pre>;
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### SOL transfers
|
|
124
|
+
|
|
125
|
+
Trigger SOL transfers with built-in status tracking.
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
import { useSolTransfer } from '@solana/react-hooks';
|
|
129
|
+
|
|
130
|
+
const SendSolButton = ({ destination, amount }) => {
|
|
131
|
+
const { send, isSending } = useSolTransfer();
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<button
|
|
135
|
+
disabled={isSending}
|
|
136
|
+
onClick={() =>
|
|
137
|
+
send({
|
|
138
|
+
destination,
|
|
139
|
+
lamports: amount,
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
>
|
|
143
|
+
{isSending ? 'Sending…' : 'Send SOL'}
|
|
144
|
+
</button>
|
|
145
|
+
);
|
|
146
|
+
};
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### SPL tokens
|
|
150
|
+
|
|
151
|
+
Scope SPL helpers by mint and reuse the same API for balances and transfers.
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
const SplBalance = ({ mint }) => {
|
|
155
|
+
const { balance, send, isSending } = useSplToken(mint);
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<div>
|
|
159
|
+
<p>Amount: {balance?.uiAmount ?? '0'}</p>
|
|
160
|
+
<button
|
|
161
|
+
disabled={isSending}
|
|
162
|
+
onClick={() =>
|
|
163
|
+
send({
|
|
164
|
+
amount: 1n,
|
|
165
|
+
destinationOwner: 'Destination111111111111111111111111',
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
>
|
|
169
|
+
Send 1 token
|
|
170
|
+
</button>
|
|
171
|
+
</div>
|
|
172
|
+
);
|
|
173
|
+
};
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Transaction pool
|
|
177
|
+
|
|
178
|
+
Compose instructions, refresh blockhashes automatically, and send transactions from one hook.
|
|
179
|
+
|
|
180
|
+
```tsx
|
|
181
|
+
import type { TransactionInstructionInput } from '@solana/client-core';
|
|
182
|
+
|
|
183
|
+
const useMemoizedInstruction = (): TransactionInstructionInput => ({
|
|
184
|
+
accounts: [],
|
|
185
|
+
data: new Uint8Array(),
|
|
186
|
+
programAddress: 'Example1111111111111111111111111111111111',
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const TransactionFlow = () => {
|
|
190
|
+
const instruction = useMemoizedInstruction();
|
|
191
|
+
const {
|
|
192
|
+
addInstruction,
|
|
193
|
+
prepareAndSend,
|
|
194
|
+
sendStatus,
|
|
195
|
+
latestBlockhash,
|
|
196
|
+
} = useTransactionPool();
|
|
197
|
+
|
|
198
|
+
return (
|
|
199
|
+
<div>
|
|
200
|
+
<button onClick={() => addInstruction(instruction)}>Add instruction</button>
|
|
201
|
+
<button disabled={sendStatus === 'loading'} onClick={() => prepareAndSend()}>
|
|
202
|
+
{sendStatus === 'loading' ? 'Sending…' : 'Prepare & Send'}
|
|
203
|
+
</button>
|
|
204
|
+
<p>Recent blockhash: {latestBlockhash.blockhash ?? 'loading…'}</p>
|
|
205
|
+
</div>
|
|
206
|
+
);
|
|
207
|
+
};
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Client store access
|
|
211
|
+
|
|
212
|
+
Drop down to the underlying Zustand store when you need bespoke selectors.
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
import { useClientStore } from '@solana/react-hooks';
|
|
216
|
+
|
|
217
|
+
function ClusterStatus() {
|
|
218
|
+
const cluster = useClientStore((state) => state.cluster);
|
|
219
|
+
return <p>Cluster: {cluster.status.status}</p>;
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Query hooks
|
|
224
|
+
|
|
225
|
+
Wrap a subtree with `<SolanaQueryProvider>` and call hooks like `useLatestBlockhash`,
|
|
226
|
+
`useProgramAccounts`, or `useSimulateTransaction`. Every hook returns `{ data, status, refresh }` so
|
|
227
|
+
you can read the current value and trigger a refetch:
|
|
228
|
+
|
|
229
|
+
### Latest blockhash
|
|
230
|
+
|
|
231
|
+
Poll or refetch the cluster's latest blockhash.
|
|
232
|
+
|
|
233
|
+
```tsx
|
|
234
|
+
import { useLatestBlockhash } from '@solana/react-hooks';
|
|
235
|
+
|
|
236
|
+
function BlockhashTicker() {
|
|
237
|
+
const { blockhash, status, refresh } = useLatestBlockhash({ refreshInterval: 20_000 });
|
|
238
|
+
|
|
239
|
+
return (
|
|
240
|
+
<div>
|
|
241
|
+
<button onClick={() => refresh()}>Refresh</button>
|
|
242
|
+
<p>Status: {status}</p>
|
|
243
|
+
<p>Blockhash: {blockhash ?? 'loading…'}</p>
|
|
244
|
+
</div>
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Program accounts
|
|
250
|
+
|
|
251
|
+
```tsx
|
|
252
|
+
import { SolanaQueryProvider, useProgramAccounts } from '@solana/react-hooks';
|
|
253
|
+
|
|
254
|
+
function ProgramAccountsList({ programAddress }) {
|
|
255
|
+
const { data, status, refresh } = useProgramAccounts(programAddress);
|
|
256
|
+
|
|
257
|
+
if (status === 'loading') return <p>Loading…</p>;
|
|
258
|
+
if (status === 'error') return <p>Retry later.</p>;
|
|
259
|
+
|
|
260
|
+
return (
|
|
261
|
+
<div>
|
|
262
|
+
<button onClick={() => refresh()}>Refresh</button>
|
|
263
|
+
<ul>
|
|
264
|
+
{data?.map(({ pubkey }) => (
|
|
265
|
+
<li key={pubkey.toString()}>{pubkey.toString()}</li>
|
|
266
|
+
))}
|
|
267
|
+
</ul>
|
|
268
|
+
</div>
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function QueryDemo({ programAddress }) {
|
|
273
|
+
return (
|
|
274
|
+
<SolanaClientProvider config={{ endpoint: 'https://api.devnet.solana.com' }}>
|
|
275
|
+
<SolanaQueryProvider>
|
|
276
|
+
<ProgramAccountsList programAddress={programAddress} />
|
|
277
|
+
</SolanaQueryProvider>
|
|
278
|
+
</SolanaClientProvider>
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Transaction simulation
|
|
284
|
+
|
|
285
|
+
Simulate any transaction payload (wire string or object) and read RPC logs.
|
|
286
|
+
|
|
287
|
+
```tsx
|
|
288
|
+
import { useSimulateTransaction } from '@solana/react-hooks';
|
|
289
|
+
|
|
290
|
+
function SimulationLogs({ transaction }) {
|
|
291
|
+
const { logs, status, refresh } = useSimulateTransaction(transaction);
|
|
292
|
+
|
|
293
|
+
if (status === 'loading') return <p>Simulating…</p>;
|
|
294
|
+
if (status === 'error') return <p>Simulation failed.</p>;
|
|
295
|
+
|
|
296
|
+
return (
|
|
297
|
+
<div>
|
|
298
|
+
<button onClick={() => refresh()}>Re-run</button>
|
|
299
|
+
<pre>{JSON.stringify(logs ?? [], null, 2)}</pre>
|
|
300
|
+
</div>
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Going further
|
|
306
|
+
|
|
307
|
+
- Need Wallet Standard buttons or sign/send helpers? Use `useSignIn`, `useSignMessage`,
|
|
308
|
+
`useSignTransaction`, and friends from `walletStandardHooks.ts`.
|
|
309
|
+
- Looking for examples? See `examples/react-hooks` for a ready-to-run playground that wires the
|
|
310
|
+
provider, hooks, and mock UIs together.
|
|
311
|
+
|
|
312
|
+
## Scripts
|
|
313
|
+
|
|
314
|
+
- `pnpm build` – run both JS compilation and type definition emit
|
|
315
|
+
- `pnpm test:typecheck` – strict type-checking without emit
|
|
316
|
+
- `pnpm lint` / `pnpm format` – Biome-powered linting and formatting
|
package/package.json
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@solana/react-hooks",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "React hooks for the @solana/client-core Solana client",
|
|
5
|
+
"exports": {
|
|
6
|
+
"edge-light": {
|
|
7
|
+
"import": "./dist/index.node.mjs",
|
|
8
|
+
"require": "./dist/index.node.cjs"
|
|
9
|
+
},
|
|
10
|
+
"workerd": {
|
|
11
|
+
"import": "./dist/index.node.mjs",
|
|
12
|
+
"require": "./dist/index.node.cjs"
|
|
13
|
+
},
|
|
14
|
+
"browser": {
|
|
15
|
+
"import": "./dist/index.browser.mjs",
|
|
16
|
+
"require": "./dist/index.browser.cjs"
|
|
17
|
+
},
|
|
18
|
+
"node": {
|
|
19
|
+
"import": "./dist/index.node.mjs",
|
|
20
|
+
"require": "./dist/index.node.cjs"
|
|
21
|
+
},
|
|
22
|
+
"react-native": "./dist/index.native.mjs",
|
|
23
|
+
"types": "./dist/types/index.d.ts"
|
|
24
|
+
},
|
|
25
|
+
"browser": {
|
|
26
|
+
"./dist/index.node.cjs": "./dist/index.browser.cjs",
|
|
27
|
+
"./dist/index.node.mjs": "./dist/index.browser.mjs"
|
|
28
|
+
},
|
|
29
|
+
"main": "./dist/index.node.cjs",
|
|
30
|
+
"module": "./dist/index.node.mjs",
|
|
31
|
+
"react-native": "./dist/index.native.mjs",
|
|
32
|
+
"types": "./dist/types/index.d.ts",
|
|
33
|
+
"type": "commonjs",
|
|
34
|
+
"files": [
|
|
35
|
+
"./dist/"
|
|
36
|
+
],
|
|
37
|
+
"sideEffects": false,
|
|
38
|
+
"keywords": [
|
|
39
|
+
"solana",
|
|
40
|
+
"web3",
|
|
41
|
+
"react",
|
|
42
|
+
"hooks"
|
|
43
|
+
],
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "pnpm compile:js && pnpm compile:typedefs",
|
|
46
|
+
"compile:js": "tsup --config ../build-scripts/tsup.config.package.ts",
|
|
47
|
+
"compile:typedefs": "tsc -p ./tsconfig.declarations.json",
|
|
48
|
+
"format": "biome check --write src",
|
|
49
|
+
"lint": "biome check src",
|
|
50
|
+
"test:typecheck": "tsc --noEmit",
|
|
51
|
+
"test": "vitest run --config ../../vitest.config.ts",
|
|
52
|
+
"typecheck": "pnpm test:typecheck"
|
|
53
|
+
},
|
|
54
|
+
"author": "Solana Labs Maintainers <maintainers@solanalabs.com>",
|
|
55
|
+
"license": "MIT",
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"@solana/client-core": "workspace:*",
|
|
58
|
+
"@solana/addresses": "^5.0.0",
|
|
59
|
+
"@solana/codecs-core": "^5.0.0",
|
|
60
|
+
"@solana/errors": "^5.0.0",
|
|
61
|
+
"@solana/keys": "^5.0.0",
|
|
62
|
+
"@solana/promises": "^5.0.0",
|
|
63
|
+
"@solana/signers": "^5.0.0",
|
|
64
|
+
"@solana/transaction-messages": "^5.0.0",
|
|
65
|
+
"@solana/transactions": "^5.0.0",
|
|
66
|
+
"@solana/wallet-standard-features": "^1.3.0",
|
|
67
|
+
"@solana/kit": "^5.0.0",
|
|
68
|
+
"swr": "^2.3.6",
|
|
69
|
+
"zustand": "^5.0.0",
|
|
70
|
+
"@wallet-standard/base": "^1.1.0",
|
|
71
|
+
"@wallet-standard/errors": "^0.1.1",
|
|
72
|
+
"@wallet-standard/ui": "^1.0.1",
|
|
73
|
+
"@wallet-standard/ui-registry": "^1.0.1"
|
|
74
|
+
},
|
|
75
|
+
"devDependencies": {
|
|
76
|
+
"@types/react": "^19",
|
|
77
|
+
"react": "^19.0.0"
|
|
78
|
+
},
|
|
79
|
+
"peerDependencies": {
|
|
80
|
+
"react": ">=18"
|
|
81
|
+
},
|
|
82
|
+
"engines": {
|
|
83
|
+
"node": ">=20.18.0"
|
|
84
|
+
}
|
|
85
|
+
}
|