bitmap 3D PFP: simplify pass after /simplify review #63
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: E2E (regtest mint) | |
| # Full-stack mint round-trip on Bitcoin regtest: | |
| # bitcoind + electrs + mariadb (from ordpool-sdk's consumer-environment) | |
| # + ordpool-backend (this repo) + ordpool/frontend serving locally | |
| # + Xverse extension headed in xvfb | |
| # + Playwright drives /cat21-mint end-to-end through to a real mint | |
| # | |
| # Iteration ladder (matches PLAN-cat21-mint-port.md): | |
| # 1. bring the backend stack up, confirm /api/v1/fees/recommended + | |
| # /api/v1/blocks/tip/height answer [done] | |
| # 2. add frontend serve + Playwright smoke (page loads, wallet picker | |
| # visible) [done] | |
| # 3. add Xverse extension + full mint click-through + 1-block mine + | |
| # pending feed transition assertion [done] | |
| # | |
| # Subsequent iterations extend the same workflow file rather than | |
| # creating new ones — keeps the CI surface stable. | |
| on: | |
| workflow_dispatch: | |
| schedule: | |
| - cron: '17 3 * * *' # nightly at 03:17 UTC | |
| push: | |
| paths: | |
| - 'e2e/docker-compose.ordpool-backend.yml' | |
| - 'e2e/mempool-config.regtest.json' | |
| - '.github/workflows/e2e-regtest-mint.yml' | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ordpool-e2e-regtest-mint-${{ github.ref }} | |
| # Don't cancel an in-flight run when a newer push / dispatch | |
| # arrives. Cancelled runs show up on the commit page as gray X | |
| # marks that read as "CI broken" even though they just got | |
| # superseded by a newer dispatch. Letting them complete keeps the | |
| # commit's check history clean. | |
| cancel-in-progress: false | |
| jobs: | |
| regtest-mint: | |
| name: Bring up stack + smoke-test backend | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| steps: | |
| - name: Checkout ordpool | |
| uses: actions/checkout@v4 | |
| - name: Setup Node | |
| # Match the working build-frontend.yml exactly — setup-node@v6 | |
| # + plain `npm ci`. Earlier runs of this workflow used | |
| # `--legacy-peer-deps` which drops the optional peer deps | |
| # (@popperjs/core, sats-connect) that the build needs; v6 + | |
| # plain ci sidesteps that. | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24 | |
| cache: 'npm' | |
| cache-dependency-path: 'frontend/package-lock.json' | |
| - name: Install frontend npm deps (brings ordpool-sdk into node_modules) | |
| run: npm ci | |
| working-directory: frontend | |
| - name: Make SDK consumer-environment scripts executable | |
| # Git on Linux preserves the bit, but defensive against any | |
| # cross-platform clone that lost it. | |
| run: chmod +x node_modules/ordpool-sdk/e2e/consumer-environment-bootstrap.sh | |
| working-directory: frontend | |
| - name: Bring up consumer-environment (bitcoind + electrs + mariadb) | |
| run: | | |
| frontend/node_modules/ordpool-sdk/e2e/consumer-environment-bootstrap.sh \ | |
| > stack.json | |
| cat stack.json | |
| - name: Start fees + electrs HTTP+WS shim on :8999 | |
| # We bring up just the SDK consumer-environment (bitcoind + | |
| # electrs + mariadb). For the mempool-style API surface the | |
| # frontend needs (/api/v1/fees/recommended + Esplora-shaped | |
| # /api/address/.../utxo + /api/tx/...) we use the tiny zero- | |
| # dep Node shim cat21-indexer uses for the same purpose. The | |
| # upstream mempool/backend Docker image only serves /api/v1/* | |
| # — it lacks ordpool's custom Express middleware that proxies | |
| # /api/* to electrs, so the SDK's getUtxos 404s against it. | |
| # | |
| # Run with cwd=frontend so the stub's dynamic `import('ws')` | |
| # resolves against frontend/node_modules/ws (a transitive dep | |
| # of the mempool fork). ordpool's StateService.recommendedFees$ | |
| # is fed from a WebSocket — without one, the cat21-mint empty | |
| # state never renders because the `(recommendedFees$ | async)` | |
| # gate is null. The stub responds to the WS upgrade on | |
| # /api/v1/ws with a single snapshot frame containing `fees`, | |
| # `mempool-blocks`, `da`, `backendInfo` so the state-service | |
| # marks itself "connected" (drops the "Reconnecting…" badge | |
| # the trace showed pinned on a failed run) and the fee | |
| # signal fires. | |
| run: | | |
| cd frontend | |
| ELECTRS_URL=http://localhost:3000 PORT=8999 WS_ENABLED=1 \ | |
| nohup node ../e2e/fees-electrs-stub.mjs > ../fees-stub.log 2>&1 & | |
| echo $! > ../fees-stub.pid | |
| cd .. | |
| for i in $(seq 1 30); do | |
| if curl -fsS http://localhost:8999/healthz >/dev/null 2>&1; then | |
| echo "fees-stub up"; break | |
| fi | |
| if [ "$i" = "30" ]; then | |
| echo "fees-stub never came up"; cat fees-stub.log || true; exit 1 | |
| fi | |
| sleep 1 | |
| done | |
| - name: Smoke-test stub endpoints | |
| run: | | |
| echo "--- fees/recommended" | |
| curl -fsS http://localhost:8999/api/v1/fees/recommended | |
| echo | |
| echo "--- blocks/tip/height (electrs)" | |
| curl -fsS http://localhost:8999/api/blocks/tip/height | |
| echo | |
| # ── Iter 2 + 3 — Playwright + Xverse + the page-driven mint ───── | |
| # | |
| # We checkout the SDK as a sibling repo because its Xverse | |
| # globalSetup (onboards the vault on Regtest, dumps the seeded | |
| # user-data-dir) needs the SDK's own dev deps + harness bundle. | |
| # The SDK's package install in node_modules ships only the | |
| # runtime; running its playwright config requires the full repo. | |
| - name: Checkout ordpool-sdk (for Xverse extension + seeded vault) | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: ordpool-space/ordpool-sdk | |
| path: sdk | |
| ref: main | |
| - name: Setup Node (sdk) | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '24.13.0' | |
| cache: 'npm' | |
| cache-dependency-path: 'sdk/package-lock.json' | |
| - name: Install SDK deps + build (harness needs the dist/ output) | |
| working-directory: sdk | |
| run: | | |
| npm ci | |
| npm run build | |
| - name: Install Playwright system deps + Chromium | |
| working-directory: sdk | |
| run: npx playwright install --with-deps chromium | |
| # Cache the unpacked Xverse extension keyed by the version pinned | |
| # in the SDK's playwright-bootstrap.sh. First run downloads .crx | |
| # from the SDK repo's release mirror; subsequent runs are a hit. | |
| - name: Cache unpacked Xverse extension | |
| id: xverse-cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: sdk/e2e/extensions/xverse | |
| key: xverse-extension-v2.3.2 | |
| - name: Download + unpack Xverse from SDK release mirror | |
| if: steps.xverse-cache.outputs.cache-hit != 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| working-directory: sdk | |
| run: bash e2e/playwright/playwright-bootstrap.sh xverse | |
| - name: Bootstrap regtest funds (101 blocks, dump WIF) | |
| working-directory: sdk | |
| run: | | |
| chmod +x e2e/regtest-bootstrap.sh | |
| ./e2e/regtest-bootstrap.sh > regtest-bootstrap.json | |
| cat regtest-bootstrap.json | |
| - name: Run SDK's Xverse onboarding (produces seeded user-data-dir) | |
| working-directory: sdk | |
| run: | | |
| # globalSetup.ts is the side-effect target — running any | |
| # xverse spec triggers it. `xverse-loads.spec.ts` is the | |
| # cheapest one and exits in ~10s once the vault is seeded. | |
| xvfb-run --auto-servernum --server-args='-screen 0 1280x900x24' \ | |
| npx playwright test e2e/playwright/specs/xverse-loads.spec.ts \ | |
| --config=e2e/playwright/playwright.config.ts | |
| - name: Verify seed user-data-dir landed | |
| run: | | |
| test -d sdk/test-results/xverse-seed-user-data-dir/Default \ | |
| || { echo "no seed user-data-dir at expected path"; ls sdk/test-results/; exit 1; } | |
| # ── Patch environment.ts + serve the frontend ────────────────── | |
| # | |
| # The local-esplora proxy config is bound to upstream mempool's | |
| # webpack-dev-server + http-proxy-middleware contract that's | |
| # diverged in newer Angular CLI versions ("Invalid context" | |
| # errors fire on every request even with valid globs). Skip the | |
| # proxy entirely: patch environment.ts so apiBaseUrl + | |
| # websocketBaseUrl are absolute localhost URLs, then run plain | |
| # `ng serve` with no -c flag (the default has no proxyConfig). | |
| # The frontend talks directly to ordpool-backend on :8999, which | |
| # in turn proxies /api/* to electrs. | |
| - name: Generate frontend config (Ordpool defaults) | |
| working-directory: frontend | |
| run: npm run config:defaults:ordpool | |
| - name: Patch environment.ts to localhost absolute URLs | |
| working-directory: frontend | |
| # Three URL knobs that have to be rewritten in-place to land on | |
| # the regtest stub: | |
| # apiBaseUrl '' → http://localhost:8999 (mempool API + electrs proxy) | |
| # websocketBaseUrl '' → ws://localhost:8999 (mempool WS) | |
| # cat21BaseUrl http://localhost:3333 → http://localhost:8999 | |
| # (the wallet asset-scanner calls /api/status + /api/cats/numbers/... | |
| # to identify cat sats among UTXOs; without redirecting these the | |
| # scanner blocks on ECONNREFUSED and the page never reaches a | |
| # connected state — observed as a "Reconnecting…" banner that | |
| # never clears in trace logs of failed runs) | |
| run: | | |
| sed -i \ | |
| -e "s|apiBaseUrl: ''|apiBaseUrl: 'http://localhost:8999'|" \ | |
| -e "s|websocketBaseUrl: ''|websocketBaseUrl: 'ws://localhost:8999'|" \ | |
| -e "s|cat21BaseUrl: 'http://localhost:3333'|cat21BaseUrl: 'http://localhost:8999'|" \ | |
| src/environments/environment.ts | |
| echo "--- patched environment.ts ---" | |
| head -32 src/environments/environment.ts | |
| - name: Patch bitcoinNetwork to Regtest (matches the seeded Xverse vault) | |
| working-directory: frontend | |
| # Frontend ships hardcoded Network.Mainnet in app.module.ts; the | |
| # SDK's globalSetup seeded Xverse on Bitcoin Regtest. Without | |
| # this swap Xverse refuses to connect (network mismatch) so the | |
| # approval popup never opens and the spec times out at 60s on | |
| # waitForApprovalPopup. | |
| run: | | |
| sed -i \ | |
| -e 's|useValue: Network\.Mainnet|useValue: Network.Regtest|' \ | |
| src/app/app.module.ts | |
| grep -n bitcoinNetwork src/app/app.module.ts | head -2 | |
| - name: Build static frontend bundle | |
| # Using `ng build` (default = base options, not the production | |
| # config) over `ng serve` because ng-serve in CI emits HTTP 200 | |
| # for / before compile finishes — the spec then hits an empty | |
| # shell. Static build + `npx serve` removes the compile race. | |
| # Pipe through tee so the error surfaces in the workflow log | |
| # AND lands in build.log for the artifact upload. | |
| working-directory: frontend | |
| run: | | |
| set -o pipefail | |
| npm run ng -- build --output-path ../dist-regtest 2>&1 | tee ../build.log | |
| - name: Locate the built browser output | |
| run: | | |
| # Angular CLI's output layout depends on builder + configuration: | |
| # could be dist-regtest/index.html (mempool's classic webpack | |
| # builder), dist-regtest/browser/index.html (esbuild/application | |
| # builder), or dist-regtest/<project>/browser/index.html | |
| # (workspace-shape variants). Try each. | |
| for candidate in \ | |
| dist-regtest \ | |
| dist-regtest/browser \ | |
| $(find dist-regtest -maxdepth 3 -type d -name browser); do | |
| if [ -f "$candidate/index.html" ]; then | |
| BROWSER_DIR="$candidate" | |
| break | |
| fi | |
| done | |
| if [ -z "${BROWSER_DIR:-}" ]; then | |
| echo "could not find any index.html under dist-regtest" | |
| ls -la dist-regtest || true | |
| tail -200 build.log || true | |
| exit 1 | |
| fi | |
| echo "BROWSER_DIR=$BROWSER_DIR" >> "$GITHUB_ENV" | |
| ls "$BROWSER_DIR" | head -8 | |
| - name: Serve the static bundle on :4200 | |
| run: | | |
| # `serve -s` enables SPA fallback so /cat21-mint resolves | |
| # to index.html (no real /cat21-mint file exists in the | |
| # build, the Angular router owns that path). | |
| nohup npx -y serve@14 -s "$BROWSER_DIR" -l 4200 --no-clipboard > serve.log 2>&1 & | |
| echo $! > serve.pid | |
| - name: Wait for static server ready on :4200 | |
| run: | | |
| for i in $(seq 1 30); do | |
| code=$(curl -s -o /dev/null -w '%{http_code}' http://localhost:4200/ 2>/dev/null || echo 000) | |
| if [ "$code" = "200" ]; then | |
| echo "static server up" | |
| break | |
| fi | |
| if [ "$i" = "30" ]; then | |
| echo "static server never came up (last HTTP $code)" | |
| tail -200 serve.log || true | |
| exit 1 | |
| fi | |
| sleep 1 | |
| done | |
| - name: Install Playwright in the frontend tree | |
| working-directory: frontend | |
| run: npx playwright install --with-deps chromium | |
| - name: Lift SDK e2e helpers out of node_modules (Node 24 strip-types refuses .ts there) | |
| working-directory: frontend | |
| run: | | |
| mkdir -p playwright/specs/regtest/sdk-lib | |
| cp node_modules/ordpool-sdk/e2e/regtest/regtest-helpers.ts playwright/specs/regtest/sdk-lib/ | |
| cp node_modules/ordpool-sdk/e2e/playwright/approval-popup.ts playwright/specs/regtest/sdk-lib/ | |
| ls -la playwright/specs/regtest/sdk-lib/ | |
| - name: Run the page-driven cat21-mint regtest spec | |
| working-directory: frontend | |
| env: | |
| # Point the spec at the SDK's seeded vault + unpacked .crx | |
| # (the workflow's cache populated these one level up). | |
| XVERSE_EXT_PATH: ${{ github.workspace }}/sdk/e2e/extensions/xverse | |
| XVERSE_SEED_USER_DATA_DIR: ${{ github.workspace }}/sdk/test-results/xverse-seed-user-data-dir | |
| FRONTEND_URL: http://localhost:4200 | |
| run: | | |
| xvfb-run --auto-servernum --server-args='-screen 0 1280x900x24' \ | |
| npx playwright test playwright/specs/regtest/cat21-mint-regtest.spec.ts \ | |
| --config=playwright.regtest.config.ts | |
| - name: Upload Playwright artifacts | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: regtest-mint-playwright-${{ github.run_id }} | |
| path: | | |
| frontend/test-results | |
| frontend/playwright-report-regtest | |
| build.log | |
| serve.log | |
| fees-stub.log | |
| retention-days: 14 | |
| if-no-files-found: warn | |
| - name: Dump container logs on failure | |
| if: failure() | |
| run: | | |
| for c in ordpool-e2e-consumer-bitcoind ordpool-e2e-consumer-electrs \ | |
| ordpool-e2e-consumer-mariadb; do | |
| echo "=== $c ===" | |
| docker logs "$c" 2>&1 | tail -200 || true | |
| done | |
| - name: Tear down stack | |
| if: always() | |
| run: | | |
| docker compose \ | |
| -f frontend/node_modules/ordpool-sdk/e2e/docker-compose.consumer-environment.yml \ | |
| down -v || true | |
| if [ -f fees-stub.pid ]; then | |
| kill "$(cat fees-stub.pid)" 2>/dev/null || true | |
| fi | |
| if [ -f frontend/serve.pid ]; then | |
| kill "$(cat frontend/serve.pid)" 2>/dev/null || true | |
| fi |