API layer that merges indexer data with Supabase metadata; handles auth (Sign In With Stellar), image storage, and notifications. Exposes REST (and later GraphQL) for the frontend and external consumers.
Stack: NestJS, Fastify, Supabase, Redis.
List raffles with optional filters and pagination. Data comes from the indexer (contract state).
| Query param | Type | Description |
|---|---|---|
status |
string | Filter by raffle status |
category |
string | Filter by category |
creator |
string | Filter by creator Stellar address |
asset |
string | Filter by asset (e.g. XLM) |
limit |
number | Page size (1–100, default 20) |
offset |
number | Pagination offset (default 0) |
Response: { raffles: RaffleListItem[], total?: number }
Single raffle detail. Merges indexer (contract state: price, tickets, winner, status) and Supabase (metadata: title, description, image_url, category).
Response: RaffleDetailResponse — contract fields + title, description, image_url, category
Create or update raffle metadata. Body: { title?, description?, image_url?, category?, metadata_cid? }. Requires JWT (Bearer token from SIWS).
Protected routes require Authorization: Bearer <token>.
- GET /auth/nonce?address=G... — Returns
{ nonce, expiresAt, issuedAt, message } - User signs the
messagein their Stellar wallet (Freighter, xBull, etc.) - POST /auth/verify — Body:
{ address, signature, nonce [, issuedAt] }wheresignatureis base64-encoded Ed25519 - Returns
{ accessToken }— use asAuthorization: Bearer <accessToken>
tikka.io wants you to sign in
Address: G...
Nonce: abc123
Issued At: 2025-02-19T12:00:00.000Z
Set SIWS_DOMAIN to customize the domain (default: tikka.io).
curl -X POST http://localhost:3001/raffles/1/metadata \
-H "Content-Type: application/json" \
-d '{"title":"Test"}'
# Expected: 401 Unauthorizednpm run test:e2eAll endpoints are protected against abuse. Limits are per IP address, enforced by @nestjs/throttler.
When a limit is exceeded the API returns HTTP 429 with a Retry-After value in the response body.
| Tier | Endpoints | Default limit |
|---|---|---|
default |
All public API (/raffles, /users, /leaderboard, /stats) |
100 req / 60 s |
nonce |
GET /auth/nonce |
30 req / 60 s |
auth |
POST /auth/verify |
10 req / 60 s |
{
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded. Please slow down and try again.",
"retryAfter": 42
}All thresholds are overridable without code changes (see .env.example):
THROTTLE_DEFAULT_LIMIT=100 # max requests per window
THROTTLE_DEFAULT_TTL=60 # window size in seconds
THROTTLE_AUTH_LIMIT=10 # POST /auth/verify
THROTTLE_AUTH_TTL=60
THROTTLE_NONCE_LIMIT=30 # GET /auth/nonce
THROTTLE_NONCE_TTL=60# Hit /auth/verify 11 times — the 11th must return 429
for i in $(seq 1 11); do
curl -s -o /dev/null -w "%{http_code}\n" -X POST http://localhost:3001/auth/verify
donesrc/api/rest/— raffles, users, leaderboard, stats, search, notificationssrc/auth/— SIWS (nonce, verify), JWT strategy, guardssrc/services/— metadata, storage, indexer client, notifications, searchsrc/middleware/— rate limit, validation (Zod), CORSsrc/config/— env configuration
Full ecosystem spec: ../docs/ARCHITECTURE.md (section 4 — tikka-backend).