Aegis: WizardConnect relay keys from the real root, overlay names the pairing origin
mountWallet zeroed the vault root after mounting, but the WizardConnect adapter kept a reference to that same buffer and read it again for every new pairing's relay key. Every pairing made after mount therefore got a Nostr identity derived from 32 zero bytes and the pairing URI alone, so anyone who saw the URI (the QR, a script on the dapp page) could read the relay traffic, xpubs included, and speak as the wallet. The adapter now gets, and keeps, its own copy. The signing overlay and the PIN request named the dapp by its own userPrompt, so a dapp paired once could present itself as any site. The pairing origin the host verified is now recorded per URI and shown instead; the dapp's text is a quoted row with invisible and bidi characters removed. One sign request per connection may be on screen at a time, and revoking a site in Aegis ends its pairings too.
This commit is contained in:
parent
561cb122ea
commit
3264c6d019
3 changed files with 105 additions and 14 deletions
|
|
@ -855,7 +855,11 @@ async function mountWallet(entry) {
|
||||||
// dapp saw an unrelated, empty wallet; anything it built spent
|
// dapp saw an unrelated, empty wallet; anything it built spent
|
||||||
// from addresses this wallet does not own, so signing failed with
|
// from addresses this wallet does not own, so signing failed with
|
||||||
// "no path for input". Silent, and only visible on testnet.
|
// "no path for input". Silent, and only visible on testnet.
|
||||||
root32: root, accountPath: entry.accountPath || adapter.snapshot?.()?.accountPath || "m/44'/145'/0'",
|
// A copy: `root` is zeroed in the finally below, and the adapter
|
||||||
|
// reads its root again for every new pairing's relay key. Sharing
|
||||||
|
// the buffer gave every pairing made after mount a relay key derived
|
||||||
|
// from 32 zero bytes, i.e. from the pairing URI alone.
|
||||||
|
root32: new Uint8Array(root), accountPath: entry.accountPath || adapter.snapshot?.()?.accountPath || "m/44'/145'/0'",
|
||||||
}).catch((e) => c.api.log(`[${entry.id}] wc start:`, e?.message || e));
|
}).catch((e) => c.api.log(`[${entry.id}] wc start:`, e?.message || e));
|
||||||
}
|
}
|
||||||
emitStateForWallet(entry.id);
|
emitStateForWallet(entry.id);
|
||||||
|
|
@ -968,7 +972,16 @@ function deriveCashaddrFromWif(wif, prefix) {
|
||||||
function buildWcApproval(payload) {
|
function buildWcApproval(payload) {
|
||||||
const req = payload?.request || {};
|
const req = payload?.request || {};
|
||||||
const tx = req.transaction || {};
|
const tx = req.transaction || {};
|
||||||
const dappName = String(tx.userPrompt || payload?.dappName || "unknown dapp").slice(0, 120);
|
// Who is asking is what Aegis recorded when the pairing was made: the page
|
||||||
|
// origin the host verified, or the panel when the user pasted the code.
|
||||||
|
// The dapp's userPrompt is its own free text, shown only as a quote — it
|
||||||
|
// used to be the overlay's origin and the PIN request's name, so a paired
|
||||||
|
// dapp could present itself as any site.
|
||||||
|
const paired = payload?.pairing && typeof payload.pairing.origin === "string" ? payload.pairing.origin : null;
|
||||||
|
const dappName = paired === "panel" ? "a dapp you paired in Aegis by pasting its code"
|
||||||
|
: paired ? paired
|
||||||
|
: "a dapp paired before Aegis recorded where pairings came from";
|
||||||
|
const says = plainLabel(tx.userPrompt || "", 160);
|
||||||
const walletName = payload?.label || payload?.walletId || "";
|
const walletName = payload?.label || payload?.walletId || "";
|
||||||
const network = ctx.runtimes.get(payload?.walletId)?.entry?.network;
|
const network = ctx.runtimes.get(payload?.walletId)?.entry?.network;
|
||||||
const prefix = network === "chipnet" ? "bchtest" : "bitcoincash";
|
const prefix = network === "chipnet" ? "bchtest" : "bitcoincash";
|
||||||
|
|
@ -989,6 +1002,7 @@ function buildWcApproval(payload) {
|
||||||
}
|
}
|
||||||
const inputCount = Array.isArray(req.inputPaths) ? req.inputPaths.length : (Array.isArray(inner?.inputs) ? inner.inputs.length : "?");
|
const inputCount = Array.isArray(req.inputPaths) ? req.inputPaths.length : (Array.isArray(inner?.inputs) ? inner.inputs.length : "?");
|
||||||
const rows = [{ label: "Wallet", value: walletName }, { label: "Inputs", value: String(inputCount) }];
|
const rows = [{ label: "Wallet", value: walletName }, { label: "Inputs", value: String(inputCount) }];
|
||||||
|
if (says) rows.unshift({ label: "The dapp says", value: `“${says}”` });
|
||||||
// What the wallet is putting IN. The outputs alone never showed that a
|
// What the wallet is putting IN. The outputs alone never showed that a
|
||||||
// transaction spends the user's tokens — and with the token prefix now
|
// transaction spends the user's tokens — and with the token prefix now
|
||||||
// signed correctly (wc-sign.js) such a transaction is valid, so the
|
// signed correctly (wc-sign.js) such a transaction is valid, so the
|
||||||
|
|
@ -1034,7 +1048,7 @@ function buildWcApproval(payload) {
|
||||||
} catch {}
|
} catch {}
|
||||||
rows.push({ label: "After signing", value: tx.broadcast ? "the dapp broadcasts it" : "signed hex is returned to the dapp" });
|
rows.push({ label: "After signing", value: tx.broadcast ? "the dapp broadcasts it" : "signed hex is returned to the dapp" });
|
||||||
rows.push({ label: "Sighash", value: "ALL | FORKID | UTXOS" });
|
rows.push({ label: "Sighash", value: "ALL | FORKID | UTXOS" });
|
||||||
return { body: `${dappName} asks you to sign a Bitcoin Cash transaction.`, rows, dappName };
|
return { body: `A site paired over WizardConnect asks you to sign a Bitcoin Cash transaction. Paired from: ${dappName}.`, rows, dappName, origin: paired && paired !== "panel" ? paired : "WizardConnect" };
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- per-purpose default wallets -------------------------------------------
|
// ---- per-purpose default wallets -------------------------------------------
|
||||||
|
|
@ -1383,7 +1397,28 @@ async function requireDappTxPin(origin, what) {
|
||||||
// benign-looking opening cannot hide what the rest commits the user to.
|
// benign-looking opening cannot hide what the rest commits the user to.
|
||||||
function previewText(text, max = 1500) {
|
function previewText(text, max = 1500) {
|
||||||
const s = String(text);
|
const s = String(text);
|
||||||
return s.length > max ? `${s.slice(0, max)}\n… [${s.length - max} more characters are NOT shown but will be signed]` : s;
|
const cut = s.length > max ? `${s.slice(0, max)}\n… [${s.length - max} more characters are NOT shown but will be signed]` : s;
|
||||||
|
return showInvisibles(cut);
|
||||||
|
}
|
||||||
|
// Characters that change how text LOOKS without being visible themselves:
|
||||||
|
// C0/C1 controls (newline and tab excepted), zero-width characters, and the
|
||||||
|
// bidi overrides/isolates that can make "0x…dead" render as "0x…daed" or
|
||||||
|
// reorder an overlay line. Built from code points so no editor or tool can
|
||||||
|
// silently turn the escapes into the characters themselves.
|
||||||
|
const INVISIBLE_RE = (() => {
|
||||||
|
const r = [[0x00, 0x08], [0x0b, 0x0c], [0x0e, 0x1f], [0x7f, 0x9f], [0x061c, 0x061c], [0x180e, 0x180e],
|
||||||
|
[0x200b, 0x200f], [0x2028, 0x202e], [0x2060, 0x2064], [0x2066, 0x206f], [0xfeff, 0xfeff]];
|
||||||
|
const esc = (c) => String.fromCharCode(92) + "u" + c.toString(16).padStart(4, "0");
|
||||||
|
return new RegExp("[" + r.map(([a, b]) => (a === b ? esc(a) : esc(a) + "-" + esc(b))).join("") + "]", "g");
|
||||||
|
})();
|
||||||
|
// Signed text keeps every character, so the overlay names the invisible ones.
|
||||||
|
function showInvisibles(s) {
|
||||||
|
return String(s).replace(INVISIBLE_RE, (c) => `⟨U+${c.charCodeAt(0).toString(16).toUpperCase().padStart(4, "0")}⟩`);
|
||||||
|
}
|
||||||
|
// A name or label that someone else chose (a dapp, a token registry): one
|
||||||
|
// line, nothing invisible, bounded.
|
||||||
|
function plainLabel(v, max = 80) {
|
||||||
|
return String(v ?? "").replace(INVISIBLE_RE, "").replace(/\s+/g, " ").trim().slice(0, max);
|
||||||
}
|
}
|
||||||
function fromPanel(m) { if (!m || m.from !== "panel") throw new Error("panel-only message"); }
|
function fromPanel(m) { if (!m || m.from !== "panel") throw new Error("panel-only message"); }
|
||||||
function fromPage(m) {
|
function fromPage(m) {
|
||||||
|
|
@ -2325,7 +2360,7 @@ function registerPanelMessages(api) {
|
||||||
if (!ctx.wc) throw new Error("WizardConnect not ready");
|
if (!ctx.wc) throw new Error("WizardConnect not ready");
|
||||||
const walletId = String(p && p.walletId || "");
|
const walletId = String(p && p.walletId || "");
|
||||||
const uri = String(p && p.uri || "");
|
const uri = String(p && p.uri || "");
|
||||||
await ctx.wc.connectUri(walletId, uri);
|
await ctx.wc.connectUri(walletId, uri, "panel");
|
||||||
return fullState();
|
return fullState();
|
||||||
});
|
});
|
||||||
api.onMessage("wcDisconnect", async (p, m) => {
|
api.onMessage("wcDisconnect", async (p, m) => {
|
||||||
|
|
@ -2443,7 +2478,7 @@ function registerPanelMessages(api) {
|
||||||
// from the options we offered, but the wallet could have gone away
|
// from the options we offered, but the wallet could have gone away
|
||||||
// while the dialog was open.
|
// while the dialog was open.
|
||||||
const chosen = candidates.find((w) => w.id === pickedId) || preferred;
|
const chosen = candidates.find((w) => w.id === pickedId) || preferred;
|
||||||
await ctx.wc.connectUri(chosen.id, uri);
|
await ctx.wc.connectUri(chosen.id, uri, origin);
|
||||||
return { paired: true, wallet: chosen.label };
|
return { paired: true, wallet: chosen.label };
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -3119,6 +3154,8 @@ function revokeOrigin(api, origin) {
|
||||||
api.storage.set("permissions", perms);
|
api.storage.set("permissions", perms);
|
||||||
sessionGrants.delete(origin);
|
sessionGrants.delete(origin);
|
||||||
syncInjectPolicy(api);
|
syncInjectPolicy(api);
|
||||||
|
// A revoked site keeps no WizardConnect pairing either.
|
||||||
|
if (ctx?.wc?.disconnectOrigin) ctx.wc.disconnectOrigin(origin).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- who can see the wallet ------------------------------------------------
|
// ---- who can see the wallet ------------------------------------------------
|
||||||
|
|
@ -4311,11 +4348,11 @@ module.exports = {
|
||||||
// keys fell back to a lone "OK" button whose id never matched, so
|
// keys fell back to a lone "OK" button whose id never matched, so
|
||||||
// every WizardConnect signing request was refused, and the HTML
|
// every WizardConnect signing request was refused, and the HTML
|
||||||
// body was shown as literal markup.
|
// body was shown as literal markup.
|
||||||
const { body, rows, dappName, unreadable } = buildWcApproval(payload);
|
const { body, rows, dappName, unreadable, origin: wcOrigin } = buildWcApproval(payload);
|
||||||
if (unreadable) { api.log(`wc sign: refused an unreadable request from ${dappName}`); return { approved: false }; }
|
if (unreadable) { api.log(`wc sign: refused an unreadable request from ${dappName}`); return { approved: false }; }
|
||||||
const pick = await api.approvalModal({
|
const pick = await api.approvalModal({
|
||||||
title: "Sign a Bitcoin Cash transaction (WizardConnect)?",
|
title: "Sign a Bitcoin Cash transaction (WizardConnect)?",
|
||||||
origin: dappName,
|
origin: wcOrigin,
|
||||||
body, rows,
|
body, rows,
|
||||||
actions: [{ id: "approve", label: "Sign", primary: true }],
|
actions: [{ id: "approve", label: "Sign", primary: true }],
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ module.exports = function makeWc({ HDKey, secp256k1, sha256, hkdf, WalletConnect
|
||||||
// HDKey and to derive per-URI relay identities via HKDF, so reconnecting
|
// HDKey and to derive per-URI relay identities via HKDF, so reconnecting
|
||||||
// yields the same Nostr identity (dapp recognises us on reload).
|
// yields the same Nostr identity (dapp recognises us on reload).
|
||||||
function makeAdapter({ root32, accountPath, walletId, label }) {
|
function makeAdapter({ root32, accountPath, walletId, label }) {
|
||||||
|
// Own copy: the caller may wipe its buffer once this returns.
|
||||||
|
root32 = new Uint8Array(root32);
|
||||||
const account = HDKey.fromMasterSeed(root32).derive(accountPath);
|
const account = HDKey.fromMasterSeed(root32).derive(accountPath);
|
||||||
const branches = new Map();
|
const branches = new Map();
|
||||||
const branchFor = (childIndex) => {
|
const branchFor = (childIndex) => {
|
||||||
|
|
@ -70,7 +72,11 @@ module.exports = function makeWc({ HDKey, secp256k1, sha256, hkdf, WalletConnect
|
||||||
// Sign a transaction the dapp has already assembled. See signTx.js for
|
// Sign a transaction the dapp has already assembled. See signTx.js for
|
||||||
// the heavy lifting (SIGHASH_ALL|FORKID|UTXOS enforcement, libauth
|
// the heavy lifting (SIGHASH_ALL|FORKID|UTXOS enforcement, libauth
|
||||||
// preimage + secp256k1 der/lowS signatures).
|
// preimage + secp256k1 der/lowS signatures).
|
||||||
async signTransaction(request) {
|
// `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) {
|
||||||
// Route through approval-modal first — the user always sees what
|
// Route through approval-modal first — the user always sees what
|
||||||
// they're signing before any private key touches the request.
|
// they're signing before any private key touches the request.
|
||||||
if (!approvalRequest) throw new Error("no approval channel");
|
if (!approvalRequest) throw new Error("no approval channel");
|
||||||
|
|
@ -78,6 +84,7 @@ module.exports = function makeWc({ HDKey, secp256k1, sha256, hkdf, WalletConnect
|
||||||
kind: "wc-sign",
|
kind: "wc-sign",
|
||||||
walletId, label,
|
walletId, label,
|
||||||
request,
|
request,
|
||||||
|
pairing,
|
||||||
});
|
});
|
||||||
if (!decision?.approved) throw new Error("cancelled");
|
if (!decision?.approved) throw new Error("cancelled");
|
||||||
const { signTx } = require("./wc-sign.js");
|
const { signTx } = require("./wc-sign.js");
|
||||||
|
|
@ -97,6 +104,8 @@ module.exports = function makeWc({ HDKey, secp256k1, sha256, hkdf, WalletConnect
|
||||||
// panel can show "Wallet A connected to 2 dapps, Wallet B to none" etc.
|
// panel can show "Wallet A connected to 2 dapps, Wallet B to none" etc.
|
||||||
const managers = new Map(); // walletId -> WalletConnectionManager
|
const managers = new Map(); // walletId -> WalletConnectionManager
|
||||||
const uris = new Map(); // walletId -> Set<uri> (persisted)
|
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
|
||||||
const listeners = new Set(); // () => void — panel resubscribes on state change
|
const listeners = new Set(); // () => void — panel resubscribes on state change
|
||||||
|
|
||||||
function fireStateChange() { for (const fn of listeners) try { fn(); } catch {} }
|
function fireStateChange() { for (const fn of listeners) try { fn(); } catch {} }
|
||||||
|
|
@ -104,6 +113,13 @@ module.exports = function makeWc({ HDKey, secp256k1, sha256, hkdf, WalletConnect
|
||||||
function persist(walletId) {
|
function persist(walletId) {
|
||||||
const list = [...(uris.get(walletId) || new Set())];
|
const list = [...(uris.get(walletId) || new Set())];
|
||||||
api.storage.set(`wc/${walletId}/uris`, list);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function startForWallet({ walletId, label, root32, accountPath }) {
|
async function startForWallet({ walletId, label, root32, accountPath }) {
|
||||||
|
|
@ -119,17 +135,29 @@ module.exports = function makeWc({ HDKey, secp256k1, sha256, hkdf, WalletConnect
|
||||||
fireStateChange();
|
fireStateChange();
|
||||||
});
|
});
|
||||||
mgr.on("pendingSignRequest", async ({ connectionId, request }) => {
|
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);
|
||||||
try {
|
try {
|
||||||
const { signedTransaction } = await adapter.signTransaction(request);
|
const uri = uriOf(mgr, connectionId);
|
||||||
|
const pairing = (uri && origins.get(walletId)?.get(uri)) || null;
|
||||||
|
const { signedTransaction } = await adapter.signTransaction(request, pairing);
|
||||||
await mgr.sendSignResponse(connectionId, request.sequence, signedTransaction);
|
await mgr.sendSignResponse(connectionId, request.sequence, signedTransaction);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(`wc[${walletId}] sign failed:`, e?.message || e);
|
log(`wc[${walletId}] sign failed:`, e?.message || e);
|
||||||
try { await mgr.sendSignError(connectionId, request.sequence, cleanErrForDapp(e)); } catch {}
|
try { await mgr.sendSignError(connectionId, request.sequence, cleanErrForDapp(e)); } catch {}
|
||||||
}
|
} finally { busy.delete(connectionId); }
|
||||||
});
|
});
|
||||||
|
|
||||||
// Restore persisted pairings.
|
// Restore persisted pairings.
|
||||||
uris.set(walletId, new Set(api.storage.get(`wc/${walletId}/uris`, []) || []));
|
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")));
|
||||||
for (const uri of uris.get(walletId)) {
|
for (const uri of uris.get(walletId)) {
|
||||||
try { mgr.connect(uri); } catch (e) { log(`wc[${walletId}] reconnect failed:`, e?.message); }
|
try { mgr.connect(uri); } catch (e) { log(`wc[${walletId}] reconnect failed:`, e?.message); }
|
||||||
}
|
}
|
||||||
|
|
@ -141,9 +169,12 @@ module.exports = function makeWc({ HDKey, secp256k1, sha256, hkdf, WalletConnect
|
||||||
try { mgr.disconnectAll?.(); } catch {}
|
try { mgr.disconnectAll?.(); } catch {}
|
||||||
managers.delete(walletId);
|
managers.delete(walletId);
|
||||||
uris.delete(walletId);
|
uris.delete(walletId);
|
||||||
|
origins.delete(walletId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function connectUri(walletId, uri) {
|
// `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") {
|
||||||
const mgr = managers.get(walletId);
|
const mgr = managers.get(walletId);
|
||||||
if (!mgr) throw new Error("wc: wallet not ready");
|
if (!mgr) throw new Error("wc: wallet not ready");
|
||||||
const trimmed = String(uri || "").trim();
|
const trimmed = String(uri || "").trim();
|
||||||
|
|
@ -152,6 +183,9 @@ module.exports = function makeWc({ HDKey, secp256k1, sha256, hkdf, WalletConnect
|
||||||
const set = uris.get(walletId) || new Set();
|
const set = uris.get(walletId) || new Set();
|
||||||
set.add(trimmed);
|
set.add(trimmed);
|
||||||
uris.set(walletId, set);
|
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);
|
||||||
persist(walletId);
|
persist(walletId);
|
||||||
fireStateChange();
|
fireStateChange();
|
||||||
return id;
|
return id;
|
||||||
|
|
@ -168,6 +202,25 @@ module.exports = function makeWc({ HDKey, secp256k1, sha256, hkdf, WalletConnect
|
||||||
fireStateChange();
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
function snapshot() {
|
function snapshot() {
|
||||||
const out = {};
|
const out = {};
|
||||||
for (const [walletId, mgr] of managers) {
|
for (const [walletId, mgr] of managers) {
|
||||||
|
|
@ -187,6 +240,7 @@ module.exports = function makeWc({ HDKey, secp256k1, sha256, hkdf, WalletConnect
|
||||||
label: c.label || null,
|
label: c.label || null,
|
||||||
dappName: c.dappName || null,
|
dappName: c.dappName || null,
|
||||||
dappIcon: c.dappIcon || null,
|
dappIcon: c.dappIcon || null,
|
||||||
|
pairedFrom: origins.get(walletId)?.get(c.uri)?.origin || null,
|
||||||
// RelayStatus is an OBJECT: { status: "connected" | "reconnecting"
|
// RelayStatus is an OBJECT: { status: "connected" | "reconnecting"
|
||||||
// | "disconnected" | "session_deleted" }. Reading `.kind` (which
|
// | "disconnected" | "session_deleted" }. Reading `.kind` (which
|
||||||
// does not exist) fell through to String(object) and put the
|
// does not exist) fell through to String(object) and put the
|
||||||
|
|
@ -209,5 +263,5 @@ module.exports = function makeWc({ HDKey, secp256k1, sha256, hkdf, WalletConnect
|
||||||
return m.replace(/\n[\s\S]*$/, "").slice(0, 200);
|
return m.replace(/\n[\s\S]*$/, "").slice(0, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { startForWallet, stopForWallet, connectUri, disconnect, snapshot, onStateChange };
|
return { startForWallet, stopForWallet, connectUri, disconnect, disconnectOrigin, snapshot, onStateChange };
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2474,7 +2474,7 @@ function renderConnectPane(bchWallets) {
|
||||||
const rowsHtml = rows.length
|
const rowsHtml = rows.length
|
||||||
? rows.map((c) => `<div class="tx" style="grid-template-columns:auto 1fr auto;cursor:default;align-items:center;margin-top:6px">
|
? rows.map((c) => `<div class="tx" style="grid-template-columns:auto 1fr auto;cursor:default;align-items:center;margin-top:6px">
|
||||||
<div>${c.dappIcon ? `<img src="${esc(c.dappIcon)}" style="width:18px;height:18px;border-radius:4px" onerror="this.hidden=true">` : ""}</div>
|
<div>${c.dappIcon ? `<img src="${esc(c.dappIcon)}" style="width:18px;height:18px;border-radius:4px" onerror="this.hidden=true">` : ""}</div>
|
||||||
<div><div>${esc(c.dappName || c.label || "(pairing…)")} ${wcStatusTag(c.status)}</div><div class="hint">on <b>${esc(c.walletLabel)}</b> · <span class="mono">${esc((c.uri || "").slice(0, 40))}…</span></div></div>
|
<div><div>${esc(c.dappName || c.label || "(pairing…)")} ${wcStatusTag(c.status)}</div><div class="hint">${c.pairedFrom ? `paired from <b>${esc(c.pairedFrom === "panel" ? "Aegis (pasted code)" : c.pairedFrom)}</b> · ` : ""}on <b>${esc(c.walletLabel)}</b> · <span class="mono">${esc((c.uri || "").slice(0, 40))}…</span></div></div>
|
||||||
<button class="btn sm" data-wcpick="${esc(c.walletId)}|${esc(c.id)}">Disconnect</button>
|
<button class="btn sm" data-wcpick="${esc(c.walletId)}|${esc(c.id)}">Disconnect</button>
|
||||||
</div>`).join("")
|
</div>`).join("")
|
||||||
: `<div class="hint" style="padding:6px 8px">No dapps paired yet.</div>`;
|
: `<div class="hint" style="padding:6px 8px">No dapps paired yet.</div>`;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue