| src | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
Bank28 reference backend
Minimal Express server that demonstrates the full Bank28-class neobank integration with AERE Network, alongside a fiat BaaS provider (Striga, Dipocket, Modulr, Solaris, …).
This is a reference, not a production app. The intent is for the Bank28 build team to copy this skeleton and harden it — replace the in-memory Map with Postgres, replace the env-var private key with KMS-managed signing, add structured logging, etc.
Architecture demonstrated
Bank28 web/mobile app
│
│ Privy/Magic provisioned wallet (email/social → EVM address)
▼
┌──────────── this backend ────────────┐ ┌────── BaaS provider ──────┐
│ /signup │◄──►│ KYC vendor │
│ /webhooks/baas/kyc-cleared │ │ IBAN issuance │
│ └─► aere.identity.addClaim() │ │ Card issuance │
│ /users/:id/portfolio │ │ SEPA / SWIFT settlement │
│ └─► aere.getPortfolio() │ └───────────────────────────┘
│ /users/:id/earn │
│ └─► returns tx instructions for Privy to sign
│ /webhooks/onramp/deposit │◄── MoonPay / Transak / Ramp
│ Live deposit watcher │◄── AERE Network (rpc.aere.network)
└───────────────────────────────────────┘
What it shows
- Wallet provisioning is non-custodial. The user's AERE address is created client-side by Privy/Magic (email login). The backend just records it. No seed phrases, no key custody, no Bank28 liability for lost keys.
- KYC attestation on-chain. When the BaaS provider clears a user, this backend writes a
kyc-tier-1claim toAereIdentityagainst the user's address. Smart contracts that gate features by KYC (lending, higher transfer limits, fiat off-ramp) checkhasValidClaim()against the Bank28 attestor address. - Portfolio aggregation. Multi-asset balance via
aere.getPortfolio()— native AERE + WAERE + AereUSD in one call. - Yield without custody.
/users/:id/earnreturns transaction instructions for the client to sign via Privy. The backend never holds the user's signing key. - Live deposit watcher. Polls blocks, matches
tx.toagainst the user registry, fires when a user receives AERE — same shape as Stripe webhooks for fiat.
Run
# 1. Build the SDK first (sibling package)
cd ../sdk-js && npm install && npx tsc
# 2. Run this backend
cd ../bank28-reference-backend
npm install
OPS_PRIVATE_KEY=0x… npm run dev
The OPS_PRIVATE_KEY is the Bank28 attestor identity — the address that signs KYC claims on-chain. In production this should be a hardware-backed or KMS-managed key, never an env var.
Endpoints
| Method | Path | Purpose |
|---|---|---|
POST |
/signup |
Create a Bank28 user record. Body: {email, aereAddress, baasUserId}. |
POST |
/webhooks/baas/kyc-cleared |
BaaS provider webhook → writes on-chain KYC attestation. Body: {baasUserId, reportHash, tier}. |
GET |
/users/:id/portfolio |
Multi-asset balance + KYC status. |
POST |
/users/:id/earn |
Returns tx instructions for the client to sign (locks AERE in AereStakingV2). Body: {tier, amountAere}. |
POST |
/webhooks/onramp/deposit |
MoonPay/Transak webhook on fiat→crypto deposit completion. |
GET |
/healthz |
Health check. |
Production checklist
- Replace the in-memory
usersMap with Postgres + Drizzle/Prisma. - Move
OPS_PRIVATE_KEYto AWS KMS / HashiCorp Vault / GCP Cloud KMS — never an env var in production. - Verify webhook signatures from the BaaS provider (HMAC) and on-ramp provider before trusting payloads.
- Add idempotency keys to all webhook handlers to prevent double-processing.
- Add structured logging (pino) and metrics (Prometheus).
- Add rate-limit middleware to all public endpoints.
- Replace polling deposit watcher with WebSocket subscription (
aere.watchTransfersTo). - Implement Travel Rule (FATF R.16) — Notabene if BaaS provider doesn't include it.
- Set up monitoring on the ops wallet's gas balance (alert if < 1 AERE).
- Audit — see
aerenew/audit-prep/INVARIANTS.mdfor the on-chain side; commission a separate web-app audit for this server.