Observed on a live testnut trade. Not filed as a defect — filed as a question the money model should answer explicitly, because the answer determines whether a budget cap means what an operator thinks it means.
What happened
A 5-sat trade, settled end to end, on a home configured per_job_budget_sats = 5 / total_budget_sats = 5:
- wallet balance before: 10 sats
- wallet balance after: 4 sats
- budget ledger (
spent.jsonl): 1 entry, amount_sats: 5, attempt 42cfbaf56e46096ca689e23d281f9da8e8ba7d2d9b059e07e7e627bcb65f250b
So the recorded spend was 5, the cap correctly bounded the authorized payment at 5, and the gate did exactly its job — but 6 sats left the wallet. On this trade size that is 20% above the recorded figure.
The question
If mint fees (or swap/keyset costs) sit outside the budget ledger, then total_budget_sats bounds authorized payments, not total outlay. An operator who sets a 100-sat ceiling to mean "this daemon cannot cost me more than 100 sats" would be wrong by whatever the aggregate fee load turns out to be.
Both readings are defensible — a cap on what we commit to sellers is a coherent design — but it should be a decision that is written down, not an artifact nobody checked.
Cause not determined
I did not diagnose it and am not asserting a mechanism. Candidates:
- a cashu swap / keyset fee on producing the 5-sat payment from a 10-sat proof
- a proof in flight that
balance_sats does not yet count (in which case the 1 sat returns and there is no gap at all)
- fee accounting that genuinely happens outside
crate::budget
The first and third are real; the second would make this a non-issue. Distinguishing them is the point of the reproduction below.
Reproduction shape
Run the same trade at a larger amount, where a fixed fee separates cleanly from a rounding or denomination artifact — e.g. 50 sats from a 100-sat balance. Then compare three numbers rather than two:
- wallet balance delta
spent.jsonl total
- wallet balance again after any pending proof settles (to rule out the in-flight case)
A fixed 1-sat gap at both 5 and 50 sats points at a per-operation fee; a gap that scales points at a proportional one; a gap that closes on re-read was never real.
Second customer for the answer
The monetization design (seller pays 10% per trade, with the seller forwarding post-redeem) performs a per-trade payment whose fee accounting hits this identical question, at every trade rather than occasionally. Whatever is decided here should be decided with that in mind, since a per-trade forward multiplies any per-operation cost by trade volume.
Provenance
Found while dogfooding the delivery-watcher slice (PR #181) — the trade itself succeeded: delivery to payment in 2 seconds, unassisted, receipt above.
Observed on a live testnut trade. Not filed as a defect — filed as a question the money model should answer explicitly, because the answer determines whether a budget cap means what an operator thinks it means.
What happened
A 5-sat trade, settled end to end, on a home configured
per_job_budget_sats = 5/total_budget_sats = 5:spent.jsonl): 1 entry,amount_sats: 5, attempt42cfbaf56e46096ca689e23d281f9da8e8ba7d2d9b059e07e7e627bcb65f250bSo the recorded spend was 5, the cap correctly bounded the authorized payment at 5, and the gate did exactly its job — but 6 sats left the wallet. On this trade size that is 20% above the recorded figure.
The question
If mint fees (or swap/keyset costs) sit outside the budget ledger, then
total_budget_satsbounds authorized payments, not total outlay. An operator who sets a 100-sat ceiling to mean "this daemon cannot cost me more than 100 sats" would be wrong by whatever the aggregate fee load turns out to be.Both readings are defensible — a cap on what we commit to sellers is a coherent design — but it should be a decision that is written down, not an artifact nobody checked.
Cause not determined
I did not diagnose it and am not asserting a mechanism. Candidates:
balance_satsdoes not yet count (in which case the 1 sat returns and there is no gap at all)crate::budgetThe first and third are real; the second would make this a non-issue. Distinguishing them is the point of the reproduction below.
Reproduction shape
Run the same trade at a larger amount, where a fixed fee separates cleanly from a rounding or denomination artifact — e.g. 50 sats from a 100-sat balance. Then compare three numbers rather than two:
spent.jsonltotalA fixed 1-sat gap at both 5 and 50 sats points at a per-operation fee; a gap that scales points at a proportional one; a gap that closes on re-read was never real.
Second customer for the answer
The monetization design (seller pays 10% per trade, with the seller forwarding post-redeem) performs a per-trade payment whose fee accounting hits this identical question, at every trade rather than occasionally. Whatever is decided here should be decided with that in mind, since a per-trade forward multiplies any per-operation cost by trade volume.
Provenance
Found while dogfooding the delivery-watcher slice (PR #181) — the trade itself succeeded: delivery to payment in 2 seconds, unassisted, receipt above.