Aegis: BCH payments list only the payees and show a site's OP_RETURN data
The HD wallet's plan listed the change output as a recipient with no address, so a dapp payment overlay showed "To #2: undefined" and an Amount and Total inflated by the change; the imported wallet's total counted change and left out the fee. Plans now list payees only, with total = payees + fee. A site's memo went on-chain as OP_RETURN data without appearing on the overlay, and could ride on a silent allowance payment. It is now a row, and a payment carrying data always asks. A 32-byte-hash cashaddr was forced into a 20-byte template, producing an unspendable script; it is refused.
This commit is contained in:
parent
df7f26552a
commit
bd6e990598
3 changed files with 24 additions and 10 deletions
|
|
@ -3656,7 +3656,8 @@ function registerPageMessages(api) {
|
|||
now[origin] = cur;
|
||||
api.storage.set("permissions", now);
|
||||
};
|
||||
if (budget && d.total <= remaining) {
|
||||
// A payment carrying data for the chain is never silent.
|
||||
if (budget && d.total <= remaining && !plan.memo) {
|
||||
// An allowance skips the overlay, not the PIN the user asked for on
|
||||
// every transaction.
|
||||
await requireDappTxPin(origin, "Bitcoin Cash payment");
|
||||
|
|
@ -3673,6 +3674,8 @@ function registerPageMessages(api) {
|
|||
rows.push({ label: "Amount", value: fmtBch(d.recipients.reduce((a, r) => a + r.value, 0)) + " BCH", strong: true });
|
||||
rows.push({ label: "Fee", value: `${plan.fee} sat (${plan.feeRate} sat/B)` });
|
||||
rows.push({ label: "Total", value: fmtBch(d.total) + " BCH" });
|
||||
// The site's OP_RETURN data is written on-chain under this payment.
|
||||
if (plan.memo) rows.push({ label: "Data (OP_RETURN)", value: previewText(plan.memo, 220), mono: true });
|
||||
const pick = await api.approvalModal({
|
||||
title: "Send Bitcoin Cash?",
|
||||
origin,
|
||||
|
|
|
|||
|
|
@ -266,6 +266,7 @@ module.exports = function makeImportedBchAdapter({
|
|||
: [{ to: spec?.to, value: spec?.amount ?? spec?.value }];
|
||||
const outs = targets.map((t) => {
|
||||
const a = cashaddr.parseAny(t.to, sha256, this._net.prefix);
|
||||
if (a.hash.length !== 20) throw new Error("this address type (32-byte hash) is not supported for sending yet");
|
||||
const script = a.type === 0
|
||||
? Uint8Array.from([0x76, 0xa9, 0x14, ...a.hash, 0x88, 0xac])
|
||||
: Uint8Array.from([0xa9, 0x14, ...a.hash, 0x87]);
|
||||
|
|
@ -283,14 +284,16 @@ module.exports = function makeImportedBchAdapter({
|
|||
const spendable = this._state.utxos.filter((u) => !u.token).sort((a, b) => (b.height > 0) - (a.height > 0));
|
||||
if (!spendable.length) throw new Error("every unspent output in this wallet carries a token; there is no plain BCH to spend");
|
||||
const sel = tx.select(spendable, outs, rate, changeScript, { sendMax: !!spec?.sendMax });
|
||||
const nonData = sel.outputs.filter((o) => !o.data);
|
||||
const total = sel.outputs.reduce((a, o) => a + o.value, 0);
|
||||
// Payees only (change is not a recipient); total = what leaves the
|
||||
// wallet, i.e. payees + fee. It used to be every output including
|
||||
// change, without the fee.
|
||||
const payees = outs.filter((o) => !o.data);
|
||||
const recipients = payees.map((o, i) => ({ to: o.to, value: sel.outputs[i].value }));
|
||||
const total = recipients.reduce((a, r) => a + r.value, 0) + sel.fee;
|
||||
return {
|
||||
...sel,
|
||||
feeRate: rate,
|
||||
recipients: nonData
|
||||
.filter((_, i) => outs[i] && !outs[i].data)
|
||||
.map((o, i) => ({ to: outs[i].to, value: o.value })),
|
||||
recipients,
|
||||
memo: spec?.memo || null,
|
||||
total,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -431,6 +431,9 @@ module.exports = function makeWallet({ client, keys, tx, cashaddr, sha256, stora
|
|||
const rate = Math.min(10, Math.max(1, Number(feeRate) || 1));
|
||||
const outs = targets.map((t) => {
|
||||
const a = cashaddr.parseAny(t.to, sha256, keys.prefix);
|
||||
// A 32-byte hash (P2SH32) does not fit the 20-byte templates below;
|
||||
// it used to be forced into one, producing a script nobody can spend.
|
||||
if (a.hash.length !== 20) throw new Error("this address type (32-byte hash) is not supported for sending yet");
|
||||
const script = a.type === 0
|
||||
? Uint8Array.from([0x76, 0xa9, 0x14, ...a.hash, 0x88, 0xac])
|
||||
: Uint8Array.from([0xa9, 0x14, ...a.hash, 0x87]);
|
||||
|
|
@ -447,12 +450,17 @@ module.exports = function makeWallet({ client, keys, tx, cashaddr, sha256, stora
|
|||
.slice()
|
||||
.sort((a, b) => (b.height > 0) - (a.height > 0));
|
||||
const sel = tx.select(spendable, outs, rate, changeEntry().script, { sendMax });
|
||||
// recipients only lists spendable (non-data) outputs, keeping the
|
||||
// panel's summary honest — the memo is surfaced separately as .memo.
|
||||
const spendable_outs = sel.outputs.filter((o) => !o.data);
|
||||
// recipients lists the payees only — the memo is surfaced separately
|
||||
// as .memo, and the change output (which select() appends after the
|
||||
// payees) is not a recipient. It used to be listed as one with no
|
||||
// address, and summed into Amount and Total.
|
||||
const payees = outs.filter((o) => !o.data);
|
||||
const recipients = payees.map((o, i) => ({ to: o.to, value: sel.outputs[i].value }));
|
||||
const sent = recipients.reduce((a, r) => a + r.value, 0);
|
||||
return {
|
||||
...sel, feeRate: rate,
|
||||
recipients: spendable_outs.map((o, i) => ({ to: outs[i]?.to, value: o.value })),
|
||||
recipients,
|
||||
total: sent + sel.fee,
|
||||
memo: memo || null,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue