Break bricks. Hit jackpots. Earn crypto.
Use this checklist when reviewing or hardening the server-side surface (backend/ local Express app and api/ serverless HTTP handlers). Treat both as one logical backend: behavior and security assumptions must stay aligned.
- Dual entrypoints: Local dev uses
backend/server.js; production HTTP API lives underapi/*.js. Any change to routes, validation, limits, or Solana flows should be reflected in both places unless intentionally documented otherwise. - Single source of truth: Prefer shared logic in
backend/services,backend/utils, andbackend/db.js; avoid duplicating business rules only inapi/without the same checks in the Express path (or vice versa). - Environment: Verify required secrets only exist server-side (
DATABASE_URL,PRIVATE_KEY,SOLANA_RPC_URL,TOKEN_MINT_ADDRESS, etc.). Never log or return raw secrets, key material, or full connection strings in responses or client-visible errors.
- Authentication model: Public endpoints accept a
wallet_addressstring; there is no cryptographic proof of wallet ownership on submit/claim in the default flow. Audit any new endpoint that moves funds or changes persistent state: document trust boundaries and whether spoofing a wallet address is an accepted risk or mitigated elsewhere. - Private keys: Reward/signing keys must only run on the server. Confirm no
PRIVATE_KEY(or equivalent) is bundled in the frontend build or exposed viaVITE_*variables. - Input validation: All externally supplied fields must pass the same validation as in
backend/services/validationService.js(wallet format, score bounds, optional IDs). Reject malformed input with4xx, not5xx. - Rate limiting and abuse:
submit-scoreis rate-limited per wallet (and IP fallback). When adding write endpoints, define per-identity and per-IP limits and document them in this README. - Dependencies: Periodically run
npm auditand review high/critical findings; pin or upgrade risky transitive dependencies when fixing production issues. - Headers and CORS: Local server uses
helmetandcors; serverless handlers should not weaken security headers without a documented reason.
- Migrations: Schema changes belong in
backend/schema.sqland/or numbered files underbackend/migrations/. Audit that production databases have applied every migration and that constraints match application assumptions (e.g. score caps, claim columns). - SQL: Use parameterized queries only (
backend/db.jspattern). No string-concatenated user input into SQL. - Integrity: Claim/submit flows should avoid double-claim and inconsistent state; review
markClaimed, idempotency, and transaction ordering (e.g. build Solana tx before marking claimed where that pattern is used).
- RPC reliability: Handle RPC/mint/ATA failures without corrupting DB state; users should be able to retry when appropriate.
- Transaction semantics: Document who pays fees, partial signing vs co-signing, and what the client is expected to sign. Any new path that broadcasts from the server must be reviewed for replay, amount correctness, and destination addresses.
- Economics: Reward calculation (
rewardTokensFromScoreRowand related) must match product rules; audit for overflow, rounding, and maximum payout bounds.
- Errors: Prefer stable
error/codefields for clients; avoid leaking stack traces or internal paths in production responses. - Health: Confirm
/health(or equivalent) for the local server and any deploy probes stay lightweight and unauthenticated only if acceptable for your threat model. - Logging: Log structured, non-sensitive context (request id, route, outcome); redact wallet addresses if policy requires minimal PII retention.
- Env vars documented and set on the server (and local
.envfrom.env.example). - Migrations applied to the target database.
- Submit score + claim + any new endpoints manually tested against staging RPC and DB.
- No secret or private key in repo, client bundle, or public logs.
This project uses one codebase for the frontend and backend API:
- Frontend: React + Vite + TailwindCSS + Phaser + Solana Wallet Adapter (Phantom)
- Backend API: serverless HTTP routes (
/api) + PostgreSQL (Neon) +@solana/web3.js+@solana/spl-token
- Brick breaker with unified pointer controls (mouse and touch drag)
- Live score at the top center
- Random jackpot brick per game:
- Value 1000
- Blink animation
- White outline
- Light transparency
- Full black background, white UI (minimal)
- Gameplay only after the wallet is connected
- Game over panel:
- Score
- Submit score
- Claim
- API: submit score + claim SPL reward (serialized transaction)
src/gamePhaser logicsrc/componentsUI / wallet / game containersrc/pagesmain React pageapiserverless API for production deploybackend/routesAPI routesbackend/controllersAPI controllersbackend/servicesDB / validation / Solana
- Install dependencies:
npm install- Copy env:
cp .env.example .env- Create tables in Neon/PostgreSQL:
-- run the contents of backend/schema.sql- Run the backend:
npm run dev:backend- Run the frontend (another terminal):
npm run dev:frontendDefaults: frontend http://localhost:5173, backend http://localhost:3001.
- Connect this repository to your host as one project (frontend + API).
- Add environment variables:
SOLANA_RPC_URLPRIVATE_KEYTOKEN_MINT_ADDRESSDATABASE_URLVITE_SOLANA_RPC_URL
- Deploy. Frontend and API share one origin:
- Frontend:
https://your-domain.com - API:
https://your-domain.com/api/submit-scoreand/api/claim
- Frontend:
The frontend uses relative /api/... paths, so you do not need a separate API base URL.
Body:
{
"wallet_address": "....",
"score": 120
}Validation:
- Max score up to PostgreSQL
INTmax (app-enforced); usebackend/migrations/001_relax_score_max.sqlif upgrading from an old 2000 cap - Rate limit: 5 requests / minute / wallet
Body:
{
"wallet_address": "....",
"score_id": "optional-uuid"
}Logic:
- Resolve the unclaimed score row (by
score_idor latest unclaimed) - Reward is whole tokens 1:1 with the stored score (see
rewardTokensFromScoreRow) - Mark the row claimed
- Build a partially signed SPL transfer; user signs in Phantom
- Return
serializedTransactionas base64
- Never expose the reward wallet private key to the frontend
- The user still signs the transaction in Phantom
- Transaction fee payer is the user wallet