My third contribution to DefiLlama, tracking yield distributions for Solomon Protocol’s sUSDv (staked USDv) on Solana.
- Protocol: Solomon USDv
- Chain: Solana
- Type: Staking yield distribution (API-based)
- Start Date: 2025-06-04
- Approach: Fetch yield data from Solomon Labs API
1. Background story
a. Learning from previous mistakes
After the Bitfi experience where I misunderstood protocol slug structure, I wanted to apply those learnings:
- Verify protocol structure first
- Read documentation carefully
- Understand fee/revenue model before coding
b. Choosing Solomon Protocol
I found Solomon Protocol which offers:
- USDv: Stablecoin backed by delta-neutral basis trading
- sUSDv: Staked USDv that receives 100% of yield generated
- Clear API endpoint for yield distributions
- Simple model: all yield → stakers (no protocol revenue cut)
This seemed straightforward. Track yield distributions from their public API.
c. Initial research
From Solomon’s documentation:
- Revenue Model: 100% yield from delta-neutral basis trading goes to sUSDv holders
- Data Source:
https://data.solomonlabs.io/api/solomon-protocol/staking/yield-distributions - Start Date: June 4, 2025 (first yield distribution)
- Chain: Solana
The model was clear:
- Fees: Yield from backing assets
- Revenue: None (no protocol takes a cut)
- Supply Side Revenue: All fees go to sUSDv stakers
2. Initial implementation
a. My original code (commit 7f2cb81)
import { Adapter, FetchOptions } from "../adapters/types";
import { CHAIN } from "../helpers/chains";
import fetchURL from "../utils/fetchURL";
interface YieldDistribution {
yieldAmount: string;
transactionTimestamp: number;
allTimeApy: string;
}
interface YieldDistributionsResponse {
data: YieldDistribution[];
}
const fetch = async (options: FetchOptions) => {
const { startTimestamp, endTimestamp } = options;
// Fetch all yield distributions
const response: YieldDistributionsResponse = await fetchURL(
"https://data.solomonlabs.io/api/solomon-protocol/staking/yield-distributions?limit=1000"
);
// Filter distributions within the time range
const distributions = response.data.filter(
(d) => d.transactionTimestamp >= startTimestamp * 1000 &&
d.transactionTimestamp < endTimestamp * 1000
);
// Sum up yield amounts for the day (amounts are in raw units with 9 decimals for USDC)
const totalYield = distributions.reduce(
(sum, d) => sum + Number(d.yieldAmount) / 1e9,
0
);
const dailyFees = options.createBalances();
dailyFees.addUSDValue(totalYield);
// All yield goes to stakers (sUSDv holders)
const dailyHoldersRevenue = options.createBalances();
dailyHoldersRevenue.addUSDValue(totalYield);
return {
dailyFees,
dailyRevenue: dailyHoldersRevenue, // ← Used dailyHoldersRevenue
dailyHoldersRevenue, // ← Added this field
};
};
const adapter: Adapter = {
version: 2, // ← Used version 2
adapter: {
[CHAIN.SOLANA]: {
fetch,
start: '2025-06-04',
},
},
methodology: {
Fees: 'Yield generated from basis trading strategies (funding rates on perpetual futures).',
Revenue: 'All yield is distributed to sUSDv holders (staked USDv).',
HoldersRevenue: 'Yield earned by sUSDv token holders from the protocol\'s delta-neutral basis trading strategy.',
},
};
export default adapter; b. What I thought
I was confident this was correct because:
- API endpoint was documented and public
- Yield distribution model was clear (100% to stakers)
- Start date matched first distribution (June 4, 2025)
- Timestamp filtering handled correctly (API uses milliseconds, adapter uses seconds)
- Decimal conversion accounted for (USDC has 9 decimals, divide by 1e9)
- Methodology clearly explained the model
3. The evolution: three commits
a. Commit 1 (my original): what I thought was right
Key characteristics:
- Used
Adaptertype withversion: 2 - Nested adapter structure:
adapter: { [CHAIN.SOLANA]: { fetch, start } } - Used
dailyHoldersRevenuefield - Set
dailyRevenue: dailyHoldersRevenue(thinking all yield = revenue) - Client-side filtering with
?limit=1000 - Fetch function signature:
async (options: FetchOptions)
My reasoning:
- “All yield goes to holders, so that’s revenue”
- “Version 2 adapter = more modern”
- “Holders revenue = appropriate field for staker earnings”
b. Commit 2 (my fix, d7a8adb): simplified structure
After reviewer feedback, I changed to:
import { SimpleAdapter, FetchOptions } from "../adapters/types"; // Changed type
const fetch = async (_timestamp: number, _chainBlocks: any, options: FetchOptions) => {
// Added standard parameters
const { startTimestamp, endTimestamp } = options;
// Changed API call to use query parameters
const response = await fetchURL(
`https://api.solomon.finance/yield-distributions?startTimestamp=${startTimestamp}&endTimestamp=${endTimestamp}`
) as YieldDistributionsResponse;
// Simplified data processing
let totalYield = 0;
for (const distribution of response.data) {
totalYield += distribution.totalYield;
}
const dailyFees = options.createBalances();
dailyFees.addUSDValue(totalYield);
const dailySupplySideRevenue = options.createBalances(); // Renamed from dailyHoldersRevenue
dailySupplySideRevenue.addUSDValue(totalYield);
return {
dailyFees,
dailyRevenue: dailySupplySideRevenue, // Still thought this was right
dailySupplySideRevenue, // Used correct field name
};
};
const adapter: SimpleAdapter = {
version: 1, // Simplified to version 1
fetch,
chains: [CHAIN.SOLANA], // Flat structure
start: '2025-06-04',
// Removed methodology (added by reviewer later)
}; Changes I made:
Adapter→SimpleAdapter- Version 2 → Version 1
- Flat structure instead of nested
dailyHoldersRevenue→dailySupplySideRevenue- API call with query parameters instead of client-side filtering
- Standard fetch function signature with
_timestampand_chainBlocks
c. Commit 3 (reviewer fix, d17ff46 “no revenue”): the real issue
Reviewer noateden made the final critical change:
return {
dailyFees,
dailyRevenue: 0, // ← Changed from dailySupplySideRevenue to 0
dailySupplySideRevenue: dailyFees,
}; And added methodology:
const adapter: SimpleAdapter = {
version: 1,
fetch,
chains: [CHAIN.SOLANA],
start: '2025-06-04',
methodology: {
Fees: 'All yield from backing assets.',
Revenue: 'No revenue share from fees.', // ← Key clarification
SupplySideRevenue: 'All fees are distributed to sUSDv stakers.',
}
}; The critical fix:
dailyRevenue: 0instead ofdailyRevenue: dailySupplySideRevenue- This clarifies: protocol takes zero revenue, all goes to supply side
4. The test results
a. CI test outputs
When the CI ran, llamabutler bot showed:
🦙 Running SOLOMON-USDV adapter 🦙
---------------------------------------------------
SOLOMON-USDV 👇
Backfill start time: 6/4/2025
Daily fees: 0.00
Daily revenue: 0.00
Daily supply side revenue: 0.00 All zeros. Even after my second commit (fix), the tests still showed zeros.
This suggests the data issues weren’t just about structure. There might have been actual data availability or API endpoint issues at the time of testing.
b. Reviewer feedback
noateden gave specific feedback on my first commit:
Line 36-39: “USDv is not governance token, I won’t count yields as dailyHoldersRevenue here, I will count yields for dailySupplySideRevenue instead”
Line 49-50: “please use version 1 adapter”
What this meant:
dailyHoldersRevenueis for governance token holders earning from protocoldailySupplySideRevenueis for yield-bearing asset holders (like sUSDv stakers)- Version 2 adapter is overkill for this simple case
- Version 1 (SimpleAdapter) is more appropriate
c. My iterations
- Commit 1 (Original): Used version 2,
dailyHoldersRevenue - Commit 2 (My fix): Changed to version 1,
dailySupplySideRevenue, but still setdailyRevenue = dailySupplySideRevenue - Commit 3 (Reviewer fix): Changed to
dailyRevenue = 0
Even after my fix, I still didn’t understand that dailyRevenue = 0 for protocols that don’t take a cut.
5. What went wrong: the real issues
a. Conceptual misunderstanding: revenue vs supply side revenue
My mistake: dailyRevenue: dailyHoldersRevenue
I thought:
- “100% yield goes to stakers”
- “All yield = revenue”
- “So
dailyRevenueshould equal the yield amount”
Reality:
- Protocol Revenue = what the protocol itself earns
- Supply Side Revenue = what goes to liquidity providers/stakers
- Solomon takes zero protocol revenue
- All yield = supply side, not protocol revenue
The fix: dailyRevenue: 0 and dailySupplySideRevenue: dailyFees
This is a fundamental conceptual error about DefiLlama’s revenue taxonomy.
b. Adapter type confusion
My mistake: Used Adapter with version 2 and nested structure
const adapter: Adapter = {
version: 2,
adapter: {
[CHAIN.SOLANA]: {
fetch,
start: '2025-06-04',
},
},
}; Reality: Should use SimpleAdapter with flat structure
const adapter: SimpleAdapter = {
version: 1,
fetch,
chains: [CHAIN.SOLANA],
start: '2025-06-04',
}; Lesson: Different adapter types for different use cases:
SimpleAdapter: Single-chain or simple multi-chainAdapter(version 2): Complex multi-chain with different logic per chain
c. API query strategy
My approach: Fetch all data with ?limit=1000, filter client-side
const response = await fetchURL(
"https://data.solomonlabs.io/api/solomon-protocol/staking/yield-distributions?limit=1000"
);
const distributions = response.data.filter(
(d) => d.transactionTimestamp >= startTimestamp * 1000 &&
d.transactionTimestamp < endTimestamp * 1000
); Better approach: Let API filter with query parameters
const response = await fetchURL(
`https://api.solomon.finance/yield-distributions?startTimestamp=${startTimestamp}&endTimestamp=${endTimestamp}`
); Why better:
- Less data transferred
- API does the filtering (more efficient)
- No risk of missing data if there are >1000 records
- No need for client-side timestamp conversion
d. Field naming confusion: HoldersRevenue vs SupplySideRevenue
Evolution of understanding:
First attempt:
dailyHoldersRevenue- I thought: “sUSDv holders get the yield = holders revenue”
- Reviewer correction: “USDv is not governance token”
- Problem:
HoldersRevenueis for governance token holders who earn from protocol operations
Second attempt:
dailySupplySideRevenue- Correctly identified this is supply-side (stakers providing liquidity)
- But still set
dailyRevenue: dailySupplySideRevenue(wrong) - Missed: Protocol revenue = 0
Final version:
dailyRevenue: 0(protocol takes nothing)dailySupplySideRevenue: dailyFees(all goes to stakers)
The key distinction:
| Field | Who Gets It | Example |
|---|---|---|
dailyHoldersRevenue | Governance token holders | GMX holders earning from GMX protocol fees |
dailySupplySideRevenue | Yield-bearing asset holders | sUSDv stakers earning yield from basis trading |
Solomon case:
- sUSDv = yield-bearing staked asset (like aUSDC, stETH)
- NOT a governance token
- Therefore:
dailySupplySideRevenue
Lesson: Learn DefiLlama’s revenue taxonomy:
dailyFees: Total fees generateddailyRevenue: What protocol earns (can be 0!)dailyProtocolRevenue: Subset of revenue that goes to protocol treasurydailySupplySideRevenue: What goes to LPs/stakers (yield-bearing positions)dailyHoldersRevenue: What goes to governance token holders
6. What I learned
a. Critical conceptual lessons
Revenue taxonomy: The biggest mistake wasn’t code. It was not understanding DefiLlama’s revenue model:
dailyFees = total fees collected ├─ dailyRevenue = protocol's share │ └─ dailyProtocolRevenue = to treasury └─ dailySupplySideRevenue = to LPs/stakersFor Solomon:
Fees = 100%, Revenue = 0%, SupplySideRevenue = 100%“All yield to stakers” ≠ “Protocol Revenue”: Just because users earn yield doesn’t mean the protocol earns revenue. Solomon passes through 100% to stakers, earning nothing itself.
b. Technical lessons
Adapter Types Matter:
SimpleAdapter: Use for single-chain or uniform multi-chainAdapter(v2): Use when each chain needs different logic- I used v2 when v1 was appropriate
API Query Strategy:
- Fetch all + filter client-side (my approach)
- Let API filter with query params (reviewer’s fix)
- Server-side filtering is more efficient and scalable
Standard Function Signatures:
// Wrong const fetch = async (options: FetchOptions) => { } // Correct const fetch = async (_timestamp: number, _chainBlocks: any, options: FetchOptions) => { }DefiLlama expects standard signatures even if you don’t use all parameters
c. Process lessons
Read the taxonomy docs first: Before implementing, I should have studied DefiLlama’s fee/revenue definitions. I jumped straight to coding.
Reviewer fixes are teaching moments: The commit message “no revenue” was a hint. I was still treating supply-side revenue as protocol revenue.
Incremental improvements: Even though my second commit fixed structure issues, I still missed the conceptual issue. Progress happens in steps.
c. What went right
Despite the issues, several things worked well:
- Correctly identified this as an API-based adapter (not event-based)
- Understood the revenue model (100% to stakers, 0% protocol)
- Used TypeScript interfaces for API response
- Clear methodology documentation
- Applied learnings from previous contributions
7. Evolution comparison
| Aspect | Commit 1 (Original) | Commit 2 (My Fix) | Commit 3 (Final) |
|---|---|---|---|
| Adapter Type | Adapter (v2) | SimpleAdapter (v1) | SimpleAdapter (v1) |
| Structure | Nested adapter: {} | Flat chains: [] | Flat chains: [] |
| Fetch Signature | (options) | (_timestamp, _chainBlocks, options) | (_timestamp, _chainBlocks, options) |
| API Query | ?limit=1000 + filter | Query params | Query params |
| Revenue Field | dailyHoldersRevenue | dailySupplySideRevenue | dailySupplySideRevenue |
| dailyRevenue | = dailyHoldersRevenue | = dailySupplySideRevenue | = 0 |
| Methodology | Included | Removed | Added back |
8. Questions I should have asked
Before submitting, I should have asked:
Revenue Taxonomy:
- What’s the difference between
dailyRevenueanddailySupplySideRevenue? - If 100% goes to stakers, does that mean protocol revenue is 0?
- Where can I find DefiLlama’s definitions for these fields?
- What’s the difference between
Adapter Types:
- When should I use
SimpleAdaptervsAdapter? - What’s the difference between version 1 and version 2?
- Is nested structure
adapter: { [CHAIN]: {} }still recommended?
- When should I use
Function Signatures:
- What’s the standard fetch function signature?
- Why include
_timestampand_chainBlocksif I don’t use them?
API Best Practices:
- Should I fetch all data and filter, or use API query params?
- How do I handle pagination for large datasets?
- What if the API has rate limits?
9. The final result
Despite the issues during review, the PR was merged successfully
This means:
- The core approach was sound (API-based tracking)
- Structural issues were fixable
- With reviewer’s help on revenue taxonomy, it works correctly
- The adapter now works in production
a. What worked:
- PR merged (not closed like Bitfi)
- Responsive to feedback (pushed fixes within 1 day)
- API-based approach was correct
- Start date and data source were accurate
b. What needed fixing:
- Adapter type (v2 → v1)
- Adapter structure (nested → flat)
- Revenue classification (supply side ≠ protocol revenue)
- API query strategy (fetch all → query params)
- Function signature (missing standard params)
c. The key lesson:
Conceptual understanding > Code correctness
My code structure issues were easily fixable. The harder lesson was understanding DefiLlama’s revenue taxonomy. This is domain knowledge that can’t be guessed. It must be learned.
10. Resources
11. Key takeaways
a. The main lesson: revenue taxonomy
The core issue wasn’t code. It was conceptual.
I needed to learn:
Fees = Total collected
├─ Protocol Revenue (what protocol keeps)
│ └─ Protocol Treasury
└─ Supply Side Revenue (what goes to LPs/stakers) For Solomon: 0% protocol, 100% supply side
b. Technical improvements made:
| Issue | Learning |
|---|---|
Used Adapter v2 | Use SimpleAdapter for simple cases |
| Nested structure | Flat structure for single/simple chains |
| Wrong function signature | Always include _timestamp, _chainBlocks |
?limit=1000 + filter | Let API filter with query params |
dailyRevenue = yield | Protocol revenue ≠ user earnings |
c. Growth trajectory:
Taiko
- Simple chain fees
- Event-based tracking
- Merged with reviewer help on fee calculation
Bitfi
- Multi-protocol complexity
- Learned: UI ≠ backend structure
- Closed, but contributed research
Solomon
- API-based tracking (new pattern)
- Learned: Revenue taxonomy
- Merged after fixes
d. Progress indicators:
- Learning from mistakes (applied Bitfi lessons)
- Responsive to feedback (same-day fixes)
- New patterns (API vs events)
- Conceptual depth (revenue models)
e. For next time:
- Study taxonomy first - Read DefiLlama’s field definitions
- Check adapter patterns - Look at similar existing adapters
- Ask conceptual questions - “Does 100% to users = 0% protocol revenue?”
- Verify assumptions - SimpleAdapter vs Adapter, v1 vs v2
The fact that it merged means progress! Each contribution builds understanding of both technical patterns and domain concepts.