Chain assets 2#11394
Conversation
📝 WalkthroughWalkthroughAdded 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 ChangesHistorical Data Storage & Caching
Supply Function Updates
TVL Aggregation, Pricing, Verification & Dry-run
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
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~65 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 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: 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
📒 Files selected for processing (3)
defi/l2/v2/storeToDb.tsdefi/src/getChainAssets.tsdefi/src/getChainAssetsChart.ts
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
defi/l2/v2/file-cache.ts (1)
27-35: Consider atomic writes to avoid serving partial JSON.
writeFileis not atomic: a crash or concurrent overlap can leave a truncated file on disk. Readers would recover via thecatchinreadData(returningnull), so it's not data-corrupting, but a cached day could silently disappear until the next refresh. Writing to a temp path andrename-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
… 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>
There was a problem hiding this comment.
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 | 🟠 MajorStore symbol-fallback amounts under the resolved price key.
The fallback resolves
coinDataby symbol, but Line 490 still stores the amount under the unresolved key likecoingecko:axlusdc. That key usually has noallMcapsentry and noallPricesentry, 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 | 🟠 MajorBucket daily snapshots in UTC.
These
date_trunc('day', to_timestamp(timestamp))expressions use the Postgres session timezone. Sincetimestampis 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 | 🟠 MajorKeep 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
📒 Files selected for processing (4)
defi/l2/utils.tsdefi/l2/v2/index.tsdefi/l2/v2/storeToDb.tsdefi/l2/v2/verifyChanges.ts
There was a problem hiding this comment.
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
📒 Files selected for processing (4)
defi/l2/utils.tsdefi/l2/v2/file-cache.tsdefi/l2/v2/index.tsdefi/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
| // 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 }; | ||
| }); |
There was a problem hiding this comment.
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.
| 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; |
There was a problem hiding this comment.
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.
Summary by CodeRabbit
New Features
Bug Fixes
Chores