# For LLMs and agents

This page is for language models, coding agents and the people who use them. It explains how to load these docs into an agent and gives a compact brief with the facts an agent needs most often.

## Machine readable docs

| URL | Contents |
|---|---|
| [`/llms.txt`](https://docs.joinstr.xyz/llms.txt) | Index of every page with a one line summary, in the [llms.txt](https://llmstxt.org) format |
| [`/llms-full.txt`](https://docs.joinstr.xyz/llms-full.txt) | Every page as markdown in one file, links made absolute |
| `/<page>.md` | The markdown source of any page, for example [`/clients/electrum.md`](https://docs.joinstr.xyz/clients/electrum.md) |

Every HTML page also links its markdown source with `<link rel="alternate" type="text/markdown">` and a link at the bottom of the page.

To give an agent the whole documentation, point it at `https://docs.joinstr.xyz/llms-full.txt`. To let it pick pages, start from `https://docs.joinstr.xyz/llms.txt`.

## Brief

joinstr is a coinjoin implementation for bitcoin. Peers coordinate over a nostr relay with no central coordinator. A pool has one denomination, a fixed number of peers and one relay. Each peer contributes one input and receives one equal output. There is no change output. Inputs are signed with `SIGHASH_ALL|ANYONECANPAY` after all outputs are known, then every peer combines, checks and broadcasts the same transaction.

### Components

| Component | Source | Language | Backend | Latest |
|---|---|---|---|---|
| Electrum plugin (reference) | `gitlab.com/invincible-privacy/joinstr`, `plugin/` | Python | Electrum 4.6.0 or newer, any build | 0.4.2 |
| Web UI and Umbrel app | `gitlab.com/invincible-privacy/joinstr`, `gui/` | Python, Flask | Bitcoin Core RPC | Umbrel 0.1.2 |
| Daemon `joinstrd` and `joinstr-cli` | `gitlab.com/invincible-privacy/joinstr`, `cli/` | Python, FastAPI | Bitcoin Core RPC | `main` |
| Android app | `gitlab.com/invincible-privacy/joinstr-kmp` | Kotlin Multiplatform | Bitcoin Core RPC | 0.3.0 |
| Sparrow fork | `github.com/QcMrHyde/sparrow-joinstr` | Java | Sparrow server | 0.2.0 |
| Rust library and Dart bindings | `github.com/rust-joinstr/joinstr` | Rust, Dart | Electrum server | `master` |

### Constants

| Name | Value |
|---|---|
| Pool announcement | nostr kind `2022`, JSON content, no tags, signed by the pool key |
| Pool messages | nostr kind `4`, NIP-04 encrypted, tag `["p", pubkey]` |
| Message types | `new_pool`, `join_pool`, credentials (no `type`), `reject`, `output`, `input`, `reregister`, `resign` |
| Default relay | `wss://nos.lol` |
| Default Tor proxy | `127.0.0.1:9050` |
| Sighash for registration | `0x81` (`ALL\|ANYONECANPAY`) |
| Sighash for recovery | `0x01` (`ALL`) |
| Output order | BIP69, by amount then `scriptPubKey` bytes |
| Output amount | `round(denom × 1e8) − floor(fee_rate × 100 × peers) ÷ peers` |
| Allowed input | denomination + 500 to denomination + 5000 sats |
| Maximum cost per peer | 15000 sats |
| Fee bounds at finalization | 100 to 10000 sats per peer |
| Pool id | 10 hex characters followed by unix time |
| Poll budget | 360 polls every 5 seconds |
| Recovery phase timeout | 120 seconds each |
| Electrum GUI pool timeout | 600 seconds |
| Release signing key | `9253062A4F92D63459085CA62D230520212A5901` |
| Android APK certificate SHA-256 | `9F:D6:8F:FB:5E:B3:2F:D6:FC:2B:5D:FB:C9:7B:23:46:C7:54:C4:62:6F:9F:44:41:19:7B:D5:73:AB:16:18:E8` |

### Common tasks

Install and verify the Electrum plugin.

```sh
gpg --keyserver hkps://keys.openpgp.org --recv-keys 9253062A4F92D63459085CA62D230520212A5901
gpg --verify joinstr.zip.asc joinstr.zip
# then Electrum > Tools > Plugins > Add > joinstr.zip
```

Run a pool from the Electrum daemon.

```sh
electrum --offline setconfig plugins.joinstr.enabled true
electrum --offline setconfig proxy "socks5:127.0.0.1:9050::"
electrum daemon -d
electrum load_wallet
electrum joinstr_list_pools
electrum joinstr_create_pool 0.001 3 <txid:vout>
electrum joinstr_join_pool <pool_id> <txid:vout>
```

Run the daemon and CLI against Bitcoin Core.

```sh
pip install -r cli/requirements.txt
cp cli/config.example.ini ~/.joinstr/config.ini   # set [rpc], [nostr], [tor]
python cli/joinstrd.py                              # REST API on 127.0.0.1:8000, docs at /docs
python cli/joinstr-cli.py create --denomination 0.001 --peers 3
python cli/joinstr-cli.py join --pool-id <id>
python cli/joinstr-cli.py input --txid <txid> --vout <n>
```

Run the test suites of the main repository.

```sh
python -m pytest plugin/tests/ cli/tests/ gui/tests/
```

### Invariants

Code that builds or changes a joinstr client must keep these, or peers will reject its messages or lose coins.

1. Sign the announcement with the pool key it advertises.
2. Accept credentials only if their `private_key` derives the announced `public_key` and every term matches the announcement.
3. Send every pool message to the relay named in the pool, not a configured default.
4. Order outputs by BIP69 before signing. Sorting by address string is wrong.
5. Sign registration inputs with `0x81` and finalize them with an empty final `scriptSig` for native segwit.
6. Never sign a registration PSBT whose single input covers every output, since anyone could broadcast it alone.
7. Refuse a round that would cost more than 15000 sats or leave an output below dust.
8. Keep Python `json.dumps` spacing in message bodies, because some peers match `"type": "<name>"` as text.
9. Route relay traffic through Tor with a new circuit per operation. Fail closed if Tor is down.
10. Use a fresh address for every output and never reuse an address across pools.

### Sources of truth

- The [protocol page](protocol/index.md) is the reference for the wire format. It follows `plugin/joinstr.py` in the main repository. NIP.md in the same repository is the original design and is older.
- [Releases](community/releases.md) lists the current versions.
- The [security policy](community/security-policy.md) explains how to report vulnerabilities. Do not file them as public issues.
