Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,11 @@ LOG_SERVICE_NAME=vortex-backend
# Sentry DSN for error reporting. Leave blank to disable Sentry entirely.
SENTRY_DSN=

# Bearer token for GET /metrics (issue #298).
# Empty = unauthenticated in dev/test; production always requires a value.
# Generate with: openssl rand -hex 32
METRICS_TOKEN=

# Optional log shipping to a central collector. Off by default so local dev and
# CI stay stdout-only. When enabled, LOG_SHIPPING_HOST is required.
LOG_SHIPPING_ENABLED=false
Expand Down
7 changes: 7 additions & 0 deletions .env.mainnet.example
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,13 @@ KILLSWITCH_PERSISTENCE=prisma
# Recommended in production: set to your Sentry project DSN.
SENTRY_DSN=<CHANGE_ME>

# Bearer token for GET /metrics (issue #298). REQUIRED in production.
# Without this the endpoint returns 401 (fail-closed). Generate with:
# openssl rand -hex 32
# Configure your Prometheus scrape job with:
# authorization: { type: Bearer, credentials: <METRICS_TOKEN> }
METRICS_TOKEN=<CHANGE_ME>

# info is the right level for production — "debug" is too noisy.
LOG_LEVEL=info

Expand Down
267 changes: 267 additions & 0 deletions .env.testnet.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
# .env.testnet.example
#
# Environment template for LOCAL DEVELOPMENT against Stellar TESTNET.
# Copy to .env and fill in any values marked with <CHANGE_ME>.
#
# cp .env.testnet.example .env
#
# Testnet is safe to experiment with — tokens have no real value and contract
# deployments are free via Friendbot. Never reuse testnet keys on mainnet.
#
# Closes #136

# ─── Database ────────────────────────────────────────────────────────────────
# Local Docker Compose default. Adjust if you use a remote or managed DB.
DATABASE_URL=postgresql://vortex:vortex@localhost:5432/vortex?schema=public

# ─── Server ──────────────────────────────────────────────────────────────────
PORT=4000
NODE_ENV=development

# ─── Stellar / Soroban ───────────────────────────────────────────────────────
STELLAR_NETWORK=testnet
INTENTS_PERSISTENCE=prisma
ETHEREUM_RPC_URL=
ETHEREUM_ESCROW_ADDRESS=
BASE_RPC_URL=
BASE_ESCROW_ADDRESS=
POLYGON_RPC_URL=
POLYGON_ESCROW_ADDRESS=
ARBITRUM_RPC_URL=
ARBITRUM_ESCROW_ADDRESS=
OPTIMISM_RPC_URL=
OPTIMISM_ESCROW_ADDRESS=
AVALANCHE_RPC_URL=
AVALANCHE_ESCROW_ADDRESS=
EVM_RPC_ALLOWLIST=
ALLOW_LEGACY_STELLAR_SIGNATURES=false
SOROBAN_RPC_URL=https://soroban-testnet.stellar.org

# Testnet contract IDs — leave blank until you have deployed contracts.
# The service boots without them; on-chain write paths are no-ops when empty.
SETTLEMENT_CONTRACT_ID=
SOLVER_REGISTRY_CONTRACT_ID=

# Testnet signing key — generate a throwaway keypair, fund it with Friendbot,
# and paste the secret seed here. Never reuse this key on mainnet.
#
# # Generate a new key:
# npx @stellar/stellar-cli keys generate local-dev --network testnet
# npx @stellar/stellar-cli keys show local-dev
#
# # Or via the SDK:
# node -e "console.log(require('@stellar/stellar-sdk').Keypair.random().secret())"
#
# # Fund it (testnet only):
# curl "https://friendbot.stellar.org/?addr=<YOUR_PUBLIC_KEY>"
#
# Optional in development — leave blank to skip on-chain writes.
SOROBAN_SIGNING_KEY=

# Fee percentile used when estimating Soroban inclusion fees.
# p50 is a safe default for testnet; raise to p90+ for time-sensitive mainnet txs.
SOROBAN_FEE_PERCENTILE=p50

# ─── CORS ────────────────────────────────────────────────────────────────────
# Wildcard is fine for local development — tighten this in staging/production.
CORS_ORIGIN=*

# ─── WebSocket ───────────────────────────────────────────────────────────────
WS_MAX_CONNECTIONS=1000

# ─── Pluggable signer backend (issue #400) ───────────────────────────────────
# SIGNER_BACKEND=local is the default for development.
# In production use SIGNER_BACKEND=vault and supply VAULT_ADDR + VAULT_TOKEN.
SIGNER_BACKEND=local
VAULT_ADDR=
VAULT_TOKEN=
VAULT_TRANSIT_KEY_NAME=vortex-signer
ALLOW_LOCAL_SIGNER_IN_PROD=false
# ─── Resource-exhaustion limits (issue #476) ─────────────────────────────────
# Maximum JSON nesting depth — rejects deeply-nested body attacks (default 10).
JSON_MAX_DEPTH=10
# Maximum chain values in a single WS subscribe message (default 20).
WS_MAX_FILTER_CHAINS=20
# Maximum active subscriptions per WS connection (default 10).
WS_MAX_SUBSCRIPTIONS=10
# Postgres statement_timeout for standard queries in ms (default 5000).
DB_QUERY_TIMEOUT_MS=5000
# Postgres statement_timeout for batch queries in ms (default 10000).
DB_BATCH_QUERY_TIMEOUT_MS=10000
# Postgres statement_timeout for stats queries in ms (default 15000).
DB_STATS_QUERY_TIMEOUT_MS=15000

# Emergency kill-switch (issue #477)
# Postgres-backed so a pause survives a restart and reaches every replica.
KILLSWITCH_OPERATOR_TOKEN=
KILLSWITCH_REDIS_URL=
KILLSWITCH_POLL_MS=2000
KILLSWITCH_PERSISTENCE=prisma

# ─── Observability (optional) ────────────────────────────────────────────────
# Leave blank to disable Sentry error reporting.
SENTRY_DSN=

# Bearer token for GET /metrics (issue #298).
# Leave blank for unauthenticated local Prometheus scraping.
# In production: generate with `openssl rand -hex 32` and set a real value.
METRICS_TOKEN=

# debug | info | warn | error (defaults to "debug" in development)
LOG_LEVEL=debug

# ── Shadow-mode divergence monitor (issue #401) ─────────────────────────
# Off by default in every environment. It runs read-only `simulateTransaction`
# calls against SETTLEMENT_CONTRACT_ID in parallel with the off-chain intent
# path and never signs or submits anything.
#
# SHADOW_SOURCE_ACCOUNT only has to be a valid Stellar public key: it is used to
# populate the source-account field of the simulated envelope and is never
# signed, never charged a fee and never broadcast. It must still be set, or
# every transition reports "contract_unconfigured".
SHADOW_MODE_ENABLED=false
SHADOW_SAMPLE_RATE=1
SHADOW_QUEUE_MAX=256
SHADOW_CONCURRENCY=4
SHADOW_SOURCE_ACCOUNT=
# ─── Governance / Protocol Parameters ────────────────────────────────────────
# On-chain governance parameters contract ID — leave blank to use code defaults.
PARAMS_CONTRACT_ID=

# Poll interval in ms. 30 000 is fine for testnet.
PARAMS_POLL_INTERVAL_MS=30000
# ─── Leader election ─────────────────────────────────────────────────────────
# Enable for multi-replica testnet deployments.
LEADER_ELECTION_ENABLED=false
LEADER_ELECTION_HEARTBEAT_MS=5000

# ─── Background jobs (issue #494) ────────────────────────────────────────────
# api | worker | all — queue workers only run in "worker" or "all".
PROCESS_ROLE=all
# memory (single-process, dev/test) | bullmq (Redis-backed, uses REDIS_URL)
JOBS_DRIVER=memory
# Grace period for in-flight jobs on SIGTERM before they are returned to the queue.
JOBS_SHUTDOWN_TIMEOUT_MS=25000

# ─── Runtime feature flags (issue #495) ──────────────────────────────────────
# Change propagation across instances: memory (single instance) | redis
FLAGS_PUBSUB=memory
# Safety-net cache reload interval (ms)
FLAGS_REFRESH_MS=30000
# Break-glass pins that win over DB state, e.g. onchain-dry-run=true
FLAG_OVERRIDES=

# ─── Admin RBAC ──────────────────────────────────────────────────────────────
# Comma-separated id:role:secret (role = admin | superadmin, secret >= 16 chars).
# Sent as the x-admin-key header (the secret part). Empty disables admin APIs.
ADMIN_API_KEYS=

# ─── Guardian emergency ingestion (issue #507) ───────────────────────────────
# Guardian / security-council contract ID. Leave blank to disable ingestion.
GUARDIAN_CONTRACT_ID=

# ─── Synthetic canary (issue #496) ───────────────────────────────────────────
# Canary user + solver addresses; excluded from public stats and leaderboards.
CANARY_ADDRESSES=

# Public anonymised datasets (docs/rfcs/0001)
# Master switch for the public dataset publication job.
DATASETS_ENABLED=false
# Hash user addresses with the rotating salt before export.
DATASETS_ANONYMIZE=true
# Base anonymisation salt. Required (>= 32 chars) when datasets are enabled
# and anonymisation is on; generate with `openssl rand -hex 32`.
DATASETS_SALT=
# How often the anonymisation salt rotates, in hours.
DATASETS_SALT_ROTATION_HOURS=24
# How many previous salt windows are retained for continuity.
DATASETS_SALT_RETENTION_WINDOWS=2
# Public bucket/prefix the published datasets live under.
DATASETS_PUBLIC_BUCKET=vortex-public-datasets
# Storage backend: local (writes to disk) | memory (tests only).
DATASETS_STORAGE=local
# Root directory for the local storage backend.
DATASETS_LOCAL_DIR=.datasets

# Secrets Manager (issue #465)
# Provider: env | aws-secrets-manager | vault-kv
SECRETS_PROVIDER=env
# Poll interval for secret rotation (ms)
SECRETS_REFRESH_INTERVAL_MS=60000
# Extra secrets: comma-separated "name:envVar:required"
SECRETS_EXTRA=

# AWS Secrets Manager
AWS_SECRETS_MANAGER_PREFIX=
AWS_SECRETS_MANAGER_POLL_INTERVAL_MS=60000

# Vault KV
VAULT_KV_MOUNT=secret
VAULT_KV_PREFIX=vortex/
VAULT_KV_POLL_INTERVAL_MS=60000

# Extra secret env vars referenced by the default SecretConfig
JWT_SIGNING_KEY=
WEBHOOK_SECRET=
CHANNEL_KEY=

# Egress/SSRF Protection
EGRESS_TIMEOUT_MS=10000
EGRESS_MAX_REDIRECTS=3
EGRESS_MAX_BODY_SIZE_BYTES=10485760
SOROBAN_RPC_ALLOWLIST=soroban-testnet.stellar.org,soroban-rpc.stellar.org
WEBHOOK_ALLOWLIST=hooks.example.com,hooks.trusted.com
ORACLE_ALLOWLIST=oracle.trusted.io
# ─── WS gateway hardening (issue #455) ───────────────────────────────────────
# Inbound frames larger than this close the socket (1009).
WS_MAX_PAYLOAD_BYTES=16384
# Concurrent WS connections per client IP (0 = unlimited).
WS_MAX_CONNECTIONS_PER_IP=20
# Trusted reverse-proxy hops for X-Forwarded-For (0 = socket address only).
WS_TRUST_PROXY_HOPS=0
# Inbound token bucket per connection; repeat violators are disconnected.
WS_RATE_LIMIT_PER_SEC=10
WS_RATE_LIMIT_BURST=20
WS_RATE_LIMIT_MAX_VIOLATIONS=5
# Outbound backpressure: messages held per slow consumer, socket buffer
# threshold (bytes), and what to do when the queue is full.
WS_OUTBOUND_QUEUE_MAX=1000
WS_OUTBOUND_BUFFER_BYTES=1048576
WS_SLOW_CONSUMER_POLICY=drop_oldest
# HS256 secret for solver JWTs from the SEP-10 auth flow (#442); >= 32 chars.
# Empty disables JWT auth on the WS gateway.
AUTH_JWT_SECRET=

# ─── API keys & distributed rate limiting (issue #441) ─────────────────────────
RATE_LIMIT_LOCAL_PRUNE_MS=60000
# Redis URL for the shared rate-limit window. Empty = bounded local limiter.
RATE_LIMIT_REDIS_URL=

# ─── Scoped solver credentials (issue #443) ───────────────────────────────────
CREDENTIAL_REVOCATION_PUBSUB=memory

# ─── SSE intent feed (issue #433) ─────────────────────────────────────────────
SSE_HEARTBEAT_MS=15000
SSE_MAX_BUFFER_BYTES=1048576

# ─── Public anonymised datasets ──────────────────────────────────────────────
DATASETS_ENABLED=false
DATASETS_ANONYMIZE=true
DATASETS_SALT=
DATASETS_SALT_ROTATION_HOURS=24
DATASETS_SALT_RETENTION_WINDOWS=2
DATASETS_PUBLIC_BUCKET=
DATASETS_STORAGE_KIND=memory
DATASETS_LOCAL_DIR=

# ─── Health probes (issue #492) ──────────────────────────────────────────────
# Roles served by this process (api, ws, worker); readiness checks follow them.
SERVICE_ROLES=api,ws,worker
HEALTH_CHECK_INTERVAL_MS=5000
# Readiness hysteresis: failures before not-ready, successes before ready again.
HEALTH_READY_FAILURE_THRESHOLD=3
HEALTH_READY_SUCCESS_THRESHOLD=2
# Liveness fails when event-loop delay exceeds this.
HEALTH_EVENT_LOOP_MAX_LAG_MS=1000
# Soroban RPC endpoints for the quorum check (default: SOROBAN_RPC_URL).
SOROBAN_RPC_HEALTH_URLS=
81 changes: 81 additions & 0 deletions .github/ISSUE_TEMPLATE/bug-report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: Bug Report
description: Report a bug you found while using or working on vortex-backend.
title: "[Bug]: "
labels: ["bug", "needs-triage"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to report a bug!
Please fill out as much of the form as possible so we can reproduce and fix it quickly.

- type: textarea
id: description
attributes:
label: What happened?
description: A clear and concise description of the bug.
placeholder: Describe what went wrong.
validations:
required: true

- type: textarea
id: expected
attributes:
label: What did you expect to happen?
description: What should have happened instead?
placeholder: Describe the expected behaviour.
validations:
required: true

- type: textarea
id: reproduction
attributes:
label: Steps to reproduce
description: Minimal steps that reliably trigger the bug.
placeholder: |
1. Start the server with `npm run dev`
2. Send `POST /api/v1/intents` with body `{ ... }`
3. See error ...
validations:
required: true

- type: textarea
id: logs
attributes:
label: Relevant logs or error output
description: Paste any stack traces, structured log lines, or Sentry event IDs here.
render: shell
validations:
required: false

- type: input
id: version
attributes:
label: Version / commit SHA
description: What version or git SHA are you running?
placeholder: e.g. 0.1.0 or abc1234
validations:
required: false

- type: dropdown
id: environment
attributes:
label: Environment
options:
- Local development
- Testnet
- Mainnet / production
- CI
- Other
validations:
required: true

- type: checkboxes
id: checklist
attributes:
label: Checklist
options:
- label: I searched existing issues and this is not a duplicate.
required: true
- label: I have included enough information to reproduce the issue.
required: true
5 changes: 5 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Security vulnerability
url: https://github.com/vortex-protocol/vortex-backend/security/advisories/new
about: Please report security issues via GitHub's private vulnerability reporting — do not open a public issue.
Loading
Loading