A Bitcoin CLI tool that detects dust attack UTXOs in your wallet and sweeps them safely using PSBTs (BIP174).
Connects directly to Bitcoin Core via RPC. No keys touched. No auto-broadcast. You stay in control.
Detect and sweep dust attack UTXOs from your Bitcoin wallet
Usage: dust-cleaner [OPTIONS] --rpc-user <RPC_USER> --rpc-pass <RPC_PASS> <COMMAND>
Commands:
scan Scan wallet for dust UTXOs
sweep Create a PSBT sweeping all dust UTXOs
help Print this message or the help of the given subcommand(s)
Options:
--rpc-url <RPC_URL> Bitcoin Core RPC URL [env: DUST_RPC_URL=] [default: http://127.0.0.1:18443]
--rpc-user <RPC_USER> Bitcoin Core RPC username [env: DUST_RPC_USER=]
--rpc-pass <RPC_PASS> Bitcoin Core RPC password [env: DUST_RPC_PASS=]
--threshold <THRESHOLD> Dust threshold in sats [env: DUST_THRESHOLD=]
-h, --help Print help
-V, --version Print version
A dust attack is a privacy attack where an adversary sends tiny amounts of Bitcoin (called "dust") to your wallet addresses. When you later spend those UTXOs alongside your real funds, the attacker can track the transaction graph to cluster your addresses and de-anonymize your wallet.
Dust amounts vary by script type — too small to spend economically on their own, but large enough to act as a tracking tag.
Further reading:
- Dust attack explained
- Disposing of dust attack UTXOs — Delving Bitcoin
- Dust UTXO Disposal Protocol — BIP draft
- BIP174 — Partially Signed Bitcoin Transactions
- Connects to your Bitcoin Core node via RPC
- Scans your wallet for UTXOs below the dust threshold
- Classifies UTXOs using per-script-type thresholds (or a custom threshold)
- Shows a dry-run preview before committing
- By default, sweeps one UTXO per transaction — no address linking
- Three sweep methods with increasing privacy levels
By default, dust-cleaner uses Bitcoin-accurate per-script-type thresholds:
| Script type | Input size | Dust threshold |
|---|---|---|
| P2PKH | 148 vbytes | 546 sats |
| P2WPKH | 68 vbytes | 294 sats |
| P2TR | 58 vbytes | 294 sats |
| P2SH | 91 vbytes | 540 sats |
Override with --threshold or DUST_THRESHOLD.
| Method | Command | Privacy | Address linking | Output |
|---|---|---|---|---|
| Per-UTXO OP_RETURN | sweep (default) |
✅ highest | ❌ none | OP_RETURN "ash" |
| ANYONECANPAY|ALL | sweep --method anyone-can-pay |
✅ highest + miner batchable | ❌ none | OP_RETURN "ash" |
| Per-UTXO consolidate | sweep --method consolidate |
✅ high | ❌ none | fresh address |
| Batch OP_RETURN | sweep --batch --method op-return |
✅ yes | OP_RETURN "ash" | |
| Batch consolidate | sweep --batch --method consolidate |
❌ lowest | ✅ yes | fresh address |
- Rust (1.70 or later)
- Bitcoin Core node (running and synced)
txindex=1in yourbitcoin.conf(required for--method anyone-can-pay)
git clone https://github.com/Jolah1/dust-cleaner.git
cd dust-cleaner
cargo build --releaseBinary at target/release/dust-cleaner.
mkdir -p ~/.bitcoin/regtest-dev
cat > ~/.bitcoin/regtest-dev/bitcoin.conf << EOF
regtest=1
fallbackfee=0.0001
txindex=1
daemon=1
server=1
[regtest]
rpcuser=user
rpcpassword=pass
rpcport=18443
EOFbitcoind -conf=$HOME/.bitcoin/regtest-dev/bitcoin.conf \
-datadir=$HOME/.bitcoin/regtest-devbitcoin-cli -rpcport=18443 -rpcuser=user -rpcpassword=pass createwallet "testwallet"
ADDRESS=$(bitcoin-cli -rpcport=18443 -rpcuser=user -rpcpassword=pass getnewaddress)
bitcoin-cli -rpcport=18443 -rpcuser=user -rpcpassword=pass generatetoaddress 101 $ADDRESSDUST1=$(bitcoin-cli -rpcport=18443 -rpcuser=user -rpcpassword=pass getnewaddress)
DUST2=$(bitcoin-cli -rpcport=18443 -rpcuser=user -rpcpassword=pass getnewaddress)
DUST3=$(bitcoin-cli -rpcport=18443 -rpcuser=user -rpcpassword=pass getnewaddress)
bitcoin-cli -rpcport=18443 -rpcuser=user -rpcpassword=pass sendtoaddress $DUST1 0.000005
bitcoin-cli -rpcport=18443 -rpcuser=user -rpcpassword=pass sendtoaddress $DUST2 0.000003
bitcoin-cli -rpcport=18443 -rpcuser=user -rpcpassword=pass sendtoaddress $DUST3 0.000008
bitcoin-cli -rpcport=18443 -rpcuser=user -rpcpassword=pass generatetoaddress 1 $ADDRESSexport DUST_RPC_USER=user
export DUST_RPC_PASS=pass
# Scan — detect dust
dust-cleaner --threshold 1000 scan
# Preview sweep
dust-cleaner --threshold 1000 sweep --dry-run
# Sweep with ANYONECANPAY|ALL (most private)
dust-cleaner --threshold 1000 sweep --method anyone-can-pay# The sweep command outputs raw signed hex for each UTXO
# Broadcast each one:
bitcoin-cli -rpcport=18443 -rpcuser=user -rpcpassword=pass sendrawtransaction <hex>
# Confirm
bitcoin-cli -rpcport=18443 -rpcuser=user -rpcpassword=pass generatetoaddress 1 $(bitcoin-cli -rpcport=18443 -rpcuser=user -rpcpassword=pass getnewaddress)
# Verify wallet is clean
dust-cleaner --threshold 1000 scanexport DUST_RPC_URL=http://127.0.0.1:18443
export DUST_RPC_USER=user
export DUST_RPC_PASS=passdust-cleaner scanOutput:
Found 6 total UTXOs (threshold: per-script-type (P2PKH:546, P2WPKH:294, P2TR:294, P2SH:540))
⚠️ DUST UTXOs (3 found):
500 sats | 3a8c360f...:0 | P2WPKH | bcrt1q60z...
300 sats | 87ef9f5b...:1 | P2WPKH | bcrt1qdpl...
800 sats | 38942b1e...:1 | P2WPKH | bcrt1qr0e...
✅ CLEAN UTXOs (3 found):
4999999860 sats | 81452410...:0
...
📊 Summary
Dust UTXOs: 3 (1600 sats)
Clean UTXOs: 3 (14999975400 sats)
dust-cleaner sweep --dry-rundust-cleaner --threshold 1000 sweep --method anyone-can-payOutput:
⚡ Method: anyonecanpay|all — maximum privacy
Sighash: SIGHASH_ALL | SIGHASH_ANYONECANPAY
Each input signed independently — no address linking
Outputs locked — miners can add inputs but not change outputs
Miners can batch these transactions permissionlessly
📊 Generated 3 signed transactions:
─── Tx 1 of 3 ───
Address: bcrt1q62sx9...
Dust: 300 sats → miner fees
Hex: 02000000...
─── Tx 2 of 3 ───
Address: bcrt1q4x2qz...
Dust: 500 sats → miner fees
Hex: 02000000...
─── Tx 3 of 3 ───
Address: bcrt1qyk3sa...
Dust: 800 sats → miner fees
Hex: 02000000...
💡 Broadcast each transaction:
bitcoin-cli sendrawtransaction <hex>
⚠️ Broadcast at different times to prevent timing correlation.
dust-cleaner sweepdust-cleaner sweep --batch
# With privacy warning shown automatically
⚠️ Mode: batch — all dust UTXOs swept in one transaction
Warning: this links all dust addresses on-chain.dust-cleaner --threshold 1000 scan
export DUST_THRESHOLD=1000
dust-cleaner scan| Flag | Environment Variable | Default | Description |
|---|---|---|---|
--rpc-url |
DUST_RPC_URL |
http://127.0.0.1:18443 |
Bitcoin Core RPC URL |
--rpc-user |
DUST_RPC_USER |
required | RPC username |
--rpc-pass |
DUST_RPC_PASS |
required | RPC password |
--threshold |
DUST_THRESHOLD |
per-script-type | Custom dust threshold in sats |
cargo test15 tests covering dust detection, script type detection, per-type thresholds, UTXO classification edge cases, and smart threshold with user override.
dust-cleaner/
├── src/
│ ├── main.rs # CLI entry point
│ ├── lib.rs # Public module interface
│ ├── cli.rs # CLI argument definitions (clap)
│ ├── rpc.rs # Bitcoin Core RPC connection
│ ├── scanner.rs # UTXO fetching via list_unspent
│ ├── analyzer.rs # Dust detection and classification
│ ├── psbt_builder.rs # PSBT construction, dry-run, ANYONECANPAY|ALL
│ └── types.rs # Owned Utxo type for testing
├── docs/
│ └── design.md
├── JOURNAL.md
└── README.md
- BIP174 — Partially Signed Bitcoin Transactions
- BIP143 — Sighash types
- Dust UTXO Disposal Protocol — BIP draft
- Bitcoin Core RPC documentation
- rust-bitcoin crate
- bitcoincore-rpc crate
- Disposing of dust attack UTXOs — Delving Bitcoin
MIT