Overview
What it is
GKOI is a multi-service Web3/NFT platform, deliberately not a monolith. Three cooperating Express 5 + TypeScript + MongoDB + Redis services split the surface along a single fault line — is this code mint-critical? — and everything that touches who is allowed to mint, or who is an admin, is carved into its own small, separately-auditable service.
| Service | Owns (source of truth) | Why it's isolated | Notable deps |
|---|---|---|---|
gkoi-authentications | Wallet login (EIP-4361 / SIWE), JWT issue/verify, the admin-role registry | Auth is the identity trust boundary — small enough to audit, blast-radius-limited | viem, jsonwebtoken |
gkoi-whitelist | The presale Merkle allowlist (root + proofs) and IPFS contract-metadata pinning | The mint gate; scales and fails independently of the core API | merkletreejs, ethers 6, Pinata |
gkoi-server | Everything non-mint-critical: NFT metadata/pricing, collections, contests, galleries, DeFi/swaps, gaming, leaderboards, audit | The large, evolving surface; kept away from the mint path | alchemy-sdk, moralis, @uniswap/v4-sdk, sharp, migrate-mongo |
All three share one four-layer convention (routes → controllers → services →
models), one IResponseData<T> = { code, message, data } envelope, Redis-backed
rate limiting, Prometheus databaseResponseTimeHistogram timers labelled
{operation, collection, method, success}, and one HMAC service-to-service auth
scheme — so the three services read as one system without sharing a database.
Wallet login without format drift
gkoi-authentications builds an EIP-4361 (Sign-In-with-Ethereum) message
server-side, stores it, and verifies the client's signature against the exact
stored copy — the client signs verbatim and never reconstructs the message, which
eliminates the entire class of client/server format-drift bugs. Verification
consumes a single-use nonce, asserts it was issued for that exact account, then
checks the signature with viem, and each distinct failure returns its own reason
(rather than a bare false) so a slow/replayed nonce can be told apart from a
wrong-key signature:
const stored = await consumeLoginNonce(nonce);
if (!stored) return { ok: false, reason: "nonce_missing_or_expired" };
if (stored.account !== account.toLowerCase())
return { ok: false, reason: "nonce_account_mismatch" };
const verified = await viemPublicClient.verifyMessage({
address: account as `0x${string}`, message: stored.message,
signature: signature as `0x${string}`,
});
return verified ? { ok: true } : { ok: false, reason: "signature_mismatch" };
Those reasons feed a failed-signature-burst anomaly ticket — the auth service watches itself.
Service-to-service auth: signed, replay-bounded, path-independent
Services never trust an ambient ADMIN_API_KEY or a shared DB; every cross-service
call is HMAC-SHA256 signed over [METHOD, ts, nonce, sha256(body)]. The path is
deliberately excluded from the signed message, so a signature survives a
proxy/mount-prefix change, and freshness is checked directionally — up to five
minutes in the past but only thirty seconds in the future, so a far-future
timestamp can't be parked inside the window:
function buildMessage({ method, timestamp, nonce, body }): string {
return [method.toUpperCase(), timestamp, nonce, sha256(body ?? "")].join("
");
}
function isTimestampFresh(ts: string): boolean {
const age = Date.now() - Number(ts); // >0 = past, <0 = future
return age <= SKEW_SECONDS * 1000 && age >= -FUTURE_SKEW_SECONDS * 1000;
}
A one-time Redis nonce (svc:nonce:<uuid>, TTL = 2× the skew) blocks replays, and
authorization is per-route by scope keyed on the authenticated x-svc-id — so
gkoi-authentications is granted exactly admins:cache:invalidate on the whitelist
and nothing else. Least privilege between services, not just between users.
The Merkle whitelist — scaling apart from the mint
Leaves are keccak256(address); the tree is built with merkletreejs under
sortPairs: true so the contract's on-chain MerkleProof.verify matches
regardless of pair order. Only a 32-byte root ever goes on-chain, so allowlist
size never inflates mint gas or contract storage — verification is O(log n) inside
the contract. The service's own trick is that it caches the sorted address
snapshot (the exact input the Merkle helpers hash), not a serialized tree, so a
cached /proof is byte-identical to a cold rebuild — and to the cached /root.
Root and snapshot share one logical version, busted together on every add/remove
via invalidateWhitelistCaches; a short TTL is only a safety net behind that
explicit invalidation. (The companion post drills into this build/proof/verify
flow.)
Writes are correct under load, too: batch add is a single unordered
insertMany({ ordered: false }) that treats a duplicate-key (11000) as "already
present → null" while preserving input order/length, and the full-list read that
feeds the tree is hard-capped by an always-applied $limit so an unbounded $sort
can never be streamed back.
Schema evolution: guarded, idempotent migrations
Schema changes run through migrate-mongo with useFileHash: true — a content
checksum decides re-runs, so every migration is written to be safely re-runnable.
The rename of the admin audit collection is representative: existence-checked on
both sides so a re-run or a fresh DB is a no-op, and renameCollection preserves
every index (including the TTL) so retention is untouched.
async up(db) {
const src = await db.listCollections({ name: "audit-logs" }).toArray();
const targetExists =
(await db.listCollections({ name: "admin-audit-logs" }).toArray()).length > 0;
if (src.length > 0 && !targetExists)
await db.collection("audit-logs").rename("admin-audit-logs");
}
The backfill_user_holdings migration goes further — a $group + $merge upsert
(unique index on {account, tokenAddress}, allowDiskUse, createdAt preserved on
match) that aggregates nfts by owner into a user-holdings collection as the
foundation for a co-ownership recommendation signal, idempotent by construction.
Auditing the auditor
Audit is a first-class, two-stream concern (admin + user), and the whitelist
controllers carry an explicit rule — actorAccount: req.account ?? "unknown", with
the comment "never substitute the target as the actor" — so an admin acting on a
user's address is never mislogged as the user.
Testing & security posture
Mocha + Chai + Sinon with nyc coverage; Supertest exercises the HTTP surface on
gkoi-server and gkoi-authentications (not the whitelist). Biome plus a
ts.check + lint-staged pre-commit gate. The strongest signal here isn't a badge:
gkoi-authentications ships its own SECURITY.md (multi-tier Redis rate limits,
exact-string CORS matching after a regex-matching vuln was removed) and a dated
SECURITY_AUDIT.md that tallies findings with file/line citations. The author
security-reviews his own mint-critical services and records the results — which is
exactly what carving the trust boundaries small enough to audit was for.