@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,896 @@
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
+
8
+ import { ethers, Contract, BigNumber } from 'ethers';
9
+
10
+ // QuickSwap V3 Contracts on Polygon
11
+ export const QUICKSWAP_ROUTER = '0xf5b509bB0909a69B1c207E495f687a596C168E12';
12
+ export const QUICKSWAP_QUOTER = '0xa15F0D7377B2A0C0c10db057f641beD21028FC89';
13
+ export const QUICKSWAP_FACTORY = '0x411b0fAcC3489691f28ad58c47006AF5E3Ab3A28';
14
+
15
+ // Wrapped MATIC for swapping native MATIC
16
+ export const WMATIC = '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270';
17
+
18
+ /**
19
+ * Supported tokens on Polygon
20
+ *
21
+ * ⚠️ IMPORTANT: USDC vs USDC.e for Polymarket CTF
22
+ *
23
+ * | Token | Address | Polymarket CTF |
24
+ * |-------------|--------------------------------------------|-----------------
25
+ * | USDC_E | 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174 | ✅ Required |
26
+ * | USDC/NATIVE | 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359 | ❌ Not accepted|
27
+ *
28
+ * For Polymarket CTF operations (split/merge/redeem):
29
+ * - Use transferUsdcE() to send USDC.e
30
+ * - Use swap('USDC', 'USDC_E', amount) to convert native USDC to USDC.e
31
+ *
32
+ * For general transfers:
33
+ * - transferUsdc() sends native USDC (most DEXs, CEXs use this)
34
+ * - transferUsdcE() sends bridged USDC.e (Polymarket CTF requires this)
35
+ */
36
+ export const POLYGON_TOKENS = {
37
+ // Native MATIC (use WMATIC address for swaps)
38
+ MATIC: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270',
39
+ WMATIC: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270',
40
+ // USDC variants - SEE ABOVE FOR POLYMARKET COMPATIBILITY
41
+ USDC: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359', // Native USDC - NOT for Polymarket CTF
42
+ NATIVE_USDC: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359', // Alias for USDC
43
+ USDC_E: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', // Bridged USDC.e - REQUIRED for Polymarket CTF
44
+ // Other stables
45
+ USDT: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F',
46
+ DAI: '0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063',
47
+ // ETH
48
+ WETH: '0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619',
49
+ } as const;
50
+
51
+ // Token decimals
52
+ export const TOKEN_DECIMALS: Record<string, number> = {
53
+ MATIC: 18,
54
+ WMATIC: 18,
55
+ USDC: 6,
56
+ NATIVE_USDC: 6,
57
+ USDC_E: 6,
58
+ USDT: 6,
59
+ DAI: 18,
60
+ WETH: 18,
61
+ };
62
+
63
+ export type SupportedToken = keyof typeof POLYGON_TOKENS;
64
+
65
+ // ABIs
66
+ const ERC20_ABI = [
67
+ 'function balanceOf(address) view returns (uint256)',
68
+ 'function decimals() view returns (uint8)',
69
+ 'function symbol() view returns (string)',
70
+ 'function approve(address spender, uint256 amount) returns (bool)',
71
+ 'function allowance(address owner, address spender) view returns (uint256)',
72
+ 'function transfer(address to, uint256 amount) returns (bool)',
73
+ ];
74
+
75
+ const QUICKSWAP_ROUTER_ABI = [
76
+ 'function exactInputSingle((address tokenIn, address tokenOut, address recipient, uint256 deadline, uint256 amountIn, uint256 amountOutMinimum, uint160 limitSqrtPrice)) external payable returns (uint256 amountOut)',
77
+ 'function exactInput((bytes path, address recipient, uint256 deadline, uint256 amountIn, uint256 amountOutMinimum)) external payable returns (uint256 amountOut)',
78
+ ];
79
+
80
+ const QUICKSWAP_QUOTER_ABI = [
81
+ 'function quoteExactInputSingle(address tokenIn, address tokenOut, uint256 amountIn, uint160 limitSqrtPrice) external returns (uint256 amountOut, uint16 fee)',
82
+ 'function quoteExactInput(bytes path, uint256 amountIn) external returns (uint256 amountOut, uint16[] fees)',
83
+ ];
84
+
85
+ const QUICKSWAP_FACTORY_ABI = [
86
+ 'function poolByPair(address tokenA, address tokenB) external view returns (address pool)',
87
+ ];
88
+
89
+ const WMATIC_ABI = [
90
+ 'function deposit() external payable',
91
+ 'function withdraw(uint256 amount) external',
92
+ ];
93
+
94
+ export interface SwapQuote {
95
+ tokenIn: string;
96
+ tokenOut: string;
97
+ amountIn: string;
98
+ estimatedAmountOut: string;
99
+ minAmountOut: string;
100
+ slippage: number;
101
+ priceImpact: string;
102
+ }
103
+
104
+ /** Quote result from Quoter contract */
105
+ export interface QuoteResult {
106
+ possible: boolean;
107
+ tokenIn: string;
108
+ tokenOut: string;
109
+ amountIn: string;
110
+ amountOut: string | null;
111
+ route: string[];
112
+ poolExists: boolean;
113
+ reason?: string;
114
+ }
115
+
116
+ /** Pool info */
117
+ export interface PoolInfo {
118
+ tokenA: string;
119
+ tokenB: string;
120
+ poolAddress: string | null;
121
+ exists: boolean;
122
+ }
123
+
124
+ export interface SwapResult {
125
+ success: boolean;
126
+ transactionHash: string;
127
+ tokenIn: string;
128
+ tokenOut: string;
129
+ amountIn: string;
130
+ amountOut: string;
131
+ gasUsed: string;
132
+ }
133
+
134
+ export interface TokenBalance {
135
+ token: string;
136
+ symbol: string;
137
+ balance: string;
138
+ decimals: number;
139
+ }
140
+
141
+ export interface TransferResult {
142
+ success: boolean;
143
+ transactionHash: string;
144
+ token: string;
145
+ to: string;
146
+ amount: string;
147
+ gasUsed: string;
148
+ }
149
+
150
+ export class SwapService {
151
+ private signer: ethers.Wallet;
152
+ private provider: ethers.providers.Provider;
153
+ private router: Contract;
154
+ private quoter: Contract;
155
+ private factory: Contract;
156
+
157
+ constructor(signer: ethers.Wallet) {
158
+ // Use signer's provider if available, otherwise create a default Polygon provider
159
+ this.provider = signer.provider || new ethers.providers.JsonRpcProvider('https://polygon-rpc.com');
160
+ // Ensure signer is connected to the provider
161
+ this.signer = signer.provider ? signer : signer.connect(this.provider);
162
+ this.router = new Contract(QUICKSWAP_ROUTER, QUICKSWAP_ROUTER_ABI, this.signer);
163
+ this.quoter = new Contract(QUICKSWAP_QUOTER, QUICKSWAP_QUOTER_ABI, this.provider);
164
+ this.factory = new Contract(QUICKSWAP_FACTORY, QUICKSWAP_FACTORY_ABI, this.provider);
165
+ }
166
+
167
+ /**
168
+ * Get dynamic gas options for Polygon network
169
+ * Uses RPC fee data with minimum priority fee of 30 gwei
170
+ */
171
+ private async getGasOptions(): Promise<{
172
+ maxPriorityFeePerGas: BigNumber;
173
+ maxFeePerGas: BigNumber;
174
+ }> {
175
+ const feeData = await this.provider.getFeeData();
176
+ const baseFee = feeData.lastBaseFeePerGas || feeData.gasPrice || ethers.utils.parseUnits('100', 'gwei');
177
+ const minPriorityFee = ethers.utils.parseUnits('30', 'gwei');
178
+ const maxPriorityFeePerGas = feeData.maxPriorityFeePerGas && feeData.maxPriorityFeePerGas.gt(minPriorityFee)
179
+ ? feeData.maxPriorityFeePerGas
180
+ : minPriorityFee;
181
+ const maxFeePerGas = baseFee.mul(3).div(2).add(maxPriorityFeePerGas);
182
+ return { maxPriorityFeePerGas, maxFeePerGas };
183
+ }
184
+
185
+ /**
186
+ * Get the wallet address
187
+ */
188
+ get address(): string {
189
+ return this.signer.address;
190
+ }
191
+
192
+ /**
193
+ * Get token address from symbol
194
+ */
195
+ getTokenAddress(token: string): string {
196
+ const upperToken = token.toUpperCase() as SupportedToken;
197
+ const address = POLYGON_TOKENS[upperToken];
198
+ if (!address) {
199
+ // Check if it's already an address
200
+ if (token.startsWith('0x') && token.length === 42) {
201
+ return token;
202
+ }
203
+ throw new Error(`Unknown token: ${token}. Supported: ${Object.keys(POLYGON_TOKENS).join(', ')}`);
204
+ }
205
+ return address;
206
+ }
207
+
208
+ /**
209
+ * Get token decimals
210
+ */
211
+ getTokenDecimals(token: string): number {
212
+ const upperToken = token.toUpperCase();
213
+ return TOKEN_DECIMALS[upperToken] || 18;
214
+ }
215
+
216
+ /**
217
+ * Check if a pool exists for a token pair
218
+ */
219
+ async checkPool(tokenA: string, tokenB: string): Promise<PoolInfo> {
220
+ const addressA = this.getTokenAddress(tokenA);
221
+ const addressB = this.getTokenAddress(tokenB);
222
+
223
+ try {
224
+ const poolAddress = await this.factory.poolByPair(addressA, addressB);
225
+ const exists = poolAddress !== ethers.constants.AddressZero;
226
+
227
+ return {
228
+ tokenA: tokenA.toUpperCase(),
229
+ tokenB: tokenB.toUpperCase(),
230
+ poolAddress: exists ? poolAddress : null,
231
+ exists,
232
+ };
233
+ } catch {
234
+ return {
235
+ tokenA: tokenA.toUpperCase(),
236
+ tokenB: tokenB.toUpperCase(),
237
+ poolAddress: null,
238
+ exists: false,
239
+ };
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Get all available pools for supported tokens
245
+ */
246
+ async getAvailablePools(): Promise<PoolInfo[]> {
247
+ const tokens = Object.keys(POLYGON_TOKENS).filter(
248
+ (t) => t !== 'NATIVE_USDC' && t !== 'WMATIC' // Skip aliases
249
+ );
250
+ const pools: PoolInfo[] = [];
251
+
252
+ for (let i = 0; i < tokens.length; i++) {
253
+ for (let j = i + 1; j < tokens.length; j++) {
254
+ const pool = await this.checkPool(tokens[i], tokens[j]);
255
+ if (pool.exists) {
256
+ pools.push(pool);
257
+ }
258
+ }
259
+ }
260
+
261
+ return pools;
262
+ }
263
+
264
+ /**
265
+ * Get a quote for a swap (checks if route is possible)
266
+ */
267
+ async getQuote(
268
+ tokenIn: string,
269
+ tokenOut: string,
270
+ amountIn: string
271
+ ): Promise<QuoteResult> {
272
+ const upperTokenIn = tokenIn.toUpperCase();
273
+ const upperTokenOut = tokenOut.toUpperCase();
274
+
275
+ // Handle MATIC → need to use WMATIC for the pool
276
+ const actualTokenIn = upperTokenIn === 'MATIC' ? 'WMATIC' : upperTokenIn;
277
+ const actualTokenOut = upperTokenOut === 'MATIC' ? 'WMATIC' : upperTokenOut;
278
+
279
+ const addressIn = this.getTokenAddress(actualTokenIn);
280
+ const addressOut = this.getTokenAddress(actualTokenOut);
281
+ const decimalsIn = this.getTokenDecimals(actualTokenIn);
282
+ const decimalsOut = this.getTokenDecimals(actualTokenOut);
283
+ const amountInWei = ethers.utils.parseUnits(amountIn, decimalsIn);
284
+
285
+ // First check if direct pool exists
286
+ const directPool = await this.checkPool(actualTokenIn, actualTokenOut);
287
+
288
+ if (directPool.exists) {
289
+ // Try direct quote
290
+ try {
291
+ const result = await this.quoter.callStatic.quoteExactInputSingle(
292
+ addressIn,
293
+ addressOut,
294
+ amountInWei,
295
+ 0 // no price limit
296
+ );
297
+ const amountOut = ethers.utils.formatUnits(result.amountOut, decimalsOut);
298
+
299
+ return {
300
+ possible: true,
301
+ tokenIn: upperTokenIn,
302
+ tokenOut: upperTokenOut,
303
+ amountIn,
304
+ amountOut,
305
+ route: [upperTokenIn, upperTokenOut],
306
+ poolExists: true,
307
+ };
308
+ } catch {
309
+ // Pool exists but quote failed (maybe low liquidity)
310
+ return {
311
+ possible: false,
312
+ tokenIn: upperTokenIn,
313
+ tokenOut: upperTokenOut,
314
+ amountIn,
315
+ amountOut: null,
316
+ route: [upperTokenIn, upperTokenOut],
317
+ poolExists: true,
318
+ reason: 'Pool exists but insufficient liquidity for this amount',
319
+ };
320
+ }
321
+ }
322
+
323
+ // Try multi-hop through USDC or WMATIC
324
+ const intermediates = ['USDC', 'WMATIC', 'WETH'];
325
+ for (const mid of intermediates) {
326
+ if (mid === actualTokenIn || mid === actualTokenOut) continue;
327
+
328
+ const pool1 = await this.checkPool(actualTokenIn, mid);
329
+ const pool2 = await this.checkPool(mid, actualTokenOut);
330
+
331
+ if (pool1.exists && pool2.exists) {
332
+ // Try multi-hop quote
333
+ try {
334
+ const midAddress = this.getTokenAddress(mid);
335
+ const path = ethers.utils.solidityPack(
336
+ ['address', 'address', 'address'],
337
+ [addressIn, midAddress, addressOut]
338
+ );
339
+
340
+ const result = await this.quoter.callStatic.quoteExactInput(path, amountInWei);
341
+ const amountOut = ethers.utils.formatUnits(result.amountOut, decimalsOut);
342
+
343
+ return {
344
+ possible: true,
345
+ tokenIn: upperTokenIn,
346
+ tokenOut: upperTokenOut,
347
+ amountIn,
348
+ amountOut,
349
+ route: [upperTokenIn, mid, upperTokenOut],
350
+ poolExists: true,
351
+ };
352
+ } catch {
353
+ // Continue to try other routes
354
+ }
355
+ }
356
+ }
357
+
358
+ // No route found
359
+ return {
360
+ possible: false,
361
+ tokenIn: upperTokenIn,
362
+ tokenOut: upperTokenOut,
363
+ amountIn,
364
+ amountOut: null,
365
+ route: [],
366
+ poolExists: false,
367
+ reason: 'No liquidity pool or route available for this pair',
368
+ };
369
+ }
370
+
371
+ /**
372
+ * Execute a multi-hop swap
373
+ */
374
+ async swapMultiHop(
375
+ tokenIn: string,
376
+ tokenOut: string,
377
+ amountIn: string,
378
+ route: string[],
379
+ options: { slippage?: number; deadline?: number } = {}
380
+ ): Promise<SwapResult> {
381
+ const { slippage = 0.5, deadline = 300 } = options;
382
+
383
+ if (route.length < 2) {
384
+ throw new Error('Route must have at least 2 tokens');
385
+ }
386
+
387
+ const upperTokenIn = tokenIn.toUpperCase();
388
+ const upperTokenOut = tokenOut.toUpperCase();
389
+
390
+ // Handle MATIC wrapping
391
+ let wrappedAmount = amountIn;
392
+ if (upperTokenIn === 'MATIC') {
393
+ await this.wrapMatic(amountIn);
394
+ }
395
+
396
+ // Build path
397
+ const addresses = route.map((t) => {
398
+ const upper = t.toUpperCase();
399
+ return this.getTokenAddress(upper === 'MATIC' ? 'WMATIC' : upper);
400
+ });
401
+
402
+ const path = ethers.utils.solidityPack(
403
+ addresses.map(() => 'address'),
404
+ addresses
405
+ );
406
+
407
+ const decimalsIn = this.getTokenDecimals(route[0] === 'MATIC' ? 'WMATIC' : route[0]);
408
+ const decimalsOut = this.getTokenDecimals(route[route.length - 1] === 'MATIC' ? 'WMATIC' : route[route.length - 1]);
409
+ const amountInWei = ethers.utils.parseUnits(wrappedAmount, decimalsIn);
410
+
411
+ // Get gas options
412
+ const gasOptions = await this.getGasOptions();
413
+
414
+ // Check and approve if needed
415
+ const tokenInAddress = addresses[0];
416
+ const tokenContract = new Contract(tokenInAddress, ERC20_ABI, this.signer);
417
+ const currentAllowance = await tokenContract.allowance(this.signer.address, QUICKSWAP_ROUTER);
418
+
419
+ if (currentAllowance.lt(amountInWei)) {
420
+ const approveTx = await tokenContract.approve(QUICKSWAP_ROUTER, ethers.constants.MaxUint256, gasOptions);
421
+ await approveTx.wait();
422
+ }
423
+
424
+ // Execute multi-hop swap
425
+ const swapParams = {
426
+ path,
427
+ recipient: this.signer.address,
428
+ deadline: Math.floor(Date.now() / 1000) + deadline,
429
+ amountIn: amountInWei,
430
+ amountOutMinimum: 0, // For simplicity; in production use quote with slippage
431
+ };
432
+
433
+ const tx = await this.router.exactInput(swapParams, { ...gasOptions, gasLimit: 500000 });
434
+ const receipt = await tx.wait();
435
+
436
+ // Get actual output amount
437
+ const tokenOutAddress = addresses[addresses.length - 1];
438
+ const tokenOutContract = new Contract(tokenOutAddress, ERC20_ABI, this.provider);
439
+ const finalBalance = await tokenOutContract.balanceOf(this.signer.address);
440
+
441
+ return {
442
+ success: receipt.status === 1,
443
+ transactionHash: receipt.transactionHash,
444
+ tokenIn: upperTokenIn,
445
+ tokenOut: upperTokenOut,
446
+ amountIn,
447
+ amountOut: ethers.utils.formatUnits(finalBalance, decimalsOut),
448
+ gasUsed: receipt.gasUsed.toString(),
449
+ };
450
+ }
451
+
452
+ /**
453
+ * Get balances for all supported tokens
454
+ */
455
+ async getBalances(): Promise<TokenBalance[]> {
456
+ const balances: TokenBalance[] = [];
457
+
458
+ // Get native MATIC balance
459
+ const maticBalance = await this.provider.getBalance(this.signer.address);
460
+ balances.push({
461
+ token: 'MATIC',
462
+ symbol: 'MATIC',
463
+ balance: ethers.utils.formatEther(maticBalance),
464
+ decimals: 18,
465
+ });
466
+
467
+ // Get ERC20 balances
468
+ const tokens = ['USDC', 'USDC_E', 'USDT', 'DAI', 'WETH', 'WMATIC'];
469
+ for (const tokenSymbol of tokens) {
470
+ const address = POLYGON_TOKENS[tokenSymbol as SupportedToken];
471
+ const contract = new Contract(address, ERC20_ABI, this.provider);
472
+ try {
473
+ const balance = await contract.balanceOf(this.signer.address);
474
+ const decimals = TOKEN_DECIMALS[tokenSymbol];
475
+ balances.push({
476
+ token: tokenSymbol,
477
+ symbol: tokenSymbol,
478
+ balance: ethers.utils.formatUnits(balance, decimals),
479
+ decimals,
480
+ });
481
+ } catch {
482
+ // Skip if token query fails
483
+ }
484
+ }
485
+
486
+ return balances;
487
+ }
488
+
489
+ /**
490
+ * Get balance for a specific token
491
+ */
492
+ async getBalance(token: string): Promise<string> {
493
+ const upperToken = token.toUpperCase();
494
+
495
+ if (upperToken === 'MATIC') {
496
+ const balance = await this.provider.getBalance(this.signer.address);
497
+ return ethers.utils.formatEther(balance);
498
+ }
499
+
500
+ const address = this.getTokenAddress(token);
501
+ const contract = new Contract(address, ERC20_ABI, this.provider);
502
+ const balance = await contract.balanceOf(this.signer.address);
503
+ const decimals = this.getTokenDecimals(token);
504
+ return ethers.utils.formatUnits(balance, decimals);
505
+ }
506
+
507
+ /**
508
+ * Wrap native MATIC to WMATIC
509
+ */
510
+ async wrapMatic(amount: string): Promise<SwapResult> {
511
+ const amountWei = ethers.utils.parseEther(amount);
512
+ const wmatic = new Contract(WMATIC, WMATIC_ABI, this.signer);
513
+ const gasOptions = await this.getGasOptions();
514
+
515
+ const tx = await wmatic.deposit({ value: amountWei, ...gasOptions });
516
+ const receipt = await tx.wait();
517
+
518
+ return {
519
+ success: true,
520
+ transactionHash: receipt.transactionHash,
521
+ tokenIn: 'MATIC',
522
+ tokenOut: 'WMATIC',
523
+ amountIn: amount,
524
+ amountOut: amount,
525
+ gasUsed: receipt.gasUsed.toString(),
526
+ };
527
+ }
528
+
529
+ /**
530
+ * Unwrap WMATIC to native MATIC
531
+ */
532
+ async unwrapMatic(amount: string): Promise<SwapResult> {
533
+ const amountWei = ethers.utils.parseEther(amount);
534
+ const wmatic = new Contract(WMATIC, WMATIC_ABI, this.signer);
535
+ const gasOptions = await this.getGasOptions();
536
+
537
+ const tx = await wmatic.withdraw(amountWei, gasOptions);
538
+ const receipt = await tx.wait();
539
+
540
+ return {
541
+ success: true,
542
+ transactionHash: receipt.transactionHash,
543
+ tokenIn: 'WMATIC',
544
+ tokenOut: 'MATIC',
545
+ amountIn: amount,
546
+ amountOut: amount,
547
+ gasUsed: receipt.gasUsed.toString(),
548
+ };
549
+ }
550
+
551
+ /**
552
+ * Execute a token swap using QuickSwap V3
553
+ */
554
+ async swap(
555
+ tokenIn: string,
556
+ tokenOut: string,
557
+ amountIn: string,
558
+ options: {
559
+ slippage?: number; // Default 0.5%
560
+ deadline?: number; // Default 5 minutes
561
+ } = {}
562
+ ): Promise<SwapResult> {
563
+ const { slippage = 0.5, deadline = 300 } = options;
564
+
565
+ const upperTokenIn = tokenIn.toUpperCase();
566
+ const upperTokenOut = tokenOut.toUpperCase();
567
+
568
+ // Handle native MATIC swaps
569
+ if (upperTokenIn === 'MATIC' && upperTokenOut === 'WMATIC') {
570
+ return this.wrapMatic(amountIn);
571
+ }
572
+ if (upperTokenIn === 'WMATIC' && upperTokenOut === 'MATIC') {
573
+ return this.unwrapMatic(amountIn);
574
+ }
575
+
576
+ // For MATIC input, first wrap to WMATIC
577
+ let actualTokenIn = upperTokenIn;
578
+ let wrappedAmount = amountIn;
579
+ if (upperTokenIn === 'MATIC') {
580
+ await this.wrapMatic(amountIn);
581
+ actualTokenIn = 'WMATIC';
582
+ }
583
+
584
+ const tokenInAddress = this.getTokenAddress(actualTokenIn);
585
+ const tokenOutAddress = this.getTokenAddress(tokenOut);
586
+ const decimalsIn = this.getTokenDecimals(actualTokenIn);
587
+ const decimalsOut = this.getTokenDecimals(tokenOut);
588
+
589
+ const amountInWei = ethers.utils.parseUnits(wrappedAmount, decimalsIn);
590
+
591
+ // Get gas options for all transactions
592
+ const gasOptions = await this.getGasOptions();
593
+
594
+ // Check and approve if needed
595
+ const tokenContract = new Contract(tokenInAddress, ERC20_ABI, this.signer);
596
+ const currentAllowance = await tokenContract.allowance(this.signer.address, QUICKSWAP_ROUTER);
597
+
598
+ if (currentAllowance.lt(amountInWei)) {
599
+ const approveTx = await tokenContract.approve(QUICKSWAP_ROUTER, ethers.constants.MaxUint256, gasOptions);
600
+ await approveTx.wait();
601
+ }
602
+
603
+ // Calculate min output with slippage
604
+ // Only stablecoin pairs can use ~1:1 ratio estimation
605
+ const stablecoins = ['USDC', 'NATIVE_USDC', 'USDC_E', 'USDT', 'DAI'];
606
+ const isStablecoinPair = stablecoins.includes(actualTokenIn) && stablecoins.includes(upperTokenOut);
607
+
608
+ let minAmountOut: BigNumber;
609
+ if (isStablecoinPair) {
610
+ // For stablecoin pairs, assume ~1:1 ratio with slippage
611
+ let estimatedOut = amountInWei;
612
+ if (decimalsIn !== decimalsOut) {
613
+ if (decimalsIn > decimalsOut) {
614
+ estimatedOut = amountInWei.div(BigNumber.from(10).pow(decimalsIn - decimalsOut));
615
+ } else {
616
+ estimatedOut = amountInWei.mul(BigNumber.from(10).pow(decimalsOut - decimalsIn));
617
+ }
618
+ }
619
+ const slippageBps = Math.floor(slippage * 100);
620
+ minAmountOut = estimatedOut.mul(10000 - slippageBps).div(10000);
621
+ } else {
622
+ // For non-stablecoin pairs (like MATIC → USDC), set minAmountOut to 0
623
+ // The actual protection comes from the DEX's price oracle
624
+ // In production, you should use a quoter contract for accurate price
625
+ minAmountOut = BigNumber.from(0);
626
+ }
627
+
628
+ // Execute swap
629
+ const swapParams = {
630
+ tokenIn: tokenInAddress,
631
+ tokenOut: tokenOutAddress,
632
+ recipient: this.signer.address,
633
+ deadline: Math.floor(Date.now() / 1000) + deadline,
634
+ amountIn: amountInWei,
635
+ amountOutMinimum: minAmountOut,
636
+ limitSqrtPrice: 0,
637
+ };
638
+
639
+ const tx = await this.router.exactInputSingle(swapParams, { ...gasOptions, gasLimit: 300000 });
640
+ const receipt = await tx.wait();
641
+
642
+ // Get actual output amount
643
+ const tokenOutContract = new Contract(tokenOutAddress, ERC20_ABI, this.provider);
644
+ const finalBalance = await tokenOutContract.balanceOf(this.signer.address);
645
+
646
+ return {
647
+ success: true,
648
+ transactionHash: receipt.transactionHash,
649
+ tokenIn: upperTokenIn,
650
+ tokenOut: upperTokenOut,
651
+ amountIn,
652
+ amountOut: ethers.utils.formatUnits(finalBalance, decimalsOut),
653
+ gasUsed: receipt.gasUsed.toString(),
654
+ };
655
+ }
656
+
657
+ /**
658
+ * Swap any supported token to USDC (for deposit)
659
+ */
660
+ async swapToUsdc(
661
+ tokenIn: string,
662
+ amountIn: string,
663
+ options: {
664
+ usdcType?: 'NATIVE_USDC' | 'USDC_E';
665
+ slippage?: number;
666
+ } = {}
667
+ ): Promise<SwapResult> {
668
+ const { usdcType = 'NATIVE_USDC', slippage = 0.5 } = options;
669
+
670
+ const upperTokenIn = tokenIn.toUpperCase();
671
+
672
+ // If already USDC, no swap needed
673
+ if (upperTokenIn === 'USDC' || upperTokenIn === 'NATIVE_USDC') {
674
+ if (usdcType === 'NATIVE_USDC') {
675
+ return {
676
+ success: true,
677
+ transactionHash: '',
678
+ tokenIn: upperTokenIn,
679
+ tokenOut: 'NATIVE_USDC',
680
+ amountIn,
681
+ amountOut: amountIn,
682
+ gasUsed: '0',
683
+ };
684
+ }
685
+ // Swap USDC to USDC.e
686
+ return this.swap('USDC', 'USDC_E', amountIn, { slippage });
687
+ }
688
+
689
+ if (upperTokenIn === 'USDC_E') {
690
+ if (usdcType === 'USDC_E') {
691
+ return {
692
+ success: true,
693
+ transactionHash: '',
694
+ tokenIn: upperTokenIn,
695
+ tokenOut: 'USDC_E',
696
+ amountIn,
697
+ amountOut: amountIn,
698
+ gasUsed: '0',
699
+ };
700
+ }
701
+ // Swap USDC.e to USDC
702
+ return this.swap('USDC_E', 'USDC', amountIn, { slippage });
703
+ }
704
+
705
+ // Swap other tokens to USDC
706
+ const targetUsdc = usdcType === 'NATIVE_USDC' ? 'USDC' : 'USDC_E';
707
+ return this.swap(tokenIn, targetUsdc, amountIn, { slippage });
708
+ }
709
+
710
+ /**
711
+ * Get list of supported tokens
712
+ */
713
+ getSupportedTokens(): string[] {
714
+ return Object.keys(POLYGON_TOKENS);
715
+ }
716
+
717
+ /**
718
+ * Get balances for any wallet address (static method, no signer required)
719
+ */
720
+ static async getWalletBalances(
721
+ address: string,
722
+ provider?: ethers.providers.Provider
723
+ ): Promise<TokenBalance[]> {
724
+ const rpcProvider = provider || new ethers.providers.JsonRpcProvider('https://polygon-rpc.com');
725
+ const balances: TokenBalance[] = [];
726
+
727
+ // Get native MATIC balance
728
+ const maticBalance = await rpcProvider.getBalance(address);
729
+ balances.push({
730
+ token: 'MATIC',
731
+ symbol: 'MATIC',
732
+ balance: ethers.utils.formatEther(maticBalance),
733
+ decimals: 18,
734
+ });
735
+
736
+ // Get ERC20 balances
737
+ const tokens = ['USDC', 'USDC_E', 'USDT', 'DAI', 'WETH', 'WMATIC'];
738
+ for (const tokenSymbol of tokens) {
739
+ const tokenAddress = POLYGON_TOKENS[tokenSymbol as SupportedToken];
740
+ const contract = new Contract(tokenAddress, ERC20_ABI, rpcProvider);
741
+ try {
742
+ const balance = await contract.balanceOf(address);
743
+ const decimals = TOKEN_DECIMALS[tokenSymbol];
744
+ balances.push({
745
+ token: tokenSymbol,
746
+ symbol: tokenSymbol,
747
+ balance: ethers.utils.formatUnits(balance, decimals),
748
+ decimals,
749
+ });
750
+ } catch {
751
+ // Skip if token query fails
752
+ }
753
+ }
754
+
755
+ return balances;
756
+ }
757
+
758
+ /**
759
+ * Get balance for a specific token for any wallet (static)
760
+ */
761
+ static async getWalletBalance(
762
+ address: string,
763
+ token: string,
764
+ provider?: ethers.providers.Provider
765
+ ): Promise<string> {
766
+ const rpcProvider = provider || new ethers.providers.JsonRpcProvider('https://polygon-rpc.com');
767
+ const upperToken = token.toUpperCase();
768
+
769
+ if (upperToken === 'MATIC') {
770
+ const balance = await rpcProvider.getBalance(address);
771
+ return ethers.utils.formatEther(balance);
772
+ }
773
+
774
+ const tokenAddress = POLYGON_TOKENS[upperToken as SupportedToken];
775
+ if (!tokenAddress) {
776
+ throw new Error(`Unknown token: ${token}`);
777
+ }
778
+
779
+ const contract = new Contract(tokenAddress, ERC20_ABI, rpcProvider);
780
+ const balance = await contract.balanceOf(address);
781
+ const decimals = TOKEN_DECIMALS[upperToken] || 18;
782
+ return ethers.utils.formatUnits(balance, decimals);
783
+ }
784
+
785
+ // ============= Transfer Methods =============
786
+
787
+ /**
788
+ * Transfer native MATIC (POL) to another address
789
+ */
790
+ async transferMatic(to: string, amount: string): Promise<TransferResult> {
791
+ const amountWei = ethers.utils.parseEther(amount);
792
+
793
+ // Check balance
794
+ const balance = await this.provider.getBalance(this.signer.address);
795
+ if (balance.lt(amountWei)) {
796
+ throw new Error(`Insufficient MATIC balance: have ${ethers.utils.formatEther(balance)}, need ${amount}`);
797
+ }
798
+
799
+ const gasOptions = await this.getGasOptions();
800
+
801
+ const tx = await this.signer.sendTransaction({
802
+ to,
803
+ value: amountWei,
804
+ ...gasOptions,
805
+ gasLimit: 21000, // Standard ETH transfer gas limit
806
+ });
807
+ const receipt = await tx.wait();
808
+
809
+ return {
810
+ success: true,
811
+ transactionHash: receipt.transactionHash,
812
+ token: 'MATIC',
813
+ to,
814
+ amount,
815
+ gasUsed: receipt.gasUsed.toString(),
816
+ };
817
+ }
818
+
819
+ /**
820
+ * Transfer an ERC20 token to another address
821
+ */
822
+ async transfer(token: string, to: string, amount: string): Promise<TransferResult> {
823
+ const upperToken = token.toUpperCase();
824
+
825
+ // For native MATIC, use transferMatic
826
+ if (upperToken === 'MATIC') {
827
+ return this.transferMatic(to, amount);
828
+ }
829
+
830
+ const tokenAddress = this.getTokenAddress(token);
831
+ const decimals = this.getTokenDecimals(token);
832
+ const amountWei = ethers.utils.parseUnits(amount, decimals);
833
+
834
+ const contract = new Contract(tokenAddress, ERC20_ABI, this.signer);
835
+
836
+ // Check balance
837
+ const balance = await contract.balanceOf(this.signer.address);
838
+ if (balance.lt(amountWei)) {
839
+ throw new Error(`Insufficient ${upperToken} balance: have ${ethers.utils.formatUnits(balance, decimals)}, need ${amount}`);
840
+ }
841
+
842
+ const gasOptions = await this.getGasOptions();
843
+
844
+ const tx = await contract.transfer(to, amountWei, {
845
+ ...gasOptions,
846
+ gasLimit: 100000, // ERC20 transfer gas limit (USDC.e needs ~71k)
847
+ });
848
+ const receipt = await tx.wait();
849
+
850
+ return {
851
+ success: true,
852
+ transactionHash: receipt.transactionHash,
853
+ token: upperToken,
854
+ to,
855
+ amount,
856
+ gasUsed: receipt.gasUsed.toString(),
857
+ };
858
+ }
859
+
860
+ /**
861
+ * Transfer native USDC to another address
862
+ *
863
+ * ⚠️ WARNING: This transfers NATIVE USDC (0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359)
864
+ *
865
+ * For Polymarket CTF operations, you need USDC.e instead.
866
+ * Use transferUsdcE() for Polymarket CTF compatibility.
867
+ *
868
+ * @see transferUsdcE - For Polymarket CTF operations
869
+ */
870
+ async transferUsdc(to: string, amount: string): Promise<TransferResult> {
871
+ return this.transfer('USDC', to, amount);
872
+ }
873
+
874
+ /**
875
+ * Transfer USDC.e (bridged USDC) to another address
876
+ *
877
+ * ✅ This is the correct method for Polymarket CTF operations.
878
+ *
879
+ * Polymarket's Conditional Token Framework (CTF) only accepts
880
+ * USDC.e (0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174).
881
+ *
882
+ * If you're funding a wallet for CTF trading, use this method.
883
+ *
884
+ * @example
885
+ * ```typescript
886
+ * // Fund a session wallet for Polymarket trading
887
+ * await swapService.transferUsdcE(sessionWallet, '100');
888
+ *
889
+ * // The session wallet can now perform CTF operations
890
+ * await ctf.split(conditionId, '100');
891
+ * ```
892
+ */
893
+ async transferUsdcE(to: string, amount: string): Promise<TransferResult> {
894
+ return this.transfer('USDC_E', to, amount);
895
+ }
896
+ }