Skip to content

Show the fee the transaction pays, not the one the response claims - #224

Merged
droplister merged 1 commit into
mainfrom
fix/show-the-fee-actually-paid
Aug 3, 2026
Merged

Show the fee the transaction pays, not the one the response claims#224
droplister merged 1 commit into
mainfrom
fix/show-the-fee-actually-paid

Conversation

@droplister

Copy link
Copy Markdown
Member

PRIORITIES.md item 1.

The problem

checkTransactionFee already recomputes the miner fee from the transaction's own inputs and outputs, resolving input values from the chain rather than from the response (ADR-019 layer 3). That number was used to accept or reject the transaction — and then discarded. The review screen rendered result.btc_fee: the composer's own assertion about itself.

The bound that is enforced is deliberately loose, because a legitimate composer needs room:

max(10_000, rate × vsize × 10)

transaction honest fee max that still passes overpay
250 vB @ 2 sat/vB 500 10,000 9,500 (20×)
400 vB @ 10 sat/vB 4,000 40,000 36,000 (10×)

So a response could pass verification while reporting a smaller fee than the transaction actually pays, and the user would sign against the number on screen. Value goes to miners rather than an attacker, so this is griefing rather than theft — but the displayed number was simply not the truth.

The change

The composer stores the computed fee in place of the reported one. Because all 28 review screens render result.btc_fee, that corrects every one of them at a single point rather than touching each screen.

  • A response that stated a different fee also produces a warning — contradicting a stated value is worth surfacing to the user.
  • A response that stated no fee is corrected silently. Filling in a value nobody claimed is not a discrepancy, and warning about it would be noise. (This distinction is why two composer tests initially failed: their fixtures omit btc_fee entirely.)

The test that was already broken

The existing should compose transaction test turned out to be a live example of the bug. Its fixture asserts btc_fee: 5000, while its stubbed input value (100,000) minus its single 95,160-sat output leaves 4,840. The fixture had been internally inconsistent all along and nothing checked — the defect in miniature. The test now asserts the corrected value and the warning.

Verification

  • tsc --noEmit clean.
  • 2524 unit tests across contexts/components/pages/blockchain.
  • E2E locally: compose/send/index.spec.ts 20/20, which includes "review page shows correct transaction details".

https://claude.ai/code/session_01CcjnCrgosSeshymXLBxdGj

checkTransactionFee already recomputes the miner fee from the
transaction's own inputs and outputs, resolving input values from the
chain rather than from the response. That number was used to accept or
reject the transaction and then discarded: the review screen rendered
result.btc_fee, the composer's own assertion about itself.

The bound is deliberately loose -- max(10_000, rate * vsize * 10) --
because a legitimate composer needs room, so a response can pass it
while reporting a smaller fee than the transaction actually pays, and
the user signs against the number on the screen. The composer now
stores the computed fee in place of the reported one, which corrects
every review screen at once since they all render that field.

A response that stated a different fee also produces a warning, since
contradicting a stated value is worth surfacing. A response that stated
no fee at all is corrected silently: filling in a value nobody claimed
is not a discrepancy.

The existing compose test turned out to be a live example -- its
fixture asserts btc_fee 5000 while its inputs minus its single output
leave 4840 -- and nothing had ever checked, which is the defect in
miniature.

Claude-Session: https://claude.ai/code/session_01CcjnCrgosSeshymXLBxdGj
@droplister
droplister merged commit 52b5b6c into main Aug 3, 2026
38 checks passed
@droplister
droplister deleted the fix/show-the-fee-actually-paid branch August 3, 2026 23:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant