Skip to content

Connect your site

bWalletX implements the open BRC-100 wallet interface. Your page talks straight to the user’s wallet: no API keys, no sign-up, nothing through our servers. Any BRC-100 wallet works the same way.

<script src="https://bwalletx.com/connect.js"></script>

Or from npm: @bwalletx/connect.

<button id="connect">Connect with bWalletX</button>
<script>
bWalletX.button('#connect', async ({ wallet, identityKey }) => {
// wallet is a BRC-100 WalletInterface, the same API as window.CWI
console.log('Connected', identityKey);
});
</script>

If bWalletX isn’t installed, the button opens bwalletx.com/extension.

const { wallet } = await bWalletX.connect();
// Sign in: prove who the user is
const { signature } = await wallet.createSignature({
data: [...new TextEncoder().encode(challenge)],
protocolID: [0, 'my site login'],
keyID: '1',
});

A signature made in the browser proves nothing until your server checks it. @bwalletx/connect/server does the two steps: a one-time challenge, then a check of the wallet’s signature. No API key, no account with us, nothing through bwalletx.com.

Terminal window
pnpm add @bwalletx/connect @bsv/sdk

This runs both sides in one file. A throwaway key stands in for the user’s BRC-100 wallet:

import { PrivateKey, ProtoWallet, Utils } from '@bsv/sdk';
import { createChallenge, verifySignIn, memoryStore } from '@bwalletx/connect/server';
const ORIGIN = 'https://example.com'; // your site, from config, never from the request
const store = memoryStore(); // dev only: use redisStore or pgStore in production
// Browser: the wallet gives its identity key.
const wallet = new ProtoWallet(PrivateKey.fromRandom());
const { publicKey: identityKey } = await wallet.getPublicKey({ identityKey: true });
// Server, POST /challenge
const ch = await createChallenge({ origin: ORIGIN, identityKey, store });
// Browser: the wallet signs the challenge text.
const { signature } = await wallet.createSignature({
data: Utils.toArray(ch.message, 'utf8'),
protocolID: ch.protocolID, // [2, 'bwallet sign in']
keyID: ch.keyID, // the nonce
counterparty: 'anyone',
});
// Server, POST /verify: send the signature as the byte array createSignature returned (not hex).
const proven = await verifySignIn({ origin: ORIGIN, store, identityKey, nonce: ch.nonce, signature });

Output when we ran it (17 Oct 2026, @bwalletx/connect 0.5.0, @bsv/sdk 3.2.1):

Sign in to example.com with bWallet
This proves you hold this wallet. It does not spend anything.
Origin: https://example.com
Nonce: <nonce>
Expires: <ISO time>
signed in as: the same identity key ✓
second try with the same nonce: null

verifySignIn returns the identity key (66 lowercase hex characters) or null. Each nonce works once and expires after 2 minutes. If you get null every time, check that you pass the signature as an array of numbers, not a hex string.

For Next.js, @bwalletx/connect/next wraps this in four route handlers and a signed session cookie; React has BwalletXProvider and useBwalletX(). See the package README and the full protocol at bwalletx.com/connect.md.

bWalletX also sets window.CWI, so sites that call it directly keep working. If the user has another wallet installed too, you can’t tell which one answers. bWalletX.connect() picks bWalletX by name (rdns com.bwalletx.extension) through BRC-100 wallet discovery. To let the user choose:

const wallets = await bWalletX.discover(); // [{ info: { name, icon, rdns, kind }, wallet }, …]

Every request is approved by the user on one sheet inside the wallet. Grants are per origin (https only, plus localhost).

More: bwalletx.com/developers, Sign in with bWalletX.

  • BRC-100: the wallet interface (createAction, createSignature, getPublicKey…).
  • BRC-43: protocol IDs and key IDs used for signatures.
  • BRC-29: paying an identity key directly.
  • ✓ connect.js and @bwalletx/connect (0.5.0)
  • ✓ Wallet discovery by rdns
  • ✓ window.CWI compatibility
  • ✓ Server-checked sign-in (createChallenge / verifySignIn), verified 17 Oct 2026