feat: add stablecoins v2 api - #889
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThe 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. ChangesV2 API pipeline
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
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (4)
api2/cron-task/index.ts (1)
74-75: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winIsolate v2 build failures from the cron run result.
buildV2Filesthrows 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 ofrun()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
endsClosedgives 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 winThe parsed-object accounting understates real memory use.
parsedOfcharges one extraentry.buf.lengthfor 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 theserveHistorypath, so the process can hold much more thanLRU_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 winSliced history payloads are rebuilt on the event loop for every 200.
Each 200 runs
sliceTuplesover grouped series and thenJSON.stringifyon the request thread. Grouped files such asby-assetandby-chainare large, and a caller can varystartandendfreely, so the same source file is re-sliced and re-serialized repeatedly.The output is fully determined by
discriminatorplus 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
📒 Files selected for processing (6)
api2/cron-task/index.tsapi2/routes/index.tsapi2/v2/build.tsapi2/v2/routes.tsapi2/v2/serve.tsapi2/v2/shared.ts
There was a problem hiding this comment.
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
📒 Files selected for processing (3)
api2/v2/routes.tsapi2/v2/serve.tsapi2/v2/shared.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- api2/v2/routes.ts
- api2/v2/shared.ts
There was a problem hiding this comment.
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
📒 Files selected for processing (2)
api2/v2/build.tsapi2/v2/shared.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- api2/v2/shared.ts
Implements the v2 proposal.
API
Payload impact
Implementation
api2/v2/build.tstransforms the v1 route files produced by the existing cron._manifestis 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
*Usd; raw counts are only under/history/supplywithunit: "count".groupBy=assetwith their flag.limit/Others: clients can derivetotal − Σ(top N)from the small response.Summary by CodeRabbit
New Features
Bug Fixes