Skip to content

Commit d9f38f2

Browse files
authored
feat(predict): add Polymarket CLOB v2 support cp-7.73.1 (#29076)
## **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 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 --- ## **Commits** This PR is now structured as 12 focused commits for easier review: ### 1. `feat(predict): add CLOB v2 feature flag plumbing` 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 ### 2. `feat(predict): add CLOB v2 protocol and preflight foundation` 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. ### 3. `feat(predict): add CLOB v2 buy and sell flow` 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 ### 4. `feat(predict): add CLOB v2 deposit flow` 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 ### 5. `feat(predict): add CLOB v2 withdraw flow` 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 ### 6. `feat(predict): add CLOB v2 claim flow` 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 ### 7. `test(predict): add CLOB v2 integration coverage` 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 ### 8. `fix: preserve EIP-712 field order in buildProtocolUnsignedOrder` Fixes 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: - 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 ### 10. `refactor(predict): tighten CLOB v2 preflight workflow seams` 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 ### 11. `feat(predict): add configurable CLOB v2 host override` 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 ### 12. `refactor(predict): gate legacy CLOB v2 host with boolean flag` 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** CHANGELOG entry: null ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/PRED-817 ## **Manual testing steps** ```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 ``` ## **Screenshots/Recordings** https://www.loom.com/share/155120bd46c44723a8b838172b4fd45b ## **Pre-merge author checklist** - [ ] 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. #### 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] > **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 -->
1 parent 63ef30c commit d9f38f2

33 files changed

Lines changed: 4125 additions & 409 deletions

app/components/UI/Predict/providers/polymarket/PolymarketProvider.test.ts

Lines changed: 409 additions & 2 deletions
Large diffs are not rendered by default.

app/components/UI/Predict/providers/polymarket/PolymarketProvider.ts

Lines changed: 562 additions & 324 deletions
Large diffs are not rendered by default.

app/components/UI/Predict/providers/polymarket/constants.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ export const POLYMARKET_PROVIDER_ID = 'polymarket';
44

55
export const POLYMARKET_TERMS_URL = 'https://polymarket.com/tos';
66

7+
export const DEFAULT_CLOB_BASE_URL = 'https://clob.polymarket.com';
8+
export const LEGACY_V2_CLOB_BASE_URL = 'https://clob-v2.polymarket.com';
9+
710
/**
811
* Default slippage for market orders.
912
*/
@@ -81,6 +84,28 @@ export const MATIC_CONTRACTS: ContractConfig = {
8184
conditionalTokens: '0x4D97DCd97eC945f40cF65F87097ACe5EA0476045',
8285
};
8386

87+
export const MATIC_CONTRACTS_V2: ContractConfig = {
88+
exchange: '0xE111180000d2663C0091e4f400237545B87B996B',
89+
negRiskAdapter: '0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296',
90+
negRiskExchange: '0xe2222d279d744050d28e00520010520000310F59',
91+
collateral: '0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB',
92+
conditionalTokens: '0x4D97DCd97eC945f40cF65F87097ACe5EA0476045',
93+
};
94+
95+
export const USDC_E_ADDRESS = MATIC_CONTRACTS.collateral;
96+
97+
export const COLLATERAL_ONRAMP_ADDRESS =
98+
'0x93070a847efEf7F70739046A929D47a521F5B8ee';
99+
100+
export const COLLATERAL_OFFRAMP_ADDRESS =
101+
'0x2957922Eb93258b93368531d39fAcCA3B4dC5854';
102+
103+
export const CTF_COLLATERAL_ADAPTER_ADDRESS =
104+
'0xADa100874d00e3331D00F2007a9c336a65009718';
105+
106+
export const NEG_RISK_CTF_COLLATERAL_ADAPTER_ADDRESS =
107+
'0xAdA200001000ef00D07553cEE7006808F895c6F1';
108+
84109
export const POLYGON_USDC_CAIP_ASSET_ID =
85110
`${POLYGON_MAINNET_CAIP_CHAIN_ID}/erc20:${MATIC_CONTRACTS.collateral}` as const;
86111

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
import { TransactionType } from '@metamask/transaction-controller';
2+
import { parseUnits } from 'ethers/lib/utils';
3+
import type { PredictPosition } from '../../../types';
4+
import type { Signer } from '../../types';
5+
import {
6+
HASH_ZERO_BYTES32,
7+
MIN_COLLATERAL_BALANCE_FOR_CLAIM,
8+
} from '../constants';
9+
import {
10+
POLYMARKET_V2_PROTOCOL,
11+
type PolymarketProtocolDefinition,
12+
} from '../protocol/definitions';
13+
import { OperationType, type SafeTransaction } from '../safe/types';
14+
import { encodeRedeemPositions } from '../utils';
15+
import {
16+
buildSignedSafeExecution,
17+
buildUnwrapTransaction,
18+
compileAllowanceMaintenanceTransactions,
19+
getRawTokenBalance,
20+
} from './core';
21+
import { inspectMissingRequirements } from './inspectMissingRequirements';
22+
import {
23+
getCanonicalV2AllowanceRequirements,
24+
type V2AllowanceRequirement,
25+
} from './v2AllowanceRequirements';
26+
27+
const MIN_GAS_STATION_USDCE_BALANCE_RAW = BigInt(
28+
parseUnits(MIN_COLLATERAL_BALANCE_FOR_CLAIM.toString(), 6).toString(),
29+
);
30+
31+
type PolymarketV2ProtocolDefinition = Extract<
32+
PolymarketProtocolDefinition,
33+
{ key: 'v2' }
34+
>;
35+
36+
function buildClaimSubtransactions({
37+
positions,
38+
protocol,
39+
}: {
40+
positions: PredictPosition[];
41+
protocol: PolymarketV2ProtocolDefinition;
42+
}): SafeTransaction[] {
43+
return positions.map((position) => ({
44+
to: position.negRisk
45+
? protocol.claim.negRiskTarget
46+
: protocol.claim.standardTarget,
47+
data: encodeRedeemPositions({
48+
collateralToken: protocol.collateral.claimToken,
49+
parentCollectionId: HASH_ZERO_BYTES32,
50+
conditionId: position.outcomeId,
51+
indexSets: [1, 2],
52+
}),
53+
operation: OperationType.Call,
54+
value: '0',
55+
}));
56+
}
57+
58+
export function getClaimRequirements({
59+
positions,
60+
protocol = POLYMARKET_V2_PROTOCOL,
61+
}: {
62+
positions: PredictPosition[];
63+
protocol?: PolymarketV2ProtocolDefinition;
64+
}): V2AllowanceRequirement[] {
65+
const requiresStandardAdapter = positions.some(
66+
(position) => !position.negRisk,
67+
);
68+
const requiresNegRiskAdapter = positions.some((position) => position.negRisk);
69+
70+
return [
71+
...getCanonicalV2AllowanceRequirements(protocol),
72+
...(requiresStandardAdapter
73+
? [
74+
{
75+
type: 'erc1155-operator' as const,
76+
tokenAddress: protocol.contracts.conditionalTokens,
77+
operator: protocol.claim.standardTarget,
78+
},
79+
]
80+
: []),
81+
...(requiresNegRiskAdapter
82+
? [
83+
{
84+
type: 'erc1155-operator' as const,
85+
tokenAddress: protocol.contracts.conditionalTokens,
86+
operator: protocol.claim.negRiskTarget,
87+
},
88+
]
89+
: []),
90+
];
91+
}
92+
93+
export interface ClaimPlan {
94+
gasStationDeficit: bigint;
95+
safeUsdceBalance: bigint;
96+
eoaUsdceBalance: bigint;
97+
missingRequirements: V2AllowanceRequirement[];
98+
transactions: SafeTransaction[];
99+
}
100+
101+
export async function planClaim({
102+
signer,
103+
positions,
104+
safeAddress,
105+
protocol = POLYMARKET_V2_PROTOCOL,
106+
}: {
107+
signer: Signer;
108+
positions: PredictPosition[];
109+
safeAddress: string;
110+
protocol?: PolymarketV2ProtocolDefinition;
111+
}): Promise<ClaimPlan> {
112+
const [missingRequirements, safeUsdceBalance, eoaUsdceBalance] =
113+
await Promise.all([
114+
inspectMissingRequirements({
115+
address: safeAddress,
116+
requirements: getClaimRequirements({ positions, protocol }),
117+
}),
118+
getRawTokenBalance({
119+
address: safeAddress,
120+
tokenAddress: protocol.collateral.legacyUsdceToken,
121+
}),
122+
getRawTokenBalance({
123+
address: signer.address,
124+
tokenAddress: protocol.collateral.legacyUsdceToken,
125+
}),
126+
]);
127+
128+
const gasStationDeficit =
129+
eoaUsdceBalance >= MIN_GAS_STATION_USDCE_BALANCE_RAW
130+
? 0n
131+
: MIN_GAS_STATION_USDCE_BALANCE_RAW - eoaUsdceBalance;
132+
133+
const transactions = compileClaimTransactions({
134+
protocol,
135+
signer,
136+
positions,
137+
safeAddress,
138+
missingRequirements,
139+
safeUsdceBalance,
140+
gasStationDeficit,
141+
});
142+
143+
return {
144+
gasStationDeficit,
145+
safeUsdceBalance,
146+
eoaUsdceBalance,
147+
missingRequirements,
148+
transactions,
149+
};
150+
}
151+
152+
function compileClaimTransactions({
153+
protocol = POLYMARKET_V2_PROTOCOL,
154+
signer,
155+
positions,
156+
safeAddress,
157+
missingRequirements,
158+
safeUsdceBalance,
159+
gasStationDeficit,
160+
}: {
161+
protocol?: PolymarketV2ProtocolDefinition;
162+
signer: Signer;
163+
positions: PredictPosition[];
164+
safeAddress: string;
165+
missingRequirements: V2AllowanceRequirement[];
166+
safeUsdceBalance: bigint;
167+
gasStationDeficit: bigint;
168+
}): SafeTransaction[] {
169+
const transactions = compileAllowanceMaintenanceTransactions({
170+
protocol,
171+
safeAddress,
172+
missingRequirements,
173+
usdceBalance: safeUsdceBalance,
174+
});
175+
176+
transactions.push(
177+
...buildClaimSubtransactions({
178+
positions,
179+
protocol,
180+
}),
181+
);
182+
183+
const unwrapTransaction = buildUnwrapTransaction({
184+
recipientAddress: signer.address,
185+
amount: gasStationDeficit,
186+
protocol,
187+
});
188+
189+
if (unwrapTransaction) {
190+
transactions.push(unwrapTransaction);
191+
}
192+
193+
return transactions;
194+
}
195+
196+
export async function buildClaimTransaction({
197+
signer,
198+
positions,
199+
safeAddress,
200+
protocol = POLYMARKET_V2_PROTOCOL,
201+
}: {
202+
signer: Signer;
203+
positions: PredictPosition[];
204+
safeAddress: string;
205+
protocol?: PolymarketV2ProtocolDefinition;
206+
}) {
207+
const plan = await planClaim({
208+
signer,
209+
positions,
210+
safeAddress,
211+
protocol,
212+
});
213+
214+
return buildSignedSafeExecution({
215+
signer,
216+
safeAddress,
217+
transactions: plan.transactions,
218+
type: TransactionType.predictClaim,
219+
});
220+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
jest.mock('../utils', () => ({
2+
encodeApprove: jest.fn(({ spender }) => `approve:${spender}`),
3+
encodeErc1155Approve: jest.fn(({ spender }) => `setApproval:${spender}`),
4+
}));
5+
6+
import { compileRequirementTransactions } from './compileRequirementTransactions';
7+
import type { V2AllowanceRequirement } from './v2AllowanceRequirements';
8+
9+
describe('compileRequirementTransactions', () => {
10+
it('compiles erc20 approvals and erc1155 operator approvals in order', () => {
11+
const requirements: V2AllowanceRequirement[] = [
12+
{
13+
type: 'erc20-allowance',
14+
tokenAddress: '0x1000000000000000000000000000000000000000',
15+
spender: '0x2000000000000000000000000000000000000000',
16+
},
17+
{
18+
type: 'erc1155-operator',
19+
tokenAddress: '0x3000000000000000000000000000000000000000',
20+
operator: '0x4000000000000000000000000000000000000000',
21+
},
22+
];
23+
24+
expect(compileRequirementTransactions(requirements)).toEqual([
25+
{
26+
to: '0x1000000000000000000000000000000000000000',
27+
data: 'approve:0x2000000000000000000000000000000000000000',
28+
operation: 0,
29+
value: '0',
30+
},
31+
{
32+
to: '0x3000000000000000000000000000000000000000',
33+
data: 'setApproval:0x4000000000000000000000000000000000000000',
34+
operation: 0,
35+
value: '0',
36+
},
37+
]);
38+
});
39+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { ethers } from 'ethers';
2+
import { OperationType, type SafeTransaction } from '../safe/types';
3+
import { encodeApprove, encodeErc1155Approve } from '../utils';
4+
import type { V2AllowanceRequirement } from './v2AllowanceRequirements';
5+
6+
export function compileRequirementTransactions(
7+
requirements: V2AllowanceRequirement[],
8+
): SafeTransaction[] {
9+
return requirements.map((requirement) => {
10+
if (requirement.type === 'erc20-allowance') {
11+
return {
12+
to: requirement.tokenAddress,
13+
data: encodeApprove({
14+
spender: requirement.spender,
15+
amount: ethers.constants.MaxUint256.toBigInt(),
16+
}),
17+
operation: OperationType.Call,
18+
value: '0',
19+
};
20+
}
21+
22+
return {
23+
to: requirement.tokenAddress,
24+
data: encodeErc1155Approve({
25+
spender: requirement.operator,
26+
approved: true,
27+
}),
28+
operation: OperationType.Call,
29+
value: '0',
30+
};
31+
});
32+
}

0 commit comments

Comments
 (0)