Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/polygon-agent-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
267 changes: 267 additions & 0 deletions packages/polygon-agent-cli/skills/POLYMARKET.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
---
name: polymarket
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 — Developer Reference

## The 3-Address Architecture

Every Polymarket user has exactly three addresses. Understanding the distinction is critical.

```
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.
```

### Address Summary Table

| 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 |

### Where the EOA Key Comes From

**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 <privateKey>`

**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.

---

## On-Chain Architecture: The Factory Pattern

### Why `factory.execute()` and not `proxy.execute()`

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.

```
proxy._OWNER_SLOT → stores factory address (0xaB45c5A4...)
NOT the EOA address

proxy.execute() → reverts: "must be called be owner"
factory.execute(txs) → factory checks EOA → proxy mapping, calls proxy internally ✓
```

Both `proxy.execute()` and `factory.execute()` take the same ABI:
```solidity
function execute(Transaction[] calldata transactions) external returns (bytes[] memory)

struct Transaction {
uint8 typeCode; // 1 = CALL
address to;
uint256 value;
bytes data;
}
```

The CLI always calls `factory.execute()` at `0xaB45c5A4B0c941a2F231C04C3f49182e1A254052`.

### Proxy Wallet Address Derivation

The proxy wallet address is deterministic (CREATE2). Given an EOA address:
```ts
import { getProxyWalletAddress } from '@polymarket/sdk';
const proxyWallet = getProxyWalletAddress(PROXY_WALLET_FACTORY, eoaAddress);
```

This matches the address shown in the Polymarket UI. The address never changes.

---

## On-Chain Approvals

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).

### Standard Markets (negRisk: false)

```
proxy wallet grants:
USDC.e.approve(CTF_EXCHANGE, MAX_UINT256)
CTF.setApprovalForAll(CTF_EXCHANGE, true)
```

### Neg-Risk Markets (negRisk: true)

```
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)
```

All approvals are batched into a single `factory.execute([...txBatch])` transaction. The EOA pays POL gas (~0.001 POL per approval batch).

---

## CLOB Authentication

The CLOB uses a two-layer auth system:

**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.

**L2 Auth (HMAC)** — Used for all trading endpoints. HMAC-SHA256 over `timestamp + method + path + body` using the derived API secret.

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);
```

`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.

### Order Signing: POLY_PROXY Signature Type

```
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)
```

The EIP-712 domain `verifyingContract` is `CTF_EXCHANGE` for standard markets and `NEG_RISK_CTF_EXCHANGE` for neg-risk markets.

---

## Gas Model

| 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 |

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.

---

## Funding Flow (Polygon-Native)

```
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.

**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.

---

## SDK Stack

| 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 |

`ethers5` is aliased in `package.json`:
```json
"ethers5": "npm:ethers@5"
```

---

## 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

| API | Base URL | Auth |
|-----|----------|------|
| Gamma (market discovery) | `https://gamma-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`.

---

## Neg-Risk Markets

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.

**Detection:** `negRisk: true` in Gamma API market response.

**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 |

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.

**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 Implementation Notes

- **`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`.
Loading
Loading