# Dapp integration Dapp integration uses `@wizardconnect/dapp` (for session management and pubkey state) together with `@wizardconnect/core` (for the relay connection and URI generation). ## DappConnectionManager Manages a single dapp–wallet session. Handles the handshake, xpub storage, and sign request round-trips. Most dapps only ever have one active session at a time. ```typescript class DappConnectionManager extends EventEmitter { readonly pubkeyState: DappPubkeyStateManager; walletName: string | null; walletIcon: string | null; /** The agreed protocol name after handshake, e.g. "hdwalletv1". Null until wallet_ready. */ protocol: string | null; constructor(dappName?: string, dappIcon?: string) /** Call from the RelayStatusCallback each time the relay status changes. * Attaches the message listener exactly once (on first client seen). * Triggers onConnected() on each "connected" event. */ updateConnection(client: RelayClient | null, status: RelayStatus): void isWalletDiscovered(): boolean /** Get the next sequence number for a SignTransactionRequest. */ nextSequence(): number /** Send a sign request and wait for the wallet's response. * Rejects if the wallet returns an error or if the connection drops. */ sendSignRequest(request: SignTransactionRequest): Promise /** Send a UserDisconnect courtesy message to the wallet. * Caller is responsible for calling dappRelay.cleanup() afterwards. */ sendDisconnect(message?: string): Promise /** Get raw PathXpub[] received in wallet_ready (for caching). */ getSessionPaths(): PathXpub[] /** Restore cached xpub paths — enables getPubkey() without wallet_ready. */ restoreSessionPaths(paths: PathXpub[]): void // Events on("walletready", (msg: WalletReadyMessage) => void) on("messagesent", (msg: ProtocolMessage) => void) on("messagereceived", (msg: ProtocolMessage) => void) on("disconnect", (reason: DisconnectReason, message: string | undefined) => void) } ``` ## Pubkey state — convenience delegation `DappConnectionManager` delegates to `pubkeyState` for all pubkey operations. These methods are also available directly on the manager: ```typescript // Get a pubkey (derives on demand from xpub if not cached) getPubkey(childIndex: number, index: bigint): Uint8Array | undefined // Get all cached pubkeys for a path getPubkeys(childIndex: number): Map // Get/set the current address index for a path getAddressIndex(childIndex: number): bigint setAddressIndex(childIndex: number, index: bigint): void // Get a smart "next index to use" (see pubkey-derivation.md) getIndexToUse(childIndex: number, options?: { index?: bigint; reuseLast?: boolean }): bigint // Get the min/max indices seen for a path getIndexRange(childIndex: number): { min?: bigint; max?: bigint } // Remove a used change address from the gap-fill queue removeFromChangeQueue(index: bigint): void // Get the stored xpub node (after wallet_ready) getXpubNode(childIndex: number): HdPublicNodeValid | undefined // Get raw PathXpub[] from wallet_ready (for caching) getSessionPaths(): PathXpub[] // Restore cached PathXpub[] — populates pubkeyState so getPubkey works without wallet_ready restoreSessionPaths(paths: PathXpub[]): void ``` Child index values: `0` = receive, `1` = change, `7` = defi (Cauldron). These are internal to the dapp layer; use `childIndexOfPathName()` to convert from `PathName` if needed. ## Session lifecycle ### Initial connect ```typescript import { initiateDappRelay } from "@wizardconnect/core"; import { DappConnectionManager } from "@wizardconnect/dapp"; const dappMgr = new DappConnectionManager("My Dapp", "https://example.com/icon.png"); const relay = initiateDappRelay( (payload) => { dappMgr.updateConnection(payload.client, payload.status); // also update your own UI state here (connected/disconnected indicator) }, { explicitRelayUrls: ["wss://relay.cauldron.quest:443"] }, ); // Show relay.uri as a QR code for the wallet to scan. console.log("Scan this URI:", relay.uri); ``` ### After wallet connects ```typescript dappMgr.on("walletready", (msg) => { console.log("Wallet:", msg.wallet_name); // pubkeyState is now populated with xpub nodes. // You can start deriving addresses. }); ``` ### Deriving addresses ```typescript // Get the first receive address pubkey: const RECEIVE = 0; const pubkey = dappMgr.getPubkey(RECEIVE, 0n); // derives from xpub if needed ``` See [pubkey-derivation.md](pubkey-derivation.md) for full details. ### Sending a sign request ```typescript const seq = dappMgr.nextSequence(); const request: SignTransactionRequest = { action: RelayMsgAction.SignTransactionRequest, sequence: seq, time: Math.floor(Date.now() / 1000), transaction: { transaction: { inputs, outputs, version: 2, locktime: 0 }, sourceOutputs, userPrompt: "Confirm swap", broadcast: true, }, inputPaths: [[0, "receive", 0], [1, "defi", 5]], // [inputIndex, pathName, addressIndex] }; try { const response = await dappMgr.sendSignRequest(request); console.log("Signed tx:", response.signedTransaction); } catch (err) { console.error("Signing failed:", err.message); } ``` `sendSignRequest` returns a Promise that resolves when the wallet sends back a `sign_transaction_response` with the matching `sequence`. It rejects if the wallet sends an error response. ### Disconnecting ```typescript // Dapp-initiated: send courtesy message, then tear down the relay await dappMgr.sendDisconnect("user closed the tab"); relay.cleanup(); // Listen for wallet-initiated disconnect or protocol mismatch: dappMgr.on("disconnect", (reason, message) => { if (reason === DisconnectReason.ProtocolMismatch) { console.error("Protocol mismatch:", message); } else { console.log("Wallet disconnected:", reason, message); } relay.cleanup(); }); ``` The `disconnect` event fires in two cases: 1. **Remote disconnect**: the wallet sent a `disconnect` message (any reason). 2. **Protocol mismatch**: `handleWalletReady` found no overlap between the dapp's and wallet's `supported_protocols`. The dapp automatically sends a `ProtocolMismatch` disconnect to the wallet before emitting the event. ### Session path persistence After `wallet_ready`, the manager stores the raw `PathXpub[]` from the wallet. Use `getSessionPaths()` to retrieve them (e.g. for caching in localStorage). On a subsequent page load, restore them with `restoreSessionPaths()` so `getPubkey()` works immediately without waiting for the wallet to reconnect: ```typescript // After wallet_ready — save for later const paths = dappMgr.getSessionPaths(); localStorage.setItem("myapp-paths", JSON.stringify(paths)); // On page load — restore before wallet reconnects const cached = JSON.parse(localStorage.getItem("myapp-paths") ?? "null"); if (cached) { dappMgr.restoreSessionPaths(cached); // getPubkey() now works without wallet_ready } ``` Throws if any xpub string is invalid (corrupt cached data should be cleared). ### Reconnection `updateConnection()` is called on every relay status change. When `status.status === "connected"`, it calls `onConnected()` which waits for key exchange and then sends a fresh `dapp_ready`. The `walletDiscovered` flag carries over reconnects (it is only reset by creating a new manager), so the correct `wallet_discovered` value is sent on each reconnect. ## Using initiateDappRelay without DappConnectionManager If you need lower-level control (e.g., in the test-cli), you can work directly with the `RelayClient` and handle `wallet_ready` manually: ```typescript const relay = initiateDappRelay(statusCallback, options); relay.events.on("keyexchangecomplete", async (walletPubkey) => { // Key exchange done — wait for relay client to be fully ready while (!relay.client.isKeyExchangeComplete()) { await sleep(50); } relay.client.on("message", (msg) => { if (isDappReadyMessage(msg)) { /* ... */ } if (isWalletReadyMessage(msg)) { /* ... */ } }); await relay.client.relay({ action: RelayMsgAction.DappReady, ... }); }); ``` ## Cauldron (cauldron-beta) implementation notes Cauldron uses `DappConnectionManager` via a vendored adapter in `src/relay/RelayWalletDapp.ts`. This adapter wraps `DappConnectionManager` to implement Cauldron's internal `Wallet` interface. Key design points: - `DappConnectionManager` is created per connection session (not a singleton). - `updateConnection()` is called from the relay status callback. - `getXpubNode(childIndex)` and `getPubkey(childIndex, index)` are the primary access patterns. - Child indices are used internally (0/1/7); `childIndexOfPathName()` converts from PathName when processing `wallet_ready` data.