Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .changeset/update-stacks-connect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
"@satoshai/kit": minor
---

Update @stacks/connect to 8.2.5 (pinned) and leverage WalletConnect.initializeProvider for faster wallet-connect connections and session restores.

New features:
- `useWallets` hook — returns configured wallets with `available` flag (installed + configured check)
- `wallets` prop on `StacksWalletProvider` — configure which wallets to support
- `reset` on `useConnect` — clear stuck connecting state when wallet modals are dismissed
- `WalletInfo` type exported for consumers
- Runtime guard: throws if `wallet-connect` is in `wallets` without a `walletConnect.projectId`

Fixes:
- Moved runtime guard from render body to `useEffect` (React Strict Mode safe)
- `reset()` now invalidates in-flight `connect()` promises via generation counter (prevents stale state)
- Guard against concurrent `WalletConnect.initializeProvider` calls between session restore and connect

Breaking:
- Removed `useAvailableWallets` (replaced by `useWallets`)
- Removed `connectors` from `useConnect` (replaced by `useWallets`)
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Typesafe Stacks wallet & contract interaction library for React. Wagmi-inspired

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

```tsx
import { StacksWalletProvider, useConnect, useAddress, useDisconnect } from '@satoshai/kit';
import { StacksWalletProvider, useConnect, useWallets, useAddress, useDisconnect } from '@satoshai/kit';

function App() {
return (
Expand All @@ -36,7 +37,8 @@ function App() {
}

function Wallet() {
const { connect, connectors } = useConnect();
const { connect, reset, isPending } = useConnect();
const { wallets } = useWallets();
const { address, isConnected } = useAddress();
const { disconnect } = useDisconnect();

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

return (
<div>
{connectors.map((wallet) => (
<button key={wallet} onClick={() => connect(wallet)}>
{wallet}
{isPending && <button onClick={reset}>Cancel</button>}
{wallets.map(({ id, available }) => (
<button key={id} onClick={() => connect(id)} disabled={!available || isPending}>
{id}
</button>
))}
</div>
Expand Down
1 change: 1 addition & 0 deletions examples/vite-react/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VITE_WALLETCONNECT_PROJECT_ID=
28 changes: 19 additions & 9 deletions examples/vite-react/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ import {
useConnect,
useDisconnect,
useBnsName,
SUPPORTED_STACKS_WALLETS,
useWallets,
} from '@satoshai/kit';

const wcProjectId = import.meta.env.VITE_WALLETCONNECT_PROJECT_ID as string | undefined;

export const App = () => {
return (
<StacksWalletProvider>
<StacksWalletProvider
walletConnect={wcProjectId ? { projectId: wcProjectId } : undefined}
>
<div style={{ fontFamily: 'system-ui', padding: '2rem' }}>
<h1>@satoshai/kit Example</h1>
<Wallet />
Expand All @@ -19,10 +23,11 @@ export const App = () => {
};

const Wallet = () => {
const { connect, isPending } = useConnect();
const { connect, reset, isPending } = useConnect();
const { address, isConnected } = useAddress();
const { disconnect } = useDisconnect();
const { bnsName, isLoading: isBnsLoading } = useBnsName(address);
const { wallets } = useWallets();

if (isConnected) {
return (
Expand All @@ -46,15 +51,20 @@ const Wallet = () => {
return (
<div>
<h2>Connect a Wallet</h2>
{isPending && <p>Connecting...</p>}
{isPending && (
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
<p>Connecting...</p>
<button onClick={reset}>Cancel</button>
</div>
)}
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem', maxWidth: '300px' }}>
{SUPPORTED_STACKS_WALLETS.map((wallet) => (
{wallets.map(({ id, available }) => (
<button
key={wallet}
onClick={() => connect(wallet)}
disabled={isPending}
key={id}
onClick={() => connect(id)}
disabled={isPending || !available}
>
{wallet}
{id}{!available ? ' (not available)' : ''}
</button>
))}
</div>
Expand Down
49 changes: 38 additions & 11 deletions packages/kit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Typesafe Stacks wallet & contract interaction library for React. Wagmi-inspired

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

```tsx
import { StacksWalletProvider, useConnect, useAddress, useDisconnect } from '@satoshai/kit';
import { StacksWalletProvider, useConnect, useWallets, useAddress, useDisconnect } from '@satoshai/kit';

function App() {
return (
Expand All @@ -33,7 +34,8 @@ function App() {
}

function Wallet() {
const { connect, connectors } = useConnect();
const { connect, reset, isPending } = useConnect();
const { wallets } = useWallets();
const { address, isConnected } = useAddress();
const { disconnect } = useDisconnect();

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

return (
<div>
{connectors.map((wallet) => (
<button key={wallet} onClick={() => connect(wallet)}>
{wallet}
{isPending && <button onClick={reset}>Cancel</button>}
{wallets.map(({ id, available }) => (
<button key={id} onClick={() => connect(id)} disabled={!available || isPending}>
{id}
</button>
))}
</div>
Expand All @@ -66,28 +69,52 @@ Wrap your app to provide wallet context to all hooks.

```tsx
<StacksWalletProvider
walletConnect={{ projectId: '...' }} // optional — enables WalletConnect
onConnect={(provider, address) => {}} // optional
onAddressChange={(newAddress) => {}} // optional — Xverse account switching
onDisconnect={() => {}} // optional
wallets={['xverse', 'leather', 'wallet-connect']} // optional — defaults to all supported
walletConnect={{ projectId: '...' }} // optional — enables WalletConnect
onConnect={(provider, address) => {}} // optional
onAddressChange={(newAddress) => {}} // optional — Xverse account switching
onDisconnect={() => {}} // optional
>
{children}
</StacksWalletProvider>
```

> If `wallets` includes `'wallet-connect'`, you must provide `walletConnect.projectId` or the provider will throw at mount.

### `useConnect()`

```ts
const { connect, connectors, isPending } = useConnect();
const { connect, reset, isPending } = useConnect();

// connectors = ['xverse', 'leather', 'okx', 'asigna', 'fordefi', 'wallet-connect']
await connect('xverse');
await connect('leather', {
onSuccess: (address, provider) => {},
onError: (error) => {},
});

// Reset stuck connecting state (e.g. when a wallet modal is dismissed)
reset();
```

> **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.

### `useWallets()`

Returns all configured wallets with their availability status.

```ts
const { wallets } = useWallets();
// [{ id: 'xverse', available: true }, { id: 'leather', available: false }, ...]

{wallets.map(({ id, available }) => (
<button key={id} onClick={() => connect(id)} disabled={!available}>
{id}
</button>
))}
```

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.

### `useDisconnect()`

```ts
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"clean": "rm -rf dist"
},
"dependencies": {
"@stacks/connect": "^8.2.2",
"@stacks/connect": "8.2.5",
"bns-v2-sdk": "^2.1.0"
},
"peerDependencies": {
Expand Down
7 changes: 3 additions & 4 deletions packages/kit/src/hooks/use-connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,18 @@

import { useMemo } from "react";

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

export const useConnect = () => {
const { connect, status } = useStacksWalletContext();
const { connect, reset, status } = useStacksWalletContext();

const value = useMemo(
() => ({
connect,
connectors: SUPPORTED_STACKS_WALLETS,
reset,
isPending: status === "connecting",
}),
[connect, status]
[connect, reset, status]
);

return value;
Expand Down
11 changes: 11 additions & 0 deletions packages/kit/src/hooks/use-wallets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use client';

import { useMemo } from 'react';

import { useStacksWalletContext } from '../provider/stacks-wallet-provider';

export const useWallets = () => {
const { wallets } = useStacksWalletContext();

return useMemo(() => ({ wallets }), [wallets]);
};
2 changes: 2 additions & 0 deletions packages/kit/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ export {
type PostConditionConfig,
} from './hooks/use-write-contract/use-write-contract';
export { useBnsName } from './hooks/use-bns-name';
export { useWallets } from './hooks/use-wallets';

// Types
export type {
WalletState,
WalletContextValue,
WalletConnectMetadata,
WalletInfo,
StacksChain,
ConnectOptions,
} from './provider/stacks-wallet-provider.types';
Expand Down
Loading