These guidelines apply to ALL adapters in this repository.
- Always provide Website and twitter links in the description
- Use on-chain data/event logs where possible. We are stricter about on-chain for chains where we maintain our own indexer, or where there is significant volume/fees, or where you suspect wash trading. EVM chains can almost always be tracked with on-chain logs - prefer logs over a protocol API
- Use
pullHourly: true, wherever evm logs and allium queries are used to avoid recomputing data for the same time period and provide more granular data - Never swallow errors silently. For recoverable chain-specific failures, return 0 and log the error so the adapter continues for other chains. For system-level or critical errors, throw/propagate to fail fast. Do NOT wrap adapter logic in
try/catchjust to ignore errors - either remove the catch orthrowinside it - Use/add helper code when multiple adapters use similar logic - check
helpers/folder first - Do NOT add npm dependencies - this leads to bloat
- Use
api.multiCallwhere possible, avoidPromise.all. Use PromisePool for non-EVM calls - Return token breakdown where possible - always include
methodologyandbreakdownMethodology
- The
fetchfunction takes a singleFetchOptionsargument for both v1 and v2:const fetch = async (options: FetchOptions) => { ... } - The old v1 3-argument signature
(timestamp, chainBlocks, options)no longer exists and will fail - never use it. Migrate any adapter still using it to(options). - Use the values already on
optionsinstead of deriving them from a raw timestamp:options.startOfDay,options.dateString,options.startTimestamp,options.endTimestamp,options.fromTimestamp,options.toTimestamp,options.getFromBlock(),options.getToBlock() - Do NOT declare fetch arguments you never use
fetchshould NOT returntimestampin its result - for v1 and v2 alike. Just return the dimension balances.
- Pass
getLogs({ targets: [...] })with the full list of contracts instead of usingnoTargetand filtering afterwards -noTargetscans every log on the chain and is very heavy - Use a human-readable
eventAbiinstead of rawtopics- easier to review and maintain - When matching trades to fee transfers, do NOT assume they share the same transaction hash - many protocols emit the fee transfer in a separate tx
- Add a comment (and a source link where possible) for any hardcoded rate, address, or magic number so maintainers can verify it later
- Express rates in a clear, self-documenting way (e.g.
0.1for a 10% fee), not opaque expressions allowNegativeValuemust be justified with an inline comment explaining why negatives are expected
| Version 2 | Version 1 | |
|---|---|---|
| Use when | On-chain logs, contract calls, subgraphs, Dune queries with timestamp filters | External API that only returns daily aggregates |
| Fetch signature | (options: FetchOptions) |
(options: FetchOptions) (same as v2 - the old 3-arg signature is removed) |
| Time range | Arbitrary start/end timestamps | Fixed day (00:00–23:59 UTC) |
pullHourly |
Required - set explicitly (true by default, false + reason if not possible) |
Not supported |
| Preference | Always prefer this | Use only when v2 is not possible |
- Dune adapters must always be
version: 1. Dune queries run once per day; a v2 adapter runs every hour and would re-run the same expensive query each hour. - For v2 adapters, drive time windows off
options.startTimestamp/options.fromTimestamp(andoptions.getFromBlock()), NOToptions.startOfDay. Because v2 runs hourly, keying off start-of-day sends the same request every hour and breaks granular/hourly data. - New adapters must be
version: 2unless they rely on Dune or on an external API that only returns daily aggregates. - Every
version: 2adapter must explicitly set thepullHourlykey. Default topullHourly: true. Only setpullHourly: falsewhen the data genuinely cannot be pulled hourly, and add a comment explaining why.
- Do NOT add extra/duplicate date filters - the
TIME_RANGEmacro already injects the date filter. Duplicates make queries slower and can conflict. - Keep queries as optimal as possible; for multi-chain protocols use a single prefetch query covering all chains rather than one query per chain.
- Share the Dune run/test results in the PR so reviewers can validate (queries that only time out on long runs are not acceptable).
Pick the simplest shape that fits. In order of preference:
1. Same start for all chains → use the chains array + a single start:
const adapter: SimpleAdapter = {
version: 2,
fetch,
chains: [CHAIN.ETHEREUM, CHAIN.BASE, CHAIN.ARBITRUM],
start: '2023-01-01',
methodology,
}2. Different start per chain (but no other per-chain config) → use the adapter object:
const adapter: SimpleAdapter = {
version: 2,
fetch,
adapter: {
[CHAIN.ETHEREUM]: { start: '2023-01-01' },
[CHAIN.BASE]: { start: '2024-06-01' },
},
methodology,
}3. Per-chain config (different contract/id/etc.) → keep it all in one chainConfig and pass it as adapter: chainConfig. Each entry carries a start plus whatever else the chain needs; fetch reads chainConfig[options.chain]. This avoids a separate chains list, start map, and config map drifting apart:
const chainConfig: Record<string, { contract: string; start: string }> = {
[CHAIN.ETHEREUM]: { contract: '0xaaa...', start: '2023-01-01' },
[CHAIN.BASE]: { contract: '0xbbb...', start: '2024-06-01' },
}
const fetch = async (options: FetchOptions) => {
const { contract } = chainConfig[options.chain]
const dailyFees = options.createBalances()
const logs = await options.getLogs({ target: contract, eventAbi: '...' })
// ...
return { dailyFees }
}
const adapter: SimpleAdapter = {
version: 2,
fetch,
adapter: chainConfig, // start dates are read from chainConfig per chain
methodology,
}| Dimension | Required | Description |
|---|---|---|
dailyVolume |
YES | Trading volume for the period |
| Dimension | Required | Description |
|---|---|---|
dailyVolume |
YES | Perpetual trading volume (TAKER volume only, do NOT double-count maker+taker) |
openInterestAtEnd |
Optional | Open interest at period end |
longOpenInterestAtEnd |
Optional | Long positions open interest |
shortOpenInterestAtEnd |
Optional | Short positions open interest |
| Dimension | Required | Description |
|---|---|---|
dailyBridgeVolume |
YES | Bridge volume for the period |
| Dimension | Required | Description |
|---|---|---|
dailyNotionalVolume |
YES | Notional volume of options contracts |
dailyPremiumVolume |
YES | Premium volume collected/paid |
openInterestAtEnd |
Optional | Open interest at period end |
| Dimension | Required | Description |
|---|---|---|
dailyFees |
YES | All fees from ALL sources (Gross Protocol Revenue) - everything protocol could theoretically keep if it took 100% |
dailyRevenue |
YES | Portion kept by protocol (Gross Profit = dailyFees - dailySupplySideRevenue) |
dailySupplySideRevenue |
When applicable | Portion to LPs, lenders, stakers, integrators, referrers, creators (Cost of Revenue) |
dailyUserFees |
Optional | Portion directly paid by end-users |
dailyProtocolRevenue |
Optional | Portion allocated to treasury |
dailyHoldersRevenue |
When applicable | All value to token holders (buybacks, burns, distributions, external airdrops, bribes) |
- Must provide all required dimensions for the adapter category (see tables above)
- For fees adapters: must provide accurate
dailyFeesanddailyRevenue - Strongly encouraged:
dailySupplySideRevenuewhen protocol has supply-side costs - Include when applicable:
dailyHoldersRevenuefor protocols distributing to holders - Always include: breakdown labels and
breakdownMethodology - Deprecated:
total*cumulative dimensions - do not use
| Display Name | Code Field |
|---|---|
| Gross Protocol Revenue | dailyFees |
| Cost of Funds | dailySupplySideRevenue |
| Gross Profit | dailyRevenue |
| Tokenholder Income | dailyHoldersRevenue |
| Attribute | DEXs | Lending | Chains | NFT Marketplace | Derivatives | CDP | Liquid Staking | Yield |
|---|---|---|---|---|---|---|---|---|
| Fees | Swap fees | Borrow interest | Gas fees | Trading fees | Trading fees + mint/burn | Borrow fees | Staking rewards | Yield |
| SupplySideRevenue | LP revenue | Interest to lenders | Sequencer costs, blob fees | Creator earnings | LP revenue, rebates | N/A | Rewards to stakers | Yield minus fees |
| Revenue | Protocol's % | Protocol's % | Burned fees | Marketplace rev | Protocol's % | Protocol's % | Protocol fee % | Protocol fees |
| HoldersRevenue | Token distributions | N/A | N/A | N/A | Staker distributions | N/A | N/A | N/A |
Notes:
Revenue = Fees - SupplySideRevenueRevenue = HoldersRevenue + ProtocolRevenue- For chains: only track transaction fees paid by users. Perp DEX fees (e.g., Hyperliquid L1) are tracked under the perp adapter, not chain adapter
- ALWAYS provide labels even when there is only one source/destination of fees
- Labels prevent needing to update and backfill data when adapter is listed under a parent protocol
dailyFees: Use source-of-fees labels (e.g., 'Swap Fees', 'Borrow Interest')dailyRevenue/dailySupplySideRevenue/dailyHoldersRevenue: Use detailed destination labels (e.g., 'Swap Fees To LPs', 'Borrow Interest To Treasury')
Every label used in .add() calls MUST appear in breakdownMethodology, and every label in breakdownMethodology must have corresponding data in code.
dailyBribesRevenueanddailyTokenTaxesare deprecated; put these as sub-sections withindailyHoldersRevenueinstead
- Fees: Only fees paid by users for transactions should be tracked as fees. Block rewards are incentives, NOT fees
- Revenue: Only the portion that gets burnt or goes to protocol treasury. Staker payments are NOT revenue
- Holder Revenue: Same as revenue unless portion is set aside for protocol
- Chain Fees: Track only transaction fees paid by users (no perp DEX fees for chains like Hyperliquid L1)
'Gross Protocol Revenue' (dailyFees) should include everything the protocol COULD charge if it became maximally greedy.
Example: For Aave, if depositors get 70% and protocol gets 30% of borrow fees, dailyFees includes 100% because protocol could theoretically take it all.
- Keys in the
methodologyobject must match dimension display names, NOT the code field names: useFees,Revenue,SupplySideRevenue,ProtocolRevenue,HoldersRevenue,Volume,NotionalVolume,PremiumVolume,OpenInterest- e.g.dailyVolume->Volume,dailyFees->Fees.
These are the issues that come up most often in review - check every PR for them:
- Wash trading - be vigilant especially on low-fee chains
- Incorrect fee/revenue classification, and
Fees = Revenue + SupplySideRevenuenot balancing within a period (note:Revenue = ProtocolRevenue + HoldersRevenueis an attribution rule, not a per-day equality - holders revenue like buybacks can land on a different day) - Counting ALL fees as revenue (forgetting the supply-side cut)
- Missing
dailyProtocolRevenue/dailyHoldersRevenuesplit when the protocol keeps some and distributes some - Unexpected negative
dailySupplySideRevenue- worth a look, but can be legitimate on realized vault losses; verify rather than assume a bug - Missing breakdown labels or
breakdownMethodology, or labels that don't match the methodology - Hardcoded values that should be dynamic; hardcoded rates/addresses without a source comment
- Double-counting (both taker and maker volume in perps; both buy and sell legs; routed volume already counted in the underlying protocol; buybacks already counted in a parent listing)
- Dune adapters not set to
version: 1, or duplicate Dune date filters - v2 adapters keying time windows off
startOfDayinstead ofstartTimestamp noTargetgetLogs, rawtopics, missingmultiCall, ortry/catchthat swallows errors- Cumulative data returned where 24h/daily data is expected