Multi-tenant trading platform on top of Polymarket. Manual swipe trading + auto-trading strategies. Browser-generated self-custodial wallet; backend orchestrates every order. Gasless end to end — each user gets a Polymarket deposit wallet, and wallet deploy, token approvals, USDC↔pUSD wrapping, and fee collection all run through Polymarket's relayer (no MATIC required).
Architecture:
ARCHITECTURE.md— start here.
- ✅ Browser wallet generation, AES-GCM encryption with PBKDF2-derived key (200k iters), 5-min in-memory unlock
- ✅ SIWE-lite wallet auth → 7-day JWT
- ✅ "Enable trading" onboarding: derive Polymarket L2 API creds → deploy deposit wallet → approve tokens
- ✅ Gasless deposit wallet (POLY_1271 / EIP-1271) — per-user CREATE2 proxy, deployed + approved via the Polymarket relayer
- ✅ pUSD collateral — USDC.e ↔ pUSD wrap/unwrap as gasless relayer batches
- ✅ Cross-chain funding — LI.FI bridge from ETH/Arbitrum/Optimism/Base → Polygon, auto-wrap to pUSD on arrival
- ✅ Backend-orchestrated trading:
prepare → browser signs EIP-712 → submit → CLOB(deposit wallet as maker) - ✅ Real Polymarket market ingestion incl.
neg_risk+accepting_ordersflags (Postgres-backed, every 30s) - ✅ Four strategies (hedge / maker / underdog / arb), one autobot per user, sharded across worker pods
- ✅ Per-user session signer for auto-trading (24h default TTL, revocable) + per-user risk config
- ✅ Reconciler: pulls each user's CLOB orders/fills every 60s, syncs order + position state
- ✅ Fee settlement: deducted-fee dollars pulled from each deposit wallet → fee receiver every 6h (gasless relayer batch)
- ✅ Notifier with Resend (verification + trade-filled emails) + worker Telegram alerts
- ✅ Dark-theme mobile-first UI: login, swipe, trade modal, auto config, portfolio, fund (QR), bridge, settings
- ✅ Fly.io configs for the four Go services, Cloudflare Pages config for the web
make devThat brings up Postgres, Redis, migrations, API, worker, ingestor, notifier (via docker-compose),
then runs the frontend dev server on http://localhost:5173 and proxies /v1/* → API.
Useful sub-targets (see Makefile for the full list):
make migrate # apply pending migrations
make logs s=api # tail one service
make psql # open psql against the local DB
make tidy # go mod tidy across all services
make build # go build all services (sanity check before push)
make stop # tear down docker-compose- Visit the app — Create wallet with email + password. PK is generated in the browser, encrypted with PBKDF2(password), and stored in localStorage.
- Sign in — browser signs the SIWE-style nonce; backend issues a 7-day JWT.
- Enable trading (3 steps, one panel):
- one signature derives your Polymarket L2 API creds (stored server-side so the backend can authenticate CLOB requests on your behalf);
- deploy your deposit wallet — a per-user CREATE2 proxy, deployed gaslessly via the Polymarket relayer;
- approve tokens — one signed batch (gasless) approves pUSD + CTF for the regular and neg-risk exchanges.
- Fund — send USDC.e to your deposit wallet on Polygon (CEX withdrawal or the in-app Bridge from ETH/Arbitrum/Optimism/Base), then wrap USDC.e → pUSD (gasless). The bridge auto-wraps on arrival.
- Swipe to trade. Right = trade modal opens, you sign the EIP-712 order, backend submits it to Polymarket (deposit wallet as maker) and writes the position row atomically.
- Auto — enable strategies (hedge/maker/underdog/arb) + grant a session signer (default 24h) so the worker can sign on your behalf while the browser is closed.
- The reconciler sweeps every 60s (syncing fills → Postgres); the fee settler pulls deducted fees from your deposit wallet every 6h.
Manual trades use the POLY_1271 deposit-wallet path: the deposit wallet is the order maker and validates the signature via EIP-1271. The backend builds the order; the browser signs an ERC-7739-wrapped EIP-712 envelope.
Browser Backend Polymarket CLOB
| | |
|--- POST /v1/trades/prepare ------->| |
| | fee-adjusts notional |
| | looks up neg_risk (server-side) |
| | builds POLY_1271 order (V2) |
| | stores intent (DB + Redis) |
|<------ {intentId, typedData} ------| |
| | |
| unlock wallet (5min in-mem cache) | |
| sign EIP-712 (TypedDataSign) | |
| | |
|--- POST /v1/trades/submit -------->| |
| {intentId, signature} | ERC-7739-wrap sig |
| | refresh balance/allowance cache |
| | POST /order to CLOB (HMAC auth) ->|
| |<-------- {clobOrderId, status} ----|
| | TX: positions + orders + fee_ledger
|<-------- {orderId, feeUsd, ...} ---| |
Auto-trades take the same POLY_1271 path, but the worker signs server-side with your session-signer key (the browser is closed).
apps/api/ HTTP gateway (Go, net/http + pgx + rueidis)
apps/worker/ Per-user autobot + reconciler + fee settler (Go)
apps/ingestor/ Polls Polymarket → markets table (Go)
apps/notifier/ Redis Streams → Resend emails (Go)
apps/web/ Vite + React + TS + Tailwind, dark theme
packages/
polymarket-go/ Shared CLOB client: signing, deposit wallet, POLY_1271, pUSD, relayer
proto/ Redis Stream event payload contracts
deploy/
docker/ Per-service Dockerfiles (distroless)
fly/ fly.toml per service + secrets template
k8s/ Skeleton manifests (not deployed yet)
migrations/ Postgres schema (goose) — 001..014
autobot/ DEPRECATED single-user bot — reference only
See ARCHITECTURE.md §17 (roadmap). Highlights:
- WebSocket push (replace REST polling for live odds)
- KMS envelope encryption for
session_signers.private_key_hexandpolymarket_creds.api_secret - Admin dashboard (
isAdminJWT claim already lands) - Trade-failure retry policy + dead-letter queue
- Shared market-tick stream →
OnTickstrategies (collapse per-user polling) - K8s + KEDA manifests once shard count > 1 (Fly autoscale fine until then)