@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.
- package/LICENSE +21 -0
- package/README.md +64 -0
- package/auto-enable.ts +76 -0
- package/dist/LpManagementService-BWrQ5-cO.mjs +353 -0
- package/dist/MockLpService-D_Apn4Fd.mjs +99 -0
- package/dist/aerodrome-CfnESC32.mjs +890 -0
- package/dist/chunk-hT5z_Zn9.mjs +35 -0
- package/dist/index.d.mts +34727 -0
- package/dist/index.mjs +21590 -0
- package/dist/lib/server-wallet-trade.d.mts +34 -0
- package/dist/lib/server-wallet-trade.mjs +306 -0
- package/dist/meteora-BPX39hZo.mjs +22640 -0
- package/dist/orca-Bybp1HXO.mjs +249 -0
- package/dist/pancakeswp-CkEXlXti.mjs +604 -0
- package/dist/plugin-ZO_MTyd0.mjs +529 -0
- package/dist/raydium-rfaM9yEf.mjs +539 -0
- package/dist/sdk/index.d.mts +32492 -0
- package/dist/sdk/index.mjs +6415 -0
- package/dist/types-D5252NZk.mjs +487 -0
- package/dist/uniswap-CReXgXVN.mjs +573 -0
- package/dist/wallet-action.d.mts +6 -0
- package/dist/wallet-action.mjs +820 -0
- package/package.json +152 -0
- package/src/actions/failure-codes.ts +79 -0
- package/src/actions/index.ts +1 -0
- package/src/analytics/birdeye/actions/wallet-search-address.ts +9 -0
- package/src/analytics/birdeye/birdeye-task.ts +175 -0
- package/src/analytics/birdeye/birdeye.ts +813 -0
- package/src/analytics/birdeye/constants.ts +74 -0
- package/src/analytics/birdeye/providers/agent-portfolio-provider.ts +18 -0
- package/src/analytics/birdeye/providers/market.ts +227 -0
- package/src/analytics/birdeye/providers/portfolio-factory.test.ts +138 -0
- package/src/analytics/birdeye/providers/portfolio-factory.ts +252 -0
- package/src/analytics/birdeye/providers/trending.ts +365 -0
- package/src/analytics/birdeye/providers/wallet.ts +14 -0
- package/src/analytics/birdeye/search-category.test.ts +207 -0
- package/src/analytics/birdeye/search-category.ts +506 -0
- package/src/analytics/birdeye/service.ts +992 -0
- package/src/analytics/birdeye/tasks/birdeye.ts +232 -0
- package/src/analytics/birdeye/types/api/common.ts +305 -0
- package/src/analytics/birdeye/types/api/defi.ts +220 -0
- package/src/analytics/birdeye/types/api/pair.ts +200 -0
- package/src/analytics/birdeye/types/api/search.ts +86 -0
- package/src/analytics/birdeye/types/api/token.ts +635 -0
- package/src/analytics/birdeye/types/api/trader.ts +76 -0
- package/src/analytics/birdeye/types/api/wallet.ts +181 -0
- package/src/analytics/birdeye/types/shared.ts +106 -0
- package/src/analytics/birdeye/utils.ts +700 -0
- package/src/analytics/dexscreener/errors.ts +28 -0
- package/src/analytics/dexscreener/index.ts +3 -0
- package/src/analytics/dexscreener/search-category.test.ts +49 -0
- package/src/analytics/dexscreener/search-category.ts +42 -0
- package/src/analytics/dexscreener/service.ts +595 -0
- package/src/analytics/dexscreener/types.ts +128 -0
- package/src/analytics/lpinfo/index.d.ts +7 -0
- package/src/analytics/lpinfo/index.ts +52 -0
- package/src/analytics/lpinfo/kamino/README.md +102 -0
- package/src/analytics/lpinfo/kamino/index.ts +24 -0
- package/src/analytics/lpinfo/kamino/providers/kaminoLiquidityProvider.ts +422 -0
- package/src/analytics/lpinfo/kamino/providers/kaminoPoolProvider.ts +365 -0
- package/src/analytics/lpinfo/kamino/providers/kaminoProvider.ts +496 -0
- package/src/analytics/lpinfo/kamino/services/kaminoLiquidityService.ts +1123 -0
- package/src/analytics/lpinfo/kamino/services/kaminoService.ts +758 -0
- package/src/analytics/lpinfo/steer/README.md +169 -0
- package/src/analytics/lpinfo/steer/index.ts +23 -0
- package/src/analytics/lpinfo/steer/providers/steerLiquidityProvider.ts +544 -0
- package/src/analytics/lpinfo/steer/services/steerLiquidityService.ts +1690 -0
- package/src/analytics/lpinfo/steer/steer-display-types.ts +99 -0
- package/src/analytics/news/index.ts +52 -0
- package/src/analytics/news/interfaces/types.ts +222 -0
- package/src/analytics/news/providers/defiNewsProvider.ts +734 -0
- package/src/analytics/news/services/newsDataService.ts +332 -0
- package/src/analytics/news/utils/formatters.ts +151 -0
- package/src/analytics/token-info/action.ts +240 -0
- package/src/analytics/token-info/index.ts +3 -0
- package/src/analytics/token-info/params.ts +215 -0
- package/src/analytics/token-info/providers.ts +681 -0
- package/src/analytics/token-info/service.ts +168 -0
- package/src/analytics/token-info/types.ts +74 -0
- package/src/audit/audit-log.ts +45 -0
- package/src/browser-shim/build-shim.ts +123 -0
- package/src/browser-shim/index.ts +5 -0
- package/src/browser-shim/shim.template.js +563 -0
- package/src/chains/evm/.github/workflows/npm-deploy.yml +112 -0
- package/src/chains/evm/LICENSE +21 -0
- package/src/chains/evm/README.md +106 -0
- package/src/chains/evm/actions/helpers.ts +147 -0
- package/src/chains/evm/actions/swap.ts +839 -0
- package/src/chains/evm/actions/transfer.ts +254 -0
- package/src/chains/evm/biome.json +61 -0
- package/src/chains/evm/bridge-router.ts +660 -0
- package/src/chains/evm/build.ts +89 -0
- package/src/chains/evm/chain-handler.ts +416 -0
- package/src/chains/evm/constants.ts +23 -0
- package/src/chains/evm/contracts/artifacts/OZGovernor.json +1707 -0
- package/src/chains/evm/contracts/artifacts/TimelockController.json +1007 -0
- package/src/chains/evm/contracts/artifacts/VoteToken.json +895 -0
- package/src/chains/evm/dex/aerodrome/index.ts +34 -0
- package/src/chains/evm/dex/aerodrome/services/AerodromeLpService.ts +558 -0
- package/src/chains/evm/dex/aerodrome/types.ts +318 -0
- package/src/chains/evm/dex/pancakeswp/index.ts +35 -0
- package/src/chains/evm/dex/pancakeswp/services/PancakeSwapV3LpService.ts +743 -0
- package/src/chains/evm/dex/pancakeswp/types.ts +65 -0
- package/src/chains/evm/dex/uniswap/index.ts +35 -0
- package/src/chains/evm/dex/uniswap/services/UniswapV3LpService.ts +759 -0
- package/src/chains/evm/dex/uniswap/types.ts +390 -0
- package/src/chains/evm/generated/specs/spec-helpers.ts +73 -0
- package/src/chains/evm/generated/specs/specs.ts +151 -0
- package/src/chains/evm/gov-router.ts +250 -0
- package/src/chains/evm/index.browser.ts +16 -0
- package/src/chains/evm/index.ts +31 -0
- package/src/chains/evm/prompts.ts +193 -0
- package/src/chains/evm/providers/get-balance.ts +123 -0
- package/src/chains/evm/providers/wallet.ts +715 -0
- package/src/chains/evm/routes/sign.ts +333 -0
- package/src/chains/evm/rpc-providers.ts +410 -0
- package/src/chains/evm/service.ts +140 -0
- package/src/chains/evm/templates/index.ts +10 -0
- package/src/chains/evm/types/index.ts +432 -0
- package/src/chains/evm/vitest.config.ts +18 -0
- package/src/chains/registry.ts +668 -0
- package/src/chains/solana/README.md +367 -0
- package/src/chains/wallet-action.ts +533 -0
- package/src/chains/wallet-router.test.ts +296 -0
- package/src/contracts.ts +65 -0
- package/src/core-augmentation.ts +10 -0
- package/src/index.ts +71 -0
- package/src/lib/server-wallet-trade.ts +192 -0
- package/src/lib/wallet-export-guard.ts +330 -0
- package/src/lp/actions/liquidity.ts +827 -0
- package/src/lp/e2e/real-token-tests.ts +428 -0
- package/src/lp/e2e/scenarios.ts +470 -0
- package/src/lp/e2e/test-utils.ts +145 -0
- package/src/lp/lp-manager-entry.ts +303 -0
- package/src/lp/services/ConcentratedLiquidityService.ts +120 -0
- package/src/lp/services/DexInteractionService.ts +226 -0
- package/src/lp/services/LpManagementService.test.ts +148 -0
- package/src/lp/services/LpManagementService.ts +632 -0
- package/src/lp/services/UserLpProfileService.ts +163 -0
- package/src/lp/services/VaultService.ts +153 -0
- package/src/lp/services/YieldOptimizationService.ts +344 -0
- package/src/lp/services/__tests__/MockLpService.ts +146 -0
- package/src/lp/tasks/LpAutoRebalanceTask.ts +117 -0
- package/src/lp/tasks/__tests__/LpAutoRebalanceTask.test.ts +370 -0
- package/src/lp/types.ts +582 -0
- package/src/lp/utils/solanaClient.ts +143 -0
- package/src/plugin.ts +125 -0
- package/src/policy/policy.ts +19 -0
- package/src/providers/canonical-provider.ts +27 -0
- package/src/providers/unified-wallet-provider.ts +79 -0
- package/src/register-routes.ts +11 -0
- package/src/routes/plugin.ts +47 -0
- package/src/routes/wallet-market-overview-route.ts +869 -0
- package/src/sdk/abi.ts +258 -0
- package/src/sdk/bridge/abis.ts +126 -0
- package/src/sdk/bridge/client.ts +518 -0
- package/src/sdk/bridge/index.ts +56 -0
- package/src/sdk/bridge/solana.ts +604 -0
- package/src/sdk/bridge/types.ts +202 -0
- package/src/sdk/convenience.ts +347 -0
- package/src/sdk/escrow/MutualStakeEscrow.ts +480 -0
- package/src/sdk/escrow/types.ts +64 -0
- package/src/sdk/escrow/verifiers.ts +73 -0
- package/src/sdk/identity/erc8004.ts +692 -0
- package/src/sdk/identity/reputation.ts +449 -0
- package/src/sdk/identity/uaid.ts +497 -0
- package/src/sdk/identity/validation.ts +372 -0
- package/src/sdk/index.ts +763 -0
- package/src/sdk/policy/SpendingPolicy.ts +260 -0
- package/src/sdk/policy/UptoBillingPolicy.ts +320 -0
- package/src/sdk/router/PaymentRouter.ts +215 -0
- package/src/sdk/router/index.ts +8 -0
- package/src/sdk/swap/SwapModule.ts +310 -0
- package/src/sdk/swap/abi.ts +117 -0
- package/src/sdk/swap/index.ts +34 -0
- package/src/sdk/swap/types.ts +135 -0
- package/src/sdk/tokens/decimals.ts +140 -0
- package/src/sdk/tokens/registry.ts +911 -0
- package/src/sdk/tokens/solana.ts +419 -0
- package/src/sdk/tokens/transfers.ts +327 -0
- package/src/sdk/types.ts +158 -0
- package/src/sdk/wallet-core.ts +115 -0
- package/src/sdk/x402/budget.ts +168 -0
- package/src/sdk/x402/chains/abstract/index.ts +280 -0
- package/src/sdk/x402/client.ts +320 -0
- package/src/sdk/x402/index.ts +46 -0
- package/src/sdk/x402/middleware.ts +92 -0
- package/src/sdk/x402/multi-asset.ts +144 -0
- package/src/sdk/x402/types.ts +156 -0
- package/src/services/wallet-backend-service.ts +328 -0
- package/src/types/wallet-router.ts +227 -0
- package/src/utils/intent-trajectory.ts +106 -0
- package/src/wallet/backend.ts +62 -0
- package/src/wallet/errors.ts +49 -0
- package/src/wallet/index.ts +27 -0
- package/src/wallet/local-eoa-backend.ts +201 -0
- package/src/wallet/pending.ts +60 -0
- package/src/wallet/select-backend.ts +47 -0
- package/src/wallet/steward-backend.ts +161 -0
- 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
|
+
}
|