@agenticmail/enterprise 0.5.441 → 0.5.443

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 (257) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +3 -2
  3. package/dist/agent-heartbeat-QJ3WTWUK.js +518 -0
  4. package/dist/agent-status-CS5NNYIO.js +11 -0
  5. package/dist/agent-tools-3MD7EPO6.js +14629 -0
  6. package/dist/agent-tools-62HGQYDL.js +14629 -0
  7. package/dist/agent-tools-BTUVVOWT.js +14629 -0
  8. package/dist/agent-tools-BV3JMJPN.js +14629 -0
  9. package/dist/agent-tools-JVNQ4RPM.js +14629 -0
  10. package/dist/agent-tools-LD7LELVT.js +14629 -0
  11. package/dist/agent-tools-NRKQ5NMW.js +14629 -0
  12. package/dist/agent-tools-OXZJOD4D.js +14629 -0
  13. package/dist/agent-tools-PT2ES6G5.js +14629 -0
  14. package/dist/agent-tools-SS335E2E.js +14629 -0
  15. package/dist/chunk-2ACTQSPC.js +1728 -0
  16. package/dist/chunk-2XZE5NT3.js +5387 -0
  17. package/dist/chunk-325NTNE6.js +2180 -0
  18. package/dist/chunk-3AHKVWCO.js +5387 -0
  19. package/dist/chunk-3DM6QDVR.js +1728 -0
  20. package/dist/chunk-3L2N6LR2.js +7517 -0
  21. package/dist/chunk-3XP7VYIS.js +7437 -0
  22. package/dist/chunk-3ZD3XOV6.js +1728 -0
  23. package/dist/chunk-44VXWYPE.js +2570 -0
  24. package/dist/chunk-4UOSUXFR.js +7529 -0
  25. package/dist/chunk-54SHHX2S.js +2180 -0
  26. package/dist/chunk-5DVY3CNK.js +1728 -0
  27. package/dist/chunk-5F5DGV63.js +5596 -0
  28. package/dist/chunk-6IUUOWZY.js +7526 -0
  29. package/dist/chunk-77LFFLQX.js +7526 -0
  30. package/dist/chunk-7BKNDBSA.js +2191 -0
  31. package/dist/chunk-7BS2XPLW.js +5359 -0
  32. package/dist/chunk-7GCSGDXQ.js +1636 -0
  33. package/dist/chunk-7IAI45PR.js +4735 -0
  34. package/dist/chunk-7QPZGO27.js +4976 -0
  35. package/dist/chunk-7S5XAXRD.js +4747 -0
  36. package/dist/chunk-AD6ZR3QD.js +5357 -0
  37. package/dist/chunk-AO556OXZ.js +2570 -0
  38. package/dist/chunk-APUWQHXC.js +7437 -0
  39. package/dist/chunk-BKSSHV6C.js +1728 -0
  40. package/dist/chunk-BMUNQHLU.js +1208 -0
  41. package/dist/chunk-BPF5DKUM.js +392 -0
  42. package/dist/chunk-CFR5OSMI.js +1220 -0
  43. package/dist/chunk-CM4GUHXP.js +1728 -0
  44. package/dist/chunk-CVFIM72Q.js +501 -0
  45. package/dist/chunk-DFXVJNKW.js +4734 -0
  46. package/dist/chunk-DJ7LHRL7.js +2214 -0
  47. package/dist/chunk-DJFI6NR6.js +1624 -0
  48. package/dist/chunk-DJHYV5VY.js +1624 -0
  49. package/dist/chunk-DYM4PQO3.js +4727 -0
  50. package/dist/chunk-DZ75RF35.js +1728 -0
  51. package/dist/chunk-E5H64U5H.js +472 -0
  52. package/dist/chunk-EOD4EGFI.js +1728 -0
  53. package/dist/chunk-EP74QR5B.js +4980 -0
  54. package/dist/chunk-FE2M4FV5.js +5273 -0
  55. package/dist/chunk-FQWJMPKW.js +305 -0
  56. package/dist/chunk-FUH6NWIX.js +7540 -0
  57. package/dist/chunk-G5ADHHAD.js +1728 -0
  58. package/dist/chunk-GAYLPSM7.js +7529 -0
  59. package/dist/chunk-GDAFZJTJ.js +7540 -0
  60. package/dist/chunk-GLAN2JBA.js +1636 -0
  61. package/dist/chunk-GNAEBGU7.js +4812 -0
  62. package/dist/chunk-HDDA2Q3Q.js +1728 -0
  63. package/dist/chunk-HG62FWWQ.js +7437 -0
  64. package/dist/chunk-HV5VIS5K.js +1624 -0
  65. package/dist/chunk-IEVA23WK.js +4976 -0
  66. package/dist/chunk-IFMZ2IYC.js +7540 -0
  67. package/dist/chunk-IN7VIORK.js +2641 -0
  68. package/dist/chunk-KAX3ZDP2.js +7529 -0
  69. package/dist/chunk-KECO53GP.js +1728 -0
  70. package/dist/chunk-KGZ74UMA.js +1728 -0
  71. package/dist/chunk-M2ZLRUMX.js +7540 -0
  72. package/dist/chunk-MKQWC6KF.js +5374 -0
  73. package/dist/chunk-MQ3JBGLU.js +1636 -0
  74. package/dist/chunk-MVB6JARX.js +7529 -0
  75. package/dist/chunk-N37MOOFE.js +2210 -0
  76. package/dist/chunk-NELCAZUQ.js +5357 -0
  77. package/dist/chunk-NHLOKTUV.js +26305 -0
  78. package/dist/chunk-OPAO5QQS.js +1728 -0
  79. package/dist/chunk-OUHU3VW6.js +1728 -0
  80. package/dist/chunk-OWEXZVZ6.js +1728 -0
  81. package/dist/chunk-P4DHSJJY.js +7437 -0
  82. package/dist/chunk-Q7QA6MNJ.js +1728 -0
  83. package/dist/chunk-QKKTNPGV.js +7592 -0
  84. package/dist/chunk-REMMXZVU.js +7529 -0
  85. package/dist/chunk-RK3KATD4.js +4756 -0
  86. package/dist/chunk-RQ33L5T3.js +5374 -0
  87. package/dist/chunk-RU77F65Q.js +1728 -0
  88. package/dist/chunk-SAM3CVIU.js +5374 -0
  89. package/dist/chunk-SFMXIKWZ.js +1728 -0
  90. package/dist/chunk-SPZ5JBGW.js +1624 -0
  91. package/dist/chunk-TDOC6WSK.js +7529 -0
  92. package/dist/chunk-TPMT5WTW.js +5357 -0
  93. package/dist/chunk-TVXUR3PB.js +2180 -0
  94. package/dist/chunk-TZEEVEKG.js +5343 -0
  95. package/dist/chunk-U327O3ZR.js +7540 -0
  96. package/dist/chunk-ULD6C5DB.js +2180 -0
  97. package/dist/chunk-ULVRCJZV.js +5374 -0
  98. package/dist/chunk-UOCDOM2S.js +1624 -0
  99. package/dist/chunk-VINDZLLX.js +1636 -0
  100. package/dist/chunk-VODW5GJL.js +2180 -0
  101. package/dist/chunk-WEWL7Z3C.js +2180 -0
  102. package/dist/chunk-WG2MEMS6.js +5343 -0
  103. package/dist/chunk-WYV6QWOJ.js +1188 -0
  104. package/dist/chunk-X5AIAD77.js +1636 -0
  105. package/dist/chunk-XUETIRDV.js +2214 -0
  106. package/dist/chunk-XWSMX7RK.js +7529 -0
  107. package/dist/chunk-YUSYXHKB.js +1728 -0
  108. package/dist/chunk-ZC2RMHQD.js +5374 -0
  109. package/dist/chunk-ZDZQQGFG.js +5374 -0
  110. package/dist/chunk-ZJ2HCWKF.js +1728 -0
  111. package/dist/chunk-ZK4QCOJ5.js +5273 -0
  112. package/dist/chunk-ZMKVEJKR.js +392 -0
  113. package/dist/cli-agent-2UFGFO24.js +2761 -0
  114. package/dist/cli-agent-5FPUCFPG.js +2761 -0
  115. package/dist/cli-agent-7AB6NIYQ.js +2761 -0
  116. package/dist/cli-agent-7CAFSYOM.js +2761 -0
  117. package/dist/cli-agent-7OVOINHR.js +2757 -0
  118. package/dist/cli-agent-AWKBFIRS.js +2761 -0
  119. package/dist/cli-agent-ES3XOPHJ.js +2757 -0
  120. package/dist/cli-agent-FSO2N7I6.js +2757 -0
  121. package/dist/cli-agent-KPXZN5JK.js +2761 -0
  122. package/dist/cli-agent-LGTCFHGS.js +2757 -0
  123. package/dist/cli-agent-LLUYUHHF.js +2757 -0
  124. package/dist/cli-agent-NHZWYX5Q.js +2761 -0
  125. package/dist/cli-agent-TYUOTYCO.js +2757 -0
  126. package/dist/cli-agent-VQO6HI65.js +2757 -0
  127. package/dist/cli-agent-VV5JWRU7.js +2757 -0
  128. package/dist/cli-agent-Z5B23XED.js +2757 -0
  129. package/dist/cli-serve-2FLQXJW7.js +322 -0
  130. package/dist/cli-serve-34YFCCUX.js +322 -0
  131. package/dist/cli-serve-3NS6MNUS.js +322 -0
  132. package/dist/cli-serve-4PWFEPNA.js +322 -0
  133. package/dist/cli-serve-4VIBMXMT.js +322 -0
  134. package/dist/cli-serve-5ZUF5MGH.js +322 -0
  135. package/dist/cli-serve-6HGL56GB.js +322 -0
  136. package/dist/cli-serve-BBBWEKKW.js +322 -0
  137. package/dist/cli-serve-D2HQC4SB.js +322 -0
  138. package/dist/cli-serve-IKCMNUNM.js +322 -0
  139. package/dist/cli-serve-LTUKYMEF.js +322 -0
  140. package/dist/cli-serve-MIWI5PBE.js +322 -0
  141. package/dist/cli-serve-R6K4SZ3L.js +322 -0
  142. package/dist/cli-serve-RQIOBQGF.js +322 -0
  143. package/dist/cli-serve-S57ROYQ6.js +322 -0
  144. package/dist/cli-serve-WVSLOISY.js +322 -0
  145. package/dist/cli-serve-XDFSJIQV.js +322 -0
  146. package/dist/cli-serve-YKZSNR3P.js +322 -0
  147. package/dist/cli-serve-ZGKXWDMZ.js +322 -0
  148. package/dist/cli-serve-ZZOJWREY.js +322 -0
  149. package/dist/cli.js +3 -3
  150. package/dist/dashboard/components/utils.js +1 -1
  151. package/dist/dashboard/docs/polymarket.html +1 -1
  152. package/dist/dashboard/pages/polymarket.js +222 -80
  153. package/dist/index.js +12 -12
  154. package/dist/pipeline-C4C3ZF4X.js +15 -0
  155. package/dist/pipeline-DAF3EV7Q.js +15 -0
  156. package/dist/pipeline-MMESLRQG.js +15 -0
  157. package/dist/polymarket-3LGJSQZK.js +17 -0
  158. package/dist/polymarket-4OIWQFBO.js +17 -0
  159. package/dist/polymarket-AZEV7C6E.js +17 -0
  160. package/dist/polymarket-DBQYJC57.js +7 -0
  161. package/dist/polymarket-EVTKWLUO.js +7 -0
  162. package/dist/polymarket-GMKDVVQH.js +17 -0
  163. package/dist/polymarket-HZOAD4W4.js +17 -0
  164. package/dist/polymarket-PKGRF7ID.js +17 -0
  165. package/dist/polymarket-QUNR2H7U.js +17 -0
  166. package/dist/polymarket-TM624BN5.js +17 -0
  167. package/dist/polymarket-VT2EDGD7.js +17 -0
  168. package/dist/polymarket-W7B4TLP3.js +17 -0
  169. package/dist/polymarket-WS4VE6U7.js +7 -0
  170. package/dist/polymarket-runtime-772ADZJW.js +108 -0
  171. package/dist/polymarket-runtime-AIYHQKYQ.js +108 -0
  172. package/dist/polymarket-runtime-APJ5HW4Y.js +108 -0
  173. package/dist/polymarket-runtime-BX3ODZZD.js +108 -0
  174. package/dist/polymarket-runtime-ISD4CKKL.js +108 -0
  175. package/dist/polymarket-runtime-JU4PQZGJ.js +108 -0
  176. package/dist/polymarket-runtime-OFLBSPCK.js +108 -0
  177. package/dist/polymarket-runtime-POJRRWFS.js +108 -0
  178. package/dist/polymarket-runtime-XN5TVHU5.js +108 -0
  179. package/dist/polymarket-runtime-ZYFTKEJ4.js +108 -0
  180. package/dist/polymarket-watcher-2CKLTIXI.js +23 -0
  181. package/dist/polymarket-watcher-3C72X36B.js +23 -0
  182. package/dist/polymarket-watcher-IHRZ2URM.js +23 -0
  183. package/dist/polymarket-watcher-J7UWIN7I.js +23 -0
  184. package/dist/polymarket-watcher-M6CDW63Y.js +23 -0
  185. package/dist/polymarket-watcher-OSI73EDX.js +23 -0
  186. package/dist/polymarket-watcher-PN4JJV3V.js +23 -0
  187. package/dist/polymarket-watcher-PXK4NS5F.js +23 -0
  188. package/dist/polymarket-watcher-VDJ4SVHP.js +23 -0
  189. package/dist/polymarket-watcher-W4BV6JTG.js +23 -0
  190. package/dist/routes-PRBWZ4MQ.js +94 -0
  191. package/dist/runtime-22E2WCL6.js +46 -0
  192. package/dist/runtime-7GYS4FQU.js +46 -0
  193. package/dist/runtime-7OVDHKNW.js +46 -0
  194. package/dist/runtime-BR6K3Q3N.js +46 -0
  195. package/dist/runtime-CTTRDVJ6.js +46 -0
  196. package/dist/runtime-GIIPTHK5.js +46 -0
  197. package/dist/runtime-HYAKLZWI.js +46 -0
  198. package/dist/runtime-IQ2KIV2L.js +50 -0
  199. package/dist/runtime-ISXECIAB.js +46 -0
  200. package/dist/runtime-IV4BY7S4.js +46 -0
  201. package/dist/runtime-O65GGHL2.js +46 -0
  202. package/dist/runtime-PKHEGQYK.js +50 -0
  203. package/dist/runtime-RFKPIFK4.js +46 -0
  204. package/dist/runtime-S35B6PIY.js +46 -0
  205. package/dist/runtime-WEZJ4YRK.js +46 -0
  206. package/dist/runtime-WLWB62ZI.js +46 -0
  207. package/dist/screener-FB47G4YX.js +26 -0
  208. package/dist/screener-WQVQO4WF.js +26 -0
  209. package/dist/server-6GWGIGBH.js +36 -0
  210. package/dist/server-7VZ6XXBC.js +36 -0
  211. package/dist/server-7XBTD3FW.js +36 -0
  212. package/dist/server-BN56IMC4.js +36 -0
  213. package/dist/server-GD6SPDER.js +36 -0
  214. package/dist/server-IJHU2FIK.js +36 -0
  215. package/dist/server-J5WZGZX3.js +36 -0
  216. package/dist/server-JVUH2E3H.js +36 -0
  217. package/dist/server-MYXWTXSE.js +36 -0
  218. package/dist/server-ORGV7O7L.js +36 -0
  219. package/dist/server-QPE3WVUQ.js +36 -0
  220. package/dist/server-SUBGS5BJ.js +36 -0
  221. package/dist/server-SWAYJTFF.js +36 -0
  222. package/dist/server-TFIWDU2H.js +36 -0
  223. package/dist/server-VH4W6LWW.js +36 -0
  224. package/dist/server-WQXRTEKF.js +36 -0
  225. package/dist/server-X5UMJJ3J.js +36 -0
  226. package/dist/server-XOK5LHNU.js +36 -0
  227. package/dist/server-ZLK24KFY.js +36 -0
  228. package/dist/server-ZNSQGCRM.js +36 -0
  229. package/dist/setup-32ZRMW7R.js +20 -0
  230. package/dist/setup-3WF7PBIS.js +20 -0
  231. package/dist/setup-4TM64QG4.js +20 -0
  232. package/dist/setup-5SGUMKN7.js +20 -0
  233. package/dist/setup-7ENTPINX.js +20 -0
  234. package/dist/setup-AGYKOCIU.js +20 -0
  235. package/dist/setup-EIP4IHZN.js +20 -0
  236. package/dist/setup-F5SAGQJJ.js +20 -0
  237. package/dist/setup-LMF76UUM.js +20 -0
  238. package/dist/setup-MA5QR3J4.js +20 -0
  239. package/dist/setup-MLIPR2U3.js +20 -0
  240. package/dist/setup-MLIZYTT6.js +20 -0
  241. package/dist/setup-OPVZ3GM7.js +20 -0
  242. package/dist/setup-PWFHUXOK.js +20 -0
  243. package/dist/setup-QF5GCTPE.js +20 -0
  244. package/dist/setup-SGF4OX3A.js +20 -0
  245. package/dist/setup-TLWKGWC6.js +20 -0
  246. package/dist/setup-TYCRH63P.js +20 -0
  247. package/dist/setup-USUBKTVX.js +20 -0
  248. package/dist/setup-VMNTNEB4.js +20 -0
  249. package/dist/shared-S5ROHYCX.js +69 -0
  250. package/dist/shared-TLWZZZSK.js +69 -0
  251. package/dist/system-prompts-7ZRL27FN.js +69 -0
  252. package/dist/system-prompts-CNXUF2AV.js +69 -0
  253. package/dist/system-prompts-SYGQRBRG.js +69 -0
  254. package/logs/cloudflared-error.log +4 -57
  255. package/logs/cloudflared-error__2026-03-11_00-00-00.log +289 -0
  256. package/package.json +1 -1
  257. package/logs/cloudflared-error__2026-03-06_00-00-00.log +0 -245
@@ -0,0 +1,4747 @@
1
+ import {
2
+ errorResult,
3
+ jsonResult
4
+ } from "./chunk-ZB3VC2MR.js";
5
+ import {
6
+ cachedFetchJSON,
7
+ safeDbDDL,
8
+ safeDbExec,
9
+ safeDbGet,
10
+ safeDbQuery
11
+ } from "./chunk-N5C3PJZC.js";
12
+ import {
13
+ checkAlerts,
14
+ createBracketAlerts,
15
+ deleteAlert,
16
+ deleteAllAlerts,
17
+ deleteAutoApproveRule,
18
+ ensureSDK,
19
+ generateWallet,
20
+ getAlerts,
21
+ getAutoApproveRules,
22
+ getBracketConfig,
23
+ getCalibration,
24
+ getClobClient,
25
+ getDailyCounter,
26
+ getPaperPositions,
27
+ getPendingTrades,
28
+ getResolvedPredictions,
29
+ getStrategyPerformance,
30
+ getUnresolvedPredictions,
31
+ importSDK,
32
+ incrementDailyCounter,
33
+ initLearningDB,
34
+ initPolymarketDB,
35
+ loadConfig,
36
+ loadWalletCredentials,
37
+ logTrade,
38
+ markLessonsExtracted,
39
+ pauseTrading,
40
+ recallLessons,
41
+ recordPrediction,
42
+ resolvePendingTrade,
43
+ resolvePrediction,
44
+ resumeTrading,
45
+ saveAlert,
46
+ saveAutoApproveRule,
47
+ saveConfig,
48
+ savePaperPosition,
49
+ savePendingTrade,
50
+ saveWalletCredentials,
51
+ storeLesson
52
+ } from "./chunk-HV5VIS5K.js";
53
+ import {
54
+ analyzeCounterparties,
55
+ analyzeFlow,
56
+ analyzeMicrostructure,
57
+ analyzeOrderbookDepth,
58
+ analyzePolymarketComments,
59
+ analyzePortfolio,
60
+ analyzeSentiment,
61
+ analyzeStatArb,
62
+ analyzeTwitterSentiment,
63
+ analyzeVolatility,
64
+ assessResolutionRisk,
65
+ attributePnL,
66
+ batchScreen,
67
+ bayesianUpdate,
68
+ calculateCorrelationMatrix,
69
+ calculateEntropy,
70
+ calculateKelly,
71
+ calculatePortfolioKelly,
72
+ calculateSmartMoneyIndex,
73
+ calculateTechnicalIndicators,
74
+ calculateVaR,
75
+ compareOdds,
76
+ decodeTransaction,
77
+ detectManipulation,
78
+ detectRegime,
79
+ fetchBreakingNews,
80
+ fetchNewsFeed,
81
+ fetchOfficialSource,
82
+ findCorrelations,
83
+ fullAnalysis,
84
+ generateCompositeSignal,
85
+ getWalletTransactions,
86
+ mapLiquidity,
87
+ measureSocialVelocity,
88
+ monitorRedditPulse,
89
+ monitorTelegram,
90
+ portfolioReview,
91
+ priceBinaryOption,
92
+ profileWallet,
93
+ quickAnalysis,
94
+ runMonteCarlo,
95
+ scanArbitrage,
96
+ scanWhaleTrades,
97
+ testMarketEfficiency,
98
+ trackResolution
99
+ } from "./chunk-X2ZHRKPL.js";
100
+ import {
101
+ screenMarkets
102
+ } from "./chunk-YHOMKT3Q.js";
103
+ import {
104
+ CLOB_API,
105
+ CTF_ADDRESS,
106
+ USDC_ADDRESS
107
+ } from "./chunk-WH37MNM7.js";
108
+ import {
109
+ createWatcherTools
110
+ } from "./chunk-ULD6C5DB.js";
111
+
112
+ // src/agent-tools/tools/polymarket-screener.ts
113
+ function createPolymarketScreenerTools(_opts) {
114
+ return [
115
+ {
116
+ name: "poly_screen_markets",
117
+ description: "Screen and rank prediction markets using a 6-dimension scoring system: liquidity, volume, spread, edge potential, timing, and momentum. Returns ranked list with recommendations. Supports strategy modes: momentum, contested, best_opportunities, high_volume, closing_soon, mispriced, safe_bets, new_markets.",
118
+ parameters: {
119
+ type: "object",
120
+ properties: {
121
+ query: { type: "string", description: "Search query to filter markets" },
122
+ strategy: {
123
+ type: "string",
124
+ description: "Screening strategy",
125
+ enum: ["momentum", "contested", "best_opportunities", "high_volume", "closing_soon", "mispriced", "safe_bets", "new_markets"]
126
+ },
127
+ limit: { type: "number", description: "Max markets to return", default: 10 },
128
+ min_volume: { type: "number", description: "Minimum 24h volume filter" },
129
+ min_liquidity: { type: "number", description: "Minimum liquidity filter" },
130
+ active_only: { type: "boolean", description: "Only active markets", default: true }
131
+ }
132
+ },
133
+ async execute(_id, p) {
134
+ try {
135
+ const raw = await screenMarkets(p);
136
+ if (raw?.markets && Array.isArray(raw.markets)) {
137
+ raw.markets = raw.markets.slice(0, 8).map((m) => {
138
+ const mkt = m.market || {};
139
+ return {
140
+ market: {
141
+ id: mkt.id,
142
+ question: mkt.question,
143
+ slug: mkt.slug,
144
+ outcomes: mkt.outcomes,
145
+ outcomePrices: mkt.outcomePrices || mkt.outcome_prices,
146
+ volume24hr: mkt.volume24hr,
147
+ liquidity: mkt.liquidity,
148
+ endDate: mkt.end_date_iso || mkt.endDate
149
+ },
150
+ scores: m.scores,
151
+ analysis: m.analysis ? {
152
+ overround: m.analysis.overround,
153
+ hoursToClose: m.analysis.hoursToClose,
154
+ volumePerHour: m.analysis.volumePerHour,
155
+ priceLevel: m.analysis.priceLevel,
156
+ edgeType: m.analysis.edgeType
157
+ } : void 0,
158
+ recommendation: m.recommendation ? { action: m.recommendation.action, confidence: m.recommendation.confidence } : void 0,
159
+ pipeline: m.pipeline ? { action: m.pipeline.action, score: m.pipeline.score, kelly: m.pipeline.kelly } : void 0
160
+ };
161
+ });
162
+ }
163
+ return jsonResult(raw);
164
+ } catch (e) {
165
+ return errorResult(e.message);
166
+ }
167
+ }
168
+ }
169
+ ];
170
+ }
171
+
172
+ // src/agent-tools/tools/polymarket-execution.ts
173
+ var CLOB_API2 = "https://clob.polymarket.com";
174
+ async function initExecutionDB(db) {
175
+ if (!db) return;
176
+ const stmts = [
177
+ `CREATE TABLE IF NOT EXISTS poly_sniper_orders (
178
+ id TEXT PRIMARY KEY,
179
+ agent_id TEXT NOT NULL,
180
+ token_id TEXT NOT NULL,
181
+ side TEXT NOT NULL,
182
+ target_price REAL NOT NULL,
183
+ max_price REAL,
184
+ trail_amount REAL DEFAULT 0.01,
185
+ size_usdc REAL NOT NULL,
186
+ cancel_price REAL,
187
+ status TEXT DEFAULT 'active',
188
+ filled_size REAL DEFAULT 0,
189
+ filled_avg_price REAL DEFAULT 0,
190
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP,
191
+ updated_at TEXT DEFAULT CURRENT_TIMESTAMP
192
+ )`,
193
+ `CREATE TABLE IF NOT EXISTS poly_scale_orders (
194
+ id TEXT PRIMARY KEY,
195
+ agent_id TEXT NOT NULL,
196
+ token_id TEXT NOT NULL,
197
+ side TEXT NOT NULL,
198
+ total_size REAL NOT NULL,
199
+ slices INTEGER NOT NULL,
200
+ interval_seconds INTEGER NOT NULL,
201
+ strategy TEXT DEFAULT 'twap',
202
+ completed_slices INTEGER DEFAULT 0,
203
+ filled_size REAL DEFAULT 0,
204
+ avg_price REAL DEFAULT 0,
205
+ status TEXT DEFAULT 'active',
206
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP
207
+ )`,
208
+ `CREATE TABLE IF NOT EXISTS poly_exit_rules (
209
+ id TEXT PRIMARY KEY,
210
+ agent_id TEXT NOT NULL,
211
+ token_id TEXT NOT NULL,
212
+ entry_price REAL NOT NULL,
213
+ position_size REAL NOT NULL,
214
+ take_profit REAL,
215
+ stop_loss REAL,
216
+ trailing_stop_pct REAL,
217
+ time_exit TEXT,
218
+ highest_price REAL,
219
+ status TEXT DEFAULT 'active',
220
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP
221
+ )`,
222
+ `CREATE TABLE IF NOT EXISTS poly_hedges (
223
+ id TEXT PRIMARY KEY,
224
+ agent_id TEXT NOT NULL,
225
+ primary_token TEXT NOT NULL,
226
+ hedge_token TEXT NOT NULL,
227
+ primary_side TEXT NOT NULL,
228
+ hedge_side TEXT NOT NULL,
229
+ primary_size REAL NOT NULL,
230
+ hedge_size REAL NOT NULL,
231
+ hedge_ratio REAL NOT NULL,
232
+ correlation REAL,
233
+ status TEXT DEFAULT 'active',
234
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP
235
+ )`
236
+ ];
237
+ for (const sql of stmts) {
238
+ await safeDbDDL(db, sql);
239
+ }
240
+ }
241
+ function createPolymarketExecutionTools(options) {
242
+ const db = options.engineDb;
243
+ const agentId = options.agentId || "default";
244
+ let dbReady = false;
245
+ async function ensureDB() {
246
+ if (dbReady || !db) return;
247
+ await initExecutionDB(db);
248
+ dbReady = true;
249
+ }
250
+ const tools = [];
251
+ tools.push({
252
+ name: "poly_sniper",
253
+ label: "Sniper Order",
254
+ description: "Smart limit order that auto-adjusts. Set a target price and the sniper will trail the best bid/ask by a small amount, automatically adjusting the limit price to stay competitive. Cancels if price hits a ceiling. Perfect for accumulating at the best possible price.",
255
+ category: "enterprise",
256
+ parameters: {
257
+ type: "object",
258
+ properties: {
259
+ action: { type: "string", enum: ["create", "list", "cancel", "status"], description: "Action to perform" },
260
+ id: { type: "string", description: "Sniper order ID (for cancel/status)" },
261
+ token_id: { type: "string", description: "Token ID (for create)" },
262
+ side: { type: "string", enum: ["BUY", "SELL"], description: "Order side" },
263
+ target_price: { type: "number", description: "Target price to buy/sell at" },
264
+ max_price: { type: "number", description: "Maximum price willing to pay (for BUY) or minimum to accept (for SELL)" },
265
+ trail_amount: { type: "number", description: "How much to trail best bid/ask by (default: 0.01)", default: 0.01 },
266
+ size_usdc: { type: "number", description: "Total USDC to spend" },
267
+ cancel_price: { type: "number", description: "Auto-cancel if price reaches this level" }
268
+ },
269
+ required: ["action"]
270
+ },
271
+ execute: async (params) => {
272
+ await ensureDB();
273
+ if (!db) return errorResult("No DB available");
274
+ const action = params.action;
275
+ if (action === "create") {
276
+ if (!params.token_id || !params.side || !params.target_price || !params.size_usdc) {
277
+ return errorResult("token_id, side, target_price, and size_usdc required");
278
+ }
279
+ const id = `snipe_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
280
+ try {
281
+ await safeDbExec(
282
+ db,
283
+ `INSERT INTO poly_sniper_orders (id, agent_id, token_id, side, target_price, max_price, trail_amount, size_usdc, cancel_price)
284
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
285
+ id,
286
+ agentId,
287
+ params.token_id,
288
+ params.side,
289
+ params.target_price,
290
+ params.max_price || null,
291
+ params.trail_amount || 0.01,
292
+ params.size_usdc,
293
+ params.cancel_price || null
294
+ );
295
+ let currentBook = null;
296
+ try {
297
+ const book = await cachedFetchJSON(`${CLOB_API2}/book?token_id=${params.token_id}`);
298
+ const bestBid = Math.max(...(book?.bids || []).map((b) => parseFloat(b.price)));
299
+ const bestAsk = Math.min(...(book?.asks || []).map((a) => parseFloat(a.price)));
300
+ currentBook = { bestBid, bestAsk, spread: +(bestAsk - bestBid).toFixed(4) };
301
+ } catch {
302
+ }
303
+ return jsonResult({
304
+ created: id,
305
+ token_id: params.token_id,
306
+ side: params.side,
307
+ target_price: params.target_price,
308
+ max_price: params.max_price,
309
+ trail_amount: params.trail_amount || 0.01,
310
+ size_usdc: params.size_usdc,
311
+ cancel_price: params.cancel_price,
312
+ current_book: currentBook,
313
+ note: "Sniper order created. In production, this would monitor the orderbook and auto-adjust your limit order. Currently tracked in DB \u2014 use poly_sniper with action=status to check. Execute manually or wait for the automated execution loop.",
314
+ execution_strategy: params.side === "BUY" ? `Will place limit bid at best_bid + ${params.trail_amount}. If price drops to ${params.target_price}, will fill. Cancels if price hits ${params.cancel_price || "never"}.` : `Will place limit ask at best_ask - ${params.trail_amount}. If price rises to ${params.target_price}, will fill. Cancels if price hits ${params.cancel_price || "never"}.`
315
+ });
316
+ } catch (e) {
317
+ return errorResult(e.message);
318
+ }
319
+ }
320
+ if (action === "cancel") {
321
+ if (!params.id) return errorResult("id required");
322
+ try {
323
+ await safeDbExec(
324
+ db,
325
+ "UPDATE poly_sniper_orders SET status = 'cancelled', updated_at = CURRENT_TIMESTAMP WHERE id = ? AND agent_id = ?",
326
+ params.id,
327
+ agentId
328
+ );
329
+ return jsonResult({ cancelled: params.id });
330
+ } catch (e) {
331
+ return errorResult(e.message);
332
+ }
333
+ }
334
+ if (action === "status") {
335
+ if (!params.id) return errorResult("id required");
336
+ try {
337
+ const row = await safeDbGet(db, "SELECT * FROM poly_sniper_orders WHERE id = ? AND agent_id = ?", params.id, agentId);
338
+ if (!row) return errorResult("Sniper order not found");
339
+ return jsonResult(row);
340
+ } catch (e) {
341
+ return errorResult(e.message);
342
+ }
343
+ }
344
+ try {
345
+ const rows = await safeDbQuery(db, "SELECT * FROM poly_sniper_orders WHERE agent_id = ? AND status = 'active' ORDER BY created_at DESC", agentId);
346
+ return jsonResult({ active_snipers: rows, total: rows.length });
347
+ } catch (e) {
348
+ return errorResult(e.message);
349
+ }
350
+ }
351
+ });
352
+ tools.push({
353
+ name: "poly_scale_in",
354
+ label: "Scale In (TWAP/VWAP)",
355
+ description: "Execute a large order by splitting it into smaller slices over time (TWAP) or proportional to volume (VWAP). Minimizes market impact when building or unwinding large positions. Essential for $1000+ orders in thin markets.",
356
+ category: "enterprise",
357
+ parameters: {
358
+ type: "object",
359
+ properties: {
360
+ action: { type: "string", enum: ["create", "list", "cancel", "status"], description: "Action" },
361
+ id: { type: "string", description: "Scale order ID (for cancel/status)" },
362
+ token_id: { type: "string", description: "Token ID (for create)" },
363
+ side: { type: "string", enum: ["BUY", "SELL"], description: "Order side" },
364
+ total_size: { type: "number", description: "Total USDC to trade" },
365
+ slices: { type: "number", description: "Number of slices (default: 10)", default: 10 },
366
+ interval_minutes: { type: "number", description: "Minutes between slices (default: 5)", default: 5 },
367
+ strategy: { type: "string", enum: ["twap", "vwap", "aggressive", "passive"], default: "twap" }
368
+ },
369
+ required: ["action"]
370
+ },
371
+ execute: async (params) => {
372
+ await ensureDB();
373
+ if (!db) return errorResult("No DB available");
374
+ const action = params.action;
375
+ if (action === "create") {
376
+ if (!params.token_id || !params.side || !params.total_size) {
377
+ return errorResult("token_id, side, and total_size required");
378
+ }
379
+ const slices = params.slices || 10;
380
+ const intervalSec = (params.interval_minutes || 5) * 60;
381
+ const sliceSize = params.total_size / slices;
382
+ const id = `scale_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
383
+ try {
384
+ await safeDbExec(
385
+ db,
386
+ `INSERT INTO poly_scale_orders (id, agent_id, token_id, side, total_size, slices, interval_seconds, strategy)
387
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
388
+ id,
389
+ agentId,
390
+ params.token_id,
391
+ params.side,
392
+ params.total_size,
393
+ slices,
394
+ intervalSec,
395
+ params.strategy || "twap"
396
+ );
397
+ const totalDuration = slices * (params.interval_minutes || 5);
398
+ return jsonResult({
399
+ created: id,
400
+ token_id: params.token_id,
401
+ side: params.side,
402
+ total_size: params.total_size,
403
+ slices,
404
+ slice_size: +sliceSize.toFixed(2),
405
+ interval_minutes: params.interval_minutes || 5,
406
+ strategy: params.strategy || "twap",
407
+ estimated_duration_minutes: totalDuration,
408
+ schedule: Array.from({ length: Math.min(slices, 10) }, (_, i) => ({
409
+ slice: i + 1,
410
+ size_usdc: +sliceSize.toFixed(2),
411
+ execute_at: `+${i * (params.interval_minutes || 5)}min`
412
+ })),
413
+ note: `Scale-in plan created. ${slices} slices of $${sliceSize.toFixed(2)} every ${params.interval_minutes || 5} minutes = ${totalDuration} minutes total. Execute each slice using poly_place_order or set up a cron job for automated execution.`
414
+ });
415
+ } catch (e) {
416
+ return errorResult(e.message);
417
+ }
418
+ }
419
+ if (action === "cancel") {
420
+ if (!params.id) return errorResult("id required");
421
+ try {
422
+ await safeDbExec(db, "UPDATE poly_scale_orders SET status = 'cancelled' WHERE id = ? AND agent_id = ?", params.id, agentId);
423
+ return jsonResult({ cancelled: params.id });
424
+ } catch (e) {
425
+ return errorResult(e.message);
426
+ }
427
+ }
428
+ if (action === "status") {
429
+ if (!params.id) return errorResult("id required");
430
+ try {
431
+ const row = await safeDbGet(db, "SELECT * FROM poly_scale_orders WHERE id = ? AND agent_id = ?", params.id, agentId);
432
+ if (!row) return errorResult("Scale order not found");
433
+ const remaining = row.slices - row.completed_slices;
434
+ return jsonResult({
435
+ ...row,
436
+ remaining_slices: remaining,
437
+ remaining_usdc: +(row.total_size - row.filled_size).toFixed(2),
438
+ progress_pct: +(row.completed_slices / row.slices * 100).toFixed(1)
439
+ });
440
+ } catch (e) {
441
+ return errorResult(e.message);
442
+ }
443
+ }
444
+ try {
445
+ const rows = await safeDbQuery(db, "SELECT * FROM poly_scale_orders WHERE agent_id = ? AND status = 'active' ORDER BY created_at DESC", agentId);
446
+ return jsonResult({ active_scales: rows, total: rows.length });
447
+ } catch (e) {
448
+ return errorResult(e.message);
449
+ }
450
+ }
451
+ });
452
+ tools.push({
453
+ name: "poly_hedge",
454
+ label: "Hedge Position",
455
+ description: "Create a hedge by taking an opposing position in a correlated market. Reduces directional risk while capturing spread between related markets. Specify the primary and hedge tokens with a ratio.",
456
+ category: "enterprise",
457
+ parameters: {
458
+ type: "object",
459
+ properties: {
460
+ action: { type: "string", enum: ["create", "list", "close", "analyze"], description: "Action" },
461
+ id: { type: "string", description: "Hedge ID (for close)" },
462
+ primary_token: { type: "string", description: "Primary position token ID" },
463
+ hedge_token: { type: "string", description: "Hedge token ID (correlated market)" },
464
+ primary_side: { type: "string", enum: ["BUY", "SELL"], description: "Primary position side" },
465
+ primary_size: { type: "number", description: "Primary position size in USDC" },
466
+ hedge_ratio: { type: "number", description: "Hedge ratio (0.5 = half hedge, 1.0 = full hedge, default: 0.5)", default: 0.5 }
467
+ },
468
+ required: ["action"]
469
+ },
470
+ execute: async (params) => {
471
+ await ensureDB();
472
+ if (!db) return errorResult("No DB available");
473
+ const action = params.action;
474
+ if (action === "create") {
475
+ if (!params.primary_token || !params.hedge_token || !params.primary_side || !params.primary_size) {
476
+ return errorResult("primary_token, hedge_token, primary_side, primary_size required");
477
+ }
478
+ const hedgeRatio = params.hedge_ratio || 0.5;
479
+ const hedgeSide = params.primary_side === "BUY" ? "SELL" : "BUY";
480
+ const hedgeSize = +(params.primary_size * hedgeRatio).toFixed(2);
481
+ const id = `hedge_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
482
+ try {
483
+ await safeDbExec(
484
+ db,
485
+ `INSERT INTO poly_hedges (id, agent_id, primary_token, hedge_token, primary_side, hedge_side, primary_size, hedge_size, hedge_ratio)
486
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
487
+ id,
488
+ agentId,
489
+ params.primary_token,
490
+ params.hedge_token,
491
+ params.primary_side,
492
+ hedgeSide,
493
+ params.primary_size,
494
+ hedgeSize,
495
+ hedgeRatio
496
+ );
497
+ return jsonResult({
498
+ created: id,
499
+ primary: { token: params.primary_token, side: params.primary_side, size: params.primary_size },
500
+ hedge: { token: params.hedge_token, side: hedgeSide, size: hedgeSize },
501
+ hedge_ratio: hedgeRatio,
502
+ net_exposure: +(params.primary_size - hedgeSize).toFixed(2),
503
+ note: "Hedge plan created. Execute both legs: (1) poly_place_order for primary, (2) poly_place_order for hedge. The hedge reduces your directional risk by " + (hedgeRatio * 100).toFixed(0) + "%."
504
+ });
505
+ } catch (e) {
506
+ return errorResult(e.message);
507
+ }
508
+ }
509
+ if (action === "analyze") {
510
+ if (!params.primary_token || !params.hedge_token) return errorResult("primary_token and hedge_token required");
511
+ try {
512
+ const [book1, book2] = await Promise.all([
513
+ cachedFetchJSON(`${CLOB_API2}/book?token_id=${params.primary_token}`).catch(() => null),
514
+ cachedFetchJSON(`${CLOB_API2}/book?token_id=${params.hedge_token}`).catch(() => null)
515
+ ]);
516
+ const mid1 = book1 ? (Math.max(...(book1.bids || []).map((b) => parseFloat(b.price))) + Math.min(...(book1.asks || []).map((a) => parseFloat(a.price)))) / 2 : 0;
517
+ const mid2 = book2 ? (Math.max(...(book2.bids || []).map((b) => parseFloat(b.price))) + Math.min(...(book2.asks || []).map((a) => parseFloat(a.price)))) / 2 : 0;
518
+ return jsonResult({
519
+ primary: { token: params.primary_token, mid_price: +mid1.toFixed(4) },
520
+ hedge: { token: params.hedge_token, mid_price: +mid2.toFixed(4) },
521
+ price_spread: +Math.abs(mid1 - mid2).toFixed(4),
522
+ recommended_ratio: mid1 > 0 && mid2 > 0 ? +(mid1 / mid2).toFixed(2) : 0.5,
523
+ note: "If tokens are from the same market (YES/NO), optimal hedge ratio is the price ratio. If from correlated markets, use poly_market_correlation first."
524
+ });
525
+ } catch (e) {
526
+ return errorResult(e.message);
527
+ }
528
+ }
529
+ if (action === "close") {
530
+ if (!params.id) return errorResult("id required");
531
+ try {
532
+ await safeDbExec(db, "UPDATE poly_hedges SET status = 'closed' WHERE id = ? AND agent_id = ?", params.id, agentId);
533
+ return jsonResult({ closed: params.id, note: "Hedge marked as closed. Unwind both legs manually using poly_place_order." });
534
+ } catch (e) {
535
+ return errorResult(e.message);
536
+ }
537
+ }
538
+ try {
539
+ const rows = await safeDbQuery(db, "SELECT * FROM poly_hedges WHERE agent_id = ? AND status = 'active' ORDER BY created_at DESC", agentId);
540
+ return jsonResult({ active_hedges: rows, total: rows.length });
541
+ } catch (e) {
542
+ return errorResult(e.message);
543
+ }
544
+ }
545
+ });
546
+ tools.push({
547
+ name: "poly_exit_strategy",
548
+ label: "Exit Strategy",
549
+ description: "Set automated exit rules for a position: take-profit, stop-loss, trailing stop, and time-based exits. When a condition triggers, the tool flags the position for immediate action. Never hold a position without an exit plan.",
550
+ category: "enterprise",
551
+ parameters: {
552
+ type: "object",
553
+ properties: {
554
+ action: { type: "string", enum: ["create", "list", "remove", "check"], description: "Action" },
555
+ id: { type: "string", description: "Exit rule ID (for remove)" },
556
+ token_id: { type: "string", description: "Token ID (for create/check)" },
557
+ entry_price: { type: "number", description: "Your entry price" },
558
+ position_size: { type: "number", description: "Position size in shares" },
559
+ take_profit: { type: "number", description: "Take profit price" },
560
+ stop_loss: { type: "number", description: "Stop loss price" },
561
+ trailing_stop_pct: { type: "number", description: "Trailing stop as % from highest price (e.g., 10 = 10%)" },
562
+ time_exit: { type: "string", description: 'Time-based exit: ISO date or relative like "24h", "7d"' }
563
+ },
564
+ required: ["action"]
565
+ },
566
+ execute: async (params) => {
567
+ await ensureDB();
568
+ if (!db) return errorResult("No DB available");
569
+ const action = params.action;
570
+ if (action === "create") {
571
+ if (!params.token_id || !params.entry_price) return errorResult("token_id and entry_price required");
572
+ if (!params.take_profit && !params.stop_loss && !params.trailing_stop_pct && !params.time_exit) {
573
+ return errorResult("At least one exit condition required: take_profit, stop_loss, trailing_stop_pct, or time_exit");
574
+ }
575
+ const id = `exit_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
576
+ let timeExit = params.time_exit || null;
577
+ if (timeExit && !timeExit.includes("T")) {
578
+ const match = timeExit.match(/^(\d+)(h|d|m)$/);
579
+ if (match) {
580
+ const ms = parseInt(match[1]) * (match[2] === "h" ? 36e5 : match[2] === "d" ? 864e5 : 6e4);
581
+ timeExit = new Date(Date.now() + ms).toISOString();
582
+ }
583
+ }
584
+ try {
585
+ await safeDbExec(
586
+ db,
587
+ `INSERT INTO poly_exit_rules (id, agent_id, token_id, entry_price, position_size, take_profit, stop_loss, trailing_stop_pct, time_exit, highest_price)
588
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
589
+ id,
590
+ agentId,
591
+ params.token_id,
592
+ params.entry_price,
593
+ params.position_size || 0,
594
+ params.take_profit || null,
595
+ params.stop_loss || null,
596
+ params.trailing_stop_pct || null,
597
+ timeExit,
598
+ params.entry_price
599
+ );
600
+ return jsonResult({
601
+ created: id,
602
+ token_id: params.token_id,
603
+ entry_price: params.entry_price,
604
+ rules: {
605
+ take_profit: params.take_profit || null,
606
+ stop_loss: params.stop_loss || null,
607
+ trailing_stop_pct: params.trailing_stop_pct || null,
608
+ time_exit: timeExit
609
+ },
610
+ note: "Exit rules set. Check triggers periodically with action=check, or set up a heartbeat/cron to auto-check."
611
+ });
612
+ } catch (e) {
613
+ return errorResult(e.message);
614
+ }
615
+ }
616
+ if (action === "check") {
617
+ try {
618
+ const rules = params.token_id ? await safeDbQuery(db, "SELECT * FROM poly_exit_rules WHERE agent_id = ? AND token_id = ? AND status = 'active'", agentId, params.token_id) : await safeDbQuery(db, "SELECT * FROM poly_exit_rules WHERE agent_id = ? AND status = 'active'", agentId);
619
+ if (!rules.length) return jsonResult({ triggers: [], note: "No active exit rules" });
620
+ const triggers = [];
621
+ const tokenIds = [...new Set(rules.map((r) => r.token_id))];
622
+ const prices = {};
623
+ await Promise.all(tokenIds.map(async (tid) => {
624
+ try {
625
+ const book = await cachedFetchJSON(`${CLOB_API2}/book?token_id=${tid}`);
626
+ const bestBid = Math.max(...(book?.bids || []).map((b) => parseFloat(b.price)), 0);
627
+ const bestAsk = Math.min(...(book?.asks || []).map((a) => parseFloat(a.price)), 1);
628
+ prices[tid] = (bestBid + bestAsk) / 2;
629
+ } catch {
630
+ }
631
+ }));
632
+ const now = /* @__PURE__ */ new Date();
633
+ for (const rule of rules) {
634
+ const currentPrice = prices[rule.token_id];
635
+ if (!currentPrice) continue;
636
+ if (currentPrice > (rule.highest_price || 0)) {
637
+ try {
638
+ await safeDbExec(db, "UPDATE poly_exit_rules SET highest_price = ? WHERE id = ?", currentPrice, rule.id);
639
+ } catch {
640
+ }
641
+ }
642
+ if (rule.take_profit && currentPrice >= rule.take_profit) {
643
+ triggers.push({
644
+ id: rule.id,
645
+ type: "TAKE_PROFIT",
646
+ token_id: rule.token_id,
647
+ trigger_price: rule.take_profit,
648
+ current_price: currentPrice,
649
+ entry_price: rule.entry_price,
650
+ pnl_pct: +((currentPrice - rule.entry_price) / rule.entry_price * 100).toFixed(2),
651
+ action: "SELL NOW \u2014 Take profit target reached"
652
+ });
653
+ }
654
+ if (rule.stop_loss && currentPrice <= rule.stop_loss) {
655
+ triggers.push({
656
+ id: rule.id,
657
+ type: "STOP_LOSS",
658
+ token_id: rule.token_id,
659
+ trigger_price: rule.stop_loss,
660
+ current_price: currentPrice,
661
+ entry_price: rule.entry_price,
662
+ pnl_pct: +((currentPrice - rule.entry_price) / rule.entry_price * 100).toFixed(2),
663
+ action: "SELL NOW \u2014 Stop loss triggered"
664
+ });
665
+ }
666
+ if (rule.trailing_stop_pct && rule.highest_price) {
667
+ const trailPrice = rule.highest_price * (1 - rule.trailing_stop_pct / 100);
668
+ if (currentPrice <= trailPrice) {
669
+ triggers.push({
670
+ id: rule.id,
671
+ type: "TRAILING_STOP",
672
+ token_id: rule.token_id,
673
+ highest_price: rule.highest_price,
674
+ trail_pct: rule.trailing_stop_pct,
675
+ trigger_price: +trailPrice.toFixed(4),
676
+ current_price: currentPrice,
677
+ action: `SELL NOW \u2014 Price dropped ${rule.trailing_stop_pct}% from high of ${rule.highest_price}`
678
+ });
679
+ }
680
+ }
681
+ if (rule.time_exit && new Date(rule.time_exit) <= now) {
682
+ triggers.push({
683
+ id: rule.id,
684
+ type: "TIME_EXIT",
685
+ token_id: rule.token_id,
686
+ time_exit: rule.time_exit,
687
+ current_price: currentPrice,
688
+ entry_price: rule.entry_price,
689
+ action: "SELL NOW \u2014 Time-based exit triggered"
690
+ });
691
+ }
692
+ }
693
+ return jsonResult({
694
+ rules_checked: rules.length,
695
+ triggers_fired: triggers.length,
696
+ triggers,
697
+ all_clear: triggers.length === 0
698
+ });
699
+ } catch (e) {
700
+ return errorResult(e.message);
701
+ }
702
+ }
703
+ if (action === "remove") {
704
+ if (!params.id) return errorResult("id required");
705
+ try {
706
+ await safeDbExec(db, "UPDATE poly_exit_rules SET status = 'removed' WHERE id = ? AND agent_id = ?", params.id, agentId);
707
+ return jsonResult({ removed: params.id });
708
+ } catch (e) {
709
+ return errorResult(e.message);
710
+ }
711
+ }
712
+ try {
713
+ const rows = await safeDbQuery(db, "SELECT * FROM poly_exit_rules WHERE agent_id = ? AND status = 'active' ORDER BY created_at DESC", agentId);
714
+ return jsonResult({ active_rules: rows, total: rows.length });
715
+ } catch (e) {
716
+ return errorResult(e.message);
717
+ }
718
+ }
719
+ });
720
+ return tools;
721
+ }
722
+
723
+ // src/agent-tools/tools/polymarket-social.ts
724
+ function createPolymarketSocialTools(_opts) {
725
+ return [
726
+ {
727
+ name: "poly_twitter_sentiment",
728
+ description: "Analyze Twitter/X sentiment for a topic via Google News RSS proxy. Returns overall sentiment, mention count, and topics detected.",
729
+ parameters: {
730
+ type: "object",
731
+ properties: {
732
+ query: { type: "string", description: "Search query" },
733
+ include_polymarket: { type: "boolean", description: 'Also search with "polymarket" appended', default: true }
734
+ },
735
+ required: ["query"]
736
+ },
737
+ async execute(_id, p) {
738
+ try {
739
+ const raw = await analyzeTwitterSentiment(p.query, p.include_polymarket);
740
+ return jsonResult({
741
+ query: raw.query,
742
+ overall_sentiment: raw.overall_sentiment,
743
+ sentiment_label: raw.sentiment_label,
744
+ total_mentions: raw.total_mentions,
745
+ signal: raw.signal,
746
+ topics_detected: raw.topics_detected,
747
+ // Only top 5 items per query, title+sentiment only
748
+ results: (raw.results || []).map((r) => ({
749
+ query: r.query,
750
+ avg_sentiment: r.avg_sentiment,
751
+ volume: r.volume,
752
+ items: (r.items || []).slice(0, 5).map((i) => ({
753
+ title: i.title,
754
+ sentiment: i.sentiment,
755
+ source: i.source
756
+ }))
757
+ }))
758
+ });
759
+ } catch (e) {
760
+ return errorResult(e.message);
761
+ }
762
+ }
763
+ },
764
+ {
765
+ name: "poly_polymarket_comments",
766
+ description: "Analyze sentiment in Polymarket market comments section. Returns bullish/bearish breakdown and top comments.",
767
+ parameters: {
768
+ type: "object",
769
+ properties: {
770
+ market_slug: { type: "string", description: "Market slug" }
771
+ },
772
+ required: ["market_slug"]
773
+ },
774
+ async execute(_id, p) {
775
+ try {
776
+ return jsonResult(await analyzePolymarketComments(p.market_slug));
777
+ } catch (e) {
778
+ return errorResult(e.message);
779
+ }
780
+ }
781
+ },
782
+ {
783
+ name: "poly_reddit_pulse",
784
+ description: "Monitor Reddit discussion on a topic across subreddits. Returns sentiment, trending posts, and engagement metrics.",
785
+ parameters: {
786
+ type: "object",
787
+ properties: {
788
+ query: { type: "string", description: "Search query" },
789
+ subreddits: { type: "array", items: { type: "string" }, description: "Subreddits (default: polymarket, politics, sports, worldnews)" },
790
+ timeframe: { type: "string", enum: ["hour", "day", "week", "month"], default: "day" }
791
+ },
792
+ required: ["query"]
793
+ },
794
+ async execute(_id, p) {
795
+ try {
796
+ return jsonResult(await monitorRedditPulse(p.query, p.subreddits, p.timeframe));
797
+ } catch (e) {
798
+ return errorResult(e.message);
799
+ }
800
+ }
801
+ },
802
+ {
803
+ name: "poly_telegram_monitor",
804
+ description: "Monitor public Telegram channels for discussion on a topic. Scrapes recent messages and scores sentiment.",
805
+ parameters: {
806
+ type: "object",
807
+ properties: {
808
+ channels: { type: "array", items: { type: "string" }, description: "Channel handles (e.g. @polymarket_alerts)" },
809
+ query: { type: "string", description: "Optional filter query" },
810
+ limit: { type: "number", default: 20 }
811
+ },
812
+ required: ["channels"]
813
+ },
814
+ async execute(_id, p) {
815
+ try {
816
+ return jsonResult(await monitorTelegram(p.channels, p.query, p.limit));
817
+ } catch (e) {
818
+ return errorResult(e.message);
819
+ }
820
+ }
821
+ },
822
+ {
823
+ name: "poly_social_velocity",
824
+ description: "Measure social discussion velocity (spike detection). Compares recent vs baseline mention rates to detect surges in attention.",
825
+ parameters: {
826
+ type: "object",
827
+ properties: {
828
+ topic: { type: "string", description: "Topic to measure" },
829
+ compare_topic: { type: "string", description: "Optional comparison topic" }
830
+ },
831
+ required: ["topic"]
832
+ },
833
+ async execute(_id, p) {
834
+ try {
835
+ return jsonResult(await measureSocialVelocity(p.topic, p.compare_topic));
836
+ } catch (e) {
837
+ return errorResult(e.message);
838
+ }
839
+ }
840
+ }
841
+ ];
842
+ }
843
+
844
+ // src/agent-tools/tools/polymarket-feeds.ts
845
+ function createPolymarketFeedsTools(opts) {
846
+ const getDb = () => opts?.engineDb;
847
+ return [
848
+ {
849
+ name: "poly_calendar_events",
850
+ description: "Manage an event calendar for tracking market-moving dates: add/remove/list events that could affect your positions.",
851
+ parameters: {
852
+ type: "object",
853
+ properties: {
854
+ action: { type: "string", enum: ["add", "remove", "list", "upcoming"], description: "Calendar action" },
855
+ title: { type: "string", description: "Event title (for add)" },
856
+ date: { type: "string", description: "Event date ISO (for add)" },
857
+ category: { type: "string", description: "Category: politics, sports, crypto, economics, legal, other" },
858
+ market_slugs: { type: "array", items: { type: "string" }, description: "Related market slugs" },
859
+ notes: { type: "string", description: "Additional notes" },
860
+ event_id: { type: "string", description: "Event ID (for remove)" },
861
+ days_ahead: { type: "number", description: "Days ahead to look (for upcoming)", default: 7 }
862
+ },
863
+ required: ["action"]
864
+ },
865
+ async execute(_id, p) {
866
+ try {
867
+ const db = getDb();
868
+ if (!db) return errorResult("No database available");
869
+ await safeDbDDL(db, `CREATE TABLE IF NOT EXISTS poly_calendar_events (
870
+ id TEXT PRIMARY KEY, title TEXT NOT NULL, event_date TEXT NOT NULL,
871
+ category TEXT DEFAULT 'other', market_slugs TEXT, notes TEXT,
872
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP
873
+ )`);
874
+ if (p.action === "add") {
875
+ if (!p.title || !p.date) return errorResult("Need title and date");
876
+ const id = `evt_${Date.now()}`;
877
+ await safeDbExec(
878
+ db,
879
+ `INSERT INTO poly_calendar_events (id, title, event_date, category, market_slugs, notes) VALUES (?, ?, ?, ?, ?, ?)`,
880
+ [id, p.title, p.date, p.category || "other", JSON.stringify(p.market_slugs || []), p.notes || ""]
881
+ );
882
+ return jsonResult({ id, title: p.title, date: p.date, status: "added" });
883
+ }
884
+ if (p.action === "remove") {
885
+ if (!p.event_id) return errorResult("Need event_id");
886
+ await safeDbExec(db, `DELETE FROM poly_calendar_events WHERE id = ?`, [p.event_id]);
887
+ return jsonResult({ id: p.event_id, status: "removed" });
888
+ }
889
+ if (p.action === "upcoming") {
890
+ const days = p.days_ahead || 7;
891
+ const rows2 = await safeDbQuery(db, `SELECT * FROM poly_calendar_events WHERE event_date >= CURRENT_DATE AND event_date <= CURRENT_DATE + INTERVAL '${days} days' ORDER BY event_date`);
892
+ return jsonResult({ upcoming: rows2, days_ahead: days });
893
+ }
894
+ const rows = await safeDbQuery(db, `SELECT * FROM poly_calendar_events ORDER BY event_date DESC LIMIT 50`);
895
+ return jsonResult({ events: rows, total: rows.length });
896
+ } catch (e) {
897
+ return errorResult(e.message);
898
+ }
899
+ }
900
+ },
901
+ {
902
+ name: "poly_official_sources",
903
+ description: "Fetch data from official sources: whitehouse, scotus, sec, fed, espn, noaa, congress, or any custom RSS/Atom URL.",
904
+ parameters: {
905
+ type: "object",
906
+ properties: {
907
+ source: { type: "string", description: 'Source name or "custom"', enum: ["whitehouse", "scotus", "sec", "fed", "espn", "noaa", "congress", "custom"] },
908
+ query: { type: "string", description: "Filter results by keyword" },
909
+ custom_url: { type: "string", description: "Custom RSS/Atom URL (when source=custom)" }
910
+ },
911
+ required: ["source"]
912
+ },
913
+ async execute(_id, p) {
914
+ try {
915
+ return jsonResult(await fetchOfficialSource(p.source, p.query, p.custom_url));
916
+ } catch (e) {
917
+ return errorResult(e.message);
918
+ }
919
+ }
920
+ },
921
+ {
922
+ name: "poly_odds_aggregator",
923
+ description: "Compare Polymarket odds against external sources. Finds divergences between Polymarket pricing and other platforms.",
924
+ parameters: {
925
+ type: "object",
926
+ properties: {
927
+ market_question: { type: "string", description: "Market question to search for" },
928
+ polymarket_price: { type: "number", description: "Current Polymarket YES price" }
929
+ },
930
+ required: ["market_question"]
931
+ },
932
+ async execute(_id, p) {
933
+ try {
934
+ return jsonResult(await compareOdds(p.market_question, p.polymarket_price));
935
+ } catch (e) {
936
+ return errorResult(e.message);
937
+ }
938
+ }
939
+ },
940
+ {
941
+ name: "poly_resolution_tracker",
942
+ description: "Track resolution details for a market: source URLs, resolution type, criteria clarity, and risk factors.",
943
+ parameters: {
944
+ type: "object",
945
+ properties: {
946
+ market_slug: { type: "string", description: "Market slug" },
947
+ condition_id: { type: "string", description: "Or condition ID" }
948
+ }
949
+ },
950
+ async execute(_id, p) {
951
+ try {
952
+ return jsonResult(await trackResolution(p.market_slug, p.condition_id));
953
+ } catch (e) {
954
+ return errorResult(e.message);
955
+ }
956
+ }
957
+ },
958
+ {
959
+ name: "poly_breaking_news",
960
+ description: "Fetch breaking news from AP, Reuters, BBC, CNN. Filter by topics and time window. Scores market relevance.",
961
+ parameters: {
962
+ type: "object",
963
+ properties: {
964
+ topics: { type: "array", items: { type: "string" }, description: "Topic keywords to filter" },
965
+ sources: { type: "array", items: { type: "string" }, description: "Sources: ap, reuters, bbc, cnn" },
966
+ since_minutes: { type: "number", description: "Look back N minutes", default: 60 }
967
+ }
968
+ },
969
+ async execute(_id, p) {
970
+ try {
971
+ return jsonResult(await fetchBreakingNews(p));
972
+ } catch (e) {
973
+ return errorResult(e.message);
974
+ }
975
+ }
976
+ }
977
+ ];
978
+ }
979
+
980
+ // src/agent-tools/tools/polymarket-onchain.ts
981
+ function createPolymarketOnchainTools(_opts) {
982
+ return [
983
+ {
984
+ name: "poly_whale_tracker",
985
+ description: "Track large trades (whale activity) on a specific token. Identifies significant buy/sell orders and unique whale wallets.",
986
+ parameters: {
987
+ type: "object",
988
+ properties: {
989
+ token_id: { type: "string", description: "Token ID to track" },
990
+ min_size: { type: "number", description: "Minimum trade value in USDC to classify as whale", default: 1e3 }
991
+ },
992
+ required: ["token_id"]
993
+ },
994
+ async execute(_id, p) {
995
+ try {
996
+ return jsonResult(await scanWhaleTrades(p.token_id, p.min_size));
997
+ } catch (e) {
998
+ return errorResult(e.message);
999
+ }
1000
+ }
1001
+ },
1002
+ {
1003
+ name: "poly_orderbook_depth",
1004
+ description: "Deep orderbook analysis: bid/ask depth at multiple levels, wall detection, spoofing indicators, and imbalance signals.",
1005
+ parameters: {
1006
+ type: "object",
1007
+ properties: {
1008
+ token_id: { type: "string", description: "Token ID to analyze" }
1009
+ },
1010
+ required: ["token_id"]
1011
+ },
1012
+ async execute(_id, p) {
1013
+ try {
1014
+ return jsonResult(await analyzeOrderbookDepth(p.token_id));
1015
+ } catch (e) {
1016
+ return errorResult(e.message);
1017
+ }
1018
+ }
1019
+ },
1020
+ {
1021
+ name: "poly_onchain_flow",
1022
+ description: "Analyze buy/sell flow over multiple time windows (5m, 15m, 1h, 4h, 24h). Detects net buying or selling pressure.",
1023
+ parameters: {
1024
+ type: "object",
1025
+ properties: {
1026
+ token_id: { type: "string", description: "Token ID to analyze" },
1027
+ windows: { type: "array", items: { type: "string" }, description: "Time windows (default: 1h, 4h, 24h)" }
1028
+ },
1029
+ required: ["token_id"]
1030
+ },
1031
+ async execute(_id, p) {
1032
+ try {
1033
+ return jsonResult(await analyzeFlow(p.token_id, p.windows));
1034
+ } catch (e) {
1035
+ return errorResult(e.message);
1036
+ }
1037
+ }
1038
+ },
1039
+ {
1040
+ name: "poly_wallet_profiler",
1041
+ description: "Profile a wallet address: total trades, volume, buy/sell ratio, unique markets, and recent activity.",
1042
+ parameters: {
1043
+ type: "object",
1044
+ properties: {
1045
+ wallet: { type: "string", description: "Wallet address to profile" },
1046
+ token_id: { type: "string", description: "Optional: filter to specific token" }
1047
+ },
1048
+ required: ["wallet"]
1049
+ },
1050
+ async execute(_id, p) {
1051
+ try {
1052
+ return jsonResult(await profileWallet(p.wallet, p.token_id));
1053
+ } catch (e) {
1054
+ return errorResult(e.message);
1055
+ }
1056
+ }
1057
+ },
1058
+ {
1059
+ name: "poly_liquidity_map",
1060
+ description: "Map liquidity across multiple tokens. Ranks by spread tightness and depth. Identifies best and worst markets to trade.",
1061
+ parameters: {
1062
+ type: "object",
1063
+ properties: {
1064
+ token_ids: { type: "array", items: { type: "string" }, description: "Token IDs to compare" }
1065
+ },
1066
+ required: ["token_ids"]
1067
+ },
1068
+ async execute(_id, p) {
1069
+ try {
1070
+ return jsonResult(await mapLiquidity(p.token_ids));
1071
+ } catch (e) {
1072
+ return errorResult(e.message);
1073
+ }
1074
+ }
1075
+ },
1076
+ {
1077
+ name: "poly_transaction_decoder",
1078
+ description: "Decode a Polygon transaction or list wallet transactions. Identifies Polymarket CTF interactions (position transfers, minting, redemption).",
1079
+ parameters: {
1080
+ type: "object",
1081
+ properties: {
1082
+ tx_hash: { type: "string", description: "Transaction hash to decode" },
1083
+ wallet: { type: "string", description: "Or list wallet transactions" },
1084
+ limit: { type: "number", description: "Max transactions (for wallet mode)", default: 20 }
1085
+ }
1086
+ },
1087
+ async execute(_id, p) {
1088
+ try {
1089
+ if (p.tx_hash) return jsonResult(await decodeTransaction(p.tx_hash));
1090
+ if (p.wallet) return jsonResult(await getWalletTransactions(p.wallet, p.limit));
1091
+ return errorResult("Provide tx_hash or wallet");
1092
+ } catch (e) {
1093
+ return errorResult(e.message);
1094
+ }
1095
+ }
1096
+ }
1097
+ ];
1098
+ }
1099
+
1100
+ // src/agent-tools/tools/polymarket-analytics.ts
1101
+ function createPolymarketAnalyticsTools(_opts) {
1102
+ return [
1103
+ {
1104
+ name: "poly_market_correlation",
1105
+ description: "Find price correlations between prediction market tokens. Identifies hedging opportunities and correlated risks.",
1106
+ parameters: {
1107
+ type: "object",
1108
+ properties: {
1109
+ token_ids: { type: "array", items: { type: "string" }, description: "Array of token IDs (2-10)" },
1110
+ min_correlation: { type: "number", description: "Minimum abs correlation to report", default: 0.5 }
1111
+ },
1112
+ required: ["token_ids"]
1113
+ },
1114
+ async execute(_id, p) {
1115
+ try {
1116
+ return jsonResult(await findCorrelations(p.token_ids, p.min_correlation));
1117
+ } catch (e) {
1118
+ return errorResult(e.message);
1119
+ }
1120
+ }
1121
+ },
1122
+ {
1123
+ name: "poly_arbitrage_scanner",
1124
+ description: "Scan for arbitrage opportunities: YES+NO != $1, multi-outcome mispricings, cross-market divergence.",
1125
+ parameters: {
1126
+ type: "object",
1127
+ properties: {
1128
+ market_slugs: { type: "array", items: { type: "string" }, description: "Specific markets to scan (or omit for top markets)" },
1129
+ scan_type: { type: "string", enum: ["yes_no", "cross_market", "multi_outcome", "all"], default: "all" },
1130
+ min_profit_pct: { type: "number", description: "Minimum profit % to flag", default: 0.5 }
1131
+ }
1132
+ },
1133
+ async execute(_id, p) {
1134
+ try {
1135
+ return jsonResult(await scanArbitrage(p));
1136
+ } catch (e) {
1137
+ return errorResult(e.message);
1138
+ }
1139
+ }
1140
+ },
1141
+ {
1142
+ name: "poly_regime_detector",
1143
+ description: "Detect market regime: TRENDING, MEAN_REVERTING, or RANDOM_WALK using Hurst exponent and trend analysis. Returns regime-specific trading strategies.",
1144
+ parameters: {
1145
+ type: "object",
1146
+ properties: {
1147
+ token_id: { type: "string", description: "Token ID to analyze" },
1148
+ lookback: { type: "number", description: "Data points to analyze", default: 72 }
1149
+ },
1150
+ required: ["token_id"]
1151
+ },
1152
+ async execute(_id, p) {
1153
+ try {
1154
+ return jsonResult(await detectRegime(p.token_id, p.lookback));
1155
+ } catch (e) {
1156
+ return errorResult(e.message);
1157
+ }
1158
+ }
1159
+ },
1160
+ {
1161
+ name: "poly_smart_money_index",
1162
+ description: "Calculate a composite Smart Money Index combining orderbook imbalance, trade flow direction, price momentum, and news sentiment.",
1163
+ parameters: {
1164
+ type: "object",
1165
+ properties: {
1166
+ token_id: { type: "string", description: "Token ID to analyze" },
1167
+ market_question: { type: "string", description: "Market question (for news sentiment component)" }
1168
+ },
1169
+ required: ["token_id"]
1170
+ },
1171
+ async execute(_id, p) {
1172
+ try {
1173
+ return jsonResult(await calculateSmartMoneyIndex(p.token_id, p.market_question));
1174
+ } catch (e) {
1175
+ return errorResult(e.message);
1176
+ }
1177
+ }
1178
+ },
1179
+ {
1180
+ name: "poly_market_microstructure",
1181
+ description: "Analyze market microstructure: spread, depth, slippage simulation for various order sizes, and optimal order type recommendation.",
1182
+ parameters: {
1183
+ type: "object",
1184
+ properties: {
1185
+ token_id: { type: "string", description: "Token ID to analyze" },
1186
+ order_sizes: { type: "array", items: { type: "number" }, description: "USDC sizes to simulate (default: [100, 500, 1000, 5000])" }
1187
+ },
1188
+ required: ["token_id"]
1189
+ },
1190
+ async execute(_id, p) {
1191
+ try {
1192
+ return jsonResult(await analyzeMicrostructure(p.token_id, p.order_sizes));
1193
+ } catch (e) {
1194
+ return errorResult(e.message);
1195
+ }
1196
+ }
1197
+ }
1198
+ ];
1199
+ }
1200
+
1201
+ // src/agent-tools/tools/polymarket-portfolio.ts
1202
+ function createPolymarketPortfolioTools(opts) {
1203
+ const getDb = () => opts?.engineDb;
1204
+ return [
1205
+ {
1206
+ name: "poly_portfolio_optimizer",
1207
+ description: "Analyze portfolio: current values, P&L, concentration (HHI), risk metrics. Uses Kelly criterion for optimal position sizing.",
1208
+ parameters: {
1209
+ type: "object",
1210
+ properties: {
1211
+ positions: {
1212
+ type: "array",
1213
+ description: "Array: { token_id, market, outcome, size, avg_price }",
1214
+ items: { type: "object" }
1215
+ },
1216
+ bankroll: { type: "number", description: "Total available capital" },
1217
+ estimated_true_probs: {
1218
+ type: "object",
1219
+ description: "Map of token_id -> your estimated true probability"
1220
+ }
1221
+ },
1222
+ required: ["positions"]
1223
+ },
1224
+ async execute(_id, p) {
1225
+ try {
1226
+ const overview = await analyzePortfolio(p.positions);
1227
+ let kellySizing = null;
1228
+ if (p.bankroll && p.estimated_true_probs) {
1229
+ const kellyInput = p.positions.map((pos) => {
1230
+ const enriched = overview.positions.find((ep) => ep.token_id === pos.token_id);
1231
+ return {
1232
+ token_id: pos.token_id,
1233
+ market: pos.market,
1234
+ current_price: enriched?.current_price || pos.avg_price,
1235
+ estimated_true_prob: p.estimated_true_probs[pos.token_id] || enriched?.current_price || pos.avg_price
1236
+ };
1237
+ });
1238
+ kellySizing = calculatePortfolioKelly(kellyInput, p.bankroll);
1239
+ }
1240
+ return jsonResult({ overview, kelly_sizing: kellySizing });
1241
+ } catch (e) {
1242
+ return errorResult(e.message);
1243
+ }
1244
+ }
1245
+ },
1246
+ {
1247
+ name: "poly_drawdown_monitor",
1248
+ description: "Monitor portfolio drawdown over time. Records snapshots and alerts when drawdown exceeds thresholds.",
1249
+ parameters: {
1250
+ type: "object",
1251
+ properties: {
1252
+ action: { type: "string", enum: ["record", "status", "history"], description: "Action to take" },
1253
+ current_value: { type: "number", description: "Current portfolio value (for record)" },
1254
+ alert_threshold: { type: "number", description: "Alert if drawdown exceeds this %", default: 10 }
1255
+ },
1256
+ required: ["action"]
1257
+ },
1258
+ async execute(_id, p) {
1259
+ try {
1260
+ const db = getDb();
1261
+ if (!db) return errorResult("No database available");
1262
+ await safeDbDDL(db, `CREATE TABLE IF NOT EXISTS poly_drawdown_log (
1263
+ id SERIAL PRIMARY KEY, value REAL NOT NULL, peak REAL NOT NULL,
1264
+ drawdown_pct REAL NOT NULL, recorded_at TIMESTAMPTZ DEFAULT NOW()
1265
+ )`);
1266
+ if (p.action === "record") {
1267
+ if (!p.current_value) return errorResult("Need current_value");
1268
+ const rows2 = await safeDbQuery(db, `SELECT MAX(peak) as max_peak FROM poly_drawdown_log`);
1269
+ const prevPeak = rows2[0]?.max_peak || p.current_value;
1270
+ const peak = Math.max(prevPeak, p.current_value);
1271
+ const drawdown = peak > 0 ? (peak - p.current_value) / peak * 100 : 0;
1272
+ await safeDbExec(
1273
+ db,
1274
+ `INSERT INTO poly_drawdown_log (value, peak, drawdown_pct) VALUES (?, ?, ?)`,
1275
+ [p.current_value, peak, drawdown]
1276
+ );
1277
+ const alert = drawdown > (p.alert_threshold || 10);
1278
+ return jsonResult({ value: p.current_value, peak, drawdown_pct: +drawdown.toFixed(2), alert, threshold: p.alert_threshold || 10 });
1279
+ }
1280
+ if (p.action === "history") {
1281
+ const rows2 = await safeDbQuery(db, `SELECT * FROM poly_drawdown_log ORDER BY recorded_at DESC LIMIT 100`);
1282
+ return jsonResult({ history: rows2 });
1283
+ }
1284
+ const rows = await safeDbQuery(db, `SELECT * FROM poly_drawdown_log ORDER BY recorded_at DESC LIMIT 1`);
1285
+ return jsonResult({ latest: rows[0] || null });
1286
+ } catch (e) {
1287
+ return errorResult(e.message);
1288
+ }
1289
+ }
1290
+ },
1291
+ {
1292
+ name: "poly_pnl_attribution",
1293
+ description: "Attribute P&L across trades and markets. Shows win rate, profit factor, best/worst trades, and per-market contribution.",
1294
+ parameters: {
1295
+ type: "object",
1296
+ properties: {
1297
+ trades: {
1298
+ type: "array",
1299
+ description: "Array: { market, pnl }",
1300
+ items: { type: "object", properties: { market: { type: "string" }, pnl: { type: "number" } } }
1301
+ }
1302
+ },
1303
+ required: ["trades"]
1304
+ },
1305
+ async execute(_id, p) {
1306
+ try {
1307
+ return jsonResult(attributePnL(p.trades));
1308
+ } catch (e) {
1309
+ return errorResult(e.message);
1310
+ }
1311
+ }
1312
+ }
1313
+ ];
1314
+ }
1315
+
1316
+ // src/agent-tools/tools/polymarket-quant.ts
1317
+ function createPolymarketQuantTools(_opts) {
1318
+ return [
1319
+ {
1320
+ name: "poly_kelly_criterion",
1321
+ description: "Calculate optimal position size using the Kelly Criterion. Given your estimated true probability and market price, returns the mathematically optimal fraction of bankroll to bet. Also returns half-Kelly and quarter-Kelly (more conservative). Formula: f* = (p\xB7b - q) / b where b = (1/price - 1), p = true probability, q = 1-p.",
1322
+ parameters: {
1323
+ type: "object",
1324
+ properties: {
1325
+ true_probability: { type: "number", description: "Your estimated true probability of the outcome (0-1)" },
1326
+ market_price: { type: "number", description: "Current market price (0-1). If omitted, fetched from token_id." },
1327
+ token_id: { type: "string", description: "Token ID to fetch live price" },
1328
+ bankroll: { type: "number", description: "Total available capital (USDC)" },
1329
+ max_fraction: { type: "number", description: "Max fraction of bankroll per bet (risk cap)", default: 0.25 }
1330
+ },
1331
+ required: ["true_probability"]
1332
+ },
1333
+ async execute(_id, p) {
1334
+ try {
1335
+ return jsonResult(await calculateKelly(p));
1336
+ } catch (e) {
1337
+ return errorResult(e.message);
1338
+ }
1339
+ }
1340
+ },
1341
+ {
1342
+ name: "poly_binary_pricing",
1343
+ description: "Price a prediction market outcome as a binary option using Black-Scholes framework. Returns theoretical fair value, Greeks (delta, gamma, theta, vega), and mispricing analysis.",
1344
+ parameters: {
1345
+ type: "object",
1346
+ properties: {
1347
+ current_price: { type: "number", description: "Current market price (0-1)" },
1348
+ token_id: { type: "string", description: "Or fetch from token" },
1349
+ time_to_expiry_hours: { type: "number", description: "Hours until market resolves" },
1350
+ end_date: { type: "string", description: "Or provide end date (ISO)" },
1351
+ volatility: { type: "number", description: "Annualized volatility (0-5). If omitted, estimated." },
1352
+ true_probability: { type: "number", description: "Your estimated true probability (for mispricing analysis)" }
1353
+ }
1354
+ },
1355
+ async execute(_id, p) {
1356
+ try {
1357
+ return jsonResult(await priceBinaryOption(p));
1358
+ } catch (e) {
1359
+ return errorResult(e.message);
1360
+ }
1361
+ }
1362
+ },
1363
+ {
1364
+ name: "poly_bayesian_update",
1365
+ description: "Update probability estimates using Bayes theorem when new evidence arrives. Start with a prior, add evidence with likelihood ratios, get posterior probability.",
1366
+ parameters: {
1367
+ type: "object",
1368
+ properties: {
1369
+ prior: { type: "number", description: "Prior probability (0-1). Use current market price or your belief." },
1370
+ token_id: { type: "string", description: "Or fetch current price as prior" },
1371
+ evidence: {
1372
+ type: "array",
1373
+ description: "Array of evidence objects: { description, likelihood_if_true, likelihood_if_false } OR { description, likelihood_ratio }",
1374
+ items: {
1375
+ type: "object",
1376
+ properties: {
1377
+ description: { type: "string" },
1378
+ likelihood_if_true: { type: "number" },
1379
+ likelihood_if_false: { type: "number" },
1380
+ likelihood_ratio: { type: "number" }
1381
+ }
1382
+ }
1383
+ }
1384
+ },
1385
+ required: ["evidence"]
1386
+ },
1387
+ async execute(_id, p) {
1388
+ try {
1389
+ return jsonResult(await bayesianUpdate(p));
1390
+ } catch (e) {
1391
+ return errorResult(e.message);
1392
+ }
1393
+ }
1394
+ },
1395
+ {
1396
+ name: "poly_monte_carlo",
1397
+ description: "Run Monte Carlo simulation on prediction market positions. Simulates thousands of outcomes for expected P&L distribution, probability of profit, VaR, and optimal exits.",
1398
+ parameters: {
1399
+ type: "object",
1400
+ properties: {
1401
+ positions: {
1402
+ type: "array",
1403
+ description: "Array of positions: { token_id, side, entry_price, size, true_probability? }",
1404
+ items: { type: "object" }
1405
+ },
1406
+ simulations: { type: "number", description: "Number of paths (default 10000)", default: 1e4 },
1407
+ time_horizon_hours: { type: "number", description: "Simulation horizon in hours", default: 24 },
1408
+ volatility: { type: "number", description: "Annualized volatility override" },
1409
+ correlation: { type: "number", description: "Assumed correlation between positions", default: 0 }
1410
+ },
1411
+ required: ["positions"]
1412
+ },
1413
+ async execute(_id, p) {
1414
+ try {
1415
+ return jsonResult(await runMonteCarlo(p));
1416
+ } catch (e) {
1417
+ return errorResult(e.message);
1418
+ }
1419
+ }
1420
+ },
1421
+ {
1422
+ name: "poly_technical_indicators",
1423
+ description: "Calculate technical analysis indicators: RSI, MACD, Bollinger Bands, EMA crossovers, rate of change, and trend strength. Adapted for 0-1 bounded prediction market prices.",
1424
+ parameters: {
1425
+ type: "object",
1426
+ properties: {
1427
+ token_id: { type: "string", description: "Token ID to analyze" },
1428
+ prices: { type: "array", items: { type: "number" }, description: "Or provide price array" },
1429
+ indicators: { type: "array", items: { type: "string" }, description: "Which indicators (default: all)" },
1430
+ rsi_period: { type: "number", default: 14 },
1431
+ macd_fast: { type: "number", default: 12 },
1432
+ macd_slow: { type: "number", default: 26 },
1433
+ macd_signal: { type: "number", default: 9 },
1434
+ bollinger_period: { type: "number", default: 20 },
1435
+ bollinger_std: { type: "number", default: 2 }
1436
+ }
1437
+ },
1438
+ async execute(_id, p) {
1439
+ try {
1440
+ return jsonResult(await calculateTechnicalIndicators(p));
1441
+ } catch (e) {
1442
+ return errorResult(e.message);
1443
+ }
1444
+ }
1445
+ },
1446
+ {
1447
+ name: "poly_volatility",
1448
+ description: "Comprehensive volatility analysis: realized volatility, EWMA, volatility term structure, Hurst exponent to determine if market is trending (H>0.5), mean-reverting (H<0.5), or random walk (H\u22480.5).",
1449
+ parameters: {
1450
+ type: "object",
1451
+ properties: {
1452
+ token_id: { type: "string", description: "Token ID to analyze" },
1453
+ prices: { type: "array", items: { type: "number" }, description: "Or provide prices directly" },
1454
+ windows: { type: "array", items: { type: "number" }, description: "Volatility windows (default: [5, 10, 20, 50])" }
1455
+ }
1456
+ },
1457
+ async execute(_id, p) {
1458
+ try {
1459
+ return jsonResult(await analyzeVolatility(p));
1460
+ } catch (e) {
1461
+ return errorResult(e.message);
1462
+ }
1463
+ }
1464
+ },
1465
+ {
1466
+ name: "poly_stat_arb",
1467
+ description: "Statistical arbitrage analysis between two related markets. Tests cointegration, calculates spread z-score, generates mean-reversion trading signals.",
1468
+ parameters: {
1469
+ type: "object",
1470
+ properties: {
1471
+ token_id_1: { type: "string", description: "First token ID" },
1472
+ token_id_2: { type: "string", description: "Second token ID" },
1473
+ lookback: { type: "number", default: 50 },
1474
+ entry_zscore: { type: "number", default: 2 },
1475
+ exit_zscore: { type: "number", default: 0.5 }
1476
+ },
1477
+ required: ["token_id_1", "token_id_2"]
1478
+ },
1479
+ async execute(_id, p) {
1480
+ try {
1481
+ return jsonResult(await analyzeStatArb(p));
1482
+ } catch (e) {
1483
+ return errorResult(e.message);
1484
+ }
1485
+ }
1486
+ },
1487
+ {
1488
+ name: "poly_value_at_risk",
1489
+ description: "Calculate VaR and CVaR (Expected Shortfall) for positions. Uses parametric, historical, and Cornish-Fisher methods.",
1490
+ parameters: {
1491
+ type: "object",
1492
+ properties: {
1493
+ positions: { type: "array", description: "Array: { token_id, size, entry_price?, side? }", items: { type: "object" } },
1494
+ confidence: { type: "number", default: 0.95 },
1495
+ horizon_hours: { type: "number", default: 24 },
1496
+ method: { type: "string", enum: ["parametric", "historical", "cornish_fisher", "all"], default: "all" }
1497
+ },
1498
+ required: ["positions"]
1499
+ },
1500
+ async execute(_id, p) {
1501
+ try {
1502
+ return jsonResult(await calculateVaR(p));
1503
+ } catch (e) {
1504
+ return errorResult(e.message);
1505
+ }
1506
+ }
1507
+ },
1508
+ {
1509
+ name: "poly_entropy",
1510
+ description: "Calculate Shannon entropy and information-theoretic measures for market probability distributions. Higher entropy = more uncertainty = potentially more trading opportunity.",
1511
+ parameters: {
1512
+ type: "object",
1513
+ properties: {
1514
+ token_id: { type: "string", description: "Token ID to analyze" },
1515
+ probabilities: { type: "array", items: { type: "number" }, description: "Or provide probabilities directly" },
1516
+ market_slug: { type: "string", description: "Or fetch from market slug" }
1517
+ }
1518
+ },
1519
+ async execute(_id, p) {
1520
+ try {
1521
+ return jsonResult(await calculateEntropy(p));
1522
+ } catch (e) {
1523
+ return errorResult(e.message);
1524
+ }
1525
+ }
1526
+ },
1527
+ {
1528
+ name: "poly_news_feed",
1529
+ description: "Fetch and analyze news articles related to a market topic. Scores sentiment, extracts key entities, and assesses potential market impact.",
1530
+ parameters: {
1531
+ type: "object",
1532
+ properties: {
1533
+ query: { type: "string", description: "Search query (market question or topic)" },
1534
+ sources: { type: "array", items: { type: "string" }, description: "News sources to check" },
1535
+ max_results: { type: "number", default: 20 }
1536
+ },
1537
+ required: ["query"]
1538
+ },
1539
+ async execute(_id, p) {
1540
+ try {
1541
+ return jsonResult(await fetchNewsFeed(p));
1542
+ } catch (e) {
1543
+ return errorResult(e.message);
1544
+ }
1545
+ }
1546
+ },
1547
+ {
1548
+ name: "poly_sentiment_analysis",
1549
+ description: "Analyze sentiment of text, headlines, or market comments. Returns sentiment score (-1 to 1), confidence, and key phrases detected.",
1550
+ parameters: {
1551
+ type: "object",
1552
+ properties: {
1553
+ texts: { type: "array", items: { type: "string" }, description: "Array of texts to analyze" },
1554
+ text: { type: "string", description: "Or single text" },
1555
+ context: { type: "string", description: "Market context for better scoring" }
1556
+ }
1557
+ },
1558
+ async execute(_id, p) {
1559
+ try {
1560
+ return jsonResult(await analyzeSentiment(p));
1561
+ } catch (e) {
1562
+ return errorResult(e.message);
1563
+ }
1564
+ }
1565
+ },
1566
+ {
1567
+ name: "poly_generate_signal",
1568
+ description: "Generate a composite trading signal combining orderbook, technicals, momentum, mean-reversion, fundamental edge, and volume analysis. Returns overall signal with confidence.",
1569
+ parameters: {
1570
+ type: "object",
1571
+ properties: {
1572
+ token_id: { type: "string", description: "Token ID to analyze" },
1573
+ market_id: { type: "string", description: "Market/condition ID" },
1574
+ true_probability: { type: "number", description: "Your estimated true probability" },
1575
+ bankroll: { type: "number", description: "Available capital" },
1576
+ risk_tolerance: { type: "string", enum: ["conservative", "moderate", "aggressive"], default: "moderate" }
1577
+ },
1578
+ required: ["token_id"]
1579
+ },
1580
+ async execute(_id, p) {
1581
+ try {
1582
+ return jsonResult(await generateCompositeSignal(p));
1583
+ } catch (e) {
1584
+ return errorResult(e.message);
1585
+ }
1586
+ }
1587
+ },
1588
+ {
1589
+ name: "poly_correlation_matrix",
1590
+ description: "Calculate correlation matrix between multiple prediction market tokens. Identifies diversification opportunities and correlated risks.",
1591
+ parameters: {
1592
+ type: "object",
1593
+ properties: {
1594
+ token_ids: { type: "array", items: { type: "string" }, description: "Array of token IDs (2-10)" },
1595
+ lookback: { type: "number", description: "Data points to use", default: 100 }
1596
+ },
1597
+ required: ["token_ids"]
1598
+ },
1599
+ async execute(_id, p) {
1600
+ try {
1601
+ return jsonResult(await calculateCorrelationMatrix(p));
1602
+ } catch (e) {
1603
+ return errorResult(e.message);
1604
+ }
1605
+ }
1606
+ },
1607
+ {
1608
+ name: "poly_efficiency_test",
1609
+ description: "Test if a prediction market is informationally efficient. Uses runs test, autocorrelation, variance ratio, and entropy analysis.",
1610
+ parameters: {
1611
+ type: "object",
1612
+ properties: {
1613
+ token_id: { type: "string", description: "Token ID to test" },
1614
+ prices: { type: "array", items: { type: "number" }, description: "Or provide prices" },
1615
+ lookback: { type: "number", default: 100 }
1616
+ }
1617
+ },
1618
+ async execute(_id, p) {
1619
+ try {
1620
+ return jsonResult(await testMarketEfficiency(p));
1621
+ } catch (e) {
1622
+ return errorResult(e.message);
1623
+ }
1624
+ }
1625
+ }
1626
+ ];
1627
+ }
1628
+
1629
+ // src/agent-tools/tools/polymarket-counterintel.ts
1630
+ function createPolymarketCounterintelTools(_opts) {
1631
+ return [
1632
+ {
1633
+ name: "poly_manipulation_detector",
1634
+ description: "Detect market manipulation: wash trading, volume concentration, rapid-fire trading, price manipulation (pump/dump), and orderbook layering.",
1635
+ parameters: {
1636
+ type: "object",
1637
+ properties: {
1638
+ token_id: { type: "string", description: "Token ID to scan" }
1639
+ },
1640
+ required: ["token_id"]
1641
+ },
1642
+ async execute(_id, p) {
1643
+ try {
1644
+ return jsonResult(await detectManipulation(p.token_id));
1645
+ } catch (e) {
1646
+ return errorResult(e.message);
1647
+ }
1648
+ }
1649
+ },
1650
+ {
1651
+ name: "poly_resolution_risk",
1652
+ description: "Assess resolution risk: ambiguous language, subjective criteria, missing sources, low liquidity, complex conditions. Scores overall risk.",
1653
+ parameters: {
1654
+ type: "object",
1655
+ properties: {
1656
+ market_slug: { type: "string", description: "Market slug" },
1657
+ condition_id: { type: "string", description: "Or condition ID" }
1658
+ }
1659
+ },
1660
+ async execute(_id, p) {
1661
+ try {
1662
+ return jsonResult(await assessResolutionRisk(p.market_slug, p.condition_id));
1663
+ } catch (e) {
1664
+ return errorResult(e.message);
1665
+ }
1666
+ }
1667
+ },
1668
+ {
1669
+ name: "poly_counterparty_analysis",
1670
+ description: "Analyze who is trading on the other side of your position. Breaks down traders into whales/mid/retail and assesses risk.",
1671
+ parameters: {
1672
+ type: "object",
1673
+ properties: {
1674
+ token_id: { type: "string", description: "Token ID" },
1675
+ side: { type: "string", enum: ["BUY", "SELL"], description: "Your intended side (to analyze counterparties on opposite side)" }
1676
+ },
1677
+ required: ["token_id"]
1678
+ },
1679
+ async execute(_id, p) {
1680
+ try {
1681
+ return jsonResult(await analyzeCounterparties(p.token_id, p.side));
1682
+ } catch (e) {
1683
+ return errorResult(e.message);
1684
+ }
1685
+ }
1686
+ }
1687
+ ];
1688
+ }
1689
+
1690
+ // src/agent-tools/tools/polymarket-pipeline.ts
1691
+ function createPolymarketPipelineTools(_opts) {
1692
+ return [
1693
+ {
1694
+ name: "poly_full_analysis",
1695
+ description: "Run the COMPLETE analysis pipeline on a market: screener \u2192 quant (Kelly, technicals, volatility, Monte Carlo, VaR) \u2192 analytics (regime, smart money, microstructure) \u2192 on-chain (orderbook, whales, flow) \u2192 social (Twitter, Reddit, velocity) \u2192 feeds (odds comparison, resolution) \u2192 counter-intel (manipulation, risk, counterparties). Returns a synthesized score (0-100), action recommendation, and detailed results from every stage.",
1696
+ parameters: {
1697
+ type: "object",
1698
+ properties: {
1699
+ token_id: { type: "string", description: "Token ID to analyze" },
1700
+ market_slug: { type: "string", description: "Market slug (for resolution/risk data)" },
1701
+ condition_id: { type: "string", description: "Condition ID" },
1702
+ market_question: { type: "string", description: "Market question (for social/news analysis)" },
1703
+ current_price: { type: "number", description: "Current price (auto-fetched if omitted)" },
1704
+ bankroll: { type: "number", description: "Available capital for Kelly sizing", default: 100 },
1705
+ estimated_true_prob: { type: "number", description: "Your estimated true probability" },
1706
+ side: { type: "string", enum: ["BUY", "SELL"], description: "Intended trading side" },
1707
+ skip_slow: { type: "boolean", description: "Skip social/feeds stages for speed", default: false }
1708
+ },
1709
+ required: ["token_id"]
1710
+ },
1711
+ async execute(_id, p) {
1712
+ try {
1713
+ return jsonResult(await fullAnalysis({
1714
+ tokenId: p.token_id,
1715
+ marketSlug: p.market_slug,
1716
+ conditionId: p.condition_id,
1717
+ marketQuestion: p.market_question,
1718
+ currentPrice: p.current_price,
1719
+ bankroll: p.bankroll,
1720
+ estimatedTrueProb: p.estimated_true_prob,
1721
+ side: p.side,
1722
+ skipSlow: p.skip_slow
1723
+ }));
1724
+ } catch (e) {
1725
+ return errorResult(e.message);
1726
+ }
1727
+ }
1728
+ },
1729
+ {
1730
+ name: "poly_quick_analysis",
1731
+ description: "Fast analysis subset: quant + orderbook + regime + smart money + manipulation check. Returns score, action, Kelly sizing, and thesis. Use for dashboard buy modal or quick decisions.",
1732
+ parameters: {
1733
+ type: "object",
1734
+ properties: {
1735
+ token_id: { type: "string", description: "Token ID" },
1736
+ market_question: { type: "string", description: "Market question" },
1737
+ bankroll: { type: "number", description: "Available capital", default: 100 }
1738
+ },
1739
+ required: ["token_id"]
1740
+ },
1741
+ async execute(_id, p) {
1742
+ try {
1743
+ return jsonResult(await quickAnalysis(p.token_id, p.market_question, p.bankroll));
1744
+ } catch (e) {
1745
+ return errorResult(e.message);
1746
+ }
1747
+ }
1748
+ },
1749
+ {
1750
+ name: "poly_batch_screen",
1751
+ description: "Screen and rank multiple markets. Alias for poly_screen_markets with pipeline integration.",
1752
+ parameters: {
1753
+ type: "object",
1754
+ properties: {
1755
+ query: { type: "string", description: "Search query" },
1756
+ limit: { type: "number", default: 10 },
1757
+ strategy: {
1758
+ type: "string",
1759
+ enum: ["momentum", "contested", "best_opportunities", "high_volume", "closing_soon", "mispriced", "safe_bets", "new_markets"]
1760
+ },
1761
+ min_score: { type: "number", description: "Minimum score to include" }
1762
+ }
1763
+ },
1764
+ async execute(_id, p) {
1765
+ try {
1766
+ return jsonResult(await batchScreen(p));
1767
+ } catch (e) {
1768
+ return errorResult(e.message);
1769
+ }
1770
+ }
1771
+ },
1772
+ {
1773
+ name: "poly_portfolio_review",
1774
+ description: "Complete portfolio review: overview + correlation matrix + Kelly sizing + P&L attribution + actionable recommendations.",
1775
+ parameters: {
1776
+ type: "object",
1777
+ properties: {
1778
+ positions: {
1779
+ type: "array",
1780
+ description: "Array: { token_id, market, outcome, size, avg_price }",
1781
+ items: { type: "object" }
1782
+ },
1783
+ bankroll: { type: "number", description: "Total capital" },
1784
+ closed_trades: {
1785
+ type: "array",
1786
+ description: "Array: { market, pnl } for P&L attribution",
1787
+ items: { type: "object" }
1788
+ }
1789
+ },
1790
+ required: ["positions", "bankroll"]
1791
+ },
1792
+ async execute(_id, p) {
1793
+ try {
1794
+ return jsonResult(await portfolioReview({ positions: p.positions, bankroll: p.bankroll, closedTrades: p.closed_trades }));
1795
+ } catch (e) {
1796
+ return errorResult(e.message);
1797
+ }
1798
+ }
1799
+ }
1800
+ ];
1801
+ }
1802
+
1803
+ // src/agent-tools/tools/polymarket.ts
1804
+ var GAMMA_API = "https://gamma-api.polymarket.com";
1805
+ var USDC_E = USDC_ADDRESS;
1806
+ var marketCache = /* @__PURE__ */ new Map();
1807
+ var CACHE_TTL = 5 * 6e4;
1808
+ var agentConfigs = /* @__PURE__ */ new Map();
1809
+ var pendingTrades = /* @__PURE__ */ new Map();
1810
+ var dailyCounters = /* @__PURE__ */ new Map();
1811
+ var circuitBreakerState = /* @__PURE__ */ new Map();
1812
+ var _screenerTools = [];
1813
+ try {
1814
+ _screenerTools = createPolymarketScreenerTools();
1815
+ } catch (e) {
1816
+ console.warn("[polymarket] Screener init:", e.message);
1817
+ }
1818
+ var _pipelineTools = [];
1819
+ try {
1820
+ _pipelineTools = createPolymarketPipelineTools();
1821
+ } catch (e) {
1822
+ console.warn("[polymarket] Pipeline init:", e.message);
1823
+ }
1824
+ var walletState = /* @__PURE__ */ new Map();
1825
+ async function apiFetch(url, opts) {
1826
+ const ctrl = new AbortController();
1827
+ const t = setTimeout(() => ctrl.abort(), opts?.timeoutMs || 1e4);
1828
+ try {
1829
+ const res = await fetch(url, { ...opts, signal: ctrl.signal });
1830
+ if (!res.ok) {
1831
+ const txt = await res.text().catch(() => "");
1832
+ throw new Error(`API ${res.status}: ${txt.slice(0, 300)}`);
1833
+ }
1834
+ const ct = res.headers.get("content-type") || "";
1835
+ return ct.includes("json") ? res.json() : res.text();
1836
+ } finally {
1837
+ clearTimeout(t);
1838
+ }
1839
+ }
1840
+ function cached(key) {
1841
+ const e = marketCache.get(key);
1842
+ return e && Date.now() - e.ts < CACHE_TTL ? e.data : null;
1843
+ }
1844
+ function setCache(key, data) {
1845
+ marketCache.set(key, { data, ts: Date.now() });
1846
+ }
1847
+ function getLocalDailyCounter(agentId) {
1848
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1849
+ let c = dailyCounters.get(agentId);
1850
+ if (!c || c.date !== today) {
1851
+ c = { count: 0, loss: 0, date: today };
1852
+ dailyCounters.set(agentId, c);
1853
+ }
1854
+ return c;
1855
+ }
1856
+ function preTradeChecks(agentId, config, params) {
1857
+ const cb = circuitBreakerState.get(agentId);
1858
+ if (cb?.paused) return `Trading paused by circuit breaker: ${cb.reason}`;
1859
+ if (params.size > config.maxOrderSize) return `Order size $${params.size} exceeds max order size $${config.maxOrderSize}`;
1860
+ if (params.size > config.maxPositionSize) return `Position size $${params.size} exceeds max position size $${config.maxPositionSize}`;
1861
+ const counter = getLocalDailyCounter(agentId);
1862
+ if (counter.count >= config.maxDailyTrades) return `Daily trade limit (${config.maxDailyTrades}) reached`;
1863
+ if (config.maxDailyLoss > 0 && counter.loss >= config.maxDailyLoss) {
1864
+ circuitBreakerState.set(agentId, { paused: true, reason: "Daily loss limit hit", pausedAt: (/* @__PURE__ */ new Date()).toISOString() });
1865
+ return `Daily loss limit ($${config.maxDailyLoss}) reached \u2014 circuit breaker activated`;
1866
+ }
1867
+ if (config.blockedMarkets.length > 0 && params.market_id && config.blockedMarkets.includes(params.market_id)) {
1868
+ return `Market ${params.market_id} is blocked`;
1869
+ }
1870
+ return null;
1871
+ }
1872
+ async function executeOrder(agentId, db, tradeId, p, source) {
1873
+ const sdk = await ensureSDK();
1874
+ if (!sdk.ready) {
1875
+ await logTrade(db, {
1876
+ id: tradeId,
1877
+ agentId,
1878
+ tokenId: p.token_id,
1879
+ marketQuestion: p.market_question,
1880
+ outcome: p.outcome,
1881
+ side: p.side,
1882
+ price: p.price,
1883
+ size: p.size,
1884
+ status: "pending_sdk",
1885
+ rationale: p.rationale
1886
+ });
1887
+ return jsonResult({
1888
+ status: "pending_sdk",
1889
+ trade_id: tradeId,
1890
+ message: `Order logged. SDK installing: ${sdk.message}. Will execute when ready.`,
1891
+ order: { tokenId: p.token_id, side: p.side, price: p.price, size: p.size },
1892
+ persisted: true
1893
+ });
1894
+ }
1895
+ const client = await getClobClient(agentId, db);
1896
+ if (!client) {
1897
+ await logTrade(db, {
1898
+ id: tradeId,
1899
+ agentId,
1900
+ tokenId: p.token_id,
1901
+ marketQuestion: p.market_question,
1902
+ outcome: p.outcome,
1903
+ side: p.side,
1904
+ price: p.price,
1905
+ size: p.size,
1906
+ status: "no_wallet",
1907
+ rationale: p.rationale
1908
+ });
1909
+ return errorResult("No wallet configured. Use poly_create_account or poly_setup_wallet first.");
1910
+ }
1911
+ try {
1912
+ const tradingConfig = await loadConfig(agentId, db);
1913
+ if (tradingConfig.mode === "approval" && !source.startsWith("auto_") && source !== "approved") {
1914
+ await savePendingTrade(db, {
1915
+ id: tradeId,
1916
+ agentId,
1917
+ tokenId: p.token_id,
1918
+ marketQuestion: p.market_question || "",
1919
+ outcome: p.outcome || "",
1920
+ side: p.side,
1921
+ price: p.price,
1922
+ size: p.size,
1923
+ rationale: p.rationale || "",
1924
+ source
1925
+ });
1926
+ return jsonResult({
1927
+ status: "pending_approval",
1928
+ trade_id: tradeId,
1929
+ message: `Trading mode is "approval". Order queued for review. Use poly_approve_trade to execute.`,
1930
+ persisted: true
1931
+ });
1932
+ }
1933
+ if (p.size > tradingConfig.maxOrderSize && tradingConfig.maxOrderSize > 0) {
1934
+ return errorResult(`Order size ${p.size} exceeds max_order_size limit of ${tradingConfig.maxOrderSize}. Update config with poly_set_config.`);
1935
+ }
1936
+ if (p.size > tradingConfig.maxPositionSize && tradingConfig.maxPositionSize > 0) {
1937
+ return errorResult(`Position size ${p.size} exceeds max_position_size limit of ${tradingConfig.maxPositionSize}.`);
1938
+ }
1939
+ const dailyCounter = await getDailyCounter(agentId, db);
1940
+ const dailyCount = typeof dailyCounter === "number" ? dailyCounter : dailyCounter?.count || 0;
1941
+ if (dailyCount >= tradingConfig.maxDailyTrades && tradingConfig.maxDailyTrades > 0 && !source.startsWith("auto_")) {
1942
+ return errorResult(`Daily trade limit reached (${dailyCount}/${tradingConfig.maxDailyTrades}). Wait until tomorrow or adjust max_daily_trades.`);
1943
+ }
1944
+ if (tradingConfig.blockedMarkets?.length && p.market_question) {
1945
+ const blocked = tradingConfig.blockedMarkets.some(
1946
+ (m) => p.market_question.toLowerCase().includes(m.toLowerCase())
1947
+ );
1948
+ if (blocked) return errorResult(`This market is blocked by your trading config.`);
1949
+ }
1950
+ } catch (configErr) {
1951
+ console.warn(`[executeOrder] Config check failed (proceeding): ${configErr.message}`);
1952
+ }
1953
+ if (p.side === "SELL") {
1954
+ try {
1955
+ const addr = client.funderAddress || client.address;
1956
+ const positions = await fetch(`https://data-api.polymarket.com/positions?user=${addr}`, { signal: AbortSignal.timeout(8e3) }).then((r) => r.json()).catch(() => []);
1957
+ const pos = (Array.isArray(positions) ? positions : []).find((pos2) => pos2.asset === p.token_id);
1958
+ if (!pos || parseFloat(pos.size) <= 0) {
1959
+ await logTrade(db, {
1960
+ id: tradeId,
1961
+ agentId,
1962
+ tokenId: p.token_id,
1963
+ marketQuestion: p.market_question,
1964
+ outcome: p.outcome,
1965
+ side: p.side,
1966
+ price: p.price,
1967
+ size: p.size,
1968
+ status: "no_position",
1969
+ rationale: `${p.rationale || ""} [No position found to sell]`
1970
+ });
1971
+ return errorResult(`Cannot SELL: no position found for this token. You may have already sold or the position was closed.`);
1972
+ }
1973
+ const availableShares = parseFloat(pos.size);
1974
+ if (p.size > availableShares) {
1975
+ console.warn(`[executeOrder] Reducing sell size from ${p.size} to ${availableShares} (available shares)`);
1976
+ p.size = availableShares;
1977
+ }
1978
+ } catch (posErr) {
1979
+ console.warn(`[executeOrder] Position pre-check failed (proceeding): ${posErr.message}`);
1980
+ }
1981
+ }
1982
+ const POLY_MIN_SIZE = 5;
1983
+ if (p.size > 0 && p.size < POLY_MIN_SIZE) {
1984
+ if (p.size >= POLY_MIN_SIZE - 0.1) {
1985
+ console.warn(`[executeOrder] Rounding size ${p.size} up to minimum ${POLY_MIN_SIZE}`);
1986
+ p.size = POLY_MIN_SIZE;
1987
+ } else {
1988
+ await logTrade(db, {
1989
+ id: tradeId,
1990
+ agentId,
1991
+ tokenId: p.token_id,
1992
+ marketQuestion: p.market_question,
1993
+ outcome: p.outcome,
1994
+ side: p.side,
1995
+ price: p.price,
1996
+ size: p.size,
1997
+ status: "rejected",
1998
+ rationale: `${p.rationale || ""} [Size ${p.size} below Polymarket minimum of ${POLY_MIN_SIZE}]`
1999
+ });
2000
+ return errorResult(`Order size ${p.size} is below Polymarket minimum of ${POLY_MIN_SIZE} shares. Cannot execute.`);
2001
+ }
2002
+ }
2003
+ try {
2004
+ const clobModule = await importSDK("@polymarket/clob-client");
2005
+ if (!clobModule) throw new Error("CLOB SDK not available. Run poly_check_sdk to verify installation.");
2006
+ const { Side } = clobModule;
2007
+ const side = p.side === "BUY" ? Side.BUY : Side.SELL;
2008
+ const orderArgs = {
2009
+ tokenID: p.token_id,
2010
+ side,
2011
+ size: p.size
2012
+ };
2013
+ if (p.price) orderArgs.price = p.price;
2014
+ if (p.tick_size) orderArgs.feeRateBps = void 0;
2015
+ if (p.neg_risk !== void 0) orderArgs.negRisk = p.neg_risk;
2016
+ const signedOrder = await client.client.createOrder(orderArgs);
2017
+ const response = await client.client.postOrder(signedOrder, p.order_type || "GTC");
2018
+ const clobOrderId = response?.orderID || response?.id;
2019
+ const orderStatus = clobOrderId ? "placed" : "rejected";
2020
+ await logTrade(db, {
2021
+ id: tradeId,
2022
+ agentId,
2023
+ tokenId: p.token_id,
2024
+ marketQuestion: p.market_question,
2025
+ outcome: p.outcome,
2026
+ side: p.side,
2027
+ price: p.price,
2028
+ size: p.size,
2029
+ status: orderStatus,
2030
+ rationale: p.rationale,
2031
+ clobOrderId: clobOrderId || null
2032
+ });
2033
+ if (!clobOrderId) {
2034
+ return jsonResult({
2035
+ status: "rejected",
2036
+ trade_id: tradeId,
2037
+ message: `Order submitted but no order ID returned \u2014 likely rejected by exchange.`,
2038
+ response,
2039
+ persisted: true
2040
+ });
2041
+ }
2042
+ let bracket = null;
2043
+ let bracketConfig = null;
2044
+ if (p.side === "BUY") {
2045
+ try {
2046
+ bracketConfig = await getBracketConfig(agentId, db);
2047
+ if (bracketConfig.enabled) {
2048
+ const buyPrice = p.price || 0.5;
2049
+ bracket = await createBracketAlerts(db, {
2050
+ agentId,
2051
+ tokenId: p.token_id,
2052
+ marketQuestion: p.market_question || "",
2053
+ buyPrice,
2054
+ size: p.size,
2055
+ takeProfitPct: bracketConfig.takeProfitPct,
2056
+ stopLossPct: bracketConfig.stopLossPct,
2057
+ sourceTradeId: tradeId
2058
+ });
2059
+ }
2060
+ } catch (bracketErr) {
2061
+ console.error(`[bracket] Failed to create bracket alerts: ${bracketErr.message}`);
2062
+ }
2063
+ try {
2064
+ const exitId = `exit_auto_${tradeId}`;
2065
+ const buyPrice = p.price || 0.5;
2066
+ const trailingPct = bracketConfig.trailingStopPct || 12;
2067
+ if (trailingPct > 0) {
2068
+ await db.execute(`
2069
+ INSERT INTO poly_exit_rules (id, agent_id, token_id, entry_price, position_size, trailing_stop_pct, highest_price, status)
2070
+ VALUES ($1,$2,$3,$4,$5,$6,$7,'active')
2071
+ `, [exitId, agentId, p.token_id, buyPrice, p.size, trailingPct, buyPrice]);
2072
+ console.log(`[exit-rule] Auto-created trailing stop (${trailingPct}%) for BUY ${tradeId}`);
2073
+ }
2074
+ } catch (exitErr) {
2075
+ console.error(`[exit-rule] Failed to create exit rule: ${exitErr.message}`);
2076
+ }
2077
+ }
2078
+ let sellCleanup = null;
2079
+ if (p.side === "SELL") {
2080
+ try {
2081
+ const tokenId = p.token_id;
2082
+ const alertResult = await db.execute(
2083
+ `UPDATE poly_price_alerts SET triggered = 1, triggered_at = NOW() WHERE agent_id = $1 AND token_id = $2 AND triggered = 0`,
2084
+ [agentId, tokenId]
2085
+ );
2086
+ const exitResult = await db.execute(
2087
+ `UPDATE poly_exit_rules SET status = 'cancelled' WHERE agent_id = $1 AND token_id = $2 AND status = 'active'`,
2088
+ [agentId, tokenId]
2089
+ );
2090
+ const watcherResult = await db.execute(
2091
+ `UPDATE poly_watchers SET status = 'paused' WHERE agent_id = $1 AND status = 'active' AND config::text LIKE $2`,
2092
+ [agentId, `%${tokenId}%`]
2093
+ );
2094
+ const alertsCancelled = alertResult?.rowCount || alertResult?.changes || 0;
2095
+ const exitsCancelled = exitResult?.rowCount || exitResult?.changes || 0;
2096
+ const watchersPaused = watcherResult?.rowCount || watcherResult?.changes || 0;
2097
+ if (alertsCancelled || exitsCancelled || watchersPaused) {
2098
+ console.log(`[sell-cleanup] Token ${tokenId.slice(0, 16)}: cancelled ${alertsCancelled} alerts, ${exitsCancelled} exit rules, paused ${watchersPaused} watchers`);
2099
+ sellCleanup = { alerts_cancelled: alertsCancelled, exit_rules_cancelled: exitsCancelled, watchers_paused: watchersPaused };
2100
+ }
2101
+ } catch (cleanupErr) {
2102
+ console.warn(`[sell-cleanup] Failed: ${cleanupErr.message}`);
2103
+ }
2104
+ }
2105
+ return jsonResult({
2106
+ status: "placed",
2107
+ trade_id: tradeId,
2108
+ clob_order_id: clobOrderId,
2109
+ source,
2110
+ message: `Order placed: ${p.side} ${p.size} shares at ${p.price || "market"}`,
2111
+ response,
2112
+ persisted: true,
2113
+ ...bracket ? {
2114
+ bracket_orders: {
2115
+ group: bracket.bracketGroup,
2116
+ take_profit: { alert_id: bracket.takeProfitAlertId, price: bracket.takeProfitPrice },
2117
+ stop_loss: { alert_id: bracket.stopLossAlertId, price: bracket.stopLossPrice },
2118
+ message: `Auto-created bracket: TP@${bracket.takeProfitPrice} / SL@${bracket.stopLossPrice}`
2119
+ }
2120
+ } : {},
2121
+ ...p.side === "BUY" && bracket ? {
2122
+ exit_rules: {
2123
+ trailing_stop: `${bracket ? bracketConfig?.trailingStopPct || 12 : 12}%`,
2124
+ message: `Auto-created trailing stop. Tracks highest price and auto-sells if price drops from peak.`
2125
+ }
2126
+ } : {},
2127
+ ...sellCleanup ? { cleanup: sellCleanup } : {}
2128
+ });
2129
+ } catch (e) {
2130
+ await logTrade(db, {
2131
+ id: tradeId,
2132
+ agentId,
2133
+ tokenId: p.token_id,
2134
+ marketQuestion: p.market_question,
2135
+ outcome: p.outcome,
2136
+ side: p.side,
2137
+ price: p.price,
2138
+ size: p.size,
2139
+ status: "failed",
2140
+ rationale: `${p.rationale || ""} [ERROR: ${e.message}]`
2141
+ });
2142
+ if (e.message?.includes("not enough balance") || e.message?.includes("allowance")) {
2143
+ return errorResult(`Order failed: insufficient balance or token allowance. Run poly_set_allowances to approve exchange contracts, then retry. Error: ${e.message}`);
2144
+ }
2145
+ return errorResult(`Order execution failed: ${e.message}`);
2146
+ }
2147
+ }
2148
+ function slimMarket(m) {
2149
+ return {
2150
+ id: m.conditionId || m.id,
2151
+ question: m.question,
2152
+ slug: m.slug,
2153
+ category: m.tags?.[0],
2154
+ outcomes: m.outcomes,
2155
+ outcomePrices: m.outcomePrices,
2156
+ clobTokenIds: m.clobTokenIds,
2157
+ volume: m.volume,
2158
+ liquidity: m.liquidity,
2159
+ endDate: m.endDate,
2160
+ active: m.active,
2161
+ closed: m.closed,
2162
+ resolved: m.resolved,
2163
+ negRisk: m.negRisk,
2164
+ tickSize: m.minimumTickSize
2165
+ };
2166
+ }
2167
+ function createPolymarketTools(options) {
2168
+ const agentId = options.agentId || "default";
2169
+ const db = options.engineDb;
2170
+ if (db) {
2171
+ initPolymarketDB(db).catch((e) => console.warn("[polymarket] DB init:", e.message));
2172
+ initLearningDB(db).catch((e) => console.warn("[polymarket] Learning DB init:", e.message));
2173
+ }
2174
+ const tools = [
2175
+ // ═══ ACCOUNT & ONBOARDING ═══════════════════════════════════
2176
+ {
2177
+ name: "poly_create_account",
2178
+ description: "Create a new Polymarket account using the browser. Generates a fresh Ethereum wallet, navigates to polymarket.com, and completes the signup flow. The wallet credentials are stored securely in the enterprise database. The agent handles the entire flow autonomously.",
2179
+ category: "enterprise",
2180
+ parameters: { type: "object", properties: {
2181
+ method: { type: "string", description: '"auto" = agent creates wallet + signs up via browser. "import" = user provides existing private key.', enum: ["auto", "import"] },
2182
+ private_key: { type: "string", description: "For import method: existing Ethereum private key" },
2183
+ funder_address: { type: "string", description: "For import method: Polymarket profile address" },
2184
+ signature_type: { type: "number", description: "0=EOA, 1=Email/Magic, 2=Browser proxy", default: 0 }
2185
+ } },
2186
+ async execute(_id, p) {
2187
+ try {
2188
+ if (p.method === "import" && p.private_key) {
2189
+ const sdk = await ensureSDK();
2190
+ let address = "(pending SDK)";
2191
+ let funder = p.funder_address;
2192
+ if (sdk.ready) {
2193
+ try {
2194
+ const { Wallet } = await import("@ethersproject/wallet");
2195
+ const key = p.private_key.startsWith("0x") ? p.private_key : `0x${p.private_key}`;
2196
+ const wallet2 = new Wallet(key);
2197
+ address = wallet2.address;
2198
+ funder = funder || address;
2199
+ } catch (e) {
2200
+ return errorResult(`Invalid private key: ${e.message}`);
2201
+ }
2202
+ }
2203
+ await saveWalletCredentials(agentId, db, {
2204
+ privateKey: p.private_key.startsWith("0x") ? p.private_key : `0x${p.private_key}`,
2205
+ funderAddress: funder,
2206
+ signatureType: p.signature_type || 0
2207
+ });
2208
+ if (sdk.ready) {
2209
+ try {
2210
+ const client = await getClobClient(agentId, db);
2211
+ if (client) {
2212
+ return jsonResult({
2213
+ status: "connected",
2214
+ method: "import",
2215
+ address: client.address,
2216
+ funder: client.funderAddress,
2217
+ message: "Wallet imported and CLOB API credentials derived. Ready to trade.",
2218
+ next_steps: ["Fund wallet with USDC on Polygon", "Configure trading with poly_set_config", "Start with poly_search_markets"]
2219
+ });
2220
+ }
2221
+ } catch {
2222
+ }
2223
+ }
2224
+ return jsonResult({
2225
+ status: "stored",
2226
+ method: "import",
2227
+ address,
2228
+ message: "Wallet credentials stored. SDK will auto-install on first trade attempt.",
2229
+ sdk_status: sdk.ready ? "ready" : sdk.message
2230
+ });
2231
+ }
2232
+ const wallet = await generateWallet();
2233
+ if (!wallet) return errorResult("Failed to generate wallet. SDK may need to be installed.");
2234
+ await saveWalletCredentials(agentId, db, {
2235
+ privateKey: wallet.privateKey,
2236
+ funderAddress: wallet.address,
2237
+ signatureType: 0
2238
+ });
2239
+ return jsonResult({
2240
+ status: "wallet_generated",
2241
+ address: wallet.address,
2242
+ message: "Fresh wallet generated and stored in database. To complete Polymarket account setup:",
2243
+ next_steps: [
2244
+ "1. Use the browser tool to navigate to https://polymarket.com and sign up with this wallet",
2245
+ "2. Or send USDC directly to the wallet address on Polygon and trade via API",
2246
+ "3. The agent can navigate polymarket.com to complete signup if browser access is available"
2247
+ ],
2248
+ funding: {
2249
+ address: wallet.address,
2250
+ network: "Polygon (MATIC)",
2251
+ token: "USDC"
2252
+ },
2253
+ note: "Private key is stored securely in the enterprise database. Never share it."
2254
+ });
2255
+ } catch (e) {
2256
+ return errorResult(e.message);
2257
+ }
2258
+ }
2259
+ },
2260
+ {
2261
+ name: "poly_check_sdk",
2262
+ description: "Check if the Polymarket SDK is installed and ready. If not installed, auto-installs it. Use this to verify the system is ready for trading before attempting any authenticated operations.",
2263
+ category: "enterprise",
2264
+ parameters: { type: "object", properties: {} },
2265
+ async execute() {
2266
+ const result = await ensureSDK();
2267
+ const walletClient = await getClobClient(agentId, db);
2268
+ return jsonResult({
2269
+ sdk_ready: result.ready,
2270
+ sdk_message: result.message,
2271
+ wallet_connected: !!walletClient,
2272
+ wallet_address: walletClient?.address || null,
2273
+ funder_address: walletClient?.funderAddress || null
2274
+ });
2275
+ }
2276
+ },
2277
+ // ═══ MARKET DISCOVERY ═══════════════════════════════════════
2278
+ {
2279
+ name: "poly_search_markets",
2280
+ description: "Search prediction markets",
2281
+ category: "enterprise",
2282
+ parameters: { type: "object", properties: {
2283
+ query: { type: "string" },
2284
+ category: { type: "string" },
2285
+ active: { type: "boolean" },
2286
+ closed: { type: "boolean" },
2287
+ limit: { type: "number" },
2288
+ offset: { type: "number" },
2289
+ order: { type: "string" },
2290
+ ascending: { type: "boolean" },
2291
+ min_volume: { type: "number" },
2292
+ min_liquidity: { type: "number" },
2293
+ end_date_before: { type: "string" },
2294
+ end_date_after: { type: "string" }
2295
+ } },
2296
+ async execute(_id, p) {
2297
+ try {
2298
+ const qs = new URLSearchParams();
2299
+ if (p.query) qs.set("search", p.query);
2300
+ if (p.category) qs.set("tag", p.category);
2301
+ qs.set("active", String(p.active !== void 0 ? p.active : true));
2302
+ if (p.closed !== void 0) qs.set("closed", String(p.closed));
2303
+ qs.set("limit", String(p.limit || 20));
2304
+ if (p.offset) qs.set("offset", String(p.offset));
2305
+ if (p.order) qs.set("order", p.order);
2306
+ if (p.ascending !== void 0) qs.set("ascending", String(p.ascending));
2307
+ if (p.end_date_before) qs.set("end_date_max", p.end_date_before);
2308
+ if (p.end_date_after) qs.set("end_date_min", p.end_date_after);
2309
+ if (!p.order) {
2310
+ qs.set("order", "volume");
2311
+ qs.set("ascending", "false");
2312
+ }
2313
+ const evQs = { active: String(p.active !== void 0 ? p.active : true), closed: String(p.closed || false), limit: String(Math.min((p.limit || 20) * 3, 100)), order: p.order || "volume", ascending: String(p.ascending ?? false) };
2314
+ if (p.query) evQs.search = p.query;
2315
+ if (p.category) evQs.tag_id = p.category;
2316
+ const [marketsRaw, eventsRaw] = await Promise.all([
2317
+ apiFetch(`${GAMMA_API}/markets?${qs}`).catch(() => []),
2318
+ apiFetch(`${GAMMA_API}/events?${new URLSearchParams(evQs)}`).catch(() => [])
2319
+ ]);
2320
+ let allRaw = Array.isArray(marketsRaw) ? [...marketsRaw] : [];
2321
+ if (Array.isArray(eventsRaw)) {
2322
+ for (const ev of eventsRaw) {
2323
+ if (ev.markets && Array.isArray(ev.markets)) {
2324
+ for (const m of ev.markets) {
2325
+ if (m.active && !m.closed) allRaw.push(m);
2326
+ }
2327
+ }
2328
+ }
2329
+ }
2330
+ const seen2 = /* @__PURE__ */ new Set();
2331
+ allRaw = allRaw.filter((m) => {
2332
+ const k = m.conditionId || m.id;
2333
+ if (seen2.has(k)) return false;
2334
+ seen2.add(k);
2335
+ return true;
2336
+ });
2337
+ if (p.active !== false && !p.closed) {
2338
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2339
+ allRaw = allRaw.filter((m) => {
2340
+ if (m.closed === true || m.closed === "true") return false;
2341
+ if (m.resolved === true || m.resolved === "true") return false;
2342
+ if (m.active === false || m.active === "false") return false;
2343
+ if (m.endDate && m.endDate < now) return false;
2344
+ if (m.end_date_iso && m.end_date_iso < now) return false;
2345
+ try {
2346
+ const prices = typeof m.outcomePrices === "string" ? JSON.parse(m.outcomePrices) : m.outcomePrices;
2347
+ if (Array.isArray(prices) && prices.length > 0) {
2348
+ const allResolved = prices.every((p2) => parseFloat(p2) <= 0.01 || parseFloat(p2) >= 0.99);
2349
+ if (allResolved) return false;
2350
+ }
2351
+ } catch {
2352
+ }
2353
+ return true;
2354
+ });
2355
+ }
2356
+ let markets = allRaw.map(slimMarket);
2357
+ if (p.min_volume) markets = markets.filter((m) => parseFloat(m.volume || "0") >= p.min_volume);
2358
+ if (p.min_liquidity) markets = markets.filter((m) => parseFloat(m.liquidity || "0") >= p.min_liquidity);
2359
+ if (p.enrich !== false && markets.length > 0) {
2360
+ try {
2361
+ const { quickAnalysis: quickAnalysis2 } = await import("./pipeline-GZ7ZFZAW.js");
2362
+ const top5 = markets.slice(0, 5).filter((m) => m.clobTokenIds?.[0]);
2363
+ const pipeResults = await Promise.all(top5.map(
2364
+ (m) => quickAnalysis2(m.clobTokenIds[0], m.question, p.bankroll || 100).catch(() => null)
2365
+ ));
2366
+ for (let i = 0; i < top5.length; i++) {
2367
+ const pr = pipeResults[i];
2368
+ if (pr) {
2369
+ top5[i].analysis = {
2370
+ score: pr.score,
2371
+ action: pr.action,
2372
+ thesis: pr.thesis,
2373
+ kelly: pr.kelly,
2374
+ regime: pr.regime,
2375
+ smart_money: pr.smart_money,
2376
+ manipulation_risk: pr.manipulation_risk
2377
+ };
2378
+ }
2379
+ }
2380
+ } catch {
2381
+ }
2382
+ }
2383
+ const maxResults = Math.min(p.limit || 8, 15);
2384
+ const capped = markets.slice(0, maxResults);
2385
+ const trimmed = capped.map((m) => {
2386
+ const { clobTokenIds: _c, ...rest } = m;
2387
+ return rest;
2388
+ });
2389
+ return jsonResult({ count: markets.length, showing: trimmed.length, markets: trimmed });
2390
+ } catch (e) {
2391
+ return errorResult(e.message);
2392
+ }
2393
+ }
2394
+ },
2395
+ {
2396
+ name: "poly_get_market",
2397
+ description: "Get market details",
2398
+ category: "enterprise",
2399
+ parameters: { type: "object", properties: { market_id: { type: "string" } }, required: ["market_id"] },
2400
+ async execute(_id, p) {
2401
+ try {
2402
+ const c = cached(`market:${p.market_id}`);
2403
+ if (c) return jsonResult(c);
2404
+ let m;
2405
+ try {
2406
+ m = await apiFetch(`${GAMMA_API}/markets/${p.market_id}`);
2407
+ } catch {
2408
+ const arr = await apiFetch(`${GAMMA_API}/markets?slug=${p.market_id}&limit=1`);
2409
+ m = Array.isArray(arr) && arr[0];
2410
+ }
2411
+ if (!m) return errorResult("Market not found");
2412
+ const result = {
2413
+ ...slimMarket(m),
2414
+ description: m.description,
2415
+ startDate: m.startDate,
2416
+ resolutionSource: m.resolutionSource,
2417
+ resolutionDetails: m.resolutionDetails,
2418
+ creator: m.creator,
2419
+ eventId: m.eventId
2420
+ };
2421
+ setCache(`market:${p.market_id}`, result);
2422
+ return jsonResult(result);
2423
+ } catch (e) {
2424
+ return errorResult(e.message);
2425
+ }
2426
+ }
2427
+ },
2428
+ {
2429
+ name: "poly_get_event",
2430
+ description: "Get event with all sub-markets. event_id can be a numeric ID or slug.",
2431
+ category: "enterprise",
2432
+ parameters: { type: "object", properties: { event_id: { type: "string" } }, required: ["event_id"] },
2433
+ async execute(_id, p) {
2434
+ try {
2435
+ let event = null;
2436
+ try {
2437
+ event = await apiFetch(`${GAMMA_API}/events/${p.event_id}`);
2438
+ } catch {
2439
+ }
2440
+ if (!event || event.error) {
2441
+ const arr = await apiFetch(`${GAMMA_API}/events?slug=${encodeURIComponent(p.event_id)}&limit=1`);
2442
+ event = Array.isArray(arr) && arr[0];
2443
+ }
2444
+ if (!event || event.error) {
2445
+ const arr = await apiFetch(`${GAMMA_API}/events?title=${encodeURIComponent(p.event_id)}&limit=1`);
2446
+ event = Array.isArray(arr) && arr[0];
2447
+ }
2448
+ if (!event) return errorResult("Event not found");
2449
+ return jsonResult({
2450
+ id: event.id,
2451
+ title: event.title || event.name,
2452
+ description: event.description,
2453
+ markets: (event.markets || []).map(slimMarket)
2454
+ });
2455
+ } catch (e) {
2456
+ return errorResult(e.message);
2457
+ }
2458
+ }
2459
+ },
2460
+ {
2461
+ name: "poly_get_prices",
2462
+ description: "Get current prices",
2463
+ category: "enterprise",
2464
+ parameters: { type: "object", properties: {
2465
+ token_id: { type: "string" },
2466
+ token_ids: { type: "array", items: { type: "string" } },
2467
+ side: { type: "string" }
2468
+ } },
2469
+ async execute(_id, p) {
2470
+ try {
2471
+ const ids = p.token_ids || (p.token_id ? [p.token_id] : []);
2472
+ if (ids.length === 0) return errorResult("Provide token_id or token_ids");
2473
+ const results = await Promise.all(ids.map(async (tid) => {
2474
+ const [mid, price] = await Promise.all([
2475
+ apiFetch(`${CLOB_API}/midpoint?token_id=${tid}`).catch(() => null),
2476
+ p.side ? apiFetch(`${CLOB_API}/price?token_id=${tid}&side=${p.side}`).catch(() => null) : null
2477
+ ]);
2478
+ return { tokenId: tid, midpoint: mid?.mid, price: price?.price };
2479
+ }));
2480
+ return jsonResult(ids.length === 1 ? results[0] : { prices: results });
2481
+ } catch (e) {
2482
+ return errorResult(e.message);
2483
+ }
2484
+ }
2485
+ },
2486
+ {
2487
+ name: "poly_get_orderbook",
2488
+ description: "Get order book",
2489
+ category: "enterprise",
2490
+ parameters: { type: "object", properties: { token_id: { type: "string" }, depth: { type: "number" } }, required: ["token_id"] },
2491
+ async execute(_id, p) {
2492
+ try {
2493
+ const book = await apiFetch(`${CLOB_API}/book?token_id=${p.token_id}`);
2494
+ if (p.depth && book) {
2495
+ if (book.bids) book.bids = book.bids.slice(0, p.depth);
2496
+ if (book.asks) book.asks = book.asks.slice(0, p.depth);
2497
+ }
2498
+ const bestBid = book?.bids?.[0]?.price;
2499
+ const bestAsk = book?.asks?.[0]?.price;
2500
+ if (bestBid && bestAsk) {
2501
+ book._spread = (parseFloat(bestAsk) - parseFloat(bestBid)).toFixed(4);
2502
+ book._spreadPct = ((parseFloat(bestAsk) - parseFloat(bestBid)) / parseFloat(bestAsk) * 100).toFixed(2) + "%";
2503
+ book._midpoint = ((parseFloat(bestAsk) + parseFloat(bestBid)) / 2).toFixed(4);
2504
+ }
2505
+ return jsonResult(book);
2506
+ } catch (e) {
2507
+ return errorResult(e.message);
2508
+ }
2509
+ }
2510
+ },
2511
+ {
2512
+ name: "poly_get_trades",
2513
+ description: "Get recent trades",
2514
+ category: "enterprise",
2515
+ parameters: { type: "object", properties: {
2516
+ token_id: { type: "string" },
2517
+ market_id: { type: "string" },
2518
+ limit: { type: "number" },
2519
+ before: { type: "string" },
2520
+ min_size: { type: "number" }
2521
+ } },
2522
+ async execute(_id, p) {
2523
+ try {
2524
+ const client = await getClobClient(_id, db);
2525
+ if (client) {
2526
+ try {
2527
+ const params = {};
2528
+ if (p.token_id) params.asset_id = p.token_id;
2529
+ if (p.market_id) params.market = p.market_id;
2530
+ if (p.before) params.before = p.before;
2531
+ let trades2 = await client.client.getTrades(params);
2532
+ if (Array.isArray(trades2)) {
2533
+ trades2 = trades2.slice(0, p.limit || 50);
2534
+ if (p.min_size) trades2 = trades2.filter((t) => parseFloat(t.size || "0") >= p.min_size);
2535
+ }
2536
+ return jsonResult(trades2);
2537
+ } catch (clientErr) {
2538
+ console.log(`[poly_get_trades] ClobClient failed (${clientErr.message}), trying Gamma fallback`);
2539
+ }
2540
+ }
2541
+ const qs = new URLSearchParams();
2542
+ if (p.token_id) qs.set("asset", p.token_id);
2543
+ if (p.market_id) qs.set("conditionId", p.market_id);
2544
+ qs.set("limit", String(p.limit || 50));
2545
+ let trades = await apiFetch(`https://data-api.polymarket.com/trades?${qs}`);
2546
+ if (p.min_size && Array.isArray(trades)) {
2547
+ trades = trades.filter((t) => parseFloat(t.size || "0") >= p.min_size);
2548
+ }
2549
+ return jsonResult(trades);
2550
+ } catch (e) {
2551
+ return errorResult(e.message);
2552
+ }
2553
+ }
2554
+ },
2555
+ {
2556
+ name: "poly_price_history",
2557
+ description: "Historical price timeseries",
2558
+ category: "enterprise",
2559
+ parameters: { type: "object", properties: {
2560
+ token_id: { type: "string" },
2561
+ market_id: { type: "string" },
2562
+ interval: { type: "string" },
2563
+ start_ts: { type: "string" },
2564
+ end_ts: { type: "string" },
2565
+ fidelity: { type: "number" }
2566
+ } },
2567
+ async execute(_id, p) {
2568
+ try {
2569
+ const qs = new URLSearchParams();
2570
+ if (p.market_id) qs.set("market", p.market_id);
2571
+ if (p.token_id) qs.set("asset_id", p.token_id);
2572
+ if (p.fidelity) qs.set("fidelity", String(p.fidelity));
2573
+ if (p.start_ts) qs.set("startTs", String(new Date(p.start_ts).getTime() / 1e3));
2574
+ if (p.end_ts) qs.set("endTs", String(new Date(p.end_ts).getTime() / 1e3));
2575
+ const history = await apiFetch(`${GAMMA_API}/markets/${p.market_id || p.token_id}/timeseries?${qs}`).catch(() => null);
2576
+ if (!history) {
2577
+ return jsonResult({ status: "limited", message: "Timeseries API not available for this market. Use poly_get_trades for raw trade data." });
2578
+ }
2579
+ return jsonResult(history);
2580
+ } catch (e) {
2581
+ return errorResult(e.message);
2582
+ }
2583
+ }
2584
+ },
2585
+ {
2586
+ name: "poly_trending_markets",
2587
+ description: "Trending markets",
2588
+ category: "enterprise",
2589
+ parameters: { type: "object", properties: {
2590
+ limit: { type: "number" },
2591
+ category: { type: "string" },
2592
+ sort_by: { type: "string" }
2593
+ } },
2594
+ async execute(_id, p) {
2595
+ try {
2596
+ const qs = new URLSearchParams({ active: "true", closed: "false", order: "volume", ascending: "false", limit: String(p.limit || 20) });
2597
+ if (p.category) qs.set("tag", p.category);
2598
+ if (p.sort_by === "closing_soon") {
2599
+ qs.set("order", "end_date");
2600
+ qs.set("ascending", "true");
2601
+ }
2602
+ if (p.sort_by === "new") {
2603
+ qs.set("order", "created_at");
2604
+ qs.set("ascending", "false");
2605
+ }
2606
+ const raw = await apiFetch(`${GAMMA_API}/markets?${qs}`);
2607
+ return jsonResult({ count: (raw || []).length, markets: (Array.isArray(raw) ? raw : []).map(slimMarket) });
2608
+ } catch (e) {
2609
+ return errorResult(e.message);
2610
+ }
2611
+ }
2612
+ },
2613
+ {
2614
+ name: "poly_market_comments",
2615
+ description: "Market comments/discussion",
2616
+ category: "enterprise",
2617
+ parameters: { type: "object", properties: { market_id: { type: "string" }, limit: { type: "number" }, order: { type: "string" } }, required: ["market_id"] },
2618
+ async execute(_id, p) {
2619
+ try {
2620
+ const qs = new URLSearchParams({ limit: String(p.limit || 30) });
2621
+ const comments = await apiFetch(`${GAMMA_API}/comments?market=${p.market_id}&${qs}`);
2622
+ return jsonResult(comments);
2623
+ } catch (e) {
2624
+ return errorResult(e.message);
2625
+ }
2626
+ }
2627
+ },
2628
+ {
2629
+ name: "poly_related_markets",
2630
+ description: "Find related markets",
2631
+ category: "enterprise",
2632
+ parameters: { type: "object", properties: { market_id: { type: "string" }, limit: { type: "number" } }, required: ["market_id"] },
2633
+ async execute(_id, p) {
2634
+ try {
2635
+ const market = await apiFetch(`${GAMMA_API}/markets/${p.market_id}`).catch(() => null);
2636
+ if (!market?.eventId) return jsonResult({ related: [], message: "No event association found" });
2637
+ const eventMarkets = await apiFetch(`${GAMMA_API}/markets?event_id=${market.eventId}&limit=${p.limit || 10}`);
2638
+ const related = (Array.isArray(eventMarkets) ? eventMarkets : []).filter((m) => (m.conditionId || m.id) !== p.market_id).map(slimMarket);
2639
+ return jsonResult({ eventId: market.eventId, related });
2640
+ } catch (e) {
2641
+ return errorResult(e.message);
2642
+ }
2643
+ }
2644
+ },
2645
+ {
2646
+ name: "poly_market_news",
2647
+ description: "Related news for a market",
2648
+ category: "enterprise",
2649
+ parameters: { type: "object", properties: { market_id: { type: "string" }, query: { type: "string" }, hours: { type: "number" }, limit: { type: "number" } } },
2650
+ async execute(_id, p) {
2651
+ try {
2652
+ let query = p.query;
2653
+ if (!query && p.market_id) {
2654
+ const m = cached(`market:${p.market_id}`) || await apiFetch(`${GAMMA_API}/markets/${p.market_id}`).catch(() => null);
2655
+ query = m?.question;
2656
+ }
2657
+ if (!query) return errorResult("Provide market_id or query");
2658
+ return jsonResult({
2659
+ status: "use_web_search",
2660
+ message: `Search news for: "${query}". Use the enterprise-http or web search tools to find related articles.`,
2661
+ suggested_query: query
2662
+ });
2663
+ } catch (e) {
2664
+ return errorResult(e.message);
2665
+ }
2666
+ }
2667
+ },
2668
+ // ═══ WALLET & ACCOUNT ═══════════════════════════════════════
2669
+ {
2670
+ name: "poly_setup_wallet",
2671
+ description: "Initialize trading wallet \u2014 auto-installs SDK if needed, stores credentials in enterprise DB",
2672
+ category: "enterprise",
2673
+ parameters: { type: "object", properties: {
2674
+ private_key: { type: "string" },
2675
+ funder_address: { type: "string" },
2676
+ signature_type: { type: "number" },
2677
+ rpc_url: { type: "string" }
2678
+ }, required: ["private_key"] },
2679
+ async execute(_id, p) {
2680
+ try {
2681
+ const sdk = await ensureSDK();
2682
+ if (!sdk.ready) {
2683
+ await saveWalletCredentials(agentId, db, {
2684
+ privateKey: p.private_key.startsWith("0x") ? p.private_key : `0x${p.private_key}`,
2685
+ funderAddress: p.funder_address,
2686
+ signatureType: p.signature_type || 0,
2687
+ rpcUrl: p.rpc_url
2688
+ });
2689
+ return jsonResult({
2690
+ status: "credentials_stored",
2691
+ message: `Wallet credentials saved to database. SDK status: ${sdk.message}`,
2692
+ note: "Credentials are persisted \u2014 they will survive server restarts. SDK will auto-install on next attempt."
2693
+ });
2694
+ }
2695
+ await saveWalletCredentials(agentId, db, {
2696
+ privateKey: p.private_key.startsWith("0x") ? p.private_key : `0x${p.private_key}`,
2697
+ funderAddress: p.funder_address,
2698
+ signatureType: p.signature_type || 0,
2699
+ rpcUrl: p.rpc_url
2700
+ });
2701
+ const client = await getClobClient(agentId, db);
2702
+ if (!client) return errorResult("Failed to initialize CLOB client after saving credentials");
2703
+ walletState.set(agentId, { connected: true, address: client.funderAddress, sigType: p.signature_type || 0 });
2704
+ return jsonResult({
2705
+ status: "connected",
2706
+ address: client.funderAddress,
2707
+ signer_address: client.address,
2708
+ signature_type: p.signature_type || 0,
2709
+ persisted: true,
2710
+ note: "Credentials stored in enterprise database. Will survive server restarts and redeployments."
2711
+ });
2712
+ } catch (e) {
2713
+ return errorResult(`Wallet setup failed: ${e.message}`);
2714
+ }
2715
+ }
2716
+ },
2717
+ {
2718
+ name: "poly_wallet_status",
2719
+ description: "Check wallet connection status",
2720
+ category: "enterprise",
2721
+ parameters: { type: "object", properties: {} },
2722
+ async execute() {
2723
+ const client = await getClobClient(agentId, db);
2724
+ if (!client) return jsonResult({ connected: false, message: "No wallet connected. Run poly_create_account or poly_setup_wallet." });
2725
+ return jsonResult({ connected: true, address: client.address, funder: client.funderAddress, signatureType: client.signatureType });
2726
+ }
2727
+ },
2728
+ {
2729
+ name: "poly_set_allowances",
2730
+ description: "Approve Polymarket exchange contracts to use your USDC for trading. This sends on-chain transactions (requires POL/MATIC for gas).",
2731
+ category: "enterprise",
2732
+ parameters: { type: "object", properties: {} },
2733
+ async execute() {
2734
+ const client = await getClobClient(agentId, db);
2735
+ if (!client) return errorResult("Wallet not connected. Run poly_create_account first.");
2736
+ try {
2737
+ const creds = await loadWalletCredentials(agentId, db);
2738
+ if (!creds) return errorResult("No wallet credentials");
2739
+ const { ethers } = await import("ethers");
2740
+ const rpcs = ["https://polygon.drpc.org", "https://polygon-bor-rpc.publicnode.com", "https://polygon.llamarpc.com", "https://polygon-rpc.com"];
2741
+ let provider;
2742
+ for (const rpc of rpcs) {
2743
+ try {
2744
+ provider = new ethers.JsonRpcProvider(rpc);
2745
+ await provider.getNetwork();
2746
+ break;
2747
+ } catch {
2748
+ provider = null;
2749
+ }
2750
+ }
2751
+ if (!provider) return errorResult("Cannot connect to Polygon network");
2752
+ const wallet = new ethers.Wallet(creds.privateKey, provider);
2753
+ const MAX_ALLOWANCE = "115792089237316195423570985008687907853269984665640564039457584007913129639935";
2754
+ const usdcAddresses = [
2755
+ USDC_E,
2756
+ // USDC.e (bridged)
2757
+ "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"
2758
+ // USDC (native)
2759
+ ];
2760
+ const spenders = [
2761
+ "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E",
2762
+ // CTF Exchange
2763
+ "0xC5d563A36AE78145C45a50134d48A1215220f80a",
2764
+ // Neg Risk CTF Exchange
2765
+ "0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296"
2766
+ // Neg Risk Adapter
2767
+ ];
2768
+ const erc20Abi = ["function approve(address spender, uint256 amount) returns (bool)", "function allowance(address owner, address spender) view returns (uint256)"];
2769
+ const ctfAbi = ["function setApprovalForAll(address operator, bool approved)", "function isApprovedForAll(address owner, address operator) view returns (bool)"];
2770
+ const txHashes = [];
2771
+ for (const usdc of usdcAddresses) {
2772
+ const contract = new ethers.Contract(usdc, erc20Abi, wallet);
2773
+ for (const spender of spenders) {
2774
+ try {
2775
+ const current = await contract.allowance(wallet.address, spender);
2776
+ if (current > BigInt(0)) continue;
2777
+ const tx = await contract.approve(spender, MAX_ALLOWANCE);
2778
+ await tx.wait();
2779
+ txHashes.push(tx.hash);
2780
+ } catch (e) {
2781
+ if (e.message?.includes("CALL_EXCEPTION")) continue;
2782
+ throw e;
2783
+ }
2784
+ }
2785
+ }
2786
+ const ctfAddress = CTF_ADDRESS;
2787
+ const ctfContract = new ethers.Contract(ctfAddress, ctfAbi, wallet);
2788
+ for (const spender of spenders) {
2789
+ try {
2790
+ const approved = await ctfContract.isApprovedForAll(wallet.address, spender);
2791
+ if (approved) continue;
2792
+ const tx = await ctfContract.setApprovalForAll(spender, true);
2793
+ await tx.wait();
2794
+ txHashes.push(tx.hash);
2795
+ } catch (e) {
2796
+ if (e.message?.includes("CALL_EXCEPTION")) continue;
2797
+ console.warn(`[allowance] CTF approval failed for ${spender}: ${e.message}`);
2798
+ }
2799
+ }
2800
+ return jsonResult({
2801
+ status: "allowances_set",
2802
+ transactions: txHashes,
2803
+ message: txHashes.length > 0 ? `Approved ${txHashes.length} contracts. You can now trade.` : "All contracts already approved."
2804
+ });
2805
+ } catch (e) {
2806
+ return jsonResult({ status: "failed", message: `Set allowances failed: ${e.message}. Ensure wallet has POL/MATIC for gas fees.` });
2807
+ }
2808
+ }
2809
+ },
2810
+ // ═══ BALANCE & FUNDS ════════════════════════════════════════
2811
+ {
2812
+ name: "poly_get_balance",
2813
+ description: "Get wallet USDC balance and exchange allowances. IMPORTANT: Polymarket requires USDC.e (bridged), NOT native USDC. If you have native USDC but no USDC.e, run poly_swap_to_usdce first.",
2814
+ category: "enterprise",
2815
+ parameters: { type: "object", properties: {} },
2816
+ async execute() {
2817
+ const client = await getClobClient(agentId, db);
2818
+ if (!client) return errorResult("Wallet not connected");
2819
+ try {
2820
+ const exchangeBalance = await client.client.getBalanceAllowance({ asset_type: "COLLATERAL" });
2821
+ let usdceBal = "0";
2822
+ let usdcNativeBal = "0";
2823
+ let polBal = "0";
2824
+ try {
2825
+ const { ethers } = await import("ethers");
2826
+ let provider;
2827
+ const rpcs = ["https://polygon.drpc.org", "https://polygon-bor-rpc.publicnode.com", "https://polygon.llamarpc.com", "https://polygon-rpc.com"];
2828
+ for (const rpc of rpcs) {
2829
+ try {
2830
+ provider = new ethers.JsonRpcProvider(rpc);
2831
+ await provider.getNetwork();
2832
+ break;
2833
+ } catch {
2834
+ provider = null;
2835
+ }
2836
+ }
2837
+ if (!provider) throw new Error("All Polygon RPCs failed");
2838
+ const balAbi = ["function balanceOf(address) view returns (uint256)"];
2839
+ try {
2840
+ const ce = new ethers.Contract(USDC_E, balAbi, provider);
2841
+ usdceBal = (Number(await ce.balanceOf(client.address)) / 1e6).toFixed(2);
2842
+ } catch {
2843
+ }
2844
+ try {
2845
+ const cn = new ethers.Contract("0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", balAbi, provider);
2846
+ usdcNativeBal = (Number(await cn.balanceOf(client.address)) / 1e6).toFixed(2);
2847
+ } catch {
2848
+ }
2849
+ try {
2850
+ polBal = ethers.formatEther(await provider.getBalance(client.address));
2851
+ polBal = parseFloat(polBal).toFixed(4);
2852
+ } catch {
2853
+ }
2854
+ } catch {
2855
+ }
2856
+ const walletUSDC = (parseFloat(usdceBal) + parseFloat(usdcNativeBal)).toFixed(2);
2857
+ const walletBal = parseFloat(walletUSDC);
2858
+ const allAllowancesZero = Object.values(exchangeBalance?.allowances || {}).every((v) => v === "0" || v === 0);
2859
+ const needsAllowances = walletBal > 0 && allAllowancesZero;
2860
+ if (needsAllowances) {
2861
+ try {
2862
+ const creds2 = await loadWalletCredentials(agentId, db);
2863
+ if (!creds2) throw new Error("No wallet credentials");
2864
+ const { ethers: ethers2 } = await import("ethers");
2865
+ let provider2;
2866
+ for (const rpc of ["https://polygon.drpc.org", "https://polygon-bor-rpc.publicnode.com", "https://polygon.llamarpc.com", "https://polygon-rpc.com"]) {
2867
+ try {
2868
+ provider2 = new ethers2.JsonRpcProvider(rpc);
2869
+ await provider2.getNetwork();
2870
+ break;
2871
+ } catch {
2872
+ provider2 = null;
2873
+ }
2874
+ }
2875
+ if (!provider2) throw new Error("Cannot connect to Polygon");
2876
+ const wallet2 = new ethers2.Wallet(creds2.privateKey, provider2);
2877
+ const MAX_ALLOWANCE = "115792089237316195423570985008687907853269984665640564039457584007913129639935";
2878
+ const usdcs = [USDC_E, "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"];
2879
+ const spenders = ["0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E", "0xC5d563A36AE78145C45a50134d48A1215220f80a", "0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296"];
2880
+ const abi = ["function approve(address,uint256) returns (bool)", "function allowance(address,address) view returns (uint256)"];
2881
+ for (const u of usdcs) {
2882
+ const c = new ethers2.Contract(u, abi, wallet2);
2883
+ for (const s of spenders) {
2884
+ try {
2885
+ const cur = await c.allowance(wallet2.address, s);
2886
+ if (cur > BigInt(0)) continue;
2887
+ const tx = await c.approve(s, MAX_ALLOWANCE);
2888
+ await tx.wait();
2889
+ } catch {
2890
+ }
2891
+ }
2892
+ }
2893
+ const updated = await client.client.getBalanceAllowance({ asset_type: "COLLATERAL" });
2894
+ const allApproved2 = Object.values(updated?.allowances || {}).every((v) => v !== "0" && v !== 0);
2895
+ return jsonResult({
2896
+ address: client.address,
2897
+ usdc_e_bridged: usdceBal,
2898
+ usdc_native: usdcNativeBal,
2899
+ pol_gas: polBal,
2900
+ wallet_usdc_total: walletUSDC,
2901
+ exchange_balance: updated.balance,
2902
+ trading_approved: allApproved2,
2903
+ status: "allowances_auto_set",
2904
+ needs_swap: parseFloat(usdceBal) === 0 && parseFloat(usdcNativeBal) > 0,
2905
+ message: parseFloat(usdceBal) === 0 && parseFloat(usdcNativeBal) > 0 ? `\u26A0\uFE0F Wallet has $${usdcNativeBal} native USDC but $0 USDC.e. Polymarket ONLY accepts USDC.e! Run poly_swap_to_usdce to convert.` : `Wallet has $${usdceBal} USDC.e. Exchange contracts approved \u2014 you can now trade.`
2906
+ });
2907
+ } catch (e) {
2908
+ return jsonResult({
2909
+ address: client.address,
2910
+ usdc_e_bridged: usdceBal,
2911
+ usdc_native: usdcNativeBal,
2912
+ pol_gas: polBal,
2913
+ exchange_balance: exchangeBalance.balance,
2914
+ trading_approved: false,
2915
+ status: "needs_allowances",
2916
+ message: `Approval failed: ${e.message}`
2917
+ });
2918
+ }
2919
+ }
2920
+ const allApproved = Object.values(exchangeBalance?.allowances || {}).every((v) => v !== "0" && v !== 0);
2921
+ const needsSwap = parseFloat(usdceBal) === 0 && parseFloat(usdcNativeBal) > 0;
2922
+ return jsonResult({
2923
+ address: client.address,
2924
+ usdc_e_bridged: usdceBal,
2925
+ usdc_native: usdcNativeBal,
2926
+ pol_gas: polBal,
2927
+ wallet_usdc_total: walletUSDC,
2928
+ exchange_balance: exchangeBalance.balance,
2929
+ trading_approved: allApproved,
2930
+ available_to_trade: usdceBal,
2931
+ needs_swap: needsSwap,
2932
+ status: needsSwap ? "needs_swap" : parseFloat(usdceBal) > 0 ? "funded" : "no_funds",
2933
+ message: needsSwap ? `\u26A0\uFE0F You have $${usdcNativeBal} native USDC but Polymarket requires USDC.e (bridged). Run poly_swap_to_usdce to convert.` : void 0
2934
+ });
2935
+ } catch (e) {
2936
+ return jsonResult({ address: client.address, status: "balance_check_failed", message: e.message });
2937
+ }
2938
+ }
2939
+ },
2940
+ {
2941
+ name: "poly_deposit",
2942
+ description: "Deposit instructions",
2943
+ category: "enterprise",
2944
+ parameters: { type: "object", properties: { amount: { type: "number" }, source_chain: { type: "string" } } },
2945
+ async execute() {
2946
+ const client = await getClobClient(agentId, db);
2947
+ const addr = client?.address || "(connect wallet first)";
2948
+ return jsonResult({
2949
+ deposit_address: addr,
2950
+ network: "Polygon (MATIC)",
2951
+ token: `USDC (${USDC_E})`,
2952
+ instructions: [
2953
+ `1. Send USDC on Polygon to: ${addr}`,
2954
+ "2. Wait for confirmation (~2 seconds on Polygon)",
2955
+ "3. Funds will appear in your Polymarket balance",
2956
+ "Note: You can also deposit via polymarket.com bridge from Ethereum, Arbitrum, Base, or Optimism."
2957
+ ]
2958
+ });
2959
+ }
2960
+ },
2961
+ {
2962
+ name: "poly_swap_to_usdce",
2963
+ description: "Swap native USDC to USDC.e (bridged) on Polygon. Polymarket ONLY accepts USDC.e for trading. Uses Uniswap V3 swap router. Run this if poly_get_balance shows native USDC but no USDC.e.",
2964
+ category: "enterprise",
2965
+ parameters: { type: "object", properties: {
2966
+ amount: { type: "number", description: "Amount in USD to swap. Leave empty to swap entire native USDC balance." }
2967
+ } },
2968
+ async execute(_id, p) {
2969
+ const creds = await loadWalletCredentials(agentId, db);
2970
+ if (!creds) return errorResult("Wallet not connected");
2971
+ try {
2972
+ const { ethers } = await import("ethers");
2973
+ let provider;
2974
+ for (const rpc of ["https://polygon.drpc.org", "https://polygon-bor-rpc.publicnode.com", "https://polygon.llamarpc.com", "https://polygon-rpc.com"]) {
2975
+ try {
2976
+ provider = new ethers.JsonRpcProvider(rpc);
2977
+ await provider.getNetwork();
2978
+ break;
2979
+ } catch {
2980
+ provider = null;
2981
+ }
2982
+ }
2983
+ if (!provider) return errorResult("Cannot connect to Polygon RPC");
2984
+ const wallet = new ethers.Wallet(creds.privateKey, provider);
2985
+ const USDC_NATIVE = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359";
2986
+ const SWAP_ROUTER = "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45";
2987
+ const erc20Abi = [
2988
+ "function balanceOf(address) view returns (uint256)",
2989
+ "function approve(address,uint256) returns (bool)",
2990
+ "function allowance(address,address) view returns (uint256)"
2991
+ ];
2992
+ const nativeContract = new ethers.Contract(USDC_NATIVE, erc20Abi, wallet);
2993
+ const nativeBal = await nativeContract.balanceOf(wallet.address);
2994
+ if (nativeBal === BigInt(0)) {
2995
+ return jsonResult({ status: "no_native_usdc", message: "No native USDC to swap. Balance is 0." });
2996
+ }
2997
+ let swapAmount = nativeBal;
2998
+ if (p.amount && p.amount > 0) {
2999
+ swapAmount = BigInt(Math.floor(p.amount * 1e6));
3000
+ if (swapAmount > nativeBal) swapAmount = nativeBal;
3001
+ }
3002
+ const swapUSD = (Number(swapAmount) / 1e6).toFixed(2);
3003
+ const currentAllowance = await nativeContract.allowance(wallet.address, SWAP_ROUTER);
3004
+ if (currentAllowance < swapAmount) {
3005
+ const approveTx = await nativeContract.approve(SWAP_ROUTER, "115792089237316195423570985008687907853269984665640564039457584007913129639935");
3006
+ await approveTx.wait();
3007
+ }
3008
+ const swapRouterAbi = [
3009
+ "function exactInputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96)) external payable returns (uint256 amountOut)"
3010
+ ];
3011
+ const router = new ethers.Contract(SWAP_ROUTER, swapRouterAbi, wallet);
3012
+ const minOut = swapAmount * BigInt(995) / BigInt(1e3);
3013
+ const tx = await router.exactInputSingle({
3014
+ tokenIn: USDC_NATIVE,
3015
+ tokenOut: USDC_E,
3016
+ fee: 100,
3017
+ // 0.01% fee tier for stablecoin pairs
3018
+ recipient: wallet.address,
3019
+ amountIn: swapAmount,
3020
+ amountOutMinimum: minOut,
3021
+ sqrtPriceLimitX96: BigInt(0)
3022
+ });
3023
+ const receipt = await tx.wait();
3024
+ const usdceContract = new ethers.Contract(USDC_E, ["function balanceOf(address) view returns (uint256)"], provider);
3025
+ const newBal = await usdceContract.balanceOf(wallet.address);
3026
+ return jsonResult({
3027
+ status: "swapped",
3028
+ swapped_amount: swapUSD,
3029
+ tx_hash: receipt.hash,
3030
+ new_usdce_balance: (Number(newBal) / 1e6).toFixed(2),
3031
+ message: `Successfully swapped $${swapUSD} native USDC \u2192 USDC.e. You can now trade on Polymarket.`
3032
+ });
3033
+ } catch (e) {
3034
+ if (e.message?.includes("revert") || e.message?.includes("STF") || e.message?.includes("Too little received")) {
3035
+ try {
3036
+ const { ethers } = await import("ethers");
3037
+ let provider;
3038
+ for (const rpc of ["https://polygon.llamarpc.com", "https://polygon-bor-rpc.publicnode.com"]) {
3039
+ try {
3040
+ provider = new ethers.JsonRpcProvider(rpc);
3041
+ await provider.getNetwork();
3042
+ break;
3043
+ } catch {
3044
+ provider = null;
3045
+ }
3046
+ }
3047
+ if (!provider) return errorResult("RPC failed on retry");
3048
+ const wallet = new ethers.Wallet(creds.privateKey, provider);
3049
+ const nativeContract = new ethers.Contract("0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", ["function balanceOf(address) view returns (uint256)"], provider);
3050
+ const nativeBal = await nativeContract.balanceOf(wallet.address);
3051
+ let swapAmount = nativeBal;
3052
+ if (p.amount && p.amount > 0) {
3053
+ swapAmount = BigInt(Math.floor(p.amount * 1e6));
3054
+ if (swapAmount > nativeBal) swapAmount = nativeBal;
3055
+ }
3056
+ const router = new ethers.Contract("0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45", [
3057
+ "function exactInputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96)) external payable returns (uint256 amountOut)"
3058
+ ], wallet);
3059
+ const minOut = swapAmount * BigInt(990) / BigInt(1e3);
3060
+ const tx = await router.exactInputSingle({
3061
+ tokenIn: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
3062
+ tokenOut: USDC_E,
3063
+ fee: 500,
3064
+ // 0.05% fee tier
3065
+ recipient: wallet.address,
3066
+ amountIn: swapAmount,
3067
+ amountOutMinimum: minOut,
3068
+ sqrtPriceLimitX96: BigInt(0)
3069
+ });
3070
+ const receipt = await tx.wait();
3071
+ const usdceContract = new ethers.Contract(USDC_E, ["function balanceOf(address) view returns (uint256)"], provider);
3072
+ const newBal = await usdceContract.balanceOf(wallet.address);
3073
+ return jsonResult({
3074
+ status: "swapped",
3075
+ fee_tier: "0.05%",
3076
+ swapped_amount: (Number(swapAmount) / 1e6).toFixed(2),
3077
+ tx_hash: receipt.hash,
3078
+ new_usdce_balance: (Number(newBal) / 1e6).toFixed(2),
3079
+ message: `Swapped via 0.05% pool. You can now trade on Polymarket.`
3080
+ });
3081
+ } catch (e2) {
3082
+ return errorResult(`Swap failed on both fee tiers. Error: ${e2.message}`);
3083
+ }
3084
+ }
3085
+ return errorResult(`Swap failed: ${e.message}`);
3086
+ }
3087
+ }
3088
+ },
3089
+ {
3090
+ name: "poly_withdraw",
3091
+ description: "Withdraw USDC",
3092
+ category: "enterprise",
3093
+ parameters: { type: "object", properties: { amount: { type: "number" }, to_address: { type: "string" } }, required: ["amount", "to_address"] },
3094
+ async execute(_id, p) {
3095
+ const config = await loadConfig(agentId, db);
3096
+ if (config.mode === "approval") {
3097
+ return jsonResult({ status: "requires_approval", message: `Withdrawal of $${p.amount} USDC to ${p.to_address} requires human approval.` });
3098
+ }
3099
+ const client = await getClobClient(agentId, db);
3100
+ if (!client) return errorResult("Wallet not connected");
3101
+ return jsonResult({ status: "requires_manual", message: `Withdraw $${p.amount} USDC to ${p.to_address}. Use polymarket.com/portfolio for manual withdrawals.` });
3102
+ }
3103
+ },
3104
+ // ═══ PORTFOLIO & POSITIONS ══════════════════════════════════
3105
+ {
3106
+ name: "poly_get_positions",
3107
+ description: "Get open positions",
3108
+ category: "enterprise",
3109
+ parameters: { type: "object", properties: { market_id: { type: "string" }, min_value: { type: "number" }, sort_by: { type: "string" } } },
3110
+ async execute(_id, p) {
3111
+ const client = await getClobClient(agentId, db);
3112
+ if (!client) return errorResult("Wallet not connected");
3113
+ try {
3114
+ const addr = client.funderAddress || client.address;
3115
+ let positions = await apiFetch(`https://data-api.polymarket.com/positions?user=${addr}`).catch(() => null);
3116
+ if (!positions) {
3117
+ positions = await apiFetch(`${GAMMA_API}/positions?user=${addr}&limit=100`).catch(() => null);
3118
+ }
3119
+ if (!positions || Array.isArray(positions) && positions.length === 0) return jsonResult({ address: addr, status: "no_positions" });
3120
+ const trimmed = positions.map((pos) => ({
3121
+ asset: pos.asset,
3122
+ conditionId: pos.conditionId,
3123
+ size: pos.size,
3124
+ avgPrice: pos.avgPrice,
3125
+ currentPrice: pos.curPrice || pos.currentPrice,
3126
+ title: pos.title,
3127
+ outcome: pos.outcome,
3128
+ market_slug: pos.market_slug || pos.slug,
3129
+ pnl: pos.cashPnl ?? pos.pnl,
3130
+ percentPnl: pos.percentPnl,
3131
+ redeemable: pos.redeemable,
3132
+ resolved: pos.resolved,
3133
+ negRisk: pos.negRisk
3134
+ }));
3135
+ return jsonResult(trimmed);
3136
+ } catch (e) {
3137
+ return errorResult(e.message);
3138
+ }
3139
+ }
3140
+ },
3141
+ {
3142
+ name: "poly_get_closed_positions",
3143
+ description: "Get closed positions",
3144
+ category: "enterprise",
3145
+ parameters: { type: "object", properties: { limit: { type: "number" }, offset: { type: "number" }, market_id: { type: "string" }, won_only: { type: "boolean" }, lost_only: { type: "boolean" } } },
3146
+ async execute(_id, p) {
3147
+ const client = await getClobClient(agentId, db);
3148
+ if (!client) return errorResult("Wallet not connected");
3149
+ try {
3150
+ const addr = client.funderAddress || client.address;
3151
+ const qs = new URLSearchParams({ user: addr, limit: String(p.limit || 50) });
3152
+ if (p.offset) qs.set("offset", String(p.offset));
3153
+ let positions = await apiFetch(`https://data-api.polymarket.com/positions/closed?${qs}`).catch(() => null);
3154
+ if (!positions) {
3155
+ positions = await apiFetch(`${GAMMA_API}/positions/closed?${qs}`).catch(() => null);
3156
+ }
3157
+ return jsonResult(positions || { address: addr, status: "no_closed_positions" });
3158
+ } catch (e) {
3159
+ return errorResult(e.message);
3160
+ }
3161
+ }
3162
+ },
3163
+ {
3164
+ name: "poly_redeem",
3165
+ description: "Redeem winning tokens from resolved markets. Checks Data API for redeemable positions and calls CTF contract redeemPositions(). Use redeem_all=true to claim ALL redeemable positions, or pass a specific condition_id.",
3166
+ category: "enterprise",
3167
+ parameters: { type: "object", properties: {
3168
+ condition_id: { type: "string", description: "Specific conditionId to redeem" },
3169
+ redeem_all: { type: "boolean", description: "Redeem all redeemable positions" }
3170
+ } },
3171
+ async execute(_id, p) {
3172
+ try {
3173
+ const creds = await loadWalletCredentials(agentId, db);
3174
+ if (!creds?.privateKey) return errorResult("No wallet credentials. Set up wallet first.");
3175
+ const wallet = creds.funderAddress;
3176
+ if (!wallet) return errorResult("No wallet address found.");
3177
+ const posRes = await fetch(`https://data-api.polymarket.com/positions?user=${wallet}&sizeThreshold=0`);
3178
+ const positions = await posRes.json();
3179
+ const redeemable = positions.filter((pos) => pos.redeemable === true);
3180
+ if (!redeemable.length) return jsonResult({ status: "nothing_to_redeem", message: "No redeemable positions found." });
3181
+ const toRedeem = p?.condition_id ? redeemable.filter((pos) => pos.conditionId === p.condition_id) : p?.redeem_all ? redeemable : redeemable.slice(0, 1);
3182
+ if (!toRedeem.length) return jsonResult({ status: "not_found", message: `No redeemable position found for condition ${p.condition_id}`, available: redeemable.map((r) => ({ title: r.title, conditionId: r.conditionId, value: r.currentValue })) });
3183
+ const { ethers } = await import("ethers");
3184
+ const CTF_ABI = [
3185
+ "function redeemPositions(address collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint256[] calldata indexSets) external"
3186
+ ];
3187
+ const rpcs = ["https://polygon-bor-rpc.publicnode.com", "https://polygon.drpc.org"];
3188
+ let provider;
3189
+ for (const rpc of rpcs) {
3190
+ try {
3191
+ provider = new ethers.JsonRpcProvider(rpc);
3192
+ await provider.getBlockNumber();
3193
+ break;
3194
+ } catch {
3195
+ continue;
3196
+ }
3197
+ }
3198
+ if (!provider) return errorResult("All RPCs failed");
3199
+ const signer = new ethers.Wallet(creds.privateKey, provider);
3200
+ const ctf = new ethers.Contract(CTF_ADDRESS, CTF_ABI, signer);
3201
+ const results = [];
3202
+ for (const pos of toRedeem) {
3203
+ try {
3204
+ const parentCollectionId = "0x0000000000000000000000000000000000000000000000000000000000000000";
3205
+ const indexSets = pos.negativeRisk ? [1] : [1, 2];
3206
+ const tx = await ctf.redeemPositions(
3207
+ USDC_E,
3208
+ parentCollectionId,
3209
+ pos.conditionId,
3210
+ indexSets,
3211
+ { gasLimit: 3e5 }
3212
+ );
3213
+ const receipt = await tx.wait();
3214
+ try {
3215
+ const { safeDbExec: safeDbExec2 } = await import("./polymarket-shared-M5ZU3HKM.js");
3216
+ await safeDbExec2(
3217
+ db,
3218
+ `UPDATE poly_trade_log SET status = 'redeemed', pnl = $1 WHERE token_id = $2 AND agent_id = $3 AND status != 'redeemed'`,
3219
+ [pos.cashPnl || 0, pos.asset, agentId]
3220
+ );
3221
+ } catch {
3222
+ }
3223
+ results.push({
3224
+ title: pos.title,
3225
+ outcome: pos.outcome,
3226
+ conditionId: pos.conditionId,
3227
+ shares: pos.size,
3228
+ value: pos.currentValue,
3229
+ profit: pos.cashPnl,
3230
+ txHash: receipt.hash,
3231
+ status: "redeemed"
3232
+ });
3233
+ } catch (e) {
3234
+ results.push({
3235
+ title: pos.title,
3236
+ conditionId: pos.conditionId,
3237
+ status: "failed",
3238
+ error: e.message
3239
+ });
3240
+ }
3241
+ }
3242
+ return jsonResult({
3243
+ status: "complete",
3244
+ redeemed: results.filter((r) => r.status === "redeemed").length,
3245
+ failed: results.filter((r) => r.status === "failed").length,
3246
+ total_value: results.filter((r) => r.status === "redeemed").reduce((s, r) => s + (r.value || 0), 0),
3247
+ total_profit: results.filter((r) => r.status === "redeemed").reduce((s, r) => s + (r.profit || 0), 0),
3248
+ details: results
3249
+ });
3250
+ } catch (e) {
3251
+ return errorResult(e.message);
3252
+ }
3253
+ }
3254
+ },
3255
+ {
3256
+ name: "poly_portfolio_summary",
3257
+ description: "Portfolio analytics",
3258
+ category: "enterprise",
3259
+ parameters: { type: "object", properties: { period: { type: "string" }, include_closed: { type: "boolean" }, include_charts: { type: "boolean" } } },
3260
+ async execute(_id, p) {
3261
+ const config = await loadConfig(agentId, db);
3262
+ const counter = await getDailyCounter(agentId, db);
3263
+ const paperPos = await getPaperPositions(agentId, db);
3264
+ const client = await getClobClient(agentId, db);
3265
+ return jsonResult({
3266
+ wallet: client?.address || "Not connected",
3267
+ config_mode: config.mode,
3268
+ daily_trades: `${counter.count}/${config.maxDailyTrades}`,
3269
+ daily_loss: `$${counter.loss}/$${config.maxDailyLoss}`,
3270
+ circuit_breaker: counter.paused ? `PAUSED: ${counter.reason}` : "OK",
3271
+ paper_positions: paperPos.length,
3272
+ sdk_ready: (await ensureSDK()).ready
3273
+ });
3274
+ }
3275
+ },
3276
+ // ═══ ORDER MANAGEMENT ═══════════════════════════════════════
3277
+ {
3278
+ name: "poly_place_order",
3279
+ description: "Place an order",
3280
+ category: "enterprise",
3281
+ parameters: { type: "object", properties: {
3282
+ token_id: { type: "string" },
3283
+ side: { type: "string" },
3284
+ price: { type: "number" },
3285
+ size: { type: "number" },
3286
+ order_type: { type: "string" },
3287
+ expiration: { type: "string" },
3288
+ max_slippage_pct: { type: "number" },
3289
+ tick_size: { type: "string" },
3290
+ neg_risk: { type: "boolean" },
3291
+ market_question: { type: "string" },
3292
+ outcome: { type: "string" },
3293
+ rationale: { type: "string" },
3294
+ urgency: { type: "string" }
3295
+ }, required: ["token_id", "side", "size"] },
3296
+ async execute(_id, p) {
3297
+ const config = await loadConfig(agentId, db);
3298
+ const check = preTradeChecks(agentId, config, p);
3299
+ if (check) return errorResult(check);
3300
+ try {
3301
+ const unresolved = await getUnresolvedPredictions(agentId, db, void 0);
3302
+ const hasPrediction = unresolved.some(
3303
+ (u) => u.token_id === p.token_id || p.market_id && u.market_id === p.market_id || p.market_question && u.market_question && u.market_question.toLowerCase().includes(p.market_question.toLowerCase().slice(0, 30))
3304
+ );
3305
+ if (!hasPrediction && unresolved.length === 0) {
3306
+ return errorResult(
3307
+ "PIPELINE: No predictions recorded yet. Before placing orders, call poly_record_prediction with your analysis (token_id, predicted_outcome, predicted_probability, confidence, reasoning). This journals your trade thesis for the learning loop."
3308
+ );
3309
+ }
3310
+ } catch {
3311
+ }
3312
+ if (config.mode === "paper") {
3313
+ const mid = await apiFetch(`${CLOB_API}/midpoint?token_id=${p.token_id}`).catch(() => ({ mid: p.price || 0.5 }));
3314
+ const fillPrice = p.price || parseFloat(mid?.mid || "0.5");
3315
+ await savePaperPosition(db, {
3316
+ agentId,
3317
+ tokenId: p.token_id,
3318
+ side: p.side,
3319
+ entryPrice: fillPrice,
3320
+ size: p.size,
3321
+ marketQuestion: p.market_question || "",
3322
+ rationale: p.rationale || ""
3323
+ });
3324
+ await incrementDailyCounter(agentId, db);
3325
+ const tradeId2 = `paper_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
3326
+ await logTrade(db, {
3327
+ id: tradeId2,
3328
+ agentId,
3329
+ tokenId: p.token_id,
3330
+ marketQuestion: p.market_question,
3331
+ outcome: p.outcome,
3332
+ side: p.side,
3333
+ price: fillPrice,
3334
+ size: p.size,
3335
+ fillPrice,
3336
+ fillSize: p.size,
3337
+ status: "paper_filled",
3338
+ rationale: p.rationale
3339
+ });
3340
+ return jsonResult({
3341
+ status: "paper_filled",
3342
+ message: `PAPER TRADE: ${p.side} ${p.size} shares at ${fillPrice}`,
3343
+ trade_id: tradeId2,
3344
+ persisted: true
3345
+ });
3346
+ }
3347
+ if (!p.price && p.max_slippage_pct) {
3348
+ try {
3349
+ const mid = await apiFetch(`${CLOB_API}/midpoint?token_id=${p.token_id}`);
3350
+ const midPrice = parseFloat(mid?.mid || "0.5");
3351
+ const book = await apiFetch(`${CLOB_API}/book?token_id=${p.token_id}`);
3352
+ const levels = p.side === "BUY" ? book?.asks || [] : book?.bids || [];
3353
+ if (levels.length === 0) return errorResult("Order book is empty \u2014 cannot execute market order");
3354
+ let filled = 0, cost = 0;
3355
+ for (const level of levels) {
3356
+ const lvlPrice = parseFloat(level.price);
3357
+ const lvlSize = parseFloat(level.size);
3358
+ const fill = Math.min(p.size - filled, lvlSize);
3359
+ cost += fill * lvlPrice;
3360
+ filled += fill;
3361
+ if (filled >= p.size) break;
3362
+ }
3363
+ if (filled < p.size) return errorResult(`Insufficient liquidity: only ${filled.toFixed(2)} of ${p.size} available`);
3364
+ const avgPrice = cost / filled;
3365
+ const slippage = Math.abs(avgPrice - midPrice) / midPrice * 100;
3366
+ if (slippage > p.max_slippage_pct) {
3367
+ return errorResult(`Slippage ${slippage.toFixed(2)}% exceeds max ${p.max_slippage_pct}%. Midpoint: ${midPrice}, estimated avg fill: ${avgPrice.toFixed(4)}`);
3368
+ }
3369
+ } catch (e) {
3370
+ }
3371
+ }
3372
+ const tradeId = `trade_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
3373
+ if (config.mode === "approval") {
3374
+ const rules = await getAutoApproveRules(agentId, db);
3375
+ const autoApproved = rules.some(
3376
+ (rule) => p.size <= rule.maxSize && rule.sides.includes(p.side)
3377
+ );
3378
+ if (autoApproved) {
3379
+ await incrementDailyCounter(agentId, db);
3380
+ return await executeOrder(agentId, db, tradeId, p, "auto_approved");
3381
+ }
3382
+ await savePendingTrade(db, {
3383
+ id: tradeId,
3384
+ agentId,
3385
+ tokenId: p.token_id,
3386
+ side: p.side,
3387
+ price: p.price || null,
3388
+ size: p.size,
3389
+ orderType: p.order_type || "GTC",
3390
+ tickSize: p.tick_size || "0.01",
3391
+ negRisk: p.neg_risk || false,
3392
+ marketQuestion: p.market_question || "",
3393
+ outcome: p.outcome || "",
3394
+ rationale: p.rationale || "",
3395
+ urgency: p.urgency || "normal"
3396
+ });
3397
+ return jsonResult({
3398
+ status: "pending_approval",
3399
+ trade_id: tradeId,
3400
+ message: `Trade queued for approval: ${p.side} $${p.size} of "${p.outcome || p.token_id}" at ${p.price || "market"}`,
3401
+ persisted: true,
3402
+ dashboard_url: "/polymarket"
3403
+ });
3404
+ }
3405
+ await incrementDailyCounter(agentId, db);
3406
+ return await executeOrder(agentId, db, tradeId, p, "autonomous");
3407
+ }
3408
+ },
3409
+ {
3410
+ name: "poly_place_batch_orders",
3411
+ description: "Place multiple orders at once",
3412
+ category: "enterprise",
3413
+ parameters: { type: "object", properties: {
3414
+ orders: { type: "array", items: { type: "object" } },
3415
+ atomic: { type: "boolean" },
3416
+ rationale: { type: "string" }
3417
+ }, required: ["orders"] },
3418
+ async execute(_id, p) {
3419
+ const config = await loadConfig(agentId, db);
3420
+ const results = [];
3421
+ const errors = [];
3422
+ for (const order of p.orders || []) {
3423
+ const check = preTradeChecks(agentId, config, order);
3424
+ if (check) {
3425
+ if (p.atomic) return errorResult(`Atomic batch failed: ${check}`);
3426
+ errors.push({ order, error: check });
3427
+ continue;
3428
+ }
3429
+ results.push({ ...order, status: config.mode === "approval" ? "pending_approval" : "queued" });
3430
+ }
3431
+ return jsonResult({
3432
+ total: (p.orders || []).length,
3433
+ accepted: results.length,
3434
+ rejected: errors.length,
3435
+ orders: results,
3436
+ errors: errors.length > 0 ? errors : void 0,
3437
+ note: config.mode === "approval" ? "All orders queued for approval" : "Batch execution requires SDK"
3438
+ });
3439
+ }
3440
+ },
3441
+ {
3442
+ name: "poly_get_open_orders",
3443
+ description: "Get open orders",
3444
+ category: "enterprise",
3445
+ parameters: { type: "object", properties: { market_id: { type: "string" }, token_id: { type: "string" } } },
3446
+ async execute() {
3447
+ const pending = Array.from(pendingTrades.values()).filter((t) => t.agentId === agentId);
3448
+ return jsonResult({ pending_approvals: pending, live_orders: "requires_sdk" });
3449
+ }
3450
+ },
3451
+ {
3452
+ name: "poly_get_order",
3453
+ description: "Get order details",
3454
+ category: "enterprise",
3455
+ parameters: { type: "object", properties: { order_id: { type: "string" } }, required: ["order_id"] },
3456
+ async execute(_id, p) {
3457
+ const pending = pendingTrades.get(p.order_id);
3458
+ if (pending) return jsonResult({ status: "pending_approval", trade: pending });
3459
+ return jsonResult({ status: "requires_sdk", message: "Live order lookup requires authenticated CLOB client." });
3460
+ }
3461
+ },
3462
+ {
3463
+ name: "poly_cancel_order",
3464
+ description: "Cancel an order",
3465
+ category: "enterprise",
3466
+ parameters: { type: "object", properties: { order_id: { type: "string" } }, required: ["order_id"] },
3467
+ async execute(_id, p) {
3468
+ if (pendingTrades.has(p.order_id)) {
3469
+ pendingTrades.delete(p.order_id);
3470
+ return jsonResult({ status: "cancelled", type: "pending_approval", order_id: p.order_id });
3471
+ }
3472
+ return jsonResult({ status: "requires_sdk", message: "Cancelling live orders requires CLOB client SDK" });
3473
+ }
3474
+ },
3475
+ {
3476
+ name: "poly_cancel_orders",
3477
+ description: "Cancel multiple orders",
3478
+ category: "enterprise",
3479
+ parameters: { type: "object", properties: {
3480
+ order_ids: { type: "array", items: { type: "string" } },
3481
+ market_id: { type: "string" },
3482
+ token_id: { type: "string" },
3483
+ side: { type: "string" }
3484
+ } },
3485
+ async execute(_id, p) {
3486
+ let cancelled = [];
3487
+ if (p.order_ids) {
3488
+ for (const id of p.order_ids) {
3489
+ if (pendingTrades.delete(id)) cancelled.push(id);
3490
+ }
3491
+ } else {
3492
+ for (const [id, t] of pendingTrades) {
3493
+ if (t.agentId !== agentId) continue;
3494
+ if (p.token_id && t.tokenId !== p.token_id) continue;
3495
+ if (p.side && t.side !== p.side) continue;
3496
+ pendingTrades.delete(id);
3497
+ cancelled.push(id);
3498
+ }
3499
+ }
3500
+ return jsonResult({ cancelled_count: cancelled.length, cancelled_ids: cancelled });
3501
+ }
3502
+ },
3503
+ {
3504
+ name: "poly_cancel_all",
3505
+ description: "Cancel ALL orders (emergency)",
3506
+ category: "enterprise",
3507
+ parameters: { type: "object", properties: { confirm: { type: "boolean" } } },
3508
+ async execute(_id, p) {
3509
+ if (!p.confirm) return errorResult("Set confirm=true to cancel all orders");
3510
+ const cancelled = [];
3511
+ for (const [id, t] of pendingTrades) {
3512
+ if (t.agentId === agentId) {
3513
+ pendingTrades.delete(id);
3514
+ cancelled.push(id);
3515
+ }
3516
+ }
3517
+ return jsonResult({ status: "all_cancelled", cancelled_pending: cancelled.length, note: "Live CLOB orders require SDK to cancel" });
3518
+ }
3519
+ },
3520
+ {
3521
+ name: "poly_replace_order",
3522
+ description: "Replace an order atomically",
3523
+ category: "enterprise",
3524
+ parameters: { type: "object", properties: {
3525
+ old_order_id: { type: "string" },
3526
+ token_id: { type: "string" },
3527
+ side: { type: "string" },
3528
+ price: { type: "number" },
3529
+ size: { type: "number" },
3530
+ order_type: { type: "string" }
3531
+ }, required: ["old_order_id", "token_id", "side", "price", "size"] },
3532
+ async execute(_id, p) {
3533
+ pendingTrades.delete(p.old_order_id);
3534
+ const config = await loadConfig(agentId, db);
3535
+ const check = preTradeChecks(agentId, config, p);
3536
+ if (check) return errorResult(check);
3537
+ return jsonResult({ status: "replaced", cancelled: p.old_order_id, new_order: { token_id: p.token_id, side: p.side, price: p.price, size: p.size } });
3538
+ }
3539
+ },
3540
+ // ═══ TRADE HISTORY ══════════════════════════════════════════
3541
+ {
3542
+ name: "poly_trade_history",
3543
+ description: "Trade history",
3544
+ category: "enterprise",
3545
+ parameters: { type: "object", properties: {
3546
+ limit: { type: "number" },
3547
+ offset: { type: "number" },
3548
+ market_id: { type: "string" },
3549
+ side: { type: "string" },
3550
+ start_date: { type: "string" },
3551
+ end_date: { type: "string" },
3552
+ min_size: { type: "number" }
3553
+ } },
3554
+ async execute(_id, p) {
3555
+ const client = await getClobClient(agentId, db);
3556
+ if (!client) return errorResult("Wallet not connected");
3557
+ try {
3558
+ const qs = new URLSearchParams({ maker_address: client.address, limit: String(p.limit || 50) });
3559
+ if (p.market_id) qs.set("market", p.market_id);
3560
+ const trades = await apiFetch(`${CLOB_API}/trades?${qs}`).catch(() => null);
3561
+ return jsonResult(trades || { status: "requires_sdk" });
3562
+ } catch (e) {
3563
+ return errorResult(e.message);
3564
+ }
3565
+ }
3566
+ },
3567
+ {
3568
+ name: "poly_export_trades",
3569
+ description: "Export trade history",
3570
+ category: "enterprise",
3571
+ parameters: { type: "object", properties: {
3572
+ format: { type: "string" },
3573
+ start_date: { type: "string" },
3574
+ end_date: { type: "string" },
3575
+ include_fees: { type: "boolean" },
3576
+ output_path: { type: "string" }
3577
+ } },
3578
+ async execute() {
3579
+ return jsonResult({ status: "requires_sdk", message: "Trade export requires authenticated CLOB client for full history access." });
3580
+ }
3581
+ },
3582
+ // ═══ ANALYSIS ═══════════════════════════════════════════════
3583
+ {
3584
+ name: "poly_analyze_market",
3585
+ description: "Deep market analysis",
3586
+ category: "enterprise",
3587
+ parameters: { type: "object", properties: {
3588
+ market_id: { type: "string" },
3589
+ depth: { type: "string" },
3590
+ include_news: { type: "boolean" },
3591
+ include_whale_trades: { type: "boolean" },
3592
+ include_book_analysis: { type: "boolean" }
3593
+ }, required: ["market_id"] },
3594
+ async execute(_id, p) {
3595
+ try {
3596
+ let marketData = await apiFetch(`${GAMMA_API}/markets/${p.market_id}`).catch(() => null);
3597
+ if (!marketData) {
3598
+ const arr = await apiFetch(`${GAMMA_API}/markets?slug=${p.market_id}&limit=1`).catch(() => null);
3599
+ if (Array.isArray(arr) && arr[0]) marketData = arr[0];
3600
+ }
3601
+ if (!marketData && p.market_id?.startsWith("0x")) {
3602
+ const arr = await apiFetch(`${GAMMA_API}/markets?condition_id=${p.market_id}&limit=1`).catch(() => null);
3603
+ if (Array.isArray(arr) && arr[0]) marketData = arr[0];
3604
+ }
3605
+ if (!marketData) return errorResult('Market not found \u2014 try using the slug (e.g. "will-trump-win") or condition_id');
3606
+ const timeseries = await apiFetch(`${GAMMA_API}/markets/${marketData.conditionId || p.market_id}/timeseries?fidelity=50`).catch(() => null);
3607
+ let yesPrice = null, noPrice = null;
3608
+ try {
3609
+ const prices = JSON.parse(marketData.outcomePrices || "[]");
3610
+ yesPrice = parseFloat(prices[0]);
3611
+ noPrice = parseFloat(prices[1]);
3612
+ } catch {
3613
+ }
3614
+ let bookAnalysis = null;
3615
+ if (p.include_book_analysis !== false && marketData.clobTokenIds) {
3616
+ try {
3617
+ const tokenIds = JSON.parse(marketData.clobTokenIds || "[]");
3618
+ if (tokenIds[0]) {
3619
+ const book = await apiFetch(`${CLOB_API}/book?token_id=${tokenIds[0]}`);
3620
+ const totalBids = (book?.bids || []).reduce((s, b) => s + parseFloat(b.size) * parseFloat(b.price), 0);
3621
+ const totalAsks = (book?.asks || []).reduce((s, a) => s + parseFloat(a.size) * parseFloat(a.price), 0);
3622
+ const bestBid = book?.bids?.[0]?.price;
3623
+ const bestAsk = book?.asks?.[0]?.price;
3624
+ bookAnalysis = {
3625
+ bestBid,
3626
+ bestAsk,
3627
+ spread: bestBid && bestAsk ? (parseFloat(bestAsk) - parseFloat(bestBid)).toFixed(4) : null,
3628
+ spreadPct: bestBid && bestAsk ? ((parseFloat(bestAsk) - parseFloat(bestBid)) / parseFloat(bestAsk) * 100).toFixed(2) + "%" : null,
3629
+ bidLiquidity: totalBids.toFixed(2),
3630
+ askLiquidity: totalAsks.toFixed(2),
3631
+ imbalance: totalBids + totalAsks > 0 ? ((totalBids - totalAsks) / (totalBids + totalAsks) * 100).toFixed(1) + "%" : null,
3632
+ bidLevels: (book?.bids || []).length,
3633
+ askLevels: (book?.asks || []).length
3634
+ };
3635
+ }
3636
+ } catch {
3637
+ }
3638
+ }
3639
+ let whaleTrades = null;
3640
+ if (p.include_whale_trades !== false) {
3641
+ try {
3642
+ const tokenIds = JSON.parse(marketData.clobTokenIds || "[]");
3643
+ if (tokenIds[0]) {
3644
+ const trades = await apiFetch(`${CLOB_API}/trades?asset_id=${tokenIds[0]}&limit=50`);
3645
+ const largeOnes = (Array.isArray(trades) ? trades : []).filter((t) => parseFloat(t.size || "0") >= 50).slice(0, 10);
3646
+ if (largeOnes.length > 0) whaleTrades = largeOnes;
3647
+ }
3648
+ } catch {
3649
+ }
3650
+ }
3651
+ let priceMovement = null;
3652
+ if (Array.isArray(timeseries) && timeseries.length >= 2) {
3653
+ const first = timeseries[0];
3654
+ const last = timeseries[timeseries.length - 1];
3655
+ const firstP = parseFloat(first?.p || first?.price || "0");
3656
+ const lastP = parseFloat(last?.p || last?.price || "0");
3657
+ if (firstP > 0) {
3658
+ priceMovement = {
3659
+ startPrice: firstP.toFixed(3),
3660
+ currentPrice: lastP.toFixed(3),
3661
+ changePct: ((lastP - firstP) / firstP * 100).toFixed(1) + "%",
3662
+ dataPoints: timeseries.length
3663
+ };
3664
+ }
3665
+ }
3666
+ return jsonResult({
3667
+ market: {
3668
+ question: marketData.question,
3669
+ description: marketData.description?.slice(0, 800),
3670
+ volume: marketData.volume,
3671
+ liquidity: marketData.liquidity,
3672
+ endDate: marketData.endDate,
3673
+ resolved: marketData.resolved,
3674
+ negRisk: marketData.negRisk,
3675
+ tickSize: marketData.minimumTickSize,
3676
+ clobTokenIds: marketData.clobTokenIds
3677
+ },
3678
+ prices: { yes: yesPrice, no: noPrice },
3679
+ impliedProbability: {
3680
+ yes: yesPrice ? `${(yesPrice * 100).toFixed(1)}%` : null,
3681
+ no: noPrice ? `${(noPrice * 100).toFixed(1)}%` : null,
3682
+ overround: yesPrice && noPrice ? ((yesPrice + noPrice - 1) * 100).toFixed(2) + "%" : null
3683
+ },
3684
+ priceMovement,
3685
+ orderBook: bookAnalysis,
3686
+ whaleTrades
3687
+ });
3688
+ } catch (e) {
3689
+ return errorResult(e.message);
3690
+ }
3691
+ }
3692
+ },
3693
+ {
3694
+ name: "poly_compare_markets",
3695
+ description: "Compare markets side-by-side",
3696
+ category: "enterprise",
3697
+ parameters: { type: "object", properties: {
3698
+ market_ids: { type: "array", items: { type: "string" } },
3699
+ metrics: { type: "array", items: { type: "string" } }
3700
+ }, required: ["market_ids"] },
3701
+ async execute(_id, p) {
3702
+ try {
3703
+ const results = await Promise.all(
3704
+ (p.market_ids || []).map(async (id) => {
3705
+ const m = await apiFetch(`${GAMMA_API}/markets/${id}`).catch(() => null);
3706
+ if (!m) return { id, error: "not found" };
3707
+ let prices = {};
3708
+ try {
3709
+ const pp = JSON.parse(m.outcomePrices || "[]");
3710
+ prices = { yes: pp[0], no: pp[1] };
3711
+ } catch {
3712
+ }
3713
+ return { id: m.conditionId || m.id, question: m.question, ...prices, volume: m.volume, liquidity: m.liquidity, endDate: m.endDate };
3714
+ })
3715
+ );
3716
+ return jsonResult({ comparison: results });
3717
+ } catch (e) {
3718
+ return errorResult(e.message);
3719
+ }
3720
+ }
3721
+ },
3722
+ {
3723
+ name: "poly_estimate_fill",
3724
+ description: "Simulate order fill against order book",
3725
+ category: "enterprise",
3726
+ parameters: { type: "object", properties: {
3727
+ token_id: { type: "string" },
3728
+ side: { type: "string" },
3729
+ size: { type: "number" }
3730
+ }, required: ["token_id", "side", "size"] },
3731
+ async execute(_id, p) {
3732
+ try {
3733
+ const [book, mid] = await Promise.all([
3734
+ apiFetch(`${CLOB_API}/book?token_id=${p.token_id}`),
3735
+ apiFetch(`${CLOB_API}/midpoint?token_id=${p.token_id}`)
3736
+ ]);
3737
+ const levels = p.side === "BUY" ? book?.asks || [] : book?.bids || [];
3738
+ const midPrice = parseFloat(mid?.mid || "0.5");
3739
+ let filled = 0, totalCost = 0, levelsConsumed = 0;
3740
+ for (const level of levels) {
3741
+ const lvlPrice = parseFloat(level.price);
3742
+ const lvlSize = parseFloat(level.size);
3743
+ const fill = Math.min(p.size - filled, lvlSize);
3744
+ totalCost += fill * lvlPrice;
3745
+ filled += fill;
3746
+ levelsConsumed++;
3747
+ if (filled >= p.size) break;
3748
+ }
3749
+ const avgPrice = filled > 0 ? totalCost / filled : 0;
3750
+ const slippage = midPrice > 0 ? Math.abs(avgPrice - midPrice) / midPrice * 100 : 0;
3751
+ return jsonResult({
3752
+ requested_size: p.size,
3753
+ fillable_size: parseFloat(filled.toFixed(4)),
3754
+ fully_fillable: filled >= p.size,
3755
+ midpoint: midPrice,
3756
+ estimated_avg_price: parseFloat(avgPrice.toFixed(6)),
3757
+ estimated_cost: parseFloat(totalCost.toFixed(2)),
3758
+ slippage_pct: parseFloat(slippage.toFixed(3)),
3759
+ price_levels_consumed: levelsConsumed,
3760
+ total_levels_available: levels.length,
3761
+ warning: filled < p.size ? `Only ${filled.toFixed(2)} of ${p.size} can be filled at current liquidity` : void 0
3762
+ });
3763
+ } catch (e) {
3764
+ return errorResult(e.message);
3765
+ }
3766
+ }
3767
+ },
3768
+ // ═══ CONFIGURATION ══════════════════════════════════════════
3769
+ {
3770
+ name: "poly_set_config",
3771
+ description: "Configure trading behavior",
3772
+ category: "enterprise",
3773
+ parameters: { type: "object", properties: {
3774
+ mode: { type: "string" },
3775
+ max_position_size: { type: "number" },
3776
+ max_order_size: { type: "number" },
3777
+ max_total_exposure: { type: "number" },
3778
+ max_daily_trades: { type: "number" },
3779
+ max_daily_loss: { type: "number" },
3780
+ max_drawdown_pct: { type: "number" },
3781
+ allowed_categories: { type: "array", items: { type: "string" } },
3782
+ blocked_categories: { type: "array", items: { type: "string" } },
3783
+ blocked_markets: { type: "array", items: { type: "string" } },
3784
+ min_liquidity: { type: "number" },
3785
+ min_volume: { type: "number" },
3786
+ max_spread_pct: { type: "number" },
3787
+ stop_loss_pct: { type: "number" },
3788
+ take_profit_pct: { type: "number" },
3789
+ trailing_stop_pct: { type: "number" },
3790
+ rebalance_interval: { type: "string" },
3791
+ notification_channel: { type: "string" },
3792
+ notify_on: { type: "array", items: { type: "string" } },
3793
+ cash_reserve_pct: { type: "number" }
3794
+ } },
3795
+ async execute(_id, p) {
3796
+ const existing = await loadConfig(agentId, db);
3797
+ const updated = {
3798
+ mode: p.mode || existing.mode,
3799
+ maxPositionSize: p.max_position_size ?? existing.maxPositionSize,
3800
+ maxOrderSize: p.max_order_size ?? existing.maxOrderSize,
3801
+ maxTotalExposure: p.max_total_exposure ?? existing.maxTotalExposure,
3802
+ maxDailyTrades: p.max_daily_trades ?? existing.maxDailyTrades,
3803
+ maxDailyLoss: p.max_daily_loss ?? existing.maxDailyLoss,
3804
+ maxDrawdownPct: p.max_drawdown_pct ?? existing.maxDrawdownPct,
3805
+ allowedCategories: p.allowed_categories ?? existing.allowedCategories,
3806
+ blockedCategories: p.blocked_categories ?? existing.blockedCategories,
3807
+ blockedMarkets: p.blocked_markets ?? existing.blockedMarkets,
3808
+ minLiquidity: p.min_liquidity ?? existing.minLiquidity,
3809
+ minVolume: p.min_volume ?? existing.minVolume,
3810
+ maxSpreadPct: p.max_spread_pct ?? existing.maxSpreadPct,
3811
+ stopLossPct: p.stop_loss_pct ?? existing.stopLossPct,
3812
+ takeProfitPct: p.take_profit_pct ?? existing.takeProfitPct,
3813
+ trailingStopPct: p.trailing_stop_pct ?? existing.trailingStopPct,
3814
+ rebalanceInterval: p.rebalance_interval ?? existing.rebalanceInterval,
3815
+ notificationChannel: p.notification_channel ?? existing.notificationChannel,
3816
+ notifyOn: p.notify_on ?? existing.notifyOn,
3817
+ cashReservePct: p.cash_reserve_pct ?? existing.cashReservePct
3818
+ };
3819
+ await saveConfig(agentId, db, updated);
3820
+ agentConfigs.set(agentId, updated);
3821
+ return jsonResult({ status: "ok", config: updated, persisted: true });
3822
+ }
3823
+ },
3824
+ {
3825
+ name: "poly_get_config",
3826
+ description: "Get current config",
3827
+ category: "enterprise",
3828
+ parameters: { type: "object", properties: {} },
3829
+ async execute() {
3830
+ return jsonResult(await loadConfig(agentId, db));
3831
+ }
3832
+ },
3833
+ {
3834
+ name: "poly_circuit_breaker",
3835
+ description: "Circuit breaker controls",
3836
+ category: "enterprise",
3837
+ parameters: { type: "object", properties: { action: { type: "string" }, reason: { type: "string" } }, required: ["action"] },
3838
+ async execute(_id, p) {
3839
+ switch (p.action) {
3840
+ case "status": {
3841
+ const counter = await getDailyCounter(agentId, db);
3842
+ return jsonResult({ paused: counter.paused, reason: counter.reason, dailyTrades: counter.count, dailyLoss: counter.loss });
3843
+ }
3844
+ case "pause":
3845
+ await pauseTrading(agentId, db, p.reason || "Manual pause");
3846
+ circuitBreakerState.set(agentId, { paused: true, reason: p.reason || "Manual pause", pausedAt: (/* @__PURE__ */ new Date()).toISOString() });
3847
+ return jsonResult({ status: "paused", reason: p.reason || "Manual pause" });
3848
+ case "resume":
3849
+ await resumeTrading(agentId, db);
3850
+ circuitBreakerState.set(agentId, { paused: false, reason: "" });
3851
+ return jsonResult({ status: "resumed" });
3852
+ case "reset_daily":
3853
+ await resumeTrading(agentId, db);
3854
+ dailyCounters.delete(agentId);
3855
+ circuitBreakerState.set(agentId, { paused: false, reason: "" });
3856
+ return jsonResult({ status: "reset", message: "Daily counters and circuit breaker reset" });
3857
+ default:
3858
+ return errorResult("action must be: status, pause, resume, or reset_daily");
3859
+ }
3860
+ }
3861
+ },
3862
+ // ═══ PRICE ALERTS ═══════════════════════════════════════════
3863
+ {
3864
+ name: "poly_set_alert",
3865
+ description: "Set a price alert",
3866
+ category: "enterprise",
3867
+ parameters: { type: "object", properties: {
3868
+ token_id: { type: "string" },
3869
+ market_question: { type: "string" },
3870
+ condition: { type: "string" },
3871
+ target_price: { type: "number" },
3872
+ pct_change: { type: "number" },
3873
+ repeat: { type: "boolean" },
3874
+ auto_trade: { type: "object" }
3875
+ }, required: ["token_id", "condition"] },
3876
+ async execute(_id, p) {
3877
+ try {
3878
+ const { safeDbGet: safeDbGet2 } = await import("./polymarket-shared-M5ZU3HKM.js");
3879
+ const watcherCount = await safeDbGet2(db, `SELECT COUNT(*) as cnt FROM poly_watchers WHERE agent_id = ? AND status = 'active'`, [agentId]);
3880
+ if (!watcherCount || watcherCount.cnt === 0 || watcherCount.cnt === "0") {
3881
+ return jsonResult({
3882
+ status: "BLOCKED",
3883
+ error: "You have ZERO active watchers. You MUST set up watchers BEFORE creating alerts.",
3884
+ required_action: "Run poly_watcher_config action=set provider=xai model=grok-3-mini FIRST, then run poly_setup_monitors to create your monitoring suite. After watchers are active, you can create alerts.",
3885
+ help: "Alerts are simple price triggers. Watchers are AI-powered monitors that appear on the Monitors tab. Your manager requires BOTH."
3886
+ });
3887
+ }
3888
+ } catch {
3889
+ }
3890
+ let basePrice = 0.5;
3891
+ try {
3892
+ const mid = await apiFetch(`${CLOB_API}/midpoint?token_id=${p.token_id}`);
3893
+ basePrice = parseFloat(mid?.mid || "0.5");
3894
+ } catch {
3895
+ }
3896
+ const alertId = `alert_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
3897
+ await saveAlert(db, {
3898
+ id: alertId,
3899
+ agentId,
3900
+ tokenId: p.token_id,
3901
+ marketQuestion: p.market_question || "",
3902
+ condition: p.condition,
3903
+ targetPrice: p.target_price,
3904
+ pctChange: p.pct_change,
3905
+ basePrice,
3906
+ repeat: p.repeat || false,
3907
+ autoTrade: p.auto_trade
3908
+ });
3909
+ return jsonResult({ status: "created", alert_id: alertId, persisted: true, note: "Alerts are checked during heartbeat/polling." });
3910
+ }
3911
+ },
3912
+ {
3913
+ name: "poly_list_alerts",
3914
+ description: "List price alerts",
3915
+ category: "enterprise",
3916
+ parameters: { type: "object", properties: {} },
3917
+ async execute() {
3918
+ const alerts = await getAlerts(agentId, db);
3919
+ return jsonResult({ count: alerts.length, alerts });
3920
+ }
3921
+ },
3922
+ {
3923
+ name: "poly_delete_alert",
3924
+ description: "Delete price alert",
3925
+ category: "enterprise",
3926
+ parameters: { type: "object", properties: { alert_id: { type: "string" }, delete_all: { type: "boolean" } } },
3927
+ async execute(_id, p) {
3928
+ if (p.delete_all) {
3929
+ await deleteAllAlerts(agentId, db);
3930
+ return jsonResult({ status: "all_deleted" });
3931
+ }
3932
+ await deleteAlert(db, p.alert_id);
3933
+ return jsonResult({ status: "deleted" });
3934
+ }
3935
+ },
3936
+ // ═══ BRACKET ORDERS (Take-Profit + Stop-Loss) ═════════════
3937
+ {
3938
+ name: "poly_bracket_config",
3939
+ description: "Configure automatic bracket orders (take-profit + stop-loss) that are created on every BUY. When a BUY executes, two sell alerts are auto-created: one at +X% (take profit) and one at -Y% (stop loss). When either fires, the other is cancelled (OCO).",
3940
+ category: "enterprise",
3941
+ parameters: { type: "object", properties: {
3942
+ enabled: { type: "boolean", description: "Enable/disable bracket orders (default: true)" },
3943
+ take_profit_pct: { type: "number", description: "Take-profit percentage above buy price (default: 15)" },
3944
+ stop_loss_pct: { type: "number", description: "Stop-loss percentage below buy price (default: 10)" }
3945
+ } },
3946
+ async execute(_id, p) {
3947
+ const config = await loadConfig(agentId, db) || {};
3948
+ const bracket = config.bracket || {};
3949
+ if (p.enabled !== void 0) bracket.enabled = p.enabled;
3950
+ if (p.take_profit_pct !== void 0) {
3951
+ bracket.take_profit_pct = p.take_profit_pct;
3952
+ config.takeProfitPct = p.take_profit_pct;
3953
+ }
3954
+ if (p.stop_loss_pct !== void 0) {
3955
+ bracket.stop_loss_pct = p.stop_loss_pct;
3956
+ config.stopLossPct = p.stop_loss_pct;
3957
+ }
3958
+ config.bracket = bracket;
3959
+ await saveConfig(agentId, db, config);
3960
+ return jsonResult({
3961
+ status: "updated",
3962
+ bracket: {
3963
+ enabled: bracket.enabled !== false,
3964
+ take_profit_pct: bracket.take_profit_pct ?? config.takeProfitPct ?? 15,
3965
+ stop_loss_pct: bracket.stop_loss_pct ?? config.stopLossPct ?? 10
3966
+ },
3967
+ note: "Also synced to central Trading Configuration (poly_set_config).",
3968
+ message: bracket.enabled === false ? "Bracket orders DISABLED. BUY orders will NOT auto-create TP/SL alerts." : `Bracket orders ENABLED. Every BUY will auto-create: TP at +${bracket.take_profit_pct ?? config.takeProfitPct ?? 15}%, SL at -${bracket.stop_loss_pct ?? config.stopLossPct ?? 10}%`
3969
+ });
3970
+ }
3971
+ },
3972
+ {
3973
+ name: "poly_list_brackets",
3974
+ description: "List active bracket order pairs (take-profit + stop-loss groups)",
3975
+ category: "enterprise",
3976
+ parameters: { type: "object", properties: {} },
3977
+ async execute() {
3978
+ try {
3979
+ const rows = await (db.query || db.execute).call(
3980
+ db,
3981
+ `SELECT * FROM poly_price_alerts WHERE agent_id = $1 AND bracket_group IS NOT NULL AND triggered = 0 ORDER BY bracket_group, bracket_role`,
3982
+ [agentId]
3983
+ );
3984
+ const alerts = rows?.rows || rows || [];
3985
+ const groups = {};
3986
+ for (const a of alerts) {
3987
+ if (!groups[a.bracket_group]) groups[a.bracket_group] = { group: a.bracket_group, alerts: [] };
3988
+ groups[a.bracket_group].alerts.push({
3989
+ id: a.id,
3990
+ role: a.bracket_role,
3991
+ condition: a.condition,
3992
+ target_price: a.target_price,
3993
+ base_price: a.base_price,
3994
+ token_id: a.token_id,
3995
+ market: a.market_question
3996
+ });
3997
+ }
3998
+ const bracketList = Object.values(groups);
3999
+ return jsonResult({ count: bracketList.length, brackets: bracketList });
4000
+ } catch {
4001
+ return jsonResult({ count: 0, brackets: [] });
4002
+ }
4003
+ }
4004
+ },
4005
+ // ═══ APPROVAL QUEUE ═════════════════════════════════════════
4006
+ {
4007
+ name: "poly_pending_trades",
4008
+ description: "List pending approvals",
4009
+ category: "enterprise",
4010
+ parameters: { type: "object", properties: {} },
4011
+ async execute() {
4012
+ const pending = await getPendingTrades(agentId, db);
4013
+ return jsonResult({ count: pending.length, trades: pending });
4014
+ }
4015
+ },
4016
+ {
4017
+ name: "poly_approve_trade",
4018
+ description: "Approve a trade and execute it",
4019
+ category: "enterprise",
4020
+ parameters: { type: "object", properties: { trade_id: { type: "string" }, modify_price: { type: "number" }, modify_size: { type: "number" } }, required: ["trade_id"] },
4021
+ async execute(_id, p) {
4022
+ await resolvePendingTrade(db, p.trade_id, "approved", "agent");
4023
+ await incrementDailyCounter(agentId, db);
4024
+ const pending = await getPendingTrades(agentId, db);
4025
+ const trade = pending.find((t) => t.id === p.trade_id);
4026
+ if (trade) {
4027
+ const execResult = await executeOrder(agentId, db, p.trade_id, {
4028
+ token_id: trade.token_id,
4029
+ side: trade.side,
4030
+ price: p.modify_price || trade.price,
4031
+ size: p.modify_size || trade.size,
4032
+ order_type: trade.order_type,
4033
+ tick_size: trade.tick_size,
4034
+ neg_risk: trade.neg_risk,
4035
+ market_question: trade.market_question,
4036
+ outcome: trade.outcome,
4037
+ rationale: trade.rationale
4038
+ }, "approved");
4039
+ return execResult;
4040
+ }
4041
+ return jsonResult({ status: "approved", trade_id: p.trade_id, message: "Trade approved" });
4042
+ }
4043
+ },
4044
+ {
4045
+ name: "poly_reject_trade",
4046
+ description: "Reject a trade",
4047
+ category: "enterprise",
4048
+ parameters: { type: "object", properties: { trade_id: { type: "string" }, reason: { type: "string" } }, required: ["trade_id"] },
4049
+ async execute(_id, p) {
4050
+ await resolvePendingTrade(db, p.trade_id, "rejected", p.reason || "agent");
4051
+ return jsonResult({ status: "rejected", trade_id: p.trade_id, reason: p.reason });
4052
+ }
4053
+ },
4054
+ {
4055
+ name: "poly_auto_approve_rule",
4056
+ description: "Auto-approve rules",
4057
+ category: "enterprise",
4058
+ parameters: { type: "object", properties: {
4059
+ action: { type: "string" },
4060
+ rule_id: { type: "string" },
4061
+ max_size: { type: "number" },
4062
+ categories: { type: "array", items: { type: "string" } },
4063
+ sides: { type: "array", items: { type: "string" } }
4064
+ }, required: ["action"] },
4065
+ async execute(_id, p) {
4066
+ const rules = await getAutoApproveRules(agentId, db);
4067
+ switch (p.action) {
4068
+ case "list":
4069
+ return jsonResult({ rules });
4070
+ case "add": {
4071
+ const rule = {
4072
+ id: `rule_${Date.now()}`,
4073
+ agentId,
4074
+ maxSize: p.max_size || 10,
4075
+ categories: p.categories || [],
4076
+ sides: p.sides || ["BUY", "SELL"]
4077
+ };
4078
+ await saveAutoApproveRule(db, rule);
4079
+ return jsonResult({ status: "added", rule, total_rules: rules.length + 1 });
4080
+ }
4081
+ case "remove": {
4082
+ await deleteAutoApproveRule(db, p.rule_id);
4083
+ return jsonResult({ status: "removed" });
4084
+ }
4085
+ default:
4086
+ return errorResult("action must be: list, add, or remove");
4087
+ }
4088
+ }
4089
+ },
4090
+ // ═══ LEADERBOARD & SOCIAL ═══════════════════════════════════
4091
+ {
4092
+ name: "poly_leaderboard",
4093
+ description: "Top traders",
4094
+ category: "enterprise",
4095
+ parameters: { type: "object", properties: { period: { type: "string" }, limit: { type: "number" }, sort_by: { type: "string" } } },
4096
+ async execute(_id, p) {
4097
+ try {
4098
+ const data = await apiFetch(`${GAMMA_API}/leaderboard?limit=${p.limit || 20}`);
4099
+ return jsonResult(data);
4100
+ } catch (e) {
4101
+ return errorResult(e.message);
4102
+ }
4103
+ }
4104
+ },
4105
+ {
4106
+ name: "poly_top_holders",
4107
+ description: "Top holders for a market",
4108
+ category: "enterprise",
4109
+ parameters: { type: "object", properties: { market_id: { type: "string" }, outcome: { type: "string" }, limit: { type: "number" } }, required: ["market_id"] },
4110
+ async execute(_id, p) {
4111
+ try {
4112
+ const data = await apiFetch(`${GAMMA_API}/markets/${p.market_id}/holders?limit=${p.limit || 20}`);
4113
+ return jsonResult(data);
4114
+ } catch (e) {
4115
+ return errorResult(e.message);
4116
+ }
4117
+ }
4118
+ },
4119
+ {
4120
+ name: "poly_track_wallet",
4121
+ description: "Track a wallet's activity",
4122
+ category: "enterprise",
4123
+ parameters: { type: "object", properties: {
4124
+ address: { type: "string" },
4125
+ include_positions: { type: "boolean" },
4126
+ include_trades: { type: "boolean" },
4127
+ limit: { type: "number" }
4128
+ }, required: ["address"] },
4129
+ async execute(_id, p) {
4130
+ try {
4131
+ const results = { address: p.address };
4132
+ const [positions, trades] = await Promise.all([
4133
+ p.include_positions !== false ? apiFetch(`https://data-api.polymarket.com/positions?user=${p.address}`).catch(() => apiFetch(`${GAMMA_API}/positions?user=${p.address}&limit=${p.limit || 20}`).catch(() => null)) : null,
4134
+ p.include_trades !== false ? apiFetch(`${CLOB_API}/trades?maker_address=${p.address}&limit=${p.limit || 20}`).catch(() => null) : null
4135
+ ]);
4136
+ if (positions) results.positions = positions;
4137
+ if (trades) results.trades = trades;
4138
+ return jsonResult(results);
4139
+ } catch (e) {
4140
+ return errorResult(e.message);
4141
+ }
4142
+ }
4143
+ },
4144
+ // ═══ PAPER TRADING ══════════════════════════════════════════
4145
+ {
4146
+ name: "poly_paper_trade",
4147
+ description: "Simulate a trade",
4148
+ category: "enterprise",
4149
+ parameters: { type: "object", properties: {
4150
+ token_id: { type: "string" },
4151
+ side: { type: "string" },
4152
+ price: { type: "number" },
4153
+ size: { type: "number" },
4154
+ market_question: { type: "string" },
4155
+ rationale: { type: "string" }
4156
+ }, required: ["token_id", "side", "size"] },
4157
+ async execute(_id, p) {
4158
+ try {
4159
+ const mid = await apiFetch(`${CLOB_API}/midpoint?token_id=${p.token_id}`).catch(() => ({ mid: p.price || "0.5" }));
4160
+ const fillPrice = p.price || parseFloat(mid?.mid || "0.5");
4161
+ await savePaperPosition(db, {
4162
+ agentId,
4163
+ tokenId: p.token_id,
4164
+ side: p.side,
4165
+ entryPrice: fillPrice,
4166
+ size: p.size,
4167
+ marketQuestion: p.market_question || "",
4168
+ rationale: p.rationale || ""
4169
+ });
4170
+ return jsonResult({ status: "paper_filled", entry_price: fillPrice, persisted: true });
4171
+ } catch (e) {
4172
+ return errorResult(e.message);
4173
+ }
4174
+ }
4175
+ },
4176
+ {
4177
+ name: "poly_paper_portfolio",
4178
+ description: "Paper trading portfolio",
4179
+ category: "enterprise",
4180
+ parameters: { type: "object", properties: {} },
4181
+ async execute() {
4182
+ const positions = await getPaperPositions(agentId, db);
4183
+ const withPnl = await Promise.all(positions.map(async (pos) => {
4184
+ try {
4185
+ const mid = await apiFetch(`${CLOB_API}/midpoint?token_id=${pos.token_id}`);
4186
+ const currentPrice = parseFloat(mid?.mid || String(pos.entry_price));
4187
+ const pnl = pos.side === "BUY" ? (currentPrice - pos.entry_price) * pos.size : (pos.entry_price - currentPrice) * pos.size;
4188
+ return { ...pos, currentPrice, pnl: parseFloat(pnl.toFixed(2)), pnlPct: parseFloat((pnl / (pos.entry_price * pos.size) * 100).toFixed(2)) };
4189
+ } catch {
4190
+ return { ...pos, currentPrice: null, pnl: null };
4191
+ }
4192
+ }));
4193
+ const totalPnl = withPnl.reduce((s, p) => s + (p.pnl || 0), 0);
4194
+ const winners = withPnl.filter((p) => (p.pnl || 0) > 0).length;
4195
+ const losers = withPnl.filter((p) => (p.pnl || 0) < 0).length;
4196
+ return jsonResult({
4197
+ positions: withPnl,
4198
+ summary: {
4199
+ totalPositions: positions.length,
4200
+ totalPnl: parseFloat(totalPnl.toFixed(2)),
4201
+ winners,
4202
+ losers,
4203
+ winRate: positions.length > 0 ? `${(winners / positions.length * 100).toFixed(0)}%` : "N/A"
4204
+ }
4205
+ });
4206
+ }
4207
+ },
4208
+ // ═══ SYSTEM & HEALTH ════════════════════════════════════════
4209
+ {
4210
+ name: "poly_api_status",
4211
+ description: "Check API health",
4212
+ category: "enterprise",
4213
+ parameters: { type: "object", properties: {} },
4214
+ async execute() {
4215
+ const start = Date.now();
4216
+ try {
4217
+ const [clobOk, gammaOk] = await Promise.all([
4218
+ apiFetch(`${CLOB_API}/`, { timeoutMs: 5e3 }).then(() => true).catch(() => false),
4219
+ apiFetch(`${GAMMA_API}/markets?limit=1`, { timeoutMs: 5e3 }).then(() => true).catch(() => false)
4220
+ ]);
4221
+ return jsonResult({
4222
+ clob_api: clobOk ? "healthy" : "unreachable",
4223
+ gamma_api: gammaOk ? "healthy" : "unreachable",
4224
+ latency_ms: Date.now() - start,
4225
+ chain: "Polygon (137)"
4226
+ });
4227
+ } catch (e) {
4228
+ return errorResult(e.message);
4229
+ }
4230
+ }
4231
+ },
4232
+ {
4233
+ name: "poly_gas_price",
4234
+ description: "Polygon gas price",
4235
+ category: "enterprise",
4236
+ parameters: { type: "object", properties: {} },
4237
+ async execute() {
4238
+ try {
4239
+ const gas = await apiFetch("https://gasstation.polygon.technology/v2");
4240
+ return jsonResult(gas);
4241
+ } catch (e) {
4242
+ return jsonResult({ status: "fallback", message: "Gas API unavailable. Polygon typically costs <0.01 MATIC per tx." });
4243
+ }
4244
+ }
4245
+ },
4246
+ {
4247
+ name: "poly_heartbeat",
4248
+ description: "COMPREHENSIVE MARKET WATCHER \u2014 Run this periodically (every 15-30 min via cron or heartbeat). Checks: (1) all active price alerts and fires triggered ones, (2) open positions for P&L changes, (3) unresolved predictions that may have settled, (4) portfolio health and balance, (5) CLOB API status. Returns a full status report with any actions needed.",
4249
+ category: "enterprise",
4250
+ parameters: { type: "object", properties: {
4251
+ check_alerts: { type: "boolean", description: "Check price alerts (default: true)" },
4252
+ check_positions: { type: "boolean", description: "Check open positions (default: true)" },
4253
+ check_predictions: { type: "boolean", description: "Check unresolved predictions (default: true)" },
4254
+ check_balance: { type: "boolean", description: "Check wallet/exchange balance (default: true)" },
4255
+ run_screener: { type: "boolean", description: "Run quick screener for new opportunities (default: false \u2014 set true for active scanning)" }
4256
+ } },
4257
+ async execute(_id, p) {
4258
+ const report = { timestamp: (/* @__PURE__ */ new Date()).toISOString(), actions_needed: [] };
4259
+ try {
4260
+ const time = await apiFetch(`${CLOB_API}/time`);
4261
+ report.api_status = "online";
4262
+ report.server_time = time;
4263
+ } catch (e) {
4264
+ report.api_status = "DOWN";
4265
+ report.api_error = e.message;
4266
+ report.actions_needed.push({ type: "critical", message: "CLOB API is DOWN \u2014 cannot trade" });
4267
+ }
4268
+ if (p.check_alerts !== false) {
4269
+ try {
4270
+ const triggered = await checkAlerts(agentId, db);
4271
+ report.alerts = {
4272
+ triggered: triggered.length,
4273
+ details: triggered
4274
+ };
4275
+ for (const t of triggered) {
4276
+ report.actions_needed.push({
4277
+ type: "alert_triggered",
4278
+ message: `ALERT: ${t.market} \u2014 ${t.reason}`,
4279
+ token_id: t.token_id,
4280
+ auto_trade: t.auto_trade
4281
+ });
4282
+ }
4283
+ const remaining = await getAlerts(agentId, db);
4284
+ report.alerts.active_count = remaining.length;
4285
+ } catch (e) {
4286
+ report.alerts = { error: e.message };
4287
+ }
4288
+ }
4289
+ if (p.check_positions !== false) {
4290
+ try {
4291
+ const client = await getClobClient(agentId, db);
4292
+ if (client) {
4293
+ const openOrders = await apiFetch(`${CLOB_API}/orders?status=live`, {
4294
+ headers: { Authorization: `Bearer ${client.apiKey}` }
4295
+ }).catch(() => []);
4296
+ const addr = client.funderAddress || client.address;
4297
+ let positions = await apiFetch(`https://data-api.polymarket.com/positions?user=${addr}`).catch(() => null);
4298
+ if (!positions) positions = await apiFetch(`${GAMMA_API}/positions?user=${addr}&limit=20`).catch(() => []);
4299
+ const activePositions = (Array.isArray(positions) ? positions : []).filter(
4300
+ (pos) => parseFloat(pos.size || "0") > 0
4301
+ );
4302
+ report.positions = {
4303
+ open_orders: Array.isArray(openOrders) ? openOrders.length : 0,
4304
+ active_positions: activePositions.length
4305
+ };
4306
+ for (const pos of activePositions.slice(0, 10)) {
4307
+ try {
4308
+ const tokenId = pos.asset || pos.token_id;
4309
+ if (!tokenId) continue;
4310
+ const mid = await apiFetch(`${CLOB_API}/midpoint?token_id=${tokenId}`);
4311
+ const currentPrice = parseFloat(mid?.mid || "0");
4312
+ const entryPrice = parseFloat(pos.avgPrice || pos.entry_price || "0");
4313
+ const size = parseFloat(pos.size || "0");
4314
+ if (currentPrice && entryPrice && size) {
4315
+ const pnl = (currentPrice - entryPrice) * size;
4316
+ const pnlPct = (currentPrice - entryPrice) / entryPrice * 100;
4317
+ if (Math.abs(pnlPct) > 10) {
4318
+ report.actions_needed.push({
4319
+ type: pnl > 0 ? "take_profit" : "stop_loss",
4320
+ message: `Position ${pos.title || tokenId}: ${pnlPct > 0 ? "+" : ""}${pnlPct.toFixed(1)}% (${pnl > 0 ? "+" : ""}$${pnl.toFixed(2)})`,
4321
+ token_id: tokenId,
4322
+ current_price: currentPrice,
4323
+ entry_price: entryPrice,
4324
+ pnl: parseFloat(pnl.toFixed(2))
4325
+ });
4326
+ }
4327
+ }
4328
+ } catch {
4329
+ }
4330
+ }
4331
+ }
4332
+ } catch (e) {
4333
+ report.positions = { error: e.message };
4334
+ }
4335
+ }
4336
+ if (p.check_predictions !== false) {
4337
+ try {
4338
+ const unresolved = await getUnresolvedPredictions(agentId, db, void 0);
4339
+ report.predictions = { unresolved: unresolved.length };
4340
+ for (const pred of unresolved.slice(0, 10)) {
4341
+ try {
4342
+ if (!pred.market_id) continue;
4343
+ const market = await apiFetch(`${GAMMA_API}/markets/${pred.market_id}`).catch(() => null);
4344
+ if (market?.resolved) {
4345
+ report.actions_needed.push({
4346
+ type: "resolve_prediction",
4347
+ message: `Market SETTLED: "${pred.market_question}" \u2014 resolve prediction ${pred.id}`,
4348
+ prediction_id: pred.id,
4349
+ market_id: pred.market_id,
4350
+ outcome: market.outcome
4351
+ });
4352
+ }
4353
+ } catch {
4354
+ }
4355
+ }
4356
+ } catch (e) {
4357
+ report.predictions = { error: e.message };
4358
+ }
4359
+ }
4360
+ if (p.check_balance !== false) {
4361
+ try {
4362
+ const client = await getClobClient(agentId, db);
4363
+ if (client) {
4364
+ const bal = await client.client.getBalanceAllowance({ asset_type: "COLLATERAL" });
4365
+ report.balance = {
4366
+ exchange: bal?.balance || "0",
4367
+ allowances_ok: Object.values(bal?.allowances || {}).every((v) => v !== "0" && v !== 0)
4368
+ };
4369
+ }
4370
+ } catch (e) {
4371
+ report.balance = { error: e.message };
4372
+ }
4373
+ }
4374
+ if (p.run_screener) {
4375
+ try {
4376
+ const { screenMarkets: screenMarkets2 } = await import("./screener-JFQSJY3B.js");
4377
+ const scan = await screenMarkets2({ strategy: "best_opportunities", limit: 5 });
4378
+ report.opportunities = {
4379
+ scanned: scan.scanned,
4380
+ top_picks: scan.markets.slice(0, 3).map((s) => ({
4381
+ question: s.market.question,
4382
+ score: s.scores.total,
4383
+ recommendation: s.recommendation
4384
+ }))
4385
+ };
4386
+ } catch (e) {
4387
+ report.opportunities = { error: e.message };
4388
+ }
4389
+ }
4390
+ report.summary = {
4391
+ actions_count: report.actions_needed.length,
4392
+ status: report.actions_needed.length > 0 ? report.actions_needed.some((a) => a.type === "critical") ? "CRITICAL" : "NEEDS_ATTENTION" : "ALL_CLEAR"
4393
+ };
4394
+ return jsonResult(report);
4395
+ }
4396
+ },
4397
+ // ═══ LEARNING & TRADE JOURNAL ═══════════════════════════════
4398
+ {
4399
+ name: "poly_record_prediction",
4400
+ description: "Record a prediction BEFORE placing a trade. This is your pre-trade journal entry \u2014 write down what you think will happen, why, and how confident you are. Essential for learning from outcomes later. Call this before every poly_place_order.",
4401
+ category: "enterprise",
4402
+ parameters: { type: "object", properties: {
4403
+ token_id: { type: "string", description: "Token being traded" },
4404
+ market_id: { type: "string", description: "Market ID" },
4405
+ market_question: { type: "string", description: "Market question text" },
4406
+ predicted_outcome: { type: "string", description: 'What you predict (e.g. "Yes", "No", "price_up", "price_down")' },
4407
+ predicted_probability: { type: "number", description: "Your estimated probability (0-1)" },
4408
+ confidence: { type: "number", description: "How confident you are in your analysis (0-1)" },
4409
+ reasoning: { type: "string", description: "Why you think this \u2014 be specific. Future you will review this." },
4410
+ signals_used: { type: "array", items: { type: "string" }, description: 'Which signals/tools influenced this (e.g. ["RSI", "news_sentiment", "order_book_imbalance"])' },
4411
+ category: { type: "string", description: 'Market category (e.g. "politics", "crypto", "sports")' }
4412
+ }, required: ["token_id", "predicted_outcome", "predicted_probability", "confidence"] },
4413
+ async execute(_id, p) {
4414
+ try {
4415
+ let marketPrice = 0.5;
4416
+ try {
4417
+ const mid = await apiFetch(`${CLOB_API}/midpoint?token_id=${p.token_id}`);
4418
+ marketPrice = parseFloat(mid?.mid || "0.5");
4419
+ } catch {
4420
+ }
4421
+ const predId = `pred_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
4422
+ await recordPrediction(db, {
4423
+ id: predId,
4424
+ agentId,
4425
+ marketId: p.market_id,
4426
+ tokenId: p.token_id,
4427
+ marketQuestion: p.market_question,
4428
+ predictedOutcome: p.predicted_outcome,
4429
+ predictedProbability: p.predicted_probability,
4430
+ marketPrice,
4431
+ confidence: p.confidence,
4432
+ reasoning: p.reasoning,
4433
+ signalsUsed: p.signals_used,
4434
+ category: p.category
4435
+ });
4436
+ const edge = p.predicted_probability - marketPrice;
4437
+ return jsonResult({
4438
+ status: "recorded",
4439
+ prediction_id: predId,
4440
+ your_estimate: `${(p.predicted_probability * 100).toFixed(1)}%`,
4441
+ market_price: `${(marketPrice * 100).toFixed(1)}%`,
4442
+ edge: `${(edge * 100).toFixed(1)}%`,
4443
+ confidence: `${(p.confidence * 100).toFixed(0)}%`,
4444
+ message: "Prediction journaled. After the market resolves, use poly_resolve_prediction to log the outcome and learn."
4445
+ });
4446
+ } catch (e) {
4447
+ return errorResult(e.message);
4448
+ }
4449
+ }
4450
+ },
4451
+ {
4452
+ name: "poly_resolve_prediction",
4453
+ description: "Resolve a prediction after a market settles. Records whether you were right or wrong, calculates P&L, and updates your calibration scores and strategy stats. This is the feedback loop \u2014 without it, you cannot learn.",
4454
+ category: "enterprise",
4455
+ parameters: { type: "object", properties: {
4456
+ prediction_id: { type: "string", description: "ID from poly_record_prediction" },
4457
+ actual_outcome: { type: "string", description: 'What actually happened (e.g. "Yes", "No")' },
4458
+ pnl: { type: "number", description: "Profit/loss in USDC from this trade" }
4459
+ }, required: ["prediction_id", "actual_outcome"] },
4460
+ async execute(_id, p) {
4461
+ try {
4462
+ await resolvePrediction(db, p.prediction_id, p.actual_outcome, p.pnl || 0);
4463
+ return jsonResult({
4464
+ status: "resolved",
4465
+ prediction_id: p.prediction_id,
4466
+ actual_outcome: p.actual_outcome,
4467
+ pnl: p.pnl || 0,
4468
+ message: "Prediction resolved. Calibration and strategy stats updated. Run poly_trade_review to extract lessons."
4469
+ });
4470
+ } catch (e) {
4471
+ return errorResult(e.message);
4472
+ }
4473
+ }
4474
+ },
4475
+ {
4476
+ name: "poly_trade_review",
4477
+ description: "Review recent resolved trades to extract lessons learned. This is your retrospective \u2014 look at what went right and wrong, identify patterns in your mistakes, and record actionable lessons. Do this regularly (daily or after every batch of resolutions). The lessons you record here will be recalled before future trades.",
4478
+ category: "enterprise",
4479
+ parameters: { type: "object", properties: {
4480
+ limit: { type: "number", description: "How many resolved trades to review (default: 10)" }
4481
+ } },
4482
+ async execute(_id, p) {
4483
+ try {
4484
+ const resolved = await getResolvedPredictions(agentId, db, p.limit || 10);
4485
+ if (resolved.length === 0) return jsonResult({ message: "No unreviewed trades. Either no trades have resolved or all have been reviewed." });
4486
+ const correct = resolved.filter((r) => r.was_correct);
4487
+ const wrong = resolved.filter((r) => !r.was_correct);
4488
+ const totalPnl = resolved.reduce((s, r) => s + (r.pnl || 0), 0);
4489
+ return jsonResult({
4490
+ trades_to_review: resolved.length,
4491
+ summary: {
4492
+ correct: correct.length,
4493
+ wrong: wrong.length,
4494
+ win_rate: `${(correct.length / resolved.length * 100).toFixed(0)}%`,
4495
+ total_pnl: totalPnl.toFixed(2)
4496
+ },
4497
+ wrong_predictions: wrong.map((w) => ({
4498
+ id: w.id,
4499
+ market: w.market_question,
4500
+ predicted: w.predicted_outcome,
4501
+ actual: w.actual_outcome,
4502
+ confidence: `${(w.confidence * 100).toFixed(0)}%`,
4503
+ reasoning: w.reasoning,
4504
+ your_probability: `${(w.predicted_probability * 100).toFixed(1)}%`,
4505
+ market_price: `${(w.market_price_at_prediction * 100).toFixed(1)}%`,
4506
+ pnl: w.pnl
4507
+ })),
4508
+ correct_predictions: correct.map((c) => ({
4509
+ id: c.id,
4510
+ market: c.market_question,
4511
+ confidence: `${(c.confidence * 100).toFixed(0)}%`,
4512
+ pnl: c.pnl
4513
+ })),
4514
+ instructions: "Review the wrong predictions carefully. For each one, ask: Why was I wrong? Was my reasoning flawed, or was it bad luck? Then call poly_record_lesson with your insights. After review, these trades will be marked as reviewed.",
4515
+ prediction_ids: resolved.map((r) => r.id)
4516
+ });
4517
+ } catch (e) {
4518
+ return errorResult(e.message);
4519
+ }
4520
+ }
4521
+ },
4522
+ {
4523
+ name: "poly_record_lesson",
4524
+ description: 'Record a lesson learned from reviewing trades. These lessons persist in the database and are recalled before future trades to prevent repeating mistakes. Be specific and actionable \u2014 "I was wrong about X because Y, next time I should Z."',
4525
+ category: "enterprise",
4526
+ parameters: { type: "object", properties: {
4527
+ lesson: { type: "string", description: 'The lesson. Be specific: "I overweighted news sentiment for crypto markets \u2014 price already moved before I saw the news. Use order flow signals instead."' },
4528
+ category: { type: "string", description: 'Market category this applies to (e.g. "politics", "crypto", "sports", "general")', default: "general" },
4529
+ source_prediction_ids: { type: "array", items: { type: "string" }, description: "Prediction IDs that led to this lesson" },
4530
+ importance: { type: "string", enum: ["critical", "high", "normal", "low"], default: "normal" }
4531
+ }, required: ["lesson"] },
4532
+ async execute(_id, p) {
4533
+ try {
4534
+ const lessonId = `lesson_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
4535
+ await storeLesson(db, {
4536
+ id: lessonId,
4537
+ agentId,
4538
+ lesson: p.lesson,
4539
+ category: p.category || "general",
4540
+ sourcePredictionIds: p.source_prediction_ids,
4541
+ importance: p.importance
4542
+ });
4543
+ if (p.source_prediction_ids?.length) {
4544
+ await markLessonsExtracted(db, p.source_prediction_ids);
4545
+ }
4546
+ return jsonResult({
4547
+ status: "recorded",
4548
+ lesson_id: lessonId,
4549
+ message: "Lesson stored. It will be recalled before future trades in this category."
4550
+ });
4551
+ } catch (e) {
4552
+ return errorResult(e.message);
4553
+ }
4554
+ }
4555
+ },
4556
+ {
4557
+ name: "poly_recall_lessons",
4558
+ description: "Recall lessons learned from past trades. Call this BEFORE making a new trade to avoid repeating mistakes. Returns relevant lessons filtered by market category.",
4559
+ category: "enterprise",
4560
+ parameters: { type: "object", properties: {
4561
+ category: { type: "string", description: 'Market category to recall lessons for (e.g. "politics", "crypto"). Omit for all lessons.' }
4562
+ } },
4563
+ async execute(_id, p) {
4564
+ try {
4565
+ const lessons = await recallLessons(agentId, db, p.category);
4566
+ if (lessons.length === 0) return jsonResult({ message: "No lessons recorded yet. Trade, review, and learn." });
4567
+ return jsonResult({
4568
+ count: lessons.length,
4569
+ lessons: lessons.map((l) => ({
4570
+ lesson: l.lesson,
4571
+ category: l.category,
4572
+ importance: l.importance,
4573
+ times_applied: l.times_applied,
4574
+ recorded_at: l.created_at
4575
+ }))
4576
+ });
4577
+ } catch (e) {
4578
+ return errorResult(e.message);
4579
+ }
4580
+ }
4581
+ },
4582
+ {
4583
+ name: "poly_calibration",
4584
+ description: 'Check your prediction calibration \u2014 are you overconfident or underconfident? Shows your accuracy at each confidence level. A well-calibrated predictor who says "70% confident" should be right ~70% of the time. Use this to adjust your confidence levels.',
4585
+ category: "enterprise",
4586
+ parameters: { type: "object", properties: {} },
4587
+ async execute() {
4588
+ try {
4589
+ const calibration = await getCalibration(agentId, db);
4590
+ if (calibration.length === 0) return jsonResult({ message: "No resolved predictions yet. Record and resolve predictions to build calibration data." });
4591
+ const totalPreds = calibration.reduce((s, c) => s + c.predictions, 0);
4592
+ const totalCorrect = calibration.reduce((s, c) => s + c.correct, 0);
4593
+ let brierSum = 0;
4594
+ for (const bucket of calibration) {
4595
+ const midpoint = parseInt(bucket.bucket) / 100 + 0.05;
4596
+ const actualRate = bucket.predictions > 0 ? bucket.correct / bucket.predictions : 0;
4597
+ brierSum += bucket.predictions * (midpoint - actualRate) ** 2;
4598
+ }
4599
+ const avgCalibrationError = totalPreds > 0 ? Math.sqrt(brierSum / totalPreds) : 0;
4600
+ return jsonResult({
4601
+ overall: {
4602
+ total_predictions: totalPreds,
4603
+ total_correct: totalCorrect,
4604
+ overall_accuracy: `${(totalCorrect / (totalPreds || 1) * 100).toFixed(1)}%`,
4605
+ calibration_error: `${(avgCalibrationError * 100).toFixed(1)}%`,
4606
+ assessment: avgCalibrationError < 0.05 ? "WELL_CALIBRATED" : avgCalibrationError < 0.1 ? "SLIGHTLY_OFF" : avgCalibrationError < 0.2 ? "POORLY_CALIBRATED" : "SEVERELY_MISCALIBRATED"
4607
+ },
4608
+ buckets: calibration.map((c) => {
4609
+ const actualRate = c.predictions > 0 ? (c.correct / c.predictions * 100).toFixed(1) : "0.0";
4610
+ const expected = parseInt(c.bucket) + 5;
4611
+ return {
4612
+ confidence_range: c.bucket,
4613
+ predictions: c.predictions,
4614
+ correct: c.correct,
4615
+ actual_accuracy: `${actualRate}%`,
4616
+ expected_accuracy: `~${expected}%`,
4617
+ bias: parseFloat(actualRate) > expected ? "UNDERCONFIDENT (good \u2014 means you have more edge than you think)" : parseFloat(actualRate) < expected - 10 ? "OVERCONFIDENT (bad \u2014 reduce confidence or be more selective)" : "CALIBRATED"
4618
+ };
4619
+ })
4620
+ });
4621
+ } catch (e) {
4622
+ return errorResult(e.message);
4623
+ }
4624
+ }
4625
+ },
4626
+ {
4627
+ name: "poly_strategy_performance",
4628
+ description: "See which trading signals/strategies are actually working for you and which are losing money. Rankings by win rate and P&L. Use this to stop using bad signals and double down on good ones.",
4629
+ category: "enterprise",
4630
+ parameters: { type: "object", properties: {} },
4631
+ async execute() {
4632
+ try {
4633
+ const strategies = await getStrategyPerformance(agentId, db);
4634
+ if (strategies.length === 0) return jsonResult({ message: "No strategy data yet. Record predictions with signals_used to track performance by strategy." });
4635
+ return jsonResult({
4636
+ strategies: strategies.map((s) => ({
4637
+ name: s.strategy_name,
4638
+ trades: s.total_predictions,
4639
+ wins: s.correct_predictions,
4640
+ win_rate: `${s.win_rate}%`,
4641
+ total_pnl: `$${s.total_pnl.toFixed(2)}`,
4642
+ avg_confidence: `${(s.avg_confidence * 100).toFixed(0)}%`,
4643
+ verdict: s.win_rate > 60 ? "KEEP \u2014 this signal works" : s.win_rate > 45 ? "NEUTRAL \u2014 needs more data" : "CONSIDER DROPPING \u2014 losing signal"
4644
+ })),
4645
+ recommendation: "Weight future trades toward your best-performing signals. Drop or reduce weight on consistently losing strategies."
4646
+ });
4647
+ } catch (e) {
4648
+ return errorResult(e.message);
4649
+ }
4650
+ }
4651
+ },
4652
+ {
4653
+ name: "poly_unresolved_predictions",
4654
+ description: "List predictions that havent been resolved yet. Use this to check which markets you have open predictions on, and resolve them when the market settles.",
4655
+ category: "enterprise",
4656
+ parameters: { type: "object", properties: {
4657
+ market_id: { type: "string", description: "Filter by market ID" }
4658
+ } },
4659
+ async execute(_id, p) {
4660
+ try {
4661
+ const unresolved = await getUnresolvedPredictions(agentId, db, p.market_id);
4662
+ return jsonResult({
4663
+ count: unresolved.length,
4664
+ predictions: unresolved.map((u) => ({
4665
+ id: u.id,
4666
+ market: u.market_question,
4667
+ predicted: u.predicted_outcome,
4668
+ your_probability: `${(u.predicted_probability * 100).toFixed(1)}%`,
4669
+ market_price_then: `${(u.market_price_at_prediction * 100).toFixed(1)}%`,
4670
+ confidence: `${(u.confidence * 100).toFixed(0)}%`,
4671
+ created: u.created_at
4672
+ }))
4673
+ });
4674
+ } catch (e) {
4675
+ return errorResult(e.message);
4676
+ }
4677
+ }
4678
+ }
4679
+ ];
4680
+ try {
4681
+ tools.push(..._screenerTools);
4682
+ tools.push(..._pipelineTools);
4683
+ } catch (e) {
4684
+ console.warn("[polymarket] Screener tools not loaded:", e.message);
4685
+ }
4686
+ try {
4687
+ const watcherTools = createWatcherTools({ db: { getEngineDB: () => db }, agentId });
4688
+ for (const wt of watcherTools) {
4689
+ tools.push({
4690
+ name: wt.name,
4691
+ description: wt.description,
4692
+ category: "enterprise",
4693
+ parameters: wt.parameters,
4694
+ async execute(_id, p) {
4695
+ try {
4696
+ const result = await wt.handler(p);
4697
+ return jsonResult(result);
4698
+ } catch (e) {
4699
+ return errorResult(e.message);
4700
+ }
4701
+ }
4702
+ });
4703
+ }
4704
+ } catch (e) {
4705
+ console.warn("[polymarket] Watcher tools not loaded:", e.message);
4706
+ }
4707
+ const extensions = [
4708
+ { name: "Execution", fn: createPolymarketExecutionTools },
4709
+ { name: "Social", fn: createPolymarketSocialTools },
4710
+ { name: "Feeds", fn: createPolymarketFeedsTools },
4711
+ { name: "OnChain", fn: createPolymarketOnchainTools },
4712
+ { name: "Analytics", fn: createPolymarketAnalyticsTools },
4713
+ { name: "Portfolio", fn: createPolymarketPortfolioTools },
4714
+ { name: "Quant", fn: createPolymarketQuantTools },
4715
+ { name: "Counterintel", fn: createPolymarketCounterintelTools }
4716
+ ];
4717
+ for (const ext of extensions) {
4718
+ try {
4719
+ const extTools = ext.fn(options);
4720
+ tools.push(...extTools);
4721
+ } catch (e) {
4722
+ console.warn(`[polymarket] ${ext.name} tools not loaded:`, e.message);
4723
+ }
4724
+ }
4725
+ const seen = /* @__PURE__ */ new Set();
4726
+ const deduped = [];
4727
+ for (let i = tools.length - 1; i >= 0; i--) {
4728
+ const name = tools[i].name;
4729
+ if (!seen.has(name)) {
4730
+ seen.add(name);
4731
+ deduped.unshift(tools[i]);
4732
+ }
4733
+ }
4734
+ return deduped;
4735
+ }
4736
+
4737
+ export {
4738
+ createPolymarketExecutionTools,
4739
+ createPolymarketSocialTools,
4740
+ createPolymarketFeedsTools,
4741
+ createPolymarketOnchainTools,
4742
+ createPolymarketAnalyticsTools,
4743
+ createPolymarketPortfolioTools,
4744
+ createPolymarketCounterintelTools,
4745
+ executeOrder,
4746
+ createPolymarketTools
4747
+ };