Real-time anti-fraud for a real-money gaming wallet. Money and the ledger live in DynamoDB (system of record); an Amazon Neptune fraud graph sits beside it and decides — in the ~50 ms it takes to process a deposit — whether an account is a real person or one node in a sockpuppet ring, and therefore how much referral bonus to release.
The right tool for each job: DynamoDB as the scalable system of record, Neptune as the graph that catches connected-component fraud a relational join never could. See
GuardGraph_Concept_Brief.pdf.
This build is code-complete against local emulators — DynamoDB Local + Memgraph (openCypher, Neptune's query language) behind swappable adapters. Flip to real AWS by changing env vars in lib/config.ts; the SDK calls and openCypher queries are unchanged.
Next.js (App Router) · TypeScript · Tailwind + dark-ops theme · AWS SDK v3 · neo4j-driver (openCypher over Bolt) · react-force-graph · Vitest · pnpm.
- Node 20+ and
pnpm - Docker (for DynamoDB Local + Memgraph)
pnpm install
docker compose up -d # DynamoDB Local :8000 + Memgraph :7687
pnpm create-table # create the GuardGraph single table + GSIs
pnpm dev # http://localhost:3000Open:
- Player wallet → http://localhost:3000/wallet — deposit, play, invite a friend, get (or not) the $10 bonus.
- Operator console (the hero) → http://localhost:3000/console — live fraud graph, event ticker, decision panel, and demo controls.
With the app running and /console open:
pnpm tsx scripts/demo.ts # legit user (FULL) → 3 sockpuppets → 3rd DENIED liveOr click "Spin up sockpuppet ring" in the console header. See docs/demo-script.md for the 90-second narrative.
SEED_N=100000 pnpm seed:graph --fresh # bulk-load synthetic graph + planted rings
# cluster query: p50 ~6ms p99 ~10ms over 100,000 nodes (flat — see docs/cost-at-scale.md)
pnpm seed:dynamo # seed DynamoDB Local with users + wallets
pnpm loadtest:local # hammer /api/deposit, report p50/p95/p99
# (k6 variant: pnpm loadtest — requires k6 installed)Player Wallet ─┐ ┌─ DynamoDB (PRIMARY: wallets, ledger, txns)
├─ /api (Vercel functions) ────┤ │ TransactWriteItems (atomic)
Operator ──────┘ deposit hot path │ └─ Streams → Lambda ─┐
Console (graph) write money → ask graph └─ Neptune (DERIVED fraud graph) ←┘
→ rules ladder → verdict openCypher 1-2 hop · ~50 ms
DynamoDB = truth, Neptune = derived. Graph maintenance runs off the deposit hot path (demo path upserts synchronously for a snappy reveal; production drains a Streams→Lambda outbox). One source layout:
lib/ types.ts (contract) · config.ts · dynamo/ · graph/ · decision/ · stream/ · services.ts · events.ts
app/ (player)/wallet · (operator)/console · api/{signup,deposit,withdraw,graph,events}
infra/ dynamodb-table.{local,aws}.json scripts/ create-table · seed-graph · seed-dynamo · demo · loadtest
docs/ cost-at-scale.md · demo-script.md
pnpm typecheck # tsc --noEmit
pnpm lint
pnpm test # vitest — 150+ tests incl. live DynamoDB Local + Memgraph integration
pnpm buildSet GUARDGRAPH_ENV=aws and the Dynamo/Neptune endpoints in the environment; deploy infra/dynamodb-table.aws.json (synthetic composite-key GSIs, identical schema to local). No business-code changes. See docs/aws-deployment.md.