feat(transaction-pay-controller): add PolymarketBridgeStrategy for deposit-wallet predictWithdraw#8754
Draft
matthewwalsh0 wants to merge 14 commits into
Draft
feat(transaction-pay-controller): add PolymarketBridgeStrategy for deposit-wallet predictWithdraw#8754matthewwalsh0 wants to merge 14 commits into
matthewwalsh0 wants to merge 14 commits into
Conversation
…rategy Adds PolymarketBridgeStrategy for predictWithdraw transactions of deposit-wallet users. Routes withdrawals through Polymarket's Bridge API (quote + one-shot deposit address) and Relayer API (signed WALLET batch dispatch). Gated behind payPolymarketBridgeWithdrawEnabled feature flag. Legacy Safe users continue to use the Relay strategy. User-facing flow: one EIP-712 signature, no on-chain transaction from the user's EOA, zero gas paid (Polymarket's relayer covers it), ~25s end-to-end. Mobile-side adoption ships in feat/polymarket-bridge-withdraw-adopt.
- Fix relayer auth: use request.from for RELAYER_API_KEY_ADDRESS header - Remove address from RelayerApiKeyCredentials (derived from from) - Fix nonce query: use EOA address instead of deposit wallet address - Add bridge status polling (pollUntilBridgeComplete) for target-side tracking - Set metamaskPay.sourceHash and isIntentComplete in execute flow - Add deposit-wallet address computation in core (computeDepositWalletAddress) - Add DEPOSIT_WALLET_IMPLEMENTATION_POLYGON constant
…ath to Relay strategy Add isPolymarketDepositWallet config flag to route Relay deposit transactions through the Polymarket gasless relayer. When set, the Relay strategy submits approve+deposit calls as a deposit-wallet Batch via the Polymarket relayer instead of TransactionController or Relay /execute. - Add isPolymarketDepositWallet to TransactionConfig, TransactionData, QuoteRequest - Propagate flag through quotes.ts and source-amounts.ts - Add getPolymarketBridgeOptions getter for cross-strategy credential access - Create polymarket-bridge/index.ts barrel for primitive reuse - Create relay/submit-polymarket-relayer.ts orchestration function - Add third branch in executeSingleQuote with mutual-exclusivity guard - Suppress originGasOverhead when isPolymarketDepositWallet is set
…osit-wallet Relay path The Polymarket gasless relayer pays source-chain gas, so the user owes nothing. Extend the existing Hyperliquid zero-fee guard in calculateSourceNetworkCost to also cover isPolymarketDepositWallet.
10 tasks
…ission path to Relay strategy" This reverts commit 2afa11a.
… PolymarketBridgeStrategy The strategy now talks to the Polymarket relayer through a URL that is read from the remote feature flag at request time. Authentication is handled out-of-band by the configured endpoint, so the controller no longer accepts or stores any relayer credentials. - Add getPolymarketRelayerUrl feature-flag accessor with prod default - Drop polymarketBridgeOptions controller constructor option - Drop PolymarketBridgeStrategyOptions, *Input, RelayerCredentials, and related auth types - Drop HMAC / API-key header construction from PolymarketRelayerApi; it now takes a base URL and nothing else - Pin PolymarketBridgeApi to the prod URL (preprod URL no longer used) - Drop preprod URL constants
… flag + envelope-based relayer transport
Convert PolymarketRelayerApi to the MetaMask Polymarket relayer-proxy
envelope contract: a single POST to /transaction with a
{ path, method, body|query } body. The proxy authenticates and forwards
to the underlying Polymarket relayer, so the controller carries no
credentials.
Add isPolymarketDepositWallet on TransactionConfig (mirrors the
isHyperliquidSource pattern). When set, the controller routes the
transaction to PolymarketBridgeStrategy and the post-quote source-amount
calculation no longer dedupes same-token-same-chain (the strategy
renormalizes the source to the on-chain deposit wallet).
- Default proxy URL constant POLYMARKET_RELAYER_PROXY_URL_PROD
- Drop unused raw-relayer URL constants
- Propagate flag through quotes.ts and source-amounts.ts
…rce and target amounts Calculate provider fees from the bridge quote's estFeeBreakdown (gasUsd + appFeeUsd + swapImpactUsd) and convert to fiat via the source-token fiat rate. Populate sourceAmount and targetAmount with fiat and USD values from token rates so the confirmation surfaces meaningful amounts instead of zeros.
…plete at execute start The 7702 batch wrapper transaction created by addTransactionBatch is never broadcast on-chain — PolymarketBridgeStrategy intercepts the publish hook and submits an off-chain envelope to the relayer, which in turn broadcasts a separate transaction signed by the relayer. Without isIntentComplete set, PendingTransactionTracker.#checkTransaction runs against the wrapper, finds no hash, and fails the transaction with NoTxHashError — even though the user's pUSD has been bridged successfully. Set isIntentComplete at the start of execute() so the wrapper is treated as confirmed by the tracker. Drop the post-submit pollUntilBridgeComplete because target-side bridge completion is independent of source-side success and does not gate the user's wrapper transaction status.
…t bridge withdraw Behind a hardcoded USE_RELAY_BRIDGE flag, the Polymarket bridge strategy now fetches a Relay quote (pUSD on Polygon -> target chain/token) at quote time and executes in two steps: 1. Transfer pUSD from the deposit wallet to the user EOA via the existing Polymarket relayer proxy (single ERC-20 transfer batch). 2. Submit the stored Relay quote from the user EOA via submitRelayQuotes, which polls Relay status and returns the destination tx hash. Synthetic QuoteRequest sets isPostQuote: true so submitRelayQuotes skips its own source-balance validation (the EOA is funded by Step 1 and the chain RPC may lag); txParams.to is stripped on the relayed transaction so the post-quote prepend stays disabled. Flag toggles to false to fall back to the original Polymarket bridge flow.
…y deposit address with USDC.e sweep The deposit wallet now unwraps pUSD directly into USDC.e at Relay's one-shot deposit address in a single relayer-broadcast batch (approve + unwrap). After Relay settles, the deposit wallet's USDC.e balance is read live from RPC and any remainder is wrapped back into pUSD via the CollateralOnramp, preserving the deposit-wallet pUSD invariant on partial fills, refunds, or solver failures. Implementation notes: - New flags USE_RELAY_DEPOSIT_ADDRESS + FORCE_SKIP_RELAY_POLL gate the new flow and the in-flight test shortcut. - Relay status poll now treats 'refund' as in-flight (the refund tx has not yet confirmed); only 'refunded'/'failure'/'success' are terminal. - submitWithBusyRetry retries the wrap submission when the Polymarket relayer reports the deposit wallet still has an active action; the retry matches against the message text, not a typed error class, so it catches both proxy-wrapped and direct relayer errors. - relayer-api now reads the JSON body on non-OK HTTP responses and surfaces the relayer's actual 'error'/'message' field, so callers can branch on the real reason instead of a generic status-code wrapper.
…uotes + submit modules
Reorganises strategy/polymarket-bridge/ to match the layout of the other
strategies (across, relay) and removes the dead code from the prior
experimental phases:
- PolymarketBridgeStrategy class renamed to PolymarketStrategy, file
renamed to match. Class shrinks from 945 to 43 lines and delegates to
the two new modules.
- New polymarket-quotes.ts owns Relay quote fetch + TransactionPayQuote
normalisation.
- New polymarket-submit.ts owns the deposit-wallet batched approve +
unwrap to Relay's deposit address, Relay status polling, the USDC.e
sweep (approve + wrap back to pUSD), the deposit-wallet batch transport
with EIP-712 signing + relayer submission, and the 'wallet busy' retry.
- New polymarket-calldata.ts owns the ABI encoders for approve, unwrap,
wrap, and the ERC-20 transfer-recipient extractor.
- Removed bridge-api.ts (no longer needed - Polymarket's own bridge API
is no longer called).
- Removed withdraw.ts (its surface moved into polymarket-submit.ts as
submitDepositWalletBatch with retry collapsed into the same function).
- Removed dead flags USE_RELAY_BRIDGE, USE_RELAY_DEPOSIT_ADDRESS,
FORCE_SKIP_RELAY_POLL and the legacy execute branches they gated.
- PolymarketBridgeQuote slimmed to { relayQuote } - the wrapper exists
only so the strategy can carry a typed quote through the controller.
…withdraw into Relay strategy
The Polymarket withdraw flow is now a flavour of the Relay strategy,
matching the HyperLiquid precedent. The standalone PolymarketStrategy
class and its TransactionPayStrategy enum entry are removed in favour of
an isPolymarketDepositWallet branch in the existing Relay quote and
submit pipelines.
Why: 90% of the previous strategy duplicated work the Relay strategy
already does (quote fetch, response normalisation, status polling,
destination tx hash extraction). The only Polymarket-specific parts are
the deposit-wallet transport (EIP-712 batch via the Polymarket relayer
proxy), the pUSD <-> USDC.e conversion (unwrap pre-deposit, wrap-back
sweep on completion), and the useDepositAddress=true Relay request shape.
Structure:
- strategy/relay/polymarket/ holds all Polymarket-specific code:
- withdraw.ts orchestrates the approve+unwrap source-leg batch, the
USDC.e sweep helper run after Relay completion, and the deposit
wallet batch transport with wallet-busy retry.
- quotes.ts builds the USDC.e + useDepositAddress=true Relay quote
body.
- calldata.ts, constants.ts, deposit-wallet.ts, relayer-api.ts,
types.ts, wallet-batch-typed-data.ts host the supporting primitives.
- relay-quotes.ts branches on isPolymarketDepositWallet when building
the Relay quote body and skips the contract-call embedding step
(Polymarket sends a bare token transfer to the deposit address).
- relay-submit.ts branches on isPolymarketDepositWallet to call
submitPolymarketDepositWalletWithdraw, then runs waitForRelayCompletion
(tolerating refund-failure to allow sweep recovery), then
sweepPolymarketDepositWalletUsdce.
- TransactionPayController short-circuit for isPolymarketDepositWallet
now returns Relay instead of the removed PolymarketBridge.
Removed:
- strategy/polymarket-bridge/ directory entirely (8 files).
- TransactionPayStrategy.PolymarketBridge enum entry.
- PolymarketStrategy class and registration.
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Explanation
Polymarket users whose USD balance lives in a deposit wallet (a deterministic per-user batch contract on Polygon) currently have no way to withdraw cross-chain via Pay. The deposit wallet is owned by the user's EOA but is not the EOA itself, so the existing Relay strategy — which submits source-chain transactions from the EOA — cannot move pUSD out of it without a manual transfer first.
This PR adds a
PolymarketBridgeStrategythat handlespredictWithdrawfor deposit-wallet users by orchestrating Polymarket's Bridge and Relayer APIs end-to-end:Batch(approve + transfer) that moves pUSD from the deposit wallet to the bridge deposit address.sourceHashand the strategy return value.Configuration model
The strategy talks to the relayer through the existing MetaMask Polymarket relayer proxy contract — a single POST to
/transactionwhose body is{ path, method, body|query }. The proxy authenticates the request and forwards it to the underlying Polymarket relayer. As a result, the controller carries no relayer credentials.The proxy base URL defaults to
https://predict.api.cx.metamask.ioand can be overridden by thepolymarketRelayerUrlfield on theconfirmations_payremote feature flag, so the proxy host can be swapped without a controller release.Strategy routing
A new
isPolymarketDepositWalletflag is added toTransactionConfig, mirroring the existingisHyperliquidSourceflag. When set viasetTransactionConfig, the controller routes the transaction's quotes and execution toPolymarketBridgeStrategyregardless of the host's normal strategy ordering. The flag also propagates intoQuoteRequestand exempts the affected source-amount entries from the same-token-same-chain dedupe, since the strategy renormalizes the source from the EOA to the deposit-wallet contract.Non-obvious decisions
References
Checklist