@catalyst-team/poly-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/.env +0 -0
  2. package/README.md +803 -0
  3. package/dist/__tests__/clob-api.test.d.ts +5 -0
  4. package/dist/__tests__/clob-api.test.d.ts.map +1 -0
  5. package/dist/__tests__/clob-api.test.js +240 -0
  6. package/dist/__tests__/clob-api.test.js.map +1 -0
  7. package/dist/__tests__/integration/bridge-client.integration.test.d.ts +11 -0
  8. package/dist/__tests__/integration/bridge-client.integration.test.d.ts.map +1 -0
  9. package/dist/__tests__/integration/bridge-client.integration.test.js +260 -0
  10. package/dist/__tests__/integration/bridge-client.integration.test.js.map +1 -0
  11. package/dist/__tests__/integration/clob-api.integration.test.d.ts +13 -0
  12. package/dist/__tests__/integration/clob-api.integration.test.d.ts.map +1 -0
  13. package/dist/__tests__/integration/clob-api.integration.test.js +170 -0
  14. package/dist/__tests__/integration/clob-api.integration.test.js.map +1 -0
  15. package/dist/__tests__/integration/ctf-client.integration.test.d.ts +17 -0
  16. package/dist/__tests__/integration/ctf-client.integration.test.d.ts.map +1 -0
  17. package/dist/__tests__/integration/ctf-client.integration.test.js +234 -0
  18. package/dist/__tests__/integration/ctf-client.integration.test.js.map +1 -0
  19. package/dist/__tests__/integration/data-api.integration.test.d.ts +9 -0
  20. package/dist/__tests__/integration/data-api.integration.test.d.ts.map +1 -0
  21. package/dist/__tests__/integration/data-api.integration.test.js +161 -0
  22. package/dist/__tests__/integration/data-api.integration.test.js.map +1 -0
  23. package/dist/__tests__/integration/gamma-api.integration.test.d.ts +9 -0
  24. package/dist/__tests__/integration/gamma-api.integration.test.d.ts.map +1 -0
  25. package/dist/__tests__/integration/gamma-api.integration.test.js +170 -0
  26. package/dist/__tests__/integration/gamma-api.integration.test.js.map +1 -0
  27. package/dist/__tests__/test-utils.d.ts +92 -0
  28. package/dist/__tests__/test-utils.d.ts.map +1 -0
  29. package/dist/__tests__/test-utils.js +143 -0
  30. package/dist/__tests__/test-utils.js.map +1 -0
  31. package/dist/clients/bridge-client.d.ts +388 -0
  32. package/dist/clients/bridge-client.d.ts.map +1 -0
  33. package/dist/clients/bridge-client.js +587 -0
  34. package/dist/clients/bridge-client.js.map +1 -0
  35. package/dist/clients/clob-api.d.ts +318 -0
  36. package/dist/clients/clob-api.d.ts.map +1 -0
  37. package/dist/clients/clob-api.js +388 -0
  38. package/dist/clients/clob-api.js.map +1 -0
  39. package/dist/clients/ctf-client.d.ts +473 -0
  40. package/dist/clients/ctf-client.d.ts.map +1 -0
  41. package/dist/clients/ctf-client.js +915 -0
  42. package/dist/clients/ctf-client.js.map +1 -0
  43. package/dist/clients/data-api.d.ts +134 -0
  44. package/dist/clients/data-api.d.ts.map +1 -0
  45. package/dist/clients/data-api.js +265 -0
  46. package/dist/clients/data-api.js.map +1 -0
  47. package/dist/clients/gamma-api.d.ts +401 -0
  48. package/dist/clients/gamma-api.d.ts.map +1 -0
  49. package/dist/clients/gamma-api.js +352 -0
  50. package/dist/clients/gamma-api.js.map +1 -0
  51. package/dist/clients/trading-client.d.ts +252 -0
  52. package/dist/clients/trading-client.d.ts.map +1 -0
  53. package/dist/clients/trading-client.js +543 -0
  54. package/dist/clients/trading-client.js.map +1 -0
  55. package/dist/clients/websocket-manager.d.ts +100 -0
  56. package/dist/clients/websocket-manager.d.ts.map +1 -0
  57. package/dist/clients/websocket-manager.js +193 -0
  58. package/dist/clients/websocket-manager.js.map +1 -0
  59. package/dist/core/cache-adapter-bridge.d.ts +36 -0
  60. package/dist/core/cache-adapter-bridge.d.ts.map +1 -0
  61. package/dist/core/cache-adapter-bridge.js +81 -0
  62. package/dist/core/cache-adapter-bridge.js.map +1 -0
  63. package/dist/core/cache.d.ts +40 -0
  64. package/dist/core/cache.d.ts.map +1 -0
  65. package/dist/core/cache.js +71 -0
  66. package/dist/core/cache.js.map +1 -0
  67. package/dist/core/errors.d.ts +38 -0
  68. package/dist/core/errors.d.ts.map +1 -0
  69. package/dist/core/errors.js +84 -0
  70. package/dist/core/errors.js.map +1 -0
  71. package/dist/core/rate-limiter.d.ts +31 -0
  72. package/dist/core/rate-limiter.d.ts.map +1 -0
  73. package/dist/core/rate-limiter.js +70 -0
  74. package/dist/core/rate-limiter.js.map +1 -0
  75. package/dist/core/types.d.ts +314 -0
  76. package/dist/core/types.d.ts.map +1 -0
  77. package/dist/core/types.js +19 -0
  78. package/dist/core/types.js.map +1 -0
  79. package/dist/core/unified-cache.d.ts +63 -0
  80. package/dist/core/unified-cache.d.ts.map +1 -0
  81. package/dist/core/unified-cache.js +114 -0
  82. package/dist/core/unified-cache.js.map +1 -0
  83. package/dist/index.d.ts +94 -0
  84. package/dist/index.d.ts.map +1 -0
  85. package/dist/index.js +258 -0
  86. package/dist/index.js.map +1 -0
  87. package/dist/mcp/errors.d.ts +33 -0
  88. package/dist/mcp/errors.d.ts.map +1 -0
  89. package/dist/mcp/errors.js +86 -0
  90. package/dist/mcp/errors.js.map +1 -0
  91. package/dist/mcp/index.d.ts +62 -0
  92. package/dist/mcp/index.d.ts.map +1 -0
  93. package/dist/mcp/index.js +173 -0
  94. package/dist/mcp/index.js.map +1 -0
  95. package/dist/mcp/server.d.ts +17 -0
  96. package/dist/mcp/server.d.ts.map +1 -0
  97. package/dist/mcp/server.js +155 -0
  98. package/dist/mcp/server.js.map +1 -0
  99. package/dist/mcp/tools/guide.d.ts +12 -0
  100. package/dist/mcp/tools/guide.d.ts.map +1 -0
  101. package/dist/mcp/tools/guide.js +801 -0
  102. package/dist/mcp/tools/guide.js.map +1 -0
  103. package/dist/mcp/tools/index.d.ts +11 -0
  104. package/dist/mcp/tools/index.d.ts.map +1 -0
  105. package/dist/mcp/tools/index.js +27 -0
  106. package/dist/mcp/tools/index.js.map +1 -0
  107. package/dist/mcp/tools/market.d.ts +11 -0
  108. package/dist/mcp/tools/market.d.ts.map +1 -0
  109. package/dist/mcp/tools/market.js +314 -0
  110. package/dist/mcp/tools/market.js.map +1 -0
  111. package/dist/mcp/tools/order.d.ts +10 -0
  112. package/dist/mcp/tools/order.d.ts.map +1 -0
  113. package/dist/mcp/tools/order.js +258 -0
  114. package/dist/mcp/tools/order.js.map +1 -0
  115. package/dist/mcp/tools/trade.d.ts +38 -0
  116. package/dist/mcp/tools/trade.d.ts.map +1 -0
  117. package/dist/mcp/tools/trade.js +314 -0
  118. package/dist/mcp/tools/trade.js.map +1 -0
  119. package/dist/mcp/tools/trader.d.ts +11 -0
  120. package/dist/mcp/tools/trader.d.ts.map +1 -0
  121. package/dist/mcp/tools/trader.js +277 -0
  122. package/dist/mcp/tools/trader.js.map +1 -0
  123. package/dist/mcp/tools/wallet.d.ts +274 -0
  124. package/dist/mcp/tools/wallet.d.ts.map +1 -0
  125. package/dist/mcp/tools/wallet.js +579 -0
  126. package/dist/mcp/tools/wallet.js.map +1 -0
  127. package/dist/mcp/types.d.ts +413 -0
  128. package/dist/mcp/types.d.ts.map +1 -0
  129. package/dist/mcp/types.js +5 -0
  130. package/dist/mcp/types.js.map +1 -0
  131. package/dist/services/authorization-service.d.ts +97 -0
  132. package/dist/services/authorization-service.d.ts.map +1 -0
  133. package/dist/services/authorization-service.js +279 -0
  134. package/dist/services/authorization-service.js.map +1 -0
  135. package/dist/services/market-service.d.ts +108 -0
  136. package/dist/services/market-service.d.ts.map +1 -0
  137. package/dist/services/market-service.js +458 -0
  138. package/dist/services/market-service.js.map +1 -0
  139. package/dist/services/realtime-service.d.ts +82 -0
  140. package/dist/services/realtime-service.d.ts.map +1 -0
  141. package/dist/services/realtime-service.js +150 -0
  142. package/dist/services/realtime-service.js.map +1 -0
  143. package/dist/services/swap-service.d.ts +217 -0
  144. package/dist/services/swap-service.d.ts.map +1 -0
  145. package/dist/services/swap-service.js +695 -0
  146. package/dist/services/swap-service.js.map +1 -0
  147. package/dist/services/wallet-service.d.ts +94 -0
  148. package/dist/services/wallet-service.d.ts.map +1 -0
  149. package/dist/services/wallet-service.js +173 -0
  150. package/dist/services/wallet-service.js.map +1 -0
  151. package/dist/utils/price-utils.d.ts +153 -0
  152. package/dist/utils/price-utils.d.ts.map +1 -0
  153. package/dist/utils/price-utils.js +236 -0
  154. package/dist/utils/price-utils.js.map +1 -0
  155. package/docs/00-design.md +760 -0
  156. package/docs/01-mcp.md +2041 -0
  157. package/docs/02-API.md +1148 -0
  158. package/docs/e2e/01-trader-tools.md +159 -0
  159. package/docs/e2e/02-market-tools.md +180 -0
  160. package/docs/e2e/03-order-tools.md +166 -0
  161. package/docs/e2e/04-wallet-tools.md +224 -0
  162. package/docs/e2e/05-trading-tools.md +327 -0
  163. package/docs/e2e/06-integration-scenarios.md +481 -0
  164. package/docs/e2e/coordinator.md +376 -0
  165. package/examples/01-basic-usage.ts +68 -0
  166. package/examples/02-smart-money.ts +95 -0
  167. package/examples/03-market-analysis.ts +108 -0
  168. package/examples/04-kline-aggregation.ts +158 -0
  169. package/examples/05-follow-wallet-strategy.ts +156 -0
  170. package/examples/06-services-demo.ts +124 -0
  171. package/examples/07-realtime-websocket.ts +117 -0
  172. package/examples/08-trading-orders.ts +278 -0
  173. package/examples/09-rewards-tracking.ts +187 -0
  174. package/examples/10-ctf-operations.ts +336 -0
  175. package/examples/11-live-arbitrage-scan.ts +221 -0
  176. package/examples/12-trending-arb-monitor.ts +406 -0
  177. package/examples/README.md +179 -0
  178. package/package.json +62 -0
  179. package/scripts/README.md +163 -0
  180. package/scripts/approvals/approve-erc1155.ts +129 -0
  181. package/scripts/approvals/approve-neg-risk-erc1155.ts +149 -0
  182. package/scripts/approvals/approve-neg-risk.ts +102 -0
  183. package/scripts/approvals/check-all-allowances.ts +150 -0
  184. package/scripts/approvals/check-allowance.ts +129 -0
  185. package/scripts/approvals/check-ctf-approval.ts +158 -0
  186. package/scripts/datas/001-report.md +486 -0
  187. package/scripts/datas/clone-modal-screenshot.png +0 -0
  188. package/scripts/deposit/deposit-native-usdc.ts +179 -0
  189. package/scripts/deposit/deposit-usdc.ts +155 -0
  190. package/scripts/deposit/swap-usdc-to-usdce.ts +375 -0
  191. package/scripts/research/research-markets.ts +166 -0
  192. package/scripts/trading/check-orders.ts +50 -0
  193. package/scripts/trading/sell-nvidia-positions.ts +206 -0
  194. package/scripts/trading/test-order.ts +172 -0
  195. package/scripts/truth.md +440 -0
  196. package/scripts/verify/test-approve-trading.ts +98 -0
  197. package/scripts/verify/test-provider-fix.ts +43 -0
  198. package/scripts/verify/test-search-mcp.ts +113 -0
  199. package/scripts/verify/verify-all-apis.ts +160 -0
  200. package/scripts/wallet/check-wallet-balances.ts +75 -0
  201. package/scripts/wallet/test-wallet-operations.ts +191 -0
  202. package/scripts/wallet/verify-wallet-tools.ts +124 -0
  203. package/src/__tests__/clob-api.test.ts +301 -0
  204. package/src/__tests__/integration/bridge-client.integration.test.ts +314 -0
  205. package/src/__tests__/integration/clob-api.integration.test.ts +218 -0
  206. package/src/__tests__/integration/ctf-client.integration.test.ts +331 -0
  207. package/src/__tests__/integration/data-api.integration.test.ts +194 -0
  208. package/src/__tests__/integration/gamma-api.integration.test.ts +206 -0
  209. package/src/__tests__/test-utils.ts +170 -0
  210. package/src/clients/bridge-client.ts +841 -0
  211. package/src/clients/clob-api.ts +629 -0
  212. package/src/clients/ctf-client.ts +1216 -0
  213. package/src/clients/data-api.ts +469 -0
  214. package/src/clients/gamma-api.ts +597 -0
  215. package/src/clients/trading-client.ts +749 -0
  216. package/src/clients/websocket-manager.ts +267 -0
  217. package/src/core/cache-adapter-bridge.ts +94 -0
  218. package/src/core/cache.ts +85 -0
  219. package/src/core/errors.ts +117 -0
  220. package/src/core/rate-limiter.ts +74 -0
  221. package/src/core/types.ts +360 -0
  222. package/src/core/unified-cache.ts +153 -0
  223. package/src/index.ts +455 -0
  224. package/src/mcp/README.md +380 -0
  225. package/src/mcp/errors.ts +124 -0
  226. package/src/mcp/index.ts +309 -0
  227. package/src/mcp/server.ts +183 -0
  228. package/src/mcp/tools/guide.ts +821 -0
  229. package/src/mcp/tools/index.ts +73 -0
  230. package/src/mcp/tools/market.ts +363 -0
  231. package/src/mcp/tools/order.ts +326 -0
  232. package/src/mcp/tools/trade.ts +417 -0
  233. package/src/mcp/tools/trader.ts +322 -0
  234. package/src/mcp/tools/wallet.ts +683 -0
  235. package/src/mcp/types.ts +472 -0
  236. package/src/services/authorization-service.ts +357 -0
  237. package/src/services/market-service.ts +544 -0
  238. package/src/services/realtime-service.ts +196 -0
  239. package/src/services/swap-service.ts +896 -0
  240. package/src/services/wallet-service.ts +259 -0
  241. package/src/utils/price-utils.ts +307 -0
  242. package/tsconfig.json +8 -0
  243. package/vitest.config.ts +19 -0
  244. package/vitest.integration.config.ts +18 -0
@@ -0,0 +1,695 @@
1
+ /**
2
+ * Swap Service
3
+ *
4
+ * Provides DEX swap functionality on Polygon using QuickSwap V3.
5
+ * Supports swapping between various tokens including MATIC, WETH, USDC, USDC.e, USDT, DAI.
6
+ */
7
+ import { ethers, Contract, BigNumber } from 'ethers';
8
+ // QuickSwap V3 Contracts on Polygon
9
+ export const QUICKSWAP_ROUTER = '0xf5b509bB0909a69B1c207E495f687a596C168E12';
10
+ export const QUICKSWAP_QUOTER = '0xa15F0D7377B2A0C0c10db057f641beD21028FC89';
11
+ export const QUICKSWAP_FACTORY = '0x411b0fAcC3489691f28ad58c47006AF5E3Ab3A28';
12
+ // Wrapped MATIC for swapping native MATIC
13
+ export const WMATIC = '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270';
14
+ /**
15
+ * Supported tokens on Polygon
16
+ *
17
+ * ⚠️ IMPORTANT: USDC vs USDC.e for Polymarket CTF
18
+ *
19
+ * | Token | Address | Polymarket CTF |
20
+ * |-------------|--------------------------------------------|-----------------
21
+ * | USDC_E | 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174 | ✅ Required |
22
+ * | USDC/NATIVE | 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359 | ❌ Not accepted|
23
+ *
24
+ * For Polymarket CTF operations (split/merge/redeem):
25
+ * - Use transferUsdcE() to send USDC.e
26
+ * - Use swap('USDC', 'USDC_E', amount) to convert native USDC to USDC.e
27
+ *
28
+ * For general transfers:
29
+ * - transferUsdc() sends native USDC (most DEXs, CEXs use this)
30
+ * - transferUsdcE() sends bridged USDC.e (Polymarket CTF requires this)
31
+ */
32
+ export const POLYGON_TOKENS = {
33
+ // Native MATIC (use WMATIC address for swaps)
34
+ MATIC: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270',
35
+ WMATIC: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270',
36
+ // USDC variants - SEE ABOVE FOR POLYMARKET COMPATIBILITY
37
+ USDC: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359', // Native USDC - NOT for Polymarket CTF
38
+ NATIVE_USDC: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359', // Alias for USDC
39
+ USDC_E: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', // Bridged USDC.e - REQUIRED for Polymarket CTF
40
+ // Other stables
41
+ USDT: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F',
42
+ DAI: '0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063',
43
+ // ETH
44
+ WETH: '0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619',
45
+ };
46
+ // Token decimals
47
+ export const TOKEN_DECIMALS = {
48
+ MATIC: 18,
49
+ WMATIC: 18,
50
+ USDC: 6,
51
+ NATIVE_USDC: 6,
52
+ USDC_E: 6,
53
+ USDT: 6,
54
+ DAI: 18,
55
+ WETH: 18,
56
+ };
57
+ // ABIs
58
+ const ERC20_ABI = [
59
+ 'function balanceOf(address) view returns (uint256)',
60
+ 'function decimals() view returns (uint8)',
61
+ 'function symbol() view returns (string)',
62
+ 'function approve(address spender, uint256 amount) returns (bool)',
63
+ 'function allowance(address owner, address spender) view returns (uint256)',
64
+ 'function transfer(address to, uint256 amount) returns (bool)',
65
+ ];
66
+ const QUICKSWAP_ROUTER_ABI = [
67
+ 'function exactInputSingle((address tokenIn, address tokenOut, address recipient, uint256 deadline, uint256 amountIn, uint256 amountOutMinimum, uint160 limitSqrtPrice)) external payable returns (uint256 amountOut)',
68
+ 'function exactInput((bytes path, address recipient, uint256 deadline, uint256 amountIn, uint256 amountOutMinimum)) external payable returns (uint256 amountOut)',
69
+ ];
70
+ const QUICKSWAP_QUOTER_ABI = [
71
+ 'function quoteExactInputSingle(address tokenIn, address tokenOut, uint256 amountIn, uint160 limitSqrtPrice) external returns (uint256 amountOut, uint16 fee)',
72
+ 'function quoteExactInput(bytes path, uint256 amountIn) external returns (uint256 amountOut, uint16[] fees)',
73
+ ];
74
+ const QUICKSWAP_FACTORY_ABI = [
75
+ 'function poolByPair(address tokenA, address tokenB) external view returns (address pool)',
76
+ ];
77
+ const WMATIC_ABI = [
78
+ 'function deposit() external payable',
79
+ 'function withdraw(uint256 amount) external',
80
+ ];
81
+ export class SwapService {
82
+ signer;
83
+ provider;
84
+ router;
85
+ quoter;
86
+ factory;
87
+ constructor(signer) {
88
+ // Use signer's provider if available, otherwise create a default Polygon provider
89
+ this.provider = signer.provider || new ethers.providers.JsonRpcProvider('https://polygon-rpc.com');
90
+ // Ensure signer is connected to the provider
91
+ this.signer = signer.provider ? signer : signer.connect(this.provider);
92
+ this.router = new Contract(QUICKSWAP_ROUTER, QUICKSWAP_ROUTER_ABI, this.signer);
93
+ this.quoter = new Contract(QUICKSWAP_QUOTER, QUICKSWAP_QUOTER_ABI, this.provider);
94
+ this.factory = new Contract(QUICKSWAP_FACTORY, QUICKSWAP_FACTORY_ABI, this.provider);
95
+ }
96
+ /**
97
+ * Get dynamic gas options for Polygon network
98
+ * Uses RPC fee data with minimum priority fee of 30 gwei
99
+ */
100
+ async getGasOptions() {
101
+ const feeData = await this.provider.getFeeData();
102
+ const baseFee = feeData.lastBaseFeePerGas || feeData.gasPrice || ethers.utils.parseUnits('100', 'gwei');
103
+ const minPriorityFee = ethers.utils.parseUnits('30', 'gwei');
104
+ const maxPriorityFeePerGas = feeData.maxPriorityFeePerGas && feeData.maxPriorityFeePerGas.gt(minPriorityFee)
105
+ ? feeData.maxPriorityFeePerGas
106
+ : minPriorityFee;
107
+ const maxFeePerGas = baseFee.mul(3).div(2).add(maxPriorityFeePerGas);
108
+ return { maxPriorityFeePerGas, maxFeePerGas };
109
+ }
110
+ /**
111
+ * Get the wallet address
112
+ */
113
+ get address() {
114
+ return this.signer.address;
115
+ }
116
+ /**
117
+ * Get token address from symbol
118
+ */
119
+ getTokenAddress(token) {
120
+ const upperToken = token.toUpperCase();
121
+ const address = POLYGON_TOKENS[upperToken];
122
+ if (!address) {
123
+ // Check if it's already an address
124
+ if (token.startsWith('0x') && token.length === 42) {
125
+ return token;
126
+ }
127
+ throw new Error(`Unknown token: ${token}. Supported: ${Object.keys(POLYGON_TOKENS).join(', ')}`);
128
+ }
129
+ return address;
130
+ }
131
+ /**
132
+ * Get token decimals
133
+ */
134
+ getTokenDecimals(token) {
135
+ const upperToken = token.toUpperCase();
136
+ return TOKEN_DECIMALS[upperToken] || 18;
137
+ }
138
+ /**
139
+ * Check if a pool exists for a token pair
140
+ */
141
+ async checkPool(tokenA, tokenB) {
142
+ const addressA = this.getTokenAddress(tokenA);
143
+ const addressB = this.getTokenAddress(tokenB);
144
+ try {
145
+ const poolAddress = await this.factory.poolByPair(addressA, addressB);
146
+ const exists = poolAddress !== ethers.constants.AddressZero;
147
+ return {
148
+ tokenA: tokenA.toUpperCase(),
149
+ tokenB: tokenB.toUpperCase(),
150
+ poolAddress: exists ? poolAddress : null,
151
+ exists,
152
+ };
153
+ }
154
+ catch {
155
+ return {
156
+ tokenA: tokenA.toUpperCase(),
157
+ tokenB: tokenB.toUpperCase(),
158
+ poolAddress: null,
159
+ exists: false,
160
+ };
161
+ }
162
+ }
163
+ /**
164
+ * Get all available pools for supported tokens
165
+ */
166
+ async getAvailablePools() {
167
+ const tokens = Object.keys(POLYGON_TOKENS).filter((t) => t !== 'NATIVE_USDC' && t !== 'WMATIC' // Skip aliases
168
+ );
169
+ const pools = [];
170
+ for (let i = 0; i < tokens.length; i++) {
171
+ for (let j = i + 1; j < tokens.length; j++) {
172
+ const pool = await this.checkPool(tokens[i], tokens[j]);
173
+ if (pool.exists) {
174
+ pools.push(pool);
175
+ }
176
+ }
177
+ }
178
+ return pools;
179
+ }
180
+ /**
181
+ * Get a quote for a swap (checks if route is possible)
182
+ */
183
+ async getQuote(tokenIn, tokenOut, amountIn) {
184
+ const upperTokenIn = tokenIn.toUpperCase();
185
+ const upperTokenOut = tokenOut.toUpperCase();
186
+ // Handle MATIC → need to use WMATIC for the pool
187
+ const actualTokenIn = upperTokenIn === 'MATIC' ? 'WMATIC' : upperTokenIn;
188
+ const actualTokenOut = upperTokenOut === 'MATIC' ? 'WMATIC' : upperTokenOut;
189
+ const addressIn = this.getTokenAddress(actualTokenIn);
190
+ const addressOut = this.getTokenAddress(actualTokenOut);
191
+ const decimalsIn = this.getTokenDecimals(actualTokenIn);
192
+ const decimalsOut = this.getTokenDecimals(actualTokenOut);
193
+ const amountInWei = ethers.utils.parseUnits(amountIn, decimalsIn);
194
+ // First check if direct pool exists
195
+ const directPool = await this.checkPool(actualTokenIn, actualTokenOut);
196
+ if (directPool.exists) {
197
+ // Try direct quote
198
+ try {
199
+ const result = await this.quoter.callStatic.quoteExactInputSingle(addressIn, addressOut, amountInWei, 0 // no price limit
200
+ );
201
+ const amountOut = ethers.utils.formatUnits(result.amountOut, decimalsOut);
202
+ return {
203
+ possible: true,
204
+ tokenIn: upperTokenIn,
205
+ tokenOut: upperTokenOut,
206
+ amountIn,
207
+ amountOut,
208
+ route: [upperTokenIn, upperTokenOut],
209
+ poolExists: true,
210
+ };
211
+ }
212
+ catch {
213
+ // Pool exists but quote failed (maybe low liquidity)
214
+ return {
215
+ possible: false,
216
+ tokenIn: upperTokenIn,
217
+ tokenOut: upperTokenOut,
218
+ amountIn,
219
+ amountOut: null,
220
+ route: [upperTokenIn, upperTokenOut],
221
+ poolExists: true,
222
+ reason: 'Pool exists but insufficient liquidity for this amount',
223
+ };
224
+ }
225
+ }
226
+ // Try multi-hop through USDC or WMATIC
227
+ const intermediates = ['USDC', 'WMATIC', 'WETH'];
228
+ for (const mid of intermediates) {
229
+ if (mid === actualTokenIn || mid === actualTokenOut)
230
+ continue;
231
+ const pool1 = await this.checkPool(actualTokenIn, mid);
232
+ const pool2 = await this.checkPool(mid, actualTokenOut);
233
+ if (pool1.exists && pool2.exists) {
234
+ // Try multi-hop quote
235
+ try {
236
+ const midAddress = this.getTokenAddress(mid);
237
+ const path = ethers.utils.solidityPack(['address', 'address', 'address'], [addressIn, midAddress, addressOut]);
238
+ const result = await this.quoter.callStatic.quoteExactInput(path, amountInWei);
239
+ const amountOut = ethers.utils.formatUnits(result.amountOut, decimalsOut);
240
+ return {
241
+ possible: true,
242
+ tokenIn: upperTokenIn,
243
+ tokenOut: upperTokenOut,
244
+ amountIn,
245
+ amountOut,
246
+ route: [upperTokenIn, mid, upperTokenOut],
247
+ poolExists: true,
248
+ };
249
+ }
250
+ catch {
251
+ // Continue to try other routes
252
+ }
253
+ }
254
+ }
255
+ // No route found
256
+ return {
257
+ possible: false,
258
+ tokenIn: upperTokenIn,
259
+ tokenOut: upperTokenOut,
260
+ amountIn,
261
+ amountOut: null,
262
+ route: [],
263
+ poolExists: false,
264
+ reason: 'No liquidity pool or route available for this pair',
265
+ };
266
+ }
267
+ /**
268
+ * Execute a multi-hop swap
269
+ */
270
+ async swapMultiHop(tokenIn, tokenOut, amountIn, route, options = {}) {
271
+ const { slippage = 0.5, deadline = 300 } = options;
272
+ if (route.length < 2) {
273
+ throw new Error('Route must have at least 2 tokens');
274
+ }
275
+ const upperTokenIn = tokenIn.toUpperCase();
276
+ const upperTokenOut = tokenOut.toUpperCase();
277
+ // Handle MATIC wrapping
278
+ let wrappedAmount = amountIn;
279
+ if (upperTokenIn === 'MATIC') {
280
+ await this.wrapMatic(amountIn);
281
+ }
282
+ // Build path
283
+ const addresses = route.map((t) => {
284
+ const upper = t.toUpperCase();
285
+ return this.getTokenAddress(upper === 'MATIC' ? 'WMATIC' : upper);
286
+ });
287
+ const path = ethers.utils.solidityPack(addresses.map(() => 'address'), addresses);
288
+ const decimalsIn = this.getTokenDecimals(route[0] === 'MATIC' ? 'WMATIC' : route[0]);
289
+ const decimalsOut = this.getTokenDecimals(route[route.length - 1] === 'MATIC' ? 'WMATIC' : route[route.length - 1]);
290
+ const amountInWei = ethers.utils.parseUnits(wrappedAmount, decimalsIn);
291
+ // Get gas options
292
+ const gasOptions = await this.getGasOptions();
293
+ // Check and approve if needed
294
+ const tokenInAddress = addresses[0];
295
+ const tokenContract = new Contract(tokenInAddress, ERC20_ABI, this.signer);
296
+ const currentAllowance = await tokenContract.allowance(this.signer.address, QUICKSWAP_ROUTER);
297
+ if (currentAllowance.lt(amountInWei)) {
298
+ const approveTx = await tokenContract.approve(QUICKSWAP_ROUTER, ethers.constants.MaxUint256, gasOptions);
299
+ await approveTx.wait();
300
+ }
301
+ // Execute multi-hop swap
302
+ const swapParams = {
303
+ path,
304
+ recipient: this.signer.address,
305
+ deadline: Math.floor(Date.now() / 1000) + deadline,
306
+ amountIn: amountInWei,
307
+ amountOutMinimum: 0, // For simplicity; in production use quote with slippage
308
+ };
309
+ const tx = await this.router.exactInput(swapParams, { ...gasOptions, gasLimit: 500000 });
310
+ const receipt = await tx.wait();
311
+ // Get actual output amount
312
+ const tokenOutAddress = addresses[addresses.length - 1];
313
+ const tokenOutContract = new Contract(tokenOutAddress, ERC20_ABI, this.provider);
314
+ const finalBalance = await tokenOutContract.balanceOf(this.signer.address);
315
+ return {
316
+ success: receipt.status === 1,
317
+ transactionHash: receipt.transactionHash,
318
+ tokenIn: upperTokenIn,
319
+ tokenOut: upperTokenOut,
320
+ amountIn,
321
+ amountOut: ethers.utils.formatUnits(finalBalance, decimalsOut),
322
+ gasUsed: receipt.gasUsed.toString(),
323
+ };
324
+ }
325
+ /**
326
+ * Get balances for all supported tokens
327
+ */
328
+ async getBalances() {
329
+ const balances = [];
330
+ // Get native MATIC balance
331
+ const maticBalance = await this.provider.getBalance(this.signer.address);
332
+ balances.push({
333
+ token: 'MATIC',
334
+ symbol: 'MATIC',
335
+ balance: ethers.utils.formatEther(maticBalance),
336
+ decimals: 18,
337
+ });
338
+ // Get ERC20 balances
339
+ const tokens = ['USDC', 'USDC_E', 'USDT', 'DAI', 'WETH', 'WMATIC'];
340
+ for (const tokenSymbol of tokens) {
341
+ const address = POLYGON_TOKENS[tokenSymbol];
342
+ const contract = new Contract(address, ERC20_ABI, this.provider);
343
+ try {
344
+ const balance = await contract.balanceOf(this.signer.address);
345
+ const decimals = TOKEN_DECIMALS[tokenSymbol];
346
+ balances.push({
347
+ token: tokenSymbol,
348
+ symbol: tokenSymbol,
349
+ balance: ethers.utils.formatUnits(balance, decimals),
350
+ decimals,
351
+ });
352
+ }
353
+ catch {
354
+ // Skip if token query fails
355
+ }
356
+ }
357
+ return balances;
358
+ }
359
+ /**
360
+ * Get balance for a specific token
361
+ */
362
+ async getBalance(token) {
363
+ const upperToken = token.toUpperCase();
364
+ if (upperToken === 'MATIC') {
365
+ const balance = await this.provider.getBalance(this.signer.address);
366
+ return ethers.utils.formatEther(balance);
367
+ }
368
+ const address = this.getTokenAddress(token);
369
+ const contract = new Contract(address, ERC20_ABI, this.provider);
370
+ const balance = await contract.balanceOf(this.signer.address);
371
+ const decimals = this.getTokenDecimals(token);
372
+ return ethers.utils.formatUnits(balance, decimals);
373
+ }
374
+ /**
375
+ * Wrap native MATIC to WMATIC
376
+ */
377
+ async wrapMatic(amount) {
378
+ const amountWei = ethers.utils.parseEther(amount);
379
+ const wmatic = new Contract(WMATIC, WMATIC_ABI, this.signer);
380
+ const gasOptions = await this.getGasOptions();
381
+ const tx = await wmatic.deposit({ value: amountWei, ...gasOptions });
382
+ const receipt = await tx.wait();
383
+ return {
384
+ success: true,
385
+ transactionHash: receipt.transactionHash,
386
+ tokenIn: 'MATIC',
387
+ tokenOut: 'WMATIC',
388
+ amountIn: amount,
389
+ amountOut: amount,
390
+ gasUsed: receipt.gasUsed.toString(),
391
+ };
392
+ }
393
+ /**
394
+ * Unwrap WMATIC to native MATIC
395
+ */
396
+ async unwrapMatic(amount) {
397
+ const amountWei = ethers.utils.parseEther(amount);
398
+ const wmatic = new Contract(WMATIC, WMATIC_ABI, this.signer);
399
+ const gasOptions = await this.getGasOptions();
400
+ const tx = await wmatic.withdraw(amountWei, gasOptions);
401
+ const receipt = await tx.wait();
402
+ return {
403
+ success: true,
404
+ transactionHash: receipt.transactionHash,
405
+ tokenIn: 'WMATIC',
406
+ tokenOut: 'MATIC',
407
+ amountIn: amount,
408
+ amountOut: amount,
409
+ gasUsed: receipt.gasUsed.toString(),
410
+ };
411
+ }
412
+ /**
413
+ * Execute a token swap using QuickSwap V3
414
+ */
415
+ async swap(tokenIn, tokenOut, amountIn, options = {}) {
416
+ const { slippage = 0.5, deadline = 300 } = options;
417
+ const upperTokenIn = tokenIn.toUpperCase();
418
+ const upperTokenOut = tokenOut.toUpperCase();
419
+ // Handle native MATIC swaps
420
+ if (upperTokenIn === 'MATIC' && upperTokenOut === 'WMATIC') {
421
+ return this.wrapMatic(amountIn);
422
+ }
423
+ if (upperTokenIn === 'WMATIC' && upperTokenOut === 'MATIC') {
424
+ return this.unwrapMatic(amountIn);
425
+ }
426
+ // For MATIC input, first wrap to WMATIC
427
+ let actualTokenIn = upperTokenIn;
428
+ let wrappedAmount = amountIn;
429
+ if (upperTokenIn === 'MATIC') {
430
+ await this.wrapMatic(amountIn);
431
+ actualTokenIn = 'WMATIC';
432
+ }
433
+ const tokenInAddress = this.getTokenAddress(actualTokenIn);
434
+ const tokenOutAddress = this.getTokenAddress(tokenOut);
435
+ const decimalsIn = this.getTokenDecimals(actualTokenIn);
436
+ const decimalsOut = this.getTokenDecimals(tokenOut);
437
+ const amountInWei = ethers.utils.parseUnits(wrappedAmount, decimalsIn);
438
+ // Get gas options for all transactions
439
+ const gasOptions = await this.getGasOptions();
440
+ // Check and approve if needed
441
+ const tokenContract = new Contract(tokenInAddress, ERC20_ABI, this.signer);
442
+ const currentAllowance = await tokenContract.allowance(this.signer.address, QUICKSWAP_ROUTER);
443
+ if (currentAllowance.lt(amountInWei)) {
444
+ const approveTx = await tokenContract.approve(QUICKSWAP_ROUTER, ethers.constants.MaxUint256, gasOptions);
445
+ await approveTx.wait();
446
+ }
447
+ // Calculate min output with slippage
448
+ // Only stablecoin pairs can use ~1:1 ratio estimation
449
+ const stablecoins = ['USDC', 'NATIVE_USDC', 'USDC_E', 'USDT', 'DAI'];
450
+ const isStablecoinPair = stablecoins.includes(actualTokenIn) && stablecoins.includes(upperTokenOut);
451
+ let minAmountOut;
452
+ if (isStablecoinPair) {
453
+ // For stablecoin pairs, assume ~1:1 ratio with slippage
454
+ let estimatedOut = amountInWei;
455
+ if (decimalsIn !== decimalsOut) {
456
+ if (decimalsIn > decimalsOut) {
457
+ estimatedOut = amountInWei.div(BigNumber.from(10).pow(decimalsIn - decimalsOut));
458
+ }
459
+ else {
460
+ estimatedOut = amountInWei.mul(BigNumber.from(10).pow(decimalsOut - decimalsIn));
461
+ }
462
+ }
463
+ const slippageBps = Math.floor(slippage * 100);
464
+ minAmountOut = estimatedOut.mul(10000 - slippageBps).div(10000);
465
+ }
466
+ else {
467
+ // For non-stablecoin pairs (like MATIC → USDC), set minAmountOut to 0
468
+ // The actual protection comes from the DEX's price oracle
469
+ // In production, you should use a quoter contract for accurate price
470
+ minAmountOut = BigNumber.from(0);
471
+ }
472
+ // Execute swap
473
+ const swapParams = {
474
+ tokenIn: tokenInAddress,
475
+ tokenOut: tokenOutAddress,
476
+ recipient: this.signer.address,
477
+ deadline: Math.floor(Date.now() / 1000) + deadline,
478
+ amountIn: amountInWei,
479
+ amountOutMinimum: minAmountOut,
480
+ limitSqrtPrice: 0,
481
+ };
482
+ const tx = await this.router.exactInputSingle(swapParams, { ...gasOptions, gasLimit: 300000 });
483
+ const receipt = await tx.wait();
484
+ // Get actual output amount
485
+ const tokenOutContract = new Contract(tokenOutAddress, ERC20_ABI, this.provider);
486
+ const finalBalance = await tokenOutContract.balanceOf(this.signer.address);
487
+ return {
488
+ success: true,
489
+ transactionHash: receipt.transactionHash,
490
+ tokenIn: upperTokenIn,
491
+ tokenOut: upperTokenOut,
492
+ amountIn,
493
+ amountOut: ethers.utils.formatUnits(finalBalance, decimalsOut),
494
+ gasUsed: receipt.gasUsed.toString(),
495
+ };
496
+ }
497
+ /**
498
+ * Swap any supported token to USDC (for deposit)
499
+ */
500
+ async swapToUsdc(tokenIn, amountIn, options = {}) {
501
+ const { usdcType = 'NATIVE_USDC', slippage = 0.5 } = options;
502
+ const upperTokenIn = tokenIn.toUpperCase();
503
+ // If already USDC, no swap needed
504
+ if (upperTokenIn === 'USDC' || upperTokenIn === 'NATIVE_USDC') {
505
+ if (usdcType === 'NATIVE_USDC') {
506
+ return {
507
+ success: true,
508
+ transactionHash: '',
509
+ tokenIn: upperTokenIn,
510
+ tokenOut: 'NATIVE_USDC',
511
+ amountIn,
512
+ amountOut: amountIn,
513
+ gasUsed: '0',
514
+ };
515
+ }
516
+ // Swap USDC to USDC.e
517
+ return this.swap('USDC', 'USDC_E', amountIn, { slippage });
518
+ }
519
+ if (upperTokenIn === 'USDC_E') {
520
+ if (usdcType === 'USDC_E') {
521
+ return {
522
+ success: true,
523
+ transactionHash: '',
524
+ tokenIn: upperTokenIn,
525
+ tokenOut: 'USDC_E',
526
+ amountIn,
527
+ amountOut: amountIn,
528
+ gasUsed: '0',
529
+ };
530
+ }
531
+ // Swap USDC.e to USDC
532
+ return this.swap('USDC_E', 'USDC', amountIn, { slippage });
533
+ }
534
+ // Swap other tokens to USDC
535
+ const targetUsdc = usdcType === 'NATIVE_USDC' ? 'USDC' : 'USDC_E';
536
+ return this.swap(tokenIn, targetUsdc, amountIn, { slippage });
537
+ }
538
+ /**
539
+ * Get list of supported tokens
540
+ */
541
+ getSupportedTokens() {
542
+ return Object.keys(POLYGON_TOKENS);
543
+ }
544
+ /**
545
+ * Get balances for any wallet address (static method, no signer required)
546
+ */
547
+ static async getWalletBalances(address, provider) {
548
+ const rpcProvider = provider || new ethers.providers.JsonRpcProvider('https://polygon-rpc.com');
549
+ const balances = [];
550
+ // Get native MATIC balance
551
+ const maticBalance = await rpcProvider.getBalance(address);
552
+ balances.push({
553
+ token: 'MATIC',
554
+ symbol: 'MATIC',
555
+ balance: ethers.utils.formatEther(maticBalance),
556
+ decimals: 18,
557
+ });
558
+ // Get ERC20 balances
559
+ const tokens = ['USDC', 'USDC_E', 'USDT', 'DAI', 'WETH', 'WMATIC'];
560
+ for (const tokenSymbol of tokens) {
561
+ const tokenAddress = POLYGON_TOKENS[tokenSymbol];
562
+ const contract = new Contract(tokenAddress, ERC20_ABI, rpcProvider);
563
+ try {
564
+ const balance = await contract.balanceOf(address);
565
+ const decimals = TOKEN_DECIMALS[tokenSymbol];
566
+ balances.push({
567
+ token: tokenSymbol,
568
+ symbol: tokenSymbol,
569
+ balance: ethers.utils.formatUnits(balance, decimals),
570
+ decimals,
571
+ });
572
+ }
573
+ catch {
574
+ // Skip if token query fails
575
+ }
576
+ }
577
+ return balances;
578
+ }
579
+ /**
580
+ * Get balance for a specific token for any wallet (static)
581
+ */
582
+ static async getWalletBalance(address, token, provider) {
583
+ const rpcProvider = provider || new ethers.providers.JsonRpcProvider('https://polygon-rpc.com');
584
+ const upperToken = token.toUpperCase();
585
+ if (upperToken === 'MATIC') {
586
+ const balance = await rpcProvider.getBalance(address);
587
+ return ethers.utils.formatEther(balance);
588
+ }
589
+ const tokenAddress = POLYGON_TOKENS[upperToken];
590
+ if (!tokenAddress) {
591
+ throw new Error(`Unknown token: ${token}`);
592
+ }
593
+ const contract = new Contract(tokenAddress, ERC20_ABI, rpcProvider);
594
+ const balance = await contract.balanceOf(address);
595
+ const decimals = TOKEN_DECIMALS[upperToken] || 18;
596
+ return ethers.utils.formatUnits(balance, decimals);
597
+ }
598
+ // ============= Transfer Methods =============
599
+ /**
600
+ * Transfer native MATIC (POL) to another address
601
+ */
602
+ async transferMatic(to, amount) {
603
+ const amountWei = ethers.utils.parseEther(amount);
604
+ // Check balance
605
+ const balance = await this.provider.getBalance(this.signer.address);
606
+ if (balance.lt(amountWei)) {
607
+ throw new Error(`Insufficient MATIC balance: have ${ethers.utils.formatEther(balance)}, need ${amount}`);
608
+ }
609
+ const gasOptions = await this.getGasOptions();
610
+ const tx = await this.signer.sendTransaction({
611
+ to,
612
+ value: amountWei,
613
+ ...gasOptions,
614
+ gasLimit: 21000, // Standard ETH transfer gas limit
615
+ });
616
+ const receipt = await tx.wait();
617
+ return {
618
+ success: true,
619
+ transactionHash: receipt.transactionHash,
620
+ token: 'MATIC',
621
+ to,
622
+ amount,
623
+ gasUsed: receipt.gasUsed.toString(),
624
+ };
625
+ }
626
+ /**
627
+ * Transfer an ERC20 token to another address
628
+ */
629
+ async transfer(token, to, amount) {
630
+ const upperToken = token.toUpperCase();
631
+ // For native MATIC, use transferMatic
632
+ if (upperToken === 'MATIC') {
633
+ return this.transferMatic(to, amount);
634
+ }
635
+ const tokenAddress = this.getTokenAddress(token);
636
+ const decimals = this.getTokenDecimals(token);
637
+ const amountWei = ethers.utils.parseUnits(amount, decimals);
638
+ const contract = new Contract(tokenAddress, ERC20_ABI, this.signer);
639
+ // Check balance
640
+ const balance = await contract.balanceOf(this.signer.address);
641
+ if (balance.lt(amountWei)) {
642
+ throw new Error(`Insufficient ${upperToken} balance: have ${ethers.utils.formatUnits(balance, decimals)}, need ${amount}`);
643
+ }
644
+ const gasOptions = await this.getGasOptions();
645
+ const tx = await contract.transfer(to, amountWei, {
646
+ ...gasOptions,
647
+ gasLimit: 100000, // ERC20 transfer gas limit (USDC.e needs ~71k)
648
+ });
649
+ const receipt = await tx.wait();
650
+ return {
651
+ success: true,
652
+ transactionHash: receipt.transactionHash,
653
+ token: upperToken,
654
+ to,
655
+ amount,
656
+ gasUsed: receipt.gasUsed.toString(),
657
+ };
658
+ }
659
+ /**
660
+ * Transfer native USDC to another address
661
+ *
662
+ * ⚠️ WARNING: This transfers NATIVE USDC (0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359)
663
+ *
664
+ * For Polymarket CTF operations, you need USDC.e instead.
665
+ * Use transferUsdcE() for Polymarket CTF compatibility.
666
+ *
667
+ * @see transferUsdcE - For Polymarket CTF operations
668
+ */
669
+ async transferUsdc(to, amount) {
670
+ return this.transfer('USDC', to, amount);
671
+ }
672
+ /**
673
+ * Transfer USDC.e (bridged USDC) to another address
674
+ *
675
+ * ✅ This is the correct method for Polymarket CTF operations.
676
+ *
677
+ * Polymarket's Conditional Token Framework (CTF) only accepts
678
+ * USDC.e (0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174).
679
+ *
680
+ * If you're funding a wallet for CTF trading, use this method.
681
+ *
682
+ * @example
683
+ * ```typescript
684
+ * // Fund a session wallet for Polymarket trading
685
+ * await swapService.transferUsdcE(sessionWallet, '100');
686
+ *
687
+ * // The session wallet can now perform CTF operations
688
+ * await ctf.split(conditionId, '100');
689
+ * ```
690
+ */
691
+ async transferUsdcE(to, amount) {
692
+ return this.transfer('USDC_E', to, amount);
693
+ }
694
+ }
695
+ //# sourceMappingURL=swap-service.js.map