Skip to content

Conversation

@gambinish
Copy link
Contributor

@gambinish gambinish commented Dec 20, 2025

Description

Withdrawal indicators in Perps could get stuck in "pending" or "bridging" state indefinitely, even after the withdrawal had successfully completed on-chain. This created a poor user experience where users saw stale pending indicators that never resolved.

Solution

Enhanced the 1useWithdrawalRequests1 hook to automatically reconcile pending withdrawal states with completed withdrawals from the HyperLiquid API:

  1. Auto-reconciliation: When a completed withdrawal from the API matches a pending one in controller state, the controller is automatically updated to reflect the completion
  2. Active polling: Polls the HyperLiquid ledger API every 10 seconds when there are active (pending/bridging) withdrawals to detect completions promptly
  3. Relaxed flexible matching: Matches withdrawals by amount (±$0.01 tolerance) and asset type; removed timestamp constraint since bridging operations can sometimes take hours
  4. Deduplication tracking: Uses a ref to track which withdrawals have already been updated, preventing duplicate updateWithdrawalStatus calls
    Account isolation: Filters withdrawals by the current selected account to prevent showing stale indicators from other accounts

Changelog

CHANGELOG entry: Fixed an issue where Perps withdrawal indicators could remain stuck in "pending" state after the withdrawal completed

Related issues

Fixes: https://consensyssoftware.atlassian.net/browse/TAT-2128

Manual testing steps

Feature: Perps Withdrawal Status Resolution

  Scenario: Pending withdrawal resolves to completed
    Given user has an active Perps account with USDC balance
    And user initiates a withdrawal from Perps

    When the withdrawal completes on HyperLiquid (wait up to 10 seconds for poll)
    Then the withdrawal status updates from "pending" or "bridging" to "completed"
    And the withdrawal displays the transaction hash

  Scenario: Pending indicators clear on account switch
    Given user has a pending withdrawal on Account A
    And user switches to Account B which has no withdrawals

    When the withdrawal list renders
    Then no pending withdrawal indicators are shown for Account B

  Scenario: Completed withdrawals persist after app restart
    Given user had a withdrawal complete while the app was closed
    
    When user opens the app and navigates to Perps withdrawals
    Then the withdrawal shows as "completed" (fetched from HyperLiquid API)
    And no stale "pending" indicator is displayed

Screenshots/Recordings

Before

After

Pre-merge author checklist

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

Note

Adds a new hook and strengthens withdrawal state reconciliation for Perps.

  • New hook: usePerpsMarketFills merges WebSocket (usePerpsLiveFills) and REST getOrderFills data with deduplication, symbol filtering, descending timestamp sort, refresh support, and optional throttleMs (defaults to 0)
  • Withdrawal reconciliation: useWithdrawalRequests now matches completions by amount (±$0.01) and asset (timestamp constraint removed); updates controller via updateWithdrawalStatus in a side-effect with dedup tracking (useRef)
  • Account isolation & polling: Filters withdrawals to the selected account; polls HyperLiquid ledger every 10s when there are active (pending/bridging) withdrawals
  • Resilience: Improved error handling/logging; handles provider absence/capabilities and non-array responses
  • Tests: Comprehensive tests for merging/dedup/sorting, refresh behavior, throttling, relaxed timestamp matching, polling, and error paths

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

@github-actions
Copy link
Contributor

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

@metamaskbot metamaskbot added the team-perps Perps team label Dec 20, 2025
@gambinish gambinish marked this pull request as ready for review January 6, 2026 20:59
@gambinish gambinish requested a review from a team as a code owner January 6, 2026 20:59
@github-actions
Copy link
Contributor

github-actions bot commented Jan 6, 2026

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokePerps
  • Risk Level: medium
  • AI Confidence: 90%
click to see 🤖 AI reasoning details

The changes are entirely scoped to the Perps (Perpetuals trading) feature:

  1. New usePerpsMarketFills.ts hook: A new hook for fetching market-specific order fills combining WebSocket and REST data. Currently only imported by its test file, so no production code uses it yet.

  2. Modified useWithdrawalRequests.ts: Refactored withdrawal matching logic to:

    • Remove timestamp constraints (bridging can take hours)
    • Move side effects from useMemo to useEffect (proper React pattern)
    • Add ref-based tracking to prevent duplicate controller updates
    • Extract matching logic into a helper function
  3. Test files: Both hooks have comprehensive unit tests updated/added.

The changes use Engine.context.PerpsController which is a critical pattern, but the scope is limited to Perps functionality. The useWithdrawalRequests hook is used by PerpsProgressBar.tsx, affecting the withdrawal flow in the Perps feature.

Risk is medium because:

  • Changes affect withdrawal status tracking logic (financial operations)
  • Uses Engine/Controller pattern (critical infrastructure)
  • But scope is limited to Perps feature only
  • Has comprehensive unit test coverage

Only the SmokePerps tag is needed as all changes are isolated to the Perpetuals trading feature.

View GitHub Actions results

const isAmountMatch = amountDiff < 0.01;
const isAssetMatch = pending.asset === completed.asset;

return isAmountMatch && isAssetMatch;
Copy link

Choose a reason for hiding this comment

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

Multiple same-amount withdrawals match incorrectly or stay stuck

Medium Severity

The matching logic uses find() which always returns the first matching element, but there's no tracking of which completed withdrawals have already been matched. When a user makes multiple withdrawals with the same amount and asset (e.g., two 100 USDC withdrawals), all pending withdrawals will match the first completed withdrawal via find(), displaying the wrong txHash for subsequent withdrawals. Additionally, in the useEffect, multiple completed withdrawals will all try to match the first pending one, but updatedWithdrawalIdsRef only tracks pending IDs—causing subsequent completed withdrawals to be blocked, leaving their corresponding pending withdrawals stuck indefinitely. Removing the timestamp constraint makes this more likely since any same-amount withdrawals now match regardless of when initiated.

Additional Locations (1)

Fix in Cursor Fix in Web

parseFloat(pending.amount) - parseFloat(completed.amount),
);
return amountDiff < 0.01 && pending.asset === completed.asset;
});
Copy link

Choose a reason for hiding this comment

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

Duplicate entries appear after withdrawal marked completed

Medium Severity

After a withdrawal is matched and its controller state is updated to status: 'completed', subsequent renders create duplicate entries. The first loop adds the controller's completed withdrawal (lines 273-276). The second loop's hasPendingMatch check only looks for withdrawals with status === 'pending' || 'bridging' (lines 287-290). Since the controller withdrawal is now 'completed', it doesn't count as a match, so the API's version of the same withdrawal is added separately. Users see two entries for the same withdrawal with different IDs but the same txHash and amount.

Additional Locations (1)

Fix in Cursor Fix in Web

@sonarqubecloud
Copy link

sonarqubecloud bot commented Jan 6, 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants