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