Skip to content

Latest commit

 

History

History
257 lines (189 loc) · 6.99 KB

File metadata and controls

257 lines (189 loc) · 6.99 KB

XCP Wallet Provider API

The XCP Wallet browser extension injects a provider at window.xcpwallet on all HTTPS pages. Websites use this provider to connect wallets, sign messages, compose transactions, and broadcast to the Bitcoin network.

Detection

The provider is injected at document_start — before page scripts run. Two detection methods:

// Synchronous (provider already injected)
if (window.xcpwallet) {
  // ready
}

// Asynchronous (wait for injection)
window.addEventListener('xcp-wallet#initialized', () => {
  // window.xcpwallet is now available
});

// Request re-announcement (for SPAs that mount after injection)
window.dispatchEvent(new Event('xcp-wallet#discover'));

Provider Interface

interface XcpProvider {
  request(args: { method: string; params?: unknown[] }): Promise<unknown>
  on(event: string, handler: (...args: any[]) => void): void
  removeListener(event: string, handler: (...args: any[]) => void): void
}

Methods

Connection

xcp_requestAccounts

Connect to the wallet. Opens a popup for user approval on first connection.

Returns a connection proof — a BIP-322 signature proving the user controls the address. The proof message is generated by the extension (not the website) and includes the requesting origin, a random nonce, and a timestamp.

const result = await xcpwallet.request({ method: 'xcp_requestAccounts' });
// {
//   accounts: ['bc1q...'],
//   proof: {
//     address: 'bc1q...',
//     message: 'xcp-wallet\norigin:https://example.com\nnonce:a1b2c3d4\nissued:1711130400',
//     signature: '<BIP-322 signature>',
//     verification: {
//       method: 'BIP-322',
//       format: 'p2wpkh'   // address format used for signing
//     }
//   }
// }

Proof verification (server-side):

  1. Parse the message — verify origin matches your domain and issued is recent (< 5 minutes)
  2. Verify the BIP-322 signature against the address using the verification.format hint
  3. Optionally store the nonce to prevent replay

The proof is auto-signed during connection — no additional user prompt beyond the connect approval.

xcp_accounts

Get currently connected accounts. No popup — returns empty array if not connected or wallet is locked.

const accounts = await xcpwallet.request({ method: 'xcp_accounts' });
// ['bc1q...'] or []

xcp_disconnect

Disconnect the current site.

await xcpwallet.request({ method: 'xcp_disconnect' });
// true

Signing

All signing methods require an active connection and open a popup for user approval.

xcp_signMessage

Sign a message with the active address using BIP-322.

const result = await xcpwallet.request({
  method: 'xcp_signMessage',
  params: ['Hello, world!']
});
// { signature: '<base64 BIP-322 signature>' }

An optional second parameter can specify the address (must match the active address):

params: ['Hello, world!', 'bc1q...']

xcp_signTransaction

Sign a raw transaction hex.

const result = await xcpwallet.request({
  method: 'xcp_signTransaction',
  params: [{ hex: '0200000001...' }]
});
// { hex: '<signed transaction hex>' }

// Also accepts a plain string:
params: ['0200000001...']

xcp_signPsbt

Sign a PSBT (Partially Signed Bitcoin Transaction).

const result = await xcpwallet.request({
  method: 'xcp_signPsbt',
  params: [{
    hex: '<PSBT hex>',
    signInputs: { 'bc1q...': [0, 1] },  // optional: which inputs to sign
    sighashTypes: [0x01]                  // optional: sighash types
  }]
});
// { hex: '<signed PSBT hex>' }

Broadcasting

xcp_broadcastTransaction

Broadcast a signed transaction to the Bitcoin network. Includes replay protection — the same transaction cannot be broadcast twice.

const result = await xcpwallet.request({
  method: 'xcp_broadcastTransaction',
  params: ['<signed transaction hex>']
});
// { txid: '<64-char transaction hash>' }

Read-Only

xcp_getBalances

Get BTC and token balances for the connected address.

const result = await xcpwallet.request({ method: 'xcp_getBalances' });
// { address: 'bc1q...', btc: { ... }, xcp: { ... }, tokens: [...] }

xcp_chainId

await xcpwallet.request({ method: 'xcp_chainId' });
// '0x0' (Bitcoin mainnet)

xcp_getNetwork

await xcpwallet.request({ method: 'xcp_getNetwork' });
// 'mainnet'

Events

// Account changed (address switch or connect/disconnect)
xcpwallet.on('accountsChanged', (accounts) => {
  // accounts: string[] — empty on disconnect
});

// Wallet disconnected this site
xcpwallet.on('disconnect', () => {
  // Connection revoked
});

SDK

An SDK is available for React/Next.js applications at lib/wallet/sdk. Copy the sdk/ folder into your project:

lib/wallet/
├── sdk/
│   ├── constants.ts   — validation patterns, error codes
│   ├── detect.ts      — provider detection with race condition handling
│   ├── errors.ts      — user-friendly error messages
│   ├── index.ts       — public exports
│   ├── provider.ts    — typed XcpWallet wrapper class
│   ├── types.ts       — TypeScript interfaces
│   └── verify.ts      — connection proof validation
├── wallet-context.tsx — React context provider (useWallet hook)
└── useCompose.ts      — compose → sign → broadcast pipeline

React usage

import { WalletProvider, useWallet } from '@/lib/wallet/wallet-context';

// Wrap your app
<WalletProvider>{children}</WalletProvider>

// In components
const { status, address, connectionProof, connect, disconnect } = useWallet();
// status: 'not_detected' | 'disconnected' | 'connected'

Direct usage (non-React)

import { detectProvider, XcpWallet } from '@/lib/wallet/sdk';

const provider = await detectProvider();
const wallet = new XcpWallet(provider);
const { accounts, proof } = await wallet.connect();

Proof verification

import { validateProof } from '@/lib/wallet/sdk';

// Client-side (structural checks only)
const result = await validateProof(proof, 'https://example.com', address);

// Server-side (with cryptographic verification)
const result = await validateProof(proof, origin, address, {
  verifySignature: async (message, signature, addr) => {
    // Use your BIP-322 verification library
    return await verifyBIP322(message, signature, addr);
  }
});

Security

  • Origin validation: The content script provides the origin — page JavaScript cannot spoof it
  • Connection proof: BIP-322 signature proving address ownership, message format controlled by extension
  • Rate limiting: Connection, transaction, and API requests are rate-limited per origin
  • Replay protection: Broadcast transactions are tracked to prevent double-submission
  • Parameter validation: All inputs are type-checked and size-limited (max 1MB)
  • CSP analysis: Sites without Content Security Policy generate console warnings