Skip to content

handle failed best_orders RPC exception during login#3263

Merged
ca333 merged 13 commits intodevfrom
tweak-prelogin-orderbook
Nov 4, 2025
Merged

handle failed best_orders RPC exception during login#3263
ca333 merged 13 commits intodevfrom
tweak-prelogin-orderbook

Conversation

@smk762
Copy link
Copy Markdown
Collaborator

@smk762 smk762 commented Oct 27, 2025

Closes #3261

This issue was limited to a brief period post login, where the rpc would return an unhandled error, leading to delays between login and app being usable.

This PR:

  • Adds backoff/retry for best_orders
  • avoids "available balance" checks before login
  • allows orderbook viewing prior to login
  • skips background bestorder calls unless user is on the bridge or the swap pages.

To test:

  • Before login, go to swap page
  • confirm available balance is 0 (no spinner)
  • Select a pair of coins
  • Confirm the orderbook populates
  • Login
  • Confirm no errors, such as shown in linked issue, are visible in logs
  • Go to swap page
  • Confirm available balance is determined and shown (spinner then value)
  • Select a pair of coins
  • Confirm the orderbook populates

Repeat for bridge page.

Summary by CodeRabbit

  • Improvements
    • Non-logged-in users now see a static unavailable balance indicator instead of loading spinners for more consistent UX
    • Enhanced API reliability with automatic retry logic for order retrieval
    • Improved app performance by avoiding unnecessary API requests when not in DEX/Bridge context

Note

Retries and hardens best_orders, limits background calls to trading pages, defers balance polling until after login, and updates UI/messages to show clear no-liquidity CTAs.

  • DEX/Bridge reliability:
    • Mm2Api.getBestOrders: add retry with linear backoff; return error text on exception.
    • DexRepository.getBestOrders: only fetch on dex/bridge pages; map "no response from any peer" to empty results; add logging; optional simulated failures via kSimulateBestOrdersFailure.
  • Pre-login behavior:
    • TakerBloc, BridgeBloc, MakerFormBloc: show AvailableBalanceState.unavailable (0.00) before login; skip spinners and balance polling; reinitialize balances immediately upon login or page entry.
  • Orderbook/UX:
    • DEX orders table: new empty-state CTA _NoOrdersCta with Maker switch; improved error message with retry.
    • Bridge empty-state: replace generic "nothing found" with cross-network routes message plus Maker CTA.
  • i18n & config:
    • Add dexNoSwapOffers, bridgeNoCrossNetworkRoutes to en.json and codegen_loader.
    • App config: add debug toggles to simulate best_orders failures.

Written by Cursor Bugbot for commit f52a907. This will update automatically on new commits. Configure here.

@smk762 smk762 changed the base branch from main to dev October 27, 2025 10:53
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Oct 27, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

The PR addresses a race condition during login by adding authentication state guards across multiple bloc and API layers. Changes prevent unauthenticated users from showing loading spinners during balance/max-sell amount updates, implement early unavailable states, short-circuit unnecessary API calls, and add retry logic to best_orders requests.

Changes

Cohort / File(s) Summary
Not-logged-in UI state handling
lib/bloc/bridge_form/bridge_bloc.dart, lib/bloc/taker_form/taker_bloc.dart, lib/blocs/maker_form_bloc.dart
Added authentication guards in balance and max-sell amount update flows to emit unavailable state and clear values for non-authenticated users instead of showing loading spinners. Prevents spinner that never resolves before user login.
Best orders API safeguards
lib/bloc/dex_repository.dart
Added menu context short-circuit to skip API call if not in dex or bridge menu. Enhanced error handling to log errors and return empty results instead of propagating exceptions.
MM2 API resilience
lib/mm2/mm2_api/mm2_api.dart
Wrapped getBestOrders call in retry logic with up to 4 attempts and LinearBackoff strategy (500ms initial, 250ms increment, 3s max). Filters null responses and error-field responses to trigger retries.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as UI Layer
    participant Bloc as Bloc (bridge/taker/maker)
    participant DexRepo as Dex Repository
    participant MM2API as MM2 API

    Note over User,MM2API: Before Login
    User->>UI: Opens bridge/swap form
    UI->>Bloc: Request balance/max-sell
    Bloc->>Bloc: Check auth status (not logged in)
    Bloc-->>UI: Emit unavailable state (no spinner)
    
    Note over User,MM2API: Login occurs
    User->>Bloc: Login completes
    
    Note over User,MM2API: After Login
    User->>UI: Updates sell coin/amount
    UI->>Bloc: _onUpdateMaxSellAmount
    Bloc->>Bloc: Check auth status (logged in)
    Bloc->>Bloc: Emit loading state
    Bloc->>DexRepo: getBestOrders request
    DexRepo->>DexRepo: Check menu context
    DexRepo->>MM2API: Call with retry wrapper
    loop Retry up to 4 times
        MM2API->>MM2API: Attempt best_orders call
        alt Success
            MM2API-->>DexRepo: Return valid result
        else Transient error
            MM2API->>MM2API: Backoff & retry
        end
    end
    DexRepo-->>Bloc: Return BestOrders
    Bloc-->>UI: Emit balance/max-sell state
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Areas requiring attention:
    • Logic flow in mm2_api.dart retry wrapper—verify LinearBackoff calculation and rejection conditions align with intent
    • Consistency of authentication guards across bridge_bloc, taker_bloc, and maker_form_bloc—ensure all paths handle not-logged-in state identically
    • Menu context check in dex_repository.dart—confirm short-circuit doesn't suppress legitimate calls
    • Integration between dex_repository empty-result handling and upstream error handling expectations

Possibly related PRs

Suggested labels

bug, QA

Suggested reviewers

  • gcharang
  • smk762

Poem

🐰 A race condition caught mid-stride,
Before login, spinners hide,
Retries bloom with backoff grace,
Best orders find their proper place,
No more ghosts in the UI's race!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues Check ✅ Passed The code changes directly address all key requirements from issue #3261. The retry logic added to mm2_api.dart handles best_orders errors gracefully with a LinearBackoff strategy, resolving the RPC error handling concern. The menu-based short-circuit in dex_repository.dart defers best_orders calls until the user is on the appropriate page, addressing the race condition aspect. Multiple bloc files (bridge_bloc.dart, taker_bloc.dart, maker_form_bloc.dart) now prevent balance loading spinners before login by checking authentication status and displaying unavailable state instead, resolving the UI stall issue. These changes collectively enable orderbook viewing before login while avoiding premature balance and best_orders requests.
Title Check ✅ Passed The title "handle failed best_orders RPC exception during login" is directly related to changes present in the pull request, specifically the retry and backoff logic added to mm2_api.dart's getBestOrders method that addresses RPC exception handling. The changes across the bloc files (bridge_form, taker_form, and maker_form) that handle pre-login states support this error-handling goal by preventing unnecessary API calls and avoiding loading states before authentication. While the title focuses on the RPC exception aspect rather than capturing the broader pre-login orderbook access theme mentioned in the branch name and PR objectives, it remains a real and significant part of the changeset and clearly summarizes one of the main improvements.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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.

@CharlVS
Copy link
Copy Markdown
Collaborator

CharlVS commented Oct 27, 2025

@coderabbitai review

@cursor review

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR fixes a race condition that occurred briefly after login where best_orders RPC calls would fail with unhandled errors, causing delays before the app became usable. The changes implement retry logic for best_orders requests and improve the user experience by allowing orderbook viewing before login while showing appropriate balance states.

Key changes:

  • Added exponential backoff retry mechanism for best_orders API calls
  • Modified balance state handling to show "0.00" instead of loading spinner when not logged in
  • Restricted background best_orders calls to only run when user is on swap or bridge pages

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
lib/mm2/mm2_api/mm2_api.dart Added retry logic with linear backoff for getBestOrders API calls
lib/blocs/maker_form_bloc.dart Updated balance state logic to check login status before showing loading state
lib/bloc/taker_form/taker_bloc.dart Added login status checks to show unavailable balance state instead of loading spinner
lib/bloc/dex_repository.dart Added page-based filtering for best_orders calls and improved error handling
lib/bloc/bridge_form/bridge_bloc.dart Reordered login status check to prevent unnecessary loading states when not logged in

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Oct 27, 2025

@CharlVS: I'll review the changes in this pull request for you.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@CharlVS
Copy link
Copy Markdown
Collaborator

CharlVS commented Oct 27, 2025

@cursoragent review

@cursor
Copy link
Copy Markdown

cursor bot commented Oct 27, 2025

I reviewed the touched files and verified the retry/backoff, pre‑login gating, and page-based filtering for best_orders.

Summary

  • Added retry with linear backoff for best_orders; improves transient post-login failures.
  • Pre-login: balances show as unavailable (no spinner) and orderbooks can load.
  • Background best_orders limited to DEX/Bridge via repository gating.

Highlights (with code references)

  • Retry/backoff in mm2_api.dart:
backoffStrategy: const LinearBackoff(
  initialDelay: Duration(milliseconds:  500),
  increment: Duration(milliseconds: 250),
  maxDelay: Duration(seconds: 3),
),
  • Page-based best_orders gating:
Future<BestOrders> getBestOrders(BestOrdersRequest request) async {
  // Only allow best_orders when user is on Swap (DEX) or Bridge pages
  final MainMenuValue current = routingState.selectedMenu;
  final bool isTradingPage =
      current == MainMenuValue.dex || current == MainMenuValue.bridge;
  if (!isTradingPage) {
    return BestOrders(result: <String, List<BestOrder>>{});
  }
  • Taker pre-login balance state:
// Before login, show 0.00 instead of spinner
if (!_isLoggedIn) {
  emit(
    state.copyWith(
      availableBalanceState: () => AvailableBalanceState.unavailable,
      maxSellAmount: () => null,
    ),
  );
}
// If not logged in, show 0.00 (unavailable) and skip spinner
if (!_isLoggedIn) {
  emitter(
    state.copyWith(
      availableBalanceState: () => AvailableBalanceState.unavailable,
      maxSellAmount: () => null,
    ),
  );
  return;
}
  • Maker pre-login/loading logic:
// Only show loading spinner when signed in
final bool isSignedIn = await kdfSdk.auth.isSignedIn();
availableBalanceState =
    isSignedIn ? AvailableBalanceState.loading : AvailableBalanceState.unavailable;
final Coin? coin = sellCoin;
final bool isSignedIn = await kdfSdk.auth.isSignedIn();
if (!isSignedIn) {
  maxSellAmount = null;
  availableBalanceState = AvailableBalanceState.unavailable;
  return;
}
  • Bridge pre-login balance state:
// Before login, show 0.00 instead of spinner
if (!_isLoggedIn) {
  emit(
    state.copyWith(
      availableBalanceState: () => AvailableBalanceState.unavailable,
      maxSellAmount: () => null,
    ),
  );
}
// If not logged in, show 0.00 (unavailable) and skip spinner
if (!_isLoggedIn) {
  emit(
    state.copyWith(
      availableBalanceState: () => AvailableBalanceState.unavailable,
      maxSellAmount: () => null,
    ),
  );
  return;
}

Suggestions

  • Backoff tuning and nit
    • Remove extra space in the duration literal:
backoffStrategy: const LinearBackoff(
  initialDelay: Duration(milliseconds:  500),
  increment: Duration(milliseconds: 250),
  • Consider aligning the parameters so maxDelay matches the reachable ceiling with 4 attempts, or increase attempts/increment to utilize 3s. For example, either lower maxDelay to ~1500ms or bump attempts/increment for the current 3s cap.
  • Unify backoff strategy constants
    • Taker (500ms→2s), Bridge (2s→10s), Maker (defaults), and mm2 differ. Consider centralizing backoff configs to keep behavior consistent and easier to tune.
  • Deduplicate unsupported-asset filtering
    • The same exclusion is applied in two places:
bestOrders.result?.removeWhere(
  (coinId, _) => excludedAssetList.contains(coinId),
);
bestOrders.result?.removeWhere(
  (coinId, _) => excludedAssetList.contains(coinId),
);
  • Consider moving this filter into DexRepository.getBestOrders so all consumers benefit and logic is in one place.

Test considerations

  • The code paths align with the PR’s manual test steps: pre-login shows unavailable balance; selecting pairs populates orderbooks; post-login balance transitions to loading→value; orderbooks populate on both DEX and Bridge pages.

Open in Cursor Open in Web

Learn more about Cursor Agents

@github-actions
Copy link
Copy Markdown

github-actions bot commented Oct 27, 2025

Visit the preview URL for this PR (updated for commit 768fcea):

https://walletrc--pull-3263-merge-fcje4b8o.web.app

(expires Tue, 11 Nov 2025 18:09:09 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

Sign: f66a4ff03faa546f12f0ae5a841bd9eff2714dcc

@cursor
Copy link
Copy Markdown

cursor bot commented Oct 27, 2025

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

Copy link
Copy Markdown
Contributor

@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: 1

♻️ Duplicate comments (1)
lib/mm2/mm2_api/mm2_api.dart (1)

239-259: Refine backoff config and fix spacing.

  • Remove the extra space before 500.
  • With 4 attempts and 250ms increments, 3s maxDelay is never reached. Either lower maxDelay or increase attempts.

Apply one of these diffs (Option A keeps attempts, Option B keeps maxDelay):

Option A — lower maxDelay:

-        backoffStrategy: const LinearBackoff(
-          initialDelay: Duration(milliseconds:  500),
-          increment: Duration(milliseconds: 250),
-          maxDelay: Duration(seconds: 3),
-        ),
+        backoffStrategy: const LinearBackoff(
+          initialDelay: Duration(milliseconds: 500),
+          increment: Duration(milliseconds: 250),
+          maxDelay: Duration(milliseconds: 1500),
+        ),

Option B — increase attempts:

-        maxAttempts: 4,
+        maxAttempts: 8,
         backoffStrategy: const LinearBackoff(
-          initialDelay: Duration(milliseconds:  500),
+          initialDelay: Duration(milliseconds: 500),
           increment: Duration(milliseconds: 250),
           maxDelay: Duration(seconds: 3),
         ),
🧹 Nitpick comments (5)
lib/bloc/taker_form/taker_bloc.dart (2)

319-327: Avoid starting the 10s max-sell timer before login.

Return early after setting the pre-login state to prevent scheduling periodic updates while logged out.

     // Before login, show 0.00 instead of spinner
     if (!_isLoggedIn) {
       emit(
         state.copyWith(
           availableBalanceState: () => AvailableBalanceState.unavailable,
           maxSellAmount: () => null,
         ),
       );
+      return;
     }

451-461: Remove redundant !_isLoggedIn check later in the method.

You already return early for logged-out users above; the check around Lines 490-496 is unreachable and can be removed.

-    if (!_isLoggedIn) {
-      emitter(
-        state.copyWith(
-          availableBalanceState: () => AvailableBalanceState.unavailable,
-        ),
-      );
-    } else {
+    {
       Rational? maxSellAmount = await _dexRepo.getMaxTakerVolume(
         state.sellCoin!.abbr,
       );
       if (maxSellAmount != null) {
         emitter(
           state.copyWith(
             maxSellAmount: () => maxSellAmount,
             availableBalanceState: () => AvailableBalanceState.success,
           ),
         );
       } else {
         maxSellAmount = await _frequentlyGetMaxTakerVolume();
         emitter(
           state.copyWith(
             maxSellAmount: () => maxSellAmount,
             availableBalanceState: maxSellAmount == null
                 ? () => AvailableBalanceState.failure
                 : () => AvailableBalanceState.success,
           ),
         );
       }
     }
lib/bloc/dex_repository.dart (2)

22-24: Decouple repository from routing/UI state.

Importing and reading routingState from the repository creates an unnecessary UI dependency. Prefer pushing this guard up to the blocs or inject a predicate (e.g., bool Function() isTradingContext) via constructor.

Also applies to: 127-134


139-146: Minor: adjust log path for clarity.

These logs originate in the repository; consider using “repo => getBestOrders” instead of “api => …” to ease triage.

-        path: 'api => getBestOrders',
+        path: 'repo => getBestOrders',

Apply similarly in both log calls.

Also applies to: 154-160

lib/bloc/bridge_form/bridge_bloc.dart (1)

241-249: Skip scheduling the periodic balance poll before login.

After setting the pre-login state, return early to avoid starting _subscribeMaxSellAmount while logged out.

     // Before login, show 0.00 instead of spinner
     if (!_isLoggedIn) {
       emit(
         state.copyWith(
           availableBalanceState: () => AvailableBalanceState.unavailable,
           maxSellAmount: () => null,
         ),
       );
+      return;
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 12a0e16 and dd26ada.

📒 Files selected for processing (5)
  • lib/bloc/bridge_form/bridge_bloc.dart (2 hunks)
  • lib/bloc/dex_repository.dart (3 hunks)
  • lib/bloc/taker_form/taker_bloc.dart (2 hunks)
  • lib/blocs/maker_form_bloc.dart (2 hunks)
  • lib/mm2/mm2_api/mm2_api.dart (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Deploy Preview
  • GitHub Check: validate_code_guidelines
  • GitHub Check: Build Desktop (linux)
  • GitHub Check: Build Desktop (windows)
  • GitHub Check: build_and_preview
  • GitHub Check: Build Mobile (Android)
  • GitHub Check: unit_tests
🔇 Additional comments (2)
lib/bloc/bridge_form/bridge_bloc.dart (2)

405-414: LGTM: early return when not logged in.

Prevents spinners and unnecessary calls while logged out.


416-424: LGTM: gate loading state by auth or explicit setLoadingStatus.

Keeps UX consistent and avoids misleading loading indicators.

@smk762 smk762 added the P0 Blocker / critical defect label Oct 28, 2025
@smk762 smk762 added bug Something isn't working P1 Major issue needs to be fixed performance Where data sourcing or rendering speed needs improvement and removed P0 Blocker / critical defect labels Oct 29, 2025
@smk762 smk762 changed the title Tweak prelogin orderbook handle best_orders exception during login Oct 29, 2025
@smk762 smk762 changed the title handle best_orders exception during login handle failed best_orders RPC exception during login Oct 29, 2025
Copy link
Copy Markdown
Contributor

@takenagain takenagain left a comment

Choose a reason for hiding this comment

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

Confirmed that the error logs are no longer present in web logs and that test flow passes

…ty (#3328)

- Propagate errors from best_orders instead of returning empty results
- Treat P2P 'No response from any peer' (including wrapped Exception) as no orders
- Add debug-only failure simulator toggled via kSimulateBestOrdersFailure

feat(ui): CTA to Maker when no orders; contextual localized messages

- Taker: show CTA when no swap offers available
- Bridge: show CTA when no cross-network routes exist
- Add locale keys: dexNoSwapOffers, bridgeNoCrossNetworkRoutes
@CharlVS CharlVS added this to the v0.9.3 Release milestone Nov 2, 2025
@ca333 ca333 merged commit 07806d0 into dev Nov 4, 2025
6 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working P1 Major issue needs to be fixed performance Where data sourcing or rendering speed needs improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

error on best_orders requests after login

5 participants