Skip to content

Commit 1ed53f2

Browse files
Calgooonclaude
andcommitted
fix(split): dynamic fee reserve scales with tx size (v0.1.22)
The hardcoded 200-sat fee reserve silently failed for large split counts or wallets with many existing UTXOs. A split of 50 outputs from 8 inputs needs ~2900 bytes of tx data, and at BSV's 100 sat/KB fee rate that's ~290 sats of fees — well over the 200 reserve. Result: the toolbox rejected with `Insufficient funds: need X, have Y` where Y was exactly (total_sats - 200), and the split silently failed. Observed during the DolphinSense fleet 2026-04-15 E21-1 recovery when topping up 5 captain wallets to 2.5M each and splitting to 50 UTXOs. The small-count synthesis split (10 outputs) worked fine, confirming the issue scales with output count. Fix: compute fee reserve from estimated tx bytes (inputs × 148 + outputs × 34 + 10) at 1 sat/byte. 1 sat/byte overshoots the actual ~0.1 sat/byte BSV miner rate by 10x — intentional generous cap so future large splits (50+ outputs, 20+ inputs) have unambiguous headroom. A few hundred sats of over-reserve per split is negligible vs. the cost of failures. Minimum floor of 500 sats so tiny splits don't race into dust. Validated on 5 fleet captain wallets: split 50 from 2.55M sats with 8+ input UTXOs now succeeds cleanly, producing 50 × ~50,940 sat outputs with ~2,900 sats going to miner fees. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ef4dffd commit 1ed53f2

3 files changed

Lines changed: 27 additions & 6 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ resolver = "2"
44

55
[package]
66
name = "bsv-wallet-cli"
7-
version = "0.1.21"
7+
version = "0.1.22"
88
edition = "2021"
99
license = "MIT"
1010
authors = ["John Calhoun"]

src/commands/split.rs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,34 @@ pub async fn run(ctx: &WalletContext, count: u32) -> Result<()> {
6868
let lock = P2PKH::lock_from_address(&address)?;
6969
let lock_bytes = lock.to_binary();
7070

71-
// Reserve ~200 sats for fees, split the rest evenly
72-
let fee_reserve: u64 = 200;
71+
// Dynamic fee reserve based on tx size estimate.
72+
//
73+
// The old hardcoded 200-sat reserve was fine for small splits (count<=10,
74+
// few inputs) but silently failed for larger splits or wallets with many
75+
// existing UTXOs — e.g. 50 outputs from 8 inputs exceeded 200 sats of fees
76+
// and the toolbox rejected with `Insufficient funds: need X, have Y` where
77+
// Y was exactly (total_sats - 200). Seen during DolphinSense fleet
78+
// captain split 50 on 2026-04-15.
79+
//
80+
// Estimate follows the usual BSV P2PKH tx footprint:
81+
// inputs: 148 bytes each (sig + pubkey + outpoint + scriptlen + seq)
82+
// outputs: 34 bytes each (value + scriptlen + P2PKH script)
83+
// overhead: ~10 bytes (version + locktime + count varints)
84+
//
85+
// Fee rate: we use 1 sat/byte as a generous cap (BSV miners typically
86+
// accept 0.5 sat/byte or lower). A 1 sat/byte estimate overshoots by ~2x
87+
// which is fine — worst case a few unused sats accumulate on the wallet.
88+
// With this headroom, a split from 15+ inputs to 100+ outputs still fits.
89+
//
90+
// Minimum floor of 500 so tiny splits don't race into dust territory.
91+
let estimated_tx_bytes: u64 = (utxo_count as u64 * 148) + (count as u64 * 34) + 10;
92+
let fee_reserve: u64 = estimated_tx_bytes.max(500);
7393
if total_sats <= fee_reserve {
7494
anyhow::bail!(
75-
"Balance too low to split ({} sats, need > {} for fees)",
95+
"Balance too low to split ({} sats, need > {} for fees at {}b estimate)",
7696
total_sats,
77-
fee_reserve
97+
fee_reserve,
98+
estimated_tx_bytes
7899
);
79100
}
80101
let available = total_sats - fee_reserve;

0 commit comments

Comments
 (0)