JoinMarket Wallet Daemon (jmwalletd)
JAM-compatible HTTP and WebSocket daemon for JoinMarket NG.
jmwalletd exposes wallet, transaction, CoinJoin, maker, and tumbler
operations over a FastAPI-based REST API that is compatible with the reference
JoinMarket jmwalletd, so JAM (the JoinMarket web UI)
can talk to JoinMarket NG without modification.
What It Provides
- REST API under
/api/v1(wallet, direct send, CoinJoin, maker, tumbler) - WebSocket notifications on
/ws,/api/v1/ws, and/jmws - JWT authentication (access and refresh tokens)
- Orderbook proxy endpoints under
/obwatch/*(also without the/api/v1prefix, as JAM expects) - Optional serving of the JAM web UI static files
Installation
The default complete profile of install.sh installs jmwalletd. On an older
installation, first run bash ~/.joinmarket-ng/install.sh --update to refresh
the saved installer, then run it again with --update --jmwalletd to add the
daemon. Older saved installers do not recognize the new flag. Single-role
installations can opt in with --jmwalletd (which also installs maker, taker,
and tumbler). To install from a clone in a virtualenv instead:
python -m venv jmvenv
source jmvenv/bin/activate
python -m pip install -e ./jmcore -e ./jmwallet -e ./maker -e ./taker \
-e ./tumbler -e ./jmwalletd
Alternatively, use the standalone Docker image published by this repository:
docker pull ghcr.io/joinmarket-ng/joinmarket-ng/jmwalletd:latest
For a single-application install that bundles the JAM web UI, jmwalletd, Tor, and neutrino, see the Flatpak page.
Running
Start the daemon:
jmwalletd
Defaults:
- bind host:
127.0.0.1 - port:
28183 - TLS: enabled, with an auto-generated self-signed certificate stored in
~/.joinmarket-ng/ssl/
Common options:
# run plain HTTP on loopback (for a local TLS-terminating reverse proxy)
jmwalletd --no-tls
# listen on all interfaces with TLS enabled
jmwalletd --host 0.0.0.0
# custom data dir and config file
jmwalletd --data-dir /path/to/data --config-file /path/to/config.toml
The wallet API accepts passwords and returns bearer tokens. Never expose it over
plain HTTP to an untrusted network. --no-tls should be used only on loopback,
or on an isolated container network behind an access-controlled, TLS-terminating
reverse proxy. Binding to 0.0.0.0 makes the API reachable on any interface
allowed by the firewall; clients must verify the daemon's TLS certificate.
The JMWALLETD_HOST, JMWALLETD_NO_TLS, JOINMARKET_DATA_DIR, and
JOINMARKET_CONFIG_FILE environment variables mirror the CLI options.
Connecting JAM
Point JAM at the daemon URL (default https://127.0.0.1:28183). With the
default self-signed certificate your browser asks for a one-time exception.
For a combined Docker deployment, use the standalone-ng image maintained by
joinmarket-webui/jam-docker.
It runs JAM, jmwalletd, Tor, and the orderbook watcher behind plain HTTP nginx
on container port 80; jmwalletd remains on its TLS-enabled loopback port
inside the container. The JoinMarket NG repository only publishes the
standalone jmwalletd image.
If a JAM build is present in <data-dir>/jam (or the system paths
/usr/share/jmwalletd/jam and /app/share/jmwalletd/jam used by packaged
installs), the daemon serves the UI itself, so opening the daemon URL in a
browser is enough.
Configuration
BIP39 Passphrases
Create, recover, and unlock request bodies accept an optional bip39_passphrase
string, separate from the file-encryption password. Omission preserves the
empty-passphrase behavior of existing clients. The CLI/TUI onboarding setting
does not substitute credentials into REST requests. External JAM clients need
their own support for entering this field; the daemon API extension alone does
not add a web UI prompt.
New containers remember the expected identity, never the passphrase. The active credential is forwarded to direct spending, CoinJoin, maker, and tumbler, then cleared on lock. A mismatch rejects unlock before changing the active session. An actual identity change resets tokens and WebSocket sessions; same-identity reopening preserves existing refresh-token validity.
Upgrade: Existing PBKDF2 and version-1 files remain readable without rewriting or scans caused by missing identity metadata. Their identity remains unconfirmed; supply the intended credential and verify the wallet independently. A deliberate recovery into another named container can establish a binding, but is not a required upgrade step and may scan. New nonempty-passphrase containers use format version 2, which older daemons reject rather than opening the wrong wallet. Keep compatible software with backups.
For API compatibility, failed unlocks still return Invalid credentials. Check
both credentials and format support, and consult daemon warnings. See the
identity and format details for
metadata privacy and downgrade limitations.
jmwalletd uses the shared JoinMarket NG config (~/.joinmarket-ng/config.toml)
and the same environment override model as the other components (see
Configuration).
The most relevant sections:
[network_config]for network selection (network,bitcoin_network,directory_servers)[bitcoin]for the backend (descriptor_walletorneutrino)[tor]for SOCKS and control settings[taker]policy settings, honored by CoinJoins and tumbles started through the API[tumbler]pacing settings for API-run tumbles
Orderbook proxy target resolution order:
OBWATCH_URLenv varorderbook_watcher.http_host+orderbook_watcher.http_port- fallback
http://127.0.0.1:8000
Development
Run the unit tests for this component:
pytest jmwalletd
For Docker-backed integration and JAM-compatibility (reference) coverage, use the root test workflow:
./scripts/run_parallel_tests.sh