A small, focused project that issues per-user deposit addresses, indexes on-chain activity, and exposes simple APIs your frontend can use to behave like a lightweight CEX.
- BE: Express + MongoDB API for signup/auth and getting deposit addresses.
- Indexer: EVM block listener (ethers v6) + Redis + MongoDB. Tracks deposits for “interested addresses” and updates user balances.
┌──────────────────────────────────┐
│ Frontend │
│ (login, show address, balance) │
└───────────────┬──────────────────┘
│ REST
▼
┌──────────────┐
│ BE │
│ Express API │
└──────┬───────┘
│ Mongo (Users)
▼
┌────────────┐
│ MongoDB │
└────────────┘
▲
│ writes (balances)
Redis (checkpoint, addr cache) │
┌──────────────────┐ │
│ Redis │◄─────┘
└────────┬─────────┘
│
▼
┌──────────────┐ EVM RPC
│ Indexer │────────────► Provider
│ (ethers v6) │ (Sepolia/Mainnet)
└──────────────┘
- Load "interested addresses" (deposit addresses) from MongoDB. Cache them in Redis.
- Read last processed block from Redis (checkpoint). If none, start at current head.
- On each new block:
- Fetch full transactions for that block in one call.
- Filter txs where
fromortomatches an interested address (normalized lowercase). - For each relevant address: fetch balance via provider and upsert to Mongo; also cache results in Redis.
- Save the processed block number back to Redis.
src/config/block.tsprocessBlock(blockNumber, addresses)– fetch block with full transactions (1 RPC), filter for relevant addresses, update balances.startBlockListener(addresses)– subscribe to new blocks, process sequentially with catch-up logging, save checkpoints.
src/services/addresses.tsLoadInteresetdAddresses()– load deposit addresses from Mongo, normalize to lowercase, cache in Redis.
src/services/bloclService.tsgetLastProcessedblock()/setLastProcessedBlock(n)– checkpoint helpers stored in Redis.
src/services/Updatedb.tscheck_balance_update_db(address)– get on-chain balance, compare with DB, update if changed, cache in Redis.
POST /api/auth/signup- Creates a new user: derives a unique deposit address (HD path), stores
email,DepositAddress,userId,privateKey, and initialbalance. - Invalidates Redis key
interested_addressesso the indexer reloads the new address.
- Creates a new user: derives a unique deposit address (HD path), stores
GET /api/getAddress- Returns the user's deposit address (implementation lives under
routes/DepositAddress).
- Returns the user's deposit address (implementation lives under
GET /health- Liveness endpoint.
Create .env files for BE and Indexer with at least:
ETH_RPC_URL=... # e.g., https://sepolia.infura.io/v3/<key>
MONGO_DB_CONNECTION=... # e.g., mongodb://localhost:27017/stake
REDIS_URL=redis://localhost:6379
PORT=5000 # for BE
- Infra (one-time):
docker run -d --name mongo -p 27017:27017 mongo:7
docker run -d --name redis -p 6379:6379 redis:7
- Backend:
cd BE
npm i
npm run dev
- Indexer:
cd indexer
npm i
npm run dev