Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ac27a86
test: add integration tests, new test compose and env
hars-21 Sep 12, 2026
805a125
refactor: standardize API error responses across controllers
hars-21 Sep 13, 2026
de1bebe
feat: implement health check for engine and worker
hars-21 Sep 15, 2026
b838f7f
feat: integrate Sentry for error tracking in backend and engine
hars-21 Sep 16, 2026
48a45df
chore: docker setup and scripts for service orchestration
hars-21 Sep 17, 2026
2de3992
perf: restructure benchmarks with new scripts
hars-21 Sep 17, 2026
e200c72
test: add integration tests for websocket
hars-21 Sep 17, 2026
60efcb0
ci: unified workflow for running unit and integration tests
hars-21 Sep 17, 2026
217860c
feat: implement daily credit system
hars-21 Sep 18, 2026
1964936
feat: integrate react-query for data fetching and state management
hars-21 Sep 18, 2026
c058931
fix: update stream retention duration
hars-21 Sep 18, 2026
a740785
feat: additional candle intervals with live updates
hars-21 Sep 19, 2026
760a7c0
feat: enhance logging with shutdown messages
hars-21 Sep 19, 2026
be79bae
fix(ui): loading, empty and error states
hars-21 Sep 19, 2026
60538e6
feat: integrate PostHog for user analytics and event tracking
hars-21 Sep 19, 2026
2997017
refactor: replace usePostHog with useAnalytics
hars-21 Sep 19, 2026
9ef1c16
docs: full v1 docs with api reference
hars-21 Sep 19, 2026
410f5a2
chore: update version to 1.0.0, remove changelog
hars-21 Sep 19, 2026
177c7e9
fix(bot): price drifts, balance handling, coinbase api
hars-21 Sep 19, 2026
2efdca6
docs: update readme and architecture
hars-21 Sep 19, 2026
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
10 changes: 8 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ BACKEND_QUEUE_ID=1
ENGINE_TIMEOUT_MS=30000

# Engine stream batching
STREAM_RETENTION_MS=1200000
STREAM_RETENTION_MS=21600000
SNAPSHOT_INTERVAL=60000
STREAM_READ_BATCH_SIZE=200
RESPONSE_BATCH_SIZE=50
Expand All @@ -33,9 +33,15 @@ RATE_LIMIT_ORDER_WINDOW_MS=60000

LOG_LEVEL=debug

# Production-only external services. They may be omitted in development and test.
# RESEND_API_KEY=re_...
# EMAIL_FROM=PaperDrill <no-reply@example.com>
# SENTRY_DSN=https://...
# SENTRY_ENVIRONMENT=production

# Bot credentials
BASE_URL=http://localhost:8000
SERVICE_TOKEN=your-service-token-here
SERVICE_EMAIL=your-service-email-here
MARKET=SOL_USD
MAX_ORDER_QTY=10
MAX_ORDER_QTY=10
25 changes: 25 additions & 0 deletions .env.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
NODE_ENV=test
PORT=8000
CORS_ORIGIN=http://localhost:3000
WS_URL=ws://127.0.0.1:8001

DATABASE_URL=postgresql://postgres:password@postgres:5432/paperdrill_test
REDIS_URL=redis://redis:6379

JWT_SECRET=paperdrill-integration-tests-only
SERVICE_TOKEN=YzW0PrwwnzD30jyadkz475WiuPAM+3W/LF8umuM/AOc=
SERVICE_TOKEN_HASH=83b9b13ea2ca0050245a4f8070d884968e71cb4e477436f36f305939d39768a4
SERVICE_EMAIL=sol.service@paperdrill.dev

INCOMING_STREAM=backend-to-engine-broker
BACKEND_QUEUE_ID=integration-tests
ENGINE_TIMEOUT_MS=5000

RATE_LIMIT_ENABLED=false
LOG_LEVEL=info

STREAM_RETENTION_MS=1200000
SNAPSHOT_INTERVAL=60000
STREAM_READ_BATCH_SIZE=200
RESPONSE_BATCH_SIZE=50
FLUSH_INTERVAL_MS=250
38 changes: 26 additions & 12 deletions .github/workflows/ci.yml → .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
name: CI
name: Tests

on:
push:
pull_request:
workflow_dispatch:

permissions:
contents: read

jobs:
test:
name: Tests
name: Unit tests
runs-on: ubuntu-latest
timeout-minutes: 10
env:
NODE_ENV: test
CORS_ORIGIN: http://localhost:3000
REDIS_URL: redis://localhost:6379
DATABASE_URL: postgresql://paperdrill:paperdrill@localhost:5432/paperdrill
JWT_SECRET: ci-jwt-secret
LOG_LEVEL: info
RATE_LIMIT_ENABLED: "false"

steps:
- name: Checkout repository
Expand All @@ -42,5 +35,26 @@ jobs:
working-directory: engine
run: bun install --frozen-lockfile

- name: Run tests
run: bun test
- name: Run unit tests
run: make test-unit

integration:
name: Integration tests
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.3

- name: Install integration test dependencies
working-directory: tests
run: bun install --frozen-lockfile

- name: Run integration tests
run: make test-integration
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
.env
.env.*
!.env.example
!.env.test

# caches
.eslintcache
Expand Down
113 changes: 74 additions & 39 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,61 +1,96 @@
ENV ?= dev
SERVICE ?=
TAG ?=

DEV_FILE := compose.dev.yml
PROD_FILE := compose.prod.yml
BOT_FILE := compose.bot.yml
DEV_COMPOSE := ./scripts/compose.sh dev
PROD_COMPOSE := ./scripts/compose.sh prod

ifeq ($(ENV),dev)
COMPOSE := docker compose -f $(DEV_FILE)
endif

ifeq ($(ENV),prod)
COMPOSE := docker compose -f $(PROD_FILE)
endif

ifeq ($(ENV),bot)
COMPOSE := docker compose -f $(BOT_FILE)
endif

.PHONY: up build down deploy start restart stop status logs migrate-dev migrate-reset migrate-deploy seed clean
.PHONY: up build down restart status logs shell bots-up bots-stop \
db-prepare db-seed migrate-dev migrate-reset \
test-unit test-integration bench \
images-build images-push prod-config deploy prod-status prod-logs \
prod-bots-up prod-bots-stop clean

# Development
up:
$(COMPOSE) up -d
$(DEV_COMPOSE) up -d --wait postgres redis
./scripts/database.sh dev prepare
$(DEV_COMPOSE) up -d --wait backend engine worker

build:
$(COMPOSE) build
$(DEV_COMPOSE) build backend engine worker

down:
$(COMPOSE) down

deploy:
$(COMPOSE) up -d --build

start:
$(COMPOSE) start
$(DEV_COMPOSE) down --remove-orphans

restart:
$(COMPOSE) restart

stop:
$(COMPOSE) stop
$(DEV_COMPOSE) restart backend engine worker

status:
$(COMPOSE) ps
$(DEV_COMPOSE) ps

logs:
$(COMPOSE) logs -f $(SERVICE)
$(DEV_COMPOSE) logs -f $(SERVICE)

shell:
@if [ -z "$(SERVICE)" ]; then echo "Set SERVICE, for example: make shell SERVICE=backend" >&2; exit 1; fi
$(DEV_COMPOSE) exec $(SERVICE) sh

bots-up:
$(DEV_COMPOSE) --profile bots up -d --build btc-bot sol-bot eth-bot

bots-stop:
$(DEV_COMPOSE) --profile bots stop btc-bot sol-bot eth-bot

# Database
db-prepare:
./scripts/database.sh dev prepare

db-seed:
./scripts/database.sh dev seed

migrate-dev:
$(COMPOSE) exec backend bun prisma migrate dev
$(DEV_COMPOSE) exec backend bun prisma migrate dev

migrate-reset:
$(COMPOSE) exec backend bun prisma migrate reset
$(DEV_COMPOSE) exec backend bun prisma migrate reset

migrate-deploy:
$(COMPOSE) exec backend bun prisma migrate deploy
# Test
test-unit:
cd backend && set -a && . ../.env.test && set +a && bun test
cd engine && set -a && . ../.env.test && set +a && bun test

db-seed:
$(COMPOSE) exec backend bun prisma db seed
test-integration:
sh tests/run-integration.sh

bench:
sh benchmarks/run.sh

# Release images
images-build:
./scripts/images.sh build "$(TAG)"

images-push:
./scripts/images.sh push "$(TAG)"

# Production
prod-config:
$(PROD_COMPOSE) config --quiet

deploy:
./scripts/deploy.sh

prod-status:
$(PROD_COMPOSE) ps

prod-logs:
$(PROD_COMPOSE) logs -f $(SERVICE)

prod-bots-up:
$(PROD_COMPOSE) --profile bots pull btc-bot sol-bot eth-bot
$(PROD_COMPOSE) --profile bots up -d btc-bot sol-bot eth-bot

prod-bots-stop:
$(PROD_COMPOSE) --profile bots stop btc-bot sol-bot eth-bot

clean:
docker system prune -f
$(DEV_COMPOSE) down --volumes --remove-orphans
69 changes: 45 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,56 @@
<p align="center">
<h1 align="center">PaperDrill</h1>
<p align="center">The exchange built for developers, not spectators.</p>
<p align="center">
<a href="#features">Features</a> •
<a href="#architecture">Architecture</a> •
<a href="#tech-stack">Tech Stack</a>
<div align="center">
<h1>PaperDrill</h1>
<p><strong>The exchange built for developers, not spectators.</strong></p>

<p>
<img alt="Release v1.0.0" src="https://img.shields.io/badge/release-v1.0.0-6d5dfc">
<img alt="Bun" src="https://img.shields.io/badge/runtime-Bun-000000?logo=bun&logoColor=white">
<img alt="TypeScript" src="https://img.shields.io/badge/language-TypeScript-3178c6?logo=typescript&logoColor=white">
<img alt="Docker" src="https://img.shields.io/badge/deployment-Docker-2496ed?logo=docker&logoColor=white">
</p>

<p>
<a href="https://paperdrill.dev">Platform</a> ·
<a href="https://paperdrill.dev/docs">API documentation</a> ·
<a href="https://api.paperdrill.dev">API</a>
</p>
</p>
</div>

A live, always-on exchange simulator with a real matching engine and order book. Trade on the UI or connect a bot via API - no KYC, no real money, no risk.
PaperDrill is a live paper-trading exchange simulator with a real matching engine and order book. Trade through the web interface or connect a bot through the API—no KYC and no real funds at risk.

## Features

- **Live matching engine** - Price-time priority order matching, not a mock.
- **Real-time order book** - Watch depth and trades move over a live WebSocket feed.
- **Persistent accounts** - Simulated balances, full order history, nothing resets.
- **Public leaderboard** _(Coming Soon)_ - See how your strategy stacks up against others.
- **Bot support** _(Coming Soon)_ - Connect trading bots via REST API and WebSocket.
- **API-first access** _(Coming Soon)_ - Full REST API and API keys for programmatic trading.
- **Live matching engine** — price-time priority matching rather than mocked fills.
- **Real-time market data** — order book, trades, tickers, and updating candles over WebSocket.
- **Persistent accounts** — simulated balances, open orders, order history, and trade history.
- **Public leaderboard** — compare simulated portfolio performance with other traders.
- **Scoped API keys** — programmable account access and order management.
- **Bot-friendly API** — REST trading endpoints and public WebSocket feeds.
- **Daily simulated credit** — idempotent recurring credit with an adjustable PnL baseline.

## Architecture

![PaperDrill-Architecture](/assets/paperdrill-architecture.png)
![PaperDrill architecture](/assets/architecture.png)

| Service | Responsibility |
| ---------- | --------------------------------------------------------------------------- |
| `frontend` | React SPA for markets, trading, account management, docs, and leaderboard |
| `backend` | Express REST/WebSocket API, authentication, validation, and market metadata |
| `engine` | In-memory balances, order books, matching, and periodic snapshots |
| `worker` | Persists orders/fills and derives candles and tickers |
| `bot` | Optional market makers for the supported markets |
| PostgreSQL | Accounts, markets, orders, fills, candles, credits, and leaderboard data |
| Redis | Commands, responses, event streams, pub/sub, and runtime checkpoints |

Backend, engine, and worker communicate through Redis. Production uses an external PostgreSQL provider; Redis and engine snapshots use persistent Docker volumes, with nginx running on the application host.

## Tech Stack

| Layer | Technology |
| ----------- | ----------------------------------- |
| Frontend | React + TypeScript + Tailwind CSS |
| Backend | Express + Prisma ORM |
| Engine | Custom matching engine (TypeScript) |
| Database | PostgreSQL (via TimescaleDB) |
| Cache/Queue | Redis |
| Infra | Docker, Nginx, Vercel (frontend) |
| Layer | Technology |
| -------------- | ----------------------------------------- |
| Runtime | Bun + TypeScript |
| Frontend | React 19, Tailwind CSS 4, TanStack Query |
| API | Express 5, WebSocket (`ws`), Zod |
| Data | PostgreSQL/TimescaleDB, Prisma 7, Redis 7 |
| Observability | Structured service logs, Sentry, PostHog |
| Infrastructure | Docker Compose, nginx, GHCR, Cloudflare |
Binary file added assets/architecture.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed assets/paperdrill-architecture.png
Binary file not shown.
Loading
Loading