Daemon and CLI¶
joinstrd runs joinstr in the background against a Bitcoin Core wallet and exposes a REST API on localhost. joinstr-cli is a command line client for that API. Together they let you run coinjoins on a headless server or build joinstr into other software over HTTP.
| Requires | Python 3, Bitcoin Core with RPC, Tor |
| Transport | Tor only |
| Source | gitlab.com/invincible-privacy/joinstr/-/tree/main/cli |
Install¶
git clone https://gitlab.com/invincible-privacy/joinstr.git
cd joinstr
pip install -r cli/requirements.txt
mkdir -p ~/.joinstr
cp cli/config.example.ini ~/.joinstr/config.ini
chmod 600 ~/.joinstr/config.ini
Configure¶
~/.joinstr/config.ini holds the node connection, the relay and the Tor proxy. The daemon warns if other users can read the file.
[rpc]
url = http://127.0.0.1:38332
user = <rpc user>
password = <rpc password>
wallet = <wallet name, empty for the default wallet>
[nostr]
relay = wss://nos.lol
[tor]
host = 127.0.0.1
port = 9050
The example uses the signet RPC port. Use 8332 for mainnet.
Tor is required. At start the daemon checks through the proxy that Tor works and exits if it does not. All relay and mempool.space traffic goes through Tor.
| Environment variable | Default | Meaning |
|---|---|---|
JOINSTR_DIR |
~/.joinstr |
Directory with config.ini and state |
JOINSTRD_HOST |
127.0.0.1 |
Address the daemon binds |
JOINSTRD_PORT |
8000 |
Port the daemon listens on |
JOINSTR_API_URL |
http://127.0.0.1:8000 |
Daemon URL used by the CLI |
Run¶
The daemon only answers requests whose Host header is a loopback address. Interactive API docs are served at http://127.0.0.1:8000/docs and /redoc, with the schema at /openapi.json.
CLI¶
| Command | What it does |
|---|---|
create --denomination <BTC> --peers <n> [--timeout <seconds>] |
Create a pool. Timeout defaults to 3600 seconds. |
list [--my \| --others] |
List your pools or open pools on the relay |
join [--pool-id <id>] |
Join a pool |
output [--address <addr>] [--pool-id <id>] |
Register an output if it was not done automatically |
input [--pool-id <id>] [--txid <txid> --vout <n>] |
Pick and sign the coin for the active pool |
history |
Show finished coinjoins |
stop |
Stop the current operation |
The CLI signs your input with your node (walletprocesspsbt with ALL|ANYONECANPAY) and hands the signed PSBT to the daemon. It reads the [rpc] section of the same config.ini.
A typical round¶
# creator
python cli/joinstr-cli.py create --denomination 0.001 --peers 3
# every other peer
python cli/joinstr-cli.py list --others
python cli/joinstr-cli.py join --pool-id <id>
# everyone, once all outputs are registered
python cli/joinstr-cli.py input --txid <txid> --vout <n>
REST API¶
All endpoints are on http://127.0.0.1:8000. The full schema is at /openapi.json.
| Method | Path | Purpose |
|---|---|---|
GET |
/status |
Daemon status |
GET |
/pools/my |
Pools you created or joined |
GET |
/pools/others |
Open pools on the relay |
POST |
/pool/create |
Create a pool |
POST |
/pool/join |
Join a pool |
GET |
/pool/info |
Details of the active pool |
GET |
/pool/status |
Phase of the active pool |
POST |
/pool/register-output |
Register an output address |
GET |
/pool/my-output |
Your registered output |
GET |
/pool/outputs |
All registered outputs |
POST |
/pool/register-input |
Register a signed input PSBT |
GET |
/pool/my-input |
Your registered input |
GET |
/pool/inputs |
All registered inputs |
GET |
/history |
Finished coinjoins |
POST |
/stop |
Stop the current operation |
/pool/register-input takes a PSBT that is already signed, so the key can live anywhere. A hardware or air gapped signer can sign the registration PSBT with SIGHASH_ALL|ANYONECANPAY and the daemon does the rest.
Fees and broadcast¶
- The pool fee rate comes from mempool.space (
hourFee) over Tor, with 5 sat/vB as a fallback. - A joiner refuses pools with a fee rate above 100 sat/vB and rounds that would cost more than 15000 sats.
- The final transaction is broadcast through your node. If the node rejects it for a reason other than its own policy, the daemon tries mempool.space over Tor on mainnet and signet.