@elizaos/plugin-wallet 2.0.0-beta.1

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 (200) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +64 -0
  3. package/auto-enable.ts +76 -0
  4. package/dist/LpManagementService-BWrQ5-cO.mjs +353 -0
  5. package/dist/MockLpService-D_Apn4Fd.mjs +99 -0
  6. package/dist/aerodrome-CfnESC32.mjs +890 -0
  7. package/dist/chunk-hT5z_Zn9.mjs +35 -0
  8. package/dist/index.d.mts +34727 -0
  9. package/dist/index.mjs +21590 -0
  10. package/dist/lib/server-wallet-trade.d.mts +34 -0
  11. package/dist/lib/server-wallet-trade.mjs +306 -0
  12. package/dist/meteora-BPX39hZo.mjs +22640 -0
  13. package/dist/orca-Bybp1HXO.mjs +249 -0
  14. package/dist/pancakeswp-CkEXlXti.mjs +604 -0
  15. package/dist/plugin-ZO_MTyd0.mjs +529 -0
  16. package/dist/raydium-rfaM9yEf.mjs +539 -0
  17. package/dist/sdk/index.d.mts +32492 -0
  18. package/dist/sdk/index.mjs +6415 -0
  19. package/dist/types-D5252NZk.mjs +487 -0
  20. package/dist/uniswap-CReXgXVN.mjs +573 -0
  21. package/dist/wallet-action.d.mts +6 -0
  22. package/dist/wallet-action.mjs +820 -0
  23. package/package.json +152 -0
  24. package/src/actions/failure-codes.ts +79 -0
  25. package/src/actions/index.ts +1 -0
  26. package/src/analytics/birdeye/actions/wallet-search-address.ts +9 -0
  27. package/src/analytics/birdeye/birdeye-task.ts +175 -0
  28. package/src/analytics/birdeye/birdeye.ts +813 -0
  29. package/src/analytics/birdeye/constants.ts +74 -0
  30. package/src/analytics/birdeye/providers/agent-portfolio-provider.ts +18 -0
  31. package/src/analytics/birdeye/providers/market.ts +227 -0
  32. package/src/analytics/birdeye/providers/portfolio-factory.test.ts +138 -0
  33. package/src/analytics/birdeye/providers/portfolio-factory.ts +252 -0
  34. package/src/analytics/birdeye/providers/trending.ts +365 -0
  35. package/src/analytics/birdeye/providers/wallet.ts +14 -0
  36. package/src/analytics/birdeye/search-category.test.ts +207 -0
  37. package/src/analytics/birdeye/search-category.ts +506 -0
  38. package/src/analytics/birdeye/service.ts +992 -0
  39. package/src/analytics/birdeye/tasks/birdeye.ts +232 -0
  40. package/src/analytics/birdeye/types/api/common.ts +305 -0
  41. package/src/analytics/birdeye/types/api/defi.ts +220 -0
  42. package/src/analytics/birdeye/types/api/pair.ts +200 -0
  43. package/src/analytics/birdeye/types/api/search.ts +86 -0
  44. package/src/analytics/birdeye/types/api/token.ts +635 -0
  45. package/src/analytics/birdeye/types/api/trader.ts +76 -0
  46. package/src/analytics/birdeye/types/api/wallet.ts +181 -0
  47. package/src/analytics/birdeye/types/shared.ts +106 -0
  48. package/src/analytics/birdeye/utils.ts +700 -0
  49. package/src/analytics/dexscreener/errors.ts +28 -0
  50. package/src/analytics/dexscreener/index.ts +3 -0
  51. package/src/analytics/dexscreener/search-category.test.ts +49 -0
  52. package/src/analytics/dexscreener/search-category.ts +42 -0
  53. package/src/analytics/dexscreener/service.ts +595 -0
  54. package/src/analytics/dexscreener/types.ts +128 -0
  55. package/src/analytics/lpinfo/index.d.ts +7 -0
  56. package/src/analytics/lpinfo/index.ts +52 -0
  57. package/src/analytics/lpinfo/kamino/README.md +102 -0
  58. package/src/analytics/lpinfo/kamino/index.ts +24 -0
  59. package/src/analytics/lpinfo/kamino/providers/kaminoLiquidityProvider.ts +422 -0
  60. package/src/analytics/lpinfo/kamino/providers/kaminoPoolProvider.ts +365 -0
  61. package/src/analytics/lpinfo/kamino/providers/kaminoProvider.ts +496 -0
  62. package/src/analytics/lpinfo/kamino/services/kaminoLiquidityService.ts +1123 -0
  63. package/src/analytics/lpinfo/kamino/services/kaminoService.ts +758 -0
  64. package/src/analytics/lpinfo/steer/README.md +169 -0
  65. package/src/analytics/lpinfo/steer/index.ts +23 -0
  66. package/src/analytics/lpinfo/steer/providers/steerLiquidityProvider.ts +544 -0
  67. package/src/analytics/lpinfo/steer/services/steerLiquidityService.ts +1690 -0
  68. package/src/analytics/lpinfo/steer/steer-display-types.ts +99 -0
  69. package/src/analytics/news/index.ts +52 -0
  70. package/src/analytics/news/interfaces/types.ts +222 -0
  71. package/src/analytics/news/providers/defiNewsProvider.ts +734 -0
  72. package/src/analytics/news/services/newsDataService.ts +332 -0
  73. package/src/analytics/news/utils/formatters.ts +151 -0
  74. package/src/analytics/token-info/action.ts +240 -0
  75. package/src/analytics/token-info/index.ts +3 -0
  76. package/src/analytics/token-info/params.ts +215 -0
  77. package/src/analytics/token-info/providers.ts +681 -0
  78. package/src/analytics/token-info/service.ts +168 -0
  79. package/src/analytics/token-info/types.ts +74 -0
  80. package/src/audit/audit-log.ts +45 -0
  81. package/src/browser-shim/build-shim.ts +123 -0
  82. package/src/browser-shim/index.ts +5 -0
  83. package/src/browser-shim/shim.template.js +563 -0
  84. package/src/chains/evm/.github/workflows/npm-deploy.yml +112 -0
  85. package/src/chains/evm/LICENSE +21 -0
  86. package/src/chains/evm/README.md +106 -0
  87. package/src/chains/evm/actions/helpers.ts +147 -0
  88. package/src/chains/evm/actions/swap.ts +839 -0
  89. package/src/chains/evm/actions/transfer.ts +254 -0
  90. package/src/chains/evm/biome.json +61 -0
  91. package/src/chains/evm/bridge-router.ts +660 -0
  92. package/src/chains/evm/build.ts +89 -0
  93. package/src/chains/evm/chain-handler.ts +416 -0
  94. package/src/chains/evm/constants.ts +23 -0
  95. package/src/chains/evm/contracts/artifacts/OZGovernor.json +1707 -0
  96. package/src/chains/evm/contracts/artifacts/TimelockController.json +1007 -0
  97. package/src/chains/evm/contracts/artifacts/VoteToken.json +895 -0
  98. package/src/chains/evm/dex/aerodrome/index.ts +34 -0
  99. package/src/chains/evm/dex/aerodrome/services/AerodromeLpService.ts +558 -0
  100. package/src/chains/evm/dex/aerodrome/types.ts +318 -0
  101. package/src/chains/evm/dex/pancakeswp/index.ts +35 -0
  102. package/src/chains/evm/dex/pancakeswp/services/PancakeSwapV3LpService.ts +743 -0
  103. package/src/chains/evm/dex/pancakeswp/types.ts +65 -0
  104. package/src/chains/evm/dex/uniswap/index.ts +35 -0
  105. package/src/chains/evm/dex/uniswap/services/UniswapV3LpService.ts +759 -0
  106. package/src/chains/evm/dex/uniswap/types.ts +390 -0
  107. package/src/chains/evm/generated/specs/spec-helpers.ts +73 -0
  108. package/src/chains/evm/generated/specs/specs.ts +151 -0
  109. package/src/chains/evm/gov-router.ts +250 -0
  110. package/src/chains/evm/index.browser.ts +16 -0
  111. package/src/chains/evm/index.ts +31 -0
  112. package/src/chains/evm/prompts.ts +193 -0
  113. package/src/chains/evm/providers/get-balance.ts +123 -0
  114. package/src/chains/evm/providers/wallet.ts +715 -0
  115. package/src/chains/evm/routes/sign.ts +333 -0
  116. package/src/chains/evm/rpc-providers.ts +410 -0
  117. package/src/chains/evm/service.ts +140 -0
  118. package/src/chains/evm/templates/index.ts +10 -0
  119. package/src/chains/evm/types/index.ts +432 -0
  120. package/src/chains/evm/vitest.config.ts +18 -0
  121. package/src/chains/registry.ts +668 -0
  122. package/src/chains/solana/README.md +367 -0
  123. package/src/chains/wallet-action.ts +533 -0
  124. package/src/chains/wallet-router.test.ts +296 -0
  125. package/src/contracts.ts +65 -0
  126. package/src/core-augmentation.ts +10 -0
  127. package/src/index.ts +71 -0
  128. package/src/lib/server-wallet-trade.ts +192 -0
  129. package/src/lib/wallet-export-guard.ts +330 -0
  130. package/src/lp/actions/liquidity.ts +827 -0
  131. package/src/lp/e2e/real-token-tests.ts +428 -0
  132. package/src/lp/e2e/scenarios.ts +470 -0
  133. package/src/lp/e2e/test-utils.ts +145 -0
  134. package/src/lp/lp-manager-entry.ts +303 -0
  135. package/src/lp/services/ConcentratedLiquidityService.ts +120 -0
  136. package/src/lp/services/DexInteractionService.ts +226 -0
  137. package/src/lp/services/LpManagementService.test.ts +148 -0
  138. package/src/lp/services/LpManagementService.ts +632 -0
  139. package/src/lp/services/UserLpProfileService.ts +163 -0
  140. package/src/lp/services/VaultService.ts +153 -0
  141. package/src/lp/services/YieldOptimizationService.ts +344 -0
  142. package/src/lp/services/__tests__/MockLpService.ts +146 -0
  143. package/src/lp/tasks/LpAutoRebalanceTask.ts +117 -0
  144. package/src/lp/tasks/__tests__/LpAutoRebalanceTask.test.ts +370 -0
  145. package/src/lp/types.ts +582 -0
  146. package/src/lp/utils/solanaClient.ts +143 -0
  147. package/src/plugin.ts +125 -0
  148. package/src/policy/policy.ts +19 -0
  149. package/src/providers/canonical-provider.ts +27 -0
  150. package/src/providers/unified-wallet-provider.ts +79 -0
  151. package/src/register-routes.ts +11 -0
  152. package/src/routes/plugin.ts +47 -0
  153. package/src/routes/wallet-market-overview-route.ts +869 -0
  154. package/src/sdk/abi.ts +258 -0
  155. package/src/sdk/bridge/abis.ts +126 -0
  156. package/src/sdk/bridge/client.ts +518 -0
  157. package/src/sdk/bridge/index.ts +56 -0
  158. package/src/sdk/bridge/solana.ts +604 -0
  159. package/src/sdk/bridge/types.ts +202 -0
  160. package/src/sdk/convenience.ts +347 -0
  161. package/src/sdk/escrow/MutualStakeEscrow.ts +480 -0
  162. package/src/sdk/escrow/types.ts +64 -0
  163. package/src/sdk/escrow/verifiers.ts +73 -0
  164. package/src/sdk/identity/erc8004.ts +692 -0
  165. package/src/sdk/identity/reputation.ts +449 -0
  166. package/src/sdk/identity/uaid.ts +497 -0
  167. package/src/sdk/identity/validation.ts +372 -0
  168. package/src/sdk/index.ts +763 -0
  169. package/src/sdk/policy/SpendingPolicy.ts +260 -0
  170. package/src/sdk/policy/UptoBillingPolicy.ts +320 -0
  171. package/src/sdk/router/PaymentRouter.ts +215 -0
  172. package/src/sdk/router/index.ts +8 -0
  173. package/src/sdk/swap/SwapModule.ts +310 -0
  174. package/src/sdk/swap/abi.ts +117 -0
  175. package/src/sdk/swap/index.ts +34 -0
  176. package/src/sdk/swap/types.ts +135 -0
  177. package/src/sdk/tokens/decimals.ts +140 -0
  178. package/src/sdk/tokens/registry.ts +911 -0
  179. package/src/sdk/tokens/solana.ts +419 -0
  180. package/src/sdk/tokens/transfers.ts +327 -0
  181. package/src/sdk/types.ts +158 -0
  182. package/src/sdk/wallet-core.ts +115 -0
  183. package/src/sdk/x402/budget.ts +168 -0
  184. package/src/sdk/x402/chains/abstract/index.ts +280 -0
  185. package/src/sdk/x402/client.ts +320 -0
  186. package/src/sdk/x402/index.ts +46 -0
  187. package/src/sdk/x402/middleware.ts +92 -0
  188. package/src/sdk/x402/multi-asset.ts +144 -0
  189. package/src/sdk/x402/types.ts +156 -0
  190. package/src/services/wallet-backend-service.ts +328 -0
  191. package/src/types/wallet-router.ts +227 -0
  192. package/src/utils/intent-trajectory.ts +106 -0
  193. package/src/wallet/backend.ts +62 -0
  194. package/src/wallet/errors.ts +49 -0
  195. package/src/wallet/index.ts +27 -0
  196. package/src/wallet/local-eoa-backend.ts +201 -0
  197. package/src/wallet/pending.ts +60 -0
  198. package/src/wallet/select-backend.ts +47 -0
  199. package/src/wallet/steward-backend.ts +161 -0
  200. package/src/wallet-action.ts +1 -0
@@ -0,0 +1,759 @@
1
+ // @ts-nocheck — legacy code from absorbed plugins (lp-manager, lpinfo, dexscreener, defi-news, birdeye); strict types pending cleanup
2
+ import { type IAgentRuntime, logger, Service } from "@elizaos/core";
3
+ import {
4
+ type Address,
5
+ type Chain,
6
+ createPublicClient,
7
+ createWalletClient,
8
+ http,
9
+ maxUint128,
10
+ type PublicClient,
11
+ type WalletClient,
12
+ } from "viem";
13
+ import { privateKeyToAccount } from "viem/accounts";
14
+ import * as viemChains from "viem/chains";
15
+ import type {
16
+ EvmAddLiquidityParams,
17
+ EvmDex,
18
+ EvmPoolInfo,
19
+ EvmPositionDetails,
20
+ EvmRemoveLiquidityParams,
21
+ EvmTransactionResult,
22
+ IEvmLpService,
23
+ } from "../../../../../lp/types.ts";
24
+ import {
25
+ ERC20_ABI,
26
+ UNISWAP_V3_ADDRESSES,
27
+ UNISWAP_V3_FACTORY_ABI,
28
+ UNISWAP_V3_FEE_TIERS,
29
+ UNISWAP_V3_POOL_ABI,
30
+ UNISWAP_V3_POSITION_MANAGER_ABI,
31
+ type UniswapV3FeeTier,
32
+ type UniswapV3Position,
33
+ } from "../types.ts";
34
+
35
+ const SUPPORTED_CHAIN_IDS = [1, 8453, 42161, 137, 10]; // Ethereum, Base, Arbitrum, Polygon, Optimism
36
+
37
+ function getViemChain(chainId: number): Chain {
38
+ const chainMap: Record<number, Chain> = {
39
+ 1: viemChains.mainnet,
40
+ 8453: viemChains.base,
41
+ 42161: viemChains.arbitrum,
42
+ 137: viemChains.polygon,
43
+ 10: viemChains.optimism,
44
+ };
45
+ const chain = chainMap[chainId];
46
+ if (!chain) {
47
+ throw new Error(`Unsupported chain ID: ${chainId}`);
48
+ }
49
+ return chain;
50
+ }
51
+
52
+ export class UniswapV3LpService extends Service implements IEvmLpService {
53
+ public static readonly serviceType = "uniswap-v3-lp";
54
+ public readonly capabilityDescription =
55
+ "Provides Uniswap V3 liquidity pool management for EVM chains.";
56
+
57
+ private publicClients: Map<number, PublicClient> = new Map();
58
+ private walletClients: Map<number, WalletClient> = new Map();
59
+ private rpcUrls: Map<number, string> = new Map();
60
+
61
+ constructor(runtime?: IAgentRuntime) {
62
+ super(runtime);
63
+ if (runtime) {
64
+ this.initializeRpcUrls();
65
+ }
66
+ }
67
+
68
+ private initializeRpcUrls(): void {
69
+ // Try to get RPC URLs from settings
70
+ const rpcSettings: Record<number, string[]> = {
71
+ 1: ["ETHEREUM_RPC_URL", "ETH_RPC_URL", "EVM_PROVIDER_MAINNET"],
72
+ 8453: ["BASE_RPC_URL", "EVM_PROVIDER_BASE"],
73
+ 42161: ["ARBITRUM_RPC_URL", "EVM_PROVIDER_ARBITRUM"],
74
+ 137: ["POLYGON_RPC_URL", "EVM_PROVIDER_POLYGON"],
75
+ 10: ["OPTIMISM_RPC_URL", "EVM_PROVIDER_OPTIMISM"],
76
+ };
77
+
78
+ for (const [chainId, envKeys] of Object.entries(rpcSettings)) {
79
+ for (const key of envKeys) {
80
+ const rpcUrl = this.runtime.getSetting(key);
81
+ if (rpcUrl && typeof rpcUrl === "string") {
82
+ this.rpcUrls.set(Number(chainId), rpcUrl);
83
+ break;
84
+ }
85
+ }
86
+ }
87
+ }
88
+
89
+ private getPublicClient(chainId: number): PublicClient {
90
+ let client = this.publicClients.get(chainId);
91
+ if (client) return client;
92
+
93
+ const rpcUrl = this.rpcUrls.get(chainId);
94
+ const chain = getViemChain(chainId);
95
+
96
+ client = createPublicClient({
97
+ chain,
98
+ transport: rpcUrl ? http(rpcUrl) : http(),
99
+ });
100
+
101
+ this.publicClients.set(chainId, client);
102
+ return client;
103
+ }
104
+
105
+ private getWalletClient(chainId: number, privateKey: `0x${string}`): WalletClient {
106
+ const cacheKey = chainId;
107
+ let client = this.walletClients.get(cacheKey);
108
+ if (client) return client;
109
+
110
+ const rpcUrl = this.rpcUrls.get(chainId);
111
+ const chain = getViemChain(chainId);
112
+ const account = privateKeyToAccount(privateKey);
113
+
114
+ client = createWalletClient({
115
+ chain,
116
+ transport: rpcUrl ? http(rpcUrl) : http(),
117
+ account,
118
+ });
119
+
120
+ this.walletClients.set(cacheKey, client);
121
+ return client;
122
+ }
123
+
124
+ static async start(runtime: IAgentRuntime): Promise<UniswapV3LpService> {
125
+ const service = new UniswapV3LpService(runtime);
126
+ logger.info("[UniswapV3LpService] started");
127
+ return service;
128
+ }
129
+
130
+ async stop(): Promise<void> {
131
+ this.publicClients.clear();
132
+ this.walletClients.clear();
133
+ logger.info("[UniswapV3LpService] stopped");
134
+ }
135
+
136
+ getDexName(): EvmDex {
137
+ return "uniswap";
138
+ }
139
+
140
+ getSupportedChainIds(): number[] {
141
+ return SUPPORTED_CHAIN_IDS.filter((chainId) => UNISWAP_V3_ADDRESSES[chainId] !== undefined);
142
+ }
143
+
144
+ supportsChain(chainId: number): boolean {
145
+ return this.getSupportedChainIds().includes(chainId);
146
+ }
147
+
148
+ async getPools(
149
+ chainId: number,
150
+ tokenA?: Address,
151
+ tokenB?: Address,
152
+ feeTier?: number
153
+ ): Promise<EvmPoolInfo[]> {
154
+ if (!this.supportsChain(chainId)) {
155
+ logger.warn(`[UniswapV3LpService] Chain ${chainId} not supported`);
156
+ return [];
157
+ }
158
+
159
+ const addresses = UNISWAP_V3_ADDRESSES[chainId];
160
+ if (!addresses) return [];
161
+
162
+ const client = this.getPublicClient(chainId);
163
+ const pools: EvmPoolInfo[] = [];
164
+
165
+ // If specific tokens are provided, look up their pools
166
+ if (tokenA && tokenB) {
167
+ const feeTiers = feeTier
168
+ ? [feeTier as UniswapV3FeeTier]
169
+ : Object.values(UNISWAP_V3_FEE_TIERS);
170
+
171
+ for (const fee of feeTiers) {
172
+ try {
173
+ const poolAddress = await client.readContract({
174
+ address: addresses.factory,
175
+ abi: UNISWAP_V3_FACTORY_ABI,
176
+ functionName: "getPool",
177
+ args: [tokenA, tokenB, fee],
178
+ });
179
+
180
+ if (poolAddress && poolAddress !== "0x0000000000000000000000000000000000000000") {
181
+ const poolInfo = await this.getPoolInfo(chainId, poolAddress as Address);
182
+ if (poolInfo) {
183
+ pools.push(poolInfo);
184
+ }
185
+ }
186
+ } catch (_error: unknown) {
187
+ logger.debug(`[UniswapV3LpService] No pool found for ${tokenA}/${tokenB} at fee ${fee}`);
188
+ }
189
+ }
190
+ }
191
+
192
+ return pools;
193
+ }
194
+
195
+ private async getPoolInfo(chainId: number, poolAddress: Address): Promise<EvmPoolInfo | null> {
196
+ const client = this.getPublicClient(chainId);
197
+ const chain = getViemChain(chainId);
198
+
199
+ try {
200
+ const [token0, token1, fee, tickSpacing, _liquidity, slot0] = await Promise.all([
201
+ client.readContract({
202
+ address: poolAddress,
203
+ abi: UNISWAP_V3_POOL_ABI,
204
+ functionName: "token0",
205
+ }),
206
+ client.readContract({
207
+ address: poolAddress,
208
+ abi: UNISWAP_V3_POOL_ABI,
209
+ functionName: "token1",
210
+ }),
211
+ client.readContract({
212
+ address: poolAddress,
213
+ abi: UNISWAP_V3_POOL_ABI,
214
+ functionName: "fee",
215
+ }),
216
+ client.readContract({
217
+ address: poolAddress,
218
+ abi: UNISWAP_V3_POOL_ABI,
219
+ functionName: "tickSpacing",
220
+ }),
221
+ client.readContract({
222
+ address: poolAddress,
223
+ abi: UNISWAP_V3_POOL_ABI,
224
+ functionName: "liquidity",
225
+ }),
226
+ client.readContract({
227
+ address: poolAddress,
228
+ abi: UNISWAP_V3_POOL_ABI,
229
+ functionName: "slot0",
230
+ }),
231
+ ]);
232
+
233
+ // Get token info
234
+ const [symbol0, decimals0, symbol1, decimals1] = await Promise.all([
235
+ client
236
+ .readContract({
237
+ address: token0 as Address,
238
+ abi: ERC20_ABI,
239
+ functionName: "symbol",
240
+ })
241
+ .catch(() => "UNKNOWN"),
242
+ client
243
+ .readContract({
244
+ address: token0 as Address,
245
+ abi: ERC20_ABI,
246
+ functionName: "decimals",
247
+ })
248
+ .catch(() => 18),
249
+ client
250
+ .readContract({
251
+ address: token1 as Address,
252
+ abi: ERC20_ABI,
253
+ functionName: "symbol",
254
+ })
255
+ .catch(() => "UNKNOWN"),
256
+ client
257
+ .readContract({
258
+ address: token1 as Address,
259
+ abi: ERC20_ABI,
260
+ functionName: "decimals",
261
+ })
262
+ .catch(() => 18),
263
+ ]);
264
+
265
+ const poolInfo: EvmPoolInfo = {
266
+ id: poolAddress,
267
+ dex: "uniswap",
268
+ chainId,
269
+ chainName: chain.name,
270
+ poolAddress,
271
+ tokenA: {
272
+ address: token0 as Address,
273
+ symbol: symbol0 as string,
274
+ decimals: Number(decimals0),
275
+ },
276
+ tokenB: {
277
+ address: token1 as Address,
278
+ symbol: symbol1 as string,
279
+ decimals: Number(decimals1),
280
+ },
281
+ feeTier: Number(fee),
282
+ tickSpacing: Number(tickSpacing),
283
+ currentTick: Number(slot0[1]),
284
+ sqrtPriceX96: slot0[0] as bigint,
285
+ fee: Number(fee) / 1_000_000, // Convert to percentage
286
+ displayName: `${symbol0}/${symbol1} (${Number(fee) / 10000}%)`,
287
+ };
288
+
289
+ return poolInfo;
290
+ } catch (error: unknown) {
291
+ logger.error(
292
+ `[UniswapV3LpService] Error fetching pool info for ${poolAddress}:`,
293
+ error instanceof Error ? error.message : String(error)
294
+ );
295
+ return null;
296
+ }
297
+ }
298
+
299
+ async addLiquidity(params: EvmAddLiquidityParams): Promise<EvmTransactionResult> {
300
+ if (!this.supportsChain(params.chainId)) {
301
+ return { success: false, error: `Chain ${params.chainId} not supported` };
302
+ }
303
+
304
+ const addresses = UNISWAP_V3_ADDRESSES[params.chainId];
305
+ if (!addresses) {
306
+ return { success: false, error: "Uniswap V3 not deployed on this chain" };
307
+ }
308
+
309
+ try {
310
+ const publicClient = this.getPublicClient(params.chainId);
311
+ const walletClient = this.getWalletClient(params.chainId, params.wallet.privateKey);
312
+
313
+ // Get pool info
314
+ const poolInfo = await this.getPoolInfo(params.chainId, params.poolAddress);
315
+ if (!poolInfo) {
316
+ return { success: false, error: "Pool not found" };
317
+ }
318
+
319
+ // Calculate min amounts with slippage
320
+ const slippageMultiplier = BigInt(10000 - params.slippageBps);
321
+ const amount0Min = (params.tokenAAmount * slippageMultiplier) / 10000n;
322
+ const amount1Min = ((params.tokenBAmount ?? 0n) * slippageMultiplier) / 10000n;
323
+
324
+ // Approve tokens if needed
325
+ await this.approveToken(
326
+ params.chainId,
327
+ params.wallet.privateKey,
328
+ poolInfo.tokenA.address,
329
+ addresses.nonfungiblePositionManager,
330
+ params.tokenAAmount
331
+ );
332
+
333
+ if (params.tokenBAmount && params.tokenBAmount > 0n) {
334
+ await this.approveToken(
335
+ params.chainId,
336
+ params.wallet.privateKey,
337
+ poolInfo.tokenB.address,
338
+ addresses.nonfungiblePositionManager,
339
+ params.tokenBAmount
340
+ );
341
+ }
342
+
343
+ const deadline = params.deadline ?? BigInt(Math.floor(Date.now() / 1000) + 1800); // 30 min default
344
+
345
+ // Determine tick range
346
+ const tickLower = params.tickLower ?? poolInfo.currentTick! - 1000;
347
+ const tickUpper = params.tickUpper ?? poolInfo.currentTick! + 1000;
348
+
349
+ // Align ticks to tick spacing
350
+ const tickSpacing = poolInfo.tickSpacing ?? 60;
351
+ const alignedTickLower = Math.floor(tickLower / tickSpacing) * tickSpacing;
352
+ const alignedTickUpper = Math.ceil(tickUpper / tickSpacing) * tickSpacing;
353
+
354
+ const mintParams = {
355
+ token0: poolInfo.tokenA.address,
356
+ token1: poolInfo.tokenB.address,
357
+ fee: poolInfo.feeTier!,
358
+ tickLower: alignedTickLower,
359
+ tickUpper: alignedTickUpper,
360
+ amount0Desired: params.tokenAAmount,
361
+ amount1Desired: params.tokenBAmount ?? 0n,
362
+ amount0Min,
363
+ amount1Min,
364
+ recipient: params.wallet.address,
365
+ deadline,
366
+ };
367
+
368
+ const { request } = await publicClient.simulateContract({
369
+ address: addresses.nonfungiblePositionManager,
370
+ abi: UNISWAP_V3_POSITION_MANAGER_ABI,
371
+ functionName: "mint",
372
+ args: [mintParams],
373
+ account: walletClient.account,
374
+ });
375
+
376
+ const hash = await walletClient.writeContract(request);
377
+
378
+ const receipt = await publicClient.waitForTransactionReceipt({ hash });
379
+
380
+ return {
381
+ success: receipt.status === "success",
382
+ transactionId: hash,
383
+ hash,
384
+ chainId: params.chainId,
385
+ blockNumber: receipt.blockNumber,
386
+ gasUsed: receipt.gasUsed,
387
+ data: {
388
+ poolAddress: params.poolAddress,
389
+ tickLower: alignedTickLower,
390
+ tickUpper: alignedTickUpper,
391
+ },
392
+ };
393
+ } catch (error: unknown) {
394
+ const errorMsg = error instanceof Error ? error.message : String(error);
395
+ logger.error("[UniswapV3LpService] Error adding liquidity:", errorMsg);
396
+ return {
397
+ success: false,
398
+ error: errorMsg || "Unknown error adding liquidity",
399
+ };
400
+ }
401
+ }
402
+
403
+ async removeLiquidity(params: EvmRemoveLiquidityParams): Promise<EvmTransactionResult> {
404
+ if (!this.supportsChain(params.chainId)) {
405
+ return { success: false, error: `Chain ${params.chainId} not supported` };
406
+ }
407
+
408
+ const addresses = UNISWAP_V3_ADDRESSES[params.chainId];
409
+ if (!addresses) {
410
+ return { success: false, error: "Uniswap V3 not deployed on this chain" };
411
+ }
412
+
413
+ if (!params.tokenId) {
414
+ return {
415
+ success: false,
416
+ error: "Position token ID required for Uniswap V3",
417
+ };
418
+ }
419
+
420
+ try {
421
+ const publicClient = this.getPublicClient(params.chainId);
422
+ const walletClient = this.getWalletClient(params.chainId, params.wallet.privateKey);
423
+
424
+ // Get position info
425
+ const position = await this.getPositionFromContract(params.chainId, params.tokenId);
426
+ if (!position) {
427
+ return { success: false, error: "Position not found" };
428
+ }
429
+
430
+ // Calculate liquidity to remove
431
+ let liquidityToRemove = position.liquidity;
432
+ if (params.percentageToRemove && params.percentageToRemove < 100) {
433
+ liquidityToRemove = (position.liquidity * BigInt(params.percentageToRemove)) / 100n;
434
+ }
435
+
436
+ const deadline = params.deadline ?? BigInt(Math.floor(Date.now() / 1000) + 1800);
437
+ const _slippageMultiplier = BigInt(10000 - params.slippageBps);
438
+
439
+ // First, decrease liquidity
440
+ const decreaseParams = {
441
+ tokenId: params.tokenId,
442
+ liquidity: liquidityToRemove,
443
+ amount0Min: 0n, // Will be calculated based on actual amounts
444
+ amount1Min: 0n,
445
+ deadline,
446
+ };
447
+
448
+ const { request: decreaseRequest } = await publicClient.simulateContract({
449
+ address: addresses.nonfungiblePositionManager,
450
+ abi: UNISWAP_V3_POSITION_MANAGER_ABI,
451
+ functionName: "decreaseLiquidity",
452
+ args: [decreaseParams],
453
+ account: walletClient.account,
454
+ });
455
+
456
+ const decreaseHash = await walletClient.writeContract(decreaseRequest);
457
+ await publicClient.waitForTransactionReceipt({ hash: decreaseHash });
458
+
459
+ // Then collect the tokens
460
+ const collectParams = {
461
+ tokenId: params.tokenId,
462
+ recipient: params.wallet.address,
463
+ amount0Max: maxUint128,
464
+ amount1Max: maxUint128,
465
+ };
466
+
467
+ const { request: collectRequest } = await publicClient.simulateContract({
468
+ address: addresses.nonfungiblePositionManager,
469
+ abi: UNISWAP_V3_POSITION_MANAGER_ABI,
470
+ functionName: "collect",
471
+ args: [collectParams],
472
+ account: walletClient.account,
473
+ });
474
+
475
+ const collectHash = await walletClient.writeContract(collectRequest);
476
+ const receipt = await publicClient.waitForTransactionReceipt({
477
+ hash: collectHash,
478
+ });
479
+
480
+ // If removing all liquidity, burn the NFT
481
+ if (params.percentageToRemove === 100 || !params.percentageToRemove) {
482
+ try {
483
+ const { request: burnRequest } = await publicClient.simulateContract({
484
+ address: addresses.nonfungiblePositionManager,
485
+ abi: UNISWAP_V3_POSITION_MANAGER_ABI,
486
+ functionName: "burn",
487
+ args: [params.tokenId],
488
+ account: walletClient.account,
489
+ });
490
+ await walletClient.writeContract(burnRequest);
491
+ } catch (burnError: unknown) {
492
+ logger.debug(
493
+ "[UniswapV3LpService] Could not burn position NFT:",
494
+ burnError instanceof Error ? burnError.message : String(burnError)
495
+ );
496
+ }
497
+ }
498
+
499
+ return {
500
+ success: receipt.status === "success",
501
+ transactionId: collectHash,
502
+ hash: collectHash,
503
+ chainId: params.chainId,
504
+ blockNumber: receipt.blockNumber,
505
+ gasUsed: receipt.gasUsed,
506
+ };
507
+ } catch (error: unknown) {
508
+ const errorMsg = error instanceof Error ? error.message : String(error);
509
+ logger.error("[UniswapV3LpService] Error removing liquidity:", errorMsg);
510
+ return {
511
+ success: false,
512
+ error: errorMsg || "Unknown error removing liquidity",
513
+ };
514
+ }
515
+ }
516
+
517
+ private async getPositionFromContract(
518
+ chainId: number,
519
+ tokenId: bigint
520
+ ): Promise<UniswapV3Position | null> {
521
+ const addresses = UNISWAP_V3_ADDRESSES[chainId];
522
+ if (!addresses) return null;
523
+
524
+ const client = this.getPublicClient(chainId);
525
+
526
+ try {
527
+ const result = await client.readContract({
528
+ address: addresses.nonfungiblePositionManager,
529
+ abi: UNISWAP_V3_POSITION_MANAGER_ABI,
530
+ functionName: "positions",
531
+ args: [tokenId],
532
+ });
533
+
534
+ return {
535
+ tokenId,
536
+ nonce: result[0],
537
+ operator: result[1] as Address,
538
+ token0: result[2] as Address,
539
+ token1: result[3] as Address,
540
+ fee: result[4] as UniswapV3FeeTier,
541
+ tickLower: result[5],
542
+ tickUpper: result[6],
543
+ liquidity: result[7],
544
+ feeGrowthInside0LastX128: result[8],
545
+ feeGrowthInside1LastX128: result[9],
546
+ tokensOwed0: result[10],
547
+ tokensOwed1: result[11],
548
+ };
549
+ } catch (error: unknown) {
550
+ logger.error(
551
+ `[UniswapV3LpService] Error fetching position ${tokenId}:`,
552
+ error instanceof Error ? error.message : String(error)
553
+ );
554
+ return null;
555
+ }
556
+ }
557
+
558
+ async getPositionDetails(
559
+ chainId: number,
560
+ owner: Address,
561
+ poolAddress: Address,
562
+ tokenId?: bigint
563
+ ): Promise<EvmPositionDetails | null> {
564
+ if (!this.supportsChain(chainId)) return null;
565
+
566
+ // If we have a token ID, get that specific position
567
+ if (tokenId) {
568
+ const position = await this.getPositionFromContract(chainId, tokenId);
569
+ if (!position) return null;
570
+
571
+ const client = this.getPublicClient(chainId);
572
+ const _chain = getViemChain(chainId);
573
+
574
+ // Get token info
575
+ const [symbol0, decimals0, symbol1, decimals1] = await Promise.all([
576
+ client
577
+ .readContract({
578
+ address: position.token0,
579
+ abi: ERC20_ABI,
580
+ functionName: "symbol",
581
+ })
582
+ .catch(() => "UNKNOWN"),
583
+ client
584
+ .readContract({
585
+ address: position.token0,
586
+ abi: ERC20_ABI,
587
+ functionName: "decimals",
588
+ })
589
+ .catch(() => 18),
590
+ client
591
+ .readContract({
592
+ address: position.token1,
593
+ abi: ERC20_ABI,
594
+ functionName: "symbol",
595
+ })
596
+ .catch(() => "UNKNOWN"),
597
+ client
598
+ .readContract({
599
+ address: position.token1,
600
+ abi: ERC20_ABI,
601
+ functionName: "decimals",
602
+ })
603
+ .catch(() => 18),
604
+ ]);
605
+
606
+ return {
607
+ poolId: poolAddress,
608
+ dex: "uniswap",
609
+ chainId,
610
+ owner,
611
+ tokenId: position.tokenId,
612
+ tickLower: position.tickLower,
613
+ tickUpper: position.tickUpper,
614
+ liquidity: position.liquidity,
615
+ lpTokenBalance: {
616
+ address: poolAddress,
617
+ balance: position.liquidity.toString(),
618
+ decimals: 0,
619
+ symbol: `UNI-V3-${symbol0}/${symbol1}`,
620
+ },
621
+ underlyingTokens: [
622
+ {
623
+ address: position.token0,
624
+ balance: position.tokensOwed0.toString(),
625
+ decimals: Number(decimals0),
626
+ symbol: symbol0 as string,
627
+ },
628
+ {
629
+ address: position.token1,
630
+ balance: position.tokensOwed1.toString(),
631
+ decimals: Number(decimals1),
632
+ symbol: symbol1 as string,
633
+ },
634
+ ],
635
+ };
636
+ }
637
+
638
+ // Otherwise, find positions for this owner in this pool
639
+ const positions = await this.getAllPositions(chainId, owner);
640
+ return positions.find((p) => p.poolId.toLowerCase() === poolAddress.toLowerCase()) ?? null;
641
+ }
642
+
643
+ async getAllPositions(chainId: number, owner: Address): Promise<EvmPositionDetails[]> {
644
+ if (!this.supportsChain(chainId)) return [];
645
+
646
+ const addresses = UNISWAP_V3_ADDRESSES[chainId];
647
+ if (!addresses) return [];
648
+
649
+ const client = this.getPublicClient(chainId);
650
+ const positions: EvmPositionDetails[] = [];
651
+
652
+ try {
653
+ const balance = await client.readContract({
654
+ address: addresses.nonfungiblePositionManager,
655
+ abi: UNISWAP_V3_POSITION_MANAGER_ABI,
656
+ functionName: "balanceOf",
657
+ args: [owner],
658
+ });
659
+
660
+ for (let i = 0; i < Number(balance); i++) {
661
+ const tokenId = await client.readContract({
662
+ address: addresses.nonfungiblePositionManager,
663
+ abi: UNISWAP_V3_POSITION_MANAGER_ABI,
664
+ functionName: "tokenOfOwnerByIndex",
665
+ args: [owner, BigInt(i)],
666
+ });
667
+
668
+ const position = await this.getPositionFromContract(chainId, tokenId as bigint);
669
+ if (position && position.liquidity > 0n) {
670
+ // Find the pool address
671
+ const poolAddress = await client.readContract({
672
+ address: addresses.factory,
673
+ abi: UNISWAP_V3_FACTORY_ABI,
674
+ functionName: "getPool",
675
+ args: [position.token0, position.token1, position.fee],
676
+ });
677
+
678
+ if (poolAddress) {
679
+ const details = await this.getPositionDetails(
680
+ chainId,
681
+ owner,
682
+ poolAddress as Address,
683
+ tokenId as bigint
684
+ );
685
+ if (details) {
686
+ positions.push(details);
687
+ }
688
+ }
689
+ }
690
+ }
691
+ } catch (error: unknown) {
692
+ logger.error(
693
+ "[UniswapV3LpService] Error fetching all positions:",
694
+ error instanceof Error ? error.message : String(error)
695
+ );
696
+ }
697
+
698
+ return positions;
699
+ }
700
+
701
+ async getMarketData(poolAddresses: Address[]): Promise<Record<string, Partial<EvmPoolInfo>>> {
702
+ const result: Record<string, Partial<EvmPoolInfo>> = {};
703
+
704
+ for (const address of poolAddresses) {
705
+ // Try each supported chain
706
+ for (const chainId of this.getSupportedChainIds()) {
707
+ try {
708
+ const poolInfo = await this.getPoolInfo(chainId, address);
709
+ if (poolInfo) {
710
+ result[address] = poolInfo;
711
+ break;
712
+ }
713
+ } catch {
714
+ // Pool not on this chain
715
+ }
716
+ }
717
+ }
718
+
719
+ return result;
720
+ }
721
+
722
+ private async approveToken(
723
+ chainId: number,
724
+ privateKey: `0x${string}`,
725
+ tokenAddress: Address,
726
+ spenderAddress: Address,
727
+ amount: bigint
728
+ ): Promise<void> {
729
+ const publicClient = this.getPublicClient(chainId);
730
+ const walletClient = this.getWalletClient(chainId, privateKey);
731
+
732
+ // Check current allowance
733
+ const allowance = await publicClient.readContract({
734
+ address: tokenAddress,
735
+ abi: ERC20_ABI,
736
+ functionName: "allowance",
737
+ args: [walletClient.account?.address, spenderAddress],
738
+ });
739
+
740
+ if ((allowance as bigint) >= amount) {
741
+ return;
742
+ }
743
+
744
+ logger.info(`[UniswapV3LpService] Approving ${tokenAddress} for ${spenderAddress}`);
745
+
746
+ const { request } = await publicClient.simulateContract({
747
+ address: tokenAddress,
748
+ abi: ERC20_ABI,
749
+ functionName: "approve",
750
+ args: [spenderAddress, amount],
751
+ account: walletClient.account,
752
+ });
753
+
754
+ const hash = await walletClient.writeContract(request);
755
+ await publicClient.waitForTransactionReceipt({ hash });
756
+
757
+ logger.info(`[UniswapV3LpService] Token approved: ${hash}`);
758
+ }
759
+ }