Mixpool: broadcast Bitcoin transactions through the Nym mixnet (CLI + Python library)

Mission: Build with Nym
GitHub: GitHub - apeirs/mixpool: Broadcast Bitcoin transactions and query mempool APIs through the Nym mixnet (CLI + stdlib Python library) · GitHub (MIT)
Nyx address for rewards: n10c7h7wae558d5zejysktztheca6t3gc3vrnep9

What it does

mixpool sends a signed raw Bitcoin transaction to public APIs (mempool.space, blockstream.info, mempool.emzy.de, plus signet/testnet ones) through the Nym mixnet, so the API never sees the broadcast come from your IP address. Fee estimates, block height and transaction status lookups go over the mixnet too.

mixpool setup                      # starts nym-socks5-client on a random active exit gateway
mixpool broadcast tx.hex           # posts the tx to every API, each over its own mixnet stream
mixpool fees / tip / status <txid> # lookups, also over the mixnet
mixpool ip                         # shows the IP servers see (the exit gateway's)

The problem it solves

When you broadcast a transaction to a block explorer API (or your wallet does it for you), the server logs “this IP published transaction X”. That links your network identity to your coins: the inputs you spent, your change, often your whole wallet. Looking up your own txid or addresses leaks the same thing. Most people don’t run their own node, and Tor leaves timing correlation open. Nym’s mixing, delays and cover traffic are a good fit for this one job: getting a transaction to the network without the network knowing it was you.

How it uses Nym (real mixnet traffic, not a mock)

  • Drives the official nym-socks5-client (v2026.19 release binary). mixpool setup asks the Nym API for active exit gateways that run a network requester with the exit policy, picks one at random, and turns on reply SURBs by default so the exit doesn’t learn the client’s mixnet address.
  • Each request is a new SOCKS5 stream through the entry gateway, the mix layers and the exit gateway’s network requester. TLS runs end to end with the API.
  • Hostnames are resolved with DNS-over-HTTPS over the mixnet, then connected by IPv4. That way the local resolver never sees them. It also works around something I hit in testing: one exit’s IPv6 route to mempool.space was broken, so every request stalled when the exit resolved the name itself.
  • There is no clearnet fallback. If the mixnet client is down, mixpool refuses to send.
  • The tx is parsed locally first (txid/vsize computed, typos caught offline), and the txid each API returns is checked.

Proof it works (live mainnet mixnet, 2026-10-10; full logs in the repo’s examples/)

The servers see the exit gateway, not me. The exit was node 2682 (identity DXiKYrsx…), and ipify reports 193.142.58.124, which is that node’s IP in the Nym API:

$ mixpool ip -v
[   0.00s] nym  exit (network requester): AfpyBUmh…@DXiKYrsx4bnGimcxC3VKpZM8VpayRnE9fsKJZAhgWEYp
[   1.79s] send GET https://api.ipify.org -> socks5 127.0.0.1:1080 -> mixnet -> 104.26.13.205:443 (attempt 1)
[   3.61s] recv HTTP 200 OK from api.ipify.org in 1.82s (14 bytes)
https://api.ipify.org sees this request coming from 193.142.58.124 (a Nym exit gateway)

GET over the mixnet:

$ mixpool fees -v
[   1.95s] send GET https://mempool.space/api/v1/fees/recommended -> socks5 127.0.0.1:1080 -> mixnet -> 103.165.192.206:443 (attempt 1)
[   3.48s] recv HTTP 200 OK from mempool.space in 1.53s (74 bytes)
fastestFee   2 sat/vB ...

POST of a raw transaction over the mixnet. I used an already-confirmed tx from block 700,000, so no coins move. Getting Bitcoin Core’s rejection back from three independent APIs proves the full round trip:

$ mixpool broadcast examples/already-confirmed-tx.hex -v
txid b2b8328e5e7af3f17449109b449517cc6641b7a1a525e0c1d019cda078ee03be  (216 vB, 1 in / 2 out)
REJECTED  https://mempool.space/api  HTTP 400  1.97s  sendrawtransaction RPC error: {"code":-25,"message":"bad-txns-inputs-missingorspent"}
REJECTED  https://blockstream.info/api  HTTP 400  3.07s  sendrawtransaction RPC error -25: bad-txns-inputs-missingorspent
REJECTED  https://mempool.emzy.de/api  HTTP 400  2.25s  sendrawtransaction RPC error: {"code":-25,"message":"bad-txns-inputs-missingorspent"}

Round trips take about 1.5 to 3.5 s with reply SURBs on.

Current progress

  • v0.1.0 is working and published. It has no Python dependencies (stdlib only) and covers mainnet, testnet4, testnet and signet.
  • Offline tests use a fake SOCKS5 proxy and a fake Esplora API. They check that nothing is resolved locally, that there is no clearnet fallback, failover, and txid checks against real mainnet transactions. There are also opt-in live tests through the real mixnet.
  • Found along the way: nym-socks5-client 1.1.86 (v2026.19) writes a config at init that run can’t load (missing field credential_requests_database). mixpool patches it automatically, and the README documents the workaround for people running the client by hand.
  • The README has an honest threat model. For example, the mixnet hides who broadcast a tx, not what the tx says on chain.

Future plans

  • A fresh mixnet identity per broadcast, plus optional random delays before posting.
  • Electrum servers and your own Bitcoin Core node, via Nym’s IP packet router.
  • A native Rust nym-sdk build so no separate client binary is needed. Also an easy path for Sparrow/Electrum users.
  • Liquid and Lightning-adjacent lookups (LNURL, BOLT12 offers).
  • zk-nym bandwidth credential support for when free bandwidth ends.

Prior art, credited in the README: btcbc-rs (2021) and broadnym (2025) send transactions over Nym to a dedicated server someone has to run. mixpool needs no extra server. It uses Nym’s public exit gateways to reach existing APIs directly, and it covers the lookups as well.

1 Like