Stay early. Stay safe in the dark forest.
Detect aggregated blockchain events and get notified before it's too late.
TellTide monitors Morpho Markets and ERC4626 vaults across Ethereum and Base and alerts you when aggregated conditions are met. Instead of tracking individual transactions, subscribe to meaningful signals like:
- π¨ "Alert when net withdrawals from Morpho markets exceed 1M USDC in 1 hour" (supply - withdraw)
- π "Notify when net borrows across 4 markets exceed $500K in 30 minutes" (borrow - repay)
- π "Trigger when specific Morpho market has net supply drop below -100K USDC"
- π "Track whale activity: alert when net withdrawals from vault exceed threshold"
- β‘ "Monitor market liquidity: detect when net borrows spike in short timeframes"
Perfect for monitoring DeFi protocols, tracking market flows, and detecting unusual on-chain activity in Morpho and ERC4626 vaults.
- β‘ Real-time Indexing - Uses SQD Pipes SDK to index Morpho market events and ERC4626 vault events
- π― Net Flow Detection - Track net supply/withdraw and net borrow/repay across markets and vaults
- π Meta-Event Detection - Create conditions on rolling time windows with aggregations (sum, avg, count, etc.)
- π Webhook Notifications - Automatic HTTP POST to your endpoint with retry logic
- π¦ Multi-Market Support - Monitor multiple Morpho markets and vaults simultaneously
Ethereum + Base Blockchains (Morpho Markets + ERC4626 Vaults)
β
SQD Portal API (fast historical data)
β
TellTide Indexer (Morpho + ERC4626 events)
β
PostgreSQL Database (events with market_id)
β
Detection Worker (net flow calculations every 30s)
β
Your Webhook URL π―
- Node.js 18+
- Docker & Docker Compose
- pnpm
1. Clone and install:
pnpm install2. Start PostgreSQL:
docker compose up -d3. Setup environment:
cp .env.example .env
# Edit .env if needed (defaults work for local dev)4. Run database migrations:
pnpm db:migrate5. Start all services:
# Start everything (unified multi-chain indexer + worker + api)
pnpm dev
# OR start individually in separate terminals:
pnpm indexer # Terminal 1 - Indexes BOTH Ethereum + Base
pnpm worker # Terminal 2
pnpm api # Terminal 3
# OR run individual chain indexers (if needed):
pnpm indexer:ethereum # Only Ethereum
pnpm indexer:base # Only BaseServices running:
- π‘ API: http://localhost:3000
- π Indexer: Unified multi-chain indexer for Ethereum + Base (ERC20 + ERC4626 + Morpho events)
- βοΈ Worker: Checking subscriptions every 30s
6. (Optional) Insert example subscriptions:
Quickly create example meta-event subscriptions for testing:
# Edit src/db/insert-subscriptions.ts first:
# - Set your webhook.site URL
# - Update contract addresses to real vaults/tokens
pnpm db:insert-subsExample 1: Net Withdrawal Alert for Morpho Market
curl -X POST http://localhost:3000/api/subscriptions \
-H "Content-Type: application/json" \
-d '{
"user_id": "alice",
"name": "Morpho Market Net Withdrawal Alert",
"webhook_url": "https://webhook.site/your-unique-url",
"cooldown_minutes": 5,
"meta_event_config": {
"chain": "ethereum",
"type": "net_aggregate",
"event_type": "morpho_supply",
"positive_event_type": "morpho_supply",
"negative_event_type": "morpho_withdraw",
"market_id": "0x58e212060645d18eab6d9b2af3d56fbc906a92ff5667385f616f662c70372284",
"window": "1h",
"aggregation": "sum",
"field": "assets",
"condition": {
"operator": "<",
"value": -1000000000000
}
}
}'This triggers when: Net withdrawals (supply - withdraw) from the specific Morpho market exceed 1M USDC in 1 hour (negative value means more withdrawals than supply). Won't send another notification for 5 minutes after triggering.
Note: No contract_address needed - automatically uses Morpho contract!
Example 2: Net Borrow Alert Across Multiple Markets
curl -X POST http://localhost:3000/api/subscriptions \
-H "Content-Type: application/json" \
-d '{
"user_id": "bob",
"name": "High Net Borrow Alert",
"webhook_url": "https://webhook.site/your-unique-url",
"cooldown_minutes": 10,
"meta_event_config": {
"chain": "ethereum",
"type": "net_aggregate",
"event_type": "morpho_borrow",
"positive_event_type": "morpho_borrow",
"negative_event_type": "morpho_repay",
"window": "30m",
"aggregation": "sum",
"field": "assets",
"condition": {
"operator": ">",
"value": 500000000000
}
}
}'This triggers when: Net borrows (borrow - repay) across ALL Morpho markets exceed 500K USDC in 30 minutes.
Note: No contract_address or contracts needed - automatically monitors all Morpho markets!
1. Monitor Net Supply Drop in Morpho Market
{
"chain": "ethereum",
"type": "net_aggregate",
"event_type": "morpho_supply",
"positive_event_type": "morpho_supply",
"negative_event_type": "morpho_withdraw",
"market_id": "0x58e212060645d18eab6d9b2af3d56fbc906a92ff5667385f616f662c70372284",
"window": "1h",
"aggregation": "sum",
"field": "assets",
"condition": {
"operator": "<",
"value": -100000000000
}
}Triggers when net supply (supply - withdraw) drops below -100K USDC in 1 hour for a specific Morpho market. Detects rapid liquidity exits.
2. Track Net Borrow Spike Across Multiple Morpho Markets
{
"chain": "ethereum",
"type": "net_aggregate",
"event_type": "morpho_borrow",
"positive_event_type": "morpho_borrow",
"negative_event_type": "morpho_repay",
"contracts": ["0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb"],
"window": "30m",
"aggregation": "sum",
"field": "assets",
"condition": {
"operator": ">",
"value": 500000000000
}
}Triggers when net borrows (borrow - repay) exceed 500K USDC in 30 minutes across all markets in the Morpho contract.
3. Monitor Net Withdrawals from ERC4626 Vault
{
"chain": "ethereum",
"type": "net_aggregate",
"event_type": "erc4626_deposit",
"positive_event_type": "erc4626_deposit",
"negative_event_type": "erc4626_withdraw",
"contract_address": "0xVaultAddress...",
"window": "2h",
"aggregation": "sum",
"field": "assets",
"condition": {
"operator": "<",
"value": -1000000000000
}
}Triggers when net deposits (deposits - withdrawals) drop below -1M USDC in 2 hours. Monitors vault outflows.
4. Track High Supply Activity in Specific Morpho Market (Base)
{
"chain": "base",
"type": "rolling_aggregate",
"event_type": "morpho_supply",
"contract_address": "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb",
"market_id": "0x...",
"window": "15m",
"lookback_blocks": 450,
"aggregation": "sum",
"field": "assets",
"condition": {
"operator": ">",
"value": 100000000000
}
}Triggers when total supply to a Morpho market exceeds 100K USDC in last 450 blocks on Base (~15 minutes with 2s blocks). Using lookback_blocks makes queries more efficient.
When triggered, your webhook receives:
{
"subscription_id": "uuid",
"subscription_name": "High Vault Withdrawal Alert",
"triggered_at": "2025-11-22T10:30:00.000Z",
"meta_event": {
"type": "rolling_aggregate",
"condition_met": true,
"aggregated_value": 1500000000000,
"threshold": 1000000000000,
"window": "2h",
"triggered_by_contract": "0xVaultB..."
}
}Base URL: http://localhost:3000
POST /api/subscriptions- Create a new meta-event subscriptionGET /api/subscriptions- List all subscriptionsGET /api/subscriptions/:id- Get subscription detailsPATCH /api/subscriptions/:id- Update subscriptionDELETE /api/subscriptions/:id- Delete subscriptionGET /api/subscriptions/:id/notifications- Get notification history
GET /health- Health check endpoint
Full API Documentation: See ARCHITECTURE.md for complete request/response formats and examples.
Key environment variables (see .env.example):
| Variable | Description | Default |
|---|---|---|
DATABASE_URL |
PostgreSQL connection string | postgresql://postgres:postgres@localhost:5432/telltide |
API_PORT |
API server port | 3000 |
WORKER_INTERVAL_SECONDS |
How often to check subscriptions | 30 |
ETHEREUM_RPC_URL |
Ethereum RPC endpoint for getting current block | https://eth.llamarpc.com |
BASE_RPC_URL |
Base RPC endpoint for getting current block | https://mainnet.base.org |
INDEXER_MAX_LOOKBACK_BLOCKS |
Max blocks back from chain head | 10000 |
INDEXER_CHAIN |
Run single chain only (optional: ethereum or base) |
not set (runs both) |
Note on indexing:
- By default,
pnpm indexerruns a unified multi-chain indexer that monitors both Ethereum and Base simultaneously in a single process - The indexer dynamically calculates the start block on every startup by fetching the current blockchain head via RPC and going back
MAX_LOOKBACK_BLOCKS - All events are tagged with their chain (
ethereumorbase) and stored in the same database - You can optionally run individual chain indexers with
pnpm indexer:ethereumorpnpm indexer:base - Duplicate events are automatically skipped
Quickly create example subscriptions for testing:
pnpm db:insert-subsThis creates example meta-event subscriptions. Edit the script first to set your webhook URL and enable the subscriptions you want.
Delete all data without destroying the database schema:
pnpm db:cleanThis removes all events, subscriptions, notifications, and resets the indexer cursor. Useful for testing from a clean slate.
docker compose down -v
docker compose up -d
pnpm db:migratedocker exec -it telltide-postgres psql -U postgres -d telltideUse webhook.site to get a test webhook URL.
morpho_supply- Morpho market supply events (market_id, caller, onBehalf, assets, shares)morpho_withdraw- Morpho market withdraw events (market_id, caller, onBehalf, receiver, assets, shares)morpho_borrow- Morpho market borrow events (market_id, caller, onBehalf, receiver, assets, shares)morpho_repay- Morpho market repay events (market_id, caller, onBehalf, assets, shares)
Morpho Blue Contract: 0xbbbbbbbbbb9cc5e90e3b3af64bdaf62c37eeffcb (same address on Ethereum and Base)
β¨ Important Notes:
- The indexer is configured to ONLY capture Morpho events from this specific contract address to save bandwidth
- You do NOT need to specify
contract_addresswhen creating subscriptions for Morpho events - it's automatically injected! - Just specify the event type (e.g.,
morpho_supply) and optionally filter bymarket_id
erc4626_deposit- ERC4626 vault deposits (sender, owner, assets, shares)erc4626_withdraw- ERC4626 vault withdrawals (sender, receiver, owner, assets, shares)
erc20_transfer- ERC20 Transfer events (from, to, value)
event_count- Count events in time windowrolling_aggregate- Aggregate field values (sum, avg, min, max) in time windownet_aggregate- Calculate net flow: positive events - negative events (e.g., supply - withdraw, borrow - repay)
Supported formats: 15m, 1h, 2h, 24h, 7d, etc.
- README.md - Quick start guide and overview (you are here!)
- ARCHITECTURE.md - Complete technical reference, API docs, and AI context
This is a hackathon project. Contributions welcome!
ISC