Commit 5b5c76c
authored
feat: Add geo-blocking analytics tracking (MetaMask#22126)
# Geoblock Analytics Implementation
## Overview
This PR implements analytics tracking for geoblocking events in Predict,
allowing us to understand when and where users are blocked from
performing actions due to geographic restrictions.
CHANGELOG entry: null
## Changes
### 1. State Structure Refactoring
**Consolidated eligibility and geoblock data into a single nested
structure:**
```typescript
// Before
eligibility: { [key: string]: boolean }
geoBlockData: { [key: string]: { country?: string } }
// After
eligibility: {
[key: string]: {
eligible: boolean;
country?: string;
}
}
```
**Benefits:**
- Simplified state management - one source of truth
- Atomic updates - eligible status and country always in sync
- Easier to consume - no need to access two separate state objects
### 2. Analytics Event Implementation
**Event:** `PREDICT_GEO_BLOCKED_TRIGGERED`
**Properties:**
- `country` - The country code where the user is located (from geoblock
API)
- `attempted_action` - The action the user tried to perform
**Attempted Actions:**
- `deposit` - User tried to add funds
- `predict_action` - User tried to place a prediction
- `cashout` - User tried to cash out a position
- `claim` - User tried to claim winnings
- `withdraw` - User tried to withdraw funds (not currently guarded)
### 3. Integration Points
**Guarded Actions:**
All actions that require eligibility checks use `executeGuardedAction`
from `usePredictActionGuard` hook:
```typescript
executeGuardedAction(
() => {
// Action logic
},
{ attemptedAction: PredictEventValues.ATTEMPTED_ACTION.DEPOSIT }
);
```
**Components Updated:**
- ✅ `PredictBalance.tsx` - Add funds (deposit)
- ✅ `PredictAddFundsSheet.tsx` - Add funds (deposit)
- ✅ `PredictMarketDetails.tsx` - Buy (predict), Claim
- ✅ `PredictPositionsHeader.tsx` - Claim
- ✅ `PredictPositionDetail.tsx` - Cash out
- ✅ `PredictMarketSingle.tsx` - Buy (predict)
- ✅ `PredictMarketOutcome.tsx` - Buy (predict)
- ✅ `PredictMarketMultiple.tsx` - Buy (predict)
### 4. Controller Updates
**`PredictController.ts`:**
- `refreshEligibility()` - Now stores both `eligible` and `country`
together
- `trackGeoBlockedEvent()` - New method to track analytics when user is
blocked
- Updated to use consolidated eligibility state
**`usePredictEligibility.ts` hook:**
- Now returns both `isEligible` and `country`
- Simplifies component consumption
### 5. Testing
**All tests updated and passing:**
- ✅ PredictController tests (1687 tests)
- ✅ usePredictEligibility tests
- ✅ Component tests updated for new hook signature
- ✅ Test suites: 67 passed
## Data Flow
```
User Action Attempt
↓
executeGuardedAction
↓
Check eligibility (from state)
↓
Blocked? → Yes → trackGeoBlockedEvent(country, attemptedAction)
↓ ↓
No Analytics Event Sent
↓
Execute Action
```
## Analytics Dashboard
**To query geoblock events in the analytics dashboard:**
```sql
SELECT
country,
attempted_action,
COUNT(*) as blocked_count,
DATE(timestamp) as date
FROM events
WHERE event_name = 'PREDICT_GEO_BLOCKED_TRIGGERED'
GROUP BY country, attempted_action, DATE(timestamp)
ORDER BY blocked_count DESC
```
**Key Metrics:**
- Most blocked countries
- Most common blocked actions
- Trends over time
- Conversion rates by geography
## Example Event
```json
{
"event": "PREDICT_GEO_BLOCKED_TRIGGERED",
"properties": {
"country": "US",
"attempted_action": "deposit"
},
"timestamp": "2024-01-15T10:30:00.000Z"
}
```
## Files Changed
### Core Implementation
- `app/components/UI/Predict/controllers/PredictController.ts`
- `app/components/UI/Predict/hooks/usePredictEligibility.ts`
- `app/components/UI/Predict/hooks/usePredictActionGuard.ts`
- `app/components/UI/Predict/constants/eventNames.ts`
### Components
-
`app/components/UI/Predict/components/PredictBalance/PredictBalance.tsx`
-
`app/components/UI/Predict/components/PredictAddFundsSheet/PredictAddFundsSheet.tsx`
-
`app/components/UI/Predict/views/PredictMarketDetails/PredictMarketDetails.tsx`
-
`app/components/UI/Predict/components/PredictPositionsHeader/PredictPositionsHeader.tsx`
-
`app/components/UI/Predict/components/PredictPositionDetail/PredictPositionDetail.tsx`
-
`app/components/UI/Predict/components/PredictMarketSingle/PredictMarketSingle.tsx`
-
`app/components/UI/Predict/components/PredictMarketOutcome/PredictMarketOutcome.tsx`
-
`app/components/UI/Predict/components/PredictMarketMultiple/PredictMarketMultiple.tsx`
### Tests
- `app/components/UI/Predict/controllers/PredictController.test.ts`
- `app/components/UI/Predict/hooks/usePredictEligibility.test.ts`
-
`app/components/UI/Predict/components/PredictAddFundsSheet/PredictAddFundsSheet.test.tsx`
## Testing Instructions
### Manual Testing
1. **Setup geoblock scenario:**
- Use VPN or modify geoblock API response to return `isEligible: false`
2. **Test each action:**
- Try to deposit funds → Check analytics event sent
- Try to place a prediction → Check analytics event sent
- Try to cash out → Check analytics event sent
- Try to claim winnings → Check analytics event sent
3. **Verify analytics:**
- Open DevTools console
- Look for `📊 [Analytics] PREDICT_GEO_BLOCKED_TRIGGERED` logs
- Verify `country` and `attempted_action` are correct
### Automated Testing
```bash
# Run all Predict tests
yarn jest ./app/components/UI/Predict
# Run specific test suites
yarn jest app/components/UI/Predict/controllers/PredictController.test.ts
yarn jest app/components/UI/Predict/hooks/usePredictEligibility.test.ts
```
## Migration Notes
### State Migration
No migration required. The consolidated state structure is backward
compatible:
- Old state with empty `eligibility` object will work correctly
- `refreshEligibility()` will populate new structure on first call
### API Compatibility
No API changes required. The geoblock API response format remains the
same:
```typescript
interface GeoBlockResponse {
isEligible: boolean;
country?: string;
}
```
## Future Enhancements
1. **Enhanced Metrics:**
- Add `ip` to analytics (currently stored but not tracked)
- Track `region` for more granular insights
2. **User Education:**
- Show country-specific messaging
- Suggest alternative actions for blocked users
3. **Dashboard Integration:**
- Real-time geoblock monitoring
- Alerts for sudden spikes in blocked regions
## Checklist
- [x] State structure refactored and tested
- [x] Analytics event implemented
- [x] All guarded actions updated with `attemptedAction`
- [x] Tests updated and passing (67 suites, 1687 tests)
- [x] Dev logging added for debugging
- [x] Code follows project guidelines
- [x] No breaking changes
## Related Issues
- Tracking user geoblocking for compliance and UX insights
- Understanding geographic distribution of blocked actions
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Add geo-block analytics and migrate eligibility to include country,
wiring attemptedAction tracking across guarded Predict actions.
>
> - **Analytics**:
> - Add `PREDICT_GEO_BLOCKED_TRIGGERED` event and properties `country`
and `attempted_action`.
> - **State/Controller**:
> - Change `PredictController.state.eligibility` to `{ [providerId]: {
eligible: boolean; country?: string } }`.
> - Update `refreshEligibility()` to store country; add
`trackGeoBlockTriggered()`.
> - **Providers**:
> - Update `PredictProvider.isEligible()` to return `{ isEligible,
country }` via `GeoBlockResponse`.
> - Implement in `PolymarketProvider.isEligible()`.
> - **Hooks**:
> - `usePredictEligibility()` returns `{ isEligible, country }` (default
`false` when unset).
> - `usePredictActionGuard()` accepts `{ checkBalance, attemptedAction
}` and tracks geo-blocks when blocked.
> - **UI (guarded actions instrumented with `attemptedAction`)**:
> - `PredictAddFundsSheet`, `PredictBalance` → deposit.
> - `PredictMarketDetails`, `PredictMarketSingle`,
`PredictMarketOutcome`, `PredictMarketMultiple` → predict/buy, claim.
> - `PredictPositionsHeader` → claim; `PredictPositionDetail` → cashout.
> - **MetaMetrics**:
> - Register new event in `MetaMetrics.events`.
> - **Tests**:
> - Update controller, hooks, provider, and component tests for new
eligibility shape, geo-block tracking, and attemptedAction args.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
e6d0566. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->1 parent 8f960c8 commit 5b5c76c
23 files changed
Lines changed: 243 additions & 74 deletions
File tree
- app
- components/UI/Predict
- components
- PredictAddFundsSheet
- PredictBalance
- PredictMarketMultiple
- PredictMarketOutcome
- PredictMarketSingle
- PredictPositionDetail
- PredictPositionsHeader
- constants
- controllers
- hooks
- providers
- polymarket
- views/PredictMarketDetails
- core/Analytics
Lines changed: 1 addition & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
372 | 372 | | |
373 | 373 | | |
374 | 374 | | |
| 375 | + | |
375 | 376 | | |
376 | 377 | | |
377 | 378 | | |
| |||
Lines changed: 7 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
| 22 | + | |
22 | 23 | | |
23 | 24 | | |
24 | 25 | | |
| |||
50 | 51 | | |
51 | 52 | | |
52 | 53 | | |
53 | | - | |
54 | | - | |
55 | | - | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
56 | 60 | | |
57 | 61 | | |
58 | 62 | | |
| |||
Lines changed: 7 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
| 38 | + | |
38 | 39 | | |
39 | 40 | | |
40 | 41 | | |
| |||
68 | 69 | | |
69 | 70 | | |
70 | 71 | | |
71 | | - | |
72 | | - | |
73 | | - | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
74 | 78 | | |
75 | 79 | | |
76 | 80 | | |
| |||
Lines changed: 8 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
11 | 19 | | |
12 | 20 | | |
13 | 21 | | |
| |||
Lines changed: 4 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
132 | 132 | | |
133 | 133 | | |
134 | 134 | | |
135 | | - | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
136 | 139 | | |
137 | 140 | | |
138 | 141 | | |
| |||
Lines changed: 8 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
13 | 21 | | |
14 | 22 | | |
15 | 23 | | |
| |||
Lines changed: 4 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
96 | 96 | | |
97 | 97 | | |
98 | 98 | | |
99 | | - | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
100 | 103 | | |
101 | 104 | | |
102 | 105 | | |
| |||
Lines changed: 8 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
19 | 27 | | |
20 | 28 | | |
21 | 29 | | |
| |||
Lines changed: 4 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
176 | 176 | | |
177 | 177 | | |
178 | 178 | | |
179 | | - | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
180 | 183 | | |
181 | 184 | | |
182 | 185 | | |
| |||
Lines changed: 17 additions & 14 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
60 | 60 | | |
61 | 61 | | |
62 | 62 | | |
63 | | - | |
64 | | - | |
65 | | - | |
66 | | - | |
67 | | - | |
68 | | - | |
69 | | - | |
70 | | - | |
71 | | - | |
72 | | - | |
73 | | - | |
74 | | - | |
75 | | - | |
76 | | - | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
77 | 80 | | |
78 | 81 | | |
79 | 82 | | |
| |||
0 commit comments