Commit b000065
authored
feat: Integrate Rewards with Predict (#22546)
# Predict Buy: Rewards Display and Fee Consolidation
## Overview
This PR adds rewards point estimation and consolidates fee display in
the Predict Buy confirmation screen. Users can now see estimated
MetaMask Rewards points they'll earn from their transaction, and view
detailed fee breakdowns through an intuitive bottom sheet.
CHANGELOG entry: null
https://github.com/user-attachments/assets/cf20a4e3-1375-4f9b-97ff-c75a3d68f737
## Changes
### 🎁 Rewards Integration
- **Added "Est. points" row** to the Predict Buy confirmation screen
- Displays estimated rewards points based on MetaMask fee
- **Calculation**: `Math.round(metamaskFee * 100)` (1 point per cent
spent on MM fee)
- Position: Last row after "Total"
- White text with gray info icon for consistency
- Reuses `RewardsAnimations` component from Swap/Bridge features
- Conditional display based on `rewardsEnabled` feature flag and
transaction amount
### 💰 Fee Display Consolidation
- **Consolidated two fee rows into single "Fees" row**
- Previously: Separate "Provider fee" and "MetaMask fee" rows
- Now: Single "Fees" row showing sum of both fees
- Added gray info icon that opens detailed breakdown
- **New Fee Breakdown Bottom Sheet** (`PredictFeeBreakdownSheet`)
- Displays individual fee breakdown:
- Polymarket fee (provider fee)
- MetaMask fee
- Opens when user taps info icon next to "Fees"
- Closes without navigating back (uses `shouldNavigateBack={false}`)
### 🎨 UI/UX Improvements
- **Fee Summary Row Order**:
1. Fees (consolidated, with info icon)
2. Total
3. Est. points (when rewards enabled)
- **Styling**:
- Est. points text: White (`TextColor.Default`)
- Info icons: Gray (`IconColor.Alternative`)
- Consistent with design system
## Technical Details
### Files Modified
#### Components
- **`PredictBuyPreview.tsx`**
- Import `selectRewardsEnabledFlag` selector
- Calculate `estimatedPoints` from `metamaskFee`
- Add `isFeeBreakdownVisible` state
- Add `handleFeesInfoPress` and `handleFeeBreakdownClose` callbacks
- Pass rewards props to `PredictFeeSummary`
- Conditionally render `PredictFeeBreakdownSheet`
- **`PredictFeeSummary.tsx`**
- Remove individual fee rows
- Add consolidated "Fees" row with `ButtonIcon`
- Calculate `totalFees = providerFee + metamaskFee`
- Move rewards row to last position (after Total)
- Update text colors (white for Est. points label)
- Add `onFeesInfoPress` callback prop
#### New Files
- **`PredictFeeBreakdownSheet.tsx`**
- Bottom sheet component for fee breakdown
- Displays provider and MetaMask fees separately
- Uses `shouldNavigateBack={false}` to prevent parent modal closure
- Accepts `onClose` callback
- **`PredictFeeBreakdownSheet/index.ts`**
- Export file for new component
#### Localization
- **`locales/languages/en.json`**
```json
{
"predict.fee_summary.fees": "Fees",
"predict.fee_summary.provider_fee_label": "Polymarket fee",
"predict.fee_summary.estimated_points": "Est. points",
"predict.fee_summary.points_tooltip": "Points",
"predict.fee_summary.points_tooltip_content_1": "Points are how you earn
MetaMask Rewards for completing transactions, like when you swap,
bridge, or predict.",
"predict.fee_summary.points_tooltip_content_2": "Keep in mind this value
is an estimate and will be finalized once the transaction is complete.
Points can take up to 1 hour to be confirmed in your Rewards balance."
}
```
### Tests
#### New Tests (22 total)
- **`PredictFeeSummary.test.tsx`** (12 tests)
- Consolidated fees display and calculation
- Fees info icon callback handling
- Rewards row conditional rendering
- Rewards row positioning
- Edge cases (zero fees, missing callbacks)
- **`PredictFeeBreakdownSheet.test.tsx`** (10 tests - New file)
- Bottom sheet rendering
- Fee display (Polymarket and MetaMask)
- `shouldNavigateBack` behavior
- Close callback handling
- Ref methods exposure
- **`PredictBuyPreview.test.tsx`** (10 new tests)
- Rewards calculation formula
- Rewards point rounding
- Feature flag conditional display
- Fee breakdown sheet visibility
- Loading state propagation
#### Updated Tests (1)
- Fixed existing test to expect "Fees" instead of "Provider fee"
**Total Test Results**: 2,038 tests passing ✅
## Rewards Calculation Logic
```typescript
// Formula: 1 point per cent spent on MetaMask fee
const estimatedPoints = useMemo(
() => Math.round(metamaskFee * 100),
[metamaskFee],
);
// Display conditions
const shouldShowRewards = rewardsEnabled && currentValue > 0;
```
### Examples:
- MetaMask fee: $0.50 → **50 points**
- MetaMask fee: $1.23 → **123 points**
- MetaMask fee: $0.00 → **0 points**
## Component Reusability
This implementation reuses existing components:
- ✅ `RewardsAnimations` (from Bridge/Swap)
- ✅ `KeyValueRow` (component library)
- ✅ `ButtonIcon` (design system)
- ✅ `BottomSheet` (design system)
- ✅ Design system tokens (`TextColor`, `IconColor`)
## Feature Flags
- **Rewards display**: Controlled by `selectRewardsEnabledFlag`
- **Fee breakdown**: Always available
## Testing Checklist
- [x] Unit tests for all new components
- [x] Unit tests for modified components
- [x] All existing tests passing (2,038 tests)
- [x] No linter errors
- [x] Tests follow AAA pattern
- [x] Tests follow project guidelines (no "should" in names)
- [x] Edge cases covered (zero values, missing callbacks, etc.)
## Manual Testing
### Test Scenarios
1. **Rewards Display**
- [ ] Verify Est. points row appears when rewards enabled
- [ ] Verify points calculation: fee * 100 rounded
- [ ] Verify Est. points row does NOT appear when rewards disabled
- [ ] Verify Est. points row does NOT appear when amount is 0
2. **Fee Consolidation**
- [ ] Verify single "Fees" row shows sum of provider + MetaMask fees
- [ ] Verify info icon appears next to "Fees" label
- [ ] Verify tapping info icon opens bottom sheet
- [ ] Verify bottom sheet shows individual fee breakdown
3. **Bottom Sheet Behavior**
- [ ] Verify bottom sheet displays "Polymarket fee" and "MetaMask fee"
- [ ] Verify closing bottom sheet does NOT close parent modal
- [ ] Verify bottom sheet closes on swipe down
- [ ] Verify bottom sheet closes on backdrop tap
4. **Visual/Styling**
- [ ] Verify Est. points text is white
- [ ] Verify all info icons are gray
- [ ] Verify row order: Fees → Total → Est. points
- [ ] Verify layout on different screen sizes
## Before/After
### Before
```
Provider fee $0.10
MetaMask fee $0.04
Total $10.14
```
### After
```
Fees [i] $0.14 ← Tappable info icon
Total $10.14
Est. points [i] 14 ← New rewards row (white text)
```
### Fee Breakdown Sheet (when tapping [i])
```
╔════════════════════════╗
║ Fees ║
╠════════════════════════╣
║ Polymarket fee $0.10 ║
║ MetaMask fee $0.04 ║
╚════════════════════════╝
```
## Commit History
1. `feat: Add rewards row to Predict Buy confirmation screen` - Initial
rewards implementation
2. `refactor: Consolidate fee rows and add breakdown sheet` - Fee
consolidation
3. `refactor: Move Est. points row to end and use white text` - Final
positioning and styling
4. `test: Add comprehensive tests for rewards and fee breakdown
features` - Complete test coverage
## Related Issues/PRs
- Related to rewards feature integration
- Consistent with Swap/Bridge rewards display patterns
## Checklist
- [x] Code follows project coding guidelines
- [x] Code follows React Native UI development guidelines
- [x] Used design system components
(`@metamask/design-system-react-native`)
- [x] Used Tailwind CSS with `useTailwind()` hook
- [x] No StyleSheet.create() used
- [x] Proper TypeScript types (no `any`)
- [x] Comprehensive unit tests added
- [x] All tests passing
- [x] No linter errors
- [x] Localization strings added
- [x] Feature flag integrated
- [x] Component reusability maintained
- [x] Follows AAA test pattern
- [x] No breaking changes
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Adds rewards point estimation and consolidates fee display with a
tappable fees breakdown sheet in Predict Buy.
>
> - **Predict UI**:
> - `PredictFeeSummary`: Replace separate fee rows with a single "Fees"
row (info icon), display total fees, and add "Est. points" row using
`RewardsAnimations`.
> - `PredictBuyPreview`: Compute `estimatedPoints =
Math.round(metamaskFee * 100)`, gate rewards by
`selectRewardsEnabledFlag`, and open fee breakdown via
`onFeesInfoPress`; pass rewards/fees props.
> - **New Component**:
> - `PredictFeeBreakdownSheet`: Bottom sheet showing per-fee breakdown
(`Polymarket fee`, `MetaMask fee`), `shouldNavigateBack=false`,
`onClose` support.
> - **Localization**:
> - Add strings for `predict.fee_summary.fees`, `provider_fee_label`,
`estimated_points`, points tooltips/error content.
> - **Tests**:
> - Add tests for `PredictFeeBreakdownSheet` and rewards/fees behavior;
update existing expectations from "Provider fee" to "Fees".
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
01962c5. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->1 parent dc67be4 commit b000065
File tree
8 files changed
+740
-69
lines changed- app/components/UI/Predict
- components
- PredictFeeBreakdownSheet
- PredictFeeSummary
- views/PredictBuyPreview
- locales/languages
8 files changed
+740
-69
lines changedLines changed: 227 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
Lines changed: 56 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
Lines changed: 1 addition & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
0 commit comments