Skip to content

fix(chain-adapters): prevent UTXO sats/byte rounding to zero#12307

Merged
kaladinlight merged 3 commits intodevelopfrom
fix/utxo-fee-sats-per-byte-rounding
Apr 28, 2026
Merged

fix(chain-adapters): prevent UTXO sats/byte rounding to zero#12307
kaladinlight merged 3 commits intodevelopfrom
fix/utxo-fee-sats-per-byte-rounding

Conversation

@kaladinlight
Copy link
Copy Markdown
Contributor

Description

UTXO fee estimation converts satsPerKiloByte (returned by unchained) into the per-byte rate that the coinselect library requires. The conversion used Math.round(satsPerKiloByte / 1000), which collapses any rate below ~500 sats/kB to 0.

This is a real condition during low-fee periods. For example, a recent observation:

slow:    { satsPerKiloByte: 291,  blocksUntilConfirmation: 10 }
average: { satsPerKiloByte: 302,  blocksUntilConfirmation: 5  }
fast:    { satsPerKiloByte: 2227, blocksUntilConfirmation: 2  }

Math.round(291/1000) = 0 and Math.round(302/1000) = 0, so slow/average tiers come back with a 0 per-byte rate. Downstream, buildSendApiTransaction rejects this via if (!satoshiPerByte) throw new Error('satoshiPerByte is required'), breaking the lower-fee tiers entirely.

Fix: use Math.ceil with a 1 sat/byte floor. Math.max(1, Math.ceil(x / 1000)) guarantees a valid positive rate for any non-negative input, and rounding up biases slightly toward inclusion rather than under-paying.

Applied at both UTXO conversion sites:

  • packages/chain-adapters/src/utxo/UtxoBaseAdapter.ts (BTC, BCH, LTC, ZEC)
  • packages/chain-adapters/src/utxo/dogecoin/DogecoinChainAdapter.ts (DOGE)

The Dogecoin adapter already had a <=0 guard with high default minimums, so it was less likely to trip in practice — but the same math issue applied for any small positive upstream value, and consistency matters.

Issue (if applicable)

closes #

Risk

High Risk PRs Require 2 approvals

What protocols, transaction types, wallets or contract interactions might be affected by this PR?

UTXO chains: BTC, BCH, LTC, ZEC, DOGE. Affects only getFeeData output (slow/average/fast per-byte rates and resulting txFee). No change to signing or broadcasting paths.

The output rate may now be slightly higher than before for the same upstream value (ceil vs round) — by at most ~0.5 sat/byte at the boundary — but this only matters when the upstream rate sits exactly on a half-byte boundary, which is rare.

Testing

Engineering

  • Send BTC during a low-fee window (or stub getNetworkFees to return e.g. { slow: 291, average: 302, fast: 2227 }); confirm slow/average/fast tiers all yield positive satoshiPerByte and a valid txFee.
  • Send BTC during a normal-fee window; confirm fees are within the same ballpark as before.
  • Send DOGE; confirm no regression (per-byte rates remain in expected range).
  • Repeat for BCH, LTC, ZEC.

Operations

  • 🏁 My feature is behind a flag and doesn't require operations testing (yet)

Screenshots (if applicable)

n/a

Math.round(satsPerKiloByte / 1000) collapses to 0 when network fees are
below ~500 sats/kB (common for slow/average tiers in low-fee periods),
producing an invalid 0 sat/byte rate that fails downstream validation.

Switch to Math.ceil with a 1 sat/byte floor so sub-1000 sats/kB rates
always yield a valid positive per-byte fee.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kaladinlight kaladinlight requested a review from a team as a code owner April 28, 2026 17:54
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 2026

Warning

Rate limit exceeded

@kaladinlight has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 32 minutes and 16 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7bb337fc-e156-4d57-a176-d8f00c9a110f

📥 Commits

Reviewing files that changed from the base of the PR and between 4916056 and d64d0da.

📒 Files selected for processing (6)
  • packages/chain-adapters/src/utxo/UtxoBaseAdapter.ts
  • packages/chain-adapters/src/utxo/bitcoin/BitcoinChainAdapter.test.ts
  • packages/chain-adapters/src/utxo/bitcoincash/BitcoinCashChainAdapter.test.ts
  • packages/chain-adapters/src/utxo/dogecoin/DogecoinChainAdapter.test.ts
  • packages/chain-adapters/src/utxo/dogecoin/DogecoinChainAdapter.ts
  • packages/chain-adapters/src/utxo/litecoin/LitecoinChainAdapter.test.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/utxo-fee-sats-per-byte-rounding

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

kaladinlight and others added 2 commits April 28, 2026 12:05
Update getNetworkFeesMockedResponse from 1024 to 1000 sats/kB across
BTC, BCH, LTC, and DOGE adapter tests so the canonical "1 sat/byte → 44
sat fee" expectation holds under the new Math.ceil + 1 sat/byte floor
conversion in getFeeData.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kaladinlight kaladinlight enabled auto-merge (squash) April 28, 2026 18:21
@kaladinlight kaladinlight merged commit 0969633 into develop Apr 28, 2026
4 checks passed
@kaladinlight kaladinlight deleted the fix/utxo-fee-sats-per-byte-rounding branch April 28, 2026 18:31
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