From ac9d82f031906f3ba73c6f8464e0445df2a822cf Mon Sep 17 00:00:00 2001 From: Dunsin Date: Wed, 9 Sep 2026 17:19:31 +0100 Subject: [PATCH 1/2] build: add cli-up, cli-stop and cli-clear for the terminal-only path Driving the wallet from the CLI had no working entry point. `up` builds the Android FFI, so it needs the NDK at ~/Android/Sdk/ndk/ and ends by telling you to run Flutter. `runtime-run` skips the NDK but starts the cosigner without ASP_URL or WEBAUTH_TOKEN_SECRET: without the first, every Ark call comes back UNAVAILABLE; without the second, the runtime rejects the session tokens the CLI mints for the group-key-authenticated ark/* routes. Neither failure names its cause, so you get a server that looks healthy and refuses everything. cli-up brings up regtest and arkd, funds the ASP, mines every 10s, and runs the cosigner in the foreground with the environment it actually needs. cli-stop pauses. It uses `docker compose stop` rather than `down` because arkd and arkd-wallet mount no volume, so their data lives in the container's writable layer. `down` would delete arkd's VTXO records while leaving the chain intact, which is worse than a clean wipe: the keystore and the cosigner would still believe in coins arkd has no record of. cli-clear is the clean slate: containers, volumes, the cosigner's SQLite and the CLI keystore. It does not reuse `down`, which sudo-prompts to delete root-owned paths this flow never creates, and whose `pkill -f "bitcoin.sh mine"` matches the shell running it and kills itself, surfacing as "Terminated (ignored)". The bracketed first letter avoids the self-match. Also points the CLI comment at cli-up rather than regtest-ark, which is `runtime-stop arkd-up bitcoin-init arkd-init` and never starts a cosigner at all. --- Makefile | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- README.md | 12 ++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 86c35192..94b24adf 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ stress-test load-test \ signet-hardware-ark signet-down e2e-mutinynet e2e-mutinynet-ark \ e2e-test e2e-ark-test regtest regtest-ark regtest-down \ - cli cli-build \ + cli cli-build cli-up cli-stop cli-clear \ release release-apk release-apk-fat release-testers-add release-testers-remove # ── Variables ───────────────────────────────────────────────────────────────── @@ -359,7 +359,7 @@ e2e-mutinynet-ark: ffi-build runtime-build # Interactive wallet REPL for driving a running stack by hand: onboard, fund, # board, send, contacts and payment requests. Point it at whatever cosigner is -# up — `make regtest-ark` locally, or a deployment. +# up — `make cli-up` locally, or a deployment. # # REGTEST ONLY. The keystore (~/.merlin-cli/wallets.json) holds PLAINTEXT # signing secrets. @@ -369,6 +369,69 @@ e2e-mutinynet-ark: ffi-build runtime-build CLI_URL ?= http://127.0.0.1:7074 URL ?= $(CLI_URL) +# Everything the CLI needs, in one command: regtest + arkd, a funded ASP, a mine +# loop, and the cosigner with ASP_URL and WEBAUTH_TOKEN_SECRET set. +# +# Foreground, mining every 10s. Drive the wallet from `make cli` in another +# terminal. Ctrl+C stops the cosigner and the mine loop; `make down` also stops +# Docker. +# +# Deliberately does NOT reset the cosigner's SQLite: it holds the server's half +# of every wallet in your CLI keystore, so wiping it here would silently orphan +# them. `make cli-clear` when you want a clean slate. +cli-up: runtime-build + @echo "=== Starting regtest + arkd ===" + docker compose -f docker-compose.yml -f docker-compose.ark.yml up -d + @echo "Waiting for services to stabilize (20s)..." + @sleep 20 + @echo "=== Initializing Bitcoin chain ===" + ./scripts/bitcoin.sh init + @echo "=== Initializing arkd ===" + ./scripts/arkd_init.sh --fund + @echo "" + @echo "==> Cosigner on :7074. Drive it from another terminal: make cli" + @echo "==> Mining a block every 10s. Ctrl+C stops both." + @echo "" + @bash -c 'set -m; \ + (while true; do ./scripts/bitcoin.sh mine 2>/dev/null; sleep 10; done) & \ + MINE_PID=$$!; \ + trap "kill $$MINE_PID 2>/dev/null || true; wait $$MINE_PID 2>/dev/null || true" EXIT INT TERM; \ + export ELECTRUM_URL=127.0.0.1 ELECTRUM_PORT=50001 \ + BITCOIN_RPC_USER=admin1 BITCOIN_RPC_PASSWORD=123 \ + ASP_URL=http://127.0.0.1:7070 \ + ESPLORA_URL=http://127.0.0.1:30000 \ + BITCOIN_NETWORK=regtest \ + WEBAUTH_TOKEN_SECRET=$${WEBAUTH_TOKEN_SECRET:-6d706377616c6c65742d6465762d746f6b656e2d7365637265742d3332622121}; \ + cd cosigner-runtime && cargo run --release --bin cosigner-runtime -- \ + --port 7074' + +# Pause. Stops the containers but keeps the chain, arkd's records, the cosigner's +# database and your wallets, so `make cli-up` picks up where you left off. +cli-stop: + @echo "Pausing the CLI stack..." + -@pkill -f "[t]arget/release/cosigner-runtime" || true + -@pkill -f "[b]itcoin.sh mine" || true + docker compose -f docker-compose.yml -f docker-compose.ark.yml stop + @echo "stopped, state kept. resume with: make cli-up" + +# Wipe everything the CLI created: chain, cosigner state, keystore. Use `cli-stop` +# to pause instead. Stopping alone is not enough to reset — the chain goes with +# the Docker volumes, but the database and the keystore survive, leaving wallets +# whose coins no longer exist. +# +# Not built on `down`, which sudo-prompts for root-owned paths this flow never +# creates. The `[t]` and `[b]` stop pkill from matching its own shell. SQLite is +# removed here rather than by `db-reset` so it happens after the cosigner dies, +# not before. +cli-clear: + @echo "Stopping the CLI stack..." + -@pkill -f "[t]arget/release/cosigner-runtime" || true + -@pkill -f "[b]itcoin.sh mine" || true + -docker compose -f docker-compose.yml -f docker-compose.ark.yml down -v 2>/dev/null || true + @rm -f $(SQLITE_PATH) $(SQLITE_PATH)-wal $(SQLITE_PATH)-shm + @rm -f $(HOME)/.merlin-cli/wallets.json + @echo "chain, cosigner state and CLI keystore cleared" + cli: @echo "merlin CLI → $(URL) (regtest only: keystore secrets are plaintext)" cd cli && COSIGNER_URL=$(URL) cargo run --release diff --git a/README.md b/README.md index 6abecf2b..3c827db9 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,7 @@ MPCWallet/ │ ├── ark/ Ark protocol: boarding, VTXO send/settle, delegate/auto-settle, checkpoints │ ├── threshold/ FROST + DKG core (no_std, secp256k1) │ └── enclave-client/ Nitro attestation verification (COSE/X.509/PCR0) + signed-response client +├── cli/ `merlin` — Rust wallet REPL for driving a regtest stack by hand ├── ffi/ Merged C-ABI shared library for Dart FFI (ark + threshold + enclave) ├── protocol/ gRPC stubs and proto definitions ├── infrastructure/ OpenTofu modules for enclave deployment (KMS, EC2, S3, SSM) @@ -130,6 +131,17 @@ make e2e # Ark E2E: builds ffi + cosigner-runtime, starts regtest The local cosigner runtime runs as a plain Rust binary (no enclave, no attestation) — the per-user native-actor isolation still applies. Useful for fast iteration. +### Driving a regtest stack from the CLI + +For exercising the protocol without the app. Needs only Rust and Docker. + +```bash +make cli-up # regtest + arkd + funded ASP + cosigner, foreground +make cli # second terminal: the wallet REPL +``` + +`make cli-stop` pauses and keeps state; `make cli-clear` wipes it. Regtest only — the keystore holds signing secrets in plaintext. + ### Cloud deployment (signet / mutinynet / mainnet) ```bash From 060cbc529a35249406e6cf57924fe2b7294ef381 Mon Sep 17 00:00:00 2001 From: Dunsin Date: Fri, 11 Sep 2026 11:49:48 +0100 Subject: [PATCH 2/2] build: wait for NBXplorer in cli-up, as up already does --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 94b24adf..f1060ffc 100644 --- a/Makefile +++ b/Makefile @@ -388,6 +388,8 @@ cli-up: runtime-build ./scripts/bitcoin.sh init @echo "=== Initializing arkd ===" ./scripts/arkd_init.sh --fund + @echo "=== Waiting 10s for NBXplorer to index initial blocks ===" + @sleep 10 @echo "" @echo "==> Cosigner on :7074. Drive it from another terminal: make cli" @echo "==> Mining a block every 10s. Ctrl+C stops both."