Q&A: Make a detailed comparison of the three main ways of connecting a wallet to a da...

Block answers a tech-support question about Make a detailed comparison of the three main ways of connecting a wallet to a dapp: WalletConnectV2 ...

Question:

Make a detailed comparison of the three main ways of connecting a wallet to a dapp: WalletConnectV2 (bch-wc2 by mainnet-pat), Wizard Connect (by Riften labs) and CashConnect.

Go as further as possible, comparing the APIs surfaces of each one.


Answer:

I’ve gathered deep material on all three protocols. Here’s the detailed comparison.


Connecting a BCH Wallet to a DApp: Three Protocols Compared

These three are the main wallet↔dapp connection standards on Bitcoin Cash. They share a goal — let a dapp propose a transaction (often with CashTokens smart contracts) and have a wallet sign it after user approval — but they differ sharply in transport, key model, transaction-building philosophy, and API surface.

Quick orientation

bch-wc2 (mainnet-pat) Wizard Connect (Riften/Whiterun) CashConnect (jimtendo)
Transport Official WalletConnect v2 relay Nostr relay + NIP-17 gift wrap WalletConnect v2 transport
App-layer protocol BCH WalletConnect (Pat’s spec) hdwalletv1 CashRPC
Key model Single address HD wallet (xpubs by named path) Sandboxed HD keypair
Transaction building Dapp builds tx, sends full tx + source outputs Dapp builds tx + inputPaths Wallet builds tx from a LibAuth template
Message signing Yes (bch_signMessage) No (extensions only) Via auth RPC
Status Production (Cashonize, Paytaca, Zapit) Active dev (2026), LGPL-3.0 Pre-alpha, merged into Cashonize

1. WalletConnect V2 → bch-wc2 (mainnet-pat)

The genesis of BCH wallet-dapp comms. Pat launched this via Flipstarter in 2023. It reuses the generic WalletConnect v2 transport layer (the @walletconnect/sign-client, relay at wss://relay.walletconnect.com, requires a projectId) but speaks a BCH-specific message set. It’s documented in the BCR at mainnet-pat/wc2-bch-bcr, with the TS interfaces + a test signer in the bch-wc2 monorepo.

Design philosophy

Deliberately single-address, Metamask-style. Pat explicitly argues against exposing an HD wallet to a dapp (analogous to giving out the xpub). Only the connected address’s key produces signatures; key rotation is advised. This trades privacy/convenience for reduced seed-exposure risk.

CAIP-2 namespace

  • bch:bitcoincash (mainnet), bch:bchtest (testnet), bch:bchreg (regtest)

Wire methods (RPC surface over the WC2 session)

  • bch_getAddressesstring[] of cashaddrs
  • bch_signTransactionWcSignTransactionRequestWcSignTransactionResponse
  • bch_signMessageWcSignMessageRequest → base64 string
  • Event: addressesChanged

The Connector interface (the de-facto API surface)

interface IConnector {
  address(): Promise<string | undefined>;
  signTransaction(o: WcSignTransactionRequest): Promise<WcSignTransactionResponse | undefined>;
  signMessage(o: WcSignMessageRequest): Promise<WcSignMessageResponse | undefined>;
  connect(): Promise<void>;
  connected(): Promise<boolean>;
  disconnect(): Promise<void>;
  on(event: "addressChanged" | "disconnect", cb: Function): void;
}

Key request/response types

interface WcSignTransactionRequest {
  transaction: Transaction | string;   // libauth obj, its stringify(), or raw hex
  sourceOutputs: WcSourceOutput[];     // Input & Output & ContractInfo
  broadcast?: boolean;                 // default true
  userPrompt?: string;
}
type WcSourceOutput = Input & Output & {
  contract?: { abiFunction; redeemScript; artifact };
};
interface WcSignTransactionResponse {
  signedTransaction: string;   // hex, ready to broadcast
  signedTransactionHash: string;
}
interface WcSignMessageRequest { message: string; userPrompt?: string; }
type WcSignMessageResponse = string; // base64, "\x18Bitcoin Signed Message:\n" prefix

How signing works for contracts: the app builds the full transaction locally (via CashScript’s TransactionBuilder.generateWcTransactionObject()) and includes sourceOutputs, which embed the contract’s ABI function, redeem script, and artifact. To signal which inputs the wallet must sign, the app either empties the unlockingBytecode or — for contract placeholders — uses zero-filled arrays: 33-byte zeros = “fill my pubkey here”, 65-byte zeros = “fill my schnorr signature here”. Source outputs are serialized with libauth’s stringify (which tolerates Uint8Array/BigInt).

Proposed but not core

  • bch_sendTransaction (simple pay: {recipientCashaddress, valueSatoshis, broadcast?, userPrompt?})
  • batchSignTransaction (sign a chain of txs, e.g. NFT mint or re-listing that needs cancel+create)

Adoption

Wallets: Cashonize, Paytaca, Zapit. Dapps: TapSwap, CashTokens Studio, Cash-Ninjas, and a catalog at tokenaut.cash.


2. Wizard Connect (Riften Labs / Whiterun LLC)

The newest and most architectural. Transport is Nostr: a wiz:// URI is scanned, and all messages travel over a Nostr relay wrapped in NIP-17 gift wrap (rumor → seal → wrap; kind 1059). End-to-end encrypted; the relay only sees ciphertext tagged to a recipient pubkey. Anyone can run a relay, so no single point of trust. Default relays: relay.riften.net, relay.cauldron.quest.

Design philosophy

HD-wallet-aware and privacy-first. Instead of exposing one address, the wallet sends BIP32 xpubs in the handshake; the dapp derives unlimited addresses locally with zero further round-trips. Derivation paths are identified by name (receive, change, defi) not numeric indices — a deliberate choice so the wallet retains control over actual derivation. Privacy caveat: because xpubs are shared, wallets may rotate paths per session (the protocol permits non-standard paths).

Protocol layers (in @wizardconnect/core)

  1. Base protocol — handshake: dapp_ready, wallet_ready, disconnect
  2. hdwalletv1 — carries session data (xpubs) + sign round-trips

Message envelope

interface ProtocolMessage { action: string; time: number; }

time is used for replay filtering (stale messages dropped after reconnect). The relay client also enforces peer filtering (only the paired pubkey) and MITM prevention via an 8-byte secret echoed from the URI.

Actions

dapp_ready, wallet_ready, sign_transaction_request, sign_transaction_response, sign_cancel, disconnect, chunk. Extensions may add custom actions (e.g. decrypt_request/decrypt_response).

Handshake / key exchange

Mutual-discovery handshake so either side can reconnect independently. wallet_ready delivers the wallet’s Nostr pubkey + session map + xpubs in one shot; the dapp verifies the secret, calls setPairedPublicKey(), picks the first mutually-supported protocol, and replies with a reactive dapp_ready(selected_protocol=...).

Session data — xpubs by name

interface Hdwalletv1Session {
  paths: PathXpub[];                    // { name: "receive"|"change"|"defi"; xpub }
  extensions?: Record<string, unknown>; // optional capabilities
}

Recommended BIP44 paths: receive = m/44’/145’/0’/0, change = …/1, defi = …/7.

The sign request (reuses bch-wc2’s tx object!)

This is the key API-shape overlap: WizardConnect reuses the exact WcSignTransactionRequest type from @bch-wc2/interfaces, and only adds HD metadata:

interface SignTransactionRequest {
  transaction: WcSignTransactionRequest;         // from @bch-wc2/interfaces
  inputPaths: [number, PathName, number][];       // [inputIndex, pathName, addressIndex]
  sequence: number;
  action: "sign_transaction_request";
  time: number;
}
interface SignTransactionResponse {
  sequence: number;
  signedTransaction: string;  // hex
  error?: string;
}

inputPaths tells the wallet which HD key signs each input — the dapp no longer needs to guess the key from the locking script. Only inputs needing wallet signing get entries; contract inputs with complete unlocking bytecode are omitted. Placeholder-filling for contract sig/pubkey args is inherited from the bch-wc2 tx format (implemented in Paytaca, wallet-dependent).

Security-critical rule: wallets MUST sign with SIGHASH_ALL | SIGHASH_FORKID | SIGHASH_UTXOS and reject anything else — because inputPaths lets the dapp pick the key, the wallet can’t independently verify the key matches the UTXO, so only SIGHASH_ALL makes a wrong-path signature invalid rather than graftable.

Dapp API surface (@wizardconnect/dappDappConnectionManager)

signTransaction(request, { signal? }): Promise<SignTransactionResponse>  // auto-cancel via AbortSignal
sendSignRequest(request): Promise<SignTransactionResponse>   // low-level
sendSignCancel(sequence, reason?)
sendDisconnect(message?)
nextSequence(): number
getPubkey(childIndex, index): Uint8Array   // derive on demand from xpub
getIndexToUse(childIndex, options?)
getSessionPaths() / restoreSessionPaths()
attachRelay(relay); loadStoredSession(); clearStoredSession();
// events: walletready, messagesent, messagereceived, disconnect

Wallet side uses WalletConnectionManager + a WalletAdapter interface with getAdditionalPaths()/getExtensions() hooks. React binding via @wizardconnect/react (useWizardConnect hook, QR dialog).

Extensions system

Protocol-level (negotiated in handshake), all backward-compatible:

  • bch_stealth_bip352 — stealth spend/scan paths (m/352’/145’/0’/0’, …/1)
  • rpa_bip47 — BIP47 reusable payment addresses (m/47’/145’/0’/0’, …/1)
  • decrypt — dapp-side encrypted storage (proposed)
  • Transport-level chunk — splits messages over NIP-44’s 65,535-byte plaintext ceiling (relevant for large multi-UTXO source outputs and signed-tx responses; consensus-max tx hex = 2 MB).

Notes

  • No generic message-signing method in core — the docs explicitly point you to bch-wc2 or a custom extension for that.
  • LGPL-3.0-or-later, copyright assigned to Whiterun LLC via CLA. Monorepo packages: core, wallet, dapp, react, test-cli.

3. CashConnect (Jim “jimtendo” Hamill)

CashConnect = CashRPC over WalletConnect v2. Jim built it as a follow-up to the WC2 thread to cover use-cases bch-wc2 couldn’t, then branded it “CashConnect” to avoid fracturing. It was merged into mainline Cashonize in Dec 2023 (spec still marked pre-alpha). Uses the same WC2 transport/projectId as bch-wc2 but a different application protocol, distinguished on the wallet side by a cc: / web+cc: URI protohandler.

Design philosophy

Make the underlying RPC transport-agnostic so it can later run over HTTP (CashPaymentProto), LibP2P, or intra-wallet (daemon/ServiceWorker). The wallet is a sandbox: the dapp gets a sandboxed keypair derived from the master key:

sandboxedPrivateKey = sha256(`${masterPrivateKey}cashrpc-over-wc:${domainName}`)

Accounts are identified as bch:${chain}:${publicKey} (deliberately not an address, to discourage dapps from sending funds directly to sandboxed accounts).

The big difference: template-based transactions

Instead of the dapp shipping a fully-built transaction + source outputs, CashConnect sends a LibAuth authentication template, and the wallet builds and signs the transaction itself (doing UTXO selection automatically and appending change). This is the philosophical split from both bch-wc2 and WizardConnect, where the dapp constructs the tx.

For security, the dapp can only specify inputs/outputs by scripts inside the template — no raw locking/unlocking bytecode. Four input/output kinds:

  1. Template-scoped input (uses a script from template)
  2. Template-scoped output (uses a script from template)
  3. Wallet-scoped input (must be a whitelisted token category)
  4. Wallet-scoped output (a change output; wallet supplies the lock script)

Templates get a top-level actions object defining the permitted transaction shapes (which scripts may be called together). This prevents a classic attack: a malicious service invoking a contract’s payoutLockingBytecode argument to pay itself a single-output tx. Template hashing/whitelisting (mark templates “trusted”) is planned.

Session negotiation / permissions

On connect, the wallet shows the template, the allowed methods (permissions), and which tokens the dapp may touch (allowedTokens = array of category ids or * for all). This supports NFT-specific and exchange use-cases.

Wallet-side integration surface (only ~150 LoC)

The CashConnectWallet library (from cashconnect npm package) requires few callbacks:

network.getSourceOutput(outpointTransactionHash, outpointIndex) → {valueSatoshis, lockingBytecode, token}
wallet.getUnspents() → UTXOs (with token data)  // LibAuth P2PKH template unlock
wallet.getChangeTemplate() → LibAuth template for change

And event callbacks: onSessionsUpdated, onSessionProposal, onSessionDelete, onRPCRequest, onError.

RPC methods (namespaced with _V0 version suffix)

From the Cashonize implementation, auto-approved methods:

  • wc_authRequest
  • bch_getTokens_V0
  • bch_getBalance_V0
  • bch_getChangeLockingBytecode_V0

Other RPCs (e.g. transaction creation/signing) require user approval modals. Method names use the bch_*_V0 convention so the protocol can version RPCs per method — a different naming scheme from bch-wc2’s flat bch_signTransaction.

Status / tooling

  • CashRPC IDE (cashrpc-ide.developers.cash) — a visual tool to design/test templates and contracts on chipnet.
  • Repos: bch-wc2-experimental (the CashRPC-over-WC library), a Cashonize fork, the IDE; a standalone CashRPC library was planned.
  • Explicitly pre-alpha with breaking changes expected.

Head-to-head: API & design trade-offs

Aspect bch-wc2 Wizard Connect CashConnect
Who builds the tx Dapp (full tx + sourceOutputs) Dapp (+ inputPaths) Wallet (from template + actions)
UTXO selection Dapp Dapp Wallet (auto, incl. change)
Key exposure to dapp One address BIP32 xpubs (named paths) Sandboxed keypair (sha256-derived)
Message signing bch_signMessage ❌ (extensions only) auth via wc_authRequest
Contract signing data Inline in sourceOutputs (artifact, ABI, redeemScript) Same as bch-wc2 + inputPaths Template + actions shapes
Placeholder mechanism 33/65-byte zero arrays Inherited from bch-wc2 tx format Template scripts, no raw bytecode
Reconnect handling WC2 session restore Mutual-discovery handshake WC2 session model
Extensibility New bch_* methods Named-path + action extensions (stealth, BIP47, decrypt) Versioned _V0 RPCs + actions
Signing flags enforced ✅ SIGHASH_ALL/FORKID/UTXOS mandatory (wallet constructs, so controlled)
Transport dependency Official WC2 relay + projectId Any Nostr relay (E2E NIP-17) WC2 relay + projectId
Privacy posture Lowest exposure, lowest flexibility High flexibility, HD-derived; xpub sharing Sandboxed, token-scoped grants

Choosing between them

  • bch-wc2 — the de-facto standard today, widest wallet/dapp support (Cashonize, Paytaca, Zapit; TapSwap, CashTokens Studio, Cash-Ninjas). Best if you want maximum reach now and need message signing.
  • Wizard Connect — the most modern/private, HD-native, reconnection-proof, with real E2E encryption and an extension system (stealth, BIP47). Strongest for Cauldron/DeFi and Nostr-aligned stacks, but newer and LGPL with a CLA. No generic message signing.
  • CashConnect — the most secure-by-construction model (wallet controls UTXO selection, template actions constrain flows, per-token grants) and transport-agnostic ambition, but it’s pre-alpha with the fewest adopters (effectively Cashonize).

Sources

Want me to go deeper on any one surface — e.g., the exact inputPaths/sequence mechanics of Wizard Connect, or the full CashRPC actions/template schema?


Public Q&A mirror from the Block tech-support bot.


Write a comment