theseus/bundled-addons/aegis/lib/wc.js

268 lines
12 KiB
JavaScript
Raw Normal View History

feat(theseus/aegis): 0.6.1 — in-panel vault setup/unlock, BCH wallet imports, opt-in fiat prices, WizardConnect Aegis Wallet 0.4.4 → 0.6.1: - Vault lifecycle from the wallet gate. The locked / not-yet-created states now show a master-password form (with optional BIP39 mnemonic on setup) instead of redirecting users to Settings › Passwords. New api.vault.lifecycle {status, setup, unlock, lock} in addons-host, gated by the existing "vault-derive" capability. api.openSettings(section) also added; settings.html honours a #section hash on open. - Imported BCH wallets (design M.1a, read-only). Paste a mnemonic + BIP44 path or a WIF; the cashaddr is derived in the add-on, the signer material goes to a separate wallet-imports.enc via api.vault.imports {list, add, remove, signer}. Argus password-vault gains createImports / unlockImports / saveImports with its own KDF salt so the imports key is disjoint from the passwords key. lib/chain-bch-imported.js is a single-address Electrum adapter; spend support is deferred to M.1b. - Opt-in USD prices via CoinGecko (lib/prices.js), off by default, persisted in add-on storage. Fiat lines under balances, in the wallet picker, and a portfolio total when 2+ wallets are open. Settings tab is now reachable while the vault is locked so the toggle is always available. - WizardConnect wallet-side pairing for BCH wallets (lib/wc.js, lib/wc-sign.js). @wizardconnect/{core,wallet} are loaded dynamically via api.import to stay on the right side of LGPL §4d. Sign requests go through approvalModal and are restricted to P2PKH inputs with SIGHASH_ALL|FORKID|UTXOS. - DGB adapter load is now soft-fail: when Aegis runs from userData/addons the bundled ESM can't resolve peer deps, so DGB becomes unavailable instead of taking the whole add-on down.
2026-09-09 10:33:21 +02:00
// WizardConnect wallet-side bridge for Aegis.
//
// Aegis's BCH runtime acts as a WizardConnect wallet: sites we build (dapps)
// pair via a wiz:// URI, get xpubs for BCH derivation paths, and send us
// sign requests that we route through the existing approval-modal capability.
//
// LGPL boundary: @wizardconnect/{core,wallet} are dynamic-linked via
// api.import(); we do not statically embed them. Their sources live at
// https://github.com/whiterun-labs/wizardconnect (also on npm) and their
// LICENSE / copyright headers are shipped by npm inside the package.
//
// Docs: https://docs.riftenlabs.com/wizardconnect/
const WC_PATH_RECEIVE = "receive"; // m/44'/145'/0'/0
const WC_PATH_CHANGE = "change"; // m/44'/145'/0'/1
const WC_PATH_CAULDRON = "defi"; // m/44'/145'/0'/7 (BCH DEX ecosystem)
const WALLET_ICON = "data:image/svg+xml;utf8," + encodeURIComponent(
`<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' fill='none'>
<polygon points='16,2 28,9 28,23 16,30 4,23 4,9' fill='#0a0a0d' stroke='#D6FF3D' stroke-width='1.6' stroke-linejoin='round'/>
<circle cx='16' cy='16' r='4.5' fill='none' stroke='#D6FF3D' stroke-width='1.4'/>
<circle cx='16' cy='16' r='1.6' fill='#D6FF3D'/>
</svg>`
);
module.exports = function makeWc({ HDKey, secp256k1, sha256, hkdf, WalletConnectionManager, wcCore, libauth, log = () => {}, api, approvalRequest }) {
// ---- WalletAdapter --------------------------------------------------------
//
// Bound to one BCH runtime. Uses its 32-byte root to reproduce the account
// HDKey and to derive per-URI relay identities via HKDF, so reconnecting
// yields the same Nostr identity (dapp recognises us on reload).
function makeAdapter({ root32, accountPath, walletId, label }) {
// Own copy: the caller may wipe its buffer once this returns.
root32 = new Uint8Array(root32);
feat(theseus/aegis): 0.6.1 — in-panel vault setup/unlock, BCH wallet imports, opt-in fiat prices, WizardConnect Aegis Wallet 0.4.4 → 0.6.1: - Vault lifecycle from the wallet gate. The locked / not-yet-created states now show a master-password form (with optional BIP39 mnemonic on setup) instead of redirecting users to Settings › Passwords. New api.vault.lifecycle {status, setup, unlock, lock} in addons-host, gated by the existing "vault-derive" capability. api.openSettings(section) also added; settings.html honours a #section hash on open. - Imported BCH wallets (design M.1a, read-only). Paste a mnemonic + BIP44 path or a WIF; the cashaddr is derived in the add-on, the signer material goes to a separate wallet-imports.enc via api.vault.imports {list, add, remove, signer}. Argus password-vault gains createImports / unlockImports / saveImports with its own KDF salt so the imports key is disjoint from the passwords key. lib/chain-bch-imported.js is a single-address Electrum adapter; spend support is deferred to M.1b. - Opt-in USD prices via CoinGecko (lib/prices.js), off by default, persisted in add-on storage. Fiat lines under balances, in the wallet picker, and a portfolio total when 2+ wallets are open. Settings tab is now reachable while the vault is locked so the toggle is always available. - WizardConnect wallet-side pairing for BCH wallets (lib/wc.js, lib/wc-sign.js). @wizardconnect/{core,wallet} are loaded dynamically via api.import to stay on the right side of LGPL §4d. Sign requests go through approvalModal and are restricted to P2PKH inputs with SIGHASH_ALL|FORKID|UTXOS. - DGB adapter load is now soft-fail: when Aegis runs from userData/addons the bundled ESM can't resolve peer deps, so DGB becomes unavailable instead of taking the whole add-on down.
2026-09-09 10:33:21 +02:00
const account = HDKey.fromMasterSeed(root32).derive(accountPath);
const branches = new Map();
const branchFor = (childIndex) => {
let b = branches.get(childIndex);
if (!b) { b = account.deriveChild(childIndex); branches.set(childIndex, b); }
return b;
};
// WC path enum → BCH child index (identity mapping today; enum members
// hold the numeric child index directly — see docs/protocol.
const childOf = (path) => Number(path);
return {
walletName: label ? `Aegis · ${label}` : "Aegis",
walletIcon: WALLET_ICON,
// Stable identity per pairing URI. HKDF salt binds it to this wallet's
// root, info binds it to the URI, so:
// - reconnecting to the same URI = same Nostr identity
// - two different URIs = uncorrelatable identities (privacy)
getRelayPrivateKey(uri) {
const salt = new TextEncoder().encode("aegis/wc/relay/v1");
const info = new TextEncoder().encode(uri);
// 32 bytes for a Nostr secp256k1 private key.
return hkdf(sha256, root32, salt, info, 32);
},
getPublicKey(path, index) {
const branch = branchFor(childOf(path));
const node = branch.deriveChild(Number(index));
return node.publicKey; // 33 bytes compressed
},
getXpub(path) {
return branchFor(childOf(path)).publicExtendedKey;
},
// Sign a transaction the dapp has already assembled. See signTx.js for
// the heavy lifting (SIGHASH_ALL|FORKID|UTXOS enforcement, libauth
// preimage + secp256k1 der/lowS signatures).
// `pairing` is what Aegis itself recorded when this connection was
// made ({ origin } — the page origin the host verified, or "panel"),
// never what the dapp says about itself: the dapp's userPrompt and
// name are free text it controls.
async signTransaction(request, pairing = null) {
feat(theseus/aegis): 0.6.1 — in-panel vault setup/unlock, BCH wallet imports, opt-in fiat prices, WizardConnect Aegis Wallet 0.4.4 → 0.6.1: - Vault lifecycle from the wallet gate. The locked / not-yet-created states now show a master-password form (with optional BIP39 mnemonic on setup) instead of redirecting users to Settings › Passwords. New api.vault.lifecycle {status, setup, unlock, lock} in addons-host, gated by the existing "vault-derive" capability. api.openSettings(section) also added; settings.html honours a #section hash on open. - Imported BCH wallets (design M.1a, read-only). Paste a mnemonic + BIP44 path or a WIF; the cashaddr is derived in the add-on, the signer material goes to a separate wallet-imports.enc via api.vault.imports {list, add, remove, signer}. Argus password-vault gains createImports / unlockImports / saveImports with its own KDF salt so the imports key is disjoint from the passwords key. lib/chain-bch-imported.js is a single-address Electrum adapter; spend support is deferred to M.1b. - Opt-in USD prices via CoinGecko (lib/prices.js), off by default, persisted in add-on storage. Fiat lines under balances, in the wallet picker, and a portfolio total when 2+ wallets are open. Settings tab is now reachable while the vault is locked so the toggle is always available. - WizardConnect wallet-side pairing for BCH wallets (lib/wc.js, lib/wc-sign.js). @wizardconnect/{core,wallet} are loaded dynamically via api.import to stay on the right side of LGPL §4d. Sign requests go through approvalModal and are restricted to P2PKH inputs with SIGHASH_ALL|FORKID|UTXOS. - DGB adapter load is now soft-fail: when Aegis runs from userData/addons the bundled ESM can't resolve peer deps, so DGB becomes unavailable instead of taking the whole add-on down.
2026-09-09 10:33:21 +02:00
// Route through approval-modal first — the user always sees what
// they're signing before any private key touches the request.
if (!approvalRequest) throw new Error("no approval channel");
const decision = await approvalRequest({
kind: "wc-sign",
walletId, label,
request,
pairing,
feat(theseus/aegis): 0.6.1 — in-panel vault setup/unlock, BCH wallet imports, opt-in fiat prices, WizardConnect Aegis Wallet 0.4.4 → 0.6.1: - Vault lifecycle from the wallet gate. The locked / not-yet-created states now show a master-password form (with optional BIP39 mnemonic on setup) instead of redirecting users to Settings › Passwords. New api.vault.lifecycle {status, setup, unlock, lock} in addons-host, gated by the existing "vault-derive" capability. api.openSettings(section) also added; settings.html honours a #section hash on open. - Imported BCH wallets (design M.1a, read-only). Paste a mnemonic + BIP44 path or a WIF; the cashaddr is derived in the add-on, the signer material goes to a separate wallet-imports.enc via api.vault.imports {list, add, remove, signer}. Argus password-vault gains createImports / unlockImports / saveImports with its own KDF salt so the imports key is disjoint from the passwords key. lib/chain-bch-imported.js is a single-address Electrum adapter; spend support is deferred to M.1b. - Opt-in USD prices via CoinGecko (lib/prices.js), off by default, persisted in add-on storage. Fiat lines under balances, in the wallet picker, and a portfolio total when 2+ wallets are open. Settings tab is now reachable while the vault is locked so the toggle is always available. - WizardConnect wallet-side pairing for BCH wallets (lib/wc.js, lib/wc-sign.js). @wizardconnect/{core,wallet} are loaded dynamically via api.import to stay on the right side of LGPL §4d. Sign requests go through approvalModal and are restricted to P2PKH inputs with SIGHASH_ALL|FORKID|UTXOS. - DGB adapter load is now soft-fail: when Aegis runs from userData/addons the bundled ESM can't resolve peer deps, so DGB becomes unavailable instead of taking the whole add-on down.
2026-09-09 10:33:21 +02:00
});
if (!decision?.approved) throw new Error("cancelled");
const { signTx } = require("./wc-sign.js");
return signTx({
request,
account,
branches: { receive: branchFor(0), change: branchFor(1), defi: branchFor(7) },
libauth, secp256k1,
});
},
};
}
// ---- connection tracker --------------------------------------------------
// One manager per BCH wallet. We keep them in a per-walletId map so the
// panel can show "Wallet A connected to 2 dapps, Wallet B to none" etc.
const managers = new Map(); // walletId -> WalletConnectionManager
const uris = new Map(); // walletId -> Set<uri> (persisted)
const origins = new Map(); // walletId -> Map<uri, { origin, at }> (persisted)
const busy = new Set(); // connection ids with a sign request on screen
feat(theseus/aegis): 0.6.1 — in-panel vault setup/unlock, BCH wallet imports, opt-in fiat prices, WizardConnect Aegis Wallet 0.4.4 → 0.6.1: - Vault lifecycle from the wallet gate. The locked / not-yet-created states now show a master-password form (with optional BIP39 mnemonic on setup) instead of redirecting users to Settings › Passwords. New api.vault.lifecycle {status, setup, unlock, lock} in addons-host, gated by the existing "vault-derive" capability. api.openSettings(section) also added; settings.html honours a #section hash on open. - Imported BCH wallets (design M.1a, read-only). Paste a mnemonic + BIP44 path or a WIF; the cashaddr is derived in the add-on, the signer material goes to a separate wallet-imports.enc via api.vault.imports {list, add, remove, signer}. Argus password-vault gains createImports / unlockImports / saveImports with its own KDF salt so the imports key is disjoint from the passwords key. lib/chain-bch-imported.js is a single-address Electrum adapter; spend support is deferred to M.1b. - Opt-in USD prices via CoinGecko (lib/prices.js), off by default, persisted in add-on storage. Fiat lines under balances, in the wallet picker, and a portfolio total when 2+ wallets are open. Settings tab is now reachable while the vault is locked so the toggle is always available. - WizardConnect wallet-side pairing for BCH wallets (lib/wc.js, lib/wc-sign.js). @wizardconnect/{core,wallet} are loaded dynamically via api.import to stay on the right side of LGPL §4d. Sign requests go through approvalModal and are restricted to P2PKH inputs with SIGHASH_ALL|FORKID|UTXOS. - DGB adapter load is now soft-fail: when Aegis runs from userData/addons the bundled ESM can't resolve peer deps, so DGB becomes unavailable instead of taking the whole add-on down.
2026-09-09 10:33:21 +02:00
const listeners = new Set(); // () => void — panel resubscribes on state change
function fireStateChange() { for (const fn of listeners) try { fn(); } catch {} }
function persist(walletId) {
const list = [...(uris.get(walletId) || new Set())];
api.storage.set(`wc/${walletId}/uris`, list);
const o = origins.get(walletId) || new Map();
for (const u of [...o.keys()]) if (!list.includes(u)) o.delete(u);
api.storage.set(`wc/${walletId}/origins`, Object.fromEntries(o));
}
function uriOf(mgr, connectionId) {
const all = typeof mgr.getConnections === "function" ? Object.values(mgr.getConnections() || {}) : [...(mgr.connections?.values?.() || [])];
return all.find((c) => c.id === connectionId)?.uri || null;
feat(theseus/aegis): 0.6.1 — in-panel vault setup/unlock, BCH wallet imports, opt-in fiat prices, WizardConnect Aegis Wallet 0.4.4 → 0.6.1: - Vault lifecycle from the wallet gate. The locked / not-yet-created states now show a master-password form (with optional BIP39 mnemonic on setup) instead of redirecting users to Settings › Passwords. New api.vault.lifecycle {status, setup, unlock, lock} in addons-host, gated by the existing "vault-derive" capability. api.openSettings(section) also added; settings.html honours a #section hash on open. - Imported BCH wallets (design M.1a, read-only). Paste a mnemonic + BIP44 path or a WIF; the cashaddr is derived in the add-on, the signer material goes to a separate wallet-imports.enc via api.vault.imports {list, add, remove, signer}. Argus password-vault gains createImports / unlockImports / saveImports with its own KDF salt so the imports key is disjoint from the passwords key. lib/chain-bch-imported.js is a single-address Electrum adapter; spend support is deferred to M.1b. - Opt-in USD prices via CoinGecko (lib/prices.js), off by default, persisted in add-on storage. Fiat lines under balances, in the wallet picker, and a portfolio total when 2+ wallets are open. Settings tab is now reachable while the vault is locked so the toggle is always available. - WizardConnect wallet-side pairing for BCH wallets (lib/wc.js, lib/wc-sign.js). @wizardconnect/{core,wallet} are loaded dynamically via api.import to stay on the right side of LGPL §4d. Sign requests go through approvalModal and are restricted to P2PKH inputs with SIGHASH_ALL|FORKID|UTXOS. - DGB adapter load is now soft-fail: when Aegis runs from userData/addons the bundled ESM can't resolve peer deps, so DGB becomes unavailable instead of taking the whole add-on down.
2026-09-09 10:33:21 +02:00
}
async function startForWallet({ walletId, label, root32, accountPath }) {
if (managers.has(walletId)) return managers.get(walletId);
const adapter = makeAdapter({ root32, accountPath, walletId, label });
const mgr = new WalletConnectionManager(adapter);
managers.set(walletId, mgr);
mgr.on("connectionsChanged", fireStateChange);
mgr.on("connectionStatusChanged", fireStateChange);
mgr.on("remoteDisconnect", (connId, reason) => {
log(`wc[${walletId}] remote disconnect ${connId}: ${reason}`);
fireStateChange();
});
mgr.on("pendingSignRequest", async ({ connectionId, request }) => {
// One request per connection on screen at a time: a paired dapp can
// send over the relay whenever it likes, with no tab open, and must not
// be able to stack overlays.
if (busy.has(connectionId)) {
try { await mgr.sendSignError(connectionId, request?.sequence, "another request from this dapp is waiting for the user"); } catch {}
return;
}
busy.add(connectionId);
feat(theseus/aegis): 0.6.1 — in-panel vault setup/unlock, BCH wallet imports, opt-in fiat prices, WizardConnect Aegis Wallet 0.4.4 → 0.6.1: - Vault lifecycle from the wallet gate. The locked / not-yet-created states now show a master-password form (with optional BIP39 mnemonic on setup) instead of redirecting users to Settings › Passwords. New api.vault.lifecycle {status, setup, unlock, lock} in addons-host, gated by the existing "vault-derive" capability. api.openSettings(section) also added; settings.html honours a #section hash on open. - Imported BCH wallets (design M.1a, read-only). Paste a mnemonic + BIP44 path or a WIF; the cashaddr is derived in the add-on, the signer material goes to a separate wallet-imports.enc via api.vault.imports {list, add, remove, signer}. Argus password-vault gains createImports / unlockImports / saveImports with its own KDF salt so the imports key is disjoint from the passwords key. lib/chain-bch-imported.js is a single-address Electrum adapter; spend support is deferred to M.1b. - Opt-in USD prices via CoinGecko (lib/prices.js), off by default, persisted in add-on storage. Fiat lines under balances, in the wallet picker, and a portfolio total when 2+ wallets are open. Settings tab is now reachable while the vault is locked so the toggle is always available. - WizardConnect wallet-side pairing for BCH wallets (lib/wc.js, lib/wc-sign.js). @wizardconnect/{core,wallet} are loaded dynamically via api.import to stay on the right side of LGPL §4d. Sign requests go through approvalModal and are restricted to P2PKH inputs with SIGHASH_ALL|FORKID|UTXOS. - DGB adapter load is now soft-fail: when Aegis runs from userData/addons the bundled ESM can't resolve peer deps, so DGB becomes unavailable instead of taking the whole add-on down.
2026-09-09 10:33:21 +02:00
try {
const uri = uriOf(mgr, connectionId);
const pairing = (uri && origins.get(walletId)?.get(uri)) || null;
const { signedTransaction } = await adapter.signTransaction(request, pairing);
feat(theseus/aegis): 0.6.1 — in-panel vault setup/unlock, BCH wallet imports, opt-in fiat prices, WizardConnect Aegis Wallet 0.4.4 → 0.6.1: - Vault lifecycle from the wallet gate. The locked / not-yet-created states now show a master-password form (with optional BIP39 mnemonic on setup) instead of redirecting users to Settings › Passwords. New api.vault.lifecycle {status, setup, unlock, lock} in addons-host, gated by the existing "vault-derive" capability. api.openSettings(section) also added; settings.html honours a #section hash on open. - Imported BCH wallets (design M.1a, read-only). Paste a mnemonic + BIP44 path or a WIF; the cashaddr is derived in the add-on, the signer material goes to a separate wallet-imports.enc via api.vault.imports {list, add, remove, signer}. Argus password-vault gains createImports / unlockImports / saveImports with its own KDF salt so the imports key is disjoint from the passwords key. lib/chain-bch-imported.js is a single-address Electrum adapter; spend support is deferred to M.1b. - Opt-in USD prices via CoinGecko (lib/prices.js), off by default, persisted in add-on storage. Fiat lines under balances, in the wallet picker, and a portfolio total when 2+ wallets are open. Settings tab is now reachable while the vault is locked so the toggle is always available. - WizardConnect wallet-side pairing for BCH wallets (lib/wc.js, lib/wc-sign.js). @wizardconnect/{core,wallet} are loaded dynamically via api.import to stay on the right side of LGPL §4d. Sign requests go through approvalModal and are restricted to P2PKH inputs with SIGHASH_ALL|FORKID|UTXOS. - DGB adapter load is now soft-fail: when Aegis runs from userData/addons the bundled ESM can't resolve peer deps, so DGB becomes unavailable instead of taking the whole add-on down.
2026-09-09 10:33:21 +02:00
await mgr.sendSignResponse(connectionId, request.sequence, signedTransaction);
} catch (e) {
log(`wc[${walletId}] sign failed:`, e?.message || e);
try { await mgr.sendSignError(connectionId, request.sequence, cleanErrForDapp(e)); } catch {}
} finally { busy.delete(connectionId); }
feat(theseus/aegis): 0.6.1 — in-panel vault setup/unlock, BCH wallet imports, opt-in fiat prices, WizardConnect Aegis Wallet 0.4.4 → 0.6.1: - Vault lifecycle from the wallet gate. The locked / not-yet-created states now show a master-password form (with optional BIP39 mnemonic on setup) instead of redirecting users to Settings › Passwords. New api.vault.lifecycle {status, setup, unlock, lock} in addons-host, gated by the existing "vault-derive" capability. api.openSettings(section) also added; settings.html honours a #section hash on open. - Imported BCH wallets (design M.1a, read-only). Paste a mnemonic + BIP44 path or a WIF; the cashaddr is derived in the add-on, the signer material goes to a separate wallet-imports.enc via api.vault.imports {list, add, remove, signer}. Argus password-vault gains createImports / unlockImports / saveImports with its own KDF salt so the imports key is disjoint from the passwords key. lib/chain-bch-imported.js is a single-address Electrum adapter; spend support is deferred to M.1b. - Opt-in USD prices via CoinGecko (lib/prices.js), off by default, persisted in add-on storage. Fiat lines under balances, in the wallet picker, and a portfolio total when 2+ wallets are open. Settings tab is now reachable while the vault is locked so the toggle is always available. - WizardConnect wallet-side pairing for BCH wallets (lib/wc.js, lib/wc-sign.js). @wizardconnect/{core,wallet} are loaded dynamically via api.import to stay on the right side of LGPL §4d. Sign requests go through approvalModal and are restricted to P2PKH inputs with SIGHASH_ALL|FORKID|UTXOS. - DGB adapter load is now soft-fail: when Aegis runs from userData/addons the bundled ESM can't resolve peer deps, so DGB becomes unavailable instead of taking the whole add-on down.
2026-09-09 10:33:21 +02:00
});
// Restore persisted pairings.
uris.set(walletId, new Set(api.storage.get(`wc/${walletId}/uris`, []) || []));
const savedOrigins = api.storage.get(`wc/${walletId}/origins`, {}) || {};
origins.set(walletId, new Map(Object.entries(savedOrigins).filter(([, v]) => v && typeof v.origin === "string")));
feat(theseus/aegis): 0.6.1 — in-panel vault setup/unlock, BCH wallet imports, opt-in fiat prices, WizardConnect Aegis Wallet 0.4.4 → 0.6.1: - Vault lifecycle from the wallet gate. The locked / not-yet-created states now show a master-password form (with optional BIP39 mnemonic on setup) instead of redirecting users to Settings › Passwords. New api.vault.lifecycle {status, setup, unlock, lock} in addons-host, gated by the existing "vault-derive" capability. api.openSettings(section) also added; settings.html honours a #section hash on open. - Imported BCH wallets (design M.1a, read-only). Paste a mnemonic + BIP44 path or a WIF; the cashaddr is derived in the add-on, the signer material goes to a separate wallet-imports.enc via api.vault.imports {list, add, remove, signer}. Argus password-vault gains createImports / unlockImports / saveImports with its own KDF salt so the imports key is disjoint from the passwords key. lib/chain-bch-imported.js is a single-address Electrum adapter; spend support is deferred to M.1b. - Opt-in USD prices via CoinGecko (lib/prices.js), off by default, persisted in add-on storage. Fiat lines under balances, in the wallet picker, and a portfolio total when 2+ wallets are open. Settings tab is now reachable while the vault is locked so the toggle is always available. - WizardConnect wallet-side pairing for BCH wallets (lib/wc.js, lib/wc-sign.js). @wizardconnect/{core,wallet} are loaded dynamically via api.import to stay on the right side of LGPL §4d. Sign requests go through approvalModal and are restricted to P2PKH inputs with SIGHASH_ALL|FORKID|UTXOS. - DGB adapter load is now soft-fail: when Aegis runs from userData/addons the bundled ESM can't resolve peer deps, so DGB becomes unavailable instead of taking the whole add-on down.
2026-09-09 10:33:21 +02:00
for (const uri of uris.get(walletId)) {
try { mgr.connect(uri); } catch (e) { log(`wc[${walletId}] reconnect failed:`, e?.message); }
}
return mgr;
}
function stopForWallet(walletId) {
const mgr = managers.get(walletId); if (!mgr) return;
try { mgr.disconnectAll?.(); } catch {}
managers.delete(walletId);
uris.delete(walletId);
origins.delete(walletId);
feat(theseus/aegis): 0.6.1 — in-panel vault setup/unlock, BCH wallet imports, opt-in fiat prices, WizardConnect Aegis Wallet 0.4.4 → 0.6.1: - Vault lifecycle from the wallet gate. The locked / not-yet-created states now show a master-password form (with optional BIP39 mnemonic on setup) instead of redirecting users to Settings › Passwords. New api.vault.lifecycle {status, setup, unlock, lock} in addons-host, gated by the existing "vault-derive" capability. api.openSettings(section) also added; settings.html honours a #section hash on open. - Imported BCH wallets (design M.1a, read-only). Paste a mnemonic + BIP44 path or a WIF; the cashaddr is derived in the add-on, the signer material goes to a separate wallet-imports.enc via api.vault.imports {list, add, remove, signer}. Argus password-vault gains createImports / unlockImports / saveImports with its own KDF salt so the imports key is disjoint from the passwords key. lib/chain-bch-imported.js is a single-address Electrum adapter; spend support is deferred to M.1b. - Opt-in USD prices via CoinGecko (lib/prices.js), off by default, persisted in add-on storage. Fiat lines under balances, in the wallet picker, and a portfolio total when 2+ wallets are open. Settings tab is now reachable while the vault is locked so the toggle is always available. - WizardConnect wallet-side pairing for BCH wallets (lib/wc.js, lib/wc-sign.js). @wizardconnect/{core,wallet} are loaded dynamically via api.import to stay on the right side of LGPL §4d. Sign requests go through approvalModal and are restricted to P2PKH inputs with SIGHASH_ALL|FORKID|UTXOS. - DGB adapter load is now soft-fail: when Aegis runs from userData/addons the bundled ESM can't resolve peer deps, so DGB becomes unavailable instead of taking the whole add-on down.
2026-09-09 10:33:21 +02:00
}
// `origin`: the verified page origin that asked to pair, or "panel" when
// the user pasted the code into Aegis themselves.
async function connectUri(walletId, uri, origin = "panel") {
feat(theseus/aegis): 0.6.1 — in-panel vault setup/unlock, BCH wallet imports, opt-in fiat prices, WizardConnect Aegis Wallet 0.4.4 → 0.6.1: - Vault lifecycle from the wallet gate. The locked / not-yet-created states now show a master-password form (with optional BIP39 mnemonic on setup) instead of redirecting users to Settings › Passwords. New api.vault.lifecycle {status, setup, unlock, lock} in addons-host, gated by the existing "vault-derive" capability. api.openSettings(section) also added; settings.html honours a #section hash on open. - Imported BCH wallets (design M.1a, read-only). Paste a mnemonic + BIP44 path or a WIF; the cashaddr is derived in the add-on, the signer material goes to a separate wallet-imports.enc via api.vault.imports {list, add, remove, signer}. Argus password-vault gains createImports / unlockImports / saveImports with its own KDF salt so the imports key is disjoint from the passwords key. lib/chain-bch-imported.js is a single-address Electrum adapter; spend support is deferred to M.1b. - Opt-in USD prices via CoinGecko (lib/prices.js), off by default, persisted in add-on storage. Fiat lines under balances, in the wallet picker, and a portfolio total when 2+ wallets are open. Settings tab is now reachable while the vault is locked so the toggle is always available. - WizardConnect wallet-side pairing for BCH wallets (lib/wc.js, lib/wc-sign.js). @wizardconnect/{core,wallet} are loaded dynamically via api.import to stay on the right side of LGPL §4d. Sign requests go through approvalModal and are restricted to P2PKH inputs with SIGHASH_ALL|FORKID|UTXOS. - DGB adapter load is now soft-fail: when Aegis runs from userData/addons the bundled ESM can't resolve peer deps, so DGB becomes unavailable instead of taking the whole add-on down.
2026-09-09 10:33:21 +02:00
const mgr = managers.get(walletId);
if (!mgr) throw new Error("wc: wallet not ready");
const trimmed = String(uri || "").trim();
if (!/^wiz:\/\//i.test(trimmed)) throw new Error("wc: URI must start with wiz://");
const id = mgr.connect(trimmed);
const set = uris.get(walletId) || new Set();
set.add(trimmed);
uris.set(walletId, set);
const o = origins.get(walletId) || new Map();
o.set(trimmed, { origin: String(origin || "panel"), at: Date.now() });
origins.set(walletId, o);
feat(theseus/aegis): 0.6.1 — in-panel vault setup/unlock, BCH wallet imports, opt-in fiat prices, WizardConnect Aegis Wallet 0.4.4 → 0.6.1: - Vault lifecycle from the wallet gate. The locked / not-yet-created states now show a master-password form (with optional BIP39 mnemonic on setup) instead of redirecting users to Settings › Passwords. New api.vault.lifecycle {status, setup, unlock, lock} in addons-host, gated by the existing "vault-derive" capability. api.openSettings(section) also added; settings.html honours a #section hash on open. - Imported BCH wallets (design M.1a, read-only). Paste a mnemonic + BIP44 path or a WIF; the cashaddr is derived in the add-on, the signer material goes to a separate wallet-imports.enc via api.vault.imports {list, add, remove, signer}. Argus password-vault gains createImports / unlockImports / saveImports with its own KDF salt so the imports key is disjoint from the passwords key. lib/chain-bch-imported.js is a single-address Electrum adapter; spend support is deferred to M.1b. - Opt-in USD prices via CoinGecko (lib/prices.js), off by default, persisted in add-on storage. Fiat lines under balances, in the wallet picker, and a portfolio total when 2+ wallets are open. Settings tab is now reachable while the vault is locked so the toggle is always available. - WizardConnect wallet-side pairing for BCH wallets (lib/wc.js, lib/wc-sign.js). @wizardconnect/{core,wallet} are loaded dynamically via api.import to stay on the right side of LGPL §4d. Sign requests go through approvalModal and are restricted to P2PKH inputs with SIGHASH_ALL|FORKID|UTXOS. - DGB adapter load is now soft-fail: when Aegis runs from userData/addons the bundled ESM can't resolve peer deps, so DGB becomes unavailable instead of taking the whole add-on down.
2026-09-09 10:33:21 +02:00
persist(walletId);
fireStateChange();
return id;
}
async function disconnect(walletId, connId) {
const mgr = managers.get(walletId); if (!mgr) return;
try { await mgr.disconnect(connId); } catch {}
// Trim the persisted URI so the next start doesn't re-add it.
const conn = [...(mgr.connections?.values?.() || [])].find((c) => c.id === connId);
if (conn?.uri) {
const set = uris.get(walletId); if (set) { set.delete(conn.uri); persist(walletId); }
}
fireStateChange();
}
// Revoking a site in Aegis also ends the WizardConnect pairings it made.
async function disconnectOrigin(origin) {
let n = 0;
for (const [walletId, o] of origins) {
const mgr = managers.get(walletId);
for (const [uri, rec] of [...o]) {
if (rec.origin !== origin) continue;
const conn = mgr ? (typeof mgr.getConnections === "function" ? Object.values(mgr.getConnections() || {}) : []).find((c) => c.uri === uri) : null;
if (conn) { try { await mgr.disconnect(conn.id); } catch {} }
uris.get(walletId)?.delete(uri);
o.delete(uri);
persist(walletId);
n++;
}
}
if (n) fireStateChange();
return n;
}
feat(theseus/aegis): 0.6.1 — in-panel vault setup/unlock, BCH wallet imports, opt-in fiat prices, WizardConnect Aegis Wallet 0.4.4 → 0.6.1: - Vault lifecycle from the wallet gate. The locked / not-yet-created states now show a master-password form (with optional BIP39 mnemonic on setup) instead of redirecting users to Settings › Passwords. New api.vault.lifecycle {status, setup, unlock, lock} in addons-host, gated by the existing "vault-derive" capability. api.openSettings(section) also added; settings.html honours a #section hash on open. - Imported BCH wallets (design M.1a, read-only). Paste a mnemonic + BIP44 path or a WIF; the cashaddr is derived in the add-on, the signer material goes to a separate wallet-imports.enc via api.vault.imports {list, add, remove, signer}. Argus password-vault gains createImports / unlockImports / saveImports with its own KDF salt so the imports key is disjoint from the passwords key. lib/chain-bch-imported.js is a single-address Electrum adapter; spend support is deferred to M.1b. - Opt-in USD prices via CoinGecko (lib/prices.js), off by default, persisted in add-on storage. Fiat lines under balances, in the wallet picker, and a portfolio total when 2+ wallets are open. Settings tab is now reachable while the vault is locked so the toggle is always available. - WizardConnect wallet-side pairing for BCH wallets (lib/wc.js, lib/wc-sign.js). @wizardconnect/{core,wallet} are loaded dynamically via api.import to stay on the right side of LGPL §4d. Sign requests go through approvalModal and are restricted to P2PKH inputs with SIGHASH_ALL|FORKID|UTXOS. - DGB adapter load is now soft-fail: when Aegis runs from userData/addons the bundled ESM can't resolve peer deps, so DGB becomes unavailable instead of taking the whole add-on down.
2026-09-09 10:33:21 +02:00
function snapshot() {
const out = {};
for (const [walletId, mgr] of managers) {
fix(aegis): 0.9.5 — WizardConnect signing actually works Pairing already worked; signing would have thrown on the first request a dapp ever sent. Found by testing against the real relay and the real @wizardconnect/wallet library rather than reading the code. Two bugs in wc-sign.js, both fatal: - The WC message nests the whole WcSignTransactionRequest under `.transaction`, so the tx is at request.transaction.transaction and the spent outputs at request.transaction.sourceOutputs. We read request.transaction as the tx and request.sourceOutputs as the outputs, so tx.inputs was undefined. index.js already read the nested request.transaction.userPrompt for the approval dialog, so only the signer had it wrong. The flat shape is still accepted. - generateSigningSerializationBCH takes TWO positional arguments, (compilationContext, {coveredBytecode, signingSerializationType}). We passed one merged object, leaving coveredBytecode undefined and throwing inside libauth. For P2PKH the covered bytecode is the spent output's locking script. Now verified end to end: a two-input transaction spending from two different derivation paths signs, decodes, and passes createVirtualMachineBCH().verify() — consensus-valid, with SIGHASH_ALL|FORKID|UTXOS (0x61) on every input as the protocol requires. Also: RelayStatus is an object ({status: "connected" | "reconnecting" | "disconnected" | "session_deleted"}), and the snapshot read a non-existent `.kind`, so every connection reported the literal "[object Object]". Reads `.status` now, uses the documented getConnections() accessor instead of the private connections Map, and carries the library's own `label` ("dapp name once known, otherwise Connecting…"). The panel shows a tag for anything other than connected — "reconnecting" is the difference between a pairing that will see the next signature and one that is dead, which was invisible before.
2026-09-23 03:23:41 +02:00
// getConnections() is the documented accessor and returns a plain
// {id: RelayConnectionState} record. We used to walk mgr.connections
// (a private Map) directly, which works but is one library refactor
// away from silently returning nothing.
const states = typeof mgr.getConnections === "function"
? Object.values(mgr.getConnections() || {})
: [...(mgr.connections?.values?.() || [])];
const list = states.map((c) => ({
feat(theseus/aegis): 0.6.1 — in-panel vault setup/unlock, BCH wallet imports, opt-in fiat prices, WizardConnect Aegis Wallet 0.4.4 → 0.6.1: - Vault lifecycle from the wallet gate. The locked / not-yet-created states now show a master-password form (with optional BIP39 mnemonic on setup) instead of redirecting users to Settings › Passwords. New api.vault.lifecycle {status, setup, unlock, lock} in addons-host, gated by the existing "vault-derive" capability. api.openSettings(section) also added; settings.html honours a #section hash on open. - Imported BCH wallets (design M.1a, read-only). Paste a mnemonic + BIP44 path or a WIF; the cashaddr is derived in the add-on, the signer material goes to a separate wallet-imports.enc via api.vault.imports {list, add, remove, signer}. Argus password-vault gains createImports / unlockImports / saveImports with its own KDF salt so the imports key is disjoint from the passwords key. lib/chain-bch-imported.js is a single-address Electrum adapter; spend support is deferred to M.1b. - Opt-in USD prices via CoinGecko (lib/prices.js), off by default, persisted in add-on storage. Fiat lines under balances, in the wallet picker, and a portfolio total when 2+ wallets are open. Settings tab is now reachable while the vault is locked so the toggle is always available. - WizardConnect wallet-side pairing for BCH wallets (lib/wc.js, lib/wc-sign.js). @wizardconnect/{core,wallet} are loaded dynamically via api.import to stay on the right side of LGPL §4d. Sign requests go through approvalModal and are restricted to P2PKH inputs with SIGHASH_ALL|FORKID|UTXOS. - DGB adapter load is now soft-fail: when Aegis runs from userData/addons the bundled ESM can't resolve peer deps, so DGB becomes unavailable instead of taking the whole add-on down.
2026-09-09 10:33:21 +02:00
id: c.id,
uri: c.uri,
fix(aegis): 0.9.5 — WizardConnect signing actually works Pairing already worked; signing would have thrown on the first request a dapp ever sent. Found by testing against the real relay and the real @wizardconnect/wallet library rather than reading the code. Two bugs in wc-sign.js, both fatal: - The WC message nests the whole WcSignTransactionRequest under `.transaction`, so the tx is at request.transaction.transaction and the spent outputs at request.transaction.sourceOutputs. We read request.transaction as the tx and request.sourceOutputs as the outputs, so tx.inputs was undefined. index.js already read the nested request.transaction.userPrompt for the approval dialog, so only the signer had it wrong. The flat shape is still accepted. - generateSigningSerializationBCH takes TWO positional arguments, (compilationContext, {coveredBytecode, signingSerializationType}). We passed one merged object, leaving coveredBytecode undefined and throwing inside libauth. For P2PKH the covered bytecode is the spent output's locking script. Now verified end to end: a two-input transaction spending from two different derivation paths signs, decodes, and passes createVirtualMachineBCH().verify() — consensus-valid, with SIGHASH_ALL|FORKID|UTXOS (0x61) on every input as the protocol requires. Also: RelayStatus is an object ({status: "connected" | "reconnecting" | "disconnected" | "session_deleted"}), and the snapshot read a non-existent `.kind`, so every connection reported the literal "[object Object]". Reads `.status` now, uses the documented getConnections() accessor instead of the private connections Map, and carries the library's own `label` ("dapp name once known, otherwise Connecting…"). The panel shows a tag for anything other than connected — "reconnecting" is the difference between a pairing that will see the next signature and one that is dead, which was invisible before.
2026-09-23 03:23:41 +02:00
// `label` is the library's own "dapp name once known, otherwise
// Connecting…", so it's the right thing to show while a pairing
// is still settling.
label: c.label || null,
feat(theseus/aegis): 0.6.1 — in-panel vault setup/unlock, BCH wallet imports, opt-in fiat prices, WizardConnect Aegis Wallet 0.4.4 → 0.6.1: - Vault lifecycle from the wallet gate. The locked / not-yet-created states now show a master-password form (with optional BIP39 mnemonic on setup) instead of redirecting users to Settings › Passwords. New api.vault.lifecycle {status, setup, unlock, lock} in addons-host, gated by the existing "vault-derive" capability. api.openSettings(section) also added; settings.html honours a #section hash on open. - Imported BCH wallets (design M.1a, read-only). Paste a mnemonic + BIP44 path or a WIF; the cashaddr is derived in the add-on, the signer material goes to a separate wallet-imports.enc via api.vault.imports {list, add, remove, signer}. Argus password-vault gains createImports / unlockImports / saveImports with its own KDF salt so the imports key is disjoint from the passwords key. lib/chain-bch-imported.js is a single-address Electrum adapter; spend support is deferred to M.1b. - Opt-in USD prices via CoinGecko (lib/prices.js), off by default, persisted in add-on storage. Fiat lines under balances, in the wallet picker, and a portfolio total when 2+ wallets are open. Settings tab is now reachable while the vault is locked so the toggle is always available. - WizardConnect wallet-side pairing for BCH wallets (lib/wc.js, lib/wc-sign.js). @wizardconnect/{core,wallet} are loaded dynamically via api.import to stay on the right side of LGPL §4d. Sign requests go through approvalModal and are restricted to P2PKH inputs with SIGHASH_ALL|FORKID|UTXOS. - DGB adapter load is now soft-fail: when Aegis runs from userData/addons the bundled ESM can't resolve peer deps, so DGB becomes unavailable instead of taking the whole add-on down.
2026-09-09 10:33:21 +02:00
dappName: c.dappName || null,
dappIcon: c.dappIcon || null,
pairedFrom: origins.get(walletId)?.get(c.uri)?.origin || null,
fix(aegis): 0.9.5 — WizardConnect signing actually works Pairing already worked; signing would have thrown on the first request a dapp ever sent. Found by testing against the real relay and the real @wizardconnect/wallet library rather than reading the code. Two bugs in wc-sign.js, both fatal: - The WC message nests the whole WcSignTransactionRequest under `.transaction`, so the tx is at request.transaction.transaction and the spent outputs at request.transaction.sourceOutputs. We read request.transaction as the tx and request.sourceOutputs as the outputs, so tx.inputs was undefined. index.js already read the nested request.transaction.userPrompt for the approval dialog, so only the signer had it wrong. The flat shape is still accepted. - generateSigningSerializationBCH takes TWO positional arguments, (compilationContext, {coveredBytecode, signingSerializationType}). We passed one merged object, leaving coveredBytecode undefined and throwing inside libauth. For P2PKH the covered bytecode is the spent output's locking script. Now verified end to end: a two-input transaction spending from two different derivation paths signs, decodes, and passes createVirtualMachineBCH().verify() — consensus-valid, with SIGHASH_ALL|FORKID|UTXOS (0x61) on every input as the protocol requires. Also: RelayStatus is an object ({status: "connected" | "reconnecting" | "disconnected" | "session_deleted"}), and the snapshot read a non-existent `.kind`, so every connection reported the literal "[object Object]". Reads `.status` now, uses the documented getConnections() accessor instead of the private connections Map, and carries the library's own `label` ("dapp name once known, otherwise Connecting…"). The panel shows a tag for anything other than connected — "reconnecting" is the difference between a pairing that will see the next signature and one that is dead, which was invisible before.
2026-09-23 03:23:41 +02:00
// RelayStatus is an OBJECT: { status: "connected" | "reconnecting"
// | "disconnected" | "session_deleted" }. Reading `.kind` (which
// does not exist) fell through to String(object) and put the
// literal "[object Object]" in the panel's status field.
status: typeof c.status === "string"
? c.status
: (c.status?.status || "unknown"),
feat(theseus/aegis): 0.6.1 — in-panel vault setup/unlock, BCH wallet imports, opt-in fiat prices, WizardConnect Aegis Wallet 0.4.4 → 0.6.1: - Vault lifecycle from the wallet gate. The locked / not-yet-created states now show a master-password form (with optional BIP39 mnemonic on setup) instead of redirecting users to Settings › Passwords. New api.vault.lifecycle {status, setup, unlock, lock} in addons-host, gated by the existing "vault-derive" capability. api.openSettings(section) also added; settings.html honours a #section hash on open. - Imported BCH wallets (design M.1a, read-only). Paste a mnemonic + BIP44 path or a WIF; the cashaddr is derived in the add-on, the signer material goes to a separate wallet-imports.enc via api.vault.imports {list, add, remove, signer}. Argus password-vault gains createImports / unlockImports / saveImports with its own KDF salt so the imports key is disjoint from the passwords key. lib/chain-bch-imported.js is a single-address Electrum adapter; spend support is deferred to M.1b. - Opt-in USD prices via CoinGecko (lib/prices.js), off by default, persisted in add-on storage. Fiat lines under balances, in the wallet picker, and a portfolio total when 2+ wallets are open. Settings tab is now reachable while the vault is locked so the toggle is always available. - WizardConnect wallet-side pairing for BCH wallets (lib/wc.js, lib/wc-sign.js). @wizardconnect/{core,wallet} are loaded dynamically via api.import to stay on the right side of LGPL §4d. Sign requests go through approvalModal and are restricted to P2PKH inputs with SIGHASH_ALL|FORKID|UTXOS. - DGB adapter load is now soft-fail: when Aegis runs from userData/addons the bundled ESM can't resolve peer deps, so DGB becomes unavailable instead of taking the whole add-on down.
2026-09-09 10:33:21 +02:00
connectedAt: c.connectedAt || null,
}));
out[walletId] = list;
}
return out;
}
function onStateChange(fn) { listeners.add(fn); return () => listeners.delete(fn); }
function cleanErrForDapp(e) {
const m = String(e?.message || e);
if (m === "cancelled") return "user rejected";
return m.replace(/\n[\s\S]*$/, "").slice(0, 200);
}
return { startForWallet, stopForWallet, connectUri, disconnect, disconnectOrigin, snapshot, onStateChange };
feat(theseus/aegis): 0.6.1 — in-panel vault setup/unlock, BCH wallet imports, opt-in fiat prices, WizardConnect Aegis Wallet 0.4.4 → 0.6.1: - Vault lifecycle from the wallet gate. The locked / not-yet-created states now show a master-password form (with optional BIP39 mnemonic on setup) instead of redirecting users to Settings › Passwords. New api.vault.lifecycle {status, setup, unlock, lock} in addons-host, gated by the existing "vault-derive" capability. api.openSettings(section) also added; settings.html honours a #section hash on open. - Imported BCH wallets (design M.1a, read-only). Paste a mnemonic + BIP44 path or a WIF; the cashaddr is derived in the add-on, the signer material goes to a separate wallet-imports.enc via api.vault.imports {list, add, remove, signer}. Argus password-vault gains createImports / unlockImports / saveImports with its own KDF salt so the imports key is disjoint from the passwords key. lib/chain-bch-imported.js is a single-address Electrum adapter; spend support is deferred to M.1b. - Opt-in USD prices via CoinGecko (lib/prices.js), off by default, persisted in add-on storage. Fiat lines under balances, in the wallet picker, and a portfolio total when 2+ wallets are open. Settings tab is now reachable while the vault is locked so the toggle is always available. - WizardConnect wallet-side pairing for BCH wallets (lib/wc.js, lib/wc-sign.js). @wizardconnect/{core,wallet} are loaded dynamically via api.import to stay on the right side of LGPL §4d. Sign requests go through approvalModal and are restricted to P2PKH inputs with SIGHASH_ALL|FORKID|UTXOS. - DGB adapter load is now soft-fail: when Aegis runs from userData/addons the bundled ESM can't resolve peer deps, so DGB becomes unavailable instead of taking the whole add-on down.
2026-09-09 10:33:21 +02:00
};