fix(bitcoin): do not create a change output below the dust limit - #812
fix(bitcoin): do not create a change output below the dust limit#812KTibow wants to merge 2 commits into
Conversation
The send screen checks the amount being sent against the network's dust limit in three places, but never checks the change. sendAction() appended change for any remainder above zero, so a send that happened to leave a few hundred base units behind produced a transaction with a dust output, which nodes reject as non standard. The send simply failed, and the limit that would have caught it was already sitting in the network config. It bites hardest on litecoin, whose limit is 0.0001 LTC against bitcoin's 0.00000546 BTC, so a remainder well under a cent is enough to trigger it. Compare the remainder against the limit and leave it as fee when it falls short. The swap path built its change output the same way and is fixed alongside. Both now share getDustThreshold(), which converts the limit from whole coins to base units. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 50 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
WalkthroughAdds a network-specific Bitcoin dust threshold helper, tests its base-unit conversion and boundary behavior, and updates send and swap transaction construction to omit change outputs below the applicable threshold. ChangesBitcoin dust threshold handling
Estimated code review effort: 2 (Simple) | ~10 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/extension/src/providers/bitcoin/tests/bitcoin.dust.test.ts (1)
21-27: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winExercise the changed transaction builders at the dust boundary.
These assertions only test JavaScript comparisons; they would still pass if either production condition in
packages/extension/src/providers/bitcoin/ui/send-transaction/index.vueorpackages/extension/src/ui/action/views/swap/libs/swap-txs.tsregressed. Add focused cases forthreshold - 1,threshold, andthreshold + 1that assert whether the change output is present.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/extension/src/providers/bitcoin/tests/bitcoin.dust.test.ts` around lines 21 - 27, Replace the comparison-only assertions in the dust test with focused transaction-builder cases covering threshold - 1, threshold, and threshold + 1, asserting whether the change output is absent or present as appropriate. Exercise both changed builders, the send flow in the transaction UI and the swap flow in the swap transaction library, using the existing dust-threshold setup and helpers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/extension/src/providers/bitcoin/tests/bitcoin.dust.test.ts`:
- Around line 21-27: Replace the comparison-only assertions in the dust test
with focused transaction-builder cases covering threshold - 1, threshold, and
threshold + 1, asserting whether the change output is absent or present as
appropriate. Exercise both changed builders, the send flow in the transaction UI
and the swap flow in the swap transaction library, using the existing
dust-threshold setup and helpers.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 647c38a1-46f3-4a6e-b782-13cc55dee7d4
📒 Files selected for processing (4)
packages/extension/src/providers/bitcoin/libs/utils.tspackages/extension/src/providers/bitcoin/tests/bitcoin.dust.test.tspackages/extension/src/providers/bitcoin/ui/send-transaction/index.vuepackages/extension/src/ui/action/views/swap/libs/swap-txs.ts
The boundary case compared plain numbers rather than exercising either change output builder, so it would have stayed green if the guard itself regressed. It asserted nothing about this repo, so it is removed rather than left to give false confidence. getDustThreshold, which is the part that can actually regress, is still covered. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@coderabbitai Good catch, and you're right that those assertions were worthless — they compared plain numbers and would have stayed green if either guard regressed. I tried the suggested fix and backed it out. and mocking that just moves the failure one layer down: Stubbing that chain is more scaffolding than this repo has ever needed — no existing test mocks any of it — and it would be a lot of machinery to cover one So rather than leave an assertion that gives false confidence, I removed it in 094663c. Happy to add the full builder test if you or the maintainers think the mocking scaffold is worth introducing here. |
|
Tip For best results, initiate chat on the files or code changes.
Keeping focused coverage for 🐇 |
Problem
A Bitcoin or Litecoin send can fail at broadcast when the amount happens to leave a very small remainder, even though the amount being sent is well above the dust limit.
Root cause
The send screen checks the dust limit in three places, and all three compare the same value — the amount being sent:
send-transaction/index.vue:119(alert visibility)send-transaction/index.vue:133(below-dustprop)send-transaction/index.vue:355(isInputsValid)The change output is never checked against it.
sendActionappends change for any remainder above zero:A remainder of a few hundred base units becomes a dust output, which makes the whole transaction non-standard, so nodes refuse to relay it. The limit that would have caught it is already in the network config — the check was just never pointed at the change.
It bites hardest on Litecoin, whose limit is an order of magnitude above Bitcoin's:
so a remainder worth a fraction of a cent is enough to break the send.
Fix
Compare the remainder against the network's limit and leave it as fee when it falls short, which is the usual wallet behaviour for change that cannot be paid out.
The swap path in
swap/libs/swap-txs.ts:61built its change output the same way, with the sameremainder > 0test, so it is fixed alongside. Both call sites now usegetDustThreshold(), which converts the limit from whole coins to base units in one place.Testing
yarn testinpackages/extension: 13 passing, up from 11.providers/bitcoin/tests/bitcoin.dust.test.tscovers the conversion for every configured network and the comparison at the boundary.vue-tsc --build --forceproduces exactly the same 309 pre-existing errors asdevelop; none added.eslintandprettier --checkclean on the touched files.Notes
I could not find an existing issue or PR covering this.
One thing I noticed while reading the swap path but did not touch, since it is a separate concern:
executeSwapsubtracts the network fee fromoutputs[0], the swap destination, rather than from the change. That means the recipient gets less than the quoted amount. Worth a look, but out of scope here.Written by Claude Opus 5 with human oversight. Every claim above was checked against the source.
Summary by CodeRabbit