Skip to content

Better balance handling#15

Merged
feruzm merged 2 commits intomainfrom
balance
Jan 12, 2026
Merged

Better balance handling#15
feruzm merged 2 commits intomainfrom
balance

Conversation

@feruzm
Copy link
Copy Markdown
Member

@feruzm feruzm commented Jan 12, 2026

Summary by CodeRabbit

  • Bug Fixes
    • Improved balance fetching with enhanced error handling and automatic fallback providers for BTC transactions
    • Better wallet processing with concurrency control to prevent overload and improve responsiveness
    • Updated error propagation to return appropriate status codes and error payloads

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 12, 2026

📝 Walkthrough

Walkthrough

Refactoring extracts balance-fetching logic into a reusable fetchChainBalance function with centralized error handling and per-chain provider routing, including BTC fallback sequences across Chainstack, Blockstream, and Chainz. The balance endpoint delegates to this new function. Wallet API replaces parallel HTTP requests with direct fetchChainBalance calls, constrained by concurrency limits (3 concurrent).

Changes

Cohort / File(s) Summary
Core balance logic extraction
src/server/handlers/private-api.ts
Extracted new fetchChainBalance() function encapsulating balance retrieval, validation, provider selection, and error handling. Updated balance endpoint to delegate to this function and construct HTTP responses (headers, status, body) from returned metadata. Added per-chain decision flow with BTC-specific fallback sequences (Chainstack → Blockstream → Chainz). Removed direct response manipulation from internal logic; error propagation now centralized.
Wallet API integration
src/server/handlers/wallet-api.ts
Replaced Promise.all parallel wallet processing with concurrency-controlled helper limiting concurrent balance requests to 3. Replaced proxy HTTP calls to balance API with direct fetchChainBalance calls. Retained per-wallet error handling with fallback to zero-balance items on failure.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 A rabbit's ode to cleaner code:

Balance logic bundles tight,
Chainstack, Blockstream, Chainz in flight—
Fallbacks flow when errors bite,
Concurrency gates keep load just right,
Refactored paths shine clean and bright! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Better balance handling' is vague and generic, using a non-descriptive term that doesn't convey specific information about the changeset. Use a more specific title that describes the main change, such as 'Refactor balance endpoint to use core fetchChainBalance function' or 'Add concurrency control and direct balance fetching to reduce API overhead'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing touches
  • 📝 Generate docstrings

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
src/server/handlers/private-api.ts (1)

1777-1946: Validation errors from fetchChainBalance() are returned as 502 by balance() (should be 400).
fetchChainBalance() throws for invalid chain/unsupported chain/invalid address, but balance() maps non-Axios errors to 502, so client input errors become “Bad Gateway”.

Proposed fix (minimal, message-based 400 mapping)
 export const balance = async (req: express.Request, res: express.Response) => {
   const { chain, address } = req.params;

   if (!chain || !address) {
     res.status(400).send("Missing chain or address");
     return;
   }

   const provider = parseBalanceProvider(req.query.provider);

   // Slightly extend server timeouts for BTC path
   const normalizedChain = chain.toLowerCase();
   if (normalizedChain === "btc") {
     const extendedTimeout = BITCOIN_RPC_TIMEOUT_MS + 30_000;
     if (typeof req.setTimeout === "function") req.setTimeout(extendedTimeout);
     if (typeof res.setTimeout === "function") res.setTimeout(extendedTimeout);
   }

   try {
     const balanceResponse = await fetchChainBalance(chain, address, provider);

     // Set response headers
     res.setHeader("x-provider", balanceResponse.provider);
     if (
       balanceResponse.provider === "blockstream" &&
       balanceResponse.rateLimitRemaining !== undefined
     ) {
       res.setHeader(
         "x-blockstream-ratelimit-remaining",
         String(balanceResponse.rateLimitRemaining),
       );
     }

     res.status(200).json(balanceResponse);
   } catch (err) {
     console.error("balance(): error while fetching chain balance", err);

     if (axios.isAxiosError(err) && err.response) {
       const { status, data } = err.response;
       if (data !== undefined) {
         if (data !== null && typeof data === "object") {
           res.status(status).json(data);
         } else {
           res.status(status).json({ error: String(data) });
         }
       } else {
         res.sendStatus(status);
       }
       return;
     }

+    // Client input errors thrown by fetchChainBalance()
+    const msg = err instanceof Error ? err.message : "Unknown error";
+    if (
+      msg === "Invalid chain parameter" ||
+      msg === "Unsupported chain" ||
+      msg === "Invalid address format" ||
+      msg === "Missing chain or address"
+    ) {
+      res.status(400).json({ error: msg });
+      return;
+    }
+
     // Final catch-all
-    const msg = err instanceof Error ? err.message : "Unknown error";
     res.status(502).json({ error: msg });
   }
 };
🤖 Fix all issues with AI agents
In @src/server/handlers/private-api.ts:
- Around line 1777-1781: The fetchChainBalance function signature references the
BalanceProvider type but it’s not imported; update the import list that brings
in helpers (where other types are imported) to also import BalanceProvider
(ensuring BalanceProvider is exported from the ../helper module), so the
function declaration export const fetchChainBalance(... provider:
BalanceProvider ...) compiles correctly. Ensure any renamed or moved type in
../helper is referenced correctly (BalanceProvider) and run the TS build to
verify no other missing type imports remain.

In @src/server/handlers/private-api.ts.backup:
- Around line 1-2896: The file src/server/handlers/private-api.ts.backup is an
accidental backup duplicate and must be removed from the PR; delete the file
from the repository (git rm --cached or git rm if not needed locally), commit
the deletion, and push the change so it’s not included in the branch, and search
the tree for any remaining “.backup” copies or references (e.g.,
private-api.ts.backup) to ensure no stray duplicates remain; also run the
test/build to confirm no code imports the .backup file before finalizing the
commit.
🧹 Nitpick comments (1)
src/server/handlers/wallet-api.ts (1)

1647-1674: processWithConcurrencyLimit() should guard concurrencyLimit and could live in a shared util.
Current call-site uses 3, but <= 0 would break (Promise.race on an empty set / always-true condition).

Proposed fix
 const processWithConcurrencyLimit = async <T, R>(
   items: T[],
   processor: (item: T) => Promise<R>,
   concurrencyLimit: number
 ): Promise<R[]> => {
+  if (!Number.isInteger(concurrencyLimit) || concurrencyLimit <= 0) {
+    throw new Error("concurrencyLimit must be a positive integer");
+  }
   const results: R[] = [];
   const executing: Set<Promise<void>> = new Set();
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cc9ad6c and c8b2970.

📒 Files selected for processing (3)
  • src/server/handlers/private-api.ts
  • src/server/handlers/private-api.ts.backup
  • src/server/handlers/wallet-api.ts
🧰 Additional context used
🧬 Code graph analysis (2)
src/server/handlers/wallet-api.ts (2)
src/server/handlers/private-api.ts (2)
  • fetchChainBalance (1777-1890)
  • balance (1892-1947)
src/server/handlers/constants.ts (1)
  • ASSET_ICON_URLS (72-87)
src/server/handlers/private-api.ts (2)
src/server/helper.ts (5)
  • BalanceProvider (9-9)
  • ChainBalanceResponse (11-19)
  • fetchBlockstreamBalance (238-294)
  • fetchChainzBalance (296-352)
  • parseBalanceProvider (145-153)
src/server/handlers/fallback.tsx (1)
  • req (5-8)
🔇 Additional comments (3)
src/server/handlers/private-api.ts (1)

1777-1890: fetchChainBalance() extraction + BTC fallback flow looks solid.
Nice separation of pure “fetch balance” logic from HTTP response handling; BTC fallback ordering is clear and bounded.

src/server/handlers/wallet-api.ts (2)

6-7: Direct fetchChainBalance() usage is a good change (removes proxy overhead).
This should reduce double-timeouts / proxy 502s compared to bouncing through the HTTP balance endpoint.


1688-1738: Bounded concurrency + per-wallet fallback behavior looks correct.
Catching per-wallet failures and returning a “0 balance + error message” item keeps the portfolio responsive.

Comment thread src/server/handlers/private-api.ts
Comment thread src/server/handlers/private-api.ts.backup Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/server/handlers/private-api.ts (1)

1798-1805: Consider clarifying provider fallback behavior to callers.

When a caller specifies provider=chainz for a non-BTC chain, the warning is only logged server-side. The caller receives a successful response from Chainstack without knowing their provider hint was ignored. This is acceptable since the response includes provider: "chainstack" in the payload, but consider whether throwing an error would be more explicit for misconfigured clients.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c8b2970 and bcf82e4.

📒 Files selected for processing (1)
  • src/server/handlers/private-api.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/server/handlers/private-api.ts (2)
src/server/helper.ts (4)
  • BalanceProvider (9-9)
  • ChainBalanceResponse (11-19)
  • fetchBlockstreamBalance (238-294)
  • fetchChainzBalance (296-352)
src/server/handlers/fallback.tsx (1)
  • req (5-8)
🔇 Additional comments (9)
src/server/handlers/private-api.ts (9)

1774-1791: LGTM - Clean extraction of balance-fetching logic into reusable function.

The function signature and initial validation are well-designed. Throwing errors instead of manipulating HTTP responses makes this function reusable from both HTTP endpoints and internal callers (like the wallet API).


1807-1825: Fallback helper design is appropriate.

The null-returning pattern allows clean sequential fallback attempts. Logging errors server-side provides observability into which providers failed.


1827-1830: Address validation logic is correct.

The optional validation pattern is appropriate since not all chains have client-side address validators defined.


1832-1839: Direct provider routing for explicit BTC requests is correct.

Errors from these explicit provider calls will propagate appropriately to the caller's error handler.


1841-1870: Node discovery with BTC fallback handling is well-implemented.

The fallback sequence (Blockstream → Chainz) is consistently applied both when node discovery fails and when no suitable node is found.


1872-1891: Balance fetch with BTC fallback logic is correct.

The error logging before fallback attempts provides good observability for debugging provider issues.


1893-1909: Clean endpoint implementation with appropriate timeout handling.

The defensive check for setTimeout availability is good practice, and extending the timeout for BTC requests accounts for the potentially slow scantxoutset RPC calls.


1911-1926: Response handling is correct.

Headers are set appropriately based on the provider used, and the rate limit header for Blockstream provides useful client-side observability.


1927-1948: Error handling is comprehensive and appropriate.

The logic correctly distinguishes between Axios errors (preserving upstream status/data) and other errors (returning 502 Bad Gateway). This provides good error transparency to clients.

@feruzm feruzm merged commit a4baf2f into main Jan 12, 2026
1 check passed
@feruzm feruzm deleted the balance branch January 12, 2026 11:24
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