Skip to content

Commit 2766df4

Browse files
committed
add claude.md
1 parent 2cbbc0d commit 2766df4

1 file changed

Lines changed: 178 additions & 0 deletions

File tree

CLAUDE.md

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
DefiLlama Bridges Server — aggregates cross-chain bridge transaction data from 100+ protocols. Fetches on-chain events via adapters, stores in PostgreSQL, aggregates into hourly/daily volumes, and exposes a REST API via Fastify.
8+
9+
## Commands
10+
11+
- **Type check:** `npm run ts` (runs `tsc --noEmit`)
12+
- **Build:** `npm run build` (Vite → `dist/`, CJS output)
13+
- **Dev server:** `npm run dev` (requires `.env` file; tsx watch on port 3000)
14+
- **Production server:** `npm run start` (runs `dist/index.js`)
15+
- **Cron worker:** `npm run start:cron` (runs `dist/startCron.js`)
16+
- **Test adapter:** `npm run test <bridgeName> [numBlocks]` — runs adapter against recent blocks
17+
- **Test historical:** `npm run test-txs <startTs> <endTs> <adapter>` — backfill test
18+
- **Backfill adapter:** `npm run adapter <startTs> <endTs> <bridgeName> [chain]` (requires `.env`)
19+
- **Aggregate:** `npm run aggregate <startTs> <endTs> <bridgeName>` (requires `.env`)
20+
- **Formatting:** Prettier with `printWidth: 120`, `tabWidth: 2`
21+
22+
## Architecture
23+
24+
### Data Pipeline Overview
25+
26+
The system follows a three-stage pipeline: **Fetch → Store → Aggregate**.
27+
28+
1. **Fetch**: Adapters pull raw bridge transaction events from on-chain logs or APIs
29+
2. **Store**: Raw transactions are inserted into `bridges.transactions` with block timestamps
30+
3. **Aggregate**: Raw txs are priced via DefiLlama's price API (`coins.llama.fi`), then rolled up into hourly and daily volume tables
31+
32+
### Adapter System (`src/adapters/`)
33+
34+
Each bridge protocol has a directory under `src/adapters/` exporting a default `BridgeAdapter`:
35+
```ts
36+
{ [chainName: string]: (fromBlock: number, toBlock: number) => Promise<EventData[]> }
37+
```
38+
39+
Two main patterns:
40+
1. **EVM event log pattern** (most adapters) — define `ContractEventParams`/`PartialContractEventParams` arrays, pass to `getTxDataFromEVMEventLogs()` from `src/helpers/processTransactions.ts`. Each params object specifies a `target` contract address, `topic` (event signature), `abi`, and key mappings (`logKeys`, `argKeys`, `txKeys`, `fixedEventData`) that map event fields to `EventData` fields. See `src/adapters/celer/index.ts` for a representative example.
41+
2. **API-based pattern** — query bridge indexer APIs directly, construct `EventData` objects manually (e.g., `src/adapters/across/`)
42+
43+
Some adapters use `AsyncBridgeAdapter` (with `isAsync: true` and a `build()` method) when they need async initialization before returning the chain function map.
44+
45+
Adapters are registered in `src/adapters/index.ts` keyed by `bridgeDbName`.
46+
47+
### Adapter Execution Flow (`src/utils/adapter.ts`)
48+
49+
**`runAdapterToCurrentBlock(bridgeNetwork)`** — the main entry point for running a single adapter:
50+
51+
1. Resolves the adapter (handling async adapters via `build()`)
52+
2. Ensures `bridges.config` entries exist for each chain the adapter supports
53+
3. For each chain in parallel (staggered by 200ms):
54+
- Looks up the `bridgeID` from the config table
55+
- Calls `getBlocksForRunningAdapter()` to determine the block range:
56+
- Gets the latest on-chain block number
57+
- Finds the last recorded block from DB (`lastRecordedBlocks` query) or Redis progress cache
58+
- Redis progress (`adapter_progress:{name}:{chain}`, 7-day TTL) tracks the last successfully processed block, allowing resumption after crashes
59+
- `startBlock = lastRecordedEndBlock + 1`
60+
- Processes blocks in chunks of `maxBlocksToQueryByChain[chain]` (defined in `src/utils/constants.ts`, ~1.5-2 hours of blocks per chain)
61+
- Each chunk calls `runAdapterHistorical()`
62+
63+
**`runAdapterHistorical(startBlock, endBlock, ...)`** — processes a block range:
64+
65+
1. Checks Redis progress to skip already-processed ranges
66+
2. Fetches event logs by calling the adapter's chain function (with `async-retry`, 4 retries)
67+
3. Estimates timestamps by sampling 10 blocks across the range from the provider (Solana uses `getBlockTime` per tx instead)
68+
4. Filters out tx groups with 100+ events per txHash
69+
5. Inserts each event into `bridges.transactions` within a SQL transaction (with retries)
70+
6. Updates Redis progress on success
71+
7. On failure after 3 attempts, logs error to DB and sends Discord notification
72+
73+
**Skipped bridges**: Certain high-volume bridges (`bridgesToSkip` in `adapter.ts`: wormhole, layerzero, hyperlane, mayan, relay, cashmere, teleswap, intersoon, ccip) are excluded from the general `runAllAdapters` job and run via dedicated handlers with separate timeouts in the cron schedule.
74+
75+
### `getTxDataFromEVMEventLogs()` (`src/helpers/processTransactions.ts`)
76+
77+
The core EVM adapter helper. For each `ContractEventParams` in the array:
78+
79+
1. If `isTransfer: true`, auto-generates ERC20 Transfer event params targeting the contract address
80+
2. Calls `@defillama/sdk` `getLogs()` to fetch raw event logs for the block range
81+
3. Processes each log with concurrency 20 via `PromisePool`:
82+
- Extracts values via `logKeys` (from raw log fields), `argKeys` (from parsed event args), `txKeys` (from transaction data)
83+
- Applies filters: `includeArg`/`excludeArg`, `includeTxData`, `functionSignatureFilter`, `custom` filter functions
84+
- Optionally extracts token from receipt (`getTokenFromReceipt`) or input data (`inputDataExtraction`)
85+
- Applies `fixedEventData` overrides and `mapTokens` token address remapping
86+
4. Returns accumulated `EventData[]` after applying address-level `excludeFrom`/`excludeTo`/`excludeToken` filters
87+
88+
### Cron System (`src/server/cron.ts`)
89+
90+
The cron worker (`npm run start:cron`) is **not** a traditional cron scheduler — it runs all jobs once with staggered `setTimeout` delays and then self-terminates after 54 minutes (designed to be restarted by PM2 or an external orchestrator).
91+
92+
**Timeline of a single cron cycle:**
93+
- **+5 min**: `aggregateLayerZero` (last 2 days), `aggregateAll` (last 36 hours), `aggregateHourly`, `aggregateDaily`, `runAllAdapters`
94+
- **+25 min**: Individual heavy adapters — `runWormhole`, `runMayan`, `runLayerZero`, `runHyperlane`, `runInterSoon`, `runRelay`, `runCashmere`, `runTeleswap`, `runCCIP`, `runSnowbridge`
95+
- **+54 min**: Process exits (prints getLogs usage summary, closes DB connections)
96+
97+
Each job runs with `withTimeout()` — a `Promise.race` against a timeout (5-40 min depending on job). Set `NO_CRON=1` to disable.
98+
99+
**`runAllAdapters` job** (`src/server/jobs/runAllAdapters.ts`):
100+
1. Queries max `tx_block` per `bridge_id` from `bridges.transactions` to get last recorded blocks
101+
2. Shuffles all bridge networks randomly (load distribution)
102+
3. Processes up to 10 adapters concurrently via `PromisePool`
103+
4. Skips bridges in `bridgesToSkip` (they have dedicated handlers)
104+
105+
### Aggregation Pipeline (`src/utils/aggregate.ts`)
106+
107+
**`aggregateData(timestamp, bridgeDbName, chain, hourly, largeTxThreshold)`**:
108+
109+
1. Determines the time window (previous hour or previous day relative to `timestamp`)
110+
2. Queries all raw transactions from `bridges.transactions` for that bridge/chain/time window
111+
3. Collects unique token addresses and fetches batch prices from `coins.llama.fi` via `getLlamaPrices()`
112+
4. For each transaction:
113+
- Converts raw token amount to USD using price + decimals (respects `transformTokens` and `transformTokenDecimals` mappings)
114+
- If `is_usd_volume` flag is set, uses amount directly as USD value
115+
- Accumulates per-token and per-address deposit/withdrawal totals
116+
- Flags transactions above `largeTxThreshold` for separate storage
117+
- Skips values over $10B as likely errors
118+
5. Inserts aggregated row into `bridges.hourly_aggregated` or `bridges.daily_aggregated`
119+
6. Inserts qualifying rows into `bridges.large_transactions`
120+
121+
**`aggregateHourlyVolume` / `aggregateDailyVolume`** (`src/server/jobs/`): Secondary aggregation that copies data from `hourly_aggregated` into simplified `hourly_volume`/`daily_volume` tables joined with chain info from `bridges.config`.
122+
123+
### Bridge Network Registry (`src/data/bridgeNetworkData.ts`)
124+
125+
Each bridge is registered as a `BridgeNetwork` with a unique `id`, `bridgeDbName`, `chains[]`, `largeTxThreshold`, and optional `chainMapping`/`destinationChain`. Lookup helpers in `src/data/importBridgeNetwork.ts`.
126+
127+
### EventData (`src/utils/types.ts`)
128+
129+
Core return type from all adapters: `blockNumber`, `txHash`, `from`, `to`, `token`, `amount` (ethers BigNumber), `isDeposit`, plus optional `chain`, `chainOverride`, `isUSDVolume`, `txsCountedAs`.
130+
131+
### Key Helpers (`src/helpers/`)
132+
133+
- `processTransactions.ts``getTxDataFromEVMEventLogs()`: core EVM log fetching/parsing
134+
- `eventParams.ts``constructTransferParams()` for ERC20 Transfer events
135+
- `bridgeAdapter.type.ts``BridgeAdapter`, `AsyncBridgeAdapter`, `ContractEventParams` types
136+
- `tokenMappings.ts` — token address transforms (`transformTokens`) and decimal overrides (`transformTokenDecimals`); also `chainMappings` for chain name aliasing
137+
- Chain-specific helpers: `solana.ts`, `sui.ts`, `tron.ts`, `stellar.ts`
138+
139+
### Database (`sql/data.sql`)
140+
141+
PostgreSQL under `bridges` schema. Key tables:
142+
- `bridges.transactions` — raw tx records (unique on bridge_id, chain, tx_hash, token, tx_from, tx_to)
143+
- `bridges.hourly_aggregated` / `bridges.daily_aggregated` — pre-aggregated volumes with token_total[] and address_total[] arrays
144+
- `bridges.hourly_volume` / `bridges.daily_volume` — simplified volume tables (joined with chain from config)
145+
- `bridges.large_transactions` — transactions above `largeTxThreshold`
146+
- `bridges.config` — maps (bridge_name, chain) → UUID
147+
148+
DB access via `src/utils/db.js` using the `postgres` npm package. Two connection pools: `sql` (max 10) for writes, `querySql` (max 6) for reads.
149+
150+
### Server (`src/server/`)
151+
152+
- `index.ts` — Fastify server with Redis caching (70-min TTL, 10-min warming interval)
153+
- `cron.ts` — Delay-based job scheduler (see Cron System above)
154+
- Handlers in `src/handlers/` use `wrap()` from `src/utils/wrap.ts` for Lambda/Fastify compatibility
155+
156+
### Utilities (`src/utils/`)
157+
158+
- `adapter.ts` — adapter runner: `runAdapterToCurrentBlock()`, `runAdapterHistorical()`, `runAllAdaptersToCurrentBlock()`; defines `bridgesToSkip`
159+
- `aggregate.ts` — fetches prices from `coins.llama.fi`, computes USD volumes, inserts aggregated rows
160+
- `blocks.ts``getLatestBlockNumber()`, `getBlockByTimestamp()` (multi-chain: EVM, Solana, Sui, Stellar, Tron, IBC)
161+
- `prices.ts` — DefiLlama price API integration
162+
- `cache.ts` — Redis wrapper with getLogs tracking per adapter:chain
163+
- `constants.ts``maxBlocksToQueryByChain` per-chain block query limits (~1.5-2 hours of blocks)
164+
165+
## Adding a New Adapter
166+
167+
1. Create `src/adapters/<bridge-name>/index.ts` exporting a default `BridgeAdapter`
168+
2. Add a `BridgeNetwork` entry in `src/data/bridgeNetworkData.ts` with a new unique `id`
169+
3. Import and register in `src/adapters/index.ts`
170+
4. Test with: `npm run test <bridge-name> <numBlocks>`
171+
172+
## Environment Variables
173+
174+
Required: `DB_URL` (or `PSQL_USERNAME` + `PSQL_PW` + `PSQL_URL`). Optional: `REDIS_URL`, `PORT` (default 3000), `DISCORD_WEBHOOK`, `ALLIUM_API_KEY`, per-chain RPC vars (e.g. `ETHEREUM_RPC`).
175+
176+
## Deployment
177+
178+
Push to `master` triggers CI: Node 18, `npm ci`, `npm run ts`, then `sls deploy --stage prod` (AWS Lambda via Serverless Framework).

0 commit comments

Comments
 (0)