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 }) {
|
|
|
|
|
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).
|
|
|
|
|
async signTransaction(request) {
|
|
|
|
|
// 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,
|
|
|
|
|
});
|
|
|
|
|
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 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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 }) => {
|
|
|
|
|
try {
|
|
|
|
|
const { signedTransaction } = await adapter.signTransaction(request);
|
|
|
|
|
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 {}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Restore persisted pairings.
|
|
|
|
|
uris.set(walletId, new Set(api.storage.get(`wc/${walletId}/uris`, []) || []));
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function connectUri(walletId, uri) {
|
|
|
|
|
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);
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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,
|
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, snapshot, onStateChange };
|
|
|
|
|
};
|