Verbose logging for exchange rate queries#6083
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 815a8bb. Configure here.
There was a problem hiding this comment.
Warning
- Copilot's review of this pull request may be incomplete because some of the changed files are excluded by your Copilot content exclusion settings. See Excluding content from Copilot for details.
Pull request overview
This PR improves exchange-rate troubleshooting by adding verbose-gated, replayable logging around v3/rates bulk queries, including an exchange-rate cache snapshot in support logs, and introducing a script to convert captured cache data back into rate-server query bodies for replay.
Changes:
- Added verbose logging in the bulk exchange-rate fetch loop (request body logging, resolved vs no-rate summary, HTTP non-OK + thrown error logging).
- Included an in-memory exchange-rate cache snapshot in the support “info” log payload.
- Added
scripts/ratesCacheToQueries.tsto transform a logged cache blob (or excerpt) into replayablev3/ratesJSON bodies orcurlcommands, and updated the changelog accordingly.
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/actions/ExchangeRateActions.ts | Adds verbose-gated logging around rate queries and exports an in-memory cache dump for support logging. |
| src/actions/LogActions.tsx | Appends an exchange-rate cache snapshot to the support log payload. |
| scripts/ratesCacheToQueries.ts | New developer/support script to convert logged cache blobs into replayable rate query bodies / curl commands. |
| CHANGELOG.md | Documents the new verbose rate-query logging, cache snapshot, and replay script. |
| package.json | Updated to support the new replay script (content excluded by policy). |
Files excluded by content exclusion policy (1)
- package.json
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
815a8bb to
4329aa0
Compare
There was a problem hiding this comment.
Warning
- Copilot's review of this pull request may be incomplete because some of the changed files are excluded by your Copilot content exclusion settings. See Excluding content from Copilot for details.
Pull request overview
Copilot reviewed 4 out of 5 changed files in this pull request and generated 1 comment.
Files excluded by content exclusion policy (1)
- package.json
There was a problem hiding this comment.
Warning
- Copilot's review of this pull request may be incomplete because some of the changed files are excluded by your Copilot content exclusion settings. See Excluding content from Copilot for details.
Pull request overview
Copilot reviewed 5 out of 6 changed files in this pull request and generated 1 comment.
Files excluded by content exclusion policy (1)
- package.json
Comments suppressed due to low confidence (1)
scripts/ratesCacheReplay.ts:226
- The
--serverflag accepts an option value, but if the user passes--serverwithout a URL (or as the last argument),server = argv[++i]will setservertoundefinedand the script will later build requests toundefined/v3/rates, which is a confusing failure mode. Add a simple validation/error for missing--servervalues.
const arg = argv[i]
if (arg === '--dry-run') dryRun = true
else if (arg === '--server') server = argv[++i]
else if (!arg.startsWith('--')) file = arg
When the Verbose Logging setting is on, the bulk rates loop logs the exact request body for each query (a condensed JSON string that can be replayed verbatim against the rates server), a resolved/no-rate outcome summary, non-OK HTTP responses, and thrown errors. Non-OK responses were previously dropped with no output at all.
The info log payload sent to the logs server now carries a snapshot of the in-memory exchange-rate cache, including the subscribed pair lists and per-rate expirations, so support can distinguish pairs that were never requested from pairs that failed to resolve.
ratesCacheToQueries.ts takes the exchange-rate cache blob captured in the support logs and converts its subscribed pair lists into the v3/rates query bodies the app would send, mirroring convertToRatesParams. Emits JSON bodies or ready-to-run curl commands so a rate issue seen in a user's logs can be reproduced directly against the rates server.
The pair cache matched entries with a linear scan comparing tokenId with ===. Pairs written without a tokenId key read back as undefined, and undefined === null is false, so every refresh appended a second pair for each chain's own asset. A real cache had 202 pairs where only 137 were distinct, inflating each rate query and pushing it over the 100-pair chunk size into an extra request. Build the pair lists from keyed maps instead, using the same key loadExchangeRateCache de-duplicates with, so a missing tokenId and an explicit null collapse to one pair. Existing duplicates are merged away on the next refresh.
9338475 to
f80cf3b
Compare
There was a problem hiding this comment.
Warning
- Copilot's review of this pull request may be incomplete because some of the changed files are excluded by your Copilot content exclusion settings. See Excluding content from Copilot for details.
Pull request overview
Copilot reviewed 5 out of 6 changed files in this pull request and generated no new comments.
Files excluded by content exclusion policy (1)
- package.json
Comments suppressed due to low confidence (1)
scripts/ratesCacheReplay.ts:227
--serverconsumes the next argv entry without validating it exists. If a user runs--serveras the last arg (or before another flag),serverbecomesundefinedand the script will attempt requests toundefined/v3/rates, which is hard to diagnose. Add a guard that throws a clear error when--serveris missing a value.
for (let i = 0; i < argv.length; i++) {
const arg = argv[i]
if (arg === '--dry-run') dryRun = true
else if (arg === '--server') server = argv[++i]
else if (!arg.startsWith('--')) file = arg
}

CHANGELOG
Does this branch warrant an entry to the CHANGELOG?
Dependencies
none
Requirements
If you have made any visual changes to the GUI. Make sure you have:
No visual changes — this PR only adds logging, a support/dev script, and a rate-cache fix.
Description
Asana: https://app.asana.com/1/9976422036640/project/1213843652804305/task/1216799838261197
Adds verbose-gated logging to the bulk exchange-rate query loop, plus a way to re-run a user's rate queries against the rates server — to diagnose cases where the app can reach the rates server but still shows no rates (e.g. a wallet balance rendering
$0.00 USDwhile the price chart, which comes from a different host, works).Query logging (
ExchangeRateActions.ts) — gated by the Settings → Verbose Logging toggle and written throughutil/logger, so it lands in the capturedlogs_info.*bundle sent via Send Logs:v3/ratesrequest is logged as a condensed JSON string — the exact body that is POSTed, so it can be replayed verbatim.no-rate, naming the pairs the server returned without a rate.fetchWaterfallonly fails over on thrown errors — an HTTP error response was treated as success and the whole chunk silently discarded.logasErrorvalues so their stack (and therefore the failing call site) is preserved.When the setting is off there is no new output and no string-building work.
Cache snapshot (
LogActions.tsx): the info log payload now includes a snapshot of the in-memory exchange-rate cache — rates plus the subscribedcryptoPairs/fiatPairslists and their expirations — so support can tell pairs that were never requested from pairs that failed to resolve.Replay script (
scripts/ratesCacheReplay.ts,npm run rates-cache-replay): rebuilds thev3/ratesqueries the app would have sent for the pairs in a logged cache blob, sends them, and reports every requested pair as one of:<rate>— the server returned a rateNO RATE— the server returned the pair but declined to price itMISSING— the server omitted the pair from its response entirelyQuery construction mirrors
convertToRatesParams. Accepts the bare cache JSON or a pasted log excerpt.--serverselects the rates host (default rates3; point it at rates4 to compare hosts) and--dry-runprints the query bodies without sending them.Duplicate rate pairs (
ExchangeRateActions.ts): the pair cache matched entries with a linear scan comparingtokenIdwith===. Pairs written without atokenIdkey read back asundefined, andundefined === nullis false, so every refresh appended a second pair for each chain's own asset. A real cache had 202 pairs where only 137 were distinct — inflating each rate query and pushing it over the 100-pair chunk size into an extra request every refresh. The pair lists are now built from keyed maps using the same keyloadExchangeRateCachede-duplicates with, so a missingtokenIdand an explicitnullcollapse to one pair; existing duplicates are merged away on the next refresh.Testing:
tsc --noEmit, eslint, and the full jest suite (231 tests / 103 snapshots) pass. NewmergePairCacheunit tests cover the de-duplication, including the two cases that fail against the previous implementation. The replay script was exercised against a real cache blob — 202 cached pairs, 3 queries, all pairs resolved against rates3.Runtime was not exercised in-app: develop currently hits an unrelated
react-native-workletsinstallTurboModuleinit crash under Xcode 26 / iOS 26.5 that blocks launching the simulator build. That crash reproduces on a clean develop and is orthogonal to this JS-only change.