Skip to content

Commit 08e83c6

Browse files
satoshai-devclaude
andauthored
feat: wallet configuration, availability detection, and @stacks/connect 8.2.5 (#2)
* feat: update @stacks/connect to 8.2.5 and leverage WalletConnect.initializeProvider Pin @stacks/connect to 8.2.5 (from ^8.2.2) picking up bundle size improvements, provider regression fixes, and the new initializeProvider API. Pre-initialize WalletConnect only when the user connects with it or when restoring a wallet-connect session from localStorage. Closes #1 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: add changeset for @stacks/connect update Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: use stable primitive dep for session restore effect Depend on walletConnect?.projectId instead of the walletConnect object to prevent the effect from re-running on every parent render when the prop is passed inline. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(example): disable buttons for unavailable wallets Disable wallet buttons when the extension is not installed, and disable wallet-connect when no WalletConnect config is provided. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add useAvailableWallets hook Expose available wallets from the provider context, accounting for both extension installation and WalletConnect configuration. Consumers no longer need to manually check wallet availability. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add wallets prop to StacksWalletProvider Allow consumers to configure which wallets they want to support via a wallets prop. When omitted, all supported wallets are available. availableWallets is now the intersection of configured wallets and installed wallets, with wallet-connect requiring a projectId. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: runtime guard for wallet-connect without projectId Throw on mount if wallet-connect is in the wallets array but no walletConnect.projectId was provided. Also wire up the example app to read WalletConnect project ID from VITE_WALLETCONNECT_PROJECT_ID. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add reset to clear stuck connecting state When a wallet modal is dismissed without completing (e.g. WalletConnect), the request promise may never settle, leaving the UI stuck on "Connecting...". Expose reset via useConnect so consumers can reset the connection state, matching wagmi's convention. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: update changeset to minor with full scope Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: replace useAvailableWallets with useWallets returning WalletInfo[] Return all configured wallets with an `available` flag instead of only available ones. Consumers can now show all wallets and disable unavailable ones. Also removes `connectors` from useConnect in favor of useWallets. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: update READMEs with new wallet API Update Quick Start and API docs to reflect useWallets, wallets prop, reset, and removal of connectors from useConnect. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: stale connect after reset, WC double-init race, and strict mode guard - Move runtime guard from render body to useEffect (React Strict Mode safe) - Add generation counter ref so reset() invalidates in-flight connect() promises - Guard concurrent WalletConnect.initializeProvider calls with wcInitRef Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add note about OKX popup close not rejecting Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 09a8f54 commit 08e83c6

File tree

12 files changed

+211
-49
lines changed

12 files changed

+211
-49
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
"@satoshai/kit": minor
3+
---
4+
5+
Update @stacks/connect to 8.2.5 (pinned) and leverage WalletConnect.initializeProvider for faster wallet-connect connections and session restores.
6+
7+
New features:
8+
- `useWallets` hook — returns configured wallets with `available` flag (installed + configured check)
9+
- `wallets` prop on `StacksWalletProvider` — configure which wallets to support
10+
- `reset` on `useConnect` — clear stuck connecting state when wallet modals are dismissed
11+
- `WalletInfo` type exported for consumers
12+
- Runtime guard: throws if `wallet-connect` is in `wallets` without a `walletConnect.projectId`
13+
14+
Fixes:
15+
- Moved runtime guard from render body to `useEffect` (React Strict Mode safe)
16+
- `reset()` now invalidates in-flight `connect()` promises via generation counter (prevents stale state)
17+
- Guard against concurrent `WalletConnect.initializeProvider` calls between session restore and connect
18+
19+
Breaking:
20+
- Removed `useAvailableWallets` (replaced by `useWallets`)
21+
- Removed `connectors` from `useConnect` (replaced by `useWallets`)

README.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Typesafe Stacks wallet & contract interaction library for React. Wagmi-inspired
1010

1111
- **`StacksWalletProvider`** — React context provider for wallet state
1212
- **`useConnect` / `useDisconnect`** — Connect and disconnect wallets
13+
- **`useWallets`** — Configured wallets with availability status
1314
- **`useAddress`** — Access connected wallet address and status
1415
- **`useSignMessage`** — Sign arbitrary messages
1516
- **`useWriteContract`** — Call smart contracts with post-conditions
@@ -25,7 +26,7 @@ pnpm add @satoshai/kit @stacks/transactions react react-dom
2526
## Quick Start
2627

2728
```tsx
28-
import { StacksWalletProvider, useConnect, useAddress, useDisconnect } from '@satoshai/kit';
29+
import { StacksWalletProvider, useConnect, useWallets, useAddress, useDisconnect } from '@satoshai/kit';
2930

3031
function App() {
3132
return (
@@ -36,7 +37,8 @@ function App() {
3637
}
3738

3839
function Wallet() {
39-
const { connect, connectors } = useConnect();
40+
const { connect, reset, isPending } = useConnect();
41+
const { wallets } = useWallets();
4042
const { address, isConnected } = useAddress();
4143
const { disconnect } = useDisconnect();
4244

@@ -51,9 +53,10 @@ function Wallet() {
5153

5254
return (
5355
<div>
54-
{connectors.map((wallet) => (
55-
<button key={wallet} onClick={() => connect(wallet)}>
56-
{wallet}
56+
{isPending && <button onClick={reset}>Cancel</button>}
57+
{wallets.map(({ id, available }) => (
58+
<button key={id} onClick={() => connect(id)} disabled={!available || isPending}>
59+
{id}
5760
</button>
5861
))}
5962
</div>

examples/vite-react/.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
VITE_WALLETCONNECT_PROJECT_ID=

examples/vite-react/src/app.tsx

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@ import {
44
useConnect,
55
useDisconnect,
66
useBnsName,
7-
SUPPORTED_STACKS_WALLETS,
7+
useWallets,
88
} from '@satoshai/kit';
99

10+
const wcProjectId = import.meta.env.VITE_WALLETCONNECT_PROJECT_ID as string | undefined;
11+
1012
export const App = () => {
1113
return (
12-
<StacksWalletProvider>
14+
<StacksWalletProvider
15+
walletConnect={wcProjectId ? { projectId: wcProjectId } : undefined}
16+
>
1317
<div style={{ fontFamily: 'system-ui', padding: '2rem' }}>
1418
<h1>@satoshai/kit Example</h1>
1519
<Wallet />
@@ -19,10 +23,11 @@ export const App = () => {
1923
};
2024

2125
const Wallet = () => {
22-
const { connect, isPending } = useConnect();
26+
const { connect, reset, isPending } = useConnect();
2327
const { address, isConnected } = useAddress();
2428
const { disconnect } = useDisconnect();
2529
const { bnsName, isLoading: isBnsLoading } = useBnsName(address);
30+
const { wallets } = useWallets();
2631

2732
if (isConnected) {
2833
return (
@@ -46,15 +51,20 @@ const Wallet = () => {
4651
return (
4752
<div>
4853
<h2>Connect a Wallet</h2>
49-
{isPending && <p>Connecting...</p>}
54+
{isPending && (
55+
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
56+
<p>Connecting...</p>
57+
<button onClick={reset}>Cancel</button>
58+
</div>
59+
)}
5060
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem', maxWidth: '300px' }}>
51-
{SUPPORTED_STACKS_WALLETS.map((wallet) => (
61+
{wallets.map(({ id, available }) => (
5262
<button
53-
key={wallet}
54-
onClick={() => connect(wallet)}
55-
disabled={isPending}
63+
key={id}
64+
onClick={() => connect(id)}
65+
disabled={isPending || !available}
5666
>
57-
{wallet}
67+
{id}{!available ? ' (not available)' : ''}
5868
</button>
5969
))}
6070
</div>

packages/kit/README.md

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Typesafe Stacks wallet & contract interaction library for React. Wagmi-inspired
66

77
- **`StacksWalletProvider`** — React context provider for wallet state
88
- **`useConnect` / `useDisconnect`** — Connect and disconnect wallets
9+
- **`useWallets`** — Configured wallets with availability status
910
- **`useAddress`** — Access connected wallet address and status
1011
- **`useSignMessage`** — Sign arbitrary messages
1112
- **`useWriteContract`** — Call smart contracts with post-conditions
@@ -22,7 +23,7 @@ pnpm add @satoshai/kit @stacks/transactions react react-dom
2223
## Quick Start
2324

2425
```tsx
25-
import { StacksWalletProvider, useConnect, useAddress, useDisconnect } from '@satoshai/kit';
26+
import { StacksWalletProvider, useConnect, useWallets, useAddress, useDisconnect } from '@satoshai/kit';
2627

2728
function App() {
2829
return (
@@ -33,7 +34,8 @@ function App() {
3334
}
3435

3536
function Wallet() {
36-
const { connect, connectors } = useConnect();
37+
const { connect, reset, isPending } = useConnect();
38+
const { wallets } = useWallets();
3739
const { address, isConnected } = useAddress();
3840
const { disconnect } = useDisconnect();
3941

@@ -48,9 +50,10 @@ function Wallet() {
4850

4951
return (
5052
<div>
51-
{connectors.map((wallet) => (
52-
<button key={wallet} onClick={() => connect(wallet)}>
53-
{wallet}
53+
{isPending && <button onClick={reset}>Cancel</button>}
54+
{wallets.map(({ id, available }) => (
55+
<button key={id} onClick={() => connect(id)} disabled={!available || isPending}>
56+
{id}
5457
</button>
5558
))}
5659
</div>
@@ -66,28 +69,52 @@ Wrap your app to provide wallet context to all hooks.
6669

6770
```tsx
6871
<StacksWalletProvider
69-
walletConnect={{ projectId: '...' }} // optional — enables WalletConnect
70-
onConnect={(provider, address) => {}} // optional
71-
onAddressChange={(newAddress) => {}} // optional — Xverse account switching
72-
onDisconnect={() => {}} // optional
72+
wallets={['xverse', 'leather', 'wallet-connect']} // optional — defaults to all supported
73+
walletConnect={{ projectId: '...' }} // optional — enables WalletConnect
74+
onConnect={(provider, address) => {}} // optional
75+
onAddressChange={(newAddress) => {}} // optional — Xverse account switching
76+
onDisconnect={() => {}} // optional
7377
>
7478
{children}
7579
</StacksWalletProvider>
7680
```
7781

82+
> If `wallets` includes `'wallet-connect'`, you must provide `walletConnect.projectId` or the provider will throw at mount.
83+
7884
### `useConnect()`
7985

8086
```ts
81-
const { connect, connectors, isPending } = useConnect();
87+
const { connect, reset, isPending } = useConnect();
8288

83-
// connectors = ['xverse', 'leather', 'okx', 'asigna', 'fordefi', 'wallet-connect']
8489
await connect('xverse');
8590
await connect('leather', {
8691
onSuccess: (address, provider) => {},
8792
onError: (error) => {},
8893
});
94+
95+
// Reset stuck connecting state (e.g. when a wallet modal is dismissed)
96+
reset();
8997
```
9098

99+
> **Note:** Some wallets (e.g. OKX) never reject the connection promise when the user closes the popup. Use `reset()` to clear the pending state in those cases.
100+
101+
### `useWallets()`
102+
103+
Returns all configured wallets with their availability status.
104+
105+
```ts
106+
const { wallets } = useWallets();
107+
// [{ id: 'xverse', available: true }, { id: 'leather', available: false }, ...]
108+
109+
{wallets.map(({ id, available }) => (
110+
<button key={id} onClick={() => connect(id)} disabled={!available}>
111+
{id}
112+
</button>
113+
))}
114+
```
115+
116+
A wallet is `available` when its browser extension is installed. For `wallet-connect`, it's `available` when a `walletConnect.projectId` is provided to the provider.
117+
91118
### `useDisconnect()`
92119

93120
```ts

packages/kit/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"clean": "rm -rf dist"
3333
},
3434
"dependencies": {
35-
"@stacks/connect": "^8.2.2",
35+
"@stacks/connect": "8.2.5",
3636
"bns-v2-sdk": "^2.1.0"
3737
},
3838
"peerDependencies": {

packages/kit/src/hooks/use-connect.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,18 @@
22

33
import { useMemo } from "react";
44

5-
import { SUPPORTED_STACKS_WALLETS } from "../constants/wallets";
65
import { useStacksWalletContext } from "../provider/stacks-wallet-provider";
76

87
export const useConnect = () => {
9-
const { connect, status } = useStacksWalletContext();
8+
const { connect, reset, status } = useStacksWalletContext();
109

1110
const value = useMemo(
1211
() => ({
1312
connect,
14-
connectors: SUPPORTED_STACKS_WALLETS,
13+
reset,
1514
isPending: status === "connecting",
1615
}),
17-
[connect, status]
16+
[connect, reset, status]
1817
);
1918

2019
return value;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
'use client';
2+
3+
import { useMemo } from 'react';
4+
5+
import { useStacksWalletContext } from '../provider/stacks-wallet-provider';
6+
7+
export const useWallets = () => {
8+
const { wallets } = useStacksWalletContext();
9+
10+
return useMemo(() => ({ wallets }), [wallets]);
11+
};

packages/kit/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ export {
1818
type PostConditionConfig,
1919
} from './hooks/use-write-contract/use-write-contract';
2020
export { useBnsName } from './hooks/use-bns-name';
21+
export { useWallets } from './hooks/use-wallets';
2122

2223
// Types
2324
export type {
2425
WalletState,
2526
WalletContextValue,
2627
WalletConnectMetadata,
28+
WalletInfo,
2729
StacksChain,
2830
ConnectOptions,
2931
} from './provider/stacks-wallet-provider.types';

0 commit comments

Comments
 (0)