Aegis: WizardConnect signing requests can be approved

approvalRequest passed approve/reject keys the host overlay does not
know, so it showed a lone "OK" button whose id never equalled
"approve": every WizardConnect signing request was refused, and the
HTML body was shown as literal markup. It now passes a Sign action and
plain rows: wallet, input count, each output decoded to a cashaddr on
the wallet's network (or OP_RETURN / raw script), total, and who
broadcasts.
This commit is contained in:
Local Dev 2026-10-03 23:01:15 +02:00
parent 5769d837bd
commit 03140fae9d

View file

@ -953,16 +953,48 @@ function deriveCashaddrFromWif(wif, prefix) {
return d.cashaddr.encode(prefix, 0, h160); return d.cashaddr.encode(prefix, 0, h160);
} }
function buildWcApprovalBody(payload) { // Plain-text body + rows for the host overlay (it escapes everything, so
// markup would show up literally). Outputs are decoded best-effort from the
// libauth transaction the dapp sent: P2PKH / P2SH locking bytecode → cashaddr.
// The WC message nests the WcSignTransactionRequest under .transaction, so
// the libauth tx itself is request.transaction.transaction (see wc-sign.js).
function buildWcApproval(payload) {
const req = payload?.request || {}; const req = payload?.request || {};
const tx = req.transaction || {}; const tx = req.transaction || {};
const dappName = tx.userPrompt || "unknown dapp"; const dappName = String(tx.userPrompt || payload?.dappName || "unknown dapp").slice(0, 120);
const inputCount = Array.isArray(req.inputPaths) ? req.inputPaths.length : "?";
const bc = tx.broadcast ? "Dapp will broadcast after signing." : "Signed hex returned to dapp.";
const walletName = payload?.label || payload?.walletId || ""; const walletName = payload?.label || payload?.walletId || "";
return "<div><b>" + dappName + "</b> requests a BCH transaction signature.</div>" const network = ctx.runtimes.get(payload?.walletId)?.entry?.network;
+ "<div>Wallet: <b>" + walletName + "</b> · " + inputCount + " input(s).</div>" const prefix = network === "chipnet" ? "bchtest" : "bitcoincash";
+ "<div class=hint>" + bc + " Signs with SIGHASH_ALL|FORKID|UTXOS.</div>"; let inner = tx.transaction;
if (typeof inner === "string") {
try {
const lib = ctx.d.libauth;
const dec = (lib.decodeTransactionCommon || lib.decodeTransaction)(ctx.d.tx.fromHex(inner));
inner = typeof dec === "string" ? null : dec;
} catch { inner = null; }
}
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) }];
try {
const outs = inner?.outputs || [];
let total = 0n;
outs.slice(0, 8).forEach((o, i) => {
const lb = o.lockingBytecode;
const hexScript = typeof lb === "string" ? lb.toLowerCase() : Buffer.from(lb || []).toString("hex");
let addr = null;
if (/^76a914[0-9a-f]{40}88ac$/.test(hexScript)) addr = ctx.d.cashaddr.encode(prefix, 0, ctx.d.tx.fromHex(hexScript.slice(6, 46)));
else if (/^a914[0-9a-f]{40}87$/.test(hexScript)) addr = ctx.d.cashaddr.encode(prefix, 1, ctx.d.tx.fromHex(hexScript.slice(4, 44)));
const v = BigInt(o.valueSatoshis ?? 0);
total += v;
const token = o.token ? " + CashTokens" : "";
rows.push({ label: `Output #${i + 1}`, value: `${fmtBch(Number(v))} BCH${token} → ${addr || (hexScript.startsWith("6a") ? "OP_RETURN data" : "script " + hexScript.slice(0, 24) + "…")}`, mono: true });
});
if (outs.length > 8) rows.push({ label: "Outputs", value: `… and ${outs.length - 8} more` });
if (outs.length) rows.push({ label: "Total out", value: `${fmtBch(Number(total))} BCH`, strong: true });
} catch {}
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" });
return { body: `${dappName} asks you to sign a Bitcoin Cash transaction.`, rows, dappName };
} }
// ---- per-purpose default wallets ------------------------------------------- // ---- per-purpose default wallets -------------------------------------------
@ -3655,12 +3687,16 @@ module.exports = {
api, api,
// Bridge sign approvals through the addon's approval-modal capability. // Bridge sign approvals through the addon's approval-modal capability.
approvalRequest: async (payload) => { approvalRequest: async (payload) => {
const dappName = payload.request?.transaction?.userPrompt || "dapp"; // The host overlay only knows `actions`; the old `approve`/`reject`
// keys fell back to a lone "OK" button whose id never matched, so
// every WizardConnect signing request was refused, and the HTML
// body was shown as literal markup.
const { body, rows, dappName } = buildWcApproval(payload);
const pick = await api.approvalModal({ const pick = await api.approvalModal({
title: `Sign transaction for ${dappName}`, title: "Sign a Bitcoin Cash transaction (WizardConnect)?",
body: buildWcApprovalBody(payload), origin: dappName,
approve: "Sign", body, rows,
reject: "Reject", actions: [{ id: "approve", label: "Sign", primary: true }],
}); });
return { approved: pick === "approve" }; return { approved: pick === "approve" };
}, },