My fourth contribution to DefiLlama, tracking volume and open interest for Variational Omni perpetual futures on Arbitrum.
- Protocol: Variational Omni
- Chain: Arbitrum
- Type: DEX derivatives (volume + open interest)
- Start Date: 2025-01-30 (Mainnet Private Beta)
- Approach: API-based stats tracking
1. Background story
a. Picking a protocol
After Solomon, I wanted to keep building on what I learned:
- API-based adapters (not events)
- Revenue taxonomy (protocol vs supply side)
- SimpleAdapter patterns
I found Variational Omni, a derivatives DEX on Arbitrum that had public API stats.
b. Issue #5349
There was an issue requesting tracking for Variational:
- Track daily volume
- Track open interest
- Data source:
https://omni-client-api.prod.ap-northeast-1.variational.io/metadata/stats
This seemed straightforward. Fetch the API, extract volume and OI metrics.
2. My implementation (PR #5352)
a. Initial code
I created open-interest/variational.ts:
import { FetchOptions, SimpleAdapter } from "../adapters/types";
import { CHAIN } from "../helpers/chains";
import fetchURL from "../utils/fetchURL";
const URL = "https://omni-client-api.prod.ap-northeast-1.variational.io/metadata/stats";
const fetch = async (_: any, __: any, options: FetchOptions) => {
const data = await fetchURL(URL);
const listings = data?.listings ?? [];
// Calculate open interest by summing long and short OI
const openInterestAtEnd = listings.reduce((acc: number, market: any) => {
const longOI = Number(market?.open_interest?.long_open_interest || 0);
const shortOI = Number(market?.open_interest?.short_open_interest || 0);
return acc + longOI + shortOI;
}, 0);
const dailyVolume = Number(data?.total_volume_24h || 0);
return {
openInterestAtEnd,
dailyVolume,
};
};
const adapter: SimpleAdapter = {
version: 2,
fetch,
chains: [CHAIN.ARBITRUM],
runAtCurrTime: true,
start: "2025-01-30", // Mainnet Private Beta
};
export default adapter; b. What I thought
I was confident because:
- Used
SimpleAdapter(learned from Solomon) - API-based approach
- Tracked both volume and open interest
- Correct start date (mainnet launch)
runAtCurrTime: truefor real-time data
c. Test results
CI tests passed! llamabutler showed:
> [email protected] test
> ts-node --transpile-only cli/testAdapter.ts open-interest variational.ts
🦙 Running VARIATIONAL.TS adapter 🦙
---------------------------------------------------
Start Date: Mon, 22 Dec 2025 21:26:17 GMT
End Date: Tue, 23 Dec 2025 21:26:17 GMT
---------------------------------------------------
ARBITRUM 👇
Backfill start time: 30/1/2025
Open interest at end: 239.34 M
Daily volume: 976.74 M
End timestamp: 1766525176 (2025-12-23T21:26:16.000Z) Both metrics were working! I thought this was ready to merge.
d. My comment
I asked for validation:
“#5349 If this interpretation is incorrect, please let me know. Thank you.”
I was learning to ask for feedback proactively.
3. The issue
a. Maintainer couldn’t checkout
treeoflife2 closed my PR with this comment:
“closing this PR as for some reason i can’t checkout to your master branch”
Technical issue: I was working on my master branch instead of a feature branch. This made it difficult for maintainers to:
- Check out my code locally
- Test it in their environment
- Make edits if needed
b. The refactor: PR #5359
treeoflife2 created a new PR with my code, but with an important change:
“your approach was good, only problem was they are still working on the oi, so i’ve ommited that for now.”
4. What changed (comparison)
a. My version (PR #5352):
const fetch = async (_: any, __: any, options: FetchOptions) => {
const data = await fetchURL(URL);
const listings = data?.listings ?? [];
// Calculate OI
const openInterestAtEnd = listings.reduce((acc: number, market: any) => {
const longOI = Number(market?.open_interest?.long_open_interest || 0);
const shortOI = Number(market?.open_interest?.short_open_interest || 0);
return acc + longOI + shortOI;
}, 0);
const dailyVolume = Number(data?.total_volume_24h || 0);
return {
openInterestAtEnd, // ← Included OI
dailyVolume,
};
}; b. Maintainer’s version (PR #5359):
const fetch = async (_: any) => { // ← Simplified signature
const data = await fetchUrl(URL);
const listings = data?.listings ?? [];
const dailyVolume = Number(data?.total_volume_24h || 0);
// COMMENTED OUT - OI still being worked on by protocol
// const openInterestAtEnd = listings.reduce((acc: number, market: any) => {
// const longOI = Number(market?.open_interest?.long_open_interest || 0);
// const shortOI = Number(market?.open_interest?.short_open_interest || 0);
// return acc + longOI + shortOI;
// }, 0);
// const dualSidedOpenInterestAtEnd = openInterestAtEnd * 2;
return {
// openInterestAtEnd: dualSidedOpenInterestAtEnd, // ← Commented out
dailyVolume, // ← Only volume tracked
};
}; c. Key differences
| Aspect | My Version | Final Version |
|---|---|---|
| File Location | open-interest/variational.ts | dexs/variational-omni/index.ts |
| Open Interest | Tracked | Commented out (protocol still working on it) |
| Function Signature | (_: any, __: any, options: FetchOptions) | (_: any) |
| OI Calculation | Single-sided (long + short) | Would be dual-sided * 2 (commented) |
| Branch | My master | Maintainer’s feature branch |
5. What went wrong
a. Git workflow issue
My mistake: Worked on my master branch
Why this is bad:
- Maintainers can’t easily checkout your code
- Hard to collaborate (can’t push commits to help)
- Makes it difficult to keep your fork in sync
- Not following Git best practices
Correct workflow:
git checkout -b feat/variational-volume-oi Always branch off from the latest main/master before starting work.
git add .
git commit -m "feat: track variational vol & OI" Use descriptive commit messages following conventional commits format.
git push origin feat/variational-volume-oi Push to your fork’s feature branch, not master.
Open a pull request from your-fork/feat/variational-volume-oi to upstream/main.
This allows maintainers to easily checkout and collaborate on your code.
b. Protocol data readiness
My assumption: Open interest data is stable and ready to track
Reality: Protocol was “still working on the oi”
Lesson: Even if API returns data, check if:
- The data is finalized
- The protocol considers it production-ready
- There might be changes coming
c. Dual-sided OI calculation
Looking at the commented code, the maintainer would have used:
const dualSidedOpenInterestAtEnd = openInterestAtEnd * 2; My approach: long OI + short OI
Their approach: (long OI + short OI) * 2
Why dual-sided? In derivatives, both sides of a trade count:
- Buyer’s long position = open interest
- Seller’s short position = open interest
- Total OI = both sides = multiply by 2
I didn’t know this derivatives convention!
6. What I learned
a. Git workflow
- Always use feature branches for PRs
- Never work directly on
master - Format:
feat/feature-nameorfix/issue-name - Makes collaboration easier
b. Protocol communication
- Check data readiness: Just because API returns data doesn’t mean it’s production-ready
- Ask about stability: “Is this metric finalized or still in development?”
- Monitor protocol updates: Features might be work-in-progress
c. Derivatives metrics
- Open Interest: Position-based, not trade-based
- Dual-sided counting: Both long and short count as OI
- Volume: Trade-based, already counted correctly
d. What went right
Despite the issues:
- Code structure was good (“your approach was good”)
- API integration worked correctly
- Tests passed
- Volume tracking was accurate
- Applied SimpleAdapter patterns correctly
7. The outcome
a. PR closed, but code used
My PR was closed, but the maintainer:
- Took my implementation
- Fixed the git workflow issue
- Omitted OI (not ready yet)
- Merged it in PR #5359
This is still a success! My code was used, just needed workflow fixes.
b. What got merged
// Volume tracking - MY CODE
const dailyVolume = Number(data?.total_volume_24h || 0);
// OI tracking - COMMENTED FOR NOW
// (Will be enabled when protocol finalizes it) 8. Comparison with previous contributions
a. Growth progression:
Taiko
- Event-based chain fees
- Reviewer helped with calculation
- Merged
Bitfi
- Multi-protocol complexity
- Closed, research used
- Learned protocol slugs
Solomon
- API-based, revenue taxonomy
- Fixed after review
- Merged
Variational
- API-based derivatives
- Git workflow issue
- Code used (closed PR, but merged in new one)
b. New lesson: git workflow
This was the first time the issue wasn’t about code quality or concepts. It was about collaboration workflow.
9. Resources
10. Key takeaways
a. What I did right:
- Code structure was sound
- API integration worked
- Tests passed
- Applied previous learnings (SimpleAdapter, API patterns)
- Asked for feedback proactively
b. What I learned:
- Git workflow matters: Always use feature branches
- Check data readiness: API data ≠ production-ready
- Derivatives conventions: Dual-sided OI counting
- File locations:
dexs/vsopen-interest/folder structure
c. For next time:
# Proper workflow
git checkout -b feat/protocol-name
# ... make changes ...
git push origin feat/protocol-name
# Create PR from feature branch d. The positive:
Even though my PR was closed:
- My code was good enough to use
- Maintainer said “your approach was good”
- Just needed workflow fixes
- Code is in production
This shows progress. Code quality is improving. I just need to refine the process!