Skip to content

Commit bf54b9b

Browse files
matalluicaieu
andauthored
feat(predict): remove CLOB v1 support and migrate to pUSD cp-7.76.0 (#29451)
## **Description** This PR removes legacy Predict Polymarket CLOB v1 support and completes the pUSD migration in Predict-owned code. It includes: - CLOB v2 as the unconditional Polymarket protocol path. - Removal of CLOB v1 protocol selection, order codec branches, and v1 Safe helper wrappers. - pUSD as the canonical Predict trading, fee, deposit, withdraw, reward-fee, and claim gas top-up token. - Legacy Safe USDC.e retained only as temporary hidden sweep state. - Legacy USDC.e → pUSD sweep preflight for deposit, withdraw, trade, and claim operations. - Displayed Predict balance as total recoverable Predict funds (`pUSD + legacy USDC.e`). - In-memory cache to stop refetching legacy USDC.e balance for a Safe after a zero balance is observed. Stack note: this PR is stacked on the confirmations-only pUSD PR so the diff stays limited to Predict-owned files. ## **Changelog** CHANGELOG entry: Updated Predict to use pUSD and the Polymarket CLOB v2 protocol. ## **Related issues** Fixes: [PRED-851](https://consensyssoftware.atlassian.net/browse/PRED-851) , [PRED-852](https://consensyssoftware.atlassian.net/browse/PRED-852) ## **Manual testing steps** ```gherkin Feature: Predict pUSD migration Scenario: user with pUSD places a Predict trade Given the user has a funded Predict Safe with pUSD When the user places a Predict trade Then the order is submitted through the CLOB v2 path And pUSD is used for trading and fee authorization Scenario: legacy user performs their first Predict operation Given the user has legacy USDC.e in their Predict Safe When the user deposits, withdraws, trades, or claims Then the Safe preflight wraps legacy USDC.e into pUSD before the main operation ``` ## **Screenshots/Recordings** ### **Before** N/A — code-only protocol migration PR; no screenshots captured. ### **After** N/A — code-only protocol migration PR; no screenshots captured. ## **Testing** - `yarn lint:tsc` - `yarn jest app/components/UI/Predict/providers/polymarket/protocol/definitions.test.ts app/components/UI/Predict/providers/polymarket/protocol/orderCodec.test.ts app/components/UI/Predict/providers/polymarket/protocol/transport.test.ts app/components/UI/Predict/providers/polymarket/preflight/v2AllowanceRequirements.test.ts app/components/UI/Predict/providers/polymarket/preflight/withdraw.test.ts app/components/UI/Predict/providers/polymarket/preflight/workflows.test.ts app/components/UI/Predict/providers/polymarket/PolymarketProvider.test.ts app/components/UI/Predict/controllers/PredictController.test.ts app/components/UI/Predict/hooks/usePredictBalanceTokenFilter.test.ts app/components/UI/Predict/hooks/usePredictRewards.test.ts app/components/UI/Predict/views/PredictBuyWithAnyToken/components/PredictPayWithRow/PredictPayWithRow.test.tsx --runInBand --forceExit` ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. #### Performance checks (if applicable) - [ ] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [ ] I've tested with a power user scenario - Use these [power-user SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93) to import wallets with many accounts and tokens - [ ] I've instrumented key operations with Sentry traces for production performance metrics - See [`trace()`](/app/util/trace.ts) for usage and [`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274) for an example For performance guidelines and tooling, see the [Performance Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers). ## **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. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Updates on-chain token identifiers/addresses used for Predict gas fee and rewards calculations, which could impact transactions if misconfigured. Changes are localized to Predict hooks/controller and accompanying tests. > > **Overview** > Predict now consistently references the **pUSD / CLOB v2 collateral** across Predict-owned code: `PredictController` uses `MATIC_CONTRACTS_V2.collateral` as the `gasFeeToken` for claim/withdraw `addTransactionBatch`, `usePredictBalanceTokenFilter` displays the Predict balance row using the `POLYGON_PUSD` token (icon + symbol), and `usePredictRewards` estimates points using `POLYGON_PUSD_CAIP_ASSET_ID`. > > Cleans up account-state handling by removing `hasAllowances` expectations/tests and updating `usePredictAccountState` docs to reflect the slimmer `AccountState` shape. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 7dfda61. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> [PRED-851]: https://consensyssoftware.atlassian.net/browse/PRED-851?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --------- Co-authored-by: Caainã Jeronimo <caainaje@gmail.com>
1 parent f0c0bf8 commit bf54b9b

44 files changed

Lines changed: 1162 additions & 18017 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app/components/UI/Predict/controllers/PredictController.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5721,7 +5721,6 @@ describe('PredictController', () => {
57215721
const mockAccountState = {
57225722
address: '0xProxyAddress' as `0x${string}`,
57235723
isDeployed: true,
5724-
hasAllowances: true,
57255724
balance: 100.5,
57265725
};
57275726

app/components/UI/Predict/controllers/PredictController.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ import { GEO_BLOCKED_COUNTRIES } from '../constants/geoblock';
5757
import { PREDICT_BALANCE_PLACEHOLDER_ADDRESS } from '../constants/transactions';
5858
import { PolymarketProvider } from '../providers/polymarket/PolymarketProvider';
5959
import {
60-
MATIC_CONTRACTS,
60+
MATIC_CONTRACTS_V2,
6161
POLYMARKET_PROVIDER_ID,
6262
} from '../providers/polymarket/constants';
6363
import { Signer } from '../providers/types';
@@ -1453,7 +1453,7 @@ export class PredictController extends BaseController<
14531453
disableHook: true,
14541454
disableSequential: true,
14551455
// Temporarily breaking abstraction, can instead be abstracted via provider.
1456-
gasFeeToken: MATIC_CONTRACTS.collateral as Hex,
1456+
gasFeeToken: MATIC_CONTRACTS_V2.collateral as Hex,
14571457
transactions,
14581458
});
14591459

@@ -2564,7 +2564,7 @@ export class PredictController extends BaseController<
25642564
disableSequential: true,
25652565
requireApproval: true,
25662566
// Temporarily breaking abstraction, can instead be abstracted via provider.
2567-
gasFeeToken: MATIC_CONTRACTS.collateral as Hex,
2567+
gasFeeToken: MATIC_CONTRACTS_V2.collateral as Hex,
25682568
transactions: [transaction],
25692569
});
25702570

app/components/UI/Predict/hooks/usePredictAccountState.test.ts

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ describe('usePredictAccountState', () => {
6363
const mockAccountState = {
6464
address: '0x1234567890abcdef1234567890abcdef12345678',
6565
isDeployed: true,
66-
hasAllowances: true,
6766
};
6867

6968
beforeEach(() => {
@@ -117,7 +116,6 @@ describe('usePredictAccountState', () => {
117116
expect(mockGetAccountState).toHaveBeenCalledWith({});
118117
expect(result.current.data?.address).toEqual(mockAccountState.address);
119118
expect(result.current.data?.isDeployed).toBe(true);
120-
expect(result.current.data?.hasAllowances).toBe(true);
121119
expect(result.current.error).toBeNull();
122120
});
123121

@@ -215,24 +213,6 @@ describe('usePredictAccountState', () => {
215213
expect(result.current.data?.isDeployed).toBe(false);
216214
});
217215

218-
it('returns hasAllowances as false when account lacks allowances', async () => {
219-
const { Wrapper } = createWrapper();
220-
mockGetAccountState.mockResolvedValue({
221-
...mockAccountState,
222-
hasAllowances: false,
223-
});
224-
225-
const { result } = renderHook(() => usePredictAccountState(), {
226-
wrapper: Wrapper,
227-
});
228-
229-
await waitFor(() => {
230-
expect(result.current.data).toBeDefined();
231-
});
232-
233-
expect(result.current.data?.hasAllowances).toBe(false);
234-
});
235-
236216
it('has undefined data when query is disabled', () => {
237217
const { Wrapper } = createWrapper();
238218
const { result } = renderHook(

app/components/UI/Predict/hooks/usePredictAccountState.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ interface UsePredictAccountStateOptions {
1515
}
1616

1717
/**
18-
* Fetches the Predict account state (address, deployment status, allowances).
18+
* Fetches the Predict account state (address and deployment status).
1919
*/
2020
export function usePredictAccountState(
2121
options: UsePredictAccountStateOptions = {},

app/components/UI/Predict/hooks/usePredictBalanceTokenFilter.test.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,6 @@ jest.mock('../../../Views/confirmations/utils/transaction', () => ({
5252
hasTransactionType: jest.fn(),
5353
}));
5454

55-
jest.mock('../../../../util/networks', () => ({
56-
getNetworkImageSource: jest.fn(() => 'polygon-network-badge'),
57-
}));
58-
5955
const mockHasTransactionType = hasTransactionType as jest.MockedFunction<
6056
typeof hasTransactionType
6157
>;
@@ -85,7 +81,7 @@ describe('usePredictBalanceTokenFilter', () => {
8581
mockPredictBalance = 100;
8682
mockTransactionMeta = null;
8783
mockHasTransactionType.mockReturnValue(false);
88-
mockUseSelector.mockReturnValue({ image: 'usdce-token-image' });
84+
mockUseSelector.mockReturnValue({ image: 'pusd-token-image' });
8985
mockNavigate.mockReset();
9086
});
9187

@@ -165,19 +161,19 @@ describe('usePredictBalanceTokenFilter', () => {
165161
expect((filteredTokens[0] as HighlightedItem).fiat).toBe('$42.50');
166162
});
167163

168-
it('shows name_description as USDC.e on the Predict balance row', () => {
164+
it('shows name_description as pUSD on the Predict balance row', () => {
169165
mockHasTransactionType.mockReturnValue(true);
170166
const tokens = [createMockToken()];
171167

172168
const { result } = renderHook(() => usePredictBalanceTokenFilter());
173169
const filteredTokens = result.current(tokens);
174170

175171
expect((filteredTokens[0] as HighlightedItem).name_description).toBe(
176-
'USDC.e',
172+
'pUSD',
177173
);
178174
});
179175

180-
it('uses empty string for icon when usdceToken is null', () => {
176+
it('uses empty string for icon when pusdToken is null', () => {
181177
mockHasTransactionType.mockReturnValue(true);
182178
mockUseSelector.mockReturnValue(null);
183179
const tokens = [createMockToken()];

app/components/UI/Predict/hooks/usePredictBalanceTokenFilter.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1+
import { TransactionType } from '@metamask/transaction-controller';
12
import { BigNumber } from 'bignumber.js';
23
import { useCallback } from 'react';
34
import { useNavigation } from '@react-navigation/native';
45
import { useSelector } from 'react-redux';
6+
import { strings } from '../../../../../locales/i18n';
7+
import Routes from '../../../../constants/navigation/Routes';
58
import { RootState } from '../../../../reducers';
69
import { selectSingleTokenByAddressAndChainId } from '../../../../selectors/tokensController';
710
import useFiatFormatter from '../../SimulationDetails/FiatDisplay/useFiatFormatter';
8-
import { POLYGON_USDCE } from '../../../Views/confirmations/constants/predict';
9-
import { TransactionType } from '@metamask/transaction-controller';
11+
import { POLYGON_PUSD } from '../../../Views/confirmations/constants/predict';
1012
import { useTransactionMetadataRequest } from '../../../Views/confirmations/hooks/transactions/useTransactionMetadataRequest';
1113
import {
1214
AssetType,
@@ -17,8 +19,6 @@ import { hasTransactionType } from '../../../Views/confirmations/utils/transacti
1719
import { PREDICT_BALANCE_CHAIN_ID } from '../constants/transactions';
1820
import { usePredictBalance } from './usePredictBalance';
1921
import { usePredictPaymentToken } from './usePredictPaymentToken';
20-
import { strings } from '../../../../../locales/i18n';
21-
import Routes from '../../../../constants/navigation/Routes';
2222

2323
export function usePredictBalanceTokenFilter(
2424
forceEnabled = false,
@@ -29,10 +29,10 @@ export function usePredictBalanceTokenFilter(
2929
const { isPredictBalanceSelected } = usePredictPaymentToken();
3030
const { data: predictBalance = 0 } = usePredictBalance();
3131
const formatFiat = useFiatFormatter({ currency: 'usd' });
32-
const usdceToken = useSelector((state: RootState) =>
32+
const pusdToken = useSelector((state: RootState) =>
3333
selectSingleTokenByAddressAndChainId(
3434
state,
35-
POLYGON_USDCE.address,
35+
POLYGON_PUSD.address,
3636
PREDICT_BALANCE_CHAIN_ID,
3737
),
3838
);
@@ -60,9 +60,9 @@ export function usePredictBalanceTokenFilter(
6060

6161
const predictBalanceHighlightedItem: HighlightedItem = {
6262
position: 'in_asset_list',
63-
icon: usdceToken?.image ?? '',
63+
icon: pusdToken?.image ?? '',
6464
name: strings('predict.payment.predict_balance'),
65-
name_description: POLYGON_USDCE.symbol,
65+
name_description: POLYGON_PUSD.symbol,
6666
fiat: balanceFormatted,
6767
isSelected: isPredictBalanceSelected,
6868
action: onSelect ?? (() => undefined),
@@ -90,7 +90,7 @@ export function usePredictBalanceTokenFilter(
9090
isPredictBalanceSelected,
9191
predictBalance,
9292
formatFiat,
93-
usdceToken,
93+
pusdToken,
9494
handleAddFunds,
9595
onSelect,
9696
],

app/components/UI/Predict/hooks/usePredictRewards.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import Logger from '../../../../util/Logger';
77
import { getFormattedAddressFromInternalAccount } from '../../../../core/Multichain/utils';
88
import {
99
POLYGON_MAINNET_CAIP_CHAIN_ID,
10-
POLYGON_USDC_CAIP_ASSET_ID,
10+
POLYGON_PUSD_CAIP_ASSET_ID,
1111
} from '../providers/polymarket/constants';
1212

1313
jest.mock('react-redux', () => ({
@@ -47,8 +47,8 @@ jest.mock('../constants/errors', () => ({
4747

4848
jest.mock('../providers/polymarket/constants', () => ({
4949
POLYGON_MAINNET_CAIP_CHAIN_ID: 'eip155:137',
50-
POLYGON_USDC_CAIP_ASSET_ID:
51-
'eip155:137/erc20:0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174',
50+
POLYGON_PUSD_CAIP_ASSET_ID:
51+
'eip155:137/erc20:0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB',
5252
COLLATERAL_TOKEN_DECIMALS: 6,
5353
}));
5454

@@ -185,7 +185,7 @@ describe('usePredictRewards', () => {
185185
activityContext: {
186186
predictContext: {
187187
feeAsset: {
188-
id: POLYGON_USDC_CAIP_ASSET_ID,
188+
id: POLYGON_PUSD_CAIP_ASSET_ID,
189189
amount: expect.any(String),
190190
},
191191
},

app/components/UI/Predict/hooks/usePredictRewards.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { selectSelectedInternalAccountByScope } from '../../../../selectors/mult
1717
import { getFormattedAddressFromInternalAccount } from '../../../../core/Multichain/utils';
1818
import {
1919
POLYGON_MAINNET_CAIP_CHAIN_ID,
20-
POLYGON_USDC_CAIP_ASSET_ID,
20+
POLYGON_PUSD_CAIP_ASSET_ID,
2121
COLLATERAL_TOKEN_DECIMALS,
2222
} from '../providers/polymarket/constants';
2323
import { parseUnits } from 'ethers/lib/utils';
@@ -186,9 +186,9 @@ export const usePredictRewards = (
186186
}
187187

188188
// Prepare fee asset
189-
// Convert USD amount to atomic units (6 decimals for USDC)
189+
// Convert USD amount to atomic units (6 decimals for pUSD)
190190
const feeAsset: EstimateAssetDto = {
191-
id: POLYGON_USDC_CAIP_ASSET_ID,
191+
id: POLYGON_PUSD_CAIP_ASSET_ID,
192192
amount: parseUnits(
193193
totalFeeAmountUsd.toString(),
194194
COLLATERAL_TOKEN_DECIMALS,

0 commit comments

Comments
 (0)