On-chain architecture
The system splits responsibilities between the VPS and Vercel so the treasury private key never leaves trusted infrastructure.
Component diagram
┌──────────────────┐ GET /api/blockchain/pending-batches ┌──────────────┐
│ Vercel (Next.js) │ ────────────────────────────────────────▶│ VPS cron │
│ │ │ (signs txs) │
│ /stats │ ◀──────────── POST /mark-batched ───────│ │
│ /blockchain │ └──────────────┘
│ HistoryTable │ │
│ │ ▼
└──────────────────┘ Base mainnet
▲ KatamaCredits.sol
│
│ read blockchain_logs
▼
┌──────────────────┐
│ Postgres (VPS) │
│ blockchain_logs │
│ blockchain_stats │
└──────────────────┘Daily flow
- 00:00 UTC — VPS cron fires
node /root/projects/katama-blockchain/daily-batch.js. - Script calls
GET /api/blockchain/pending-batches?date=YYYY-MM-DDwith theCRON_SECRETbearer token. Vercel aggregates yesterday's generations per user and writesblockchain_logsrows withstatus='pending'. - For each pending row, the script claims it (
POST /pending-batchesflips it tobroadcasting) and signslogUserDaily(...)on the contract. - On confirmation, the script reports the TX hash back via
POST /mark-batched. Vercel updates the row toconfirmedand rolls upblockchain_platform_stats. - Failed broadcasts retry up to 3× with backoff. After that, the row stays in
failedwith the error string for diagnosis.
Why this split
Treasury key on VPS only
The signing wallet's private key lives in /root/.katama-treasury.key with mode 600. It is never copied to Vercel, never logged, never echoed to stdout. Compromising Vercel leaks user data but not signing capability.
DB on VPS, Vercel reads remotely
Postgres runs locally on the VPS. The Vercel app connects to the VPS via the public IP on port 5432. The VPS-side signer connects to the same DB over localhost, so it sees the same data without an extra network hop.
CRON_SECRET as the only API trust boundary
The three blockchain endpoints — /api/blockchain/pending-batches, /api/blockchain/mark-batched, and /api/admin/migrate — all require a Bearer token equal to CRON_SECRET. No user session can reach them.
Idempotency
blockchain_logshas a unique index on(user_id, date). Re-running pending-batches the same day inserts nothing new; it returns the same pending rows.KatamaCredits.solstoresbatchKeyUsed[keccak256(userIdHash, date)]and reverts on duplicate calls. Even if the script somehow broadcasts the same batch twice, only one succeeds on-chain.- A confirmed row will not be re-rolled into
blockchain_platform_statsbecause mark-batched short-circuits when the same logId+txHash arrives twice.
Files
| Path | Role |
|---|---|
/root/projects/katama-blockchain/contracts/KatamaCredits.sol | Smart contract source |
/root/projects/katama-blockchain/build/KatamaCredits.abi.json | Compiled ABI |
/root/projects/katama-blockchain/deploy.js | One-shot Base deployment |
/root/projects/katama-blockchain/daily-batch.js | Cron entrypoint |
web/src/lib/blockchain/index.ts | Shared helpers (hash, dates, links) |
web/src/app/api/blockchain/* | 3 Vercel endpoints |
Next: Smart contract →