aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/app.js13
-rw-r--r--src/i18n.js1
-rw-r--r--src/wallet.js20
3 files changed, 21 insertions, 13 deletions
diff --git a/src/app.js b/src/app.js
index c8ff8b1..4aca644 100644
--- a/src/app.js
+++ b/src/app.js
@@ -1288,8 +1288,12 @@ function claimScreen() {
);
}
const pv = previewGift(ui.claimCode);
- const estFee = Math.ceil((11 + 68 + 31 * 2) * ((wallet.feeRates && wallet.feeRates.halfHourFee) || 5));
- const approx = pv ? Math.max(0, pv.room - estFee) : 0;
+ // The headline is the full amount received (inputs minus the sender's change).
+ // The network fee is determined now, at claim time, and comes out of that
+ // amount โ€” we surface the estimate on the Claim button so it isn't a surprise.
+ const rate = Math.max(1, Math.round((wallet.feeRates && wallet.feeRates.halfHourFee) || 5));
+ const estFee = pv ? Math.ceil((11 + 68 * pv.inputs + 31 * 2) * rate) : 0;
+ const total = pv ? pv.room : 0;
return h(
'div',
{ class: 'col', style: 'gap:16px' },
@@ -1298,14 +1302,15 @@ function claimScreen() {
h('div', { class: 'check-badge', style: 'background:var(--accent)' }, '๐ŸŽ'),
h('h2', { style: 'margin:0' }, t('giftWelcome')),
h('div', { class: 'amt', style: 'font-size:30px' },
- h('span', { class: 'amount-pos' }, '~' + fmtAmount(approx)), ' ', unitTag('unit')
+ h('span', { class: 'amount-pos' }, fmtAmount(total)), ' ', unitTag('unit')
),
h('p', { class: 'muted', style: 'margin:0' }, t('claimBody'))
),
ui.claimError && h('div', { class: 'notice err' }, ui.claimError),
ui.busy
? h('button', { class: 'btn-primary btn-block', disabled: true }, h('span', { class: 'spinner' }))
- : h('button', { class: 'btn-primary btn-block', onClick: doClaim }, t('claimBtn'))
+ : h('button', { class: 'btn-primary btn-block', onClick: doClaim },
+ t('claimBtn') + ' ยท ' + t('claimFeeNote', { n: fmtAmount(estFee) + ' ' + unitLabel() }))
);
}
diff --git a/src/i18n.js b/src/i18n.js
index e427733..42fd34b 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -206,6 +206,7 @@ const STR = {
copyLink: 'Copy link',
claimBody: 'Claim it into a brand-new wallet that only you control.',
claimBtn: 'Claim it',
+ claimFeeNote: '~{n} fee',
claimFailed: 'This gift has already been claimed or is no longer available.',
claimedTitle: 'Claimed!',
claimedBody: 'Your Bitcoin is on its way. Back up your recovery phrase to keep it safe.',
diff --git a/src/wallet.js b/src/wallet.js
index 194b9dc..72d33be 100644
--- a/src/wallet.js
+++ b/src/wallet.js
@@ -878,10 +878,10 @@ export class Wallet {
if (gift < BigInt(giftMinimum(rate))) throw new Error('Gift amount is too small.');
// Select confirmed coins (largest first, to minimize inputs) until they
- // cover the gift + the claimer's fee (which grows with input count) + a dust
- // change. The change output goes back to us and is protected; the rest is
- // the claimer's to direct.
- const claimFeeFor = (n) => BigInt(Math.ceil((11 + 68 * n + 31 * 2) * rate));
+ // cover the gift plus a dust change. The change output goes back to us and
+ // is protected; the rest โ€” the full gift amount โ€” is the claimer's to
+ // direct. No fee is reserved here: the claimer's wallet looks up the fee
+ // rate at claim time and subtracts it from this amount.
const pool = this.utxos
.filter((u) => u.confirmed && !this.isReserved(utxoId(u)))
.sort((a, b) => b.value - a.value);
@@ -890,12 +890,11 @@ export class Wallet {
for (const u of pool) {
sel.push(u);
sum += BigInt(u.value);
- if (sum >= gift + claimFeeFor(sel.length) + DUST) break;
+ if (sum >= gift + DUST) break;
}
- const claimFee = claimFeeFor(sel.length);
- if (sum < gift + claimFee + DUST) throw new Error('Not enough confirmed funds for that gift amount.');
+ if (sum < gift + DUST) throw new Error('Not enough confirmed funds for that gift amount.');
- const change = sum - gift - claimFee; // back to us, >= DUST by construction
+ const change = sum - gift; // back to us, >= DUST by construction
const t = new btc.Transaction({ allowUnknownOutputs: true });
// First input commits the change output (SIGHASH_SINGLE on output 0); the
// rest commit only the inputs (SIGHASH_NONE). Together: inputs and our
@@ -1423,7 +1422,10 @@ function _sumInputs(tx) {
export function previewGift(code) {
try {
const tx = btc.Transaction.fromPSBT(base64urlnopad.decode(code));
- return { room: Number(_sumInputs(tx) - tx.getOutput(0).amount) }; // room minus the claimer fee
+ // room is the full amount the claimer receives (inputs minus our change);
+ // the claim fee is subtracted from it at claim time, so report inputs too
+ // so the caller can size that fee for this PSBT.
+ return { room: Number(_sumInputs(tx) - tx.getOutput(0).amount), inputs: tx.inputsLength };
} catch {
return null;
}