@satoshai/kit 0.6.0 → 0.8.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 +193 -16
- package/dist/index.cjs +227 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +446 -7
- package/dist/index.d.ts +446 -7
- package/dist/index.js +223 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,14 +11,16 @@ Typesafe Stacks wallet & contract interaction library for React. Wagmi-inspired
|
|
|
11
11
|
- **`StacksWalletProvider`** — React context provider for wallet state
|
|
12
12
|
- **`useConnect` / `useDisconnect`** — Connect and disconnect wallets
|
|
13
13
|
- **`useWallets`** — Configured wallets with availability status
|
|
14
|
-
- **`useAddress`** — Access connected wallet address and status
|
|
14
|
+
- **`useAddress`** — Access connected wallet address and status (discriminated union)
|
|
15
15
|
- **`useSignMessage`** — Sign arbitrary messages
|
|
16
16
|
- **`useSignStructuredMessage`** — Sign SIP-018 structured data
|
|
17
17
|
- **`useSignTransaction`** — Sign serialized transactions (sponsored tx flows)
|
|
18
|
-
- **`useWriteContract`** — Call smart contracts with post-conditions
|
|
18
|
+
- **`useWriteContract`** — Call smart contracts with post-conditions (typed or untyped)
|
|
19
19
|
- **`useTransferSTX`** — Native STX transfers
|
|
20
20
|
- **`useBnsName`** — Resolve BNS v2 names
|
|
21
|
+
- **Typed errors** — `BaseError`, `WalletNotConnectedError`, `WalletNotFoundError`, `UnsupportedMethodError`, `WalletRequestError`
|
|
21
22
|
- **6 wallets supported** — Xverse, Leather, OKX, Asigna, Fordefi, WalletConnect
|
|
23
|
+
- **WalletConnect session management** — Zombie session detection, wallet-initiated disconnect, and account change events
|
|
22
24
|
- **Next.js App Router compatible** — `"use client"` directives included
|
|
23
25
|
|
|
24
26
|
## Install
|
|
@@ -75,13 +77,22 @@ Wrap your app to provide wallet context to all hooks.
|
|
|
75
77
|
connectModal={true} // optional — defaults to true
|
|
76
78
|
walletConnect={{ projectId: '...' }} // optional — enables WalletConnect
|
|
77
79
|
onConnect={(provider, address) => {}} // optional
|
|
78
|
-
onAddressChange={(newAddress) => {}} // optional — Xverse account switching
|
|
80
|
+
onAddressChange={(newAddress) => {}} // optional — Xverse/WalletConnect account switching
|
|
79
81
|
onDisconnect={() => {}} // optional
|
|
80
82
|
>
|
|
81
83
|
{children}
|
|
82
84
|
</StacksWalletProvider>
|
|
83
85
|
```
|
|
84
86
|
|
|
87
|
+
| Prop | Type | Default | Description |
|
|
88
|
+
|------|------|---------|-------------|
|
|
89
|
+
| `wallets` | `SupportedStacksWallet[]` | All 6 wallets | Wallets to enable. |
|
|
90
|
+
| `connectModal` | `boolean` | `true` | Show `@stacks/connect` modal on `connect()` with no args. |
|
|
91
|
+
| `walletConnect` | `{ projectId, metadata?, chains? }` | — | WalletConnect config. Required when `wallets` includes `'wallet-connect'`. |
|
|
92
|
+
| `onConnect` | `(provider, address) => void` | — | Called after successful connection. |
|
|
93
|
+
| `onAddressChange` | `(newAddress) => void` | — | Called when the connected account changes. |
|
|
94
|
+
| `onDisconnect` | `() => void` | — | Called when the wallet disconnects. |
|
|
95
|
+
|
|
85
96
|
> If `wallets` includes `'wallet-connect'`, you must provide `walletConnect.projectId` or the provider will throw at mount.
|
|
86
97
|
|
|
87
98
|
> **Important:** Define `wallets` and `walletConnect` outside of your component (or memoize them) so they remain referentially stable across renders. These values are treated as static configuration.
|
|
@@ -111,8 +122,10 @@ When `connectModal` is enabled:
|
|
|
111
122
|
|
|
112
123
|
### `useConnect()`
|
|
113
124
|
|
|
125
|
+
Connect to a Stacks wallet. Returns a mutation-style object.
|
|
126
|
+
|
|
114
127
|
```ts
|
|
115
|
-
const { connect, reset, isPending } = useConnect();
|
|
128
|
+
const { connect, reset, error, isPending, isSuccess, isError, isIdle, status } = useConnect();
|
|
116
129
|
|
|
117
130
|
// Open the @stacks/connect modal (when connectModal is enabled, the default)
|
|
118
131
|
await connect();
|
|
@@ -153,8 +166,10 @@ A wallet is `available` when its browser extension is installed. For `wallet-con
|
|
|
153
166
|
|
|
154
167
|
### `useDisconnect()`
|
|
155
168
|
|
|
169
|
+
Disconnect the current wallet and clear the persisted session.
|
|
170
|
+
|
|
156
171
|
```ts
|
|
157
|
-
const { disconnect } = useDisconnect();
|
|
172
|
+
const { disconnect, reset, error, isSuccess, isError, isIdle, isPending, status } = useDisconnect();
|
|
158
173
|
|
|
159
174
|
disconnect();
|
|
160
175
|
disconnect(() => { /* callback after disconnect */ });
|
|
@@ -162,24 +177,29 @@ disconnect(() => { /* callback after disconnect */ });
|
|
|
162
177
|
|
|
163
178
|
### `useAddress()`
|
|
164
179
|
|
|
180
|
+
Read the connected wallet's address and connection status. Returns a **discriminated union** — when `isConnected` is `true`, `address` and `provider` are narrowed to defined values (no null checks needed).
|
|
181
|
+
|
|
165
182
|
```ts
|
|
166
183
|
const { address, isConnected, isConnecting, isDisconnected, provider } = useAddress();
|
|
167
184
|
|
|
168
185
|
if (isConnected) {
|
|
169
|
-
console.log(address); // 'SP...' or 'ST...'
|
|
186
|
+
console.log(address); // 'SP...' or 'ST...' — narrowed to string
|
|
170
187
|
console.log(provider); // 'xverse' | 'leather' | ...
|
|
171
188
|
}
|
|
172
189
|
```
|
|
173
190
|
|
|
174
191
|
### `useSignMessage()`
|
|
175
192
|
|
|
193
|
+
Sign an arbitrary plaintext message.
|
|
194
|
+
|
|
176
195
|
```ts
|
|
177
|
-
const { signMessage, signMessageAsync, data, error, isPending } = useSignMessage();
|
|
196
|
+
const { signMessage, signMessageAsync, data, error, isPending, reset } = useSignMessage();
|
|
178
197
|
|
|
179
198
|
// Callback style
|
|
180
199
|
signMessage({ message: 'Hello Stacks' }, {
|
|
181
200
|
onSuccess: ({ publicKey, signature }) => {},
|
|
182
201
|
onError: (error) => {},
|
|
202
|
+
onSettled: (data, error) => {},
|
|
183
203
|
});
|
|
184
204
|
|
|
185
205
|
// Async style
|
|
@@ -190,7 +210,7 @@ const { publicKey, signature } = await signMessageAsync({ message: 'Hello Stacks
|
|
|
190
210
|
|
|
191
211
|
Sign SIP-018 structured data for typed, verifiable off-chain messages.
|
|
192
212
|
|
|
193
|
-
> **Note:** OKX wallet does not support structured message signing and will throw an
|
|
213
|
+
> **Note:** OKX wallet does not support structured message signing and will throw an `UnsupportedMethodError`.
|
|
194
214
|
|
|
195
215
|
```ts
|
|
196
216
|
import { tupleCV, stringAsciiCV, uintCV } from '@stacks/transactions';
|
|
@@ -222,14 +242,18 @@ const { publicKey, signature } = await signStructuredMessageAsync({
|
|
|
222
242
|
|
|
223
243
|
### `useTransferSTX()`
|
|
224
244
|
|
|
245
|
+
Transfer native STX tokens. Amount is in **microSTX** (1 STX = 1,000,000 microSTX).
|
|
246
|
+
|
|
225
247
|
```ts
|
|
226
|
-
const { transferSTX, transferSTXAsync, data, error, isPending } = useTransferSTX();
|
|
248
|
+
const { transferSTX, transferSTXAsync, data, error, isPending, reset } = useTransferSTX();
|
|
227
249
|
|
|
228
250
|
// Callback style
|
|
229
251
|
transferSTX({
|
|
230
252
|
recipient: 'SP2...',
|
|
231
|
-
amount: 1000000n, //
|
|
253
|
+
amount: 1000000n, // 1 STX
|
|
232
254
|
memo: 'optional memo',
|
|
255
|
+
fee: 2000n, // optional custom fee
|
|
256
|
+
nonce: 42n, // optional custom nonce
|
|
233
257
|
}, {
|
|
234
258
|
onSuccess: (txid) => {},
|
|
235
259
|
onError: (error) => {},
|
|
@@ -244,10 +268,14 @@ const txid = await transferSTXAsync({
|
|
|
244
268
|
|
|
245
269
|
### `useWriteContract()`
|
|
246
270
|
|
|
271
|
+
Call a public function on a Clarity smart contract. Supports two modes:
|
|
272
|
+
|
|
273
|
+
#### Untyped mode (ClarityValue[] args)
|
|
274
|
+
|
|
247
275
|
```ts
|
|
248
|
-
import { Pc, PostConditionMode } from '@stacks/transactions';
|
|
276
|
+
import { uintCV, Pc, PostConditionMode } from '@stacks/transactions';
|
|
249
277
|
|
|
250
|
-
const { writeContract, writeContractAsync, data, error, isPending } = useWriteContract();
|
|
278
|
+
const { writeContract, writeContractAsync, data, error, isPending, reset } = useWriteContract();
|
|
251
279
|
|
|
252
280
|
writeContract({
|
|
253
281
|
address: 'SP...',
|
|
@@ -264,14 +292,58 @@ writeContract({
|
|
|
264
292
|
});
|
|
265
293
|
```
|
|
266
294
|
|
|
295
|
+
#### Typed mode (with ABI — autocomplete + type-checked args)
|
|
296
|
+
|
|
297
|
+
When you pass an `abi` object, `functionName` is autocompleted from the ABI's public functions and `args` becomes a named, type-checked object. Use [`@satoshai/abi-cli`](https://github.com/satoshai-dev/abi-cli) to generate typed ABIs from deployed contracts.
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
import { PostConditionMode } from '@stacks/transactions';
|
|
301
|
+
import type { ClarityAbi } from '@satoshai/kit';
|
|
302
|
+
|
|
303
|
+
// 1. Define your ABI (use @satoshai/abi-cli to generate it — https://github.com/satoshai-dev/abi-cli)
|
|
304
|
+
const poolAbi = { functions: [...], ... } as const satisfies ClarityAbi;
|
|
305
|
+
|
|
306
|
+
// 2. Call with full type safety
|
|
307
|
+
const txid = await writeContractAsync({
|
|
308
|
+
abi: poolAbi,
|
|
309
|
+
address: 'SP...',
|
|
310
|
+
contract: 'pool-v1',
|
|
311
|
+
functionName: 'deposit', // autocompleted
|
|
312
|
+
args: { amount: 1000000n }, // named args, type-checked
|
|
313
|
+
pc: { postConditions: [], mode: PostConditionMode.Deny },
|
|
314
|
+
});
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
#### `createContractConfig()`
|
|
318
|
+
|
|
319
|
+
Pre-bind ABI + address + contract for reuse across multiple calls:
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
import { createContractConfig } from '@satoshai/kit';
|
|
323
|
+
|
|
324
|
+
const pool = createContractConfig({
|
|
325
|
+
abi: poolAbi,
|
|
326
|
+
address: 'SP...',
|
|
327
|
+
contract: 'pool-v1',
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// Spread into writeContract — functionName and args stay typed
|
|
331
|
+
writeContract({
|
|
332
|
+
...pool,
|
|
333
|
+
functionName: 'deposit',
|
|
334
|
+
args: { amount: 1000000n },
|
|
335
|
+
pc: { postConditions: [], mode: PostConditionMode.Deny },
|
|
336
|
+
});
|
|
337
|
+
```
|
|
338
|
+
|
|
267
339
|
### `useSignTransaction()`
|
|
268
340
|
|
|
269
341
|
Sign a serialized transaction without automatically broadcasting it. Useful for sponsored transaction flows where a separate service pays the fee.
|
|
270
342
|
|
|
271
|
-
> **Note:** OKX wallet does not support raw transaction signing and will throw an
|
|
343
|
+
> **Note:** OKX wallet does not support raw transaction signing and will throw an `UnsupportedMethodError`.
|
|
272
344
|
|
|
273
345
|
```ts
|
|
274
|
-
const { signTransaction, signTransactionAsync, data, error, isPending } = useSignTransaction();
|
|
346
|
+
const { signTransaction, signTransactionAsync, data, error, isPending, reset } = useSignTransaction();
|
|
275
347
|
|
|
276
348
|
// Callback style
|
|
277
349
|
signTransaction({ transaction: '0x0100...', broadcast: false }, {
|
|
@@ -288,6 +360,8 @@ const { transaction, txid } = await signTransactionAsync({
|
|
|
288
360
|
|
|
289
361
|
### `useBnsName()`
|
|
290
362
|
|
|
363
|
+
Resolve a BNS v2 primary name for a Stacks address. Returns `null` when no name is registered.
|
|
364
|
+
|
|
291
365
|
```ts
|
|
292
366
|
const { bnsName, isLoading } = useBnsName(address);
|
|
293
367
|
// bnsName = 'satoshi.btc' | null
|
|
@@ -296,14 +370,94 @@ const { bnsName, isLoading } = useBnsName(address);
|
|
|
296
370
|
### Utilities
|
|
297
371
|
|
|
298
372
|
```ts
|
|
299
|
-
import {
|
|
300
|
-
|
|
373
|
+
import {
|
|
374
|
+
getNetworkFromAddress,
|
|
375
|
+
getStacksWallets,
|
|
376
|
+
getLocalStorageWallet,
|
|
377
|
+
createContractConfig,
|
|
378
|
+
} from '@satoshai/kit';
|
|
379
|
+
|
|
380
|
+
// Infer network from address prefix
|
|
301
381
|
getNetworkFromAddress('SP...'); // 'mainnet'
|
|
302
382
|
getNetworkFromAddress('ST...'); // 'testnet'
|
|
303
383
|
|
|
384
|
+
// Detect supported and installed wallets
|
|
304
385
|
const { supported, installed } = getStacksWallets();
|
|
386
|
+
|
|
387
|
+
// Read persisted wallet session (returns null on server or when empty)
|
|
388
|
+
const session = getLocalStorageWallet();
|
|
389
|
+
// { address: 'SP...', provider: 'xverse' } | null
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
## Mutation Hook Return Types
|
|
393
|
+
|
|
394
|
+
All mutation hooks (`useConnect`, `useSignMessage`, `useWriteContract`, etc.) return the same status shape:
|
|
395
|
+
|
|
396
|
+
| Field | Type | Description |
|
|
397
|
+
|-------|------|-------------|
|
|
398
|
+
| `data` | `T \| undefined` | The successful result. |
|
|
399
|
+
| `error` | `BaseError \| null` | The error, if any. |
|
|
400
|
+
| `status` | `'idle' \| 'pending' \| 'error' \| 'success'` | Current mutation status. |
|
|
401
|
+
| `isIdle` | `boolean` | `true` when no operation has been triggered. |
|
|
402
|
+
| `isPending` | `boolean` | `true` while waiting for wallet response. |
|
|
403
|
+
| `isSuccess` | `boolean` | `true` after a successful operation. |
|
|
404
|
+
| `isError` | `boolean` | `true` after a failed operation. |
|
|
405
|
+
| `reset()` | `() => void` | Reset the mutation state back to idle. |
|
|
406
|
+
|
|
407
|
+
Each hook also provides both a **callback** variant (fire-and-forget with `onSuccess`/`onError`/`onSettled` callbacks) and an **async** variant that returns a promise.
|
|
408
|
+
|
|
409
|
+
## Error Handling
|
|
410
|
+
|
|
411
|
+
All errors thrown by hooks extend `BaseError`. You can catch and narrow them:
|
|
412
|
+
|
|
413
|
+
```ts
|
|
414
|
+
import {
|
|
415
|
+
BaseError,
|
|
416
|
+
WalletNotConnectedError,
|
|
417
|
+
WalletNotFoundError,
|
|
418
|
+
UnsupportedMethodError,
|
|
419
|
+
WalletRequestError,
|
|
420
|
+
} from '@satoshai/kit';
|
|
421
|
+
|
|
422
|
+
try {
|
|
423
|
+
await signMessageAsync({ message: 'hello' });
|
|
424
|
+
} catch (err) {
|
|
425
|
+
if (err instanceof WalletNotConnectedError) {
|
|
426
|
+
// No wallet connected — prompt user to connect
|
|
427
|
+
} else if (err instanceof UnsupportedMethodError) {
|
|
428
|
+
// Wallet doesn't support this method (e.g. OKX + structured signing)
|
|
429
|
+
console.log(err.method, err.wallet);
|
|
430
|
+
} else if (err instanceof WalletNotFoundError) {
|
|
431
|
+
// Wallet extension not installed
|
|
432
|
+
console.log(err.wallet);
|
|
433
|
+
} else if (err instanceof WalletRequestError) {
|
|
434
|
+
// Wallet rejected or failed — original error in cause
|
|
435
|
+
console.log(err.method, err.wallet, err.cause);
|
|
436
|
+
} else if (err instanceof BaseError) {
|
|
437
|
+
// Any other kit error
|
|
438
|
+
console.log(err.shortMessage);
|
|
439
|
+
console.log(err.walk()); // root cause
|
|
440
|
+
}
|
|
441
|
+
}
|
|
305
442
|
```
|
|
306
443
|
|
|
444
|
+
| Error | When |
|
|
445
|
+
|-------|------|
|
|
446
|
+
| `WalletNotConnectedError` | A mutation hook is called before connecting. |
|
|
447
|
+
| `WalletNotFoundError` | A wallet's browser extension is not installed (e.g. OKX). |
|
|
448
|
+
| `UnsupportedMethodError` | The wallet doesn't support the requested method. |
|
|
449
|
+
| `WalletRequestError` | The wallet rejected or failed the RPC request. |
|
|
450
|
+
|
|
451
|
+
## WalletConnect Session Management
|
|
452
|
+
|
|
453
|
+
When using WalletConnect, the kit automatically handles session lifecycle events:
|
|
454
|
+
|
|
455
|
+
- **Zombie session detection** — On app restore, the relay is pinged (10s timeout). If the wallet on the other end doesn't respond, the session is cleaned up and `onDisconnect` fires.
|
|
456
|
+
- **Wallet-initiated disconnect** — If the wallet disconnects via the relay, state is cleaned up automatically.
|
|
457
|
+
- **Account changes** — Listens for `accountsChanged`, `stx_accountChange` (SIP-030), and `stx_accountsChanged` events. When the connected account changes, `onAddressChange` fires.
|
|
458
|
+
|
|
459
|
+
No additional setup is needed — these features activate when `wallets` includes `'wallet-connect'` and a session is active.
|
|
460
|
+
|
|
307
461
|
## Supported Wallets
|
|
308
462
|
|
|
309
463
|
All 6 wallets work with both headless (`connect('xverse')`) and modal (`connect()`) modes.
|
|
@@ -317,6 +471,29 @@ All 6 wallets work with both headless (`connect('xverse')`) and modal (`connect(
|
|
|
317
471
|
| WalletConnect | `wallet-connect` |
|
|
318
472
|
| OKX | `okx` |
|
|
319
473
|
|
|
474
|
+
### Wallet Support Matrix
|
|
475
|
+
|
|
476
|
+
| Hook | Xverse | Leather | Asigna | Fordefi | WalletConnect | OKX |
|
|
477
|
+
|------|--------|---------|--------|---------|---------------|-----|
|
|
478
|
+
| `useConnect` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
479
|
+
| `useSignMessage` | ✓ | ✓ | ? | ? | ~ | ✓ |
|
|
480
|
+
| `useSignStructuredMessage` | ✓ | ✓ | ? | ? | ~ | ✗ |
|
|
481
|
+
| `useSignTransaction` | ✓ | ✓ | ? | ? | ~ | ✗ |
|
|
482
|
+
| `useWriteContract` | ✓ | ✓ | ✓ | ✓ | ~ | ✓ |
|
|
483
|
+
| `useTransferSTX` | ✓ | ✓ | ✓ | ✓ | ~ | ✓ |
|
|
484
|
+
|
|
485
|
+
✓ Confirmed supported | ✗ Unsupported (throws `UnsupportedMethodError`) | ? Unverified | ~ Depends on the connected wallet
|
|
486
|
+
|
|
487
|
+
**Notes:**
|
|
488
|
+
|
|
489
|
+
- **OKX** uses a proprietary API (`window.okxwallet.stacks`) instead of the standard `@stacks/connect` RPC. `useSignStructuredMessage` and `useSignTransaction` are explicitly unsupported and will throw `UnsupportedMethodError`.
|
|
490
|
+
- **Asigna** is a multisig wallet. Transaction-based hooks (`useWriteContract`, `useTransferSTX`) work, but message signing hooks may be limited since there is no multisig message signature standard on Stacks.
|
|
491
|
+
- **Fordefi** supports transactions and contract calls on Stacks, but their [supported blockchains](https://docs.fordefi.com/docs/supported-blockchains) page does not list Stacks under message signing capabilities.
|
|
492
|
+
- **WalletConnect** is a relay protocol — all methods are forwarded, but actual support depends on the wallet on the other end.
|
|
493
|
+
- **Xverse** and **Leather** support all hooks provided by `@satoshai/kit`. Neither fully implements [SIP-030](https://github.com/janniks/sips/blob/main/sips/sip-030/sip-030-wallet-interface.md) — for example, account change detection uses Xverse's proprietary `XverseProviders.StacksProvider.addListener('accountChange')` API, and Leather does not emit account change events at all.
|
|
494
|
+
|
|
495
|
+
This matrix was compiled from wallet documentation as of March 2026. Sources: [Xverse Sats Connect docs](https://docs.xverse.app/sats-connect/stacks-methods), [Leather developer docs](https://leather.gitbook.io/developers), [Asigna docs](https://asigna.gitbook.io/asigna), [Fordefi docs](https://docs.fordefi.com/docs/supported-blockchains), [@stacks/connect WalletConnect source](https://github.com/stx-labs/connect/tree/main/packages/connect/src/walletconnect).
|
|
496
|
+
|
|
320
497
|
## Peer Dependencies
|
|
321
498
|
|
|
322
499
|
- `react` ^18 or ^19
|