VaultBridge Escrow — MultiChain crypto escrow with reorgSafe blockchain listener and KMS custodial wallets
A NestJS microservice mesh that holds funds between buyer and seller during realWorld transactions (goods, domains, vehicles, milestone work) and releases them only on mutual confirmation or admin dispute resolution. Under the hood: a reorgSafe multiChain blockchain listener (Ethereum, BSC, Polygon, Solana, Tron) feeds deposit events into Kafka; a KMSEncrypted custodial wallet service manages keys across five chain runtimes with hot/funding/cold tiering and noCache decryption. Twelve services, two React/Vite dashboards, one Postgres with perService schemas, glued by Kafka events.
Stack · NestJS 10 · Prisma + PostgreSQL multiSchema · Kafka (kafkajs) · Redis (ioredis, Socket.IO adapter) · AWS KMS · S3 · Secrets Manager · ethers · @solana/web3.js · @solana/spl-token · TronWeb · React + Vite + Tailwind + TanStack Query + Zustand
Domain · Escrow · Fintech · MultiChain infrastructure · Custodial wallets · Microservices · Kafka outbox pattern
Status · Architecture documentation only · No source code included (commercial product)
flowchart LR
Buyer([Buyer])
Seller([Seller])
Admin([Platform admin])
subgraph "UserFacing"
UD[User Dashboard<br/>React + Vite]
AD[Admin Console<br/>React + Vite]
end
subgraph "Service mesh (12 NestJS services)"
BFF[BFF Gateway]
Esc[Escrow Service<br/>state machine]
Wal[Wallet Service<br/>KMS · multiChain executors]
Lis[Listener Engine<br/>multiChain event polling]
Notif[Notification Service]
Ledg[Ledger Service]
Misc[+ auth, admin, compliance,<br/>connect, inquiry, reporting]
end
subgraph "Kafka topics"
K1[deposit.detected]
K2[escrow.transitioned]
K3[wallet.transfer.completed]
K4[notification.requested]
end
subgraph "Chains"
ETH[Ethereum]
BSC[BSC]
POL[Polygon]
SOL[Solana]
TRX[Tron]
end
Buyer --> UD --> BFF
Seller --> UD
Admin --> AD --> BFF
BFF --> Esc
BFF --> Wal
Lis -.->|poll| ETH
Lis -.->|poll| BSC
Lis -.->|poll| POL
Lis -.->|poll| SOL
Lis -.->|poll| TRX
Lis --> K1
Esc --> K2
Wal --> K3
Notif --> K4
K1 --> Esc
K2 --> Notif
K3 --> Ledg
- Problem · Crypto escrow is a fintech product. Customers expect their funds to be held during a transaction and released only when both sides confirm. The complication: customers want to pay in their preferred chain's native token or stablecoin — and that means the escrow has to detect deposits on multiple chains, hold balances in custodial wallets on each chain, and release funds in either direction with chainAppropriate signing.
- Approach · Twelve NestJS services, each scoped to one concern:
escrowruns the state machine;walletmanages keys + transfers;listenerEnginepolls chains;notificationfans out emails/push;complianceruns KYC checks;ledgerrecords the canonical accounting view. Services communicate via Kafka (events) + a BFF gateway (synchronous userFacing queries). Postgres has perService schemas inside one cluster — keeps the operational footprint manageable while preserving service ownership boundaries. - Stack · NestJS 10 + Prisma + PostgreSQL multiSchema + Kafka + Redis. Two React + Vite SPAs (user dashboard + admin console). AWS KMS for key encryption; S3 + Secrets Manager for ops.
- Status · Documentation only. No source code; this case study explains the architecture and key engineering decisions of the two most distinctive services (
listenerEngineandwallet).
A crypto escrow product has unusual constraints compared to a fiat escrow:
- Deposit detection. Fiat deposits come via bank webhooks. Crypto deposits arrive as onChain events that have to be polled, parsed, and confirmed past reorg risk. Five chains means five different event models (ERC20
Transfer, SPL transfer, TRC20Transfer, etc.). - Custody. Holding customer funds means holding private keys. Private keys can't be backed up like passwords; they're irrevocably bearer instruments. Compromise = total loss.
- Withdrawal precision. When releasing escrow funds, you have to know the chain's gasCost model and account quirks (Solana requires Associated Token Account creation; Tron uses SUN, not Wei; HederaStyle HBAR transfer issues exist on some chains).
- State machine complexity. Real escrow has 8+ states (agreement → accepted → funded → delivery → inspection → completed/disputed/cancelled) with admin escape hatches. Every transition must be auditable.
The architecture is built around these constraints. The two services worth examining in detail — listenerEngine and wallet — are where the deep engineering lives.
flowchart TD
A[Buyer creates escrow agreement<br/>userDashboard → BFF → escrow svc]
B[Buyer funds with crypto<br/>sends to perEscrow deposit address]
C[Listener Engine detects onChain Transfer event<br/>past confirmation buffer]
D[Listener publishes Kafka deposit.detected]
E[Escrow service consumes event<br/>transitions state to funded]
F[Seller delivers goods]
G[Buyer marks delivery accepted<br/>state → completed]
H[Wallet service initiates release transfer<br/>KMSDecrypt key, sign on chain, broadcast]
I[Funds arrive at seller's wallet]
J[Ledger service records the full audit trail]
A --> B --> C --> D --> E --> F --> G --> H --> I --> J
Two pieces are worth focusing on:
- Listener Engine (multiChain event detection, deepDive.md) — sequential polling with chainSpecific confirmation buffers, PostgreSQL checkpoint state for replayAfterRestart, RPC failover across multiple endpoints per chain.
- Wallet Service (custodial key management, deepDive.md) — AWS KMS direct encryption (no key caching, fresh decrypt per use), hot/funding/cold key tiering, perChain executors handling EVM (ethers), Solana (SPL + ATA creation), and Tron (TRC20 + SUN math).
- ReorgSafe blockchain listener with confirmation buffer + sequential polling + PostgreSQL checkpoints + replay mode + RPC failover. See techStack.md §1.
- AWS KMS directEncryption with no key caching — every transfer decrypts the key fresh, with an encryption context bound to the operation for audit trail. See techStack.md §2.
- Hot / funding / cold wallet tiering — operational separation between highThroughput payouts and largeValue cold storage. See techStack.md §3.
- Kafka outbox pattern via Prisma — escrow state transitions and Kafka publishes happen in the same DB transaction via an outbox table, guaranteeing exactlyOnce semantics. See techStack.md §6.
- MultiSchema Postgres — twelve NestJS services share one Postgres cluster, each with its own schema, generated Prisma client, and migration history. Balances service isolation with operational sanity.
- BFF gateway pattern — userFacing requests hit a single API surface that orchestrates calls across the internal mesh.
architecture.md # Service mesh topology + Kafka topic map + perService responsibilities
techStack.md # Deep What/Why/When/How/Alternatives report — focused on listener + wallet
deepDive.md # Two chapters: listenerEngine internals, walletService internals
docs/
└── flows/
└── depositDetection.md # Chain → Listener → Kafka → Escrow state transition
No source code is included in this repository. The product is commercial; the codebase spans ~370K LOC across twelve backend services and two SPAs.
The architecture and key engineering decisions are documented here in enough detail to support technical interview discussion. Happy to walk through specific services and patterns in interviews under appropriate NDA.
Built by Hafiz Abdullah · hafiz.abdullah641@gmail.com · Open to interviews under NDA.