WizardConnect/docs/extensions.md
Dagur Valberg Johannsson 4d1ba8e207
Add 'extensions' to hdwalletv1 protocol
This backward compatible change allows wallets/dapps to add additional
features outside the basic transaction signature support to the
hdwalletv1 protocol.
2026-03-26 10:50:40 +01:00

170 lines
5 KiB
Markdown

# Extensions
The hdwalletv1 protocol supports optional extensions that let wallets and dapps negotiate
additional capabilities beyond the core sign-transaction flow. Extensions are backward-compatible:
existing wallets and dapps that don't know about extensions continue to work unchanged.
## How extensions work
Extensions use three mechanisms, all of which are optional and additive:
### 1. Session extensions field
Wallets advertise supported extensions in the `wallet_ready` handshake via an `extensions` field
on the `Hdwalletv1Session` object:
```typescript
interface Hdwalletv1Session {
paths: PathXpub[];
extensions?: Record<string, unknown>;
}
```
Each key in `extensions` is an extension name. Its **presence** indicates the wallet supports that
extension. The value carries extension-specific handshake data, or `{}` if no data is needed.
Example:
```json
{
"paths": [
{ "name": "receive", "xpub": "xpub6..." },
{ "name": "change", "xpub": "xpub6..." },
{ "name": "defi", "xpub": "xpub6..." }
],
"extensions": {
"bip352": {},
"decrypt": { "public_key": "02abc...", "scheme": "ecies" }
}
}
```
### 2. Additional path names
`PathName` is an open string type. Wallets may include paths beyond the well-known
`receive`/`change`/`defi` set. Extension-defined paths are carried in the standard `paths` array:
```json
{
"paths": [
{ "name": "receive", "xpub": "xpub6..." },
{ "name": "change", "xpub": "xpub6..." },
{ "name": "stealth_scan", "xpub": "xpub6..." },
{ "name": "stealth_spend", "xpub": "xpub6..." }
],
"extensions": {
"bip352": {}
}
}
```
Dapps should ignore path names they do not recognize. The standard pubkey derivation logic
(`DappPubkeyStateManager`) automatically skips unknown paths.
### 3. Custom message actions
Extensions may define new message action strings beyond the well-known set. Custom messages follow
the standard `ProtocolMessage` shape (`action` + `time`) and use the existing relay transport.
Convention for request/response operations:
```typescript
// Request (dapp → wallet):
{ action: "<operation>_request", sequence: number, time: number, ...params }
// Response (wallet → dapp):
{ action: "<operation>_response", sequence: number, time: number, ...result }
```
The `sequence` field ties responses to requests, matching the pattern used by
`sign_transaction_request`/`sign_transaction_response`.
**Wallet side**: custom messages are emitted via the `"message"` event on
`WalletConnectionManager`:
```typescript
manager.on("message", (connectionId, msg) => {
if (msg.action === "decrypt_request") {
// handle decrypt request
}
});
```
**Dapp side**: all messages (including custom ones) are emitted via the `"messagereceived"` event
on `DappConnectionManager`:
```typescript
manager.on("messagereceived", (msg) => {
if (msg.action === "decrypt_response") {
// handle decrypt response
}
});
```
## Implementing an extension (wallet side)
Wallets advertise extensions by implementing optional methods on `WalletAdapter`:
```typescript
interface WalletAdapter {
// ... core methods ...
/** Additional paths to include in the session (e.g. stealth_scan). */
getAdditionalPaths?(): PathXpub[];
/** Extension data for the session handshake. */
getExtensions?(): Record<string, unknown>;
}
```
Example:
```typescript
const adapter: WalletAdapter = {
// ... core implementation ...
getAdditionalPaths() {
return [
{ name: "stealth_scan", xpub: deriveScanXpub() },
{ name: "stealth_spend", xpub: deriveSpendXpub() },
];
},
getExtensions() {
return { "bip352": {} };
},
};
```
Wallets that don't implement these methods produce the same session as before (receive/change/defi
paths only, no extensions field).
## Discovering extensions (dapp side)
Dapps check for extension support after receiving `wallet_ready`:
```typescript
manager.on("walletready", (msg) => {
const session = msg.session["hdwalletv1"] as Hdwalletv1Session;
if (session.extensions?.bip352) {
const scanPath = session.paths.find(p => p.name === "stealth_scan");
// enable stealth address features
} else {
// show: "Stealth payments require a wallet that supports BIP352"
}
});
```
Dapps should degrade gracefully when an extension is absent. If a feature requires an extension the
wallet doesn't support, inform the user rather than failing silently.
## Known extensions
No extensions are standardized yet. The following are under discussion:
| Extension name | Purpose | Status |
|---------------|---------|--------|
| `bip352` | BIP352 silent/stealth payments. Adds `stealth_scan` and `stealth_spend` paths. | Proposed |
| `bip47_rpa` | BIP47 reusable payment addresses. Adds `rpa` path and payment code data. | Proposed |
| `decrypt` | Dapp-side encrypted storage. Wallet provides a public key; dapp encrypts data for storage and sends `decrypt_request` messages when the data is needed. | Proposed |
See the discussions and specifications for each extension as they are formalized.