A gamified financial transfer platform that visualizes real-time transactions as physical "packets" travelling through a 3D simulated solar system.
Kronos pairs a performance-oriented Go backend with a React + Three.js frontend to turn an otherwise invisible action >> sending money >> into something you can watch. Every user is assigned a home planet on signup. When you transfer credits to another user, the backend physics engine spawns a packet that physically flies through 3D space from the sender's planet to the receiver's planet. Funds only settle when the packet arrives >> and a central black hole can pull packets in, voiding the transfer and refunding the sender.
Live demo: kronos-lime.vercel.app Generated docs: DeepWiki >> TANICE-GAWD/Kronos
- Preview
- Core Concept
- Features
- Tech Stack
- Architecture
- Transfer & Packet Lifecycle
- Physics Engine
- Data Model
- Repository Structure
- Getting Started
- API Reference
- WebSocket Protocol
- Frontend Layer
- Deployment
- Glossary
Screenshots of the live solar-system view go here >> see the comment above for suggested shots, or try the live demo.
A transfer in Kronos is not an instant database row update >> it is a simulated journey:
- A user initiates a transfer over REST. The sender's funds are immediately locked (escrow) and a
pendingtransaction is recorded. - The backend instantiates a Packet at the sender's home-planet position and hands it to the Scheduler.
- The Scheduler runs a deterministic 60 FPS physics loop that moves the packet toward the receiver's (orbiting, moving) planet, applying velocity, gravitational time dilation, and black-hole attraction.
- On arrival, the transaction is settled >> the receiver is credited. If the packet falls into the black hole, the transaction is voided and the sender is refunded.
- Throughout the flight, the server broadcasts the full world state over WebSockets so every connected client renders the packets, wallets, and transactions in real time.
- Real-time 3D visualization of money in motion using React Three Fiber.
- Authoritative server-side physics >> clients are pure renderers; the Go backend is the single source of truth.
- Orbital mechanics >> eight planets orbit the sun at scaled real-world speeds; packets intercept their moving destination.
- Gravitational hazard >> a black hole stalls (time-dilates) and ultimately destroys packets that drift too close.
- Escrow ledger >> funds are locked on send, settled on arrival, voided on destruction (no money created or lost).
- JWT authentication with bcrypt-hashed passwords; per-planet currency wallets seeded on registration.
- WebSocket state diffing on the client for efficient UI updates and auto-reconnect.
- Per-planet currencies (EARTH, MARS, VENUS, JUPITER, …) and a detailed transaction/wealth history.
| Layer | Technology | Purpose |
|---|---|---|
| Backend | Go 1.25 · Gin | REST API, routing, middleware |
| Real-time | Gorilla WebSocket | Live world-state broadcast |
| Auth | golang-jwt · bcrypt | JWT tokens, password hashing |
| Database | PostgreSQL (lib/pq) |
Users, wallets, transactions, ledger |
| Frontend | React 19 · Vite 7 | SPA, routing, build tooling |
| 3D | Three.js · @react-three/fiber · drei | Solar-system rendering |
| Deployment | Railway (backend) · Vercel (frontend) | Hosting |
The system is split into a stateless-rendering frontend and an authoritative backend. REST handles authentication and actions; WebSockets handle continuous state synchronization.
flowchart TB
subgraph Client["Frontend >> React + Three.js (Vercel)"]
UI["UI Layer<br/>LoginPage · WalletUI · TransferModal · TransactionHistory"]
Scene["3D Scene<br/>Sun · Planets · BlackHole · StarCreditManager"]
WSM["WebSocketManager<br/>singleton · state diffing · auto-reconnect"]
end
subgraph Server["Backend >> Go / Gin (Railway)"]
Router["HTTP Router + CORS"]
Auth["Auth Service<br/>JWT + bcrypt"]
Handlers["REST Handlers<br/>transfer · balance · history · wealth"]
Sched["Scheduler<br/>60 FPS physics loop"]
Phys["Physics Engine<br/>orbits · gravity · time dilation"]
Ledger["In-memory Ledger<br/>lock / settle / void"]
Hub["WebSocket Hub<br/>state enrichment + broadcast"]
end
DB[("PostgreSQL<br/>users · wallets · transactions · ledger")]
UI -->|REST /api| Router
Scene --> WSM
WSM <-->|WebSocket /ws| Hub
Router --> Auth
Router --> Handlers
Handlers -->|AddPacket| Sched
Sched --> Phys
Sched --> Ledger
Sched -->|StateUpdate| Hub
Handlers --> DB
Auth --> DB
Hub --> DB
A single transfer flows from a REST call into the physics loop and back out over WebSockets:
sequenceDiagram
participant U as User (Frontend)
participant API as Gin REST Handler
participant DB as PostgreSQL
participant S as Scheduler (60 FPS)
participant P as Physics Engine
participant H as WebSocket Hub
U->>API: POST /api/transfer (receiver, amount, currency)
API->>DB: Lock funds + create transaction (pending)
API->>S: AddPacket(packet @ origin planet)
API-->>U: 200 OK { status: "active" }
loop every 1/60 s
S->>P: RunPhysics(packet, blackHole, dt)
P->>P: Move toward predicted planet position
P->>P: Apply gravity / time dilation
alt Reached destination
P-->>S: status = settled
S->>DB: SettleTransaction (credit receiver)
else Pulled into black hole
P-->>S: status = destroyed
S->>DB: VoidTransaction (refund sender)
end
S->>H: StateUpdate { packets, wallets, transactions }
H-->>U: Broadcast enriched state to all clients
end
A packet moves through four states:
stateDiagram-v2
[*] --> Active: Transfer initiated
Active --> Stalled: Near black hole (dilation down)
Stalled --> Active: Escapes gravity well
Stalled --> Destroyed: Crosses event horizon
Active --> Destroyed: Crosses event horizon
Active --> Settled: Reaches destination planet
Settled --> [*]: Receiver credited
Destroyed --> [*]: Sender refunded
The engine runs server-side at a fixed 60 FPS tick. All positions are derived from server time, making orbits deterministic and identical for every client.
Tunable constants (backend/internal/engine/physics.go):
| Constant | Value | Meaning |
|---|---|---|
SpeedOfLight |
50.0 |
Base packet velocity (scale-independent) |
ArrivalThreshold |
2.0 |
Distance at which a packet counts as "arrived" |
Pull_r |
40.0 |
Black-hole event-horizon radius (packet destroyed) |
Time_dil |
0.3 |
Minimum time-dilation factor near the black hole |
Behavior:
- Orbits >> each planet's position is computed as
distance · (cos θ, 0, sin θ)whereθ = serverTime · speed + initialAngle. - Interception >> packets don't aim at where the planet is, but iteratively predict where it will be (up to a 10s lead), so they curve to meet a moving target.
- Gravity & time dilation >> within
Pull_r + 20of the black hole a packet isStalledand itsDilationFactordecays (slowing it); insidePull_rit isDestroyed. - Curved paths >> trajectories use quadratic Bézier interpolation for a natural arc.
Scaled orbital data (1 AU ≈ 100 world units):
| Planet | Distance | Speed | Currency |
|---|---|---|---|
| Mercury | 39 | 0.82 | MERCURY |
| Venus | 72 | 0.32 | VENUS |
| Earth | 100 | 0.20 | EARTH |
| Mars | 152 | 0.11 | MARS |
| Jupiter | 520 | 0.017 | JUPITER |
| Saturn | 954 | 0.0067 | SATURN |
| Uranus | 1919 | 0.0024 | URANUS |
| Neptune | 3007 | 0.0012 | NEPTUNE |
The Scheduler (scheduler.go) guards the active-packet map with a sync.RWMutex, advances physics each tick, triggers settlement/voiding, and pushes a StateUpdate onto a buffered channel that the Hub broadcasts.
PostgreSQL holds the durable state; an in-memory ledger mirrors the escrow logic during a packet's flight.
erDiagram
USERS ||--o{ WALLETS : owns
USERS ||--o{ TRANSACTIONS : "sends / receives"
CURRENCIES ||--o{ WALLETS : denominates
WALLETS ||--o{ LEDGER_ENTRIES : records
USERS {
uuid id PK
string username
string password_hash
string home_planet
}
WALLETS {
uuid id PK
uuid user_id FK
string currency_id FK
decimal available_balance
decimal locked_balance
}
TRANSACTIONS {
uuid id PK
uuid sender_id FK
uuid receiver_id FK
decimal amount
string status
string origin_planet
string destination_planet
}
CURRENCIES {
string id PK
string planet_name
string symbol
}
LEDGER_ENTRIES {
uuid id PK
uuid wallet_id FK
}
- Wallets track
available_balanceandlocked_balanceseparately >> locking on send is what makes escrow safe. - Transactions carry a
status(pending→settled/failed) plus origin and destination planets. - Stored procedures and views (
procedures.sql,views.sql) back atomic settlement and the user-facing history endpoints.
Kronos/
├── backend/ # Go API + physics engine
│ ├── cmd/api/main.go # Entry point: wires repos, services, routes
│ └── internal/
│ ├── engine/ # Physics + 60 FPS scheduler
│ │ ├── physics.go # Orbits, gravity, interception, arrival
│ │ └── scheduler.go # Tick loop, settle/void, state broadcast
│ ├── finance/ledger.go # In-memory escrow ledger (lock/settle/void)
│ ├── transport/ # HTTP + WebSocket layer
│ │ ├── handlers.go # Transfer, balance, history, wealth
│ │ ├── auth_handlers.go # Register, login, user search
│ │ ├── middleware.go # JWT auth middleware
│ │ ├── hub.go # WebSocket hub + state enrichment
│ │ └── client.go # Per-connection WebSocket client
│ ├── auth/auth_service.go # JWT issuing/verification, bcrypt
│ ├── models/ # User, Wallet, Transaction, Packet
│ ├── repository/ # PostgreSQL data access + seeding
│ └── db/ # schema_enhanced.sql, procedures.sql, views.sql
│
└── kronos/ # React + Three.js frontend
└── src/
├── App.jsx # Main scene, routing, HUD
├── components/ # 3D objects + UI
│ ├── Sun.jsx, Planet.jsx, BlackHole.jsx, OrbitLine.jsx
│ ├── StarCreditManager.jsx # Renders in-flight packets
│ ├── WalletUI.jsx, TransferModal.jsx, TransactionHistory.jsx
│ └── PlanetFollowCamera.jsx, FollowPlanetTab.jsx, Notification.jsx
├── pages/ # LoginPage.jsx, RegisterPage.jsx
├── hooks/useWebSocket.js # React hook over WebSocketManager
├── services/WebSocketManager.js # Singleton WS client + diffing
└── utils/ # timeSync.js, planetCurrency.js
- Go 1.25+
- Node.js 18+ and npm
- PostgreSQL 14+
Create a database and apply the schema, procedures, and views:
createdb kronos
psql kronos -f backend/internal/db/schema_enhanced.sql
psql kronos -f backend/internal/db/procedures.sql
psql kronos -f backend/internal/db/views.sqlCreate backend/.env:
DATABASE_URL=postgres://user:password@localhost:5432/kronos?sslmode=disable
JWT_SECRET=replace-with-a-long-random-secretRun it:
cd backend
go mod download
go run ./cmd/apiThe API starts on http://localhost:8080. Currencies are seeded automatically on boot.
cd kronos
npm install
npm run devThe app starts on Vite's dev server (http://localhost:5173) and connects to the backend at http://localhost:8080 / ws://localhost:8080/ws.
Note: the frontend currently points at
localhost:8080directly in source. For a non-local deployment, update the API/WS URLs insrc/pages/*,src/components/*, andsrc/services/WebSocketManager.js.
Base URL: http://localhost:8080/api. Protected routes require an Authorization: Bearer <token> header.
| Method | Endpoint | Description |
|---|---|---|
POST |
/register |
Create a user (username, password ≥ 8, home_planet). Seeds a 1000-credit wallet. |
POST |
/login |
Authenticate; returns a 24h JWT. |
GET |
/users/search?q= |
Search users by username (for transfers). |
| Method | Endpoint | Description |
|---|---|---|
POST |
/transfer |
Initiate a transfer (receiver_username, amount, currency_id). Locks funds, launches a packet. |
GET |
/balance/:userID |
Available balances + escrow snapshot. |
GET |
/history/:userID |
In-memory ledger history. |
GET |
/user/me/wealth |
Wealth summary across all currencies. |
GET |
/user/me/wallets-detailed |
Wallets with currency metadata. |
GET |
/user/me/transactions |
Persisted transaction history (view-backed). |
GET |
/transactions/:txID/status-history |
Status-change audit trail for a transaction. |
| Endpoint | Description |
|---|---|
GET /ws |
Upgrade to WebSocket; receive continuous world-state broadcasts. |
Example >> register & login:
curl -X POST http://localhost:8080/api/register \
-H 'Content-Type: application/json' \
-d '{"username":"astro","password":"supersecret","home_planet":"earth"}'
curl -X POST http://localhost:8080/api/login \
-H 'Content-Type: application/json' \
-d '{"username":"astro","password":"supersecret"}'On every physics tick that has active packets, the Hub enriches the raw scheduler snapshot with the latest users, wallets, and transactions from the database, then broadcasts a single JSON StateUpdate:
On the client, the WebSocketManager singleton maintains the connection (with exponential-backoff reconnect), diffs incoming state against the previous snapshot, and notifies subscribed React components only when something relevant changes.
App.jsxsets up the React Three Fiber<Canvas>, the orbit-controlled camera (far plane at 100k units), the HUD, and routing between the auth pages and the main scene.- Scene components (
Sun,Planet,BlackHole,OrbitLine) render the solar system;PlanetFollowCamera+FollowPlanetTablet you lock the camera onto a planet. StarCreditManagerconsumes the live packet stream and renders each in-flight transfer as a moving object.WalletUI,TransferModal, andTransactionHistorymake up the financial UI >> checking balances, searching recipients, sending credits, and reviewing history.useWebSocketwrapsWebSocketManagerfor idiomatic React consumption.
| Component | Platform | Notes |
|---|---|---|
| Backend | Railway | Set DATABASE_URL and JWT_SECRET; exposes port 8080. |
| Frontend | Vercel | Static Vite build (npm run build). |
| Database | Managed PostgreSQL | Apply schema_enhanced.sql, procedures.sql, views.sql. |
CORS on the backend already allows the deployed Vercel origin and common localhost dev ports.
| Term | Meaning |
|---|---|
| Packet | Ephemeral 3D object representing money in transit between two planets. |
| Home planet | The planet assigned to a user at signup; defines their default currency. |
| Ledger | In-memory escrow tracking available / locked / settled funds during flight. |
| Settlement | Crediting the receiver when a packet reaches its destination. |
| Voiding | Refunding the sender when a packet is destroyed by the black hole. |
| Time dilation | Slowing of a packet (DilationFactor) as it nears the black hole. |
| Scheduler | Server loop that advances physics at 60 FPS and broadcasts state. |
| Hub | WebSocket broadcaster that enriches and fans out world state to all clients. |
{ "timestamp": 1716800000000, "packets": { "<uuid>": { "id": "<uuid>", "origin_planet": "earth", "destination_planet": "mars", "current_pos": { "x": 12.3, "y": 0.4, "z": -88.1 }, "status": "active", // active | stalled | destroyed | settled "dilationfactor": 1.0, "velocity": 50.0 } }, "wallets": { "<userID>": { "currency_id": "EARTH", "available_balance": 950 } }, "transactions": [ { "id": "...", "status": "pending", "amount": 50 } ], "users": { "<userID>": { "username": "astro", "home_planet": "earth" } } }