Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 1 addition & 2 deletions packages/polygon-agent-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@
"@noble/ciphers": "^1.2.1",
"@noble/curves": "^1.8.1",
"@noble/hashes": "^1.7.2",
"@polymarket/clob-client": "^5.2.4",
"@polymarket/order-utils": "^3.0.1",
"@polymarket/clob-client-v2": "1.0.0",
"@polymarket/sdk": "^6.0.1",
"@x402/core": "^2.3.1",
"@x402/evm": "^2.3.1",
Expand Down
89 changes: 70 additions & 19 deletions packages/polygon-agent-cli/src/commands/polymarket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ import {
executeViaProxyWallet,
getPositions,
USDC_E,
PUSD,
CTF,
CTF_EXCHANGE,
NEG_RISK_CTF_EXCHANGE,
NEG_RISK_ADAPTER
NEG_RISK_ADAPTER,
COLLATERAL_ONRAMP
} from '../lib/polymarket.ts';
import { loadWalletSession, savePolymarketKey, loadPolymarketKey } from '../lib/storage.ts';

Expand Down Expand Up @@ -107,7 +109,7 @@ async function handleProxyWallet(): Promise<void> {
ok: true,
eoaAddress: account.address,
proxyWalletAddress,
note: 'Fund proxyWalletAddress with USDC.e on Polygon to enable CLOB trading.'
note: 'Fund proxyWalletAddress with USDC.e (auto-wrapped to pUSD) on Polygon to enable CLOB V2 trading.'
},
null,
2
Expand Down Expand Up @@ -148,25 +150,38 @@ async function handleApprove(argv: { negRisk?: boolean; broadcast?: boolean }):
let approvalLabels: string[];
if (negRisk) {
txBatch = [
erc20Approve(USDC_E, NEG_RISK_ADAPTER, MAX_UINT256),
erc20Approve(USDC_E, NEG_RISK_CTF_EXCHANGE, MAX_UINT256),
// pUSD approvals for V2 exchange contracts
erc20Approve(PUSD, NEG_RISK_ADAPTER, MAX_UINT256),
erc20Approve(PUSD, NEG_RISK_CTF_EXCHANGE, MAX_UINT256),
// CTF (ERC1155) approvals for V2 exchange contracts
erc1155ApproveAll(CTF, CTF_EXCHANGE),
erc1155ApproveAll(CTF, NEG_RISK_CTF_EXCHANGE),
erc1155ApproveAll(CTF, NEG_RISK_ADAPTER)
erc1155ApproveAll(CTF, NEG_RISK_ADAPTER),
// USDC.e approval for CollateralOnramp (wrapping USDC.e → pUSD)
erc20Approve(USDC_E, COLLATERAL_ONRAMP, MAX_UINT256)
];
approvalLabels = [
'USDC.e → NEG_RISK_ADAPTER',
'USDC.e → NEG_RISK_CTF_EXCHANGE',
'CTF → CTF_EXCHANGE',
'CTF → NEG_RISK_CTF_EXCHANGE',
'CTF → NEG_RISK_ADAPTER'
'pUSD → NEG_RISK_ADAPTER',
'pUSD → NEG_RISK_CTF_EXCHANGE',
'CTF → CTF_EXCHANGE (V2)',
'CTF → NEG_RISK_CTF_EXCHANGE (V2)',
'CTF → NEG_RISK_ADAPTER',
'USDC.e → COLLATERAL_ONRAMP (for wrapping)'
];
} else {
txBatch = [
erc20Approve(USDC_E, CTF_EXCHANGE, MAX_UINT256),
erc1155ApproveAll(CTF, CTF_EXCHANGE)
// pUSD approval for V2 exchange contract
erc20Approve(PUSD, CTF_EXCHANGE, MAX_UINT256),
// CTF (ERC1155) approval for V2 exchange contract
erc1155ApproveAll(CTF, CTF_EXCHANGE),
// USDC.e approval for CollateralOnramp (wrapping USDC.e → pUSD)
erc20Approve(USDC_E, COLLATERAL_ONRAMP, MAX_UINT256)
];
approvalLabels = [
'pUSD → CTF_EXCHANGE (V2)',
'CTF → CTF_EXCHANGE (V2)',
'USDC.e → COLLATERAL_ONRAMP (for wrapping)'
];
approvalLabels = ['USDC.e → CTF_EXCHANGE', 'CTF → CTF_EXCHANGE'];
}

if (!broadcast) {
Expand Down Expand Up @@ -290,12 +305,13 @@ async function handleClobBuy(argv: {
price: priceArg ?? 'market',
proxyWalletAddress,
flow: skipFund
? ['Place CLOB BUY order (using existing proxy wallet USDC.e balance)']
? ['Place CLOB BUY order (using existing proxy wallet pUSD balance)']
: [
`Smart wallet (${walletName}) → fund proxy wallet with ${amountUsd} USDC.e`,
'Proxy wallet wraps USDC.e → pUSD via CollateralOnramp',
'Place CLOB BUY order (maker=proxyWallet, signatureType=POLY_PROXY)'
],
note: 'Requires proxy wallet approvals — run `polymarket approve --broadcast` once first. Re-run with --broadcast to execute.'
note: 'Requires proxy wallet approvals for V2 exchange — run `polymarket approve --broadcast` first. Re-run with --broadcast to execute.'
},
null,
2
Expand All @@ -314,20 +330,21 @@ async function handleClobBuy(argv: {
const account = privateKeyToAccount(privateKey as `0x${string}`);
const proxyWalletAddress = await getPolymarketProxyWalletAddress(account.address);
process.stderr.write(
`[polymarket] CLOB BUY ${amountUsd} USDC → ${outcomeArg} via proxy wallet ${proxyWalletAddress}\n`
`[polymarket] CLOB V2 BUY ${amountUsd} USDC → ${outcomeArg} via proxy wallet ${proxyWalletAddress}\n`
);

let fundTxHash: string | null = null;
let wrapTxHash: string | null = null;
if (skipFund) {
process.stderr.write(`[polymarket] --skip-fund: using existing proxy wallet balance\n`);
process.stderr.write(`[polymarket] --skip-fund: using existing proxy wallet pUSD balance\n`);
} else {
process.stderr.write(
`[polymarket] Funding proxy wallet ${proxyWalletAddress} with ${amountUsd} USDC.e...\n`
);
const amountUnits = BigInt(Math.round(amountUsd * 1e6));
const pad = (hex: string, n = 64) => String(hex).replace(/^0x/, '').padStart(n, '0');
const padHex = (hex: string, n = 64) => String(hex).replace(/^0x/, '').padStart(n, '0');
const transferData =
'0xa9059cbb' + pad(proxyWalletAddress) + pad('0x' + amountUnits.toString(16));
'0xa9059cbb' + padHex(proxyWalletAddress) + padHex('0x' + amountUnits.toString(16));
const fundResult = await runDappClientTx({
walletName,
chainId: 137,
Expand All @@ -337,6 +354,39 @@ async function handleClobBuy(argv: {
});
fundTxHash = fundResult.txHash ?? null;
process.stderr.write(`[polymarket] Funded: ${fundTxHash}\n`);

// Wrap USDC.e → pUSD via CollateralOnramp (executed from proxy wallet)
process.stderr.write(
`[polymarket] Wrapping ${amountUsd} USDC.e → pUSD via CollateralOnramp...\n`
);
const {
createWalletClient: cwc,
createPublicClient: cpc,
http: httpTransport
} = await import('viem');
const { polygon: polygonChain } = await import('viem/chains');
const wrapWalletClient = cwc({
account,
chain: polygonChain,
transport: httpTransport()
});
const wrapPublicClient = cpc({ chain: polygonChain, transport: httpTransport() });

// CollateralOnramp.wrap(address _asset, address _to, uint256 _amount)
// selector: keccak256("wrap(address,address,uint256)") = 0x62355638
const wrapData =
'0x62355638' +
padHex(USDC_E) +
padHex(proxyWalletAddress) +
padHex('0x' + amountUnits.toString(16));

wrapTxHash = await executeViaProxyWallet(
wrapWalletClient,
wrapPublicClient,
proxyWalletAddress,
[{ typeCode: 1, to: COLLATERAL_ONRAMP, value: '0', data: wrapData }]
);
process.stderr.write(`[polymarket] Wrapped to pUSD: ${wrapTxHash}\n`);
}

let orderResult;
Expand Down Expand Up @@ -374,6 +424,7 @@ async function handleClobBuy(argv: {
proxyWalletAddress,
signerAddress: account.address,
fundTxHash,
wrapTxHash,
orderId: orderResult?.orderId || orderResult?.orderID || orderResult?.id || null,
orderType,
orderStatus: orderResult?.status || null
Expand Down
44 changes: 24 additions & 20 deletions packages/polygon-agent-cli/src/lib/polymarket.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Polymarket integration library
// Covers: Gamma API (market discovery), CLOB API (trading via @polymarket/clob-client), on-chain ops
// Polymarket integration library — CLOB V2
// Covers: Gamma API (market discovery), CLOB V2 API (trading via @polymarket/clob-client-v2), on-chain ops
//
// Architecture: Sequence smart wallet → Polymarket proxy wallet → CLOB
// - Sequence smart wallet funds the Polymarket proxy wallet (USDC.e transfer)
// - EOA calls proxy.execute([approve, split]) to run on-chain ops FROM the proxy wallet
// - Proxy wallet wraps USDC.e → pUSD via CollateralOnramp before trading
// - EOA calls proxy.execute([approve, wrap]) to run on-chain ops FROM the proxy wallet
// - CLOB orders use maker=proxyWallet, signer=EOA, signatureType=POLY_PROXY

// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -40,10 +41,13 @@ export const DATA_URL = process.env.POLYMARKET_DATA_URL || 'https://data-api.pol

// Polygon mainnet (chain 137)
export const USDC_E = '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174'; // USDC.e — 6 decimals
export const PUSD = '0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB'; // pUSD — Polymarket USD, 6 decimals
export const CTF = '0x4D97DCd97eC945f40cF65F87097ACe5EA0476045'; // Conditional Token Framework
export const CTF_EXCHANGE = '0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E'; // CLOB exchange
export const NEG_RISK_CTF_EXCHANGE = '0xC5d563A36AE78145C45a50134d48A1215220f80a';
export const CTF_EXCHANGE = '0xE111180000d2663C0091e4f400237545B87B996B'; // CLOB V2 exchange
export const NEG_RISK_CTF_EXCHANGE = '0xe2222d279d744050d28e00520010520000310F59'; // V2 neg-risk exchange
export const NEG_RISK_ADAPTER = '0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296';
export const COLLATERAL_ONRAMP = '0x93070a847efEf7F70739046A929D47a521F5B8ee'; // USDC.e → pUSD wrapping
export const COLLATERAL_OFFRAMP = '0x2957922Eb93258b93368531d39fAcCA3B4dC5854'; // pUSD → USDC.e unwrapping

// Polymarket proxy wallet factory (Polygon mainnet)
export const PROXY_WALLET_FACTORY = '0xaB45c5A4B0c941a2F231C04C3f49182e1A254052';
Expand All @@ -56,10 +60,10 @@ export async function getPolymarketProxyWalletAddress(eoaAddress: string): Promi
return getProxyWalletAddress(PROXY_WALLET_FACTORY, eoaAddress);
}

// Proxy wallet execute ABI: execute(Transaction[]) — selector 0x34ee9791
// Proxy wallet ABI: proxy(Transaction[]) — selector 0x34ee9791
const PROXY_EXECUTE_ABI = [
{
name: 'execute',
name: 'proxy',
type: 'function',
inputs: [
{
Expand Down Expand Up @@ -93,7 +97,7 @@ export async function executeViaProxyWallet(
}));
const data = encodeFunctionData({
abi: PROXY_EXECUTE_ABI,
functionName: 'execute',
functionName: 'proxy',
args: [transactions]
});
const hash = await walletClient.sendTransaction({ to: PROXY_WALLET_FACTORY, data, value: 0n });
Expand Down Expand Up @@ -221,29 +225,27 @@ export async function getOrderBook(tokenId: string): Promise<any> {
return res.json();
}

// ─── CLOB API — @polymarket/clob-client ─────────────────────────────────────
// ─── CLOB V2 API — @polymarket/clob-client-v2 ──────────────────────────────

async function getClobClient(
privateKey: string,
proxyWalletAddress?: string
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): Promise<{ client: any; creds: any; address: string }> {
const { Wallet } = await import('ethers5');
const { ClobClient } = await import('@polymarket/clob-client');
const { SignatureType } = await import('@polymarket/order-utils');
const { ClobClient, SignatureTypeV2 } = await import('@polymarket/clob-client-v2');
const signer = new Wallet(privateKey);
const chainId = 137;
const anonClient = new ClobClient(CLOB_URL, chainId, signer);
const anonClient = new ClobClient({ host: CLOB_URL, chain: 137, signer });
const creds = await anonClient.createOrDeriveApiKey();
const signatureType = proxyWalletAddress ? SignatureType.POLY_PROXY : SignatureType.EOA;
const client = new ClobClient(
CLOB_URL,
chainId,
const signatureType = proxyWalletAddress ? SignatureTypeV2.POLY_PROXY : SignatureTypeV2.EOA;
const client = new ClobClient({
host: CLOB_URL,
chain: 137,
signer,
creds,
signatureType,
proxyWalletAddress
);
funderAddress: proxyWalletAddress
});
return { client, creds, address: await signer.getAddress() };
}

Expand All @@ -259,7 +261,7 @@ export async function cancelOrder(orderId: string, privateKey: string): Promise<
return client.cancelOrder({ orderID: orderId });
}

// ─── CLOB API — order creation ───────────────────────────────────────────────
// ─── CLOB V2 API — order creation ───────────────────────────────────────────

export async function createAndPostOrder({
tokenId,
Expand All @@ -280,6 +282,7 @@ export async function createAndPostOrder({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}): Promise<any> {
const { client } = await getClobClient(privateKey, proxyWalletAddress);
// V2: client auto-fetches tickSize and negRisk from getClobMarketInfo
const order = await client.createOrder({
tokenID: tokenId,
price,
Expand All @@ -306,6 +309,7 @@ export async function createAndPostMarketOrder({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}): Promise<any> {
const { client } = await getClobClient(privateKey, proxyWalletAddress);
// V2: no feeRateBps — fees determined by protocol at match time
const order = await client.createMarketOrder({
tokenID: tokenId,
side,
Expand Down
Loading
Loading