Skip to content

Chain assets 2#11394

Open
waynebruce0x wants to merge 9 commits into
masterfrom
chain-assets-2-feb
Open

Chain assets 2#11394
waynebruce0x wants to merge 9 commits into
masterfrom
chain-assets-2-feb

Conversation

@waynebruce0x

@waynebruce0x waynebruce0x commented Feb 13, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Added file-based caching for faster historical data and chart loads
    • Added dry-run mode to safely test data processing
    • Enabled authenticated API access for improved data sources
  • Bug Fixes

    • Fixed token deduplication and casing for Stellar assets
    • Improved price resolution and reverse-symbol fallback for bridge/protocol tokens
    • Added stricter data validation with regression/drift detection
  • Chores

    • Optimized historical storage and retrieval performance
    • Updated default RPC endpoint for Aptos for better reliability

@coderabbitai

coderabbitai Bot commented Mar 31, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Added a versioned on-disk file cache for historical chain data, DB-side per-day aggregation and a cache precompute step after writes, refactored historical fetch APIs (added breakdown flag and direct DB fallback with cache warm), introduced verifyChangesV2 with chain-specific checks, added dryRun support, and improved Stellar/Aptos supply handling.

Changes

Historical Data Storage & Caching

Layer / File(s) Summary
Data Shape / Cache layout
defi/l2/v2/file-cache.ts
New versioned JSON file-cache module with atomic temp+rename writes, read returning parsed arrays or null, per-folder ensureDir promise cache, and chain-name sanitization.
DB Aggregation / Precompute
defi/l2/v2/storeToDb.ts
Added precomputeHistoricalCache() which queries chainassets2 using DB-side per-day distinct on (date_trunc(...)) selection to pick latest row per day and builds all-chains and per-chain daily totals. storeHistoricalToDB now calls this best-effort post-insert.
Core Fetch API / Cache Fast Path
defi/l2/v2/storeToDb.ts
fetchHistoricalFromDB(chain, isRaw=false, breakdown=false) now accepts breakdown; totals-only (!isRaw && !breakdown) reads file cache (readChainHistory/readAllChainsHistory) as fast path; cache-miss invokes fetchHistoricalFromDBDirect(...) and warms the cache on success.
Direct DB Read / Breakdown
defi/l2/v2/storeToDb.ts
fetchHistoricalFromDBDirect returns raw DB rows when isRaw true; otherwise converts rows to totals-only or breakdown structures, using chainAssetsSymbolMap to map and aggregate asset keys that resolve to the same symbol.
Chart API Simplification
defi/l2/v2/storeToDb.ts
fetchChartData(chain) simplified to directly return fetchHistoricalFromDB(chain). Removed exported fetchFlows and findDailyTimestamps.

Supply Function Updates

Layer / File(s) Summary
Defaults & Input Normalization
defi/l2/utils.ts
getAptosSupplies now defaults RPC to https://fullnode.mainnet.aptoslabs.com when env var unset. getStellarSupplies deduplicates input tokens by canonical (asset_code, asset_issuer) key (stripping version suffixes) keeping first-seen raw token.
Horizon Query & Keying
defi/l2/utils.ts
Stellar logic uppercases tokens and asset_issuer for parsing/Horizon queries and writes supplies under stellar:${rawToken} (original casing) to avoid duplicate summation.

TVL Aggregation, Pricing, Verification & Dry-run

Layer / File(s) Summary
Aggregation input handling
defi/l2/v2/index.ts
Iterate adapter outputs using rawChain, skip excluded keys, then lowercase rawChain when indexing sourceChainAmounts/protocolAmounts to normalize aggregation.
Authenticated API helper
defi/l2/v2/index.ts
Added proApi(path, publicFallback) to prefer pro-api.llama.fi when INTERNAL_API_KEY present, with try/catch fallbacks and warnings.
Price fill & reverse lookup
defi/l2/v2/index.ts
Bridge/protocol token keys missing prices are batch-resolved via coins.getPrices; builds symbolToPrice map and attempts symbol→price resolution for mis-keyed coingecko:* destination outputs, re-keying amounts and adjusting divisor logic. Newly priced protocol keys trigger market-cap fetches.
Control Flow & Persistence
defi/l2/v2/index.ts
storeChainAssetsV2 signature extended to (..., dryRun: boolean = false); uses verifyChangesV2(symbolData) unless override; if dryRun true, skips all R2/DB writes and returns { rawData, symbolData }; defers writing chainAssetsSymbolMap until post-validation.
Verification module
defi/l2/v2/verifyChanges.ts
New verifyChangesV2(chains) queries latest chainassets2 snapshot, applies chain-specific thresholds (Solana, Tron), logs BSC tokens, detects regressions-to-zero and large bidirectional drifts, and throws with newline-formatted issues if checks fail.

Sequence Diagram(s)

sequenceDiagram
    participant App as Application
    participant DB as PostgreSQL
    participant FileCache as File Cache

    App->>DB: storeHistoricalToDB (INSERT)
    DB-->>App: INSERT OK

    App->>DB: precomputeHistoricalCache() -> SELECT distinct per-day rows
    DB-->>App: per-day rows

    App->>FileCache: storeAllChainsHistory(data)
    FileCache-->>App: OK
    App->>FileCache: storeChainHistory(chain, data)
    FileCache-->>App: OK

    App->>FileCache: fetchHistoricalFromDB(chain, breakdown=false)
    alt Cache Hit
        FileCache-->>App: Return cached totals
    else Cache Miss
        FileCache-->>App: Miss
        App->>DB: fetchHistoricalFromDBDirect(...)
        DB-->>App: Return DB rows
        App->>FileCache: storeChainHistory / storeAllChainsHistory (warm)
        FileCache-->>App: OK
    end
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Poem

🐰 I nibble bytes and cache them bright,
Daily rows tucked in soft disk light,
Checks that guard each chain's parade,
Stars deduped in Stellar glade,
Dry-run naps while changes take flight.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.18% 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 "Chain assets 2" is vague and generic, failing to convey meaningful information about the substantial changes made (cache rebuild, historical data refactoring, verification logic). Use a more descriptive title that highlights the main change, such as 'Refactor historical data fetching with file-based caching' or 'Add cache rebuild and verification for chain assets V2'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chain-assets-2-feb

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.

@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 the current code and only fix it if needed.

Inline comments:
In `@defi/l2/v2/storeToDb.ts`:
- Line 39: The call to precomputeHistoricalCache() in storeHistoricalToDB() must
be treated as best-effort so a cache rebuild failure does not reject the DB
persistence; wrap the precomputeHistoricalCache() invocation in a try/catch
inside storeHistoricalToDB() (catch and log the error with context but do not
rethrow) so the DB write succeeds even if cache rebuild fails, and adjust the
cache-read logic referenced around the acceptance-of-any-non-empty-cache (the
code at the acceptance check used when serving cached reads) to gate
freshness—add a simple freshness check (e.g., timestamp or size+age) before
returning the cached file and fall back to serve the persisted DB row if the
cache is stale or empty. Ensure you update references in the same function
(storeHistoricalToDB) and the cache-read/acceptance logic (the non-empty-cache
check) so failures are non-fatal and stale caches aren’t served.
- Around line 45-50: The daily bucketing uses date_trunc('day',
to_timestamp(timestamp)) which respects the session timezone; update all three
queries in precomputeHistoricalCache() and fetchHistoricalFromDBDirect() to
bucket in UTC by applying AT TIME ZONE 'UTC' to the timestamp expression (e.g.
replace date_trunc('day', to_timestamp(timestamp)) with date_trunc('day',
to_timestamp(timestamp) AT TIME ZONE 'UTC') and use the same expression in the
corresponding ORDER BY/distinct on clauses) so the Unix epoch seconds are
grouped by UTC days consistently.

In `@defi/src/getChainAssetsChart.ts`:
- Around line 8-9: The code calls chainParam.replace(...) without checking
chainParam exists, causing a TypeError for malformed requests; update the
handler to guard against a missing path param by checking
event.pathParameters?.chain (or chainParam) before normalizing, and if missing
return/throw a clear validation error; specifically modify the logic around
chainParam and the call to getChainIdFromDisplayName to only call
chainParam.replace when chainParam is a non-empty string and otherwise handle
the missing param with a proper validation response.
🪄 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

Run ID: 924828c4-f637-4816-be8b-0cb451ac0f6d

📥 Commits

Reviewing files that changed from the base of the PR and between 2ff7745 and 2c7c964.

📒 Files selected for processing (3)
  • defi/l2/v2/storeToDb.ts
  • defi/src/getChainAssets.ts
  • defi/src/getChainAssetsChart.ts

Comment thread defi/l2/v2/storeToDb.ts Outdated
Comment thread defi/l2/v2/storeToDb.ts Outdated
Comment thread defi/src/getChainAssetsChart.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

🧹 Nitpick comments (1)
defi/l2/v2/file-cache.ts (1)

27-35: Consider atomic writes to avoid serving partial JSON.

writeFile is not atomic: a crash or concurrent overlap can leave a truncated file on disk. Readers would recover via the catch in readData (returning null), so it's not data-corrupting, but a cached day could silently disappear until the next refresh. Writing to a temp path and rename-ing is cheap insurance here.

♻️ Proposed change
 async function storeData(subPath: string, data: any): Promise<void> {
   const filePath = path.join(VERSIONED_CACHE_DIR, subPath);
   await ensureDirExists(path.dirname(filePath));
   try {
-    await fs.promises.writeFile(filePath, JSON.stringify(data));
+    const tmpPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
+    await fs.promises.writeFile(tmpPath, JSON.stringify(data));
+    await fs.promises.rename(tmpPath, filePath);
   } catch (e) {
     console.error(`Error storing cache ${filePath}:`, (e as any)?.message);
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@defi/l2/v2/file-cache.ts` around lines 27 - 35, storeData currently writes
JSON directly with fs.promises.writeFile which can leave a truncated file on
crash; to make writes atomic, write the JSON to a temporary file in the same
directory (e.g., derive a temp name from filePath like filePath +
`.tmp-${process.pid}-${Date.now()}`), fs.promises.writeFile the temp, then
fs.promises.rename(tempPath, filePath) to atomically replace the target; update
storeData to perform this temp-write-and-rename sequence and keep the existing
error handling (and ensure ensureDirExists is still called on
path.dirname(filePath)).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@defi/l2/v2/file-cache.ts`:
- Around line 47-53: Sanitize the chain parameter before using it in file paths
in storeChainHistory and readChainHistory by normalizing it the same way as
fileNameNormalizer in defi/src/api2/cache/file-cache.ts: remove characters
matching /[^a-zA-Z0-9\/\.]/g, convert to lowercase, and fall back to a safe
default (e.g., "unknown") if the result is empty; apply this normalizedChain
when building the path (`history/${normalizedChain}.json`) and update both
functions accordingly (or import and reuse the existing normalizer if
available).

---

Nitpick comments:
In `@defi/l2/v2/file-cache.ts`:
- Around line 27-35: storeData currently writes JSON directly with
fs.promises.writeFile which can leave a truncated file on crash; to make writes
atomic, write the JSON to a temporary file in the same directory (e.g., derive a
temp name from filePath like filePath + `.tmp-${process.pid}-${Date.now()}`),
fs.promises.writeFile the temp, then fs.promises.rename(tempPath, filePath) to
atomically replace the target; update storeData to perform this
temp-write-and-rename sequence and keep the existing error handling (and ensure
ensureDirExists is still called on path.dirname(filePath)).
🪄 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

Run ID: 9f986402-ed09-4e50-8c8f-cb863530017f

📥 Commits

Reviewing files that changed from the base of the PR and between 2c7c964 and 76c639d.

📒 Files selected for processing (1)
  • defi/l2/v2/file-cache.ts

Comment thread defi/l2/v2/file-cache.ts
waynebruce0x and others added 2 commits April 22, 2026 10:35
… verify & cache

- stellar: uppercase asset_issuer in getStellarSupplies (Horizon is case-sensitive
  on issuer); dedupe by (code, issuer) so versioned ES variants don't double-count;
  add stellar to chainsWithCaseSensitiveDataProviders.
  Result: stellar data restored (was total=0); USDC $240M matches Horizon supply.
- aptos: fall back to public fullnode when APTOS_RPC is unset so per-token
  supply fetches succeed; recovers ~\$1.69B of aptos TVL that was dropping
  to zero.
- canonical / protocol bridges (noble, xion, echelon_initia, inertia):
  after fetchOutgoingAmountsFromDB, batch-resolve any bridge-emitted keys
  missing from allPrices via coins.getPrices. Adds a symbol-keyed fallback
  for adapters that emit plain symbols. Lowercase source-chain key in
  fetchOutgoingAmountsFromDB so sourceChainAmounts aligns with allChainKeys.
- verify: new verifyChangesV2 that compares against the latest chainassets2
  row (instead of the V1 /chain-assets/chains API), adds a "regressed to
  zero" check with a \$100k floor, and skips the drift check when prev is
  >6h stale. V1 verifyChanges kept unchanged for V1.
- read-path: warm the file cache on DB-direct hits (25s cold -> <50ms warm),
  add a null-chain guard to fetchHistoricalFromDBDirect, and fix a
  d[c]/d[chain] alias in the single-chain breakdown branch.
- yields/stablecoins endpoints: prefer pro-api.llama.fi/\${INTERNAL_API_KEY}
  when the key is set, with graceful degradation when upstream returns a
  non-array (e.g. 402 paywall).
- storeChainAssetsV2 now takes an optional dryRun flag that skips all prod
  writes; used for local end-to-end verification.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
defi/l2/v2/index.ts (1)

432-490: ⚠️ Potential issue | 🟠 Major

Store symbol-fallback amounts under the resolved price key.

The fallback resolves coinData by symbol, but Line 490 still stores the amount under the unresolved key like coingecko:axlusdc. That key usually has no allMcaps entry and no allPrices entry, so the amount is dropped later or breaks symbol mapping.

Suggested fix
-  const symbolToPrice: { [symbol: string]: CoinsApiData } = {};
-  Object.values(allPrices).forEach((p) => {
+  const symbolToPrice: { [symbol: string]: { key: string; data: CoinsApiData } } = {};
+  Object.entries(allPrices).forEach(([priceKey, p]) => {
     const sym = p?.symbol?.toUpperCase();
     if (!sym) return;
     // Prefer the first resolution; symbol collisions are ambiguous by construction.
-    if (!symbolToPrice[sym]) symbolToPrice[sym] = p;
+    if (!symbolToPrice[sym]) symbolToPrice[sym] = { key: priceKey, data: p };
   });
         let coinData = allPrices[key];
+        let outputKey = key;
         // When the adapter was symbol-keyed, `key` looks like "coingecko:axlusdc" but
         // there is no such coingecko id. Fall back to a symbol reverse lookup.
@@
           const symbol = key.slice("coingecko:".length).toUpperCase();
           const resolved = symbolToPrice[symbol];
-          if (resolved?.price) {
-            coinData = resolved;
+          if (resolved?.data?.price) {
+            coinData = resolved.data;
+            outputKey = resolved.key;
             isWholeTokenAmount = true;
           }
         }
@@
-        nativeDataAfterDeductions[chain][key] = usdAmount;
+        nativeDataAfterDeductions[chain][outputKey] = usdAmount;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@defi/l2/v2/index.ts` around lines 432 - 490, The symbol-fallback branch
currently uses the original unresolved key (e.g., "coingecko:axlusdc") when
writing into nativeDataAfterDeductions, which causes those amounts to be lost;
update the destination handling so that when you resolve coinData via
symbolToPrice (the resolved variable), compute and use the resolved price key
(e.g., the normalized key for the resolved CoinGecko id) instead of the original
key, keep the isWholeTokenAmount logic for divisor calculation, and write
nativeDataAfterDeductions[chain][resolvedKey] = usdAmount (or
nativeDataAfterDeductions[chain][key] = usdAmount only when no resolution
happened); refer to symbolToPrice, key, resolved, coinData, isWholeTokenAmount,
destinationChainAmount and nativeDataAfterDeductions to locate and change the
assignment.
♻️ Duplicate comments (2)
defi/l2/v2/storeToDb.ts (2)

47-50: ⚠️ Potential issue | 🟠 Major

Bucket daily snapshots in UTC.

These date_trunc('day', to_timestamp(timestamp)) expressions use the Postgres session timezone. Since timestamp is Unix seconds, bucket explicitly in UTC in all three daily queries.

Suggested query change
-      select distinct on (date_trunc('day', to_timestamp(timestamp)))
+      select distinct on (date_trunc('day', to_timestamp(timestamp) AT TIME ZONE 'UTC'))
         timestamp, value
       from chainassets2
-      order by date_trunc('day', to_timestamp(timestamp)) asc, timestamp desc
+      order by date_trunc('day', to_timestamp(timestamp) AT TIME ZONE 'UTC') asc, timestamp desc
#!/bin/bash
# Verify all day buckets are explicitly UTC-based.
rg -n "date_trunc\('day', to_timestamp\(timestamp\)(?!\s+AT\s+TIME\s+ZONE\s+'UTC')" --pcre2 --iglob '*.ts'

Also applies to: 129-132, 140-143

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@defi/l2/v2/storeToDb.ts` around lines 47 - 50, The SQL daily bucketing uses
date_trunc('day', to_timestamp(timestamp)) which depends on the session
timezone; update each occurrence in storeToDb.ts (the three daily snapshot
queries around the select distinct block and the two other blocks referenced) to
explicitly bucket in UTC by adding AT TIME ZONE 'UTC' inside the date_trunc call
and in corresponding ORDER BY expressions (e.g. use date_trunc('day',
to_timestamp(timestamp) AT TIME ZONE 'UTC') and order by that same expression
and timestamp desc) so all three daily queries consistently use UTC-based day
buckets.

39-39: ⚠️ Potential issue | 🟠 Major

Keep cache rebuild/read freshness from affecting persisted writes.

precomputeHistoricalCache() can still reject after the DB insert succeeds, while the read path accepts any non-empty cached file. This can report a failed publish after persistence and keep serving stale cache data.

Also applies to: 95-99

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@defi/l2/v2/storeToDb.ts` at line 39, The DB write flow currently awaits
precomputeHistoricalCache() so a cache rebuild error can cause the publish to
fail after persistence; change the publish flow in storeToDb to treat
precomputeHistoricalCache() as best-effort: call it but catch and log any errors
(do not rethrow or make the DB insert promise fail). Additionally, harden the
cache read logic (the code that currently accepts "any non-empty cached file")
to validate cache freshness/version (e.g., compare a timestamp, version, or
consistency marker produced at write time) before serving it so stale cache
files are rejected; apply the same catch-and-log pattern and freshness check for
the other occurrences noted around the block at lines ~95-99.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@defi/l2/utils.ts`:
- Around line 315-318: The regex that normalizes versioned asset keys is off by
one and only captures 55 issuer chars; update the pattern used around
classicKey/canonicalKey to match the full 56-character Stellar issuer (change
the quantifier from {55} to {56}) so keys like "USDC-G...56chars-1" strip the
"-1" correctly; locate the normalization using classicKey.match(...) that
assigns canonicalKey and replace the pattern accordingly, then run or add a unit
test that verifies "CODE-G<56chars>-<version>" normalizes to "CODE-G<56chars>".
- Around line 335-338: getSorobanTokenSupply() returns number | null but the
code in the isSorobanContractId branch compares it to BigInt(0); change the
comparison to use the number domain (e.g., compare supply > 0) and keep the null
guard, so in the isSorobanContractId branch check that supply !== null && supply
> 0 before assigning supplies[`stellar:${rawToken}`] = Number(supply); this
ensures consistent numeric types for isSorobanContractId, getSorobanTokenSupply,
supplies, and rawToken.

In `@defi/l2/v2/index.ts`:
- Around line 407-430: When filling missing bridge/protocol keys (allBridgeKeys
→ missingBridgeKeys) you only merge prices into allPrices but later filtering
(nativeDataAfterMcaps) drops keys missing in allMcaps; fix by also fetching and
merging market caps for those same missingBridgeKeys. After the coins.getPrices
call (or in the same branch) call the corresponding mcaps API (e.g.
coins.getMcaps or coins.getMarketCaps) with missingBridgeKeys and timestamp,
then merge results into allMcaps (same key semantics and any decimals handling)
so keys resolved here are preserved by nativeDataAfterMcaps; apply the same
change to the similar block around lines 495-503.
- Around line 574-577: The symbol map R2 write is started before validation, so
move the invocation of storeR2JSONString into the post-validation path: don't
create symbolMapPromise (or call storeR2JSONString) until after
verifyChangesV2() has completed successfully and only when dryRun is false;
ensure symbolMapPromise is assigned inside the success branch (and
resolved/no-op when dryRun) so that the symbol map is written only if
verifyChangesV2 passes (apply the same change to the other similar block
referencing chainassets2).

In `@defi/l2/v2/storeToDb.ts`:
- Around line 44-54: The DB connection opened by iniDbConnection() around the
queryPostgresWithRetry call must be closed in a finally block to avoid leaking
when the query throws; wrap the query and any subsequent use of the sql client
(including the allData query in fetchHistoricalFromDBDirect()) in try { ... }
finally { sql.end() } similar to the pattern in verifyChangesV2(), ensuring
sql.end() is always called even on error.
- Around line 194-197: The breakdown assignment currently overwrites values when
multiple asset keys map to the same symbol; update the loop that iterates
Object.keys(d[chain][section].breakdown) to aggregate (e.g., sum) values into
symbolEntry.data[section].breakdown[symbolMap[asset]] instead of assigning,
handling missing keys by initializing to zero before adding the incoming amount
from d[chain][section].breakdown[asset]; apply the identical aggregation fix to
the analogous block around the other occurrence (lines referenced in the
comment) so both places use summation rather than direct assignment.

---

Outside diff comments:
In `@defi/l2/v2/index.ts`:
- Around line 432-490: The symbol-fallback branch currently uses the original
unresolved key (e.g., "coingecko:axlusdc") when writing into
nativeDataAfterDeductions, which causes those amounts to be lost; update the
destination handling so that when you resolve coinData via symbolToPrice (the
resolved variable), compute and use the resolved price key (e.g., the normalized
key for the resolved CoinGecko id) instead of the original key, keep the
isWholeTokenAmount logic for divisor calculation, and write
nativeDataAfterDeductions[chain][resolvedKey] = usdAmount (or
nativeDataAfterDeductions[chain][key] = usdAmount only when no resolution
happened); refer to symbolToPrice, key, resolved, coinData, isWholeTokenAmount,
destinationChainAmount and nativeDataAfterDeductions to locate and change the
assignment.

---

Duplicate comments:
In `@defi/l2/v2/storeToDb.ts`:
- Around line 47-50: The SQL daily bucketing uses date_trunc('day',
to_timestamp(timestamp)) which depends on the session timezone; update each
occurrence in storeToDb.ts (the three daily snapshot queries around the select
distinct block and the two other blocks referenced) to explicitly bucket in UTC
by adding AT TIME ZONE 'UTC' inside the date_trunc call and in corresponding
ORDER BY expressions (e.g. use date_trunc('day', to_timestamp(timestamp) AT TIME
ZONE 'UTC') and order by that same expression and timestamp desc) so all three
daily queries consistently use UTC-based day buckets.
- Line 39: The DB write flow currently awaits precomputeHistoricalCache() so a
cache rebuild error can cause the publish to fail after persistence; change the
publish flow in storeToDb to treat precomputeHistoricalCache() as best-effort:
call it but catch and log any errors (do not rethrow or make the DB insert
promise fail). Additionally, harden the cache read logic (the code that
currently accepts "any non-empty cached file") to validate cache
freshness/version (e.g., compare a timestamp, version, or consistency marker
produced at write time) before serving it so stale cache files are rejected;
apply the same catch-and-log pattern and freshness check for the other
occurrences noted around the block at lines ~95-99.
🪄 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

Run ID: 9ca3362d-24e7-4f64-a3a8-1d9670c6371e

📥 Commits

Reviewing files that changed from the base of the PR and between 76c639d and ed2446f.

📒 Files selected for processing (4)
  • defi/l2/utils.ts
  • defi/l2/v2/index.ts
  • defi/l2/v2/storeToDb.ts
  • defi/l2/v2/verifyChanges.ts

Comment thread defi/l2/utils.ts
Comment thread defi/l2/utils.ts
Comment thread defi/l2/v2/index.ts
Comment thread defi/l2/v2/index.ts Outdated
Comment thread defi/l2/v2/storeToDb.ts Outdated
Comment thread defi/l2/v2/storeToDb.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: 2

🤖 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 `@defi/l2/v2/index.ts`:
- Around line 438-452: The symbolToPrice reverse map is non-deterministic and
only used in the destination branch, so shared symbols can be lost; make the
resolver deterministic and use it for both protocol- and destination-token
lookups by: build symbolToPrice deterministically (e.g., sort
Object.keys(allPrices) or apply a stable preference rule before iterating)
instead of taking the first hit, expose that lookup as
resolveBridgeToken(keyOrSymbol) (returning { key, data } or null) and replace
direct allPrices[key] and the destination-only symbolToPrice usage with calls to
resolveBridgeToken() in both the protocolAmounts loop and the destination loop
so all symbol-keyed tokens resolve consistently.
- Around line 483-504: The code overwrites
nativeDataAfterDeductions[chain][outputKey] when multiple input tokens resolve
to the same outputKey; change the assignment to accumulate by adding usdAmount
to any existing value. In the block that computes divisor and usdAmount (using
coinData, isWholeTokenAmount, BigNumber and destinationChainAmount[token]),
check nativeDataAfterDeductions[chain][outputKey] for an existing BigNumber and
set nativeDataAfterDeductions[chain][outputKey] = (existing ?
existing.plus(usdAmount) : usdAmount), ensuring allPrices/symbolToPrice
re-keying merges sums rather than replacing prior amounts.
🪄 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

Run ID: 67e8d591-ad74-4df3-b15f-5b5834c71ab4

📥 Commits

Reviewing files that changed from the base of the PR and between ed2446f and 7a140e4.

📒 Files selected for processing (4)
  • defi/l2/utils.ts
  • defi/l2/v2/file-cache.ts
  • defi/l2/v2/index.ts
  • defi/l2/v2/storeToDb.ts
✅ Files skipped from review due to trivial changes (1)
  • defi/l2/v2/file-cache.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • defi/l2/utils.ts
  • defi/l2/v2/storeToDb.ts

Comment thread defi/l2/v2/index.ts
Comment on lines +438 to +452
// Some canonical-bridge adapters emit tokens keyed by uppercase symbol
// ("AXLUSDC", "ATOM"). normalizeKey turns those into "coingecko:axlusdc", which
// almost never matches a real coingecko id. Build a reverse symbol → pricing
// index from allPrices so those can still be resolved in the destination loop.
// We also keep the resolved key alongside the data so the destination loop
// can re-key the amount under a key that actually exists in allMcaps; storing
// under the unresolved "coingecko:axlusdc" would cause it to be dropped by
// the mcap stage downstream.
const symbolToPrice: { [symbol: string]: { key: string; data: CoinsApiData } } = {};
Object.entries(allPrices).forEach(([priceKey, p]) => {
const sym = p?.symbol?.toUpperCase();
if (!sym) return;
// Prefer the first resolution; symbol collisions are ambiguous by construction.
if (!symbolToPrice[sym]) symbolToPrice[sym] = { key: priceKey, data: p };
});

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make the reverse symbol resolver deterministic and use it for protocol balances too.

This map currently keeps the first symbol match from allPrices, but allPrices is assembled from concurrent chain fetches, so the “winner” for shared symbols like USDC/ETH is order-dependent. Also, this resolver is only used in the destination-chain branch, so symbol-keyed protocolAmounts still get dropped later on the plain allPrices[key] lookup. Please only keep unique/deterministically-preferred symbol mappings and route both destination and protocol flows through the same resolver.

Suggested direction
- const symbolToPrice: { [symbol: string]: { key: string; data: CoinsApiData } } = {};
+ const symbolToPrice: { [symbol: string]: { key: string; data: CoinsApiData } | null } = {};
  Object.entries(allPrices).forEach(([priceKey, p]) => {
    const sym = p?.symbol?.toUpperCase();
    if (!sym) return;
-   if (!symbolToPrice[sym]) symbolToPrice[sym] = { key: priceKey, data: p };
+   if (!symbolToPrice[sym]) {
+     symbolToPrice[sym] = { key: priceKey, data: p };
+     return;
+   }
+   if (symbolToPrice[sym]?.key !== priceKey) symbolToPrice[sym] = null;
  });
+
+ function resolveBridgeToken(key: string) {
+   const direct = allPrices[key];
+   if (direct?.price) return { coinData: direct, outputKey: key, isWholeTokenAmount: false };
+   if (!key.startsWith("coingecko:")) return null;
+   const symbol = key.slice("coingecko:".length).toUpperCase();
+   const resolved = symbolToPrice[symbol];
+   if (!resolved) return null;
+   return { coinData: resolved.data, outputKey: resolved.key, isWholeTokenAmount: true };
+ }

Then reuse resolveBridgeToken() in both the protocol and destination loops.

🤖 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 `@defi/l2/v2/index.ts` around lines 438 - 452, The symbolToPrice reverse map is
non-deterministic and only used in the destination branch, so shared symbols can
be lost; make the resolver deterministic and use it for both protocol- and
destination-token lookups by: build symbolToPrice deterministically (e.g., sort
Object.keys(allPrices) or apply a stable preference rule before iterating)
instead of taking the first hit, expose that lookup as
resolveBridgeToken(keyOrSymbol) (returning { key, data } or null) and replace
direct allPrices[key] and the destination-only symbolToPrice usage with calls to
resolveBridgeToken() in both the protocolAmounts loop and the destination loop
so all symbol-keyed tokens resolve consistently.

Comment thread defi/l2/v2/index.ts
Comment on lines +483 to +504
let coinData = allPrices[key];
let outputKey = key;
// When the adapter was symbol-keyed, `key` looks like "coingecko:axlusdc" but
// there is no such coingecko id. Fall back to a symbol reverse lookup.
// Symbol-keyed adapter output is also typically in whole tokens, not base
// units, so we must skip the `/ 10^decimals` step for these.
let isWholeTokenAmount = false;
if ((!coinData || !coinData.price) && key.startsWith("coingecko:")) {
const symbol = key.slice("coingecko:".length).toUpperCase();
const resolved = symbolToPrice[symbol];
if (resolved?.data?.price) {
coinData = resolved.data;
// Re-key the amount under the resolved key so allMcaps lookups
// downstream actually find an entry.
outputKey = resolved.key;
isWholeTokenAmount = true;
}
}
if (!coinData || !coinData.price) return;
const usdAmount = destinationChainAmount[token].times(coinData.price).div(BigNumber(10).pow(coinData.decimals));
nativeDataAfterDeductions[chain][key] = usdAmount;
const divisor = isWholeTokenAmount ? BigNumber(1) : BigNumber(10).pow(coinData.decimals);
const usdAmount = destinationChainAmount[token].times(coinData.price).div(divisor);
nativeDataAfterDeductions[chain][outputKey] = usdAmount;

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Accumulate when multiple inputs resolve to the same outputKey.

After the new re-keying step, distinct adapter tokens can collapse onto one canonical key. Line 504 overwrites any earlier USD amount instead of adding to it, so part of the bridge TVL disappears whenever two entries resolve to the same asset.

Suggested fix
-        nativeDataAfterDeductions[chain][outputKey] = usdAmount;
+        nativeDataAfterDeductions[chain][outputKey] =
+          (nativeDataAfterDeductions[chain][outputKey] ?? zero).plus(usdAmount);
🤖 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 `@defi/l2/v2/index.ts` around lines 483 - 504, The code overwrites
nativeDataAfterDeductions[chain][outputKey] when multiple input tokens resolve
to the same outputKey; change the assignment to accumulate by adding usdAmount
to any existing value. In the block that computes divisor and usdAmount (using
coinData, isWholeTokenAmount, BigNumber and destinationChainAmount[token]),
check nativeDataAfterDeductions[chain][outputKey] for an existing BigNumber and
set nativeDataAfterDeductions[chain][outputKey] = (existing ?
existing.plus(usdAmount) : usdAmount), ensuring allPrices/symbolToPrice
re-keying merges sums rather than replacing prior amounts.

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