From 4455de6e92a4f91cef2e22ea9609f3e52170706c Mon Sep 17 00:00:00 2001 From: dangershony Date: Wed, 30 Sep 2026 00:10:12 +0100 Subject: [PATCH] Add manual UAT test guide and indexer/relay deployment docs - Add MANUAL-TEST-GUIDE.md mirroring the automated App.Test.Uat suite for human testers, with step-by-step UI instructions per test scenario. - Add DEPLOY-INDEXER-AND-RELAY.md with a full walkthrough for deploying a new Mempool.space indexer (Angornet or mainnet) and a new strfry Nostr relay. - Update mainnet explorer compose to use stock mempool/backend and mempool/frontend images (drop the old blockcore/mempool-* fork and ANGOR_ENABLED flag, no longer required), and add a Fulcrum container for address indexing so mainnet no longer depends on a host-installed Fulcrum instance. --- docker/DEPLOY-INDEXER-AND-RELAY.md | 301 ++++++++++++++++++ docker/explorers/mainnet/docker-compose.yml | 57 +++- src/design/App.Test.Uat/MANUAL-TEST-GUIDE.md | 307 +++++++++++++++++++ 3 files changed, 653 insertions(+), 12 deletions(-) create mode 100644 docker/DEPLOY-INDEXER-AND-RELAY.md create mode 100644 src/design/App.Test.Uat/MANUAL-TEST-GUIDE.md diff --git a/docker/DEPLOY-INDEXER-AND-RELAY.md b/docker/DEPLOY-INDEXER-AND-RELAY.md new file mode 100644 index 000000000..00f33abef --- /dev/null +++ b/docker/DEPLOY-INDEXER-AND-RELAY.md @@ -0,0 +1,301 @@ +# Deploying a New Indexer and a New Nostr Relay + +This guide walks through standing up your own **blockchain indexer** (a Mempool.space instance) +and your own **Nostr relay** (strfry) for use with Angor — either for mainnet, for Angor's custom +testnet ("Angornet"), or purely for local development/testing. + +All Docker Compose files referenced here already exist in this repo under `docker/`, so in most +cases you're just running `docker compose up -d` with the right environment variables, not +writing new config from scratch. + +## Prerequisites (all deployments) + +- A Linux VPS or server with Docker + Docker Compose v2 installed (`docker compose version`). +- Outbound internet access (to pull images and, for the indexer, to sync the chain). +- A domain name if you want a public HTTPS endpoint (not required for local/dev use). + +--- + +## Part 1 — Deploying a New Indexer (Mempool.space instance) + +The indexer provides address lookups, transaction history, and fee estimates that the Angor app +depends on. It is **not** a custom Angor component — it's a Mempool.space (`mempool/backend` + +`mempool/frontend`) instance backed by an Electrum server (Fulcrum), pointed at either mainnet +Bitcoin or Angor's custom signet ("Angornet"). Decide which network you're indexing first, since +the two composes are quite different. + +### Option A — Indexer for Angornet (custom signet testnet) + +This is the easiest option to stand up because the compose file is **self-contained** — it +includes the signet node itself, so you don't need a separate Bitcoin Core install. + +**Location:** `docker/explorers/angornet/docker-compose.yml` + +1. **Build the custom signet node image locally** — it is *not* published to Docker Hub: + ```bash + git clone https://github.com/block-core/bitcoin-custom-signet.git + cd bitcoin-custom-signet + docker build -t blockcore/bitcoin-signet:latest . + ``` + (See that repo's own README for exact build instructions/Dockerfile location if it has + changed.) + +2. Copy `docker/explorers/angornet/docker-compose.yml` to your server (or clone this repo there). + +3. Review/override the environment variables at the top of the `node` service if needed: + - `SIGNETCHALLENGE` — must match Angor's signet challenge (already set correctly in the file; + don't change this unless you are deliberately running a different signet). + - `ADDNODE` — the Angor signet seed peer (`207.180.254.78`); leave as-is so your node syncs + from the real Angornet chain instead of starting an isolated one. + - `RPCUSER` / `RPCPASSWORD` — change from the defaults for anything beyond local testing. + +4. Start the stack: + ```bash + cd docker/explorers/angornet + docker compose up -d + ``` + +5. Wait for the signet node to sync. Check progress with: + ```bash + docker exec angornet-btc-node bitcoin-cli -signet getblockchaininfo + ``` + Then confirm Fulcrum has caught up: + ```bash + docker logs -f angornet-fulcrum + ``` + +6. Once Fulcrum reports it's caught up, the Mempool backend/frontend will start serving. Verify: + ```bash + curl http://localhost:8080/api/v1/fees/recommended + ``` + +7. **Exposed ports** you'll need to forward/proxy externally: + | Port | Purpose | + |---|---| + | 8080 | Mempool frontend + API (this is your public indexer URL) | + | 8999 | Mempool backend API (internal only — proxied by the frontend) | + | 38333 | Signet P2P (only needed if you want other nodes to peer with you) | + | 38332 | Signet RPC (keep this firewalled off from the public internet) | + +8. Put TLS + a public hostname in front of port 8080. Angor's own deployment uses + [FRP](https://github.com/fatedier/frp) tunnels + [Caddy](https://caddyserver.com/) on a + separate VPS (see the [deploy-proxy-frp](https://github.com/block-core/deploy-proxy-frp) repo + for that exact setup), but any reverse proxy (nginx, Caddy, Traefik) terminating TLS and + forwarding to `your-host:8080` works fine. + +9. Point the Angor app at your new indexer. In the app: **Settings** → set the custom indexer URL + to `https://your-domain/` (or, for local dev, set the `ANGOR_INDEXER_URL` environment variable + before launching `App.Desktop` — see `src/design/App.Test.Integration/docker/README.md` for + how the integration test stack does this). + +### Option B — Indexer for Mainnet + +Use this if you already run (or plan to run) your own **Bitcoin Core** node on the host machine. +Fulcrum (the Electrum server needed for address indexing) and the Mempool frontend/backend now +run in Docker as part of this compose file — you no longer need to install Fulcrum separately. + +**Location:** `docker/explorers/mainnet/docker-compose.yml` + +**Prerequisites (must exist on the host, not in this compose file):** +- Bitcoin Core, fully synced, with: + - RPC enabled (default port 8332) + - `txindex=1` (required by Fulcrum) + - ZMQ block/tx notifications enabled in `bitcoin.conf`: + ``` + zmqpubrawblock=tcp://0.0.0.0:28332 + zmqpubrawtx=tcp://0.0.0.0:28333 + ``` + +1. Copy `docker/explorers/mainnet/docker-compose.yml` to your server. +2. Set environment variables (via a `.env` file next to the compose file, or exported in your + shell) so the Fulcrum container can reach your host's Bitcoin Core: + ```bash + CORE_RPC_HOST=172.17.0.1 # or host.docker.internal, or your host's LAN IP + CORE_RPC_PORT=8332 + CORE_RPC_USERNAME=rpcuser + CORE_RPC_PASSWORD= + ``` + `172.17.0.1` is the default Docker bridge gateway IP, which lets containers reach services + bound on the host — confirm it matches your Docker network (`docker network inspect bridge`). +3. Start the stack: + ```bash + cd docker/explorers/mainnet + docker compose up -d + ``` +4. Fulcrum needs to build its own address index against your Bitcoin Core node before the + indexer is useful — this can take a while on first run. Follow progress with: + ```bash + docker logs -f mainnet-fulcrum + ``` +5. Once Fulcrum has caught up, verify the Mempool API: + ```bash + curl http://localhost:8189/api/v1/fees/recommended + ``` +6. **Exposed ports:** + | Port | Purpose | + |---|---| + | 8189 | Mempool frontend + API (public indexer URL) | + | 8999 | Mempool backend API (internal only) | + | 50001 | Fulcrum electrum TCP (internal — only expose if other tools need direct Electrum access) | +7. Put TLS + a public hostname in front of port 8189, same as Option A step 8. +8. Point the Angor app at it via **Settings** (custom indexer URL) or the `ANGOR_INDEXER_URL` env + var. + +### Notes common to both options + +- Both options now use the **standard, unmodified Mempool.space images** (`mempool/backend` / + `mempool/frontend`) with **no custom fork and no `ANGOR_ENABLED` flag** — any stock Mempool.space + instance works as-is. (An older Angor deployment used a custom `blockcore/mempool-*` fork with + an `ANGOR_ENABLED` flag; this is no longer required.) +- Both options run their own **Fulcrum** container for address indexing — Angornet's Fulcrum + points at the in-compose signet node; mainnet's Fulcrum points at your host's Bitcoin Core. +- Data persists in named Docker volumes (`*-mempool-cache`, `*-mempool-db`, `*-btc-data`, + `*-fulcrum-data`). Back these up if you care about not re-syncing/re-indexing from scratch. +- Angor's own live instances for reference: mainnet `https://indexer.angor.io`, testnet + `https://test.indexer.angor.io`. + +--- + +## Part 2 — Deploying a New Nostr Relay (strfry) + +The relay stores/serves the Nostr events that carry Angor project metadata (profiles, updates, +etc.). Angor uses [strfry](https://github.com/hoytech/strfry) via the community Docker image +`dockurr/strfry`. This is a good option for project founders who want to host a relay for their +own community. + +**Location:** `docker/relays/docker-compose.yml` and `docker/relays/strfry.conf` + +### Steps + +1. Copy the `docker/relays/` directory to your server, preserving the layout — the compose file + expects `./strfry/` (data dir) and `./strfry/strfry.conf` (config) relative to itself: + ```bash + mkdir -p docker/relays/strfry + cp docker/relays/strfry.conf docker/relays/strfry/strfry.conf + ``` + (Adjust: the compose file currently mounts `./strfry/strfry.conf` — make sure your config file + ends up at exactly that path relative to the compose file.) + +2. Edit `strfry.conf` before first start (some settings require a restart to change later, but + are much easier to get right from the start): + - `relay.info.name` — short relay name shown to clients (NIP-11), e.g. `"My Angor Relay"`. + - `relay.info.description` — free-form description. + - `relay.info.pubkey` / `relay.info.contact` — your admin nostr pubkey / contact email, so + users know who runs it. + - Leave `db`, `port` (7777), and the threading/negentropy settings at their defaults unless you + have a specific reason to change them. + +3. This compose file assumes an **external nginx-proxy + acme-companion (Let's Encrypt) stack** + is already running on the host, on a Docker network named `proxy`. If you don't have one: + ```bash + docker network create proxy + docker run -d --name nginx-proxy --network proxy -p 80:80 -p 443:443 \ + -v /var/run/docker.sock:/tmp/docker.sock:ro \ + -v certs:/etc/nginx/certs -v vhost:/etc/nginx/vhost.d -v html:/usr/share/nginx/html \ + nginxproxy/nginx-proxy + docker run -d --name acme-companion --network proxy \ + --volumes-from nginx-proxy \ + -v /var/run/docker.sock:/var/run/docker.sock:ro \ + -v acme:/etc/acme.sh \ + nginxproxy/acme-companion + ``` + (This is the standard `nginxproxy/nginx-proxy` + `nginxproxy/acme-companion` pairing that + reads the `VIRTUAL_HOST`/`LETSENCRYPT_HOST` env vars automatically — you only need to set this + up once per host, even if you later add more services behind it.) + +4. Edit the environment variables in `docker-compose.yml` for your own domain: + ```yaml + environment: + VIRTUAL_HOST: relay.yourdomain.com + VIRTUAL_PORT: 7777 + VIRTUAL_PROTO: http + VIRTUAL_NETWORK: proxy + LETSENCRYPT_HOST: relay.yourdomain.com + LETSENCRYPT_EMAIL: you@yourdomain.com + ``` + Point your domain's DNS `A`/`AAAA` record at the server before starting, so Let's Encrypt can + validate it. + +5. Start the relay: + ```bash + cd docker/relays + docker compose up -d + ``` + +6. Verify: + ```bash + # Websocket handshake (should not error) + curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" \ + -H "Sec-WebSocket-Version: 13" -H "Sec-WebSocket-Key: test==" \ + http://localhost:7777 + + # NIP-11 relay info document + curl -H "Accept: application/nostr+json" http://localhost:7777 + ``` + Once TLS is up: `wss://relay.yourdomain.com` should be reachable from any Nostr client. + +7. Optional web view: the `web` service (`getumbrel/umbrel-nostr-relay`) is exposed on port 3000 + for a simple browser view of relay activity — reachable at + `http://relay.yourdomain.com:3000` (not proxied through nginx-proxy in the sample config). + +8. Point the Angor app at your relay: **Settings** → add `wss://relay.yourdomain.com` to the list + of relays. Angor's default relays (for reference) are `wss://relay.angor.io` and + `wss://relay2.angor.io`; mainnet additionally includes public relays like + `wss://relay.damus.io` and `wss://nos.lol`. You can run yours alongside or instead of these. + +### Notes + +- Data lives in `./strfry/` (LMDB database) on the host via a bind mount — back this directory up + if you want to preserve relay history. +- `strfry.conf` has a `writePolicy.plugin` option (commented out) if you want to restrict who can + publish to your relay (e.g. only allow specific pubkeys) — point it at an executable script; see + [strfry's own docs](https://github.com/hoytech/strfry) for the plugin protocol. +- `rejectEventsOlderThanSeconds` / `ephemeralEventsLifetimeSeconds` control retention — defaults + are generous (about 3 years for normal events); tune if disk space is a concern. + +--- + +## Part 3 — Quick Local Dev Alternative (no public hosting needed) + +If you just want an indexer + relay for local development/testing (not a public deployment), +there's already a fully self-contained stack used by the integration tests: + +**Location:** `src/design/App.Test.Integration/docker/docker-compose.yml` +(see the accompanying `README.md` in that folder for exact usage) + +This spins up, on `localhost` only: +- A private signet node + Fulcrum + Mempool indexer at `http://localhost:48080` +- Two strfry relays at `ws://localhost:47777` and `ws://localhost:47778` +- A faucet API at `http://localhost:48500` for funding test wallets + +Start it with: +```bash +cd src/design/App.Test.Integration/docker +docker compose up -d +``` + +Then launch the app pointed at this stack via environment variables: +```bash +ANGOR_INDEXER_URL=http://localhost:48080 \ +ANGOR_RELAY_URLS=ws://localhost:47777,ws://localhost:47778 \ +ANGOR_FAUCET_BASE_URL=http://localhost:48500 \ +dotnet run --project src/design/App.Desktop +``` +This is the fastest way to get an isolated indexer + relay pair running for development without +touching DNS, TLS, or any of the reverse-proxy setup described above. + +--- + +## Reference: Live Angor Endpoints + +| Service | Network | URL | +|---|---|---| +| Indexer | Mainnet | `https://indexer.angor.io` | +| Indexer | Testnet (Angornet) | `https://test.indexer.angor.io` | +| Relay | Both | `wss://relay.angor.io`, `wss://relay2.angor.io` | +| Faucet | Testnet | `https://test.faucet.angor.io/api/faucet/send/{address}/{amount}` | +| Boltz (swaps) | Testnet | `https://test.boltz.angor.io` | + +Use these as a sanity check — e.g. compare your new indexer's `/api/v1/fees/recommended` response +against `https://test.indexer.angor.io/api/v1/fees/recommended` to confirm it's returning sane +data. diff --git a/docker/explorers/mainnet/docker-compose.yml b/docker/explorers/mainnet/docker-compose.yml index 3506cf677..993aa1c73 100644 --- a/docker/explorers/mainnet/docker-compose.yml +++ b/docker/explorers/mainnet/docker-compose.yml @@ -1,34 +1,68 @@ # Angor Mainnet Explorer Stack # -# This runs the mempool stack only: MariaDB, mempool backend (API), -# and mempool frontend (explorer). It connects to a Bitcoin Core node -# and Fulcrum electrum server running natively on the host. +# This runs the mempool stack: Fulcrum (Electrum server), MariaDB, +# mempool backend (API), and mempool frontend (explorer). It uses the +# standard, unmodified Mempool.space images - no custom fork or +# Angor-specific flags are required. +# +# Fulcrum indexes addresses/UTXOs from a Bitcoin Core node and is +# required for the mempool backend's address-lookup features. Bitcoin +# Core itself still runs natively on the host (not in this compose). # # TLS is handled externally via FRP reverse proxy + Caddy on the VPS. # # Exposed ports: # - 8189 -> mempool frontend + API (indexer.angor.io) # - 8999 -> mempool backend API (internal, proxied by frontend) +# - 50001 -> Fulcrum electrum TCP (internal, only needed if you want +# to point other tools/clients at this Fulcrum instance) # # Prerequisites: -# - Bitcoin Core running on the host (RPC on port 8332) -# - Fulcrum running on the host (TCP on port 50001) +# - Bitcoin Core running on the host, fully synced, with: +# - RPC enabled (default port 8332) +# - txindex=1 (Fulcrum requires this) +# - ZMQ block/tx notifications enabled, e.g. in bitcoin.conf: +# zmqpubrawblock=tcp://0.0.0.0:28332 +# zmqpubrawtx=tcp://0.0.0.0:28333 # # Configuration: -# Set CORE_RPC_HOST, ELECTRUM_HOST to the host IP or use +# Set CORE_RPC_HOST / CORE_ZMQ_HOST to the host IP, or use # host.docker.internal / 172.17.0.1 (default Docker bridge gateway). # # Usage: # docker compose up -d +# (Fulcrum will take a while to do its initial address index sync - +# follow progress with: docker logs -f mainnet-fulcrum) volumes: mempool_api: name: mainnet-mempool-cache mempool_db: name: mainnet-mempool-db + fulcrum_data: + name: mainnet-fulcrum-data services: + fulcrum: + container_name: mainnet-fulcrum + image: cculianu/fulcrum:latest + user: root + volumes: + - fulcrum_data:/fulcrum_db + command: > + Fulcrum + --bitcoind ${CORE_RPC_HOST:-172.17.0.1}:${CORE_RPC_PORT:-8332} + --rpcuser ${CORE_RPC_USERNAME:-rpcuser} + --rpcpassword ${CORE_RPC_PASSWORD:-rpcpassword} + --tcp 0.0.0.0:50001 + --stats 0.0.0.0:3003 + restart: unless-stopped + ports: + - "50001:50001" + networks: + - mainnet + db: container_name: mainnet-mempool-db image: mariadb:10.5.21 @@ -47,15 +81,16 @@ services: api: container_name: mainnet-mempool-api - image: blockcore/mempool-backend:latest + image: mempool/backend:latest user: "0:0" depends_on: - db + - fulcrum environment: MEMPOOL_NETWORK: "mainnet" MEMPOOL_BACKEND: "electrum" - ELECTRUM_HOST: ${ELECTRUM_HOST:-172.17.0.1} - ELECTRUM_PORT: ${ELECTRUM_PORT:-50001} + ELECTRUM_HOST: "mainnet-fulcrum" + ELECTRUM_PORT: "50001" ELECTRUM_TLS_ENABLED: "false" CORE_RPC_HOST: ${CORE_RPC_HOST:-172.17.0.1} CORE_RPC_PORT: ${CORE_RPC_PORT:-8332} @@ -67,7 +102,6 @@ services: DATABASE_USERNAME: "mempool" DATABASE_PASSWORD: "mempool" STATISTICS_ENABLED: "true" - ANGOR_ENABLED: "true" restart: unless-stopped stop_grace_period: 1m command: "./wait-for-it.sh mainnet-mempool-db:3306 --timeout=720 --strict -- ./start.sh" @@ -80,7 +114,7 @@ services: web: container_name: mainnet-mempool-web - image: blockcore/mempool-frontend:latest + image: mempool/frontend:latest user: "0:0" depends_on: - api @@ -89,7 +123,6 @@ services: BACKEND_MAINNET_HTTP_HOST: "mainnet-mempool-api" MAINNET_ENABLED: "true" ROOT_NETWORK: "mainnet" - ANGOR_ENABLED: "true" LIGHTNING: "false" restart: unless-stopped stop_grace_period: 1m diff --git a/src/design/App.Test.Uat/MANUAL-TEST-GUIDE.md b/src/design/App.Test.Uat/MANUAL-TEST-GUIDE.md new file mode 100644 index 000000000..c45d89688 --- /dev/null +++ b/src/design/App.Test.Uat/MANUAL-TEST-GUIDE.md @@ -0,0 +1,307 @@ +# Angor App — Manual Test Guide + +This document is the manual (human-driven) equivalent of the automated UAT test suite in +`src/design/App.Test.Uat/`. Use it when you don't have a dev environment to run the automated +tests, or when you want a human to sanity-check the same flows visually in the real UI. + +Each section below mirrors one automated test file (named in the heading) so that if a manual +test fails, a developer can find/re-run the matching automated test for a deeper investigation. + +> All tests run against **Angornet** (Angor's signet-based test network), never Mainnet, unless a +> step explicitly says otherwise. Never use real funds for these tests. + +## General setup (do this once per test run) + +1. Install/launch the Angor App (Desktop build of `src/design/App.Desktop`). +2. Go to **Settings**: + - If you want a totally clean start, use **Wipe Data** (and tick **Purge recovery files** if you + also want previously-created test wallets forgotten). + - Set **Network** to **Angornet**. + - Enable **Debug Mode** (several test-only features — auto-approve toggle, faucet button — + only appear in debug mode). +3. Create a wallet: **Funds** → **Add Wallet** → **Generate** → confirm. Write down the seed + words shown (you'll need them for recovery tests). +4. Fund the wallet: on the wallet screen use the **Get Test Coins** (faucet) button. Wait for the + balance to update (may take up to a minute). + +For multi-user tests (founder + one or more investors) you need **separate app +instances/profiles** running at the same time — e.g. install the app on a second device, run a +second user profile/VM, or use separate app data directories if the build supports a profile +switch. Treat each "user" in the steps below as its own wallet + its own app session. + +--- + +## 1. Create & Edit Project (`CreateProjectTest`) + +**Purpose:** Founder can create Investment and Fund-type projects, upload images, and edit the +project profile with changes reflected publicly. + +**Preconditions:** Funded wallet, internet access. + +**Steps:** +1. Go to **My Projects** → **Create Project**. +2. Choose type **Investment**. Enter a name and "about" text. For images, either upload a photo + or paste an image URL for both banner and profile picture. Set a target amount (e.g. 1 BTC) + and an end date. Let the wizard generate a 3-month funding stage schedule. Click **Deploy**. + - ✅ Verify the project appears in **My Projects** with type "Investment" after deployment. +3. Repeat, this time choosing type **Fund**, frequency **Monthly**, 6 installments, target 1 BTC, + threshold 0.01 BTC, payout day = today's day of month. Deploy. + - ✅ Verify it appears with type "Fund" / Monthly. +4. Repeat again with frequency **Weekly**, 3 installments, target 0.5 BTC, threshold 0.005 BTC, + payout day = today's weekday. Deploy. + - ✅ Verify it appears with type "Fund" / Weekly. +5. Open the **Investment** project from step 2 → **Edit Profile**. Upload a new banner image and + a new profile image (these upload to the Blossom image host) — wait for each upload to + complete and show a preview. +6. Edit the name, about text, website, and markdown description. Save. +7. Wait ~10-15 seconds (Nostr relay propagation), then close and reopen the project's public + profile page. + - ✅ Verify every field you changed (name, about, picture, banner, website, description) + shows the new values, not the old ones. + +--- + +## 2. Fund Project Full Lifecycle (`MultiFundClaimAndRecoverTest`) + +**Purpose:** Exercise a Fund-type project across auto-approval, manual approval, staged +claiming, and all recovery types. + +**Preconditions:** 1 founder + at least 2 investor sessions (4 recommended), funded wallets. + +**Steps:** +1. Founder: create a **Fund** project — Monthly, 6 installments, target 1 BTC, threshold + 0.01 BTC, penalty days 0, payout day = today, start date = yesterday. Deploy. +2. Investor A: invest a small amount **below the threshold** (e.g. 0.001 BTC), choosing the + 6-stage funding pattern. + - ✅ Verify this is **auto-approved** immediately (no founder action needed) — check + **Funded** tab shows it as approved/active without visiting the Funders screen. +3. Investor B: invest another below-threshold amount (e.g. 0.002 BTC) with a 3-stage pattern. + - ✅ Also auto-approved. +4. Investor C: invest **above the threshold** (e.g. 0.02 BTC), 3-stage pattern. +5. Investor D: invest above threshold (e.g. 0.03 BTC), 6-stage pattern. + - ✅ Verify C and D show as **"waiting for approval"**. +6. Founder: go to **Funders**, filter by "waiting" — approve both C's and D's requests. +7. Investors C and D: go to **Funded** → **Manage** → **Confirm Investment**. + - ✅ Verify each reaches the "active" state (Step 3). +8. Founder: **My Projects** → project → **Manage** → **Claim Stage 1**. + - ✅ Verify the claim screen lists 6 stages and shows 4 claimable UTXOs (one per investor). + - Click claim, confirm. + - ✅ Success modal appears. +9. Investor C: **Funded** → **Manage** — wait for status to change to "recovery" → click + **Recover Funds** → **Confirm Recovery**. + - ✅ Succeeds, investment shows "In Penalty". +10. Investor C: open the **Penalties** popup (button on Funded tab). + - ✅ Verify it lists exactly 1 penalty entry, correct project name, 0 days remaining, status + "Penalty release available now". Close popup. +11. Investor C: wait for status "penaltyRelease" → **Recover Funds** → **Confirm Release**. + - ✅ Succeeds. +12. Founder: **Manage** → **Release Funds** (releases remaining unclaimed stages). +13. Investor D: wait for status "unfundedRelease" → **Recover Funds** → **Confirm Release**. + - ✅ Succeeds. +14. Investors A and B: wait for status "belowThreshold" → **Recover Funds** → **Confirm + Recovery**. + - ✅ Succeeds for both. +15. Founder: reopen the **Claim** view for the project. + - ✅ Verify it still loads without error, still shows 6 stages, and no rows are missing/blank + even though investors have since spent their own UTXOs. + +--- + +## 3. Investment Project Full Lifecycle (`MultiInvestClaimAndRecoverTest`) + +**Purpose:** Exercise an Investment-type project: cancel-before-approval, cancel-after-approval ++ reinvest, auto-approve toggle, claim, release, recovery. + +**Preconditions:** 1 founder + 4 investor sessions. + +**Steps:** +1. Founder: deploy a default **Investment**-type project. +2. Investor A: invest 0.02 BTC, then immediately click **Cancel Investment** (the "before + approval" cancel button) — then invest 0.02 BTC again (don't confirm yet). +3. Investor B: invest 0.02 BTC. Founder: approve just Investor B's request on **Funders**. + Investor B: click **Cancel Investment** (the "after approval" cancel button) — then invest + 0.02 BTC again. +4. Investor C: invest 0.02 BTC normally. +5. Investor D: invest 0.03 BTC normally. +6. Founder: go to **Funders** → toggle on **Auto-Approve** (only visible in Debug Mode). + - ✅ Wait a short while and verify all 4 outstanding investment requests get approved + automatically without clicking anything else. +7. All 4 investors: **Funded** → **Manage** → **Confirm Investment**. + - ✅ Verify each reaches "active" (Step 3). +8. Founder: **Claim Stage 1**. + - ✅ Verify 4 claimable UTXOs shown, claim succeeds. +9. Founder: **Release Funds**. +10. All 4 investors, one at a time: **Recover Funds** ("unfundedRelease") → **Confirm Release**. + - ✅ Each succeeds. +11. Founder: reopen **Claim** view. + - ✅ Verify it still loads correctly with a non-empty stage list and no missing UTXO rows. + +--- + +## 4. One-Click Invoice Payments (`OneClickInvestInvoiceTest`) + +**Purpose:** Verify paying via a generated invoice (on-chain address or Lightning BOLT11) works +for both deploying a project and investing, and that a wallet is auto-created the first time it's +needed. + +**Preconditions:** A Lightning wallet/app (e.g. a phone Lightning wallet or ThunderHub access) +capable of paying a BOLT11 invoice. **Do not pre-create wallets** for founder or investor — the +point of this test is that the app creates them automatically. + +**Steps:** +1. Launch a fresh Founder app session (no wallet) and a fresh Investor app session (no wallet). + Wipe data, switch to Angornet, enable Debug Mode on both. +2. Founder: **My Projects** → **Create Project** (Investment type). At the deploy-payment step, + choose **On-chain**. An address is shown. + - Pay that address using the faucet. + - ✅ Wait up to 5 minutes; verify the project deploy completes once payment is detected, and + that a wallet now exists for this session. +3. Founder: create a second project (Fund type, Monthly, 3 installments). Choose **Lightning** as + the deploy-payment method. + - ✅ Verify a BOLT11 invoice is shown (starts with "ln..."). + - Pay it with your Lightning wallet. + - ✅ Verify deploy completes once payment is detected. +4. Investor: open the Fund project from step 3, invest 0.001 BTC, choose **Lightning**. + - ✅ Verify a wallet is auto-created for the investor session and a BOLT11 invoice is shown. + - Pay it with your Lightning wallet. + - ✅ Verify the investment succeeds once payment is detected. +5. Investor: open the Investment project from step 2, invest 0.001 BTC, choose **On-chain**. + - ✅ Verify an address is shown; pay via faucet; verify investment succeeds. + +--- + +## 5. Wallet Send/Receive Stress Test (`SendFundsTest`) + +**Purpose:** Verify sending/receiving between wallets works reliably, balances never exceed the +funded total, and "Send All" sweeps a wallet to zero. + +**Preconditions:** 3 funded wallets/sessions (User A, B, C). + +**Steps:** +1. Fund all 3 wallets via faucet; wait until balances stop changing. Note each balance and the + combined total. +2. Repeat 5 times: + - Get a fresh receive address for each user. + - Send 0.005 BTC: A→B, B→C, C→A (use a manual fee rate around 2 sats/vB if the option is + available). + - ✅ Verify each send reports success with a transaction ID. + - Wait ~10 seconds, refresh balances. + - ✅ Verify the sum of all 3 balances never exceeds the original combined total (only + transaction fees should ever be lost). +3. After all rounds, verify all 3 users still show a positive balance. +4. **Sweep test:** User C sends their **entire** balance to User A using the **Send All / 100%** + button (don't type a manual amount) at a low fee rate. + - ✅ Verify User C's balance drops to exactly 0. + - ✅ Verify User A's balance increases by C's swept amount (minus network fee). + +--- + +## 6. Settings — Theme, Backup, Network Switch (`SettingsTest`) + +**Purpose:** Verify theme toggle persistence, seed word backup/reveal, and network switching +doesn't break the project list or lose the wallet. + +**Preconditions:** A wallet (doesn't need funding). + +**Steps:** +1. Create a wallet, write down the seed words shown at creation time. +2. Go to **Settings**. Toggle **Dark Theme** on/off. + - ✅ Verify the UI switches theme immediately, and the toggle stays in the new position after + leaving and re-entering Settings. + - Toggle back to the original value. +3. In Settings, click **Reveal Seed** (or "Backup Account"). + - ✅ Verify the seed words shown exactly match those captured in step 1. + - ✅ Verify a **Download Seed** option becomes available. + - Click **Reveal** again to hide the words. +4. Go to **Find Projects** while on Angornet; wait until at least one project loads (up to 2 + minutes). +5. Go to **Settings** → switch **Network** to **Mainnet**. + - ✅ Verify the network label updates to "Mainnet". + - Go to **Find Projects** again. + - ✅ Verify the list reloads with Mainnet projects (not the old Angornet list). +6. Switch network back to **Angornet**. + - ✅ Verify the label updates back and **Find Projects** reloads Angornet projects again. +7. Go to **Funds**. + - ✅ Verify the wallet created in step 1 still exists after switching networks twice. + +--- + +## 7. Wallet & Investment Recovery from Seed Words (`WalletRecoveryTest`) + +**Purpose:** Verify a wiped app can fully recover a wallet from seed words, and that an active +investment automatically reappears from network data (no manual re-entry). + +**Preconditions:** Founder + Investor sessions, funded wallets. + +**Steps:** +1. Founder: create/fund a wallet, note the seed words and wallet ID (visible on wallet details). +2. Founder: deploy an Investment-type project. +3. Investor: create/fund a wallet, note its seed words and wallet ID. +4. Investor: invest 0.02 BTC in the founder's project. +5. Founder: **Funders** → approve the investor's request. +6. Investor: **Funded** → **Manage** → **Confirm Investment**. + - ✅ Verify it reaches "active" (Step 3). +7. Founder: go to **Settings** → **Wipe Data** (do NOT purge recovery files). Switch to Angornet. + Then **Funds** → **Add Wallet** → **Import** → paste the founder's seed words. + - ✅ Verify the restored wallet's ID exactly matches the wallet ID noted in step 1. +8. Investor: same wipe + import process using the investor's seed words. + - ✅ Verify the restored wallet ID matches step 3. +9. Investor: go to **Funded** tab and wait (poll/refresh over up to 5 minutes if needed). + - ✅ Verify the investment from step 4 **reappears automatically** in the portfolio for the + same project — with no manual re-entry of any investment details. + +--- + +## 8. Wipe Data vs. Purge Recovery Files (`WipeDataRecoveryTest`) + +**Purpose:** Verify the difference between a standard "Wipe Data" (keeps wallet recovery files +for restore) and "Wipe Data + Purge Recovery Files" (deletes them entirely). + +**Preconditions:** None (wallets don't need funding). + +**Steps:** +1. Start from a clean slate: **Settings** → **Wipe Data** with **Purge recovery files** ticked. + Switch to Angornet, enable Debug Mode. +2. Create wallet #1 (leave unfunded). Note its ID. +3. Create wallet #2 (use the "create new" / force-new option so it doesn't reuse #1). Note its + ID — confirm it's different from #1. +4. Sanity check: switch network to Mainnet, wait a moment, switch back to Angornet. +5. **Settings** → **Wipe Data** (this time WITHOUT the purge option). Switch back to Angornet. +6. Go to **Funds** → **Add Wallet** → **Import** → expand **Restore from backup** (or similarly + named list of previously-known wallets). + - ✅ Verify **2** entries are listed (both wallets survived the standard wipe). +7. Select wallet #1's entry to restore it. + - ✅ Verify it restores successfully with the same wallet ID as step 2. +8. **Settings** → **Wipe Data** WITH **Purge recovery files** ticked this time. Switch to + Angornet. +9. Go to **Funds** → **Add Wallet** → **Import** → **Restore from backup**. + - ✅ Verify the list is now **empty** (0 entries) — the purge deleted all recovery files. +10. Create a fresh wallet to confirm the app still works normally after the full purge. + +--- + +## 9 & 10. Large-Scale Load Tests (`BigFundTest`, `BigInvestTest`) + +**Purpose:** Confirm the app handles a project with **15 concurrent investors** without errors +in claiming, approval, or recovery. + +> These are long-running (many minutes) and resource-heavy (15 simultaneous investor sessions). +> **Skip in routine manual testing** — only run before a major release or when specifically +> investigating a scale-related bug. If you do run them, follow the same steps as Test 2 +> (`BigFundTest` ≈ Fund project lifecycle) or Test 3 (`BigInvestTest` ≈ Investment project +> lifecycle) above, but with 15 investor sessions instead of 2-4, and expect the claim screen to +> show 15 claimable UTXOs instead of 2-4. + +--- + +## Reporting Issues + +For each failed step, capture: +- Which numbered test and step failed. +- Screenshot(s) of the unexpected state. +- The network (should always be Angornet) and approximate time. +- Any error message/toast text shown by the app. + +File the issue against the matching automated test name (e.g. "MultiFundClaimAndRecoverTest step +9 — claim screen") so a developer can correlate it with `src/design/App.Test.Uat/` and re-run the +automated version for a full stack trace.