Data Flow
This page explains how data moves through the NMT platform, from a user clicking "Deposit" to seeing their position value update.
Transaction Flow: Safe Mode (Writing to Blockchain)
When a delegate executes a DeFi strategy through a user's Safe:
1. Delegate clicks "Deposit $1,000 into Aave" in the frontend
|
2. Frontend builds an array of BundlerCalls
(approve USDC + supply USDC, encoded via DeFi adapters)
|
3. Frontend reads nonce + EIP-712 domain from DelegateBundler contract
|
4. Delegate signs an EIP-712 typed message:
{ callsDataHash, nonce, deadline }
|
5. Frontend encodes DelegateBundler.execute(delegate, deadline, calls, signature)
|
6. Frontend sends the encoded calldata to Gelato Relay API
(server-side, using GELATO_API_KEY via Next.js server action)
|
7. Gelato's relayer (0xC4E7...6888) submits the tx on-chain
and PAYS THE GAS — the delegate/user pays nothing
|
8. On-chain: DelegateBundler.execute() runs:
a) Verifies the delegate's EIP-712 signature
b) Checks nonce (replay protection) and deadline (expiry)
c) Loops through each call in the bundle:
→ AaveModule._checkPermission(safe, delegate, "APPROVE")
→ AaveModule._checkPermission(safe, delegate, "DEPOSIT")
→ Each module calls Safe.execTransactionFromModule()
|
9. Blockchain emits events (Supply, Transfer, etc.)
|
10. Backend workers detect the events and update PostgreSQL
|
11. Frontend reads updated position from DB on next page load
Where Gelato Fits
Gelato is an off-chain transaction relay service. It is NOT a smart contract in the execution path. Its role:
- Receives the fully-encoded transaction calldata from the frontend (via
relay.sponsoredCall()) - Submits it on-chain from Gelato's own relayer address
- Pays gas on behalf of the user (NMT's Gelato account covers the cost)
- Reports status back to the frontend (ExecSuccess, ExecReverted, Cancelled)
The frontend polls Gelato's task status API every 3 seconds (up to 15 attempts) to confirm execution.
Frontend (browser)
|
| 1. encodeExecute() → raw calldata
v
Next.js Server Action (server-side)
|
| 2. GelatoRelay.sponsoredCall(calldata, GELATO_API_KEY)
v
Gelato Relay Network (off-chain)
|
| 3. Submits tx from relayer address, pays gas
v
Blockchain → DelegateBundler.execute() → Modules → Safe
Transaction Flow: EOA Mode (Writing to Blockchain)
When a user trades directly from their wallet (no Safe):
1. User clicks "Withdraw from Aave" in the frontend
|
2. Frontend encodes the withdraw calldata via DeFi adapter
|
3. walletClient.sendTransaction({ to: aavePool, data: ... })
User signs directly and pays their own gas
|
4. Transaction lands on-chain (no bundler, no modules, no permissions)
|
5. Blockchain emits events → Backend indexes → DB updated
Data Indexing Flow (Reading from Blockchain)
The backend continuously monitors the blockchain for relevant events:
Blockchain (Base, Ethereum, Arbitrum, Optimism)
|
| WebSocket subscription (Alchemy WSS)
| + Periodic RPC polling (dRPC HTTPS)
v
Backend Workers
|
|--- DefiWorkerPool: per-protocol pool services
| (fetches pool metadata, TVL, APY)
| (every 30 min for most, every 2 min for Uniswap V3/V4)
|
|--- PositionCoordinator:
| |--- LogListener: real-time WebSocket events
| |--- LogProcessor: parses events, stores positions
| |--- GapFiller: backfills missed blocks + valuation recalcs (every 24h)
|
|--- PriceFetcher: on-demand token prices (CoinGecko)
| (called by other workers as needed, not a background worker)
|
v
PostgreSQL
|
|--- nmt_pool: pool-level data (APY, TVL, tokens)
|--- nmt_position: user positions (status, amounts)
|--- nmt_position_valuation: USD value snapshots
|--- price: token price history
|--- apy_history: historical APY data
Frontend Data Sources
The frontend pulls data from multiple sources depending on the use case:
| Data Type | Source | Method | Why |
|---|---|---|---|
| User settings, Safe configs | PostgreSQL | Prisma (server components) | Fast, no API hop needed |
| Pool listings, APYs | Backend API | HTTP REST (/api/v1/pools) |
Aggregated by backend workers |
| Token balances | Blockchain | Viem RPC calls | Real-time accuracy |
| Portfolio history | Zerion API | HTTP REST | Rich historical data |
| Token prices | CoinGecko / Backend | HTTP REST | Price feeds |
| Chat messages | CometChat | WebSocket SDK | Real-time messaging |
Update Frequencies
| Data | Update Interval | Source |
|---|---|---|
| Pool metadata (most protocols) | Every 30 minutes | Backend workers |
| Pool metadata (Uniswap V3/V4) | Every 2 minutes | Backend workers |
| Position valuations | Every 24 hours | Backend workers |
| Token prices | On-demand + cached | CoinGecko API |
| Subscription status | Every 1 hour | Backend workers |
| User balances | Real-time on page load | Blockchain RPC |
EOA Position Tracking
EOA (direct wallet) positions are not tracked by the backend workers. The backend's position detection relies on events emitted by NMT's custom protocol modules (PositionOpened, PositionClosed, etc.), which only fire during Safe-based execution via the DelegateBundler.
For EOA users, the frontend relies on Zerion API to provide portfolio data — token balances, DeFi positions, and transaction history. Zerion independently indexes all major DeFi protocols and returns position data for any Ethereum address.
| Mode | Position Source | Valuation Source |
|---|---|---|
| Safe (delegate) | Backend workers (on-chain events → PostgreSQL) | Backend workers (periodic valuation snapshots) |
| EOA (direct wallet) | Zerion API | Zerion API |
This means EOA positions do not appear in the nmt_position table and are not included in backend-computed analytics. The frontend bridges this gap at the display layer by merging data from both sources.