diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9bfb838..f754cbe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,10 +4,10 @@ jobs: regtest: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Run tests run: | - git clone https://github.com/lnbits/lnbits-legend.git - docker build -t lnbitsdocker/lnbits-legend lnbits-legend - chmod +x ./tests - ./tests + git clone https://github.com/lnbits/lnbits.git + docker build -t lnbits/lnbits lnbits + chmod +x ./start-regtest + ./start-regtest diff --git a/.gitignore b/.gitignore index c542600..e60bd79 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,13 @@ data + !data/boltz data/boltz/* !data/boltz/boltz.conf + +!data/boltz-client +data/boltz-client/* +!data/boltz-client/boltz.toml + !data/electrs data/electrs/* !data/electrs/config.toml diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..94e7945 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,90 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Overview + +This is a Bitcoin Lightning Network regtest environment for local development and testing. It provides a Docker Compose setup with multiple interconnected Lightning nodes (LND, Core Lightning, Eclair), Bitcoin Core, Elements/Liquid sidechain, and supporting services like LNbits, Boltz, and Electrs. + +## Common Commands + +### Start the Environment +```sh +./start-regtest # Start all services and run initialization tests +source docker-scripts.sh # Load helper functions for CLI access +``` + +### Stop the Environment +```sh +source docker-scripts.sh +lnbits-regtest-stop # Stops containers and cleans up node data +``` + +### CLI Helpers (after sourcing docker-scripts.sh) +```sh +bitcoin-cli-sim -generate 1 # Mine blocks +lightning-cli-sim 1 getinfo # CLN node 1 (use 1, 2, or 3) +lncli-sim 1 getinfo # LND node 1 (use 1, 2, 3, or 4) +elements-cli-sim getinfo # Elements/Liquid +boltzcli-sim getinfo # Boltz client +``` + +### View Logs +```sh +docker logs lnbits-lnbits-1 -f +docker logs lnbits-lnd-1-1 -f +docker logs lnbits-clightning-1-1 -f +docker logs lnbits-boltz-1 -f +``` + +## Architecture + +### Lightning Network Topology +- **lnd-1**: Hub node with channels to all other nodes. Used for local LNbits testing +- **lnd-2**: Used for Boltz backend swaps +- **lnd-3**: Backend for the dockerized LNbits instance +- **lnd-4**: Standalone node +- **cln-1, cln-2, cln-3**: Core Lightning nodes. cln-2 has REST API via clightning-2-rest +- **eclair-1**: Eclair node with channels from lnd-1 and lnd-2 + +### Channel Graph +lnd-1 is the central hub connected to: lnd-2, lnd-3, cln-1, cln-2, cln-3, eclair-1. Additional connections: lnd-2→cln-2, lnd-3→cln-3, lnd-3→cln-1, lnd-2→eclair-1 + +### Supporting Services +- **bitcoind**: Regtest Bitcoin Core (RPC on 18443) +- **elementsd**: Liquid regtest sidechain (RPC on 18884) +- **electrs**: Bitcoin Electrum server (ports 19001, 3002) +- **electrs-liquid**: Liquid Electrum server (ports 19002, 3003) +- **boltz + boltz-client**: Swap service with Postgres backend +- **lnbits**: Lightning wallet/app platform (port 5001) +- **litd**: Lightning Terminal connected to lnd-1 (ports 8443, 8080) +- **fava**: Beancount accounting interface (port 3333) + +## Web Interfaces +- LNbits: http://localhost:5001/ +- Mempool (via electrs): http://localhost:3002/ +- Boltz API: http://localhost:9001/ +- Lightning Terminal: https://localhost:8443/ (password: testpassword123) +- Fava: http://localhost:3333/ +- Eclair API: http://localhost:8082/ (password: lnbits) + +## Configuration for Local LNbits Development + +When running LNbits locally against this regtest: + +```sh +# LND backend +LNBITS_BACKEND_WALLET_CLASS="LndRestWallet" +LND_REST_ENDPOINT=https://127.0.0.1:8081/ +LND_REST_CERT=./docker/data/lnd-1/tls.cert +LND_REST_MACAROON=./docker/data/lnd-1/data/chain/bitcoin/regtest/admin.macaroon + +# Or CLN backend +LNBITS_BACKEND_WALLET_CLASS="CoreLightningWallet" +CORELIGHTNING_RPC=./docker/data/clightning-1/regtest/lightning-rpc +``` + +## Data Persistence +- Node data stored in `./data//` +- `lnbits-regtest-stop` cleans up Lightning node directories but preserves config files +- Bitcoin/Elements data uses Docker volumes (cleaned on stop) diff --git a/README.md b/README.md index 4ffa6df..2b68d3f 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,8 @@ docker build -t lnbits/lnbits . mkdir docker git clone https://github.com/lnbits/legend-regtest-enviroment.git docker cd docker -chmod +x ./tests -./tests # start the regtest and also run tests +chmod +x ./start-regtest +./start-regtest # start the regtest and also run tests sudo chown -R $USER ./data # Give the data file permissions for user ``` @@ -52,10 +52,10 @@ make dev # testing ```sh -chmod +x ./tests -./tests +chmod +x ./start-regtest +./start-regtest # short answer :) -./tests && echo "PASSED" || echo "FAILED" > /dev/null +./start-regtest && echo "PASSED" || echo "FAILED" > /dev/null ``` usage of the `bitcoin-cli-sim`, `lightning-cli-sim` and `lncli-sim` aliases diff --git a/data/boltz-client/boltz.toml b/data/boltz-client/boltz.toml new file mode 100644 index 0000000..0045820 --- /dev/null +++ b/data/boltz-client/boltz.toml @@ -0,0 +1,49 @@ +standalone = true +network = "regtest" + +# Path the the log file +logfile = "" + +electrumUrl = "electrs:19001" +electrumLiquidUrl = "electrs-liquid:19002" + +[BOLTZ] +# By default the daemon automatically connects to the official Boltz instance for the network LND is on +# This value is used to override that +url = "http://boltz-nginx:9001" + +[DATABASE] +# Path to the SQLite database file +# path = "/home/michael/test.db" + +[RPC] +# Host of +host = "0.0.0.0" + +# Port of the gRPC interface +port = 9002 + +# Whether the REST proxy for the gRPC interface should be disabled +restDisabled = false + +# Host of the REST proxy +restHost = "0.0.0.0" + +# Port of the REST proxy +restPort = 9003 + +# Path to the TLS cert for the gRPC and REST interface +tlsCert = "" + +# Path to the TLS private key for the gRPC and REST interface +tlsKey = "" +noTls = true + +# Whether the macaroon authentication for the gRPC and REST interface should be disabled +noMacaroons = true + +# Path to the admin macaroon for the gRPC and REST interface +adminMacaroonPath = "" + +# Path to the read only macaroon for the gRPC and REST interface +readOnlyMacaroonPath = "" diff --git a/data/boltz-nginx/default.conf b/data/boltz-nginx/default.conf new file mode 100644 index 0000000..116fca2 --- /dev/null +++ b/data/boltz-nginx/default.conf @@ -0,0 +1,49 @@ +upstream boltz { + server boltz:9001; +} + +upstream boltzr { + server boltz:9005; +} + +upstream ws { + server boltz:9004; +} + +server { + listen 9001; + listen [::]:9001; + server_name localhost; + + add_header Access-Control-Allow-Origin "*" always; + add_header Access-Control-Allow-Methods 'GET, PATCH, DELETE, POST, OPTIONS' always; + add_header Access-Control-Allow-Headers "*" always; + + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + if ($request_method = OPTIONS) { + return 204; + } + + location /v2/ws { + proxy_pass http://ws/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + } + + location ~ ^/v2/(lightning|swap/rescue|swap/restore) { + proxy_pass http://boltzr; + } + + location /streamswapstatus { + proxy_pass http://boltzr; + } + + location / { + proxy_pass http://boltz; + } +} diff --git a/data/boltz/boltz.conf b/data/boltz/boltz.conf new file mode 100644 index 0000000..6a3ef7c --- /dev/null +++ b/data/boltz/boltz.conf @@ -0,0 +1,111 @@ +[api] +host = "0.0.0.0" +port = 9_001 + +[grpc] +host = "0.0.0.0" +port = 9_000 + +[postgres] +host = "boltz-postgres" +port = 5432 +database = "boltz" +username = "boltz" +password = "boltz" + +[sidecar] + [sidecar.grpc] + host = "127.0.0.1" + port = 9003 + + [sidecar.ws] + host = "0.0.0.0" + port = 9004 + + [sidecar.api] + host = "0.0.0.0" + port = 9005 + +[[pairs]] +isLegacy = true +base = "BTC" +quote = "BTC" +rate = 1 +fee = 0.5 +swapInFee = 0.1 +maxSwapAmount = 40_294_967 +minSwapAmount = 50_000 + + [pairs.timeoutDelta] + chain = 1440 + reverse = 1440 + swapMinimal = 1440 + swapMaximal = 2880 + swapTaproot = 10080 + +[[pairs]] +isLegacy = true +base = "L-BTC" +quote = "BTC" +fee = 0.25 +swapInFee = 0.1 +rate = 1 +maxSwapAmount = 40_294_967 +minSwapAmount = 100 + + [pairs.submarineSwap] + minSwapAmount = 1_000 + minBatchedAmount = 21 + + [pairs.chainSwap] + minSwapAmount = 25_000 + + [pairs.timeoutDelta] + chain = 1440 + reverse = 1440 + swapMinimal = 1440 + swapMaximal = 2880 + swapTaproot = 10080 + +[[currencies]] +symbol = "BTC" +network = "bitcoinRegtest" +minWalletBalance = 10_000_000 +minChannelBalance = 10_000_000 +maxSwapAmount = 40_294_967 +minSwapAmount = 10_000 +maxZeroConfAmount = 0 + + [currencies.chain] + # mempoolSpace = "http://mempool-web:8090/api" + host = "bitcoind" + zmqpubrawtx = "tcp://bitcoind:29000" + zmqpubrawblock = "tcp://bitcoind:29001" + port = 18_443 + cookie = "/root/.bitcoin/regtest/.cookie" + + wallet = "lnbits" + + [currencies.lnd] + host = "lnd-2" + port = 10_009 + certpath = "/data/lnd/tls.cert" + macaroonpath = "/data/lnd/data/chain/bitcoin/regtest/admin.macaroon" + + +[liquid] +symbol = "L-BTC" +network = "liquidRegtest" + +maxSwapAmount = 40_294_967 +minSwapAmount = 10_000 +maxZeroConfAmount = 40_294_967 + + [liquid.chain] + host = "elementsd" + port = 18884 + cookie = "/root/.elements/liquidregtest/.cookie" + zmqpubrawtx = "tcp://elementsd:31000" + zmqpubhashblock = "tcp://elementsd:31002" + + wallet = "lnbits" diff --git a/docker-compose.yml b/docker-compose.yml index 08f4550..232b814 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,22 +7,100 @@ services: image: lnbits/lnbits restart: on-failure user: "0:0" - entrypoint: "sh -c 'sleep 30; uv run lnbits'" environment: - HOST: lnbits - PORT: 5001 + LNBITS_PORT: 5001 DEBUG: true - LNBITS_ADMIN_UI: false + LNBITS_ADMIN_UI: true LNBITS_BACKEND_WALLET_CLASS: "LndRestWallet" LNBITS_DATA_FOLDER: "./data" + LNBITS_EXTENSIONS_PATH: "/shared" LND_REST_ENDPOINT: "https://lnd-3:8081/" LND_REST_CERT: "./lnd/tls.cert" LND_REST_MACAROON: "./lnd/data/chain/bitcoin/regtest/admin.macaroon" ports: - 5001:5001 volumes: - - lnbits-data:/app/data + - ./data/lnbits:/app/data - ./data/lnd-3:/app/lnd:uid=1000,gid=1000 + - /home/padreug/dev/shared/extensions:/shared/extensions + + fava: + hostname: fava + build: ./fava + restart: on-failure + ports: + - 3333:5000 + volumes: + - ./data/fava:/bean + + boltz: + hostname: boltz + depends_on: + - lnd-2 + - boltz-postgres + restart: always + image: boltz/boltz:latest + ports: + - 9000:9000 + entrypoint: "sh -c 'sleep 30; /boltz-backend/bin/boltzd'" + volumes: + - ./data/lnd-2:/data/lnd/ + - ./data/boltz/:/root/.boltz/ + - elements-data:/root/.elements + - bitcoin-data:/root/.bitcoin + + boltz-client: + hostname: boltz-client + depends_on: + - boltz + restart: always + image: boltz/boltz-client:latest + ports: + - 9002:9002 + - 9003:9003 + expose: + - 9002 + healthcheck: + test: ['CMD', 'boltzcli', '--host', 'boltz-client', 'getinfo'] + interval: 5s + timeout: 3s + retries: 10 + start_period: 0s + volumes: + - elements-data:/root/.elements + - ./data/boltz-client:/root/.boltz + + boltz-backend-nginx: + hostname: boltz-nginx + restart: always + image: nginx:latest + ports: + - 9001:9001 + volumes: + - nginx-data:/etc/nginx/conf.d + healthcheck: + test: ['CMD-SHELL', 'curl http://localhost:9001/version'] + timeout: 1s + retries: 10 + interval: 1s + start_period: 0s + + boltz-postgres: + hostname: boltz-postgres + restart: always + image: postgres:14-alpine + healthcheck: + test: ["CMD-SHELL", "pg_isready --dbname boltz --username boltz"] + interval: 5s + timeout: 30s + retries: 10 + start_period: 5s + environment: + - POSTGRES_DB=boltz + - POSTGRES_USER=boltz + - POSTGRES_PASSWORD=boltz + expose: + - 5432 bitcoind: hostname: bitcoind @@ -144,7 +222,7 @@ services: hostname: lnd-1 depends_on: - bitcoind - image: boltz/lnd:0.18.4-beta + image: boltz/lnd:0.19.3-beta restart: on-failure command: - --listen=lnd-1:9735 @@ -159,6 +237,7 @@ services: - --bitcoind.zmqpubrawblock=bitcoind:29001 - --noseedbackup - --protocol.wumbo-channels + - --rpcmiddleware.enable expose: - 8081 - 9735 @@ -171,7 +250,7 @@ services: hostname: lnd-2 depends_on: - bitcoind - image: boltz/lnd:0.18.4-beta + image: boltz/lnd:0.19.3-beta restart: on-failure command: - --listen=lnd-2:9735 @@ -251,6 +330,39 @@ services: - ./data/lnd-4:/root/.lnd/ - bitcoin-data:/root/.bitcoin + litd: + hostname: litd + depends_on: + - lnd-1 + image: lightninglabs/lightning-terminal:v0.16.0-alpha + restart: on-failure + entrypoint: /bin/sh + command: + - -c + - | + echo "Waiting for LND to be ready..." + while ! nc -z lnd-1 10009 2>/dev/null; do + echo "Waiting for lnd-1:10009..." + sleep 2 + done + sleep 5 + exec /bin/litd \ + --httpslisten=0.0.0.0:8443 \ + --insecure-httplisten=0.0.0.0:8080 \ + --uipassword=testpassword123 \ + --network=regtest \ + --lnd-mode=remote \ + --remote.lnd.rpcserver=lnd-1:10009 \ + --remote.lnd.macaroonpath=/root/.lnd/data/chain/bitcoin/regtest/admin.macaroon \ + --remote.lnd.tlscertpath=/root/.lnd/tls.cert \ + --autopilot.disable + ports: + - 8443:8443 + - 8080:8080 + volumes: + - ./data/lnd-1:/root/.lnd + - ./data/litd:/root/.lit + eclair: hostname: eclair depends_on: @@ -355,6 +467,12 @@ services: - elements-data:/root/.elements volumes: - lnbits-data: bitcoin-data: elements-data: + nginx-data: + name: nginx-data + driver: local + driver_opts: + type: none + o: bind + device: ./data/boltz-nginx/ diff --git a/docker-scripts.sh b/docker-scripts.sh index 7e64274..eca62fd 100644 --- a/docker-scripts.sh +++ b/docker-scripts.sh @@ -1,6 +1,10 @@ #!/bin/sh export COMPOSE_PROJECT_NAME=lnbits +boltzcli-sim() { + docker exec -it lnbits-boltz-client-1 boltzcli "$@" +} + bitcoin-cli-sim() { docker exec lnbits-bitcoind-1 bitcoin-cli -regtest "$@" } @@ -47,6 +51,14 @@ wait-for-eclair-channel() { done } +# args(i) +fund_boltz_client() { + # first address of seed: abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about + address="el1qq2xvpcvfup5j8zscjq05u2wxxjcyewk7979f3mmz5l7uw5pqmx6xf5xy50hsn6vhkm5euwt72x878eq6zxx2z0z676mna6kdq" + echo "funding: $address on boltz-client" + elements-cli-sim -named sendtoaddress address=$address amount=30 fee_rate=100 > /dev/null +} + # args(i) fund_clightning_node() { @@ -98,7 +110,7 @@ lnbits-regtest-start-log(){ lnbits-regtest-stop(){ docker compose down --volumes # clean up lightning node data - sudo rm -rf ./data/clightning-1 ./data/clightning-2 ./data/clightning-3 ./data/lnd-1 ./data/lnd-2 ./data/lnd-3 ./data/lnd-4 ./data/boltz/boltz.db ./data/eclair/regtest + sudo rm -rf ./data/clightning-1 ./data/clightning-2 ./data/clightning-3 ./data/lnd-1 ./data/lnd-2 ./data/lnd-3 ./data/lnd-4 ./data/boltz/boltz.db ./data/eclair/regtest ./data/boltz-client/liquid-wallet ./data/boltz-client/bitcoin-wallet ./data/boltz-client/wallet ./data/boltz-client/boltz.db # recreate lightning node data folders preventing permission errors mkdir ./data/clightning-1 ./data/clightning-2 ./data/clightning-3 ./data/lnd-1 ./data/lnd-2 ./data/lnd-3 ./data/lnd-4 } @@ -108,6 +120,13 @@ lnbits-regtest-restart(){ lnbits-regtest-start } +boltz-client-init(){ + for i in 0 1 2; do + fund_boltz_client + done + elements-cli-sim -generate 3 > /dev/null +} + lnbits-bitcoin-init(){ echo "init_bitcoin_wallet..." bitcoin-cli-sim createwallet lnbits || bitcoin-cli-sim loadwallet lnbits @@ -120,6 +139,7 @@ lnbits-elements-init(){ elements-cli-sim createwallet lnbits || elements-cli-sim loadwallet lnbits echo "mining 150 blocks..." elements-cli-sim -generate 150 > /dev/null + elements-cli-sim rescanblockchain } lnbits-init(){ @@ -132,6 +152,7 @@ lnbits-regtest-init(){ lnbits-elements-init lnbits-lightning-sync lnbits-lightning-init + boltz-client-init lnbits-init } diff --git a/fava/Dockerfile b/fava/Dockerfile new file mode 100644 index 0000000..83f2fbb --- /dev/null +++ b/fava/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.12-slim + +RUN pip install --no-cache-dir fava==1.30.11 + +WORKDIR /bean + +EXPOSE 5000 + +ENTRYPOINT ["fava"] +CMD ["-H", "0.0.0.0", "/bean/ledger.beancount"] diff --git a/lndconnect.sh b/lndconnect.sh new file mode 100755 index 0000000..7af3378 --- /dev/null +++ b/lndconnect.sh @@ -0,0 +1,110 @@ +#!/bin/bash +# +# Generate lndconnect QR strings for Zeus wallet connection +# +# Usage: +# ./lndconnect.sh [node-number] +# +# Examples: +# ./lndconnect.sh 1 # lnd-1 (requires port exposure) +# ./lndconnect.sh 3 # lnd-3 (REST port 8081 exposed by default) +# ./lndconnect.sh 4 # lnd-4 (Lightning.Pub's node, requires port exposure) +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +NODE_NUM="${1:-3}" +DATA_DIR="$SCRIPT_DIR/data/lnd-$NODE_NUM" + +# Colors +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' + +log() { echo -e "${GREEN}[lndconnect]${NC} $1"; } +warn() { echo -e "${YELLOW}[lndconnect]${NC} $1"; } +error() { echo -e "${RED}[lndconnect]${NC} $1"; exit 1; } + +# Get local IP +get_local_ip() { + ip route get 1 2>/dev/null | awk '{print $7; exit}' || hostname -I | awk '{print $1}' +} + +# Base64url encode (no padding, URL-safe) +base64url_encode() { + base64 -w0 | tr '+/' '-_' | tr -d '=' +} + +# Check if node data exists +if [ ! -d "$DATA_DIR" ]; then + error "Node data directory not found: $DATA_DIR" +fi + +MACAROON_PATH="$DATA_DIR/data/chain/bitcoin/regtest/admin.macaroon" +CERT_PATH="$DATA_DIR/tls.cert" + +if [ ! -f "$MACAROON_PATH" ]; then + error "Macaroon not found: $MACAROON_PATH" +fi + +if [ ! -f "$CERT_PATH" ]; then + error "TLS cert not found: $CERT_PATH" +fi + +# Get host IP +HOST_IP=$(get_local_ip) + +# Determine REST port based on node +case $NODE_NUM in + 3) REST_PORT=8081 ;; # Exposed in docker-compose + *) + warn "lnd-$NODE_NUM REST port may not be exposed to host." + warn "You may need to add port mapping to docker-compose.yml" + REST_PORT=8081 + ;; +esac + +log "Generating lndconnect for lnd-$NODE_NUM..." +log "Host: $HOST_IP:$REST_PORT" + +# Encode macaroon and cert +MACAROON_B64=$(cat "$MACAROON_PATH" | base64url_encode) +CERT_B64=$(cat "$CERT_PATH" | base64url_encode) + +# Build lndconnect URL +LNDCONNECT_URL="lndconnect://${HOST_IP}:${REST_PORT}?macaroon=${MACAROON_B64}&cert=${CERT_B64}" + +echo "" +echo "============================================================" +echo "lndconnect URL for lnd-$NODE_NUM:" +echo "============================================================" +echo "" +echo "$LNDCONNECT_URL" +echo "" + +# Generate QR code if qrencode is available +if command -v qrencode &>/dev/null; then + log "Generating QR code..." + echo "" + qrencode -t ANSIUTF8 "$LNDCONNECT_URL" + echo "" +else + warn "Install 'qrencode' for terminal QR code: sudo pacman -S qrencode" +fi + +echo "============================================================" +echo "Instructions:" +echo "============================================================" +echo "1. Open Zeus wallet" +echo "2. Go to Settings → Add a new node" +echo "3. Scan QR or paste the lndconnect URL" +echo "" +if [ "$NODE_NUM" != "3" ]; then + echo "NOTE: lnd-$NODE_NUM REST port is not exposed by default." + echo "Add this to docker-compose.yml under lnd-$NODE_NUM:" + echo " ports:" + echo " - '808$NODE_NUM:8081'" + echo "" +fi diff --git a/tests b/start-regtest similarity index 96% rename from tests rename to start-regtest index b547666..82a5ad6 100755 --- a/tests +++ b/start-regtest @@ -57,6 +57,7 @@ done run "eclair-1 openchannels" 2 $(docker exec lnbits-eclair-1 curl -s http://localhost:8080/channels -X POST -u :lnbits| jq '. | length') run "eclair-1 blockHeight" $blockheight $(docker exec lnbits-eclair-1 curl -s http://localhost:8080/getinfo -X POST -u :lnbits| jq '.blockHeight') run "lnbits service status" "200" $(curl -s -o /dev/null -w "%{http_code}" "http://localhost:5001/") +run "boltz service status" "200" $(curl -s -o /dev/null --head -w "%{http_code}" "http://localhost:9001/version") # return non-zero exit code if a test fails if [[ "$failed" == "true" ]]; then