From 8698b9f2c378ccb856b223d0324c47767ba66bd5 Mon Sep 17 00:00:00 2001 From: Stephan Butler Date: Tue, 24 Mar 2026 13:40:00 +0200 Subject: [PATCH 1/7] refactor: support for mockgatehub in local environment --- .github/copilot-instructions.md | 167 ++++++++ docker/dev/.env.example | 33 -- docker/dev/docker-compose.yml | 336 ---------------- docker/dev/kratos.yml | 91 ----- docker/entrypoint.sh | 13 - docker/identity.schema.json | 33 -- docker/prod/.env.example | 109 ----- docker/prod/docker-compose.yml | 305 -------------- docker/prod/kratos.yml | 91 ----- docker/temp/private-key.pem | 3 - local/.env.example | 116 ++++++ local/.gitignore | 1 + local/docker-compose.yml | 287 +++++++++++++ {docker => local/init}/dbinit.sql | 0 local/scripts/rafiki-setup.js | 379 ++++++++++++++++++ packages/wallet/backend/src/config/env.ts | 1 + packages/wallet/backend/src/gatehub/client.ts | 14 + .../wallet/backend/src/gatehub/service.ts | 24 ++ .../backend/src/middleware/withSession.ts | 9 +- packages/wallet/frontend/Dockerfile.dev | 16 + packages/wallet/frontend/next.config.js | 19 +- .../wallet/frontend/src/lib/httpClient.ts | 8 +- packages/wallet/frontend/src/middleware.ts | 25 +- packages/wallet/frontend/src/utils/helpers.ts | 3 +- pnpm-lock.yaml | 270 ++++++++++++- pnpm-workspace.yaml | 1 + 26 files changed, 1322 insertions(+), 1032 deletions(-) create mode 100644 .github/copilot-instructions.md delete mode 100644 docker/dev/.env.example delete mode 100644 docker/dev/docker-compose.yml delete mode 100644 docker/dev/kratos.yml delete mode 100755 docker/entrypoint.sh delete mode 100644 docker/identity.schema.json delete mode 100644 docker/prod/.env.example delete mode 100644 docker/prod/docker-compose.yml delete mode 100644 docker/prod/kratos.yml delete mode 100644 docker/temp/private-key.pem create mode 100644 local/.env.example create mode 100644 local/.gitignore create mode 100644 local/docker-compose.yml rename {docker => local/init}/dbinit.sql (100%) create mode 100644 local/scripts/rafiki-setup.js diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000..361a19b90 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,167 @@ +# GitHub Copilot Instructions for Interledger Test Network + +## Purpose And Scope + +Test Network is a full-stack pnpm workspace monorepo for Interledger sandbox integrations. It contains two apps: + +- Wallet (`packages/wallet/*`): Next.js frontend + Node/Express backend. +- Boutique (`packages/boutique/*`): Vite/React frontend + Node/Express backend. + +Shared packages are in `packages/shared/backend`, `packages/wallet/shared`, and `packages/boutique/shared`. + +Use this file as the default source of truth. Trust these instructions and only search the repo when this file is incomplete or proven wrong. + +## Runtime And Tooling (Strict) + +- Node: `^20.12.1` required by `package.json` engines. +- Package manager: `pnpm@9.1.4` (`packageManager` field). +- Never use `npm install` or `yarn` in this repo. + +Local shell note validated on this machine: default `node` was `v18.19.1`, which causes engine failures even if `pnpm` exists. Ensure Node 20 is first on `PATH` before running scripts. + +Example reliable setup: + +```bash +PATH=/home/$USER/.nvm/versions/node/v20.20.0/bin:$PATH +corepack pnpm -v +node -v +``` + +## Bootstrap, Build, Test, Lint (Validated) + +Run from repo root `testnet/`. + +1. Bootstrap (always first): + +```bash +PATH=/home/$USER/.nvm/versions/node/v20.20.0/bin:$PATH corepack pnpm install --frozen-lockfile +``` + +Validated: passes in ~1.5s when lockfile is up to date. + +2. Quality checks: + +```bash +PATH=/home/$USER/.nvm/versions/node/v20.20.0/bin:$PATH corepack pnpm checks +``` + +Validated behavior: may fail on existing repo formatting drift (Prettier). In this workspace it failed on: + +- `.github/copilot-instructions.md` +- `docker/local/docker-compose.yml` +- `docker/local/rafiki-setup.js` +- `packages/wallet/frontend/next.config.js` +- `packages/wallet/frontend/src/middleware.ts` + +3. Lint only (for signal isolation): + +```bash +PATH=/home/$USER/.nvm/versions/node/v20.20.0/bin:$PATH corepack pnpm lint:check +``` + +Validated behavior: currently fails on `packages/wallet/frontend/src/middleware.ts` (`@typescript-eslint/no-explicit-any`). + +4. Build all: + +```bash +PATH=/home/$USER/.nvm/versions/node/v20.20.0/bin:$PATH corepack pnpm build +``` + +Validated behavior: currently fails quickly if Docker-created artifacts are root-owned (TS5033 / EACCES in `packages/wallet/shared/dist`). + +5. Package-scoped backend verification: + +```bash +PATH=/home/$USER/.nvm/versions/node/v20.20.0/bin:$PATH corepack pnpm boutique:backend build +PATH=/home/$USER/.nvm/versions/node/v20.20.0/bin:$PATH corepack pnpm boutique:backend test +``` + +Validated: passes (`5` suites, `29` tests) in ~17s for tests. + +Wallet backend flow currently blocked by the same `wallet/shared/dist` permission issue: + +```bash +PATH=/home/$USER/.nvm/versions/node/v20.20.0/bin:$PATH corepack pnpm wallet:backend build +``` + +## Known Failure Modes And Workarounds + +1. Engine mismatch (`Unsupported environment`, Node 18 shown): + +- Cause: shell not using Node 20. +- Fix: put Node 20 bin first in `PATH` for every command (or `nvm use lts/iron` in an interactive shell that works reliably). + +2. `TS5033` / `EACCES` writing under `packages/wallet/shared/dist`: + +- Cause: prior Docker runs produced root-owned build artifacts. +- Symptom: root build and wallet backend build fail. +- Fix: ensure those files are writable by your user before rebuilding (example: adjust ownership/permissions of `packages/wallet/shared/dist` and `packages/wallet/shared/tsconfig.build.tsbuildinfo`). + +3. `pnpm checks` failing even without your changes: + +- Cause: pre-existing formatting/lint drift. +- Mitigation: run targeted checks for touched packages and report baseline failures explicitly in PR notes. + +No command timeouts were observed in this validation pass. Failing commands exited quickly (under ~6s except build/test commands). + +## Local Run Flow + +Required precondition before first `pnpm dev`: + +```bash +cp docker/dev/.env.example docker/dev/.env +``` + +GateHub-related variables in `docker/dev/.env` are required for full KYC/funding flows. + +Main run modes: + +- `pnpm dev` -> hot-reload backend containers + frontend dev servers. +- `pnpm dev:debug` -> backend debug mode (`9229`, `9230`). +- `pnpm dev:lite` -> run built backend (no hot reload). +- `pnpm localenv:stop` -> stop local docker environment. + +Service endpoints: + +- Wallet FE: `http://localhost:4003` +- Wallet BE: `http://localhost:3003` +- Boutique FE: `http://localhost:4004` +- Boutique BE: `http://localhost:3004` +- Rafiki Admin UI: `http://localhost:3012` + +## Architecture And File Map + +High-signal root files: + +- `package.json`: canonical scripts and engine constraints. +- `pnpm-workspace.yaml`: workspace package patterns. +- `tsconfig.json`: top-level project references. +- `eslint.config.mjs`, `.prettierrc.js`: repo-wide code quality rules. +- `docker/dev/docker-compose.yml`: full local dependency graph (Postgres, Redis, Rafiki, Kratos, app backends). + +Key source areas: + +- Wallet backend: `packages/wallet/backend/src` +- Wallet frontend: `packages/wallet/frontend` +- Boutique backend: `packages/boutique/backend/src` +- Boutique frontend: `packages/boutique/frontend/src` +- Shared backend utilities: `packages/shared/backend/src` + +## CI And PR Expectations + +Primary workflows: + +- `.github/workflows/ci.yml` + - Always runs `pnpm checks`. + - Build/test jobs are gated by PR labels (`package: wallet/backend`, `package: boutique/frontend`, etc.). + - Wallet backend CI test command: `pnpm wallet:backend build && pnpm wallet:backend test --detectOpenHandles --forceExit`. +- `.github/workflows/pr_title_check.yml` + - PR title must satisfy Conventional Commits. +- `.github/workflows/build-publish.yaml` + - Builds package matrix on PR/push; publishes images on `v*` tags. + +Before opening a PR, replicate the relevant CI subset for your changed package(s) and note any pre-existing baseline failures. + +## Agent Operating Rule + +When implementing changes, follow this file first, execute commands in the documented order, and avoid broad repo searches unless required details are missing or incorrect. diff --git a/docker/dev/.env.example b/docker/dev/.env.example deleted file mode 100644 index 4c1762e7c..000000000 --- a/docker/dev/.env.example +++ /dev/null @@ -1,33 +0,0 @@ -SEND_EMAIL= -FROM_EMAIL= -SENDGRID_API_KEY= -AUTH_IDENTITY_SERVER_SECRET= -AUTH_COOKIE_KEY= -AUTH_INTERACTION_COOKIE_SAME_SITE= -RATE_API_KEY= -WALLET_ADDRESS_REDIRECT_HTML_PAGE= - -GATEHUB_ACCESS_KEY= -GATEHUB_SECRET_KEY= -GATEHUB_WEBHOOK_SECRET= -GATEHUB_GATEWAY_UUID= -GATEHUB_SETTLEMENT_WALLET_ADDRESS= -GATEHUB_ORG_ID= -GATEHUB_CARD_APP_ID= -RATE_LIMIT= -RATE_LIMIT_LEVEL= -GATEHUB_ACCOUNT_PRODUCT_CODE= -GATEHUB_CARD_PRODUCT_CODE= -GATEHUB_NAME_ON_CARD= -GATEHUB_CARD_PP_PREFIX= -CARD_PIN_HREF= -CARD_DATA_HREF= - -# commerce env variables -# encoded base 64 private key -PRIVATE_KEY= -KEY_ID= -PAYMENT_POINTER= - -OPERATOR_TENANT_ID= -ADMIN_API_SECRET= diff --git a/docker/dev/docker-compose.yml b/docker/dev/docker-compose.yml deleted file mode 100644 index 8afce4675..000000000 --- a/docker/dev/docker-compose.yml +++ /dev/null @@ -1,336 +0,0 @@ -version: '3.5' - -x-logging: &logging - logging: - driver: 'json-file' - options: - max-size: '100m' - -services: - postgres: - container_name: postgres - image: 'postgres:15' - environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: password - ports: - - '5433:5432' - restart: unless-stopped - networks: - - testnet - volumes: - - pg-data:/var/lib/postgresql/data - - ../dbinit.sql:/docker-entrypoint-initdb.d/init.sql - - # Wallet - wallet-backend: - container_name: wallet-backend - build: - context: ../.. - args: - DEV_MODE: ${DEV_MODE} - dockerfile: ./packages/wallet/backend/Dockerfile.dev - depends_on: - - postgres - - rafiki-backend - - redis - volumes: - - ../../packages/wallet/backend:/home/testnet/packages/wallet/backend - - ../../packages/wallet/shared:/home/testnet/packages/wallet/shared - environment: - NODE_ENV: development - PORT: 3003 - DEBUG_PORT: 9229 - DATABASE_URL: postgres://wallet_backend:wallet_backend@postgres/wallet_backend - COOKIE_NAME: testnet.cookie - COOKIE_PASSWORD: testnet.cookie.password.super.secret.ilp - COOKIE_TTL: 2630000 - OPEN_PAYMENTS_HOST: https://rafiki-backend - GRAPHQL_ENDPOINT: http://rafiki-backend:3001/graphql - AUTH_GRAPHQL_ENDPOINT: http://rafiki-auth:3008/graphql - AUTH_DOMAIN: http://rafiki-auth:3009 - AUTH_IDENTITY_SERVER_SECRET: ${AUTH_IDENTITY_SERVER_SECRET} - RAFIKI_WEBHOOK_SIGNATURE_SECRET: ${RAFIKI_SIGNATURE_SECRET:-327132b5-99e9-4eb8-8a25-2b7d7738ece1} - SENDGRID_API_KEY: ${SENDGRID_API_KEY} - FROM_EMAIL: ${FROM_EMAIL} - SEND_EMAIL: ${SEND_EMAIL:-false} - REDIS_URL: redis://redis:6379/0 - KRATOS_ADMIN_URL: 'http://kratos:4434/admin' - GATEHUB_ACCESS_KEY: ${GATEHUB_ACCESS_KEY} - GATEHUB_SECRET_KEY: ${GATEHUB_SECRET_KEY} - GATEHUB_WEBHOOK_SECRET: ${GATEHUB_WEBHOOK_SECRET} - GATEHUB_GATEWAY_UUID: ${GATEHUB_GATEWAY_UUID} - GATEHUB_SETTLEMENT_WALLET_ADDRESS: ${GATEHUB_SETTLEMENT_WALLET_ADDRESS} - GATEHUB_ORG_ID: ${GATEHUB_ORG_ID} - GATEHUB_CARD_APP_ID: ${GATEHUB_CARD_APP_ID} - RATE_LIMIT: ${RATE_LIMIT} - RATE_LIMIT_LEVEL: ${RATE_LIMIT_LEVEL} - GATEHUB_ACCOUNT_PRODUCT_CODE: ${GATEHUB_ACCOUNT_PRODUCT_CODE} - GATEHUB_CARD_PRODUCT_CODE: ${GATEHUB_CARD_PRODUCT_CODE} - GATEHUB_NAME_ON_CARD: ${GATEHUB_NAME_ON_CARD} - GATEHUB_CARD_PP_PREFIX: ${GATEHUB_CARD_PP_PREFIX} - CARD_DATA_HREF: ${CARD_DATA_HREF} - CARD_PIN_HREF: ${CARD_PIN_HREF} - STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY} - STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET} - USE_STRIPE: ${USE_STRIPE} - OPERATOR_TENANT_ID: ${OPERATOR_TENANT_ID:-f829c064-762a-4430-ac5d-7af5df198551} - ADMIN_API_SECRET: ${ADMIN_API_SECRET:-secret-key} - ADMIN_SIGNATURE_VERSION: 1 - restart: always - networks: - - testnet - ports: - - '3003:3003' - - '9229:9229' # Map debugger port to local machine's port 9229 - <<: *logging - - # Boutique - boutique-backend: - container_name: boutique-backend - build: - context: ../.. - args: - DEV_MODE: ${DEV_MODE} - dockerfile: ./packages/boutique/backend/Dockerfile.dev - volumes: - - ../../packages/boutique/backend:/home/testnet/packages/boutique/backend - - ../../packages/boutique/shared:/home/testnet/packages/boutique/shared - depends_on: - - postgres - environment: - NODE_ENV: development - PORT: 3004 - DEBUG_PORT: 9230 - DATABASE_URL: postgres://boutique_backend:boutique_backend@postgres/boutique_backend - PRIVATE_KEY: ${PRIVATE_KEY} - KEY_ID: ${KEY_ID} - PAYMENT_POINTER: ${PAYMENT_POINTER} - restart: always - networks: - - testnet - ports: - - '3004:3004' - - '9230:9230' # Map debugger port to local machine's port 9230 - <<: *logging - - # Rafiki - rafiki-auth: - container_name: rafiki-auth - image: ghcr.io/interledger/rafiki-auth:v2.3.0-beta - restart: always - networks: - - testnet - ports: - - '3006:3006' - - '3008:3008' - environment: - AUTH_PORT: 3006 - INTROSPECTION_PORT: 3007 - ADMIN_PORT: 3008 - NODE_ENV: development - AUTH_SERVER_URL: http://localhost:3006 - AUTH_DATABASE_URL: postgresql://rafiki_auth:rafiki_auth@postgres/rafiki_auth - IDENTITY_SERVER_URL: http://localhost:4003/grant-interactions - IDENTITY_SERVER_SECRET: ${AUTH_IDENTITY_SERVER_SECRET:-327132b5-99e9-4eb8-8a25-2b7d7738ece1} - COOKIE_KEY: ${AUTH_COOKIE_KEY:-8fd398393c47dd27a3167d9c081c094f} - INTERACTION_COOKIE_SAME_SITE: ${AUTH_INTERACTION_COOKIE_SAME_SITE:-lax} - WAIT_SECONDS: 1 - REDIS_URL: redis://redis:6379/0 - OPERATOR_TENANT_ID: ${OPERATOR_TENANT_ID:-f829c064-762a-4430-ac5d-7af5df198551} - ADMIN_API_SECRET: ${ADMIN_API_SECRET:-secret-key} - ADMIN_SIGNATURE_VERSION: 1 - depends_on: - - postgres - <<: *logging - - rafiki-backend: - container_name: rafiki-backend - image: ghcr.io/interledger/rafiki-backend:v2.3.0-beta - restart: always - privileged: true - volumes: - - ../temp/:/workspace/temp/ - ports: - - '3010:80' - - '3011:3001' - - '3005:3005' - - '3002:3002' - networks: - - testnet - environment: - NODE_ENV: development - LOG_LEVEL: debug - ADMIN_PORT: 3001 - CONNECTOR_PORT: 3002 - OPEN_PAYMENTS_PORT: 80 - DATABASE_URL: postgresql://rafiki_backend:rafiki_backend@postgres/rafiki_backend - USE_TIGERBEETLE: true - TIGERBEETLE_CLUSTER_ID: 0 - TIGERBEETLE_REPLICA_ADDRESSES: 10.5.0.50:4342 - NONCE_REDIS_KEY: test - AUTH_SERVER_GRANT_URL: http://rafiki-auth:3006 - AUTH_SERVER_INTROSPECTION_URL: http://rafiki-auth:3007 - ILP_ADDRESS: test.net - ILP_CONNECTOR_URL: http://127.0.0.1:3002 - STREAM_SECRET: BjPXtnd00G2mRQwP/8ZpwyZASOch5sUXT5o0iR5b5wU= - ADMIN_KEY: admin - OPEN_PAYMENTS_URL: https://rafiki-backend - REDIS_URL: redis://redis:6379/0 - WALLET_ADDRESS_URL: https://rafiki-backend/.well-known/pay - # Testnet urls - not implemented - WEBHOOK_URL: http://wallet-backend:3003/webhooks - WEBHOOK_TIMEOUT: 60000 - SIGNATURE_SECRET: ${RAFIKI_SIGNATURE_SECRET:-327132b5-99e9-4eb8-8a25-2b7d7738ece1} - EXCHANGE_RATES_URL: http://wallet-backend:3003/rates - ENABLE_AUTO_PEERING: true - AUTO_PEERING_SERVER_PORT: 3005 - INSTANCE_NAME: 'Testnet Wallet' - SLIPPAGE: 0.01 - KEY_ID: rafiki - WALLET_ADDRESS_REDIRECT_HTML_PAGE: ${WALLET_ADDRESS_REDIRECT_HTML_PAGE} - OPERATOR_TENANT_ID: ${OPERATOR_TENANT_ID:-f829c064-762a-4430-ac5d-7af5df198551} - ADMIN_API_SECRET: ${ADMIN_API_SECRET:-secret-key} - ADMIN_SIGNATURE_VERSION: 1 - AUTH_SERVICE_API_URL: http://rafiki-auth:3011 - CARD_SERVICE_URL: 'http://rafiki-card-service:3007' - CARD_WEBHOOK_SERVICE_URL: 'http://rafiki-card-service:3007/webhook' - POS_SERVICE_URL: 'http://rafiki-pos-servicee:3014' - POS_WEBHOOK_SERVICE_URL: 'http://rafiki-pos-service:3014/webhook' - depends_on: - - postgres - - redis - <<: *logging - - rafiki-frontend: - container_name: rafiki-frontend - image: ghcr.io/interledger/rafiki-frontend:v2.3.0-beta - depends_on: - - rafiki-backend - restart: always - privileged: true - ports: - - '3012:3012' - networks: - - testnet - environment: - PORT: 3012 - GRAPHQL_URL: http://rafiki-backend:3001/graphql - OPEN_PAYMENTS_URL: https://rafiki-backend/ - ENABLE_INSECURE_MESSAGE_COOKIE: true - KRATOS_CONTAINER_PUBLIC_URL: 'http://kratos:4433' - KRATOS_BROWSER_PUBLIC_URL: 'http://localhost:4433' - KRATOS_ADMIN_URL: 'http://kratos:4434/admin' - AUTH_ENABLED: false - SIGNATURE_VERSION: 1 - <<: *logging - - rafiki-card-service: - container_name: rafiki-card-service - image: ghcr.io/interledger/rafiki-card-service:v2.3.0-beta - restart: always - privileged: true - networks: - - testnet - ports: - - '3007:3007' - environment: - NODE_ENV: development - LOG_LEVEL: debug - CARD_SERVICE_PORT: 3007 - REDIS_URL: redis://redis:6379/0 - GRAPHQL_URL: http://rafiki-backend:3001/graphql - TENANT_ID: ${OPERATOR_TENANT_ID:-f829c064-762a-4430-ac5d-7af5df198551} - TENANT_SECRET: ${ADMIN_API_SECRET:-secret-key} - TENANT_SIGNATURE_VERSION: 1 - <<: *logging - - rafiki-pos-service: - container_name: rafiki-pos-service - image: ghcr.io/interledger/rafiki-point-of-sale:v2.3.0-beta - restart: always - privileged: true - networks: - - testnet - ports: - - '3014:3014' - environment: - NODE_ENV: development - LOG_LEVEL: debug - PORT: 3014 - GRAPHQL_URL: http://rafiki-backend:3001/graphql - TENANT_ID: ${OPERATOR_TENANT_ID:-f829c064-762a-4430-ac5d-7af5df198551} - TENANT_SECRET: ${ADMIN_API_SECRET:-secret-key} - TENANT_SIGNATURE_VERSION: 1 - WEBHOOK_SIGNATURE_SECRET: ${RAFIKI_SIGNATURE_SECRET:-327132b5-99e9-4eb8-8a25-2b7d7738ece1} - WEBHOOK_SIGNATURE_VERSION: 1 - USE_HTTP: true - <<: *logging - - kratos: - image: 'oryd/kratos:v1.3.1' - privileged: true - depends_on: - - postgres - - mailslurper - ports: - - '4433:4433' - volumes: - - ../entrypoint.sh:/entrypoint.sh - - ../identity.schema.json:/etc/config/kratos/identity.schema.json - - ./kratos.yml:/etc/config/kratos/kratos.yml - entrypoint: ['/entrypoint.sh'] - networks: - - testnet - - tigerbeetle: - image: ghcr.io/tigerbeetle/tigerbeetle:0.16.60 - privileged: true - volumes: - - tigerbeetle-data:/var/lib/tigerbeetle - networks: - testnet: - ipv4_address: 10.5.0.50 - entrypoint: - - /bin/sh - - -c - - | - set -ex - DATA_FILE=/var/lib/tigerbeetle/cluster_0_replica_0.tigerbeetle - set +e - ls $$DATA_FILE - DATA_FILE_EXISTS="$$?" - set -e - echo $$DATA_FILE_EXISTS - if [ "$$DATA_FILE_EXISTS" != 0 ]; then - ./tigerbeetle format --cluster=0 --replica=0 --replica-count=1 $$DATA_FILE; - fi - hostname -i - ls /var/lib/tigerbeetle - ./tigerbeetle start --addresses=0.0.0.0:4342 $$DATA_FILE - - redis: - image: 'redis:7' - restart: unless-stopped - networks: - - testnet - - mailslurper: - image: oryd/mailslurper:latest-smtps - ports: - - '4436:4436' - - '4437:4437' - networks: - - testnet - -networks: - testnet: - driver: bridge - ipam: - config: - - subnet: 10.5.0.0/24 - gateway: 10.5.0.1 - -volumes: - pg-data: - tigerbeetle-data: # named volumes can be managed easier using docker-compose diff --git a/docker/dev/kratos.yml b/docker/dev/kratos.yml deleted file mode 100644 index 6089d1a3d..000000000 --- a/docker/dev/kratos.yml +++ /dev/null @@ -1,91 +0,0 @@ -version: v0.13.0 - -dsn: postgres://kratos:kratos@postgres:5432/kratos?sslmode=disable&max_conns=20&max_idle_conns=4 - -serve: - public: - base_url: http://localhost:4433/ - cors: - enabled: true - admin: - base_url: http://kratos:4434/ - -selfservice: - default_browser_return_url: http://localhost:3012/ - allowed_return_urls: - - http://localhost:3012 - - methods: - link: - config: - lifespan: 1h - base_url: http://localhost:4433 - enabled: true - password: - enabled: true - - flows: - error: - ui_url: http://localhost:3012/error - - settings: - ui_url: http://localhost:3012/settings - privileged_session_max_age: 15m - required_aal: highest_available - - recovery: - enabled: true - ui_url: http://localhost:3012/auth/recovery - use: link - after: - hooks: - - hook: revoke_active_sessions - - verification: - enabled: false - - logout: - after: - default_browser_return_url: http://localhost:3012/auth - - login: - ui_url: http://localhost:3012/auth/login - lifespan: 10m - - registration: - enabled: false - -log: - level: debug - format: json - leak_sensitive_values: true - -secrets: - cookie: - - PLEASE-CHANGE-ME-I-AM-VERY-INSECURE - cipher: - - 32-LONG-SECRET-NOT-SECURE-AT-ALL - -ciphers: - algorithm: xchacha20-poly1305 - -hashers: - algorithm: bcrypt - bcrypt: - cost: 8 - -identity: - schemas: - - id: default - url: file:///etc/config/kratos/identity.schema.json - -courier: - smtp: - connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true - -session: - lifespan: 1h - cookie: - persistent: false - same_site: Strict - path: / diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh deleted file mode 100755 index 57a708b2b..000000000 --- a/docker/entrypoint.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh -set -e - -echo "Running Kratos Migrations..." -kratos -c /etc/config/kratos/kratos.yml migrate sql -e --yes - -if [ "$DEV_MODE" = true ]; then - echo "Starting Kratos in dev mode..." - exec kratos serve -c /etc/config/kratos/kratos.yml --dev --watch-courier -else - echo "Starting Kratos..." - exec kratos serve -c /etc/config/kratos/kratos.yml -fi \ No newline at end of file diff --git a/docker/identity.schema.json b/docker/identity.schema.json deleted file mode 100644 index 5af61c822..000000000 --- a/docker/identity.schema.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Person", - "type": "object", - "properties": { - "traits": { - "type": "object", - "properties": { - "email": { - "type": "string", - "format": "email", - "title": "E-Mail", - "minLength": 3, - "ory.sh/kratos": { - "credentials": { - "password": { - "identifier": true - } - }, - "verification": { - "via": "email" - }, - "recovery": { - "via": "email" - } - } - } - }, - "required": ["email"], - "additionalProperties": false - } - } -} diff --git a/docker/prod/.env.example b/docker/prod/.env.example deleted file mode 100644 index ff5b63f16..000000000 --- a/docker/prod/.env.example +++ /dev/null @@ -1,109 +0,0 @@ -# GENERAL -NODE_ENV= - -# DATABASE -POSTGRES_USER= -POSTGRES_PASSWORD= - -# WALLET FRONTEND -WALLET_FRONTEND_PORT= -WALLET_FRONTEND_BACKEND_URL= -WALLET_FRONTEND_OPEN_PAYMENTS_HOST= -WALLET_FRONTEND_AUTH_HOST= -WALLET_FRONTEND_THEME= - -# WALLET BACKEND -WALLET_BACKEND_PORT= -WALLET_BACKEND_DATABASE_URL= -WALLET_BACKEND_REDIS_URL= -WALLET_BACKEND_COOKIE_NAME= -WALLET_BACKEND_COOKIE_PASSWORD= -WALLET_BACKEND_COOKIE_TTL= -WALLET_BACKEND_OPEN_PAYMENTS_HOST= -WALLET_BACKEND_GRAPHQL_ENDPOINT= -WALLET_BACKEND_RAFIKI_MONEY_FRONTEND_HOST= -WALLET_BACKEND_SENDGRID_API_KEY= -WALLET_BACKEND_FROM_EMAIL= -WALLET_BACKEND_SEND_EMAIL= -WALLET_BACKEND_AUTH_DOMAIN= -WALLET_BACKEND_GATEHUB_ENV= -WALLET_BACKEND_GATEHUB_ACCESS_KEY= -WALLET_BACKEND_GATEHUB_SECRET_KEY= -WALLET_BACKEND_GATEHUB_WEBHOOK_SECRET= -WALLET_BACKEND_GATEHUB_GATEWAY_UUID= -WALLET_BACKEND_GATEHUB_SETTLEMENT_WALLET_ADDRESS= -WALLET_BACKEND_GATEHUB_ORG_ID= -WALLET_BACKEND_GATEHUB_CARD_APP_ID= -WALLET_BACKEND_GATEHUB_ACCOUNT_PRODUCT_CODE= -WALLET_BACKEND_GATEHUB_CARD_PRODUCT_CODE= -WALLET_BACKEND_GATEHUB_NAME_ON_CARD= -WALLET_BACKEND_GATEHUB_CARD_PP_PREFIX= - -# BOUTIQUE -BOUTIQUE_BACKEND_PORT= -BOUTIQUE_BACKEND_DATABASE_URL= -BOUTIQUE_BACKEND_FRONTEND_URL= -BOUTIQUE_BACKEND_PRIVATE_KEY= -BOUTIQUE_BACKEND_KEY_ID= -BOUTIQUE_BACKEND_PAYMENT_POINTER= - -BOUTIQUE_FRONTEND_PORT= -BOUTIQUE_FRONTEND_API_BASE_URL= -BOUTIQUE_FRONTEND_CURRENCY= -BOUTIQUE_FRONTEND_THEME= - -# RAFIKI AUTH -RAFIKI_AUTH_PORT= -RAFIKI_AUTH_ADMIN_PORT= -RAFIKI_AUTH_INTROSPECTION_PORT= -RAFIKI_AUTH_DATABASE_URL= -RAFIKI_AUTH_IDENTITY_SERVER_DOMAIN= -RAFIKI_AUTH_IDENTITY_SERVER_SECRET= -RAFIKI_AUTH_COOKIE_KEY= -RAFIKI_AUTH_INTERACTION_COOKIE_SAME_SITE= -RAFIKI_AUTH_SERVER_DOMAIN= -RAFIKI_AUTH_WAIT_SECONDS= - -# RAFIKI BACKEND -RAFIKI_BACKEND_LOG_LEVEL= -RAFIKI_BACKEND_ADMIN_PORT= -RAFIKI_BACKEND_CONNECTOR_PORT= -RAFIKI_BACKEND_OPEN_PAYMENTS_PORT= -RAFIKI_BACKEND_DATABASE_URL= -RAFIKI_BACKEND_USE_TIGERBEETLE= -RAFIKI_BACKEND_TIGERBEETLE_CLUSTER_ID= -RAFIKI_BACKEND_TIGERBEETLE_REPLICA_ADDRESSES= -RAFIKI_BACKEND_NONCE_REDIS_KEY= -RAFIKI_BACKEND_AUTH_SERVER_GRANT_URL= -RAFIKI_BACKEND_AUTH_SERVER_INTROSPECTION_URL= -RAFIKI_BACKEND_ILP_ADDRESS= -RAFIKI_BACKEND_STREAM_SECRET= -RAFIKI_BACKEND_ADMIN_KEY= -RAFIKI_BACKEND_OPEN_PAYMENTS_URL= -RAFIKI_BACKEND_REDIS_URL= -RAFIKI_BACKEND_WALLET_ADDRESS_URL= -RAFIKI_BACKEND_WEBHOOK_URL= -RAFIKI_BACKEND_WEBHOOK_TIMEOUT= -RAFIKI_BACKEND_EXCHANGE_RATES_URL= -RAFIKI_BACKEND_AUTOPEERING_PORT= -RAFIKI_BACKEND_ILP_CONNECTOR_ADDRESS= -RAFIKI_BACKEND_INSTANCE_NAME= -RAFIKI_BACKEND_SLIPPAGE= -RAFIKI_BACKEND_ENABLE_TELEMETRY= -RAFIKI_BACKEND_LIVENET= -RAFIKI_BACKEND_SIGNATURE_SECRET= -RAFIKI_BACKEND_WALLET_ADDRESS_REDIRECT_HTML_PAGE= - -# RAFIKI FRONTEND -RAFIKI_FRONTEND_PORT= -RAFIKI_FRONTEND_GRAPHQL_URL= -RAFIKI_FRONTEND_OPEN_PAYMENTS_URL= - -# INTERLEDGER PAY -INTERLEDGER_PAY_PORT= -INTERLEDGER_PAY_KEY_ID= -INTERLEDGER_PAY_PRIVATE_KEY= -INTERLEDGER_PAY_WALLET_ADDRESS= -INTERLEDGER_PAY_REDIRECT_URL= -INTERLEDGER_PAY_HOST= -INTERLEDGER_PAY_SESSION_COOKIE_SECRET_KEY= diff --git a/docker/prod/docker-compose.yml b/docker/prod/docker-compose.yml deleted file mode 100644 index 67df5ad8a..000000000 --- a/docker/prod/docker-compose.yml +++ /dev/null @@ -1,305 +0,0 @@ -version: '3.8' -name: 'testnet' - -x-logging: &logging - logging: - driver: 'gcplogs' - -services: - postgres: - image: 'postgres:15' - container_name: postgres - environment: - POSTGRES_USER: ${POSTGRES_USER} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - networks: - - testnet - restart: unless-stopped - volumes: - - pg-data:/var/lib/postgresql/data - - ../dbinit.sql:/docker-entrypoint-initdb.d/init.sql - - wallet-frontend: - hostname: wallet-frontend - build: - context: ../.. - dockerfile: ./packages/wallet/frontend/Dockerfile.prod - args: - PORT: ${WALLET_FRONTEND_PORT} - COOKIE_NAME: ${WALLET_BACKEND_COOKIE_NAME} - NEXT_PUBLIC_BACKEND_URL: ${WALLET_FRONTEND_BACKEND_URL} - NEXT_PUBLIC_OPEN_PAYMENTS_HOST: ${WALLET_FRONTEND_OPEN_PAYMENTS_HOST} - NEXT_PUBLIC_AUTH_HOST: ${WALLET_FRONTEND_AUTH_HOST} - NEXT_PUBLIC_GATEHUB_ENV: ${WALLET_BACKEND_GATEHUB_ENV} - NEXT_PUBLIC_THEME: ${WALLET_FRONTEND_THEME} - container_name: wallet-frontend - ports: - - '${WALLET_FRONTEND_PORT}:${WALLET_FRONTEND_PORT}' - restart: always - <<: *logging - - wallet-backend: - build: - context: ../.. - dockerfile: ./packages/wallet/backend/Dockerfile.prod - container_name: wallet-backend - depends_on: - - postgres - - rafiki-backend - environment: - NODE_ENV: ${NODE_ENV} - PORT: ${WALLET_BACKEND_PORT} - DATABASE_URL: ${WALLET_BACKEND_DATABASE_URL} - COOKIE_NAME: ${WALLET_BACKEND_COOKIE_NAME} - COOKIE_PASSWORD: ${WALLET_BACKEND_COOKIE_PASSWORD} - COOKIE_TTL: ${WALLET_BACKEND_COOKIE_TTL} - OPEN_PAYMENTS_HOST: ${WALLET_BACKEND_OPEN_PAYMENTS_HOST} - GRAPHQL_ENDPOINT: ${WALLET_BACKEND_GRAPHQL_ENDPOINT} - RAFIKI_MONEY_FRONTEND_HOST: ${WALLET_BACKEND_RAFIKI_MONEY_FRONTEND_HOST} - SENDGRID_API_KEY: ${WALLET_BACKEND_SENDGRID_API_KEY} - FROM_EMAIL: ${WALLET_BACKEND_FROM_EMAIL} - SEND_EMAIL: ${WALLET_BACKEND_SEND_EMAIL} - AUTH_IDENTITY_SERVER_SECRET: ${RAFIKI_AUTH_IDENTITY_SERVER_SECRET} - RAFIKI_WEBHOOK_SIGNATURE_SECRET: ${RAFIKI_BACKEND_SIGNATURE_SECRET} - AUTH_DOMAIN: ${WALLET_BACKEND_AUTH_DOMAIN} - REDIS_URL: ${WALLET_BACKEND_REDIS_URL} - GATEHUB_ENV: ${WALLET_BACKEND_GATEHUB_ENV} - GATEHUB_ACCESS_KEY: ${WALLET_BACKEND_GATEHUB_ACCESS_KEY} - GATEHUB_SECRET_KEY: ${WALLET_BACKEND_GATEHUB_SECRET_KEY} - GATEHUB_WEBHOOK_SECRET: ${WALLET_BACKEND_GATEHUB_WEBHOOK_SECRET} - GATEHUB_GATEWAY_UUID: ${WALLET_BACKEND_GATEHUB_GATEWAY_UUID} - GATEHUB_SETTLEMENT_WALLET_ADDRESS: ${WALLET_BACKEND_GATEHUB_SETTLEMENT_WALLET_ADDRESS} - GATEHUB_ORG_ID: ${WALLET_BACKEND_GATEHUB_ORG_ID} - GATEHUB_CARD_APP_ID: ${WALLET_BACKEND_GATEHUB_CARD_APP_ID} - GATEHUB_ACCOUNT_PRODUCT_CODE: ${WALLET_BACKEND_GATEHUB_ACCOUNT_PRODUCT_CODE} - GATEHUB_CARD_PRODUCT_CODE: ${WALLET_BACKEND_GATEHUB_CARD_PRODUCT_CODE} - GATEHUB_NAME_ON_CARD: ${WALLET_BACKEND_GATEHUB_NAME_ON_CARD} - GATEHUB_CARD_PP_PREFIX: ${WALLET_BACKEND_GATEHUB_CARD_PP_PREFIX} - STRIPE_SECRET_KEY: ${WALLET_BACKEND_STRIPE_SECRET_KEY} - STRIPE_WEBHOOK_SECRET: ${WALLET_BACKEND_STRIPE_WEBHOOK_SECRET} - USE_STRIPE: ${USE_STRIPE} - networks: - - testnet - ports: - - '${WALLET_BACKEND_PORT}:${WALLET_BACKEND_PORT}' - restart: always - <<: *logging - - boutique-frontend: - container_name: boutique-frontend - build: - context: ../.. - dockerfile: ./packages/boutique/frontend/Dockerfile.prod - args: - PORT: ${BOUTIQUE_FRONTEND_API_BASE_URL} - VITE_API_BASE_URL: ${BOUTIQUE_FRONTEND_API_BASE_URL} - VITE_CURRENCY: ${BOUTIQUE_FRONTEND_CURRENCY} - VITE_THEME: ${BOUTIQUE_FRONTEND_THEME} - depends_on: - - postgres - environment: - NODE_ENV: ${NODE_ENV} - restart: always - networks: - - testnet - ports: - - '${BOUTIQUE_FRONTEND_PORT}:${BOUTIQUE_FRONTEND_PORT}' - <<: *logging - - boutique-backend: - container_name: boutique-backend - build: - context: ../.. - dockerfile: ./packages/boutique/backend/Dockerfile.prod - depends_on: - - postgres - environment: - NODE_ENV: ${NODE_ENV} - PORT: ${BOUTIQUE_BACKEND_PORT} - FRONTEND_URL: ${BOUTIQUE_BACKEND_FRONTEND_URL} - DATABASE_URL: ${BOUTIQUE_BACKEND_DATABASE_URL} - PRIVATE_KEY: ${BOUTIQUE_BACKEND_PRIVATE_KEY} - KEY_ID: ${BOUTIQUE_BACKEND_KEY_ID} - PAYMENT_POINTER: ${BOUTIQUE_BACKEND_PAYMENT_POINTER} - restart: always - networks: - - testnet - ports: - - '3004:3004' - <<: *logging - - rafiki-auth: - image: ghcr.io/interledger/rafiki-auth:v1.2.0-beta - container_name: rafiki-auth - environment: - NODE_ENV: ${NODE_ENV} - AUTH_PORT: ${RAFIKI_AUTH_PORT} - ADMIN_PORT: ${RAFIKI_AUTH_ADMIN_PORT} - INTROSPECTION_PORT: ${RAFIKI_AUTH_INTROSPECTION_PORT} - AUTH_DATABASE_URL: ${RAFIKI_AUTH_DATABASE_URL} - COOKIE_KEY: ${RAFIKI_AUTH_COOKIE_KEY} - INTERACTION_COOKIE_SAME_SITE: ${RAFIKI_AUTH_INTERACTION_COOKIE_SAME_SITE} - IDENTITY_SERVER_SECRET: ${RAFIKI_AUTH_IDENTITY_SERVER_SECRET} - IDENTITY_SERVER_URL: ${RAFIKI_AUTH_IDENTITY_SERVER_DOMAIN} - AUTH_SERVER_URL: ${RAFIKI_AUTH_SERVER_DOMAIN} - WAIT_SECONDS: ${RAFIKI_AUTH_WAIT_SECONDS} - TRUST_PROXY: true - REDIS_URL: redis://redis:6379/0 - depends_on: - - postgres - networks: - - testnet - ports: - - '${RAFIKI_AUTH_PORT}:${RAFIKI_AUTH_PORT}' - - '${RAFIKI_AUTH_ADMIN_PORT}:${RAFIKI_AUTH_ADMIN_PORT}' - restart: always - <<: *logging - - rafiki-backend: - image: ghcr.io/interledger/rafiki-backend:v1.1.2-beta - container_name: rafiki-backend - depends_on: - - postgres - - redis - environment: - NODE_ENV: ${NODE_ENV} - LOG_LEVEL: ${RAFIKI_BACKEND_LOG_LEVEL} - ADMIN_PORT: ${RAFIKI_BACKEND_ADMIN_PORT} - CONNECTOR_PORT: ${RAFIKI_BACKEND_CONNECTOR_PORT} - OPEN_PAYMENTS_PORT: ${RAFIKI_BACKEND_OPEN_PAYMENTS_PORT} - DATABASE_URL: ${RAFIKI_BACKEND_DATABASE_URL} - USE_TIGERBEETLE: ${RAFIKI_BACKEND_USE_TIGERBEETLE} - TIGERBEETLE_CLUSTER_ID: ${RAFIKI_BACKEND_TIGERBEETLE_CLUSTER_ID} - TIGERBEETLE_REPLICA_ADDRESSES: ${RAFIKI_BACKEND_TIGERBEETLE_REPLICA_ADDRESSES} - NONCE_REDIS_KEY: ${RAFIKI_BACKEND_NONCE_REDIS_KEY} - AUTH_SERVER_GRANT_URL: ${RAFIKI_BACKEND_AUTH_SERVER_GRANT_URL} - AUTH_SERVER_INTROSPECTION_URL: ${RAFIKI_BACKEND_AUTH_SERVER_INTROSPECTION_URL} - ILP_ADDRESS: ${RAFIKI_BACKEND_ILP_ADDRESS} - STREAM_SECRET: ${RAFIKI_BACKEND_STREAM_SECRET} - ADMIN_KEY: ${RAFIKI_BACKEND_ADMIN_KEY} - OPEN_PAYMENTS_URL: ${RAFIKI_BACKEND_OPEN_PAYMENTS_URL} - REDIS_URL: ${RAFIKI_BACKEND_REDIS_URL} - WALLET_ADDRESS_URL: ${RAFIKI_BACKEND_WALLET_ADDRESS_URL} - WEBHOOK_URL: ${RAFIKI_BACKEND_WEBHOOK_URL} - WEBHOOK_TIMEOUT: ${RAFIKI_BACKEND_WEBHOOK_TIMEOUT} - SIGNATURE_SECRET: ${RAFIKI_BACKEND_SIGNATURE_SECRET} - EXCHANGE_RATES_URL: ${RAFIKI_BACKEND_EXCHANGE_RATES_URL} - TRUST_PROXY: true - ENABLE_AUTO_PEERING: true - AUTO_PEERING_SERVER_PORT: ${RAFIKI_BACKEND_AUTOPEERING_PORT} - ILP_CONNECTOR_URL: ${RAFIKI_BACKEND_ILP_CONNECTOR_ADDRESS} - INSTANCE_NAME: ${RAFIKI_BACKEND_INSTANCE_NAME} - SLIPPAGE: ${RAFIKI_BACKEND_SLIPPAGE} - ENABLE_TELEMETRY: ${RAFIKI_BACKEND_ENABLE_TELEMETRY} - LIVENET: ${RAFIKI_BACKEND_LIVENET} - KEY_ID: ${RAFIKI_BACKEND_KEY_ID} - WALLET_ADDRESS_REDIRECT_HTML_PAGE: ${RAFIKI_BACKEND_WALLET_ADDRESS_REDIRECT_HTML_PAGE} - networks: - - testnet - ports: - - '3010:80' - - '3011:3001' - - '${RAFIKI_BACKEND_AUTOPEERING_PORT}:${RAFIKI_BACKEND_AUTOPEERING_PORT}' - - '${RAFIKI_BACKEND_CONNECTOR_PORT}:${RAFIKI_BACKEND_CONNECTOR_PORT}' - privileged: true - restart: always - volumes: - - ../temp/:/workspace/temp/ - <<: *logging - - interledger-pay: - image: ghcr.io/interledger/interledger-pay:latest - container_name: interledger-pay - environment: - NODE_ENV: ${NODE_ENV} - PORT: ${INTERLEDGER_PAY_PORT} - KEY_ID: ${INTERLEDGER_PAY_KEY_ID} - PRIVATE_KEY: ${INTERLEDGER_PAY_PRIVATE_KEY} - WALLET_ADDRESS: ${INTERLEDGER_PAY_WALLET_ADDRESS} - REDIRECT_URL: ${INTERLEDGER_PAY_REDIRECT_URL} - INTERLEDGER_PAY_HOST: ${INTERLEDGER_PAY_HOST} - SESSION_COOKIE_SECRET_KEY: ${INTERLEDGER_PAY_SESSION_COOKIE_SECRET_KEY} - networks: - - testnet - restart: always - privileged: true - ports: - - '${INTERLEDGER_PAY_PORT}:${INTERLEDGER_PAY_PORT}' - <<: *logging - - rafiki-frontend: - image: ghcr.io/interledger/rafiki-frontend:v1.1.2-beta - container_name: rafiki-frontend - depends_on: - - rafiki-backend - environment: - PORT: ${RAFIKI_FRONTEND_PORT} - GRAPHQL_URL: ${RAFIKI_FRONTEND_GRAPHQL_URL} - OPEN_PAYMENTS_URL: ${RAFIKI_FRONTEND_OPEN_PAYMENTS_URL} - KRATOS_CONTAINER_PUBLIC_URL: 'http://kratos:4433' - KRATOS_BROWSER_PUBLIC_URL: 'https://admin.rafiki.money/kratos' - KRATOS_ADMIN_URL: 'http://kratos:4434/admin' - networks: - - testnet - restart: always - privileged: true - ports: - - '${RAFIKI_FRONTEND_PORT}:${RAFIKI_FRONTEND_PORT}' - <<: *logging - - tigerbeetle: - image: ghcr.io/tigerbeetle/tigerbeetle:0.16.60 - privileged: true - volumes: - - tigerbeetle-data:/var/lib/tigerbeetle - networks: - testnet: - ipv4_address: 10.5.0.50 - entrypoint: - - /bin/sh - - -c - - | - set -ex - DATA_FILE=/var/lib/tigerbeetle/cluster_0_replica_0.tigerbeetle - set +e - ls $$DATA_FILE - DATA_FILE_EXISTS="$$?" - set -e - echo $$DATA_FILE_EXISTS - if [ "$$DATA_FILE_EXISTS" != 0 ]; then - ./tigerbeetle format --cluster=0 --replica=0 --replica-count=1 $$DATA_FILE; - fi - hostname -i - ls /var/lib/tigerbeetle - ./tigerbeetle start --addresses=0.0.0.0:4342 $$DATA_FILE - - redis: - image: 'redis:7' - restart: unless-stopped - networks: - - testnet - - kratos: - image: 'oryd/kratos:v1.3.1' - privileged: true - ports: - - '4433:4433' - volumes: - - ../entrypoint.sh:/entrypoint.sh - - ../identity.schema.json:/etc/config/kratos/identity.schema.json - - ./kratos.yml:/etc/config/kratos/kratos.yml - entrypoint: ['/entrypoint.sh'] - networks: - - testnet - -networks: - testnet: - driver: bridge - ipam: - config: - - subnet: 10.5.0.0/24 - gateway: 10.5.0.1 - -volumes: - pg-data: - tigerbeetle-data: # named volumes can be managed easier using docker-compose diff --git a/docker/prod/kratos.yml b/docker/prod/kratos.yml deleted file mode 100644 index 386d43672..000000000 --- a/docker/prod/kratos.yml +++ /dev/null @@ -1,91 +0,0 @@ -version: v0.13.0 - -dsn: postgres://kratos:kratos@postgres:5432/kratos?sslmode=disable&max_conns=20&max_idle_conns=4 - -serve: - public: - base_url: https://admin.rafiki.money/kratos - cors: - enabled: true - admin: - base_url: http://kratos:4434/ - -selfservice: - default_browser_return_url: https://admin.rafiki.money/ - allowed_return_urls: - - https://admin.rafiki.money - - methods: - link: - config: - lifespan: 1h - base_url: https://admin.rafiki.money/kratos - enabled: true - password: - enabled: true - - flows: - error: - ui_url: https://admin.rafiki.money/error - - settings: - ui_url: https://admin.rafiki.money/settings - privileged_session_max_age: 15m - required_aal: highest_available - - recovery: - enabled: true - ui_url: https://admin.rafiki.money/auth/recovery - use: link - after: - hooks: - - hook: revoke_active_sessions - - verification: - enabled: false - - logout: - after: - default_browser_return_url: https://admin.rafiki.money/auth - - login: - ui_url: https://admin.rafiki.money/auth/login - lifespan: 10m - - registration: - enabled: false - -log: - level: debug - format: json - leak_sensitive_values: true - -secrets: - cookie: - - PLEASE-CHANGE-ME-I-AM-VERY-INSECURE - cipher: - - 32-LONG-SECRET-NOT-SECURE-AT-ALL - -ciphers: - algorithm: xchacha20-poly1305 - -hashers: - algorithm: bcrypt - bcrypt: - cost: 8 - -identity: - schemas: - - id: default - url: file:///etc/config/kratos/identity.schema.json - -courier: - smtp: - connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true - -session: - lifespan: 1h - cookie: - persistent: false - same_site: Strict - path: / diff --git a/docker/temp/private-key.pem b/docker/temp/private-key.pem deleted file mode 100644 index 43fefaf8a..000000000 --- a/docker/temp/private-key.pem +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN PRIVATE KEY----- -MC4CAQAwBQYDK2VwBCIEIIZFs7Y4AoIP/4WcVtKt74Uim4mxnZhV9zQ5RSnQen5u ------END PRIVATE KEY----- diff --git a/local/.env.example b/local/.env.example new file mode 100644 index 000000000..f5f83619c --- /dev/null +++ b/local/.env.example @@ -0,0 +1,116 @@ +# Testnet local docker-compose environment file (example) +# Copy to .env and adjust as needed for your machine. +# +# By default, all GateHub variables point to the local MockGatehub container +# (no real credentials needed). To use the real GateHub Sandbox instead, see +# the "Real GateHub Sandbox overrides" section below. + +# --------------------------------------------------------------------------- +# General build / dev flags +# --------------------------------------------------------------------------- + +# DEV_MODE — Controls how the wallet and boutique backend containers start. +# true (default) — hot-reload via nodemon; source changes restart the server +# automatically. Best for day-to-day development. +# debug — runs the pre-built server with the Node inspector open on +# DEBUG_PORT (default 9229 / 9230), so you can attach a +# debugger from your IDE. +# lite — runs the pre-built server directly (no watcher, no +# debugger). Useful for quick smoke-testing of production- +# like behaviour without the overhead of a file watcher. +# Note: frontend containers always run in dev/HMR mode regardless of this flag. +DEV_MODE=true + +# NODE_ENV — Standard Node.js environment flag, passed to every service. +# Defaults to "development" in docker-compose.yml when unset. +# development (default) — enables error stack traces in API responses, +# allows HTTP (not just HTTPS) for internal service +# calls, relaxes cookie security (secure=false, +# sameSite=lax), and enables the /signup auth route. +# production — hides stack traces, enforces HTTPS and secure +# cookies (secure=true, sameSite=none), and may +# disable certain non-production endpoints. +# For local development you almost never need to change this. +#NODE_ENV=development + +# Wallet Backend secrets and config +AUTH_IDENTITY_SERVER_SECRET=dev_identity_server_secret +SENDGRID_API_KEY= +FROM_EMAIL= +SEND_EMAIL=false + +# --------------------------------------------------------------------------- +# GateHub integration +# --------------------------------------------------------------------------- +# Two modes are supported: +# +# 1. MockGatehub (default) — everything works out of the box. +# The wallet backend talks to the mockgatehub container inside the +# compose network. No real credentials required. +# +# 2. Real GateHub Sandbox — for full KYC / fiat on-off ramp testing. +# Create an account at https://sandbox.gatehub.net (or contact +# timea@interledger.foundation) to obtain the values below, then +# uncomment the "Real GateHub Sandbox overrides" block. +# --------------------------------------------------------------------------- + +# --- Mock defaults (active when the override block below is commented) ----- +GATEHUB_API_BASE_URL=http://mockgatehub:8080 +GATEHUB_ENV=sandbox +GATEHUB_IFRAME_BASE_URL=http://localhost:8080 +GATEHUB_ACCESS_KEY=mock_access_key +GATEHUB_SECRET_KEY=mock_secret_key +GATEHUB_WEBHOOK_SECRET=mock_webhook_secret_please_change +GATEHUB_GATEWAY_UUID=mock-gateway-uuid +GATEHUB_SETTLEMENT_WALLET_ADDRESS=$ilp.interledger-test.dev/interledger +GATEHUB_ORG_ID=mock-org-id +GATEHUB_CARD_APP_ID=mock-card-app-id + +# --- Real GateHub Sandbox overrides ---------------------------------------- +# To use the real sandbox, comment out the mock block above and uncomment the +# lines below, filling in your credentials. +# +# IMPORTANT: Leave GATEHUB_API_BASE_URL **unset** (or comment it out). +# When it is absent the wallet backend auto-constructs the correct sub-domain +# URLs (api.sandbox.gatehub.net, managed-ramp.sandbox.gatehub.net, etc.) +# based on GATEHUB_ENV. +# +# GATEHUB_ENV=sandbox +# GATEHUB_IFRAME_BASE_URL=https://sandbox.gatehub.net +# GATEHUB_ACCESS_KEY= +# GATEHUB_SECRET_KEY= +# GATEHUB_WEBHOOK_SECRET= +# GATEHUB_GATEWAY_UUID= +# GATEHUB_SETTLEMENT_WALLET_ADDRESS= +# GATEHUB_ORG_ID= +# GATEHUB_CARD_APP_ID= + +# Wallet backend rate limits and product codes (optional) +RATE_LIMIT=100 +RATE_LIMIT_LEVEL=per_minute +GATEHUB_ACCOUNT_PRODUCT_CODE=DEFAULT +GATEHUB_CARD_PRODUCT_CODE=DEFAULT +GATEHUB_NAME_ON_CARD=TEST USER +GATEHUB_CARD_PP_PREFIX=ILF + +# Card service links (only used when cards are enabled) +CARD_DATA_HREF=http://rafiki-card-service:3007/card-data +CARD_PIN_HREF=http://rafiki-card-service:3007/card-pin + +# Stripe (optional for local) +STRIPE_SECRET_KEY= +STRIPE_WEBHOOK_SECRET= +USE_STRIPE=false + +# Admin/shared IDs and secrets +OPERATOR_TENANT_ID=f829c064-762a-4430-ac5d-7af5df198551 +ADMIN_API_SECRET=secret-key +RAFIKI_SIGNATURE_SECRET=327132b5-99e9-4eb8-8a25-2b7d7738ece1 + +# Wallet Frontend public envs +NEXT_PUBLIC_BACKEND_URL=http://localhost:3003 +NEXT_PUBLIC_AUTH_HOST=http://localhost:3006 +NEXT_PUBLIC_OPEN_PAYMENTS_HOST=http://localhost:3010 +NEXT_PUBLIC_GATEHUB_ENV=sandbox +NEXT_PUBLIC_THEME=light +NEXT_PUBLIC_FEATURES_ENABLED=false diff --git a/local/.gitignore b/local/.gitignore new file mode 100644 index 000000000..9e06ea81e --- /dev/null +++ b/local/.gitignore @@ -0,0 +1 @@ +pg-data \ No newline at end of file diff --git a/local/docker-compose.yml b/local/docker-compose.yml new file mode 100644 index 000000000..0745e0abe --- /dev/null +++ b/local/docker-compose.yml @@ -0,0 +1,287 @@ +services: + postgres: + container_name: postgres-local + image: 'postgres:15' + environment: + POSTGRES_USER: ${POSTGRES_USER:-postgres} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password} + ports: + - '15434:5432' + restart: unless-stopped + networks: + - testnet + volumes: + - pg-data:/var/lib/postgresql/data + - ./init/dbinit.sql:/docker-entrypoint-initdb.d/init.sql + + # MockGatehub - Mock Gatehub API service for local development + mockgatehub: + container_name: mockgatehub-local + image: ghcr.io/interledger/mockgatehub:1.12.3 + ports: + - '8080:8080' + environment: + MOCKGATEHUB_REDIS_URL: ${MOCKGATEHUB_REDIS_URL:-redis://redis:6379/1} + MOCKGATEHUB_REDIS_DB: ${MOCKGATEHUB_REDIS_DB:-1} + MOCKGATEHUB_VALID_CREDENTIALS: ${GATEHUB_ACCESS_KEY:-local-test-app-id}:${GATEHUB_SECRET_KEY:-local-test-app-secret} + WEBHOOK_URL: ${MOCKGATEHUB_WEBHOOK_URL:-http://wallet-backend:3003/gatehub-webhooks} + WEBHOOK_SECRET: ${GATEHUB_WEBHOOK_SECRET:-6d6f636b5f776562686f6f6b5f736563726574} + depends_on: + - redis + restart: always + networks: + - testnet + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/health"] + interval: 10s + timeout: 5s + retries: 3 + + # Wallet + wallet-backend: + container_name: wallet-backend-local + build: + context: ../ + args: + DEV_MODE: ${DEV_MODE:-true} + dockerfile: ./packages/wallet/backend/Dockerfile.dev + depends_on: + - postgres + - rafiki-backend + - redis + - mockgatehub + volumes: + - ../packages/wallet/backend:/home/testnet/packages/wallet/backend + - ../packages/wallet/shared:/home/testnet/packages/wallet/shared + environment: + NODE_ENV: ${NODE_ENV:-development} + PORT: ${WALLET_BACKEND_PORT:-3003} + DEBUG_PORT: ${WALLET_BACKEND_DEBUG_PORT:-9229} + DATABASE_URL: ${WALLET_BACKEND_DATABASE_URL:-postgres://wallet_backend:wallet_backend@postgres-local/wallet_backend} + COOKIE_NAME: ${WALLET_BACKEND_COOKIE_NAME:-testnet.cookie} + COOKIE_PASSWORD: ${WALLET_BACKEND_COOKIE_PASSWORD:-testnet.cookie.password.super.secret.ilp} + COOKIE_TTL: ${WALLET_BACKEND_COOKIE_TTL:-2630000} + OPEN_PAYMENTS_HOST: ${WALLET_BACKEND_OPEN_PAYMENTS_HOST:-https://rafiki-backend} + GRAPHQL_ENDPOINT: ${WALLET_BACKEND_GRAPHQL_ENDPOINT:-http://rafiki-backend:3001/graphql} + AUTH_GRAPHQL_ENDPOINT: ${WALLET_BACKEND_AUTH_GRAPHQL_ENDPOINT:-http://rafiki-auth:3008/graphql} + AUTH_DOMAIN: ${WALLET_BACKEND_AUTH_DOMAIN:-http://rafiki-auth:3006} + AUTH_IDENTITY_SERVER_SECRET: ${AUTH_IDENTITY_SERVER_SECRET:-auth-secret-key-12345} + RAFIKI_WEBHOOK_SIGNATURE_SECRET: ${RAFIKI_SIGNATURE_SECRET:-327132b5-99e9-4eb8-8a25-2b7d7738ece1} + SENDGRID_API_KEY: ${SENDGRID_API_KEY:-} + FROM_EMAIL: ${FROM_EMAIL:-noreply@testnet.local} + SEND_EMAIL: ${SEND_EMAIL:-false} + REDIS_URL: ${REDIS_URL:-redis://redis:6379/0} + GATEHUB_API_BASE_URL: ${GATEHUB_API_BASE_URL:-http://mockgatehub:8080} + GATEHUB_ENV: ${GATEHUB_ENV:-sandbox} + GATEHUB_IFRAME_BASE_URL: ${GATEHUB_IFRAME_BASE_URL:-http://localhost:8080} + GATEHUB_ACCESS_KEY: ${GATEHUB_ACCESS_KEY:-local-test-app-id} + GATEHUB_SECRET_KEY: ${GATEHUB_SECRET_KEY:-local-test-app-secret} + GATEHUB_WEBHOOK_SECRET: ${GATEHUB_WEBHOOK_SECRET:-6d6f636b5f776562686f6f6b5f736563726574} + GATEHUB_GATEWAY_UUID: ${GATEHUB_GATEWAY_UUID:-mock_gateway_uuid} + GATEHUB_SETTLEMENT_WALLET_ADDRESS: ${GATEHUB_SETTLEMENT_WALLET_ADDRESS:-rMockSettlementAddress} + GATEHUB_ORG_ID: ${GATEHUB_ORG_ID:-mock_org_id} + GATEHUB_CARD_APP_ID: ${GATEHUB_CARD_APP_ID:-mock_card_app_id} + RATE_LIMIT: ${RATE_LIMIT:-false} + RATE_LIMIT_LEVEL: ${RATE_LIMIT_LEVEL:-LAX} + GATEHUB_ACCOUNT_PRODUCT_CODE: ${GATEHUB_ACCOUNT_PRODUCT_CODE:-account_product} + GATEHUB_CARD_PRODUCT_CODE: ${GATEHUB_CARD_PRODUCT_CODE:-card_product} + GATEHUB_NAME_ON_CARD: ${GATEHUB_NAME_ON_CARD:-TestnetUser} + GATEHUB_CARD_PP_PREFIX: ${GATEHUB_CARD_PP_PREFIX:-TEST} + CARD_DATA_HREF: ${CARD_DATA_HREF:-https://card.example.com/data} + CARD_PIN_HREF: ${CARD_PIN_HREF:-https://card.example.com/pin} + STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY:-notset} + STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET:-notset} + USE_STRIPE: ${USE_STRIPE:-false} + OPERATOR_TENANT_ID: ${OPERATOR_TENANT_ID:-f829c064-762a-4430-ac5d-7af5df198551} + ADMIN_API_SECRET: ${ADMIN_API_SECRET:-secret-key} + ADMIN_SIGNATURE_VERSION: ${ADMIN_SIGNATURE_VERSION:-1} + WEBHOOK_SECRET: ${GATEHUB_WEBHOOK_SECRET:-6d6f636b5f776562686f6f6b5f736563726574} + restart: always + networks: + - testnet + ports: + - '3003:3003' + - '9229:9229' # Map debugger port to local machine's port 9229 + + wallet-frontend: + container_name: wallet-frontend-local + build: + context: .. + args: + DEV_MODE: ${DEV_MODE:-true} + NEXT_PUBLIC_BACKEND_URL: ${NEXT_PUBLIC_BACKEND_URL:-http://localhost:3003} + NEXT_PUBLIC_AUTH_HOST: ${NEXT_PUBLIC_AUTH_HOST:-http://localhost:3006} + NEXT_PUBLIC_OPEN_PAYMENTS_HOST: ${NEXT_PUBLIC_OPEN_PAYMENTS_HOST:-http://localhost:3010} + NEXT_PUBLIC_GATEHUB_ENV: ${NEXT_PUBLIC_GATEHUB_ENV:-sandbox} + NEXT_PUBLIC_THEME: ${NEXT_PUBLIC_THEME:-light} + NEXT_PUBLIC_FEATURES_ENABLED: ${NEXT_PUBLIC_FEATURES_ENABLED:-false} + dockerfile: ./packages/wallet/frontend/Dockerfile.dev + depends_on: + - wallet-backend + volumes: + - ../packages/wallet/frontend:/home/testnet/packages/wallet/frontend + - ../packages/wallet/shared:/home/testnet/packages/wallet/shared + environment: + NODE_ENV: ${NODE_ENV:-development} + BACKEND_URL: ${WALLET_FRONTEND_BACKEND_URL:-http://wallet-backend:3003} + NEXT_PUBLIC_BACKEND_URL: ${NEXT_PUBLIC_BACKEND_URL:-http://localhost:3003} + NEXT_PUBLIC_AUTH_HOST: ${NEXT_PUBLIC_AUTH_HOST:-http://localhost:3006} + NEXT_PUBLIC_OPEN_PAYMENTS_HOST: ${NEXT_PUBLIC_OPEN_PAYMENTS_HOST:-http://localhost:3010} + NEXT_PUBLIC_GATEHUB_ENV: ${NEXT_PUBLIC_GATEHUB_ENV:-sandbox} + NEXT_PUBLIC_THEME: ${NEXT_PUBLIC_THEME:-light} + NEXT_PUBLIC_FEATURES_ENABLED: ${NEXT_PUBLIC_FEATURES_ENABLED:-false} + restart: always + networks: + - testnet + ports: + - '4003:4003' + + # Rafiki + rafiki-auth: + container_name: rafiki-auth-local + image: ghcr.io/interledger/rafiki-auth:v2.2.0-beta + restart: always + networks: + - testnet + ports: + - '3006:3006' + - '3008:3008' + environment: + AUTH_PORT: ${RAFIKI_AUTH_PORT:-3006} + INTROSPECTION_PORT: ${RAFIKI_AUTH_INTROSPECTION_PORT:-3007} + ADMIN_PORT: ${RAFIKI_AUTH_ADMIN_PORT:-3008} + NODE_ENV: ${NODE_ENV:-development} + AUTH_SERVER_URL: ${RAFIKI_AUTH_SERVER_URL:-http://rafiki-auth:3006} + AUTH_DATABASE_URL: ${RAFIKI_AUTH_DATABASE_URL:-postgresql://rafiki_auth:rafiki_auth@postgres-local/rafiki_auth} + IDENTITY_SERVER_URL: ${RAFIKI_AUTH_IDENTITY_SERVER_URL:-http://wallet-frontend:4003/grant-interactions} + IDENTITY_SERVER_SECRET: ${AUTH_IDENTITY_SERVER_SECRET:-auth-secret-key-12345} + COOKIE_KEY: ${AUTH_COOKIE_KEY:-8fd398393c47dd27a3167d9c081c094f} + INTERACTION_COOKIE_SAME_SITE: ${AUTH_INTERACTION_COOKIE_SAME_SITE:-lax} + WAIT_SECONDS: ${RAFIKI_AUTH_WAIT_SECONDS:-1} + REDIS_URL: ${REDIS_URL:-redis://redis:6379/0} + OPERATOR_TENANT_ID: ${OPERATOR_TENANT_ID:-f829c064-762a-4430-ac5d-7af5df198551} + ADMIN_API_SECRET: ${ADMIN_API_SECRET:-secret-key} + ADMIN_SIGNATURE_VERSION: ${ADMIN_SIGNATURE_VERSION:-1} + depends_on: + - postgres + + rafiki-backend: + container_name: rafiki-backend-local + image: ghcr.io/interledger/rafiki-backend:v2.2.0-beta + restart: always + privileged: true + volumes: + - ../temp/:/workspace/temp/ + ports: + - '3010:80' + - '3011:3001' + - '3005:3005' + - '3002:3002' + networks: + - testnet + environment: + NODE_ENV: ${NODE_ENV:-development} + LOG_LEVEL: ${LOG_LEVEL:-debug} + ADMIN_PORT: ${RAFIKI_BACKEND_ADMIN_PORT:-3001} + CONNECTOR_PORT: ${RAFIKI_BACKEND_CONNECTOR_PORT:-3002} + OPEN_PAYMENTS_PORT: ${RAFIKI_BACKEND_OPEN_PAYMENTS_PORT:-80} + DATABASE_URL: ${RAFIKI_BACKEND_DATABASE_URL:-postgresql://rafiki_backend:rafiki_backend@postgres-local/rafiki_backend} + USE_TIGERBEETLE: ${RAFIKI_BACKEND_USE_TIGERBEETLE:-false} + NONCE_REDIS_KEY: ${RAFIKI_BACKEND_NONCE_REDIS_KEY:-test} + AUTH_SERVER_GRANT_URL: ${RAFIKI_BACKEND_AUTH_SERVER_GRANT_URL:-http://rafiki-auth:3006} + AUTH_SERVER_INTROSPECTION_URL: ${RAFIKI_BACKEND_AUTH_SERVER_INTROSPECTION_URL:-http://rafiki-auth:3007} + ILP_ADDRESS: ${RAFIKI_BACKEND_ILP_ADDRESS:-test.net} + ILP_CONNECTOR_URL: ${RAFIKI_BACKEND_ILP_CONNECTOR_URL:-http://127.0.0.1:3002} + STREAM_SECRET: ${RAFIKI_BACKEND_STREAM_SECRET:-BjPXtnd00G2mRQwP/8ZpwyZASOch5sUXT5o0iR5b5wU=} + ADMIN_KEY: ${RAFIKI_BACKEND_ADMIN_KEY:-admin} + OPEN_PAYMENTS_URL: ${RAFIKI_BACKEND_OPEN_PAYMENTS_URL:-https://rafiki-backend} + REDIS_URL: ${REDIS_URL:-redis://redis:6379/0} + WALLET_ADDRESS_URL: ${RAFIKI_BACKEND_WALLET_ADDRESS_URL:-https://rafiki-backend/.well-known/pay} + WEBHOOK_URL: ${RAFIKI_BACKEND_WEBHOOK_URL:-http://wallet-backend:3003/webhooks} + WEBHOOK_TIMEOUT: ${RAFIKI_BACKEND_WEBHOOK_TIMEOUT:-60000} + SIGNATURE_SECRET: ${RAFIKI_SIGNATURE_SECRET:-327132b5-99e9-4eb8-8a25-2b7d7738ece1} + EXCHANGE_RATES_URL: ${RAFIKI_BACKEND_EXCHANGE_RATES_URL:-http://wallet-backend:3003/rates} + ENABLE_AUTO_PEERING: ${RAFIKI_BACKEND_ENABLE_AUTO_PEERING:-true} + AUTO_PEERING_SERVER_PORT: ${RAFIKI_BACKEND_AUTO_PEERING_SERVER_PORT:-3005} + INSTANCE_NAME: ${RAFIKI_BACKEND_INSTANCE_NAME:-Testnet Wallet} + SLIPPAGE: ${RAFIKI_BACKEND_SLIPPAGE:-0.01} + KEY_ID: ${RAFIKI_BACKEND_KEY_ID:-rafiki} + WALLET_ADDRESS_REDIRECT_HTML_PAGE: ${RAFIKI_BACKEND_WALLET_ADDRESS_REDIRECT_HTML_PAGE:-http://localhost:4003/account?walletAddress=%ewa} + OPERATOR_TENANT_ID: ${OPERATOR_TENANT_ID:-f829c064-762a-4430-ac5d-7af5df198551} + ADMIN_API_SECRET: ${ADMIN_API_SECRET:-secret-key} + ADMIN_SIGNATURE_VERSION: ${ADMIN_SIGNATURE_VERSION:-1} + AUTH_SERVICE_API_URL: ${RAFIKI_BACKEND_AUTH_SERVICE_API_URL:-http://rafiki-auth:3011} + CARD_SERVICE_URL: ${RAFIKI_BACKEND_CARD_SERVICE_URL:-http://rafiki-card-service:3007} + CARD_WEBHOOK_SERVICE_URL: ${RAFIKI_BACKEND_CARD_WEBHOOK_SERVICE_URL:-http://rafiki-card-service:3007/webhook} + POS_SERVICE_URL: ${RAFIKI_BACKEND_POS_SERVICE_URL:-http://rafiki-pos-service:3014} + POS_WEBHOOK_SERVICE_URL: ${RAFIKI_BACKEND_POS_WEBHOOK_SERVICE_URL:-http://rafiki-pos-service:3014/webhook} + depends_on: + - postgres + - redis + + rafiki-frontend: + container_name: rafiki-frontend-local + image: ghcr.io/interledger/rafiki-frontend:v2.2.0-beta + depends_on: + - rafiki-backend + restart: always + privileged: true + ports: + - '3012:3012' + networks: + - testnet + environment: + PORT: ${RAFIKI_FRONTEND_PORT:-3012} + GRAPHQL_URL: ${GRAPHQL_URL:-http://rafiki-backend:3001/graphql} + OPEN_PAYMENTS_URL: ${RAFIKI_FRONTEND_OPEN_PAYMENTS_URL:-https://rafiki-backend/} + ENABLE_INSECURE_MESSAGE_COOKIE: ${RAFIKI_FRONTEND_ENABLE_INSECURE_MESSAGE_COOKIE:-true} + AUTH_ENABLED: ${RAFIKI_FRONTEND_AUTH_ENABLED:-false} + SIGNATURE_VERSION: ${RAFIKI_FRONTEND_SIGNATURE_VERSION:-1} + + rafiki-card-service: + container_name: rafiki-card-service-local + image: ghcr.io/interledger/rafiki-card-service:v2.2.0-beta + restart: always + privileged: true + networks: + - testnet + ports: + - '3007:3007' + environment: + NODE_ENV: ${NODE_ENV:-development} + LOG_LEVEL: ${LOG_LEVEL:-debug} + CARD_SERVICE_PORT: ${RAFIKI_CARD_SERVICE_PORT:-3007} + REDIS_URL: ${REDIS_URL:-redis://redis:6379/0} + GRAPHQL_URL: ${GRAPHQL_URL:-http://rafiki-backend:3001/graphql} + TENANT_ID: ${OPERATOR_TENANT_ID:-f829c064-762a-4430-ac5d-7af5df198551} + TENANT_SECRET: ${ADMIN_API_SECRET:-secret-key} + TENANT_SIGNATURE_VERSION: ${RAFIKI_CARD_SERVICE_TENANT_SIGNATURE_VERSION:-1} + + redis: + container_name: redis + image: 'redis:7-alpine' + restart: unless-stopped + networks: + - testnet + ports: + - '6379:6379' + + mailslurper: + container_name: mailslurper-local + image: oryd/mailslurper:latest-smtps + ports: + - '4436:4436' + - '4437:4437' + networks: + - testnet + +networks: + testnet: + driver: bridge + ipam: + config: + - subnet: 10.5.0.0/24 + gateway: 10.5.0.1 + +volumes: + pg-data: diff --git a/docker/dbinit.sql b/local/init/dbinit.sql similarity index 100% rename from docker/dbinit.sql rename to local/init/dbinit.sql diff --git a/local/scripts/rafiki-setup.js b/local/scripts/rafiki-setup.js new file mode 100644 index 000000000..053ce48ca --- /dev/null +++ b/local/scripts/rafiki-setup.js @@ -0,0 +1,379 @@ +#!/usr/bin/env node +/** + * Configure Rafiki (local docker stack) with a tenant + assets. + * - Reads values from docker/local/.env when present (process.env takes priority) + * - Creates the operator tenant (idpConsentUrl + idpSecret) + * - Ensures assets exist for the Testnet wallet + * + * Run after `docker compose up -d` from docker/local: + * node rafiki-setup.js + */ + +const fs = require('fs') +const path = require('path') +const crypto = require('crypto') + +// ---- helpers --------------------------------------------------------------- +function loadDotEnv(envPath) { + const result = {} + if (!fs.existsSync(envPath)) return result + const lines = fs.readFileSync(envPath, 'utf8').split(/\r?\n/) + for (const line of lines) { + if (!line || line.trim().startsWith('#')) continue + const idx = line.indexOf('=') + if (idx === -1) continue + const key = line.slice(0, idx).trim() + const value = line.slice(idx + 1).trim() + result[key] = value + } + return result +} + +function canonicalize(value) { + if (value === null || typeof value !== 'object') return value + if (Array.isArray(value)) return value.map(canonicalize) + const sortedKeys = Object.keys(value).sort() + const obj = {} + for (const key of sortedKeys) { + obj[key] = canonicalize(value[key]) + } + return obj +} + +function canonicalizeAndStringify(value) { + return JSON.stringify(canonicalize(value)) +} + +function buildEnv() { + const envPath = path.join(__dirname, '.env') + const fileEnv = loadDotEnv(envPath) + const get = (key, fallback) => + process.env[key] ?? fileEnv[key] ?? fallback + + return { + GRAPHQL_ENDPOINT: get('GRAPHQL_ENDPOINT', 'http://localhost:3011/graphql'), + ADMIN_API_SECRET: get('ADMIN_API_SECRET', 'secret-key'), + ADMIN_SIGNATURE_VERSION: get('ADMIN_SIGNATURE_VERSION', '1'), + OPERATOR_TENANT_ID: get( + 'OPERATOR_TENANT_ID', + 'f829c064-762a-4430-ac5d-7af5df198551' + ), + AUTH_IDENTITY_SERVER_SECRET: get( + 'AUTH_IDENTITY_SERVER_SECRET', + 'auth-secret-key-12345' + ), + IDP_CONSENT_URL: get( + 'IDP_CONSENT_URL', + 'http://wallet-frontend:4003/grant-interactions' + ) + } +} + +function signRequest({ query, variables, operationName }, env, timestamp) { + const payload = `${timestamp}.${canonicalizeAndStringify({ + variables: variables ?? {}, + operationName, + query + })}` + const hmac = crypto.createHmac('sha256', env.ADMIN_API_SECRET) + hmac.update(payload) + const digest = hmac.digest('hex') + return `t=${timestamp}, v${env.ADMIN_SIGNATURE_VERSION}=${digest}` +} + +async function graphqlRequest({ query, variables, operationName }, env) { + const timestamp = Date.now() + const signature = signRequest({ query, variables, operationName }, env, timestamp) + const body = JSON.stringify({ query, variables, operationName }) + + const response = await fetch(env.GRAPHQL_ENDPOINT, { + method: 'POST', + headers: { + 'content-type': 'application/json', + signature, + 'tenant-id': env.OPERATOR_TENANT_ID + }, + body + }) + + const data = await response.json() + if (data.errors && data.errors.length) { + const message = data.errors.map((e) => e.message).join('\n') + throw new Error(message) + } + return data.data +} + +// ---- operations ----------------------------------------------------------- +const getTenantQuery = /* GraphQL */ ` + query GetTenant($id: String!) { + tenant(id: $id) { + id + publicName + idpConsentUrl + idpSecret + } + } +` + +const createTenantMutation = /* GraphQL */ ` + mutation CreateTenant($input: CreateTenantInput!) { + createTenant(input: $input) { + tenant { + id + publicName + idpConsentUrl + idpSecret + } + } + } +` + +const updateTenantMutation = /* GraphQL */ ` + mutation UpdateTenant($input: UpdateTenantInput!) { + updateTenant(input: $input) { + tenant { + id + publicName + idpConsentUrl + idpSecret + } + } + } +` + +const listAssetsQuery = /* GraphQL */ ` + query Assets($first: Int = 100) { + assets(first: $first) { + edges { + node { + id + code + scale + } + } + } + } +` + +const createAssetMutation = /* GraphQL */ ` + mutation CreateAsset($input: CreateAssetInput!) { + createAsset(input: $input) { + asset { + id + code + scale + } + } + } +` + +const getAssetByCodeAndScaleQuery = /* GraphQL */ ` + query AssetByCodeAndScale($code: String!, $scale: UInt8!) { + assetByCodeAndScale(code: $code, scale: $scale) { + id + code + scale + } + } +` + +const depositAssetLiquidityMutation = /* GraphQL */ ` + mutation DepositAssetLiquidity($input: DepositAssetLiquidityInput!) { + depositAssetLiquidity(input: $input) { + success + } + } +` + +const assetsToEnsure = [ + { code: 'USD', scale: 2 }, + { code: 'EUR', scale: 2 }, + { code: 'GBP', scale: 2 }, + { code: 'ZAR', scale: 2 }, + { code: 'MXN', scale: 2 }, + { code: 'SGD', scale: 2 }, + { code: 'CAD', scale: 2 }, + { code: 'EGG', scale: 2 }, + { code: 'PEB', scale: 2 }, + { code: 'PKR', scale: 2 } +] + +async function ensureTenant(env) { + try { + const existing = await graphqlRequest( + { query: getTenantQuery, variables: { id: env.OPERATOR_TENANT_ID } }, + env + ) + if (existing?.tenant) { + console.log( + `Tenant already present: ${existing.tenant.id} (consent URL ${existing.tenant.idpConsentUrl})` + ) + if (!existing.tenant.idpConsentUrl || !existing.tenant.idpSecret) { + console.log('Updating tenant idp fields...') + await graphqlRequest( + { + query: updateTenantMutation, + variables: { + input: { + id: env.OPERATOR_TENANT_ID, + idpConsentUrl: env.IDP_CONSENT_URL, + idpSecret: env.AUTH_IDENTITY_SERVER_SECRET + } + } + }, + env + ) + console.log('Tenant idp fields updated') + } + return + } + } catch (err) { + // continue and try to create + console.log('Tenant lookup failed, attempting to create...', err.message) + } + + console.log('Creating tenant...') + try { + const created = await graphqlRequest( + { + query: createTenantMutation, + variables: { + input: { + id: env.OPERATOR_TENANT_ID, + publicName: 'Testnet Wallet', + apiSecret: env.ADMIN_API_SECRET, + idpSecret: env.AUTH_IDENTITY_SERVER_SECRET, + idpConsentUrl: env.IDP_CONSENT_URL + } + } + }, + env + ) + console.log('Tenant created:', created.createTenant.tenant) + } catch (err) { + if ( + typeof err.message === 'string' && + err.message.toLowerCase().includes('duplicate') + ) { + console.log('Tenant already exists (duplicate key), continuing...') + return + } + throw err + } +} + +async function ensureAssets(env) { + let current = { assets: { edges: [] } } + try { + current = await graphqlRequest( + { query: listAssetsQuery, variables: { first: 200 } }, + env + ) + } catch (err) { + console.log('Asset list failed, continuing to create assets...', err.message) + } + + const existingCodes = new Set( + (current?.assets?.edges ?? []).map((e) => e.node.code) + ) + + for (const asset of assetsToEnsure) { + if (existingCodes.has(asset.code)) { + console.log(`Asset ${asset.code} already exists`) + continue + } + console.log(`Creating asset ${asset.code}...`) + try { + await graphqlRequest( + { + query: createAssetMutation, + variables: { + input: { + code: asset.code, + scale: asset.scale + } + } + }, + env + ) + console.log(`Asset ${asset.code} created`) + } catch (err) { + const msg = (err.message || '').toLowerCase() + if (msg.includes('already exists') || msg.includes('duplicate')) { + console.log(`Asset ${asset.code} already exists (api), continuing...`) + continue + } + throw err + } + } +} + +// Deposit liquidity for all assets (100000 units per asset, converted to minor units by scale) +async function ensureLiquidity(env) { + console.log('Ensuring asset liquidity...') + + for (const asset of assetsToEnsure) { + let node + try { + const res = await graphqlRequest( + { + query: getAssetByCodeAndScaleQuery, + variables: { code: asset.code, scale: asset.scale } + }, + env + ) + node = res?.assetByCodeAndScale + } catch (err) { + console.log(`Lookup failed for ${asset.code}:`, err.message) + continue + } + + if (!node?.id) { + console.log(`Skipping liquidity for ${asset.code}: asset id not found`) + continue + } + + // Amount in minor units: 100000 * 10^scale + const amount = BigInt(100000) * BigInt(10) ** BigInt(node.scale) + + console.log(`Depositing liquidity for ${asset.code}: ${amount.toString()} (scale ${node.scale})`) + try { + const res = await graphqlRequest( + { + query: depositAssetLiquidityMutation, + variables: { + input: { + id: crypto.randomUUID(), + assetId: node.id, + amount: amount.toString(), + idempotencyKey: crypto.randomUUID() + } + } + }, + env + ) + + if (!res?.depositAssetLiquidity?.success) { + console.log(`Liquidity deposit failed for ${asset.code}`) + } else { + console.log(`Liquidity deposited for ${asset.code}`) + } + } catch (err) { + console.log(`Liquidity deposit error for ${asset.code}:`, err.message) + } + } +} + +// ---- main ----------------------------------------------------------------- +;(async function main() { + const env = buildEnv() + console.log('Rafiki admin endpoint:', env.GRAPHQL_ENDPOINT) + await ensureTenant(env) + await ensureAssets(env) + await ensureLiquidity(env) + console.log('✅ Rafiki configuration complete') +})().catch((err) => { + console.error('Setup failed:', err.message) + process.exit(1) +}) diff --git a/packages/wallet/backend/src/config/env.ts b/packages/wallet/backend/src/config/env.ts index 92b0f60d3..62dc86140 100644 --- a/packages/wallet/backend/src/config/env.ts +++ b/packages/wallet/backend/src/config/env.ts @@ -13,6 +13,7 @@ const envSchema = z.object({ .default('testnet.cookie.password.super.secret.ilp'), // min. 32 chars COOKIE_TTL: z.coerce.number().default(2630000), // 1 month GATEHUB_ENV: z.enum(['production', 'sandbox']).default('sandbox'), + GATEHUB_API_BASE_URL: z.string().optional(), GATEHUB_ACCESS_KEY: z.string().default('GATEHUB_ACCESS_KEY'), GATEHUB_SECRET_KEY: z.string().default('GATEHUB_SECRET_KEY'), GATEHUB_SEPA_ACCESS_KEY: z.string().optional(), diff --git a/packages/wallet/backend/src/gatehub/client.ts b/packages/wallet/backend/src/gatehub/client.ts index dc5abd46b..92a75055a 100644 --- a/packages/wallet/backend/src/gatehub/client.ts +++ b/packages/wallet/backend/src/gatehub/client.ts @@ -97,18 +97,32 @@ export class GateHubClient { } get apiUrl() { + // If GATEHUB_API_BASE_URL is set (e.g., for local development with mockgatehub), + // use it instead of constructing the URL from mainUrl + if (this.env.GATEHUB_API_BASE_URL) { + return this.env.GATEHUB_API_BASE_URL + } return `https://api.${this.mainUrl}` } get rampUrl() { + if (this.env.GATEHUB_API_BASE_URL) { + return this.apiUrl + } return `https://managed-ramp.${this.mainUrl}` } get exchangeUrl() { + if (this.env.GATEHUB_API_BASE_URL) { + return this.apiUrl + } return `https://exchange.${this.mainUrl}` } get onboardingUrl() { + if (this.env.GATEHUB_API_BASE_URL) { + return this.apiUrl + } return `https://onboarding.${this.mainUrl}` } diff --git a/packages/wallet/backend/src/gatehub/service.ts b/packages/wallet/backend/src/gatehub/service.ts index 976c51745..bf51357c4 100644 --- a/packages/wallet/backend/src/gatehub/service.ts +++ b/packages/wallet/backend/src/gatehub/service.ts @@ -260,6 +260,15 @@ export class GateHubService { } let customerId + // Check if customer already exists to prevent race condition between + // direct addUserToGateway call and webhook handler + if (user.customerId) { + this.logger.debug( + `Customer already exists for user ${userId}, skipping customer creation` + ) + return { isApproved, customerId: user.customerId } + } + if ( this.env.NODE_ENV === 'development' && this.env.GATEHUB_ENV === 'sandbox' @@ -325,6 +334,21 @@ export class GateHubService { firstName: string, lastName: string ): Promise { + // Check if customer setup already in progress or completed + // to prevent race condition between concurrent calls + const existingAccount = await Account.query().findOne({ + userId, + assetCode: 'EUR' + }) + + if (existingAccount) { + this.logger.warn( + `EUR account already exists for user ${userId}, skipping sandbox customer creation` + ) + const user = await User.query().findById(userId) + return user!.customerId || '' + } + const { account, walletAddress } = await this.createDefaultAccountAndWAForManagedUser(userId, true) diff --git a/packages/wallet/backend/src/middleware/withSession.ts b/packages/wallet/backend/src/middleware/withSession.ts index 5e1eaf946..fe97dca9d 100644 --- a/packages/wallet/backend/src/middleware/withSession.ts +++ b/packages/wallet/backend/src/middleware/withSession.ts @@ -6,10 +6,15 @@ import { getIronSession } from 'iron-session' -let domain = env.RAFIKI_MONEY_FRONTEND_HOST - +// Determine cookie domain. Avoid setting Domain=localhost, browsers ignore it. +let domain: string | undefined = undefined if (env.NODE_ENV === 'production' && env.GATEHUB_ENV === 'production') { domain = 'interledger.cards' +} else if ( + env.RAFIKI_MONEY_FRONTEND_HOST && + env.RAFIKI_MONEY_FRONTEND_HOST !== 'localhost' +) { + domain = env.RAFIKI_MONEY_FRONTEND_HOST } export const SESSION_OPTIONS: SessionOptions = { diff --git a/packages/wallet/frontend/Dockerfile.dev b/packages/wallet/frontend/Dockerfile.dev index 4a13dcab2..2e5f64a44 100644 --- a/packages/wallet/frontend/Dockerfile.dev +++ b/packages/wallet/frontend/Dockerfile.dev @@ -23,4 +23,20 @@ ADD . ./ # Install packages from virtual store RUN pnpm install -r --offline +# Accept build arguments for Next.js public environment variables +ARG NEXT_PUBLIC_BACKEND_URL +ARG NEXT_PUBLIC_AUTH_HOST +ARG NEXT_PUBLIC_OPEN_PAYMENTS_HOST +ARG NEXT_PUBLIC_GATEHUB_ENV +ARG NEXT_PUBLIC_THEME +ARG NEXT_PUBLIC_FEATURES_ENABLED + +# Make them available as environment variables during build +ENV NEXT_PUBLIC_BACKEND_URL=$NEXT_PUBLIC_BACKEND_URL +ENV NEXT_PUBLIC_AUTH_HOST=$NEXT_PUBLIC_AUTH_HOST +ENV NEXT_PUBLIC_OPEN_PAYMENTS_HOST=$NEXT_PUBLIC_OPEN_PAYMENTS_HOST +ENV NEXT_PUBLIC_GATEHUB_ENV=$NEXT_PUBLIC_GATEHUB_ENV +ENV NEXT_PUBLIC_THEME=$NEXT_PUBLIC_THEME +ENV NEXT_PUBLIC_FEATURES_ENABLED=$NEXT_PUBLIC_FEATURES_ENABLED + CMD ["pnpm", "wallet:frontend", "dev"] diff --git a/packages/wallet/frontend/next.config.js b/packages/wallet/frontend/next.config.js index 9e98bb8b9..0d43b0312 100644 --- a/packages/wallet/frontend/next.config.js +++ b/packages/wallet/frontend/next.config.js @@ -2,13 +2,18 @@ const withBundleAnalyzer = require('@next/bundle-analyzer')({ enabled: process.env.ANALYZE === 'true' }) -let NEXT_PUBLIC_FEATURES_ENABLED = 'true' +// Default to env override; fall back to previous production/sandbox rule, then to 'true' +let NEXT_PUBLIC_FEATURES_ENABLED = process.env.NEXT_PUBLIC_FEATURES_ENABLED -if ( - process.env.NODE_ENV === 'production' && - process.env.NEXT_PUBLIC_GATEHUB_ENV === 'sandbox' -) { - NEXT_PUBLIC_FEATURES_ENABLED = 'false' +if (!NEXT_PUBLIC_FEATURES_ENABLED) { + if ( + process.env.NODE_ENV === 'production' && + process.env.NEXT_PUBLIC_GATEHUB_ENV === 'sandbox' + ) { + NEXT_PUBLIC_FEATURES_ENABLED = 'false' + } else { + NEXT_PUBLIC_FEATURES_ENABLED = 'true' + } } /** @type {import('next').NextConfig} */ @@ -18,6 +23,8 @@ const nextConfig = { env: { NEXT_PUBLIC_BACKEND_URL: process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:3003', + // Internal URL for server-side (middleware) to reach backend in Docker + BACKEND_INTERNAL_URL: process.env.BACKEND_URL || 'http://wallet-backend:3003', NEXT_PUBLIC_OPEN_PAYMENTS_HOST: process.env.NEXT_PUBLIC_OPEN_PAYMENTS_HOST || '$rafiki-backend/', NEXT_PUBLIC_AUTH_HOST: diff --git a/packages/wallet/frontend/src/lib/httpClient.ts b/packages/wallet/frontend/src/lib/httpClient.ts index 8b2a1ad9d..957dde238 100644 --- a/packages/wallet/frontend/src/lib/httpClient.ts +++ b/packages/wallet/frontend/src/lib/httpClient.ts @@ -14,8 +14,14 @@ export type ErrorResponse = { errors?: T extends FieldValues ? Record, string> : undefined } +// Use internal backend URL when running on the server (SSR/middleware) +const isServer = typeof window === 'undefined' +const baseUrl = isServer + ? process.env.BACKEND_INTERNAL_URL || 'http://wallet-backend:3003' + : process.env.NEXT_PUBLIC_BACKEND_URL + export const httpClient = ky.extend({ - prefixUrl: process.env.NEXT_PUBLIC_BACKEND_URL, + prefixUrl: baseUrl, credentials: 'include', retry: 0, hooks: { diff --git a/packages/wallet/frontend/src/middleware.ts b/packages/wallet/frontend/src/middleware.ts index db8f681c0..1ed16a166 100644 --- a/packages/wallet/frontend/src/middleware.ts +++ b/packages/wallet/frontend/src/middleware.ts @@ -1,6 +1,7 @@ import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' -import { userService } from './lib/api/user' +// Do not use the browser httpClient here; middleware runs in the container. +// Call backend using the internal Docker hostname to validate the session. const isPublicPath = (path: string) => { return publicPaths.find((x) => @@ -15,9 +16,22 @@ export async function middleware(req: NextRequest) { const isPublic = isPublicPath(req.nextUrl.pathname) const cookieName = process.env.COOKIE_NAME || 'testnet.cookie' - const response = await userService.me( - `${cookieName}=${req.cookies.get(cookieName)?.value}` - ) + const cookieVal = req.cookies.get(cookieName)?.value + + // Build internal backend URL for middleware + const backendUrl = process.env.BACKEND_INTERNAL_URL || 'http://wallet-backend:3003' + let response: { success: boolean; result?: any; message?: string } = { + success: false + } + try { + const meRes = await fetch(`${backendUrl}/me`, { + headers: cookieVal ? { Cookie: `${cookieName}=${cookieVal}` } : {} + }) + const json = await meRes.json() + response = json + } catch (e) { + // Ignore connectivity errors; fallback logic below handles unauthenticated state + } // Success TRUE - the user is logged in if (response.success && response.result) { @@ -40,7 +54,8 @@ export async function middleware(req: NextRequest) { } if (isPublic) { - return NextResponse.redirect(new URL(callbackUrl ?? '/', req.url)) + const dest = callbackUrl ?? '/' + return NextResponse.redirect(new URL(dest, req.url)) } } else { // If the user is not logged in and tries to access a private resource, diff --git a/packages/wallet/frontend/src/utils/helpers.ts b/packages/wallet/frontend/src/utils/helpers.ts index fb8f20f2d..e3436f0da 100644 --- a/packages/wallet/frontend/src/utils/helpers.ts +++ b/packages/wallet/frontend/src/utils/helpers.ts @@ -44,8 +44,7 @@ export const formatAmount = (args: FormatAmountArgs): FormattedAmount => { const scaledValue = Number(`${value}e-${assetScale}`) const flooredValue = - Math.floor(Math.round(scaledValue * 10 ** displayScale)) / - 10 ** displayScale + Math.floor(scaledValue * 10 ** displayScale) / 10 ** displayScale const symbol = getCurrencySymbol(assetCode) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c44467fae..29b1f0f88 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -60,6 +60,24 @@ importers: specifier: ^5.9.3 version: 5.9.3 + e2e: + devDependencies: + '@playwright/test': + specifier: ^1.56.0 + version: 1.58.2 + '@types/node': + specifier: ^20.17.30 + version: 20.17.30 + dotenv: + specifier: ^17.2.3 + version: 17.3.1 + playwright-bdd: + specifier: ^8.0.0 + version: 8.5.0(@playwright/test@1.58.2) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + packages/boutique/backend: dependencies: '@boutique/shared': @@ -453,7 +471,7 @@ importers: version: 1.11.0 next: specifier: 14.2.32 - version: 14.2.32(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 14.2.32(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-qrcode: specifier: ^2.5.1 version: 2.5.1(react@18.3.1) @@ -900,10 +918,51 @@ packages: '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@colors/colors@1.5.0': + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} + '@colors/colors@1.6.0': resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} engines: {node: '>=0.1.90'} + '@cucumber/cucumber-expressions@18.0.1': + resolution: {integrity: sha512-NSid6bI+7UlgMywl5octojY5NXnxR9uq+JisjOrO52VbFsQM6gTWuQFE8syI10KnIBEdPzuEUSVEeZ0VFzRnZA==} + + '@cucumber/gherkin-utils@9.2.0': + resolution: {integrity: sha512-3nmRbG1bUAZP3fAaUBNmqWO0z0OSkykZZotfLjyhc8KWwDSOrOmMJlBTd474lpA8EWh4JFLAX3iXgynBqBvKzw==} + hasBin: true + + '@cucumber/gherkin@31.0.0': + resolution: {integrity: sha512-wlZfdPif7JpBWJdqvHk1Mkr21L5vl4EfxVUOS4JinWGf3FLRV6IKUekBv5bb5VX79fkDcfDvESzcQ8WQc07Wgw==} + + '@cucumber/gherkin@32.2.0': + resolution: {integrity: sha512-X8xuVhSIqlUjxSRifRJ7t0TycVWyX58fygJH3wDNmHINLg9sYEkvQT0SO2G5YlRZnYc11TIFr4YPenscvdlBIw==} + + '@cucumber/html-formatter@21.15.1': + resolution: {integrity: sha512-tjxEpP161sQ7xc3VREc94v1ymwIckR3ySViy7lTvfi1jUpyqy2Hd/p4oE3YT1kQ9fFDvUflPwu5ugK5mA7BQLA==} + peerDependencies: + '@cucumber/messages': '>=18' + + '@cucumber/junit-xml-formatter@0.7.1': + resolution: {integrity: sha512-AzhX+xFE/3zfoYeqkT7DNq68wAQfBcx4Dk9qS/ocXM2v5tBv6eFQ+w8zaSfsktCjYzu4oYRH/jh4USD1CYHfaQ==} + peerDependencies: + '@cucumber/messages': '*' + + '@cucumber/messages@26.0.1': + resolution: {integrity: sha512-DIxSg+ZGariumO+Lq6bn4kOUIUET83A4umrnWmidjGFl8XxkBieUZtsmNbLYgH/gnsmP07EfxxdTr0hOchV1Sg==} + + '@cucumber/messages@27.2.0': + resolution: {integrity: sha512-f2o/HqKHgsqzFLdq6fAhfG1FNOQPdBdyMGpKwhb7hZqg0yZtx9BVqkTyuoNk83Fcvk3wjMVfouFXXHNEk4nddA==} + + '@cucumber/query@13.6.0': + resolution: {integrity: sha512-tiDneuD5MoWsJ9VKPBmQok31mSX9Ybl+U4wqDoXeZgsXHDURqzM3rnpWVV3bC34y9W6vuFxrlwF/m7HdOxwqRw==} + peerDependencies: + '@cucumber/messages': '*' + + '@cucumber/tag-expressions@6.2.0': + resolution: {integrity: sha512-KIF0eLcafHbWOuSDWFw0lMmgJOLdDRWjEL1kfXEWrqHmx2119HxVAr35WuEd9z542d3Yyg+XNqSr+81rIKqEdg==} + '@dabh/diagnostics@2.0.8': resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==} @@ -1822,6 +1881,11 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@playwright/test@1.58.2': + resolution: {integrity: sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==} + engines: {node: '>=18'} + hasBin: true + '@polka/url@1.0.0-next.25': resolution: {integrity: sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==} @@ -2501,6 +2565,10 @@ packages: '@tanstack/virtual-core@3.13.9': resolution: {integrity: sha512-3jztt0jpaoJO5TARe2WIHC1UQC3VMLAFUW5mmMo0yrkwtDB2AQP0+sh10BVUpWrnvHjSLvzFizydtEGLCJKFoQ==} + '@teppeis/multimaps@3.0.0': + resolution: {integrity: sha512-ID7fosbc50TbT0MK0EG12O+gAP3W3Aa/Pz4DaTtQtEvlc9Odaqi0de+xuZ7Li2GtK4HzEX7IuRWS/JmZLksR3Q==} + engines: {node: '>=14'} + '@tootallnate/once@2.0.0': resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} @@ -3193,6 +3261,9 @@ packages: cjs-module-lexer@1.3.1: resolution: {integrity: sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==} + class-transformer@0.5.1: + resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} + class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} @@ -3208,6 +3279,10 @@ packages: resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} engines: {node: '>=6'} + cli-table3@0.6.5: + resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} + engines: {node: 10.* || >= 12.*} + cli-truncate@2.1.0: resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} engines: {node: '>=8'} @@ -3292,6 +3367,10 @@ packages: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} + commander@13.1.0: + resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} + engines: {node: '>=18'} + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -3612,6 +3691,10 @@ packages: resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} engines: {node: '>=12'} + dotenv@17.3.1: + resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==} + engines: {node: '>=12'} + drange@1.1.1: resolution: {integrity: sha512-pYxfDYpued//QpnLIm4Avk7rsNtAtQkUES2cwAYSvD/wd2pKD71gN2Ebj3e7klzXwjocvE8c5vx/1fxwpqmSxA==} engines: {node: '>=4'} @@ -4028,6 +4111,11 @@ packages: fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -4921,6 +5009,10 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + luxon@3.7.2: + resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==} + engines: {node: '>=12'} + make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} @@ -4981,10 +5073,18 @@ packages: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} @@ -5424,6 +5524,23 @@ packages: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} + playwright-bdd@8.5.0: + resolution: {integrity: sha512-w/Bd5C1d6Xe5e1oREsbt2rDN0/Mcp+J2OjQwSl49/mroa2K6UZU33P7v91pLQPl1otDitMwJOaOeJsqaj7WU7w==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@playwright/test': '>=1.44' + + playwright-core@1.58.2: + resolution: {integrity: sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.58.2: + resolution: {integrity: sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==} + engines: {node: '>=18'} + hasBin: true + plimit-lit@1.6.1: resolution: {integrity: sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==} engines: {node: '>=12'} @@ -5808,6 +5925,9 @@ packages: resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} engines: {node: '>=4'} + reflect-metadata@0.2.2: + resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + reflect.getprototypeof@1.0.9: resolution: {integrity: sha512-r0Ay04Snci87djAsI4U+WNRcSw5S4pOH7qFjd/veA5gC7TbqESR3tcj28ia95L/fYUDw11JKP7uqUKUAfVvV5Q==} engines: {node: '>= 0.4'} @@ -5815,6 +5935,13 @@ packages: regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + regexp-match-indices@1.0.2: + resolution: {integrity: sha512-DwZuAkt8NF5mKwGGER1EGh2PRqyvhRhhLviH+R8y8dIuaQROlUfXjt4s9ZTXstIsSkptf06BSvwcEmmfheJJWQ==} + + regexp-tree@0.1.27: + resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} + hasBin: true + regexp.prototype.flags@1.5.3: resolution: {integrity: sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==} engines: {node: '>= 0.4'} @@ -6090,6 +6217,9 @@ packages: source-map-support@0.5.13: resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + source-map@0.5.7: resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} engines: {node: '>=0.10.0'} @@ -6595,6 +6725,10 @@ packages: resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} hasBin: true + uuid@11.0.5: + resolution: {integrity: sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==} + hasBin: true + uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true @@ -6773,6 +6907,10 @@ packages: utf-8-validate: optional: true + xmlbuilder@15.1.1: + resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==} + engines: {node: '>=8.0'} + xmlhttprequest-ssl@2.1.1: resolution: {integrity: sha512-ptjR8YSJIXoA3Mbv5po7RtSYHO6mZr8s7i5VGmEk7QY2pQWyT1o0N+W1gKbOyJPUCGXGnuw0wqe8f0L6Y0ny7g==} engines: {node: '>=0.4.0'} @@ -7274,8 +7412,65 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} + '@colors/colors@1.5.0': + optional: true + '@colors/colors@1.6.0': {} + '@cucumber/cucumber-expressions@18.0.1': + dependencies: + regexp-match-indices: 1.0.2 + + '@cucumber/gherkin-utils@9.2.0': + dependencies: + '@cucumber/gherkin': 31.0.0 + '@cucumber/messages': 27.2.0 + '@teppeis/multimaps': 3.0.0 + commander: 13.1.0 + source-map-support: 0.5.21 + + '@cucumber/gherkin@31.0.0': + dependencies: + '@cucumber/messages': 26.0.1 + + '@cucumber/gherkin@32.2.0': + dependencies: + '@cucumber/messages': 27.2.0 + + '@cucumber/html-formatter@21.15.1(@cucumber/messages@27.2.0)': + dependencies: + '@cucumber/messages': 27.2.0 + + '@cucumber/junit-xml-formatter@0.7.1(@cucumber/messages@27.2.0)': + dependencies: + '@cucumber/messages': 27.2.0 + '@cucumber/query': 13.6.0(@cucumber/messages@27.2.0) + '@teppeis/multimaps': 3.0.0 + luxon: 3.7.2 + xmlbuilder: 15.1.1 + + '@cucumber/messages@26.0.1': + dependencies: + '@types/uuid': 10.0.0 + class-transformer: 0.5.1 + reflect-metadata: 0.2.2 + uuid: 10.0.0 + + '@cucumber/messages@27.2.0': + dependencies: + '@types/uuid': 10.0.0 + class-transformer: 0.5.1 + reflect-metadata: 0.2.2 + uuid: 11.0.5 + + '@cucumber/query@13.6.0(@cucumber/messages@27.2.0)': + dependencies: + '@cucumber/messages': 27.2.0 + '@teppeis/multimaps': 3.0.0 + lodash.sortby: 4.7.0 + + '@cucumber/tag-expressions@6.2.0': {} + '@dabh/diagnostics@2.0.8': dependencies: '@so-ric/colorspace': 1.1.6 @@ -8453,6 +8648,10 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@playwright/test@1.58.2': + dependencies: + playwright: 1.58.2 + '@polka/url@1.0.0-next.25': {} '@protobufjs/aspromise@1.1.2': {} @@ -9075,6 +9274,8 @@ snapshots: '@tanstack/virtual-core@3.13.9': {} + '@teppeis/multimaps@3.0.0': {} + '@tootallnate/once@2.0.0': {} '@types/babel__core@7.20.5': @@ -9955,6 +10156,8 @@ snapshots: cjs-module-lexer@1.3.1: {} + class-transformer@0.5.1: {} + class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 @@ -9967,6 +10170,12 @@ snapshots: cli-spinners@2.9.2: {} + cli-table3@0.6.5: + dependencies: + string-width: 4.2.3 + optionalDependencies: + '@colors/colors': 1.5.0 + cli-truncate@2.1.0: dependencies: slice-ansi: 3.0.0 @@ -10037,6 +10246,8 @@ snapshots: commander@10.0.1: {} + commander@13.1.0: {} + commander@4.1.1: {} commander@7.2.0: {} @@ -10337,6 +10548,8 @@ snapshots: dotenv@16.4.5: {} + dotenv@17.3.1: {} + drange@1.1.1: {} dset@3.1.3: {} @@ -10912,6 +11125,9 @@ snapshots: fs.realpath@1.0.0: {} + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -12099,6 +12315,8 @@ snapshots: dependencies: yallist: 3.1.1 + luxon@3.7.2: {} + make-dir@4.0.0: dependencies: semver: 7.7.2 @@ -12141,10 +12359,16 @@ snapshots: mime-db@1.52.0: {} + mime-db@1.54.0: {} + mime-types@2.1.35: dependencies: mime-db: 1.52.0 + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + mime@1.6.0: {} mimic-fn@2.1.0: {} @@ -12218,7 +12442,7 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - next@14.2.32(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@14.2.32(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@next/env': 14.2.32 '@swc/helpers': 0.5.5 @@ -12240,6 +12464,7 @@ snapshots: '@next/swc-win32-ia32-msvc': 14.2.32 '@next/swc-win32-x64-msvc': 14.2.32 '@opentelemetry/api': 1.9.0 + '@playwright/test': 1.58.2 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros @@ -12588,6 +12813,30 @@ snapshots: dependencies: find-up: 4.1.0 + playwright-bdd@8.5.0(@playwright/test@1.58.2): + dependencies: + '@cucumber/cucumber-expressions': 18.0.1 + '@cucumber/gherkin': 32.2.0 + '@cucumber/gherkin-utils': 9.2.0 + '@cucumber/html-formatter': 21.15.1(@cucumber/messages@27.2.0) + '@cucumber/junit-xml-formatter': 0.7.1(@cucumber/messages@27.2.0) + '@cucumber/messages': 27.2.0 + '@cucumber/tag-expressions': 6.2.0 + '@playwright/test': 1.58.2 + cli-table3: 0.6.5 + commander: 13.1.0 + fast-glob: 3.3.3 + mime-types: 3.0.2 + xmlbuilder: 15.1.1 + + playwright-core@1.58.2: {} + + playwright@1.58.2: + dependencies: + playwright-core: 1.58.2 + optionalDependencies: + fsevents: 2.3.2 + plimit-lit@1.6.1: dependencies: queue-lit: 1.5.2 @@ -12944,6 +13193,8 @@ snapshots: dependencies: redis-errors: 1.2.0 + reflect-metadata@0.2.2: {} + reflect.getprototypeof@1.0.9: dependencies: call-bind: 1.0.8 @@ -12957,6 +13208,12 @@ snapshots: regenerator-runtime@0.14.1: {} + regexp-match-indices@1.0.2: + dependencies: + regexp-tree: 0.1.27 + + regexp-tree@0.1.27: {} + regexp.prototype.flags@1.5.3: dependencies: call-bind: 1.0.8 @@ -13332,6 +13589,11 @@ snapshots: buffer-from: 1.1.2 source-map: 0.6.1 + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + source-map@0.5.7: {} source-map@0.6.1: {} @@ -13882,6 +14144,8 @@ snapshots: uuid@10.0.0: {} + uuid@11.0.5: {} + uuid@8.3.2: {} uuid@9.0.1: {} @@ -14045,6 +14309,8 @@ snapshots: ws@8.18.0: {} + xmlbuilder@15.1.1: {} + xmlhttprequest-ssl@2.1.1: {} xtend@4.0.2: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index b5bd259eb..9097680e7 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,4 +1,5 @@ packages: + - 'e2e' - 'packages/wallet/*' - 'packages/boutique/*' - 'packages/shared/*' From d9bcf75ddefca67631a02b11b7b20ed87e6b3387 Mon Sep 17 00:00:00 2001 From: Stephan Butler Date: Tue, 24 Mar 2026 14:07:42 +0200 Subject: [PATCH 2/7] fix: fixed linting issues --- .prettierignore | 3 +++ local/docker-compose.yml | 2 +- local/scripts/rafiki-setup.js | 18 +++++++++++++----- packages/wallet/frontend/next.config.js | 3 ++- packages/wallet/frontend/src/middleware.ts | 9 +++++++-- 5 files changed, 26 insertions(+), 9 deletions(-) diff --git a/.prettierignore b/.prettierignore index 0138993b0..f4e85359a 100644 --- a/.prettierignore +++ b/.prettierignore @@ -8,3 +8,6 @@ Dockerfile .gitignore .prettierignore coverage +e2e/.features-gen +e2e/playwright-report +e2e/test-results diff --git a/local/docker-compose.yml b/local/docker-compose.yml index 0745e0abe..813b71ba0 100644 --- a/local/docker-compose.yml +++ b/local/docker-compose.yml @@ -32,7 +32,7 @@ services: networks: - testnet healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:8080/health"] + test: ['CMD', 'curl', '-f', 'http://localhost:8080/health'] interval: 10s timeout: 5s retries: 3 diff --git a/local/scripts/rafiki-setup.js b/local/scripts/rafiki-setup.js index 053ce48ca..50a0817f1 100644 --- a/local/scripts/rafiki-setup.js +++ b/local/scripts/rafiki-setup.js @@ -47,8 +47,7 @@ function canonicalizeAndStringify(value) { function buildEnv() { const envPath = path.join(__dirname, '.env') const fileEnv = loadDotEnv(envPath) - const get = (key, fallback) => - process.env[key] ?? fileEnv[key] ?? fallback + const get = (key, fallback) => process.env[key] ?? fileEnv[key] ?? fallback return { GRAPHQL_ENDPOINT: get('GRAPHQL_ENDPOINT', 'http://localhost:3011/graphql'), @@ -83,7 +82,11 @@ function signRequest({ query, variables, operationName }, env, timestamp) { async function graphqlRequest({ query, variables, operationName }, env) { const timestamp = Date.now() - const signature = signRequest({ query, variables, operationName }, env, timestamp) + const signature = signRequest( + { query, variables, operationName }, + env, + timestamp + ) const body = JSON.stringify({ query, variables, operationName }) const response = await fetch(env.GRAPHQL_ENDPOINT, { @@ -271,7 +274,10 @@ async function ensureAssets(env) { env ) } catch (err) { - console.log('Asset list failed, continuing to create assets...', err.message) + console.log( + 'Asset list failed, continuing to create assets...', + err.message + ) } const existingCodes = new Set( @@ -337,7 +343,9 @@ async function ensureLiquidity(env) { // Amount in minor units: 100000 * 10^scale const amount = BigInt(100000) * BigInt(10) ** BigInt(node.scale) - console.log(`Depositing liquidity for ${asset.code}: ${amount.toString()} (scale ${node.scale})`) + console.log( + `Depositing liquidity for ${asset.code}: ${amount.toString()} (scale ${node.scale})` + ) try { const res = await graphqlRequest( { diff --git a/packages/wallet/frontend/next.config.js b/packages/wallet/frontend/next.config.js index 0d43b0312..0068cb7ce 100644 --- a/packages/wallet/frontend/next.config.js +++ b/packages/wallet/frontend/next.config.js @@ -24,7 +24,8 @@ const nextConfig = { NEXT_PUBLIC_BACKEND_URL: process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:3003', // Internal URL for server-side (middleware) to reach backend in Docker - BACKEND_INTERNAL_URL: process.env.BACKEND_URL || 'http://wallet-backend:3003', + BACKEND_INTERNAL_URL: + process.env.BACKEND_URL || 'http://wallet-backend:3003', NEXT_PUBLIC_OPEN_PAYMENTS_HOST: process.env.NEXT_PUBLIC_OPEN_PAYMENTS_HOST || '$rafiki-backend/', NEXT_PUBLIC_AUTH_HOST: diff --git a/packages/wallet/frontend/src/middleware.ts b/packages/wallet/frontend/src/middleware.ts index 1ed16a166..657d49e2d 100644 --- a/packages/wallet/frontend/src/middleware.ts +++ b/packages/wallet/frontend/src/middleware.ts @@ -19,8 +19,13 @@ export async function middleware(req: NextRequest) { const cookieVal = req.cookies.get(cookieName)?.value // Build internal backend URL for middleware - const backendUrl = process.env.BACKEND_INTERNAL_URL || 'http://wallet-backend:3003' - let response: { success: boolean; result?: any; message?: string } = { + const backendUrl = + process.env.BACKEND_INTERNAL_URL || 'http://wallet-backend:3003' + let response: { + success: boolean + result?: Record + message?: string + } = { success: false } try { From 49df99df597893a930774719281796b062b287c7 Mon Sep 17 00:00:00 2001 From: Stephan Butler Date: Tue, 24 Mar 2026 16:53:23 +0200 Subject: [PATCH 3/7] fix: clean up --- .github/copilot-instructions.md | 11 ++++---- local/.env.example | 6 ++--- local/scripts/rafiki-setup.js | 14 +++++------ .../wallet/backend/src/gatehub/service.ts | 14 ++++++++++- packages/wallet/frontend/next.config.js | 4 ++- packages/wallet/frontend/src/middleware.ts | 25 ++++++++++++------- packages/wallet/frontend/src/utils/helpers.ts | 3 ++- 7 files changed, 49 insertions(+), 28 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 361a19b90..a03d96841 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -47,9 +47,8 @@ PATH=/home/$USER/.nvm/versions/node/v20.20.0/bin:$PATH corepack pnpm checks Validated behavior: may fail on existing repo formatting drift (Prettier). In this workspace it failed on: -- `.github/copilot-instructions.md` -- `docker/local/docker-compose.yml` -- `docker/local/rafiki-setup.js` +- `local/docker-compose.yml` +- `local/scripts/rafiki-setup.js` - `packages/wallet/frontend/next.config.js` - `packages/wallet/frontend/src/middleware.ts` @@ -109,10 +108,10 @@ No command timeouts were observed in this validation pass. Failing commands exit Required precondition before first `pnpm dev`: ```bash -cp docker/dev/.env.example docker/dev/.env +cp local/.env.example local/.env ``` -GateHub-related variables in `docker/dev/.env` are required for full KYC/funding flows. +GateHub-related variables in `local/.env` are required for full KYC/funding flows. Main run modes: @@ -137,7 +136,7 @@ High-signal root files: - `pnpm-workspace.yaml`: workspace package patterns. - `tsconfig.json`: top-level project references. - `eslint.config.mjs`, `.prettierrc.js`: repo-wide code quality rules. -- `docker/dev/docker-compose.yml`: full local dependency graph (Postgres, Redis, Rafiki, Kratos, app backends). +- `local/docker-compose.yml`: full local dependency graph (Postgres, Redis, Rafiki, Kratos, app backends). Key source areas: diff --git a/local/.env.example b/local/.env.example index f5f83619c..7f160e14e 100644 --- a/local/.env.example +++ b/local/.env.example @@ -60,7 +60,7 @@ GATEHUB_ENV=sandbox GATEHUB_IFRAME_BASE_URL=http://localhost:8080 GATEHUB_ACCESS_KEY=mock_access_key GATEHUB_SECRET_KEY=mock_secret_key -GATEHUB_WEBHOOK_SECRET=mock_webhook_secret_please_change +GATEHUB_WEBHOOK_SECRET=6d6f636b5f776562686f6f6b5f736563726574 GATEHUB_GATEWAY_UUID=mock-gateway-uuid GATEHUB_SETTLEMENT_WALLET_ADDRESS=$ilp.interledger-test.dev/interledger GATEHUB_ORG_ID=mock-org-id @@ -86,8 +86,8 @@ GATEHUB_CARD_APP_ID=mock-card-app-id # GATEHUB_CARD_APP_ID= # Wallet backend rate limits and product codes (optional) -RATE_LIMIT=100 -RATE_LIMIT_LEVEL=per_minute +RATE_LIMIT=false +RATE_LIMIT_LEVEL=LAX GATEHUB_ACCOUNT_PRODUCT_CODE=DEFAULT GATEHUB_CARD_PRODUCT_CODE=DEFAULT GATEHUB_NAME_ON_CARD=TEST USER diff --git a/local/scripts/rafiki-setup.js b/local/scripts/rafiki-setup.js index 50a0817f1..5aa532de1 100644 --- a/local/scripts/rafiki-setup.js +++ b/local/scripts/rafiki-setup.js @@ -1,12 +1,12 @@ #!/usr/bin/env node /** * Configure Rafiki (local docker stack) with a tenant + assets. - * - Reads values from docker/local/.env when present (process.env takes priority) + * - Reads values from .env in this directory (local/scripts/.env) when present (process.env takes priority) * - Creates the operator tenant (idpConsentUrl + idpSecret) * - Ensures assets exist for the Testnet wallet * - * Run after `docker compose up -d` from docker/local: - * node rafiki-setup.js + * Run after `docker compose up -d` from local/: + * node scripts/rafiki-setup.js */ const fs = require('fs') @@ -280,13 +280,13 @@ async function ensureAssets(env) { ) } - const existingCodes = new Set( - (current?.assets?.edges ?? []).map((e) => e.node.code) + const existingAssets = new Set( + (current?.assets?.edges ?? []).map((e) => `${e.node.code}:${e.node.scale}`) ) for (const asset of assetsToEnsure) { - if (existingCodes.has(asset.code)) { - console.log(`Asset ${asset.code} already exists`) + if (existingAssets.has(`${asset.code}:${asset.scale}`)) { + console.log(`Asset ${asset.code} (scale ${asset.scale}) already exists`) continue } console.log(`Creating asset ${asset.code}...`) diff --git a/packages/wallet/backend/src/gatehub/service.ts b/packages/wallet/backend/src/gatehub/service.ts index bf51357c4..761fa72f5 100644 --- a/packages/wallet/backend/src/gatehub/service.ts +++ b/packages/wallet/backend/src/gatehub/service.ts @@ -346,7 +346,19 @@ export class GateHubService { `EUR account already exists for user ${userId}, skipping sandbox customer creation` ) const user = await User.query().findById(userId) - return user!.customerId || '' + if (!user) { + this.logger.error( + `User ${userId} not found while EUR account exists, cannot retrieve customerId` + ) + throw new NotFound('User not found') + } + if (!user.customerId) { + this.logger.error( + `Missing customerId for user ${userId} with existing EUR account` + ) + throw new Error('CustomerId is missing for existing EUR account') + } + return user.customerId } const { account, walletAddress } = diff --git a/packages/wallet/frontend/next.config.js b/packages/wallet/frontend/next.config.js index 0068cb7ce..361eec487 100644 --- a/packages/wallet/frontend/next.config.js +++ b/packages/wallet/frontend/next.config.js @@ -25,7 +25,9 @@ const nextConfig = { process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:3003', // Internal URL for server-side (middleware) to reach backend in Docker BACKEND_INTERNAL_URL: - process.env.BACKEND_URL || 'http://wallet-backend:3003', + process.env.BACKEND_INTERNAL_URL || + process.env.BACKEND_URL || + 'http://wallet-backend:3003', NEXT_PUBLIC_OPEN_PAYMENTS_HOST: process.env.NEXT_PUBLIC_OPEN_PAYMENTS_HOST || '$rafiki-backend/', NEXT_PUBLIC_AUTH_HOST: diff --git a/packages/wallet/frontend/src/middleware.ts b/packages/wallet/frontend/src/middleware.ts index 657d49e2d..a1a59853c 100644 --- a/packages/wallet/frontend/src/middleware.ts +++ b/packages/wallet/frontend/src/middleware.ts @@ -28,14 +28,16 @@ export async function middleware(req: NextRequest) { } = { success: false } - try { - const meRes = await fetch(`${backendUrl}/me`, { - headers: cookieVal ? { Cookie: `${cookieName}=${cookieVal}` } : {} - }) - const json = await meRes.json() - response = json - } catch (e) { - // Ignore connectivity errors; fallback logic below handles unauthenticated state + if (cookieVal) { + try { + const meRes = await fetch(`${backendUrl}/me`, { + headers: { Cookie: `${cookieName}=${cookieVal}` } + }) + const json = await meRes.json() + response = json + } catch (e) { + // Ignore connectivity errors; fallback logic below handles unauthenticated state + } } // Success TRUE - the user is logged in @@ -59,7 +61,12 @@ export async function middleware(req: NextRequest) { } if (isPublic) { - const dest = callbackUrl ?? '/' + const dest = + callbackUrl && + callbackUrl.startsWith('/') && + !callbackUrl.startsWith('//') + ? callbackUrl + : '/' return NextResponse.redirect(new URL(dest, req.url)) } } else { diff --git a/packages/wallet/frontend/src/utils/helpers.ts b/packages/wallet/frontend/src/utils/helpers.ts index e3436f0da..deba0ce42 100644 --- a/packages/wallet/frontend/src/utils/helpers.ts +++ b/packages/wallet/frontend/src/utils/helpers.ts @@ -44,7 +44,8 @@ export const formatAmount = (args: FormatAmountArgs): FormattedAmount => { const scaledValue = Number(`${value}e-${assetScale}`) const flooredValue = - Math.floor(scaledValue * 10 ** displayScale) / 10 ** displayScale + Math.floor(Math.round(scaledValue * 10 ** (displayScale + 4)) / 10 ** 4) / + 10 ** displayScale const symbol = getCurrencySymbol(assetCode) From 63f1439cbb1cc32faa36bacac6213a183b13adad Mon Sep 17 00:00:00 2001 From: Stephan Butler Date: Tue, 24 Mar 2026 17:14:46 +0200 Subject: [PATCH 4/7] fix: repaired broken test related to emailService --- packages/wallet/backend/tests/auth/controller.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/wallet/backend/tests/auth/controller.test.ts b/packages/wallet/backend/tests/auth/controller.test.ts index 62be689c0..a9fcaef03 100644 --- a/packages/wallet/backend/tests/auth/controller.test.ts +++ b/packages/wallet/backend/tests/auth/controller.test.ts @@ -18,6 +18,7 @@ import { withSession } from '@/middleware/withSession' import { getRedisClient } from '@/config/redis' import { rateLimiterLogin, rateLimiterEmail } from '@/middleware/rateLimit' import type { UserService } from '@/user/service' +import type { EmailService } from '@/email/service' import { fakeLoginData, mockGateHubClient, @@ -37,6 +38,7 @@ describe('Authentication Controller', (): void => { let authService: AuthService let authController: AuthController let userService: UserService + let emailService: EmailService let req: MockRequest let res: MockResponse @@ -50,6 +52,7 @@ describe('Authentication Controller', (): void => { authService = await bindings.resolve('authService') authController = await bindings.resolve('authController') userService = await bindings.resolve('userService') + emailService = await bindings.resolve('emailService') Reflect.set( userService, @@ -77,6 +80,7 @@ describe('Authentication Controller', (): void => { describe('Sign Up', (): void => { it('should return status 201 if the user is created', async (): Promise => { req.body = mockSignUpRequest().body + jest.spyOn(emailService, 'verifyDomain').mockResolvedValueOnce(undefined) await authController.signUp(req, res, next) expect(next).toHaveBeenCalledTimes(0) @@ -107,6 +111,7 @@ describe('Authentication Controller', (): void => { it('should return status 500 on unexpected error', async (): Promise => { req.body = mockSignUpRequest().body + jest.spyOn(emailService, 'verifyDomain').mockResolvedValueOnce(undefined) const createSpy = jest .spyOn(userService, 'create') From c04075af289730b5ae557fda7b00735f3e12b54c Mon Sep 17 00:00:00 2001 From: Stephan Butler Date: Wed, 25 Mar 2026 18:04:13 +0200 Subject: [PATCH 5/7] fix: removed the +4 precision guard we don't need --- packages/wallet/frontend/src/utils/helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/wallet/frontend/src/utils/helpers.ts b/packages/wallet/frontend/src/utils/helpers.ts index deba0ce42..7c256e575 100644 --- a/packages/wallet/frontend/src/utils/helpers.ts +++ b/packages/wallet/frontend/src/utils/helpers.ts @@ -44,7 +44,7 @@ export const formatAmount = (args: FormatAmountArgs): FormattedAmount => { const scaledValue = Number(`${value}e-${assetScale}`) const flooredValue = - Math.floor(Math.round(scaledValue * 10 ** (displayScale + 4)) / 10 ** 4) / + Math.floor(Math.round(scaledValue * 10 ** displayScale) / 10 ** 4) / 10 ** displayScale const symbol = getCurrencySymbol(assetCode) From 0e519cf307042e6dca1e287e3f3d56effc2b5193 Mon Sep 17 00:00:00 2001 From: Stephan Butler Date: Thu, 26 Mar 2026 05:58:18 +0200 Subject: [PATCH 6/7] fix: address PR review feedback and fix Docker build --- local/scripts/rafiki-setup.js | 21 +++++++++++++----- package.json | 5 +---- packages/boutique/backend/Dockerfile.dev | 2 +- packages/wallet/backend/Dockerfile.dev | 2 +- packages/wallet/frontend/Dockerfile.dev | 2 +- packages/wallet/frontend/src/middleware.ts | 25 +++++++--------------- 6 files changed, 28 insertions(+), 29 deletions(-) diff --git a/local/scripts/rafiki-setup.js b/local/scripts/rafiki-setup.js index 5aa532de1..fe59caa78 100644 --- a/local/scripts/rafiki-setup.js +++ b/local/scripts/rafiki-setup.js @@ -16,22 +16,33 @@ const crypto = require('crypto') // ---- helpers --------------------------------------------------------------- function loadDotEnv(envPath) { const result = {} - if (!fs.existsSync(envPath)) return result + if (!fs.existsSync(envPath)) { + return result + } const lines = fs.readFileSync(envPath, 'utf8').split(/\r?\n/) for (const line of lines) { - if (!line || line.trim().startsWith('#')) continue + if (!line || line.trim().startsWith('#')) { + continue + } const idx = line.indexOf('=') - if (idx === -1) continue + if (idx === -1) { + continue + } const key = line.slice(0, idx).trim() const value = line.slice(idx + 1).trim() result[key] = value } + return result } function canonicalize(value) { - if (value === null || typeof value !== 'object') return value - if (Array.isArray(value)) return value.map(canonicalize) + if (value === null || typeof value !== 'object') { + return value + } + if (Array.isArray(value)) { + return value.map(canonicalize) + } const sortedKeys = Object.keys(value).sort() const obj = {} for (const key of sortedKeys) { diff --git a/package.json b/package.json index 3ab353a94..704e2a3ee 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,7 @@ "format": "pnpm prettier:write && pnpm lint:fix", "lint:check": "eslint --max-warnings=0 .", "lint:fix": "eslint --max-warnings=0 --fix .", - "compose": "docker compose -f ./docker/dev/docker-compose.yml", - "compose:prod": "docker compose -f ./docker/prod/docker-compose.yml", + "compose": "docker compose -f ./local/docker-compose.yml", "localenv:start": "cross-env DEV_MODE=hot-reload pnpm compose up -d --build", "localenv:start:debug": "cross-env DEV_MODE=debug pnpm compose up -d --build", "localenv:start:lite": "cross-env DEV_MODE=lite pnpm compose up -d --build", @@ -31,8 +30,6 @@ "preinstall": "npx only-allow pnpm", "prettier:write": "prettier --config \".prettierrc.js\" --write .", "prettier:check": "prettier --config \".prettierrc.js\" --check .", - "prod": "pnpm compose:prod up -d --build", - "prod:down": "pnpm compose:prod down", "wallet:backend": "pnpm --filter @wallet/backend --", "wallet:frontend": "pnpm --filter @wallet/frontend --" }, diff --git a/packages/boutique/backend/Dockerfile.dev b/packages/boutique/backend/Dockerfile.dev index b55f45b98..3fe3ed21e 100644 --- a/packages/boutique/backend/Dockerfile.dev +++ b/packages/boutique/backend/Dockerfile.dev @@ -21,7 +21,7 @@ RUN pnpm fetch ADD . ./ # Install packages from virtual store -RUN pnpm install -r --offline +RUN pnpm install --filter @boutique/backend... --offline RUN pnpm boutique:backend build diff --git a/packages/wallet/backend/Dockerfile.dev b/packages/wallet/backend/Dockerfile.dev index 8c480a4f0..21540a3bb 100644 --- a/packages/wallet/backend/Dockerfile.dev +++ b/packages/wallet/backend/Dockerfile.dev @@ -21,7 +21,7 @@ RUN pnpm fetch ADD . ./ # Install packages from virtual store -RUN pnpm install -r --offline +RUN pnpm install --filter @wallet/backend... --offline # Build backend RUN pnpm wallet:backend build diff --git a/packages/wallet/frontend/Dockerfile.dev b/packages/wallet/frontend/Dockerfile.dev index 2e5f64a44..d6bbe8d22 100644 --- a/packages/wallet/frontend/Dockerfile.dev +++ b/packages/wallet/frontend/Dockerfile.dev @@ -21,7 +21,7 @@ RUN pnpm fetch ADD . ./ # Install packages from virtual store -RUN pnpm install -r --offline +RUN pnpm install --filter @wallet/frontend... --offline # Accept build arguments for Next.js public environment variables ARG NEXT_PUBLIC_BACKEND_URL diff --git a/packages/wallet/frontend/src/middleware.ts b/packages/wallet/frontend/src/middleware.ts index a1a59853c..c9a9c9b70 100644 --- a/packages/wallet/frontend/src/middleware.ts +++ b/packages/wallet/frontend/src/middleware.ts @@ -1,7 +1,8 @@ import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' -// Do not use the browser httpClient here; middleware runs in the container. -// Call backend using the internal Docker hostname to validate the session. +import { userService } from '@/lib/api/user' +import type { SuccessResponse, ErrorResponse } from '@/lib/httpClient' +import type { UserResponse } from '@wallet/shared' const isPublicPath = (path: string) => { return publicPaths.find((x) => @@ -18,24 +19,14 @@ export async function middleware(req: NextRequest) { const cookieVal = req.cookies.get(cookieName)?.value - // Build internal backend URL for middleware - const backendUrl = - process.env.BACKEND_INTERNAL_URL || 'http://wallet-backend:3003' - let response: { - success: boolean - result?: Record - message?: string - } = { - success: false + let response: SuccessResponse | ErrorResponse = { + success: false, + message: '' } if (cookieVal) { try { - const meRes = await fetch(`${backendUrl}/me`, { - headers: { Cookie: `${cookieName}=${cookieVal}` } - }) - const json = await meRes.json() - response = json - } catch (e) { + response = await userService.me(`${cookieName}=${cookieVal}`) + } catch { // Ignore connectivity errors; fallback logic below handles unauthenticated state } } From 683ae9784a1d1c88a59a8e3cc24a1ea5343ac8d1 Mon Sep 17 00:00:00 2001 From: Stephan Butler Date: Fri, 27 Mar 2026 05:39:31 +0200 Subject: [PATCH 7/7] fix: removed changes to formatAmount --- packages/wallet/frontend/src/utils/helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/wallet/frontend/src/utils/helpers.ts b/packages/wallet/frontend/src/utils/helpers.ts index 7c256e575..fb8f20f2d 100644 --- a/packages/wallet/frontend/src/utils/helpers.ts +++ b/packages/wallet/frontend/src/utils/helpers.ts @@ -44,7 +44,7 @@ export const formatAmount = (args: FormatAmountArgs): FormattedAmount => { const scaledValue = Number(`${value}e-${assetScale}`) const flooredValue = - Math.floor(Math.round(scaledValue * 10 ** displayScale) / 10 ** 4) / + Math.floor(Math.round(scaledValue * 10 ** displayScale)) / 10 ** displayScale const symbol = getCurrencySymbol(assetCode)