chore(runway): cherry-pick feat(predict): add Polymarket CLOB v2 support cp-7.73.1#29256
Merged
Merged
Conversation
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. |
This PR implements Polymarket CLOB v2 support for MetaMask Predict (7.74 target), introducing a clean coexistence architecture that keeps v1 working while v2 is toggled on via `predictClobV2`. During the temporary CLOB host migration window, a companion rollout flag (`predictClobV2UseLegacyClobHost`) can force v2 traffic onto the legacy host for internal RC testing without turning v2 on for everyone by default. The implementation is designed with deletion in mind — after the v1/v2 coexistence window, v1 can be cleanly removed with minimal surgery. The work follows the "data-first protocol definition" architecture from the internal plan: a private `protocol/` module owns all version-specific differences and is resolved once at the provider entry point. Lower-level helpers never read feature flags directly. **Key design decisions:** - Primary remote flag: `predictClobV2` (default `false`) enables the v2 protocol - Temporary rollout flag: `predictClobV2UseLegacyClobHost` forces v2 to use `https://clob-v2.polymarket.com` during the migration window - Canonical/default v2 host remains `https://clob.polymarket.com` - Host selection is resolved once into `protocol.transport.clobBaseUrl`; lower-level helpers never read raw flags - v2 API key caching is host-aware to avoid reusing credentials across host changes - v2 `getBalance()` always returns `Safe USDC.e + Safe pUSD` — no branching on upgrade state - Trade path is **relayer-only**, **Permit2-only** fee collection on pUSD, always runs preflight before submission - Canonical v2 allowance requirement set defined once; inspector + compiler are small pure modules - Deposit/withdraw use **release-time code choices** for preferred vs fallback variant — not runtime flags - Wrap: always wraps **entire current Safe USDC.e balance** (never `MaxUint256`) when maintenance tx is already being emitted - Unwrap: always unwraps **exact deficit** needed — no over-unwrapping --- This PR is now structured as 12 focused commits for easier review: Wires the `predictClobV2` boolean through the Predict feature-flag infrastructure: - Adds `predictClobV2Enabled` to `PredictFeatureFlags` interface - Adds `selectPredictClobV2EnabledFlag` selector - Extends `resolvePredictFeatureFlags` to resolve the new flag - Refactors `resolveVersionGatedBooleanFlag` helper to reduce duplication across the existing flag resolution logic - Adds selector and resolver tests Introduces the two new private modules that everything else builds on: **`protocol/`** — data-first protocol definitions: - `definitions.ts`: v1 and v2 `PolymarketProtocolDefinition` objects; protocol resolution; builder code env config (`MM_PREDICT_BUILDER_CODE`); deposit/withdraw execution mode types - `orderCodec.ts`: v2-aware order build, EIP-712 typed data, and relayer payload serialization - `transport.ts`: shared CLOB transport helpers parameterized by protocol endpoint; collapses v1/v2 endpoint differences **`preflight/`** — private readiness inspection and Safe plan construction: - `v2AllowanceRequirements.ts`: canonical declarative list of all 10 v2 allowance requirements (7 ERC-20, 3 ERC-1155) - `inspectMissingRequirements.ts`: reads on-chain state and returns the subset of requirements not yet satisfied - `compileRequirementTransactions.ts`: compiles missing requirements into `SafeTransaction[]` - `core.ts`: shared raw-fact readers, wrap/unwrap builders, and signed Safe execution helpers All modules covered by unit tests. Implements the full v2 trade path inside `PolymarketProvider`: - `preflight/trade.ts`: `planTradePreflight` + `buildTradeAllowancesTx` — inspects missing v2 allowances and Safe USDC.e balance; compiles optional maintenance tx (allowances-only / wrap-only / allowances+wrap / none) - `PolymarketProvider`: `placeOrder` now resolves the protocol once, runs preflight under v2, builds the optional `allowancesTx`, and uses the protocol's `orderCodec` for order construction, EIP-712 signing, and relayer payload - `utils.ts`: adds `encodeWrapUsdceTransaction` and `encodeUnwrapTransaction` helpers (+ tests) - v2 preview keeps `feeRateBps = '0'` until the upstream fee endpoint is confirmed Adds the v2 deposit maintenance planner and wires it into `PolymarketProvider.prepareDeposit`: - `preflight/deposit.ts`: `planDepositMaintenance` + `compileDepositMaintenanceTransactions` — inspects missing v2 allowances and **pre-existing** Safe USDC.e balance (does not incorporate the just-entered deposit amount); emits optional maintenance tx - `PolymarketProvider.prepareDeposit`: under v2, resolves protocol, runs the maintenance planner, and attaches the optional maintenance tx to the deposit plan — the 3-step shape (optional deploy → transfer funding asset → optional maintenance tx) is preserved - Currently wired to the `usdce-transfer` fallback mode; flip `depositMode` in the protocol definition to switch to pUSD-native when that dependency lands Adds the v2 withdraw planner and wires both the `prepareWithdraw` / `signWithdraw` contract: - `preflight/withdraw.ts`: `planWithdraw` — reads missing v2 allowances and Safe USDC.e balance; computes the exact USDC.e deficit; compiles the final MultiSend (allowance repair → exact-deficit unwrap-to-Safe → USDC.e transfer to EOA) - `PolymarketProvider.signWithdraw`: under v2, parses the requested amount from the original template calldata, calls `planWithdraw`, and returns the user-requested amount (not any larger intermediate amount) - `safe/utils.ts`: adds `parseTransactionCalldata` helper to extract the withdraw amount from the stored template - Currently wired to the `usdce-deficit-unwrap` fallback mode; flip `withdrawMode` in the protocol definition to switch to pUSD-native Adds the v2 claim planner and wires it into `PolymarketProvider.claimWinnings`: - `preflight/claim.ts`: `planClaim` — reads EOA USDC.e directly (not Safe balances or provider `getBalance()`); computes `gasStationDeficit = max(0, MIN_GAS_STATION_USDCE_BALANCE - eoaUsdceBalance)`; proactively wraps the **entire current Safe USDC.e balance**; compiles the MultiSend in the required order: (1) missing allowance/operator repair, (2) wrap-all current Safe USDC.e, (3) claim subcalls, (4) optional exact-deficit unwrap to EOA - `PolymarketProvider.claimWinnings`: under v2, resolves protocol and delegates to `planClaim`; uses protocol-owned claim targets (pUSD as collateral) rather than per-position collateral metadata Adds integration-level tests and reorganizes the provider test suite: - `preflight/workflows.test.ts`: end-to-end workflow planner tests covering all four preflight paths (trade, deposit, claim, withdraw) with concrete on-chain mock scenarios — verifies MultiSend ordering invariant (approvals first, then wraps/claims/transfers) - `PolymarketProvider.test.ts`: reorganized into `v1` / `v2` describe blocks; covers protocol routing (flag off → v1, flag on → v2), v2 `getBalance()` aggregation, all four trade preflight outcomes (none / allowances-only / wrap-only / allowances+wrap), deposit/withdraw preferred vs fallback shape, and claim ordering Fixes the v2 order-signing codec so the generated typed data preserves the expected field ordering for EIP-712 signing. Addresses review feedback in the allowance inspector path: - forwards `requirement.tokenAddress` into the ERC-1155 operator approval read instead of relying on a hardcoded contract address - adds assertions proving the token address is propagated correctly through the read path Addresses follow-up review feedback without changing behavior: - makes lower-level transaction compilers private where possible - keeps `getClaimRequirements` exported for focused pure testing - tightens `preflight/workflows.test.ts` to assert through planner entry points instead of private helpers - renames claim gas-top-up terminology for clarity (`MIN_GAS_STATION_USDCE_BALANCE_RAW`, `gasStationDeficit`) while keeping `eoaUsdceBalance` unchanged Adds temporary host-migration plumbing while keeping canonical host resolution centralized: - introduces canonical and legacy CLOB host constants - resolves the selected host into `protocol.transport.clobBaseUrl` - threads the resolved host through API key creation, order-book reads, and preview flow - isolates API key cache entries by protocol + host + address Simplifies the rollout shape for internal RC testing: - replaces the nested raw `predictClobV2.clobBaseUrl` rollout control with a second version-gated boolean flag: `predictClobV2UseLegacyClobHost` - keeps the lower-level host plumbing from commit 11 intact - allows internal RC builds to enable the legacy host remotely while internal users locally toggle `predictClobV2` --- CHANGELOG entry: null Fixes: https://consensyssoftware.atlassian.net/browse/PRED-817 ```gherkin Feature: Polymarket CLOB v2 trading Scenario: user trades with CLOB v2 enabled Given the predictClobV2 remote flag is enabled And the user has an active Polymarket Safe When user places a buy or sell order Then the provider runs preflight and submits any needed allowance/wrap tx first And the order is submitted via the relayer with X-Clob-Version: 2 Scenario: internal RC uses the legacy v2 CLOB host Given the predictClobV2UseLegacyClobHost remote flag is enabled And predictClobV2 is enabled via local override When user places a buy or sell order Then v2 CLOB requests use https://clob-v2.polymarket.com And API keys are cached separately from canonical-host credentials Scenario: user deposits with CLOB v2 enabled Given the predictClobV2 remote flag is enabled When user initiates a deposit Then the deposit plan includes an optional maintenance tx (allowance repair + wrap pre-existing USDC.e) And the newly deposited amount is not included in the wrap Scenario: user withdraws with CLOB v2 enabled Given the predictClobV2 remote flag is enabled And the user requests a specific withdrawal amount When user confirms the withdraw Then only the exact USDC.e deficit is unwrapped (not all pUSD) And the reported amount matches the user-requested amount Scenario: user claims winnings with CLOB v2 enabled Given the predictClobV2 remote flag is enabled And the user has winning positions When user claims Then all missing v2 allowances are repaired first And the entire pre-existing Safe USDC.e is wrapped And claim subcalls execute after And only the exact EOA gas-top-up deficit is unwrapped to USDC.e Scenario: CLOB v2 flag is disabled (v1 behavior) Given the predictClobV2 remote flag is disabled When user interacts with any predict flow Then v1 protocol is used unchanged ``` https://www.loom.com/share/155120bd46c44723a8b838172b4fd45b - [ ] 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). - [ ] I've completed the PR template to the best of my ability - [ ] I've included tests if applicable - [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] 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. - [ ] 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). - [ ] 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] > **High Risk** > High risk because it changes Polymarket trade/claim/deposit/withdraw transaction construction and relayer submission paths, including new Safe preflight logic and EIP-712 signing for a new protocol version. > > **Overview** > Adds **Polymarket CLOB v2** support behind new Predict feature flags (`predictClobV2Enabled` plus optional legacy host override), with host-aware API key caching and endpoint selection. > > Refactors `PolymarketProvider` to resolve a protocol definition once and route **preview + order submission** through a new `protocol/` module (v2 uses new order schema/typed data, zero preview fee rate, and a relayer request header for v2 routing). > > Introduces a `preflight/` layer that inspects on-chain allowance/approval requirements and builds signed Safe executions for **trade allowances**, **deposit maintenance**, **claim**, and **withdraw** (including wrap/unwrap flows and balance aggregation across USDC.e + pUSD for v2). Adds extensive unit/integration tests and updates Safe utilities (raw USDC amount decoding, token-address-aware approvals, Permit2 token selection) plus CI env wiring for `MM_PREDICT_BUILDER_CODE`. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 5df04d5. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> (cherry picked from commit d9f38f2)
0cfa90a to
94bec69
Compare
Contributor
🔍 Smart E2E Test Selection⏭️ Smart E2E selection skipped - skip-smart-e2e-selection label found All E2E tests pre-selected. |
|
Contributor
|
✅ E2E Fixture Validation — Schema is up to date |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Description
This PR implements Polymarket CLOB v2 support for MetaMask Predict (7.74
target), introducing a clean coexistence architecture that keeps v1
working while v2 is toggled on via
predictClobV2. During the temporaryCLOB host migration window, a companion rollout flag
(
predictClobV2UseLegacyClobHost) can force v2 traffic onto the legacyhost for internal RC testing without turning v2 on for everyone by
default.
The implementation is designed with deletion in mind — after the v1/v2
coexistence window, v1 can be cleanly removed with minimal surgery. The
work follows the "data-first protocol definition" architecture from the
internal plan: a private
protocol/module owns all version-specificdifferences and is resolved once at the provider entry point.
Lower-level helpers never read feature flags directly.
Key design decisions:
predictClobV2(defaultfalse) enables the v2protocol
predictClobV2UseLegacyClobHostforces v2 touse
https://clob-v2.polymarket.comduring the migration windowhttps://clob.polymarket.comprotocol.transport.clobBaseUrl;lower-level helpers never read raw flags
host changes
getBalance()always returnsSafe USDC.e + Safe pUSD— nobranching on upgrade state
pUSD, always runs preflight before submission
compiler are small pure modules
fallback variant — not runtime flags
MaxUint256) when maintenance tx is already being emittedCommits
This PR is now structured as 12 focused commits for easier review:
1.
feat(predict): add CLOB v2 feature flag plumbingWires the
predictClobV2boolean through the Predict feature-flaginfrastructure:
predictClobV2EnabledtoPredictFeatureFlagsinterfaceselectPredictClobV2EnabledFlagselectorresolvePredictFeatureFlagsto resolve the new flagresolveVersionGatedBooleanFlaghelper to reduceduplication across the existing flag resolution logic
2.
feat(predict): add CLOB v2 protocol and preflight foundationIntroduces the two new private modules that everything else builds on:
protocol/— data-first protocol definitions:definitions.ts: v1 and v2PolymarketProtocolDefinitionobjects;protocol resolution; builder code env config
(
MM_PREDICT_BUILDER_CODE); deposit/withdraw execution mode typesorderCodec.ts: v2-aware order build, EIP-712 typed data, and relayerpayload serialization
transport.ts: shared CLOB transport helpers parameterized byprotocol endpoint; collapses v1/v2 endpoint differences
preflight/— private readiness inspection and Safe planconstruction:
v2AllowanceRequirements.ts: canonical declarative list of all 10 v2allowance requirements (7 ERC-20, 3 ERC-1155)
inspectMissingRequirements.ts: reads on-chain state and returns thesubset of requirements not yet satisfied
compileRequirementTransactions.ts: compiles missing requirementsinto
SafeTransaction[]core.ts: shared raw-fact readers, wrap/unwrap builders, and signedSafe execution helpers
All modules covered by unit tests.
3.
feat(predict): add CLOB v2 buy and sell flowImplements the full v2 trade path inside
PolymarketProvider:preflight/trade.ts:planTradePreflight+buildTradeAllowancesTx— inspects missing v2 allowances and Safe USDC.e balance; compiles
optional maintenance tx (allowances-only / wrap-only / allowances+wrap /
none)
PolymarketProvider:placeOrdernow resolves the protocol once,runs preflight under v2, builds the optional
allowancesTx, and usesthe protocol's
orderCodecfor order construction, EIP-712 signing, andrelayer payload
utils.ts: addsencodeWrapUsdceTransactionandencodeUnwrapTransactionhelpers (+ tests)feeRateBps = '0'until the upstream fee endpoint isconfirmed
4.
feat(predict): add CLOB v2 deposit flowAdds the v2 deposit maintenance planner and wires it into
PolymarketProvider.prepareDeposit:preflight/deposit.ts:planDepositMaintenance+compileDepositMaintenanceTransactions— inspects missing v2 allowancesand pre-existing Safe USDC.e balance (does not incorporate the
just-entered deposit amount); emits optional maintenance tx
PolymarketProvider.prepareDeposit: under v2, resolves protocol, runsthe maintenance planner, and attaches the optional maintenance tx to the
deposit plan — the 3-step shape (optional deploy → transfer funding
asset → optional maintenance tx) is preserved
usdce-transferfallback mode; flipdepositModein the protocol definition to switch to pUSD-native whenthat dependency lands
5.
feat(predict): add CLOB v2 withdraw flowAdds the v2 withdraw planner and wires both the
prepareWithdraw/signWithdrawcontract:preflight/withdraw.ts:planWithdraw— reads missing v2 allowancesand Safe USDC.e balance; computes the exact USDC.e deficit; compiles the
final MultiSend (allowance repair → exact-deficit unwrap-to-Safe →
USDC.e transfer to EOA)
PolymarketProvider.signWithdraw: under v2, parses the requestedamount from the original template calldata, calls
planWithdraw, andreturns the user-requested amount (not any larger intermediate amount)
safe/utils.ts: addsparseTransactionCalldatahelper to extract thewithdraw amount from the stored template
usdce-deficit-unwrapfallback mode; flipwithdrawModein the protocol definition to switch to pUSD-native6.
feat(predict): add CLOB v2 claim flowAdds the v2 claim planner and wires it into
PolymarketProvider.claimWinnings:preflight/claim.ts:planClaim— reads EOA USDC.e directly (notSafe balances or provider
getBalance()); computesgasStationDeficit = max(0, MIN_GAS_STATION_USDCE_BALANCE - eoaUsdceBalance); proactivelywraps the entire current Safe USDC.e balance; compiles the MultiSend
in the required order: (1) missing allowance/operator repair, (2)
wrap-all current Safe USDC.e, (3) claim subcalls, (4) optional
exact-deficit unwrap to EOA
PolymarketProvider.claimWinnings: under v2, resolves protocol anddelegates to
planClaim; uses protocol-owned claim targets (pUSD ascollateral) rather than per-position collateral metadata
7.
test(predict): add CLOB v2 integration coverageAdds integration-level tests and reorganizes the provider test suite:
preflight/workflows.test.ts: end-to-end workflow planner testscovering all four preflight paths (trade, deposit, claim, withdraw) with
concrete on-chain mock scenarios — verifies MultiSend ordering invariant
(approvals first, then wraps/claims/transfers)
PolymarketProvider.test.ts: reorganized intov1/v2describeblocks; covers protocol routing (flag off → v1, flag on → v2), v2
getBalance()aggregation, all four trade preflight outcomes (none /allowances-only / wrap-only / allowances+wrap), deposit/withdraw
preferred vs fallback shape, and claim ordering
8.
fix: preserve EIP-712 field order in buildProtocolUnsignedOrderFixes the v2 order-signing codec so the generated typed data preserves
the expected field ordering for EIP-712 signing.
9.
codex: address PR review feedback (#29076)Addresses review feedback in the allowance inspector path:
requirement.tokenAddressinto the ERC-1155 operatorapproval read instead of relying on a hardcoded contract address
through the read path
10.
refactor(predict): tighten CLOB v2 preflight workflow seamsAddresses follow-up review feedback without changing behavior:
getClaimRequirementsexported for focused pure testingpreflight/workflows.test.tsto assert through planner entrypoints instead of private helpers
(
MIN_GAS_STATION_USDCE_BALANCE_RAW,gasStationDeficit) while keepingeoaUsdceBalanceunchanged11.
feat(predict): add configurable CLOB v2 host overrideAdds temporary host-migration plumbing while keeping canonical host
resolution centralized:
protocol.transport.clobBaseUrland preview flow
12.
refactor(predict): gate legacy CLOB v2 host with boolean flagSimplifies the rollout shape for internal RC testing:
predictClobV2.clobBaseUrlrollout controlwith a second version-gated boolean flag:
predictClobV2UseLegacyClobHostinternal users locally toggle
predictClobV2Changelog
CHANGELOG entry: null
Related issues
Fixes: https://consensyssoftware.atlassian.net/browse/PRED-817
Manual testing steps
Screenshots/Recordings
https://www.loom.com/share/155120bd46c44723a8b838172b4fd45b
Pre-merge author checklist
Docs and MetaMask Mobile
Coding
Standards.
if applicable
guidelines).
Not required for external contributors.
Performance checks (if applicable)
SRPs
to import wallets with many accounts and tokens
performance metrics
trace()for usage andaddTokenfor an example
For performance guidelines and tooling, see the Performance
Guide.
Pre-merge reviewer checklist
app, test code being changed).
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
Note
High Risk
High risk because it changes Polymarket trade/claim/deposit/withdraw
transaction construction and relayer submission paths, including new
Safe preflight logic and EIP-712 signing for a new protocol version.
Overview
Adds Polymarket CLOB v2 support behind new Predict feature flags
(
predictClobV2Enabledplus optional legacy host override), withhost-aware API key caching and endpoint selection.
Refactors
PolymarketProviderto resolve a protocol definition onceand route preview + order submission through a new
protocol/module (v2 uses new order schema/typed data, zero preview fee rate, and
a relayer request header for v2 routing).
Introduces a
preflight/layer that inspects on-chainallowance/approval requirements and builds signed Safe executions for
trade allowances, deposit maintenance, claim, and
withdraw (including wrap/unwrap flows and balance aggregation across
USDC.e + pUSD for v2). Adds extensive unit/integration tests and updates
Safe utilities (raw USDC amount decoding, token-address-aware approvals,
Permit2 token selection) plus CI env wiring for
MM_PREDICT_BUILDER_CODE.Reviewed by Cursor Bugbot for commit
5df04d5. Bugbot is set up for automated
code reviews on this repo. Configure
here.