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>
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):
- https://docs.riftenlabs.com/wizardconnect/ — describes the transport and encryption model (Nostr NIP-17 gift-wrap, xpub handshake, auto-reconnect).
- https://cashscript.org/docs/guides/walletconnect#wizardconnect —
describes the transaction payload format (reuses the WC2
WcTransactionObjectshape + addsinputPathstriplets).
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(perkey-exchange.js:DEFAULT_RELAY_URLS). Usesnostr-tools/poolunder the hood. NIP-17 gift-wrap encryption for E2E confidentiality. - URI scheme =
wiz://?p=<publicKey>&s=<secret>— plus a QR-alphanumeric-safe variantWIZ://%3FP%3D...%26S%3D.... Both are encoded/decoded byencodeKeyExchangeURI/decodeKeyExchangeURI. NOTwc: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 isHdwalletv1Session { paths: PathXpub[], extensions? }. - Sign-transaction payload wraps a WC2-compatible tx object —
SignTransactionRequest.transaction: WcSignTransactionRequest(imported from@bch-wc2/interfaces). PlusinputPaths: [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(perPROTOCOL_NAMEexport).
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
inputPathsto 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 attachinputPathsmetadata 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.tswrapping@wizardconnect/wallet'sWalletConnectionManager. responder.tscarries 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
classifyUriwith a fourth branch for the WizardConnect URI scheme (TBD — capture from SDK on adoption). - Extend the
bch-namespace.tsconstants with WizardConnect's method surface once we read itsprotocol-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
KeyStorewithexportXpub(): 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 (addmanualChunksrule invite.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:443vianostr-tools, NIP-17 gift-wrap encryption. NOT the WalletConnect v2 relay. wiz://URI scheme at pairing time, NOTwc:.- WC2-shape transaction payload inside the encrypted Nostr message —
the
SignTransactionRequest.transactionfield is aWcSignTransactionRequestfrom@bch-wc2/interfaces(the same tx-object shape as WC2). Plus theinputPathstriplets.
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.
-
Add deps:
@wizardconnect/core@0.2.4,@wizardconnect/wallet@0.2.2. Both proprietary — verify LICENSE before merging. -
Extend
KeyStore(src/wallet/keystore.ts) — Task 2 already shippedderiveChildKey+supportedPathNames+ BIP44 branch map (receive → 0,change → 1,defi → 7) matching WizardConnect'shdwalletv1.js:childIndexOfPathName. Still needed for Task 3:exportXpub(pathName: string): Promise<string>— returns the BIP32 base58-encoded xpub atm/44'/145'/0'/<branch>(branch level, one step above address indices). Thedefipath is officially aliased "Cauldron" in the docs.
-
New
shells/app/src/connect/wz-adapter.ts— implementsWalletAdapterper 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 }getRelayPrivateKeyreturns a fresh 32-byte relay-encryption key per session (the docs say either ephemeral or HD-derived — we usecrypto.getRandomValues(new Uint8Array(32))for ephemeral). -
New
shells/app/src/connect/wz-handler.ts— instantiatesWalletConnectionManager(adapter)and wires it into the responder's event streams. EmitspendingSignRequest→ responder surfaces to UI. On approve →manager.sendSignResponse(connectionId, sequence, signedTxHex). -
Extend
classifier.ts:classifyUri— recognizewiz:///WIZ://prefixes (URI format:wiz://?p=<pubkey_bech32>&s=<secret_bech32>default, orwiz://<hostname>:<port>?p=...&s=...&pr=<protocol>custom-relay). Return a new'wz'protocol tag. -
Extend
responder.ts— hold two transport clients (the shared WalletKit + the WizardConnectWalletConnectionManager). Both feed the same event streams (session proposal, sign request) — union the state in the UI. -
Extend
signer.ts:coSignTransaction— honourinputPathstriplets. For each[inputIndex, pathName, addressIndex]:- Call
KeyProvider.deriveForInput(pathName, addressIndex)— wires to Task 2'sKeyStore.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 usesSIGHASH_ALL | SIGHASH_FORKID = 0x41; WizardConnect REQUIRES the additionalSIGHASH_UTXOSflag (see libauthSigningSerializationFlag.utxos).
- Call
-
URI routing in
qr-uri.ts— addwiz:/WIZ:(case-insensitive) prefixes to theconnectclassification. QR encoding uses alphanumeric mode with?/=/&percent-encoded as%3F/%3D/%26— thedecodeKeyExchangeURIhelper in@wizardconnect/coreaccepts 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'swallet_readymessage 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-fillsaction,sequence,time. Acceptsoptions.signalforAbortControllercancellation.sendSignRequest(request: SignTransactionRequest): Promise<SignTransactionResponse>— low-level. Caller setssequence(vianextSequence()) andtime.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 | undefinedgetPubkeys(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 sendswallet_ready(xpubs available). Payload includeswallet_nameand thePathXpub[]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
WzSessionclass in the proposer that wrapsDappConnectionManager(mirrors the existingWcSession/CcSessionpattern inspike/wc-connect/). - Expose
signTransaction(request, options?)on the session; the caller builds a WC2-shape tx +inputPathslist and gets a signed hex back. - Reuse Deviant's
WcTransactionObjectbuilder — thetransaction+sourceOutputspayload 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 formWIZ://%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.transactionis aWcSignTransactionRequest(WC2-shape tx object) +inputPathstriplets - 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
initiateWalletRelayaccepts 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 (
hdwalletv1protocol only definessign_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
extensionsfield onHdwalletv1Sessionis 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 rulewiki/decisions.mdADR-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