Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
978672f
feat(transaction-pay-controller): add Polymarket Bridge withdrawal st…
matthewwalsh0 May 6, 2026
75ff63c
fix(transaction-pay-controller): bridge strategy fixes from E2E testing
matthewwalsh0 May 7, 2026
2afa11a
feat(transaction-pay-controller): add Polymarket relayer submission p…
matthewwalsh0 May 7, 2026
bcd0be2
fix(transaction-pay-controller): zero network fees for Polymarket dep…
matthewwalsh0 May 7, 2026
9a95578
chore(transaction-pay-controller): link changelog entries to PR #8754
matthewwalsh0 May 11, 2026
8451115
Revert "feat(transaction-pay-controller): add Polymarket relayer subm…
matthewwalsh0 May 11, 2026
162aaf8
refactor(transaction-pay-controller): remove credential plumbing from…
matthewwalsh0 May 11, 2026
59b18c8
feat(transaction-pay-controller): route via isPolymarketDepositWallet…
matthewwalsh0 May 11, 2026
f59f50a
feat(transaction-pay-controller): surface Polymarket bridge fees, sou…
matthewwalsh0 May 11, 2026
78c9180
fix(transaction-pay-controller): mark Polymarket bridge withdraws com…
matthewwalsh0 May 11, 2026
0deeabd
feat(transaction-pay-controller): experimental Relay-backed Polymarke…
matthewwalsh0 May 11, 2026
89a3fb3
feat(transaction-pay-controller): Polymarket bridge withdraw via Rela…
matthewwalsh0 May 11, 2026
85888dc
refactor(transaction-pay-controller): split PolymarketStrategy into q…
matthewwalsh0 May 11, 2026
ea5f293
refactor(transaction-pay-controller): fold Polymarket deposit wallet …
matthewwalsh0 May 11, 2026
33eb5e0
refactor(transaction-pay-controller): tighten Polymarket integration …
matthewwalsh0 May 11, 2026
a7ad075
refactor(transaction-pay-controller): cleanup Polymarket relay integr…
matthewwalsh0 May 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/transaction-pay-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Add Polymarket deposit-wallet support to the Relay strategy for `predictWithdraw` transactions, routed via the `isPolymarketDepositWallet` flag on `TransactionConfig` ([#8754](https://github.com/MetaMask/core/pull/8754))

### Changed

- Bump `@metamask/gas-fee-controller` from `^26.1.1` to `^26.2.0` ([#8722](https://github.com/MetaMask/core/pull/8722))
Expand Down
3 changes: 3 additions & 0 deletions packages/transaction-pay-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@
},
"dependencies": {
"@ethersproject/abi": "^5.7.0",
"@ethersproject/address": "^5.7.0",
"@ethersproject/bytes": "^5.7.0",
"@ethersproject/contracts": "^5.7.0",
"@ethersproject/keccak256": "^5.7.0",
"@ethersproject/providers": "^5.7.0",
"@metamask/assets-controller": "^6.4.0",
"@metamask/assets-controllers": "^106.0.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export class TransactionPayController extends BaseController<
isMaxAmount: transactionData.isMaxAmount,
isPostQuote: transactionData.isPostQuote,
isHyperliquidSource: transactionData.isHyperliquidSource,
isPolymarketDepositWallet: transactionData.isPolymarketDepositWallet,
refundTo: transactionData.refundTo,
accountOverride: transactionData.accountOverride,
};
Expand All @@ -131,6 +132,8 @@ export class TransactionPayController extends BaseController<
transactionData.isMaxAmount = config.isMaxAmount;
transactionData.isPostQuote = config.isPostQuote;
transactionData.isHyperliquidSource = config.isHyperliquidSource;
transactionData.isPolymarketDepositWallet =
config.isPolymarketDepositWallet;
transactionData.refundTo = config.refundTo;

if (config.accountOverride !== previousAccountOverride) {
Expand Down Expand Up @@ -311,6 +314,12 @@ export class TransactionPayController extends BaseController<
#getStrategiesWithFallback(
transaction: TransactionMeta,
): TransactionPayStrategy[] {
const transactionData = this.state.transactionData[transaction.id];

if (transactionData?.isPolymarketDepositWallet) {
return [TransactionPayStrategy.Relay];
}

const strategyCandidates: unknown[] =
this.#getStrategies?.(transaction) ??
(this.#getStrategy ? [this.#getStrategy(transaction)] : []);
Expand All @@ -324,7 +333,6 @@ export class TransactionPayController extends BaseController<
return validStrategies;
}

const transactionData = this.state.transactionData[transaction.id];
const paymentToken = transactionData?.paymentToken;

return getStrategyOrder(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { Hex } from '@metamask/utils';

const ERC20_TRANSFER_SELECTOR = '0xa9059cbb';

export function encodeApprove(spender: Hex, amount: bigint): Hex {
return `0x095ea7b3${padAddress(spender)}${padUint256(amount)}` as Hex;
}

export function encodeUnwrap({
asset,
recipient,
amount,
}: {
asset: Hex;
recipient: Hex;
amount: bigint;
}): Hex {
return `0x8cc7104f${padAddress(asset)}${padAddress(recipient)}${padUint256(amount)}` as Hex;
}

export function encodeWrap({
asset,
recipient,
amount,
}: {
asset: Hex;
recipient: Hex;
amount: bigint;
}): Hex {
return `0x62355638${padAddress(asset)}${padAddress(recipient)}${padUint256(amount)}` as Hex;
}

export function extractErc20TransferRecipient(data: Hex): Hex {
if (!data.startsWith(ERC20_TRANSFER_SELECTOR)) {
throw new Error(
`Expected ERC-20 transfer calldata, got selector ${data.slice(0, 10)}`,
);
}
return `0x${data.slice(34, 74)}` as Hex;
}

function padAddress(address: Hex): string {
return address.slice(2).toLowerCase().padStart(64, '0');
}

function padUint256(value: bigint): string {
return value.toString(16).padStart(64, '0');
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { Hex } from '@metamask/utils';

export const POLYMARKET_RELAYER_PROXY_URL_PROD =
'https://predict.api.cx.metamask.io';

export const DEPOSIT_WALLET_FACTORY_ADDRESS_POLYGON =
'0x00000000000Fb5C9ADea0298D729A0CB3823Cc07' as Hex;

export const DEPOSIT_WALLET_IMPLEMENTATION_POLYGON =
'0x58CA52ebe0DadfdF531Cde7062e76746de4Db1eB' as Hex;

export const PUSD_ADDRESS_POLYGON =
'0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB' as Hex;

export const USDC_E_ADDRESS_POLYGON =
'0x2791bca1f2de4661ed88a30c99a7a9449aa84174' as Hex;

export const POLYMARKET_COLLATERAL_OFFRAMP_POLYGON =
'0x2957922Eb93258b93368531d39fAcCA3B4dC5854' as Hex;

export const POLYMARKET_COLLATERAL_ONRAMP_POLYGON =
'0x93070a847efEf7F70739046A929D47a521F5B8ee' as Hex;

export const POLYMARKET_WALLET_DOMAIN_NAME = 'DepositWallet';
export const POLYMARKET_WALLET_DOMAIN_VERSION = '1';

export const POLYMARKET_BATCH_DEADLINE_SECONDS = 240;

export const POLYMARKET_RELAYER_TERMINAL_STATES = [
'STATE_MINED',
'STATE_CONFIRMED',
'STATE_FAILED',
'STATE_INVALID',
] as const;
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { defaultAbiCoder } from '@ethersproject/abi';
import { getCreate2Address } from '@ethersproject/address';
import { hexConcat, hexZeroPad } from '@ethersproject/bytes';
import { keccak256 } from '@ethersproject/keccak256';
import type { Hex } from '@metamask/utils';

import {
DEPOSIT_WALLET_FACTORY_ADDRESS_POLYGON,
DEPOSIT_WALLET_IMPLEMENTATION_POLYGON,
} from './constants';

// Solady v0.1.26 LibClone.initCodeHashERC1967 byte constants.
const ERC1967_CONST1 =
'0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3';
const ERC1967_CONST2 =
'0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076';
const ERC1967_PREFIX = 0x61003d3d8160233d3973n;

/**
* Compute the deterministic Polymarket deposit-wallet address for an EOA.
*
* Uses CREATE2 with the Solady ERC-1967 proxy init-code pattern, matching
* the reference implementation in Polymarket's builder-relayer-client.
*
* @param ownerAddress - The EOA that owns the deposit wallet.
* @returns The deterministic deposit wallet address on Polygon.
*/
export function computeDepositWalletAddress(ownerAddress: string): Hex {
const walletId = hexZeroPad(ownerAddress.toLowerCase(), 32);

const args = defaultAbiCoder.encode(
['address', 'bytes32'],
[DEPOSIT_WALLET_FACTORY_ADDRESS_POLYGON, walletId],
);

const salt = keccak256(args);
const initCodeHash = computeSoladyERC1967InitCodeHash(
DEPOSIT_WALLET_IMPLEMENTATION_POLYGON,
args,
);

return getCreate2Address(
DEPOSIT_WALLET_FACTORY_ADDRESS_POLYGON,
salt,
initCodeHash,
) as Hex;
}

function computeSoladyERC1967InitCodeHash(
implementation: string,
args: string,
): string {
const argByteLength = BigInt((args.length - 2) / 2);
const prefixWithLength = ERC1967_PREFIX + (argByteLength << 56n);

return keccak256(
hexConcat([
`0x${prefixWithLength.toString(16).padStart(20, '0')}`,
implementation,
'0x6009',
ERC1967_CONST2,
ERC1967_CONST1,
args,
]),
);
}
Loading