@catalyst-team/poly-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/.env +0 -0
  2. package/README.md +803 -0
  3. package/dist/__tests__/clob-api.test.d.ts +5 -0
  4. package/dist/__tests__/clob-api.test.d.ts.map +1 -0
  5. package/dist/__tests__/clob-api.test.js +240 -0
  6. package/dist/__tests__/clob-api.test.js.map +1 -0
  7. package/dist/__tests__/integration/bridge-client.integration.test.d.ts +11 -0
  8. package/dist/__tests__/integration/bridge-client.integration.test.d.ts.map +1 -0
  9. package/dist/__tests__/integration/bridge-client.integration.test.js +260 -0
  10. package/dist/__tests__/integration/bridge-client.integration.test.js.map +1 -0
  11. package/dist/__tests__/integration/clob-api.integration.test.d.ts +13 -0
  12. package/dist/__tests__/integration/clob-api.integration.test.d.ts.map +1 -0
  13. package/dist/__tests__/integration/clob-api.integration.test.js +170 -0
  14. package/dist/__tests__/integration/clob-api.integration.test.js.map +1 -0
  15. package/dist/__tests__/integration/ctf-client.integration.test.d.ts +17 -0
  16. package/dist/__tests__/integration/ctf-client.integration.test.d.ts.map +1 -0
  17. package/dist/__tests__/integration/ctf-client.integration.test.js +234 -0
  18. package/dist/__tests__/integration/ctf-client.integration.test.js.map +1 -0
  19. package/dist/__tests__/integration/data-api.integration.test.d.ts +9 -0
  20. package/dist/__tests__/integration/data-api.integration.test.d.ts.map +1 -0
  21. package/dist/__tests__/integration/data-api.integration.test.js +161 -0
  22. package/dist/__tests__/integration/data-api.integration.test.js.map +1 -0
  23. package/dist/__tests__/integration/gamma-api.integration.test.d.ts +9 -0
  24. package/dist/__tests__/integration/gamma-api.integration.test.d.ts.map +1 -0
  25. package/dist/__tests__/integration/gamma-api.integration.test.js +170 -0
  26. package/dist/__tests__/integration/gamma-api.integration.test.js.map +1 -0
  27. package/dist/__tests__/test-utils.d.ts +92 -0
  28. package/dist/__tests__/test-utils.d.ts.map +1 -0
  29. package/dist/__tests__/test-utils.js +143 -0
  30. package/dist/__tests__/test-utils.js.map +1 -0
  31. package/dist/clients/bridge-client.d.ts +388 -0
  32. package/dist/clients/bridge-client.d.ts.map +1 -0
  33. package/dist/clients/bridge-client.js +587 -0
  34. package/dist/clients/bridge-client.js.map +1 -0
  35. package/dist/clients/clob-api.d.ts +318 -0
  36. package/dist/clients/clob-api.d.ts.map +1 -0
  37. package/dist/clients/clob-api.js +388 -0
  38. package/dist/clients/clob-api.js.map +1 -0
  39. package/dist/clients/ctf-client.d.ts +473 -0
  40. package/dist/clients/ctf-client.d.ts.map +1 -0
  41. package/dist/clients/ctf-client.js +915 -0
  42. package/dist/clients/ctf-client.js.map +1 -0
  43. package/dist/clients/data-api.d.ts +134 -0
  44. package/dist/clients/data-api.d.ts.map +1 -0
  45. package/dist/clients/data-api.js +265 -0
  46. package/dist/clients/data-api.js.map +1 -0
  47. package/dist/clients/gamma-api.d.ts +401 -0
  48. package/dist/clients/gamma-api.d.ts.map +1 -0
  49. package/dist/clients/gamma-api.js +352 -0
  50. package/dist/clients/gamma-api.js.map +1 -0
  51. package/dist/clients/trading-client.d.ts +252 -0
  52. package/dist/clients/trading-client.d.ts.map +1 -0
  53. package/dist/clients/trading-client.js +543 -0
  54. package/dist/clients/trading-client.js.map +1 -0
  55. package/dist/clients/websocket-manager.d.ts +100 -0
  56. package/dist/clients/websocket-manager.d.ts.map +1 -0
  57. package/dist/clients/websocket-manager.js +193 -0
  58. package/dist/clients/websocket-manager.js.map +1 -0
  59. package/dist/core/cache-adapter-bridge.d.ts +36 -0
  60. package/dist/core/cache-adapter-bridge.d.ts.map +1 -0
  61. package/dist/core/cache-adapter-bridge.js +81 -0
  62. package/dist/core/cache-adapter-bridge.js.map +1 -0
  63. package/dist/core/cache.d.ts +40 -0
  64. package/dist/core/cache.d.ts.map +1 -0
  65. package/dist/core/cache.js +71 -0
  66. package/dist/core/cache.js.map +1 -0
  67. package/dist/core/errors.d.ts +38 -0
  68. package/dist/core/errors.d.ts.map +1 -0
  69. package/dist/core/errors.js +84 -0
  70. package/dist/core/errors.js.map +1 -0
  71. package/dist/core/rate-limiter.d.ts +31 -0
  72. package/dist/core/rate-limiter.d.ts.map +1 -0
  73. package/dist/core/rate-limiter.js +70 -0
  74. package/dist/core/rate-limiter.js.map +1 -0
  75. package/dist/core/types.d.ts +314 -0
  76. package/dist/core/types.d.ts.map +1 -0
  77. package/dist/core/types.js +19 -0
  78. package/dist/core/types.js.map +1 -0
  79. package/dist/core/unified-cache.d.ts +63 -0
  80. package/dist/core/unified-cache.d.ts.map +1 -0
  81. package/dist/core/unified-cache.js +114 -0
  82. package/dist/core/unified-cache.js.map +1 -0
  83. package/dist/index.d.ts +94 -0
  84. package/dist/index.d.ts.map +1 -0
  85. package/dist/index.js +258 -0
  86. package/dist/index.js.map +1 -0
  87. package/dist/mcp/errors.d.ts +33 -0
  88. package/dist/mcp/errors.d.ts.map +1 -0
  89. package/dist/mcp/errors.js +86 -0
  90. package/dist/mcp/errors.js.map +1 -0
  91. package/dist/mcp/index.d.ts +62 -0
  92. package/dist/mcp/index.d.ts.map +1 -0
  93. package/dist/mcp/index.js +173 -0
  94. package/dist/mcp/index.js.map +1 -0
  95. package/dist/mcp/server.d.ts +17 -0
  96. package/dist/mcp/server.d.ts.map +1 -0
  97. package/dist/mcp/server.js +155 -0
  98. package/dist/mcp/server.js.map +1 -0
  99. package/dist/mcp/tools/guide.d.ts +12 -0
  100. package/dist/mcp/tools/guide.d.ts.map +1 -0
  101. package/dist/mcp/tools/guide.js +801 -0
  102. package/dist/mcp/tools/guide.js.map +1 -0
  103. package/dist/mcp/tools/index.d.ts +11 -0
  104. package/dist/mcp/tools/index.d.ts.map +1 -0
  105. package/dist/mcp/tools/index.js +27 -0
  106. package/dist/mcp/tools/index.js.map +1 -0
  107. package/dist/mcp/tools/market.d.ts +11 -0
  108. package/dist/mcp/tools/market.d.ts.map +1 -0
  109. package/dist/mcp/tools/market.js +314 -0
  110. package/dist/mcp/tools/market.js.map +1 -0
  111. package/dist/mcp/tools/order.d.ts +10 -0
  112. package/dist/mcp/tools/order.d.ts.map +1 -0
  113. package/dist/mcp/tools/order.js +258 -0
  114. package/dist/mcp/tools/order.js.map +1 -0
  115. package/dist/mcp/tools/trade.d.ts +38 -0
  116. package/dist/mcp/tools/trade.d.ts.map +1 -0
  117. package/dist/mcp/tools/trade.js +314 -0
  118. package/dist/mcp/tools/trade.js.map +1 -0
  119. package/dist/mcp/tools/trader.d.ts +11 -0
  120. package/dist/mcp/tools/trader.d.ts.map +1 -0
  121. package/dist/mcp/tools/trader.js +277 -0
  122. package/dist/mcp/tools/trader.js.map +1 -0
  123. package/dist/mcp/tools/wallet.d.ts +274 -0
  124. package/dist/mcp/tools/wallet.d.ts.map +1 -0
  125. package/dist/mcp/tools/wallet.js +579 -0
  126. package/dist/mcp/tools/wallet.js.map +1 -0
  127. package/dist/mcp/types.d.ts +413 -0
  128. package/dist/mcp/types.d.ts.map +1 -0
  129. package/dist/mcp/types.js +5 -0
  130. package/dist/mcp/types.js.map +1 -0
  131. package/dist/services/authorization-service.d.ts +97 -0
  132. package/dist/services/authorization-service.d.ts.map +1 -0
  133. package/dist/services/authorization-service.js +279 -0
  134. package/dist/services/authorization-service.js.map +1 -0
  135. package/dist/services/market-service.d.ts +108 -0
  136. package/dist/services/market-service.d.ts.map +1 -0
  137. package/dist/services/market-service.js +458 -0
  138. package/dist/services/market-service.js.map +1 -0
  139. package/dist/services/realtime-service.d.ts +82 -0
  140. package/dist/services/realtime-service.d.ts.map +1 -0
  141. package/dist/services/realtime-service.js +150 -0
  142. package/dist/services/realtime-service.js.map +1 -0
  143. package/dist/services/swap-service.d.ts +217 -0
  144. package/dist/services/swap-service.d.ts.map +1 -0
  145. package/dist/services/swap-service.js +695 -0
  146. package/dist/services/swap-service.js.map +1 -0
  147. package/dist/services/wallet-service.d.ts +94 -0
  148. package/dist/services/wallet-service.d.ts.map +1 -0
  149. package/dist/services/wallet-service.js +173 -0
  150. package/dist/services/wallet-service.js.map +1 -0
  151. package/dist/utils/price-utils.d.ts +153 -0
  152. package/dist/utils/price-utils.d.ts.map +1 -0
  153. package/dist/utils/price-utils.js +236 -0
  154. package/dist/utils/price-utils.js.map +1 -0
  155. package/docs/00-design.md +760 -0
  156. package/docs/01-mcp.md +2041 -0
  157. package/docs/02-API.md +1148 -0
  158. package/docs/e2e/01-trader-tools.md +159 -0
  159. package/docs/e2e/02-market-tools.md +180 -0
  160. package/docs/e2e/03-order-tools.md +166 -0
  161. package/docs/e2e/04-wallet-tools.md +224 -0
  162. package/docs/e2e/05-trading-tools.md +327 -0
  163. package/docs/e2e/06-integration-scenarios.md +481 -0
  164. package/docs/e2e/coordinator.md +376 -0
  165. package/examples/01-basic-usage.ts +68 -0
  166. package/examples/02-smart-money.ts +95 -0
  167. package/examples/03-market-analysis.ts +108 -0
  168. package/examples/04-kline-aggregation.ts +158 -0
  169. package/examples/05-follow-wallet-strategy.ts +156 -0
  170. package/examples/06-services-demo.ts +124 -0
  171. package/examples/07-realtime-websocket.ts +117 -0
  172. package/examples/08-trading-orders.ts +278 -0
  173. package/examples/09-rewards-tracking.ts +187 -0
  174. package/examples/10-ctf-operations.ts +336 -0
  175. package/examples/11-live-arbitrage-scan.ts +221 -0
  176. package/examples/12-trending-arb-monitor.ts +406 -0
  177. package/examples/README.md +179 -0
  178. package/package.json +62 -0
  179. package/scripts/README.md +163 -0
  180. package/scripts/approvals/approve-erc1155.ts +129 -0
  181. package/scripts/approvals/approve-neg-risk-erc1155.ts +149 -0
  182. package/scripts/approvals/approve-neg-risk.ts +102 -0
  183. package/scripts/approvals/check-all-allowances.ts +150 -0
  184. package/scripts/approvals/check-allowance.ts +129 -0
  185. package/scripts/approvals/check-ctf-approval.ts +158 -0
  186. package/scripts/datas/001-report.md +486 -0
  187. package/scripts/datas/clone-modal-screenshot.png +0 -0
  188. package/scripts/deposit/deposit-native-usdc.ts +179 -0
  189. package/scripts/deposit/deposit-usdc.ts +155 -0
  190. package/scripts/deposit/swap-usdc-to-usdce.ts +375 -0
  191. package/scripts/research/research-markets.ts +166 -0
  192. package/scripts/trading/check-orders.ts +50 -0
  193. package/scripts/trading/sell-nvidia-positions.ts +206 -0
  194. package/scripts/trading/test-order.ts +172 -0
  195. package/scripts/truth.md +440 -0
  196. package/scripts/verify/test-approve-trading.ts +98 -0
  197. package/scripts/verify/test-provider-fix.ts +43 -0
  198. package/scripts/verify/test-search-mcp.ts +113 -0
  199. package/scripts/verify/verify-all-apis.ts +160 -0
  200. package/scripts/wallet/check-wallet-balances.ts +75 -0
  201. package/scripts/wallet/test-wallet-operations.ts +191 -0
  202. package/scripts/wallet/verify-wallet-tools.ts +124 -0
  203. package/src/__tests__/clob-api.test.ts +301 -0
  204. package/src/__tests__/integration/bridge-client.integration.test.ts +314 -0
  205. package/src/__tests__/integration/clob-api.integration.test.ts +218 -0
  206. package/src/__tests__/integration/ctf-client.integration.test.ts +331 -0
  207. package/src/__tests__/integration/data-api.integration.test.ts +194 -0
  208. package/src/__tests__/integration/gamma-api.integration.test.ts +206 -0
  209. package/src/__tests__/test-utils.ts +170 -0
  210. package/src/clients/bridge-client.ts +841 -0
  211. package/src/clients/clob-api.ts +629 -0
  212. package/src/clients/ctf-client.ts +1216 -0
  213. package/src/clients/data-api.ts +469 -0
  214. package/src/clients/gamma-api.ts +597 -0
  215. package/src/clients/trading-client.ts +749 -0
  216. package/src/clients/websocket-manager.ts +267 -0
  217. package/src/core/cache-adapter-bridge.ts +94 -0
  218. package/src/core/cache.ts +85 -0
  219. package/src/core/errors.ts +117 -0
  220. package/src/core/rate-limiter.ts +74 -0
  221. package/src/core/types.ts +360 -0
  222. package/src/core/unified-cache.ts +153 -0
  223. package/src/index.ts +455 -0
  224. package/src/mcp/README.md +380 -0
  225. package/src/mcp/errors.ts +124 -0
  226. package/src/mcp/index.ts +309 -0
  227. package/src/mcp/server.ts +183 -0
  228. package/src/mcp/tools/guide.ts +821 -0
  229. package/src/mcp/tools/index.ts +73 -0
  230. package/src/mcp/tools/market.ts +363 -0
  231. package/src/mcp/tools/order.ts +326 -0
  232. package/src/mcp/tools/trade.ts +417 -0
  233. package/src/mcp/tools/trader.ts +322 -0
  234. package/src/mcp/tools/wallet.ts +683 -0
  235. package/src/mcp/types.ts +472 -0
  236. package/src/services/authorization-service.ts +357 -0
  237. package/src/services/market-service.ts +544 -0
  238. package/src/services/realtime-service.ts +196 -0
  239. package/src/services/swap-service.ts +896 -0
  240. package/src/services/wallet-service.ts +259 -0
  241. package/src/utils/price-utils.ts +307 -0
  242. package/tsconfig.json +8 -0
  243. package/vitest.config.ts +19 -0
  244. package/vitest.integration.config.ts +18 -0
@@ -0,0 +1,629 @@
1
+ /**
2
+ * CLOB API Client for Polymarket
3
+ *
4
+ * The Central Limit Order Book (CLOB) API provides access to Polymarket's
5
+ * trading infrastructure. This client handles market information retrieval
6
+ * and orderbook data.
7
+ *
8
+ * @remarks
9
+ * - Base URL: https://clob.polymarket.com
10
+ * - Rate limits are automatically handled by the RateLimiter
11
+ * - Market data is cached to reduce API calls
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * import { ClobApiClient, RateLimiter, Cache } from '@prediction-router/poly-sdk';
16
+ *
17
+ * const client = new ClobApiClient(new RateLimiter(), new Cache());
18
+ *
19
+ * // Get market info
20
+ * const market = await client.getMarket('0x123...');
21
+ * console.log(market.question);
22
+ *
23
+ * // Get orderbook
24
+ * const orderbook = await client.getOrderbook(market.tokens[0].tokenId);
25
+ * console.log('Best bid:', orderbook.bids[0]);
26
+ * ```
27
+ *
28
+ * @see {@link https://docs.polymarket.com/#clob-api CLOB API Documentation}
29
+ *
30
+ * @module clients/clob-api
31
+ */
32
+
33
+ import { RateLimiter, ApiType } from '../core/rate-limiter.js';
34
+ import type { UnifiedCache } from '../core/unified-cache.js';
35
+ import { CACHE_TTL } from '../core/unified-cache.js';
36
+ import { PolymarketError, ErrorCode } from '../core/errors.js';
37
+ import type { ProcessedOrderbook } from '../core/types.js';
38
+
39
+ /** CLOB API base URL */
40
+ const CLOB_API_BASE = 'https://clob.polymarket.com';
41
+
42
+ // ===== Types =====
43
+
44
+ /**
45
+ * Market information from the CLOB API
46
+ *
47
+ * @remarks
48
+ * A market represents a binary prediction market with YES/NO outcomes.
49
+ * Each outcome has its own ERC-1155 token that can be traded.
50
+ */
51
+ export interface ClobMarket {
52
+ // Core identifiers
53
+ conditionId: string;
54
+ questionId?: string;
55
+ marketSlug: string;
56
+
57
+ // Market content
58
+ question: string;
59
+ description?: string;
60
+ image?: string;
61
+ icon?: string;
62
+
63
+ // Tokens (YES/NO outcomes)
64
+ tokens: ClobToken[];
65
+ tags?: string[];
66
+
67
+ // Status flags
68
+ active: boolean;
69
+ closed: boolean;
70
+ archived?: boolean;
71
+ acceptingOrders: boolean;
72
+ acceptingOrderTimestamp?: string;
73
+ enableOrderBook?: boolean;
74
+
75
+ // Trading parameters
76
+ minimumOrderSize?: number;
77
+ minimumTickSize?: number;
78
+ makerBaseFee?: number;
79
+ takerBaseFee?: number;
80
+
81
+ // Timing
82
+ endDateIso?: string | null;
83
+ gameStartTime?: string | null;
84
+ secondsDelay?: number;
85
+
86
+ // Neg risk (multi-outcome markets)
87
+ negRisk?: boolean;
88
+ negRiskMarketId?: string;
89
+ negRiskRequestId?: string;
90
+
91
+ // Rewards program
92
+ rewards?: {
93
+ rates?: unknown;
94
+ minSize?: number;
95
+ maxSpread?: number;
96
+ };
97
+
98
+ // Other
99
+ fpmm?: string;
100
+ notificationsEnabled?: boolean;
101
+ is5050Outcome?: boolean;
102
+ }
103
+
104
+ /**
105
+ * Token information for a market outcome
106
+ *
107
+ * @remarks
108
+ * Each outcome (YES/NO) has its own ERC-1155 token with a unique ID.
109
+ * The tokenId is used for trading and querying orderbooks.
110
+ */
111
+ export interface ClobToken {
112
+ /**
113
+ * ERC-1155 token ID for this outcome
114
+ * @example "21742633143463906290569050155826241533067272736897614950488156847949938836455"
115
+ */
116
+ tokenId: string;
117
+
118
+ /**
119
+ * Outcome name (typically "Yes" or "No")
120
+ */
121
+ outcome: string;
122
+
123
+ /**
124
+ * Current mid-market price (0-1)
125
+ * @example 0.65 for 65% probability
126
+ */
127
+ price: number;
128
+
129
+ /**
130
+ * Whether this token is the winning outcome (after resolution)
131
+ */
132
+ winner?: boolean;
133
+ }
134
+
135
+ /**
136
+ * Single price level in an orderbook
137
+ *
138
+ * @remarks
139
+ * Represents one row in the order book with a price and total size at that price.
140
+ */
141
+ export interface OrderbookLevel {
142
+ /**
143
+ * Price level (0.001 to 0.999)
144
+ * @example 0.55
145
+ */
146
+ price: number;
147
+
148
+ /**
149
+ * Total size available at this price (in shares)
150
+ * @example 1500.5
151
+ */
152
+ size: number;
153
+ }
154
+
155
+ /**
156
+ * Complete orderbook for a token
157
+ *
158
+ * @remarks
159
+ * The orderbook contains all open orders for a specific outcome token.
160
+ * - Bids are sorted descending (highest bid first)
161
+ * - Asks are sorted ascending (lowest ask first)
162
+ */
163
+ export interface Orderbook {
164
+ /**
165
+ * Buy orders, sorted by price descending (best bid first)
166
+ */
167
+ bids: OrderbookLevel[];
168
+
169
+ /**
170
+ * Sell orders, sorted by price ascending (best ask first)
171
+ */
172
+ asks: OrderbookLevel[];
173
+
174
+ /**
175
+ * Timestamp when the orderbook was fetched (Unix ms)
176
+ */
177
+ timestamp: number;
178
+
179
+ // Additional fields from API
180
+ market?: string; // conditionId
181
+ assetId?: string; // tokenId
182
+ hash?: string; // orderbook hash
183
+ minOrderSize?: string;
184
+ tickSize?: string;
185
+ negRisk?: boolean;
186
+ }
187
+
188
+ // ===== Client =====
189
+
190
+ /**
191
+ * CLOB API client for interacting with Polymarket's orderbook
192
+ *
193
+ * @remarks
194
+ * This client provides read-only access to market data and orderbooks.
195
+ * For trading operations, use {@link TradingClient} instead.
196
+ *
197
+ * @example
198
+ * ```typescript
199
+ * const client = new ClobApiClient(rateLimiter, cache);
200
+ *
201
+ * // Get market details
202
+ * const market = await client.getMarket('0x123...');
203
+ *
204
+ * // Get processed orderbook with analytics
205
+ * const processed = await client.getProcessedOrderbook('0x123...');
206
+ * console.log('Long arb profit:', processed.summary.longArbProfit);
207
+ * ```
208
+ */
209
+ export class ClobApiClient {
210
+ /**
211
+ * Creates a new CLOB API client
212
+ *
213
+ * @param rateLimiter - Rate limiter instance for API throttling
214
+ * @param cache - Cache instance for storing market data (supports both legacy Cache and CacheAdapter)
215
+ * @param config - Optional configuration for trading capabilities
216
+ * @param config.chainId - Polygon chain ID (137 for mainnet, 80002 for Amoy testnet)
217
+ * @param config.signer - Ethers signer for authenticated requests
218
+ * @param config.creds - API credentials for L2 authentication
219
+ */
220
+ constructor(
221
+ private rateLimiter: RateLimiter,
222
+ private cache: UnifiedCache,
223
+ private config?: {
224
+ /** Polygon chain ID (137 = mainnet, 80002 = Amoy testnet) */
225
+ chainId?: number;
226
+ /** Ethers signer for authenticated requests */
227
+ signer?: unknown;
228
+ /** API credentials for L2 authentication */
229
+ creds?: {
230
+ key: string;
231
+ secret: string;
232
+ passphrase: string;
233
+ };
234
+ }
235
+ ) {}
236
+
237
+ /**
238
+ * Get the signer if configured
239
+ * @returns The signer or undefined if not configured
240
+ */
241
+ get signer(): unknown {
242
+ return this.config?.signer;
243
+ }
244
+
245
+ // ===== Market Info =====
246
+
247
+ /**
248
+ * Get market information by condition ID
249
+ *
250
+ * @param conditionId - The unique condition identifier for the market
251
+ * @returns Market information including tokens and status
252
+ *
253
+ * @throws {@link PolymarketError} If the market is not found or API fails
254
+ *
255
+ * @example
256
+ * ```typescript
257
+ * const market = await client.getMarket('0x82ace55...');
258
+ * console.log(market.question); // "Will BTC reach $100k?"
259
+ * console.log(market.tokens[0].tokenId); // YES token ID
260
+ * console.log(market.tokens[1].tokenId); // NO token ID
261
+ * ```
262
+ */
263
+ async getMarket(conditionId: string): Promise<ClobMarket> {
264
+ const cacheKey = `clob:market:${conditionId}`;
265
+ return this.cache.getOrSet(cacheKey, CACHE_TTL.MARKET_INFO, async () => {
266
+ return this.rateLimiter.execute(ApiType.CLOB_API, async () => {
267
+ const response = await fetch(`${CLOB_API_BASE}/markets/${conditionId}`);
268
+ if (!response.ok)
269
+ throw PolymarketError.fromHttpError(
270
+ response.status,
271
+ await response.json().catch(() => null)
272
+ );
273
+ const data = (await response.json()) as Record<string, unknown>;
274
+ return this.normalizeMarket(data);
275
+ });
276
+ });
277
+ }
278
+
279
+ // ===== Orderbook =====
280
+
281
+ /**
282
+ * Get raw orderbook for a specific token
283
+ *
284
+ * @param tokenId - The ERC-1155 token ID (either YES or NO token)
285
+ * @returns Orderbook with sorted bids and asks
286
+ *
287
+ * @remarks
288
+ * - Bids are sorted descending (highest bid first)
289
+ * - Asks are sorted ascending (lowest ask first)
290
+ * - This returns the raw orderbook for ONE outcome token
291
+ * - For complete market analysis, use {@link getProcessedOrderbook}
292
+ *
293
+ * @throws {@link PolymarketError} If the token is not found or API fails
294
+ *
295
+ * @example
296
+ * ```typescript
297
+ * const orderbook = await client.getOrderbook('21742633...');
298
+ *
299
+ * console.log('Best bid:', orderbook.bids[0]?.price); // e.g., 0.55
300
+ * console.log('Best ask:', orderbook.asks[0]?.price); // e.g., 0.57
301
+ * console.log('Spread:', orderbook.asks[0]?.price - orderbook.bids[0]?.price);
302
+ * ```
303
+ */
304
+ async getOrderbook(tokenId: string): Promise<Orderbook> {
305
+ return this.rateLimiter.execute(ApiType.CLOB_API, async () => {
306
+ const response = await fetch(`${CLOB_API_BASE}/book?token_id=${tokenId}`);
307
+ if (!response.ok)
308
+ throw PolymarketError.fromHttpError(
309
+ response.status,
310
+ await response.json().catch(() => null)
311
+ );
312
+ const data = (await response.json()) as {
313
+ market?: string;
314
+ asset_id?: string;
315
+ timestamp?: string;
316
+ hash?: string;
317
+ bids?: Array<{ price: string; size: string }>;
318
+ asks?: Array<{ price: string; size: string }>;
319
+ min_order_size?: string;
320
+ tick_size?: string;
321
+ neg_risk?: boolean;
322
+ };
323
+ // Sort bids descending (highest bid first)
324
+ // Sort asks ascending (lowest ask first)
325
+ const bids = (data.bids || [])
326
+ .map((l) => ({
327
+ price: Number(l.price),
328
+ size: Number(l.size),
329
+ }))
330
+ .sort((a, b) => b.price - a.price);
331
+
332
+ const asks = (data.asks || [])
333
+ .map((l) => ({
334
+ price: Number(l.price),
335
+ size: Number(l.size),
336
+ }))
337
+ .sort((a, b) => a.price - b.price);
338
+
339
+ return {
340
+ bids,
341
+ asks,
342
+ timestamp: data.timestamp ? Number(data.timestamp) : Date.now(),
343
+ market: data.market,
344
+ assetId: data.asset_id,
345
+ hash: data.hash,
346
+ minOrderSize: data.min_order_size,
347
+ tickSize: data.tick_size,
348
+ negRisk: data.neg_risk,
349
+ };
350
+ });
351
+ }
352
+
353
+ /**
354
+ * Get processed orderbook with complete market analysis
355
+ *
356
+ * @param conditionId - The unique condition identifier for the market
357
+ * @returns Processed orderbook with both YES/NO books and arbitrage analysis
358
+ *
359
+ * @remarks
360
+ * This method fetches both YES and NO orderbooks and calculates:
361
+ * - Effective prices (accounting for order book mirroring)
362
+ * - Arbitrage opportunities (long and short)
363
+ * - Depth and liquidity metrics
364
+ *
365
+ * **Important**: Polymarket orderbooks have a mirroring property:
366
+ * - Buying YES @ P = Selling NO @ (1-P)
367
+ * - The same order appears in both books
368
+ *
369
+ * Therefore, correct arbitrage calculation must use "effective prices":
370
+ * - effectiveBuyYes = min(YES.ask, 1 - NO.bid)
371
+ * - effectiveBuyNo = min(NO.ask, 1 - YES.bid)
372
+ *
373
+ * @throws {@link PolymarketError} If market not found or missing tokens
374
+ *
375
+ * @example
376
+ * ```typescript
377
+ * const processed = await client.getProcessedOrderbook('0x82ace55...');
378
+ *
379
+ * // Check for arbitrage
380
+ * if (processed.summary.longArbProfit > 0.003) {
381
+ * console.log('Long arb opportunity!');
382
+ * console.log('Buy YES @', processed.summary.effectivePrices.effectiveBuyYes);
383
+ * console.log('Buy NO @', processed.summary.effectivePrices.effectiveBuyNo);
384
+ * console.log('Profit:', processed.summary.longArbProfit * 100, '%');
385
+ * }
386
+ * ```
387
+ */
388
+ async getProcessedOrderbook(conditionId: string): Promise<ProcessedOrderbook> {
389
+ const market = await this.getMarket(conditionId);
390
+ const yesToken = market.tokens.find((t) => t.outcome === 'Yes');
391
+ const noToken = market.tokens.find((t) => t.outcome === 'No');
392
+
393
+ if (!yesToken || !noToken) {
394
+ throw new PolymarketError(
395
+ ErrorCode.INVALID_RESPONSE,
396
+ 'Missing tokens in market'
397
+ );
398
+ }
399
+
400
+ const [yesBook, noBook] = await Promise.all([
401
+ this.getOrderbook(yesToken.tokenId),
402
+ this.getOrderbook(noToken.tokenId),
403
+ ]);
404
+
405
+ return this.processOrderbooks(yesBook, noBook, yesToken.tokenId, noToken.tokenId);
406
+ }
407
+
408
+ /**
409
+ * Process orderbooks and calculate analytics
410
+ *
411
+ * 关键概念:Polymarket 订单簿的镜像特性
412
+ *
413
+ * 买 YES @ P = 卖 NO @ (1-P)
414
+ * 因此同一订单会在 YES 和 NO 订单簿中同时出现
415
+ *
416
+ * 正确的套利计算必须使用"有效价格":
417
+ * - effectiveBuyYes = min(YES.ask, 1 - NO.bid)
418
+ * - effectiveBuyNo = min(NO.ask, 1 - YES.bid)
419
+ * - effectiveSellYes = max(YES.bid, 1 - NO.ask)
420
+ * - effectiveSellNo = max(NO.bid, 1 - YES.ask)
421
+ *
422
+ * 详细文档见: docs/01-polymarket-orderbook-arbitrage.md
423
+ */
424
+ private processOrderbooks(
425
+ yesBook: Orderbook,
426
+ noBook: Orderbook,
427
+ yesTokenId?: string,
428
+ noTokenId?: string
429
+ ): ProcessedOrderbook {
430
+ const yesBestBid = yesBook.bids[0]?.price || 0;
431
+ const yesBestAsk = yesBook.asks[0]?.price || 1;
432
+ const noBestBid = noBook.bids[0]?.price || 0;
433
+ const noBestAsk = noBook.asks[0]?.price || 1;
434
+
435
+ const yesBidDepth = yesBook.bids.reduce(
436
+ (sum, l) => sum + l.price * l.size,
437
+ 0
438
+ );
439
+ const yesAskDepth = yesBook.asks.reduce(
440
+ (sum, l) => sum + l.price * l.size,
441
+ 0
442
+ );
443
+ const noBidDepth = noBook.bids.reduce(
444
+ (sum, l) => sum + l.price * l.size,
445
+ 0
446
+ );
447
+ const noAskDepth = noBook.asks.reduce(
448
+ (sum, l) => sum + l.price * l.size,
449
+ 0
450
+ );
451
+
452
+ // 原始价格和(仅供参考,可能包含重复计算)
453
+ const askSum = yesBestAsk + noBestAsk;
454
+ const bidSum = yesBestBid + noBestBid;
455
+
456
+ // ===== 计算有效价格(考虑镜像订单)=====
457
+ // 这是正确的套利计算方式
458
+ const effectivePrices = {
459
+ // 买 YES: 直接买 YES.ask 或 通过卖 NO (成本 = 1 - NO.bid)
460
+ effectiveBuyYes: Math.min(yesBestAsk, 1 - noBestBid),
461
+
462
+ // 买 NO: 直接买 NO.ask 或 通过卖 YES (成本 = 1 - YES.bid)
463
+ effectiveBuyNo: Math.min(noBestAsk, 1 - yesBestBid),
464
+
465
+ // 卖 YES: 直接卖 YES.bid 或 通过买 NO (收入 = 1 - NO.ask)
466
+ effectiveSellYes: Math.max(yesBestBid, 1 - noBestAsk),
467
+
468
+ // 卖 NO: 直接卖 NO.bid 或 通过买 YES (收入 = 1 - YES.ask)
469
+ effectiveSellNo: Math.max(noBestBid, 1 - yesBestAsk),
470
+ };
471
+
472
+ // 有效套利成本/收入
473
+ const effectiveLongCost = effectivePrices.effectiveBuyYes + effectivePrices.effectiveBuyNo;
474
+ const effectiveShortRevenue = effectivePrices.effectiveSellYes + effectivePrices.effectiveSellNo;
475
+
476
+ // 套利利润(基于有效价格)
477
+ const longArbProfit = 1 - effectiveLongCost;
478
+ const shortArbProfit = effectiveShortRevenue - 1;
479
+
480
+ // YES spread(由于镜像,这也能反映整体市场效率)
481
+ const yesSpread = yesBestAsk - yesBestBid;
482
+
483
+ return {
484
+ yes: {
485
+ bid: yesBestBid,
486
+ ask: yesBestAsk,
487
+ bidSize: yesBook.bids[0]?.size || 0,
488
+ askSize: yesBook.asks[0]?.size || 0,
489
+ bidDepth: yesBidDepth,
490
+ askDepth: yesAskDepth,
491
+ spread: yesSpread,
492
+ tokenId: yesTokenId,
493
+ },
494
+ no: {
495
+ bid: noBestBid,
496
+ ask: noBestAsk,
497
+ bidSize: noBook.bids[0]?.size || 0,
498
+ askSize: noBook.asks[0]?.size || 0,
499
+ bidDepth: noBidDepth,
500
+ askDepth: noAskDepth,
501
+ spread: noBestAsk - noBestBid,
502
+ tokenId: noTokenId,
503
+ },
504
+ summary: {
505
+ // 原始价格和(仅供参考)
506
+ askSum,
507
+ bidSum,
508
+
509
+ // 有效价格
510
+ effectivePrices,
511
+
512
+ // 有效成本/收入
513
+ effectiveLongCost,
514
+ effectiveShortRevenue,
515
+
516
+ // 套利利润(基于有效价格,这才是正确的计算)
517
+ longArbProfit, // > 0 means long arbitrage opportunity
518
+ shortArbProfit, // > 0 means short arbitrage opportunity
519
+
520
+ // 其他指标
521
+ totalBidDepth: yesBidDepth + noBidDepth,
522
+ totalAskDepth: yesAskDepth + noAskDepth,
523
+ imbalanceRatio:
524
+ (yesBidDepth + noBidDepth) / (yesAskDepth + noAskDepth + 0.001),
525
+ yesSpread,
526
+ },
527
+ };
528
+ }
529
+
530
+ // ===== Trading (requires authentication) =====
531
+
532
+ /**
533
+ * Check if this client has trading capabilities
534
+ *
535
+ * @returns True if a signer or API credentials are configured
536
+ *
537
+ * @remarks
538
+ * Trading requires either:
539
+ * - A signer (for L1 authentication)
540
+ * - API credentials (for L2 authentication)
541
+ *
542
+ * For actual trading, use the {@link TradingClient} instead.
543
+ *
544
+ * @example
545
+ * ```typescript
546
+ * if (client.hasTradingCapabilities()) {
547
+ * console.log('Client can execute trades');
548
+ * } else {
549
+ * console.log('Read-only mode - use TradingClient for trading');
550
+ * }
551
+ * ```
552
+ */
553
+ hasTradingCapabilities(): boolean {
554
+ return !!(this.config?.signer || this.config?.creds);
555
+ }
556
+
557
+ // ===== Data Normalization =====
558
+
559
+ private normalizeMarket(m: Record<string, unknown>): ClobMarket {
560
+ const tokens = m.tokens as Array<{
561
+ token_id: string;
562
+ outcome: string;
563
+ price: string | number;
564
+ winner?: boolean;
565
+ }>;
566
+
567
+ const rewards = m.rewards as Record<string, unknown> | undefined;
568
+
569
+ return {
570
+ // Core identifiers
571
+ conditionId: String(m.condition_id || ''),
572
+ questionId: m.question_id ? String(m.question_id) : undefined,
573
+ marketSlug: String(m.market_slug || ''),
574
+
575
+ // Market content
576
+ question: String(m.question || ''),
577
+ description: m.description ? String(m.description) : undefined,
578
+ image: m.image ? String(m.image) : undefined,
579
+ icon: m.icon ? String(m.icon) : undefined,
580
+
581
+ // Tokens
582
+ tokens: Array.isArray(tokens)
583
+ ? tokens.map((t) => ({
584
+ tokenId: String(t.token_id || ''),
585
+ outcome: String(t.outcome || ''),
586
+ price: Number(t.price),
587
+ winner: t.winner !== undefined ? Boolean(t.winner) : undefined,
588
+ }))
589
+ : [],
590
+ tags: Array.isArray(m.tags) ? (m.tags as string[]) : undefined,
591
+
592
+ // Status flags
593
+ active: Boolean(m.active),
594
+ closed: Boolean(m.closed),
595
+ archived: m.archived !== undefined ? Boolean(m.archived) : undefined,
596
+ acceptingOrders: Boolean(m.accepting_orders),
597
+ acceptingOrderTimestamp: m.accepting_order_timestamp ? String(m.accepting_order_timestamp) : undefined,
598
+ enableOrderBook: m.enable_order_book !== undefined ? Boolean(m.enable_order_book) : undefined,
599
+
600
+ // Trading parameters
601
+ minimumOrderSize: m.minimum_order_size !== undefined ? Number(m.minimum_order_size) : undefined,
602
+ minimumTickSize: m.minimum_tick_size !== undefined ? Number(m.minimum_tick_size) : undefined,
603
+ makerBaseFee: m.maker_base_fee !== undefined ? Number(m.maker_base_fee) : undefined,
604
+ takerBaseFee: m.taker_base_fee !== undefined ? Number(m.taker_base_fee) : undefined,
605
+
606
+ // Timing
607
+ endDateIso: m.end_date_iso !== undefined ? (m.end_date_iso === null ? null : String(m.end_date_iso)) : undefined,
608
+ gameStartTime: m.game_start_time !== undefined ? (m.game_start_time === null ? null : String(m.game_start_time)) : undefined,
609
+ secondsDelay: m.seconds_delay !== undefined ? Number(m.seconds_delay) : undefined,
610
+
611
+ // Neg risk
612
+ negRisk: m.neg_risk !== undefined ? Boolean(m.neg_risk) : undefined,
613
+ negRiskMarketId: m.neg_risk_market_id ? String(m.neg_risk_market_id) : undefined,
614
+ negRiskRequestId: m.neg_risk_request_id ? String(m.neg_risk_request_id) : undefined,
615
+
616
+ // Rewards
617
+ rewards: rewards ? {
618
+ rates: rewards.rates,
619
+ minSize: rewards.min_size !== undefined ? Number(rewards.min_size) : undefined,
620
+ maxSpread: rewards.max_spread !== undefined ? Number(rewards.max_spread) : undefined,
621
+ } : undefined,
622
+
623
+ // Other
624
+ fpmm: m.fpmm ? String(m.fpmm) : undefined,
625
+ notificationsEnabled: m.notifications_enabled !== undefined ? Boolean(m.notifications_enabled) : undefined,
626
+ is5050Outcome: m.is_50_50_outcome !== undefined ? Boolean(m.is_50_50_outcome) : undefined,
627
+ };
628
+ }
629
+ }