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;
|
now[origin] = cur;
|
||||||
api.storage.set("permissions", now);
|
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
|
// An allowance skips the overlay, not the PIN the user asked for on
|
||||||
// every transaction.
|
// every transaction.
|
||||||
await requireDappTxPin(origin, "Bitcoin Cash payment");
|
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: "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: "Fee", value: `${plan.fee} sat (${plan.feeRate} sat/B)` });
|
||||||
rows.push({ label: "Total", value: fmtBch(d.total) + " BCH" });
|
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({
|
const pick = await api.approvalModal({
|
||||||
title: "Send Bitcoin Cash?",
|
title: "Send Bitcoin Cash?",
|
||||||
origin,
|
origin,
|
||||||
|
|
|
||||||
|
|
@ -266,6 +266,7 @@ module.exports = function makeImportedBchAdapter({
|
||||||
: [{ to: spec?.to, value: spec?.amount ?? spec?.value }];
|
: [{ to: spec?.to, value: spec?.amount ?? spec?.value }];
|
||||||
const outs = targets.map((t) => {
|
const outs = targets.map((t) => {
|
||||||
const a = cashaddr.parseAny(t.to, sha256, this._net.prefix);
|
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
|
const script = a.type === 0
|
||||||
? Uint8Array.from([0x76, 0xa9, 0x14, ...a.hash, 0x88, 0xac])
|
? Uint8Array.from([0x76, 0xa9, 0x14, ...a.hash, 0x88, 0xac])
|
||||||
: Uint8Array.from([0xa9, 0x14, ...a.hash, 0x87]);
|
: 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));
|
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");
|
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 sel = tx.select(spendable, outs, rate, changeScript, { sendMax: !!spec?.sendMax });
|
||||||
const nonData = sel.outputs.filter((o) => !o.data);
|
// Payees only (change is not a recipient); total = what leaves the
|
||||||
const total = sel.outputs.reduce((a, o) => a + o.value, 0);
|
// 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 {
|
return {
|
||||||
...sel,
|
...sel,
|
||||||
feeRate: rate,
|
feeRate: rate,
|
||||||
recipients: nonData
|
recipients,
|
||||||
.filter((_, i) => outs[i] && !outs[i].data)
|
|
||||||
.map((o, i) => ({ to: outs[i].to, value: o.value })),
|
|
||||||
memo: spec?.memo || null,
|
memo: spec?.memo || null,
|
||||||
total,
|
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 rate = Math.min(10, Math.max(1, Number(feeRate) || 1));
|
||||||
const outs = targets.map((t) => {
|
const outs = targets.map((t) => {
|
||||||
const a = cashaddr.parseAny(t.to, sha256, keys.prefix);
|
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
|
const script = a.type === 0
|
||||||
? Uint8Array.from([0x76, 0xa9, 0x14, ...a.hash, 0x88, 0xac])
|
? Uint8Array.from([0x76, 0xa9, 0x14, ...a.hash, 0x88, 0xac])
|
||||||
: Uint8Array.from([0xa9, 0x14, ...a.hash, 0x87]);
|
: Uint8Array.from([0xa9, 0x14, ...a.hash, 0x87]);
|
||||||
|
|
@ -447,12 +450,17 @@ module.exports = function makeWallet({ client, keys, tx, cashaddr, sha256, stora
|
||||||
.slice()
|
.slice()
|
||||||
.sort((a, b) => (b.height > 0) - (a.height > 0));
|
.sort((a, b) => (b.height > 0) - (a.height > 0));
|
||||||
const sel = tx.select(spendable, outs, rate, changeEntry().script, { sendMax });
|
const sel = tx.select(spendable, outs, rate, changeEntry().script, { sendMax });
|
||||||
// recipients only lists spendable (non-data) outputs, keeping the
|
// recipients lists the payees only — the memo is surfaced separately
|
||||||
// panel's summary honest — the memo is surfaced separately as .memo.
|
// as .memo, and the change output (which select() appends after the
|
||||||
const spendable_outs = sel.outputs.filter((o) => !o.data);
|
// 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 {
|
return {
|
||||||
...sel, feeRate: rate,
|
...sel, feeRate: rate,
|
||||||
recipients: spendable_outs.map((o, i) => ({ to: outs[i]?.to, value: o.value })),
|
recipients,
|
||||||
|
total: sent + sel.fee,
|
||||||
memo: memo || null,
|
memo: memo || null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue