deviant/Parameters/WalletConnect/WizardConnect_reference.md
Local Dev 6987591065 feat(connect): unblock WizardConnect end-to-end + honest sign preview
Two prior sessions and this one collectively unblocked WizardConnect for
chipnet beta. Landed here:

Licence trap resolved:
- @wizardconnect/* is LGPL-3.0-or-later (verified against gitlab
  repo LICENSE.txt, README, Riften Labs announcement). "Proprietary"
  in `npm view` is a missing `license` field artefact, not a term.
- WizardConnect_reference.md §0/§3.3, README wallet table, wz-adapter.ts
  and wz-handler.ts headers corrected. Mainnet is no longer blocked;
  §6 of the integration guide lists the remaining LGPL §4 obligations.

Wrapper unwrap bug fixed (responder.ts:279-310):
- SignTransactionRequest.transaction is a WcSignTransactionRequest
  wrapper, not the tx. Old code passed the wrapper through as `tx`, so
  every WZ sign request threw "malformed request — missing tx.inputs"
  at signer.ts:136. Now unwrapped + hex-string variant decoded +
  broadcast/userPrompt forwarded. Regression tests added.

Sign-preview honesty (AppsScreen.tsx + preflight.ts):
- Replaced hardcoded "✓ no token UTXOs spent" / "bchtest:qp2…7lm" fake
  pre-flight block with real request-derived outputs, fee, and token
  flags. Renders dApp's `userPrompt` verbatim per protocol.
- Hides the block when the tx can't be decoded rather than lying about
  what was checked.

Dropped SDK events surfaced (wz-handler.ts + responder.ts + useConnect.ts):
- signCancelled + remoteDisconnect now flow through WzHandlerEventMap.
- Responder evicts pending WZ entries on cancel; new
  ResponderEvents.onSignRequestCancel clears the approval screen if the
  user was about to approve a withdrawn request.

WIF-restored wallets get an honest message (useConnect.ts):
- KeyStoreError('not-supported') from a WIF-restored wallet now logs a
  quiet console info line instead of a red "WizardConnect not supported
  by this build" banner. WC/CC remain available. Other failures still
  surface normally.

Definition of Done (see WIZARDCONNECT-INTEGRATION-GUIDE.md §11) items
§1–§4 landed here. §5–§9 handed off to a fresh session via
NEXT-SESSION-PROMPT.md — includes Reown decoupling, LGPL §4(d)(1)
chunk split, session re-hydrate, on-device test against Silent Mode
registrar (with Cashonize v0.9.0 as comparison implementation), and
ADR-0044 for the Nostr transport.

Tests: 162/162 passing. tsc --noEmit clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-27 20:07:50 +02:00

26 KiB

WizardConnect — design reference + future-implementation notes

BCH connector by Riften Labs (devs Dagur & H4lvor). Two source pages describe it two different ways — reconciliation below is the load-bearing part of this doc. Reference for evaluating it as a third Apps-tab protocol in the Deviant wallet alongside WalletConnect (WalletConnect_integration_reference.md) and CashConnect (CashConnect_reference.md). Not yet implemented in Deviant.

⏱ Update 2026-07-25 — adoption has moved; older "Paytaca-only" facts below are stale.

  • Cashonize v0.9.0 (released 2026-07-25, desktop Win/Linux + Android) added WizardConnect — so wallet support is no longer Paytaca-only. The same release ships an upgraded CashConnect "now over Nostr" and a unified sessions view for all three connectors. Source: Cashonize v0.9.0 announcement (TradingView/CoinMarketCal, 25 Jul 2026).
  • Broader adoption reported (multiple wallets + dApps beyond Paytaca) but not yet independently verified per-wallet/per-dApp here — re-pin against the SDK + each wallet's release notes when confirming. Treat the §0/§0a tables below as the 2026-06-29 source-pinned snapshot, corrected by this note.
  • Notable second-order fact: CashConnect moving to Nostr transport narrows the WC2-vs-CC-vs-WZ distinction — two of the three now ride Nostr.

Sources (both partially describe the same thing — pinned against the installed source 2026-06-29):

0a. Reconciled — what actually ships in @wizardconnect/core@0.2.4

Read the tarball; the two source pages describe the same protocol at different layers:

  • Transport = Nostr WebSocket relay at wss://relay.riften.net:443 (per key-exchange.js:DEFAULT_RELAY_URLS). Uses nostr-tools/pool under the hood. NIP-17 gift-wrap encryption for E2E confidentiality.
  • URI scheme = wiz://?p=<publicKey>&s=<secret> — plus a QR-alphanumeric-safe variant WIZ://%3FP%3D...%26S%3D.... Both are encoded/decoded by encodeKeyExchangeURI / decodeKeyExchangeURI. NOT wc: and NOT a WC2 URI extension.
  • Handshake: wallet sends PathXpub[] (array of {name, xpub} per named HD path) so the dApp derives addresses locally. Session state is Hdwalletv1Session { paths: PathXpub[], extensions? }.
  • Sign-transaction payload wraps a WC2-compatible tx object — SignTransactionRequest.transaction: WcSignTransactionRequest (imported from @bch-wc2/interfaces). Plus inputPaths: [inputIndex, pathName, addressIndex][]. So the CashScript-guide's "reuses the BCH WalletConnect transaction object" is correct — but only about the tx payload shape inside the Nostr message. The transport itself is Nostr, not WC2.
  • Protocol name: hdwalletv1 (per PROTOCOL_NAME export).

Correction to a previous version of this doc (2026-06-29): I had described "Path A = WC2 extension" and "Path B = Nostr stack" as two possibilities. There is only one path today — the Nostr stack with a WC2-shape tx payload. Any Deviant adapter needs to run a Nostr relay client, not extend the existing WC handler with inputPaths support.


0. TL;DR (pinned against @wizardconnect/core@0.2.4 source)

Question Answer
Protocol name hdwalletv1 (PROTOCOL_NAME in protocols/hdwalletv1.js)
Transport Nostr WebSocket relay (default wss://relay.riften.net:443)
Encryption NIP-17 gift-wrap end-to-end (relay sees only ciphertext)
URI scheme wiz://?p=<publicKey>&s=<secret> (also QR-safe WIZ://%3F...)
Account model Wallet sends PathXpub[] at handshake; dApp derives addresses locally
Path names receive, change, defi — plus extension names (opaque to protocol)
BIP44 branch indices receive → 0, change → 1, defi → 7 (via childIndexOfPathName); extensions have no fixed index
Full BIP44 path m/44'/145'/<account>'/<branchFromMap>/<addressIndex> (BCH coin type 145)
Sign-tx payload SignTransactionRequest.transaction: WcSignTransactionRequest (imported from @bch-wc2/interfaces) + inputPaths: [inputIndex, pathName, addressIndex][]
Message actions dapp_ready, wallet_ready, sign_transaction_request, sign_transaction_response, sign_cancel, disconnect, chunk, ping, pong
Reconnection Auto — both sides reconnect independently (per Riften Labs docs)
Wallet support Paytaca (reference wallet) + Cashonize v0.9.0 (added 2026-07-25). No longer Paytaca-only; broader adoption reported, verify per-wallet.
dApp support Cauldron (per docs.riftenlabs.com/cauldron/), ParyonUSD — 2026-06-29 snapshot; likely broader now (unverified)
SDK packages @wizardconnect/core@0.2.4, @wizardconnect/wallet@0.2.2, @wizardconnect/dapp@0.2.2, @wizardconnect/react@?
Dependencies of core @bch-wc2/interfaces, nostr-tools, @bitauth/libauth, eventemitter3, isomorphic-ws, lossless-json, ws
License LGPL-3.0-or-later — free for commercial + non-commercial use. npm view shows "Proprietary" only because the packages omit the license field + exclude LICENSE.txt from files. Verified 2026-07-27 against gitlab.com/riftenlabs/lib/wizardconnect LICENSE.txt (verbatim LGPL, no additional terms), repo README, and Riften Labs' launch announcement. Correction to earlier "Proprietary … NOT LGPL" claim in this doc — that "correction" was itself the error. See WIZARDCONNECT-INTEGRATION-GUIDE.md §6 for the LGPL §4 obligations.
Status in Deviant Implemented + fixed. Adapter, handler, responder wiring, and unwrap bug fix all in shells/app/src/connect/. On-device pairing pending — see WIZARDCONNECT-INTEGRATION-GUIDE.md §8.

0b. What CashScript itself says (verbatim summary)

  • "WizardConnect is an HD-wallet-aware signing protocol."
  • "It reuses the BCH WalletConnect transaction object but adds HD path metadata called inputPaths to specify which HD key should sign each input." → Correct about the tx-object shape, but only inside the Nostr-encrypted wrapper.
  • "CashScript does not add a separate WizardConnect abstraction." Developers build transactions using the existing generateWcTransactionObject() method and attach inputPaths metadata themselves before sending requests to the wallet.
  • Wallet-side entry point (per the SDK): dappConnectionManager.signTransaction() on the dApp side; initiateWalletRelay() on the wallet side.

1. Why it's distinctive

Three genuine advantages over WC2 / CashConnect:

1.1 End-to-end encryption (privacy ceiling raised)

The wallet and dApp exchange NIP-17 gift-wrap encrypted messages over a Nostr relay. The relay sees only ciphertext. WC2's TLS-only relay can see session-pairing metadata (peer names, topics, request counts); WizardConnect removes that observability.

1.2 xpub handshake (no per-address round-trips)

"The wallet sends BIP32 xpubs in the handshake; the dapp derives all addresses locally with no further round-trips."

For dApps that need multiple addresses from the same wallet (DEXes, prediction markets, multisig flows), this avoids the chatty WC2 pattern of bch_getAddresses per derivation. The dApp computes any address from the xpub locally.

1.3 Automatic reconnection

"Both sides can disconnect and reconnect independently … and converge back to a live session without user action."

WC2 sessions need explicit resurrection or re-pairing when either side goes offline.

1.4 Semantic derivation paths

receive, change, defi rather than numeric BIP32 indices. Easier for dApps to reason about purpose-of-address.


2. SDK packages

Four packages (LGPL-3.0-or-later — corrected 2026-07-27; see §0 table):

Package Role
@wizardconnect/core Transport + encryption + protocol types
@wizardconnect/wallet WalletConnectionManager — wallet-side multi-connection management
@wizardconnect/dapp DappConnectionManager — dApp-side xpub state
@wizardconnect/react Components, hooks, QR dialog

3. Architecture notes for a future Deviant adapter

It does NOT fit the existing Deviant multi-protocol classifier (shells/app/src/connect/classifier.ts). The classifier routes by WC2 proposal shape and assumes the relay is WC2; WizardConnect needs its own Nostr-relay client and its own session lifecycle.

KeyStore (one)
   │
   ▼
WalletKit (WC2 relay)  ──┬── WC handler  (bch_*)
   │                     └── CashRPC_V0  (bch_*_V0)
   │
WizardManager (Nostr relay) ──── WZ handler   ← THIS is what would be added

3.1 Sketch of the integration

  • Add shells/app/src/connect/wz-handler.ts wrapping @wizardconnect/wallet's WalletConnectionManager.
  • responder.ts carries two clients now (WalletKit + Wizard manager). They don't conflict — they're isolated by transport — but the UI state surface (sessions list, pending proposal, sign request) needs to union both event streams.
  • Extend classifyUri with a fourth branch for the WizardConnect URI scheme (TBD — capture from SDK on adoption).
  • Extend the bch-namespace.ts constants with WizardConnect's method surface once we read its protocol-types.ts.

3.2 KeyStore implications

The xpub handshake means we need to expose an xpub from the active KeyStore. The current KeyStore interface (src/wallet/keystore.ts) returns an AccountIdentity with a single 33-byte compressed publicKey

  • a derivation path. Either:
  • Extend KeyStore with exportXpub(): Promise<string> (clean), or
  • Compute the xpub from the seed at WizardConnect-init time (separate derivation pass)

Pick the first — the KeyStore is the right authority for what derived material a session may see.

3.3 License implications

LGPL-3.0-or-later (verified 2026-07-27 — see §0 table). Free for commercial + non-commercial use. We link the library and do not modify it, so this is LGPL §4 Combined Work — copyleft does not propagate to Deviant's own code. Obligations:

  • §4(a) — attribute WizardConnect + LGPL in the About/Licences screen.
  • §4(b)(c) — ship copies of LGPL-3.0 and GPL-3.0 with the app.
  • §4(d)(1) — keep @wizardconnect/* a separately replaceable chunk (add manualChunks rule in vite.config.ts).

Full checklist in WIZARDCONNECT-INTEGRATION-GUIDE.md §6. Mainnet is no longer blocked by licensing (was previously flagged as blocked; that flag was based on the incorrect Proprietary reading).


4. Implementation plan (single path — the two-path framing was wrong)

Corrected 2026-06-29 after reading @wizardconnect/core@0.2.4 source. Previous versions of this doc alternated between "distinct proprietary protocol" (which was too strong at the payload-format layer) and "WC2 extension" (which was wrong at the transport layer). Both were partial views. The actual shipping protocol is:

  • Nostr transport — WebSocket to wss://relay.riften.net:443 via nostr-tools, NIP-17 gift-wrap encryption. NOT the WalletConnect v2 relay.
  • wiz:// URI scheme at pairing time, NOT wc:.
  • WC2-shape transaction payload inside the encrypted Nostr message — the SignTransactionRequest.transaction field is a WcSignTransactionRequest from @bch-wc2/interfaces (the same tx-object shape as WC2). Plus the inputPaths triplets.

So there is one implementation path, not two. It IS the "parallel Nostr adapter" from an earlier draft of this doc — the "WC handler extension" hypothesis is architecturally wrong at the transport level.

4.1 The plan (revised 2026-07-04 after reading official docs)

The wallet-side integration surface is WalletConnectionManager + WalletAdapter interface — not the raw initiateWalletRelay primitive from a previous version of this doc. Higher-level, less code to write.

  1. Add deps: @wizardconnect/core@0.2.4, @wizardconnect/wallet@0.2.2. Both proprietary — verify LICENSE before merging.

  2. Extend KeyStore (src/wallet/keystore.ts) — Task 2 already shipped deriveChildKey + supportedPathNames + BIP44 branch map (receive → 0, change → 1, defi → 7) matching WizardConnect's hdwalletv1.js:childIndexOfPathName. Still needed for Task 3:

    • exportXpub(pathName: string): Promise<string> — returns the BIP32 base58-encoded xpub at m/44'/145'/0'/<branch> (branch level, one step above address indices). The defi path is officially aliased "Cauldron" in the docs.
  3. New shells/app/src/connect/wz-adapter.ts — implements WalletAdapter per the docs:

    interface WalletAdapter {
      walletName: string;
      walletIcon: string;
      getRelayPrivateKey(): Uint8Array;                    // 32-byte, ephemeral or HD
      getPublicKey(path: string, index: number): Uint8Array; // 33-byte compressed secp256k1
      getXpub(path: string): string;                        // base58 BIP32 xpub
      signTransaction(request): Promise<{ signedTransactionHex: string }>;
      getAdditionalPaths?(): string[];                      // optional extension paths
      getExtensions?(): Record<string, unknown>;            // optional protocol extensions
    }
    

    getRelayPrivateKey returns a fresh 32-byte relay-encryption key per session (the docs say either ephemeral or HD-derived — we use crypto.getRandomValues(new Uint8Array(32)) for ephemeral).

  4. New shells/app/src/connect/wz-handler.ts — instantiates WalletConnectionManager(adapter) and wires it into the responder's event streams. Emits pendingSignRequest → responder surfaces to UI. On approve → manager.sendSignResponse(connectionId, sequence, signedTxHex).

  5. Extend classifier.ts:classifyUri — recognize wiz:// / WIZ:// prefixes (URI format: wiz://?p=<pubkey_bech32>&s=<secret_bech32> default, or wiz://<hostname>:<port>?p=...&s=...&pr=<protocol> custom-relay). Return a new 'wz' protocol tag.

  6. Extend responder.ts — hold two transport clients (the shared WalletKit + the WizardConnect WalletConnectionManager). Both feed the same event streams (session proposal, sign request) — union the state in the UI.

  7. Extend signer.ts:coSignTransaction — honour inputPaths triplets. For each [inputIndex, pathName, addressIndex]:

    • Call KeyProvider.deriveForInput(pathName, addressIndex) — wires to Task 2's KeyStore.deriveChildKey.
    • Derive the child address, confirm it matches the source-output's lockingBytecode — structural non-custody, ADR-0041 §5.
    • Sign the mapped input.
    • SIGHASH REQUIREMENT (from docs, non-negotiable): wallet MUST sign with SIGHASH_ALL | SIGHASH_FORKID | SIGHASH_UTXOS. Other sighash flags MUST be rejected. This is stronger than our WC path which uses SIGHASH_ALL | SIGHASH_FORKID = 0x41; WizardConnect REQUIRES the additional SIGHASH_UTXOS flag (see libauth SigningSerializationFlag.utxos).
  8. URI routing in qr-uri.ts — add wiz: / WIZ: (case-insensitive) prefixes to the connect classification. QR encoding uses alphanumeric mode with ?/=/& percent-encoded as %3F/%3D/%26 — the decodeKeyExchangeURI helper in @wizardconnect/core accepts both standard and QR-encoded formats, so we just pass the raw scan through.

4.2 Effort estimate

  • KeyStore extension + impls + tests: ~1 day
  • wz-handler.ts + responder wiring + classifier/qr-uri updates: ~1 day
  • On-device validation with Cauldron: ~half day (device work is user's)
  • Total: ~2.5 days of focused work

4.3 The named-precedent dApps

Cauldron (per docs.riftenlabs.com/cauldron/ — has a dedicated WizardConnect docs section) and ParyonUSD (paryonusd.com/app) both use WizardConnect. Cauldron is the highest-signal test target because its swap + LP flows genuinely need HD-key-per-input mapping.


5. dApp-side integration reference (from docs.riftenlabs.com/wizardconnect/dapp/)

The reference above (§4) covers the wallet-side SDK — the surface Deviant implements to serve WizardConnect dApps. This section documents the dApp-side SDK — the surface a Deviant-facing dApp (Cauldron, or a Deviant-authored dApp like the tote) implements to pair with wallets. Pinned against the same package generation (@wizardconnect/dapp@0.2.2).

5.1 DappConnectionManager — the primary class

constructor(
  dappName?: string,
  dappIcon?: string,
  options?: { session?: DappSessionOptions | false; },
)

Manages one dApp↔wallet session — handshake, xpub storage, sign-request round-trips. Extends EventEmitter.

5.2 Methods (grouped by purpose)

Connection lifecycle:

  • updateConnection(client: RelayClient | null, status: RelayStatus): void — called from the relay callback (initiateDappRelay), keeps the manager's internal relay-status view in sync.
  • attachRelay(relay) — bind the initialised relay to the manager.
  • isWalletDiscovered(): boolean — true once the wallet's wallet_ready message has been received. Used to gate address-derivation calls.
  • sendDisconnect(message?: string): Promise<void> — courtesy disconnect.
  • clearStoredSession(): void — wipes the persisted session.

Sign requests (two levels):

  • signTransaction(request, options?): Promise<SignTransactionResponse> — the recommended high-level API. Auto-fills action, sequence, time. Accepts options.signal for AbortController cancellation.
  • sendSignRequest(request: SignTransactionRequest): Promise<SignTransactionResponse> — low-level. Caller sets sequence (via nextSequence()) and time.
  • sendSignCancel(sequence: number, reason?: string): Promise<void> — cancel an in-flight request by sequence number.
  • nextSequence(): number — get the next monotonic sequence.

Address derivation (via delegated pubkeyState):

  • getPubkey(childIndex: number, index: bigint): Uint8Array | undefined
  • getPubkeys(childIndex: number): Map<bigint, Uint8Array>
  • getAddressIndex(childIndex: number): bigint / setAddressIndex(...)
  • getIndexToUse(childIndex, options?): bigint — smart index selection (dApps often want "next unused address"; this handles the bookkeeping).
  • getIndexRange(childIndex): { min?: bigint; max?: bigint }
  • getXpubNode(childIndex): HdPublicNodeValid | undefined

childIndex = the enum from @wizardconnect/wallet (0 receive, 1 change, 7 defi/Cauldron). Same numbers as bip44BranchPathFor in Deviant's src/wallet/keystore.ts.

Session cache:

  • getSessionPaths(): PathXpub[]
  • restoreSessionPaths(paths: PathXpub[]): void

5.3 Events

  • walletready — fires when the wallet sends wallet_ready (xpubs available). Payload includes wallet_name and the PathXpub[] list.
  • messagesent / messagereceived — protocol-level events for logging / debugging.
  • disconnect — wallet gone or protocol mismatch. Handler receives (reason: DisconnectReason, message?: string).

5.4 Connection lifecycle (verbatim code from docs)

// 1. Construct + initiate relay
const dappMgr = new DappConnectionManager('My Dapp', 'https://…/icon.png');
const relay = initiateDappRelay((payload) => {
  dappMgr.updateConnection(payload.client, payload.status);
});
dappMgr.attachRelay(relay);
console.log('Scan this URI:', relay.uri);   // wiz://?p=…&s=…

// 2. Wait for wallet
dappMgr.on('walletready', (msg) => {
  console.log('Wallet:', msg.wallet_name);
  // xpubs now available — dApp can derive addresses locally
});

// 3. Derive addresses (any time after walletready)
const RECEIVE = 0;
const pubkey = dappMgr.getPubkey(RECEIVE, 0n);

// 4. Sign a transaction (high-level, recommended)
const response = await dappMgr.signTransaction({
  transaction: {
    transaction: txHex,
    sourceOutputs,
    userPrompt: 'Confirm swap',
    broadcast: true,
  },
  inputPaths: [[0, 'receive', 0], [1, 'defi', 5]],
});
console.log('Signed tx:', response.signedTransaction);

// 5. Disconnect
dappMgr.clearStoredSession();
await dappMgr.sendDisconnect('user closed the tab');
relay.cleanup();

5.5 Cancellation (AbortController)

const controller = new AbortController();
try {
  const response = await dappMgr.signTransaction(
    { transaction: {...}, inputPaths: [...] },
    { signal: controller.signal },
  );
} catch (err) {
  if (err.name === 'AbortError') console.log('Cancelled');
}

Under the hood: signTransaction sends the request, then races the promise against the abort signal. On abort, calls sendSignCancel(seq).

5.6 Session persistence

Enabled by default. The manager auto-restores xpub paths at construction and saves wallet metadata + xpubs on wallet_ready.

Config options:

Option Effect
session omitted Default — localStorage, key wizardconnect-session
session: { key: 'my-app-session' } Custom localStorage key
session: { storage: myCustomStorage } Custom storage backend (must expose getItem / setItem / removeItem)
session: false Disable persistence entirely

Manual access if you need to interoperate with the persistence layer:

const session = loadSession(); // standalone loader
if (session?.walletPublicKey) {
  const relay = initiateDappRelay(callback, {
    existingCredentials: session,
  });
}

5.7 Reconnection semantics

When the relay's status transitions to 'connected', the manager sends a fresh dapp_ready. walletDiscovered state persists across reconnects. Any pending sign requests are automatically re-sent on wallet_ready, keeping the caller's promise valid — the dApp doesn't need to manually retry sign requests during a transient disconnect.

5.8 React integration (@wizardconnect/react)

Hook: useWizardConnect({ dappName }) returns { manager, ... }.

const wc = useWizardConnect({ dappName: 'My Dapp' });

useEffect(() => {
  if (!wc.manager) return;
  const wallet = new MyWalletAdapter(wc.manager);
  // dispatch to store …
}, [wc.manager]);

The hook manages session persistence, auto-reconnect, and cleanup automatically. Suitable when a Deviant-authored dApp is built as a React SPA.

5.9 Implications for Deviant proposer (src/connect/proposer.ts)

The Deviant proposer library today wraps WalletConnect v2's dApp-side sign-client. If a Deviant-authored dApp (tote, escrow, etc.) needs to support WizardConnect wallets, the natural extension pattern is:

  • Add a WzSession class in the proposer that wraps DappConnectionManager (mirrors the existing WcSession / CcSession pattern in spike/wc-connect/).
  • Expose signTransaction(request, options?) on the session; the caller builds a WC2-shape tx + inputPaths list and gets a signed hex back.
  • Reuse Deviant's WcTransactionObject builder — the transaction + sourceOutputs payload is identical between WC and WizardConnect. Only the transport wrapper differs.

Checkers explicitly asked about this in Checkers-response-to-instructions.md §Ask 2. If the Deviant proposer grows a WzSession, Checkers can vendor it and skip reimplementing.


6. Why it's still interesting to track

  • The xpub-handshake-then-derive-locally model is genuinely better than WC2's per-address pattern. If the BCH dApp ecosystem migrates toward this, we want to know.
  • The E2E encryption raises the privacy ceiling. For a privacy- oriented wallet like Deviant, this aligns with the project's posture.
  • Nostr as a transport decouples from WalletConnect, Inc. and Reown's product cycle. WC2's relay is run by a company; Nostr relays are pluralistic. Politically + operationally desirable.

7. Pinned facts (2026-06-29 tarball inspection) + remaining open items

Pinned from @wizardconnect/core@0.2.4:

  • URI scheme: wiz:// (QR-safe form WIZ://%3F...)
  • Default relay: wss://relay.riften.net:443
  • Protocol name: hdwalletv1
  • Path-name → BIP44 branch: receive→0, change→1, defi→7, extensions are opaque (no fixed branch index — dApp discovers via xpub)
  • Sign-tx payload: SignTransactionRequest.transaction is a WcSignTransactionRequest (WC2-shape tx object) + inputPaths triplets
  • Sign-transaction semantics: dApp-built pass-through (same as WC2 — the tx object is a WC2 object). So the covenant-routing rule from ADR-0043 §5 applies: WizardConnect CAN co-sign covenants (unlike CashConnect V0).

Still open:

  • Whether initiateWalletRelay accepts a caller-managed nostr-tools relay pool, or insists on owning the connection. Determines whether Deviant can share one Nostr relay client across multiple sessions or needs one per session.
  • Sign-message format (hdwalletv1 protocol only defines sign_transaction_request — no message-sign action). Per the CashScript guide: "WizardConnect covers transaction signing only. For arbitrary message signing, use BCH WalletConnect." So message-sign falls through to WC2 in a dual-protocol dApp.
  • Whether the extensions field on Hdwalletv1Session is used by any shipping dApp today (e.g., stealth_scan).

8. Cross-references

  • WalletConnect_integration_reference.md — WC2 reference (this folder)
  • CashConnect_reference.md — CC reference (this folder)
  • wiki/connector.md §3a — multi-protocol routing rule
  • wiki/decisions.md ADR-0043 — multi-protocol responder architecture
  • Snapshots: 0153 (WizardConnect research recorded), 0062 (the "is it WC2 underneath?" probe resolved)
  • Memory: wizardconnect-bch-connector.md
  • Upstream docs: https://docs.riftenlabs.com/wizardconnect/
  • Reference dApp: https://paryonusd.com/app