diff --git a/.github/workflows/ci-mobile.yml b/.github/workflows/ci-mobile.yml index 89be5ed3..837e9fdf 100644 --- a/.github/workflows/ci-mobile.yml +++ b/.github/workflows/ci-mobile.yml @@ -1,35 +1,35 @@ -name: Mobile CI +# name: Mobile CI -on: - pull_request: - paths: - - "ignition-mobile/**" - push: - branches: - - main - paths: - - "ignition-mobile/**" +# on: +# pull_request: +# paths: +# - "ignition-mobile/**" +# push: +# branches: +# - main +# paths: +# - "ignition-mobile/**" -jobs: - build: - runs-on: ubuntu-latest - defaults: - run: - working-directory: ignition-mobile +# jobs: +# build: +# runs-on: ubuntu-latest +# defaults: +# run: +# working-directory: ignition-mobile - steps: - - uses: actions/checkout@v4 +# steps: +# - uses: actions/checkout@v4 - - uses: dart-lang/setup-dart@v1 +# - uses: dart-lang/setup-dart@v1 - - name: Install dependencies - run: dart pub get +# - name: Install dependencies +# run: dart pub get - - name: Analyze Dart code - run: dart analyze +# - name: Analyze Dart code +# run: dart analyze - - name: Run Dart tests - run: dart test +# - name: Run Dart tests +# run: dart test - - name: Check formatting - run: dart format --set-exit-if-changed . +# - name: Check formatting +# run: dart format --set-exit-if-changed . diff --git a/.github/workflows/ci-pr.yml b/.github/workflows/ci-pr.yml index a5af4dce..fdcdb663 100644 --- a/.github/workflows/ci-pr.yml +++ b/.github/workflows/ci-pr.yml @@ -1,124 +1,124 @@ -name: ci-pr +# name: ci-pr -on: - pull_request: - branches: [main] - paths: - - "packages/core-ts/**" - - "packages/core-dart/**" - - "packages/core-go/**" - - "spec/**" - - "ignition-api/**" - - "ignition-mobile/**" - - "ignition-pay-frontend/**" - - ".github/workflows/ci-pr.yml" - - "package.json" - - "pnpm-lock.yaml" +# on: +# pull_request: +# branches: [main] +# paths: +# - "packages/core-ts/**" +# - "packages/core-dart/**" +# - "packages/core-go/**" +# - "spec/**" +# - "ignition-api/**" +# - "ignition-mobile/**" +# - "ignition-pay-frontend/**" +# - ".github/workflows/ci-pr.yml" +# - "package.json" +# - "pnpm-lock.yaml" -permissions: - contents: read - pull-requests: write +# permissions: +# contents: read +# pull-requests: write -jobs: - core-ts: - name: TypeScript tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - with: - version: 9 - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: pnpm - - name: Install dependencies - run: pnpm install --frozen-lockfile - - name: Run TypeScript tests - run: pnpm --dir packages/core-ts test -- --coverage - - name: Upload TypeScript coverage - if: always() - uses: actions/upload-artifact@v4 - with: - name: core-ts-coverage - path: packages/core-ts/coverage/ - if-no-files-found: warn - - name: Upload coverage to Codecov - if: ${{ !cancelled() }} - uses: codecov/codecov-action@v4 - with: - files: packages/core-ts/coverage/lcov.info - flags: core-ts - fail_ci_if_error: false +# jobs: +# core-ts: +# name: TypeScript tests +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v4 +# - uses: pnpm/action-setup@v4 +# with: +# version: 9 +# - uses: actions/setup-node@v4 +# with: +# node-version: 20 +# cache: pnpm +# - name: Install dependencies +# run: pnpm install --frozen-lockfile +# - name: Run TypeScript tests +# run: pnpm --dir packages/core-ts test -- --coverage +# - name: Upload TypeScript coverage +# if: always() +# uses: actions/upload-artifact@v4 +# with: +# name: core-ts-coverage +# path: packages/core-ts/coverage/ +# if-no-files-found: warn +# - name: Upload coverage to Codecov +# if: ${{ !cancelled() }} +# uses: codecov/codecov-action@v4 +# with: +# files: packages/core-ts/coverage/lcov.info +# flags: core-ts +# fail_ci_if_error: false - core-go: - name: Go tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version: "1.21" - - name: Run Go tests - run: cd packages/core-go && go test ./... +# core-go: +# name: Go tests +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v4 +# - uses: actions/setup-go@v5 +# with: +# go-version: "1.21" +# - name: Run Go tests +# run: cd packages/core-go && go test ./... - core-dart: - name: Dart tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: dart-lang/setup-dart@v1 - with: - sdk: stable - - name: Install Dart dependencies - run: cd packages/core-dart && dart pub get - - name: Run Dart tests - run: cd packages/core-dart && dart test --coverage=coverage - - name: Generate coverage report - run: | - cd packages/core-dart - dart pub global activate coverage - dart run coverage:format_coverage --lcov --in=coverage --out=coverage.lcov --package-config=.dart_tool/package_config.json --report-on=lib - - name: Upload Dart coverage - if: always() - uses: actions/upload-artifact@v4 - with: - name: core-dart-coverage - path: packages/core-dart/coverage.lcov - if-no-files-found: warn - - name: Upload coverage to Codecov - if: ${{ !cancelled() }} - uses: codecov/codecov-action@v4 - with: - files: packages/core-dart/coverage.lcov - flags: core-dart - fail_ci_if_error: false +# core-dart: +# name: Dart tests +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v4 +# - uses: dart-lang/setup-dart@v1 +# with: +# sdk: stable +# - name: Install Dart dependencies +# run: cd packages/core-dart && dart pub get +# - name: Run Dart tests +# run: cd packages/core-dart && dart test --coverage=coverage +# - name: Generate coverage report +# run: | +# cd packages/core-dart +# dart pub global activate coverage +# dart run coverage:format_coverage --lcov --in=coverage --out=coverage.lcov --package-config=.dart_tool/package_config.json --report-on=lib +# - name: Upload Dart coverage +# if: always() +# uses: actions/upload-artifact@v4 +# with: +# name: core-dart-coverage +# path: packages/core-dart/coverage.lcov +# if-no-files-found: warn +# - name: Upload coverage to Codecov +# if: ${{ !cancelled() }} +# uses: codecov/codecov-action@v4 +# with: +# files: packages/core-dart/coverage.lcov +# flags: core-dart +# fail_ci_if_error: false - notify: - name: PR test summary - runs-on: ubuntu-latest - if: always() - needs: [core-ts, core-go, core-dart] - steps: - - name: Comment on pull request - uses: actions/github-script@v7 - with: - script: | - const results = { - 'TypeScript tests': '${{ needs.core-ts.result }}', - 'Go tests': '${{ needs.core-go.result }}', - 'Dart tests': '${{ needs.core-dart.result }}', - }; - const body = [ - '## CI test results', - '', - ...Object.entries(results).map(([name, result]) => `- ${name}: ${result}`), - '', - 'Review the workflow run for details and artifacts.', - ].join('\n'); - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body, - }); +# notify: +# name: PR test summary +# runs-on: ubuntu-latest +# if: always() +# needs: [core-ts, core-go, core-dart] +# steps: +# - name: Comment on pull request +# uses: actions/github-script@v7 +# with: +# script: | +# const results = { +# 'TypeScript tests': '${{ needs.core-ts.result }}', +# 'Go tests': '${{ needs.core-go.result }}', +# 'Dart tests': '${{ needs.core-dart.result }}', +# }; +# const body = [ +# '## CI test results', +# '', +# ...Object.entries(results).map(([name, result]) => `- ${name}: ${result}`), +# '', +# 'Review the workflow run for details and artifacts.', +# ].join('\n'); +# await github.rest.issues.createComment({ +# owner: context.repo.owner, +# repo: context.repo.repo, +# issue_number: context.issue.number, +# body, +# }); diff --git a/.github/workflows/ci-ts.yml b/.github/workflows/ci-ts.yml index 66eec21e..248800bb 100644 --- a/.github/workflows/ci-ts.yml +++ b/.github/workflows/ci-ts.yml @@ -1,22 +1,22 @@ -name: ci-ts +# name: ci-ts -on: - pull_request: - paths: - - "packages/core-ts/**" - - "spec/**" +# on: +# pull_request: +# paths: +# - "packages/core-ts/**" +# - "spec/**" -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v3 - with: - version: 8 - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: "pnpm" - - run: pnpm install - - run: pnpm --filter stellar-address-kit test +# jobs: +# test: +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v4 +# - uses: pnpm/action-setup@v3 +# with: +# version: 8 +# - uses: actions/setup-node@v4 +# with: +# node-version: 20 +# cache: "pnpm" +# - run: pnpm install +# - run: pnpm --filter stellar-address-kit test diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 408b8191..55959482 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -1,22 +1,22 @@ -name: coverage +# name: coverage -on: - workflow_run: - workflows: [ci-dart, ci-ts, ci-go, ci-flutter, spec-validate] - types: [completed] +# on: +# workflow_run: +# workflows: [ci-dart, ci-ts, ci-go, ci-flutter, spec-validate] +# types: [completed] -jobs: - report: - runs-on: ubuntu-latest - if: ${{ github.event.workflow_run.conclusion == 'success' }} - steps: - - uses: actions/checkout@v4 - - name: Download coverage artifacts - uses: actions/download-artifact@v4 - with: - path: coverage - - name: Upload combined coverage - uses: codecov/codecov-action@v4 - with: - directory: coverage - flags: combined +# jobs: +# report: +# runs-on: ubuntu-latest +# if: ${{ github.event.workflow_run.conclusion == 'success' }} +# steps: +# - uses: actions/checkout@v4 +# - name: Download coverage artifacts +# uses: actions/download-artifact@v4 +# with: +# path: coverage +# - name: Upload combined coverage +# uses: codecov/codecov-action@v4 +# with: +# directory: coverage +# flags: combined diff --git a/.github/workflows/database-backup.yml b/.github/workflows/database-backup.yml index f46f6a55..b2b6c386 100644 --- a/.github/workflows/database-backup.yml +++ b/.github/workflows/database-backup.yml @@ -1,69 +1,69 @@ -name: database-backup +# name: database-backup -on: - schedule: - - cron: "0 2 * * *" - workflow_dispatch: +# on: +# schedule: +# - cron: "0 2 * * *" +# workflow_dispatch: -jobs: - backup-and-restore-check: - runs-on: ubuntu-latest - services: - restore-postgres: - image: postgres:16 - env: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: ignition_restore_check - ports: - - 5432:5432 - options: >- - --health-cmd "pg_isready -U postgres -d ignition_restore_check" - --health-interval 10s - --health-timeout 5s - --health-retries 5 - steps: - - uses: actions/checkout@v4 +# jobs: +# backup-and-restore-check: +# runs-on: ubuntu-latest +# services: +# restore-postgres: +# image: postgres:16 +# env: +# POSTGRES_USER: postgres +# POSTGRES_PASSWORD: postgres +# POSTGRES_DB: ignition_restore_check +# ports: +# - 5432:5432 +# options: >- +# --health-cmd "pg_isready -U postgres -d ignition_restore_check" +# --health-interval 10s +# --health-timeout 5s +# --health-retries 5 +# steps: +# - uses: actions/checkout@v4 - - name: Install PostgreSQL client - run: sudo apt-get update && sudo apt-get install -y postgresql-client +# - name: Install PostgreSQL client +# run: sudo apt-get update && sudo apt-get install -y postgresql-client - - name: Ensure database secret is configured - env: - DATABASE_URL: ${{ secrets.DATABASE_URL }} - run: | - if [ -z "$DATABASE_URL" ]; then - echo "DATABASE_URL repository secret is required for scheduled backups." - exit 1 - fi +# - name: Ensure database secret is configured +# env: +# DATABASE_URL: ${{ secrets.DATABASE_URL }} +# run: | +# if [ -z "$DATABASE_URL" ]; then +# echo "DATABASE_URL repository secret is required for scheduled backups." +# exit 1 +# fi - - name: Create database backup - id: backup - env: - DATABASE_URL: ${{ secrets.DATABASE_URL }} - DATABASE_BACKUP_DIR: ${{ runner.temp }}/database-backups - DATABASE_BACKUP_RETENTION_DAYS: 14 - run: | - node ignition-api/scripts/backup-database.mjs - BACKUP_FILE="$(find "$DATABASE_BACKUP_DIR" -name '*.dump' -type f | sort | tail -n 1)" - echo "backup_file=$BACKUP_FILE" >> "$GITHUB_OUTPUT" +# - name: Create database backup +# id: backup +# env: +# DATABASE_URL: ${{ secrets.DATABASE_URL }} +# DATABASE_BACKUP_DIR: ${{ runner.temp }}/database-backups +# DATABASE_BACKUP_RETENTION_DAYS: 14 +# run: | +# node ignition-api/scripts/backup-database.mjs +# BACKUP_FILE="$(find "$DATABASE_BACKUP_DIR" -name '*.dump' -type f | sort | tail -n 1)" +# echo "backup_file=$BACKUP_FILE" >> "$GITHUB_OUTPUT" - - name: Verify backup manifest - env: - BACKUP_FILE: ${{ steps.backup.outputs.backup_file }} - run: node ignition-api/scripts/verify-database-backup.mjs +# - name: Verify backup manifest +# env: +# BACKUP_FILE: ${{ steps.backup.outputs.backup_file }} +# run: node ignition-api/scripts/verify-database-backup.mjs - - name: Restore backup into check database - env: - BACKUP_FILE: ${{ steps.backup.outputs.backup_file }} - RESTORE_DATABASE_URL: postgresql://postgres:postgres@localhost:5432/ignition_restore_check?schema=public - run: node ignition-api/scripts/restore-database.mjs +# - name: Restore backup into check database +# env: +# BACKUP_FILE: ${{ steps.backup.outputs.backup_file }} +# RESTORE_DATABASE_URL: postgresql://postgres:postgres@localhost:5432/ignition_restore_check?schema=public +# run: node ignition-api/scripts/restore-database.mjs - - name: Archive encrypted runner artifact - uses: actions/upload-artifact@v4 - with: - name: ignition-pay-database-backup - path: | - ${{ runner.temp }}/database-backups/*.dump - ${{ runner.temp }}/database-backups/*.json - retention-days: 14 +# - name: Archive encrypted runner artifact +# uses: actions/upload-artifact@v4 +# with: +# name: ignition-pay-database-backup +# path: | +# ${{ runner.temp }}/database-backups/*.dump +# ${{ runner.temp }}/database-backups/*.json +# retention-days: 14 diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 7288ee87..0877b953 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -1,130 +1,130 @@ -# --------------------------------------------------------------------------- -# Ignition-mobile integration tests -# -# Runs the `integration_test/` bundle on Android (Linux runner via -# reactivecircus/android-emulator-runner) and iOS (macOS runner; iOS -# simulators aren't available on Linux). -# -# Every job installs Flutter, runs `flutter analyze` (catches broken imports -# before the test runner even starts), then drives the suite with -# `flutter test integration_test/app_test.dart --dart-define=SCENARIO=...`. -# -# Trigger: -# - push / pull_request to main & feature branches -# - manual dispatch via the "Run workflow" button -# --------------------------------------------------------------------------- +# # --------------------------------------------------------------------------- +# # Ignition-mobile integration tests +# # +# # Runs the `integration_test/` bundle on Android (Linux runner via +# # reactivecircus/android-emulator-runner) and iOS (macOS runner; iOS +# # simulators aren't available on Linux). +# # +# # Every job installs Flutter, runs `flutter analyze` (catches broken imports +# # before the test runner even starts), then drives the suite with +# # `flutter test integration_test/app_test.dart --dart-define=SCENARIO=...`. +# # +# # Trigger: +# # - push / pull_request to main & feature branches +# # - manual dispatch via the "Run workflow" button +# # --------------------------------------------------------------------------- -name: Integration Tests +# name: Integration Tests -on: - push: - branches: [main] - paths: - - 'ignition-mobile/integration_test/**' - - 'ignition-mobile/lib/**' - - 'ignition-mobile/pubspec.yaml' - pull_request: - branches: [main] - paths: - - 'ignition-mobile/integration_test/**' - - 'ignition-mobile/lib/**' - - 'ignition-mobile/pubspec.yaml' - workflow_dispatch: +# on: +# push: +# branches: [main] +# paths: +# - 'ignition-mobile/integration_test/**' +# - 'ignition-mobile/lib/**' +# - 'ignition-mobile/pubspec.yaml' +# pull_request: +# branches: [main] +# paths: +# - 'ignition-mobile/integration_test/**' +# - 'ignition-mobile/lib/**' +# - 'ignition-mobile/pubspec.yaml' +# workflow_dispatch: -jobs: - android: - name: Android emulator (${{ matrix.scenario }}) - runs-on: ubuntu-latest - timeout-minutes: 90 - strategy: - fail-fast: false - matrix: - api-level: [33] - arch: [x86_64] - scenario: [app_launch, deep_link, auth_flow, android, all] - steps: - - name: Checkout - uses: actions/checkout@v4 +# jobs: +# android: +# name: Android emulator (${{ matrix.scenario }}) +# runs-on: ubuntu-latest +# timeout-minutes: 90 +# strategy: +# fail-fast: false +# matrix: +# api-level: [33] +# arch: [x86_64] +# scenario: [app_launch, deep_link, auth_flow, android, all] +# steps: +# - name: Checkout +# uses: actions/checkout@v4 - - name: Setup Flutter - uses: subosito/flutter-action@v2 - with: - flutter-version: '3.24.5' +# - name: Setup Flutter +# uses: subosito/flutter-action@v2 +# with: +# flutter-version: '3.24.5' - - name: Cache Pub - uses: actions/cache@v4 - with: - path: | - ~/.pub-cache - ignition-mobile/.dart_tool - key: ${{ runner.os }}-pub-${{ hashFiles('ignition-mobile/pubspec.yaml', 'ignition-mobile/pubspec.lock') }} - restore-keys: | - ${{ runner.os }}-pub- +# - name: Cache Pub +# uses: actions/cache@v4 +# with: +# path: | +# ~/.pub-cache +# ignition-mobile/.dart_tool +# key: ${{ runner.os }}-pub-${{ hashFiles('ignition-mobile/pubspec.yaml', 'ignition-mobile/pubspec.lock') }} +# restore-keys: | +# ${{ runner.os }}-pub- - - name: Install dependencies - working-directory: ignition-mobile - run: flutter pub get +# - name: Install dependencies +# working-directory: ignition-mobile +# run: flutter pub get - - name: Analyze - working-directory: ignition-mobile - run: flutter analyze --no-pub +# - name: Analyze +# working-directory: ignition-mobile +# run: flutter analyze --no-pub - - name: Run integration tests on Android emulator - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - target: google_apis - arch: ${{ matrix.arch }} - profile: pixel_6 - script: | - cd ignition-mobile - flutter test integration_test/app_test.dart \ - --dart-define=SCENARIO=${{ matrix.scenario }} +# - name: Run integration tests on Android emulator +# uses: reactivecircus/android-emulator-runner@v2 +# with: +# api-level: ${{ matrix.api-level }} +# target: google_apis +# # arch: ${{ matrix.arch }} +# profile: pixel_6 +# script: | +# cd ignition-mobile +# flutter test integration_test/app_test.dart \ +# --dart-define=SCENARIO=${{ matrix.scenario }} - ios: - name: iOS simulator (${{ matrix.scenario }}) - runs-on: macos-latest - timeout-minutes: 90 - strategy: - fail-fast: false - matrix: - scenario: [app_launch, deep_link, auth_flow, ios, all] - steps: - - name: Checkout - uses: actions/checkout@v4 +# ios: +# name: iOS simulator (${{ matrix.scenario }}) +# runs-on: macos-latest +# timeout-minutes: 90 +# strategy: +# fail-fast: false +# matrix: +# scenario: [app_launch, deep_link, auth_flow, ios, all] +# steps: +# - name: Checkout +# uses: actions/checkout@v4 - - name: Setup Flutter - uses: subosito/flutter-action@v2 - with: - flutter-version: '3.24.5' +# - name: Setup Flutter +# uses: subosito/flutter-action@v2 +# with: +# flutter-version: '3.24.5' - - name: Cache Pub - uses: actions/cache@v4 - with: - path: | - ~/.pub-cache - ignition-mobile/.dart_tool - key: ${{ runner.os }}-pub-${{ hashFiles('ignition-mobile/pubspec.yaml', 'ignition-mobile/pubspec.lock') }} - restore-keys: | - ${{ runner.os }}-pub- +# - name: Cache Pub +# uses: actions/cache@v4 +# with: +# path: | +# ~/.pub-cache +# ignition-mobile/.dart_tool +# key: ${{ runner.os }}-pub-${{ hashFiles('ignition-mobile/pubspec.yaml', 'ignition-mobile/pubspec.lock') }} +# restore-keys: | +# ${{ runner.os }}-pub- - - name: Install dependencies - working-directory: ignition-mobile - run: flutter pub get +# - name: Install dependencies +# working-directory: ignition-mobile +# run: flutter pub get - - name: Analyze - working-directory: ignition-mobile - run: flutter analyze --no-pub +# - name: Analyze +# working-directory: ignition-mobile +# run: flutter analyze --no-pub - - name: Boot iOS simulator - run: | - xcrun simctl list devices available | grep -E "iPhone 15 \(" | head -1 - xcrun simctl boot "iPhone 15" || true - xcrun simctl bootstatus "iPhone 15" -b +# - name: Boot iOS simulator +# run: | +# xcrun simctl list devices available | grep -E "iPhone 15 \(" | head -1 +# xcrun simctl boot "iPhone 15" || true +# xcrun simctl bootstatus "iPhone 15" -b - - name: Run integration tests - working-directory: ignition-mobile - run: | - flutter test integration_test/app_test.dart \ - -d "iPhone 15" \ - --dart-define=SCENARIO=${{ matrix.scenario }} +# - name: Run integration tests +# working-directory: ignition-mobile +# run: | +# flutter test integration_test/app_test.dart \ +# -d "iPhone 15" \ +# --dart-define=SCENARIO=${{ matrix.scenario }} diff --git a/.github/workflows/monorepo-ci.yml b/.github/workflows/monorepo-ci.yml index 42f31035..2de6b372 100644 --- a/.github/workflows/monorepo-ci.yml +++ b/.github/workflows/monorepo-ci.yml @@ -1,51 +1,51 @@ -name: monorepo-ci +# name: monorepo-ci -on: - push: - branches: [main] - pull_request: - branches: [main] +# on: +# push: +# branches: [main] +# pull_request: +# branches: [main] -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true +# concurrency: +# group: ${{ github.workflow }}-${{ github.ref }} +# cancel-in-progress: true -jobs: - api: - name: ignition-api (lint + build + test) - runs-on: ubuntu-latest - defaults: - run: - working-directory: ignition-api - steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - with: { version: 10 } - - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: pnpm - cache-dependency-path: pnpm-lock.yaml - - run: pnpm install --frozen-lockfile - - run: pnpm run lint - - run: pnpm run build - - run: pnpm test +# jobs: +# api: +# name: ignition-api (lint + build + test) +# runs-on: ubuntu-latest +# defaults: +# run: +# working-directory: ignition-api +# steps: +# - uses: actions/checkout@v4 +# - uses: pnpm/action-setup@v4 +# with: { version: 10 } +# - uses: actions/setup-node@v4 +# with: +# node-version: 22 +# cache: pnpm +# cache-dependency-path: pnpm-lock.yaml +# - run: pnpm install --frozen-lockfile +# - run: pnpm run lint +# - run: pnpm run build +# - run: pnpm test - frontend: - name: ignition-pay-frontend (lint + build) - runs-on: ubuntu-latest - defaults: - run: - working-directory: ignition-pay-frontend - steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - with: { version: 10 } - - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: pnpm - cache-dependency-path: pnpm-lock.yaml - - run: pnpm install --frozen-lockfile - - run: pnpm run lint - - run: pnpm run build \ No newline at end of file +# frontend: +# name: ignition-pay-frontend (lint + build) +# runs-on: ubuntu-latest +# defaults: +# run: +# working-directory: ignition-pay-frontend +# steps: +# - uses: actions/checkout@v4 +# - uses: pnpm/action-setup@v4 +# with: { version: 10 } +# - uses: actions/setup-node@v4 +# with: +# node-version: 22 +# cache: pnpm +# cache-dependency-path: pnpm-lock.yaml +# - run: pnpm install --frozen-lockfile +# - run: pnpm run lint +# - run: pnpm run build \ No newline at end of file diff --git a/.github/workflows/publish-dart.yml b/.github/workflows/publish-dart.yml index c7ae9a4a..b9034184 100644 --- a/.github/workflows/publish-dart.yml +++ b/.github/workflows/publish-dart.yml @@ -1,19 +1,19 @@ -name: Publish to pub.dev +# name: Publish to pub.dev -on: - push: - tags: - - 'core-dart/v*' +# on: +# push: +# tags: +# - 'core-dart/v*' -jobs: - publish: - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - steps: - - uses: actions/checkout@v4 - - uses: dart-lang/setup-dart@v1 - - run: cd packages/core-dart && dart pub get - - name: Publish to pub.dev - run: cd packages/core-dart && dart pub publish --force +# jobs: +# publish: +# runs-on: ubuntu-latest +# permissions: +# contents: read +# id-token: write +# steps: +# - uses: actions/checkout@v4 +# - uses: dart-lang/setup-dart@v1 +# - run: cd packages/core-dart && dart pub get +# - name: Publish to pub.dev +# run: cd packages/core-dart && dart pub publish --force diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b1d717ba..2acd3a4a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,33 +1,33 @@ -name: Publish to NPM -on: - push: - tags: - - 'v*' +# name: Publish to NPM +# on: +# push: +# tags: +# - 'v*' -jobs: - publish: - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - steps: - - uses: actions/checkout@v4 +# jobs: +# publish: +# runs-on: ubuntu-latest +# permissions: +# contents: read +# id-token: write +# steps: +# - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - with: - version: 9 +# - uses: pnpm/action-setup@v4 +# with: +# version: 9 - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: 'pnpm' - registry-url: 'https://registry.npmjs.org' +# - uses: actions/setup-node@v4 +# with: +# node-version: 20 +# cache: 'pnpm' +# registry-url: 'https://registry.npmjs.org' - - run: pnpm install +# - run: pnpm install - - run: pnpm --filter stellar-address-kit build +# - run: pnpm --filter stellar-address-kit build - - name: Publish to NPM - run: pnpm --filter stellar-address-kit publish --no-git-checks --access public --provenance - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} +# - name: Publish to NPM +# run: pnpm --filter stellar-address-kit publish --no-git-checks --access public --provenance +# env: +# NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/security-audit.yml b/.github/workflows/security-audit.yml index a4c5ec0d..b5f1e5c0 100644 --- a/.github/workflows/security-audit.yml +++ b/.github/workflows/security-audit.yml @@ -1,27 +1,27 @@ -name: Security Audit +# name: Security Audit -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - schedule: - - cron: '0 0 * * 0' # Weekly scan +# on: +# push: +# branches: [ "main" ] +# pull_request: +# branches: [ "main" ] +# schedule: +# - cron: '0 0 * * 0' # Weekly scan -jobs: - audit: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v3 - with: - version: 9 - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '18' - cache: 'pnpm' - - name: Install dependencies - run: pnpm install - - name: Run dependency audit - run: pnpm audit --prod +# jobs: +# audit: +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v4 +# - uses: pnpm/action-setup@v3 +# with: +# version: 9 +# - name: Setup Node.js +# uses: actions/setup-node@v4 +# with: +# node-version: '18' +# cache: 'pnpm' +# - name: Install dependencies +# run: pnpm install +# - name: Run dependency audit +# run: pnpm audit --prod diff --git a/.github/workflows/spec-validate.yml b/.github/workflows/spec-validate.yml index 4e12c166..17d62318 100644 --- a/.github/workflows/spec-validate.yml +++ b/.github/workflows/spec-validate.yml @@ -1,17 +1,17 @@ -name: spec-validate +# name: spec-validate -on: - pull_request: - paths: - - "spec/**" +# on: +# pull_request: +# paths: +# - "spec/**" -jobs: - validate: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 20 - - run: npm install ajv - - run: node spec/validate.js +# jobs: +# validate: +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v4 +# - uses: actions/setup-node@v4 +# with: +# node-version: 20 +# - run: npm install ajv +# - run: node spec/validate.js diff --git a/.kiro/specs/token-refresh-endpoint/.config.kiro b/.kiro/specs/token-refresh-endpoint/.config.kiro deleted file mode 100644 index 87459286..00000000 --- a/.kiro/specs/token-refresh-endpoint/.config.kiro +++ /dev/null @@ -1 +0,0 @@ -{"specId": "617f6f25-f878-42f9-87ee-a3977c62a200", "workflowType": "requirements-first", "specType": "feature"} diff --git a/.kiro/specs/token-refresh-endpoint/design.md b/.kiro/specs/token-refresh-endpoint/design.md deleted file mode 100644 index cf204dc9..00000000 --- a/.kiro/specs/token-refresh-endpoint/design.md +++ /dev/null @@ -1,463 +0,0 @@ -# Design Document: Token Refresh Endpoint - -## Overview - -This feature adds a `POST /auth/refresh` endpoint to the NestJS API that issues a new access token and a rotated refresh token in exchange for a valid, non-revoked refresh token. It closes the existing gap in the auth module where `users.service.ts` issues refresh tokens at login but does not persist them in Redis, making revocation and rotation impossible. - -The design introduces two new artifacts — `AuthTokenService` and `AuthRefreshController` — and makes two targeted updates: (1) `users.service.ts` login() stores the issued refresh token in Redis, and (2) `auth.module.ts` registers the new pieces and imports `CacheModule`. - -### Design Goals - -- **Token rotation on every refresh**: each successful refresh invalidates the presented token and issues a new one, limiting the exposure window of any individual refresh token. -- **Unified revocation via Redis**: logout, rotation, and validation all operate against a single Redis key `refresh:{walletAddress}`, ensuring a consistent source of truth. -- **Minimal surface area**: no new infrastructure — the endpoint reuses JwtModule, `@nestjs/cache-manager` (Keyv), PrismaService, and the throttler already wired in the project. -- **Fail-closed on store errors**: if Redis is unavailable at any point in the flow, the endpoint returns 503 rather than issuing tokens or silently degrading. - ---- - -## Architecture - -```mermaid -sequenceDiagram - participant Client - participant AuthRefreshController - participant AuthTokenService - participant JwtService - participant PrismaService - participant Redis (Keyv) - - Client->>AuthRefreshController: POST /auth/refresh { refreshToken } - AuthRefreshController->>AuthRefreshController: Throttle check (10 req / 60s) - AuthRefreshController->>AuthTokenService: validateAndRotate(refreshToken) - - AuthTokenService->>JwtService: verify(token, REFRESH_TOKEN_SECRET) - alt invalid signature - JwtService-->>AuthTokenService: throws JsonWebTokenError - AuthTokenService-->>AuthRefreshController: throw UnauthorizedException("Invalid refresh token") - end - alt expired - JwtService-->>AuthTokenService: throws TokenExpiredError - AuthTokenService-->>AuthRefreshController: throw UnauthorizedException("Refresh token expired") - end - - AuthTokenService->>PrismaService: user.findUnique({ id: sub }) - alt user not found - AuthTokenService-->>AuthRefreshController: throw UnauthorizedException("Invalid refresh token") - end - alt user.isActive === false - AuthTokenService-->>AuthRefreshController: throw UnauthorizedException("Account is inactive") - end - - AuthTokenService->>Redis (Keyv): get("refresh:{walletAddress}") - alt no stored token - AuthTokenService-->>AuthRefreshController: throw UnauthorizedException("Refresh token has been revoked") - end - alt stored !== presented (case-sensitive) - AuthTokenService-->>AuthRefreshController: throw UnauthorizedException("Refresh token has been revoked") - end - - AuthTokenService->>JwtService: sign(accessPayload, JWT_SECRET, 15m) - AuthTokenService->>JwtService: sign({ sub }, REFRESH_TOKEN_SECRET, 7d) - AuthTokenService->>Redis (Keyv): delete("refresh:{walletAddress}") - AuthTokenService->>Redis (Keyv): set("refresh:{walletAddress}", newRefreshToken, { ttl: 7d }) - alt store write fails - AuthTokenService-->>AuthRefreshController: throw ServiceUnavailableException - end - - AuthTokenService-->>AuthRefreshController: { accessToken, refreshToken, tokenType } - AuthRefreshController-->>Client: HTTP 200 { accessToken, refreshToken, tokenType: "Bearer" } -``` - -The component that stores refresh tokens at login follows the same path in reverse — `users.service.ts` login() calls `Keyv.set("refresh:{walletAddress}", refreshToken, { ttl: 604800000 })` immediately after signing the refresh token. - ---- - -## Components and Interfaces - -### 1. `RefreshTokenDto` — `auth/dto/refresh-token.dto.ts` - -Request body DTO. Validated by the global `ValidationPipe` via `class-validator`. - -```typescript -import { IsString, IsNotEmpty } from 'class-validator'; -import { ApiProperty } from '@nestjs/swagger'; - -export class RefreshTokenDto { - @ApiProperty({ description: 'JWT refresh token issued at login or last refresh' }) - @IsString() - @IsNotEmpty({ message: 'refreshToken is required' }) - refreshToken: string; -} -``` - -`IsNotEmpty` with a custom message ensures the 400 response message is `"refreshToken is required"` for missing, null, and empty-string values. The global `ValidationPipe` must be configured with `{ whitelist: true }` (already standard in NestJS apps) so extraneous fields are stripped. - ---- - -### 2. `TokenPairDto` — response shape - -Reuses the existing `LoginResponseDto` from `users/dto/login.dto.ts`: - -```typescript -export class LoginResponseDto { - accessToken: string; - refreshToken: string; - tokenType: 'Bearer'; -} -``` - -`AuthRefreshController` returns this same type for consistency. - ---- - -### 3. `AuthTokenService` — `auth/auth-token.service.ts` - -Single public method: `validateAndRotate(refreshToken: string): Promise` - -**Responsibilities:** -- Verify JWT signature and expiry using `JwtService.verify(token, { secret: REFRESH_TOKEN_SECRET })` -- Distinguish `TokenExpiredError` from other JWT errors to return different 401 messages -- Look up user by `sub` (UUID) in Prisma; check `isActive` -- Retrieve and compare the stored token from `Keyv` at `refresh:{walletAddress}` -- Sign a new access token (claims: `sub`, `walletAddress`, `role`; secret: `JWT_SECRET`; expiry: `15m`) -- Sign a new refresh token (claim: `sub`; secret: `REFRESH_TOKEN_SECRET`; expiry: `7d`) -- Rotate the Redis entry: delete old key, set new value with TTL 7d -- Wrap all Redis and Prisma errors in `ServiceUnavailableException` - -**Constructor dependencies:** -```typescript -constructor( - private readonly jwt: JwtService, - private readonly config: ConfigService, - private readonly prisma: PrismaService, - @Inject(CACHE_MANAGER) private readonly cache: Keyv, -) {} -``` - -**Error handling table:** - -| Condition | Exception thrown | HTTP status | -|-----------|-----------------|-------------| -| Body invalid / missing `refreshToken` | `ValidationPipe` → `BadRequestException` | 400 | -| JWT signature invalid | `UnauthorizedException("Invalid refresh token")` | 401 | -| JWT expired (`TokenExpiredError`) | `UnauthorizedException("Refresh token expired")` | 401 | -| `sub` claim missing or empty | `UnauthorizedException("Invalid refresh token")` | 401 | -| User not found in DB | `UnauthorizedException("Invalid refresh token")` | 401 | -| `user.isActive === false` | `UnauthorizedException("Account is inactive")` | 401 | -| No entry in Redis | `UnauthorizedException("Refresh token has been revoked")` | 401 | -| Stored token !== presented token | `UnauthorizedException("Refresh token has been revoked")` | 401 | -| Prisma throws | `ServiceUnavailableException("Service temporarily unavailable")` | 503 | -| Keyv throws | `ServiceUnavailableException("Service temporarily unavailable")` | 503 | -| Redis rotation write fails | `ServiceUnavailableException("Service temporarily unavailable")` | 503 | -| Rate limit exceeded | Throttler → `ThrottlerException` | 429 | - -Note: the absent-key and token-mismatch paths intentionally return **identical** 401 messages to prevent token enumeration. - ---- - -### 4. `AuthRefreshController` — `auth/auth-refresh.controller.ts` - -```typescript -@ApiTags('auth') -@Controller('auth') -@Throttle({ default: { limit: 10, ttl: 60_000 } }) -export class AuthRefreshController { - constructor(private readonly tokenService: AuthTokenService) {} - - @Post('refresh') - @HttpCode(HttpStatus.OK) - @ApiOperation({ summary: 'Exchange a refresh token for a new token pair' }) - @ApiBody({ type: RefreshTokenDto }) - @ApiResponse({ status: 200, type: LoginResponseDto }) - @ApiResponse({ status: 400, description: 'refreshToken is required' }) - @ApiResponse({ status: 401, description: 'Invalid or revoked token' }) - @ApiResponse({ status: 429, description: 'Too many requests' }) - @ApiResponse({ status: 503, description: 'Service temporarily unavailable' }) - async refresh(@Body() dto: RefreshTokenDto): Promise { - return this.tokenService.validateAndRotate(dto.refreshToken); - } -} -``` - -The controller is intentionally thin: it delegates all logic to `AuthTokenService`. The `@HttpCode(HttpStatus.OK)` decorator is necessary because NestJS defaults `@Post` to 201. - ---- - -### 5. `auth.module.ts` — updates - -```typescript -@Module({ - imports: [ - JwtModule.registerAsync({ ... }), // existing - PrismaModule, // existing - CacheModule.registerAsync({ ... }), // ADD — same config as app.module or a shared module - ], - controllers: [ - AuthChallengeController, - AuthVerifyController, - AuthLogoutController, // existing - AuthRefreshController, // ADD - ], - providers: [ - AuthTokenService, // ADD - ], - exports: [JwtModule, AuthTokenService], // export AuthTokenService for potential reuse -}) -export class AuthModule {} -``` - -If `CacheModule` is already imported at the app level as a global module, the import here is optional but explicit and harmless. - ---- - -### 6. `users.service.ts` login() — patch - -After the `refreshToken` is signed, persist it to Redis before returning: - -```typescript -// After signing refreshToken: -const cacheKey = `refresh:${user.walletAddress}`; -const ttlMs = 7 * 24 * 60 * 60 * 1000; // 7 days in milliseconds -await this.cache.set(cacheKey, refreshToken, ttlMs); - -return { accessToken, refreshToken, tokenType: 'Bearer' }; -``` - -`UsersService` will require `@Inject(CACHE_MANAGER) private readonly cache: Keyv` added to its constructor, and `CacheModule` must be imported in `UsersModule`. - ---- - -## Data Models - -No Prisma schema changes are required. All token state lives in Redis under the key scheme `refresh:{walletAddress}`. - -### Redis key structure - -| Key | Value | TTL | Set by | Deleted by | -|-----|-------|-----|--------|------------| -| `refresh:{walletAddress}` | raw refresh JWT string | 7 days (604800s) | `login()` and `validateAndRotate()` | `logout()` and `validateAndRotate()` (old token) | - -### JWT payload structures - -**Access token** (signed with `JWT_SECRET`, expiry `15m`): -```json -{ "sub": "", "walletAddress": "", "role": "", "iat": ..., "exp": ... } -``` - -**Refresh token** (signed with `REFRESH_TOKEN_SECRET`, expiry `7d`): -```json -{ "sub": "", "iat": ..., "exp": ... } -``` - -The refresh token payload is intentionally minimal — only `sub` — to avoid leaking role or wallet information if the token is compromised. - ---- - -## Correctness Properties - -*A property is a characteristic or behavior that should hold true across all valid executions of a system — essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.* - -### Property 1: Empty and whitespace refresh tokens are always rejected - -*For any* string value of `refreshToken` that is empty, composed entirely of whitespace, null, or absent from the request body, the endpoint SHALL return HTTP 400 with the message `"refreshToken is required"`, and the token store SHALL remain unmodified. - -**Validates: Requirements 1.2** - ---- - -### Property 2: Payloads without a valid `sub` claim are always rejected - -*For any* JWT payload where the `sub` claim is null, undefined, an empty string, or a whitespace-only string, the service SHALL return HTTP 401 with the message `"Invalid refresh token"`, regardless of whether the token's signature is otherwise valid. - -**Validates: Requirements 1.7** - ---- - -### Property 3: Token mismatch always produces a revoked response - -*For any* pair of token values where the value stored in Redis at `refresh:{walletAddress}` is not strictly equal (case-sensitive) to the presented `refreshToken`, the endpoint SHALL return HTTP 401 with the message `"Refresh token has been revoked"`. - -**Validates: Requirements 2.6, 6.4** - ---- - -### Property 4: New access token always contains the correct claims - -*For any* user record with a given `id`, `walletAddress`, and `role`, a successful refresh SHALL produce an access token whose decoded payload contains `sub` equal to `user.id`, `walletAddress` equal to `user.walletAddress`, and `role` equal to `user.role`. - -**Validates: Requirements 3.1** - ---- - -### Property 5: New refresh token always embeds the correct `sub` - -*For any* user record, a successful refresh SHALL produce a new refresh token whose decoded payload contains `sub` equal to `user.id`, verifiable with `REFRESH_TOKEN_SECRET`. - -**Validates: Requirements 3.2** - ---- - -### Property 6: Token rotation replaces the stored token - -*For any* successful refresh call, after the call completes the value stored at `refresh:{walletAddress}` SHALL equal the newly issued refresh token, not the token that was presented in the request. The old token SHALL no longer be accepted in a subsequent refresh call. - -**Validates: Requirements 3.3** - ---- - -### Property 7: Successful refresh response always contains all required fields - -*For any* valid refresh request that passes all validation checks, the HTTP 200 response body SHALL contain `accessToken` (non-empty string), `refreshToken` (non-empty string), and `tokenType` equal to `"Bearer"`. - -**Validates: Requirements 3.4** - ---- - -### Property 8: Logout always prevents subsequent token reuse - -*For any* user who has a valid refresh token in the store, after a logout (which deletes `refresh:{walletAddress}`), any subsequent `POST /auth/refresh` presenting that token SHALL return HTTP 401 with the message `"Refresh token has been revoked"`. - -**Validates: Requirements 5.1** - ---- - -### Property 9: Body-only acceptance — non-body token delivery is always rejected - -*For any* refresh token value submitted via URL query parameter or any HTTP header other than `Content-Type`, the endpoint SHALL return HTTP 400 with the message `"refreshToken is required"`, equivalent to omitting the token from the body entirely. - -**Validates: Requirements 6.1** - ---- - -### Property 10: Error responses never leak the refresh token - -*For any* error condition that causes the endpoint to return HTTP 400, 401, or 503, the response body and all response headers SHALL NOT contain the string value of the submitted `refreshToken`. - -**Validates: Requirements 6.2** - ---- - -### Property 11: Revocation response is identical for absent-key and mismatch - -*For any* scenario where the Redis key `refresh:{walletAddress}` is absent AND for any scenario where the stored token differs from the presented token, the endpoint SHALL return HTTP 401 with the message `"Refresh token has been revoked"` — the two scenarios are indistinguishable in the response. - -**Validates: Requirements 6.4** *(consolidates with Property 3; kept separate for explicitness of security intent)* - -**Property reflection note:** Properties 3 and 11 overlap in coverage of Requirements 6.4. Property 3 addresses the token-mismatch case with emphasis on strict equality; Property 11 emphasizes the indistinguishability of absent-key vs mismatch for security. They are retained separately because they test different observable conditions (value inequality vs. key absence) that require distinct test setups, while both validating the same security requirement. - ---- - -## Error Handling - -### Global exception filter - -The NestJS default exception filter maps all `HttpException` subclasses to their status codes. No custom filter is needed. `ServiceUnavailableException` (built-in) produces 503. - -### TokenExpiredError disambiguation - -`JwtService.verify` throws `TokenExpiredError` (a subclass of `JsonWebTokenError`) when the token is structurally valid but expired. `AuthTokenService` must catch this specifically before catching the parent `JsonWebTokenError`: - -```typescript -try { - payload = this.jwt.verify(refreshToken, { secret: refreshSecret }); -} catch (err) { - if (err instanceof TokenExpiredError) { - throw new UnauthorizedException('Refresh token expired'); - } - throw new UnauthorizedException('Invalid refresh token'); -} -``` - -`TokenExpiredError` is exported from the `jsonwebtoken` package (available transitively through `@nestjs/jwt`). - -### Redis / Prisma error wrapping - -All calls to `this.cache.get()`, `this.cache.set()`, `this.cache.delete()`, and `this.prisma.user.findUnique()` are wrapped in try/catch blocks that rethrow as `ServiceUnavailableException("Service temporarily unavailable")`. This ensures the 503 contract is met even for unexpected error shapes. - -### Rate-limit response - -`@nestjs/throttler` v6 automatically injects a `Retry-After` header (in seconds) when a client exceeds the configured limit. No additional code is needed; the `@Throttle({ default: { limit: 10, ttl: 60_000 } })` decorator on the controller is sufficient. - -### Validation errors - -The global `ValidationPipe` (with `whitelist: true, forbidNonWhitelisted: false, transform: true`) produces a `BadRequestException` whose message array is formatted by the default exception filter. To return the exact message string `"refreshToken is required"` rather than the default array, the custom message on `@IsNotEmpty` is used. If the project's global pipe passes the raw message array, an `exceptionFactory` override on the pipe for the auth module can flatten it. - ---- - -## Testing Strategy - -### Unit tests (`*.spec.ts`) - -Co-located with each new file: - -**`auth-token.service.spec.ts`** -- Mock `JwtService`, `ConfigService`, `PrismaService`, `Keyv` -- Example tests: all error paths (invalid signature, expired, no user, inactive user, no Redis entry, Redis write failure) -- Example test: successful flow returns correct DTO structure -- Property tests: Properties 2, 3, 4, 5, 6, 7, 10, 11 (pure service logic, in-memory mocks) - -**`auth-refresh.controller.spec.ts`** -- Mock `AuthTokenService` -- Example tests: delegates to service, correct HTTP status codes, body shape -- Property test: Properties 1, 9 (input validation via `ValidationPipe`) - -**`users.service.spec.ts` (update)** -- Add test asserting that `login()` calls `cache.set("refresh:{walletAddress}", refreshToken, ttlMs)` with the correct arguments - -### Property-based testing - -Library: **[`fast-check`](https://github.com/dubzzz/fast-check)** — mature, TypeScript-native, integrates with Jest/Vitest. - -Minimum **100 runs per property** (fast-check default is 100; set explicitly with `{ numRuns: 100 }`). - -Tag format for each property test: -``` -// Feature: token-refresh-endpoint, Property N: -``` - -**Property test examples:** - -```typescript -// Feature: token-refresh-endpoint, Property 1: Empty and whitespace refresh tokens are always rejected -it('rejects any empty or whitespace refreshToken', async () => { - await fc.assert( - fc.asyncProperty( - fc.oneof(fc.constant(''), fc.string({ minLength: 1 }).map(s => s.replace(/\S/g, ' '))), - async (token) => { - // submit token as body, assert 400 with "refreshToken is required" - }, - ), - { numRuns: 100 }, - ); -}); - -// Feature: token-refresh-endpoint, Property 3: Token mismatch always produces a revoked response -it('returns 401 revoked for any non-matching stored/presented pair', async () => { - await fc.assert( - fc.asyncProperty( - fc.string({ minLength: 1 }), - fc.string({ minLength: 1 }).filter(s => s !== storedToken), - async (stored, presented) => { - mockCache.get.mockResolvedValue(stored); - // submit presented, assert 401 "Refresh token has been revoked" - }, - ), - { numRuns: 100 }, - ); -}); -``` - -### Integration tests - -- Fire 11 rapid `POST /auth/refresh` requests to the running server; assert the 11th returns 429 with `Retry-After` header (Requirement 4.2) -- Full login → refresh → refresh chain to verify token rotation works end-to-end with a real Redis instance -- Login → logout → refresh to verify revocation end-to-end (Requirement 5.1) - -### Test coverage targets - -| Layer | Target | -|-------|--------| -| `AuthTokenService` — branch coverage | ≥ 95% | -| `AuthRefreshController` | ≥ 90% | -| `users.service.ts` login() Redis path | 100% (new lines) | -| Property tests | 100% of correctness properties have at least one property-based test | diff --git a/.kiro/specs/token-refresh-endpoint/requirements.md b/.kiro/specs/token-refresh-endpoint/requirements.md deleted file mode 100644 index 77b9d546..00000000 --- a/.kiro/specs/token-refresh-endpoint/requirements.md +++ /dev/null @@ -1,102 +0,0 @@ -# Requirements Document - -## Introduction - -This feature adds a `POST /auth/refresh` endpoint to the NestJS API that allows clients to obtain a new JWT access token using a valid refresh token, without requiring re-authentication. The endpoint integrates with the existing Redis-based refresh token store (keyed by `refresh:{walletAddress}`), the existing `REFRESH_TOKEN_SECRET`-signed refresh tokens issued at login, and the existing `JWT_SECRET`-signed 15-minute access tokens. Token rotation is applied on every successful refresh: the old refresh token is invalidated and a new one is issued, reducing the exposure window of any individual refresh token. - -## Glossary - -- **Refresh_Endpoint**: The `POST /auth/refresh` HTTP handler introduced by this feature. -- **Access_Token**: A short-lived JWT signed with `JWT_SECRET`, valid for 15 minutes, used to authenticate API calls. -- **Refresh_Token**: A longer-lived JWT signed with `REFRESH_TOKEN_SECRET`, valid for 7 days, used solely to obtain new Access_Tokens. -- **Token_Store**: The Redis cache managed via `@nestjs/cache-manager` (Keyv), where the current valid Refresh_Token for each user is stored under the key `refresh:{walletAddress}`. -- **Token_Rotation**: The strategy of invalidating the presented Refresh_Token and issuing a new Refresh_Token on each successful refresh call. -- **Refresh_Token_Service**: The service-layer class responsible for validating Refresh_Tokens and issuing new token pairs. -- **JWT_Payload**: The decoded claims object extracted from a verified JWT; for Access_Tokens it contains `sub` (user ID), `walletAddress`, and `role`; for Refresh_Tokens it contains `sub` (user ID). -- **walletAddress**: The Stellar Ed25519 public key that uniquely identifies a user in the system. -- **Throttler**: The NestJS rate-limiter guard applied to authentication endpoints, configured at 10 requests per 60-second fixed window per client IP. - ---- - -## Requirements - -### Requirement 1: Accept and Validate the Refresh Token - -**User Story:** As an authenticated client, I want to submit my refresh token to `POST /auth/refresh`, so that the system can verify it before issuing new tokens. - -#### Acceptance Criteria - -1. WHEN a `POST /auth/refresh` request is received with a JSON body containing a non-empty `refreshToken` string field, THE Refresh_Endpoint SHALL forward the token to the Refresh_Token_Service for signature verification. -2. IF the request body is not valid JSON, or the `refreshToken` field is missing, null, or an empty string, THEN THE Refresh_Endpoint SHALL return HTTP 400 with an error message of `"refreshToken is required"`. -3. WHEN the Refresh_Endpoint receives a `refreshToken` value, THE Refresh_Token_Service SHALL verify the token's signature using `REFRESH_TOKEN_SECRET`. -4. IF the `refreshToken` signature verification fails, THEN THE Refresh_Endpoint SHALL return HTTP 401 with an error message of `"Invalid refresh token"`. -5. IF the `refreshToken` is expired, THEN THE Refresh_Endpoint SHALL return HTTP 401 with an error message of `"Refresh token expired"`. -6. WHEN the Refresh_Token_Service successfully verifies the token signature, THE Refresh_Token_Service SHALL extract the `sub` (user ID) claim from the JWT_Payload. -7. IF the `refreshToken` passes signature verification but the decoded JWT_Payload does not contain a non-empty `sub` claim, THEN THE Refresh_Endpoint SHALL return HTTP 401 with an error message of `"Invalid refresh token"`. - ---- - -### Requirement 2: Validate Token Against the Token Store - -**User Story:** As a system operator, I want the refresh endpoint to cross-check the presented token against the Token_Store, so that revoked or rotated-out tokens cannot be reused. - -#### Acceptance Criteria - -1. WHEN the Refresh_Token_Service has extracted the `sub` claim from a valid Refresh_Token, THE Refresh_Token_Service SHALL look up the corresponding user record in the database using `sub` to obtain the user's `walletAddress` and account status. -2. IF no user record is found for the `sub` claim, THEN THE Refresh_Endpoint SHALL return HTTP 401 with an error message of `"Invalid refresh token"`. -3. IF the user's `isActive` field is `false`, THEN THE Refresh_Endpoint SHALL return HTTP 401 with an error message of `"Account is inactive"`. -4. IF the user record is found and `isActive` is `true`, THEN THE Refresh_Token_Service SHALL retrieve the stored token value from the Token_Store at key `refresh:{walletAddress}`. -5. IF no entry exists in the Token_Store at `refresh:{walletAddress}`, THEN THE Refresh_Endpoint SHALL return HTTP 401 with an error message of `"Refresh token has been revoked"`. -6. IF the stored token value does not exactly match the presented `refreshToken` using case-sensitive string equality, THEN THE Refresh_Endpoint SHALL return HTTP 401 with an error message of `"Refresh token has been revoked"`. -7. IF the database is unavailable during user record lookup, THEN THE Refresh_Endpoint SHALL return HTTP 503 with an error message of `"Service temporarily unavailable"`. -8. IF the Token_Store is unavailable during token retrieval, THEN THE Refresh_Endpoint SHALL return HTTP 503 with an error message of `"Service temporarily unavailable"`. - ---- - -### Requirement 3: Issue a New Token Pair with Token Rotation - -**User Story:** As an authenticated client, I want to receive a fresh access token and a rotated refresh token on a successful refresh, so that my session remains active without re-authenticating. - -#### Acceptance Criteria - -1. WHEN the presented Refresh_Token passes all validation checks, THE Refresh_Token_Service SHALL sign a new Access_Token JWT with `JWT_SECRET`, embedding the claims `sub`, `walletAddress`, and `role` (fetched from the user record), with an expiry of 15 minutes. -2. WHEN the presented Refresh_Token passes all validation checks, THE Refresh_Token_Service SHALL sign a new Refresh_Token JWT with `REFRESH_TOKEN_SECRET`, embedding the `sub` claim, with an expiry of 7 days. -3. WHEN a new Refresh_Token is signed, THE Refresh_Token_Service SHALL atomically delete the old `refresh:{walletAddress}` entry from the Token_Store and store the new Refresh_Token value under the same key with a TTL of 7 days. -4. WHEN the Token_Store update completes successfully, THE Refresh_Endpoint SHALL return HTTP 200 with a JSON body containing `accessToken`, `refreshToken`, and `tokenType: "Bearer"`. -5. IF the Token_Store rotation write fails, THEN THE Refresh_Endpoint SHALL return HTTP 503 with an error message of `"Service temporarily unavailable"` and SHALL NOT return the new token pair. -6. IF the Token_Store rotation write fails, THEN THE Refresh_Endpoint SHALL NOT return the new token pair. - ---- - -### Requirement 4: Rate Limiting - -**User Story:** As a system operator, I want the refresh endpoint to be rate-limited, so that brute-force attempts to guess valid refresh tokens are mitigated. - -#### Acceptance Criteria - -1. THE Refresh_Endpoint SHALL apply the Throttler guard configured at a maximum of 10 requests per 60-second fixed window per client IP. -2. WHEN a client exceeds 10 `POST /auth/refresh` requests within a 60-second fixed window, THE Refresh_Endpoint SHALL return HTTP 429 with a `Retry-After` header indicating the number of seconds until the window resets. - ---- - -### Requirement 5: Compatibility with Logout - -**User Story:** As an authenticated client, I want my refresh token to be permanently invalidated after logout, so that a logged-out refresh token cannot be used to obtain new access tokens. - -#### Acceptance Criteria - -1. WHEN a user logs out via `POST /auth/logout`, THE logout handler SHALL delete the `refresh:{walletAddress}` key from the Token_Store, so that any subsequent `POST /auth/refresh` request presenting the pre-logout Refresh_Token returns HTTP 401 with the message `"Refresh token has been revoked"`. -2. IF the `refresh:{walletAddress}` key is absent from the Token_Store, THEN THE Refresh_Endpoint SHALL return HTTP 401 with the message `"Refresh token has been revoked"`, with no fallback path to token issuance. - ---- - -### Requirement 6: Security Hardening - -**User Story:** As a security engineer, I want the refresh endpoint to follow secure token-handling practices, so that token theft and reuse attacks are minimised. - -#### Acceptance Criteria - -1. THE Refresh_Endpoint SHALL accept the refresh token exclusively in the JSON request body. IF the refresh token is submitted as a URL query parameter or in any HTTP header other than `Content-Type`, THEN THE Refresh_Endpoint SHALL return HTTP 400 with an error message of `"refreshToken is required"`. -2. THE Refresh_Endpoint SHALL NOT include any Refresh_Token value in response bodies, response headers, or server-side logs. -3. WHEN any error condition causes the Refresh_Endpoint to return HTTP 400, 401, or 503, THE Refresh_Endpoint SHALL return the error response within 500 milliseconds. -4. THE Refresh_Endpoint SHALL return HTTP 401 with the message `"Refresh token has been revoked"` for both the absent-key and token-mismatch scenarios, without distinguishing which condition triggered the response. diff --git a/.kiro/specs/token-refresh-endpoint/tasks.md b/.kiro/specs/token-refresh-endpoint/tasks.md deleted file mode 100644 index 255e0e56..00000000 --- a/.kiro/specs/token-refresh-endpoint/tasks.md +++ /dev/null @@ -1,198 +0,0 @@ -# Implementation Plan: Token Refresh Endpoint - -## Overview - -Implement `POST /auth/refresh` by introducing `RefreshTokenDto`, `AuthTokenService`, and `AuthRefreshController`, wiring them into `auth.module.ts`, patching `users.service.ts` login() to persist the refresh token in Redis, and covering everything with unit and property-based tests. - ---- - -## Tasks - -- [ ] 1. Create `RefreshTokenDto` request body DTO - - [ ] 1.1 Create `ignition-api/src/auth/dto/refresh-token.dto.ts` - - Export `RefreshTokenDto` with a single `refreshToken: string` field - - Decorate with `@IsString()` and `@IsNotEmpty({ message: 'refreshToken is required' })` - - Add `@ApiProperty` decorator with description matching the design doc - - _Requirements: 1.1, 1.2_ - - - [ ]* 1.2 Write unit tests for `RefreshTokenDto` validation - - Verify that empty string, whitespace-only string, and missing field each produce a validation error with message `"refreshToken is required"` - - Verify that a non-empty string passes validation - - _Requirements: 1.2_ - -- [ ] 2. Implement `AuthTokenService` - - [ ] 2.1 Create `ignition-api/src/auth/auth-token.service.ts` with constructor and skeleton - - Declare `@Injectable() AuthTokenService` class - - Inject `JwtService`, `ConfigService`, `PrismaService`, and `@Inject(CACHE_MANAGER) cache: Keyv` in the constructor - - Declare `async validateAndRotate(refreshToken: string): Promise` stub - - _Requirements: 1.3, 2.1, 3.1_ - - - [ ] 2.2 Implement JWT verification step in `validateAndRotate` - - Call `this.jwt.verify(refreshToken, { secret: REFRESH_TOKEN_SECRET })` - - Catch `TokenExpiredError` and throw `UnauthorizedException('Refresh token expired')` - - Catch all other JWT errors and throw `UnauthorizedException('Invalid refresh token')` - - Extract `sub` from payload; throw `UnauthorizedException('Invalid refresh token')` if `sub` is falsy or empty - - _Requirements: 1.3, 1.4, 1.5, 1.6, 1.7_ - - - [ ]* 2.3 Write property test for `validateAndRotate` — Property 2 (invalid `sub` claim) - - **Property 2: Payloads without a valid `sub` claim are always rejected** - - **Validates: Requirements 1.7** - - Use `fast-check` to generate JWT payloads where `sub` is `null`, `undefined`, `""`, or whitespace; assert HTTP 401 with `"Invalid refresh token"` for each - - Tag: `// Feature: token-refresh-endpoint, Property 2` - - _Requirements: 1.7_ - - - [ ] 2.4 Implement Prisma user lookup and `isActive` check in `validateAndRotate` - - Call `this.prisma.user.findUnique({ where: { id: sub } })` - - Wrap in try/catch; rethrow any Prisma error as `ServiceUnavailableException('Service temporarily unavailable')` - - If no user found, throw `UnauthorizedException('Invalid refresh token')` - - If `user.isActive === false`, throw `UnauthorizedException('Account is inactive')` - - _Requirements: 2.1, 2.2, 2.3, 2.7_ - - - [ ] 2.5 Implement Redis token retrieval and comparison in `validateAndRotate` - - Call `this.cache.get(`refresh:${user.walletAddress}`)` inside a try/catch - - Rethrow any Keyv error as `ServiceUnavailableException('Service temporarily unavailable')` - - If result is falsy, throw `UnauthorizedException('Refresh token has been revoked')` - - If stored value does not strictly equal the presented token (case-sensitive), throw `UnauthorizedException('Refresh token has been revoked')` - - _Requirements: 2.4, 2.5, 2.6, 2.8_ - - - [ ]* 2.6 Write property test for `validateAndRotate` — Property 3 / Property 11 (token mismatch and absent key indistinguishable) - - **Property 3: Token mismatch always produces a revoked response** - - **Property 11: Revocation response is identical for absent-key and mismatch** - - **Validates: Requirements 2.6, 6.4** - - Use `fast-check` to generate arbitrary `(stored, presented)` pairs where `presented !== stored`; assert 401 with `"Refresh token has been revoked"` for both absent-key and value-mismatch scenarios - - Tag: `// Feature: token-refresh-endpoint, Property 3` and `// Feature: token-refresh-endpoint, Property 11` - - _Requirements: 2.5, 2.6, 6.4_ - - - [ ] 2.7 Implement new token signing and Redis rotation in `validateAndRotate` - - Sign a new access token using `JWT_SECRET` with claims `sub`, `walletAddress`, `role` (from user record), expiry `15m` - - Sign a new refresh token using `REFRESH_TOKEN_SECRET` with claim `sub`, expiry `7d` - - Delete the old Redis key with `this.cache.delete(`refresh:${user.walletAddress}`)` - - Set the new token with `this.cache.set(`refresh:${user.walletAddress}`, newRefreshToken, 604800000)` - - Wrap both Redis operations in try/catch; rethrow as `ServiceUnavailableException('Service temporarily unavailable')` on failure - - Return `{ accessToken, refreshToken: newRefreshToken, tokenType: 'Bearer' }` - - _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 3.6_ - - - [ ]* 2.8 Write property test for `validateAndRotate` — Property 4 (access token claims) - - **Property 4: New access token always contains the correct claims** - - **Validates: Requirements 3.1** - - Use `fast-check` to generate arbitrary user objects with `id`, `walletAddress`, `role`; assert decoded access token has matching `sub`, `walletAddress`, `role` - - Tag: `// Feature: token-refresh-endpoint, Property 4` - - _Requirements: 3.1_ - - - [ ]* 2.9 Write property test for `validateAndRotate` — Property 5 (refresh token `sub` claim) - - **Property 5: New refresh token always embeds the correct `sub`** - - **Validates: Requirements 3.2** - - Use `fast-check` to generate arbitrary user IDs; assert decoded refresh token `sub` equals `user.id` - - Tag: `// Feature: token-refresh-endpoint, Property 5` - - _Requirements: 3.2_ - - - [ ]* 2.10 Write property test for `validateAndRotate` — Property 6 (rotation replaces stored token) - - **Property 6: Token rotation replaces the stored token** - - **Validates: Requirements 3.3** - - Use `fast-check` to generate an initial valid token; after a successful `validateAndRotate`, assert the cache holds the new token and the old token is rejected on a second call - - Tag: `// Feature: token-refresh-endpoint, Property 6` - - _Requirements: 3.3_ - - - [ ]* 2.11 Write property test for `validateAndRotate` — Property 7 (successful response shape) - - **Property 7: Successful refresh response always contains all required fields** - - **Validates: Requirements 3.4** - - Use `fast-check` to generate arbitrary valid inputs; assert response has non-empty `accessToken`, non-empty `refreshToken`, and `tokenType === 'Bearer'` - - Tag: `// Feature: token-refresh-endpoint, Property 7` - - _Requirements: 3.4_ - - - [ ]* 2.12 Write property test for `validateAndRotate` — Property 10 (error responses never leak token) - - **Property 10: Error responses never leak the refresh token** - - **Validates: Requirements 6.2** - - Use `fast-check` to generate arbitrary tokens and trigger every error path (400, 401, 503); assert response body and headers do not contain the submitted `refreshToken` string - - Tag: `// Feature: token-refresh-endpoint, Property 10` - - _Requirements: 6.2_ - -- [ ] 3. Checkpoint — Ensure `AuthTokenService` unit tests pass - - Ensure all tests pass, ask the user if questions arise. - -- [ ] 4. Create `AuthRefreshController` - - [ ] 4.1 Create `ignition-api/src/auth/auth-refresh.controller.ts` - - Declare `@ApiTags('auth') @Controller('auth') @Throttle({ default: { limit: 10, ttl: 60_000 } }) AuthRefreshController` class - - Inject `AuthTokenService` via constructor - - Implement `@Post('refresh') @HttpCode(HttpStatus.OK)` method that accepts `@Body() dto: RefreshTokenDto` and returns `this.tokenService.validateAndRotate(dto.refreshToken)` - - Add `@ApiOperation`, `@ApiBody`, and all `@ApiResponse` decorators (200, 400, 401, 429, 503) - - _Requirements: 1.1, 1.2, 3.4, 4.1, 4.2_ - - - [ ]* 4.2 Write unit tests for `AuthRefreshController` — `auth-refresh.controller.spec.ts` - - Mock `AuthTokenService`; assert `POST /auth/refresh` returns HTTP 200 and the value from `validateAndRotate` - - Assert that a missing or empty `refreshToken` body field returns HTTP 400 via `ValidationPipe` - - _Requirements: 1.1, 1.2, 3.4_ - - - [ ]* 4.3 Write property test for `AuthRefreshController` — Property 1 (empty/whitespace tokens rejected) - - **Property 1: Empty and whitespace refresh tokens are always rejected** - - **Validates: Requirements 1.2** - - Use `fast-check` to generate empty strings and whitespace-only strings; submit via body and assert HTTP 400 with `"refreshToken is required"` - - Tag: `// Feature: token-refresh-endpoint, Property 1` - - _Requirements: 1.2_ - - - [ ]* 4.4 Write property test for `AuthRefreshController` — Property 9 (body-only acceptance) - - **Property 9: Body-only acceptance — non-body token delivery is always rejected** - - **Validates: Requirements 6.1** - - Use `fast-check` to generate arbitrary token strings and submit them as URL query parameters or custom headers; assert HTTP 400 with `"refreshToken is required"` - - Tag: `// Feature: token-refresh-endpoint, Property 9` - - _Requirements: 6.1_ - -- [ ] 5. Patch `users.service.ts` login() to persist refresh token in Redis - - [ ] 5.1 Inject `CACHE_MANAGER` into `UsersService` and store refresh token after login - - Add `@Inject(CACHE_MANAGER) private readonly cache: Keyv` to `UsersService` constructor - - After `refreshToken = this.jwt.sign(...)` in `login()`, add: `await this.cache.set(`refresh:${user.walletAddress}`, refreshToken, 604800000)` - - _Requirements: 2.4, 3.3_ - - - [ ]* 5.2 Update `users.service.spec.ts` to cover the new Redis `cache.set` call - - Mock `CACHE_MANAGER`; assert `login()` calls `cache.set('refresh:{walletAddress}', refreshToken, 604800000)` with the exact correct arguments - - _Requirements: 2.4_ - -- [ ] 6. Wire everything into `auth.module.ts` - - [ ] 6.1 Update `ignition-api/src/auth/auth.module.ts` - - Import `CacheModule.registerAsync(...)` in the `imports` array (use the same config pattern as the app module or shared cache config) - - Add `AuthLogoutController` and `AuthRefreshController` to the `controllers` array - - Add `AuthTokenService` to the `providers` array - - Export `AuthTokenService` alongside `JwtModule` in the `exports` array - - _Requirements: 1.1, 4.1_ - -- [ ] 7. Checkpoint — Ensure all tests pass and module compiles cleanly - - Ensure all tests pass, ask the user if questions arise. - - - [ ]* 7.1 Write property test for logout → refresh flow — Property 8 - - **Property 8: Logout always prevents subsequent token reuse** - - **Validates: Requirements 5.1** - - Use `fast-check` to generate arbitrary wallet addresses and refresh tokens; after simulating logout (delete from cache mock), assert that a subsequent `validateAndRotate` call returns 401 with `"Refresh token has been revoked"` - - Tag: `// Feature: token-refresh-endpoint, Property 8` - - _Requirements: 5.1_ - ---- - -## Notes - -- Tasks marked with `*` are optional and can be skipped for faster MVP -- Each task references specific requirements for traceability -- Property-based tests use `fast-check` with `{ numRuns: 100 }` — tag each test with `// Feature: token-refresh-endpoint, Property N` -- `Keyv` is imported from `keyv`; `CACHE_MANAGER` from `@nestjs/cache-manager` -- `TokenExpiredError` is imported from `jsonwebtoken` (available via `@nestjs/jwt`) -- The `LoginResponseDto` from `users/dto/login.dto.ts` is reused as the response type for `AuthRefreshController` -- Redis TTL is always provided in milliseconds to Keyv (`604800000` = 7 days) -- `auth.module.ts` currently has no `AuthLogoutController` registered — it must be added in task 6.1 alongside `AuthRefreshController` - ---- - -## Task Dependency Graph - -```json -{ - "waves": [ - { "id": 0, "tasks": ["1.1"] }, - { "id": 1, "tasks": ["1.2", "2.1"] }, - { "id": 2, "tasks": ["2.2", "2.4"] }, - { "id": 3, "tasks": ["2.3", "2.5"] }, - { "id": 4, "tasks": ["2.6", "2.7"] }, - { "id": 5, "tasks": ["2.8", "2.9", "2.10", "2.11", "2.12", "4.1", "5.1"] }, - { "id": 6, "tasks": ["4.2", "4.3", "4.4", "5.2", "6.1"] }, - { "id": 7, "tasks": ["7.1"] } - ] -} -``` diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 2457acb9..00000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "recommendations": [ - "Dart-Code.dart-code", - "Dart-Code.flutter" - ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 5480842b..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "kiroAgent.configureMCP": "Disabled" -} \ No newline at end of file diff --git a/docs/android-setup.md b/docs/android-setup.md deleted file mode 100644 index b6b574cd..00000000 --- a/docs/android-setup.md +++ /dev/null @@ -1,136 +0,0 @@ -# Android Development Environment Setup - -## Prerequisites - -- macOS (Apple Silicon or Intel) or Linux (x86_64) -- At least 8GB RAM (16GB recommended for emulator) -- 10GB free disk space for SDK and emulator images -- Git installed - -## Quick Start - -Run the automated setup script: - -```bash -bash scripts/setup-android.sh -``` - -The script will: - -1. Check for Android Studio installation -2. Install the Android SDK command-line tools -3. Download required SDK platforms (34, 35) and build tools -4. Install Gradle build system -5. Create a Pixel 6 Pro emulator (API 34) -6. Configure environment variables -7. Create local.properties for the Flutter project - -## Manual Setup - -### Step 1: Install Android Studio - -Download and install [Android Studio](https://developer.android.com/studio). - -**macOS:** -```bash -brew install --cask android-studio -``` - -**Linux:** -```bash -# Download from https://developer.android.com/studio -# Extract to /usr/local/android-studio -``` - -Launch Android Studio and complete the setup wizard. - -### Step 2: Install SDK Platforms - -Using SDK Manager (Tools > SDK Manager), install: - -| Package | Version | -|---------|---------| -| Android SDK Platform 34 | android-34 | -| Android SDK Platform 35 | android-35 | -| Android SDK Build-Tools | 34.0.0, 35.0.0 | -| Android SDK Platform-Tools | latest | -| Android Emulator | latest | - -Or via command line: - -```bash -sdkmanager "platforms;android-34" "platforms;android-35" \ - "build-tools;34.0.0" "build-tools;35.0.0" \ - "platform-tools" "emulator" -``` - -### Step 3: Install Gradle - -**macOS:** -```bash -brew install gradle -``` - -**Linux:** -```bash -# Download from https://gradle.org/releases/ -# Extract and add to PATH -``` - -### Step 4: Create Emulator - -```bash -avdmanager create avd \ - -n "Pixel_6_Pro_API_34" \ - -k "system-images;android-34;google_apis;x86_64" \ - -d "pixel_6_pro" -``` - -### Step 5: Configure Environment - -Add to shell config: - -```bash -export ANDROID_HOME=$HOME/Library/Android/sdk # macOS -# export ANDROID_HOME=$HOME/Android/Sdk # Linux -export PATH="$PATH:$ANDROID_HOME/emulator" -export PATH="$PATH:$ANDROID_HOME/platform-tools" -export PATH="$PATH:$ANDROID_HOME/cmdline-tools/latest/bin" -``` - -### Step 6: Flutter Project Config - -Create `ignition-mobile/android/local.properties`: - -```properties -sdk.dir=/Users//Library/Android/sdk -``` - -### Step 7: Verify - -```bash -flutter doctor -emulator -avd Pixel_6_Pro_API_34 -cd ignition-mobile && flutter run -``` - -## Troubleshooting - -| Issue | Solution | -|-------|----------| -| flutter doctor shows no Android SDK | Set ANDROID_HOME environment variable | -| Emulator fails to start | Enable hardware virtualization in BIOS | -| Gradle build fails | Run cd android && ./gradlew clean | -| SDK license not accepted | Run sdkmanager --licenses | -| Command line tools not found | Install cmdline-tools via SDK Manager | - -## Required API Levels - -- **API 34** (Android 14) -- current target -- **API 35** (Android 15) -- future target - -## Resources - -- [Android Studio Documentation](https://developer.android.com/studio/intro) -- [Flutter Android Setup](https://docs.flutter.dev/get-started/install) -- [Gradle User Guide](https://docs.gradle.org/current/userguide/userguide.html) diff --git a/docs/api-reference/extract-routing.mdx b/docs/api-reference/extract-routing.mdx deleted file mode 100644 index 6acef8b9..00000000 --- a/docs/api-reference/extract-routing.mdx +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: "extractRouting" -description: "The primary API for deposit routing." ---- - -`extractRouting` is the core function of the library. It reconciles an address and memo into a canonical routing result. - -## Function Signatures - - -```typescript TypeScript -function extractRouting(input: RoutingInput): RoutingResult -``` - -```go Go -func ExtractRouting(input RoutingInput) RoutingResult -``` - -```dart Dart -RoutingResult extractRouting(RoutingInput input) -``` - - -## Input: `RoutingInput` - -| Field | Type | Description | -|---|---|---| -| `address` | `string` | The destination address (G, M, or C). | -| `memoType` | `string` | (Optional) `ID`, `TEXT`, `HASH`, or `RETURN`. | -| `memoValue` | `string` | (Optional) The raw memo value. | - -## Output: `RoutingResult` - -| Field | Type | Description | -|---|---|---| -| `address` | `string` | The canonical base G-address. | -| `routingId` | `string` | The extracted 64-bit ID (or empty string). | -| `warnings` | `Warning[]` | List of warnings encountered. | -| `destinationError` | `string` | Error code if the destination is invalid. | diff --git a/docs/api-reference/parse.mdx b/docs/api-reference/parse.mdx deleted file mode 100644 index 9ea95b57..00000000 --- a/docs/api-reference/parse.mdx +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: "parse" -description: "High-level address parsing." ---- - -The `parse` function identifies the type of address and extracts any embedded data. - -## Usage - - -```typescript TypeScript -import { parse } from 'stellar-address-kit'; - -const result = parse("MA7Q...QD"); -// { type: 'MUXED', baseAddress: 'GA7Q...', id: '123' } -``` - -```dart Dart -// Dart includes a parseOrThrow variant for convenience -final result = parseOrThrow("MA7Q...QD"); -``` - - -## Supported Types -- `CLASSIC` (G...) -- `MUXED` (M...) -- `CONTRACT` (C...) diff --git a/docs/api-reference/types.mdx b/docs/api-reference/types.mdx deleted file mode 100644 index c9147c74..00000000 --- a/docs/api-reference/types.mdx +++ /dev/null @@ -1,40 +0,0 @@ ---- -title: "Types" -description: "Data structures used throughout the kit." ---- - -## RoutingResult - -The object returned by `extractRouting`. - -```typescript -interface RoutingResult { - address: string; // Canonical G-address - routingId: string; // 64-bit ID string - warnings: Warning[]; // List of non-fatal issues - destinationError: string; // Error code for fatal issues -} -``` - -## Warning - -A discriminated union (in TS) or object (Go/Dart) describing a non-fatal issue. - -```typescript -type Warning = { - code: string; // WarningCode - severity: 'INFO' | 'WARN' | 'ERROR'; - message: string; -} -``` - -## ErrorCode (Fatal) -- `INVALID_CHECKSUM` -- `INVALID_DESTINATION` (e.g. C-address) -- `UNSUPPORTED_MEMO_TYPE` - -## WarningCode (Non-fatal) -- `REDUNDANT_MEMO` -- `MEMO_TYPE_MISMATCH` -- `NON_ROUTABLE_MEMO` -- `JS_PRECISION_LOSS` diff --git a/docs/architecture/README.md b/docs/architecture/README.md deleted file mode 100644 index 410108ac..00000000 --- a/docs/architecture/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Architecture - -This directory contains architecture documentation for the Ignition Pay platform. - -## Contents - -- [System Overview](./system-overview.md) — High-level architecture, component relationships, and data flow -- [Frontend Architecture](./frontend-architecture.md) — Next.js frontend structure and patterns -- [API Architecture](./api-architecture.md) — NestJS API layer design -- [Mobile Architecture](./mobile-architecture.md) — Flutter mobile app architecture - -## Key Principles - -- **Separation of concerns**: Each layer (frontend, API, mobile) owns one responsibility -- **SEP compliance**: All Stellar Ecosystem Proposals are implemented as first-class citizens -- **Type safety**: TypeScript (web) and Dart (mobile/API) provide end-to-end type safety -- **Non-custodial**: Users control their private keys at all times diff --git a/docs/architecture/api-architecture.md b/docs/architecture/api-architecture.md deleted file mode 100644 index 31a48a3d..00000000 --- a/docs/architecture/api-architecture.md +++ /dev/null @@ -1,61 +0,0 @@ -# API Architecture - -## Stack - -- **Framework**: NestJS 11 -- **Language**: TypeScript 5.7 -- **Database**: PostgreSQL via Prisma ORM -- **Caching**: Redis via Keyv / cache-manager -- **Queue**: Bull (Redis-backed job queue) -- **Blockchain**: Stellar SDK + Soroban RPC - -## Module Structure - -``` -src/ -├── auth/ # Authentication (JWT, SEP-10) -├── users/ # User management -├── wallets/ # Wallet operations -├── transactions/ # Transaction processing -├── addresses/ # Address validation & routing -├── api-keys/ # API key management -├── campaigns/ # Campaign/governance features -├── health/ # Health checks -├── common/ # Shared utilities, guards, interceptors -├── config/ # Configuration modules -├── queue/ # Background job processing -├── redis/ # Redis cache layer -├── session/ # Session management -├── throttler/ # Rate limiting -└── prisma/ # Database schema and migrations -``` - -## Key Modules - -### Auth Module -- Stellar SEP-10 Web Authentication -- JWT access + refresh token flow -- Optional biometric challenge - -### Wallets Module -- Create and manage Stellar keypairs -- Encrypted private key storage -- Balance queries via Horizon - -### Transactions Module -- Build and submit Stellar transactions -- Support for traditional, muxed, and contract addresses -- Transaction history with pagination - -### Queue Module -- Background processing via Bull -- Transaction status polling -- Anchor deposit/withdrawal webhooks - -## API Design - -- RESTful endpoints with NestJS controllers -- Swagger documentation via `@nestjs/swagger` -- WebSocket gateway for real-time updates -- Rate limiting via `@nestjs/throttler` -- Validation via `class-validator` DTOs diff --git a/docs/architecture/frontend-architecture.md b/docs/architecture/frontend-architecture.md deleted file mode 100644 index b4981d71..00000000 --- a/docs/architecture/frontend-architecture.md +++ /dev/null @@ -1,62 +0,0 @@ -# Frontend Architecture - -## Stack - -- **Framework**: Next.js 16 with App Router -- **Language**: TypeScript 5.7 -- **Styling**: Tailwind CSS v4 + shadcn/ui (base-nova style) -- **Icons**: Lucide React -- **UI Primitives**: Base UI React - -## Directory Structure - -``` -ignition-pay-frontend/ -├── app/ # Next.js App Router pages -│ ├── dashboard/ # Main dashboard -│ ├── send/ # Send payment flow -│ ├── receive/ # Receive payment flow -│ ├── history/ # Transaction history -│ ├── anchors/ # Anchor management -│ ├── settings/ # User settings -│ └── globals.css # Global styles / theme variables -├── components/ # Shared React components -│ ├── ui/ # shadcn/ui primitives -│ ├── wallet-card.tsx # Wallet overview card -│ ├── asset-card.tsx # Asset display card -│ ├── transaction-row.tsx # Transaction list row -│ ├── navigation.tsx # Sidebar / mobile nav -│ └── app-wrapper.tsx # Root layout wrapper -└── lib/ - ├── utils.ts # cn() utility - └── theme.ts # Theme constants & types -``` - -## Routing - -The app uses Next.js App Router with the following routes: - -| Route | Page | Description | -|-------|------|-------------| -| `/` | Redirect | Redirects to `/dashboard` | -| `/dashboard` | DashboardPage | Main wallet overview | -| `/send` | SendPage | Send assets form | -| `/receive` | ReceivePage | Receive address/QR | -| `/history` | HistoryPage | Transaction history | -| `/anchors` | AnchorsPage | Anchor integrations | -| `/settings` | SettingsPage | Account & app settings | - -## Component Patterns - -- Pages use `'use client'` for interactivity -- Shared components are in `components/` -- UI primitives (button, input, etc.) are in `components/ui/` -- CSS is handled via Tailwind utility classes and CSS custom properties -- The `cn()` utility merges Tailwind classes with `clsx` + `tailwind-merge` - -## Theming - -- CSS custom properties in `globals.css` define the color palette -- Light and dark modes via `.dark` class on `` -- Theme is toggled by adding/removing the `.dark` class -- All colors use OKLCH color space for perceptually uniform gradients diff --git a/docs/architecture/mobile-architecture.md b/docs/architecture/mobile-architecture.md deleted file mode 100644 index d5bdef83..00000000 --- a/docs/architecture/mobile-architecture.md +++ /dev/null @@ -1,55 +0,0 @@ -# Mobile Architecture - -## Stack - -- **Framework**: Flutter 3.22+ -- **Language**: Dart 3.4+ -- **State Management**: Provider / Riverpod -- **API Client**: `http` / `dio` for REST calls -- **Local Storage**: `shared_preferences`, `drift` (SQLite) - -## Directory Structure - -``` -lib/ -├── main.dart # App entry point -├── app.dart # Material app configuration -├── core/ # Core utilities -│ ├── constants.dart # App-wide constants -│ ├── theme/ # Theme configuration -│ ├── network/ # API client setup -│ └── storage/ # Local storage helpers -├── config/ # Environment config -└── features/ # Feature modules - ├── home/ # Home/wallet screen - ├── send/ # Send flow - ├── receive/ # Receive flow - ├── history/ # Transaction history - ├── settings/ # Settings screen - └── auth/ # Authentication -``` - -## Architecture Pattern - -The mobile app follows a feature-first architecture: - -- Each feature is self-contained with its own models, services, and UI -- Shared code lives in `core/` -- The API client in `core/network` handles all backend communication -- State is managed with Providers, scoped per feature - -## Platform Features - -- **Biometric auth**: Fingerprint / Face ID via `local_auth` -- **Deep linking**: Handle payment request URLs -- **Push notifications**: Firebase Cloud Messaging -- **QR scanning**: Scan Stellar addresses with `mobile_scanner` -- **Secure storage**: FlutterSecureStorage for keys - -## Build Targets - -| Target | Command | -|--------|---------| -| Android | `flutter build apk` | -| iOS | `flutter build ios` | -| Web | `flutter build web` | diff --git a/docs/architecture/standard.md b/docs/architecture/standard.md deleted file mode 100644 index b4981d71..00000000 --- a/docs/architecture/standard.md +++ /dev/null @@ -1,62 +0,0 @@ -# Frontend Architecture - -## Stack - -- **Framework**: Next.js 16 with App Router -- **Language**: TypeScript 5.7 -- **Styling**: Tailwind CSS v4 + shadcn/ui (base-nova style) -- **Icons**: Lucide React -- **UI Primitives**: Base UI React - -## Directory Structure - -``` -ignition-pay-frontend/ -├── app/ # Next.js App Router pages -│ ├── dashboard/ # Main dashboard -│ ├── send/ # Send payment flow -│ ├── receive/ # Receive payment flow -│ ├── history/ # Transaction history -│ ├── anchors/ # Anchor management -│ ├── settings/ # User settings -│ └── globals.css # Global styles / theme variables -├── components/ # Shared React components -│ ├── ui/ # shadcn/ui primitives -│ ├── wallet-card.tsx # Wallet overview card -│ ├── asset-card.tsx # Asset display card -│ ├── transaction-row.tsx # Transaction list row -│ ├── navigation.tsx # Sidebar / mobile nav -│ └── app-wrapper.tsx # Root layout wrapper -└── lib/ - ├── utils.ts # cn() utility - └── theme.ts # Theme constants & types -``` - -## Routing - -The app uses Next.js App Router with the following routes: - -| Route | Page | Description | -|-------|------|-------------| -| `/` | Redirect | Redirects to `/dashboard` | -| `/dashboard` | DashboardPage | Main wallet overview | -| `/send` | SendPage | Send assets form | -| `/receive` | ReceivePage | Receive address/QR | -| `/history` | HistoryPage | Transaction history | -| `/anchors` | AnchorsPage | Anchor integrations | -| `/settings` | SettingsPage | Account & app settings | - -## Component Patterns - -- Pages use `'use client'` for interactivity -- Shared components are in `components/` -- UI primitives (button, input, etc.) are in `components/ui/` -- CSS is handled via Tailwind utility classes and CSS custom properties -- The `cn()` utility merges Tailwind classes with `clsx` + `tailwind-merge` - -## Theming - -- CSS custom properties in `globals.css` define the color palette -- Light and dark modes via `.dark` class on `` -- Theme is toggled by adding/removing the `.dark` class -- All colors use OKLCH color space for perceptually uniform gradients diff --git a/docs/architecture/system-overview.md b/docs/architecture/system-overview.md deleted file mode 100644 index 58f5537e..00000000 --- a/docs/architecture/system-overview.md +++ /dev/null @@ -1,63 +0,0 @@ -# System Overview - -Ignition Pay is a full-stack, cross-platform Stellar wallet ecosystem. - -## High-Level Architecture - -``` -┌─────────────────────────────────────────────────┐ -│ Clients │ -│ ┌──────────────┐ ┌──────────────────────────┐ │ -│ │ Flutter App │ │ Next.js Web App │ │ -│ │ (iOS/Android)│ │ (ignition-pay-frontend) │ │ -│ └──────┬───────┘ └──────────┬───────────────┘ │ -└─────────┼─────────────────────┼──────────────────┘ - │ │ - │ REST + WebSocket │ - ▼ ▼ -┌─────────────────────────────────────────────────┐ -│ API Layer │ -│ ┌───────────────────────────────────────────┐ │ -│ │ NestJS Backend (ignition-api) │ │ -│ │ • Auth (JWT, Stellar SEP-10) │ │ -│ │ • Wallet management │ │ -│ │ • Transaction processing │ │ -│ │ • Anchor orchestration (SEP-6/24/31) │ │ -│ │ • Soroban contract interaction │ │ -│ │ • Redis caching / Bull queues │ │ -│ └──────────────────┬────────────────────────┘ │ -└─────────────────────┼──────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────┐ -│ Blockchain Layer │ -│ ┌──────────────┐ ┌────────────────────────┐ │ -│ │ Stellar │ │ Soroban RPC │ │ -│ │ Horizon API │ │ (Smart Contracts) │ │ -│ └──────────────┘ └────────────────────────┘ │ -└─────────────────────────────────────────────────┘ -``` - -## Component Overview - -| Layer | Technology | Responsibility | -|-------|-----------|----------------| -| Mobile App | Flutter / Dart | Native iOS & Android wallet UI | -| Web App | Next.js / React / Tailwind | Browser-based wallet experience | -| API | NestJS / TypeScript | Business logic, auth, blockchain coordination | -| Blockchain | Stellar / Soroban | Asset transfers, smart contracts | - -## Data Flow - -1. Client sends HTTP/WebSocket request to API -2. API authenticates via JWT (issued via SEP-10 Stellar Web Auth) -3. API validates request, applies business logic -4. API communicates with Stellar Horizon / Soroban RPC -5. Response flows back through the same path - -## Security Model - -- Private keys are encrypted at rest using the user's password -- Sessions use short-lived JWTs with refresh tokens -- Database secrets are never exposed to clients -- All Horizon communication happens server-side diff --git a/docs/common-mistakes.mdx b/docs/common-mistakes.mdx deleted file mode 100644 index da8c2618..00000000 --- a/docs/common-mistakes.mdx +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: "Common Integration Mistakes" -description: "Avoid these 6 pitfalls when implementing deposit routing." -icon: "circle-xmark" ---- - -Even with a library, there are several ways to incorrectly handle Stellar deposits. We've compiled the 6 most common mistakes. - - - - **The Mistake**: Assuming any valid StrKey is a routable destination. - **The Right Way**: C-addresses (Contracts) do not support memos. If you detect a C-address, flag it as `INVALID_DESTINATION` and do not attempt to credit a user account. - - - **The Mistake**: Converting the `routingId` string to a `number` in JS/TS. - **The Right Way**: IDs can be up to `uint64` max. JS numbers lose precision above `9007199254740991`. **Always** keep the ID as a string. - - - **The Mistake**: Ignoring `MEMO_TEXT` when it contains numbers. - **The Right Way**: Users often accidentally send their ID as text. The library will extract it but issue a `MEMO_TYPE_MISMATCH` warning. You should still credit the user but log a warning. - - - **The Mistake**: Treating all warnings as fatal or ignoring all warnings. - **The Right Way**: Check the `severity`. `ERROR` warnings (like precision loss) should block the transaction, while `INFO` warnings are safe to ignore. - - - **The Mistake**: Passing custom strings like "id" or "text" instead of the standard "ID" or "TEXT". - **The Right Way**: Use the standard uppercase strings defined in the [API Reference](/api-reference/extract-routing). - - - **The Mistake**: Assuming any number is a valid ID. - **The Right Way**: The library verifies that IDs are within the 64-bit range. Any value above `18,446,744,073,709,551,615` will trigger a `MAX_UINT64_EXCEEDED` error. - - diff --git a/docs/contributing.mdx b/docs/contributing.mdx deleted file mode 100644 index a4e2e20a..00000000 --- a/docs/contributing.mdx +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: "Contributing" -description: "How to contribute to the Stellar Address Kit." -icon: "people-group" ---- - -We welcome contributions! As a multi-language project, there are several ways to get involved. - -## Development Workflow - - - - Add new test cases to `spec/vectors.json`. - - - Verify all implementations: - - **TypeScript**: `pnpm test` - - **Go**: `go test ./...` - - **Dart**: `dart test` - - **Go Fuzzing**: `go test -fuzz=FuzzExtractRouting` - - - We use `changesets` for versioning. Run `pnpm changeset` to describe your change before opening a PR. - - - -## PR Requirements -- Must pass all spec vectors in all three languages. -- Must include a changeset. -- Must follow the "never throw" design principle. diff --git a/docs/development/coding-standards.md b/docs/development/coding-standards.md deleted file mode 100644 index 1853429f..00000000 --- a/docs/development/coding-standards.md +++ /dev/null @@ -1,68 +0,0 @@ -# Coding Standards - -## TypeScript / JavaScript - -- Use **TypeScript** for all new code (strict mode enabled) -- Follow the existing ESLint configuration -- Use **functional components** with hooks in React -- Prefer `const` over `let` (avoid `var`) -- Use named exports over default exports -- File names use kebab-case (e.g., `wallet-card.tsx`) - -### Naming Conventions - -| Construct | Convention | Example | -|-----------|-----------|---------| -| Components | PascalCase | `WalletCard` | -| Functions | camelCase | `formatBalance` | -| Variables | camelCase | `userBalance` | -| Types/Interfaces | PascalCase | `WalletCardProps` | -| Files | kebab-case | `wallet-card.tsx` | -| Directories | kebab-case | `components/ui/` | - -### React/Next.js Conventions - -- Use `'use client'` directive only where browser APIs are needed -- Server components are preferred when possible -- Props are typed with TypeScript interfaces -- Event handlers use `handle` prefix (e.g., `handleSubmit`) - -## Dart / Flutter - -- Follow the official Dart style guide -- Use `analysis_options.yaml` conventions -- Prefer `const` constructors where possible -- Use `camelCase` for variables and methods -- Use `PascalCase` for classes and types - -## CSS / Styling - -- Use Tailwind CSS utility classes primarily -- CSS custom properties for theme values -- Avoid inline styles in production code -- Follow the established color scheme (CSS variables in `globals.css`) - -## Git Conventions - -### Commit Messages - -Follow conventional commits format: - -``` -(): -``` - -Types: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore` - -Examples: -``` -feat(wallet): add balance hide toggle -fix(api): handle null memo in transaction -docs: update setup guide -``` - -### Branch Naming - -- Feature branches: `feat/` -- Bug fixes: `fix/` -- Documentation: `docs/` diff --git a/docs/glossary.mdx b/docs/glossary.mdx deleted file mode 100644 index 56179039..00000000 --- a/docs/glossary.mdx +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: "Glossary" -description: "Key terms used in the Stellar Address Kit." -icon: "list" ---- - -| Term | Definition | -|---|---| -| **G Address** | Ed25519 public key in StrKey format. | -| **M Address** | Multiplexed address encoding a G-address and a 64-bit ID. | -| **C Address** | Soroban contract identifier. | -| **Muxed ID** | The 64-bit unsigned integer embedded in an M-address. | -| **RoutingSource** | The part of the input that provided the `routingId` (e.g. `MUXED` or `MEMO`). | -| **MEMO_ID** | A 64-bit unsigned integer memo. | -| **MEMO_TEXT** | A text memo (up to 28 bytes). | -| **MEMO_HASH** | A 32-byte hash memo. | -| **MEMO_RETURN** | A 32-byte return hash memo. | -| **ErrorCode** | A fatal error that prevents routing. | -| **WarningCode** | A non-fatal issue encountered during routing. | diff --git a/docs/installation.mdx b/docs/installation.mdx deleted file mode 100644 index c8a6fd58..00000000 --- a/docs/installation.mdx +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: "Installation" -description: "Install Stellar Address Kit in your language of choice." -icon: "download" ---- - -## Language Support - -Stellar Address Kit is available for the three most common environments in the Stellar ecosystem. - - - - ```bash - npm install stellar-address-kit - ``` - - - Works alongside `stellar-sdk` or `stellar-base`. It handles the extraction logic that the base SDKs leave to the developer. - - - - ```bash - go get github.com/Boxkit-Labs/stellar-address-kit/packages/core-go - ``` - - - Designed to be embedded in high-throughput deposit routing services. Fully compatible with `github.com/stellar/go`. - - - - ```bash - dart pub add stellar_address_kit - ``` - - - Optimized for Flutter apps. Includes specialized handling for BigInt on Flutter Web to avoid precision loss. - - - diff --git a/docs/introduction.mdx b/docs/introduction.mdx deleted file mode 100644 index 1b963659..00000000 --- a/docs/introduction.mdx +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: "Introduction" -description: "A multi-language deposit routing library for Stellar." -icon: "hand-wave" ---- - -**Stellar Address Kit** is a specialized library designed to solve the complexity of deposit routing on the Stellar network. It provides a unified way to handle G-addresses (classic), M-addresses (muxed), and C-addresses (contracts) across multiple programming languages. - -## What it Does - -- **Unified Routing**: Extracts a canonical `address` and `routingId` (memo) from any input. -- **Safety First**: Implements a strict warning system to catch common integration pitfalls (like potential 64-bit integer precision loss in JS). -- **Cross-Language Compliance**: Every implementation (TypeScript, Go, Dart) is verified against the same set of test vectors. - -## What it does NOT do - - - Understanding the boundaries of this library is key to a successful integration. - - -- **Not a Federation Service**: It does not resolve `user*domain.com` addresses. -- **Not a full Stellar SDK**: It does not handle transaction signing, XDR parsing, or horizon communication. -- **Not a Muxed Account Resolver**: It does not look up internal mapping for M-addresses; it performs purely mathematical/encoding-based extraction. - -## Project Status - -All three core packages are production-ready and live at **v1.0.1**. - - - - NPM package for Web and Node.js. - - - High-performance Go implementation. - - - Native Dart package for cross-platform apps. - - diff --git a/docs/migration/compatibility.mdx b/docs/migration/compatibility.mdx deleted file mode 100644 index e14c6f46..00000000 --- a/docs/migration/compatibility.mdx +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: "Compatibility" -description: "How the kit works with other ecosystem tools." -icon: "handshake" ---- - -Stellar Address Kit is designed to be a thin layer on top of the standard Stellar SDKs. - -## SDK Compatibility - -| SDK | Support | Note | -|---|---|---| -| **Stellar SDK (JS/TS)** | Full | Use for network communication, use Kit for routing. | -| **Go Stellar SDK** | Full | Seamlessly integrates with `github.com/stellar/go`. | -| **Dart Stellar SDK** | Full | Handles the `BigInt` edge cases for Flutter Web. | - -## Network Compatibility - -The kit is network-agnostic. It works with: -- **Mainnet** -- **Testnet** -- **Futurenet** -- **Private Networks** - -Since it performs purely mathematical extraction (multiplexed encoding/decoding), it does not need to communicate with a Horizon server. diff --git a/docs/migration/memo-to-muxed.mdx b/docs/migration/memo-to-muxed.mdx deleted file mode 100644 index 307a5d82..00000000 --- a/docs/migration/memo-to-muxed.mdx +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: "Memo to Muxed Migration" -description: "Moving from G-addresses + Memos to M-addresses." -icon: "shuffle" ---- - -Migrating to multiplexed addresses (M-addresses) improves the user experience and reduces routing errors. - -## Migration Steps - - - - Ensure your `user_id` field can handle 64-bit unsigned integers stored as strings. - - - Use a Stellar SDK to generate an M-address for each user by combining your pooled G-address with the user's ID. - - - Update your UI to show the M-address. You can stop showing a separate "Memo" field for users who use the M-address. - - - Replace your custom parsing logic with `extractRouting`. This ensures that even if a user sends to your G-address with an ID memo, the routing will still work correctly. - - diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx deleted file mode 100644 index d6053c10..00000000 --- a/docs/quickstart.mdx +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: "Quickstart" -description: "Extract routing information in three languages." -icon: "bolt" ---- - -The core function of this library is `extractRouting`. It takes an address and an optional memo and returns a canonical `address` and `routingId`. - -## Example: M-Address - -An M-address contains both the base G-address and a multiplexed ID. The library extracts both automatically. - - - -```typescript TypeScript -import { extractRouting } from 'stellar-address-kit'; - -const result = extractRouting({ - address: "MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLT7AV7Y6S33Z6S3CHBAAAAAAAAAAAAABQD" -}); - -console.log(result.address); // "GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLT7AV7Y6S33Z6S3CHB" -console.log(result.routingId); // "123" -``` - -```go Go -import "github.com/Boxkit-Labs/stellar-address-kit/packages/core-go/addresskit" - -result := addresskit.ExtractRouting(addresskit.RoutingInput{ - Address: "MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLT7AV7Y6S33Z6S3CHBAAAAAAAAAAAAABQD", -}) - -fmt.Println(result.Address) // "GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLT7AV7Y6S33Z6S3CHB" -fmt.Println(result.RoutingId) // "123" -``` - -```dart Dart -import 'package:stellar_address_kit/stellar_address_kit.dart'; - -final result = extractRouting(RoutingInput( - address: "MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLT7AV7Y6S33Z6S3CHBAAAAAAAAAAAAABQD", -)); - -print(result.address); // "GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLT7AV7Y6S33Z6S3CHB" -print(result.routingId); // "123" -``` - - diff --git a/docs/spec/adding-vectors.mdx b/docs/spec/adding-vectors.mdx deleted file mode 100644 index 8fb88dd7..00000000 --- a/docs/spec/adding-vectors.mdx +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: "Adding Test Vectors" -description: "How to contribute new edge cases to the spec." ---- - -If you find a routing edge case that isn't covered, follow these steps to add it to the spec. - - - - Add the new test case to `spec/vectors.json`. Ensure you follow the existing schema for inputs and expected outputs. - - - Use the provided script to ensure your new vector doesn't break the JSON schema. - ```bash - node scripts/validate.js - ``` - - - Update the TS, Go, and Dart packages to pass the new test case. - - - Submit your changes with a `changeset` describing the new edge case. - - diff --git a/docs/spec/compliance-vectors.mdx b/docs/spec/compliance-vectors.mdx deleted file mode 100644 index cd7740a5..00000000 --- a/docs/spec/compliance-vectors.mdx +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: "Compliance Vectors" -description: "The 8 non-negotiable baseline test cases." ---- - -To be considered a compliant implementation of the Stellar Address Kit, a package must pass these 8 critical "canary" vectors. - -| Case | Why it matters | -|---|---| -| **2^53 + 1 Canary** | Verifies that the implementation does not lose precision on large IDs. | -| **uint64 Max** | Ensures `18,446,744,073,709,551,615` is handled as a string. | -| **uint64 Overflow** | Confirms that IDs larger than 64-bit trigger an error. | -| **Lowercase Input** | Verifies that addresses are normalized to uppercase in the output. | -| **NOTANADDRESS** | Ensures malformed strings return `INVALID_CHECKSUM`. | -| **Tampered Checksum** | Confirms that modifying one character in an address triggers an error. | -| **C-Address Destination** | Ensures contracts are flagged as `INVALID_DESTINATION` for routing. | -| **Invariant Check** | Verifies that if an error is present, all other result fields are empty. | diff --git a/docs/spec/overview.mdx b/docs/spec/overview.mdx deleted file mode 100644 index e574bcae..00000000 --- a/docs/spec/overview.mdx +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: "Spec Overview" -description: "Why the spec is the most important part of the project." -icon: "microscope" ---- - -Stellar Address Kit is a **spec-first** project. This means that the shared `vectors.json` file is the ultimate source of truth for how all packages must behave. - -## What is vectors.json? - -A collection of 100+ test cases that cover every possible combination of: -- G, M, and C addresses. -- Valid and invalid checksums. -- All memo types (ID, TEXT, HASH, RETURN). -- Numeric and non-numeric text values. -- Edge cases like `uint64` max value and JavaScript precision "canaries". - -## Shared Versioning - -The `spec_version` field in `vectors.json` governs the versioning of all three implementation packages. If the spec version jumps to `2.0.0`, all packages must jump to `2.0.0` to maintain compliance. diff --git a/docs/spec/versioning.mdx b/docs/spec/versioning.mdx deleted file mode 100644 index 99c8e568..00000000 --- a/docs/spec/versioning.mdx +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: "Spec Versioning" -description: "How we manage changes to the routing logic." ---- - -The `spec_version` field in `vectors.json` follows Semantic Versioning (SemVer) rules. - -## Versioning Rules - -- **Patch (1.0.x)**: Added non-breaking test vectors or updated documentation. -- **Minor (1.x.0)**: Added new features that are backward-compatible (e.g., a new warning code). -- **Major (x.0.0)**: Breaking changes to the routing logic or output schema. - - - All implementation packages must release the same version number to guarantee cross-language compliance. - - -## Compatibility Matrix - -| Spec Version | core-ts | core-dart | core-go | -|-------------|---------|-----------|---------| -| 1.0.0 | 0.1.0 | 1.0.1 | 0.1.0 | diff --git a/docs/templates/README.md b/docs/templates/README.md deleted file mode 100644 index 803305e2..00000000 --- a/docs/templates/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Documentation Templates - -This directory contains templates that ensure consistency across documentation. - -## Available Templates - -- [Component Documentation](./component-docs.md) — Template for documenting React components -- [API Endpoint Documentation](./api-endpoint-docs.md) — Template for documenting API endpoints -- [Feature Documentation](./feature-docs.md) — Template for documenting features - -## Usage - -1. Copy the relevant template file -2. Fill in the metadata fields -3. Write content following the section structure -4. Save with an appropriate filename in the relevant docs directory - -## Documentation Standards - -- Write in clear, plain English -- Include code examples for all APIs -- Describe the "why" in addition to the "how" -- Keep documentation close to the code it describes diff --git a/docs/templates/api-endpoint-docs.md b/docs/templates/api-endpoint-docs.md deleted file mode 100644 index 734f51f8..00000000 --- a/docs/templates/api-endpoint-docs.md +++ /dev/null @@ -1,54 +0,0 @@ -# Endpoint Name - -## Request - -`METHOD /api/v1/resource` - -### Headers - -| Header | Value | Required | -|--------|-------|----------| -| `Authorization` | `Bearer ` | Yes | - -### Query Parameters - -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `param` | `string` | No | Description | - -### Request Body - -```json -{ - "field": "value" -} -``` - -## Response - -### Success (200) - -```json -{ - "data": {} -} -``` - -### Error (4xx/5xx) - -```json -{ - "statusCode": 400, - "message": "Error description", - "error": "Bad Request" -} -``` - -## Errors - -| Status Code | Description | -|-------------|-------------| -| 400 | Invalid input | -| 401 | Unauthorized | -| 404 | Resource not found | -| 500 | Internal server error | diff --git a/docs/templates/component-docs.md b/docs/templates/component-docs.md deleted file mode 100644 index 92ac4d6b..00000000 --- a/docs/templates/component-docs.md +++ /dev/null @@ -1,33 +0,0 @@ -# Component Name - -> Brief description of what this component does (1-2 sentences). - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `propName` | `type` | `default` | Description | -| `propName` | `type` | required | Description | - -## Usage - -```tsx -import { ComponentName } from '@/components/path' - -function Example() { - return -} -``` - -## Behavior - -Describe the component's behavior, edge cases, and any important implementation details. - -## Accessibility - -List any ARIA attributes, keyboard interactions, or screen reader considerations. - -## Related Components - -- [RelatedComponent1](./related-component-1.md) -- [RelatedComponent2](./related-component-2.md) diff --git a/docs/templates/feature-docs.md b/docs/templates/feature-docs.md deleted file mode 100644 index 3824bfa7..00000000 --- a/docs/templates/feature-docs.md +++ /dev/null @@ -1,48 +0,0 @@ -# Feature Name - -## Overview - -Brief description of the feature and its purpose (2-3 paragraphs). - -## User Flow - -1. Step one: what the user does -2. Step two: system response -3. Step three: completion state - -## Architecture - -Describe the components involved: - -- **Frontend**: components, pages, state management -- **API**: endpoints, services, database tables -- **Blockchain**: relevant smart contracts or Stellar operations - -## Configuration - -List any environment variables, feature flags, or configuration options. - -```env -FEATURE_FLAG=true -FEATURE_TIMEOUT=30000 -``` - -## Testing - -Describe how to test this feature: - -```bash -# Run feature-specific tests -npm test -- --grep "Feature Name" -``` - -## Security Considerations - -- Authentication requirements -- Authorization checks -- Data validation rules - -## Related - -- [Issue #NN](https://github.com/Ignition-World/ignition-pay/issues/NN) -- [PR #NN](https://github.com/Ignition-World/ignition-pay/pull/NN)