From 1a67055e4f02b04f4fbf16e076d500901dcc47f2 Mon Sep 17 00:00:00 2001 From: AkshatGada Date: Thu, 5 Mar 2026 06:17:41 +0530 Subject: [PATCH 1/2] feat(polymarket): port polymarket feature to TypeScript Add polymarket trading commands (approve, clob-buy, sell, markets, proxy-wallet, set-key, orders, cancel, positions) as a yargs CommandModule following the new src/commands/*.ts structure. - src/commands/polymarket.ts: all 10 subcommands - src/lib/polymarket.ts: Gamma API, CLOB client, proxy wallet helpers - src/lib/storage.ts: add savePolymarketKey + loadPolymarketKey - src/index.ts: register polymarketCommand - package.json: add @polymarket/clob-client, @polymarket/sdk, ethers5 - skills/POLYMARKET.md: full reference doc Co-Authored-By: Claude Sonnet 4.6 --- packages/polygon-agent-cli/package.json | 3 + .../polygon-agent-cli/skills/POLYMARKET.md | 460 +++++++++++ .../src/commands/polymarket.ts | 732 ++++++++++++++++++ packages/polygon-agent-cli/src/index.ts | 4 +- .../polygon-agent-cli/src/lib/polymarket.ts | 359 +++++++++ packages/polygon-agent-cli/src/lib/storage.ts | 24 + pnpm-lock.yaml | 542 +++++++++++++ 7 files changed, 2123 insertions(+), 1 deletion(-) create mode 100644 packages/polygon-agent-cli/skills/POLYMARKET.md create mode 100644 packages/polygon-agent-cli/src/commands/polymarket.ts create mode 100644 packages/polygon-agent-cli/src/lib/polymarket.ts diff --git a/packages/polygon-agent-cli/package.json b/packages/polygon-agent-cli/package.json index 65521ff..b4776dd 100644 --- a/packages/polygon-agent-cli/package.json +++ b/packages/polygon-agent-cli/package.json @@ -35,10 +35,13 @@ "@0xsequence/wallet-primitives": "3.0.0-beta.16", "@0xsequence/wallet-wdk": "3.0.0-beta.16", "@0xtrails/api": "^0.10.4", + "@polymarket/clob-client": "^5.2.4", + "@polymarket/sdk": "^6.0.1", "@x402/core": "^2.3.1", "@x402/evm": "^2.3.1", "@x402/fetch": "^2.3.0", "dotenv": "^16.4.5", + "ethers5": "npm:ethers@^5.7.4", "ethers": "^6.13.0", "tweetnacl": "^1.0.3", "tweetnacl-sealedbox-js": "^1.2.0", diff --git a/packages/polygon-agent-cli/skills/POLYMARKET.md b/packages/polygon-agent-cli/skills/POLYMARKET.md new file mode 100644 index 0000000..967059e --- /dev/null +++ b/packages/polygon-agent-cli/skills/POLYMARKET.md @@ -0,0 +1,460 @@ +--- +name: polymarket +description: Place bets on Polymarket prediction markets via the Polygon Agent CLI. Browse markets, buy YES/NO positions, manage CLOB orders, check positions. Uses Sequence ecosystem wallet → Polymarket proxy wallet → CLOB (POLY_PROXY signature type). +--- + +# Polymarket Integration + +## Architecture + +Polymarket trading requires three layers working together: + +``` +Sequence Smart Wallet (holds USDC.e) + │ + │ ① USDC.e transfer (smart wallet tx, USDC gas fee) + ▼ +Polymarket Proxy Wallet (deterministic CREATE2, owned by EOA) + │ + │ ② On-chain batch: approve + split (EOA signs, EOA pays POL gas) + ▼ +CLOB — order maker = proxy wallet, signer = EOA (POLY_PROXY sig type) +``` + +### The Three Actors + +| Actor | Created by | Role | +|-------|-----------|------| +| Sequence smart wallet | `polygon-agent wallet create` | Holds USDC.e; funds the proxy wallet | +| Polymarket proxy wallet | Polymarket factory (deterministic) | On-chain identity for CLOB trading; holds outcome tokens | +| Builder EOA | `polygon-agent setup` | Signs all on-chain txs and CLOB API requests | + +### Why a Proxy Wallet? + +Polymarket's CLOB requires every order to be signed by a private key. +A Sequence smart contract wallet cannot sign CLOB payloads directly. +The proxy wallet pattern solves this: the EOA (which has a private key) **owns** the proxy wallet and signs on its behalf (`signatureType=POLY_PROXY`), while the smart wallet is the ultimate source of funds. + +The proxy wallet address is **deterministic** — computed via CREATE2 from the EOA address and the Polymarket factory (`0xaB45c5A4B0c941a2F231C04C3f49182e1A254052`). It never changes. + +### Gas Model + +| Operation | Who pays gas | Token | +|-----------|-------------|-------| +| USDC.e transfer (smart wallet → proxy) | Sequence relayer | USDC.e (fee abstraction) | +| On-chain batch (approve + split) via `proxy.execute()` | Builder EOA | POL (native) | +| CLOB order posting | Off-chain (no gas) | — | + +**The EOA must hold native POL for gas.** The Sequence gas abstraction only applies to smart wallet transactions. Send ~0.1 POL to the EOA before first use. + +--- + +## Prerequisites (One-Time Setup) + +```bash +# 1. Setup builder EOA + Sequence project +polygon-agent setup --name "MyAgent" +# → save accessKey and eoaAddress from output + +# 2. Export access key +export SEQUENCE_PROJECT_ACCESS_KEY= +export SEQUENCE_INDEXER_ACCESS_KEY=$SEQUENCE_PROJECT_ACCESS_KEY + +# 3. Create ecosystem wallet with USDC spending limit +polygon-agent wallet create --usdc-limit 100 + +# 4. Fund the smart wallet with USDC.e on Polygon +polygon-agent fund +# → open the returned fundingUrl in browser, deposit via Trails widget + +# 5. Fund the builder EOA with native POL for gas +# EOA address is in ~/.polygon-agent/builder.json → eoaAddress field +polygon-agent send-native --to --amount 0.1 --broadcast + +# 6. Show your proxy wallet address (for reference) +polygon-agent polymarket proxy-wallet + +# 7. Required for orders/cancel — accept Polymarket terms +# Visit https://polymarket.com, connect the builder EOA wallet, accept terms once. +``` + +--- + +## Commands Reference + +### `polymarket markets` + +List active Polymarket markets sorted by 24-hour volume. + +```bash +polygon-agent polymarket markets [--search ] [--limit ] [--offset ] +``` + +| Flag | Default | Description | +|------|---------|-------------| +| `--search` | — | Client-side substring filter on market question | +| `--limit` | 20 | Max markets to return | +| `--offset` | 0 | Pagination offset | + +```bash +# Top 20 by volume +polygon-agent polymarket markets + +# Search for Bitcoin markets +polygon-agent polymarket markets --search "bitcoin" --limit 10 + +# Paginate +polygon-agent polymarket markets --limit 20 --offset 20 +``` + +**Output fields per market:** +- `conditionId` — unique market ID used in all other commands +- `question` — the market question text +- `yesPrice` / `noPrice` — current implied probabilities (0–1, e.g. `0.65` = 65% chance) +- `yesTokenId` / `noTokenId` — CLOB token IDs +- `volume24hr` — 24h trading volume in USD +- `negRisk` — `true` if neg-risk market (different approval flow, handled automatically) +- `endDate` — market resolution date + +--- + +### `polymarket market ` + +Get full details for a single market. + +```bash +polygon-agent polymarket market +``` + +Use this to confirm token IDs and current prices before placing a bet. The Gamma API does not support direct conditionId lookup — internally scans up to 500 markets by volume then falls back to a closed-market query. + +--- + +### `polymarket proxy-wallet` + +Show the Polymarket proxy wallet address derived from your builder EOA. + +```bash +polygon-agent polymarket proxy-wallet +``` + +Output: +```json +{ + "ok": true, + "eoaAddress": "0x1218...", + "proxyWalletAddress": "0xabcd...", + "note": "Fund proxyWalletAddress with USDC.e on Polygon to enable CLOB trading." +} +``` + +The proxy wallet is the actual holder of outcome tokens after a buy. It is permanent and deterministic per EOA. + +--- + +### `polymarket approve` + +Set the required on-chain approvals on the proxy wallet. **Run once before first use** — approvals are permanent on-chain. + +```bash +polygon-agent polymarket approve [--neg-risk] [--broadcast] +``` + +| Flag | Description | +|------|-------------| +| `--neg-risk` | Set neg-risk approvals (adds `NEG_RISK_ADAPTER` + `NEG_RISK_CTF_EXCHANGE`) | +| `--broadcast` | Execute. Without this flag, shows a dry-run plan. EOA must have POL (~0.001 POL). | + +**What it sets (regular markets):** +``` +USDC.e → CTF_EXCHANGE (ERC20 approve, max) +CTF → CTF_EXCHANGE (setApprovalForAll) +``` + +**What it sets (`--neg-risk`):** +``` +USDC.e → NEG_RISK_ADAPTER (ERC20 approve, max) +USDC.e → NEG_RISK_CTF_EXCHANGE (ERC20 approve, max) +CTF → CTF_EXCHANGE (setApprovalForAll) +CTF → NEG_RISK_CTF_EXCHANGE (setApprovalForAll) +CTF → NEG_RISK_ADAPTER (setApprovalForAll) +``` + +All approvals are bundled in a single `proxy.execute()` transaction. + +```bash +# Dry-run — see what will be set +polygon-agent polymarket approve + +# Execute +polygon-agent polymarket approve --broadcast + +# For neg-risk markets +polygon-agent polymarket approve --neg-risk --broadcast +``` + +**Success output:** +```json +{ + "ok": true, + "proxyWalletAddress": "0xabcd...", + "signerAddress": "0x1218...", + "negRisk": false, + "approveTxHash": "0x...", + "note": "Proxy wallet approvals set. Ready for clob-buy and sell." +} +``` + +--- + +### `polymarket clob-buy YES|NO ` + +The primary command for entering a position. Funds the proxy wallet from the smart wallet, then buys outcome tokens directly from the CLOB. + +**Prerequisite:** Run `polymarket approve --broadcast` once before first use. + +```bash +polygon-agent polymarket clob-buy YES|NO \ + [--price <0-1>] \ + [--fak] \ + [--wallet ] \ + [--skip-fund] \ + [--broadcast] +``` + +| Argument / Flag | Description | +|----------------|-------------| +| `conditionId` | Market ID from `markets` or `market` | +| `YES` or `NO` | Outcome to buy | +| `usdcAmount` | USDC.e to spend (e.g. `10` = $10) | +| `--price <0-1>` | GTC limit order at this price. Omit = market order (FOK or FAK) | +| `--fak` | FAK (fill-and-kill, allows partial fill) instead of FOK | +| `--wallet ` | Smart wallet to fund from (default: `main`) | +| `--skip-fund` | Skip smart wallet → proxy transfer; use existing proxy balance | +| `--broadcast` | Execute. Without this flag, prints a dry-run plan with no funds moved. | + +#### What happens internally (with `--broadcast`) + +**Step 1 — Fund proxy wallet** +Smart wallet transfers `usdcAmount` USDC.e to the proxy wallet. Sequence tx — paid in USDC.e via fee abstraction. + +**Step 2 — CLOB BUY order** +Posts a BUY order: `maker=proxyWallet`, `signer=EOA`, `signatureType=POLY_PROXY`. +Tokens arrive in the proxy wallet. + +```bash +# Market buy $10 USDC worth of YES tokens +polygon-agent polymarket clob-buy \ + 0x5e5c9dfaf695371a0cc321b47b35f66a6dbd1482f0503526603d2bd2a91bfdc7 \ + YES 10 --broadcast + +# Limit buy — fill only if price ≤ 0.65 +polygon-agent polymarket clob-buy \ + 0x5e5c9dfaf695371a0cc321b47b35f66a6dbd1482f0503526603d2bd2a91bfdc7 \ + YES 10 --price 0.65 --broadcast + +# Skip re-funding (proxy already has USDC.e) +polygon-agent polymarket clob-buy \ + 0x5e5c9dfaf695371a0cc321b47b35f66a6dbd1482f0503526603d2bd2a91bfdc7 \ + YES 5 --skip-fund --broadcast +``` + +**Success output:** +```json +{ + "ok": true, + "conditionId": "0x5e5c...", + "question": "Will Bitcoin reach $150,000?", + "outcome": "YES", + "amountUsd": 10, + "currentPrice": 0.65, + "proxyWalletAddress": "0xabcd...", + "signerAddress": "0x1218...", + "fundTxHash": "0x...", + "orderId": "0x...", + "orderType": "FOK" +} +``` + +--- + +### `polymarket sell YES|NO ` + +Sell outcome tokens held in the proxy wallet via a CLOB SELL order. Pure off-chain — no on-chain transaction, no gas needed. + +```bash +polygon-agent polymarket sell YES|NO \ + [--price <0-1>] \ + [--fak] \ + [--broadcast] +``` + +| Argument / Flag | Description | +|----------------|-------------| +| `` | Number of outcome tokens to sell (e.g. `10` = 10 YES tokens) | +| `--price <0-1>` | GTC limit order. Omit = FOK market order | +| `--fak` | FAK (partial fill allowed) instead of FOK | +| `--broadcast` | Execute | + +```bash +# Market sell 10 YES tokens +polygon-agent polymarket sell \ + 0x5e5c9dfaf695371a0cc321b47b35f66a6dbd1482f0503526603d2bd2a91bfdc7 \ + YES 10 --broadcast + +# Limit sell — only fill at 0.80 or above +polygon-agent polymarket sell \ + 0x5e5c9dfaf695371a0cc321b47b35f66a6dbd1482f0503526603d2bd2a91bfdc7 \ + YES 10 --price 0.80 --broadcast +``` + +--- + +### `polymarket positions` + +List open positions for the smart wallet address (queries Polymarket Data API). + +```bash +polygon-agent polymarket positions [--wallet ] +``` + +Note: outcome tokens from `buy` are held in the **proxy wallet**, not the smart wallet. This command queries by smart wallet address — it may not show proxy wallet holdings. To see proxy wallet holdings, check polymarket.com using the address from `proxy-wallet`. + +--- + +### `polymarket orders` + +List open CLOB orders placed by the builder EOA. + +```bash +polygon-agent polymarket orders +``` + +Authenticates via EIP-712 L1 auth + HMAC L2 credentials derived from the EOA private key (via `@polymarket/clob-client`). Requires the EOA to have accepted Polymarket terms at polymarket.com. + +--- + +### `polymarket cancel ` + +Cancel an open CLOB order by ID. + +```bash +polygon-agent polymarket cancel +``` + +```bash +# Get order IDs first +polygon-agent polymarket orders + +# Cancel +polygon-agent polymarket cancel 0xabc123... +``` + +--- + +## Full Flow: End-to-End Example + +```bash +# --- Prerequisites (run once) --- +export SEQUENCE_PROJECT_ACCESS_KEY= +export SEQUENCE_INDEXER_ACCESS_KEY=$SEQUENCE_PROJECT_ACCESS_KEY + +# Fund EOA with POL for gas (eoaAddress is in ~/.polygon-agent/builder.json) +polygon-agent send-native --to --amount 0.1 --broadcast + +# Set proxy wallet approvals (one-time, permanent) +polygon-agent polymarket approve --broadcast +# For neg-risk markets: polygon-agent polymarket approve --neg-risk --broadcast + +# --- Find a market --- +polygon-agent polymarket markets --search "trump" --limit 5 +# → note the conditionId you want + +# Inspect it +polygon-agent polymarket market 0x +# → check yesPrice, noPrice, endDate, negRisk + +# --- Place a bet --- +# Dry-run first +polygon-agent polymarket clob-buy 0x YES 10 + +# Execute (funds proxy wallet + places CLOB BUY) +polygon-agent polymarket clob-buy 0x YES 10 --broadcast + +# --- Manage --- +# Check open orders +polygon-agent polymarket orders + +# Cancel if needed +polygon-agent polymarket cancel + +# Sell your position +polygon-agent polymarket sell 0x YES 10 --broadcast +``` + +--- + +## Contracts (Polygon Mainnet, Chain 137) + +| Contract | Address | +|----------|---------| +| USDC.e | `0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174` | +| CTF (Conditional Token Framework) | `0x4D97DCd97eC945f40cF65F87097ACe5EA0476045` | +| CTF Exchange | `0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E` | +| Neg Risk CTF Exchange | `0xC5d563A36AE78145C45a50134d48A1215220f80a` | +| Neg Risk Adapter | `0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296` | +| Proxy Wallet Factory | `0xaB45c5A4B0c941a2F231C04C3f49182e1A254052` | + +--- + +## APIs Used + +| API | Base URL | Auth | +|-----|----------|------| +| Gamma (market discovery) | `https://gamma-api.polymarket.com` | None | +| CLOB (trading) | `https://clob.polymarket.com` | EIP-712 L1 + HMAC L2 (via `@polymarket/clob-client`) | +| Data (positions) | `https://data-api.polymarket.com` | None | + +Override via env: `POLYMARKET_GAMMA_URL`, `POLYMARKET_CLOB_URL`, `POLYMARKET_DATA_URL`. + +--- + +## Neg-Risk Markets + +When `negRisk: true` in market data, `polymarket approve --neg-risk` sets the extended approval set: + +| | Regular Market | Neg-Risk Market (`--neg-risk`) | +|-|---------------|-------------------------------| +| USDC.e approval | → CTF Exchange | → Neg Risk Adapter + Neg Risk CTF Exchange | +| CTF approval | → CTF Exchange | → CTF Exchange + Neg Risk CTF Exchange + Neg Risk Adapter | + +Run `polymarket approve --neg-risk --broadcast` to set neg-risk approvals, then `clob-buy` works normally. + +--- + +## Troubleshooting + +| Error | Cause | Fix | +|-------|-------|-----| +| `insufficient funds for gas` | EOA has no POL | `polygon-agent send-native --to --amount 0.1 --broadcast` | +| `Could not create api key` | EOA not registered on Polymarket | Visit polymarket.com, connect EOA wallet, accept terms | +| `CLOB order error: 403` | Cloudflare blocking POST | Set `HTTPS_PROXY` env var or retry | +| `Market not found` | conditionId not in top 500 by volume | Market may be low-volume or closed | +| `Market has no tokenIds` | Market closed or not CLOB-deployed | Check `endDate` — may have resolved | +| `Wallet not found: main` | No ecosystem wallet | Run `polygon-agent wallet create` | +| `Builder EOA not found` | Setup not done | Run `polygon-agent setup` | +| `Session expired` | 24h session window elapsed | Re-run `polygon-agent wallet create` | +| `clob-buy` fails with approval error | Proxy wallet not approved | Run `polygon-agent polymarket approve --broadcast` | + +--- + +## Key Behaviours + +| Behaviour | Detail | +|-----------|--------| +| Dry-run by default | `approve`, `clob-buy`, `sell` without `--broadcast` show a plan; no funds moved | +| Proxy wallet is permanent | Same address forever — derived from EOA via CREATE2. Token balances persist across sessions. | +| Approvals are one-time | Run `approve --broadcast` once. Permanent on-chain — no need to repeat unless switching to neg-risk markets. | +| CLOB-only flow | `clob-buy` buys tokens directly from the order book. No minting/splitting. | +| CLOB auth is stateless | Credentials derived on-the-fly via `createOrDeriveApiKey()` from the EOA private key. No stored CLOB API keys needed. | +| `ethers5` alias | `@polymarket/clob-client` requires ethers v5. It is aliased as `ethers5` in `package.json` alongside the main `ethers` v6 dependency. Both coexist without conflict. | diff --git a/packages/polygon-agent-cli/src/commands/polymarket.ts b/packages/polygon-agent-cli/src/commands/polymarket.ts new file mode 100644 index 0000000..4c09ecf --- /dev/null +++ b/packages/polygon-agent-cli/src/commands/polymarket.ts @@ -0,0 +1,732 @@ +// Polymarket CLI commands +// Architecture: Sequence smart wallet → Polymarket proxy wallet → CLOB +// - `approve`: sets on-chain approvals on proxy wallet (one-time) +// - `clob-buy`: funds proxy wallet from smart wallet, then places CLOB BUY order +// - CLOB orders: maker=proxyWallet, signer=EOA, signatureType=POLY_PROXY + +import type { CommandModule } from 'yargs'; + +import { runDappClientTx } from '../lib/dapp-client.ts'; +import { + getMarkets, + getMarket, + getOpenOrders, + cancelOrder, + createAndPostOrder, + createAndPostMarketOrder, + getPolymarketProxyWalletAddress, + executeViaProxyWallet, + getPositions, + USDC_E, + CTF, + CTF_EXCHANGE, + NEG_RISK_CTF_EXCHANGE, + NEG_RISK_ADAPTER +} from '../lib/polymarket.ts'; +import { loadWalletSession, savePolymarketKey, loadPolymarketKey } from '../lib/storage.ts'; + +// ─── handlers ──────────────────────────────────────────────────────────────── + +async function handleMarkets(argv: { + search?: string; + limit?: number; + offset?: number; +}): Promise { + try { + const markets = await getMarkets({ + search: argv.search, + limit: argv.limit ?? 20, + offset: argv.offset ?? 0 + }); + console.log(JSON.stringify({ ok: true, count: markets.length, markets }, null, 2)); + } catch (err) { + console.error(JSON.stringify({ ok: false, error: (err as Error).message }, null, 2)); + process.exit(1); + } +} + +async function handleMarket(argv: { conditionId: string }): Promise { + try { + const market = await getMarket(argv.conditionId); + console.log(JSON.stringify({ ok: true, market }, null, 2)); + } catch (err) { + console.error(JSON.stringify({ ok: false, error: (err as Error).message }, null, 2)); + process.exit(1); + } +} + +async function handleSetKey(argv: { privateKey: string }): Promise { + const pk = argv.privateKey.startsWith('0x') ? argv.privateKey : `0x${argv.privateKey}`; + + if (!/^0x[0-9a-fA-F]{64}$/.test(pk)) { + console.error( + JSON.stringify( + { ok: false, error: 'Invalid private key — must be 32 bytes (64 hex chars)' }, + null, + 2 + ) + ); + process.exit(1); + } + + try { + const { privateKeyToAccount } = await import('viem/accounts'); + const account = privateKeyToAccount(pk as `0x${string}`); + const proxyWalletAddress = await getPolymarketProxyWalletAddress(account.address); + + await savePolymarketKey(pk); + + console.log( + JSON.stringify( + { + ok: true, + eoaAddress: account.address, + proxyWalletAddress, + note: 'Polymarket signing key saved (encrypted). All polymarket commands will use this EOA. Remember to accept Polymarket ToS at polymarket.com with this address.' + }, + null, + 2 + ) + ); + } catch (err) { + console.error(JSON.stringify({ ok: false, error: (err as Error).message }, null, 2)); + process.exit(1); + } +} + +async function handleProxyWallet(): Promise { + try { + const privateKey = await loadPolymarketKey(); + const { privateKeyToAccount } = await import('viem/accounts'); + const account = privateKeyToAccount(privateKey as `0x${string}`); + const proxyWalletAddress = await getPolymarketProxyWalletAddress(account.address); + + console.log( + JSON.stringify( + { + ok: true, + eoaAddress: account.address, + proxyWalletAddress, + note: 'Fund proxyWalletAddress with USDC.e on Polygon to enable CLOB trading.' + }, + null, + 2 + ) + ); + } catch (err) { + console.error(JSON.stringify({ ok: false, error: (err as Error).message }, null, 2)); + process.exit(1); + } +} + +async function handleApprove(argv: { negRisk?: boolean; broadcast?: boolean }): Promise { + const negRisk = argv.negRisk ?? false; + const broadcast = argv.broadcast ?? false; + + try { + const privateKey = await loadPolymarketKey(); + const { privateKeyToAccount } = await import('viem/accounts'); + const account = privateKeyToAccount(privateKey as `0x${string}`); + const proxyWalletAddress = await getPolymarketProxyWalletAddress(account.address); + + const MAX_UINT256 = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; + const pad = (val: string, n = 64) => val.replace(/^0x/, '').padStart(n, '0'); + const erc20Approve = (token: string, spender: string, amount: string) => ({ + typeCode: 1, + to: token, + value: '0', + data: '0x095ea7b3' + pad(spender) + pad(amount) + }); + const erc1155ApproveAll = (token: string, operator: string) => ({ + typeCode: 1, + to: token, + value: '0', + data: '0xa22cb465' + pad(operator) + pad('0x01') + }); + + let txBatch; + let approvalLabels: string[]; + if (negRisk) { + txBatch = [ + erc20Approve(USDC_E, NEG_RISK_ADAPTER, MAX_UINT256), + erc20Approve(USDC_E, NEG_RISK_CTF_EXCHANGE, MAX_UINT256), + erc1155ApproveAll(CTF, CTF_EXCHANGE), + erc1155ApproveAll(CTF, NEG_RISK_CTF_EXCHANGE), + erc1155ApproveAll(CTF, NEG_RISK_ADAPTER) + ]; + approvalLabels = [ + 'USDC.e → NEG_RISK_ADAPTER', + 'USDC.e → NEG_RISK_CTF_EXCHANGE', + 'CTF → CTF_EXCHANGE', + 'CTF → NEG_RISK_CTF_EXCHANGE', + 'CTF → NEG_RISK_ADAPTER' + ]; + } else { + txBatch = [ + erc20Approve(USDC_E, CTF_EXCHANGE, MAX_UINT256), + erc1155ApproveAll(CTF, CTF_EXCHANGE) + ]; + approvalLabels = ['USDC.e → CTF_EXCHANGE', 'CTF → CTF_EXCHANGE']; + } + + if (!broadcast) { + console.log( + JSON.stringify( + { + ok: true, + dryRun: true, + proxyWalletAddress, + signerAddress: account.address, + negRisk, + approvals: approvalLabels, + note: 'Re-run with --broadcast to execute. EOA must have POL for gas.' + }, + null, + 2 + ) + ); + return; + } + + const { createWalletClient, createPublicClient, http } = await import('viem'); + const { polygon } = await import('viem/chains'); + const walletClient = createWalletClient({ account, chain: polygon, transport: http() }); + const publicClient = createPublicClient({ chain: polygon, transport: http() }); + + process.stderr.write( + `[polymarket] Setting ${txBatch.length} approvals on proxy wallet ${proxyWalletAddress}...\n` + ); + const approveTxHash = await executeViaProxyWallet( + walletClient, + publicClient, + proxyWalletAddress, + txBatch + ); + process.stderr.write(`[polymarket] Approvals set: ${approveTxHash}\n`); + + console.log( + JSON.stringify( + { + ok: true, + proxyWalletAddress, + signerAddress: account.address, + negRisk, + approveTxHash, + note: 'Proxy wallet approvals set. Ready for clob-buy and sell.' + }, + null, + 2 + ) + ); + } catch (err) { + console.error( + JSON.stringify( + { ok: false, error: (err as Error).message, stack: (err as Error).stack }, + null, + 2 + ) + ); + process.exit(1); + } +} + +async function handleClobBuy(argv: { + conditionId: string; + outcome: string; + amount: number; + wallet?: string; + price?: number; + fak?: boolean; + skipFund?: boolean; + broadcast?: boolean; +}): Promise { + const conditionId = argv.conditionId; + const outcomeArg = argv.outcome.toUpperCase(); + const amountUsd = argv.amount; + const walletName = argv.wallet ?? 'main'; + const priceArg = argv.price; + const useFak = argv.fak ?? false; + const skipFund = argv.skipFund ?? false; + const broadcast = argv.broadcast ?? false; + + if (!['YES', 'NO'].includes(outcomeArg)) { + console.error(JSON.stringify({ ok: false, error: 'Outcome must be YES or NO' }, null, 2)); + process.exit(1); + } + + try { + const market = await getMarket(conditionId); + const tokenId = outcomeArg === 'YES' ? market.yesTokenId : market.noTokenId; + if (!tokenId) + throw new Error(`Market ${conditionId} has no tokenIds (may be closed or invalid)`); + + const currentPrice = outcomeArg === 'YES' ? market.yesPrice : market.noPrice; + const orderType = priceArg ? 'GTC' : useFak ? 'FAK' : 'FOK'; + + if (!broadcast) { + let proxyWalletAddress: string | null = null; + try { + const { privateKeyToAccount } = await import('viem/accounts'); + const pk = await loadPolymarketKey(); + proxyWalletAddress = await getPolymarketProxyWalletAddress( + privateKeyToAccount(pk as `0x${string}`).address + ); + } catch { + /* ignore */ + } + + console.log( + JSON.stringify( + { + ok: true, + dryRun: true, + conditionId, + question: market.question, + outcome: outcomeArg, + tokenId, + currentPrice, + amountUsd, + orderType, + price: priceArg ?? 'market', + proxyWalletAddress, + flow: skipFund + ? ['Place CLOB BUY order (using existing proxy wallet USDC.e balance)'] + : [ + `Smart wallet (${walletName}) → fund proxy wallet with ${amountUsd} USDC.e`, + '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.' + }, + null, + 2 + ) + ); + return; + } + + const [session, privateKey] = await Promise.all([ + loadWalletSession(walletName), + loadPolymarketKey() + ]); + if (!session) throw new Error(`Wallet not found: ${walletName}`); + + const { privateKeyToAccount } = await import('viem/accounts'); + 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` + ); + + let fundTxHash: string | null = null; + if (skipFund) { + process.stderr.write(`[polymarket] --skip-fund: using existing proxy wallet 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 transferData = + '0xa9059cbb' + pad(proxyWalletAddress) + pad('0x' + amountUnits.toString(16)); + const fundResult = await runDappClientTx({ + walletName, + chainId: 137, + transactions: [{ to: USDC_E, value: 0n, data: transferData }], + broadcast: true, + preferNativeFee: false + }); + fundTxHash = fundResult.txHash ?? null; + process.stderr.write(`[polymarket] Funded: ${fundTxHash}\n`); + } + + let orderResult; + if (priceArg) { + const estimatedShares = amountUsd / priceArg; + orderResult = await createAndPostOrder({ + tokenId, + side: 'BUY', + size: estimatedShares, + price: priceArg, + orderType: 'GTC', + privateKey, + proxyWalletAddress + }); + } else { + orderResult = await createAndPostMarketOrder({ + tokenId, + side: 'BUY', + amount: amountUsd, + orderType, + privateKey, + proxyWalletAddress + }); + } + + console.log( + JSON.stringify( + { + ok: true, + conditionId, + question: market.question, + outcome: outcomeArg, + amountUsd, + currentPrice, + proxyWalletAddress, + signerAddress: account.address, + fundTxHash, + orderId: orderResult?.orderId || orderResult?.orderID || orderResult?.id || null, + orderType, + orderStatus: orderResult?.status || null + }, + null, + 2 + ) + ); + } catch (err) { + console.error( + JSON.stringify( + { ok: false, error: (err as Error).message, stack: (err as Error).stack }, + null, + 2 + ) + ); + process.exit(1); + } +} + +async function handleSell(argv: { + conditionId: string; + outcome: string; + shares: number; + price?: number; + fak?: boolean; + broadcast?: boolean; +}): Promise { + const conditionId = argv.conditionId; + const outcomeArg = argv.outcome.toUpperCase(); + const shares = argv.shares; + const priceArg = argv.price; + const useFak = argv.fak ?? false; + const broadcast = argv.broadcast ?? false; + + if (!['YES', 'NO'].includes(outcomeArg)) { + console.error(JSON.stringify({ ok: false, error: 'Outcome must be YES or NO' }, null, 2)); + process.exit(1); + } + + try { + const market = await getMarket(conditionId); + const tokenId = outcomeArg === 'YES' ? market.yesTokenId : market.noTokenId; + if (!tokenId) + throw new Error(`Market ${conditionId} has no tokenIds (may be closed or invalid)`); + + const currentPrice = outcomeArg === 'YES' ? market.yesPrice : market.noPrice; + const estimatedUsd = shares * (currentPrice || 0); + + if (!broadcast) { + let proxyWalletAddress: string | null = null; + try { + const { privateKeyToAccount } = await import('viem/accounts'); + const pk = await loadPolymarketKey(); + proxyWalletAddress = await getPolymarketProxyWalletAddress( + privateKeyToAccount(pk as `0x${string}`).address + ); + } catch { + /* ignore */ + } + + console.log( + JSON.stringify( + { + ok: true, + dryRun: true, + conditionId, + question: market.question, + outcome: outcomeArg, + tokenId, + shares, + currentPrice, + estimatedUsd: Math.round(estimatedUsd * 100) / 100, + orderType: priceArg ? 'GTC' : useFak ? 'FAK' : 'FOK', + price: priceArg ?? 'market', + proxyWalletAddress, + note: 'Direct CLOB SELL of existing position. Tokens must be in proxy wallet. Re-run with --broadcast.' + }, + null, + 2 + ) + ); + return; + } + + const privateKey = await loadPolymarketKey(); + const { privateKeyToAccount } = await import('viem/accounts'); + const account = privateKeyToAccount(privateKey as `0x${string}`); + const proxyWalletAddress = await getPolymarketProxyWalletAddress(account.address); + process.stderr.write( + `[polymarket] CLOB SELL ${shares} ${outcomeArg} tokens via proxy wallet ${proxyWalletAddress}\n` + ); + + let orderResult; + if (priceArg) { + orderResult = await createAndPostOrder({ + tokenId, + side: 'SELL', + size: shares, + price: priceArg, + orderType: 'GTC', + privateKey, + proxyWalletAddress + }); + } else { + const orderType = useFak ? 'FAK' : 'FOK'; + orderResult = await createAndPostMarketOrder({ + tokenId, + side: 'SELL', + amount: shares, + orderType, + privateKey, + proxyWalletAddress + }); + } + + console.log( + JSON.stringify( + { + ok: true, + conditionId, + question: market.question, + outcome: outcomeArg, + shares, + currentPrice, + estimatedUsd: Math.round(estimatedUsd * 100) / 100, + proxyWalletAddress, + signerAddress: account.address, + orderId: orderResult?.orderId || orderResult?.orderID || orderResult?.id || null, + orderStatus: orderResult?.status || null + }, + null, + 2 + ) + ); + } catch (err) { + console.error( + JSON.stringify( + { ok: false, error: (err as Error).message, stack: (err as Error).stack }, + null, + 2 + ) + ); + process.exit(1); + } +} + +async function handlePositions(argv: { wallet?: string }): Promise { + const walletName = argv.wallet ?? 'main'; + try { + const session = await loadWalletSession(walletName); + if (!session) throw new Error(`Wallet not found: ${walletName}`); + + const positions = await getPositions(session.walletAddress); + console.log( + JSON.stringify( + { + ok: true, + walletAddress: session.walletAddress, + count: Array.isArray(positions) ? positions.length : 0, + positions + }, + null, + 2 + ) + ); + } catch (err) { + console.error(JSON.stringify({ ok: false, error: (err as Error).message }, null, 2)); + process.exit(1); + } +} + +async function handleOrders(): Promise { + try { + const privateKey = await loadPolymarketKey(); + const orders = await getOpenOrders(privateKey); + console.log( + JSON.stringify( + { + ok: true, + count: Array.isArray(orders) ? orders.length : 0, + orders + }, + null, + 2 + ) + ); + } catch (err) { + console.error(JSON.stringify({ ok: false, error: (err as Error).message }, null, 2)); + process.exit(1); + } +} + +async function handleCancel(argv: { orderId: string }): Promise { + try { + const privateKey = await loadPolymarketKey(); + const result = await cancelOrder(argv.orderId, privateKey); + console.log(JSON.stringify({ ok: true, orderId: argv.orderId, result }, null, 2)); + } catch (err) { + console.error(JSON.stringify({ ok: false, error: (err as Error).message }, null, 2)); + process.exit(1); + } +} + +// ─── Command module ─────────────────────────────────────────────────────────── + +export const polymarketCommand: CommandModule = { + command: 'polymarket', + describe: 'Polymarket prediction market trading', + builder: (yargs) => + yargs + .command({ + command: 'markets', + describe: 'List active markets by volume', + builder: (y) => + y + .option('search', { type: 'string', describe: 'Filter by question text' }) + .option('limit', { type: 'number', default: 20, describe: 'Number of results' }) + .option('offset', { type: 'number', default: 0, describe: 'Pagination offset' }), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + handler: (argv) => handleMarkets(argv as any) + }) + .command({ + command: 'market ', + describe: 'Get a single market by conditionId', + builder: (y) => + y.positional('conditionId', { + type: 'string', + demandOption: true, + describe: 'Market condition ID' + }), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + handler: (argv) => handleMarket(argv as any) + }) + .command({ + command: 'set-key ', + describe: 'Import EOA private key for Polymarket signing (stored encrypted)', + builder: (y) => + y.positional('privateKey', { + type: 'string', + demandOption: true, + describe: 'EOA private key (hex)' + }), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + handler: (argv) => handleSetKey(argv as any) + }) + .command({ + command: 'proxy-wallet', + describe: 'Show Polymarket proxy wallet address for the active EOA', + builder: (y) => y, + handler: () => handleProxyWallet() + }) + .command({ + command: 'approve', + describe: 'Set proxy wallet approvals (run once before clob-buy)', + builder: (y) => + y + .option('neg-risk', { + type: 'boolean', + default: false, + describe: 'Set neg-risk approvals' + }) + .option('broadcast', { + type: 'boolean', + default: false, + describe: 'Execute (dry-run without)' + }), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + handler: (argv) => handleApprove(argv as any) + }) + .command({ + command: 'clob-buy ', + describe: 'Buy YES/NO tokens via CLOB (funds proxy wallet first)', + builder: (y) => + y + .positional('conditionId', { type: 'string', demandOption: true }) + .positional('outcome', { type: 'string', demandOption: true, describe: 'YES or NO' }) + .positional('amount', { type: 'number', demandOption: true, describe: 'USDC to spend' }) + .option('wallet', { + type: 'string', + default: 'main', + describe: 'Smart wallet to fund from' + }) + .option('price', { + type: 'number', + describe: 'Limit price 0-1 (GTC); omit for market order' + }) + .option('fak', { type: 'boolean', default: false, describe: 'Use FAK instead of FOK' }) + .option('skip-fund', { + type: 'boolean', + default: false, + describe: 'Skip wallet→proxy funding' + }) + .option('broadcast', { + type: 'boolean', + default: false, + describe: 'Execute (dry-run without)' + }), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + handler: (argv) => handleClobBuy(argv as any) + }) + .command({ + command: 'sell ', + describe: 'Sell YES/NO tokens via CLOB', + builder: (y) => + y + .positional('conditionId', { type: 'string', demandOption: true }) + .positional('outcome', { type: 'string', demandOption: true, describe: 'YES or NO' }) + .positional('shares', { + type: 'number', + demandOption: true, + describe: 'Number of tokens to sell' + }) + .option('price', { + type: 'number', + describe: 'Limit price 0-1 (GTC); omit for market order' + }) + .option('fak', { type: 'boolean', default: false, describe: 'Use FAK instead of FOK' }) + .option('broadcast', { + type: 'boolean', + default: false, + describe: 'Execute (dry-run without)' + }), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + handler: (argv) => handleSell(argv as any) + }) + .command({ + command: 'positions', + describe: 'List open positions for the smart wallet', + builder: (y) => + y.option('wallet', { type: 'string', default: 'main', describe: 'Wallet name' }), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + handler: (argv) => handlePositions(argv as any) + }) + .command({ + command: 'orders', + describe: 'List open CLOB orders for the active EOA', + builder: (y) => y, + handler: () => handleOrders() + }) + .command({ + command: 'cancel ', + describe: 'Cancel an open CLOB order', + builder: (y) => + y.positional('orderId', { + type: 'string', + demandOption: true, + describe: 'Order ID to cancel' + }), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + handler: (argv) => handleCancel(argv as any) + }) + .demandCommand(1, '') + .showHelpOnFail(true), + handler: () => {} +}; diff --git a/packages/polygon-agent-cli/src/index.ts b/packages/polygon-agent-cli/src/index.ts index 5df4cca..3c108ef 100644 --- a/packages/polygon-agent-cli/src/index.ts +++ b/packages/polygon-agent-cli/src/index.ts @@ -18,6 +18,7 @@ import { swapCommand, x402PayCommand } from './commands/operations.ts'; +import { polymarketCommand } from './commands/polymarket.ts'; import { setupCommand } from './commands/setup.ts'; import { walletCommand } from './commands/wallet.ts'; @@ -89,7 +90,8 @@ const parser = yargs(hideBin(process.argv)) .command(swapCommand) .command(depositCommand) .command(x402PayCommand) - .command(agentCommand); + .command(agentCommand) + .command(polymarketCommand); // Register legacy aliases for (const alias of legacyAliases) { diff --git a/packages/polygon-agent-cli/src/lib/polymarket.ts b/packages/polygon-agent-cli/src/lib/polymarket.ts new file mode 100644 index 0000000..3d5d726 --- /dev/null +++ b/packages/polygon-agent-cli/src/lib/polymarket.ts @@ -0,0 +1,359 @@ +// Polymarket integration library +// Covers: Gamma API (market discovery), CLOB API (trading via @polymarket/clob-client), 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 +// - CLOB orders use maker=proxyWallet, signer=EOA, signatureType=POLY_PROXY + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type AnyWalletClient = any; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type AnyPublicClient = any; + +export interface Market { + id: string; + conditionId: string; + question: string; + yesTokenId: string | null; + noTokenId: string | null; + yesPrice: number | null; + noPrice: number | null; + outcomes: string[]; + volume24hr: number; + negRisk: boolean; + endDate: string | null; +} + +export interface ProxyTx { + typeCode?: number; + to: string; + value?: string | number | bigint; + data: string; +} + +// ─── Constants ────────────────────────────────────────────────────────────── + +export const GAMMA_URL = process.env.POLYMARKET_GAMMA_URL || 'https://gamma-api.polymarket.com'; +export const CLOB_URL = process.env.POLYMARKET_CLOB_URL || 'https://clob.polymarket.com'; +export const DATA_URL = process.env.POLYMARKET_DATA_URL || 'https://data-api.polymarket.com'; + +// Polygon mainnet (chain 137) +export const USDC_E = '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174'; // USDC.e — 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 NEG_RISK_ADAPTER = '0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296'; + +// Polymarket proxy wallet factory (Polygon mainnet) +export const PROXY_WALLET_FACTORY = '0xaB45c5A4B0c941a2F231C04C3f49182e1A254052'; + +// ─── Proxy wallet helpers ──────────────────────────────────────────────────── + +// Compute the Polymarket proxy wallet address for a given EOA address (CREATE2, deterministic) +export async function getPolymarketProxyWalletAddress(eoaAddress: string): Promise { + const { getProxyWalletAddress } = await import('@polymarket/sdk'); + return getProxyWalletAddress(PROXY_WALLET_FACTORY, eoaAddress); +} + +// Proxy wallet execute ABI: execute(Transaction[]) — selector 0x34ee9791 +const PROXY_EXECUTE_ABI = [ + { + name: 'execute', + type: 'function', + inputs: [ + { + name: 'transactions', + type: 'tuple[]', + components: [ + { name: 'typeCode', type: 'uint8' }, + { name: 'to', type: 'address' }, + { name: 'value', type: 'uint256' }, + { name: 'data', type: 'bytes' } + ] + } + ], + outputs: [{ name: '', type: 'bytes[]' }] + } +]; + +// Execute a batch of transactions through the Polymarket proxy wallet (EOA is owner/caller) +export async function executeViaProxyWallet( + walletClient: AnyWalletClient, + publicClient: AnyPublicClient, + proxyWalletAddress: string, + txs: ProxyTx[] +): Promise { + const { encodeFunctionData } = await import('viem'); + const transactions = txs.map((t) => ({ + typeCode: Number(t.typeCode ?? 1), + to: t.to, + value: BigInt(t.value || 0), + data: t.data + })); + const data = encodeFunctionData({ + abi: PROXY_EXECUTE_ABI, + functionName: 'execute', + args: [transactions] + }); + const hash = await walletClient.sendTransaction({ to: proxyWalletAddress, data, value: 0n }); + await publicClient.waitForTransactionReceipt({ hash }); + return hash; +} + +// ─── Gamma API ────────────────────────────────────────────────────────────── + +export async function getMarkets({ + search, + limit = 20, + offset = 0 +}: { + search?: string; + limit?: number; + offset?: number; +} = {}): Promise { + const fetchLimit = search ? Math.max(100, limit * 5) : limit; + const params = new URLSearchParams({ + limit: String(fetchLimit), + offset: String(offset), + active: 'true', + closed: 'false', + order: 'volume24hr', + ascending: 'false' + }); + + const res = await fetch(`${GAMMA_URL}/markets?${params}`); + if (!res.ok) throw new Error(`Gamma API error: ${res.status} ${await res.text()}`); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let markets: any[] = (await res.json()) as any[]; + + if (search) { + const q = search.toLowerCase(); + markets = markets.filter((m) => (m.question || '').toLowerCase().includes(q)); + markets = markets.slice(0, limit); + } + + return markets.map(parseMarket); +} + +export async function getMarket(conditionId: string): Promise { + const needle = conditionId.toLowerCase(); + for (let offset = 0; offset < 500; offset += 100) { + const params = new URLSearchParams({ + limit: '100', + offset: String(offset), + active: 'true', + closed: 'false', + order: 'volume24hr', + ascending: 'false' + }); + const res = await fetch(`${GAMMA_URL}/markets?${params}`); + if (!res.ok) throw new Error(`Gamma API error: ${res.status} ${await res.text()}`); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const markets: any[] = (await res.json()) as any[]; + if (!markets?.length) break; + const found = markets.find((m) => m.conditionId?.toLowerCase() === needle); + if (found) return parseMarket(found); + } + const resClosed = await fetch( + `${GAMMA_URL}/markets?conditionId=${encodeURIComponent(conditionId)}&limit=100` + ); + if (resClosed.ok) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const closed: any[] = (await resClosed.json()) as any[]; + const found = (closed || []).find((m) => m.conditionId?.toLowerCase() === needle); + if (found) return parseMarket(found); + } + throw new Error(`Market not found: ${conditionId}`); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function parseMarket(m: any): Market { + let tokenIds: string[] = []; + let prices: string[] = []; + let outcomes: string[] = []; + try { + tokenIds = JSON.parse(m.clobTokenIds || '[]'); + } catch { + /* ignore */ + } + try { + prices = JSON.parse(m.outcomePrices || '[]'); + } catch { + /* ignore */ + } + try { + outcomes = JSON.parse(m.outcomes || '["Yes","No"]'); + } catch { + /* ignore */ + } + + return { + id: m.id, + conditionId: m.conditionId, + question: m.question, + yesTokenId: tokenIds[0] || null, + noTokenId: tokenIds[1] || null, + yesPrice: prices[0] ? Number(prices[0]) : null, + noPrice: prices[1] ? Number(prices[1]) : null, + outcomes, + volume24hr: m.volume24hr || 0, + negRisk: !!m.negRisk, + endDate: m.endDate || null + }; +} + +// ─── CLOB API — public endpoints ───────────────────────────────────────────── + +export async function getClobPrice(tokenId: string, side = 'BUY'): Promise { + const res = await fetch(`${CLOB_URL}/price?token_id=${tokenId}&side=${side}`); + if (!res.ok) throw new Error(`CLOB price error: ${res.status} ${await res.text()}`); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const data: any = await res.json(); + return Number(data.price); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export async function getOrderBook(tokenId: string): Promise { + const res = await fetch(`${CLOB_URL}/book?token_id=${tokenId}`); + if (!res.ok) throw new Error(`CLOB book error: ${res.status} ${await res.text()}`); + return res.json(); +} + +// ─── CLOB API — @polymarket/clob-client ───────────────────────────────────── + +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 signer = new Wallet(privateKey); + const chainId = 137; + const anonClient = new ClobClient(CLOB_URL, chainId, signer); + const creds = await anonClient.createOrDeriveApiKey(); + const signatureType = proxyWalletAddress ? SignatureType.POLY_PROXY : SignatureType.EOA; + const client = new ClobClient( + CLOB_URL, + chainId, + signer, + creds, + signatureType, + proxyWalletAddress + ); + return { client, creds, address: await signer.getAddress() }; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export async function getOpenOrders(privateKey: string): Promise { + const { client } = await getClobClient(privateKey); + return client.getOpenOrders(); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export async function cancelOrder(orderId: string, privateKey: string): Promise { + const { client } = await getClobClient(privateKey); + return client.cancelOrder({ orderID: orderId }); +} + +// ─── CLOB API — order creation ─────────────────────────────────────────────── + +export async function createAndPostOrder({ + tokenId, + side, + size, + price, + orderType = 'GTC', + privateKey, + proxyWalletAddress +}: { + tokenId: string; + side: 'BUY' | 'SELL'; + size: number; + price: number; + orderType?: string; + privateKey: string; + proxyWalletAddress?: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any +}): Promise { + const { client } = await getClobClient(privateKey, proxyWalletAddress); + const order = await client.createOrder({ + tokenID: tokenId, + price, + size, + side, + feeRateBps: '0' + }); + return client.postOrder(order, orderType); +} + +export async function createAndPostMarketOrder({ + tokenId, + side, + amount, + orderType = 'FOK', + privateKey, + proxyWalletAddress +}: { + tokenId: string; + side: 'BUY' | 'SELL'; + amount: number; + orderType?: string; + privateKey: string; + proxyWalletAddress?: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any +}): Promise { + const { client } = await getClobClient(privateKey, proxyWalletAddress); + const order = await client.createMarketOrder({ + tokenID: tokenId, + side, + amount, + orderType, + feeRateBps: '0' + }); + return client.postOrder(order, orderType); +} + +// ─── Data API — positions ──────────────────────────────────────────────────── + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export async function getPositions(address: string, limit = 20): Promise { + const res = await fetch(`${DATA_URL}/positions?user=${address}&limit=${limit}`); + if (!res.ok) throw new Error(`Data API error: ${res.status} ${await res.text()}`); + return res.json(); +} + +// ─── Helper: fetch with Cloudflare retry ───────────────────────────────────── + +export async function fetchWithRetry( + url: string, + init: RequestInit = {}, + retries = 5 +): Promise { + let lastErr: unknown; + for (let i = 0; i < retries; i++) { + try { + const res = await fetch(url, init); + if ((res.status === 403 || res.status === 503) && i < retries - 1) { + const text = await res.text(); + if (text.includes('Cloudflare') || text.includes('cf-ray')) { + await sleep(1000 * (i + 1)); + continue; + } + return new Response(text, { status: res.status, headers: res.headers }); + } + return res; + } catch (err) { + lastErr = err; + if (i < retries - 1) await sleep(500 * (i + 1)); + } + } + throw lastErr || new Error('fetch failed after retries'); +} + +function sleep(ms: number): Promise { + return new Promise((r) => setTimeout(r, ms)); +} diff --git a/packages/polygon-agent-cli/src/lib/storage.ts b/packages/polygon-agent-cli/src/lib/storage.ts index 6d4e692..8b38d26 100644 --- a/packages/polygon-agent-cli/src/lib/storage.ts +++ b/packages/polygon-agent-cli/src/lib/storage.ts @@ -193,3 +193,27 @@ export async function deleteWallet(name: string): Promise { return false; } + +export async function savePolymarketKey(privateKey: string): Promise { + ensureStorageDir(); + const configPath = path.join(STORAGE_DIR, 'builder.json'); + let data: Record = {}; + if (fs.existsSync(configPath)) { + data = JSON.parse(fs.readFileSync(configPath, 'utf8')); + } + data.polymarketPrivateKey = encrypt(privateKey); + fs.writeFileSync(configPath, JSON.stringify(data, null, 2), { mode: 0o600 }); +} + +export async function loadPolymarketKey(): Promise { + const configPath = path.join(STORAGE_DIR, 'builder.json'); + if (!fs.existsSync(configPath)) { + throw new Error('No builder config found. Run: polygon-agent setup'); + } + const data = JSON.parse(fs.readFileSync(configPath, 'utf8')); + if (data.polymarketPrivateKey) return decrypt(data.polymarketPrivateKey as CipherData); + if (data.privateKey) return decrypt(data.privateKey as CipherData); + throw new Error( + 'No EOA key found. Run: polygon-agent setup or polygon-agent polymarket set-key ' + ); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 703a599..c851ac9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -141,6 +141,12 @@ importers: '@0xtrails/api': specifier: ^0.10.4 version: 0.10.4 + '@polymarket/clob-client': + specifier: ^5.2.4 + version: 5.5.0(typescript@5.9.3)(zod@3.25.76) + '@polymarket/sdk': + specifier: ^6.0.1 + version: 6.0.1(@ethersproject/abi@5.8.0)(@ethersproject/address@5.8.0)(@ethersproject/bignumber@5.8.0)(@ethersproject/constants@5.8.0)(@ethersproject/contracts@5.8.0)(@ethersproject/keccak256@5.8.0)(@ethersproject/providers@5.8.0)(@ethersproject/solidity@5.8.0) '@x402/core': specifier: ^2.3.1 version: 2.5.0 @@ -156,6 +162,9 @@ importers: ethers: specifier: ^6.13.0 version: 6.16.0 + ethers5: + specifier: npm:ethers@^5.7.4 + version: ethers@5.8.0 tweetnacl: specifier: ^1.0.3 version: 1.0.3 @@ -810,6 +819,96 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ethersproject/abi@5.8.0': + resolution: {integrity: sha512-b9YS/43ObplgyV6SlyQsG53/vkSal0MNA1fskSC4mbnCMi8R+NkcH8K9FPYNESf6jUefBUniE4SOKms0E/KK1Q==} + + '@ethersproject/abstract-provider@5.8.0': + resolution: {integrity: sha512-wC9SFcmh4UK0oKuLJQItoQdzS/qZ51EJegK6EmAWlh+OptpQ/npECOR3QqECd8iGHC0RJb4WKbVdSfif4ammrg==} + + '@ethersproject/abstract-signer@5.8.0': + resolution: {integrity: sha512-N0XhZTswXcmIZQdYtUnd79VJzvEwXQw6PK0dTl9VoYrEBxxCPXqS0Eod7q5TNKRxe1/5WUMuR0u0nqTF/avdCA==} + + '@ethersproject/address@5.8.0': + resolution: {integrity: sha512-GhH/abcC46LJwshoN+uBNoKVFPxUuZm6dA257z0vZkKmU1+t8xTn8oK7B9qrj8W2rFRMch4gbJl6PmVxjxBEBA==} + + '@ethersproject/base64@5.8.0': + resolution: {integrity: sha512-lN0oIwfkYj9LbPx4xEkie6rAMJtySbpOAFXSDVQaBnAzYfB4X2Qr+FXJGxMoc3Bxp2Sm8OwvzMrywxyw0gLjIQ==} + + '@ethersproject/basex@5.8.0': + resolution: {integrity: sha512-PIgTszMlDRmNwW9nhS6iqtVfdTAKosA7llYXNmGPw4YAI1PUyMv28988wAb41/gHF/WqGdoLv0erHaRcHRKW2Q==} + + '@ethersproject/bignumber@5.8.0': + resolution: {integrity: sha512-ZyaT24bHaSeJon2tGPKIiHszWjD/54Sz8t57Toch475lCLljC6MgPmxk7Gtzz+ddNN5LuHea9qhAe0x3D+uYPA==} + + '@ethersproject/bytes@5.8.0': + resolution: {integrity: sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A==} + + '@ethersproject/constants@5.8.0': + resolution: {integrity: sha512-wigX4lrf5Vu+axVTIvNsuL6YrV4O5AXl5ubcURKMEME5TnWBouUh0CDTWxZ2GpnRn1kcCgE7l8O5+VbV9QTTcg==} + + '@ethersproject/contracts@5.8.0': + resolution: {integrity: sha512-0eFjGz9GtuAi6MZwhb4uvUM216F38xiuR0yYCjKJpNfSEy4HUM8hvqqBj9Jmm0IUz8l0xKEhWwLIhPgxNY0yvQ==} + + '@ethersproject/hash@5.8.0': + resolution: {integrity: sha512-ac/lBcTbEWW/VGJij0CNSw/wPcw9bSRgCB0AIBz8CvED/jfvDoV9hsIIiWfvWmFEi8RcXtlNwp2jv6ozWOsooA==} + + '@ethersproject/hdnode@5.8.0': + resolution: {integrity: sha512-4bK1VF6E83/3/Im0ERnnUeWOY3P1BZml4ZD3wcH8Ys0/d1h1xaFt6Zc+Dh9zXf9TapGro0T4wvO71UTCp3/uoA==} + + '@ethersproject/json-wallets@5.8.0': + resolution: {integrity: sha512-HxblNck8FVUtNxS3VTEYJAcwiKYsBIF77W15HufqlBF9gGfhmYOJtYZp8fSDZtn9y5EaXTE87zDwzxRoTFk11w==} + + '@ethersproject/keccak256@5.8.0': + resolution: {integrity: sha512-A1pkKLZSz8pDaQ1ftutZoaN46I6+jvuqugx5KYNeQOPqq+JZ0Txm7dlWesCHB5cndJSu5vP2VKptKf7cksERng==} + + '@ethersproject/logger@5.8.0': + resolution: {integrity: sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA==} + + '@ethersproject/networks@5.8.0': + resolution: {integrity: sha512-egPJh3aPVAzbHwq8DD7Po53J4OUSsA1MjQp8Vf/OZPav5rlmWUaFLiq8cvQiGK0Z5K6LYzm29+VA/p4RL1FzNg==} + + '@ethersproject/pbkdf2@5.8.0': + resolution: {integrity: sha512-wuHiv97BrzCmfEaPbUFpMjlVg/IDkZThp9Ri88BpjRleg4iePJaj2SW8AIyE8cXn5V1tuAaMj6lzvsGJkGWskg==} + + '@ethersproject/properties@5.8.0': + resolution: {integrity: sha512-PYuiEoQ+FMaZZNGrStmN7+lWjlsoufGIHdww7454FIaGdbe/p5rnaCXTr5MtBYl3NkeoVhHZuyzChPeGeKIpQw==} + + '@ethersproject/providers@5.8.0': + resolution: {integrity: sha512-3Il3oTzEx3o6kzcg9ZzbE+oCZYyY+3Zh83sKkn4s1DZfTUjIegHnN2Cm0kbn9YFy45FDVcuCLLONhU7ny0SsCw==} + + '@ethersproject/random@5.8.0': + resolution: {integrity: sha512-E4I5TDl7SVqyg4/kkA/qTfuLWAQGXmSOgYyO01So8hLfwgKvYK5snIlzxJMk72IFdG/7oh8yuSqY2KX7MMwg+A==} + + '@ethersproject/rlp@5.8.0': + resolution: {integrity: sha512-LqZgAznqDbiEunaUvykH2JAoXTT9NV0Atqk8rQN9nx9SEgThA/WMx5DnW8a9FOufo//6FZOCHZ+XiClzgbqV9Q==} + + '@ethersproject/sha2@5.8.0': + resolution: {integrity: sha512-dDOUrXr9wF/YFltgTBYS0tKslPEKr6AekjqDW2dbn1L1xmjGR+9GiKu4ajxovnrDbwxAKdHjW8jNcwfz8PAz4A==} + + '@ethersproject/signing-key@5.8.0': + resolution: {integrity: sha512-LrPW2ZxoigFi6U6aVkFN/fa9Yx/+4AtIUe4/HACTvKJdhm0eeb107EVCIQcrLZkxaSIgc/eCrX8Q1GtbH+9n3w==} + + '@ethersproject/solidity@5.8.0': + resolution: {integrity: sha512-4CxFeCgmIWamOHwYN9d+QWGxye9qQLilpgTU0XhYs1OahkclF+ewO+3V1U0mvpiuQxm5EHHmv8f7ClVII8EHsA==} + + '@ethersproject/strings@5.8.0': + resolution: {integrity: sha512-qWEAk0MAvl0LszjdfnZ2uC8xbR2wdv4cDabyHiBh3Cldq/T8dPH3V4BbBsAYJUeonwD+8afVXld274Ls+Y1xXg==} + + '@ethersproject/transactions@5.8.0': + resolution: {integrity: sha512-UglxSDjByHG0TuU17bDfCemZ3AnKO2vYrL5/2n2oXvKzvb7Cz+W9gOWXKARjp2URVwcWlQlPOEQyAviKwT4AHg==} + + '@ethersproject/units@5.8.0': + resolution: {integrity: sha512-lxq0CAnc5kMGIiWW4Mr041VT8IhNM+Pn5T3haO74XZWFulk7wH1Gv64HqE96hT4a7iiNMdOCFEBgaxWuk8ETKQ==} + + '@ethersproject/wallet@5.8.0': + resolution: {integrity: sha512-G+jnzmgg6UxurVKRKvw27h0kvG75YKXZKdlLYmAHeF32TGUzHkOFd7Zn6QHOTYRFWnfjtSSFjBowKo7vfrXzPA==} + + '@ethersproject/web@5.8.0': + resolution: {integrity: sha512-j7+Ksi/9KfGviws6Qtf9Q7KCqRhpwrYKQPs+JBA/rKVFF/yaWLHJEH3zfVP2plVu+eys0d2DlFmhoQJayFewcw==} + + '@ethersproject/wordlists@5.8.0': + resolution: {integrity: sha512-2df9bbXicZws2Sb5S6ET493uJ0Z84Fjr3pC4tu/qlnZERibZCeUVuqdtt+7Tv9xxhUxHoIekIA7avrKUWHrezg==} + '@gar/promise-retry@1.0.2': resolution: {integrity: sha512-Lm/ZLhDZcBECta3TmCQSngiQykFdfw+QtI1/GYMsZd4l3nG+P8WLB16XuS7WaBGLQ+9E+cOcWQsth9cayuGt8g==} engines: {node: ^20.17.0 || >=22.9.0} @@ -1403,6 +1502,26 @@ packages: typescript: optional: true + '@polymarket/builder-signing-sdk@0.0.8': + resolution: {integrity: sha512-rZLCFxEdYahl5FiJmhe22RDXysS1ibFJlWz4NT0s3itJRYq3XJzXXHXEZkAQplU+nIS1IlbbKjA4zDQaeCyYtg==} + + '@polymarket/clob-client@5.5.0': + resolution: {integrity: sha512-ArESpXkq2Bapn3D/TQoXwxUWhz8UNPHGViU078ojQtuLvlB5IyhHipWBkUqkNNYvQ4gwAvxQEq1FTMgDTnWe6g==} + engines: {node: '>=20.10'} + + '@polymarket/sdk@6.0.1': + resolution: {integrity: sha512-CJSzGuT/Aavvc8ex2rDFgYm1zhwt8uu0h9NKcpTbVqKcsTjNre82/ZEaUK2Mpc8yAesMk5yHZ80aFZIMYtevpw==} + engines: {node: '>=8', npm: '>=5'} + peerDependencies: + '@ethersproject/abi': ^5.4.1 + '@ethersproject/address': ^5.0.8 + '@ethersproject/bignumber': ^5.0.8 + '@ethersproject/constants': ^5.0.8 + '@ethersproject/contracts': ^5.0.9 + '@ethersproject/keccak256': ^5.0.6 + '@ethersproject/providers': ^5.0.14 + '@ethersproject/solidity': ^5.0.7 + '@poppinss/colors@4.1.6': resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==} @@ -1756,6 +1875,9 @@ packages: '@types/minimist@1.2.5': resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} + '@types/node@18.19.130': + resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} + '@types/node@22.19.13': resolution: {integrity: sha512-akNQMv0wW5uyRpD2v2IEyRSZiR+BeGuoB6L310EgGObO44HSMNT8z1xzio28V8qOrgYaopIDNA18YgdXd+qTiw==} @@ -2015,6 +2137,9 @@ packages: add-stream@1.0.0: resolution: {integrity: sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==} + aes-js@3.0.0: + resolution: {integrity: sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==} + aes-js@4.0.0-beta.5: resolution: {integrity: sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==} @@ -2131,6 +2256,9 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + bech32@1.1.4: + resolution: {integrity: sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==} + before-after-hook@2.2.3: resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} @@ -2147,6 +2275,12 @@ packages: blakejs@1.2.1: resolution: {integrity: sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==} + bn.js@4.12.3: + resolution: {integrity: sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==} + + bn.js@5.2.3: + resolution: {integrity: sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==} + boxen@7.0.0: resolution: {integrity: sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==} engines: {node: '>=14.16'} @@ -2165,6 +2299,12 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + brorand@1.1.0: + resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} + + browser-or-node@2.1.1: + resolution: {integrity: sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg==} + browserslist@4.28.1: resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -2571,6 +2711,9 @@ packages: electron-to-chromium@1.5.302: resolution: {integrity: sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==} + elliptic@6.6.1: + resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} + emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} @@ -2752,6 +2895,9 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + ethers@5.8.0: + resolution: {integrity: sha512-DUq+7fHrCg1aPDFCHx6UIPb3nmt2XMpM7Y/g2gLhsl3lIBqeAfOJIl1qEvRf2uq3BiKxmh6Fh5pfp2ieyek7Kg==} + ethers@6.16.0: resolution: {integrity: sha512-U1wulmetNymijEhpSEQ7Ct/P/Jw9/e7R1j5XIbPRydgV2DjLVMsULDlNksq3RQnFgKoLlZf88ijYtWEXcPa07A==} engines: {node: '>=14.0.0'} @@ -2999,10 +3145,16 @@ packages: has-unicode@2.0.1: resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + hash.js@1.1.7: + resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} + hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hmac-drbg@1.0.1: + resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} + hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} @@ -3240,6 +3392,9 @@ packages: js-base64@3.7.8: resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} + js-sha3@0.8.0: + resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -3588,6 +3743,12 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + minimalistic-assert@1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + + minimalistic-crypto-utils@1.0.1: + resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} + minimatch@10.2.1: resolution: {integrity: sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==} engines: {node: 20 || >=22} @@ -4247,6 +4408,9 @@ packages: scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + scrypt-js@3.0.1: + resolution: {integrity: sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==} + semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true @@ -4586,6 +4750,9 @@ packages: engines: {node: '>=0.8.0'} hasBin: true + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} @@ -5552,6 +5719,261 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 + '@ethersproject/abi@5.8.0': + dependencies: + '@ethersproject/address': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/constants': 5.8.0 + '@ethersproject/hash': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/strings': 5.8.0 + + '@ethersproject/abstract-provider@5.8.0': + dependencies: + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/networks': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/transactions': 5.8.0 + '@ethersproject/web': 5.8.0 + + '@ethersproject/abstract-signer@5.8.0': + dependencies: + '@ethersproject/abstract-provider': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + + '@ethersproject/address@5.8.0': + dependencies: + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/rlp': 5.8.0 + + '@ethersproject/base64@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + + '@ethersproject/basex@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/properties': 5.8.0 + + '@ethersproject/bignumber@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + bn.js: 5.2.3 + + '@ethersproject/bytes@5.8.0': + dependencies: + '@ethersproject/logger': 5.8.0 + + '@ethersproject/constants@5.8.0': + dependencies: + '@ethersproject/bignumber': 5.8.0 + + '@ethersproject/contracts@5.8.0': + dependencies: + '@ethersproject/abi': 5.8.0 + '@ethersproject/abstract-provider': 5.8.0 + '@ethersproject/abstract-signer': 5.8.0 + '@ethersproject/address': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/constants': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/transactions': 5.8.0 + + '@ethersproject/hash@5.8.0': + dependencies: + '@ethersproject/abstract-signer': 5.8.0 + '@ethersproject/address': 5.8.0 + '@ethersproject/base64': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/strings': 5.8.0 + + '@ethersproject/hdnode@5.8.0': + dependencies: + '@ethersproject/abstract-signer': 5.8.0 + '@ethersproject/basex': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/pbkdf2': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/sha2': 5.8.0 + '@ethersproject/signing-key': 5.8.0 + '@ethersproject/strings': 5.8.0 + '@ethersproject/transactions': 5.8.0 + '@ethersproject/wordlists': 5.8.0 + + '@ethersproject/json-wallets@5.8.0': + dependencies: + '@ethersproject/abstract-signer': 5.8.0 + '@ethersproject/address': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/hdnode': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/pbkdf2': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/random': 5.8.0 + '@ethersproject/strings': 5.8.0 + '@ethersproject/transactions': 5.8.0 + aes-js: 3.0.0 + scrypt-js: 3.0.1 + + '@ethersproject/keccak256@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + js-sha3: 0.8.0 + + '@ethersproject/logger@5.8.0': {} + + '@ethersproject/networks@5.8.0': + dependencies: + '@ethersproject/logger': 5.8.0 + + '@ethersproject/pbkdf2@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/sha2': 5.8.0 + + '@ethersproject/properties@5.8.0': + dependencies: + '@ethersproject/logger': 5.8.0 + + '@ethersproject/providers@5.8.0': + dependencies: + '@ethersproject/abstract-provider': 5.8.0 + '@ethersproject/abstract-signer': 5.8.0 + '@ethersproject/address': 5.8.0 + '@ethersproject/base64': 5.8.0 + '@ethersproject/basex': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/constants': 5.8.0 + '@ethersproject/hash': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/networks': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/random': 5.8.0 + '@ethersproject/rlp': 5.8.0 + '@ethersproject/sha2': 5.8.0 + '@ethersproject/strings': 5.8.0 + '@ethersproject/transactions': 5.8.0 + '@ethersproject/web': 5.8.0 + bech32: 1.1.4 + ws: 8.18.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@ethersproject/random@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + + '@ethersproject/rlp@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + + '@ethersproject/sha2@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + hash.js: 1.1.7 + + '@ethersproject/signing-key@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + bn.js: 5.2.3 + elliptic: 6.6.1 + hash.js: 1.1.7 + + '@ethersproject/solidity@5.8.0': + dependencies: + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/sha2': 5.8.0 + '@ethersproject/strings': 5.8.0 + + '@ethersproject/strings@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/constants': 5.8.0 + '@ethersproject/logger': 5.8.0 + + '@ethersproject/transactions@5.8.0': + dependencies: + '@ethersproject/address': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/constants': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/rlp': 5.8.0 + '@ethersproject/signing-key': 5.8.0 + + '@ethersproject/units@5.8.0': + dependencies: + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/constants': 5.8.0 + '@ethersproject/logger': 5.8.0 + + '@ethersproject/wallet@5.8.0': + dependencies: + '@ethersproject/abstract-provider': 5.8.0 + '@ethersproject/abstract-signer': 5.8.0 + '@ethersproject/address': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/hash': 5.8.0 + '@ethersproject/hdnode': 5.8.0 + '@ethersproject/json-wallets': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/random': 5.8.0 + '@ethersproject/signing-key': 5.8.0 + '@ethersproject/transactions': 5.8.0 + '@ethersproject/wordlists': 5.8.0 + + '@ethersproject/web@5.8.0': + dependencies: + '@ethersproject/base64': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/strings': 5.8.0 + + '@ethersproject/wordlists@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/hash': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/strings': 5.8.0 + '@gar/promise-retry@1.0.2': dependencies: retry: 0.13.1 @@ -6209,6 +6631,43 @@ snapshots: - eslint-plugin-import - supports-color + '@polymarket/builder-signing-sdk@0.0.8': + dependencies: + '@types/node': 18.19.130 + axios: 1.13.6 + tslib: 2.8.1 + transitivePeerDependencies: + - debug + + '@polymarket/clob-client@5.5.0(typescript@5.9.3)(zod@3.25.76)': + dependencies: + '@ethersproject/providers': 5.8.0 + '@ethersproject/units': 5.8.0 + '@ethersproject/wallet': 5.8.0 + '@polymarket/builder-signing-sdk': 0.0.8 + axios: 1.13.6 + browser-or-node: 2.1.1 + ethers: 5.8.0 + tslib: 2.8.1 + viem: 2.46.3(typescript@5.9.3)(zod@3.25.76) + transitivePeerDependencies: + - bufferutil + - debug + - typescript + - utf-8-validate + - zod + + '@polymarket/sdk@6.0.1(@ethersproject/abi@5.8.0)(@ethersproject/address@5.8.0)(@ethersproject/bignumber@5.8.0)(@ethersproject/constants@5.8.0)(@ethersproject/contracts@5.8.0)(@ethersproject/keccak256@5.8.0)(@ethersproject/providers@5.8.0)(@ethersproject/solidity@5.8.0)': + dependencies: + '@ethersproject/abi': 5.8.0 + '@ethersproject/address': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/constants': 5.8.0 + '@ethersproject/contracts': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/providers': 5.8.0 + '@ethersproject/solidity': 5.8.0 + '@poppinss/colors@4.1.6': dependencies: kleur: 4.1.5 @@ -6492,6 +6951,10 @@ snapshots: '@types/minimist@1.2.5': {} + '@types/node@18.19.130': + dependencies: + undici-types: 5.26.5 + '@types/node@22.19.13': dependencies: undici-types: 6.21.0 @@ -6762,6 +7225,8 @@ snapshots: add-stream@1.0.0: {} + aes-js@3.0.0: {} + aes-js@4.0.0-beta.5: {} agent-base@7.1.4: {} @@ -6864,6 +7329,8 @@ snapshots: baseline-browser-mapping@2.10.0: {} + bech32@1.1.4: {} + before-after-hook@2.2.3: {} bin-links@5.0.0: @@ -6884,6 +7351,10 @@ snapshots: blakejs@1.2.1: {} + bn.js@4.12.3: {} + + bn.js@5.2.3: {} + boxen@7.0.0: dependencies: ansi-align: 3.0.1 @@ -6912,6 +7383,10 @@ snapshots: dependencies: fill-range: 7.1.1 + brorand@1.1.0: {} + + browser-or-node@2.1.1: {} + browserslist@4.28.1: dependencies: baseline-browser-mapping: 2.10.0 @@ -7285,6 +7760,16 @@ snapshots: electron-to-chromium@1.5.302: {} + elliptic@6.6.1: + dependencies: + bn.js: 4.12.3 + brorand: 1.1.0 + hash.js: 1.1.7 + hmac-drbg: 1.0.1 + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 + emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} @@ -7523,6 +8008,42 @@ snapshots: esutils@2.0.3: {} + ethers@5.8.0: + dependencies: + '@ethersproject/abi': 5.8.0 + '@ethersproject/abstract-provider': 5.8.0 + '@ethersproject/abstract-signer': 5.8.0 + '@ethersproject/address': 5.8.0 + '@ethersproject/base64': 5.8.0 + '@ethersproject/basex': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/constants': 5.8.0 + '@ethersproject/contracts': 5.8.0 + '@ethersproject/hash': 5.8.0 + '@ethersproject/hdnode': 5.8.0 + '@ethersproject/json-wallets': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/networks': 5.8.0 + '@ethersproject/pbkdf2': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/providers': 5.8.0 + '@ethersproject/random': 5.8.0 + '@ethersproject/rlp': 5.8.0 + '@ethersproject/sha2': 5.8.0 + '@ethersproject/signing-key': 5.8.0 + '@ethersproject/solidity': 5.8.0 + '@ethersproject/strings': 5.8.0 + '@ethersproject/transactions': 5.8.0 + '@ethersproject/units': 5.8.0 + '@ethersproject/wallet': 5.8.0 + '@ethersproject/web': 5.8.0 + '@ethersproject/wordlists': 5.8.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + ethers@6.16.0: dependencies: '@adraffy/ens-normalize': 1.10.1 @@ -7781,10 +8302,21 @@ snapshots: has-unicode@2.0.1: {} + hash.js@1.1.7: + dependencies: + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + hasown@2.0.2: dependencies: function-bind: 1.1.2 + hmac-drbg@1.0.1: + dependencies: + hash.js: 1.1.7 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 + hosted-git-info@2.8.9: {} hosted-git-info@4.1.0: @@ -7981,6 +8513,8 @@ snapshots: js-base64@3.7.8: {} + js-sha3@0.8.0: {} + js-tokens@4.0.0: {} js-yaml@3.14.2: @@ -8403,6 +8937,10 @@ snapshots: - bufferutil - utf-8-validate + minimalistic-assert@1.0.1: {} + + minimalistic-crypto-utils@1.0.1: {} + minimatch@10.2.1: dependencies: brace-expansion: 5.0.4 @@ -9177,6 +9715,8 @@ snapshots: dependencies: loose-envify: 1.4.0 + scrypt-js@3.0.1: {} + semver@5.7.2: {} semver@6.3.1: {} @@ -9529,6 +10069,8 @@ snapshots: uglify-js@3.19.3: optional: true + undici-types@5.26.5: {} + undici-types@6.19.8: {} undici-types@6.21.0: {} From 48f8636600a278d512be0c377969e24178325078 Mon Sep 17 00:00:00 2001 From: AkshatGada Date: Thu, 5 Mar 2026 11:13:03 +0530 Subject: [PATCH 2/2] feat(polymarket): port to TypeScript, fix factory routing, update docs --- .../polygon-agent-cli/skills/POLYMARKET.md | 501 ++++++------------ .../skills/polymarket-skill.md | 330 ++++++++++++ .../src/commands/polymarket.ts | 21 +- .../polygon-agent-cli/src/lib/polymarket.ts | 2 +- 4 files changed, 495 insertions(+), 359 deletions(-) create mode 100644 packages/polygon-agent-cli/skills/polymarket-skill.md diff --git a/packages/polygon-agent-cli/skills/POLYMARKET.md b/packages/polygon-agent-cli/skills/POLYMARKET.md index 967059e..8c94b52 100644 --- a/packages/polygon-agent-cli/skills/POLYMARKET.md +++ b/packages/polygon-agent-cli/skills/POLYMARKET.md @@ -1,395 +1,213 @@ --- name: polymarket -description: Place bets on Polymarket prediction markets via the Polygon Agent CLI. Browse markets, buy YES/NO positions, manage CLOB orders, check positions. Uses Sequence ecosystem wallet → Polymarket proxy wallet → CLOB (POLY_PROXY signature type). +description: Developer and architecture reference for the Polymarket integration in Polygon Agent CLI. Covers the 3-address system, proxy wallet factory, on-chain approval model, CLOB auth, neg-risk markets, and SDK internals. --- -# Polymarket Integration +# Polymarket Integration — Developer Reference -## Architecture +## The 3-Address Architecture -Polymarket trading requires three layers working together: +Every Polymarket user has exactly three addresses. Understanding the distinction is critical. ``` -Sequence Smart Wallet (holds USDC.e) - │ - │ ① USDC.e transfer (smart wallet tx, USDC gas fee) - ▼ -Polymarket Proxy Wallet (deterministic CREATE2, owned by EOA) - │ - │ ② On-chain batch: approve + split (EOA signs, EOA pays POL gas) - ▼ -CLOB — order maker = proxy wallet, signer = EOA (POLY_PROXY sig type) +EOA (0x19A413...) + │ Your private key. Signs every on-chain tx and every CLOB API request. + │ Holds POL for gas. Never holds USDC or outcome tokens directly. + │ + │ calls factory.execute([...txs]) + ▼ +Proxy Wallet Factory (0xaB45c5A4...) + │ Owns all proxy wallets. Routes execute() calls to the correct proxy + │ by looking up the EOA → proxy mapping internally. + │ + ▼ +Proxy Wallet (0x07E35CC...) + │ Your "Polymarket account" address — shown in the Polymarket UI. + │ Holds USDC.e and outcome (CTF) tokens. + │ The CLOB uses this as the order `maker`. + │ Address is deterministic: CREATE2(factory, EOA) — never changes. + │ + ▼ +CLOB — maker=proxyWallet, signer=EOA, signatureType=POLY_PROXY + +Deposit Address (0x7c583...) + A separate bridge ingress contract generated by Polymarket's bridge API. + Linked to the proxy wallet — assets sent here (from Ethereum, Arbitrum, + Solana, BTC, Tron) are auto-bridged and credited to the proxy wallet as USDC.e. + Polygon-native flows do NOT use the deposit address. + Send USDC.e directly to the proxy wallet address on Polygon. ``` -### The Three Actors +### Address Summary Table -| Actor | Created by | Role | -|-------|-----------|------| -| Sequence smart wallet | `polygon-agent wallet create` | Holds USDC.e; funds the proxy wallet | -| Polymarket proxy wallet | Polymarket factory (deterministic) | On-chain identity for CLOB trading; holds outcome tokens | -| Builder EOA | `polygon-agent setup` | Signs all on-chain txs and CLOB API requests | +| Address | Where it appears | Holds | Purpose | +|---------|-----------------|-------|---------| +| EOA | `eoaAddress` in CLI output | POL (gas only) | Signs txs + CLOB requests | +| Proxy Wallet | Polymarket UI "your address" | USDC.e + outcome tokens | On-chain trading identity | +| Deposit Address | Polymarket UI deposit flow | Nothing (pass-through) | Cross-chain bridge ingress | -### Why a Proxy Wallet? +### Where the EOA Key Comes From -Polymarket's CLOB requires every order to be signed by a private key. -A Sequence smart contract wallet cannot sign CLOB payloads directly. -The proxy wallet pattern solves this: the EOA (which has a private key) **owns** the proxy wallet and signs on its behalf (`signatureType=POLY_PROXY`), while the smart wallet is the ultimate source of funds. +**Option A — Email login (Polymarket website)** +1. Create account at polymarket.com using email (Magic Link) +2. After accepting Terms of Service, go to: https://reveal.magic.link/polymarket +3. Export your private key from there +4. The address shown in the Polymarket UI is the **proxy wallet**, not the EOA +5. The EOA address is visible in a connected wallet app (e.g. MetaMask after importing the key) +6. Import into CLI: `polygon-agent polymarket set-key ` -The proxy wallet address is **deterministic** — computed via CREATE2 from the EOA address and the Polymarket factory (`0xaB45c5A4B0c941a2F231C04C3f49182e1A254052`). It never changes. - -### Gas Model - -| Operation | Who pays gas | Token | -|-----------|-------------|-------| -| USDC.e transfer (smart wallet → proxy) | Sequence relayer | USDC.e (fee abstraction) | -| On-chain batch (approve + split) via `proxy.execute()` | Builder EOA | POL (native) | -| CLOB order posting | Off-chain (no gas) | — | - -**The EOA must hold native POL for gas.** The Sequence gas abstraction only applies to smart wallet transactions. Send ~0.1 POL to the EOA before first use. +**Option B — Use the builder EOA from `polygon-agent setup`** +The builder EOA created during setup can be used directly — skip `set-key`. +Run `polygon-agent polymarket proxy-wallet` to see the derived proxy wallet address. --- -## Prerequisites (One-Time Setup) - -```bash -# 1. Setup builder EOA + Sequence project -polygon-agent setup --name "MyAgent" -# → save accessKey and eoaAddress from output - -# 2. Export access key -export SEQUENCE_PROJECT_ACCESS_KEY= -export SEQUENCE_INDEXER_ACCESS_KEY=$SEQUENCE_PROJECT_ACCESS_KEY +## On-Chain Architecture: The Factory Pattern -# 3. Create ecosystem wallet with USDC spending limit -polygon-agent wallet create --usdc-limit 100 +### Why `factory.execute()` and not `proxy.execute()` -# 4. Fund the smart wallet with USDC.e on Polygon -polygon-agent fund -# → open the returned fundingUrl in browser, deposit via Trails widget +Polymarket's v1 proxy wallet factory changed the ownership model. The factory itself is the **owner** of all proxy wallets. The EOA does not own the proxy directly. -# 5. Fund the builder EOA with native POL for gas -# EOA address is in ~/.polygon-agent/builder.json → eoaAddress field -polygon-agent send-native --to --amount 0.1 --broadcast - -# 6. Show your proxy wallet address (for reference) -polygon-agent polymarket proxy-wallet - -# 7. Required for orders/cancel — accept Polymarket terms -# Visit https://polymarket.com, connect the builder EOA wallet, accept terms once. ``` +proxy._OWNER_SLOT → stores factory address (0xaB45c5A4...) + NOT the EOA address ---- - -## Commands Reference - -### `polymarket markets` - -List active Polymarket markets sorted by 24-hour volume. - -```bash -polygon-agent polymarket markets [--search ] [--limit ] [--offset ] +proxy.execute() → reverts: "must be called be owner" +factory.execute(txs) → factory checks EOA → proxy mapping, calls proxy internally ✓ ``` -| Flag | Default | Description | -|------|---------|-------------| -| `--search` | — | Client-side substring filter on market question | -| `--limit` | 20 | Max markets to return | -| `--offset` | 0 | Pagination offset | - -```bash -# Top 20 by volume -polygon-agent polymarket markets - -# Search for Bitcoin markets -polygon-agent polymarket markets --search "bitcoin" --limit 10 +Both `proxy.execute()` and `factory.execute()` take the same ABI: +```solidity +function execute(Transaction[] calldata transactions) external returns (bytes[] memory) -# Paginate -polygon-agent polymarket markets --limit 20 --offset 20 +struct Transaction { + uint8 typeCode; // 1 = CALL + address to; + uint256 value; + bytes data; +} ``` -**Output fields per market:** -- `conditionId` — unique market ID used in all other commands -- `question` — the market question text -- `yesPrice` / `noPrice` — current implied probabilities (0–1, e.g. `0.65` = 65% chance) -- `yesTokenId` / `noTokenId` — CLOB token IDs -- `volume24hr` — 24h trading volume in USD -- `negRisk` — `true` if neg-risk market (different approval flow, handled automatically) -- `endDate` — market resolution date - ---- +The CLI always calls `factory.execute()` at `0xaB45c5A4B0c941a2F231C04C3f49182e1A254052`. -### `polymarket market ` +### Proxy Wallet Address Derivation -Get full details for a single market. - -```bash -polygon-agent polymarket market +The proxy wallet address is deterministic (CREATE2). Given an EOA address: +```ts +import { getProxyWalletAddress } from '@polymarket/sdk'; +const proxyWallet = getProxyWalletAddress(PROXY_WALLET_FACTORY, eoaAddress); ``` -Use this to confirm token IDs and current prices before placing a bet. The Gamma API does not support direct conditionId lookup — internally scans up to 500 markets by volume then falls back to a closed-market query. +This matches the address shown in the Polymarket UI. The address never changes. --- -### `polymarket proxy-wallet` +## On-Chain Approvals -Show the Polymarket proxy wallet address derived from your builder EOA. +Before trading, the proxy wallet must grant token allowances to the exchange contracts. These are **permanent** — set once, never need repeating (unless you need neg-risk). -```bash -polygon-agent polymarket proxy-wallet -``` +### Standard Markets (negRisk: false) -Output: -```json -{ - "ok": true, - "eoaAddress": "0x1218...", - "proxyWalletAddress": "0xabcd...", - "note": "Fund proxyWalletAddress with USDC.e on Polygon to enable CLOB trading." -} ``` - -The proxy wallet is the actual holder of outcome tokens after a buy. It is permanent and deterministic per EOA. - ---- - -### `polymarket approve` - -Set the required on-chain approvals on the proxy wallet. **Run once before first use** — approvals are permanent on-chain. - -```bash -polygon-agent polymarket approve [--neg-risk] [--broadcast] +proxy wallet grants: + USDC.e.approve(CTF_EXCHANGE, MAX_UINT256) + CTF.setApprovalForAll(CTF_EXCHANGE, true) ``` -| Flag | Description | -|------|-------------| -| `--neg-risk` | Set neg-risk approvals (adds `NEG_RISK_ADAPTER` + `NEG_RISK_CTF_EXCHANGE`) | -| `--broadcast` | Execute. Without this flag, shows a dry-run plan. EOA must have POL (~0.001 POL). | +### Neg-Risk Markets (negRisk: true) -**What it sets (regular markets):** ``` -USDC.e → CTF_EXCHANGE (ERC20 approve, max) -CTF → CTF_EXCHANGE (setApprovalForAll) -``` - -**What it sets (`--neg-risk`):** -``` -USDC.e → NEG_RISK_ADAPTER (ERC20 approve, max) -USDC.e → NEG_RISK_CTF_EXCHANGE (ERC20 approve, max) -CTF → CTF_EXCHANGE (setApprovalForAll) -CTF → NEG_RISK_CTF_EXCHANGE (setApprovalForAll) -CTF → NEG_RISK_ADAPTER (setApprovalForAll) -``` - -All approvals are bundled in a single `proxy.execute()` transaction. - -```bash -# Dry-run — see what will be set -polygon-agent polymarket approve - -# Execute -polygon-agent polymarket approve --broadcast - -# For neg-risk markets -polygon-agent polymarket approve --neg-risk --broadcast +proxy wallet grants: + USDC.e.approve(NEG_RISK_ADAPTER, MAX_UINT256) + USDC.e.approve(NEG_RISK_CTF_EXCHANGE, MAX_UINT256) + CTF.setApprovalForAll(CTF_EXCHANGE, true) + CTF.setApprovalForAll(NEG_RISK_CTF_EXCHANGE, true) + CTF.setApprovalForAll(NEG_RISK_ADAPTER, true) ``` -**Success output:** -```json -{ - "ok": true, - "proxyWalletAddress": "0xabcd...", - "signerAddress": "0x1218...", - "negRisk": false, - "approveTxHash": "0x...", - "note": "Proxy wallet approvals set. Ready for clob-buy and sell." -} -``` +All approvals are batched into a single `factory.execute([...txBatch])` transaction. The EOA pays POL gas (~0.001 POL per approval batch). --- -### `polymarket clob-buy YES|NO ` +## CLOB Authentication -The primary command for entering a position. Funds the proxy wallet from the smart wallet, then buys outcome tokens directly from the CLOB. +The CLOB uses a two-layer auth system: -**Prerequisite:** Run `polymarket approve --broadcast` once before first use. +**L1 Auth (EIP-712)** — Used to create/derive API credentials. Signs a typed message with the EOA private key. Polymarket derives a deterministic API keypair from this. No stored secrets needed. -```bash -polygon-agent polymarket clob-buy YES|NO \ - [--price <0-1>] \ - [--fak] \ - [--wallet ] \ - [--skip-fund] \ - [--broadcast] -``` +**L2 Auth (HMAC)** — Used for all trading endpoints. HMAC-SHA256 over `timestamp + method + path + body` using the derived API secret. -| Argument / Flag | Description | -|----------------|-------------| -| `conditionId` | Market ID from `markets` or `market` | -| `YES` or `NO` | Outcome to buy | -| `usdcAmount` | USDC.e to spend (e.g. `10` = $10) | -| `--price <0-1>` | GTC limit order at this price. Omit = market order (FOK or FAK) | -| `--fak` | FAK (fill-and-kill, allows partial fill) instead of FOK | -| `--wallet ` | Smart wallet to fund from (default: `main`) | -| `--skip-fund` | Skip smart wallet → proxy transfer; use existing proxy balance | -| `--broadcast` | Execute. Without this flag, prints a dry-run plan with no funds moved. | - -#### What happens internally (with `--broadcast`) - -**Step 1 — Fund proxy wallet** -Smart wallet transfers `usdcAmount` USDC.e to the proxy wallet. Sequence tx — paid in USDC.e via fee abstraction. - -**Step 2 — CLOB BUY order** -Posts a BUY order: `maker=proxyWallet`, `signer=EOA`, `signatureType=POLY_PROXY`. -Tokens arrive in the proxy wallet. - -```bash -# Market buy $10 USDC worth of YES tokens -polygon-agent polymarket clob-buy \ - 0x5e5c9dfaf695371a0cc321b47b35f66a6dbd1482f0503526603d2bd2a91bfdc7 \ - YES 10 --broadcast - -# Limit buy — fill only if price ≤ 0.65 -polygon-agent polymarket clob-buy \ - 0x5e5c9dfaf695371a0cc321b47b35f66a6dbd1482f0503526603d2bd2a91bfdc7 \ - YES 10 --price 0.65 --broadcast - -# Skip re-funding (proxy already has USDC.e) -polygon-agent polymarket clob-buy \ - 0x5e5c9dfaf695371a0cc321b47b35f66a6dbd1482f0503526603d2bd2a91bfdc7 \ - YES 5 --skip-fund --broadcast +Both layers are handled transparently by `@polymarket/clob-client`: +```ts +const creds = await client.createOrDeriveApiKey(); // L1 auth → derives L2 creds +const client = new ClobClient(CLOB_URL, 137, signer, creds, SignatureType.POLY_PROXY, proxyWalletAddress); ``` -**Success output:** -```json -{ - "ok": true, - "conditionId": "0x5e5c...", - "question": "Will Bitcoin reach $150,000?", - "outcome": "YES", - "amountUsd": 10, - "currentPrice": 0.65, - "proxyWalletAddress": "0xabcd...", - "signerAddress": "0x1218...", - "fundTxHash": "0x...", - "orderId": "0x...", - "orderType": "FOK" -} -``` +`createOrDeriveApiKey()` is idempotent — safe to call every session. ---- +**Important:** The EOA must have accepted Polymarket's Terms of Service at polymarket.com at least once. Without this, `createOrDeriveApiKey()` returns `400 "Could not create api key"`. The CLI handles this gracefully (falls back to `deriveApiKey`) but CLOB order posting will fail if ToS was never accepted. -### `polymarket sell YES|NO ` +### Order Signing: POLY_PROXY Signature Type -Sell outcome tokens held in the proxy wallet via a CLOB SELL order. Pure off-chain — no on-chain transaction, no gas needed. - -```bash -polygon-agent polymarket sell YES|NO \ - [--price <0-1>] \ - [--fak] \ - [--broadcast] ``` - -| Argument / Flag | Description | -|----------------|-------------| -| `` | Number of outcome tokens to sell (e.g. `10` = 10 YES tokens) | -| `--price <0-1>` | GTC limit order. Omit = FOK market order | -| `--fak` | FAK (partial fill allowed) instead of FOK | -| `--broadcast` | Execute | - -```bash -# Market sell 10 YES tokens -polygon-agent polymarket sell \ - 0x5e5c9dfaf695371a0cc321b47b35f66a6dbd1482f0503526603d2bd2a91bfdc7 \ - YES 10 --broadcast - -# Limit sell — only fill at 0.80 or above -polygon-agent polymarket sell \ - 0x5e5c9dfaf695371a0cc321b47b35f66a6dbd1482f0503526603d2bd2a91bfdc7 \ - YES 10 --price 0.80 --broadcast +maker = proxyWalletAddress (who is committing funds) +signer = EOA address (who signs the EIP-712 payload) +signatureType = POLY_PROXY (tells exchange to verify via proxy wallet ownership) ``` ---- - -### `polymarket positions` - -List open positions for the smart wallet address (queries Polymarket Data API). - -```bash -polygon-agent polymarket positions [--wallet ] -``` - -Note: outcome tokens from `buy` are held in the **proxy wallet**, not the smart wallet. This command queries by smart wallet address — it may not show proxy wallet holdings. To see proxy wallet holdings, check polymarket.com using the address from `proxy-wallet`. +The EIP-712 domain `verifyingContract` is `CTF_EXCHANGE` for standard markets and `NEG_RISK_CTF_EXCHANGE` for neg-risk markets. --- -### `polymarket orders` - -List open CLOB orders placed by the builder EOA. +## Gas Model -```bash -polygon-agent polymarket orders -``` +| Operation | Who pays | Token | Approx cost | +|-----------|----------|-------|-------------| +| `approve --broadcast` | EOA | POL | ~0.001 POL | +| `clob-buy --broadcast` (fund step) | Sequence relayer | USDC.e | ~$0.01–0.05 USDC.e | +| `clob-buy --broadcast` (order step) | Off-chain | — | Free | +| `sell --broadcast` | Off-chain | — | Free | -Authenticates via EIP-712 L1 auth + HMAC L2 credentials derived from the EOA private key (via `@polymarket/clob-client`). Requires the EOA to have accepted Polymarket terms at polymarket.com. +The Sequence gas abstraction (USDC.e fee token) only applies to smart wallet transactions. The EOA must hold POL for the `approve` step. After that, only the Sequence smart wallet needs USDC.e. --- -### `polymarket cancel ` - -Cancel an open CLOB order by ID. +## Funding Flow (Polygon-Native) -```bash -polygon-agent polymarket cancel ``` - -```bash -# Get order IDs first -polygon-agent polymarket orders - -# Cancel -polygon-agent polymarket cancel 0xabc123... +Sequence Smart Wallet (holds USDC.e) + │ + │ ERC20 transfer: USDC.e → proxy wallet + │ (Sequence tx, paid in USDC.e via fee abstraction) + ▼ +Proxy Wallet (now has USDC.e for trading) + │ + │ CLOB BUY order (off-chain, no gas) + ▼ +Outcome tokens arrive in proxy wallet ``` ---- +`clob-buy` without `--skip-fund` performs both steps atomically. +`--skip-fund` skips the transfer and uses whatever USDC.e is already in the proxy wallet. -## Full Flow: End-to-End Example +**Cross-chain funding** (not implemented in CLI — manual): +Use the deposit address from Polymarket UI to bridge from other chains. Assets auto-convert to USDC.e on Polygon. -```bash -# --- Prerequisites (run once) --- -export SEQUENCE_PROJECT_ACCESS_KEY= -export SEQUENCE_INDEXER_ACCESS_KEY=$SEQUENCE_PROJECT_ACCESS_KEY - -# Fund EOA with POL for gas (eoaAddress is in ~/.polygon-agent/builder.json) -polygon-agent send-native --to --amount 0.1 --broadcast - -# Set proxy wallet approvals (one-time, permanent) -polygon-agent polymarket approve --broadcast -# For neg-risk markets: polygon-agent polymarket approve --neg-risk --broadcast - -# --- Find a market --- -polygon-agent polymarket markets --search "trump" --limit 5 -# → note the conditionId you want - -# Inspect it -polygon-agent polymarket market 0x -# → check yesPrice, noPrice, endDate, negRisk - -# --- Place a bet --- -# Dry-run first -polygon-agent polymarket clob-buy 0x YES 10 - -# Execute (funds proxy wallet + places CLOB BUY) -polygon-agent polymarket clob-buy 0x YES 10 --broadcast +--- -# --- Manage --- -# Check open orders -polygon-agent polymarket orders +## SDK Stack -# Cancel if needed -polygon-agent polymarket cancel +| Package | Version | Used for | +|---------|---------|----------| +| `@polymarket/clob-client` | latest | CLOB API: order creation, posting, cancellation, open orders | +| `@polymarket/sdk` | latest | Proxy wallet address derivation (`getProxyWalletAddress`) | +| `@polymarket/order-utils` | latest | `SignatureType` enum (POLY_PROXY, EOA) | +| `ethers5` | aliased | Required by `@polymarket/clob-client` (ethers v5 only); aliased to coexist with main ethers v6 | +| `viem` | project default | `encodeFunctionData`, wallet/public clients for on-chain calls | -# Sell your position -polygon-agent polymarket sell 0x YES 10 --broadcast +`ethers5` is aliased in `package.json`: +```json +"ethers5": "npm:ethers@5" ``` --- @@ -407,13 +225,14 @@ polygon-agent polymarket sell 0x YES 10 --broadcast --- -## APIs Used +## APIs | API | Base URL | Auth | |-----|----------|------| | Gamma (market discovery) | `https://gamma-api.polymarket.com` | None | -| CLOB (trading) | `https://clob.polymarket.com` | EIP-712 L1 + HMAC L2 (via `@polymarket/clob-client`) | -| Data (positions) | `https://data-api.polymarket.com` | None | +| CLOB (trading) | `https://clob.polymarket.com` | EIP-712 L1 + HMAC L2 | +| Data (positions, trades) | `https://data-api.polymarket.com` | None | +| Bridge | `https://bridge.polymarket.com` | None (deposit address creation) | Override via env: `POLYMARKET_GAMMA_URL`, `POLYMARKET_CLOB_URL`, `POLYMARKET_DATA_URL`. @@ -421,40 +240,28 @@ Override via env: `POLYMARKET_GAMMA_URL`, `POLYMARKET_CLOB_URL`, `POLYMARKET_DAT ## Neg-Risk Markets -When `negRisk: true` in market data, `polymarket approve --neg-risk` sets the extended approval set: +Neg-risk markets are multi-outcome markets (e.g. "Which team wins the NBA Finals?") where outcomes are negatively correlated — if one wins, all others lose. They use different exchange contracts. -| | Regular Market | Neg-Risk Market (`--neg-risk`) | -|-|---------------|-------------------------------| -| USDC.e approval | → CTF Exchange | → Neg Risk Adapter + Neg Risk CTF Exchange | -| CTF approval | → CTF Exchange | → CTF Exchange + Neg Risk CTF Exchange + Neg Risk Adapter | +**Detection:** `negRisk: true` in Gamma API market response. -Run `polymarket approve --neg-risk --broadcast` to set neg-risk approvals, then `clob-buy` works normally. +**Approval difference:** ---- +| Approval | Standard | Neg-Risk | +|----------|---------|---------| +| USDC.e approve | CTF_EXCHANGE | NEG_RISK_ADAPTER + NEG_RISK_CTF_EXCHANGE | +| CTF setApprovalForAll | CTF_EXCHANGE | CTF_EXCHANGE + NEG_RISK_CTF_EXCHANGE + NEG_RISK_ADAPTER | -## Troubleshooting +Run `polygon-agent polymarket approve --neg-risk --broadcast` once to enable neg-risk trading. If you already ran standard `approve`, run neg-risk `approve` on top — it adds the missing approvals. -| Error | Cause | Fix | -|-------|-------|-----| -| `insufficient funds for gas` | EOA has no POL | `polygon-agent send-native --to --amount 0.1 --broadcast` | -| `Could not create api key` | EOA not registered on Polymarket | Visit polymarket.com, connect EOA wallet, accept terms | -| `CLOB order error: 403` | Cloudflare blocking POST | Set `HTTPS_PROXY` env var or retry | -| `Market not found` | conditionId not in top 500 by volume | Market may be low-volume or closed | -| `Market has no tokenIds` | Market closed or not CLOB-deployed | Check `endDate` — may have resolved | -| `Wallet not found: main` | No ecosystem wallet | Run `polygon-agent wallet create` | -| `Builder EOA not found` | Setup not done | Run `polygon-agent setup` | -| `Session expired` | 24h session window elapsed | Re-run `polygon-agent wallet create` | -| `clob-buy` fails with approval error | Proxy wallet not approved | Run `polygon-agent polymarket approve --broadcast` | +**Order difference:** The `negRisk: true` option must be passed to `createOrder`/`createMarketOrder` in the CLOB client. The CLI handles this automatically based on the Gamma market data. --- -## Key Behaviours +## Key Implementation Notes -| Behaviour | Detail | -|-----------|--------| -| Dry-run by default | `approve`, `clob-buy`, `sell` without `--broadcast` show a plan; no funds moved | -| Proxy wallet is permanent | Same address forever — derived from EOA via CREATE2. Token balances persist across sessions. | -| Approvals are one-time | Run `approve --broadcast` once. Permanent on-chain — no need to repeat unless switching to neg-risk markets. | -| CLOB-only flow | `clob-buy` buys tokens directly from the order book. No minting/splitting. | -| CLOB auth is stateless | Credentials derived on-the-fly via `createOrDeriveApiKey()` from the EOA private key. No stored CLOB API keys needed. | -| `ethers5` alias | `@polymarket/clob-client` requires ethers v5. It is aliased as `ethers5` in `package.json` alongside the main `ethers` v6 dependency. Both coexist without conflict. | +- **`executeViaProxyWallet` always calls the factory**, not the proxy. `to: PROXY_WALLET_FACTORY`. Calling the proxy directly reverts with "must be called be owner". +- **`positions` command queries the proxy wallet**, not the Sequence smart wallet. The Data API lookup is by `proxyWalletAddress`. +- **`loadPolymarketKey()` has a fallback**: returns the Polymarket-specific key if `set-key` was called, otherwise falls back to the builder EOA key from `setup`. Both are stored encrypted in `~/.polygon-agent/builder.json`. +- **CLOB API key derivation is stateless** — `createOrDeriveApiKey()` always produces the same credentials from the same EOA private key. No API keys are stored on disk. +- **FOK vs FAK vs GTC**: FOK (default) must fill entirely or cancels. FAK allows partial fills. GTC stays open until manually cancelled. Use GTC with `--price` for limit orders. +- **CLOB minimum order size is $1 USD** for marketable BUY orders. Orders below $1 are rejected with `"invalid amount for a marketable BUY order ($X), min size: $1"`. If the fund step completed before the rejection, USDC.e is already in the proxy wallet — retry with `--skip-fund`. diff --git a/packages/polygon-agent-cli/skills/polymarket-skill.md b/packages/polygon-agent-cli/skills/polymarket-skill.md new file mode 100644 index 0000000..0cac0ff --- /dev/null +++ b/packages/polygon-agent-cli/skills/polymarket-skill.md @@ -0,0 +1,330 @@ +--- +name: polymarket-skill +description: Place bets on Polymarket prediction markets using the Polygon Agent CLI. Browse markets, check prices, buy YES/NO positions, sell positions, manage orders. All commands are JSON output. Dry-run by default — always add --broadcast to execute. +--- + +# Polymarket Skill + +## Session Initialization + +Before any polymarket command, initialize the session. Read `~/.polygon-agent/builder.json` and export the access key: + +```bash +export SEQUENCE_PROJECT_ACCESS_KEY= +export SEQUENCE_INDEXER_ACCESS_KEY=$SEQUENCE_PROJECT_ACCESS_KEY +``` + +Also verify the Polymarket key is set: +```bash +polygon-agent polymarket proxy-wallet +``` +If this returns `ok: true` with an `eoaAddress` and `proxyWalletAddress`, the key is configured. If it errors, the user needs to run `set-key` (see Onboarding below). + +--- + +## Understanding the 3 Addresses + +Every Polymarket user has three addresses. Do not confuse them: + +| Name | What it is | Used for | +|------|-----------|---------| +| EOA | Private key owner. Shown as `eoaAddress` in CLI output | Signs transactions and CLOB orders. Holds POL for gas | +| Proxy Wallet | Shown as `proxyWalletAddress` in CLI output. This is what Polymarket shows as "your address" in the UI | Holds USDC.e and outcome tokens. The CLOB `maker` | +| Deposit Address | A cross-chain bridge ingress — only relevant for bridging from other chains | Ignore for Polygon-native usage | + +**For trading:** funds go from the Sequence smart wallet → proxy wallet → CLOB orders. The proxy wallet is the trading identity. + +--- + +## Onboarding: First-Time Setup + +### Option A — Using email login (Polymarket account) + +If the user has a Polymarket account via email login: + +**Step 1: Get the private key from Polymarket** +``` +1. Go to: https://reveal.magic.link/polymarket +2. Connect/authenticate with the same email used for Polymarket +3. Copy the exported private key (0x...) +``` + +**Step 2: Accept Terms of Service** +``` +1. Go to: https://polymarket.com +2. Connect wallet using the exported private key (import to MetaMask or similar) +3. Accept Terms of Service when prompted + — This is REQUIRED for CLOB order placement. Without it, orders will fail. +``` + +**Step 3: Import the key into the CLI** +```bash +polygon-agent polymarket set-key +``` +Output confirms the `eoaAddress` and `proxyWalletAddress`. + +**Step 3b: Confirm your addresses (show this to the user)** +```bash +polygon-agent polymarket proxy-wallet +``` +**Tell the user:** "Your EOA is `` — this needs POL for gas. Your Polymarket trading address (proxy wallet) is `` — this is where your USDC.e and outcome tokens live. The proxy wallet does not need POL. You must fund the EOA with POL and run approvals before trading." + +**Step 4: Fund the EOA with POL for gas** +```bash +# Check EOA address from set-key output, then send ~0.1 POL to it +polygon-agent send-native --to --amount 0.1 --broadcast +``` + +**Step 5: Set proxy wallet approvals (one-time, permanent)** +```bash +polygon-agent polymarket approve --broadcast +``` +This sends a transaction directly from the EOA (not through Polymarket's UI), so the EOA must hold POL for gas. This is different from trading on polymarket.com, where their UI sponsors gas for you. + +### Option B — Using the builder EOA (no Polymarket account needed) + +If the user has done `polygon-agent setup` already, the builder EOA can be used directly. Skip `set-key`. + +**Step 1: Confirm addresses (show this to the user)** +```bash +polygon-agent polymarket proxy-wallet +``` +**Tell the user:** "Your EOA is `` — this needs POL for gas. Your Polymarket trading address (proxy wallet) is `` — this is where your USDC.e and outcome tokens live. The proxy wallet does not need POL. You must accept Polymarket ToS, fund the EOA with POL, and run approvals before trading." + +**Step 2: Accept Terms of Service (required — CLOB orders will fail without this)** +``` +1. Go to https://polymarket.com +2. Connect with the EOA wallet address shown above +3. Accept Terms of Service when prompted +``` + +**Step 3: Fund the EOA with POL for gas** +```bash +polygon-agent send-native --to --amount 0.1 --broadcast +``` + +**Step 4: Set proxy wallet approvals (one-time)** +```bash +polygon-agent polymarket approve --broadcast +``` +This sends a transaction directly from the EOA (not through Polymarket's UI), so the EOA must hold POL for gas. + +--- + +## Commands + +### Browse Markets + +```bash +# List top markets by volume +polygon-agent polymarket markets + +# Search by keyword +polygon-agent polymarket markets --search "bitcoin" --limit 10 + +# Paginate +polygon-agent polymarket markets --limit 20 --offset 20 +``` + +Key output fields per market: +- `conditionId` — the ID needed for all trading commands +- `question` — what the market is asking +- `yesPrice` / `noPrice` — current probability (0 to 1, e.g. `0.65` = 65%) +- `negRisk` — if `true`, set neg-risk approvals before trading this market +- `endDate` — when the market resolves + +### Get a Single Market + +```bash +polygon-agent polymarket market +``` + +Use this to confirm prices and token IDs before placing an order. + +### Show Proxy Wallet Address + +```bash +polygon-agent polymarket proxy-wallet +``` + +Confirms which EOA and proxy wallet are active. The proxy wallet is where USDC.e and tokens are held. + +### Set Approvals (One-Time) + +```bash +# Standard markets +polygon-agent polymarket approve --broadcast + +# Neg-risk markets (if you see negRisk: true on any market you want to trade) +polygon-agent polymarket approve --neg-risk --broadcast +``` + +Run once per EOA. Permanent on-chain — no need to repeat unless enabling neg-risk. +**Dry-run (no --broadcast) shows what will be approved without executing.** + +### Buy a Position + +```bash +# Dry-run first — always check before executing +polygon-agent polymarket clob-buy YES|NO + +# Execute — funds proxy wallet from smart wallet, then places order +polygon-agent polymarket clob-buy YES|NO --broadcast + +# If proxy wallet already has USDC.e (skip the funding step) +polygon-agent polymarket clob-buy YES|NO --skip-fund --broadcast + +# Limit order — fill only at this price or better +polygon-agent polymarket clob-buy YES --price 0.45 --broadcast +``` + +**How it works:** +1. Smart wallet transfers `usdcAmount` USDC.e to the proxy wallet (Sequence tx, USDC.e fee) +2. Posts CLOB BUY order: maker=proxy wallet, signer=EOA (off-chain, no gas) +3. Tokens arrive in proxy wallet on fill + +**Order types:** +- No `--price`: FOK market order (fill entirely or cancel) +- `--fak`: FAK market order (partial fills allowed) +- `--price 0.x`: GTC limit order (stays open until filled or cancelled) + +**Minimum order size: $1 USDC.** The CLOB rejects marketable BUY orders below $1. If the fund step runs but the order is rejected, the USDC.e stays in the proxy wallet — use `--skip-fund` on the retry. + +### Sell a Position + +```bash +# Dry-run first +polygon-agent polymarket sell YES|NO + +# Execute +polygon-agent polymarket sell YES|NO --broadcast + +# Limit sell +polygon-agent polymarket sell YES --price 0.80 --broadcast +``` + +`` is the number of outcome tokens (not USD). Get share count from `positions`. +Selling is pure off-chain — no gas, no on-chain tx. + +### Check Positions + +```bash +polygon-agent polymarket positions +``` + +Shows all open positions in the proxy wallet with current value, P&L, and outcome. + +### Check Open Orders + +```bash +polygon-agent polymarket orders +``` + +Lists GTC limit orders that are still open (FOK/FAK orders are never "open" — they fill or cancel immediately). + +### Cancel an Order + +```bash +polygon-agent polymarket cancel +``` + +Get `orderId` from the `orders` command or from the `orderId` field in `clob-buy` output. + +--- + +## Full Autonomous Trading Flow + +This is the exact sequence to go from zero to a filled trade: + +```bash +# ── SETUP (run once per EOA) ──────────────────────────────────────────── + +# 1. Import your Polymarket private key +# (get it from https://reveal.magic.link/polymarket after email login) +polygon-agent polymarket set-key 0x +# → save eoaAddress and proxyWalletAddress from output + +# 2. Fund the EOA with POL for gas +polygon-agent send-native --to --amount 0.1 --broadcast + +# 3. Set proxy wallet approvals (one-time) +polygon-agent polymarket approve --broadcast +# → save approveTxHash, wait for confirmation + +# ── FIND A MARKET ──────────────────────────────────────────────────────── + +# 4. Search for markets +polygon-agent polymarket markets --search "fed rate" --limit 10 + +# 5. Get details on a specific market +polygon-agent polymarket market 0x +# → check: yesPrice, noPrice, negRisk, endDate +# → if negRisk: true → run approve --neg-risk --broadcast first + +# ── ENTER A POSITION ──────────────────────────────────────────────────── + +# 6. Dry-run to confirm everything +polygon-agent polymarket clob-buy 0x YES 5 +# → review: currentPrice, proxyWalletAddress, flow + +# 7. Execute +polygon-agent polymarket clob-buy 0x YES 5 --broadcast +# → save orderId, check orderStatus === "matched" + +# ── MANAGE ────────────────────────────────────────────────────────────── + +# 8. Check your positions +polygon-agent polymarket positions +# → review: size (shares), curPrice, cashPnl, title, outcome + +# 9. Sell when ready +polygon-agent polymarket sell 0x YES --broadcast +# → orderStatus === "matched" means USDC.e is back in proxy wallet +``` + +--- + +## Decision Logic for an Autonomous Agent + +When deciding whether to buy: +1. Check `positions` — avoid doubling up on already-held positions +2. Check `markets` — use `yesPrice`/`noPrice` as probability inputs +3. Check `negRisk` — if `true`, verify neg-risk approvals were set +4. Check proxy wallet USDC.e balance before buying (use `proxy-wallet` to get address, then check balance externally or via `balances`) +5. Use `--skip-fund` if the proxy wallet already has enough USDC.e from a previous `clob-buy` +6. Always dry-run first, then broadcast + +When deciding whether to sell: +1. Get current `size` (shares) from `positions` +2. Use `curPrice` vs `avgPrice` to assess profit/loss +3. Market sell (`sell --broadcast`) for immediate exit +4. Limit sell (`--price 0.x --broadcast`) to wait for a better price + +--- + +## Troubleshooting + +| Error | Cause | Fix | +|-------|-------|-----| +| `No EOA key found` | `set-key` not run | Run `polygon-agent polymarket set-key ` | +| `Could not create api key` (stderr) | ToS not accepted | Visit polymarket.com, connect EOA wallet, accept terms. This error in **stderr** is non-fatal — the CLI retries with `deriveApiKey` and may still succeed. | +| `CLOB order error: not authorized` | ToS not accepted | Same as above — fatal for order posting | +| `insufficient funds for gas` | EOA has no POL | `polygon-agent send-native --to --amount 0.1 --broadcast` | +| `execution reverted: must be called be owner` | Old code calling proxy directly | Upgrade CLI — fixed in current version (calls factory) | +| `Market not found` | Low-volume or closed market | Market may have resolved; try `--search` with different terms | +| `Market has no tokenIds` | Closed market | Check `endDate` — market resolved | +| `orderStatus: "unmatched"` on FOK | No liquidity at market price | Try `--fak` for partial fill, or `--price 0.x` for limit order | +| `invalid amount for a marketable BUY order ($X), min size: $1` | Amount below CLOB minimum | Use at least $1. If USDC.e was already funded, retry with `--skip-fund` | +| `Wallet not found: main` | No Sequence wallet | Run `polygon-agent wallet create` | + +--- + +## Key Facts for Agents + +- **All commands are dry-run by default.** `approve`, `clob-buy`, `sell` do nothing without `--broadcast`. +- **`clob-buy` transfers USDC.e from the smart wallet to the proxy wallet automatically** (unless `--skip-fund`). +- **Positions live in the proxy wallet**, not the Sequence smart wallet. `positions` queries the proxy wallet. +- **Approvals are one-time.** Don't run `approve` before every trade — only once per EOA (and once more if enabling neg-risk). +- **Sell is free.** No gas, no on-chain tx. Selling via CLOB is a signed off-chain message only. +- **`orderStatus: "matched"`** means the trade filled. `"unmatched"` means FOK failed (no liquidity). +- **The proxy wallet address never changes.** It is deterministic from the EOA via CREATE2. diff --git a/packages/polygon-agent-cli/src/commands/polymarket.ts b/packages/polygon-agent-cli/src/commands/polymarket.ts index 4c09ecf..a1cfbae 100644 --- a/packages/polygon-agent-cli/src/commands/polymarket.ts +++ b/packages/polygon-agent-cli/src/commands/polymarket.ts @@ -521,18 +521,19 @@ async function handleSell(argv: { } } -async function handlePositions(argv: { wallet?: string }): Promise { - const walletName = argv.wallet ?? 'main'; +async function handlePositions(): Promise { try { - const session = await loadWalletSession(walletName); - if (!session) throw new Error(`Wallet not found: ${walletName}`); + const privateKey = await loadPolymarketKey(); + const { privateKeyToAccount } = await import('viem/accounts'); + const account = privateKeyToAccount(privateKey as `0x${string}`); + const proxyWalletAddress = await getPolymarketProxyWalletAddress(account.address); - const positions = await getPositions(session.walletAddress); + const positions = await getPositions(proxyWalletAddress); console.log( JSON.stringify( { ok: true, - walletAddress: session.walletAddress, + proxyWalletAddress, count: Array.isArray(positions) ? positions.length : 0, positions }, @@ -702,11 +703,9 @@ export const polymarketCommand: CommandModule = { }) .command({ command: 'positions', - describe: 'List open positions for the smart wallet', - builder: (y) => - y.option('wallet', { type: 'string', default: 'main', describe: 'Wallet name' }), - // eslint-disable-next-line @typescript-eslint/no-explicit-any - handler: (argv) => handlePositions(argv as any) + describe: 'List open positions for the Polymarket proxy wallet', + builder: (y) => y, + handler: () => handlePositions() }) .command({ command: 'orders', diff --git a/packages/polygon-agent-cli/src/lib/polymarket.ts b/packages/polygon-agent-cli/src/lib/polymarket.ts index 3d5d726..a76219e 100644 --- a/packages/polygon-agent-cli/src/lib/polymarket.ts +++ b/packages/polygon-agent-cli/src/lib/polymarket.ts @@ -96,7 +96,7 @@ export async function executeViaProxyWallet( functionName: 'execute', args: [transactions] }); - const hash = await walletClient.sendTransaction({ to: proxyWalletAddress, data, value: 0n }); + const hash = await walletClient.sendTransaction({ to: PROXY_WALLET_FACTORY, data, value: 0n }); await publicClient.waitForTransactionReceipt({ hash }); return hash; }