Skip to content

Conversation

@matallui
Copy link
Contributor

@matallui matallui commented Nov 11, 2025

Description

This PR implements optimistic updates for Predict positions to provide immediate user feedback when placing BUY/SELL orders, significantly improving the user experience by showing skeleton loaders instead of stale data while waiting for API confirmation.

What is the reason for the change?

Previously, when users placed orders in the Predict feature, they had to wait for the API to confirm the transaction before seeing their position update. This created a poor UX where:

  1. Users saw outdated position values after placing orders
  2. No visual feedback indicated that an order was processing
  3. Users were unsure if their action succeeded until API confirmed

What is the improvement/solution?

Implemented a comprehensive optimistic updates system:

Core Features

  • Optimistic position creation: When users BUY, create an immediate optimistic position with expected values
  • Optimistic position updates: When users BUY more of an existing position, optimistically update with accumulated values
  • Optimistic position removal: When users SELL or CLAIM, immediately hide the position from the list
  • Smart validation: Compare API responses with expected sizes to know when to remove optimistic updates
  • Auto-cleanup: Remove optimistic updates after 1-minute timeout if API never confirms
  • Defensive checks: Prevent optimistic updates on claimable positions

Implementation Details

  1. PolymarketProvider.ts (~450 lines added):

    • Added OptimisticUpdateType enum (CREATE, UPDATE, REMOVE)
    • Added OptimisticPositionUpdate interface with type, position data, and expected size
    • Added #optimisticPositionUpdatesByAddress Map to track updates per user address
    • Implemented createOrUpdateOptimisticPosition() for BUY orders
    • Implemented removeOptimisticPosition() for SELL/CLAIM orders
    • Implemented applyOptimisticPositionUpdates() to merge optimistic data with API responses
    • Implemented isApiPositionUpdated() with 0.1% tolerance for size validation
    • Added cleanup logic for expired optimistic updates (1-minute timeout)
  2. UI Components:

    • PredictPosition.tsx: Added skeleton loaders for current value and PnL when optimistic: true
    • PredictPositionDetail.tsx: Added skeleton loaders and disabled "Cash out" button for optimistic positions
    • PredictPosition types: Added optimistic?: boolean flag to PredictPosition type
  3. Controller:

    • PredictController.ts: Include fees in optimistic balance calculation for accurate display
  4. Comprehensive Test Coverage (~1,830 lines of tests):

    • Fixed 8 failing tests from system migration
    • Added 21 new tests covering CREATE, UPDATE, REMOVE, integration, and UI scenarios
    • Achieved 91.53% statement coverage and 91.75% line coverage

Changelog

CHANGELOG entry: null

Related issues

Fixes: PRED-294

Manual testing steps

Feature: Optimistic updates for Predict positions

  Scenario: User buys a new position
    Given user is on the Predict Positions screen
    And user has no position in a market

    When user places a BUY order for a position
    Then user immediately sees the new position in their list
    And the position shows skeleton loaders for value and PnL
    And the initial value and shares are displayed

    When the API confirms the order (within 1 minute)
    Then the skeleton loaders are replaced with actual values
    And the position shows real-time current value and PnL

  Scenario: User sells an existing position
    Given user is on the Predict Positions screen
    And user has an existing position

    When user places a SELL order for the position
    Then the position immediately disappears from the list

    When the API confirms the sale
    Then the position remains hidden (optimistic update removed)

  Scenario: User buys more of an existing position
    Given user has an existing position with 10 shares

    When user places another BUY order for the same position
    Then the position shows skeleton loaders
    And the expected accumulated values are displayed

    When the API confirms the order
    Then actual updated values replace the skeletons

  Scenario: API timeout cleanup
    Given user placed a BUY order 1 minute ago
    And the API has not yet confirmed

    When 1 minute has passed since the order
    Then the optimistic update is automatically cleaned up
    And user sees either the confirmed API position or no position

Screenshots/Recordings

Before

After placing an order, users saw stale position data with no indication that an order was processing.

After

After placing an order, users immediately see:

  • New positions appear with skeleton loaders
  • Existing positions update with skeleton loaders
  • Sold positions disappear immediately
  • Skeleton loaders are replaced with real values when API confirms
Screen.Recording.2025-11-10.at.9.27.17.PM.mov

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.

Technical Implementation Summary

Files Changed

  • PolymarketProvider.ts: +450 lines (optimistic update system)
  • PredictPosition.tsx: +29 lines (skeleton loaders)
  • PredictPositionDetail.tsx: +27 lines (skeleton loaders, disabled button)
  • PredictController.ts: +3 lines (fee calculation)
  • types/index.ts: +1 line (optimistic?: boolean flag)
  • Test files: +1,830 lines (comprehensive coverage)

Optimistic Update Flow

BUY Order:

  1. User places BUY order → placeOrder() called
  2. Order submitted to API via submitClobOrder()
  3. On success, createOrUpdateOptimisticPosition() called
  4. Optimistic position created with optimistic: true flag
  5. getPositions() merges optimistic position with API data
  6. UI shows skeleton loaders for value/PnL
  7. When API returns position with expected size → optimistic update removed
  8. If API doesn't confirm within 1 minute → optimistic update expires

SELL/CLAIM Order:

  1. User places SELL/CLAIM order → placeOrder() or confirmClaim() called
  2. removeOptimisticPosition() marks position for removal
  3. getPositions() filters out the position
  4. Position immediately disappears from UI
  5. When API confirms (position gone or size reduced) → optimistic update removed

Test Coverage

Total: 21 new tests added

PolymarketProvider (14 tests):

  • CREATE scenarios: 7 tests (position creation, value calculation, market details)
  • UPDATE scenarios: 4 tests (accumulation, avgPrice, preservation)
  • Integration: 3 tests (BUY→confirm, timeout, BUY→SELL)

UI Components (7 tests):

  • PredictPosition: 4 tests (skeleton display, value hiding)
  • PredictPositionDetail: 3 tests (skeleton display, button state)

Coverage Achieved:

  • Statements: 91.53% ✅
  • Branches: 83.01% ✅
  • Functions: 91.6% ✅
  • Lines: 91.75% ✅

All metrics exceed the 80% target.


Note

Adds full optimistic position create/update/remove flow with UI skeletons, disables actions during optimism, adjusts balance for fees, and expands tests.

  • Predict Provider (Polymarket):
    • Implement optimistic updates system (CREATE/UPDATE/REMOVE) via in-memory map keyed by address and outcomeTokenId.
    • Merge optimistic state in getPositions with timeout cleanup (1 min) and API-size validation tolerance.
    • Block orders on claimable positions; add targeted fetch by outcomeId in getPositions.
    • Apply optimistic removal on SELL and CLAIM; create/update optimistic positions on BUY using market details when available.
  • UI:
    • PredictPosition and PredictPositionDetail: show skeletons for value/PnL when position.optimistic is true; disable "Cash out" for optimistic positions.
  • Controller:
    • Deduct total fees from balance during optimistic BUY balance update.
  • Types/Providers API:
    • Add optimistic?: boolean to PredictPosition; add outcomeId to GetPositionsParams and wire through provider method.
  • Tests:
    • Extensive new tests covering optimistic create/update/remove flows, integration scenarios, and UI behavior.
  • Misc:
    • Minor hook lint suppression in usePredictPrices.

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

… system

Update existing tests to work with the new optimistic position updates
system that replaced the old recentlySoldPositions tracking:

- Update confirmClaim tests to track by outcomeTokenId
- Update getPositions filtering tests for optimistic removal system
- Change timeout from 5 minutes to 1 minute per new implementation
- Update placeOrder tests to verify optimistic position creation on BUY
- Update all mocked positions to include outcomeTokenId field
- Update assertions to match new optimistic update behavior

All 149 tests now passing.
Add first batch of tests for optimistic position creation during BUY orders:

- Test creating optimistic position when buying new shares
- Test helper function validation for optimistic flag

These tests use the new helper functions (createOptimisticPosition,
setupOptimisticUpdateTest, mockMarketDetailsForOptimistic) and validate
the basic optimistic position creation flow.

Tests: 151 passing (+2 from baseline)
Add comprehensive tests for optimistic position creation during BUY orders:

- Calculate initial values correctly (amount, initialValue, avgPrice, size)
- Set expected size for API validation
- Fetch market details for complete position data
- Handle market details fetch failures gracefully
- Block orders on claimable positions (defensive check)

Tests validate that optimistic positions are created with correct values
using raw USDC amounts, and handle edge cases like API failures.

Tests: 156 passing (+5 from previous commit)
Add tests for updating existing optimistic positions when buying more shares:

- Update existing position when buying more shares
- Accumulate amount and initialValue (tests actual behavior)
- Recalculate avgPrice after update
- Preserve existing position data (marketId, outcomeId, title)

Tests validate that subsequent BUY orders on same outcomeTokenId create
new optimistic positions (since optimistic positions aren't returned from API).

Tests: 160 passing (+4 from previous commit)
Add tests for PredictPosition and PredictPositionDetail components:

PredictPosition (4 tests):
- Hide current value when optimistic
- Hide percent PnL when optimistic
- Show actual values when not optimistic
- Show initial value line when optimistic

PredictPositionDetail (3 tests):
- Hide current value when optimistic and market open
- Hide percent PnL when optimistic and market open
- Show initial value and outcome when optimistic

Tests validate that skeleton loaders replace values for optimistic positions.

Tests: 20 passing across both components
Add end-to-end integration tests for optimistic position flows:

- Create optimistic position on BUY, remove when API confirms
- Clean up after 1-minute timeout if API never confirms (using fake timers)
- Handle BUY followed by SELL on same position

Tests validate complete workflows from order placement through API
confirmation or timeout cleanup.

Tests: 163 passing (+3 integration tests)
Coverage: 91.53% statements, 91.75% lines (exceeds 80% target)
@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-predict Predict team label Nov 11, 2025
state.balances[providerId] = state.balances[providerId] || {};
state.balances[providerId][signer.address] = {
balance: cachedBalance - realAmountUsd,
balance: cachedBalance - (realAmountUsd + totalFee),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a quick fix to the optimistic balance updates, which were missing the fees.

@matallui matallui requested review from caieu and kevinbluer November 11, 2025 17:01
@matallui matallui marked this pull request as ready for review November 11, 2025 17:01
@codecov-commenter
Copy link

Codecov Report

❌ Patch coverage is 88.98305% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.90%. Comparing base (6e7d005) to head (4f00f1a).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...Predict/providers/polymarket/PolymarketProvider.ts 89.52% 7 Missing and 4 partials ⚠️
...redictTransactionsView/PredictTransactionsView.tsx 60.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #22493      +/-   ##
==========================================
+ Coverage   77.88%   77.90%   +0.01%     
==========================================
  Files        3841     3841              
  Lines       98201    98294      +93     
  Branches    19290    19316      +26     
==========================================
+ Hits        76488    76579      +91     
  Misses      16473    16473              
- Partials     5240     5242       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Replace raw React Native components with design system equivalents to
comply with UI development guidelines:

- Replace TouchableOpacity → ButtonBase
- Replace View → Box with twClassName props
- Replace StyleSheet.create() → Tailwind classes
- Delete PredictPosition.styles.ts file
- Use useTailwind() hook for inline styles

Tailwind class mappings:
- positionContainer → flex-row items-start py-2 gap-4 w-full
- positionImageContainer → pt-1
- positionImage → w-10 h-10 rounded-full
- positionDetails → flex-1 gap-0.5
- positionPnl → gap-2 items-end
- skeletonSpacing → mb-1

Fixes CODEBOT violations:
- Component Hierarchy (STRICT ORDER) - now uses design system first
- Styling Rules (ENFORCE STRICTLY) - removed StyleSheet.create()

All tests passing (22/22).
@sonarqubecloud
Copy link

Copy link
Contributor

@caieu caieu left a comment

Choose a reason for hiding this comment

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

LGTM

@kevinbluer
Copy link
Member

Quick note during testing on Samsung A32...it did get stuck with the skeleton loader on the positions feed after I tapped back to it very quickly after placing the bet. Tapping to a different screen and back did cause it to load correctly though:

saumsung-a32-feed

Copy link
Member

@kevinbluer kevinbluer left a comment

Choose a reason for hiding this comment

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

Other than the earlier comment it LGTM 👍

@matallui matallui enabled auto-merge November 11, 2025 20:14
@matallui matallui added this pull request to the merge queue Nov 11, 2025
Merged via the queue into main with commit 338aaf4 Nov 11, 2025
135 of 137 checks passed
@matallui matallui deleted the predict/optimistic-positions branch November 11, 2025 20:53
@github-actions github-actions bot locked and limited conversation to collaborators Nov 11, 2025
@metamaskbot metamaskbot added the release-7.60.0 Issue or pull request that will be included in release 7.60.0 label Nov 11, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release-7.60.0 Issue or pull request that will be included in release 7.60.0 size-XL team-predict Predict team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants