Disappear Into the Crowd: Wasabi's Hidden Payment Superpower

npub1klkk3vrzme455yh9rl2jshq7rc8dpegj3ndf82c3ks2sk40dxt7qulx3vt
hex
69de157db1b181402b42762729455914774177f6253e7d734193dc1d53054344nevent
nevent1qqsxnhs40kcmrq2q9dp8vfefg4v3ga6pwlmz20nawdqe8hqa2vz5x3qprpmhxue69uhhyetvv9ujuem4d36kwatvw5hx6mm9qgst0mtgkp3du662ztj3l4fgts0purksu5fgek5n4vgmg9gt2hkn9lqpjy950naddr
naddr1qqgxywf5xg6ryerxvdnrjwf5vscxxqgcwaehxw309aex2mrp0yhxwatvw4nh2mr49ekk7egzyzm7669svt0xkjsju50a22zurc0qa589z2xd4yatzx6p2z64a5e0cqcyqqq823c935ee0Kind-30023 (Article)
The best Bitcoin privacy feature you're overlooking
Wasabi Wallet has a feature called payincoinjoin that most users overlook. The typical flow coinjoins your coins, sends the payment, then coinjoins the change as a separate step. With payincoinjoin, the payment embeds directly inside the coinjoin transaction itself.
The result: your payment lands cheaper and more private, one of dozens of identical-looking outputs. Chain analysts see a coinjoin with many participants. Your payment and the change outputs are indistinguishable from one another. With a single transaction, there is no separate step to correlate, no timing gap to analyze, and no amount fingerprinting when you use standard denominations.
The Problem
Wasabi's RPC interface exposes this feature, but the raw commands are ugly:
curl -s -d '{"jsonrpc":"2.0","id":"1","method":"payincoinjoin","params":["tb1q...",50000]}' http://127.0.0.1:37128/MyWallet
And then you have to manually start coinjoin, babysit it, check if payments went through, and remember to stop it when done.
The solution: three scripts
I vibed three interactive scripts that make this workflow painless.
wpay.sh - Queue Payments
Queue multiple payments with smart denomination suggestions:
$ ./wpay.sh
Wallets:
[1] savings
[2] spending
Select wallet: 2
Loading wallet spending (this may take a moment)...
Wallet ready.
=== Add Payments ===
Address: tb1qxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Amount (sats): 75000
Standard denominations blend better in coinjoins.
Options:
[L] Send less: 65536 sats (-9464 sats, -12.62%)
[M] Send more: 100000 sats (+25000 sats, +33.33%)
[E] Exact amount: 75000 sats (non-standard)
Choice [L/M/E]: m
Queued: 100000 sats -> tb1qxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Payment ID: 65c21ec1-9865-4cd6-bd67-c2f058a45d24
Add another payment? [Y/n]: n
Done. Run wcj.sh to start coinjoin.
The script suggests rounding to standard denominations because they blend in with other coinjoin outputs. A payment of exactly 73,847 sats stands out. A payment of 65,536 sats looks like everyone else's change.
wcancel.sh - Cancel Payments
Made a mistake? Cancel payments interactively:
$ ./wcancel.sh
Wallets:
[1] savings
[2] spending
Select wallet: 2
Loading wallet spending (this may take a moment)...
Wallet ready.
=== Pending Payments ===
[1] 100000 sats -> tb1qxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
[2] 50000 sats -> tb1qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
[A] Cancel all
[Q] Quit
Cancel which? 2
Cancelled: 50000 sats -> tb1qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
Select by number, cancel multiple with 1,3 or 1 3, or cancel all with A.
wcj.sh - Run Coinjoin
Start coinjoin and monitor until all payments complete:
$ ./wcj.sh
Wallets:
[1] savings
[2] spending
Select wallet: 2
Loading wallet spending (this may take a moment)...
Wallet ready.
=== Wallet: spending ===
Pending payments:
100000 sats -> tb1qxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
50000 sats -> tb1qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
=== CoinJoin started ===
[14:32:15] Sent: 50000 sats -> tb1qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
[14:45:02] Sent: 100000 sats -> tb1qxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
=== All payments done ===
CoinJoin stopped
The script:
- Shows initial state
- Starts coinjoin automatically
- Detects when payments complete
- Detects if you add new payments while running
- Stops coinjoin when done
- Handles Ctrl+C gracefully
Setup
- Enable RPC in Wasabi's
Config.json:
"JsonRpcServerEnabled": true
- Make scripts executable:
chmod +x wpay.sh wcancel.sh wcj.sh
- Requirements:
curl,jq
Typical Workflow
./wpay.sh # Queue your payments
./wcj.sh # Start coinjoin, wait for completion
That's it. Your payments disappear into the crowd.
Full Scripts
wpay.sh
#!/usr/bin/env bash
# Wasabi Pay in CoinJoin
# Interactive payment queuing with standard denomination suggestions
RPC=" http://127.0.0.1:37128"
# Check RPC connection
status=$(curl -s --connect-timeout 3 -d '{"jsonrpc":"2.0","id":"1","method":"getstatus"}' "$RPC" 2>/dev/null)
if [ -z "$status" ]; then
echo "Error: Cannot connect to Wasabi RPC at $RPC"
echo "Make sure Wasabi is running and RPC is enabled in Config.json"
exit 1
fi
DENOMS=(5000 6561 8192 10000 13122 16384 19683 20000 32768 39366 50000 59049 65536 100000 118098 131072 177147 200000 262144 354294 500000 524288 531441 1000000 1048576 1062882 1594323 2000000 2097152 3188646 4194304 4782969 5000000 8388608 9565938 10000000 14348907 16777216 20000000 28697814 33554432 43046721 50000000 67108864 86093442 100000000 129140163 134217728 200000000 258280326 268435456 387420489 500000000 536870912 774840978 1000000000 1073741824 1162261467 2000000000 2147483648 2324522934 3486784401 4294967296 5000000000 6973568802 8589934592 10000000000 10460353203 17179869184 20000000000 20920706406 31381059609 34359738368 50000000000 62762119218 68719476736 94143178827 100000000000 137438953472)
# Get wallet list
wallets=$(curl -s -d '{"jsonrpc":"2.0","id":"1","method":"listwallets"}' "$RPC" | jq -r '.result')
wallet_count=$(echo "$wallets" | jq 'length')
if [ "$wallet_count" -eq 0 ]; then
echo "No wallets found."
exit 1
fi
# Select wallet
echo "Wallets:"
echo ""
for i in $(seq 0 $((wallet_count - 1))); do
num=$((i + 1))
name=$(echo "$wallets" | jq -r ".[$i].walletName")
echo " [$num] $name"
done
echo ""
read -p "Select wallet: " wallet_choice
idx=$((wallet_choice - 1))
if [ "$idx" -lt 0 ] || [ "$idx" -ge "$wallet_count" ]; then
echo "Invalid selection."
exit 1
fi
WALLET=$(echo "$wallets" | jq -r ".[$idx].walletName")
# Load wallet if not already loaded
echo ""
echo "Loading wallet $WALLET (this may take a moment)..."
load_result=$(curl -s -d '{"jsonrpc":"2.0","id":"1","method":"loadwallet","params":["'"$WALLET"'"]}' "$RPC")
load_error=$(echo "$load_result" | jq -r '.error.message // empty')
if [ -n "$load_error" ] && [[ "$load_error" != *"already"* ]]; then
echo "Error loading wallet: $load_error"
exit 1
fi
echo "Wallet ready."
echo ""
echo "=== Add Payments ==="
add_payment() {
echo ""
# Get address
read -p "Address: " ADDRESS
if [ -z "$ADDRESS" ]; then
echo "Address required."
return 1
fi
# Get amount
read -p "Amount (sats): " AMOUNT
if ! [[ "$AMOUNT" =~ ^[0-9]+$ ]]; then
echo "Invalid amount."
return 1
fi
# Find nearest denominations
local lower=""
local higher=""
local exact=""
for d in "${DENOMS[@]}"; do
if [ "$d" -eq "$AMOUNT" ]; then
exact="$d"
break
elif [ "$d" -lt "$AMOUNT" ]; then
lower="$d"
elif [ "$d" -gt "$AMOUNT" ] && [ -z "$higher" ]; then
higher="$d"
break
fi
done
# Calculate differences
local lower_diff lower_pct higher_diff higher_pct
if [ -n "$lower" ]; then
lower_diff=$((AMOUNT - lower))
lower_pct=$(awk "BEGIN {printf \"%.2f\", ($lower_diff / $AMOUNT) * 100}")
fi
if [ -n "$higher" ]; then
higher_diff=$((higher - AMOUNT))
higher_pct=$(awk "BEGIN {printf \"%.2f\", ($higher_diff / $AMOUNT) * 100}")
fi
# Display options
echo ""
echo "Standard denominations blend better in coinjoins."
echo ""
local selected
if [ -n "$exact" ]; then
echo "Your amount is already a standard denomination."
selected="$exact"
else
echo "Options:"
echo ""
if [ -n "$lower" ]; then
echo " [L] Send less: $lower sats (-$lower_diff sats, -$lower_pct%)"
fi
if [ -n "$higher" ]; then
echo " [M] Send more: $higher sats (+$higher_diff sats, +$higher_pct%)"
fi
echo " [E] Exact amount: $AMOUNT sats (non-standard)"
echo ""
read -p "Choice [L/M/E]: " choice
case "${choice^^}" in
L)
if [ -n "$lower" ]; then
selected="$lower"
else
echo "No lower denomination available."
return 1
fi
;;
M)
if [ -n "$higher" ]; then
selected="$higher"
else
echo "No higher denomination available."
return 1
fi
;;
E)
selected="$AMOUNT"
;;
*)
echo "Invalid choice."
return 1
;;
esac
fi
# Send payment
local result error payment_id
result=$(curl -s -d '{"jsonrpc":"2.0","id":"1","method":"payincoinjoin","params":["'"$ADDRESS"'",'"$selected"']}' "$RPC/$WALLET")
error=$(echo "$result" | jq -r '.error.message // empty')
if [ -n "$error" ]; then
echo "Error: $error"
return 1
fi
payment_id=$(echo "$result" | jq -r '.result')
echo ""
echo "Queued: $selected sats -> $ADDRESS"
echo "Payment ID: $payment_id"
return 0
}
# Main loop
while true; do
add_payment
echo ""
read -p "Add another payment? [Y/n]: " again
if [[ "${again^^}" == "N" ]]; then
break
fi
done
echo ""
echo "Done. Run wcj.sh to start coinjoin."
wcancel.sh
#!/usr/bin/env bash
# Wasabi Cancel Payments in CoinJoin
# Interactive selection to cancel pending payments
RPC=" http://127.0.0.1:37128"
# Check RPC connection
status=$(curl -s --connect-timeout 3 -d '{"jsonrpc":"2.0","id":"1","method":"getstatus"}' "$RPC" 2>/dev/null)
if [ -z "$status" ]; then
echo "Error: Cannot connect to Wasabi RPC at $RPC"
echo "Make sure Wasabi is running and RPC is enabled in Config.json"
exit 1
fi
# Get wallet list
wallets=$(curl -s -d '{"jsonrpc":"2.0","id":"1","method":"listwallets"}' "$RPC" | jq -r '.result')
wallet_count=$(echo "$wallets" | jq 'length')
if [ "$wallet_count" -eq 0 ]; then
echo "No wallets found."
exit 1
fi
# Select wallet
echo "Wallets:"
echo ""
for i in $(seq 0 $((wallet_count - 1))); do
num=$((i + 1))
name=$(echo "$wallets" | jq -r ".[$i].walletName")
echo " [$num] $name"
done
echo ""
read -p "Select wallet: " wallet_choice
idx=$((wallet_choice - 1))
if [ "$idx" -lt 0 ] || [ "$idx" -ge "$wallet_count" ]; then
echo "Invalid selection."
exit 1
fi
WALLET=$(echo "$wallets" | jq -r ".[$idx].walletName")
# Load wallet if not already loaded
echo ""
echo "Loading wallet $WALLET (this may take a moment)..."
load_result=$(curl -s -d '{"jsonrpc":"2.0","id":"1","method":"loadwallet","params":["'"$WALLET"'"]}' "$RPC")
load_error=$(echo "$load_result" | jq -r '.error.message // empty')
if [ -n "$load_error" ] && [[ "$load_error" != *"already"* ]]; then
echo "Error loading wallet: $load_error"
exit 1
fi
echo "Wallet ready."
echo ""
echo "=== Pending Payments ==="
echo ""
# Get pending payments
result=$(curl -s -d '{"jsonrpc":"2.0","id":"1","method":"listpaymentsincoinjoin"}' "$RPC/$WALLET")
error=$(echo "$result" | jq -r '.error.message // empty')
if [ -n "$error" ]; then
echo "Error: $error"
exit 1
fi
payments=$(echo "$result" | jq -r '.result')
count=$(echo "$payments" | jq 'length')
if [ "$count" -eq 0 ]; then
echo "No pending payments."
exit 0
fi
# Display numbered list
for i in $(seq 0 $((count - 1))); do
num=$((i + 1))
amount=$(echo "$payments" | jq -r ".[$i].amount")
address=$(echo "$payments" | jq -r ".[$i].address")
echo " [$num] $amount sats -> $address"
done
echo ""
echo " [A] Cancel all"
echo " [Q] Quit"
echo ""
read -p "Cancel which? " choice
# Quit
if [[ "${choice^^}" == "Q" ]]; then
exit 0
fi
# Cancel all
if [[ "${choice^^}" == "A" ]]; then
echo ""
ids=$(echo "$payments" | jq -r '.[].id')
for id in $ids; do
curl -s -d '{"jsonrpc":"2.0","id":"1","method":"cancelpaymentincoinjoin","params":["'"$id"'"]}' "$RPC/$WALLET" > /dev/null
amount=$(echo "$payments" | jq -r ".[] | select(.id == \"$id\") | .amount")
address=$(echo "$payments" | jq -r ".[] | select(.id == \"$id\") | .address")
echo "Cancelled: $amount sats -> $address"
done
exit 0
fi
# Cancel specific numbers (comma or space separated)
selections=$(echo "$choice" | tr ',' ' ')
for sel in $selections; do
if ! [[ "$sel" =~ ^[0-9]+$ ]]; then
echo "Invalid selection: $sel"
continue
fi
idx=$((sel - 1))
if [ "$idx" -lt 0 ] || [ "$idx" -ge "$count" ]; then
echo "Invalid selection: $sel"
continue
fi
id=$(echo "$payments" | jq -r ".[$idx].id")
amount=$(echo "$payments" | jq -r ".[$idx].amount")
address=$(echo "$payments" | jq -r ".[$idx].address")
cancel_result=$(curl -s -d '{"jsonrpc":"2.0","id":"1","method":"cancelpaymentincoinjoin","params":["'"$id"'"]}' "$RPC/$WALLET")
cancel_error=$(echo "$cancel_result" | jq -r '.error.message // empty')
if [ -n "$cancel_error" ]; then
echo "Failed to cancel [$sel]: $cancel_error"
else
echo "Cancelled: $amount sats -> $address"
fi
done
wcj.sh
#!/usr/bin/env bash
# Wasabi CoinJoin Payment Runner
# Starts coinjoin and monitors payments, adapting to new/cancelled payments
RPC=" http://127.0.0.1:37128"
# Check RPC connection
status=$(curl -s --connect-timeout 3 -d '{"jsonrpc":"2.0","id":"1","method":"getstatus"}' "$RPC" 2>/dev/null)
if [ -z "$status" ]; then
echo "Error: Cannot connect to Wasabi RPC at $RPC"
echo "Make sure Wasabi is running and RPC is enabled in Config.json"
exit 1
fi
# Get wallet list
wallets=$(curl -s -d '{"jsonrpc":"2.0","id":"1","method":"listwallets"}' "$RPC" | jq -r '.result')
wallet_count=$(echo "$wallets" | jq 'length')
if [ "$wallet_count" -eq 0 ]; then
echo "No wallets found."
exit 1
fi
# Select wallet
echo "Wallets:"
echo ""
for i in $(seq 0 $((wallet_count - 1))); do
num=$((i + 1))
name=$(echo "$wallets" | jq -r ".[$i].walletName")
echo " [$num] $name"
done
echo ""
read -p "Select wallet: " wallet_choice
idx=$((wallet_choice - 1))
if [ "$idx" -lt 0 ] || [ "$idx" -ge "$wallet_count" ]; then
echo "Invalid selection."
exit 1
fi
WALLET=$(echo "$wallets" | jq -r ".[$idx].walletName")
# Load wallet if not already loaded
echo ""
echo "Loading wallet $WALLET (this may take a moment)..."
load_result=$(curl -s -d '{"jsonrpc":"2.0","id":"1","method":"loadwallet","params":["'"$WALLET"'"]}' "$RPC")
load_error=$(echo "$load_result" | jq -r '.error.message // empty')
if [ -n "$load_error" ] && [[ "$load_error" != *"already"* ]]; then
echo "Error loading wallet: $load_error"
exit 1
fi
echo "Wallet ready."
# Handle Ctrl+C gracefully
cleanup() {
echo ""
echo "Stopping coinjoin..."
curl -s -d '{"jsonrpc":"2.0","id":"1","method":"stopcoinjoin"}' "$RPC/$WALLET" > /dev/null
echo "CoinJoin stopped."
exit 0
}
trap cleanup SIGINT
get_pending() {
curl -s -d '{"jsonrpc":"2.0","id":"1","method":"listpaymentsincoinjoin"}' "$RPC/$WALLET" \
| jq '[.result[] | select(.state[0].status == "Pending")] | sort_by(.address)'
}
show_pending() {
local payments="$1"
local count=$(echo "$payments" | jq 'length')
if [ "$count" -eq 0 ]; then
echo " (none)"
else
echo "$payments" | jq -r '.[] | " \(.amount) sats -> \(.address)"'
fi
}
# Show initial state
echo ""
echo "=== Wallet: $WALLET ==="
echo ""
echo "Pending payments:"
prev=$(get_pending)
show_pending "$prev"
prev_count=$(echo "$prev" | jq 'length')
if [ "$prev_count" -eq 0 ]; then
echo ""
read -p "No pending payments. Start coinjoin anyway? [y/N]: " confirm
if [[ "${confirm^^}" != "Y" ]]; then
exit 0
fi
fi
# Start coinjoin
echo ""
curl -s -d '{"jsonrpc":"2.0","id":"1","method":"startcoinjoin","params":["",false,true]}' "$RPC/$WALLET" > /dev/null
echo "=== CoinJoin started ==="
echo ""
# Track payments
prev_addrs=$(echo "$prev" | jq -r '.[].address' | sort)
ever_had_payments=false
if [ "$prev_count" -gt 0 ]; then
ever_had_payments=true
fi
while true; do
curr=$(get_pending)
curr_count=$(echo "$curr" | jq 'length')
curr_addrs=$(echo "$curr" | jq -r '.[].address' | sort)
# Track if we ever had payments
if [ "$curr_count" -gt 0 ]; then
ever_had_payments=true
fi
# Check for completed payments
if [ -n "$prev_addrs" ]; then
for addr in $prev_addrs; do
if ! echo "$curr_addrs" | grep -q "^${addr}$"; then
amount=$(echo "$prev" | jq -r ".[] | select(.address == \"$addr\") | .amount")
echo "[$(date +%H:%M:%S)] Sent: $amount sats -> $addr"
fi
done
fi
# Check for new payments
if [ -n "$curr_addrs" ]; then
for addr in $curr_addrs; do
if [ -z "$prev_addrs" ] || ! echo "$prev_addrs" | grep -q "^${addr}$"; then
amount=$(echo "$curr" | jq -r ".[] | select(.address == \"$addr\") | .amount")
echo "[$(date +%H:%M:%S)] Added: $amount sats -> $addr"
fi
done
fi
# All done? Only exit if we ever had payments and now have none
if [ "$curr_count" -eq 0 ] && [ "$ever_had_payments" = true ]; then
break
fi
# Update state
prev="$curr"
prev_count="$curr_count"
prev_addrs="$curr_addrs"
sleep 30
done
# Final state
echo ""
echo "=== All payments done ==="
curl -s -d '{"jsonrpc":"2.0","id":"1","method":"stopcoinjoin"}' "$RPC/$WALLET" > /dev/null
echo "CoinJoin stopped"
原始 JSON
{
"kind": 30023,
"id": "69de157db1b181402b42762729455914774177f6253e7d734193dc1d53054344",
"pubkey": "b7ed68b062de6b4a12e51fd5285c1e1e0ed0e5128cda93ab11b4150b55ed32fc",
"created_at": 1777543606,
"tags": [
[
"d",
"b94242dfcf994d0c"
],
[
"image",
"https://image.nostr.build/f4f78b26b50cbac0beac77b721ae5827a4e451112f42bde49bfcbca1b8588af2.jpg"
],
[
"title",
"Disappear Into the Crowd: Wasabi's Hidden Payment Superpower"
],
[
"summary",
"Three bash scripts to queue, cancel, and run Wasabi coinjoin payments from terminal - with smart denomination suggestions for maximum privacy."
],
[
"published_at",
"1764601762"
],
[
"t",
"wasabi"
],
[
"t",
"coinjoin"
],
[
"t",
"bitcoin"
],
[
"t",
"privacy"
],
[
"t",
"anonymity"
],
[
"t",
"opsec"
],
[
"t",
"freedom-tech"
],
[
"t",
"austrian-economics"
],
[
"t",
"wasabi-wallet"
]
],
"content": "## The best Bitcoin privacy feature you're overlooking\n\nWasabi Wallet has a feature called `payincoinjoin` that most users overlook. The typical flow coinjoins your coins, sends the payment, then coinjoins the change as a separate step. With `payincoinjoin`, the payment embeds directly inside the coinjoin transaction itself.\n\nThe result: your payment lands cheaper and more private, one of dozens of identical-looking outputs. Chain analysts see a coinjoin with many participants. Your payment and the change outputs are indistinguishable from one another. With a single transaction, there is no separate step to correlate, no timing gap to analyze, and no amount fingerprinting when you use standard denominations.\n\n## The Problem\n\nWasabi's RPC interface exposes this feature, but the raw commands are ugly:\n\n```bash\ncurl -s -d '{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"method\":\"payincoinjoin\",\"params\":[\"tb1q...\",50000]}' http://127.0.0.1:37128/MyWallet\n```\n\nAnd then you have to manually start coinjoin, babysit it, check if payments went through, and remember to stop it when done.\n\n## The solution: three scripts\n\nI vibed three interactive scripts that make this workflow painless.\n\n### wpay.sh - Queue Payments\n\nQueue multiple payments with smart denomination suggestions:\n\n```\n$ ./wpay.sh\nWallets:\n\n [1] savings\n [2] spending\n\nSelect wallet: 2\n\nLoading wallet spending (this may take a moment)...\nWallet ready.\n\n=== Add Payments ===\n\nAddress: tb1qxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\nAmount (sats): 75000\n\nStandard denominations blend better in coinjoins.\n\nOptions:\n\n [L] Send less: 65536 sats (-9464 sats, -12.62%)\n [M] Send more: 100000 sats (+25000 sats, +33.33%)\n [E] Exact amount: 75000 sats (non-standard)\n\nChoice [L/M/E]: m\n\nQueued: 100000 sats -\u003e tb1qxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\nPayment ID: 65c21ec1-9865-4cd6-bd67-c2f058a45d24\n\nAdd another payment? [Y/n]: n\n\nDone. Run wcj.sh to start coinjoin.\n```\n\nThe script suggests rounding to standard denominations because they blend in with other coinjoin outputs. A payment of exactly 73,847 sats stands out. A payment of 65,536 sats looks like everyone else's change.\n\n### wcancel.sh - Cancel Payments\n\nMade a mistake? Cancel payments interactively:\n\n```\n$ ./wcancel.sh\nWallets:\n\n [1] savings\n [2] spending\n\nSelect wallet: 2\n\nLoading wallet spending (this may take a moment)...\nWallet ready.\n\n=== Pending Payments ===\n\n [1] 100000 sats -\u003e tb1qxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n [2] 50000 sats -\u003e tb1qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\n\n [A] Cancel all\n [Q] Quit\n\nCancel which? 2\nCancelled: 50000 sats -\u003e tb1qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\n```\n\nSelect by number, cancel multiple with `1,3` or `1 3`, or cancel all with `A`.\n\n### wcj.sh - Run Coinjoin\n\nStart coinjoin and monitor until all payments complete:\n\n```\n$ ./wcj.sh\nWallets:\n\n [1] savings\n [2] spending\n\nSelect wallet: 2\n\nLoading wallet spending (this may take a moment)...\nWallet ready.\n\n=== Wallet: spending ===\n\nPending payments:\n 100000 sats -\u003e tb1qxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n 50000 sats -\u003e tb1qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\n\n=== CoinJoin started ===\n\n[14:32:15] Sent: 50000 sats -\u003e tb1qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\n[14:45:02] Sent: 100000 sats -\u003e tb1qxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n=== All payments done ===\nCoinJoin stopped\n```\n\nThe script:\n- Shows initial state\n- Starts coinjoin automatically\n- Detects when payments complete\n- Detects if you add new payments while running\n- Stops coinjoin when done\n- Handles Ctrl+C gracefully\n\n## Setup\n\n1. Enable RPC in Wasabi's `Config.json`:\n```json\n\"JsonRpcServerEnabled\": true\n```\n\n2. Make scripts executable:\n```bash\nchmod +x wpay.sh wcancel.sh wcj.sh\n```\n\n3. Requirements: `curl`, `jq`\n\n## Typical Workflow\n\n```bash\n./wpay.sh # Queue your payments\n./wcj.sh # Start coinjoin, wait for completion\n```\n\nThat's it. Your payments disappear into the crowd.\n\n---\n\n## Full Scripts\n\n### wpay.sh\n\n```bash\n#!/usr/bin/env bash\n\n# Wasabi Pay in CoinJoin\n# Interactive payment queuing with standard denomination suggestions\n\nRPC=\" http://127.0.0.1:37128\"\n\n# Check RPC connection\nstatus=$(curl -s --connect-timeout 3 -d '{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"method\":\"getstatus\"}' \"$RPC\" 2\u003e/dev/null)\nif [ -z \"$status\" ]; then\n echo \"Error: Cannot connect to Wasabi RPC at $RPC\"\n echo \"Make sure Wasabi is running and RPC is enabled in Config.json\"\n exit 1\nfi\n\nDENOMS=(5000 6561 8192 10000 13122 16384 19683 20000 32768 39366 50000 59049 65536 100000 118098 131072 177147 200000 262144 354294 500000 524288 531441 1000000 1048576 1062882 1594323 2000000 2097152 3188646 4194304 4782969 5000000 8388608 9565938 10000000 14348907 16777216 20000000 28697814 33554432 43046721 50000000 67108864 86093442 100000000 129140163 134217728 200000000 258280326 268435456 387420489 500000000 536870912 774840978 1000000000 1073741824 1162261467 2000000000 2147483648 2324522934 3486784401 4294967296 5000000000 6973568802 8589934592 10000000000 10460353203 17179869184 20000000000 20920706406 31381059609 34359738368 50000000000 62762119218 68719476736 94143178827 100000000000 137438953472)\n\n# Get wallet list\nwallets=$(curl -s -d '{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"method\":\"listwallets\"}' \"$RPC\" | jq -r '.result')\nwallet_count=$(echo \"$wallets\" | jq 'length')\n\nif [ \"$wallet_count\" -eq 0 ]; then\n echo \"No wallets found.\"\n exit 1\nfi\n\n# Select wallet\necho \"Wallets:\"\necho \"\"\nfor i in $(seq 0 $((wallet_count - 1))); do\n num=$((i + 1))\n name=$(echo \"$wallets\" | jq -r \".[$i].walletName\")\n echo \" [$num] $name\"\ndone\necho \"\"\nread -p \"Select wallet: \" wallet_choice\n\nidx=$((wallet_choice - 1))\nif [ \"$idx\" -lt 0 ] || [ \"$idx\" -ge \"$wallet_count\" ]; then\n echo \"Invalid selection.\"\n exit 1\nfi\n\nWALLET=$(echo \"$wallets\" | jq -r \".[$idx].walletName\")\n\n# Load wallet if not already loaded\necho \"\"\necho \"Loading wallet $WALLET (this may take a moment)...\"\nload_result=$(curl -s -d '{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"method\":\"loadwallet\",\"params\":[\"'\"$WALLET\"'\"]}' \"$RPC\")\nload_error=$(echo \"$load_result\" | jq -r '.error.message // empty')\n\nif [ -n \"$load_error\" ] \u0026\u0026 [[ \"$load_error\" != *\"already\"* ]]; then\n echo \"Error loading wallet: $load_error\"\n exit 1\nfi\necho \"Wallet ready.\"\necho \"\"\necho \"=== Add Payments ===\"\n\nadd_payment() {\n echo \"\"\n # Get address\n read -p \"Address: \" ADDRESS\n if [ -z \"$ADDRESS\" ]; then\n echo \"Address required.\"\n return 1\n fi\n\n # Get amount\n read -p \"Amount (sats): \" AMOUNT\n if ! [[ \"$AMOUNT\" =~ ^[0-9]+$ ]]; then\n echo \"Invalid amount.\"\n return 1\n fi\n\n # Find nearest denominations\n local lower=\"\"\n local higher=\"\"\n local exact=\"\"\n\n for d in \"${DENOMS[@]}\"; do\n if [ \"$d\" -eq \"$AMOUNT\" ]; then\n exact=\"$d\"\n break\n elif [ \"$d\" -lt \"$AMOUNT\" ]; then\n lower=\"$d\"\n elif [ \"$d\" -gt \"$AMOUNT\" ] \u0026\u0026 [ -z \"$higher\" ]; then\n higher=\"$d\"\n break\n fi\n done\n\n # Calculate differences\n local lower_diff lower_pct higher_diff higher_pct\n if [ -n \"$lower\" ]; then\n lower_diff=$((AMOUNT - lower))\n lower_pct=$(awk \"BEGIN {printf \\\"%.2f\\\", ($lower_diff / $AMOUNT) * 100}\")\n fi\n\n if [ -n \"$higher\" ]; then\n higher_diff=$((higher - AMOUNT))\n higher_pct=$(awk \"BEGIN {printf \\\"%.2f\\\", ($higher_diff / $AMOUNT) * 100}\")\n fi\n\n # Display options\n echo \"\"\n echo \"Standard denominations blend better in coinjoins.\"\n echo \"\"\n\n local selected\n if [ -n \"$exact\" ]; then\n echo \"Your amount is already a standard denomination.\"\n selected=\"$exact\"\n else\n echo \"Options:\"\n echo \"\"\n if [ -n \"$lower\" ]; then\n echo \" [L] Send less: $lower sats (-$lower_diff sats, -$lower_pct%)\"\n fi\n if [ -n \"$higher\" ]; then\n echo \" [M] Send more: $higher sats (+$higher_diff sats, +$higher_pct%)\"\n fi\n echo \" [E] Exact amount: $AMOUNT sats (non-standard)\"\n echo \"\"\n read -p \"Choice [L/M/E]: \" choice\n\n case \"${choice^^}\" in\n L)\n if [ -n \"$lower\" ]; then\n selected=\"$lower\"\n else\n echo \"No lower denomination available.\"\n return 1\n fi\n ;;\n M)\n if [ -n \"$higher\" ]; then\n selected=\"$higher\"\n else\n echo \"No higher denomination available.\"\n return 1\n fi\n ;;\n E)\n selected=\"$AMOUNT\"\n ;;\n *)\n echo \"Invalid choice.\"\n return 1\n ;;\n esac\n fi\n\n # Send payment\n local result error payment_id\n result=$(curl -s -d '{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"method\":\"payincoinjoin\",\"params\":[\"'\"$ADDRESS\"'\",'\"$selected\"']}' \"$RPC/$WALLET\")\n error=$(echo \"$result\" | jq -r '.error.message // empty')\n\n if [ -n \"$error\" ]; then\n echo \"Error: $error\"\n return 1\n fi\n\n payment_id=$(echo \"$result\" | jq -r '.result')\n echo \"\"\n echo \"Queued: $selected sats -\u003e $ADDRESS\"\n echo \"Payment ID: $payment_id\"\n return 0\n}\n\n# Main loop\nwhile true; do\n add_payment\n echo \"\"\n read -p \"Add another payment? [Y/n]: \" again\n if [[ \"${again^^}\" == \"N\" ]]; then\n break\n fi\ndone\n\necho \"\"\necho \"Done. Run wcj.sh to start coinjoin.\"\n```\n\n### wcancel.sh\n\n```bash\n#!/usr/bin/env bash\n\n# Wasabi Cancel Payments in CoinJoin\n# Interactive selection to cancel pending payments\n\nRPC=\" http://127.0.0.1:37128\"\n\n# Check RPC connection\nstatus=$(curl -s --connect-timeout 3 -d '{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"method\":\"getstatus\"}' \"$RPC\" 2\u003e/dev/null)\nif [ -z \"$status\" ]; then\n echo \"Error: Cannot connect to Wasabi RPC at $RPC\"\n echo \"Make sure Wasabi is running and RPC is enabled in Config.json\"\n exit 1\nfi\n\n# Get wallet list\nwallets=$(curl -s -d '{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"method\":\"listwallets\"}' \"$RPC\" | jq -r '.result')\nwallet_count=$(echo \"$wallets\" | jq 'length')\n\nif [ \"$wallet_count\" -eq 0 ]; then\n echo \"No wallets found.\"\n exit 1\nfi\n\n# Select wallet\necho \"Wallets:\"\necho \"\"\nfor i in $(seq 0 $((wallet_count - 1))); do\n num=$((i + 1))\n name=$(echo \"$wallets\" | jq -r \".[$i].walletName\")\n echo \" [$num] $name\"\ndone\necho \"\"\nread -p \"Select wallet: \" wallet_choice\n\nidx=$((wallet_choice - 1))\nif [ \"$idx\" -lt 0 ] || [ \"$idx\" -ge \"$wallet_count\" ]; then\n echo \"Invalid selection.\"\n exit 1\nfi\n\nWALLET=$(echo \"$wallets\" | jq -r \".[$idx].walletName\")\n\n# Load wallet if not already loaded\necho \"\"\necho \"Loading wallet $WALLET (this may take a moment)...\"\nload_result=$(curl -s -d '{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"method\":\"loadwallet\",\"params\":[\"'\"$WALLET\"'\"]}' \"$RPC\")\nload_error=$(echo \"$load_result\" | jq -r '.error.message // empty')\n\nif [ -n \"$load_error\" ] \u0026\u0026 [[ \"$load_error\" != *\"already\"* ]]; then\n echo \"Error loading wallet: $load_error\"\n exit 1\nfi\necho \"Wallet ready.\"\necho \"\"\necho \"=== Pending Payments ===\"\necho \"\"\n\n# Get pending payments\nresult=$(curl -s -d '{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"method\":\"listpaymentsincoinjoin\"}' \"$RPC/$WALLET\")\nerror=$(echo \"$result\" | jq -r '.error.message // empty')\n\nif [ -n \"$error\" ]; then\n echo \"Error: $error\"\n exit 1\nfi\n\npayments=$(echo \"$result\" | jq -r '.result')\ncount=$(echo \"$payments\" | jq 'length')\n\nif [ \"$count\" -eq 0 ]; then\n echo \"No pending payments.\"\n exit 0\nfi\n\n# Display numbered list\nfor i in $(seq 0 $((count - 1))); do\n num=$((i + 1))\n amount=$(echo \"$payments\" | jq -r \".[$i].amount\")\n address=$(echo \"$payments\" | jq -r \".[$i].address\")\n echo \" [$num] $amount sats -\u003e $address\"\ndone\n\necho \"\"\necho \" [A] Cancel all\"\necho \" [Q] Quit\"\necho \"\"\nread -p \"Cancel which? \" choice\n\n# Quit\nif [[ \"${choice^^}\" == \"Q\" ]]; then\n exit 0\nfi\n\n# Cancel all\nif [[ \"${choice^^}\" == \"A\" ]]; then\n echo \"\"\n ids=$(echo \"$payments\" | jq -r '.[].id')\n for id in $ids; do\n curl -s -d '{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"method\":\"cancelpaymentincoinjoin\",\"params\":[\"'\"$id\"'\"]}' \"$RPC/$WALLET\" \u003e /dev/null\n amount=$(echo \"$payments\" | jq -r \".[] | select(.id == \\\"$id\\\") | .amount\")\n address=$(echo \"$payments\" | jq -r \".[] | select(.id == \\\"$id\\\") | .address\")\n echo \"Cancelled: $amount sats -\u003e $address\"\n done\n exit 0\nfi\n\n# Cancel specific numbers (comma or space separated)\nselections=$(echo \"$choice\" | tr ',' ' ')\n\nfor sel in $selections; do\n if ! [[ \"$sel\" =~ ^[0-9]+$ ]]; then\n echo \"Invalid selection: $sel\"\n continue\n fi\n \n idx=$((sel - 1))\n \n if [ \"$idx\" -lt 0 ] || [ \"$idx\" -ge \"$count\" ]; then\n echo \"Invalid selection: $sel\"\n continue\n fi\n \n id=$(echo \"$payments\" | jq -r \".[$idx].id\")\n amount=$(echo \"$payments\" | jq -r \".[$idx].amount\")\n address=$(echo \"$payments\" | jq -r \".[$idx].address\")\n \n cancel_result=$(curl -s -d '{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"method\":\"cancelpaymentincoinjoin\",\"params\":[\"'\"$id\"'\"]}' \"$RPC/$WALLET\")\n cancel_error=$(echo \"$cancel_result\" | jq -r '.error.message // empty')\n \n if [ -n \"$cancel_error\" ]; then\n echo \"Failed to cancel [$sel]: $cancel_error\"\n else\n echo \"Cancelled: $amount sats -\u003e $address\"\n fi\ndone\n```\n\n### wcj.sh\n\n```bash\n#!/usr/bin/env bash\n\n# Wasabi CoinJoin Payment Runner\n# Starts coinjoin and monitors payments, adapting to new/cancelled payments\n\nRPC=\" http://127.0.0.1:37128\"\n\n# Check RPC connection\nstatus=$(curl -s --connect-timeout 3 -d '{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"method\":\"getstatus\"}' \"$RPC\" 2\u003e/dev/null)\nif [ -z \"$status\" ]; then\n echo \"Error: Cannot connect to Wasabi RPC at $RPC\"\n echo \"Make sure Wasabi is running and RPC is enabled in Config.json\"\n exit 1\nfi\n\n# Get wallet list\nwallets=$(curl -s -d '{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"method\":\"listwallets\"}' \"$RPC\" | jq -r '.result')\nwallet_count=$(echo \"$wallets\" | jq 'length')\n\nif [ \"$wallet_count\" -eq 0 ]; then\n echo \"No wallets found.\"\n exit 1\nfi\n\n# Select wallet\necho \"Wallets:\"\necho \"\"\nfor i in $(seq 0 $((wallet_count - 1))); do\n num=$((i + 1))\n name=$(echo \"$wallets\" | jq -r \".[$i].walletName\")\n echo \" [$num] $name\"\ndone\necho \"\"\nread -p \"Select wallet: \" wallet_choice\n\nidx=$((wallet_choice - 1))\nif [ \"$idx\" -lt 0 ] || [ \"$idx\" -ge \"$wallet_count\" ]; then\n echo \"Invalid selection.\"\n exit 1\nfi\n\nWALLET=$(echo \"$wallets\" | jq -r \".[$idx].walletName\")\n\n# Load wallet if not already loaded\necho \"\"\necho \"Loading wallet $WALLET (this may take a moment)...\"\nload_result=$(curl -s -d '{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"method\":\"loadwallet\",\"params\":[\"'\"$WALLET\"'\"]}' \"$RPC\")\nload_error=$(echo \"$load_result\" | jq -r '.error.message // empty')\n\nif [ -n \"$load_error\" ] \u0026\u0026 [[ \"$load_error\" != *\"already\"* ]]; then\n echo \"Error loading wallet: $load_error\"\n exit 1\nfi\necho \"Wallet ready.\"\n\n# Handle Ctrl+C gracefully\ncleanup() {\n echo \"\"\n echo \"Stopping coinjoin...\"\n curl -s -d '{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"method\":\"stopcoinjoin\"}' \"$RPC/$WALLET\" \u003e /dev/null\n echo \"CoinJoin stopped.\"\n exit 0\n}\ntrap cleanup SIGINT\n\nget_pending() {\n curl -s -d '{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"method\":\"listpaymentsincoinjoin\"}' \"$RPC/$WALLET\" \\\n | jq '[.result[] | select(.state[0].status == \"Pending\")] | sort_by(.address)'\n}\n\nshow_pending() {\n local payments=\"$1\"\n local count=$(echo \"$payments\" | jq 'length')\n if [ \"$count\" -eq 0 ]; then\n echo \" (none)\"\n else\n echo \"$payments\" | jq -r '.[] | \" \\(.amount) sats -\u003e \\(.address)\"'\n fi\n}\n\n# Show initial state\necho \"\"\necho \"=== Wallet: $WALLET ===\"\necho \"\"\necho \"Pending payments:\"\nprev=$(get_pending)\nshow_pending \"$prev\"\nprev_count=$(echo \"$prev\" | jq 'length')\n\nif [ \"$prev_count\" -eq 0 ]; then\n echo \"\"\n read -p \"No pending payments. Start coinjoin anyway? [y/N]: \" confirm\n if [[ \"${confirm^^}\" != \"Y\" ]]; then\n exit 0\n fi\nfi\n\n# Start coinjoin\necho \"\"\ncurl -s -d '{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"method\":\"startcoinjoin\",\"params\":[\"\",false,true]}' \"$RPC/$WALLET\" \u003e /dev/null\necho \"=== CoinJoin started ===\"\necho \"\"\n\n# Track payments\nprev_addrs=$(echo \"$prev\" | jq -r '.[].address' | sort)\never_had_payments=false\nif [ \"$prev_count\" -gt 0 ]; then\n ever_had_payments=true\nfi\n\nwhile true; do\n curr=$(get_pending)\n curr_count=$(echo \"$curr\" | jq 'length')\n curr_addrs=$(echo \"$curr\" | jq -r '.[].address' | sort)\n \n # Track if we ever had payments\n if [ \"$curr_count\" -gt 0 ]; then\n ever_had_payments=true\n fi\n \n # Check for completed payments\n if [ -n \"$prev_addrs\" ]; then\n for addr in $prev_addrs; do\n if ! echo \"$curr_addrs\" | grep -q \"^${addr}$\"; then\n amount=$(echo \"$prev\" | jq -r \".[] | select(.address == \\\"$addr\\\") | .amount\")\n echo \"[$(date +%H:%M:%S)] Sent: $amount sats -\u003e $addr\"\n fi\n done\n fi\n \n # Check for new payments\n if [ -n \"$curr_addrs\" ]; then\n for addr in $curr_addrs; do\n if [ -z \"$prev_addrs\" ] || ! echo \"$prev_addrs\" | grep -q \"^${addr}$\"; then\n amount=$(echo \"$curr\" | jq -r \".[] | select(.address == \\\"$addr\\\") | .amount\")\n echo \"[$(date +%H:%M:%S)] Added: $amount sats -\u003e $addr\"\n fi\n done\n fi\n \n # All done? Only exit if we ever had payments and now have none\n if [ \"$curr_count\" -eq 0 ] \u0026\u0026 [ \"$ever_had_payments\" = true ]; then\n break\n fi\n \n # Update state\n prev=\"$curr\"\n prev_count=\"$curr_count\"\n prev_addrs=\"$curr_addrs\"\n \n sleep 30\ndone\n\n# Final state\necho \"\"\necho \"=== All payments done ===\"\n\ncurl -s -d '{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"method\":\"stopcoinjoin\"}' \"$RPC/$WALLET\" \u003e /dev/null\necho \"CoinJoin stopped\"\n```",
"sig": "4da83f9dce24c8f8960a8b48cf59bcc0a974f060c0d1bb03e472bde41f74820b87d07f35619107c039620ac5e100bc056fd28bc27c94904e93e6f8307c8edb8d"
}