docs / @orangecheck/wallet-adapter

@orangecheck/wallet-adapter

Every Bitcoin browser wallet exposes a different signing API. This package hides that behind one SignFn = (message) => Promise<string>.

yarn add @orangecheck/wallet-adapter

React components are under a subpath to keep the core library zero-dep:

import { detectWallets, getSigner } from '@orangecheck/wallet-adapter';
import { OcWalletButton }           from '@orangecheck/wallet-adapter/react';

detectWallets()

Returns every supported wallet with a detected flag.

detectWallets();
// [
//   { id: 'unisat',  name: 'UniSat',  detected: true,  installUrl: '…' },
//   { id: 'xverse',  name: 'Xverse',  detected: false, installUrl: '…' },
//   { id: 'leather', name: 'Leather', detected: false, installUrl: '…' },
//   { id: 'alby',    name: 'Alby',    detected: true,  installUrl: '…' },
//   { id: 'manual',  name: 'Paste signature', detected: true, isManual: true }
// ]

getSigner(id, { address })

Returns a SignFn bound to a particular wallet.

import { getSigner } from '@orangecheck/wallet-adapter';

const sign = getSigner('unisat', { address: userBtcAddress });
const signature = await sign(canonicalMessage);

Throws when the wallet is unavailable or the user cancels.

<OcWalletButton />

Pre-built wallet picker. Detects installed wallets, renders install prompts for missing ones, calls onSigned with the signature.

import { OcWalletButton } from '@orangecheck/wallet-adapter/react';

<OcWalletButton
  address={userBtcAddress}
  message={challenge.message}
  onSigned={(sig, walletId) => {
    console.log(`${walletId} returned`, sig);
    postVerify({ signature: sig });
  }}
  onError={(err) => console.error(err)}
/>
PropTypeNotes
addressstringRequired by Xverse and Leather.
messagestringCanonical message to sign.
onSigned(sig, walletId) => voidSuccess callback.
onError(err, walletId) => voidFailure callback.
hideUninstalledbooleanDefault false — uninstalled wallets render as install prompts.
headingReactNodeHeader text; default "Sign with your wallet".

Wallet details

WalletGlobalStyle
UniSatwindow.unisat.signMessage(msg, 'bip322-simple')Simple BIP-322
Xversewindow.BitcoinProvider.request('signMessage', {...})Full BIP-322
Leatherwindow.LeatherProvider.request('signMessage', {...})Full BIP-322
Albywindow.webln.signMessage(msg)Legacy-ish (best for 1… addrs)
Manualwindow.prompt()User pastes sig from hardware / Sparrow / Core

The shims are duck-typed — we check the shape of window.*, not just the presence of a global. Spoofing wrappers don't produce false positives.

End-to-end sign-in example

import { useState } from 'react';
import { OcWalletButton } from '@orangecheck/wallet-adapter/react';

export function SignIn({ address }: { address: string }) {
  const [step, setStep] = useState<'idle' | 'ready' | 'done'>('idle');
  const [message, setMessage] = useState('');
  const [proven, setProven] = useState('');

  async function start() {
    const r = await fetch(`/api/challenge?addr=${address}`);
    const { message } = await r.json();
    setMessage(message);
    setStep('ready');
  }

  async function handleSigned(signature: string) {
    const r = await fetch('/api/challenge', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ message, signature }),
    });
    const body = await r.json();
    if (body.ok) {
      setProven(body.address);
      setStep('done');
    }
  }

  if (step === 'idle') return <button onClick={start}>Start sign-in</button>;
  if (step === 'ready') {
    return <OcWalletButton address={address} message={message} onSigned={handleSigned} />;
  }
  return <p>Signed in as {proven}</p>;
}

License

MIT.