Wallet integration
Wallet integration uses @wizardconnect/wallet. The wallet implements the WalletAdapter
interface and hands it to WalletConnectionManager, which handles everything else.
WalletAdapter
interface WalletAdapter {
walletName: string; // shown in the dapp's connection UI
walletIcon: string; // URL or data-URI, shown in the dapp's connection UI
/**
* Return the relay identity private key for this session.
* May be ephemeral (random per session) or stable (HD-derived) — both work.
* The dapp learns the wallet's public key from the wallet_ready message,
* so stability across restarts is not required.
*/
getRelayPrivateKey(): Uint8Array;
/** Returns the compressed 33-byte secp256k1 public key at path/index. */
getPublicKey(path: DerivationPath, index: bigint): Uint8Array;
/** Returns the BIP32 base58-encoded xpub for the given derivation path.
* The dapp derives all addresses from this — no further pubkey requests needed. */
getXpub(path: DerivationPath): string;
/** Sign the transaction. May show approval UI to the user.
* Called when the wallet has received and validated a sign_transaction_request. */
signTransaction(request: SignTransactionRequest): Promise<SignTransactionResult>;
}
DerivationPath
enum DerivationPath {
Receive = 0, // m/44'/145'/0'/0 — external (receive) addresses
Change = 1, // m/44'/145'/0'/1 — internal (change) addresses
Cauldron = 7, // m/44'/145'/0'/7 — DeFi/Cauldron addresses
}
The numeric values are wallet-internal; the protocol uses names (receive, change, defi).
childIndexOfPath(path) and pathOfChildIndex(index) convert between them.
SignTransactionResult
interface SignTransactionResult {
signedTransactionHex: string;
}
WalletConnectionManager
class WalletConnectionManager extends EventEmitter {
constructor(adapter: WalletAdapter)
// Connect to a dapp. Returns a stable connection ID.
// If a connection for this URI already exists, returns the existing ID.
connect(uri: string): string
// Tear down a specific connection, sending a UserDisconnect courtesy message.
disconnect(connectionId: string): void
// Tear down all connections.
disconnectAll(): void
// Snapshot of all connections for UI rendering.
getConnections(): Record<string, RelayConnectionState>
// Send the signed transaction back to the dapp.
sendSignResponse(connectionId: string, sequence: number, signedTx: string): Promise<void>
// Send an error back to the dapp (user rejected, signing failed, etc.)
sendSignError(connectionId: string, sequence: number, errorMessage: string): Promise<void>
// Events
on("connectionStatusChanged", (id: string, status: RelayStatus) => void)
on("pendingSignRequest", (req: PendingSignRequest) => void)
on("connectionsChanged", () => void)
on("remoteDisconnect", (connectionId: string, reason: DisconnectReason, message: string | undefined) => void)
}
RelayConnectionState
interface RelayConnectionState {
id: string;
uri: string;
status: RelayStatus; // { status: "connected" | "reconnecting" | "disconnected" }
label: string; // dapp name once known, otherwise "Connecting..."
dappName: string | null;
dappIcon: string | null;
connectedAt: number; // Unix ms
}
PendingSignRequest
interface PendingSignRequest {
connectionId: string;
request: SignTransactionRequest;
}
Connection lifecycle
connect()
- A unique
connectionIdis generated. initiateWalletRelay(statusCallback, { uri, walletPrivateKey })is called.- The relay decodes the URI, extracts the dapp's public key and secret, and connects.
- On the first
"connected"status,onConnected()is called.
onConnected()
walletReadySentThisCycleis reset tofalse.- A notification processor interval is started (1 second, for retry on send errors).
- The wallet polls until
client.isKeyExchangeComplete()(key exchange with dapp done). pushWalletReady()is called.
pushWalletReady()
Sends wallet_ready with:
- supported_protocols: ["hdwalletv1"]
- wallet_name, wallet_icon from the adapter.
- session["hdwalletv1"]: one { name, xpub } per DerivationPath (receive/change/defi).
- dapp_discovered: whether the dapp was seen in this runtime session.
The message is pushed to a per-connection notificationQueue and flushed immediately. Retry
is handled by the interval processor — if relay() throws (e.g. network drop), the message
stays in the queue and is retried on the next tick.
Receiving dapp_ready
wallet_discovered=false → reset walletReadySentThisCycle, call pushWalletReady() again
wallet_discovered=true → set dappDiscovered=true, no further action
dapp_name and dapp_icon are captured from the first dapp_ready that includes them.
Receiving disconnect
When a disconnect message arrives from the dapp:
1. The remoteDisconnect event is emitted with (connectionId, reason, message).
2. The connection is cleaned up without sending a reply disconnect.
Receiving sign_transaction_request
The wallet emits pendingSignRequest with the connectionId and the full request. The host
application is responsible for:
- Queueing or displaying the request.
- Getting user approval.
- Calling
sendSignResponse(connectionId, sequence, signedTxHex)orsendSignError(connectionId, sequence, errorMessage).
The wallet library does not auto-sign or auto-reject anything.
Minimal example
import { WalletConnectionManager } from "@wizardconnect/wallet";
import type { WalletAdapter, DerivationPath } from "@wizardconnect/wallet";
class MyAdapter implements WalletAdapter {
walletName = "My Wallet";
walletIcon = "";
getRelayPrivateKey() { return crypto.getRandomValues(new Uint8Array(32)); }
getPublicKey(path: DerivationPath, index: bigint) { /* ... */ }
getXpub(path: DerivationPath) { /* ... */ }
async signTransaction(request) {
// show approval UI, sign, return hex
return { signedTransactionHex: "..." };
}
}
const manager = new WalletConnectionManager(new MyAdapter());
// When user scans a QR code:
const connId = manager.connect("wiz://?p=...&s=...");
// When a sign request arrives:
manager.on("pendingSignRequest", async ({ connectionId, request }) => {
try {
const result = await showApprovalUI(request);
await manager.sendSignResponse(connectionId, request.sequence, result.signedTransactionHex);
} catch {
await manager.sendSignError(connectionId, request.sequence, "User rejected");
}
});
// When the dapp disconnects:
manager.on("remoteDisconnect", (id, reason, message) => {
console.log(`Dapp disconnected: ${reason}`, message);
store.dispatch(setConnections(manager.getConnections()));
});
// For Redux / UI updates:
manager.on("connectionsChanged", () => {
store.dispatch(setConnections(manager.getConnections()));
});