@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,734 @@
1
+ // @ts-nocheck — legacy code from absorbed plugins (lp-manager, lpinfo, dexscreener, defi-news, birdeye); strict types pending cleanup
2
+ import type { IAgentRuntime, Memory, Provider, State } from "@elizaos/core";
3
+ import type { NewsDataService } from "../services/newsDataService";
4
+
5
+ interface CoinGeckoDefiData {
6
+ defi_market_cap: string;
7
+ eth_market_cap: string;
8
+ defi_to_eth_ratio: string;
9
+ trading_volume_24h: string;
10
+ defi_dominance: string;
11
+ top_coin_name: string;
12
+ top_coin_defi_dominance: number;
13
+ }
14
+
15
+ interface CoinGeckoGlobalCryptoData {
16
+ active_cryptocurrencies: number;
17
+ markets: number;
18
+ total_market_cap: { usd: number };
19
+ total_volume: { usd: number };
20
+ market_cap_change_percentage_24h_usd: number;
21
+ market_cap_percentage?: Record<string, number>;
22
+ }
23
+
24
+ interface CoinGeckoSearchResult {
25
+ id: string;
26
+ platforms?: { solana?: string };
27
+ }
28
+
29
+ interface CoinGeckoCoinData {
30
+ name: string;
31
+ symbol: string;
32
+ market_data?: {
33
+ current_price?: { usd?: number };
34
+ market_cap?: { usd?: number };
35
+ total_volume?: { usd?: number };
36
+ price_change_percentage_24h?: number;
37
+ price_change_percentage_7d?: number;
38
+ price_change_percentage_30d?: number;
39
+ };
40
+ community_data?: {
41
+ twitter_followers?: number;
42
+ reddit_subscribers?: number;
43
+ };
44
+ }
45
+
46
+ interface CoinGeckoService {
47
+ getGlobalDefiData(): Promise<CoinGeckoDefiData>;
48
+ getGlobalCryptoData(): Promise<CoinGeckoGlobalCryptoData>;
49
+ searchCoin(symbol: string): Promise<CoinGeckoSearchResult[]>;
50
+ getCoinData(tokenId: string): Promise<CoinGeckoCoinData>;
51
+ }
52
+
53
+ interface BirdeyeSymbolOption {
54
+ symbol: string;
55
+ address: string;
56
+ }
57
+
58
+ interface BirdeyeLookupService {
59
+ lookupSymbolAllChains(symbol: string): Promise<BirdeyeSymbolOption[]>;
60
+ }
61
+
62
+ interface SolanaTokenInfoService {
63
+ getAddressType(address: string): Promise<string>;
64
+ getTokenSymbol(publicKey: object): Promise<string | null | undefined>;
65
+ }
66
+ const DEFI_NEWS_TEXT_LIMIT = 4000;
67
+
68
+ /**
69
+ * DeFi News Provider
70
+ *
71
+ * Automatically provides comprehensive DeFi and crypto market context to conversations.
72
+ * This provider is dynamic and fetches fresh data on each request.
73
+ *
74
+ * The provider aggregates data from:
75
+ * - Global DeFi market statistics (market cap, volume, dominance) - requires CoinGecko service
76
+ * - Global crypto market data (total market cap, active cryptocurrencies, dominance) - requires CoinGecko service
77
+ * - Latest crypto news from Brave New Coin RSS feed (top 5 articles) - always available
78
+ * - Token-specific data when mentioned - requires CoinGecko and optional Birdeye services
79
+ *
80
+ * The data is formatted as a comprehensive market report that can be used
81
+ * by the agent to provide informed responses about DeFi and crypto markets.
82
+ *
83
+ * Note: The CoinGecko service should be provided by the analytics plugin or similar.
84
+ * If not available, the provider will still work with news data only.
85
+ *
86
+ * @example
87
+ * // The provider is automatically called by the framework
88
+ * // No manual invocation needed - just add to plugin.providers array
89
+ */
90
+ export const defiNewsProvider: Provider = {
91
+ name: "DEFI_NEWS",
92
+ description:
93
+ "Provides DeFi market data, global crypto statistics, token information, and real-world crypto news",
94
+ descriptionCompressed:
95
+ "provide DeFi market data, global crypto statistic, token information, real-world crypto new",
96
+ dynamic: true,
97
+ contexts: ["finance", "crypto", "wallet"],
98
+ contextGate: { anyOf: ["finance", "crypto", "wallet"] },
99
+ cacheStable: false,
100
+ cacheScope: "turn",
101
+ roleGate: { minRole: "USER" },
102
+ get: async (runtime: IAgentRuntime, message: Memory, _state: State) => {
103
+ console.log("DEFI_NEWS provider called");
104
+
105
+ let defiNewsInfo = "";
106
+
107
+ try {
108
+ // Get services - CoinGecko from analytics or similar plugin, NewsData from this plugin
109
+ const coinGeckoService = runtime.getService(
110
+ "COINGECKO_SERVICE",
111
+ ) as CoinGeckoService | null;
112
+ const newsDataService = runtime.getService(
113
+ "NEWS_DATA_SERVICE",
114
+ ) as NewsDataService;
115
+
116
+ if (!newsDataService) {
117
+ console.log("NewsData service not available");
118
+ return {
119
+ data: {},
120
+ values: {},
121
+ text: "DeFi News service not available.",
122
+ };
123
+ }
124
+
125
+ console.log("DeFi News services found, generating report...");
126
+
127
+ // Check if a specific token is mentioned in the message
128
+ const messageText = message.content?.text || "";
129
+
130
+ defiNewsInfo += `=== DEFI & CRYPTO MARKET REPORT ===\n\n`;
131
+
132
+ // Extract symbols dynamically from the message
133
+ let extractedSymbols = extractSymbols(messageText, "loose");
134
+ extractedSymbols = filterTokenSymbols(extractedSymbols);
135
+
136
+ // Also check for token names (bitcoin, ethereum, etc.)
137
+ const namedSymbol = getSymbolFromTokenName(messageText);
138
+ if (namedSymbol && !extractedSymbols.includes(namedSymbol)) {
139
+ extractedSymbols.unshift(namedSymbol); // Add to front
140
+ }
141
+
142
+ console.log(`Extracted symbols: ${extractedSymbols.join(", ")}`);
143
+
144
+ // If token symbols are detected and services are available, look them up
145
+ if (extractedSymbols.length > 0 && coinGeckoService) {
146
+ // Try to get Birdeye service for symbol lookup
147
+ const birdeyeService = runtime.getService(
148
+ "birdeye",
149
+ ) as BirdeyeLookupService | null;
150
+ const solanaService = runtime.getService(
151
+ "chain_solana",
152
+ ) as SolanaTokenInfoService | null;
153
+
154
+ if (birdeyeService && solanaService) {
155
+ // Process up to 3 tokens
156
+ for (const detectedSymbol of extractedSymbols.slice(0, 3)) {
157
+ console.log(`Looking up symbol: ${detectedSymbol}`);
158
+
159
+ try {
160
+ // Look up token by symbol across all chains
161
+ const options =
162
+ await birdeyeService.lookupSymbolAllChains(detectedSymbol);
163
+ const exactOptions = options.filter(
164
+ (t) => t.symbol.toUpperCase() === detectedSymbol.toUpperCase(),
165
+ );
166
+
167
+ console.log(
168
+ `Birdeye found ${exactOptions.length} exact matches for ${detectedSymbol}`,
169
+ );
170
+
171
+ if (exactOptions.length > 0) {
172
+ // Use the first exact match (usually the most popular/main token)
173
+ const tokenOption = exactOptions[0];
174
+ const tokenCA = tokenOption.address;
175
+
176
+ console.log(`Using token: ${tokenOption.symbol} at ${tokenCA}`);
177
+
178
+ // Verify it's actually a token
179
+ const addressType = await solanaService.getAddressType(tokenCA);
180
+
181
+ if (addressType === "Token") {
182
+ const tokenData = await getTokenInfoByAddress(
183
+ coinGeckoService,
184
+ solanaService,
185
+ tokenCA,
186
+ tokenOption.symbol,
187
+ );
188
+ if (tokenData) {
189
+ defiNewsInfo += tokenData;
190
+ }
191
+ } else {
192
+ console.log(
193
+ `Address ${tokenCA} is not a Token, it's a ${addressType}`,
194
+ );
195
+ }
196
+ } else {
197
+ console.log(
198
+ `No exact matches found for ${detectedSymbol}, skipping...`,
199
+ );
200
+ }
201
+ } catch (error) {
202
+ console.log(
203
+ `Error looking up ${detectedSymbol} via Birdeye:`,
204
+ error,
205
+ );
206
+ }
207
+ }
208
+ } else {
209
+ // Fallback to CoinGecko ID lookup for major tokens
210
+ console.log(
211
+ "Birdeye or Solana service not available, using CoinGecko fallback",
212
+ );
213
+ for (const detectedSymbol of extractedSymbols.slice(0, 1)) {
214
+ const coingeckoId = getCoinGeckoIdFromSymbol(detectedSymbol);
215
+ if (coingeckoId) {
216
+ const tokenData = await getTokenInfo(
217
+ coinGeckoService,
218
+ coingeckoId,
219
+ );
220
+ defiNewsInfo += tokenData;
221
+ break; // Only one token in fallback mode
222
+ }
223
+ }
224
+ }
225
+ }
226
+
227
+ // Get global DeFi data (if CoinGecko service is available)
228
+ if (coinGeckoService) {
229
+ const globalDefiData = await getGlobalDefiData(coinGeckoService);
230
+ defiNewsInfo += globalDefiData;
231
+
232
+ // Get global crypto market data
233
+ const globalCryptoData = await getGlobalCryptoData(coinGeckoService);
234
+ defiNewsInfo += globalCryptoData;
235
+ } else {
236
+ console.log("CoinGecko service not available, skipping market data");
237
+ defiNewsInfo +=
238
+ "āš ļø Market data unavailable (CoinGecko service not configured)\n\n";
239
+ }
240
+
241
+ // Get latest crypto news (always available)
242
+ const latestNews = await getLatestCryptoNews(newsDataService);
243
+ defiNewsInfo += latestNews;
244
+ } catch (error) {
245
+ console.error("Error in DeFi News provider:", error);
246
+ defiNewsInfo = `Error generating DeFi News report: ${error instanceof Error ? error.message : "Unknown error"}`;
247
+ }
248
+
249
+ const data = {
250
+ defiNews: defiNewsInfo,
251
+ };
252
+
253
+ const values = {};
254
+
255
+ const text = `${defiNewsInfo}\n`.slice(0, DEFI_NEWS_TEXT_LIMIT);
256
+
257
+ return {
258
+ data,
259
+ values,
260
+ text,
261
+ };
262
+ },
263
+ };
264
+
265
+ /**
266
+ * Extract symbols from text
267
+ * Dynamically extracts token symbols from natural language
268
+ *
269
+ * @param text - The text to extract symbols from
270
+ * @param mode - "strict" only matches $SYMBOL format, "loose" matches various patterns
271
+ * @returns Array of detected symbols
272
+ */
273
+ export const extractSymbols = (
274
+ text: string,
275
+ // loose mode will try to extract more symbols but may include false positives
276
+ // strict mode will only extract symbols that are clearly formatted as a symbol using $SOL format
277
+ mode: "strict" | "loose" = "loose",
278
+ ): string[] => {
279
+ if (!text?.matchAll) return [];
280
+ const symbols = new Set<string>();
281
+
282
+ // Match patterns
283
+ const patterns =
284
+ mode === "strict"
285
+ ? [
286
+ // $SYMBOL format
287
+ /\$([A-Z0-9]{2,10})\b/gi,
288
+ // $SYMBOL format with lowercase
289
+ /\$([a-z0-9]{2,10})\b/gi,
290
+ ]
291
+ : [
292
+ // $SYMBOL format
293
+ /\$([A-Z0-9]{2,10})\b/gi,
294
+ // After articles (a/an)
295
+ /\b(?:a|an)\s+([A-Z0-9]{2,10})\b/gi,
296
+ // Standalone caps
297
+ /\b[A-Z0-9]{2,10}\b/g,
298
+ // Quoted symbols
299
+ /["']([A-Z0-9]{2,10})["']/gi,
300
+ // Common price patterns
301
+ /\b([A-Z0-9]{2,10})\/USD\b/gi,
302
+ /\b([A-Z0-9]{2,10})-USD\b/gi,
303
+ ];
304
+
305
+ // Extract all matches
306
+ patterns.forEach((pattern) => {
307
+ const matches = text.matchAll(pattern);
308
+ for (const match of matches) {
309
+ const symbol = (match[1] || match[0]).toUpperCase();
310
+ symbols.add(symbol);
311
+ }
312
+ });
313
+
314
+ return Array.from(symbols);
315
+ };
316
+
317
+ /**
318
+ * Filter extracted symbols to remove common words and validate potential tokens
319
+ */
320
+ function filterTokenSymbols(symbols: string[]): string[] {
321
+ // Common words to exclude (not tokens)
322
+ const excludeWords = new Set([
323
+ "THE",
324
+ "AND",
325
+ "FOR",
326
+ "NOT",
327
+ "BUT",
328
+ "GET",
329
+ "SET",
330
+ "CAN",
331
+ "ARE",
332
+ "WAS",
333
+ "HAS",
334
+ "HAD",
335
+ "HER",
336
+ "HIS",
337
+ "OUR",
338
+ "YOU",
339
+ "ALL",
340
+ "OUT",
341
+ "NEW",
342
+ "OLD",
343
+ "NOW",
344
+ "SEE",
345
+ "OWN",
346
+ "TWO",
347
+ "WAY",
348
+ "WHO",
349
+ "ITS",
350
+ "MAY",
351
+ "DAY",
352
+ "USE",
353
+ "USD",
354
+ "EUR",
355
+ "GBP",
356
+ "JPY",
357
+ "CNY", // Fiat currencies
358
+ ]);
359
+
360
+ return symbols.filter((symbol) => {
361
+ // Must be 2-10 characters
362
+ if (symbol.length < 2 || symbol.length > 10) return false;
363
+
364
+ // Exclude common words
365
+ if (excludeWords.has(symbol)) return false;
366
+
367
+ // Should have at least one letter
368
+ if (!/[A-Z]/.test(symbol)) return false;
369
+
370
+ return true;
371
+ });
372
+ }
373
+
374
+ /**
375
+ * Map common token names to their symbols
376
+ */
377
+ function getSymbolFromTokenName(text: string): string | null {
378
+ const lowerText = text.toLowerCase();
379
+
380
+ const tokenNameToSymbol: Record<string, string> = {
381
+ bitcoin: "BTC",
382
+ ethereum: "ETH",
383
+ solana: "SOL",
384
+ cardano: "ADA",
385
+ polkadot: "DOT",
386
+ avalanche: "AVAX",
387
+ polygon: "MATIC",
388
+ uniswap: "UNI",
389
+ chainlink: "LINK",
390
+ "binance coin": "BNB",
391
+ ripple: "XRP",
392
+ };
393
+
394
+ for (const [name, symbol] of Object.entries(tokenNameToSymbol)) {
395
+ if (lowerText.includes(name)) {
396
+ return symbol;
397
+ }
398
+ }
399
+
400
+ return null;
401
+ }
402
+
403
+ /**
404
+ * Get CoinGecko ID from token symbol
405
+ * Fallback mapping for major tokens when Birdeye is not available
406
+ */
407
+ function getCoinGeckoIdFromSymbol(symbol: string): string | null {
408
+ const symbolToCoinGeckoId: Record<string, string> = {
409
+ BTC: "bitcoin",
410
+ ETH: "ethereum",
411
+ SOL: "solana",
412
+ ADA: "cardano",
413
+ DOT: "polkadot",
414
+ AVAX: "avalanche",
415
+ MATIC: "matic-network",
416
+ UNI: "uniswap",
417
+ LINK: "chainlink",
418
+ BNB: "binancecoin",
419
+ XRP: "ripple",
420
+ USDC: "usd-coin",
421
+ USDT: "tether",
422
+ };
423
+
424
+ return symbolToCoinGeckoId[symbol.toUpperCase()] || null;
425
+ }
426
+
427
+ /**
428
+ * Get global DeFi market data
429
+ */
430
+ async function getGlobalDefiData(
431
+ coinGeckoService: CoinGeckoService,
432
+ ): Promise<string> {
433
+ let defiInfo = "šŸ“Š GLOBAL DEFI MARKET DATA:\n\n";
434
+
435
+ try {
436
+ const defiData = await coinGeckoService.getGlobalDefiData();
437
+
438
+ defiInfo += `šŸ’° DeFi Market Cap: $${parseFloat(defiData.defi_market_cap).toLocaleString()}\n`;
439
+ defiInfo += `šŸ’Ž ETH Market Cap: $${parseFloat(defiData.eth_market_cap).toLocaleString()}\n`;
440
+ defiInfo += `šŸ“ˆ DeFi/ETH Ratio: ${parseFloat(defiData.defi_to_eth_ratio).toFixed(4)}\n`;
441
+ defiInfo += `šŸ“Š 24h Trading Volume: $${parseFloat(defiData.trading_volume_24h).toLocaleString()}\n`;
442
+ defiInfo += `šŸŽÆ DeFi Dominance: ${parseFloat(defiData.defi_dominance).toFixed(2)}%\n`;
443
+ defiInfo += `šŸ‘‘ Top DeFi Coin: ${defiData.top_coin_name} (${defiData.top_coin_defi_dominance.toFixed(2)}% dominance)\n\n`;
444
+ } catch (error) {
445
+ console.error("Error fetching global DeFi data:", error);
446
+ defiInfo += "Error fetching DeFi data. Please try again later.\n\n";
447
+ }
448
+
449
+ return defiInfo;
450
+ }
451
+
452
+ /**
453
+ * Get global crypto market data
454
+ */
455
+ async function getGlobalCryptoData(
456
+ coinGeckoService: CoinGeckoService,
457
+ ): Promise<string> {
458
+ let cryptoInfo = "🌐 GLOBAL CRYPTO MARKET DATA:\n\n";
459
+
460
+ try {
461
+ const cryptoData = await coinGeckoService.getGlobalCryptoData();
462
+
463
+ cryptoInfo += `šŸŖ™ Active Cryptocurrencies: ${cryptoData.active_cryptocurrencies.toLocaleString()}\n`;
464
+ cryptoInfo += `šŸ’± Active Markets: ${cryptoData.markets.toLocaleString()}\n`;
465
+ cryptoInfo += `šŸ’° Total Market Cap: $${(cryptoData.total_market_cap.usd / 1e9).toFixed(2)}B\n`;
466
+ cryptoInfo += `šŸ“Š 24h Volume: $${(cryptoData.total_volume.usd / 1e9).toFixed(2)}B\n`;
467
+ cryptoInfo += `šŸ“ˆ 24h Market Cap Change: ${cryptoData.market_cap_change_percentage_24h_usd.toFixed(2)}%\n`;
468
+
469
+ if (cryptoData.market_cap_percentage) {
470
+ cryptoInfo += "\nšŸ† MARKET DOMINANCE:\n";
471
+ const topCoins = Object.entries(cryptoData.market_cap_percentage)
472
+ .sort((a, b) => (b[1] as number) - (a[1] as number))
473
+ .slice(0, 5) as [string, number][];
474
+ topCoins.forEach(([coin, percentage]) => {
475
+ cryptoInfo += ` • ${coin.toUpperCase()}: ${percentage.toFixed(2)}%\n`;
476
+ });
477
+ }
478
+
479
+ cryptoInfo += "\n";
480
+ } catch (error) {
481
+ console.error("Error fetching global crypto data:", error);
482
+ cryptoInfo +=
483
+ "Error fetching crypto market data. Please try again later.\n\n";
484
+ }
485
+
486
+ return cryptoInfo;
487
+ }
488
+
489
+ /**
490
+ * Get latest crypto news
491
+ */
492
+ async function getLatestCryptoNews(
493
+ newsDataService: NewsDataService,
494
+ ): Promise<string> {
495
+ let newsInfo = "šŸ“° LATEST CRYPTO NEWS:\n\n";
496
+
497
+ try {
498
+ const articles = await newsDataService.getLatestNews({
499
+ limit: 5,
500
+ });
501
+
502
+ if (articles.length === 0) {
503
+ newsInfo += "No recent news articles available.\n\n";
504
+ return newsInfo;
505
+ }
506
+
507
+ articles.forEach((article, index) => {
508
+ newsInfo += `${index + 1}. ${article.title}\n`;
509
+
510
+ if (article.description) {
511
+ const shortDesc = article.description.substring(0, 100);
512
+ newsInfo += ` ${shortDesc}${article.description.length > 100 ? "..." : ""}\n`;
513
+ }
514
+
515
+ if (article.pubDate) {
516
+ const pubDate = new Date(article.pubDate);
517
+ newsInfo += ` šŸ“… ${pubDate.toLocaleDateString()} | šŸ“° ${article.source_id}\n`;
518
+ }
519
+
520
+ if (article.link) {
521
+ newsInfo += ` šŸ”— ${article.link}\n`;
522
+ }
523
+
524
+ newsInfo += "\n";
525
+ });
526
+ } catch (error) {
527
+ console.error("Error fetching latest crypto news:", error);
528
+ newsInfo += "Error fetching news. Please try again later.\n\n";
529
+ }
530
+
531
+ return newsInfo;
532
+ }
533
+
534
+ /**
535
+ * Get token information by contract address
536
+ * Uses Birdeye + CoinGecko to fetch comprehensive token data
537
+ */
538
+ async function getTokenInfoByAddress(
539
+ coinGeckoService: CoinGeckoService,
540
+ solanaService: SolanaTokenInfoService,
541
+ tokenAddress: string,
542
+ symbol: string,
543
+ ): Promise<string | null> {
544
+ let tokenInfo = `šŸ“Š TOKEN INFORMATION:\n\n`;
545
+
546
+ try {
547
+ // Import PublicKey if needed
548
+ const { PublicKey } = await import("@solana/web3.js");
549
+
550
+ // Get token symbol from Solana (for verification)
551
+ let tokenSymbol = symbol;
552
+ try {
553
+ const onChainSymbol = await solanaService.getTokenSymbol(
554
+ new PublicKey(tokenAddress),
555
+ );
556
+ if (onChainSymbol) {
557
+ tokenSymbol = onChainSymbol;
558
+ }
559
+ } catch (_error) {
560
+ console.log("Could not fetch on-chain symbol, using provided:", symbol);
561
+ }
562
+
563
+ console.log(
564
+ `Fetching CoinGecko data for ${tokenSymbol} at ${tokenAddress}`,
565
+ );
566
+
567
+ // Try to search CoinGecko by symbol
568
+ let coinData = null;
569
+ const searchResults = await coinGeckoService.searchCoin(tokenSymbol);
570
+
571
+ if (searchResults && searchResults.length > 0) {
572
+ // Try to find exact match by Solana platform address
573
+ const solanaMatch = searchResults.find(
574
+ (coin) =>
575
+ coin.platforms?.solana?.toLowerCase() === tokenAddress.toLowerCase(),
576
+ );
577
+
578
+ if (solanaMatch) {
579
+ console.log(`Found exact Solana platform match: ${solanaMatch.id}`);
580
+ coinData = await coinGeckoService.getCoinData(solanaMatch.id);
581
+ } else {
582
+ // Use first result as fallback
583
+ console.log(`Using first search result: ${searchResults[0].id}`);
584
+ coinData = await coinGeckoService.getCoinData(searchResults[0].id);
585
+ }
586
+ }
587
+
588
+ if (!coinData) {
589
+ tokenInfo += `šŸŖ™ Token: ${tokenSymbol}\n`;
590
+ tokenInfo += `šŸ“ Address: ${tokenAddress}\n`;
591
+ tokenInfo += `āš ļø Detailed market data not available on CoinGecko\n\n`;
592
+ return tokenInfo;
593
+ }
594
+
595
+ // Format comprehensive token data
596
+ tokenInfo += `šŸŖ™ ${coinData.name} (${coinData.symbol.toUpperCase()})\n`;
597
+ tokenInfo += `šŸ“ Contract Address: ${tokenAddress}\n\n`;
598
+
599
+ if (coinData.market_data) {
600
+ const md = coinData.market_data;
601
+ tokenInfo += "šŸ’µ PRICE INFORMATION:\n";
602
+ if (md.current_price?.usd) {
603
+ tokenInfo += ` Current Price: $${md.current_price.usd.toLocaleString()}\n`;
604
+ }
605
+ if (md.market_cap?.usd) {
606
+ tokenInfo += ` Market Cap: $${(md.market_cap.usd / 1e9).toFixed(2)}B\n`;
607
+ }
608
+ if (md.total_volume?.usd) {
609
+ tokenInfo += ` 24h Volume: $${(md.total_volume.usd / 1e9).toFixed(2)}B\n`;
610
+ }
611
+ if (md.market_cap_rank) {
612
+ tokenInfo += ` Market Cap Rank: #${md.market_cap_rank}\n`;
613
+ }
614
+
615
+ tokenInfo += "\nšŸ“ˆ PRICE CHANGES:\n";
616
+ if (md.price_change_percentage_24h !== undefined) {
617
+ const emoji = md.price_change_percentage_24h >= 0 ? "šŸ“ˆ" : "šŸ“‰";
618
+ tokenInfo += ` ${emoji} 24h: ${md.price_change_percentage_24h.toFixed(2)}%\n`;
619
+ }
620
+ if (md.price_change_percentage_7d !== undefined) {
621
+ const emoji = md.price_change_percentage_7d >= 0 ? "šŸ“ˆ" : "šŸ“‰";
622
+ tokenInfo += ` ${emoji} 7d: ${md.price_change_percentage_7d.toFixed(2)}%\n`;
623
+ }
624
+ if (md.price_change_percentage_30d !== undefined) {
625
+ const emoji = md.price_change_percentage_30d >= 0 ? "šŸ“ˆ" : "šŸ“‰";
626
+ tokenInfo += ` ${emoji} 30d: ${md.price_change_percentage_30d.toFixed(2)}%\n`;
627
+ }
628
+
629
+ if (md.high_24h?.usd && md.low_24h?.usd) {
630
+ tokenInfo += "\nšŸ“Š 24H RANGE:\n";
631
+ tokenInfo += ` High: $${md.high_24h.usd.toLocaleString()}\n`;
632
+ tokenInfo += ` Low: $${md.low_24h.usd.toLocaleString()}\n`;
633
+ }
634
+ }
635
+
636
+ if (coinData.community_data) {
637
+ const cd = coinData.community_data;
638
+ if (
639
+ cd.twitter_followers ||
640
+ cd.reddit_subscribers ||
641
+ cd.telegram_channel_user_count
642
+ ) {
643
+ tokenInfo += "\nšŸ‘„ COMMUNITY:\n";
644
+ if (cd.twitter_followers)
645
+ tokenInfo += ` 🐦 Twitter: ${cd.twitter_followers.toLocaleString()} followers\n`;
646
+ if (cd.reddit_subscribers)
647
+ tokenInfo += ` šŸ”“ Reddit: ${cd.reddit_subscribers.toLocaleString()} subscribers\n`;
648
+ if (cd.telegram_channel_user_count)
649
+ tokenInfo += ` āœˆļø Telegram: ${cd.telegram_channel_user_count.toLocaleString()} members\n`;
650
+ }
651
+ }
652
+
653
+ if (coinData.developer_data) {
654
+ const dd = coinData.developer_data;
655
+ if (dd.stars || dd.forks) {
656
+ tokenInfo += "\nšŸ’» DEVELOPER ACTIVITY:\n";
657
+ if (dd.stars)
658
+ tokenInfo += ` ⭐ GitHub Stars: ${dd.stars.toLocaleString()}\n`;
659
+ if (dd.forks)
660
+ tokenInfo += ` šŸ”± Forks: ${dd.forks.toLocaleString()}\n`;
661
+ }
662
+ }
663
+
664
+ tokenInfo += "\n";
665
+ } catch (error) {
666
+ console.error("Error fetching token info by address:", error);
667
+ return null;
668
+ }
669
+
670
+ return tokenInfo;
671
+ }
672
+
673
+ /**
674
+ * Get token information
675
+ * This is a helper function that can be used for specific token queries
676
+ */
677
+ export async function getTokenInfo(
678
+ coinGeckoService: CoinGeckoService,
679
+ tokenId: string,
680
+ ): Promise<string> {
681
+ let tokenInfo = `šŸ“Š TOKEN INFORMATION:\n\n`;
682
+
683
+ try {
684
+ const tokenData = await coinGeckoService.getCoinData(tokenId);
685
+
686
+ tokenInfo += `šŸŖ™ ${tokenData.name} (${tokenData.symbol.toUpperCase()})\n\n`;
687
+
688
+ if (tokenData.market_data) {
689
+ const md = tokenData.market_data;
690
+ tokenInfo += "šŸ’µ PRICE INFORMATION:\n";
691
+ if (md.current_price?.usd) {
692
+ tokenInfo += ` Current Price: $${md.current_price.usd.toLocaleString()}\n`;
693
+ }
694
+ if (md.market_cap?.usd) {
695
+ tokenInfo += ` Market Cap: $${(md.market_cap.usd / 1e9).toFixed(2)}B\n`;
696
+ }
697
+ if (md.total_volume?.usd) {
698
+ tokenInfo += ` 24h Volume: $${(md.total_volume.usd / 1e9).toFixed(2)}B\n`;
699
+ }
700
+
701
+ tokenInfo += "\nšŸ“ˆ PRICE CHANGES:\n";
702
+ if (md.price_change_percentage_24h !== undefined) {
703
+ const emoji = md.price_change_percentage_24h >= 0 ? "šŸ“ˆ" : "šŸ“‰";
704
+ tokenInfo += ` ${emoji} 24h: ${md.price_change_percentage_24h.toFixed(2)}%\n`;
705
+ }
706
+ if (md.price_change_percentage_7d !== undefined) {
707
+ const emoji = md.price_change_percentage_7d >= 0 ? "šŸ“ˆ" : "šŸ“‰";
708
+ tokenInfo += ` ${emoji} 7d: ${md.price_change_percentage_7d.toFixed(2)}%\n`;
709
+ }
710
+ if (md.price_change_percentage_30d !== undefined) {
711
+ const emoji = md.price_change_percentage_30d >= 0 ? "šŸ“ˆ" : "šŸ“‰";
712
+ tokenInfo += ` ${emoji} 30d: ${md.price_change_percentage_30d.toFixed(2)}%\n`;
713
+ }
714
+ }
715
+
716
+ if (tokenData.community_data) {
717
+ const cd = tokenData.community_data;
718
+ if (cd.twitter_followers || cd.reddit_subscribers) {
719
+ tokenInfo += "\nšŸ‘„ COMMUNITY:\n";
720
+ if (cd.twitter_followers)
721
+ tokenInfo += ` 🐦 Twitter: ${cd.twitter_followers.toLocaleString()} followers\n`;
722
+ if (cd.reddit_subscribers)
723
+ tokenInfo += ` šŸ”“ Reddit: ${cd.reddit_subscribers.toLocaleString()} subscribers\n`;
724
+ }
725
+ }
726
+
727
+ tokenInfo += "\n";
728
+ } catch (error) {
729
+ console.error("Error fetching token info:", error);
730
+ tokenInfo += "Error fetching token data. Please try again later.\n\n";
731
+ }
732
+
733
+ return tokenInfo;
734
+ }