S2.4 — SDK + CLI migration to CTF stack (2026-05-27)
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 데모도 통과.
1. 결과 요약
| 작업 | 상태 |
|---|---|
@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 진행:
- ✅ CTF mint → split → merge → redeem cycle (S2.1)
- ✅ CTF order fill end-to-end (Foundry-level) (S2.3)
- ✅ SDK + CLI CTF migration (오늘) (S2.4)
- ⏳ MM v0 maintains two-sided quotes (S2.5 — 미착수)
- ⏳
@verex/apiVerexClientimport는 여전히 broken (별도 정리 필요)
2. SDK 신규 구조
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)
2.1 핵심 설계 결정 (사후 정리)
- Q-S2.3.1 (hashOrder 구현 방식) — 순수 off-chain 채택. 구현이 viem의
hashTypedData한 번 호출로 끝남(45줄 미만). 검증은 forge에서 emit한 golden digest 비교로 충분. - API shape — flat helpers를 primary, thin clients를 secondary로. 한 줄짜리 CT operation은 flat helper 직접 호출, 같은 주소가 여러 호출에 반복되는 경우(CLI, MM agent)는 client builder 사용. v1의 client-only 패턴 폐기.
- v1 cleanup — outright delete. git history에 보존되므로 deprecated 마킹 대신 완전 제거. 대신 history doc에서 "삭제됨" 명시.
- fee handling — 호출자가 결정하도록
feeRateBps를Order필드로 노출만. SDK 기본값은 0이지만 강제 안 함. Q-S2.3.3 (런칭 시 fee 정책)은 여전히 open — S2.5 진입 전에 결정 필요. - ERC-1271 / smart-account 서명 — Stage 1에서 미지원.
signatureType: EOA만 처리. POLY_PROXY / POLY_GNOSIS_SAFE는 S7 AA work에서.
2.2 EIP-712 parity 검증 (S2.4의 핵심 risk 해소)
// 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에 반영
3. CLI 신규 커맨드
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.
4. 검증 방법 (How to verify)
4.1 SDK + 컨트랙트 회귀
# (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 (변경 없음)
4.2 anvil 위 E2E 데모
# 별도 터미널
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.
4.3 사용자가 직접 확인하면 좋을 항목
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 흐름
5. 명시적으로 deferred한 것 (다음 슬라이스)
@verex/api의 brokenVerexClientimport 정리 — S2.4 이전부터 깨져있던 stub. 한 줄짜리 placeholder로 교체하거나, API를 CTF 기반으로 부분 마이그레이션 시작.- MM Agent v0 (S2.5) — paper-trading minimum maker.
matchOrdersmodel 선호 (Q-S2.3.2 추천). 새 패키지packages/mm-agent생성부터. - fee 정책 결정 (Q-S2.3.3) —
feeRateBps런칭 시 0인지 nonzero인지. SDK는 양쪽 다 지원하지만 protocol-wide 정책은 미정. - Smart-account signing (POLY_PROXY / POLY_GNOSIS_SAFE) — S7 AA work.
getCollectionId/getPositionId순수 off-chain 구현 — 현재는 CT 컨트랙트 호출로 우회 (Gnosis CTHelpers EC arithmetic 때문). MM Agent에서 hot path가 되면 BN254 EC 라이브러리 도입 검토.docs/plan/watch-list.md— Glamsterdam BAL 친화 설계 항목 등록 (트리거 발생 시 결정).
6. Open Questions (이전 슬라이스에서 이월)
- Q-S2.3.3 (fee 정책) — 미해결. S2.5 진입 전 필요.
- Q-S2.3.4 (operator multisig) — 미해결. S6에서 다룰 예정.
7. 다음 세션 시작 시 권장 reading order
- 이 doc (5분) — 무엇을 했고 무엇이 남았는지
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— 미래 트리거 인박스 구조
8. 변경 파일
신규
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)