Monero <-> Ethereum atomic swap engine

moos 97aa431f68 tor: correct what tor reads from onion.key 6 timmar sedan
.githooks 6ffb856593 repo: add commit convention hook 1 vecka sedan
contracts f50491c5cf contracts: pin the pre-fix artifact in the payout test 6 dagar sedan
docs 6aaea45af5 docs: drop the stale no-address reason for no download 7 timmar sedan
engine 97aa431f68 tor: correct what tor reads from onion.key 6 timmar sedan
packaging 2540d702da packaging: retire the artifact publish path 6 dagar sedan
tools c08c48b106 tools: publish main to the public mirror 7 timmar sedan
.gitignore fbc17cc8dd repo: ignore local analysis notes 3 dagar sedan
AGENTS.md 993709e2f6 docs: drop the legacy proof exemption note 2 dagar sedan
LICENSE 6276b77d7d repo: add license 1 vecka sedan
README.md 589690f396 docs: add the public git clone instructions 7 timmar sedan
moos 54fea05ea5 repo: drop devnet from the build contract 1 vecka sedan
moos-tui e30759fb11 engine: bake the mainnet swapcreator v3 address 6 dagar sedan
whitepaper.md 1128cc3be7 repo: merge the send fences into main 10 timmar sedan

README.md

moos

moos is a Monero⟷Ethereum atomic swap engine: a swap daemon, a terminal UI, a GTK desktop shell and a P2P offer book, all in one repository. Swaps run trust-minimized against a pinned SwapCreator contract on Ethereum mainnet; chain traffic is tor-only. Licensed GPL-3.0 — see LICENSE.

Alpha software. This is early software running with real funds on mainnet. Swap small amounts only, verify every address before sending, and expect no guarantees. Read docs/OPERATIONS.md before running a funded swap.

Status: version 0.1.1 (binaries answer --version as moos 0.1.1 (rev <git-describe>); the TUI and GUI banners show the same rev). Working today: the ETH-provides swap (the maker locks ETH, the taker sends XMR and claims it), the tor transport (mainnet is tor-only), and the P2P offer book (DHT-backed, with direct dial to a known maker).

Get the source

git clone https://git.moos.st/moos/moos.git
cd moos

The same history is on Radicle (rad clone rad:z3vS8s9mTztnMwo1gK6pmatiFeBBc moos); the git mirror exists so a plain git clone is enough. AGENTS.md has the build and test commands; the build is offline from the vendored crates.

Prerequisites

Source build (Linux x86_64):

  • a Rust toolchain via rustup (rustc, cargo);
  • a C compiler (cc — e.g. sudo apt install build-essential);
  • curl, tar, sha256sum for the one-time pinned Monero CLI download.

Linking uses the system linker (cc → ld); mold is not required and not requested by the build. If cc cannot link a trivial program (missing binutils, or a compiler/linker version mismatch — common on minimal templates), the launcher stops before the build and prints the linker's own error. On a low-RAM VM a large link can be OOM-killed (ld terminated with signal 9); opt into the lighter mold linker per shell: CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS="-C link-arg=-fuse-ld=mold".

The build itself is offline: all crates.io dependencies are vendored under engine/vendor and no CMake is needed.

Desktop shell only (in addition to the above): python3 with PyGObject, GTK 3 and WebKit2GTK 4.1 — e.g. sudo apt install python3-gi gir1.2-gtk-3.0 gir1.2-webkit2-4.1.

Quickstart: mainnet (real funds)

./moos-tui

./moos-tui builds what it needs (--offline), fetches the pinned Monero CLI itself if tools/fetch-monero.sh was skipped, and injects --mainnet. This is real funds: fund small, check every address.

  • tor is required. Mainnet chain traffic is tor-only. moos starts its own private user tor if a tor binary is available (system tor on PATH is enough — sudo apt install tor; no daemon mode or group setup needed). MOOS_TOR_SOCKS / MOOS_TOR_CONTROL / MOOS_TOR_COOKIE point moos at a tor you run yourself instead.
  • Chain endpoints default to pinned tor .onion nodes; override with MOOS_MONEROD_HOST (loopback or .onion — a clearnet monerod is refused) / MOOS_ETH_ENDPOINT (loopback, .onion or clearnet; clearnet is dialed through moos's own tor). Decisive ETH reads are corroborated by a second, independent provider — eth.drpc.org by default (MOOS_ETH_VERIFY_ENDPOINT overrides; it must not be the primary's host). The SwapCreator v3 address defaults to the pinned mainnet deployment (0xb39074e5f5bb5fd3a5fd48df867115d5c8b97e71; override with --contract-address / MOOS_CONTRACT_ADDRESS, stagenet always needs one) and its pinned bytecode hash is verified at launch.
  • Both legs need funding before a swap: the taker's lock side, plus gas on the EOA that claims the ETH leg. The funding matrix is in docs/OPERATIONS.md §4.

Desktop shell

./moos

Same launcher and daemon as the TUI, but opens a GTK3/WebKit2GTK window (no JavaScript; plain form posts served over loopback). Needs the GUI prerequisites above plus a graphical session (Wayland or X11).

Install tree (no published artifact)

There is no binary download: run from this checkout. An install tree that bundles the binaries, tor and monero-wallet-rpc can still be assembled locally with packaging/fetch.sh → packaging/build.sh → tools/pack/pack.sh and checked with packaging/verify.sh; the layout is in docs/PACKAGING.md. A packaged install defaults to --mainnet.

Where state lives

What Source checkout Packaged install
identity (ETH key + swap wallet) rundir-mainnet/identity/ in the repo if it exists, else <state>/identity/ <state>/identity/
tor root (DataDirectory, cookie) rundir-mainnet/tor/ if it exists, else <state>/tor/ <state>/tor/
per-run logs <state>/run/mainnet/moos-<secs>-<pid>/ same

<state> is $MOOS_STATE_DIR, else $XDG_DATA_HOME/moos, else ~/.local/share/moos (macOS: ~/Library/Application Support/moos). An existing source identity is reused in place, never copied. MOOS_MAINNET_IDENTITY_DIR relocates the identity (also how two instances coexist on one machine). Details: docs/PACKAGING.md.

Logs

./moos shows its log stream in the launching terminal and writes the full lines to taker.log; ./moos-tui keeps the terminal clean and writes the file only. The terminal takes the short form in UTC — 15:36:47 [WARN] [tui] dial … — and collapses a repeat only when the line differs solely in a timing/count field (attempt=, latency_ms=, conns=, size_bytes= …), as (×28 more in 5s) at INFO/DEBUG and (×28 more in 30s) at WARN/ERROR; a line carrying an id, height, amount or balance (swap_id=, txid=, height=, amount=, balance= …) always prints because the number is the information. The terminal is capped at 50 lines per second, while the file keeps the precise 2026-10-26T14:58:22.146Z WARN [tui] [TUI-003] dial … line, uncapped and uncollapsed. Each run gets its own directory, <state>/run/mainnet/moos-<secs>-<pid>/, and latest in the run root points at the newest one (a spawned daemon's maker.log and the chain sidecar logs land there too).

MOOS_LOG=debug ./moos          # verbosity: debug|info|warn|error (default info)
MOOS_LOG_DEST=file ./moos      # destinations: file|stderr|both
MOOS_LOG_TARGETS="p2p=debug,tor=warn,!tui" ./moos   # per-component levels/mutes
./moos --logs                  # state root, newest logs, follow command
tail -f ~/.local/share/moos/run/mainnet/latest/taker.log

MOOS_LOG_TARGETS is comma-separated component=level (error|warn|info| debug) and/or !component to mute it; the longest matching component prefix wins (p2p covers p2p.transport), a mute beats a level, and it filters the file as well as the terminal. MOOS_LOG_DEST=both is the desktop shell's default; the terminal UI defaults to file and accepts both/stderr too. Logs are local files and are never uploaded.

Documentation

License

GPL-3.0 — see LICENSE.