Skip to content

feat(v2): Polymarket CLOB V2 SDK 完整迁移 (constants + trading + wrap + approvals + calldata + 3 P0 fixes)#29

Merged
cyl19970726 merged 4 commits into
copy-tradingfrom
feat/v2-sdk-a-constants
May 5, 2026
Merged

feat(v2): Polymarket CLOB V2 SDK 完整迁移 (constants + trading + wrap + approvals + calldata + 3 P0 fixes)#29
cyl19970726 merged 4 commits into
copy-tradingfrom
feat/v2-sdk-a-constants

Conversation

@cyl19970726
Copy link
Copy Markdown
Owner

背景

Polymarket CLOB V2 已于 2026-04-28 11:00 UTC 上线。V1 SDK 与 V1-signed orders 在 production 已不再被接受 — 破坏性更新,没有向后兼容

本 PR 是 poly-sdk 的完整 V2 迁移,覆盖所有 SDK 层 V1→V2 改造。包含 4 个 commit(stage A → B → C → 3 P0 fixes from multi-perspective review),最终通过 154 tests + tsc clean + 4 视角 review。

参考:

  • 迁移 SSOT: `earning-engine/.claude/skills/guide-polymarket-v2-migration/`
  • 详细方案: `plans/02-poly-sdk-migration.md`, `plans/12-poly-sdk-pr-templates.md`
  • 4 视角 review reports: `reviews/poly-sdk-v2-{correctness,safety,fidelity,completeness}.md`
  • audit: `audits/01-poly-sdk-audit.md` (closes 4 P0)

修改方案

1. SDK 包切换 (89f65d3 - stage A)

V1 V2
`@polymarket/clob-client@5.x` `@polymarket/clob-client-v2@1.0.3`
EIP-712 域 version `"1"` `"2"` (V2 SDK 自动 resolve)
ClobAuthDomain `"1"` `"1"` (不变)
保留 - `@polymarket/builder-signing-sdk` (用于 Relayer gasless TX)

新建 `src/constants/v2-contracts.ts` — V2 合约 + EIP-712 SSOT:

合约 V2 地址
CTF Exchange `0xE111180000d2663C0091e4f400237545B87B996B` (changed)
NegRisk Exchange `0xe2222d279d744050d28e00520010520000310F59` (changed)
NegRisk Adapter `0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296` (unchanged)
pUSD `0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB`
Onramp `0x93070a847efEf7F70739046A929D47a521F5B8ee` (on-chain verified)
Offramp `0x2957922Eb93258b93368531d39fAcCA3B4dC5854` (separate from pUSD)

2. TradingService V2 (7e09210 - stage B)

  • 6 处 `new ClobClient(...)` 全部转换为 V2 options-bag (`chainId` → `chain`)
  • Order Struct V2: 移除 `nonce`/`feeRateBps`/`taker`,添加 `timestamp(ms)`/`metadata`/`builder(bytes32)`
  • Builder 认证拆分: `builderCode` (bytes32) for order signing; HMAC creds 仍用于 Relayer
  • 新建 `src/constants/builder-config.ts` — `POLY_BUILDER_CODE` env 验证 + `ensureBuilderCode()` fail-fast 门
  • 移除 V1 SDK package
  • 20 新单元测试

3. Wrap + Approvals + Calldata V2 (2f52712 - stage C)

  • `RelayerService.wrapUsdcToPUSD()` + `unwrapPUSDtoUsdc()` (USDC.e ↔ pUSD via Onramp/Offramp)
  • `AuthorizationService` V2 spenders 矩阵(4 ERC20 + 3 ERC1155,token-aware: pUSD vs USDC.e)
  • `calldata-decoder` V1/V2 dual decoder (V2 selector `0x3c2b4399`, V1 selector `0x2287e350`, auto-detect with V1 fallback)
  • 32 新单元测试

4. 3 P0 Fixes from 4-perspective review (09bc31c)

  • Fidelity P0 (builderCreds silent drop): SDK constructor 现在 throw clear migration error if builderCreds present without builderCode
  • Completeness P0-A (relayer V1 collateral): 6 个 relayer 函数 (`split / merge / redeem / redeemBatch / approveUsdc / transferUsdc`) 添加 `token: 'pUSD' | 'USDC.e'` 参数,默认 'pUSD'
  • Completeness P0-C (ctf-client V1 addrs): `CTF_EXCHANGE` → `CTF_EXCHANGE_V1_DEPRECATED` (alias preserved with @deprecated JSDoc) + 新 `_V2` exports
  • 17 新单元测试

数据流

修改前 (V1)

```
strategy → TradingService.placeOrder()
├── ClobClient V1 (positional ctor)
├── EIP-712 sign with domain v1
├── HMAC headers (POLY_BUILDER_API_KEY/SECRET/PASSPHRASE)
└── POST /order to CLOB

钱包 ops → RelayerService
├── HMAC envelope sign
└── POST /relayer-tx (使用 USDC.e 作 collateral)
```

修改后 (V2)

```
strategy → TradingService.placeOrder()
├── ClobClient V2 (options-bag ctor: chain, builderConfig: { builderCode })
├── EIP-712 sign with domain v2 (auto-resolved by V2 SDK)
├── Order struct includes builder bytes32 from POLY_BUILDER_CODE env
└── POST /order to CLOB

钱包 ops → RelayerService
├── HMAC envelope sign (path 不变)
├── token: 'pUSD' (default for V2 trading) or 'USDC.e' (fund-out only)
└── wrapUsdcToPUSD / unwrapPUSDtoUsdc via Onramp/Offramp
```

测试

数量 状态
Baseline tests (V1 era) 85 ✅ pass
V2 builder-config 13 ✅ pass
V2 trading-service-init 7 ✅ pass
V2 relayer-wrap 9 ✅ pass
V2 authorization 7 ✅ pass
V2 calldata-decoder 16 ✅ pass
V2 trading-service-init (P0 fix +) 11 ✅ pass
V2 relayer-token-routing (P0 fix) 13 ✅ pass
总计 154 ✅ all pass
  • `npx tsc --noEmit` → 0 errors
  • `pnpm build` → clean
  • `pnpm test` duration: 9.17s

4 视角 Review 结果

视角 GATE 关键 finding
Correctness ✅ pass 0 P0 / 0 P1 / 3 P2 polish (all V2 claims verified via SDK source + on-chain bytecode + ethers selector probe)
Safety 🟡 conditional GO poly-sdk PR 本身 safe to merge; production wrap 需先 ship Plan 11 §1 PM2 halt + §2 revoke CLI + §8 idempotency (Plan 11 已 backlog)
Fidelity ✅ fixed 1 P0 silent-break (`builderCreds` drop) — fixed in 09bc31c
Completeness 🟡 partial 4 P0; 3 fixed in 09bc31c; 1 deferred (smart-money mempool V1 selector — known issue, earning-engine layer follow-up) + 1 noted (Unwrap Offramp approval — Plan 11 §6)

详见 `reviews/poly-sdk-v2-{correctness,safety,fidelity,completeness}.md`。

已知遗留 (out of scope, follow-up)

  • Smart-money mempool V2 selector support: `smart-money/monitor.ts` 仍用 V1 router + V1 selector — 滤掉 V2 settlement TX。Smart-money 不在 trading 关键路径,不影响本 PR merge。Earning-engine 层 follow-up。
  • Unwrap Offramp approval not in default approveAll(): documented in code comments + Plan 11 §6 + `ee wallet reapprove --include-offramp` flag (待 earning-engine 层加)
  • 3 P2 polish from correctness: Offramp pre-approval ergonomics / tradingReady semantics / decoder field projection — 后续小 PR
  • 2 P1 from fidelity: `approveUsdc` API rename / `AllowancesResult.usdcBalance` alias semantics — earning-engine 层调用方需 audit
  • 2 P2 from fidelity: V2 tests surface-only / RelayerService.approveUsdc hardcoded USDC.e (still — different method) — follow-up

风险与回滚

行为变化

  • 全 V2 SDK:所有 V1 SDK 调用站已迁移
  • `approveUsdc` 默认 token 从 USDC.e → pUSD(V2 trading collateral)— breaking for callers expecting old default
  • `AllowancesResult.usdcBalance` 从字面 USDC.e balance → pUSD balance alias — breaking for readers
  • Order timestamp 从 seconds → milliseconds (内部由 V2 SDK 自动处理)

回滚

  • 单 commit revert + submodule pointer 回退到 `7c8c7b4` (copy-trading base before V2 work)
  • 因为 poly-sdk 是 submodule, earning-engine 端只需 bump pointer

Self-review checklist

  • §A Universal: 代码 grep 无 V1 残留 / tests pass / typecheck / build / commit messages
  • §B Code-change: helper isolation / backward compat (deprecated alias 保留) / boundary tests / numerical sanity / SDK 源码引用
  • §C High-risk: 4 视角 evaluate 完成 / pre-condition checks / rollback steps / 测试覆盖

关闭 audit Q

本 PR 关闭:

  • Q1 pUSD 地址 ✅ (`0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB`)
  • Q2 Onramp 地址 ✅ (`0x93070a847efEf7F70739046A929D47a521F5B8ee` + Offramp `0x2957922eB9...`)
  • Q3 NegRisk Adapter ✅ (unchanged from V1)
  • Q4 CTF Router V2 ✅ (V2 settles direct-to-exchange, no router proxy)
  • Q5 WS USER_TRADE feeRateBps ✅ (V2 SDK 自动处理)
  • Q6 `@polymarket/clob-client-v2` published ✅ (1.0.3 on npm)
  • Q7 builder code sourcing ✅ (`POLY_BUILDER_CODE` env)
  • Q9 V2 sandbox ✅ (no testnet)
  • Q11 Onramp ABI ✅ (`wrap(address,address,uint256)`)

仍 open: Q8 (test market fee rate, Phase 6 production validation), Q10 (`feeExponent` per-market verification, Phase 6 前查 `getMarket`).

🤖 Generated with Claude Code

hhh0x and others added 4 commits May 5, 2026 12:28
## 背景

Polymarket CLOB V2 已于 2026-04-28 11:00 UTC 上线,V1 SDK 与 V1-signed
orders 在 production 已被拒绝。这是 V2 迁移 SDK 侧 3 个 PR 中的第一步
(PR-A),目标只动「基础设施层」(依赖 + 常量),不动业务逻辑。

迁移 SSOT:
  - earning-engine/.claude/skills/guide-polymarket-v2-migration/SKILL.md
  - plans/02-poly-sdk-migration.md
  - plans/12-poly-sdk-pr-templates.md §PR-A
  - audits/01-poly-sdk-audit.md

## 修改方案

### 1. 包依赖(package.json)
  - 新增 @polymarket/clob-client-v2@1.0.3 (V2 SDK)
  - 保留 @polymarket/clob-client@^5.1.3 (V1 SDK) — 见下方"范围偏离"说明
  - 保留 @polymarket/builder-signing-sdk@^0.0.8(仍用于 Relayer HMAC,
    不再用于 order 签名)
  - 更新 pnpm-lock.yaml(standalone install: pnpm install --ignore-workspace)

### 2. V2 合约常量 SSOT (src/constants/v2-contracts.ts, 新文件)
集中所有 V2 Polygon 合约地址 + 链 ID + EIP-712 域 version:

| 字段                          | V1                                           | V2                                           |
|-------------------------------|----------------------------------------------|----------------------------------------------|
| CTF Exchange                  | 0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E   | 0xE111180000d2663C0091e4f400237545B87B996B   |
| NegRisk CTF Exchange          | 0xC5d563A36AE78145C45a50134d48A1215220f80a   | 0xe2222d279d744050d28e00520010520000310F59   |
| NegRisk Adapter               | 0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296   | 不变                                          |
| ConditionalTokens (CTF)       | 0x4D97DCd97eC945f40cF65F87097ACe5EA0476045   | 不变                                          |
| Collateral (USDC.e → pUSD)    | 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174   | 0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB   |
| EIP-712 CTF Exchange version  | "1"                                          | "2"                                          |
| ClobAuthDomain version (API)  | "1"                                          | "1" (不变)                                    |

  - V1 旧地址保留为 POLYGON_CONTRACTS_V1_LEGACY(@deprecated)便于
    grep 历史代码追溯。
  - Onramp 合约地址 TBD (audit 01 P0-5),留 commented-out 坑位由 PR-C
    填补 (RelayerService.wrapUsdcToPUSD)。
  - 地址来源:clob-client-v2@1.0.3 dist getContractConfig(137) 表
    + Polygonscan 双向交叉验证。

### 3. EIP-712 域 version(trading-service.ts 不变)
  - 当前 TradingService 通过 ClobClient 委托签名,文件中没有显式的
    `version: "1"` 字面量(V1 SDK 内部 PROTOCOL_VERSION="1" 是私有
    常量)。
  - V2 SDK 已经把 `"2"` 内化为 CTF_EXCHANGE_V2_DOMAIN_VERSION(dist/index.js
    line 663),切到 V2 SDK 时自动生效。
  - 因此本 PR 在 trading-service.ts 没有可改的字面量;新模块的
    EIP_712 常量是给 PR-B(TradingService 重写)+ 未来 fixture / decoder
    使用的统一引用。

## 范围偏离说明(重要 — 给 reviewer)

原 PR-A 描述要求「移除 @polymarket/clob-client@5.x」。**本 PR 没有移除**,
原因:

  1. trading-service.ts / market-service.ts / core/order-status.ts 仍在
     `from '@polymarket/clob-client'` 导入。
  2. V2 SDK 的 ClobClient 构造签名变更(V1 positional → V2 options-bag),
     `new ClobClient(host, chainId, signer, ...)` 等 6 处调用站点会触发
     TS2554 编译错误。
  3. 任务约束「不要修改 TradingService 业务逻辑(PR-B 范围)」+
     「tsc --noEmit 必须 0 errors」存在张力——只有把 import 切到 V2 SDK
     才能"移除 V1",但切换会破坏 tsc。

实测:把 3 个 import 站点同时切到 `@polymarket/clob-client-v2` 后,
tsc 恰好报 6 个错(全是构造函数签名 / 参数数):
  - market-service.ts:215 / :218 (Expected 1 args, got 3 / 2)
  - trading-service.ts:359 / :392 / :1360 / :1380 (Expected 1 args, got 3 / 9 / 3 / 4)

这些都是 plan 02 §5.1 明确划归 PR-B (sub-workstream 2.3 TradingService
Rewrite) 的工作。本 PR 选择让两个 SDK 共存:V2 已可用、constants 已就位,
PR-B 接管 import 切换 + 构造函数适配 + Order Struct 重组。pnpm workspace
允许两个版本同存(pnpm-lock 已收录),运行时所有 require 仍走 V1,没有行
为变化。

## 数据流影响

本 PR 是纯添加:constants 模块新增 + V2 SDK 安装。运行时数据流 0 变化
(trading-service / market-service / order-status 三个文件未改)。

## 测试

  - npx tsc --noEmit → 0 errors(已验证)
  - pnpm test → 85/85 passed(5 test files: price-utils, types,
    realtime-service-v2-unsubscribe-h0, order-handle, order-manager;
    duration ~8.7s)
  - tsx smoke test (constants module) →
    POLYGON_CONTRACTS_V2.ctfExchange = "0xE11118…996B" ✅
    EIP_712.domainVersionV2 = "2" ✅
    POLYGON_CHAIN_ID = 137 ✅

## 风险 + 回滚

  - 风险:V1 SDK 仍在依赖图中,可能让 reviewer 误以为 PR-A 没完成。
    mitigated by 本 commit message 的"范围偏离说明"段落 + PR-B 计划接管
    SDK 切换的明确分工。
  - 回滚:单 commit revert,把 package.json / pnpm-lock.yaml 还原 +
    删 src/constants/v2-contracts.ts 即可。submodule 指针在 earning-engine
    端不变,影响隔离。

## Audit Q 关闭

本 PR 关闭以下 Q(plans/00-overview.md §5):
  - Q1 pUSD 地址 ✅ (0xC011a7E12…)
  - Q3 NegRisk Adapter 地址 ✅ (UNCHANGED)
  - Q6 clob-client-v2 是否发布 ✅ (npm 1.0.3)

仍 OPEN(PR-C 接管):
  - Q2 Collateral Onramp 地址 (Polygonscan ABI 待查)
  - Q4 NegRisk Adapter ABI 是否变化(地址不变)
  - Q5 V2 BuilderConfig vs builderCode 形态 (PR-B)
…C (PR-B)

Stage B 接 Stage A (89f65d3) 的 SDK 装入 + 常量 SSOT,把 ClobClient + 订单
签名路径完全切到 V2,并从 package.json 移除 V1 SDK。

## 修改方案

### 1. ClobClient 切到 V2 options-bag
6 处 `new ClobClient(...)` 全部从 V1 positional args 切换到 V2 options 对象:
- `src/services/market-service.ts` × 2 (ensureInitialized 内 auth/read-only 两路)
- `src/services/trading-service.ts` × 4 (initialize L1/L2 + standalone
  createBuilderApiKey L1/L2)

V2 改动:`chainId → chain`,所有命名参数提名(signer / creds /
signatureType / funderAddress / builderConfig),`SignatureTypeV2` 替代手写
`= 2` 常量。

### 2. Order Struct V2 字段重组
V2 SDK 的 `UserOrderV2` 接口直接消除了 V1 的 `nonce` / `feeRateBps` /
`taker`,新增 `metadata` (bytes32) 与 `builderCode` (bytes32);`timestamp`
由 SDK 内部填 `Date.now()` (ms) — TradingService 调用方只继续传 `tokenID /
side / price / size / expiration`,其余 SDK 自动填。

验证:grep `nonce:|feeRateBps:|taker:` 在 src/services/ 下无匹配。

### 3. EIP-712 域 version 验证
通过 grep V2 SDK dist:`CTF_EXCHANGE_V2_DOMAIN_VERSION = "2"` 是
hardcoded,由 `buildOrder()` 自动选择。我们不需要显式传 version。Stage A
的 `EIP_712.domainVersionV2` 仍作为 raw signing / fixture / decoder 的 SSOT
保留。`ClobAuthDomain` 仍是 "1"(API 认证不变)。

### 4. builderConfig 内容拆分
- V1 builderConfig = `{ apiKey, secret, passphrase }` (HMAC three-tuple)
- V2 builderConfig = `{ builderCode: bytes32 }` (per-order attribution)

实现:
- `TradingServiceConfig.builderCreds` (HMAC) → `TradingServiceConfig.builderCode`
- 同步更新 `OrderManagerConfig` (新增 `builderCode`,保留 `builderCreds`
  for upstream Relayer 兼容) 和 `PolySDKOptions` (同上)
- HMAC 三件套 (`@polymarket/builder-signing-sdk`) 仍在 package.json,被
  RelayerService 用于 gasless TX envelope,不变

### 5. POLY_BUILDER_CODE env 处理
新建 `src/constants/builder-config.ts`:
- `readBuilderCode()`: 严格读取 + bytes32 格式校验,缺失/非法立即抛错
- `readBuilderCodeOptional()`: 缺失返回 undefined,但格式非法仍抛错(典型陷阱
  防护:env 拼错也不能默默退化为无 builder 归属)

TradingService 在 3 个下单入口 (`createLimitOrder` /
`createMarketOrder` / `createBatchOrders`) 进入 SDK 之前调用
`ensureBuilderCode()` fail-fast,之后在 `initialize()` 把 builderCode 通过
`builderConfig.builderCode` 传入 ClobClient,让 SDK 自动嵌入到每笔订单的
`Order.builder` 字段。

### 6. 移除 V1 SDK package
- `package.json`: 移除 `@polymarket/clob-client@^5.1.3`
- `pnpm install --ignore-workspace` 重新生成 lockfile
- `node_modules/@polymarket/` 现仅含 builder-relayer-client / builder-signing-sdk
  / clob-client-v2

### 7. 测试更新
新增 2 个 unit test 文件(共 20 测试):
- `src/__tests__/unit/v2-builder-config.test.ts`:env 严格 / optional /
  malformed / SSOT 链接,13 测试
- `src/__tests__/unit/v2-trading-service-init.test.ts`:3 个下单入口在缺失
  builderCode 时正确 fail-fast,env vs config 优先级,最低值校验仍优先
  执行(不会泄漏 env 错误),7 测试

现有 85 个测试全部不受影响(mock 层用的是 MockTradingService,未触及 SDK)。

### 顺带:debug scripts 跟随 V1 移除
6 个 git-tracked 调试脚本(不在 tsconfig include 内,但运行时仍会 import)的
`@polymarket/clob-client` 替换为 `@polymarket/clob-client-v2`,并把 `new
ClobClient(host, chainId, wallet, ...)` 换成 V2 options-bag 形式。涉及:
- scripts/verify-user-events.ts
- scripts/verify-earning-engine-data.ts
- scripts/test-creds-with-address.ts
- scripts/ordermanager/{bug20-deep-analysis,bug24-tokenid-test,test-market-order-lifecycle}.ts

## 不在 stage B 范围(Stage C 处理)
- AuthorizationService V2 spenders 列表(authorization-service.ts:22
  仍有 V1 CTF_EXCHANGE 常量)
- RelayerService.wrapUsdcToPUSD()
- calldata-decoder V2 13-tuple

## 验证
- `npx tsc --noEmit`: 0 errors
- `pnpm test`: 7 files / 105 passed
- `pnpm build`: clean
- grep 检查:src/services/ 中无 V1 SDK import;pnpm-lock 无 V1 SDK

## Audit Q 关闭
- Q4: V2 SDK 内部 ClobClient 实测兼容(构造 + builderConfig + 自动 version
  decision)
- Q7: builderCode 来源 — `POLY_BUILDER_CODE` env,bytes32 格式

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…(PR-C)

PR-C 是 poly-sdk V2 迁移的最后一块拼图,把钱包侧适配上 V2:USDC.e ↔ pUSD
gasless wrap helper、AuthorizationService 的 V2 approvals 7 项矩阵、以及
copy-trading 的 matchOrders calldata 解析器 V1/V2 双轨。

参考:
- 迁移 SSOT:earning-engine/.claude/skills/guide-polymarket-v2-migration/
- 详细计划:plans/02-poly-sdk-migration.md §6-7
- audit:audits/01-poly-sdk-audit.md(P0-5 + P1-1)
- 依赖 PR:PR-A (89f65d3)、PR-B (7e09210)

## 修改方案

### 1. RelayerService.wrapUsdcToPUSD / unwrapPUSDtoUsdc

V2 trade 用 pUSD 作抵押品。新增双向 helper:
- `wrapUsdcToPUSD(amountWei: bigint)` — Safe → Onramp.wrap(USDC.e, safe, amount)
- `unwrapPUSDtoUsdc(amountWei: bigint)` — Safe → Offramp.unwrap(USDC.e, safe, amount)

ABI inspection(关闭 Audit Q2 + Q11):
- Onramp `0x93070a847efEf7F70739046A929D47a521F5B8ee` 暴露
  `wrap(address asset, address to, uint256 amount)` (selector 0x62355638)
- Offramp `0x2957922Eb93258b93368531d39fAcCA3B4dC5854` 暴露
  `unwrap(address asset, address to, uint256 amount)` (selector 0x8cc7104f)
- 两者 `COLLATERAL_TOKEN()` 都返回 pUSD proxy(验证 1:1 backing)
- 来源:docs.polymarket.com/concepts/pusd + Polygon RPC bytecode 探针

预条件(caller 负责):
- wrap 前:Safe 已 approve(Onramp, USDC.e) — 由 approveAll() 默认设置
- unwrap 前:Safe 已 approve(Offramp, pUSD) — 当前 approveAll() **不**默认设置,
  需 caller 在 collect 流程中手动 approve(off-exchange 路径)

零金额 / 负金额在 helper 内 short-circuit,不调 relayer。

### 2. AuthorizationService V2 spenders 矩阵(7 项)

| 行 | 类型  | 地址                          | 涉及 token  |
|----|-------|-------------------------------|-------------|
| 1  | ERC20 | V2 CTF Exchange (0xE111...)   | pUSD        |
| 2  | ERC20 | V2 NegRisk Exchange (0xe222...)| pUSD       |
| 3  | ERC20 | NegRisk Adapter (0xd91E...)   | pUSD        |
| 4  | ERC20 | Onramp (0x9307...)            | USDC.e      |
| 5  | 1155  | V2 CTF Exchange               | (CTF tokens)|
| 6  | 1155  | V2 NegRisk Exchange           | (CTF tokens)|
| 7  | 1155  | NegRisk Adapter               | (CTF tokens)|

关键设计:每个 ERC20 行带 `token: 'pUSD' | 'USDC.e'` 标签,让 token 路由
显式:交易抵押 → pUSD;wrap 入口 → USDC.e。同名通用方法 (`approveUsdc`)
默认 token=pUSD(V2 抵押),caller 可显式传 `'USDC.e'` 走 Onramp 路径。

V1 approvals 不做 revoke(V1 Exchange 已不接订单,不影响 V2)。

### 3. calldata-decoder.ts V1/V2 双轨

V2 settlement 直接打 V2 CTF Exchange,不再过 FeeModule router。验证:
- V1 selector: 0x2287e350 (FeeModule, 13-field tuple, has taker/nonce/feeRateBps)
- V2 selector: 0x3c2b4399 (V2 CTF Exchange, 12-field tuple)

V2 tuple = `(salt, maker, signer, tokenId, makerAmount, takerAmount,
side, signatureType, timestamp, metadata, builder, signature)`。
V1 tuple 中的 `taker / nonce / feeRateBps / expiration` 全部移除;
V2 新增 `timestamp(ms) / metadata(bytes32) / builder(bytes32)`。

V2 selector 对应签名 via 4byte + openchain.xyz:
`matchOrders(bytes32, V2Order, V2Order[], uint256, uint256[], uint256, uint256[])`

新 API:
- `decodeMatchOrdersCalldataV1(data)` — V1-only(保留供历史回放)
- `decodeMatchOrdersCalldataV2(data)` — V2-only(post-cutover)
- `decodeMatchOrdersCalldata(data)` — 自动 V2-first,selector 不匹配再回退 V1

老 API `decodeMatchOrdersCalldata` 保持兼容(`MATCH_ORDERS_SELECTOR` alias
仍指 V1 selector,避免 break smart-money copy-trading 调用方)。

### 4. v2-contracts.ts 补充 Onramp + Offramp 地址

`POLYGON_CONTRACTS_V2.collateralOnramp` 和 `.collateralOfframp` 取消 TBD
注释,填入实测地址 + ABI 注释。

## 测试

新增 3 个 test 文件,32 个 test cases:
- `v2-relayer-wrap.test.ts` (9) — mock RelayClient.execute,验证 wrap/unwrap
  调用正确合约 + 方法 + 参数;零金额边界;FAILED/INVALID 状态映射。
- `v2-authorization.test.ts` (7) — 4 ERC20 + 3 ERC1155 矩阵;token 路由正确
  (pUSD vs USDC.e);已 approved 行短路;不含 V1 地址。
- `v2-calldata-decoder.test.ts` (16) — V1/V2 fixture round-trip;selector 校验;
  跨版本 isolation(V1-only decoder 拒 V2 fixture,反之亦然);malformed body
  返回 null 不抛异常;自动 detect 优先 V2-first。

总测试数:105 baseline → 137(+32),全部通过。

## 验收

- [x] `pnpm test` 全绿(137 passed / 137)
- [x] `npx tsc --noEmit` 0 errors
- [x] `pnpm build` 成功
- [x] grep V1 `0x4bFb41d5` / `0xC5d563A36` 在 src/services/ 仅限 0 occurrences
- [x] V1 router 地址(0xE3f1.. / 0xB768..) 仅在 calldata-decoder.ts 内 @deprecated
- [x] 105 baseline tests 不破

## Audit Q 关闭

- Q2: Onramp 地址 ✅ `0x93070a847efEf7F70739046A929D47a521F5B8ee`
- Q11: Onramp ABI ✅ `wrap(address,address,uint256)`
- 新发现:Offramp 是独立合约(不是 Onramp 自身),见 v2-contracts.ts 注释。

## 不在本 PR 范围

- earning-engine 端 CLI 命令(`ee wallet wrap` / `ee wallet reapprove`)—
  那是 earning-engine 仓库的 PR
- ctf-client.ts 的 USDC_CONTRACT 切换(保留 V1 USDC.e 引用,等 earning-engine
  端决定 collect 流程是否仍用 USDC.e 后再处理)

## 回滚

回滚单 commit;earning-engine submodule 指针仍指向 PR-B (7e09210) 即可,
不影响 PR-A/PR-B 已合的功能。
… + ctf-client @deprecated

Fix three P0 findings from the 4-perspective poly-sdk V2 migration review.
All run-time correctness gaps caught silently by structural typing or V1
hardcodes that would revert against post-2026-04-28 V2 markets.

P0-1 (fidelity): PolySDKOptions.builderCreds silently dropped from
trading path. PolymarketSDK constructor now fail-fast throws when
builderCreds is supplied without a builderCode (config or
POLY_BUILDER_CODE env), with an explicit migration message. Catches the
3 earning-engine call sites still passing V1 HMAC creds for order
signing.

P0-2 (completeness P0-A): RelayerService split/merge/redeem/redeemBatch/
approveUsdc/transferUsdc hardcoded V1 USDC.e. Added optional
`token: 'pUSD' | 'USDC.e'` parameter (default 'pUSD' = V2 trading
collateral) to each. transferUsdc default also flips to pUSD; pass
'USDC.e' explicitly for fund-out collect paths.

P0-3 (completeness P0-C): ctf-client.ts CTF_EXCHANGE /
NEG_RISK_CTF_EXCHANGE were V1 addresses re-exported as canonical names
with no @deprecated marker. Renamed canonical to *_V1_DEPRECATED, kept
old names as @deprecated aliases for back-compat, added *_V2 aliases
sourced from POLYGON_CONTRACTS_V2. index.ts now re-exports all V2
contract constants from constants/v2-contracts.ts so consumers can
import the V2 SSOT directly.

Tests: 137 baseline + 17 new (4 builderCreds + 13 relayer token routing)
= 154 passing. tsc --noEmit clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cyl19970726 cyl19970726 merged commit e5f00c3 into copy-trading May 5, 2026
1 of 2 checks passed
@cyl19970726 cyl19970726 deleted the feat/v2-sdk-a-constants branch May 5, 2026 13:22
cyl19970726 added a commit that referenced this pull request May 5, 2026
## 背景

2026-04-28 V2 cutover 后,order signing 通过 bytes32 `builderCode` 做链上
attribution,HMAC `builderCreds` 三件套在 trading 路径上彻底不再被消费 —
仅 `RelayerService` 还用于 gasless TX envelope (Safe deploy / wrap / transfer)。

V1→V2 迁移 (PR #29) 时为了避免破坏 caller 同时保留了多个 dead surface:
- `OrderManagerConfig.builderCreds` (从未被 V2 OrderManager 消费)
- `PolymarketSDKConfig.builderCreds` (从未在 SDK constructor 内被 thread)
- `index.ts` 中长达 30 行的 fail-fast guard, 用来在结构性类型仍接受旧字段时
  做 V1 mental-model 的硬告警

这些是 V2 落地后的 dead weight, 增加阅读心智成本和未来 maintenance 风险。

## 修改方案

### §1 OrderManagerConfig.builderCreds — 移除
确认 dead, 仅在 type definition 上, 没有任何 runtime 消费路径。

### §2 TradingService.createBuilderApiKey() helper — 保留 AS-IS
- 函数名 mirror 上游 Polymarket SDK `l2Client.createBuilderApiKey()` (L3 endpoint)
- return field `builderCreds` 也是上游 SDK 自身的 terminology
- earning-engine `cli/wallet.ts` 与 `wallet/file-wallet-store.ts` 直接消费此 return,
  其中 `BuilderCreds` 已是持久化磁盘 schema (FileWalletStore 的 JSON 字段)
- Rename 会跨 repo 破坏 + 与 Polymarket 自己 API 命名错位 → 保守 keep

注释 (trading-service.ts:119-121) 已说明 V1/V2 distinction:
"replaces the V1 builderCreds HMAC three-tuple. HMAC creds are still
consumed by RelayerService for gasless TX envelopes; they are NOT
used by V2 order signing."

### §3 PolymarketSDKConfig.builderCreds — 移除
- 调研 confirm: SDK constructor 中 `config.builderCreds` 仅出现于 fail-fast guard,
  从未 thread 到 RelayerService / TradingService / OrderManager
- earning-engine 所有 `new PolymarketSDK()` 调用均不传 config (4 处, 全部空构造)
- earning-engine 创建 RelayerService 时直接 `new RelayerService({ builderCreds: ... })`,
  不经过 PolymarketSDK
- 因此移除是 safe 的 (无 cross-repo break), 比 rename 更诚实
  (没有任何字段需要被 rename, 因为它本来就是 non-functional)

### §4 index.ts fail-fast guard — 移除
没有 `PolymarketSDKConfig.builderCreds` 字段就没有 V1 误传场景, guard 自然失去意义。
保留 7 行注释指向 guide-polymarket-v2-migration 供将来追溯。

### §5 测试
- v2-trading-service-init.test.ts: 删除第二个 describe (4 个 fail-fast tests),
  替换为 NOTE 解释为何被删
- v2-relayer-wrap.test.ts / v2-relayer-token-routing.test.ts: 保留 `builderCreds`
  使用 — 这是 RelayerServiceConfig 的合法字段, 仍由 V2 Relayer 消费

## API 变化

破坏性 (poly-sdk public API):
- `PolymarketSDKConfig.builderCreds` 字段移除 — 迁移路径: 直接 `new RelayerService({ builderCreds })`
- `OrderManagerConfig.builderCreds` 字段移除 — 没有 caller (V2 后即 dead)

非破坏性 (保留):
- `TradingService.createBuilderApiKey()` 函数 + `BuilderKeyResult.builderCreds` return
- `RelayerServiceConfig.builderCreds` (Relayer 仍用于 gasless TX)

## 风险与回滚

风险低:
1. earning-engine 所有 `new PolymarketSDK()` 都是空 config → 不传 builderCreds, 0 break
2. earning-engine RelayerService 直接构造, 不经 PolymarketSDK → 0 break
3. earning-engine OrderManager (poly-sdk 的) 没有 caller (search 0 hits in /cli/, /trading-engine/) → 0 break

回滚: `git revert <sha>` 即可, 仅 4 个文件 + 单 commit。

注: 发现 earning-engine `trading-engine/src/impl/live-order-executor.ts:67` 向
TradingServiceConfig 传 `builderCreds` (excess property), 这是 PRE-EXISTING bug
(早于本次清理), 不在 poly-sdk 清理范围, 由 earning-engine 自己跟进。

## 测试结果

- `npx tsc --noEmit`: 0 errors
- `pnpm test`: 11 files / 150 tests passed (V1 时 154; 删除 4 个 fail-fast tests)
- `pnpm build`: clean

净行数: -93 (24 inserted, 117 deleted), 4 files changed.

Co-authored-by: hhh0x <hhhquickcreation@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant