Skip to content

feat: add stablecoins v2 api - #889

Open
DaniiRix wants to merge 9 commits into
DefiLlama:masterfrom
DaniiRix:stablecoins-v2
Open

feat: add stablecoins v2 api#889
DaniiRix wants to merge 9 commits into
DefiLlama:masterfrom
DaniiRix:stablecoins-v2

Conversation

@DaniiRix

@DaniiRix DaniiRix commented Aug 4, 2026

Copy link
Copy Markdown
Member

Implements the v2 proposal.

API

GET /v2/assets[?chain=]
GET /v2/assets/:asset
GET /v2/chains

GET /v2/history/market-cap
GET /v2/history/volume
GET /v2/history/supply

Payload impact

v1 v2
Global market cap 3.25 MB / 194 ms parse 27 KB / 0.3 ms
Full asset breakdown ~36 MB 1.46 MB / 70 ms
Weekly breakdown 256 KB / 3.9 ms

Implementation

api2/v2/build.ts transforms the v1 route files produced by the existing cron.

_manifest is written last and gates publication. Serving rejects epoch mismatches, refuses builds >24h old, and marks builds >3h old as stale. Builds abort on missing/empty required sources to avoid publishing partial data.

Key decisions

  • Market cap: preserves v1 price-adjusted semantics. USD fields use *Usd; raw counts are only under /history/supply with unit: "count".
  • Double-counted assets: excluded from totals server-side (66/413), but still returned by groupBy=asset with their flag.
  • Resolution: period-end sampling; no averaging.
  • No limit / Others: clients can derive total − Σ(top N) from the small response.

Summary by CodeRabbit

  • New Features

    • Added a V2 API with asset, chain, market-cap, volume, and supply endpoints.
    • Added daily, weekly, and monthly history views with date filtering and chain-specific data.
    • Added compressed responses, caching, ETags, and conditional requests for faster delivery.
    • Added automated generation of V2 data files and build metadata.
    • Added standardized errors, redirects, and unavailable-data responses.
  • Bug Fixes

    • Added safeguards to prevent serving incomplete, stale, corrupted, or regressed data.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 26dfac70-ed65-4bbb-a979-a4d3fb93d191

📥 Commits

Reviewing files that changed from the base of the PR and between 9159477 and e83174c.

📒 Files selected for processing (2)
  • api2/v2/build.ts
  • api2/v2/serve.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • api2/v2/build.ts
  • api2/v2/serve.ts

📝 Walkthrough

Walkthrough

The PR adds a V2 data pipeline. The cron task builds versioned artifacts and manifests. New routes validate requests and serve current and historical data with caching, compression, freshness checks, slicing, and structured errors.

Changes

V2 API pipeline

Layer / File(s) Summary
Shared V2 data contracts and transformations
api2/v2/shared.ts
Defines asset registries, tuple and resolution types, aggregation, sampling, slicing, identity fields, and point conversion.
V2 artifact build pipeline
api2/v2/build.ts, api2/cron-task/index.ts
Builds current, market-cap, supply, and volume artifacts. It validates source data, derives volume routes, writes JSON and Brotli files atomically, and records a final manifest.
V2 artifact loading and response handling
api2/v2/serve.ts
Adds cached artifact loading, concurrent-read deduplication, integrity checks, build freshness gates, ETags, Brotli negotiation, sliced responses, redirects, and structured errors.
V2 route validation and registration
api2/v2/routes.ts, api2/routes/index.ts
Registers V2 endpoints and adds query validation, canonical redirects, asset and chain resolution, and market-cap, volume, supply, snapshot, and detail responses.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant V2Routes
  participant buildGate
  participant loadV2File
  participant sendV2Entry

  Client->>V2Routes: request V2 endpoint
  V2Routes->>buildGate: validate manifest freshness
  buildGate-->>V2Routes: return build status
  V2Routes->>loadV2File: load artifact
  loadV2File-->>V2Routes: return cached validated entry
  V2Routes->>sendV2Entry: send JSON or Brotli response
  sendV2Entry-->>Client: return response or 304
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the addition of the Stablecoins V2 API.
Description check ✅ Passed The description explains the proposal, endpoints, implementation, behavior, performance impact, and key design decisions; the listing template does not apply.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@DaniiRix
DaniiRix marked this pull request as ready for review August 4, 2026 09:23

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (4)
api2/cron-task/index.ts (1)

74-75: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Isolate v2 build failures from the cron run result.

buildV2Files throws by design, for example on a source regression or a collapsed asset count. Those aborts intentionally leave the previous v2 build intact. Because the call is unguarded, the throw propagates out of run() after all v1 data was already persisted. The cron job then reports a failure for a successful v1 publish, and a retry re-runs the whole pipeline.

Catch and log the error so the exit status reflects only unrecoverable failures.

♻️ Proposed change
-  await buildV2Files()
+  try {
+    await buildV2Files()
+  } catch (e) {
+    console.error('v2 build failed; previous v2 artifacts are left intact', e)
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api2/cron-task/index.ts` around lines 74 - 75, Wrap the await buildV2Files()
call in a try-catch block to isolate v2 build failures from the run() function's
result. In the catch block, log the error details but do not rethrow it,
allowing the cron job to complete successfully when v2 build fails by design
while v1 has already persisted. This ensures the exit status reflects only
unrecoverable failures, not expected v2 validation failures.
api2/v2/serve.ts (2)

227-234: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

endsClosed gives up after 7 trailing bytes.

The loop stops at buf.length - 8. An artifact that ends with 7 or more whitespace bytes is then reported as corrupt and returns 500. Scan all trailing whitespace instead, with a small bound on the scan.

♻️ Proposed change
 function endsClosed(buf: Buffer): boolean {
-  for (let i = buf.length - 1; i >= 0 && i > buf.length - 8; i--) {
+  for (let i = buf.length - 1; i >= 0 && i > buf.length - 64; i--) {
     const c = buf[i];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api2/v2/serve.ts` around lines 227 - 234, Update endsClosed to scan all
trailing whitespace rather than stopping after seven bytes, while retaining a
small upper bound on the scan to prevent unbounded work. Preserve the existing
whitespace checks and return whether the first non-whitespace byte is the
closing-brace byte.

123-133: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

The parsed-object accounting understates real memory use.

parsedOf charges one extra entry.buf.length for the parsed value. A parsed graph of tuple arrays is normally several times the size of its JSON text. Large history files are parsed on the serveHistory path, so the process can hold much more than LRU_MAX_BYTES.

Charge a multiplier so the ceiling stays meaningful.

♻️ Proposed change
+const PARSED_SIZE_FACTOR = num(process.env.STABLECOINS_V2_PARSED_FACTOR, 6);
+
 export function parsedOf(entry: Entry): any {
   if (entry.parsed === undefined) {
     entry.parsed = JSON.parse(entry.buf.toString("utf8"));
     if (lru.get(entry.key) === entry) {
-      entry.bytes += entry.buf.length;
-      lruBytes += entry.buf.length;
+      const parsedBytes = entry.buf.length * PARSED_SIZE_FACTOR;
+      entry.bytes += parsedBytes;
+      lruBytes += parsedBytes;
       evict();
     }
   }
   return entry.parsed;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api2/v2/serve.ts` around lines 123 - 133, Update parsedOf so parsed JSON
memory accounting uses a multiplier of entry.buf.length rather than a single
text-buffer length; apply the adjusted charge consistently to entry.bytes and
lruBytes before evict(), preserving the existing LRU ownership check and
eviction flow.
api2/v2/routes.ts (1)

152-162: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Sliced history payloads are rebuilt on the event loop for every 200.

Each 200 runs sliceTuples over grouped series and then JSON.stringify on the request thread. Grouped files such as by-asset and by-chain are large, and a caller can vary start and end freely, so the same source file is re-sliced and re-serialized repeatedly.

The output is fully determined by discriminator plus the build epoch. Cache the serialized buffer in a small bounded LRU keyed by ${epoch}|${discriminator} and reuse it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api2/v2/routes.ts` around lines 152 - 162, Update the sliced history response
flow around sendV2Sliced to cache the serialized payload in a small bounded LRU
keyed by `${epoch}|${discriminator}`. Reuse the cached buffer for identical
epoch/discriminator requests, and only run the parsed series slicing and JSON
serialization on cache misses; preserve the existing response shape and epoch
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api2/v2/build.ts`:
- Around line 405-420: The price variable defaults to 0 when both priceUsd and
refUsd are null, causing the USD field calculations to report exactly 0 instead
of null to indicate an unknown price. Following the convention in shared.ts line
129 where null means unknown and 0 means measured zero, change the price
calculation to preserve null when the price is unknown instead of defaulting to
0. Then update the computations for unreleasedUsd, bridgedInUsd, mintedUsd, and
the non-scoped circulatingUsd fallback to use the toUsd function, which will
properly emit null when the price is unknown rather than computing with a fake 0
value.
- Around line 342-348: In api2/v2/build.ts at lines 119-133 and 342-348, use
chainCacheSlug(chain) derived from the lowercased chain key for
stablecoincharts2 cache filename generation, including readRouteData(...) and
exists(...). Keep chainSlugFromLabel(label) only for by-asset-chain/<slug>
output paths.
- Around line 499-510: When readdir fails in buildVolume, set a flag on the
stats object to indicate that volume sources are missing. Then update the
degraded check logic around line 468 to treat this missing volume sources flag
as a degraded condition if volume artifacts already exist from a previous build
(check for existence of v2/history/volume/daily/total or similar). This prevents
sweepOrphans from deleting all published volume artifacts when a temporary
source failure occurs, matching the regression policy already applied to chains
and assets.
- Around line 165-167: Update buildV2Files to inspect reg.issues immediately
after obtaining assetRegistry(). If issues are present, abort the build before
generating or marking any v2 artifacts complete, preserving the documented
refusal-to-publish behavior and preventing _manifest from indicating success.

In `@api2/v2/routes.ts`:
- Around line 187-189: Update the asset slug decoding in the v2 route handler
around rawSlug and slug to catch URIError from decodeURIComponent and return a
400 client-error response instead of allowing v2Wrapper to produce a 500 or log
a stack trace; preserve the existing lowercase redirect behavior for valid
encoded slugs.

In `@api2/v2/serve.ts`:
- Around line 97-107: Update the compressed sibling validation in readEntry to
fully decompress and validate the .br contents once per build, rather than
relying on inflateHead’s partial output and swallowed errors. Reuse the fully
inflated bytes for the epoch check, and accept brRead only when decompression
succeeds and the inflated content matches the raw buf; preserve the existing
mtime race checks and fallback to the raw response when validation fails.

---

Nitpick comments:
In `@api2/cron-task/index.ts`:
- Around line 74-75: Wrap the await buildV2Files() call in a try-catch block to
isolate v2 build failures from the run() function's result. In the catch block,
log the error details but do not rethrow it, allowing the cron job to complete
successfully when v2 build fails by design while v1 has already persisted. This
ensures the exit status reflects only unrecoverable failures, not expected v2
validation failures.

In `@api2/v2/routes.ts`:
- Around line 152-162: Update the sliced history response flow around
sendV2Sliced to cache the serialized payload in a small bounded LRU keyed by
`${epoch}|${discriminator}`. Reuse the cached buffer for identical
epoch/discriminator requests, and only run the parsed series slicing and JSON
serialization on cache misses; preserve the existing response shape and epoch
behavior.

In `@api2/v2/serve.ts`:
- Around line 227-234: Update endsClosed to scan all trailing whitespace rather
than stopping after seven bytes, while retaining a small upper bound on the scan
to prevent unbounded work. Preserve the existing whitespace checks and return
whether the first non-whitespace byte is the closing-brace byte.
- Around line 123-133: Update parsedOf so parsed JSON memory accounting uses a
multiplier of entry.buf.length rather than a single text-buffer length; apply
the adjusted charge consistently to entry.bytes and lruBytes before evict(),
preserving the existing LRU ownership check and eviction flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f857ac11-91d7-4fb5-9d7c-8cec4c3e8cf9

📥 Commits

Reviewing files that changed from the base of the PR and between d5a09a1 and e797d57.

📒 Files selected for processing (6)
  • api2/cron-task/index.ts
  • api2/routes/index.ts
  • api2/v2/build.ts
  • api2/v2/routes.ts
  • api2/v2/serve.ts
  • api2/v2/shared.ts

Comment thread api2/v2/build.ts
Comment thread api2/v2/build.ts Outdated
Comment thread api2/v2/build.ts Outdated
Comment thread api2/v2/build.ts
Comment thread api2/v2/routes.ts Outdated
Comment thread api2/v2/serve.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api2/v2/serve.ts`:
- Around line 183-191: The setCommonHeaders flow must cap cache TTLs at the next
freshness boundary instead of always using the static CACHE_HEADERS values.
Compute buildAge from the response’s generatedAt timestamp, then limit max-age
and stale-while-revalidate based on V2_STALE_AFTER and V2_MAX_AGE before
emitting the cache metadata, preserving the existing stale/refused-build headers
and Vary behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3809ef1c-17f0-44bd-9833-150a4cabdc04

📥 Commits

Reviewing files that changed from the base of the PR and between e797d57 and 428e350.

📒 Files selected for processing (3)
  • api2/v2/routes.ts
  • api2/v2/serve.ts
  • api2/v2/shared.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • api2/v2/routes.ts
  • api2/v2/shared.ts

Comment thread api2/v2/serve.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api2/v2/build.ts`:
- Around line 65-77: Update writeV2 and the final manifest publication flow to
stage every artifact under a unique build ID rather than overwriting live paths,
including ensuring generatedAt/build identifiers cannot collide across retries.
Publish the complete staged build first, then atomically switch _manifest only
after all writes succeed; retain the previous artifact set until that switch
completes, and clean up failed staging without deleting the currently published
build.
- Around line 144-145: Update the publishedSlugs helper to exclude temporary
route-data files, including both .tmp and .br.tmp variants, while retaining the
existing Brotli-file exclusion. Ensure only finalized published slugs are
returned so writeV2 does not report temporary artifacts as retired entities.
- Around line 307-308: Reject the build when the resolved asset collection
represented by listed is empty, before calling assertSourcesNotRegressed or
publishing the manifest. Ensure this validation covers sources whose entries are
absent from reg.byId, including first builds with no prior artifacts, while
preserving the existing sourceAssets guards.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b1bed599-a035-4082-9c9d-d8f3f789f641

📥 Commits

Reviewing files that changed from the base of the PR and between 428e350 and 9159477.

📒 Files selected for processing (2)
  • api2/v2/build.ts
  • api2/v2/shared.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • api2/v2/shared.ts

Comment thread api2/v2/build.ts
Comment thread api2/v2/build.ts Outdated
Comment thread api2/v2/build.ts
@DaniiRix DaniiRix self-assigned this Aug 6, 2026
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.

1 participant