@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,1690 @@
1
+ // @ts-nocheck — legacy code from absorbed plugins (lp-manager, lpinfo, dexscreener, defi-news, birdeye); strict types pending cleanup
2
+ import type { IAgentRuntime, JsonValue } from "@elizaos/core";
3
+ import { logger, Service } from "@elizaos/core";
4
+ import type { StakingPool } from "@steerprotocol/sdk";
5
+ import {
6
+ AMMType,
7
+ StakingClient,
8
+ SteerClient,
9
+ VaultClient,
10
+ } from "@steerprotocol/sdk";
11
+ import { createPublicClient, createWalletClient, http } from "viem";
12
+ // Import Viem for proper chain and transport configuration
13
+ import { arbitrum, base, mainnet, optimism, polygon } from "viem/chains";
14
+
15
+ import type {
16
+ SteerStakingPoolDetailInput,
17
+ SteerVaultDetailInput,
18
+ } from "../steer-display-types.js";
19
+
20
+ type StakingClientCtor = ConstructorParameters<typeof StakingClient>;
21
+
22
+ type SteerEarnedRewardsResult = Awaited<ReturnType<StakingClient["earned"]>>;
23
+ type SteerStakingSupplyResult = Awaited<
24
+ ReturnType<StakingClient["totalSupply"]>
25
+ >;
26
+ type SteerStakingBalanceResult = Awaited<
27
+ ReturnType<StakingClient["balanceOf"]>
28
+ >;
29
+ type SteerPreviewDepositResult = Awaited<
30
+ ReturnType<VaultClient["previewSingleAssetDeposit"]>
31
+ >;
32
+ type SteerSingleDepositResult = Awaited<
33
+ ReturnType<VaultClient["singleAssetDeposit"]>
34
+ >;
35
+
36
+ interface SteerPoolNode {
37
+ poolAddress?: string;
38
+ id?: string;
39
+ totalValueLockedUSD?: string;
40
+ volumeUSD?: string;
41
+ feeTier?: string;
42
+ liquidity?: number;
43
+ }
44
+
45
+ /** Raw vault node shape returned by `@steerprotocol/sdk` before enrichment */
46
+ interface RawSteerVault {
47
+ vaultAddress?: string;
48
+ address?: string;
49
+ name?: string;
50
+ token0?: string | { address?: string };
51
+ token1?: string | { address?: string };
52
+ pool?: { poolAddress?: string; feeTier?: number };
53
+ poolAddress?: string;
54
+ fee?: number;
55
+ aprData?: Record<string, number>;
56
+ apy?: number;
57
+ apr?: number;
58
+ tvl?: number;
59
+ volume24h?: number;
60
+ }
61
+
62
+ // Supported chain IDs
63
+ const SUPPORTED_CHAIN_IDS = [1, 137, 42161, 10, 8453]; // mainnet, polygon, arbitrum, optimism, base
64
+
65
+ // GraphQL endpoint for Steer Protocol
66
+ const STEER_GRAPHQL_ENDPOINT =
67
+ "https://api.subgraph.ormilabs.com/api/public/803c8c8c-be12-4188-8523-b9853e23051d/subgraphs/steer-protocol-base/prod/gn";
68
+
69
+ // Interfaces for type safety
70
+ interface TokenLiquidityStats {
71
+ tokenIdentifier: string;
72
+ normalizedToken: string;
73
+ tokenName: string;
74
+ timestamp: string;
75
+ vaults: SteerVaultDetailInput[];
76
+ stakingPools: SteerStakingPoolDetailInput[];
77
+ totalTvl: number;
78
+ totalVolume: number;
79
+ apyRange: { min: number; max: number };
80
+ vaultCount: number;
81
+ stakingPoolCount: number;
82
+ }
83
+
84
+ interface ConnectionTestResult {
85
+ connectionTest: boolean;
86
+ supportedChains: number[];
87
+ vaultCount: number;
88
+ stakingPoolCount: number;
89
+ error?: string;
90
+ }
91
+
92
+ // GraphQL Vault Data Interface
93
+ interface GraphQLVaultData {
94
+ id: string;
95
+ name: string;
96
+ token0: string;
97
+ token1: string;
98
+ pool: string;
99
+ weeklyFeeAPR: string;
100
+ token0Symbol: string;
101
+ token0Decimals: string;
102
+ token1Symbol: string;
103
+ token1Decimals: string;
104
+ token0Balance: string;
105
+ token1Balance: string;
106
+ totalLPTokensIssued: string;
107
+ feeTier: string;
108
+ fees0: string;
109
+ fees1: string;
110
+ strategyToken: {
111
+ id: string;
112
+ name: string;
113
+ creator: {
114
+ id: string;
115
+ };
116
+ admin: string;
117
+ executionBundle: string;
118
+ };
119
+ beaconName: string;
120
+ payloadIpfs: string;
121
+ deployer: string;
122
+ }
123
+
124
+ interface GraphQLResponse {
125
+ data: {
126
+ vault: GraphQLVaultData;
127
+ };
128
+ }
129
+
130
+ /**
131
+ * Steer Finance Liquidity Protocol Service
132
+ * Handles interactions with Steer Finance protocol using the official SDK
133
+ */
134
+ export class SteerLiquidityService extends Service {
135
+ private isRunning = false;
136
+ private supportedChains: number[];
137
+ private vaultClients: Map<number, VaultClient> = new Map();
138
+ private stakingClients: Map<number, StakingClient> = new Map();
139
+ private cache: Map<string, { data: JsonValue; timestamp: number }> =
140
+ new Map();
141
+ private readonly CACHE_TTL = 5 * 60 * 1000; // 5 minutes cache TTL
142
+
143
+ static serviceType = "STEER_LIQUIDITY_SERVICE";
144
+ static serviceName = "SteerLiquidityService";
145
+ capabilityDescription =
146
+ "Provides detailed access to Steer Finance vaults and staking pools for specific tokens using the official SDK." as const;
147
+
148
+ constructor(runtime: IAgentRuntime) {
149
+ super(runtime);
150
+
151
+ // Initialize supported chains
152
+ this.supportedChains = SUPPORTED_CHAIN_IDS;
153
+
154
+ // Initialize Steer SDK client
155
+ try {
156
+ // Create a proper Viem client configuration for each chain
157
+ const viemClient = createPublicClient({
158
+ chain: mainnet,
159
+ transport: http(),
160
+ });
161
+
162
+ this.steerClient = new SteerClient({
163
+ client: viemClient,
164
+ });
165
+ logger.log("Steer SDK client initialized successfully");
166
+ } catch (error) {
167
+ logger.error("Failed to initialize Steer SDK client:", error);
168
+ throw new Error("Steer SDK initialization failed");
169
+ }
170
+
171
+ // Initialize vault and staking clients for each supported chain
172
+ this.initializeChainClients();
173
+
174
+ logger.log("SteerLiquidityService initialized with multi-chain support");
175
+ logger.log(`Supported chains: ${this.supportedChains.join(", ")}`);
176
+ logger.log(
177
+ "SteerLiquidityService ready to handle requests using official SDK",
178
+ );
179
+
180
+ // Verify runtime has required methods
181
+ if (!runtime.getService) {
182
+ logger.warn("Runtime missing getService method");
183
+ }
184
+ if (!runtime.getCache) {
185
+ logger.warn("Runtime missing getCache method");
186
+ }
187
+
188
+ // Log successful initialization
189
+ logger.log("SteerLiquidityService constructor completed successfully");
190
+ }
191
+
192
+ /**
193
+ * Get the appropriate Viem chain object for a given chain ID
194
+ */
195
+ private getViemChain(chainId: number) {
196
+ switch (chainId) {
197
+ case 1:
198
+ return mainnet;
199
+ case 137:
200
+ return polygon;
201
+ case 42161:
202
+ return arbitrum;
203
+ case 10:
204
+ return optimism;
205
+ case 8453:
206
+ return base;
207
+ default:
208
+ return mainnet;
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Initialize vault and staking clients for each supported chain
214
+ */
215
+ private initializeChainClients(): void {
216
+ try {
217
+ for (const chainId of this.supportedChains) {
218
+ // Get the appropriate Viem chain object
219
+ const viemChain = this.getViemChain(chainId);
220
+
221
+ // Create Viem clients for this chain
222
+ const publicClient = createPublicClient({
223
+ chain: viemChain,
224
+ transport: http(),
225
+ });
226
+
227
+ const walletClient = createWalletClient({
228
+ chain: viemChain,
229
+ transport: http(),
230
+ });
231
+
232
+ // Initialize vault client for this chain
233
+ const vaultClient = new VaultClient(
234
+ publicClient,
235
+ walletClient,
236
+ "production",
237
+ );
238
+ this.vaultClients.set(chainId, vaultClient);
239
+
240
+ // Initialize staking client for this chain
241
+ const stakingClient = new StakingClient(
242
+ walletClient as StakingClientCtor[0],
243
+ );
244
+ this.stakingClients.set(chainId, stakingClient);
245
+
246
+ logger.log(`Initialized clients for chain ${chainId}`);
247
+ }
248
+ logger.log(
249
+ `Successfully initialized clients for ${this.supportedChains.length} chains`,
250
+ );
251
+ } catch (error) {
252
+ logger.error("Error initializing chain clients:", error);
253
+ throw new Error("Failed to initialize chain clients");
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Get token liquidity information from Steer Finance across all supported chains or a specific chain
259
+ */
260
+ async getTokenLiquidityStats(
261
+ tokenIdentifier: string,
262
+ targetChainId?: number | null,
263
+ ): Promise<TokenLiquidityStats> {
264
+ try {
265
+ logger.log(`Getting Steer liquidity info for token: ${tokenIdentifier}`);
266
+ if (targetChainId) {
267
+ logger.log(
268
+ `Chain filtering enabled - targeting chain: ${targetChainId}`,
269
+ );
270
+ }
271
+
272
+ // Normalize token identifier
273
+ const normalizedToken = this.normalizeTokenIdentifier(tokenIdentifier);
274
+ const tokenName = this.getTokenName(normalizedToken);
275
+
276
+ const allVaults: SteerVaultDetailInput[] = [];
277
+ const allStakingPools: SteerStakingPoolDetailInput[] = [];
278
+
279
+ // Determine which chains to search
280
+ let chainsToSearch: number[];
281
+ if (targetChainId) {
282
+ // Validate that the target chain is supported
283
+ if (!this.supportedChains.includes(targetChainId)) {
284
+ throw new Error(
285
+ `Chain ${targetChainId} is not supported. Supported chains: ${this.supportedChains.join(", ")}`,
286
+ );
287
+ }
288
+ chainsToSearch = [targetChainId];
289
+ logger.log(
290
+ `Chain filtering enabled - targeting chain: ${targetChainId} (${this.getChainName(targetChainId)})`,
291
+ );
292
+ } else {
293
+ chainsToSearch = this.supportedChains;
294
+ logger.log(
295
+ `No chain filter specified - searching all supported chains: ${chainsToSearch.join(", ")}`,
296
+ );
297
+ }
298
+
299
+ // Check if this is a token address for targeted search
300
+ const isTokenAddress =
301
+ tokenIdentifier.startsWith("0x") && tokenIdentifier.length === 42;
302
+
303
+ if (isTokenAddress) {
304
+ logger.log(
305
+ `Token address detected, searching for specific vaults containing ${tokenIdentifier}`,
306
+ );
307
+
308
+ // Search for vaults containing the specific token using SDK
309
+ for (const chainId of chainsToSearch) {
310
+ try {
311
+ logger.log(
312
+ `Searching for token ${tokenIdentifier} on chain ${chainId}...`,
313
+ );
314
+ const tokenVaults = await this.getVaultsForToken(
315
+ chainId,
316
+ tokenIdentifier,
317
+ );
318
+
319
+ if (tokenVaults && tokenVaults.length > 0) {
320
+ allVaults.push(...tokenVaults);
321
+ logger.log(
322
+ `Chain ${chainId}: Found ${tokenVaults.length} vaults containing token ${tokenIdentifier}`,
323
+ );
324
+ } else {
325
+ logger.log(
326
+ `Chain ${chainId}: No vaults found containing token ${tokenIdentifier}`,
327
+ );
328
+ }
329
+ } catch (error) {
330
+ logger.error(
331
+ `Error searching for token ${tokenIdentifier} on chain ${chainId}:`,
332
+ error,
333
+ );
334
+ }
335
+ }
336
+
337
+ if (allVaults.length === 0) {
338
+ logger.log(
339
+ `No vaults found containing token ${tokenIdentifier}, falling back to general search...`,
340
+ );
341
+ }
342
+ }
343
+
344
+ // If no specific token vaults found or this is a general search, get all vaults
345
+ if (allVaults.length === 0) {
346
+ logger.log(`Fetching all vault data from Steer Finance using SDK...`);
347
+
348
+ // Fetch data from specified chains using the SDK
349
+ for (const chainId of chainsToSearch) {
350
+ try {
351
+ logger.log(`Fetching data for chain ${chainId}...`);
352
+
353
+ // Get vaults for this chain using SDK
354
+ const chainVaults = await this.getAllVaultsForChain(chainId);
355
+ allVaults.push(...chainVaults);
356
+
357
+ logger.log(
358
+ `Chain ${chainId}: Successfully processed ${chainVaults.length} vaults`,
359
+ );
360
+ } catch (error) {
361
+ logger.error(`Error fetching data for chain ${chainId}:`, error);
362
+ }
363
+ }
364
+ }
365
+
366
+ logger.log(
367
+ `Total vaults processed across ${chainsToSearch.length} chain(s): ${allVaults.length}`,
368
+ );
369
+
370
+ // Calculate aggregate statistics
371
+ const totalTvl = allVaults.reduce(
372
+ (sum, vault) => sum + (vault.tvl || 0),
373
+ 0,
374
+ );
375
+ const totalVolume = allVaults.reduce(
376
+ (sum, vault) => sum + (vault.volume24h || 0),
377
+ 0,
378
+ );
379
+ const apyValues = allVaults
380
+ .map((vault) => vault.apy || vault.apr || 0)
381
+ .filter((apy) => apy > 0);
382
+ const apyRange = {
383
+ min: apyValues.length > 0 ? Math.min(...apyValues) : 0,
384
+ max: apyValues.length > 0 ? Math.max(...apyValues) : 0,
385
+ };
386
+
387
+ // Log summary of what was found
388
+ logger.log(`=== STEER LIQUIDITY STATS SUMMARY ===`);
389
+ logger.log(`Total vaults found: ${allVaults.length}`);
390
+ logger.log(`Total staking pools found: ${allStakingPools.length}`);
391
+ logger.log(`Total TVL: $${totalTvl.toLocaleString()}`);
392
+ logger.log(`Total 24h Volume: $${totalVolume.toLocaleString()}`);
393
+ logger.log(
394
+ `APY Range: ${apyRange.min.toFixed(2)}% - ${apyRange.max.toFixed(2)}%`,
395
+ );
396
+
397
+ // Log breakdown by chain
398
+ const vaultsByChain = allVaults.reduce(
399
+ (acc, vault) => {
400
+ acc[vault.chainId] = (acc[vault.chainId] || 0) + 1;
401
+ return acc;
402
+ },
403
+ {} as { [key: number]: number },
404
+ );
405
+
406
+ logger.log(`Vaults by chain:`, vaultsByChain);
407
+
408
+ const stats: TokenLiquidityStats = {
409
+ tokenIdentifier,
410
+ normalizedToken,
411
+ tokenName,
412
+ timestamp: new Date().toISOString(),
413
+ vaults: allVaults,
414
+ stakingPools: allStakingPools,
415
+ totalTvl,
416
+ totalVolume,
417
+ apyRange,
418
+ vaultCount: allVaults.length,
419
+ stakingPoolCount: allStakingPools.length,
420
+ };
421
+
422
+ const chainInfo = targetChainId
423
+ ? ` on ${this.getChainName(targetChainId)}`
424
+ : " across all chains";
425
+ logger.log(
426
+ `Found ${stats.vaultCount} vaults and ${stats.stakingPoolCount} staking pools for ${normalizedToken}${chainInfo} with total TVL: $${stats.totalTvl.toLocaleString()}`,
427
+ );
428
+
429
+ return stats;
430
+ } catch (error) {
431
+ logger.error("Error getting Steer liquidity stats:", error);
432
+ throw new Error(
433
+ `Failed to get Steer liquidity stats: ${error instanceof Error ? error.message : "Unknown error"}`,
434
+ );
435
+ }
436
+ }
437
+
438
+ /**
439
+ * Get all vaults for a specific chain using the SDK
440
+ */
441
+ private async getAllVaultsForChain(
442
+ chainId: number,
443
+ ): Promise<SteerVaultDetailInput[]> {
444
+ try {
445
+ const vaultClient = this.vaultClients.get(chainId);
446
+ if (!vaultClient) {
447
+ logger.warn(`No vault client available for chain ${chainId}`);
448
+ return [];
449
+ }
450
+
451
+ type VaultSdkList = {
452
+ success: boolean;
453
+ data?: { edges?: Array<{ node?: RawSteerVault }> };
454
+ error?: string;
455
+ };
456
+ let vaultsResponse: VaultSdkList;
457
+ try {
458
+ vaultsResponse = await vaultClient.getVaults({ chainId }, 100, null);
459
+ } catch (error) {
460
+ logger.error(`API call failed for chain ${chainId}:`, error);
461
+ return [];
462
+ }
463
+
464
+ if (!vaultsResponse.success || !vaultsResponse.data) {
465
+ logger.warn(
466
+ `Failed to get vaults for chain ${chainId}: ${vaultsResponse.error || "Unknown error"}`,
467
+ );
468
+ // If it's a server error, log it but continue with other chains
469
+ if (vaultsResponse.error?.includes("INTERNAL_SERVER_ERROR")) {
470
+ logger.warn(`Chain ${chainId} has server issues, skipping for now`);
471
+ }
472
+ return [];
473
+ }
474
+
475
+ // Debug: Log the response structure
476
+ logger.log(`Vault response structure for chain ${chainId}:`, {
477
+ success: vaultsResponse.success,
478
+ hasData: !!vaultsResponse.data,
479
+ dataType: typeof vaultsResponse.data,
480
+ isArray: Array.isArray(vaultsResponse.data),
481
+ hasEdges: !!vaultsResponse.data?.edges,
482
+ edgesLength: vaultsResponse.data?.edges?.length || 0,
483
+ });
484
+
485
+ // Extract vaults from the paginated response structure
486
+ const vaults: RawSteerVault[] =
487
+ vaultsResponse.data?.edges
488
+ ?.map((edge: { node?: RawSteerVault }) => edge.node)
489
+ .filter((n): n is RawSteerVault => n !== undefined) || [];
490
+ logger.log(
491
+ `Retrieved ${vaults.length} vaults from SDK for chain ${chainId}`,
492
+ );
493
+
494
+ // Process and enrich vault data
495
+ const processedVaults = await Promise.all(
496
+ vaults.map(async (vault) => {
497
+ try {
498
+ if (!vault) return null;
499
+ return await this.processVaultData(vault, chainId);
500
+ } catch (error) {
501
+ logger.error(`Error processing vault ${vault.address}:`, error);
502
+ return null;
503
+ }
504
+ }),
505
+ );
506
+
507
+ return processedVaults.filter(
508
+ (vault): vault is SteerVaultDetailInput => vault !== null,
509
+ );
510
+ } catch (error) {
511
+ logger.error(`Error getting vaults for chain ${chainId}:`, error);
512
+ return [];
513
+ }
514
+ }
515
+
516
+ /**
517
+ * Get vaults for a specific token on a specific chain using SDK
518
+ */
519
+ private async getVaultsForToken(
520
+ chainId: number,
521
+ tokenAddress: string,
522
+ ): Promise<SteerVaultDetailInput[]> {
523
+ try {
524
+ const vaultClient = this.vaultClients.get(chainId);
525
+ if (!vaultClient) {
526
+ logger.warn(`No vault client available for chain ${chainId}`);
527
+ return [];
528
+ }
529
+
530
+ type VaultSdkList = {
531
+ success: boolean;
532
+ data?: { edges?: Array<{ node?: RawSteerVault }> };
533
+ error?: string;
534
+ };
535
+ let vaultsResponse: VaultSdkList;
536
+ try {
537
+ vaultsResponse = await vaultClient.getVaults({ chainId }, 100, null);
538
+ } catch (error) {
539
+ logger.error(`API call failed for chain ${chainId}:`, error);
540
+ return [];
541
+ }
542
+
543
+ if (!vaultsResponse.success || !vaultsResponse.data) {
544
+ logger.warn(
545
+ `Failed to get vaults for chain ${chainId}: ${vaultsResponse.error || "Unknown error"}`,
546
+ );
547
+ return [];
548
+ }
549
+
550
+ // Extract vaults from the paginated response structure
551
+ const allVaults: RawSteerVault[] =
552
+ vaultsResponse.data?.edges
553
+ ?.map((edge: { node?: RawSteerVault }) => edge.node)
554
+ .filter((n): n is RawSteerVault => n !== undefined) || [];
555
+ logger.log(
556
+ `Searching ${allVaults.length} vaults for token ${tokenAddress} on chain ${chainId}`,
557
+ );
558
+
559
+ // Debug: Log first vault structure to understand the data format
560
+ if (allVaults.length > 0) {
561
+ logger.log(`Sample vault structure for chain ${chainId}:`, {
562
+ vaultAddress: allVaults[0].vaultAddress,
563
+ address: allVaults[0].address,
564
+ token0: allVaults[0].token0,
565
+ token1: allVaults[0].token1,
566
+ token0Type: typeof allVaults[0].token0,
567
+ token1Type: typeof allVaults[0].token1,
568
+ pool: allVaults[0].pool,
569
+ });
570
+ }
571
+
572
+ const matchingVaults: SteerVaultDetailInput[] = [];
573
+
574
+ // Filter vaults that contain the target token
575
+ for (const vault of allVaults) {
576
+ try {
577
+ // Check if the vault contains the target token
578
+ if (this.vaultContainsToken(vault, tokenAddress)) {
579
+ logger.log(
580
+ `Found matching vault ${vault.vaultAddress || vault.address} for token ${tokenAddress}`,
581
+ );
582
+
583
+ // Process the vault data
584
+ const processedVault = await this.processVaultData(vault, chainId);
585
+ if (processedVault) {
586
+ matchingVaults.push(processedVault);
587
+ }
588
+ }
589
+ } catch (error) {
590
+ logger.log(`Error processing vault ${vault.address}:`, error);
591
+ }
592
+ }
593
+
594
+ logger.log(
595
+ `Found ${matchingVaults.length} vaults containing token ${tokenAddress} on chain ${chainId}`,
596
+ );
597
+ return matchingVaults;
598
+ } catch (error) {
599
+ logger.error(
600
+ `Error getting vaults for token ${tokenAddress} on chain ${chainId}:`,
601
+ error,
602
+ );
603
+ return [];
604
+ }
605
+ }
606
+
607
+ /**
608
+ * Check if a vault contains a specific token
609
+ */
610
+ private vaultContainsToken(
611
+ vault: RawSteerVault,
612
+ tokenAddress: string,
613
+ ): boolean {
614
+ const targetAddress = tokenAddress.toLowerCase();
615
+
616
+ // Check token0 and token1 - handle both string and object formats
617
+ const token0Address =
618
+ typeof vault.token0 === "string" ? vault.token0 : vault.token0?.address;
619
+ const token1Address =
620
+ typeof vault.token1 === "string" ? vault.token1 : vault.token1?.address;
621
+
622
+ if (
623
+ token0Address?.toLowerCase() === targetAddress ||
624
+ token1Address?.toLowerCase() === targetAddress
625
+ ) {
626
+ return true;
627
+ }
628
+
629
+ // Check pool address if available
630
+ if (vault.poolAddress) {
631
+ return true;
632
+ }
633
+
634
+ return false;
635
+ }
636
+
637
+ /**
638
+ * Process vault data from SDK response
639
+ */
640
+ private async processVaultData(
641
+ vault: RawSteerVault,
642
+ chainId: number,
643
+ ): Promise<SteerVaultDetailInput | null> {
644
+ try {
645
+ // Extract basic vault information
646
+ const vaultAddress = vault.vaultAddress || vault.address || "";
647
+ const poolAddress = vault.pool?.poolAddress || vault.poolAddress;
648
+ const feeTier = vault.pool?.feeTier || vault.fee || 0.3;
649
+
650
+ // Extract APY data from various sources
651
+ const apyData = vault.aprData || {};
652
+ const apy =
653
+ vault.apy ||
654
+ vault.apr ||
655
+ apyData.apr1dAvg ||
656
+ apyData.apr7dAvg ||
657
+ apyData.apr14dAvg ||
658
+ 0;
659
+
660
+ const processedVault = {
661
+ address: vaultAddress,
662
+ name: vault.name || `Steer Vault ${vaultAddress?.slice(0, 8)}...`,
663
+ chainId,
664
+ token0: vault.token0 || "Unknown",
665
+ token1: vault.token1 || "Unknown",
666
+ fee: feeTier,
667
+ tvl: vault.tvl || 0,
668
+ volume24h: vault.volume24h || 0,
669
+ apy: apy,
670
+ isActive: vault.isActive !== false, // Default to true unless explicitly false
671
+ createdAt:
672
+ typeof vault.createdAt === "number"
673
+ ? vault.createdAt
674
+ : typeof vault.createdAt === "string"
675
+ ? Number.parseInt(vault.createdAt, 10) || Date.now()
676
+ : Date.now(),
677
+ strategyType: vault.protocol || vault.strategyType || "Unknown",
678
+ positions: vault.positions || [],
679
+ poolAddress: poolAddress,
680
+ ammType: vault.ammType || "UniswapV3",
681
+ singleAssetDepositContract: vault.singleAssetDepositContract,
682
+ // Additional fields from SDK
683
+ protocol: vault.protocol,
684
+ beaconName: vault.beaconName,
685
+ protocolBaseType: vault.protocolBaseType,
686
+ targetProtocol: vault.targetProtocol,
687
+ // APY breakdown
688
+ apr1d: apyData.apr1dAvg,
689
+ apr7d: apyData.apr7dAvg,
690
+ apr14d: apyData.apr14dAvg,
691
+ // Fee breakdown
692
+ feeApr: vault.feeApr,
693
+ stakingApr: vault.stakingApr,
694
+ merklApr: vault.merklApr,
695
+ };
696
+
697
+ // Try to get additional data from SDK if available
698
+ try {
699
+ const gqlPeek = await this.getVaultDataFromGraphQL(vaultAddress);
700
+ if (gqlPeek) {
701
+ const gqlTvl = this.calculateTvlFromBalances(
702
+ gqlPeek.token0Balance,
703
+ gqlPeek.token1Balance,
704
+ parseInt(gqlPeek.token0Decimals, 10) || 18,
705
+ parseInt(gqlPeek.token1Decimals, 10) || 18,
706
+ );
707
+ if (gqlTvl > 0) processedVault.tvl = gqlTvl;
708
+ const wApr = parseFloat(gqlPeek.weeklyFeeAPR);
709
+ if (Number.isFinite(wApr) && wApr > 0) {
710
+ processedVault.apy = wApr * 52;
711
+ }
712
+ }
713
+
714
+ // Try to get pool-specific data if we have a pool address
715
+ if (poolAddress) {
716
+ const poolData = await this.getPoolData(poolAddress, chainId);
717
+ if (poolData) {
718
+ // Update with pool data
719
+ if (poolData.tvl) processedVault.tvl = poolData.tvl;
720
+ if (poolData.volume24h)
721
+ processedVault.volume24h = poolData.volume24h;
722
+ if (poolData.fee) processedVault.fee = poolData.fee;
723
+ }
724
+ }
725
+
726
+ // Try to get token price data for better TVL calculation
727
+ try {
728
+ const token0Address =
729
+ typeof vault.token0 === "string"
730
+ ? vault.token0
731
+ : vault.token0?.address;
732
+ const token1Address =
733
+ typeof vault.token1 === "string"
734
+ ? vault.token1
735
+ : vault.token1?.address;
736
+
737
+ if (token0Address && token1Address) {
738
+ const priceData = await this.getTokenPrices(
739
+ [token0Address, token1Address],
740
+ chainId,
741
+ );
742
+ if (priceData && processedVault.tvl === 0) {
743
+ // If we don't have TVL but have price data, we could calculate it
744
+ logger.log(
745
+ `Price data available for vault ${vaultAddress}: Token0: $${priceData[token0Address]}, Token1: $${priceData[token1Address]}`,
746
+ );
747
+ }
748
+ }
749
+ } catch (error) {
750
+ logger.log(
751
+ `Could not fetch price data for vault ${vaultAddress}:`,
752
+ error,
753
+ );
754
+ }
755
+ } catch (_error) {
756
+ logger.log(
757
+ `Could not fetch additional data for vault ${vaultAddress}, using basic info`,
758
+ );
759
+ }
760
+
761
+ // Enrich vault data with GraphQL information
762
+ try {
763
+ const enrichedVault = await this.enrichVaultWithGraphQLData(
764
+ processedVault,
765
+ chainId,
766
+ );
767
+ return enrichedVault;
768
+ } catch (error) {
769
+ logger.log(
770
+ `Could not enrich vault ${vaultAddress} with GraphQL data, returning basic info:`,
771
+ error,
772
+ );
773
+ return processedVault;
774
+ }
775
+ } catch (error) {
776
+ logger.error(`Error processing vault ${vault.address}:`, error);
777
+ return null;
778
+ }
779
+ }
780
+
781
+ /**
782
+ * Get vault details by address using GraphQL (preferred) or SDK fallback
783
+ */
784
+ async getVaultDetails(
785
+ vaultAddress: string,
786
+ chainId: number,
787
+ ): Promise<SteerVaultDetailInput | RawSteerVault | null> {
788
+ try {
789
+ // First try to get data from GraphQL
790
+ const graphqlData = await this.getVaultDataFromGraphQL(vaultAddress);
791
+ if (graphqlData) {
792
+ logger.log(`Found vault ${vaultAddress} via GraphQL`);
793
+ const feeTierBp = parseInt(graphqlData.feeTier, 10) || 3000;
794
+ return {
795
+ address: vaultAddress,
796
+ name: graphqlData.name,
797
+ token0: graphqlData.token0,
798
+ token1: graphqlData.token1,
799
+ poolAddress: graphqlData.pool,
800
+ weeklyFeeAPR: parseFloat(graphqlData.weeklyFeeAPR) || 0,
801
+ token0Symbol: graphqlData.token0Symbol,
802
+ token1Symbol: graphqlData.token1Symbol,
803
+ token0Balance: graphqlData.token0Balance,
804
+ token1Balance: graphqlData.token1Balance,
805
+ totalLPTokensIssued: graphqlData.totalLPTokensIssued,
806
+ feeTier: feeTierBp,
807
+ fees0: graphqlData.fees0,
808
+ fees1: graphqlData.fees1,
809
+ strategyToken: graphqlData.strategyToken,
810
+ beaconName: graphqlData.beaconName,
811
+ deployer: graphqlData.deployer,
812
+ chainId,
813
+ volume24h: 0,
814
+ strategyType: "graphql",
815
+ fee: feeTierBp / 10000,
816
+ createdAt: Date.now(),
817
+ // Calculate basic metrics
818
+ tvl: this.calculateTvlFromBalances(
819
+ graphqlData.token0Balance,
820
+ graphqlData.token1Balance,
821
+ parseInt(graphqlData.token0Decimals, 10) || 18,
822
+ parseInt(graphqlData.token1Decimals, 10) || 18,
823
+ ),
824
+ apy: parseFloat(graphqlData.weeklyFeeAPR) * 52 || 0, // Convert weekly to annual
825
+ isActive: true,
826
+ };
827
+ }
828
+
829
+ // Fallback to SDK if GraphQL fails
830
+ logger.log(
831
+ `GraphQL data not available for ${vaultAddress}, falling back to SDK`,
832
+ );
833
+ return await this.getVaultDetailsFromSDK(vaultAddress, chainId);
834
+ } catch (error) {
835
+ logger.error(`Error getting vault details for ${vaultAddress}:`, error);
836
+ return null;
837
+ }
838
+ }
839
+
840
+ /**
841
+ * Get vault details by address using SDK (fallback method)
842
+ */
843
+ private async getVaultDetailsFromSDK(
844
+ vaultAddress: string,
845
+ chainId: number,
846
+ ): Promise<RawSteerVault | null> {
847
+ try {
848
+ const vaultClient = this.vaultClients.get(chainId);
849
+ if (!vaultClient) {
850
+ logger.warn(`No vault client available for chain ${chainId}`);
851
+ return null;
852
+ }
853
+
854
+ // Get vault details using SDK - use getVaults and filter
855
+ const vaultResponse = await vaultClient.getVaults({ chainId }, 100, null);
856
+
857
+ if (!vaultResponse.success || !vaultResponse.data) {
858
+ logger.warn(
859
+ `Failed to get vault details for ${vaultAddress}: ${vaultResponse.error || "Unknown error"}`,
860
+ );
861
+ return null;
862
+ }
863
+
864
+ // Extract vaults from the paginated response structure
865
+ const vaults: RawSteerVault[] =
866
+ vaultResponse.data?.edges
867
+ ?.map((edge: { node?: RawSteerVault }) => edge.node)
868
+ .filter((n): n is RawSteerVault => n !== undefined) || [];
869
+ return vaults.length > 0 ? vaults[0] : null;
870
+ } catch (error) {
871
+ logger.error(
872
+ `Error getting vault details for ${vaultAddress} on chain ${chainId}:`,
873
+ error,
874
+ );
875
+ return null;
876
+ }
877
+ }
878
+
879
+ /**
880
+ * Get token prices for TVL calculation
881
+ */
882
+ async getTokenPrices(
883
+ _tokenAddresses: string[],
884
+ chainId: number,
885
+ ): Promise<{ [address: string]: number } | null> {
886
+ try {
887
+ // This is a placeholder - in a real implementation, you'd call a price API
888
+ // For now, we'll return null to indicate no price data
889
+ logger.log(`Price fetching not yet implemented for chain ${chainId}`);
890
+ return null;
891
+ } catch (error) {
892
+ logger.error(`Error getting token prices for chain ${chainId}:`, error);
893
+ return null;
894
+ }
895
+ }
896
+
897
+ /**
898
+ * Get pool data including TVL, volume, and fee information
899
+ */
900
+ async getPoolData(
901
+ poolAddress: string,
902
+ chainId: number,
903
+ ): Promise<{
904
+ tvl: number;
905
+ volume24h: number;
906
+ fee: number;
907
+ liquidity: number;
908
+ } | null> {
909
+ try {
910
+ const vaultClient = this.vaultClients.get(chainId);
911
+ if (!vaultClient) {
912
+ logger.warn(`No vault client available for chain ${chainId}`);
913
+ return null;
914
+ }
915
+
916
+ // Try to get pool information using the SDK
917
+ try {
918
+ const poolsResponse = await vaultClient.getPools(
919
+ { chainId, protocol: "uniswap-v3" },
920
+ 100,
921
+ null,
922
+ );
923
+ if (poolsResponse.success && poolsResponse.data) {
924
+ const pools: SteerPoolNode[] =
925
+ poolsResponse.data.edges
926
+ ?.map((edge: { node?: SteerPoolNode }) => edge.node)
927
+ .filter((n): n is SteerPoolNode => n !== undefined) || [];
928
+ const matchingPool = pools.find(
929
+ (pool: SteerPoolNode) =>
930
+ pool.poolAddress?.toLowerCase() === poolAddress.toLowerCase() ||
931
+ pool.id?.toLowerCase() === poolAddress.toLowerCase(),
932
+ );
933
+
934
+ if (matchingPool) {
935
+ return {
936
+ tvl: matchingPool.totalValueLockedUSD
937
+ ? parseFloat(matchingPool.totalValueLockedUSD)
938
+ : 0,
939
+ volume24h: matchingPool.volumeUSD
940
+ ? parseFloat(matchingPool.volumeUSD)
941
+ : 0,
942
+ fee: matchingPool.feeTier
943
+ ? parseFloat(matchingPool.feeTier) / 10000
944
+ : 0.3, // Convert basis points to percentage
945
+ liquidity: matchingPool.liquidity || 0,
946
+ };
947
+ }
948
+ }
949
+ } catch (error) {
950
+ logger.log(
951
+ `Could not fetch pool data from SDK for ${poolAddress}:`,
952
+ error,
953
+ );
954
+ }
955
+
956
+ // Fallback: return basic pool data
957
+ return {
958
+ tvl: 0,
959
+ volume24h: 0,
960
+ fee: 0.3,
961
+ liquidity: 0,
962
+ };
963
+ } catch (error) {
964
+ logger.error(
965
+ `Error getting pool data for ${poolAddress} on chain ${chainId}:`,
966
+ error,
967
+ );
968
+ return null;
969
+ }
970
+ }
971
+
972
+ /**
973
+ * Get staking pool details by address using SDK
974
+ */
975
+ async getStakingPoolDetails(
976
+ poolAddress: string,
977
+ chainId: number,
978
+ ): Promise<StakingPool | null> {
979
+ try {
980
+ const stakingClient = this.stakingClients.get(chainId);
981
+ if (!stakingClient) {
982
+ logger.warn(`No staking client available for chain ${chainId}`);
983
+ return null;
984
+ }
985
+
986
+ // Get staking pool details using SDK
987
+ const poolResponse = await stakingClient.getStakingPools({ chainId });
988
+
989
+ if (!poolResponse.success || !poolResponse.data) {
990
+ logger.warn(
991
+ `Failed to get staking pool details for ${poolAddress}: ${poolResponse.error || "Unknown error"}`,
992
+ );
993
+ return null;
994
+ }
995
+
996
+ const pools = poolResponse.data;
997
+ return pools.length > 0 ? pools[0] : null;
998
+ } catch (error) {
999
+ logger.error(
1000
+ `Error getting staking pool details for ${poolAddress} on chain ${chainId}:`,
1001
+ error,
1002
+ );
1003
+ return null;
1004
+ }
1005
+ }
1006
+
1007
+ /**
1008
+ * Test GraphQL connection specifically
1009
+ */
1010
+ async testGraphQLConnection(): Promise<{ success: boolean; error?: string }> {
1011
+ try {
1012
+ logger.log("Testing GraphQL connection to Steer Protocol subgraph...");
1013
+
1014
+ const query = `
1015
+ query TestConnection {
1016
+ _meta {
1017
+ block {
1018
+ number
1019
+ }
1020
+ }
1021
+ }
1022
+ `;
1023
+
1024
+ const response = await fetch(STEER_GRAPHQL_ENDPOINT, {
1025
+ method: "POST",
1026
+ headers: {
1027
+ "Content-Type": "application/json",
1028
+ },
1029
+ body: JSON.stringify({ query }),
1030
+ });
1031
+
1032
+ if (!response.ok) {
1033
+ throw new Error(
1034
+ `GraphQL request failed: ${response.status} ${response.statusText}`,
1035
+ );
1036
+ }
1037
+
1038
+ const result = await response.json();
1039
+
1040
+ if (result.data?._meta) {
1041
+ logger.log("GraphQL connection test successful");
1042
+ return { success: true };
1043
+ } else {
1044
+ logger.warn("GraphQL response missing expected data structure");
1045
+ return { success: false, error: "Unexpected response structure" };
1046
+ }
1047
+ } catch (error) {
1048
+ logger.error("GraphQL connection test failed:", error);
1049
+ return {
1050
+ success: false,
1051
+ error: error instanceof Error ? error.message : "Unknown error",
1052
+ };
1053
+ }
1054
+ }
1055
+
1056
+ /**
1057
+ * Test GraphQL vault query with a specific vault address
1058
+ */
1059
+ async testGraphQLVaultQuery(
1060
+ vaultAddress: string,
1061
+ ): Promise<{ success: boolean; data?: GraphQLVaultData; error?: string }> {
1062
+ try {
1063
+ logger.log(`Testing GraphQL vault query for: ${vaultAddress}`);
1064
+
1065
+ const vaultData = await this.getVaultDataFromGraphQL(vaultAddress);
1066
+
1067
+ if (vaultData) {
1068
+ logger.log(`GraphQL vault query successful for ${vaultAddress}`);
1069
+ return { success: true, data: vaultData };
1070
+ } else {
1071
+ logger.warn(`No vault data found for ${vaultAddress}`);
1072
+ return { success: false, error: "Vault not found" };
1073
+ }
1074
+ } catch (error) {
1075
+ logger.error(
1076
+ `GraphQL vault query test failed for ${vaultAddress}:`,
1077
+ error,
1078
+ );
1079
+ return {
1080
+ success: false,
1081
+ error: error instanceof Error ? error.message : "Unknown error",
1082
+ };
1083
+ }
1084
+ }
1085
+
1086
+ /**
1087
+ * Test connection to Steer Finance services using SDK
1088
+ */
1089
+ async testConnection(): Promise<ConnectionTestResult> {
1090
+ try {
1091
+ logger.log("Testing Steer Finance connection using SDK...");
1092
+
1093
+ let totalVaultCount = 0;
1094
+ let totalStakingPoolCount = 0;
1095
+ const connectionErrors: string[] = [];
1096
+
1097
+ // Test SDK connection for each chain
1098
+ for (const chainId of this.supportedChains) {
1099
+ try {
1100
+ logger.log(`Testing connection for chain ${chainId}...`);
1101
+
1102
+ // Test vault client
1103
+ const vaultClient = this.vaultClients.get(chainId);
1104
+ if (vaultClient) {
1105
+ const vaultsResponse = await vaultClient.getVaults(
1106
+ { chainId },
1107
+ 100,
1108
+ null,
1109
+ );
1110
+ logger.log(`Chain ${chainId} vault response:`, {
1111
+ success: vaultsResponse.success,
1112
+ hasData: !!vaultsResponse.data,
1113
+ dataType: typeof vaultsResponse.data,
1114
+ isArray: Array.isArray(vaultsResponse.data),
1115
+ hasEdges: !!vaultsResponse.data?.edges,
1116
+ edgesLength: vaultsResponse.data?.edges?.length || 0,
1117
+ });
1118
+
1119
+ if (vaultsResponse.success && vaultsResponse.data) {
1120
+ const vaults =
1121
+ vaultsResponse.data.edges
1122
+ ?.map((edge: { node?: RawSteerVault }) => edge.node)
1123
+ .filter((n): n is RawSteerVault => n !== undefined) || [];
1124
+ totalVaultCount += vaults.length;
1125
+ logger.log(`Chain ${chainId}: Found ${vaults.length} vaults`);
1126
+ }
1127
+ }
1128
+
1129
+ // Test staking client
1130
+ const stakingClient = this.stakingClients.get(chainId);
1131
+ if (stakingClient) {
1132
+ const poolsResponse = await stakingClient.getStakingPools({
1133
+ chainId,
1134
+ });
1135
+ if (poolsResponse.success && poolsResponse.data) {
1136
+ const pools = poolsResponse.data;
1137
+ totalStakingPoolCount += pools.length;
1138
+ logger.log(
1139
+ `Chain ${chainId}: Found ${pools.length} staking pools`,
1140
+ );
1141
+ }
1142
+ }
1143
+ } catch (error) {
1144
+ const errorMsg = `Chain ${chainId}: ${error instanceof Error ? error.message : "Unknown error"}`;
1145
+ connectionErrors.push(errorMsg);
1146
+ logger.error(`Connection test failed for chain ${chainId}:`, error);
1147
+ }
1148
+ }
1149
+
1150
+ const result: ConnectionTestResult = {
1151
+ connectionTest: connectionErrors.length === 0,
1152
+ supportedChains: this.supportedChains,
1153
+ vaultCount: totalVaultCount,
1154
+ stakingPoolCount: totalStakingPoolCount,
1155
+ error:
1156
+ connectionErrors.length > 0 ? connectionErrors.join("; ") : undefined,
1157
+ };
1158
+
1159
+ logger.log(
1160
+ `Steer connection test completed. Vaults: ${totalVaultCount}, Staking Pools: ${totalStakingPoolCount}`,
1161
+ );
1162
+ return result;
1163
+ } catch (error) {
1164
+ logger.error("Error testing Steer connection:", error);
1165
+ return {
1166
+ connectionTest: false,
1167
+ supportedChains: this.supportedChains,
1168
+ vaultCount: 0,
1169
+ stakingPoolCount: 0,
1170
+ error: error instanceof Error ? error.message : "Unknown error",
1171
+ };
1172
+ }
1173
+ }
1174
+
1175
+ /**
1176
+ * Normalize token identifier (handle different formats)
1177
+ */
1178
+ private normalizeTokenIdentifier(tokenIdentifier: string): string {
1179
+ // Remove common prefixes and normalize
1180
+ const normalized = tokenIdentifier.trim();
1181
+
1182
+ // Handle Solana-style addresses (base58)
1183
+ if (
1184
+ normalized.length === 44 &&
1185
+ /^[1-9A-HJ-NP-Za-km-z]+$/.test(normalized)
1186
+ ) {
1187
+ // This is likely a Solana address, but Steer is EVM-based
1188
+ logger.warn(
1189
+ `Token ${normalized} appears to be a Solana address, but Steer Finance is EVM-based`,
1190
+ );
1191
+ }
1192
+
1193
+ // Handle Ethereum-style addresses (0x...)
1194
+ if (normalized.startsWith("0x")) {
1195
+ return normalized.toLowerCase();
1196
+ }
1197
+
1198
+ // For symbol-based lookups, return as-is (no hardcoded mapping)
1199
+ return normalized;
1200
+ }
1201
+
1202
+ /**
1203
+ * Get token name from identifier
1204
+ */
1205
+ private getTokenName(tokenIdentifier: string): string {
1206
+ // For token addresses, return a formatted version
1207
+ if (tokenIdentifier.startsWith("0x")) {
1208
+ return `Token ${tokenIdentifier.slice(0, 8)}...${tokenIdentifier.slice(-6)}`;
1209
+ }
1210
+
1211
+ // For symbols or other identifiers, return as-is
1212
+ return tokenIdentifier;
1213
+ }
1214
+
1215
+ /**
1216
+ * Get chain name from chain ID
1217
+ */
1218
+ private getChainName(chainId: number): string {
1219
+ const chainNames: { [key: number]: string } = {
1220
+ 1: "Ethereum Mainnet",
1221
+ 137: "Polygon",
1222
+ 42161: "Arbitrum One",
1223
+ 10: "Optimism",
1224
+ 8453: "Base",
1225
+ };
1226
+ return chainNames[chainId] || `Chain ${chainId}`;
1227
+ }
1228
+
1229
+ /**
1230
+ * Clear expired cache entries
1231
+ */
1232
+ private clearExpiredCache(): void {
1233
+ const now = Date.now();
1234
+ for (const [key, value] of this.cache.entries()) {
1235
+ if (now - value.timestamp > this.CACHE_TTL) {
1236
+ this.cache.delete(key);
1237
+ }
1238
+ }
1239
+ }
1240
+
1241
+ // Service lifecycle methods
1242
+
1243
+ static async create(runtime: IAgentRuntime): Promise<SteerLiquidityService> {
1244
+ return new SteerLiquidityService(runtime);
1245
+ }
1246
+
1247
+ static async start(runtime: IAgentRuntime): Promise<SteerLiquidityService> {
1248
+ const service = new SteerLiquidityService(runtime);
1249
+ await service.start();
1250
+ return service;
1251
+ }
1252
+
1253
+ static async stop(runtime: IAgentRuntime): Promise<void> {
1254
+ const service = runtime.getService(
1255
+ "STEER_LIQUIDITY_SERVICE",
1256
+ ) as SteerLiquidityService;
1257
+ if (service) {
1258
+ await service.stop();
1259
+ }
1260
+ }
1261
+
1262
+ async start(): Promise<void> {
1263
+ if (this.isRunning) {
1264
+ logger.warn("SteerLiquidityService is already running");
1265
+ return;
1266
+ }
1267
+
1268
+ try {
1269
+ // Clear any expired cache entries
1270
+ this.clearExpiredCache();
1271
+
1272
+ this.isRunning = true;
1273
+ logger.log("SteerLiquidityService started successfully");
1274
+ } catch (error) {
1275
+ logger.error("Failed to start SteerLiquidityService:", error);
1276
+ throw error;
1277
+ }
1278
+ }
1279
+
1280
+ async stop(): Promise<void> {
1281
+ if (!this.isRunning) {
1282
+ logger.warn("SteerLiquidityService is not running");
1283
+ return;
1284
+ }
1285
+
1286
+ try {
1287
+ this.isRunning = false;
1288
+ logger.log("SteerLiquidityService stopped successfully");
1289
+ } catch (error) {
1290
+ logger.error("Failed to stop SteerLiquidityService:", error);
1291
+ throw error;
1292
+ }
1293
+ }
1294
+
1295
+ isServiceRunning(): boolean {
1296
+ return this.isRunning;
1297
+ }
1298
+
1299
+ /**
1300
+ * Preview single-asset deposit for a vault using SDK
1301
+ */
1302
+ async previewSingleAssetDeposit(
1303
+ vaultAddress: string,
1304
+ chainId: number,
1305
+ assets: bigint,
1306
+ isToken0: boolean,
1307
+ depositSlippagePercent: bigint = 5n,
1308
+ swapSlippageBP: number = 500,
1309
+ ): Promise<SteerPreviewDepositResult> {
1310
+ try {
1311
+ const vaultClient = this.vaultClients.get(chainId);
1312
+ if (!vaultClient) {
1313
+ throw new Error(`No vault client available for chain ${chainId}`);
1314
+ }
1315
+
1316
+ // Get vault details to get the pool address and single asset deposit contract
1317
+ const vault = await this.getVaultDetails(vaultAddress, chainId);
1318
+ if (!vault) {
1319
+ throw new Error(`Vault ${vaultAddress} not found on chain ${chainId}`);
1320
+ }
1321
+
1322
+ if (!vault.poolAddress || !vault.singleAssetDepositContract) {
1323
+ throw new Error(
1324
+ `Vault ${vaultAddress} does not support single-asset deposits`,
1325
+ );
1326
+ }
1327
+
1328
+ // Preview the single-asset deposit using SDK
1329
+ const preview = await vaultClient.previewSingleAssetDeposit(
1330
+ {
1331
+ assets,
1332
+ receiver:
1333
+ "0x0000000000000000000000000000000000000000" as `0x${string}`, // Placeholder
1334
+ vault: vaultAddress as `0x${string}`,
1335
+ isToken0,
1336
+ depositSlippagePercent,
1337
+ swapSlippageBP,
1338
+ ammType: AMMType.UniswapV3,
1339
+ singleAssetDepositContract: vault.singleAssetDepositContract,
1340
+ },
1341
+ vault.poolAddress,
1342
+ );
1343
+
1344
+ return preview;
1345
+ } catch (error) {
1346
+ logger.error(
1347
+ `Error previewing single-asset deposit for vault ${vaultAddress}:`,
1348
+ error,
1349
+ );
1350
+ throw error;
1351
+ }
1352
+ }
1353
+
1354
+ /**
1355
+ * Execute single-asset deposit for a vault using SDK
1356
+ */
1357
+ async executeSingleAssetDeposit(
1358
+ vaultAddress: string,
1359
+ chainId: number,
1360
+ assets: bigint,
1361
+ receiver: string,
1362
+ isToken0: boolean,
1363
+ depositSlippagePercent: bigint = 5n,
1364
+ swapSlippageBP: number = 500,
1365
+ ): Promise<SteerSingleDepositResult> {
1366
+ try {
1367
+ const vaultClient = this.vaultClients.get(chainId);
1368
+ if (!vaultClient) {
1369
+ throw new Error(`No vault client available for chain ${chainId}`);
1370
+ }
1371
+
1372
+ // Get vault details to get the single asset deposit contract
1373
+ const vault = await this.getVaultDetails(vaultAddress, chainId);
1374
+ if (!vault) {
1375
+ throw new Error(`Vault ${vaultAddress} not found on chain ${chainId}`);
1376
+ }
1377
+
1378
+ if (!vault.singleAssetDepositContract) {
1379
+ throw new Error(
1380
+ `Vault ${vaultAddress} does not support single-asset deposits`,
1381
+ );
1382
+ }
1383
+
1384
+ // Execute the single-asset deposit using SDK
1385
+ const result = await vaultClient.singleAssetDeposit({
1386
+ assets,
1387
+ receiver: receiver as `0x${string}`,
1388
+ vault: vaultAddress as `0x${string}`,
1389
+ isToken0,
1390
+ depositSlippagePercent,
1391
+ swapSlippageBP,
1392
+ ammType: AMMType.UniswapV3,
1393
+ singleAssetDepositContract: vault.singleAssetDepositContract,
1394
+ });
1395
+
1396
+ return result;
1397
+ } catch (error) {
1398
+ logger.error(
1399
+ `Error executing single-asset deposit for vault ${vaultAddress}:`,
1400
+ error,
1401
+ );
1402
+ throw error;
1403
+ }
1404
+ }
1405
+
1406
+ /**
1407
+ * Get earned rewards for a staking pool using SDK
1408
+ */
1409
+ async getEarnedRewards(
1410
+ poolAddress: string,
1411
+ accountAddress: string,
1412
+ chainId: number,
1413
+ ): Promise<SteerEarnedRewardsResult> {
1414
+ try {
1415
+ const stakingClient = this.stakingClients.get(chainId);
1416
+ if (!stakingClient) {
1417
+ throw new Error(`No staking client available for chain ${chainId}`);
1418
+ }
1419
+
1420
+ const earned = await stakingClient.earned(
1421
+ poolAddress as `0x${string}`,
1422
+ accountAddress as `0x${string}`,
1423
+ );
1424
+ return earned;
1425
+ } catch (error) {
1426
+ logger.error(
1427
+ `Error getting earned rewards for pool ${poolAddress}:`,
1428
+ error,
1429
+ );
1430
+ throw error;
1431
+ }
1432
+ }
1433
+
1434
+ /**
1435
+ * Get total supply of a staking pool using SDK
1436
+ */
1437
+ async getStakingPoolTotalSupply(
1438
+ poolAddress: string,
1439
+ chainId: number,
1440
+ ): Promise<SteerStakingSupplyResult> {
1441
+ try {
1442
+ const stakingClient = this.stakingClients.get(chainId);
1443
+ if (!stakingClient) {
1444
+ throw new Error(`No staking client available for chain ${chainId}`);
1445
+ }
1446
+
1447
+ const totalSupply = await stakingClient.totalSupply(
1448
+ poolAddress as `0x${string}`,
1449
+ );
1450
+ return totalSupply;
1451
+ } catch (error) {
1452
+ logger.error(
1453
+ `Error getting total supply for pool ${poolAddress}:`,
1454
+ error,
1455
+ );
1456
+ throw error;
1457
+ }
1458
+ }
1459
+
1460
+ /**
1461
+ * Get balance of a user in a staking pool using SDK
1462
+ */
1463
+ async getStakingPoolBalance(
1464
+ poolAddress: string,
1465
+ accountAddress: string,
1466
+ chainId: number,
1467
+ ): Promise<SteerStakingBalanceResult> {
1468
+ try {
1469
+ const stakingClient = this.stakingClients.get(chainId);
1470
+ if (!stakingClient) {
1471
+ throw new Error(`No staking client available for chain ${chainId}`);
1472
+ }
1473
+
1474
+ const balance = await stakingClient.balanceOf(
1475
+ poolAddress as `0x${string}`,
1476
+ accountAddress as `0x${string}`,
1477
+ );
1478
+ return balance;
1479
+ } catch (error) {
1480
+ logger.error(`Error getting balance for pool ${poolAddress}:`, error);
1481
+ throw error;
1482
+ }
1483
+ }
1484
+
1485
+ /**
1486
+ * Fetch detailed vault data from Steer Protocol GraphQL subgraph
1487
+ */
1488
+ async getVaultDataFromGraphQL(
1489
+ vaultAddress: string,
1490
+ ): Promise<GraphQLVaultData | null> {
1491
+ try {
1492
+ logger.log(`Fetching GraphQL data for vault: ${vaultAddress}`);
1493
+
1494
+ const query = `
1495
+ query GetVault($vaultId: ID!) {
1496
+ vault(id: $vaultId) {
1497
+ id
1498
+ name
1499
+ token0
1500
+ token1
1501
+ pool
1502
+ weeklyFeeAPR
1503
+ token0Symbol
1504
+ token0Decimals
1505
+ token1Symbol
1506
+ token1Decimals
1507
+ token0Balance
1508
+ token1Balance
1509
+ totalLPTokensIssued
1510
+ feeTier
1511
+ fees0
1512
+ fees1
1513
+ strategyToken {
1514
+ id
1515
+ name
1516
+ creator {
1517
+ id
1518
+ }
1519
+ admin
1520
+ executionBundle
1521
+ }
1522
+ beaconName
1523
+ payloadIpfs
1524
+ deployer
1525
+ }
1526
+ }
1527
+ `;
1528
+
1529
+ const variables = {
1530
+ vaultId: vaultAddress.toLowerCase(),
1531
+ };
1532
+
1533
+ const response = await fetch(STEER_GRAPHQL_ENDPOINT, {
1534
+ method: "POST",
1535
+ headers: {
1536
+ "Content-Type": "application/json",
1537
+ },
1538
+ body: JSON.stringify({
1539
+ query,
1540
+ variables,
1541
+ }),
1542
+ });
1543
+
1544
+ if (!response.ok) {
1545
+ throw new Error(
1546
+ `GraphQL request failed: ${response.status} ${response.statusText}`,
1547
+ );
1548
+ }
1549
+
1550
+ const result: GraphQLResponse = await response.json();
1551
+
1552
+ if (result.data?.vault) {
1553
+ logger.log(
1554
+ `Successfully fetched GraphQL data for vault ${vaultAddress}`,
1555
+ );
1556
+ logger.log(`GraphQL vault data:`, {
1557
+ name: result.data.vault.name,
1558
+ token0Symbol: result.data.vault.token0Symbol,
1559
+ token1Symbol: result.data.vault.token1Symbol,
1560
+ weeklyFeeAPR: result.data.vault.weeklyFeeAPR,
1561
+ token0Balance: result.data.vault.token0Balance,
1562
+ token1Balance: result.data.vault.token1Balance,
1563
+ });
1564
+ return result.data.vault;
1565
+ } else {
1566
+ logger.warn(
1567
+ `No vault data found in GraphQL response for ${vaultAddress}`,
1568
+ );
1569
+ logger.log(`GraphQL response:`, JSON.stringify(result, null, 2));
1570
+ return null;
1571
+ }
1572
+ } catch (error) {
1573
+ logger.error(
1574
+ `Error fetching GraphQL data for vault ${vaultAddress}:`,
1575
+ error,
1576
+ );
1577
+ return null;
1578
+ }
1579
+ }
1580
+
1581
+ /**
1582
+ * Enrich vault data with GraphQL information
1583
+ */
1584
+ async enrichVaultWithGraphQLData(
1585
+ vault: SteerVaultDetailInput,
1586
+ _chainId: number,
1587
+ ): Promise<SteerVaultDetailInput> {
1588
+ try {
1589
+ if (!vault.address && !vault.vaultAddress) {
1590
+ logger.warn("Vault missing address, cannot fetch GraphQL data");
1591
+ return vault;
1592
+ }
1593
+
1594
+ const vaultAddress = vault.address || vault.vaultAddress;
1595
+ logger.log(`Enriching vault ${vaultAddress} with GraphQL data...`);
1596
+
1597
+ // Fetch GraphQL data
1598
+ const graphqlData = await this.getVaultDataFromGraphQL(vaultAddress);
1599
+
1600
+ if (graphqlData) {
1601
+ // Merge GraphQL data with existing vault data
1602
+ const enrichedVault = {
1603
+ ...vault,
1604
+ // GraphQL specific fields
1605
+ graphqlData: {
1606
+ weeklyFeeAPR: parseFloat(graphqlData.weeklyFeeAPR) || 0,
1607
+ token0Symbol: graphqlData.token0Symbol,
1608
+ token0Decimals: parseInt(graphqlData.token0Decimals, 10) || 18,
1609
+ token1Symbol: graphqlData.token1Symbol,
1610
+ token1Decimals: parseInt(graphqlData.token1Decimals, 10) || 18,
1611
+ token0Balance: graphqlData.token0Balance,
1612
+ token1Balance: graphqlData.token1Balance,
1613
+ totalLPTokensIssued: graphqlData.totalLPTokensIssued,
1614
+ feeTier: parseInt(graphqlData.feeTier, 10) || 3000,
1615
+ fees0: graphqlData.fees0,
1616
+ fees1: graphqlData.fees1,
1617
+ strategyToken: graphqlData.strategyToken,
1618
+ beaconName: graphqlData.beaconName,
1619
+ payloadIpfs: graphqlData.payloadIpfs,
1620
+ deployer: graphqlData.deployer,
1621
+ },
1622
+ // Update existing fields with GraphQL data if available
1623
+ name: graphqlData.name || vault.name,
1624
+ token0: graphqlData.token0 || vault.token0,
1625
+ token1: graphqlData.token1 || vault.token1,
1626
+ poolAddress: graphqlData.pool || vault.poolAddress,
1627
+ // Update TVL with GraphQL data if available
1628
+ tvl:
1629
+ vault.tvl ||
1630
+ this.calculateTvlFromBalances(
1631
+ graphqlData.token0Balance,
1632
+ graphqlData.token1Balance,
1633
+ parseInt(graphqlData.token0Decimals, 10) || 18,
1634
+ parseInt(graphqlData.token1Decimals, 10) || 18,
1635
+ ),
1636
+ // Calculate TVL from token balances if not available
1637
+ calculatedTvl: this.calculateTvlFromBalances(
1638
+ graphqlData.token0Balance,
1639
+ graphqlData.token1Balance,
1640
+ parseInt(graphqlData.token0Decimals, 10) || 18,
1641
+ parseInt(graphqlData.token1Decimals, 10) || 18,
1642
+ ),
1643
+ };
1644
+
1645
+ logger.log(
1646
+ `Successfully enriched vault ${vaultAddress} with GraphQL data`,
1647
+ );
1648
+ return enrichedVault;
1649
+ }
1650
+
1651
+ logger.log(
1652
+ `No GraphQL data available for vault ${vaultAddress}, returning original data`,
1653
+ );
1654
+ return vault;
1655
+ } catch (error) {
1656
+ logger.error(`Error enriching vault with GraphQL data:`, error);
1657
+ return vault; // Return original vault data if enrichment fails
1658
+ }
1659
+ }
1660
+
1661
+ /**
1662
+ * Calculate TVL from token balances using LP tokens as proxy
1663
+ */
1664
+ private calculateTvlFromBalances(
1665
+ token0Balance: string,
1666
+ token1Balance: string,
1667
+ token0Decimals: number,
1668
+ token1Decimals: number,
1669
+ ): number {
1670
+ try {
1671
+ // Convert string balances to numbers with proper decimals
1672
+ const token0Amount = parseFloat(token0Balance) / 10 ** token0Decimals;
1673
+ const token1Amount = parseFloat(token1Balance) / 10 ** token1Decimals;
1674
+
1675
+ logger.log(
1676
+ `Token balances - Token0: ${token0Amount}, Token1: ${token1Amount}`,
1677
+ );
1678
+
1679
+ // Use a simple estimation: assume average token price of $1
1680
+ // This is a rough approximation - in production you'd fetch real prices
1681
+ const estimatedTvl = (token0Amount + token1Amount) * 1;
1682
+
1683
+ logger.log(`Estimated TVL: $${estimatedTvl.toLocaleString()}`);
1684
+ return estimatedTvl;
1685
+ } catch (error) {
1686
+ logger.error("Error calculating TVL from balances:", error);
1687
+ return 0;
1688
+ }
1689
+ }
1690
+ }