@catalyst-team/poly-sdk 0.1.0 → 0.2.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 (201) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +538 -0
  3. package/README.md +357 -78
  4. package/docs/00-design.md +1 -1
  5. package/docs/02-API.md +21 -21
  6. package/docs/arb/test-plan.md +387 -0
  7. package/docs/arb/test-results.md +336 -0
  8. package/docs/arbitrage.md +754 -0
  9. package/docs/reports/smart-money-analysis-2025-12-23-cn.md +840 -0
  10. package/examples/13-arbitrage-service.ts +211 -0
  11. package/examples/README.md +1 -1
  12. package/package.json +19 -19
  13. package/scripts/arb/faze-bo3-arb.ts +385 -0
  14. package/scripts/arb/settle-position.ts +190 -0
  15. package/scripts/arb/token-rebalancer.ts +420 -0
  16. package/scripts/arb-tests/01-unit-tests.ts +495 -0
  17. package/scripts/arb-tests/02-integration-tests.ts +412 -0
  18. package/scripts/arb-tests/03-e2e-tests.ts +503 -0
  19. package/scripts/arb-tests/README.md +109 -0
  20. package/scripts/verify/verify-all-apis.ts +1 -1
  21. package/src/clients/clob-api.ts +1 -1
  22. package/src/clients/gamma-api.ts +1 -1
  23. package/src/core/cache-adapter-bridge.ts +1 -1
  24. package/src/core/types.ts +3 -3
  25. package/src/core/unified-cache.ts +1 -1
  26. package/src/index.ts +25 -19
  27. package/src/services/arbitrage-service.ts +1807 -0
  28. package/.env +0 -0
  29. package/dist/__tests__/clob-api.test.d.ts +0 -5
  30. package/dist/__tests__/clob-api.test.d.ts.map +0 -1
  31. package/dist/__tests__/clob-api.test.js +0 -240
  32. package/dist/__tests__/clob-api.test.js.map +0 -1
  33. package/dist/__tests__/integration/bridge-client.integration.test.d.ts +0 -11
  34. package/dist/__tests__/integration/bridge-client.integration.test.d.ts.map +0 -1
  35. package/dist/__tests__/integration/bridge-client.integration.test.js +0 -260
  36. package/dist/__tests__/integration/bridge-client.integration.test.js.map +0 -1
  37. package/dist/__tests__/integration/clob-api.integration.test.d.ts +0 -13
  38. package/dist/__tests__/integration/clob-api.integration.test.d.ts.map +0 -1
  39. package/dist/__tests__/integration/clob-api.integration.test.js +0 -170
  40. package/dist/__tests__/integration/clob-api.integration.test.js.map +0 -1
  41. package/dist/__tests__/integration/ctf-client.integration.test.d.ts +0 -17
  42. package/dist/__tests__/integration/ctf-client.integration.test.d.ts.map +0 -1
  43. package/dist/__tests__/integration/ctf-client.integration.test.js +0 -234
  44. package/dist/__tests__/integration/ctf-client.integration.test.js.map +0 -1
  45. package/dist/__tests__/integration/data-api.integration.test.d.ts +0 -9
  46. package/dist/__tests__/integration/data-api.integration.test.d.ts.map +0 -1
  47. package/dist/__tests__/integration/data-api.integration.test.js +0 -161
  48. package/dist/__tests__/integration/data-api.integration.test.js.map +0 -1
  49. package/dist/__tests__/integration/gamma-api.integration.test.d.ts +0 -9
  50. package/dist/__tests__/integration/gamma-api.integration.test.d.ts.map +0 -1
  51. package/dist/__tests__/integration/gamma-api.integration.test.js +0 -170
  52. package/dist/__tests__/integration/gamma-api.integration.test.js.map +0 -1
  53. package/dist/__tests__/test-utils.d.ts +0 -92
  54. package/dist/__tests__/test-utils.d.ts.map +0 -1
  55. package/dist/__tests__/test-utils.js +0 -143
  56. package/dist/__tests__/test-utils.js.map +0 -1
  57. package/dist/clients/bridge-client.d.ts +0 -388
  58. package/dist/clients/bridge-client.d.ts.map +0 -1
  59. package/dist/clients/bridge-client.js +0 -587
  60. package/dist/clients/bridge-client.js.map +0 -1
  61. package/dist/clients/clob-api.d.ts +0 -318
  62. package/dist/clients/clob-api.d.ts.map +0 -1
  63. package/dist/clients/clob-api.js +0 -388
  64. package/dist/clients/clob-api.js.map +0 -1
  65. package/dist/clients/ctf-client.d.ts +0 -473
  66. package/dist/clients/ctf-client.d.ts.map +0 -1
  67. package/dist/clients/ctf-client.js +0 -915
  68. package/dist/clients/ctf-client.js.map +0 -1
  69. package/dist/clients/data-api.d.ts +0 -134
  70. package/dist/clients/data-api.d.ts.map +0 -1
  71. package/dist/clients/data-api.js +0 -265
  72. package/dist/clients/data-api.js.map +0 -1
  73. package/dist/clients/gamma-api.d.ts +0 -401
  74. package/dist/clients/gamma-api.d.ts.map +0 -1
  75. package/dist/clients/gamma-api.js +0 -352
  76. package/dist/clients/gamma-api.js.map +0 -1
  77. package/dist/clients/trading-client.d.ts +0 -252
  78. package/dist/clients/trading-client.d.ts.map +0 -1
  79. package/dist/clients/trading-client.js +0 -543
  80. package/dist/clients/trading-client.js.map +0 -1
  81. package/dist/clients/websocket-manager.d.ts +0 -100
  82. package/dist/clients/websocket-manager.d.ts.map +0 -1
  83. package/dist/clients/websocket-manager.js +0 -193
  84. package/dist/clients/websocket-manager.js.map +0 -1
  85. package/dist/core/cache-adapter-bridge.d.ts +0 -36
  86. package/dist/core/cache-adapter-bridge.d.ts.map +0 -1
  87. package/dist/core/cache-adapter-bridge.js +0 -81
  88. package/dist/core/cache-adapter-bridge.js.map +0 -1
  89. package/dist/core/cache.d.ts +0 -40
  90. package/dist/core/cache.d.ts.map +0 -1
  91. package/dist/core/cache.js +0 -71
  92. package/dist/core/cache.js.map +0 -1
  93. package/dist/core/errors.d.ts +0 -38
  94. package/dist/core/errors.d.ts.map +0 -1
  95. package/dist/core/errors.js +0 -84
  96. package/dist/core/errors.js.map +0 -1
  97. package/dist/core/rate-limiter.d.ts +0 -31
  98. package/dist/core/rate-limiter.d.ts.map +0 -1
  99. package/dist/core/rate-limiter.js +0 -70
  100. package/dist/core/rate-limiter.js.map +0 -1
  101. package/dist/core/types.d.ts +0 -314
  102. package/dist/core/types.d.ts.map +0 -1
  103. package/dist/core/types.js +0 -19
  104. package/dist/core/types.js.map +0 -1
  105. package/dist/core/unified-cache.d.ts +0 -63
  106. package/dist/core/unified-cache.d.ts.map +0 -1
  107. package/dist/core/unified-cache.js +0 -114
  108. package/dist/core/unified-cache.js.map +0 -1
  109. package/dist/index.d.ts +0 -94
  110. package/dist/index.d.ts.map +0 -1
  111. package/dist/index.js +0 -258
  112. package/dist/index.js.map +0 -1
  113. package/dist/mcp/errors.d.ts +0 -33
  114. package/dist/mcp/errors.d.ts.map +0 -1
  115. package/dist/mcp/errors.js +0 -86
  116. package/dist/mcp/errors.js.map +0 -1
  117. package/dist/mcp/index.d.ts +0 -62
  118. package/dist/mcp/index.d.ts.map +0 -1
  119. package/dist/mcp/index.js +0 -173
  120. package/dist/mcp/index.js.map +0 -1
  121. package/dist/mcp/server.d.ts +0 -17
  122. package/dist/mcp/server.d.ts.map +0 -1
  123. package/dist/mcp/server.js +0 -155
  124. package/dist/mcp/server.js.map +0 -1
  125. package/dist/mcp/tools/guide.d.ts +0 -12
  126. package/dist/mcp/tools/guide.d.ts.map +0 -1
  127. package/dist/mcp/tools/guide.js +0 -801
  128. package/dist/mcp/tools/guide.js.map +0 -1
  129. package/dist/mcp/tools/index.d.ts +0 -11
  130. package/dist/mcp/tools/index.d.ts.map +0 -1
  131. package/dist/mcp/tools/index.js +0 -27
  132. package/dist/mcp/tools/index.js.map +0 -1
  133. package/dist/mcp/tools/market.d.ts +0 -11
  134. package/dist/mcp/tools/market.d.ts.map +0 -1
  135. package/dist/mcp/tools/market.js +0 -314
  136. package/dist/mcp/tools/market.js.map +0 -1
  137. package/dist/mcp/tools/order.d.ts +0 -10
  138. package/dist/mcp/tools/order.d.ts.map +0 -1
  139. package/dist/mcp/tools/order.js +0 -258
  140. package/dist/mcp/tools/order.js.map +0 -1
  141. package/dist/mcp/tools/trade.d.ts +0 -38
  142. package/dist/mcp/tools/trade.d.ts.map +0 -1
  143. package/dist/mcp/tools/trade.js +0 -314
  144. package/dist/mcp/tools/trade.js.map +0 -1
  145. package/dist/mcp/tools/trader.d.ts +0 -11
  146. package/dist/mcp/tools/trader.d.ts.map +0 -1
  147. package/dist/mcp/tools/trader.js +0 -277
  148. package/dist/mcp/tools/trader.js.map +0 -1
  149. package/dist/mcp/tools/wallet.d.ts +0 -274
  150. package/dist/mcp/tools/wallet.d.ts.map +0 -1
  151. package/dist/mcp/tools/wallet.js +0 -579
  152. package/dist/mcp/tools/wallet.js.map +0 -1
  153. package/dist/mcp/types.d.ts +0 -413
  154. package/dist/mcp/types.d.ts.map +0 -1
  155. package/dist/mcp/types.js +0 -5
  156. package/dist/mcp/types.js.map +0 -1
  157. package/dist/services/authorization-service.d.ts +0 -97
  158. package/dist/services/authorization-service.d.ts.map +0 -1
  159. package/dist/services/authorization-service.js +0 -279
  160. package/dist/services/authorization-service.js.map +0 -1
  161. package/dist/services/market-service.d.ts +0 -108
  162. package/dist/services/market-service.d.ts.map +0 -1
  163. package/dist/services/market-service.js +0 -458
  164. package/dist/services/market-service.js.map +0 -1
  165. package/dist/services/realtime-service.d.ts +0 -82
  166. package/dist/services/realtime-service.d.ts.map +0 -1
  167. package/dist/services/realtime-service.js +0 -150
  168. package/dist/services/realtime-service.js.map +0 -1
  169. package/dist/services/swap-service.d.ts +0 -217
  170. package/dist/services/swap-service.d.ts.map +0 -1
  171. package/dist/services/swap-service.js +0 -695
  172. package/dist/services/swap-service.js.map +0 -1
  173. package/dist/services/wallet-service.d.ts +0 -94
  174. package/dist/services/wallet-service.d.ts.map +0 -1
  175. package/dist/services/wallet-service.js +0 -173
  176. package/dist/services/wallet-service.js.map +0 -1
  177. package/dist/utils/price-utils.d.ts +0 -153
  178. package/dist/utils/price-utils.d.ts.map +0 -1
  179. package/dist/utils/price-utils.js +0 -236
  180. package/dist/utils/price-utils.js.map +0 -1
  181. package/docs/01-mcp.md +0 -2041
  182. package/docs/e2e/01-trader-tools.md +0 -159
  183. package/docs/e2e/02-market-tools.md +0 -180
  184. package/docs/e2e/03-order-tools.md +0 -166
  185. package/docs/e2e/04-wallet-tools.md +0 -224
  186. package/docs/e2e/05-trading-tools.md +0 -327
  187. package/docs/e2e/06-integration-scenarios.md +0 -481
  188. package/docs/e2e/coordinator.md +0 -376
  189. package/scripts/truth.md +0 -440
  190. package/src/mcp/README.md +0 -380
  191. package/src/mcp/errors.ts +0 -124
  192. package/src/mcp/index.ts +0 -309
  193. package/src/mcp/server.ts +0 -183
  194. package/src/mcp/tools/guide.ts +0 -821
  195. package/src/mcp/tools/index.ts +0 -73
  196. package/src/mcp/tools/market.ts +0 -363
  197. package/src/mcp/tools/order.ts +0 -326
  198. package/src/mcp/tools/trade.ts +0 -417
  199. package/src/mcp/tools/trader.ts +0 -322
  200. package/src/mcp/tools/wallet.ts +0 -683
  201. package/src/mcp/types.ts +0 -472
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * Example 13: ArbitrageService - Complete Workflow
4
+ *
5
+ * Demonstrates the full arbitrage workflow:
6
+ * 1. Scan markets for opportunities
7
+ * 2. Start real-time monitoring
8
+ * 3. Auto-execute arbitrage
9
+ * 4. Stop and clear positions
10
+ *
11
+ * Environment variables:
12
+ * POLY_PRIVKEY - Private key for trading (optional for scan-only mode)
13
+ *
14
+ * Run with:
15
+ * pnpm example:arb-service
16
+ *
17
+ * Or scan-only (no trading):
18
+ * npx tsx examples/13-arbitrage-service.ts --scan-only
19
+ */
20
+
21
+ import { ArbitrageService } from '../src/index.js';
22
+
23
+ // Parse arguments
24
+ const args = process.argv.slice(2);
25
+ const SCAN_ONLY = args.includes('--scan-only');
26
+ const RUN_DURATION = parseInt(args.find(a => a.startsWith('--duration='))?.split('=')[1] || '60') * 1000; // default 60s
27
+
28
+ async function main() {
29
+ console.log('╔════════════════════════════════════════════════════════════════╗');
30
+ console.log('ā•‘ ArbitrageService - Complete Workflow ā•‘');
31
+ console.log('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•');
32
+ console.log();
33
+
34
+ const privateKey = process.env.POLY_PRIVKEY;
35
+
36
+ if (!privateKey && !SCAN_ONLY) {
37
+ console.log('No POLY_PRIVKEY provided. Running in scan-only mode.\n');
38
+ }
39
+
40
+ // ========== Initialize ArbitrageService ==========
41
+ const arbService = new ArbitrageService({
42
+ privateKey: SCAN_ONLY ? undefined : privateKey,
43
+ profitThreshold: 0.005, // 0.5% minimum profit
44
+ minTradeSize: 5, // $5 minimum
45
+ maxTradeSize: 100, // $100 maximum
46
+ autoExecute: !SCAN_ONLY && !!privateKey,
47
+ enableLogging: true,
48
+
49
+ // Rebalancer config
50
+ enableRebalancer: !SCAN_ONLY && !!privateKey,
51
+ minUsdcRatio: 0.2,
52
+ maxUsdcRatio: 0.8,
53
+ targetUsdcRatio: 0.5,
54
+ imbalanceThreshold: 5,
55
+ rebalanceInterval: 10000,
56
+ rebalanceCooldown: 30000,
57
+
58
+ // Execution safety
59
+ sizeSafetyFactor: 0.8,
60
+ autoFixImbalance: true,
61
+ });
62
+
63
+ // ========== Set up event listeners ==========
64
+ arbService.on('opportunity', (opp) => {
65
+ console.log(`\nšŸŽÆ ${opp.type.toUpperCase()} ARB: ${opp.profitPercent.toFixed(2)}%`);
66
+ console.log(` ${opp.description}`);
67
+ console.log(` Recommended size: ${opp.recommendedSize.toFixed(2)}, Est profit: $${opp.estimatedProfit.toFixed(2)}`);
68
+ });
69
+
70
+ arbService.on('execution', (result) => {
71
+ if (result.success) {
72
+ console.log(`\nāœ… Execution succeeded!`);
73
+ console.log(` Type: ${result.type}, Size: ${result.size.toFixed(2)}`);
74
+ console.log(` Profit: $${result.profit.toFixed(2)}`);
75
+ console.log(` Time: ${result.executionTimeMs}ms`);
76
+ } else {
77
+ console.log(`\nāŒ Execution failed: ${result.error}`);
78
+ }
79
+ });
80
+
81
+ arbService.on('rebalance', (result) => {
82
+ if (result.success) {
83
+ console.log(`\nšŸ”„ Rebalance: ${result.action.type} ${result.action.amount.toFixed(2)}`);
84
+ console.log(` Reason: ${result.action.reason}`);
85
+ } else {
86
+ console.log(`\nāš ļø Rebalance failed: ${result.error}`);
87
+ }
88
+ });
89
+
90
+ arbService.on('balanceUpdate', (balance) => {
91
+ console.log(`\nšŸ’° Balance: USDC=${balance.usdc.toFixed(2)}, YES=${balance.yesTokens.toFixed(2)}, NO=${balance.noTokens.toFixed(2)}`);
92
+ });
93
+
94
+ arbService.on('error', (error) => {
95
+ console.error(`\n🚨 Error: ${error.message}`);
96
+ });
97
+
98
+ // ========== Step 1: Scan Markets ==========
99
+ console.log('═══════════════════════════════════════════════════════════════');
100
+ console.log('Step 1: Scanning markets for arbitrage opportunities...');
101
+ console.log('═══════════════════════════════════════════════════════════════\n');
102
+
103
+ const scanResults = await arbService.scanMarkets(
104
+ { minVolume24h: 5000, limit: 50 },
105
+ 0.003 // 0.3% min profit for scanning
106
+ );
107
+
108
+ const opportunities = scanResults.filter(r => r.arbType !== 'none');
109
+
110
+ console.log(`\nFound ${opportunities.length} opportunities out of ${scanResults.length} markets scanned\n`);
111
+
112
+ if (opportunities.length > 0) {
113
+ console.log('Top 5 opportunities:');
114
+ console.log('ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”');
115
+ for (const r of opportunities.slice(0, 5)) {
116
+ console.log(`│ ${r.market.name.slice(0, 50).padEnd(50)} │`);
117
+ console.log(`│ ${r.arbType.toUpperCase()} +${r.profitPercent.toFixed(2)}% Size: ${r.availableSize.toFixed(0)} Vol: $${r.volume24h.toLocaleString().padEnd(10)} │`);
118
+ console.log(`ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤`);
119
+ }
120
+ console.log('ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜');
121
+ }
122
+
123
+ if (SCAN_ONLY || opportunities.length === 0) {
124
+ console.log('\nāœ… Scan complete.');
125
+ if (SCAN_ONLY) {
126
+ console.log(' (Running in scan-only mode, not starting arbitrage)');
127
+ }
128
+ if (opportunities.length === 0) {
129
+ console.log(' (No profitable opportunities found)');
130
+ }
131
+ return;
132
+ }
133
+
134
+ // ========== Step 2: Start Arbitrage ==========
135
+ console.log('\n═══════════════════════════════════════════════════════════════');
136
+ console.log('Step 2: Starting arbitrage on best market...');
137
+ console.log('═══════════════════════════════════════════════════════════════\n');
138
+
139
+ const best = opportunities[0];
140
+ console.log(`Selected: ${best.market.name}`);
141
+ console.log(`Type: ${best.arbType.toUpperCase()}, Profit: +${best.profitPercent.toFixed(2)}%\n`);
142
+
143
+ await arbService.start(best.market);
144
+
145
+ // ========== Step 3: Run for duration ==========
146
+ console.log('\n═══════════════════════════════════════════════════════════════');
147
+ console.log(`Step 3: Running for ${RUN_DURATION / 1000} seconds...`);
148
+ console.log('═══════════════════════════════════════════════════════════════\n');
149
+ console.log('Monitoring for arbitrage opportunities...');
150
+ console.log('(Press Ctrl+C to stop early)\n');
151
+
152
+ // Handle graceful shutdown
153
+ let stopped = false;
154
+ const shutdown = async () => {
155
+ if (stopped) return;
156
+ stopped = true;
157
+ console.log('\n\nShutting down...');
158
+ await cleanup();
159
+ process.exit(0);
160
+ };
161
+
162
+ process.on('SIGINT', shutdown);
163
+ process.on('SIGTERM', shutdown);
164
+
165
+ // Wait for duration
166
+ await new Promise(resolve => setTimeout(resolve, RUN_DURATION));
167
+
168
+ // ========== Step 4: Stop and Clear ==========
169
+ async function cleanup() {
170
+ console.log('\n═══════════════════════════════════════════════════════════════');
171
+ console.log('Step 4: Stopping and clearing positions...');
172
+ console.log('═══════════════════════════════════════════════════════════════\n');
173
+
174
+ await arbService.stop();
175
+
176
+ // Print stats
177
+ const stats = arbService.getStats();
178
+ console.log('Session Statistics:');
179
+ console.log(` Opportunities detected: ${stats.opportunitiesDetected}`);
180
+ console.log(` Executions attempted: ${stats.executionsAttempted}`);
181
+ console.log(` Executions succeeded: ${stats.executionsSucceeded}`);
182
+ console.log(` Total profit: $${stats.totalProfit.toFixed(2)}`);
183
+ console.log(` Running time: ${(stats.runningTimeMs / 1000).toFixed(0)}s`);
184
+
185
+ // Clear positions
186
+ if (privateKey) {
187
+ console.log('\nClearing positions...');
188
+ const clearResult = await arbService.clearPositions(best.market, false);
189
+
190
+ console.log(`\nPosition status:`);
191
+ console.log(` Market status: ${clearResult.marketStatus}`);
192
+ console.log(` YES balance: ${clearResult.yesBalance.toFixed(4)}`);
193
+ console.log(` NO balance: ${clearResult.noBalance.toFixed(4)}`);
194
+ console.log(` Expected recovery: $${clearResult.totalUsdcRecovered.toFixed(2)}`);
195
+
196
+ if (clearResult.actions.length > 0) {
197
+ console.log(`\nPlanned actions:`);
198
+ for (const action of clearResult.actions) {
199
+ console.log(` - ${action.type}: ${action.amount.toFixed(4)} → ~$${action.usdcResult.toFixed(2)}`);
200
+ }
201
+ console.log('\n(Run with --execute-clear to actually clear positions)');
202
+ }
203
+ }
204
+
205
+ console.log('\nāœ… Done!');
206
+ }
207
+
208
+ await cleanup();
209
+ }
210
+
211
+ main().catch(console.error);
@@ -41,7 +41,7 @@ pnpm example:smart-money # 02-smart-money.ts
41
41
  Get started with the SDK. Fetches trending markets and orderbook data.
42
42
 
43
43
  ```typescript
44
- import { PolymarketSDK } from '@prediction-router/poly-sdk';
44
+ import { PolymarketSDK } from '@catalyst-team/poly-sdk';
45
45
  const sdk = new PolymarketSDK();
46
46
  const trending = await sdk.gammaApi.getTrendingMarkets(5);
47
47
  ```
package/package.json CHANGED
@@ -1,36 +1,38 @@
1
1
  {
2
2
  "name": "@catalyst-team/poly-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "private": false,
5
+ "description": "TypeScript SDK for Polymarket - prediction markets trading, smart money analysis, and market data",
5
6
  "type": "module",
6
7
  "main": "./dist/index.js",
7
8
  "types": "./dist/index.d.ts",
8
- "bin": {
9
- "poly-mcp": "./dist/mcp/server.js"
10
- },
11
9
  "exports": {
12
10
  ".": {
13
11
  "types": "./dist/index.d.ts",
14
12
  "import": "./dist/index.js"
15
- },
16
- "./mcp": {
17
- "types": "./dist/mcp/index.d.ts",
18
- "import": "./dist/mcp/index.js"
19
- },
20
- "./mcp/server": {
21
- "types": "./dist/mcp/server.d.ts",
22
- "import": "./dist/mcp/server.js"
23
13
  }
24
14
  },
15
+ "keywords": [
16
+ "polymarket",
17
+ "prediction-markets",
18
+ "trading",
19
+ "crypto",
20
+ "defi",
21
+ "smart-money"
22
+ ],
23
+ "author": "Catalyst Team",
24
+ "license": "MIT",
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
25
28
  "dependencies": {
26
- "@modelcontextprotocol/sdk": "^1.25.1",
27
29
  "@nevuamarkets/poly-websockets": "^0.3.0",
28
30
  "@polymarket/clob-client": "4.22.8",
29
31
  "@types/ws": "^8.18.1",
30
32
  "bottleneck": "^2.19.5",
31
33
  "ethers": "5",
32
34
  "ws": "^8.18.3",
33
- "@prediction-router/cache": "0.1.0"
35
+ "@catalyst-team/cache": "0.1.0"
34
36
  },
35
37
  "devDependencies": {
36
38
  "tsx": "^4.7.0",
@@ -39,8 +41,6 @@
39
41
  },
40
42
  "scripts": {
41
43
  "build": "tsc",
42
- "mcp": "node dist/mcp/server.js",
43
- "mcp:dev": "tsx src/mcp/server.ts",
44
44
  "dev": "tsc --watch",
45
45
  "test": "vitest run",
46
46
  "test:watch": "vitest",
@@ -55,8 +55,8 @@
55
55
  "example:trading": "tsx examples/08-trading-orders.ts",
56
56
  "example:rewards": "tsx examples/09-rewards-tracking.ts",
57
57
  "example:ctf": "tsx examples/10-ctf-operations.ts",
58
- "example:arbitrage": "tsx examples/11-arbitrage-service.ts",
59
- "example:live-arb": "tsx examples/12-live-arbitrage-scan.ts",
60
- "example:trending-arb": "tsx examples/13-trending-arb-monitor.ts"
58
+ "example:live-arb": "tsx examples/11-live-arbitrage-scan.ts",
59
+ "example:trending-arb": "tsx examples/12-trending-arb-monitor.ts",
60
+ "example:arb-service": "tsx examples/13-arbitrage-service.ts"
61
61
  }
62
62
  }
@@ -0,0 +1,385 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * FaZe vs Passion UA - BO3 Match Winner Arbitrage Monitor (V2 - Instant Merge)
4
+ *
5
+ * V2 ē­–ē•„ļ¼šé…åˆ token-rebalancer.ts 使用
6
+ * - Long Arb: Buy YES + NO → ē«‹å³ Mergeļ¼ˆå› äøŗč“¦ęˆ·é¢„å­˜äŗ† tokens)
7
+ * - Short Arb: ē›“ęŽ„å–å‡ŗé¢„å­˜ēš„ tokens
8
+ * - Rebalancer č‡ŖåŠØč”„å…… tokensļ¼Œē”®äæéšę—¶ęœ‰ęµåŠØę€§
9
+ *
10
+ * Usage:
11
+ * POLY_PRIVKEY=0x... npx tsx scripts/v2/faze-bo3-arb.ts
12
+ * POLY_PRIVKEY=0x... npx tsx scripts/v2/faze-bo3-arb.ts --auto-execute
13
+ * POLY_PRIVKEY=0x... npx tsx scripts/v2/faze-bo3-arb.ts --auto-execute --threshold 0.003
14
+ */
15
+
16
+ import {
17
+ WebSocketManager,
18
+ CTFClient,
19
+ TradingClient,
20
+ RateLimiter,
21
+ type BookUpdate,
22
+ type TokenIds,
23
+ } from '../../src/index.js';
24
+
25
+ // ═══════════════════════════════════════════════════════════════════════════════
26
+ // MARKET CONFIGURATION - FaZe vs Passion UA BO3 Match Winner
27
+ // ═══════════════════════════════════════════════════════════════════════════════
28
+
29
+ const MARKET_CONFIG = {
30
+ name: 'CS2: FaZe vs Passion UA (BO3)',
31
+ conditionId: '0x5ac2e92a82ea41ec3a4d9332d323d8d198a8c9acc732b7e6373bd61f45e1df49',
32
+ outcomes: ['FaZe', 'Passion UA'],
33
+ yesTokenId: '89062136554637645106569570664838812035010963361832797298254486917225439629146',
34
+ noTokenId: '54956090965819006918015199317329813503156478664679332459223691084797135448319',
35
+ };
36
+
37
+ // ═══════════════════════════════════════════════════════════════════════════════
38
+ // CONFIGURATION
39
+ // ═══════════════════════════════════════════════════════════════════════════════
40
+
41
+ const PRIVATE_KEY = process.env.POLY_PRIVKEY || process.env.POLYMARKET_PRIVATE_KEY || '';
42
+ const RPC_URL = process.env.POLYGON_RPC_URL || 'https://polygon-rpc.com';
43
+
44
+ const args = process.argv.slice(2);
45
+ const AUTO_EXECUTE = args.includes('--auto-execute');
46
+ const thresholdIdx = args.indexOf('--threshold');
47
+ const PROFIT_THRESHOLD = thresholdIdx !== -1 ? parseFloat(args[thresholdIdx + 1]) : 0.005;
48
+ const MIN_SIZE = 5;
49
+ const MAX_SINGLE_TRADE = 100;
50
+ const MIN_TOKEN_RESERVE = 10;
51
+
52
+ // ═══════════════════════════════════════════════════════════════════════════════
53
+ // STATE
54
+ // ═══════════════════════════════════════════════════════════════════════════════
55
+
56
+ interface OrderbookState {
57
+ yesBids: Array<{ price: number; size: number }>;
58
+ yesAsks: Array<{ price: number; size: number }>;
59
+ noBids: Array<{ price: number; size: number }>;
60
+ noAsks: Array<{ price: number; size: number }>;
61
+ lastUpdate: number;
62
+ }
63
+
64
+ interface BalanceState {
65
+ usdc: number;
66
+ yesTokens: number;
67
+ noTokens: number;
68
+ lastUpdate: number;
69
+ }
70
+
71
+ let orderbook: OrderbookState = {
72
+ yesBids: [],
73
+ yesAsks: [],
74
+ noBids: [],
75
+ noAsks: [],
76
+ lastUpdate: 0,
77
+ };
78
+
79
+ let balance: BalanceState = {
80
+ usdc: 0,
81
+ yesTokens: 0,
82
+ noTokens: 0,
83
+ lastUpdate: 0,
84
+ };
85
+
86
+ let wsManager: WebSocketManager;
87
+ let ctf: CTFClient | null = null;
88
+ let tradingClient: TradingClient | null = null;
89
+
90
+ let isExecuting = false;
91
+ let lastArbCheck = 0;
92
+ let totalOpportunities = 0;
93
+ let totalExecuted = 0;
94
+ let totalProfit = 0;
95
+
96
+ const tokenIds: TokenIds = {
97
+ yesTokenId: MARKET_CONFIG.yesTokenId,
98
+ noTokenId: MARKET_CONFIG.noTokenId,
99
+ };
100
+
101
+ // ═══════════════════════════════════════════════════════════════════════════════
102
+ // MAIN
103
+ // ═══════════════════════════════════════════════════════════════════════════════
104
+
105
+ async function main() {
106
+ console.log('╔════════════════════════════════════════════════════════════════╗');
107
+ console.log('ā•‘ FAZE vs PASSION UA - BO3 ARBITRAGE (V2 INSTANT) ā•‘');
108
+ console.log('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•');
109
+ console.log('');
110
+ console.log(`Market: ${MARKET_CONFIG.name}`);
111
+ console.log(`Outcomes: ${MARKET_CONFIG.outcomes[0]} (YES) vs ${MARKET_CONFIG.outcomes[1]} (NO)`);
112
+ console.log(`Condition ID: ${MARKET_CONFIG.conditionId.slice(0, 20)}...`);
113
+ console.log(`Profit Threshold: ${(PROFIT_THRESHOLD * 100).toFixed(2)}%`);
114
+ console.log(`Auto Execute: ${AUTO_EXECUTE ? 'YES' : 'NO'}`);
115
+ console.log(`Strategy: INSTANT MERGE (with Rebalancer support)`);
116
+ console.log('');
117
+
118
+ const rateLimiter = new RateLimiter();
119
+
120
+ if (PRIVATE_KEY) {
121
+ ctf = new CTFClient({ privateKey: PRIVATE_KEY, rpcUrl: RPC_URL });
122
+ console.log(`Wallet: ${ctf.getAddress()}`);
123
+
124
+ tradingClient = new TradingClient(rateLimiter, { privateKey: PRIVATE_KEY, chainId: 137 });
125
+ await tradingClient.initialize();
126
+ console.log('Trading client initialized');
127
+
128
+ await updateBalance();
129
+ console.log(`USDC.e Balance: ${balance.usdc.toFixed(2)}`);
130
+ console.log(`YES Tokens: ${balance.yesTokens.toFixed(2)}`);
131
+ console.log(`NO Tokens: ${balance.noTokens.toFixed(2)}`);
132
+
133
+ const heldPairs = Math.min(balance.yesTokens, balance.noTokens);
134
+ if (heldPairs < MIN_TOKEN_RESERVE) {
135
+ console.log(`\nāš ļø Token reserve low (${heldPairs.toFixed(0)} < ${MIN_TOKEN_RESERVE})`);
136
+ console.log(` Run token-rebalancer.ts to auto-split tokens`);
137
+ }
138
+ } else {
139
+ console.log('No wallet configured - monitoring only');
140
+ }
141
+
142
+ console.log('');
143
+ console.log('═══════════════════════════════════════════════════════════════');
144
+ console.log('Connecting to WebSocket...');
145
+ console.log('');
146
+
147
+ wsManager = new WebSocketManager({ enableLogging: false });
148
+ wsManager.on('bookUpdate', handleBookUpdate);
149
+ wsManager.on('connected', ({ assetIds }) => console.log(`WebSocket connected for ${assetIds.length} assets`));
150
+ wsManager.on('error', (error) => console.error('WebSocket error:', error.message));
151
+
152
+ await wsManager.subscribe([MARKET_CONFIG.yesTokenId, MARKET_CONFIG.noTokenId]);
153
+ setInterval(updateBalance, 30000);
154
+
155
+ process.on('SIGINT', async () => {
156
+ console.log('\n\nShutting down...');
157
+ console.log(`Total opportunities: ${totalOpportunities}, Executed: ${totalExecuted}, Profit: $${totalProfit.toFixed(2)}`);
158
+ await wsManager.unsubscribeAll();
159
+ process.exit(0);
160
+ });
161
+
162
+ console.log('Monitoring for arbitrage opportunities...\n');
163
+ }
164
+
165
+ // ═══════════════════════════════════════════════════════════════════════════════
166
+ // BALANCE & ORDERBOOK
167
+ // ═══════════════════════════════════════════════════════════════════════════════
168
+
169
+ async function updateBalance() {
170
+ if (!ctf) return;
171
+ try {
172
+ const [usdcBalance, positions] = await Promise.all([
173
+ ctf.getUsdcBalance(),
174
+ ctf.getPositionBalanceByTokenIds(MARKET_CONFIG.conditionId, tokenIds),
175
+ ]);
176
+ balance.usdc = parseFloat(usdcBalance);
177
+ balance.yesTokens = parseFloat(positions.yesBalance);
178
+ balance.noTokens = parseFloat(positions.noBalance);
179
+ balance.lastUpdate = Date.now();
180
+ } catch (error) {
181
+ console.error('Failed to update balance:', (error as Error).message);
182
+ }
183
+ }
184
+
185
+ function handleBookUpdate(update: BookUpdate) {
186
+ const { assetId, bids, asks } = update;
187
+ if (assetId === MARKET_CONFIG.yesTokenId) {
188
+ orderbook.yesBids = bids.sort((a, b) => b.price - a.price);
189
+ orderbook.yesAsks = asks.sort((a, b) => a.price - b.price);
190
+ } else if (assetId === MARKET_CONFIG.noTokenId) {
191
+ orderbook.noBids = bids.sort((a, b) => b.price - a.price);
192
+ orderbook.noAsks = asks.sort((a, b) => a.price - b.price);
193
+ }
194
+ orderbook.lastUpdate = Date.now();
195
+ checkArbitrage();
196
+ }
197
+
198
+ // ═══════════════════════════════════════════════════════════════════════════════
199
+ // ARBITRAGE DETECTION
200
+ // ═══════════════════════════════════════════════════════════════════════════════
201
+
202
+ function checkArbitrage() {
203
+ const now = Date.now();
204
+ if (now - lastArbCheck < 10) return;
205
+ lastArbCheck = now;
206
+
207
+ if (orderbook.yesBids.length === 0 || orderbook.noAsks.length === 0) return;
208
+
209
+ const yesBestBid = orderbook.yesBids[0]?.price || 0;
210
+ const yesBestAsk = orderbook.yesAsks[0]?.price || 1;
211
+ const noBestBid = orderbook.noBids[0]?.price || 0;
212
+ const noBestAsk = orderbook.noAsks[0]?.price || 1;
213
+
214
+ const effectiveBuyYes = Math.min(yesBestAsk, 1 - noBestBid);
215
+ const effectiveBuyNo = Math.min(noBestAsk, 1 - yesBestBid);
216
+ const effectiveSellYes = Math.max(yesBestBid, 1 - noBestAsk);
217
+ const effectiveSellNo = Math.max(noBestBid, 1 - yesBestAsk);
218
+
219
+ const longCost = effectiveBuyYes + effectiveBuyNo;
220
+ const longProfit = 1 - longCost;
221
+ const shortRevenue = effectiveSellYes + effectiveSellNo;
222
+ const shortProfit = shortRevenue - 1;
223
+
224
+ const orderbookLongSize = Math.min(orderbook.yesAsks[0]?.size || 0, orderbook.noAsks[0]?.size || 0);
225
+ const orderbookShortSize = Math.min(orderbook.yesBids[0]?.size || 0, orderbook.noBids[0]?.size || 0);
226
+
227
+ const heldPairs = Math.min(balance.yesTokens, balance.noTokens);
228
+ const balanceLongSize = longCost > 0 ? balance.usdc / longCost : 0;
229
+ const longSize = Math.min(orderbookLongSize, balanceLongSize, MAX_SINGLE_TRADE);
230
+ const shortSize = Math.min(orderbookShortSize, heldPairs, MAX_SINGLE_TRADE);
231
+
232
+ const timestamp = new Date().toISOString().split('T')[1].split('.')[0];
233
+ const status = [
234
+ `[${timestamp}]`,
235
+ `${MARKET_CONFIG.outcomes[0]}: ${yesBestBid.toFixed(2)}/${yesBestAsk.toFixed(2)}`,
236
+ `${MARKET_CONFIG.outcomes[1]}: ${noBestBid.toFixed(2)}/${noBestAsk.toFixed(2)}`,
237
+ `Long: ${(longProfit * 100).toFixed(2)}%`,
238
+ `Short: ${(shortProfit * 100).toFixed(2)}%`,
239
+ `Bal: $${balance.usdc.toFixed(0)}`,
240
+ `Pairs: ${heldPairs.toFixed(0)}`,
241
+ ].join(' | ');
242
+
243
+ const hasLongArb = longProfit > PROFIT_THRESHOLD && longSize >= MIN_SIZE && balance.usdc >= MIN_SIZE;
244
+ const hasShortArb = shortProfit > PROFIT_THRESHOLD && shortSize >= MIN_SIZE && heldPairs >= MIN_TOKEN_RESERVE;
245
+
246
+ if (hasLongArb || hasShortArb) {
247
+ console.log('\n' + '!'.repeat(75));
248
+ console.log(status);
249
+
250
+ if (hasLongArb) {
251
+ totalOpportunities++;
252
+ console.log(`\nšŸŽÆ LONG ARB: Buy ${MARKET_CONFIG.outcomes[0]} @ ${effectiveBuyYes.toFixed(4)} + ${MARKET_CONFIG.outcomes[1]} @ ${effectiveBuyNo.toFixed(4)}`);
253
+ console.log(` Cost: ${longCost.toFixed(4)}, Profit: ${(longProfit * 100).toFixed(2)}%, Size: ${longSize.toFixed(2)}, Est: $${(longProfit * longSize).toFixed(2)}`);
254
+ if (AUTO_EXECUTE && !isExecuting) executeLongArb(effectiveBuyYes, effectiveBuyNo, longSize, longProfit);
255
+ }
256
+
257
+ if (hasShortArb) {
258
+ totalOpportunities++;
259
+ console.log(`\nšŸŽÆ SHORT ARB: Sell ${MARKET_CONFIG.outcomes[0]} @ ${effectiveSellYes.toFixed(4)} + ${MARKET_CONFIG.outcomes[1]} @ ${effectiveSellNo.toFixed(4)}`);
260
+ console.log(` Revenue: ${shortRevenue.toFixed(4)}, Profit: ${(shortProfit * 100).toFixed(2)}%, Size: ${shortSize.toFixed(2)}, Est: $${(shortProfit * shortSize).toFixed(2)}`);
261
+ if (AUTO_EXECUTE && !isExecuting) executeShortArb(shortSize, shortProfit * shortSize);
262
+ }
263
+
264
+ console.log('!'.repeat(75) + '\n');
265
+ } else {
266
+ process.stdout.write('\r' + status + ' ');
267
+ }
268
+ }
269
+
270
+ // ═══════════════════════════════════════════════════════════════════════════════
271
+ // EXECUTION
272
+ // ═══════════════════════════════════════════════════════════════════════════════
273
+
274
+ async function executeLongArb(buyYesPrice: number, buyNoPrice: number, size: number, profitRate: number) {
275
+ if (!ctf || !tradingClient || isExecuting) return;
276
+ isExecuting = true;
277
+ const startTime = Date.now();
278
+ console.log('\nšŸ”„ Executing Long Arb (Buy → Instant Merge)...');
279
+
280
+ try {
281
+ const requiredUsdc = (buyYesPrice + buyNoPrice) * size;
282
+ if (balance.usdc < requiredUsdc) {
283
+ console.log(`āŒ Insufficient USDC.e: have ${balance.usdc.toFixed(2)}, need ${requiredUsdc.toFixed(2)}`);
284
+ isExecuting = false;
285
+ return;
286
+ }
287
+
288
+ console.log(` 1. Buying tokens in parallel...`);
289
+ const orderStartTime = Date.now();
290
+ const [buyYesResult, buyNoResult] = await Promise.all([
291
+ tradingClient.createMarketOrder({ tokenId: MARKET_CONFIG.yesTokenId, side: 'BUY', amount: size * buyYesPrice, orderType: 'FOK' }),
292
+ tradingClient.createMarketOrder({ tokenId: MARKET_CONFIG.noTokenId, side: 'BUY', amount: size * buyNoPrice, orderType: 'FOK' }),
293
+ ]);
294
+ console.log(` ${MARKET_CONFIG.outcomes[0]}: ${buyYesResult.success ? 'āœ“' : 'āœ—'}, ${MARKET_CONFIG.outcomes[1]}: ${buyNoResult.success ? 'āœ“' : 'āœ—'} (${Date.now() - orderStartTime}ms)`);
295
+
296
+ if (!buyYesResult.success || !buyNoResult.success) {
297
+ console.log(`āŒ Order(s) failed - will retry on next opportunity`);
298
+ if (buyYesResult.errorMsg) console.log(` YES Error: ${buyYesResult.errorMsg}`);
299
+ if (buyNoResult.errorMsg) console.log(` NO Error: ${buyNoResult.errorMsg}`);
300
+ isExecuting = false;
301
+ return;
302
+ }
303
+
304
+ // Immediate merge using pre-held tokens
305
+ const heldPairs = Math.min(balance.yesTokens, balance.noTokens);
306
+ const mergeSize = Math.floor(Math.min(size, heldPairs) * 1e6) / 1e6;
307
+
308
+ if (mergeSize >= MIN_SIZE) {
309
+ console.log(` 2. Merging ${mergeSize.toFixed(2)} pairs from pre-held tokens...`);
310
+ try {
311
+ const mergeResult = await ctf.merge(MARKET_CONFIG.conditionId, mergeSize.toString());
312
+ console.log(` TX: ${mergeResult.txHash}`);
313
+
314
+ const estProfit = profitRate * mergeSize;
315
+ totalProfit += estProfit;
316
+ console.log(`āœ… Long Arb completed! Profit: ~$${estProfit.toFixed(2)}. Total: ${Date.now() - startTime}ms`);
317
+ } catch (mergeError: any) {
318
+ console.log(` āš ļø Merge failed: ${mergeError.message}`);
319
+ console.log(` Tokens will be held for later merge by rebalancer`);
320
+ }
321
+ } else {
322
+ console.log(` 2. Skipping merge (held pairs ${heldPairs.toFixed(2)} < ${MIN_SIZE})`);
323
+ console.log(` Tokens held - rebalancer will merge later`);
324
+ }
325
+
326
+ totalExecuted++;
327
+ await new Promise(r => setTimeout(r, 2000));
328
+ await updateBalance();
329
+ console.log(` New balance: USDC=${balance.usdc.toFixed(2)}, YES=${balance.yesTokens.toFixed(2)}, NO=${balance.noTokens.toFixed(2)}`);
330
+ } catch (error) {
331
+ console.log(`āŒ Long Arb failed: ${(error as Error).message}`);
332
+ }
333
+ isExecuting = false;
334
+ }
335
+
336
+ async function executeShortArb(size: number, estProfit: number) {
337
+ if (!ctf || !tradingClient || isExecuting) return;
338
+ isExecuting = true;
339
+ const startTime = Date.now();
340
+ console.log('\nšŸ”„ Executing Short Arb (Sell Pre-held Tokens)...');
341
+
342
+ try {
343
+ const heldPairs = Math.min(balance.yesTokens, balance.noTokens);
344
+ if (heldPairs < size) {
345
+ console.log(`āŒ Insufficient held tokens: have ${heldPairs.toFixed(2)}, need ${size.toFixed(2)}`);
346
+ console.log(` Run token-rebalancer.ts to auto-split more tokens`);
347
+ isExecuting = false;
348
+ return;
349
+ }
350
+
351
+ console.log(` 1. Selling pre-held tokens in parallel...`);
352
+ const orderStartTime = Date.now();
353
+ const [sellYesResult, sellNoResult] = await Promise.all([
354
+ tradingClient.createMarketOrder({ tokenId: MARKET_CONFIG.yesTokenId, side: 'SELL', amount: size, orderType: 'FOK' }),
355
+ tradingClient.createMarketOrder({ tokenId: MARKET_CONFIG.noTokenId, side: 'SELL', amount: size, orderType: 'FOK' }),
356
+ ]);
357
+ console.log(` ${MARKET_CONFIG.outcomes[0]}: ${sellYesResult.success ? 'āœ“' : 'āœ—'}, ${MARKET_CONFIG.outcomes[1]}: ${sellNoResult.success ? 'āœ“' : 'āœ—'} (${Date.now() - orderStartTime}ms)`);
358
+
359
+ if (!sellYesResult.success || !sellNoResult.success) {
360
+ console.log(`āŒ Order(s) failed`);
361
+ if (sellYesResult.errorMsg) console.log(` YES Error: ${sellYesResult.errorMsg}`);
362
+ if (sellNoResult.errorMsg) console.log(` NO Error: ${sellNoResult.errorMsg}`);
363
+ isExecuting = false;
364
+ return;
365
+ }
366
+
367
+ console.log(`āœ… Short Arb completed! Profit: ~$${estProfit.toFixed(2)}. Total: ${Date.now() - startTime}ms`);
368
+ totalExecuted++;
369
+ totalProfit += estProfit;
370
+
371
+ await new Promise(r => setTimeout(r, 2000));
372
+ await updateBalance();
373
+ console.log(` New balance: USDC=${balance.usdc.toFixed(2)}, YES=${balance.yesTokens.toFixed(2)}, NO=${balance.noTokens.toFixed(2)}`);
374
+
375
+ const newHeldPairs = Math.min(balance.yesTokens, balance.noTokens);
376
+ if (newHeldPairs < MIN_TOKEN_RESERVE) {
377
+ console.log(` āš ļø Token reserve low (${newHeldPairs.toFixed(0)}) - rebalancer should split more`);
378
+ }
379
+ } catch (error) {
380
+ console.log(`āŒ Short Arb failed: ${(error as Error).message}`);
381
+ }
382
+ isExecuting = false;
383
+ }
384
+
385
+ main().catch(console.error);