diff options
| author | Adam Soltys <[email protected]> | 2026-06-24 20:28:12 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-06-24 20:28:12 -0700 |
| commit | 08d1753e8433fcae6e9068e87cff93813f0ed76d (patch) | |
| tree | 82e36ab3181c80d48217bb79a03940813e84b47d /src/wallet.js | |
| parent | aeb6a68e15b04ff35c8154a8f219dcca0a07de0e (diff) | |
| download | coinosv3-08d1753e8433fcae6e9068e87cff93813f0ed76d.tar.gz coinosv3-08d1753e8433fcae6e9068e87cff93813f0ed76d.zip | |
Gift links: stop pre-reserving fee, show it at claim time
Previously createGift reserved a claim-fee headroom on top of the entered
amount based on the fee rate at creation. Because the fee was only actually
charged at claim time, fee-rate drift between the two moments silently ate
into (or padded) the recipient's amount.
Now the entered amount is exactly the recipient's full received total (sum of
inputs less our change); no fee is reserved. The claim screen shows that full
total as the headline and surfaces the live network-fee estimate on the Claim
button, where it's deducted from the recipient's balance at claim time.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Diffstat (limited to 'src/wallet.js')
| -rw-r--r-- | src/wallet.js | 20 |
1 files changed, 11 insertions, 9 deletions
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; } |
