Skip to content
Open
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
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,9 @@ CORS_ORIGIN=*
# Connections beyond this limit are rejected with close code 1013 (try again later).
# Set to 0 to disable the cap (not recommended in production).
WS_MAX_CONNECTIONS=1000

# Optional backplane for multi-instance fan-out. Default is "memory".
# Set to "redis" to publish WS events through Redis pub/sub and maintain a
# globally-shared seq counter for replay semantics.
WS_BACKPLANE=memory
REDIS_URL=redis://localhost:6379
8 changes: 7 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,18 @@ npm run dev
# → http://localhost:4000
# → Swagger UI: http://localhost:4000/docs
# → WebSocket: ws://localhost:4000/ws

# 4. If you need a local Postgres-backed app, use the bundled compose stack
# (one command; matches the CI service container config)
docker compose up --build
```

> Most feature work does not require Postgres — the service uses an in-memory
> store by default. `DATABASE_URL` has a sensible default so the app boots
> without a live database. You only need Postgres if you are working on
> Prisma migrations or the database-backed health check.
> Prisma migrations or the database-backed health check. The repo now includes a
> root-level `docker-compose.yml` so that workflow is one command instead of a
> hand-rolled local setup.

---

Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ GET /api/v1/chain/account/:key — Stellar account lookup
### Prerequisites

- Node.js 20+
- Docker (optional, for the one-command local Postgres + app dev stack)

```bash
npm install
Expand All @@ -60,6 +61,21 @@ cp .env.testnet.example .env # testnet development (most contributors)
npm run dev # http://localhost:4000
```

### One-command local stack with Postgres

If you need the Prisma-backed local database flow, use the repo-provided compose stack:

```bash
docker compose up --build
```

This starts:
- a `postgres:16-alpine` service matching the CI credentials (`vortex` / `vortex` / `vortex`)
- the app service built from the existing Dockerfile
- the app already pointed at `DATABASE_URL=postgresql://vortex:vortex@postgres:5432/vortex?schema=public`

After the stack is up, the backend is available at http://localhost:4000 and the DB is reachable using the same default credentials shown in `.env.example`.

Three `.env.example` variants are provided for different deployment targets:

| File | Use case |
Expand Down
42 changes: 42 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
services:
postgres:
image: postgres:16-alpine
container_name: vortex-postgres
restart: unless-stopped
environment:
POSTGRES_USER: vortex
POSTGRES_PASSWORD: vortex
POSTGRES_DB: vortex
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U vortex -d vortex"]
interval: 10s
timeout: 5s
retries: 5
volumes:
- postgres-data:/var/lib/postgresql/data

app:
build:
context: .
dockerfile: Dockerfile
container_name: vortex-backend
depends_on:
postgres:
condition: service_healthy
environment:
NODE_ENV: development
PORT: 4000
DATABASE_URL: postgresql://vortex:vortex@postgres:5432/vortex?schema=public
CORS_ORIGIN: "*"
WS_MAX_CONNECTIONS: 1000
WS_BACKPLANE: memory
ports:
- "4000:4000"
volumes:
- .:/app
command: sh -c "npx prisma migrate deploy && npm run dev -- --host 0.0.0.0"

volumes:
postgres-data:
2 changes: 2 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ model Intent {
filledAt Int? @map("filled_at")
/// Actual amount received by the user (base unit string).
fillAmount String? @map("fill_amount")
/// Realized protocol fee charged on this fill (destination-token base units).
feeAmount String? @map("fee_amount")
/// On-chain transaction hash of the Stellar fill transaction.
txHash String? @map("tx_hash")

Expand Down
4 changes: 4 additions & 0 deletions src/config/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ export interface AppConfig {
corsOrigin: string;
/** Maximum concurrent WebSocket connections (0 = unlimited). */
wsMaxConnections: number;
wsBackplane: "memory" | "redis";
redisUrl: string;
}

export default (): AppConfig => ({
Expand All @@ -71,4 +73,6 @@ export default (): AppConfig => ({
onchainIntentsEnabled: (process.env.ONCHAIN_INTENTS_ENABLED ?? "false") === "true",
corsOrigin: process.env.CORS_ORIGIN ?? "*",
wsMaxConnections: parseInt(process.env.WS_MAX_CONNECTIONS ?? "1000", 10),
wsBackplane: (process.env.WS_BACKPLANE ?? "memory") as "memory" | "redis",
redisUrl: process.env.REDIS_URL ?? "redis://localhost:6379",
});
3 changes: 3 additions & 0 deletions src/config/env.validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ export const envValidationSchema = Joi.object({

CORS_ORIGIN: Joi.string().default("*"),

WS_BACKPLANE: Joi.string().valid("memory", "redis").default("memory"),
REDIS_URL: Joi.string().uri({ scheme: ["redis", "rediss"] }).default("redis://localhost:6379"),

// ── Persistence adapter selection ─────────────────────────────────────────
// Controls which repository adapter is used for intents and solvers.
// "memory" (default) keeps everything in-process — no database required.
Expand Down
25 changes: 25 additions & 0 deletions src/health/health.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,31 @@ export class HealthController {
private readonly dbHealth: DatabaseHealthService,
) {}

@Get("live")
live() {
return {
status: "ok",
service: "vortex-backend",
version: "0.1.0",
network: `stellar-${this.configService.get("stellar.network", { infer: true })}`,
uptime: process.uptime(),
};
}

@Get("ready")
async ready() {
const db = await this.dbHealth.check();

return {
status: db.status === "ok" ? "ok" : "unreachable",
service: "vortex-backend",
version: "0.1.0",
network: `stellar-${this.configService.get("stellar.network", { infer: true })}`,
uptime: process.uptime(),
db,
};
}

@Get()
async check() {
const db = await this.dbHealth.check();
Expand Down
3 changes: 3 additions & 0 deletions src/intents/intents.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,9 +280,12 @@ export class IntentsController {
});
}

const feeAmount = (BigInt(dto.fillAmount) * 5n) / 10000n;

const updated = await this.intentsService.fillIfAccepted(id, dto.solver, {
filledAt: now,
fillAmount: dto.fillAmount,
feeAmount: feeAmount.toString(),
txHash: dto.txHash,
});
if (!updated) {
Expand Down
Loading
Loading