| name | dapp-connect |
| description | This skill should be used when building a dApp that connects to a 1Sat wallet — using @1sat/connect for wallet connection via popup or browser extension, @1sat/react for React hooks and components, or integrating with the BigBlocks shadcn registry. Triggers on 'connect wallet', 'dApp integration', 'wallet provider', 'ConnectButton', 'WalletProvider', 'useWallet', 'ConnectDialog', 'SigmaCallback', 'browser extension', 'popup wallet', 'BRC-100', 'Sigma OAuth', 'BigBlocks registry', or 'shadcn wallet components'. |
dApp Connect
Connect a dApp to an existing BRC-100 wallet (browser extension, desktop wallet, or Sigma Identity) using @1sat/connect (vanilla JS) and @1sat/react (React).
This skill is about connecting to a wallet a user already has. To create and run a wallet programmatically (local/remote storage, address sync, backups), see ../../../wallet/skills/wallet-setup.
Architecture
@1sat/react (React components + hooks)
└── @1sat/connect (connection core: connectWallet)
├── BRC-100 auto-detect — WalletClient('auto'): extensions, desktop (localhost), XDM, React Native
├── Sigma OAuth — browser redirect flow + CWI iframe
└── Custom providers — CWI iframe bridge (url) or a custom connect() fn
connectWallet races auto-detect and every configured provider with Promise.any; first to authenticate wins, and its provider type is saved to localStorage for warm reconnects.
Quick Start (React)
import { WalletProvider, ConnectButton, useWallet } from '@1sat/react'
function App() {
return (
<WalletProvider autoReconnect>
<ConnectButton />
<Dashboard />
</WalletProvider>
)
}
function Dashboard() {
const { wallet, status, identityKey } = useWallet()
if (status !== 'connected' || !wallet) return <p>Not connected</p>
return <p>Connected: {identityKey?.slice(0, 8)}...</p>
}
React Components
All components from @1sat/react are deliberately unstyled — they provide functional primitives with minimal inline styles. Themed UI wrappers are distributed via the BigBlocks registry as shadcn-compatible registry items.
WalletProvider
App-level context provider. Wrap the application root. Props (WalletProviderProps):
<WalletProvider
autoReconnect={false}
autoDetect={true}
providers={customProviders}
>
{children}
</WalletProvider>
There is no appName prop on WalletProvider. With autoReconnect, mount reads the stored provider type: brc100 re-runs auto-detect; a custom type must be present in providers; sigma is guarded against redirect loops via a sessionStorage flag.
ConnectButton
Unstyled button that triggers wallet connection. Supports render-prop children for full customization.
<ConnectButton
className="my-button"
style={{ }}
connectLabel="Connect"
connectingLabel="Connecting..."
connectedLabel={(key) => `${key.slice(0,6)}...${key.slice(-4)}`}
onConnect={() => console.log('connected')}
onDisconnect={() => console.log('disconnected')}
onError={(err) => console.error(err)}
disconnectOnClick={true}
/>
Render-prop form receives { isConnected, isConnecting, identityKey, connect, disconnect }. isConnecting is true while status is detecting or connecting. When connected and no specific provider is configured, clicking calls the plain connect() which re-runs detection — there is no built-in provider picker on the button; pair it with ConnectDialogProvider (below) for selection.
ConnectDialog
Controlled dialog (native <dialog>) for wallet provider selection. Requires open and onOpenChange. Render-prop children is optional — omit it for a basic unstyled provider list.
<ConnectDialog
open={isOpen}
onOpenChange={setIsOpen}
>
{({ providers, error, close }) => (
<div>
{providers.map(p => (
<button
key={p.type}
disabled={p.isConnecting}
onClick={p.connect} // each provider carries its own bound connect()
>
{p.icon && <img src={p.icon} alt="" />}
{p.name}{p.isConnecting && ' ...'}
</button>
))}
{error && <p>{error.message}</p>}
<button onClick={close}>Cancel</button>
</div>
)}
</ConnectDialog>
Render props are { providers, error, close }. Each provider is { type, name, icon?, isConnecting, connect } — connect takes no arguments (it's pre-bound to that provider's type). There is no detected flag; the dialog lists the providers configured on WalletProvider.
ConnectDialogProvider + useConnectDialog
App-level provider that mounts a ConnectDialog and auto-opens it when status becomes 'selecting' (auto-detect found nothing). Place it inside WalletProvider. Pass renderDialog to customize the dialog content (same render props as ConnectDialog).
<WalletProvider autoReconnect>
<ConnectDialogProvider
renderDialog={({ providers, error, close }) => (/* custom UI */ null)} // optional
>
<App />
</ConnectDialogProvider>
</WalletProvider>
function ConnectTrigger() {
const { openConnectDialog } = useConnectDialog()
return <button onClick={openConnectDialog}>Connect</button>
}
useConnectDialog() returns { openConnectDialog } and throws if used outside ConnectDialogProvider.
WalletSelector
Headless, render-prop-only component (no default UI at all — children is required). Reads availableProviders, connect, and error from context; tracks a per-provider connecting state; calls onClose after a provider connects. Use it to build a bespoke selector when ConnectDialog's <dialog> shell doesn't fit.
<WalletSelector onClose={() => setOpen(false)}>
{({ providers, error }) => (
<ul>
{providers.map(p => (
<li key={p.type}>
<button disabled={p.isConnecting} onClick={p.connect}>
{p.icon && <img src={p.icon} alt="" />}
{p.name}{p.isConnecting && ' (connecting...)'}
</button>
</li>
))}
{error && <p>{error.message}</p>}
</ul>
)}
</WalletSelector>
Render props: { providers, error }. Each provider is { type, name, icon?, isConnecting, connect } with connect pre-bound (no args). Unlike ConnectDialog, WalletSelector renders nothing but what children returns — no <dialog>, no open/onOpenChange.
SigmaCallback
Page component for the Sigma OAuth redirect. Place it at the OAuth callback route. It runs completeSigmaOAuth → connectSigmaWallet, applies the result to the wallet context, then redirects (or calls onComplete).
import { SigmaCallback } from '@1sat/react'
export default function AuthCallback() {
return (
<SigmaCallback
redirectTo="/" // default '/'
onComplete={() => router.push('/')} // no args; use for SPA nav instead of hard redirect
loadingContent={<p>Completing authentication...</p>}
renderError={(error, goBack) => <p>{error} <button onClick={goBack}>Back</button></p>}
/>
)
}
onComplete takes no arguments (the connected wallet is already applied to context). renderError receives (error: string, goBack: () => void).
useWallet Hook
Primary hook for accessing wallet context:
interface WalletContextValue {
wallet: WalletInterface | null
status: WalletStatus
identityKey: string | null
providerType: string | null
availableProviders: AvailableProvider[]
connect: (providerType?: string) => Promise<void>
applyResult: (result: ConnectWalletResult) => void
disconnect: () => void
error: Error | null
}
Vanilla JS
The public entry point is connectWallet(config). It races BRC-100 auto-detect and every configured provider, and returns a ConnectWalletResult — or null when nothing connected (treat null as "show the provider picker").
import { connectWallet } from '@1sat/connect'
const result = await connectWallet({
autoDetect: true,
providers: [
{ type: 'my-wallet', name: 'My Wallet', url: 'https://wallet.example.com' },
],
})
if (!result) {
} else {
const { wallet, provider, identityKey, disconnect } = result
const { publicKey } = await wallet.getPublicKey({ identityKey: true })
disconnect()
}
ConnectWalletResult is { wallet: WalletInterface, provider: string, identityKey: string, disconnect: () => void }. All wallet operations go through the standard @bsv/sdk WalletInterface on result.wallet — @1sat/connect does not wrap them in its own method surface.
Provider config
Each entry in providers is a WalletProviderConfig:
{
type: string,
name: string,
icon?: string,
url?: string,
connect?: () => Promise<ConnectWalletResult>,
}
Resolution order per provider: custom connect → url (CWI iframe bridge via createWebCWI) → error.
Helpers
import { connectWallet, getAvailableProviders, loadLastProvider } from '@1sat/connect'
const providers = getAvailableProviders({ providers: myProviders })
await providers[0].connect()
const last = loadLastProvider()
connectWallet saves the winning provider type to localStorage and, on the next call, attempts that last provider first before the full race. There is no createOneSat factory, isOneSatInjected, waitForOneSat, saveConnection/loadConnection, or event emitter on the public @1sat/connect surface — those are internal to the popup OneSatBrowserProvider and not exported. OneSatProvider is exported as a type only.
Connection Flow
WalletProvider drives status transitions:
detecting — connectWallet races BRC-100 auto-detect (WalletClient('auto'): extensions, desktop/localhost, XDM, React Native) and all configured providers.
selecting — nothing auto-connected; connectWallet returned null. ConnectDialogProvider auto-opens the provider selector.
connecting → connected — a specific provider was chosen (or won the race); on success the result is applied to context.
- Sigma OAuth — redirect-based; completed by
SigmaCallback, which calls connectSigmaWallet and applies the result.
Sigma OAuth (vanilla)
For non-React apps, the Sigma flow is three exported functions:
import { initiateSigmaOAuth, completeSigmaOAuth, connectSigmaWallet } from '@1sat/connect'
await initiateSigmaOAuth({ clientId: 'my-client', callbackURL: '/auth/sigma/callback' })
const { bapId, pubkey, user, accessToken } = await completeSigmaOAuth(
new URLSearchParams(window.location.search),
)
const { wallet, identityKey, disconnect } = await connectSigmaWallet(bapId)
BigBlocks Registry Integration
@1sat/react components are unstyled primitives. The BigBlocks registry serves 30 shadcn-themed blocks installable via:
npx bigblocks add <block>
bunx shadcn@latest add https://registry.bigblocks.dev/r/<block>.json
Connection and wallet blocks that wrap @1sat/react:
| Block | Description |
|---|
connect-wallet | Wallet connection button with provider selection dialog and connected-state dropdown |
wallet-overview | Dashboard card with balance, addresses, identity key, and send/receive actions |
send-bsv | Send BSV dialog with sats/BSV toggle, fee estimate, and confirmation |
receive-address | QR code and deposit address with copy and optional rotation |
transaction-history | Transaction list with status indicators and pagination |
mnemonic-flow | Multi-mode seed phrase create/display/import/verify |
unlock-wallet | Passphrase and Touch ID unlock screen |
bigblocks-provider | Context provider for web-mode (API) or custom-mode (desktop RPC) data fetching |
BigBlocks components wrap @1sat/react primitives in shadcn UI (Button, Dialog, Drawer, DropdownMenu) with full theme support. The source for @1sat/react stays in this repo — BigBlocks serves them without duplicating code. See the full 30-block registry at https://bigblocks.dev.
Requirements
bun add @1sat/connect
bun add @1sat/react
bun add @1sat/extension