Points System Overview
The Rocky Points System rewards traders for activity on the platform. Points accumulate within each Epoch and determine token distribution at the end of each Season.
Base URL: https://api.rocky.exchange
Epoch & Season Structure
Season (≈56 days)
├── Epoch 1 (≈14 days)
├── Epoch 2 (≈14 days)
├── Epoch 3 (≈14 days)
└── Epoch 4 (≈14 days)
- Each Epoch is a row in
points.epochs(id,start_date,end_date,status); its length is DB configuration, not pinned by code. The ≈14-day figure above is the intended cadence — the epoch seeded by the backend migration (Epoch 1) actually runs2026-01-01→2026-12-31. UseGET /v1/epochsfor the live boundaries. Points accumulate within the epoch. - A Season spans
start_epoch..end_epoch(also DB configuration; the seeded Season 1 covers epochs 1–4). At season end, tokens are intended to be distributed proportionally to weighted points — the snapshot/distribution engine is not implemented yet (see below).
Point Types
| Type | Code | How Earned | Status |
|---|---|---|---|
| Trading Points | TP | Calculated per fill based on volume × tier rate | Implemented — a background accrual pipeline (services/api-gateway/src/accrual.rs) polls ledger.trades every 5s and writes points.balances/points.events using exactly this rate table |
| PnL Points | PP | Calculated on position close based on realized PnL | Not implemented — no accrual source (would need a durable log of realized-PnL deltas over time; ledger.positions only holds current state) |
| Holding Points | HP | Accrued per minute based on open position value | Not implemented — needs a continuous position-valuation sampler, not built |
| Referral Points | RP | Awarded when a referee reaches the volume trigger within 7 days of binding | Not implemented (distinct from referral commission, which is implemented — see that page) |
| Staking Points | SP | Accrued while staking tokens | Not implemented — no staking feature exists in rocky-backend at all |
points.balances.trading_points/trading_volume/trade_count are the only fields with a real writer; pnl_points/holding_points/referral_points/staking_points stay at their schema default of 0 for every user. The season snapshot/distribution engine (points.distributions, POST /v1/points/claim/:distribution_id, weighted-share calculation) is also not implemented — it depends on all five point types being accrued to produce a meaningful total_points weighting.
Tier System
Trading point rates are multiplied by the user's Tier, determined by 14-day rolling trade volume:
| Tier | Volume (14-day rolling) | Maker Rate | Taker Rate |
|---|---|---|---|
| T1 | $0 – $4,999,999 | 1.2 TP / $1,000 | 0.8 TP / $1,000 |
| T2 | $5M – $99,999,999 | 1.5 TP / $1,000 | 1.0 TP / $1,000 |
| T3 | ≥ $100M | 2.0 TP / $1,000 | 1.3 TP / $1,000 |
The accrual pipeline selects the rate from the trader's trailing-14-day volume at fill time. The
tierreported byGET /v1/points,GET /v1/points/tierand the leaderboard is instead derived from the epoch-cumulativetrading_volumestored inpoints.balances, using the same $5M / $100M boundaries.
Earn Level
Total points within an epoch determine the user's Earn Level, which sets a weight multiplier used for season token distribution:
| Level | Points Range | Weight |
|---|---|---|
| L0 | 0 – 999 | 4 |
| L1 | 1,000 – 9,999 | 8 |
| L2 | 10,000 – 49,999 | 12 |
| L3 | 50,000 – 199,999 | 25 |
| L4 | 200,000 – 499,999 | 60 |
| L5 | 500,000+ | 120 |
Token Distribution Formula
Intended formula — the engine that computes it is not implemented, so points.distributions stays empty:
weighted_points = total_points × earn_level_weight
share_pct = user_weighted_points / Σ(all_users_weighted_points)
token_reward = season_user_pool × share_pct
Claim window: the per-record claim_deadline on the distribution row (nullable, set by the distribution engine — no fixed 30-day constant exists in code). POST /v1/points/claim/:distribution_id currently returns 501 because on-chain EIP-712 claim signing is not configured.
Season Token Pool
| Season | Epochs | User Pool | MM Pool |
|---|---|---|---|
| S1 | 1–4 | 35,000,000 | 20,000,000 |
| S2 | 5–8 | 25,000,000 | 16,000,000 |
| S3 | 9–12 | 20,000,000 | 13,000,000 |
| S4 | 13–16 | 20,000,000 | 13,000,000 |
| S5 | 17–20 | 20,000,000 | 13,000,000 |
| S6 | 21–24 | 30,000,000 | 25,000,000 |
Only Season 1 (35,000,000 / 20,000,000, epochs 1–4) is seeded by the backend migration. Seasons 2–6 are the planned schedule; they are DB rows in
points.seasons, not code constants, and do not exist until inserted.GET /v1/points/seasonsreturns what is actually configured.
Authentication
Protected endpoints require the wallet session token returned by POST /v1/wallet/verify (an opaque 64-char hex token, not a JWT):
Authorization: Bearer <session_token>
A missing, invalid, expired or revoked token returns 401 with a plain-text body (missing bearer token / invalid session), not JSON. Other errors on /v1/points/* are likewise plain-text bodies: 400 for a malformed UUID path parameter, 500 for a database failure.
API Endpoints
Public Endpoints (No Auth Required)
| Method | Endpoint | Description |
|---|---|---|
| GET | /v1/epochs | List all epochs |
| GET | /v1/points/earn-level-config | Get Earn Level configuration |
| GET | /v1/points/seasons | List all seasons |
| GET | /v1/points/distribution/:season_id | Season token distribution records (all users) |
| GET | /v1/points/leaderboard | Epoch cumulative leaderboard (top 100, current epoch) |
| GET | /v1/points/leaderboard/daily | Daily points increment leaderboard — stub, returns an empty board |
| POST | /v1/points/simulate | Simulate points for a trade |
Protected Endpoints (Session Token Required)
| Method | Endpoint | Description |
|---|---|---|
| GET | /v1/points | Get user points summary (aliases: /v1/points/balance, /v1/points/summary) |
| GET | /v1/points/history | Get points history |
| GET | /v1/points/tier | Get user tier info |
| GET | /v1/points/earn-quota | Get Earn Level quota info |
| POST | /v1/points/claim/:distribution_id | Claim season tokens (EIP-712) — not implemented, always 501 |