Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
220ef89
feat(transaction-pay-controller): add Polymarket Bridge withdrawal st…
matthewwalsh0 May 6, 2026
4df13c7
fix(transaction-pay-controller): bridge strategy fixes from E2E testing
matthewwalsh0 May 7, 2026
6c94130
feat(transaction-pay-controller): add Polymarket relayer submission p…
matthewwalsh0 May 7, 2026
757bba3
fix(transaction-pay-controller): zero network fees for Polymarket dep…
matthewwalsh0 May 7, 2026
d2d8e6b
chore(transaction-pay-controller): link changelog entries to PR #8754
matthewwalsh0 May 11, 2026
f83acdf
Revert "feat(transaction-pay-controller): add Polymarket relayer subm…
matthewwalsh0 May 11, 2026
32bc681
refactor(transaction-pay-controller): remove credential plumbing from…
matthewwalsh0 May 11, 2026
6be3462
feat(transaction-pay-controller): route via isPolymarketDepositWallet…
matthewwalsh0 May 11, 2026
b0e0150
feat(transaction-pay-controller): surface Polymarket bridge fees, sou…
matthewwalsh0 May 11, 2026
1ed1ea1
fix(transaction-pay-controller): mark Polymarket bridge withdraws com…
matthewwalsh0 May 11, 2026
6d53a4c
feat(transaction-pay-controller): experimental Relay-backed Polymarke…
matthewwalsh0 May 11, 2026
15d85b3
feat(transaction-pay-controller): Polymarket bridge withdraw via Rela…
matthewwalsh0 May 11, 2026
2a482ff
refactor(transaction-pay-controller): split PolymarketStrategy into q…
matthewwalsh0 May 11, 2026
1525b4d
refactor(transaction-pay-controller): fold Polymarket deposit wallet …
matthewwalsh0 May 11, 2026
ed93efb
refactor(transaction-pay-controller): tighten Polymarket integration …
matthewwalsh0 May 11, 2026
f5081a7
refactor(transaction-pay-controller): cleanup Polymarket relay integr…
matthewwalsh0 May 11, 2026
6fdc6ef
refactor(transaction-pay-controller): split Polymarket relayer into a…
matthewwalsh0 May 12, 2026
bf8f568
fix(transaction-pay-controller): wait for refunded before sweeping an…
matthewwalsh0 May 12, 2026
06f9673
fix(transaction-pay-controller): bump Polymarket batch deadline to th…
matthewwalsh0 May 12, 2026
9608b20
refactor(transaction-pay-controller): inject Polymarket relayer via c…
matthewwalsh0 May 12, 2026
68dea46
feat(transaction-pay-controller): export PolymarketCallbacks and poly…
matthewwalsh0 May 12, 2026
0118420
feat(transaction-pay-controller): log Polymarket callback boundaries
matthewwalsh0 May 12, 2026
f597724
test(transaction-pay-controller): cover Polymarket deposit-wallet flow
matthewwalsh0 May 12, 2026
db00c0e
feat(transaction-pay-controller): refund-aware Polymarket USDC.e sweep
matthewwalsh0 May 13, 2026
d2fc4fc
chore(transaction-pay-controller): restore Polymarket changelog entry…
matthewwalsh0 May 13, 2026
7a63768
chore(transaction-pay-controller): drop duplicate Polymarket changelo…
matthewwalsh0 May 13, 2026
623936a
refactor(transaction-pay-controller): shorten requirePolymarket error
matthewwalsh0 May 13, 2026
ae87f2d
docs(transaction-pay-controller): document RelayQuoteRequest
matthewwalsh0 May 13, 2026
fbccd0e
revert(transaction-pay-controller): drop RelayQuoteRequest JSDoc
matthewwalsh0 May 13, 2026
1eea710
docs(transaction-pay-controller): explain pending blockTag cache bypass
matthewwalsh0 May 13, 2026
4210fea
refactor(transaction-pay-controller): reuse canonical Polygon address…
matthewwalsh0 May 13, 2026
466119a
fix(transaction-pay-controller): strip RelayQuoteRequest JSDoc and pr…
matthewwalsh0 May 13, 2026
cce1b30
chore(transaction-pay-controller): move Polymarket entry back to Unre…
matthewwalsh0 May 13, 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))

## [22.4.0]

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,30 @@ export type TransactionPayControllerGetStrategyAction = {
handler: TransactionPayController['getStrategy'];
};

/**
* Derives the Polymarket deposit-wallet address for an EOA via the
* client-supplied callback.
*
* @param args - The arguments forwarded to {@link PolymarketCallbacks.getDepositWalletAddress}.
* @returns A promise resolving to the deposit-wallet address.
*/
export type TransactionPayControllerPolymarketGetDepositWalletAddressAction = {
type: `TransactionPayController:polymarketGetDepositWalletAddress`;
handler: TransactionPayController['polymarketGetDepositWalletAddress'];
};

/**
* Signs and broadcasts a Polymarket deposit-wallet batch via the
* client-supplied callback.
*
* @param args - The arguments forwarded to {@link PolymarketCallbacks.submitDepositWalletBatch}.
* @returns A promise resolving to the relayer-issued source hash.
*/
export type TransactionPayControllerPolymarketSubmitDepositWalletBatchAction = {
type: `TransactionPayController:polymarketSubmitDepositWalletBatch`;
handler: TransactionPayController['polymarketSubmitDepositWalletBatch'];
};

/**
* Union of all TransactionPayController action types.
*/
Expand All @@ -89,4 +113,6 @@ export type TransactionPayControllerMethodActions =
| TransactionPayControllerUpdatePaymentTokenAction
| TransactionPayControllerUpdateFiatPaymentAction
| TransactionPayControllerGetDelegationTransactionAction
| TransactionPayControllerGetStrategyAction;
| TransactionPayControllerGetStrategyAction
| TransactionPayControllerPolymarketGetDepositWalletAddressAction
| TransactionPayControllerPolymarketSubmitDepositWalletBatchAction;
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,94 @@ describe('TransactionPayController', () => {
});
});

describe('polymarket callbacks', () => {
const EOA_MOCK = '0x1111111111111111111111111111111111111111' as Hex;
const DEPOSIT_WALLET_MOCK =
'0x2222222222222222222222222222222222222222' as Hex;
const SOURCE_HASH_MOCK: Hex = `0x${'aa'.repeat(32)}`;

it('delegates polymarketGetDepositWalletAddress to the callback', async () => {
const getDepositWalletAddressMock = jest
.fn()
.mockResolvedValue(DEPOSIT_WALLET_MOCK);

new TransactionPayController({
getDelegationTransaction: jest.fn(),
messenger,
polymarket: {
getDepositWalletAddress: getDepositWalletAddressMock,
submitDepositWalletBatch: jest.fn(),
},
});

const result = await messenger.call(
'TransactionPayController:polymarketGetDepositWalletAddress',
{ eoa: EOA_MOCK },
);

expect(getDepositWalletAddressMock).toHaveBeenCalledWith({
eoa: EOA_MOCK,
});
expect(result).toBe(DEPOSIT_WALLET_MOCK);
});

it('delegates polymarketSubmitDepositWalletBatch to the callback', async () => {
const submitDepositWalletBatchMock = jest
.fn()
.mockResolvedValue({ sourceHash: SOURCE_HASH_MOCK });

new TransactionPayController({
getDelegationTransaction: jest.fn(),
messenger,
polymarket: {
getDepositWalletAddress: jest.fn(),
submitDepositWalletBatch: submitDepositWalletBatchMock,
},
});

const params = {
eoa: EOA_MOCK,
depositWallet: DEPOSIT_WALLET_MOCK,
calls: [],
};
const result = await messenger.call(
'TransactionPayController:polymarketSubmitDepositWalletBatch',
params,
);

expect(submitDepositWalletBatchMock).toHaveBeenCalledWith(params);
expect(result).toStrictEqual({ sourceHash: SOURCE_HASH_MOCK });
});

it('throws if polymarketGetDepositWalletAddress is invoked without callbacks supplied', () => {
new TransactionPayController({
getDelegationTransaction: jest.fn(),
messenger,
});

expect(() =>
messenger.call(
'TransactionPayController:polymarketGetDepositWalletAddress',
{ eoa: EOA_MOCK },
),
).toThrow('Polymarket callbacks missing');
});

it('throws if polymarketSubmitDepositWalletBatch is invoked without callbacks supplied', () => {
new TransactionPayController({
getDelegationTransaction: jest.fn(),
messenger,
});

expect(() =>
messenger.call(
'TransactionPayController:polymarketSubmitDepositWalletBatch',
{ eoa: EOA_MOCK, depositWallet: DEPOSIT_WALLET_MOCK, calls: [] },
),
).toThrow('Polymarket callbacks missing');
});
});

describe('getStrategy Action', () => {
it('returns relay if no callback', async () => {
createController();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { QuoteRefresher } from './helpers/QuoteRefresher';
import { deriveFiatAssetForFiatPayment } from './strategy/fiat/utils';
import type {
GetDelegationTransactionCallback,
PolymarketCallbacks,
TransactionConfigCallback,
TransactionData,
TransactionPayControllerMessenger,
Expand All @@ -36,6 +37,8 @@ import {
const MESSENGER_EXPOSED_METHODS = [
'getDelegationTransaction',
'getStrategy',
'polymarketGetDepositWalletAddress',
'polymarketSubmitDepositWalletBatch',
'setTransactionConfig',
'updateFiatPayment',
'updatePaymentToken',
Expand Down Expand Up @@ -69,11 +72,14 @@ export class TransactionPayController extends BaseController<
transaction: TransactionMeta,
) => TransactionPayStrategy[];

readonly #polymarket?: PolymarketCallbacks;

constructor({
getDelegationTransaction,
getStrategy,
getStrategies,
messenger,
polymarket,
state,
}: TransactionPayControllerOptions) {
super({
Expand All @@ -86,6 +92,7 @@ export class TransactionPayController extends BaseController<
this.#getDelegationTransaction = getDelegationTransaction;
this.#getStrategy = getStrategy;
this.#getStrategies = getStrategies;
this.#polymarket = polymarket;

this.messenger.registerMethodActionHandlers(
this,
Expand Down Expand Up @@ -130,6 +137,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 @@ -142,6 +150,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 (
Expand Down Expand Up @@ -219,6 +229,39 @@ export class TransactionPayController extends BaseController<
return this.#getStrategiesWithFallback(transaction)[0];
}

/**
* Derives the Polymarket deposit-wallet address for an EOA via the
* client-supplied callback.
*
* @param args - The arguments forwarded to {@link PolymarketCallbacks.getDepositWalletAddress}.
* @returns A promise resolving to the deposit-wallet address.
*/
polymarketGetDepositWalletAddress(
...args: Parameters<PolymarketCallbacks['getDepositWalletAddress']>
): ReturnType<PolymarketCallbacks['getDepositWalletAddress']> {
return this.#requirePolymarket().getDepositWalletAddress(...args);
}

/**
* Signs and broadcasts a Polymarket deposit-wallet batch via the
* client-supplied callback.
*
* @param args - The arguments forwarded to {@link PolymarketCallbacks.submitDepositWalletBatch}.
* @returns A promise resolving to the relayer-issued source hash.
*/
polymarketSubmitDepositWalletBatch(
...args: Parameters<PolymarketCallbacks['submitDepositWalletBatch']>
): ReturnType<PolymarketCallbacks['submitDepositWalletBatch']> {
return this.#requirePolymarket().submitDepositWalletBatch(...args);
}

#requirePolymarket(): PolymarketCallbacks {
if (!this.#polymarket) {
throw new Error('TransactionPayController: Polymarket callbacks missing');
}
return this.#polymarket;
}

#removeTransactionData(transactionId: string): void {
this.update((state) => {
delete state.transactionData[transactionId];
Expand Down Expand Up @@ -328,6 +371,8 @@ export class TransactionPayController extends BaseController<
#getStrategiesWithFallback(
transaction: TransactionMeta,
): TransactionPayStrategy[] {
const transactionData = this.state.transactionData[transaction.id];

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

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

return getStrategyOrder(
Expand Down
3 changes: 3 additions & 0 deletions packages/transaction-pay-controller/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type {
TransactionPayControllerMessenger,
TransactionPayControllerOptions,
TransactionPayControllerState,
PolymarketCallbacks,
TransactionPayControllerStateChangeEvent,
TransactionPaymentToken,
TransactionPayQuote,
Expand All @@ -22,6 +23,8 @@ export type {
export type {
TransactionPayControllerGetDelegationTransactionAction,
TransactionPayControllerGetStrategyAction,
TransactionPayControllerPolymarketGetDepositWalletAddressAction,
TransactionPayControllerPolymarketSubmitDepositWalletBatchAction,
TransactionPayControllerSetTransactionConfigAction,
TransactionPayControllerUpdatePaymentTokenAction,
TransactionPayControllerUpdateFiatPaymentAction,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Interface } from '@ethersproject/abi';
import type { Hex } from '@metamask/utils';

const iface = new Interface([
'function approve(address spender, uint256 amount)',
'function unwrap(address asset, address recipient, uint256 amount)',
'function wrap(address asset, address recipient, uint256 amount)',
'function transfer(address recipient, uint256 amount)',
]);

export function encodeApprove(spender: Hex, amount: bigint): Hex {
return iface.encodeFunctionData('approve', [spender, amount]) as Hex;
}

export function encodeUnwrap({
asset,
recipient,
amount,
}: {
asset: Hex;
recipient: Hex;
amount: bigint;
}): Hex {
return iface.encodeFunctionData('unwrap', [asset, recipient, amount]) as Hex;
}

export function encodeWrap({
asset,
recipient,
amount,
}: {
asset: Hex;
recipient: Hex;
amount: bigint;
}): Hex {
return iface.encodeFunctionData('wrap', [asset, recipient, amount]) as Hex;
}

export function extractErc20TransferRecipient(data: Hex): Hex {
const [recipient] = iface.decodeFunctionData('transfer', data);
return recipient as Hex;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Hex } from '@metamask/utils';

export const POLYMARKET_COLLATERAL_OFFRAMP_POLYGON =
'0x2957922Eb93258b93368531d39fAcCA3B4dC5854' as Hex;

export const POLYMARKET_COLLATERAL_ONRAMP_POLYGON =
'0x93070a847efEf7F70739046A929D47a521F5B8ee' as Hex;

export const SWEEP_BALANCE_RETRY_ATTEMPTS = 5;

export const SWEEP_BALANCE_RETRY_DELAY_MS = 1000;

export const SWEEP_RELAYER_SETTLE_DELAY_MS = 3000;
Loading
Loading