v1 escrow API(
Market/MarketFactory)를 SDK·CLI에서 완전 제거하고, CTF stack(MockUSDC + ConditionalTokens + CTFExchange) 기반의 새 surface로 교체. 핵심 risk였던 off-chain EIP-712 reconstruction은 forge로 생성한 golden digest와 vitest로 parity 검증. anvil 위 E2E 데모도 통과.
| 작업 | 상태 |
|---|---|
@verex/sdk v1 (factory.ts / market.ts) 제거 |
✅ |
@verex/sdk CTF surface 추가 (orders, conditions, ct, exchange, usdc, clients) |
✅ |
sync-abis.mjs — CTFExchange / IConditionalTokens / MockUSDC 동기화 |
✅ |
순수 off-chain hashOrder ↔ CTFExchange.hashOrder() parity 테스트 |
✅ vitest 3/3 |
signOrder + EIP-712 signature recovery roundtrip |
✅ |
@verex/cli 재작성 — 10개 CTF 커맨드 + 데모 스크립트 |
✅ |
| anvil 위 E2E 데모 실행 (deploy → setup → sign+fill → resolve → redeem) | ✅ alice 100 USDC → 140 USDC (+40) |
| forge test 회귀 검증 | ✅ 34/34 통과 |
docs/plan/watch-list.md 신규 — Glamsterdam BAL 친화 설계 후보 항목 등록 |
✅ |
S2 milestone 진행:
@verex/api VerexClient import는 여전히 broken (별도 정리 필요)packages/sdk/
├─ scripts/sync-abis.mjs ← CONTRACTS = ["CTFExchange", "IConditionalTokens", "MockUSDC"]
├─ src/
│ ├─ index.ts ← curated re-exports
│ ├─ types.ts ← Order, Side, SignatureType, OrderDomain, ClientConfig, OrderSigner
│ ├─ orders.ts ← signOrder + hashOrder (순수 off-chain EIP-712)
│ ├─ conditions.ts ← getConditionId (encodePacked + keccak)
│ ├─ ct.ts ← splitBinaryPosition, mergeBinaryPosition, redeemPositions,
│ │ prepareCondition, reportPayouts, getBinaryPositionIds,
│ │ balanceOf1155, setApprovalForAll, payoutDenominator,
│ │ getOutcomeSlotCount, getCollectionId, getPositionId
│ ├─ exchange.ts ← fillOrder, registerToken, addOperator, cancelOrder,
│ │ hashOrderViaContract, getDomainSeparator
│ ├─ usdc.ts ← mint, approve, balanceOf, allowance
│ ├─ clients.ts ← createCTClient, createExchangeClient, createUsdcClient
│ └─ abis/ ← auto-generated, .gitignored
└─ test/
└─ orders.test.ts ← 3 vitest tests (parity + sign roundtrip + raw-pk signer)
hashTypedData 한 번 호출로 끝남(45줄 미만). 검증은 forge에서 emit한 golden digest 비교로 충분.feeRateBps를 Order 필드로 노출만. SDK 기본값은 0이지만 강제 안 함. Q-S2.3.3 (런칭 시 fee 정책)은 여전히 open — S2.5 진입 전에 결정 필요.signatureType: EOA만 처리. POLY_PROXY / POLY_GNOSIS_SAFE는 S7 AA work에서.// packages/contracts/script/EmitOrderHash.s.sol
// 결정적 Order 하나에 대해 exchange.hashOrder() 결과를 stdout에 emit.
// 출력값을 그대로 SDK test에 박아넣어 golden value 비교.
// packages/sdk/test/orders.test.ts
const EXPECTED_DIGEST = "0x68d8d9bd3897ca4fc1977b681d787f0da781b37f9f91c0b0b7a0fe7292571f93";
const EXCHANGE_ADDRESS = "0xf13D09eD3cbdD1C930d4de74808de1f33B6b3D4f";
expect(hashOrder(ORDER, DOMAIN)).toBe(EXPECTED_DIGEST); // ✅
이 한 줄이 SDK의 도메인 reconstruction(name: "Polymarket CTF Exchange", version: "1", chainId, verifyingContract) + Order 타입 인코딩이 OpenZeppelin EIP712 mixin 결과와 byte-for-byte 일치함을 증명. Order 스키마 / 도메인 / 타입 순서 중 어느 하나만 어긋나도 즉시 fail.
추가 검증: signOrder → recoverTypedDataAddress → maker 주소 일치 (cryptographic side도 통과).
Golden digest 재생성:
cd packages/contracts
forge script script/EmitOrderHash.s.sol
# 출력의 chainId / exchange address / digest를 test/orders.test.ts에 반영
verex condition --oracle <addr> [--question <bytes32>] [--slots <n>]
verex balance --oracle <addr> [--question <bytes32>] [--account <i>]
verex setup [--question <bytes32>] [--mint <units>] [--account <i>]
verex resolve --yes <n> --no <n> [--question <bytes32>] [--account <i>]
verex split --condition <bytes32> --amount <units> [--account <i>]
verex merge --condition <bytes32> --amount <units> [--account <i>]
verex redeem --condition <bytes32> [--side yes|no|both] [--account <i>]
verex mint --amount <units> [--to <addr>] [--account <i>]
verex order sign --token <id> --maker-amount <n> --taker-amount <n> [--side buy|sell] [--out <file>]
verex order fill --order <file> --amount <units> [--account <i>]
모든 커맨드는 USDC_ADDR / CTF_ADDR / EXCHANGE_ADDR 환경변수로 주소를 가져오고, --usdc / --ctf / --exchange 플래그로 override 가능 — DemoMarket.s.sol convention과 호환.
demo.ts — anvil에서 단일 명령으로 deploy → setup → BUY 서명/체결 → resolve → redeem 전체 lifecycle 실행. Polymarket의 manual oracle flow(Stage 1)와 SDK signature path 모두 한 번에 exercise.
# (1) SDK 빌드 + 타입체크
pnpm --filter @verex/sdk build # tsc clean, dist/ regenerate
# (2) SDK 단위 테스트 (vitest)
pnpm --filter @verex/sdk test # 3 tests pass
# hashOrder matches golden digest
# signOrder roundtrip recovers maker address
# signOrder accepts raw private-key signer
# (3) CLI 빌드
pnpm --filter @verex/cli build # tsc clean
# (4) 컨트랙트 회귀
cd packages/contracts && forge test # 34/34 pass (변경 없음)
# 별도 터미널
anvil
# CLI 데모
pnpm --filter @verex/cli build
pnpm --filter @verex/cli demo
기대 출력 (요지):
[1] deploying CTF backbone via forge script...
USDC 0x5FbD...
CTF 0xe7f1...
Exchange 0x9fE4...
[2] preparing condition...
conditionId=0x235274e4...
YES id=52923191629941...
NO id=19941161922911...
[3] alice signing BUY order: 60 USDC -> 100 YES (price=$0.60/YES)
on-chain digest: 0x8c44237e...
[4] operator filling alice's BUY order (full 60 USDC)...
alice now holds: 100000000 YES + 40000000 USDC
[5] reporting YES wins (manual oracle, Stage 1)...
[6] alice redeems (YES only — cheapest path)...
alice final USDC: 140000000 ← +40 USDC 수익
✓ CTF end-to-end demo complete
수학 검증: alice 100 USDC 시작 → BUY로 60 지출(40 잔여) → YES 100개 → 해결 후 redeem 100 USDC → 최종 140 USDC.
pnpm --filter @verex/sdk test — vitest 3/3 통과하는지verex --help — 새 커맨드 surface가 의도대로 노출되는지 (CLI bin은 packages/cli/dist/index.js)verex order sign --out tmp.json ... → verex order fill --order tmp.json --amount ... — 두 단계 분리해서 호출해도 SDK가 JSON 직렬화·역직렬화(bigint → string round-trip) 잘 처리하는지verex balance — setup 후 인벤토리(operator의 YES/NO)가 표시되는지verex condition — 같은 oracle / questionId / slotCount 입력에 대해 forge script DemoMarket 출력 conditionId와 일치하는지 (off-chain 도출이 on-chain과 일치)verex split → verex merge 라운드트립 — 같은 양이 USDC로 정확히 되돌아오는지 (가스 제외)verex split → verex resolve → verex redeem --side yes — 깔끔한 winner-only redemption 흐름@verex/api의 broken VerexClient import 정리 — S2.4 이전부터 깨져있던 stub. 한 줄짜리 placeholder로 교체하거나, API를 CTF 기반으로 부분 마이그레이션 시작.matchOrders model 선호 (Q-S2.3.2 추천). 새 패키지 packages/mm-agent 생성부터.feeRateBps 런칭 시 0인지 nonzero인지. SDK는 양쪽 다 지원하지만 protocol-wide 정책은 미정.getCollectionId / getPositionId 순수 off-chain 구현 — 현재는 CT 컨트랙트 호출로 우회 (Gnosis CTHelpers EC arithmetic 때문). MM Agent에서 hot path가 되면 BN254 EC 라이브러리 도입 검토.docs/plan/watch-list.md — Glamsterdam BAL 친화 설계 항목 등록 (트리거 발생 시 결정).packages/sdk/src/orders.ts (3분) — EIP-712 도메인·타입 정의가 한 파일에 모여 있어 가장 빠른 entrypackages/sdk/test/orders.test.ts (3분) — golden digest 검증 패턴packages/cli/src/demo.ts (5분) — SDK 전 surface가 어떻게 조합되는지 한눈에packages/contracts/script/EmitOrderHash.s.sol (2분) — golden 재생성 절차 이해docs/plan/watch-list.md — 미래 트리거 인박스 구조신규
docs/history/2026-05-27-s2.4-sdk-cli-migration.md (이 doc)docs/plan/watch-list.mdpackages/contracts/script/EmitOrderHash.s.solpackages/sdk/src/conditions.tspackages/sdk/src/orders.tspackages/sdk/src/ct.tspackages/sdk/src/exchange.tspackages/sdk/src/usdc.tspackages/sdk/src/clients.tspackages/sdk/test/orders.test.ts수정
packages/sdk/src/index.ts — 새 surface re-exportpackages/sdk/src/types.ts — v1 타입 제거, Order / Side / SignatureType / OrderDomain / OrderSigner 추가packages/sdk/scripts/sync-abis.mjs — CONTRACTS 리스트 교체packages/sdk/package.json — vitest devDep + test scriptpackages/cli/src/index.ts — v1 커맨드 → CTF 커맨드 10종packages/cli/src/demo.ts — CTF E2E 데모로 재작성pnpm-lock.yaml — vitest 의존성 반영삭제
packages/sdk/src/factory.ts (v1 MarketFactory client)packages/sdk/src/market.ts (v1 Market client)packages/sdk/src/abis/Market.ts (auto-gen, no longer in sync list)packages/sdk/src/abis/MarketFactory.ts (auto-gen, no longer in sync list)