Skip to content

Repository files navigation

Bridgelet SDK

Backend SDK for ephemeral Stellar account management

MVP Stubs

🚧 MVP — Active Development: encryptSecret() — base64, not real encryption, must be replaced before any production deployment 🚧 The expiresIn → expiry_ledger conversion — needs verification or explicit documentation of where it happens 🚧 Webhook coverage gaps..

Overview

The Bridgelet SDK is a NestJS-based backend service that manages the lifecycle of ephemeral Stellar accounts. It handles account creation, claim authentication, webhook notifications, and integration with the bridgelet-core smart contracts.


⚠️ TEMPORARY DEVELOPMENT WORKAROUNDS (IMPORTANT)

PLEASE READ THIS SECTION BEFORE DEVELOPMENT

The following services/imports are currently commented out to allow npm run start:dev to run without errors. These are NOT removed and MUST be restored once proper implementations exist.

Temporary Development Notes:

How to Find Temporary Changes:

  1. Search the codebase for comments containing TEMPORARY: to locate all commented-out code that needs restoration..

  2. Every TEMPORARY: comment MUST reference a linked GitHub issue using the format TEMPORARY: <reason> (issue #NNN). This is enforced by CI (see .github/workflows/ci.yml), so a temporary workaround can never silently become permanent. Track restoration in the linked issue. There are currently zero TEMPORARY: comments in the codebase (issue #458).

  3. Secret Encryption (src/modules/accounts/accounts.service.ts)

    • Current: Base64 encoding (NOT encryption)
    • Impact: Ephemeral secret keys are not protected at rest
    • Required: AES-256-GCM or KMS-backed encryption before any deployment with real funds
  4. Ledger Expiry Conversion

    • CreateAccountDto.expiresIn is converted to an absolute expiresAt Date and then mapped to the contract expiry_ledger during initialization.
    • expiresAt is the API-level source of truth; the ledger number is derived from that deadline at the time the account is initialized.
    • Conversion formula: expiry_ledger = current_ledger + ceil(remaining_seconds / 5) + 10
  5. Sweep Authorization Signature (src/modules/sweeps/providers/contract.provider.ts)

    • Current: generateAuthSignature() produces a fake 64-byte stub signature
    • Works because: EphemeralAccount.verify_sweep_authorization() in bridgelet-core is also a stub that accepts any signature (documented in bridgelet-core README)
    • Impact: Sweep authorization is not cryptographically enforced in development
    • Guard: Method throws if called outside development or test environments
    • Required: Real Ed25519 signing against the SweepController's authorized_signer once bridgelet-core implements real verification.

Status:

This is a temporary stabilization to enable local development and onboarding until missing implementations are complete. No code was deleted - all logic remains in place as comments.


Tech Stack

  • Framework: NestJS (Node.js + TypeScript)
  • Database: PostgreSQL
  • ORM: TypeORM
  • Blockchain: Stellar SDK + Soroban RPC
  • API: REST api

Stellar SDK Version

@stellar/stellar-sdk is pinned to an exact version (14.6.1) in package.json — no caret or tilde range — for the following reasons:

  • The SDK exposes raw Stellar XDR and Soroban RPC types. A minor or patch bump can change serialization behaviour, breaking transaction building or contract call encoding in ways that are difficult to detect without end-to-end tests against a live network.
  • Exact pinning makes the dependency tree fully reproducible across developer machines and CI without relying on lock-file-only guarantees.

Upgrade process:

  1. Update the version in package.json to the new exact version.
  2. Run npm install to update package-lock.json.
  3. Run the full test suite: npm test.
  4. Manually test account creation, sweep, and expiry flows against testnet before merging.
  5. Only promote to production after all testnet checks pass.

Features

  • Account lifecycle management (create, claim, expire)
  • Claim authentication via signed tokens
  • Webhook system for payment events
  • Integration with bridgelet-core contracts
  • Admin dashboard API endpoints

Project Structure

src/
├── modules/
│   ├── accounts/        # Ephemeral account management
│   ├── claims/          # Claim authentication & processing
│   ├── sweeps/          # Fund sweep orchestration
│   ├── webhooks/        # Event notification system
│   └── stellar/         # Stellar/Soroban integration
├── common/
│   ├── guards/          # Auth guards
│   ├── interceptors/    # Logging, transform
│   └── filters/         # Exception filters
├── config/              # Environment configuration
└── database/            # Migrations, entities

scripts/
└── generate-migrations.sh  # Regenerates src/database/migrations/ from scratch

Installation

# Install dependencies
npm install

# Setup environment
cp .env.example .env
# Edit .env with your configuration

# Run database migrations
# DataSource config : src/config/typeorm.config.ts
# Migrations        : src/database/migrations/
#   1718100000000-CreateAccountsTable              (accounts table, status enum, expiredAt, metadata)
#   1718100001000-CreateClaimsTable                (claims table + FK to accounts)
#   1718100002000-AddInitializingToAccountStatus   (adds INITIALIZING to account_status enum)
#   1718100003000-CreateWebhooksTable              (webhooks table)
#   1718100004000-AddClaimingToAccountStatus       (adds CLAIMING to account_status enum)
#   1718100005000-CreateWebhookDeliveriesTable     (webhook delivery attempt log table)
#   1718100006000-AddHighTrafficIndexes            (accounts composite and range indexes)
#   1718100007000-CreateContractEventsTable        (Soroban contract event index table)
#   1718100008000-AddDeletedAtToAccountsTable      (soft-delete column + index on accounts)
#   1718100008000-AddPartialSweepToAccountStatus   (adds PARTIAL_SWEEP to account_status enum)
#   1718100008000-CreateClaimAuditLogTable         (claim attempt audit log table)
npm run migration:run

# Start development server
npm run start:dev

Regenerating the migrations folder

src/database/migrations/ is scripted rather than hand-maintained. scripts/generate-migrations.sh deletes the folder and rewrites every file listed above verbatim, so the folder on disk is always reproducible from the script instead of relying on files someone created by hand.

# Recreate src/database/migrations/ from the script (prompts for confirmation)
./scripts/generate-migrations.sh

# Same, but skip the confirmation prompt (useful in CI)
./scripts/generate-migrations.sh --yes

This does not apply migrations to a database — it only (re)writes the .ts files. Run npm run migration:run afterwards as usual. See CONTRIBUTING.md for the workflow to follow when adding a new migration.

Tests

npm test

## or to run specific tests
npm test -- test_Service_File_Name

## e.g
npm test -- sweeps.service.spec.ts

Coverage

Run the full coverage report (enforces 80% minimum threshold):

npm run test:cov

Coverage reports are generated in the coverage/ directory. The build will fail if any metric (branches, functions, lines, statements) falls below 80%.

Load Testing

Generate API keys for the concurrent account load test:

npm run load:accounts:seed -- 50

Then run the burst test against a locally running server:

npm run load:accounts

This test sends 50 concurrent POST /accounts requests using unique integrator API keys and verifies the endpoint remains responsive under burst load.

The claim-redemption (sweep) path — a much heavier operation that triggers on-chain sweep transactions — has its own burst test. Seed ephemeral accounts in PENDING_CLAIM with valid claim tokens:

npm run load:claims:seed -- 50

Then run the burst test against a locally running server:

npm run load:claims

This sends 50 concurrent POST /claims/redeem requests and reports the success/failure rate and latency distribution. Note that POST /claims/redeem is throttled at 5 requests/min/IP, so simultaneous arrivals exercise the throttle as well as the reconcile/locking path under burst; for a pure un-throttled sweep burst run the requests from distinct clients (or disable the throttle in a load-test profile).

Local sandbox integration

The repository includes a local sandbox integration test for the Bridgelet contract flow in test/accounts-local-sandbox.e2e-spec.ts.

Set the required Stellar environment variables and run:

npm run test:local-sandbox

Required env vars:

  • STELLAR_HORIZON_URL
  • STELLAR_SOROBAN_RPC_URL
  • EPHEMERAL_ACCOUNT_CONTRACT_ID
  • SWEEP_CONTROLLER_CONTRACT_ID
  • FUNDING_ACCOUNT_SECRET
  • RECOVERY_ACCOUNT_PUBLIC
  • SWEEP_SIGNING_KEY_SEED
  • STELLAR_LOCAL_SANDBOX=true

The repository also includes an embedded-Postgres integration test in src/database/migrations.integration.spec.ts that starts a fresh PostgreSQL instance, runs all current migrations, verifies the resulting schema matches the TypeORM entities, checks the account_status_enum values, confirms the claims.accountId -> accounts.id and webhook_deliveries.subscription_id -> webhooks.id foreign keys are enforced, verifies the high-traffic accounts indexes added by migration 1718100006000, and verifies the contract_events table shape.

To check coverage for a specific file:

npm test -- sweeps.service.spec.ts --coverage

Environment Variables

# Database
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_NAME=bridgelet
DATABASE_USER=postgres
DATABASE_PASSWORD=postgres

# Stellar
STELLAR_NETWORK=testnet
STELLAR_HORIZON_URL=https://horizon-testnet.stellar.org
STELLAR_SOROBAN_RPC_URL=https://soroban-testnet.stellar.org

# Security
JWT_SECRET=your-secret-key
CLAIM_TOKEN_EXPIRY=2592000  # 30 days
API_RATE_LIMIT=100          # General API rate limit (requests/minute)

# Scheduler
INITIALIZING_TIMEOUT_MS=600000      # INITIALIZING -> FAILED cleanup timeout
SWEEP_RECONCILIATION_TIMEOUT_MS=600000  # Stale CLAIMING -> PARTIAL_SWEEP reconciliation (#475)

# Stellar funding / sweep secrets (high privilege — see Deployment Guide)
FUNDING_ACCOUNT_SECRET=...
RECOVERY_ACCOUNT_PUBLIC=G...
SWEEP_SIGNING_KEY_SEED=...
ENCRYPTION_KEY=your-64-char-hex-string

# Application
PORT=3000
NODE_ENV=development
# Swagger UI (/api/docs) — disabled by default in production. Set to 'true' to expose it.
# Never enable in production unless the endpoint is behind authentication.
ENABLE_SWAGGER=false

High-privilege secrets: FUNDING_ACCOUNT_SECRET, SWEEP_SIGNING_KEY_SEED, JWT_SECRET, and ENCRYPTION_KEY control accounts that can move or authorise real funds. In production these must be supplied from a secrets manager (AWS Secrets Manager / SSM, Vault, KMS-backed injection, etc.) — not a plain .env value — and must never be committed or logged. See the Deployment Guide for secure storage, rotation, and balance-monitoring guidance for the funding account.

JWT_SECRET must be a strong, random value of at least 32 characters. In non-development environments (NODE_ENV=production, staging, etc.) the application refuses to start if JWT_SECRET is empty, shorter than 32 characters, or a known placeholder such as your-secret-key.

API Documentation

Once running, access API docs at:

  • Swagger: http://localhost:3000/api/docs

Production note: the Swagger UI (/api/docs) is disabled by default when NODE_ENV=production. A publicly reachable Swagger UI would expose the full REST surface, request/response schemas and potential internal field names to anyone who finds the URL. To opt in explicitly (e.g. for a private, authenticated staging deployment) set ENABLE_SWAGGER=true.

Key Endpoints

POST /accounts # Create ephemeral account (also generates the claim token) GET /accounts/:id # Get account details POST /claims/initiate # Generate claim token POST /claims/redeem # Redeem claim and sweep GET /webhooks # List webhook subscriptions POST /webhooks # Subscribe to events PUT /webhooks/:id # Update webhook subscription (e.g. URL, events) DELETE /webhooks/:id # Delete webhook subscription

Metadata (issue #462)

POST /accounts accepts an optional metadata JSON object for integration bookkeeping (e.g. userId, orderId). Constraints:

  • Must be a plain JSON object (arrays and primitives are rejected).
  • Must serialise to at most 4 KB (4096 bytes) — larger payloads are rejected.
  • Any top-level key matching a known PII identifier (email, phone, name, address, ssn, dob, passport, taxid, etc.) is stripped before storage. PII is never persisted.

Validation is applied on write only, so existing metadata is unaffected retroactively.

Rate Limiting

Claim tokens gate real fund movement, so the endpoints that generate and consume them are rate-limited per API key AND per IP (see the getTracker configuration in src/app.module.ts), stricter than the general API limit. Exceeding a limit returns HTTP 429 with a Retry-After header.

Endpoint Limit (per API key + IP) Justification
POST /accounts 10 / min Generates a claim token; limit prevents mass token generation / account-existence probing (#473)
POST /claims/verify 20 / min Cheap token check
POST /claims/redeem 5 / min Moves funds; aggressive limit slows token-guessing (#474)
General API (all routes) 100 / min Baseline throttle

In addition to the redeem rate limit, repeated failed redemption attempts against the same token raise a brute-force alert log line (see ClaimRedemptionProvider). This is defense-in-depth layered on top of the high-entropy claim token generation (see SECURITY_AUDIT.md), not a substitute for it.

Database Schema

See Database Schema Documentation

Development

# Run tests
npm run test

# Run e2e tests
npm run test:e2e

# Lint
npm run lint

# Format
npm run format

Contributing

Automated PR Naming Checks

All pull requests are validated automatically for branch naming and PR title format.

  • During the initial rollout, checks ran in warning mode until 2026-02-27.
  • Since then, enforcement is active: pull requests are blocked until naming issues are fixed (verified 2026-08-27, and the workflow below still blocks on non-conforming names/titles).

Branch Name Format

Accepted pattern:

(<conventional-type>)/brief-description

Regex used by CI:

^(fix|feature|feat|test|chore|docs|refactor|ref|hotfix|release|ci|build|revert)/[a-z0-9-]+$

Examples:

  • fix/jwt-error-handling
  • feature/webhook-service

main and develop are exempt for release/hotfix workflows.

PR Title Format

Accepted pattern:

<conventional-type>: Brief description

Regex used by CI:

^(fix|feature|feat|test|chore|docs|refactor|ref|hotfix|release|ci|build|revert): .+$

Examples:

  • fix: Handle JWT errors in TokenVerificationProvider
  • test: Add unit tests for ClaimLookupProvider
  • feature: Implement WebhooksService

How To Fix A Branch Name

Rename your local branch and push the new branch:

git branch -m fix/jwt-error-handling
git push origin -u fix/jwt-error-handling

Then update the PR to use the renamed branch. If needed, close the old PR and open a new one from the renamed branch.

How To Fix A PR Title

Edit the PR title directly in GitHub:

  1. Open the pull request.
  2. Click the title field.
  3. Update it to the required format.
  4. Save changes.

Deployment

See Deployment Guide for production setup.

Documentation

Visit http://localhost:3000/api/docs for API documentation.

See Getting Started Guide for full setup instructions.

Support

(Nest)https://nestjs.com

License

UNLICENSED

About

Backend service for ephemeral Stellar account management

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages