@elizaos/plugin-wallet 2.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +64 -0
  3. package/auto-enable.ts +76 -0
  4. package/dist/LpManagementService-BWrQ5-cO.mjs +353 -0
  5. package/dist/MockLpService-D_Apn4Fd.mjs +99 -0
  6. package/dist/aerodrome-CfnESC32.mjs +890 -0
  7. package/dist/chunk-hT5z_Zn9.mjs +35 -0
  8. package/dist/index.d.mts +34727 -0
  9. package/dist/index.mjs +21590 -0
  10. package/dist/lib/server-wallet-trade.d.mts +34 -0
  11. package/dist/lib/server-wallet-trade.mjs +306 -0
  12. package/dist/meteora-BPX39hZo.mjs +22640 -0
  13. package/dist/orca-Bybp1HXO.mjs +249 -0
  14. package/dist/pancakeswp-CkEXlXti.mjs +604 -0
  15. package/dist/plugin-ZO_MTyd0.mjs +529 -0
  16. package/dist/raydium-rfaM9yEf.mjs +539 -0
  17. package/dist/sdk/index.d.mts +32492 -0
  18. package/dist/sdk/index.mjs +6415 -0
  19. package/dist/types-D5252NZk.mjs +487 -0
  20. package/dist/uniswap-CReXgXVN.mjs +573 -0
  21. package/dist/wallet-action.d.mts +6 -0
  22. package/dist/wallet-action.mjs +820 -0
  23. package/package.json +152 -0
  24. package/src/actions/failure-codes.ts +79 -0
  25. package/src/actions/index.ts +1 -0
  26. package/src/analytics/birdeye/actions/wallet-search-address.ts +9 -0
  27. package/src/analytics/birdeye/birdeye-task.ts +175 -0
  28. package/src/analytics/birdeye/birdeye.ts +813 -0
  29. package/src/analytics/birdeye/constants.ts +74 -0
  30. package/src/analytics/birdeye/providers/agent-portfolio-provider.ts +18 -0
  31. package/src/analytics/birdeye/providers/market.ts +227 -0
  32. package/src/analytics/birdeye/providers/portfolio-factory.test.ts +138 -0
  33. package/src/analytics/birdeye/providers/portfolio-factory.ts +252 -0
  34. package/src/analytics/birdeye/providers/trending.ts +365 -0
  35. package/src/analytics/birdeye/providers/wallet.ts +14 -0
  36. package/src/analytics/birdeye/search-category.test.ts +207 -0
  37. package/src/analytics/birdeye/search-category.ts +506 -0
  38. package/src/analytics/birdeye/service.ts +992 -0
  39. package/src/analytics/birdeye/tasks/birdeye.ts +232 -0
  40. package/src/analytics/birdeye/types/api/common.ts +305 -0
  41. package/src/analytics/birdeye/types/api/defi.ts +220 -0
  42. package/src/analytics/birdeye/types/api/pair.ts +200 -0
  43. package/src/analytics/birdeye/types/api/search.ts +86 -0
  44. package/src/analytics/birdeye/types/api/token.ts +635 -0
  45. package/src/analytics/birdeye/types/api/trader.ts +76 -0
  46. package/src/analytics/birdeye/types/api/wallet.ts +181 -0
  47. package/src/analytics/birdeye/types/shared.ts +106 -0
  48. package/src/analytics/birdeye/utils.ts +700 -0
  49. package/src/analytics/dexscreener/errors.ts +28 -0
  50. package/src/analytics/dexscreener/index.ts +3 -0
  51. package/src/analytics/dexscreener/search-category.test.ts +49 -0
  52. package/src/analytics/dexscreener/search-category.ts +42 -0
  53. package/src/analytics/dexscreener/service.ts +595 -0
  54. package/src/analytics/dexscreener/types.ts +128 -0
  55. package/src/analytics/lpinfo/index.d.ts +7 -0
  56. package/src/analytics/lpinfo/index.ts +52 -0
  57. package/src/analytics/lpinfo/kamino/README.md +102 -0
  58. package/src/analytics/lpinfo/kamino/index.ts +24 -0
  59. package/src/analytics/lpinfo/kamino/providers/kaminoLiquidityProvider.ts +422 -0
  60. package/src/analytics/lpinfo/kamino/providers/kaminoPoolProvider.ts +365 -0
  61. package/src/analytics/lpinfo/kamino/providers/kaminoProvider.ts +496 -0
  62. package/src/analytics/lpinfo/kamino/services/kaminoLiquidityService.ts +1123 -0
  63. package/src/analytics/lpinfo/kamino/services/kaminoService.ts +758 -0
  64. package/src/analytics/lpinfo/steer/README.md +169 -0
  65. package/src/analytics/lpinfo/steer/index.ts +23 -0
  66. package/src/analytics/lpinfo/steer/providers/steerLiquidityProvider.ts +544 -0
  67. package/src/analytics/lpinfo/steer/services/steerLiquidityService.ts +1690 -0
  68. package/src/analytics/lpinfo/steer/steer-display-types.ts +99 -0
  69. package/src/analytics/news/index.ts +52 -0
  70. package/src/analytics/news/interfaces/types.ts +222 -0
  71. package/src/analytics/news/providers/defiNewsProvider.ts +734 -0
  72. package/src/analytics/news/services/newsDataService.ts +332 -0
  73. package/src/analytics/news/utils/formatters.ts +151 -0
  74. package/src/analytics/token-info/action.ts +240 -0
  75. package/src/analytics/token-info/index.ts +3 -0
  76. package/src/analytics/token-info/params.ts +215 -0
  77. package/src/analytics/token-info/providers.ts +681 -0
  78. package/src/analytics/token-info/service.ts +168 -0
  79. package/src/analytics/token-info/types.ts +74 -0
  80. package/src/audit/audit-log.ts +45 -0
  81. package/src/browser-shim/build-shim.ts +123 -0
  82. package/src/browser-shim/index.ts +5 -0
  83. package/src/browser-shim/shim.template.js +563 -0
  84. package/src/chains/evm/.github/workflows/npm-deploy.yml +112 -0
  85. package/src/chains/evm/LICENSE +21 -0
  86. package/src/chains/evm/README.md +106 -0
  87. package/src/chains/evm/actions/helpers.ts +147 -0
  88. package/src/chains/evm/actions/swap.ts +839 -0
  89. package/src/chains/evm/actions/transfer.ts +254 -0
  90. package/src/chains/evm/biome.json +61 -0
  91. package/src/chains/evm/bridge-router.ts +660 -0
  92. package/src/chains/evm/build.ts +89 -0
  93. package/src/chains/evm/chain-handler.ts +416 -0
  94. package/src/chains/evm/constants.ts +23 -0
  95. package/src/chains/evm/contracts/artifacts/OZGovernor.json +1707 -0
  96. package/src/chains/evm/contracts/artifacts/TimelockController.json +1007 -0
  97. package/src/chains/evm/contracts/artifacts/VoteToken.json +895 -0
  98. package/src/chains/evm/dex/aerodrome/index.ts +34 -0
  99. package/src/chains/evm/dex/aerodrome/services/AerodromeLpService.ts +558 -0
  100. package/src/chains/evm/dex/aerodrome/types.ts +318 -0
  101. package/src/chains/evm/dex/pancakeswp/index.ts +35 -0
  102. package/src/chains/evm/dex/pancakeswp/services/PancakeSwapV3LpService.ts +743 -0
  103. package/src/chains/evm/dex/pancakeswp/types.ts +65 -0
  104. package/src/chains/evm/dex/uniswap/index.ts +35 -0
  105. package/src/chains/evm/dex/uniswap/services/UniswapV3LpService.ts +759 -0
  106. package/src/chains/evm/dex/uniswap/types.ts +390 -0
  107. package/src/chains/evm/generated/specs/spec-helpers.ts +73 -0
  108. package/src/chains/evm/generated/specs/specs.ts +151 -0
  109. package/src/chains/evm/gov-router.ts +250 -0
  110. package/src/chains/evm/index.browser.ts +16 -0
  111. package/src/chains/evm/index.ts +31 -0
  112. package/src/chains/evm/prompts.ts +193 -0
  113. package/src/chains/evm/providers/get-balance.ts +123 -0
  114. package/src/chains/evm/providers/wallet.ts +715 -0
  115. package/src/chains/evm/routes/sign.ts +333 -0
  116. package/src/chains/evm/rpc-providers.ts +410 -0
  117. package/src/chains/evm/service.ts +140 -0
  118. package/src/chains/evm/templates/index.ts +10 -0
  119. package/src/chains/evm/types/index.ts +432 -0
  120. package/src/chains/evm/vitest.config.ts +18 -0
  121. package/src/chains/registry.ts +668 -0
  122. package/src/chains/solana/README.md +367 -0
  123. package/src/chains/wallet-action.ts +533 -0
  124. package/src/chains/wallet-router.test.ts +296 -0
  125. package/src/contracts.ts +65 -0
  126. package/src/core-augmentation.ts +10 -0
  127. package/src/index.ts +71 -0
  128. package/src/lib/server-wallet-trade.ts +192 -0
  129. package/src/lib/wallet-export-guard.ts +330 -0
  130. package/src/lp/actions/liquidity.ts +827 -0
  131. package/src/lp/e2e/real-token-tests.ts +428 -0
  132. package/src/lp/e2e/scenarios.ts +470 -0
  133. package/src/lp/e2e/test-utils.ts +145 -0
  134. package/src/lp/lp-manager-entry.ts +303 -0
  135. package/src/lp/services/ConcentratedLiquidityService.ts +120 -0
  136. package/src/lp/services/DexInteractionService.ts +226 -0
  137. package/src/lp/services/LpManagementService.test.ts +148 -0
  138. package/src/lp/services/LpManagementService.ts +632 -0
  139. package/src/lp/services/UserLpProfileService.ts +163 -0
  140. package/src/lp/services/VaultService.ts +153 -0
  141. package/src/lp/services/YieldOptimizationService.ts +344 -0
  142. package/src/lp/services/__tests__/MockLpService.ts +146 -0
  143. package/src/lp/tasks/LpAutoRebalanceTask.ts +117 -0
  144. package/src/lp/tasks/__tests__/LpAutoRebalanceTask.test.ts +370 -0
  145. package/src/lp/types.ts +582 -0
  146. package/src/lp/utils/solanaClient.ts +143 -0
  147. package/src/plugin.ts +125 -0
  148. package/src/policy/policy.ts +19 -0
  149. package/src/providers/canonical-provider.ts +27 -0
  150. package/src/providers/unified-wallet-provider.ts +79 -0
  151. package/src/register-routes.ts +11 -0
  152. package/src/routes/plugin.ts +47 -0
  153. package/src/routes/wallet-market-overview-route.ts +869 -0
  154. package/src/sdk/abi.ts +258 -0
  155. package/src/sdk/bridge/abis.ts +126 -0
  156. package/src/sdk/bridge/client.ts +518 -0
  157. package/src/sdk/bridge/index.ts +56 -0
  158. package/src/sdk/bridge/solana.ts +604 -0
  159. package/src/sdk/bridge/types.ts +202 -0
  160. package/src/sdk/convenience.ts +347 -0
  161. package/src/sdk/escrow/MutualStakeEscrow.ts +480 -0
  162. package/src/sdk/escrow/types.ts +64 -0
  163. package/src/sdk/escrow/verifiers.ts +73 -0
  164. package/src/sdk/identity/erc8004.ts +692 -0
  165. package/src/sdk/identity/reputation.ts +449 -0
  166. package/src/sdk/identity/uaid.ts +497 -0
  167. package/src/sdk/identity/validation.ts +372 -0
  168. package/src/sdk/index.ts +763 -0
  169. package/src/sdk/policy/SpendingPolicy.ts +260 -0
  170. package/src/sdk/policy/UptoBillingPolicy.ts +320 -0
  171. package/src/sdk/router/PaymentRouter.ts +215 -0
  172. package/src/sdk/router/index.ts +8 -0
  173. package/src/sdk/swap/SwapModule.ts +310 -0
  174. package/src/sdk/swap/abi.ts +117 -0
  175. package/src/sdk/swap/index.ts +34 -0
  176. package/src/sdk/swap/types.ts +135 -0
  177. package/src/sdk/tokens/decimals.ts +140 -0
  178. package/src/sdk/tokens/registry.ts +911 -0
  179. package/src/sdk/tokens/solana.ts +419 -0
  180. package/src/sdk/tokens/transfers.ts +327 -0
  181. package/src/sdk/types.ts +158 -0
  182. package/src/sdk/wallet-core.ts +115 -0
  183. package/src/sdk/x402/budget.ts +168 -0
  184. package/src/sdk/x402/chains/abstract/index.ts +280 -0
  185. package/src/sdk/x402/client.ts +320 -0
  186. package/src/sdk/x402/index.ts +46 -0
  187. package/src/sdk/x402/middleware.ts +92 -0
  188. package/src/sdk/x402/multi-asset.ts +144 -0
  189. package/src/sdk/x402/types.ts +156 -0
  190. package/src/services/wallet-backend-service.ts +328 -0
  191. package/src/types/wallet-router.ts +227 -0
  192. package/src/utils/intent-trajectory.ts +106 -0
  193. package/src/wallet/backend.ts +62 -0
  194. package/src/wallet/errors.ts +49 -0
  195. package/src/wallet/index.ts +27 -0
  196. package/src/wallet/local-eoa-backend.ts +201 -0
  197. package/src/wallet/pending.ts +60 -0
  198. package/src/wallet/select-backend.ts +47 -0
  199. package/src/wallet/steward-backend.ts +161 -0
  200. package/src/wallet-action.ts +1 -0
@@ -0,0 +1,146 @@
1
+ // @ts-nocheck — test-only mock for LP registry and E2E setup
2
+ import { type IAgentRuntime, Service } from "@elizaos/core";
3
+ import type {
4
+ AddLiquidityConfig,
5
+ ILpService,
6
+ LpPositionDetails,
7
+ PoolInfo,
8
+ RemoveLiquidityConfig,
9
+ TransactionResult,
10
+ } from "../../types.ts";
11
+ import {
12
+ createSolanaLpProtocolProvider,
13
+ getLpManagementService,
14
+ } from "../LpManagementService.ts";
15
+
16
+ export class MockLpService extends Service implements ILpService {
17
+ public readonly capabilityDescription =
18
+ "Test-only LP service for registry scenarios.";
19
+ private positions = new Map<string, LpPositionDetails[]>();
20
+
21
+ constructor(
22
+ runtime: IAgentRuntime,
23
+ private readonly dexName = "mock-dex",
24
+ ) {
25
+ super(runtime);
26
+ }
27
+
28
+ getDexName(): string {
29
+ return this.dexName;
30
+ }
31
+
32
+ async getPools(
33
+ tokenAMint?: string,
34
+ tokenBMint?: string,
35
+ ): Promise<PoolInfo[]> {
36
+ const pools: PoolInfo[] = [
37
+ {
38
+ id: `${this.dexName}-sol-usdc`,
39
+ displayName: `${this.dexName} SOL/USDC`,
40
+ dex: this.dexName,
41
+ tokenA: {
42
+ mint: "So11111111111111111111111111111111111111112",
43
+ symbol: "SOL",
44
+ decimals: 9,
45
+ },
46
+ tokenB: {
47
+ mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
48
+ symbol: "USDC",
49
+ decimals: 6,
50
+ },
51
+ apr: 12,
52
+ tvl: 1000000,
53
+ },
54
+ ];
55
+
56
+ if (!tokenAMint && !tokenBMint) return pools;
57
+ return pools.filter((pool) => {
58
+ const mints = [pool.tokenA?.mint, pool.tokenB?.mint];
59
+ return (
60
+ (!tokenAMint || mints.includes(tokenAMint)) &&
61
+ (!tokenBMint || mints.includes(tokenBMint))
62
+ );
63
+ });
64
+ }
65
+
66
+ async addLiquidity(config: AddLiquidityConfig): Promise<TransactionResult> {
67
+ const owner = config.userVault?.publicKey?.toBase58?.() || "test-owner";
68
+ const position: LpPositionDetails = {
69
+ poolId: config.poolId,
70
+ dex: this.dexName,
71
+ lpTokenBalance: {
72
+ address: `lp-${config.poolId}`,
73
+ balance: "1000",
74
+ decimals: 6,
75
+ symbol: "LP",
76
+ },
77
+ underlyingTokens: [],
78
+ accruedFees: [],
79
+ rewards: [],
80
+ valueUsd: 100,
81
+ };
82
+ this.positions.set(owner, [...(this.positions.get(owner) || []), position]);
83
+ return {
84
+ success: true,
85
+ transactionId: `mock-open-${this.dexName}`,
86
+ data: { poolId: config.poolId },
87
+ };
88
+ }
89
+
90
+ async removeLiquidity(
91
+ config: RemoveLiquidityConfig,
92
+ ): Promise<TransactionResult> {
93
+ const owner = config.userVault?.publicKey?.toBase58?.() || "test-owner";
94
+ this.positions.set(
95
+ owner,
96
+ (this.positions.get(owner) || []).filter(
97
+ (position) => position.poolId !== config.poolId,
98
+ ),
99
+ );
100
+ return {
101
+ success: true,
102
+ transactionId: `mock-close-${this.dexName}`,
103
+ data: { poolId: config.poolId },
104
+ };
105
+ }
106
+
107
+ async getLpPositionDetails(
108
+ owner: string,
109
+ poolOrPositionIdentifier: string,
110
+ ): Promise<LpPositionDetails | null> {
111
+ return (
112
+ (this.positions.get(owner) || []).find(
113
+ (position) => position.poolId === poolOrPositionIdentifier,
114
+ ) || null
115
+ );
116
+ }
117
+
118
+ async getMarketDataForPools(
119
+ poolIds: string[],
120
+ ): Promise<Record<string, Partial<PoolInfo>>> {
121
+ return Object.fromEntries(
122
+ poolIds.map((poolId) => [poolId, { apr: 12, tvl: 1000000 }]),
123
+ );
124
+ }
125
+
126
+ async start(): Promise<void> {}
127
+ async stop(): Promise<void> {}
128
+ }
129
+
130
+ export async function registerMockDexServices(
131
+ runtime: IAgentRuntime,
132
+ ): Promise<void> {
133
+ const registry = await getLpManagementService(runtime);
134
+ if (!registry) return;
135
+
136
+ for (const dex of ["raydium", "orca", "meteora"]) {
137
+ const service = new MockLpService(runtime, dex);
138
+ registry.registerProtocol(
139
+ createSolanaLpProtocolProvider({
140
+ dex,
141
+ label: dex,
142
+ service,
143
+ }),
144
+ );
145
+ }
146
+ }
@@ -0,0 +1,117 @@
1
+ // @ts-nocheck — legacy code from absorbed plugins (lp-manager, lpinfo, dexscreener, defi-news, birdeye); strict types pending cleanup
2
+ import { type IAgentRuntime, logger } from "@elizaos/core";
3
+
4
+ /**
5
+ * LpAutoRebalanceTask - Automated task for rebalancing LP positions
6
+ *
7
+ * This task monitors LP positions and triggers rebalancing when positions
8
+ * drift outside acceptable ranges.
9
+ */
10
+ export class LpAutoRebalanceTask {
11
+ private runtime: IAgentRuntime | null = null;
12
+ private intervalId: ReturnType<typeof setInterval> | null = null;
13
+ private isRunning = false;
14
+
15
+ // Configuration
16
+ private checkIntervalMs = 60000; // Default: check every minute
17
+ private rebalanceThresholdBps = 500; // Default: 5% drift threshold
18
+
19
+ constructor(config?: {
20
+ checkIntervalMs?: number;
21
+ rebalanceThresholdBps?: number;
22
+ }) {
23
+ if (config?.checkIntervalMs) {
24
+ this.checkIntervalMs = config.checkIntervalMs;
25
+ }
26
+ if (config?.rebalanceThresholdBps) {
27
+ this.rebalanceThresholdBps = config.rebalanceThresholdBps;
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Start the auto-rebalance task
33
+ */
34
+ async start(runtime: IAgentRuntime): Promise<void> {
35
+ if (this.isRunning) {
36
+ logger.warn("[LpAutoRebalanceTask] Task is already running");
37
+ return;
38
+ }
39
+
40
+ this.runtime = runtime;
41
+ this.isRunning = true;
42
+
43
+ logger.info(
44
+ `[LpAutoRebalanceTask] Starting with interval ${this.checkIntervalMs}ms and threshold ${this.rebalanceThresholdBps}bps`,
45
+ );
46
+
47
+ // Run initial check
48
+ await this.checkAndRebalance();
49
+
50
+ // Set up periodic checks
51
+ this.intervalId = setInterval(async () => {
52
+ await this.checkAndRebalance();
53
+ }, this.checkIntervalMs);
54
+ }
55
+
56
+ /**
57
+ * Stop the auto-rebalance task
58
+ */
59
+ stop(): void {
60
+ if (this.intervalId) {
61
+ clearInterval(this.intervalId);
62
+ this.intervalId = null;
63
+ }
64
+ this.isRunning = false;
65
+ logger.info("[LpAutoRebalanceTask] Stopped");
66
+ }
67
+
68
+ /**
69
+ * Check positions and rebalance if needed
70
+ */
71
+ private async checkAndRebalance(): Promise<void> {
72
+ if (!this.runtime) {
73
+ logger.error("[LpAutoRebalanceTask] Runtime not initialized");
74
+ return;
75
+ }
76
+
77
+ try {
78
+ logger.debug("[LpAutoRebalanceTask] Checking positions...");
79
+
80
+ // Get DexInteractionService to check positions
81
+ const dexService = this.runtime.getService("dex-interaction");
82
+ if (!dexService) {
83
+ logger.debug(
84
+ "[LpAutoRebalanceTask] DexInteractionService not available",
85
+ );
86
+ return;
87
+ }
88
+
89
+ // The actual rebalancing logic would be implemented here
90
+ // This is a placeholder that can be extended with real DEX integration
91
+ logger.debug("[LpAutoRebalanceTask] Position check complete");
92
+ } catch (error: unknown) {
93
+ logger.error(
94
+ "[LpAutoRebalanceTask] Error checking positions:",
95
+ error instanceof Error ? error.message : String(error),
96
+ );
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Get current status of the task
102
+ */
103
+ getStatus(): {
104
+ isRunning: boolean;
105
+ checkIntervalMs: number;
106
+ rebalanceThresholdBps: number;
107
+ } {
108
+ return {
109
+ isRunning: this.isRunning,
110
+ checkIntervalMs: this.checkIntervalMs,
111
+ rebalanceThresholdBps: this.rebalanceThresholdBps,
112
+ };
113
+ }
114
+ }
115
+
116
+ // Export a default instance for convenience
117
+ export const lpAutoRebalanceTask = new LpAutoRebalanceTask();
@@ -0,0 +1,370 @@
1
+ // @ts-nocheck — legacy code from absorbed plugins (lp-manager, lpinfo, dexscreener, defi-news, birdeye); strict types pending cleanup
2
+ /// <reference types="vitest/globals" />
3
+ import type { IAgentRuntime } from "@elizaos/core";
4
+ import { Keypair as SolanaKeypair } from "@solana/web3.js";
5
+ import {
6
+ beforeEach,
7
+ describe,
8
+ expect,
9
+ it,
10
+ type Mock,
11
+ type Mocked,
12
+ vi,
13
+ } from "vitest";
14
+ import type {
15
+ IDexInteractionService,
16
+ IUserLpProfileService,
17
+ IVaultService,
18
+ IYieldOptimizationService,
19
+ LpPositionDetails,
20
+ OptimizationOpportunity,
21
+ PoolInfo,
22
+ TokenBalance,
23
+ TransactionResult,
24
+ UserLpProfile,
25
+ } from "../../types.ts";
26
+ import { LpAutoRebalanceTask } from "../LpAutoRebalanceTask.ts";
27
+
28
+ // Extended transaction results (matching the types in LpAutoRebalanceTask)
29
+ interface RemoveLiquidityResult extends TransactionResult {
30
+ tokensReceived?: TokenBalance[];
31
+ }
32
+
33
+ interface AddLiquidityResult extends TransactionResult {
34
+ lpTokensReceived?: TokenBalance;
35
+ }
36
+
37
+ // Create a mock runtime with getService method
38
+ const createMockRuntime = (services: Record<string, any>): IAgentRuntime => {
39
+ return {
40
+ getService: vi.fn((serviceName: string) => services[serviceName]),
41
+ } as any as IAgentRuntime;
42
+ };
43
+
44
+ const mockUserLpProfileService: Mocked<IUserLpProfileService> = {
45
+ ensureProfile: vi.fn(),
46
+ getProfile: vi.fn(),
47
+ updateProfile: vi.fn(),
48
+ addTrackedPosition: vi.fn(),
49
+ removeTrackedPosition: vi.fn(),
50
+ getTrackedPositions: vi.fn(),
51
+ getAllProfilesWithAutoRebalanceEnabled: vi.fn(),
52
+ start: vi.fn(),
53
+ stop: vi.fn(),
54
+ capabilityDescription: "Mock UserLpProfileService",
55
+ } as any;
56
+
57
+ const mockDexInteractionService: Mocked<IDexInteractionService> = {
58
+ registerDexService: vi.fn(),
59
+ getPools: vi.fn(),
60
+ addLiquidity: vi.fn(),
61
+ removeLiquidity: vi.fn(),
62
+ getLpPosition: vi.fn(),
63
+ getAllUserLpPositions: vi.fn(),
64
+ start: vi.fn(),
65
+ stop: vi.fn(),
66
+ capabilityDescription: "Mock DexInteractionService",
67
+ } as any;
68
+
69
+ const mockYieldOptimizationService: Mocked<IYieldOptimizationService> = {
70
+ fetchAllPoolData: vi.fn(),
71
+ findBestYieldOpportunities: vi.fn(),
72
+ calculateRebalanceCost: vi.fn(),
73
+ findBestYield: vi.fn(),
74
+ start: vi.fn(),
75
+ stop: vi.fn(),
76
+ capabilityDescription: "Mock YieldOptimizationService",
77
+ } as any;
78
+
79
+ const mockVaultService: Mocked<IVaultService> = {
80
+ createVault: vi.fn(),
81
+ getVaultKeypair: vi.fn(),
82
+ getVaultPublicKey: vi.fn(),
83
+ getBalances: vi.fn(),
84
+ exportPrivateKey: vi.fn(),
85
+ start: vi.fn(),
86
+ stop: vi.fn(),
87
+ capabilityDescription: "Mock VaultService",
88
+ } as any;
89
+
90
+ const testUserKeypair = SolanaKeypair.generate();
91
+
92
+ const testUserId1 = "rebalanceUser1";
93
+ const testUserProfile1: UserLpProfile = {
94
+ userId: testUserId1,
95
+ vaultPublicKey: testUserKeypair.publicKey.toBase58(),
96
+ encryptedSecretKey: "key1",
97
+ autoRebalanceConfig: {
98
+ enabled: true,
99
+ minGainThresholdPercent: 1,
100
+ maxSlippageBps: 50,
101
+ preferredDexes: ["meteora"],
102
+ },
103
+ version: 1,
104
+ createdAt: "now",
105
+ updatedAt: "now",
106
+ trackedPositions: [],
107
+ };
108
+
109
+ // Updated Mock Data with correct property names
110
+ const SOL_MINT = "So11111111111111111111111111111111111111112";
111
+ const USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
112
+
113
+ const testLpPosition1: LpPositionDetails = {
114
+ poolId: "currentPool1",
115
+ dex: "orca",
116
+ valueUsd: 1000,
117
+ lpTokenBalance: {
118
+ address: "lpMint1",
119
+ balance: "100",
120
+ decimals: 6,
121
+ symbol: "ORCA_LP_SOL_USDC",
122
+ uiAmount: 0.0001,
123
+ },
124
+ underlyingTokens: [
125
+ {
126
+ address: SOL_MINT,
127
+ balance: "10",
128
+ decimals: 9,
129
+ symbol: "SOL",
130
+ uiAmount: 0.00000001,
131
+ },
132
+ {
133
+ address: USDC_MINT,
134
+ balance: "500",
135
+ decimals: 6,
136
+ symbol: "USDC",
137
+ uiAmount: 0.0005,
138
+ },
139
+ ],
140
+ };
141
+
142
+ const targetPool1: PoolInfo = {
143
+ id: "targetPoolGood",
144
+ dex: "meteora",
145
+ tokenA: { mint: SOL_MINT, symbol: "SOL", decimals: 9 },
146
+ tokenB: { mint: USDC_MINT, symbol: "USDC", decimals: 6 },
147
+ apr: 0.25,
148
+ displayName: "Good Yield Pool (SOL/USDC)",
149
+ };
150
+
151
+ const goodOpportunity: OptimizationOpportunity = {
152
+ sourcePosition: testLpPosition1,
153
+ targetPool: targetPool1,
154
+ estimatedNewYield: 25,
155
+ currentYield: 10,
156
+ netGainPercent: 5,
157
+ actions: ["Withdraw from orca", "Deposit to meteora"],
158
+ estimatedCostToMoveLamports: "5000",
159
+ };
160
+
161
+ describe("LpAutoRebalanceTask", () => {
162
+ let task: LpAutoRebalanceTask;
163
+ let mockRuntime: IAgentRuntime;
164
+
165
+ beforeEach(async () => {
166
+ vi.clearAllMocks();
167
+
168
+ // Create mock runtime with services
169
+ mockRuntime = createMockRuntime({
170
+ UserLpProfileService: mockUserLpProfileService,
171
+ "dex-interaction": mockDexInteractionService,
172
+ YieldOptimizationService: mockYieldOptimizationService,
173
+ VaultService: mockVaultService,
174
+ });
175
+
176
+ // Create task and initialize it
177
+ task = new LpAutoRebalanceTask();
178
+ await task.onReady(mockRuntime);
179
+
180
+ // Setup default mock behaviors
181
+ (
182
+ mockUserLpProfileService.getAllProfilesWithAutoRebalanceEnabled as Mock
183
+ ).mockResolvedValue([testUserProfile1]);
184
+ (mockDexInteractionService.getAllUserLpPositions as Mock).mockResolvedValue(
185
+ [testLpPosition1],
186
+ );
187
+ (
188
+ mockYieldOptimizationService.findBestYieldOpportunities as Mock
189
+ ).mockResolvedValue([goodOpportunity]);
190
+ (mockVaultService.getVaultKeypair as Mock).mockImplementation(
191
+ async (userId, secret) => {
192
+ if (
193
+ userId === testUserId1 &&
194
+ secret === testUserProfile1.encryptedSecretKey
195
+ ) {
196
+ return testUserKeypair;
197
+ }
198
+ console.error(
199
+ `MockVaultService.getVaultKeypair called with unexpected args: userId=${userId}, secret=${secret}`,
200
+ );
201
+ return null;
202
+ },
203
+ );
204
+
205
+ const removeResult: RemoveLiquidityResult = {
206
+ success: true,
207
+ tokensReceived: testLpPosition1.underlyingTokens,
208
+ };
209
+ (mockDexInteractionService.removeLiquidity as Mock).mockResolvedValue(
210
+ removeResult,
211
+ );
212
+
213
+ const addResult: AddLiquidityResult = {
214
+ success: true,
215
+ transactionId: "txAddLiq",
216
+ };
217
+ (mockDexInteractionService.addLiquidity as Mock).mockResolvedValue(
218
+ addResult,
219
+ );
220
+ });
221
+
222
+ describe("execute", () => {
223
+ it("should fetch enabled profiles and process them", async () => {
224
+ await task.execute();
225
+ expect(
226
+ mockUserLpProfileService.getAllProfilesWithAutoRebalanceEnabled,
227
+ ).toHaveBeenCalled();
228
+ expect(
229
+ mockYieldOptimizationService.findBestYieldOpportunities,
230
+ ).toHaveBeenCalledWith(testUserId1, [testLpPosition1], []);
231
+ });
232
+
233
+ it("should handle errors when processing a user profile", async () => {
234
+ const userProfile2 = { ...testUserProfile1, userId: "rebalanceUser2" };
235
+ (
236
+ mockUserLpProfileService.getAllProfilesWithAutoRebalanceEnabled as Mock
237
+ ).mockResolvedValue([testUserProfile1, userProfile2]);
238
+ (mockDexInteractionService.getAllUserLpPositions as Mock)
239
+ .mockRejectedValueOnce(new Error("Failed to get positions for user1"))
240
+ .mockResolvedValueOnce([]); // For user2, no positions
241
+
242
+ await task.execute();
243
+ // Should continue processing other users despite error
244
+ expect(
245
+ mockDexInteractionService.getAllUserLpPositions,
246
+ ).toHaveBeenCalledTimes(2);
247
+ });
248
+ });
249
+
250
+ describe("processUserProfile", () => {
251
+ it("should execute rebalance if a good opportunity is found and meets criteria", async () => {
252
+ // The task should find opportunities and execute rebalance
253
+ await task.execute();
254
+
255
+ expect(
256
+ mockYieldOptimizationService.findBestYieldOpportunities,
257
+ ).toHaveBeenCalledWith(testUserProfile1.userId, [testLpPosition1], []);
258
+ expect(mockVaultService.getVaultKeypair).toHaveBeenCalledWith(
259
+ testUserProfile1.userId,
260
+ testUserProfile1.encryptedSecretKey,
261
+ );
262
+ expect(mockDexInteractionService.removeLiquidity).toHaveBeenCalledTimes(
263
+ 1,
264
+ );
265
+ expect(mockDexInteractionService.addLiquidity).toHaveBeenCalledTimes(1);
266
+ });
267
+
268
+ it("should handle rebalance execution failures", async () => {
269
+ // Make addLiquidity fail
270
+ const addResult: AddLiquidityResult = {
271
+ success: false,
272
+ error: "DEX add liquidity failed",
273
+ };
274
+ (mockDexInteractionService.addLiquidity as Mock).mockResolvedValue(
275
+ addResult,
276
+ );
277
+
278
+ // Execute should complete without throwing
279
+ await task.execute();
280
+
281
+ // Verify error was handled gracefully
282
+ expect(mockDexInteractionService.removeLiquidity).toHaveBeenCalled();
283
+ expect(mockDexInteractionService.addLiquidity).toHaveBeenCalled();
284
+ });
285
+
286
+ it("should not execute if opportunity does not meet criteria", async () => {
287
+ // Create opportunity with low net gain
288
+ const lowGainOpp = { ...goodOpportunity, netGainPercent: 0.5 }; // Below 1% threshold
289
+ (
290
+ mockYieldOptimizationService.findBestYieldOpportunities as Mock
291
+ ).mockResolvedValue([lowGainOpp]);
292
+
293
+ await task.execute();
294
+
295
+ // Should not execute rebalance
296
+ expect(mockDexInteractionService.removeLiquidity).not.toHaveBeenCalled();
297
+ expect(mockDexInteractionService.addLiquidity).not.toHaveBeenCalled();
298
+ });
299
+
300
+ it("should not execute if target DEX is not in preferred list", async () => {
301
+ // Create opportunity with non-preferred DEX
302
+ const nonPreferredDexOpp = {
303
+ ...goodOpportunity,
304
+ targetPool: { ...targetPool1, dex: "raydium" },
305
+ };
306
+ (
307
+ mockYieldOptimizationService.findBestYieldOpportunities as Mock
308
+ ).mockResolvedValue([nonPreferredDexOpp]);
309
+
310
+ await task.execute();
311
+
312
+ expect(mockDexInteractionService.removeLiquidity).not.toHaveBeenCalled();
313
+ });
314
+ });
315
+
316
+ describe("executeRebalance edge cases", () => {
317
+ it("should handle remove liquidity failures", async () => {
318
+ const removeResult: RemoveLiquidityResult = {
319
+ success: false,
320
+ error: "Remove failed",
321
+ };
322
+ (mockDexInteractionService.removeLiquidity as Mock).mockResolvedValue(
323
+ removeResult,
324
+ );
325
+
326
+ await task.execute();
327
+
328
+ // Should not proceed to add liquidity
329
+ expect(mockDexInteractionService.addLiquidity).not.toHaveBeenCalled();
330
+ });
331
+
332
+ it("should handle token mismatch scenarios", async () => {
333
+ const mismatchedTokens: TokenBalance[] = [
334
+ {
335
+ address: "otherToken1",
336
+ balance: "1",
337
+ decimals: 6,
338
+ uiAmount: 0.000001,
339
+ },
340
+ {
341
+ address: "otherToken2",
342
+ balance: "1",
343
+ decimals: 6,
344
+ uiAmount: 0.000001,
345
+ },
346
+ ];
347
+ const removeResult: RemoveLiquidityResult = {
348
+ success: true,
349
+ tokensReceived: mismatchedTokens,
350
+ };
351
+ (mockDexInteractionService.removeLiquidity as Mock).mockResolvedValue(
352
+ removeResult,
353
+ );
354
+
355
+ await task.execute();
356
+
357
+ // Should not proceed to add liquidity due to token mismatch
358
+ expect(mockDexInteractionService.addLiquidity).not.toHaveBeenCalled();
359
+ });
360
+
361
+ it("should handle null vault keypair", async () => {
362
+ (mockVaultService.getVaultKeypair as Mock).mockResolvedValue(null);
363
+
364
+ await task.execute();
365
+
366
+ // Should not proceed with rebalance
367
+ expect(mockDexInteractionService.removeLiquidity).not.toHaveBeenCalled();
368
+ });
369
+ });
370
+ });