Skip to content

Commit f2e8259

Browse files
satoshai-devclaude
andauthored
docs: JSDoc all consumer-facing exports and update README (#59)
* docs: add JSDoc to all consumer-facing exports and update README Add comprehensive JSDoc documentation to all hooks, errors, types, utilities, provider, and constants. Update README with typed useWriteContract examples, createContractConfig, error handling guide, mutation return types table, and WalletConnect session management docs. Closes #34 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add link to @satoshai/abi-cli in typed useWriteContract section Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: correct SIP-030 compliance claim for Xverse and Leather Neither wallet fully implements SIP-030. Xverse uses a proprietary addListener('accountChange') API, and Leather does not emit account change events at all. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4ededf1 commit f2e8259

21 files changed

+563
-20
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@satoshai/kit": patch
3+
---
4+
5+
Add JSDoc documentation to all consumer-facing exports (hooks, errors, types, utilities, provider) and update README with typed `useWriteContract` examples, `createContractConfig`, error handling guide, mutation return types, and WalletConnect session management.

README.md

Lines changed: 173 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@ Typesafe Stacks wallet & contract interaction library for React. Wagmi-inspired
1111
- **`StacksWalletProvider`** — React context provider for wallet state
1212
- **`useConnect` / `useDisconnect`** — Connect and disconnect wallets
1313
- **`useWallets`** — Configured wallets with availability status
14-
- **`useAddress`** — Access connected wallet address and status
14+
- **`useAddress`** — Access connected wallet address and status (discriminated union)
1515
- **`useSignMessage`** — Sign arbitrary messages
1616
- **`useSignStructuredMessage`** — Sign SIP-018 structured data
1717
- **`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)
1919
- **`useTransferSTX`** — Native STX transfers
2020
- **`useBnsName`** — Resolve BNS v2 names
21+
- **Typed errors**`BaseError`, `WalletNotConnectedError`, `WalletNotFoundError`, `UnsupportedMethodError`, `WalletRequestError`
2122
- **6 wallets supported** — Xverse, Leather, OKX, Asigna, Fordefi, WalletConnect
23+
- **WalletConnect session management** — Zombie session detection, wallet-initiated disconnect, and account change events
2224
- **Next.js App Router compatible**`"use client"` directives included
2325

2426
## Install
@@ -75,13 +77,22 @@ Wrap your app to provide wallet context to all hooks.
7577
connectModal={true} // optional — defaults to true
7678
walletConnect={{ projectId: '...' }} // optional — enables WalletConnect
7779
onConnect={(provider, address) => {}} // optional
78-
onAddressChange={(newAddress) => {}} // optional — Xverse account switching
80+
onAddressChange={(newAddress) => {}} // optional — Xverse/WalletConnect account switching
7981
onDisconnect={() => {}} // optional
8082
>
8183
{children}
8284
</StacksWalletProvider>
8385
```
8486

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+
8596
> If `wallets` includes `'wallet-connect'`, you must provide `walletConnect.projectId` or the provider will throw at mount.
8697
8798
> **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:
111122

112123
### `useConnect()`
113124

125+
Connect to a Stacks wallet. Returns a mutation-style object.
126+
114127
```ts
115-
const { connect, reset, isPending } = useConnect();
128+
const { connect, reset, error, isPending, isSuccess, isError, isIdle, status } = useConnect();
116129

117130
// Open the @stacks/connect modal (when connectModal is enabled, the default)
118131
await connect();
@@ -153,33 +166,40 @@ A wallet is `available` when its browser extension is installed. For `wallet-con
153166

154167
### `useDisconnect()`
155168

169+
Disconnect the current wallet and clear the persisted session.
170+
156171
```ts
157-
const { disconnect } = useDisconnect();
172+
const { disconnect, reset, error, isSuccess, isError, isIdle, isPending, status } = useDisconnect();
158173

159174
disconnect();
160175
disconnect(() => { /* callback after disconnect */ });
161176
```
162177

163178
### `useAddress()`
164179

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+
165182
```ts
166183
const { address, isConnected, isConnecting, isDisconnected, provider } = useAddress();
167184

168185
if (isConnected) {
169-
console.log(address); // 'SP...' or 'ST...'
186+
console.log(address); // 'SP...' or 'ST...' — narrowed to string
170187
console.log(provider); // 'xverse' | 'leather' | ...
171188
}
172189
```
173190

174191
### `useSignMessage()`
175192

193+
Sign an arbitrary plaintext message.
194+
176195
```ts
177-
const { signMessage, signMessageAsync, data, error, isPending } = useSignMessage();
196+
const { signMessage, signMessageAsync, data, error, isPending, reset } = useSignMessage();
178197

179198
// Callback style
180199
signMessage({ message: 'Hello Stacks' }, {
181200
onSuccess: ({ publicKey, signature }) => {},
182201
onError: (error) => {},
202+
onSettled: (data, error) => {},
183203
});
184204

185205
// Async style
@@ -190,7 +210,7 @@ const { publicKey, signature } = await signMessageAsync({ message: 'Hello Stacks
190210

191211
Sign SIP-018 structured data for typed, verifiable off-chain messages.
192212

193-
> **Note:** OKX wallet does not support structured message signing and will throw an error.
213+
> **Note:** OKX wallet does not support structured message signing and will throw an `UnsupportedMethodError`.
194214
195215
```ts
196216
import { tupleCV, stringAsciiCV, uintCV } from '@stacks/transactions';
@@ -222,14 +242,18 @@ const { publicKey, signature } = await signStructuredMessageAsync({
222242

223243
### `useTransferSTX()`
224244

245+
Transfer native STX tokens. Amount is in **microSTX** (1 STX = 1,000,000 microSTX).
246+
225247
```ts
226-
const { transferSTX, transferSTXAsync, data, error, isPending } = useTransferSTX();
248+
const { transferSTX, transferSTXAsync, data, error, isPending, reset } = useTransferSTX();
227249

228250
// Callback style
229251
transferSTX({
230252
recipient: 'SP2...',
231-
amount: 1000000n, // in microSTX
253+
amount: 1000000n, // 1 STX
232254
memo: 'optional memo',
255+
fee: 2000n, // optional custom fee
256+
nonce: 42n, // optional custom nonce
233257
}, {
234258
onSuccess: (txid) => {},
235259
onError: (error) => {},
@@ -244,10 +268,14 @@ const txid = await transferSTXAsync({
244268

245269
### `useWriteContract()`
246270

271+
Call a public function on a Clarity smart contract. Supports two modes:
272+
273+
#### Untyped mode (ClarityValue[] args)
274+
247275
```ts
248-
import { Pc, PostConditionMode } from '@stacks/transactions';
276+
import { uintCV, Pc, PostConditionMode } from '@stacks/transactions';
249277

250-
const { writeContract, writeContractAsync, data, error, isPending } = useWriteContract();
278+
const { writeContract, writeContractAsync, data, error, isPending, reset } = useWriteContract();
251279

252280
writeContract({
253281
address: 'SP...',
@@ -264,14 +292,58 @@ writeContract({
264292
});
265293
```
266294

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+
267339
### `useSignTransaction()`
268340

269341
Sign a serialized transaction without automatically broadcasting it. Useful for sponsored transaction flows where a separate service pays the fee.
270342

271-
> **Note:** OKX wallet does not support raw transaction signing and will throw an error.
343+
> **Note:** OKX wallet does not support raw transaction signing and will throw an `UnsupportedMethodError`.
272344
273345
```ts
274-
const { signTransaction, signTransactionAsync, data, error, isPending } = useSignTransaction();
346+
const { signTransaction, signTransactionAsync, data, error, isPending, reset } = useSignTransaction();
275347

276348
// Callback style
277349
signTransaction({ transaction: '0x0100...', broadcast: false }, {
@@ -288,6 +360,8 @@ const { transaction, txid } = await signTransactionAsync({
288360

289361
### `useBnsName()`
290362

363+
Resolve a BNS v2 primary name for a Stacks address. Returns `null` when no name is registered.
364+
291365
```ts
292366
const { bnsName, isLoading } = useBnsName(address);
293367
// bnsName = 'satoshi.btc' | null
@@ -296,14 +370,94 @@ const { bnsName, isLoading } = useBnsName(address);
296370
### Utilities
297371

298372
```ts
299-
import { getNetworkFromAddress, getStacksWallets, getLocalStorageWallet } from '@satoshai/kit';
300-
373+
import {
374+
getNetworkFromAddress,
375+
getStacksWallets,
376+
getLocalStorageWallet,
377+
createContractConfig,
378+
} from '@satoshai/kit';
379+
380+
// Infer network from address prefix
301381
getNetworkFromAddress('SP...'); // 'mainnet'
302382
getNetworkFromAddress('ST...'); // 'testnet'
303383

384+
// Detect supported and installed wallets
304385
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
305390
```
306391

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+
}
442+
```
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+
307461
## Supported Wallets
308462

309463
All 6 wallets work with both headless (`connect('xverse')`) and modal (`connect()`) modes.
@@ -328,15 +482,15 @@ All 6 wallets work with both headless (`connect('xverse')`) and modal (`connect(
328482
| `useWriteContract` ||||| ~ ||
329483
| `useTransferSTX` ||||| ~ ||
330484

331-
✓ Confirmed supported | ✗ Unsupported (throws error) | ? Unverified | ~ Depends on the connected wallet
485+
✓ Confirmed supported | ✗ Unsupported (throws `UnsupportedMethodError`) | ? Unverified | ~ Depends on the connected wallet
332486

333487
**Notes:**
334488

335-
- **OKX** uses a proprietary API (`window.okxwallet.stacks`) instead of the standard `@stacks/connect` RPC. `useSignStructuredMessage` and `useSignTransaction` are explicitly unsupported and will throw.
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`.
336490
- **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.
337491
- **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.
338492
- **WalletConnect** is a relay protocol — all methods are forwarded, but actual support depends on the wallet on the other end.
339-
- **Xverse** and **Leather** implement the full [SIP-030](https://github.com/janniks/sips/blob/main/sips/sip-030/sip-030-wallet-interface.md) interface.
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.
340494

341495
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).
342496

packages/kit/src/constants/wallets.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/** All wallet IDs supported by `@satoshai/kit`. */
12
export const SUPPORTED_STACKS_WALLETS = [
23
"xverse",
34
"leather",
@@ -7,4 +8,5 @@ export const SUPPORTED_STACKS_WALLETS = [
78
"okx",
89
] as const;
910

11+
/** Union of supported wallet identifiers. */
1012
export type SupportedStacksWallet = (typeof SUPPORTED_STACKS_WALLETS)[number];

0 commit comments

Comments
 (0)