Skip to content

wip: add volatility data#2340

Merged
slasher125 merged 9 commits intoDefiLlama:masterfrom
0xkr3p:feat/add-volatiltiy-metrics
Feb 10, 2026
Merged

wip: add volatility data#2340
slasher125 merged 9 commits intoDefiLlama:masterfrom
0xkr3p:feat/add-volatiltiy-metrics

Conversation

@0xkr3p
Copy link
Copy Markdown
Contributor

@0xkr3p 0xkr3p commented Feb 4, 2026

Summary by CodeRabbit

  • New Features

    • Added a GET /volatility endpoint returning 30-day APY volatility stats (average, median, std, coefficient of variation) keyed by asset config.
    • Endpoint validates an internal API key header and returns 200, 401 (unauthorized), or 404 (no data) as appropriate.
  • Improvements

    • Responses are cached in-memory with a 5-minute TTL to reduce load.
  • Chores

    • Exposed a new environment configuration entry for the internal API key.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 4, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new /volatility API endpoint: query function, controller with x-internal-key validation against YIELDS_INTERNAL_API_KEY, route registration, in-memory TTL cache, export of the query, and serverless/env wiring. (≤50 words)

Changes

Cohort / File(s) Summary
Configuration & Environment
package.json, env.js, serverless.yml
Expose YIELDS_INTERNAL_API_KEY in exported config and provider.environment; package manifest touched.
Database Query Layer
src/queries/yield.js
Add getVolatility30d() to query the volatility view and return results keyed by configID; returns 404 when no rows. Export added.
API Controller & Routes
src/api/controllers/volatility.js, src/api/routes/volatility.js
New controller getVolatility(req,res) with x-internal-key check, in-memory TTL cache, DB query and response shaping; route wired to GET /volatility.
App Integration
src/api/app.js
Import and mount the new volatility route (app.use('/', [volatility])) into the middleware stack before redisCache.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant App as API_App
  participant Controller as Volatility_Controller
  participant Env as Config_YIELDS_INTERNAL_API_KEY
  participant Queries as Yield_Queries
  participant DB as Database

  Client->>App: GET /volatility (x-internal-key)
  App->>Controller: forward request
  Controller->>Env: validate x-internal-key
  Env-->>Controller: valid / invalid
  alt valid
    Controller->>Queries: getVolatility30d()
    Queries->>DB: SELECT ... FROM volatility
    DB-->>Queries: rows
    Queries-->>Controller: formatted {configID: [apy_avg, apy_median, apy_std, cv]}
    Controller-->>Client: 200 OK + JSON
  else invalid
    Controller-->>Client: 401 Unauthorized
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I nibble logs and hop through rows,

Keys checked softly where the cold wind blows,
Thirty days of APY in tidy arrays,
Mapped by configID through busy days,
Hooray — volatility served, pocketed with praise!

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'wip: add volatility data' is vague and uses a non-descriptive prefix; it lacks specificity about what volatility functionality is being added. Remove 'wip:' prefix and provide more specific details about the main change, such as 'Add volatility metrics API endpoint with 30-day historical data' or similar.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@0xkr3p 0xkr3p marked this pull request as ready for review February 6, 2026 09:27
@0xkr3p 0xkr3p changed the title wip: add volatility data feat: add volatility data Feb 6, 2026
@0xkr3p 0xkr3p requested a review from slasher125 February 6, 2026 09:29
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/handlers/triggerEnrichment.js`:
- Around line 91-102: The Promise.all call makes the whole enrichment pipeline
fail if getVolatility30d rejects; change to a resilient pattern (e.g., use
Promise.allSettled or catch getVolatility30d separately) so getYieldAvg30d still
resolves and volatilityData defaults to an empty object on error; specifically,
keep calling getYieldAvg30d as before, call getVolatility30d in a way that
captures errors and sets volatilityData = {} on failure, then continue the
dataEnriched = dataEnriched.map(...) step so apyMedian30d/apyStd30d/apyCv30d
fall back to null.

In `@src/queries/yield.js`:
- Around line 321-351: The getVolatility30d function currently returns new
AppError(...) on failure which treats the error as a successful return value;
change this to throw the AppError (or throw a plain Error) so callers can catch
it, and optionally log the failure before throwing; locate the early check in
getVolatility30d (after const response = await conn.query(query)) and replace
the "return new AppError(...)" with throwing the error (e.g., throw new
AppError(...)) so Promise.all consumers and triggerEnrichment.js will receive a
rejection instead of an AppError object.
🧹 Nitpick comments (1)
src/queries/yield.js (1)

325-332: Hardcoded table name volatility deviates from the parameterized pattern used elsewhere.

All other queries in this file use $<table:name> or $<yieldTable:name> for table references, which guards against SQL injection and keeps naming consistent. Consider parameterizing:

Suggested fix
   const query = `
     SELECT
         "configID",
         apy_median_30d as "apyMedian30d",
         apy_std_30d as "apyStd30d",
         cv_30d as "apyCv30d"
-    FROM volatility
+    FROM $<table:name>
   `;
 
-  const response = await conn.query(query);
+  const response = await conn.query(query, { table: 'volatility' });

Comment thread src/handlers/triggerEnrichment.js Outdated
Comment thread src/queries/yield.js Outdated
@0xkr3p 0xkr3p changed the title feat: add volatility data wip: add volatility data Feb 6, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@src/api/app.js`:
- Line 48: The redisCache middleware currently runs before the volatility route
and serves cached responses, allowing requests to bypass the x-internal-key
check; update the routing/middleware order so that the auth check runs before
redisCache or explicitly skip caching for the volatility endpoint: move the
volatility route's authentication logic into a dedicated middleware (e.g.,
validateInternalKey) and register that middleware before redisCache, or modify
redisCache to detect the volatility route (or the volatility route handler name)
and short-circuit caching for paths handled by the volatility controller so
unauthenticated requests cannot receive cached responses.

In `@src/api/controllers/volatility.js`:
- Around line 17-19: The current check if (!response) after calling conn.query()
is ineffective because conn.query returns a truthy array/Result even when empty;
change the conditional in the volatility controller to detect an empty result
set (e.g., response.length === 0 or check rows.length if using pg Result) and
return res.status(404).json({ error: "Couldn't get volatility data" }) when no
rows are found; update the logic around the response variable (and any use of
response.rows) in the function handling the query so an empty query result
triggers the 404.
- Around line 5-31: The getVolatility async handler can throw (e.g., conn.query)
and lacks error handling; wrap the body of getVolatility in a try/catch, call
conn.query inside the try, and on catch log the error and return
res.status(500).json({ error: 'Internal Server Error' }) (or include the
error.message if acceptable), ensuring any early 401/404 returns remain
unchanged and the catch covers all unexpected failures from conn.query or other
awaited operations.
🧹 Nitpick comments (1)
src/api/controllers/volatility.js (1)

7-8: Timing-safe comparison recommended for API key validation.

Using !== for secret comparison is susceptible to timing attacks. Consider using crypto.timingSafeEqual for the key comparison.

Proposed fix
+const crypto = require('crypto');
+
+function safeEqual(a, b) {
+  if (a.length !== b.length) return false;
+  return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
+}
+
 const getVolatility = async (req, res) => {
   const apiKey = req.headers['x-internal-key'];
-  if (!INTERNAL_KEY || !apiKey || apiKey !== INTERNAL_KEY) {
+  if (!INTERNAL_KEY || !apiKey || !safeEqual(apiKey, INTERNAL_KEY)) {
     return res.status(401).json({ error: 'Unauthorized' });
   }

Comment thread src/api/app.js Outdated
Comment thread src/api/controllers/volatility.js
Comment thread src/api/controllers/volatility.js Outdated
Comment thread src/queries/yield.js Outdated
Comment thread src/queries/yield.js Outdated
Comment thread src/api/controllers/volatility.js Outdated
return res.status(401).json({ error: 'Unauthorized' });
}

if (cache.data && Date.now() - cache.ts < CACHE_TTL) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one thing about our APIs, we use both cloudflare and cloutfront as CDN.

we need to prevent that route from being cached on CDN cause otherwise a subsequent request without key would just go through. also, fyi: any route without cache headers would trigger our cache defaults of i think 24hours

try {
const query = `
SELECT "configID", apy_avg_30d, apy_median_30d, apy_std_30d, cv_30d
FROM volatility
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cv_30d can be null, lets add WHERE cv_30d IS NOT NULL cause these won't matter on the ui, better to fetch less rows

Comment thread env.js Outdated
Comment thread src/api/controllers/volatility.js Outdated
Comment thread src/api/controllers/volatility.js Outdated
@slasher125 slasher125 merged commit 5cc34ac into DefiLlama:master Feb 10, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants