@elizaos/plugin-social-alpha 2.0.3-beta.6 → 2.0.3-beta.7

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 (151) hide show
  1. package/dist/clients.d.ts +354 -0
  2. package/dist/clients.d.ts.map +1 -0
  3. package/dist/clients.js +670 -0
  4. package/dist/clients.js.map +1 -0
  5. package/dist/config.d.ts +144 -0
  6. package/dist/config.d.ts.map +1 -0
  7. package/dist/config.js +122 -0
  8. package/dist/config.js.map +1 -0
  9. package/dist/events.d.ts +5 -0
  10. package/dist/events.d.ts.map +1 -0
  11. package/dist/events.js +426 -0
  12. package/dist/events.js.map +1 -0
  13. package/dist/frontend/LeaderboardView.helpers.d.ts +6 -0
  14. package/dist/frontend/LeaderboardView.helpers.d.ts.map +1 -0
  15. package/dist/frontend/LeaderboardView.helpers.js +59 -0
  16. package/dist/frontend/LeaderboardView.helpers.js.map +1 -0
  17. package/dist/frontend/SocialAlphaSpatialView.d.ts +52 -0
  18. package/dist/frontend/SocialAlphaSpatialView.d.ts.map +1 -0
  19. package/dist/frontend/SocialAlphaSpatialView.js +72 -0
  20. package/dist/frontend/SocialAlphaSpatialView.js.map +1 -0
  21. package/dist/frontend/SocialAlphaView.d.ts +35 -0
  22. package/dist/frontend/SocialAlphaView.d.ts.map +1 -0
  23. package/dist/frontend/SocialAlphaView.js +125 -0
  24. package/dist/frontend/SocialAlphaView.js.map +1 -0
  25. package/dist/index.d.ts +24 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +73 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/mockPriceService.d.ts +22 -0
  30. package/dist/mockPriceService.d.ts.map +1 -0
  31. package/dist/mockPriceService.js +21 -0
  32. package/dist/mockPriceService.js.map +1 -0
  33. package/dist/providers/socialAlphaProvider.d.ts +15 -0
  34. package/dist/providers/socialAlphaProvider.d.ts.map +1 -0
  35. package/dist/providers/socialAlphaProvider.js +261 -0
  36. package/dist/providers/socialAlphaProvider.js.map +1 -0
  37. package/dist/register-terminal-view.d.ts +15 -0
  38. package/dist/register-terminal-view.d.ts.map +1 -0
  39. package/dist/register-terminal-view.js +21 -0
  40. package/dist/register-terminal-view.js.map +1 -0
  41. package/dist/register.d.ts +10 -0
  42. package/dist/register.d.ts.map +1 -0
  43. package/dist/register.js +5 -0
  44. package/dist/register.js.map +1 -0
  45. package/dist/reports.d.ts +57 -0
  46. package/dist/reports.d.ts.map +1 -0
  47. package/dist/reports.js +455 -0
  48. package/dist/reports.js.map +1 -0
  49. package/dist/routes.d.ts +3 -0
  50. package/dist/routes.d.ts.map +1 -0
  51. package/dist/routes.js +59 -0
  52. package/dist/routes.js.map +1 -0
  53. package/dist/schemas.d.ts +151 -0
  54. package/dist/schemas.d.ts.map +1 -0
  55. package/dist/schemas.js +258 -0
  56. package/dist/schemas.js.map +1 -0
  57. package/dist/service.d.ts +306 -0
  58. package/dist/service.d.ts.map +1 -0
  59. package/dist/service.js +3078 -0
  60. package/dist/service.js.map +1 -0
  61. package/dist/services/balancedTrustScoreCalculator.d.ts +61 -0
  62. package/dist/services/balancedTrustScoreCalculator.d.ts.map +1 -0
  63. package/dist/services/balancedTrustScoreCalculator.js +207 -0
  64. package/dist/services/balancedTrustScoreCalculator.js.map +1 -0
  65. package/dist/services/historicalPriceService.d.ts +59 -0
  66. package/dist/services/historicalPriceService.d.ts.map +1 -0
  67. package/dist/services/historicalPriceService.js +291 -0
  68. package/dist/services/historicalPriceService.js.map +1 -0
  69. package/dist/services/index.d.ts +12 -0
  70. package/dist/services/index.d.ts.map +1 -0
  71. package/dist/services/index.js +17 -0
  72. package/dist/services/index.js.map +1 -0
  73. package/dist/services/priceEnrichmentService.d.ts +109 -0
  74. package/dist/services/priceEnrichmentService.d.ts.map +1 -0
  75. package/dist/services/priceEnrichmentService.js +780 -0
  76. package/dist/services/priceEnrichmentService.js.map +1 -0
  77. package/dist/services/simulationActorsV2.d.ts +54 -0
  78. package/dist/services/simulationActorsV2.d.ts.map +1 -0
  79. package/dist/services/simulationActorsV2.js +362 -0
  80. package/dist/services/simulationActorsV2.js.map +1 -0
  81. package/dist/services/simulationRunner.d.ts +113 -0
  82. package/dist/services/simulationRunner.d.ts.map +1 -0
  83. package/dist/services/simulationRunner.js +771 -0
  84. package/dist/services/simulationRunner.js.map +1 -0
  85. package/dist/services/tokenSimulationService.d.ts +34 -0
  86. package/dist/services/tokenSimulationService.d.ts.map +1 -0
  87. package/dist/services/tokenSimulationService.js +297 -0
  88. package/dist/services/tokenSimulationService.js.map +1 -0
  89. package/dist/services/trustScoreOptimizer.d.ts +110 -0
  90. package/dist/services/trustScoreOptimizer.d.ts.map +1 -0
  91. package/dist/services/trustScoreOptimizer.js +635 -0
  92. package/dist/services/trustScoreOptimizer.js.map +1 -0
  93. package/dist/simulationActors.d.ts +35 -0
  94. package/dist/simulationActors.d.ts.map +1 -0
  95. package/dist/simulationActors.js +160 -0
  96. package/dist/simulationActors.js.map +1 -0
  97. package/dist/social-alpha-view-bundle.d.ts +2 -0
  98. package/dist/social-alpha-view-bundle.d.ts.map +1 -0
  99. package/dist/social-alpha-view-bundle.js +5 -0
  100. package/dist/social-alpha-view-bundle.js.map +1 -0
  101. package/dist/types.d.ts +937 -0
  102. package/dist/types.d.ts.map +1 -0
  103. package/dist/types.js +46 -0
  104. package/dist/types.js.map +1 -0
  105. package/dist/views/brand/background/clouds_background.jpg +0 -0
  106. package/dist/views/brand/banners/eliza_banner.svg +20 -0
  107. package/dist/views/brand/banners/elizacloud_banner.svg +20 -0
  108. package/dist/views/brand/banners/elizaos_banner.svg +20 -0
  109. package/dist/views/brand/concepts/billboard_concept_1200.jpg +0 -0
  110. package/dist/views/brand/concepts/chibi_usb_concept_900.jpg +0 -0
  111. package/dist/views/brand/concepts/concept_minipc_900.jpg +0 -0
  112. package/dist/views/brand/concepts/concept_phone_800.jpg +0 -0
  113. package/dist/views/brand/concepts/concept_usbdrive_900.jpg +0 -0
  114. package/dist/views/brand/favicons/android-chrome-192x192.png +0 -0
  115. package/dist/views/brand/favicons/android-chrome-512x512.png +0 -0
  116. package/dist/views/brand/favicons/apple-touch-icon.png +0 -0
  117. package/dist/views/brand/favicons/favicon-16x16.png +0 -0
  118. package/dist/views/brand/favicons/favicon-32x32.png +0 -0
  119. package/dist/views/brand/favicons/favicon.ico +0 -0
  120. package/dist/views/brand/favicons/favicon.svg +17 -0
  121. package/dist/views/brand/logos/elizaOS_text_black.svg +3 -0
  122. package/dist/views/brand/logos/elizaOS_text_white.svg +3 -0
  123. package/dist/views/brand/logos/eliza_logotext.svg +26 -0
  124. package/dist/views/brand/logos/eliza_logotext_black.svg +26 -0
  125. package/dist/views/brand/logos/eliza_text_black.svg +3 -0
  126. package/dist/views/brand/logos/eliza_text_white.svg +3 -0
  127. package/dist/views/brand/logos/elizacloud_logotext.svg +26 -0
  128. package/dist/views/brand/logos/elizacloud_logotext_black.svg +26 -0
  129. package/dist/views/brand/logos/elizacloud_text_black.svg +3 -0
  130. package/dist/views/brand/logos/elizacloud_text_white.svg +3 -0
  131. package/dist/views/brand/logos/elizaos_logotext.svg +26 -0
  132. package/dist/views/brand/logos/elizaos_logotext_black.svg +26 -0
  133. package/dist/views/brand/logos/logo_blue_blackbg.svg +18 -0
  134. package/dist/views/brand/logos/logo_blue_nobg.svg +17 -0
  135. package/dist/views/brand/logos/logo_orange_blackbg.svg +18 -0
  136. package/dist/views/brand/logos/logo_orange_nobg.svg +17 -0
  137. package/dist/views/brand/logos/logo_white_blackbg.svg +25 -0
  138. package/dist/views/brand/logos/logo_white_bluebg.svg +25 -0
  139. package/dist/views/brand/logos/logo_white_graybg.svg +18 -0
  140. package/dist/views/brand/logos/logo_white_nobg.svg +24 -0
  141. package/dist/views/brand/logos/logo_white_orangebg.svg +25 -0
  142. package/dist/views/brand/ogembeds/eliza_ogembed.png +0 -0
  143. package/dist/views/brand/ogembeds/eliza_ogembed.svg +20 -0
  144. package/dist/views/brand/ogembeds/elizacloud_ogembed.png +0 -0
  145. package/dist/views/brand/ogembeds/elizacloud_ogembed.svg +20 -0
  146. package/dist/views/brand/ogembeds/elizaos_ogembed.png +0 -0
  147. package/dist/views/brand/ogembeds/elizaos_ogembed.svg +20 -0
  148. package/dist/views/bundle.js +268 -0
  149. package/dist/views/bundle.js.map +1 -0
  150. package/dist/views/site.webmanifest +19 -0
  151. package/package.json +5 -5
@@ -0,0 +1,780 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { BirdeyeClient, DexscreenerClient } from "../clients.js";
4
+ import { SupportedChain } from "../types.js";
5
+ import {
6
+ HistoricalPriceService
7
+ } from "./historicalPriceService.js";
8
+ class PriceEnrichmentService {
9
+ birdeyeClient;
10
+ dexscreenerClient;
11
+ historicalPriceService;
12
+ constructor(runtime) {
13
+ this.birdeyeClient = BirdeyeClient.createFromRuntime(runtime);
14
+ this.dexscreenerClient = DexscreenerClient.createFromRuntime(runtime);
15
+ this.historicalPriceService = new HistoricalPriceService(runtime);
16
+ }
17
+ /**
18
+ * Load batch files from the cache directory
19
+ */
20
+ async loadBatchFiles(batchCacheDir) {
21
+ const allCalls = [];
22
+ try {
23
+ const files = await fs.readdir(batchCacheDir);
24
+ const batchFiles = files.filter(
25
+ (file) => file.startsWith("chat_") && file.endsWith(".json")
26
+ );
27
+ for (const file of batchFiles) {
28
+ try {
29
+ const filePath = path.join(batchCacheDir, file);
30
+ const content = await fs.readFile(filePath, "utf-8");
31
+ const batchData = JSON.parse(content);
32
+ allCalls.push(...batchData);
33
+ } catch (error) {
34
+ console.error(`Error loading batch file ${file}:`, error);
35
+ }
36
+ }
37
+ console.log(
38
+ `Loaded ${allCalls.length} trading calls from ${batchFiles.length} batch files`
39
+ );
40
+ return allCalls;
41
+ } catch (error) {
42
+ console.error("Error loading batch files:", error);
43
+ return [];
44
+ }
45
+ }
46
+ /**
47
+ * Resolve contract address or token mention to standardized token info
48
+ */
49
+ async resolveToken(call) {
50
+ try {
51
+ if (call.caMentioned) {
52
+ const tokenData = await this.getTokenInfo(
53
+ call.caMentioned,
54
+ this.chainStringToEnum(call.chain)
55
+ );
56
+ if (tokenData) {
57
+ return {
58
+ address: call.caMentioned,
59
+ symbol: tokenData.symbol || "UNKNOWN",
60
+ name: tokenData.name || "Unknown Token",
61
+ chain: this.chainStringToEnum(call.chain)
62
+ };
63
+ }
64
+ }
65
+ if (call.tokenMentioned) {
66
+ const resolved = await this.searchTokenBySymbol(
67
+ call.tokenMentioned,
68
+ this.chainStringToEnum(call.chain)
69
+ );
70
+ return resolved;
71
+ }
72
+ return null;
73
+ } catch (error) {
74
+ console.error(`Error resolving token for call ${call.callId}:`, error);
75
+ return null;
76
+ }
77
+ }
78
+ /**
79
+ * Get historical price data for a token within a time window
80
+ */
81
+ async getPriceDataInWindow(tokenAddress, chain, callTimestamp, windowDays = 30) {
82
+ try {
83
+ const windowEnd = callTimestamp + windowDays * 24 * 60 * 60 * 1e3;
84
+ const historicalData = chain === SupportedChain.SOLANA ? await this.historicalPriceService.fetchBirdeyeHistoricalPrices(
85
+ tokenAddress,
86
+ callTimestamp,
87
+ windowEnd
88
+ ) : await this.historicalPriceService.fetchDexscreenerHistoricalPrices(
89
+ tokenAddress,
90
+ chain,
91
+ callTimestamp,
92
+ windowEnd
93
+ );
94
+ if (!historicalData) return null;
95
+ const calledPrice = this.historicalPriceService.getPriceAtTimestamp(
96
+ historicalData,
97
+ callTimestamp
98
+ ) ?? historicalData.firstPrice ?? historicalData.lastPrice;
99
+ const bestPrice = this.historicalPriceService.getMaxPriceInWindow(
100
+ historicalData,
101
+ callTimestamp,
102
+ windowEnd
103
+ );
104
+ const worstPrice = this.getMinPriceInWindow(
105
+ historicalData,
106
+ callTimestamp,
107
+ windowEnd
108
+ );
109
+ if (calledPrice === void 0 || !bestPrice || !worstPrice) {
110
+ return null;
111
+ }
112
+ return {
113
+ calledPrice,
114
+ bestPrice: bestPrice.price,
115
+ bestPriceTimestamp: bestPrice.timestamp,
116
+ worstPrice: worstPrice.price,
117
+ worstPriceTimestamp: worstPrice.timestamp
118
+ };
119
+ } catch (error) {
120
+ console.error(`Error getting price data for ${tokenAddress}:`, error);
121
+ return null;
122
+ }
123
+ }
124
+ /**
125
+ * Enrich a single trading call with price data
126
+ */
127
+ async enrichCall(call) {
128
+ const enrichedCall = {
129
+ ...call,
130
+ enrichmentStatus: "pending"
131
+ };
132
+ try {
133
+ const resolvedToken = await this.resolveToken(call);
134
+ if (!resolvedToken) {
135
+ enrichedCall.enrichmentStatus = "failed";
136
+ enrichedCall.enrichmentError = "Could not resolve token";
137
+ return enrichedCall;
138
+ }
139
+ enrichedCall.resolvedToken = resolvedToken;
140
+ const windowDays = call.sentiment === "negative" ? 14 : 30;
141
+ const priceData = await this.getPriceDataInWindow(
142
+ resolvedToken.address,
143
+ resolvedToken.chain,
144
+ call.timestamp,
145
+ windowDays
146
+ );
147
+ if (!priceData) {
148
+ enrichedCall.enrichmentStatus = "failed";
149
+ enrichedCall.enrichmentError = "Could not get price data";
150
+ return enrichedCall;
151
+ }
152
+ const idealProfitLoss = call.sentiment === "positive" ? priceData.bestPrice - priceData.calledPrice : priceData.calledPrice - priceData.worstPrice;
153
+ const idealProfitLossPercent = idealProfitLoss / priceData.calledPrice * 100;
154
+ enrichedCall.priceData = {
155
+ calledPrice: priceData.calledPrice,
156
+ calledPriceTimestamp: call.timestamp,
157
+ bestPrice: priceData.bestPrice,
158
+ bestPriceTimestamp: priceData.bestPriceTimestamp,
159
+ worstPrice: priceData.worstPrice,
160
+ worstPriceTimestamp: priceData.worstPriceTimestamp,
161
+ idealProfitLoss,
162
+ idealProfitLossPercent,
163
+ windowDays
164
+ };
165
+ enrichedCall.enrichmentStatus = "success";
166
+ enrichedCall.enrichedAt = Date.now();
167
+ } catch (error) {
168
+ console.error(`Error enriching call ${call.callId}:`, error);
169
+ enrichedCall.enrichmentStatus = "failed";
170
+ enrichedCall.enrichmentError = error instanceof Error ? error.message : "Unknown error";
171
+ }
172
+ return enrichedCall;
173
+ }
174
+ /**
175
+ * Process all calls in batches and save enriched data
176
+ */
177
+ async enrichAllCalls(batchCacheDir, outputDir, batchSize = 10) {
178
+ const calls = await this.loadBatchFiles(batchCacheDir);
179
+ const enrichedCalls = [];
180
+ let successCount = 0;
181
+ let failureCount = 0;
182
+ let resolvedTokenCount = 0;
183
+ console.log(
184
+ `Starting enrichment of ${calls.length} calls in batches of ${batchSize}`
185
+ );
186
+ const startTime = Date.now();
187
+ for (let i = 0; i < calls.length; i += batchSize) {
188
+ const batch = calls.slice(i, i + batchSize);
189
+ const batchNumber = Math.floor(i / batchSize) + 1;
190
+ const totalBatches = Math.ceil(calls.length / batchSize);
191
+ console.log(
192
+ `Processing batch ${batchNumber}/${totalBatches} (${(batchNumber / totalBatches * 100).toFixed(1)}%)`
193
+ );
194
+ const batchPromises = batch.map((call) => this.enrichCall(call));
195
+ const enrichedBatch = await Promise.all(batchPromises);
196
+ enrichedCalls.push(...enrichedBatch);
197
+ enrichedBatch.forEach((call) => {
198
+ if (call.enrichmentStatus === "success") {
199
+ successCount++;
200
+ if (call.resolvedToken) resolvedTokenCount++;
201
+ } else {
202
+ failureCount++;
203
+ }
204
+ });
205
+ if (batchNumber % 100 === 0 || batchNumber === totalBatches) {
206
+ const outputFile = path.join(
207
+ outputDir,
208
+ `enriched_batch_${Math.floor(i / batchSize)}.json`
209
+ );
210
+ await fs.writeFile(outputFile, JSON.stringify(enrichedBatch, null, 2));
211
+ const elapsed = (Date.now() - startTime) / 1e3 / 60;
212
+ const rate = batchNumber / elapsed;
213
+ const remaining = totalBatches - batchNumber;
214
+ const eta = remaining / rate;
215
+ console.log(
216
+ `\u{1F4CA} Progress: ${successCount} success, ${failureCount} failed, ${resolvedTokenCount} tokens resolved`
217
+ );
218
+ console.log(
219
+ `\u23F1\uFE0F Time: ${elapsed.toFixed(1)}min elapsed, ~${eta.toFixed(1)}min remaining`
220
+ );
221
+ }
222
+ await new Promise((resolve) => setTimeout(resolve, 500));
223
+ }
224
+ const completeOutputFile = path.join(
225
+ outputDir,
226
+ "enriched_calls_complete.json"
227
+ );
228
+ await fs.writeFile(
229
+ completeOutputFile,
230
+ JSON.stringify(enrichedCalls, null, 2)
231
+ );
232
+ const totalTime = (Date.now() - startTime) / 1e3 / 60;
233
+ console.log(`
234
+ \u2705 Enrichment complete in ${totalTime.toFixed(2)} minutes!`);
235
+ console.log(
236
+ `\u{1F4C8} Results: ${successCount} success (${(successCount / calls.length * 100).toFixed(1)}%), ${failureCount} failed`
237
+ );
238
+ console.log(
239
+ `\u{1F3AF} Tokens resolved: ${resolvedTokenCount} (${(resolvedTokenCount / calls.length * 100).toFixed(1)}%)`
240
+ );
241
+ console.log(
242
+ `\u{1F4BE} Saved ${enrichedCalls.length} enriched calls to ${completeOutputFile}`
243
+ );
244
+ }
245
+ /**
246
+ * Calculate trust scores for all users
247
+ */
248
+ async calculateTrustScores(enrichedCalls) {
249
+ const userStats = /* @__PURE__ */ new Map();
250
+ for (const call of enrichedCalls) {
251
+ if (call.enrichmentStatus !== "success" || !call.priceData) continue;
252
+ if (!userStats.has(call.userId)) {
253
+ userStats.set(call.userId, {
254
+ calls: [],
255
+ totalProfitLoss: 0,
256
+ totalProfitLossPercent: 0,
257
+ successfulCalls: 0,
258
+ failedCalls: 0
259
+ });
260
+ }
261
+ const stats = userStats.get(call.userId);
262
+ if (!stats) continue;
263
+ stats.calls.push(call);
264
+ stats.totalProfitLoss += call.priceData.idealProfitLoss;
265
+ stats.totalProfitLossPercent += call.priceData.idealProfitLossPercent;
266
+ if (call.priceData.idealProfitLossPercent > 0) {
267
+ stats.successfulCalls++;
268
+ } else {
269
+ stats.failedCalls++;
270
+ }
271
+ }
272
+ const trustScores = [];
273
+ for (const [userId, stats] of userStats) {
274
+ const totalCalls = stats.successfulCalls + stats.failedCalls;
275
+ if (totalCalls === 0) continue;
276
+ const averageProfitLoss = stats.totalProfitLoss / totalCalls;
277
+ const averageProfitLossPercent = stats.totalProfitLossPercent / totalCalls;
278
+ const successRate = stats.successfulCalls / totalCalls;
279
+ const profitLossValues = stats.calls.map((call) => call.priceData?.idealProfitLossPercent).filter((v) => v !== void 0);
280
+ const variance = profitLossValues.reduce(
281
+ (sum, val) => sum + (val - averageProfitLossPercent) ** 2,
282
+ 0
283
+ ) / totalCalls;
284
+ const consistency = Math.max(0, 100 - Math.sqrt(variance));
285
+ const now = Date.now();
286
+ const recencyWeight = stats.calls.reduce((sum, call) => {
287
+ const daysSince = (now - call.timestamp) / (1e3 * 60 * 60 * 24);
288
+ return sum + Math.exp(-daysSince / 30);
289
+ }, 0) / totalCalls;
290
+ const convictionAccuracy = this.calculateConvictionAccuracy(stats.calls);
291
+ const trustScore = successRate * 40 + // 40% weight on success rate
292
+ Math.max(0, Math.min(100, averageProfitLossPercent * 2)) * 30 + // 30% weight on profit %
293
+ consistency * 20 + // 20% weight on consistency
294
+ convictionAccuracy * 10;
295
+ const username = stats.calls[0]?.username || "Unknown";
296
+ trustScores.push({
297
+ userId,
298
+ username,
299
+ totalCalls,
300
+ successfulCalls: stats.successfulCalls,
301
+ failedCalls: stats.failedCalls,
302
+ averageProfitLoss,
303
+ averageProfitLossPercent,
304
+ trustScore: Math.round(trustScore * 100) / 100,
305
+ consistency: Math.round(consistency * 100) / 100,
306
+ recencyWeight: Math.round(recencyWeight * 100) / 100,
307
+ convictionAccuracy: Math.round(convictionAccuracy * 100) / 100,
308
+ lastUpdated: Date.now()
309
+ });
310
+ }
311
+ return trustScores.sort((a, b) => b.trustScore - a.trustScore);
312
+ }
313
+ // Helper methods
314
+ chainStringToEnum(chain) {
315
+ switch (chain.toUpperCase()) {
316
+ case "SOLANA":
317
+ return SupportedChain.SOLANA;
318
+ case "ETHEREUM":
319
+ return SupportedChain.ETHEREUM;
320
+ case "BASE":
321
+ return SupportedChain.BASE;
322
+ default:
323
+ return SupportedChain.UNKNOWN;
324
+ }
325
+ }
326
+ async getTokenInfo(address, chain) {
327
+ try {
328
+ if (chain === SupportedChain.SOLANA) {
329
+ const overview = await this.birdeyeClient.fetchTokenOverview(address);
330
+ return {
331
+ name: overview.name,
332
+ symbol: overview.symbol,
333
+ currentPrice: 0
334
+ // Will be fetched separately
335
+ };
336
+ }
337
+ const dexData = await this.dexscreenerClient.searchForHighestLiquidityPair(address);
338
+ if (dexData) {
339
+ return {
340
+ name: dexData.baseToken.name,
341
+ symbol: dexData.baseToken.symbol,
342
+ currentPrice: parseFloat(dexData.priceUsd)
343
+ };
344
+ }
345
+ return null;
346
+ } catch (error) {
347
+ console.error(`Error getting token info for ${address}:`, error);
348
+ return null;
349
+ }
350
+ }
351
+ async searchTokenBySymbol(symbol, chain) {
352
+ try {
353
+ const staticMapping = this.getStaticTokenMapping(symbol, chain);
354
+ if (staticMapping) {
355
+ return staticMapping;
356
+ }
357
+ const dexscreenerResult = await this.searchTokenOnDexscreener(
358
+ symbol,
359
+ chain
360
+ );
361
+ if (dexscreenerResult) {
362
+ return dexscreenerResult;
363
+ }
364
+ console.warn(`Could not resolve token symbol: ${symbol} on ${chain}`);
365
+ return null;
366
+ } catch (error) {
367
+ console.error(`Error searching for token ${symbol}:`, error);
368
+ return null;
369
+ }
370
+ }
371
+ getMinPriceInWindow(historicalData, fromTimestamp, toTimestamp) {
372
+ const pricesInWindow = historicalData.priceHistory.filter(
373
+ (point) => point.timestamp >= fromTimestamp && point.timestamp <= toTimestamp
374
+ );
375
+ if (pricesInWindow.length === 0) {
376
+ const priceAtStart = this.historicalPriceService.getPriceAtTimestamp(
377
+ historicalData,
378
+ fromTimestamp
379
+ );
380
+ const priceAtEnd = this.historicalPriceService.getPriceAtTimestamp(
381
+ historicalData,
382
+ toTimestamp
383
+ );
384
+ if (priceAtStart !== null && priceAtEnd !== null) {
385
+ return priceAtStart <= priceAtEnd ? { price: priceAtStart, timestamp: fromTimestamp } : { price: priceAtEnd, timestamp: toTimestamp };
386
+ }
387
+ return null;
388
+ }
389
+ let minPrice = pricesInWindow[0];
390
+ for (const point of pricesInWindow) {
391
+ if (point.price < minPrice.price) {
392
+ minPrice = point;
393
+ }
394
+ }
395
+ return { price: minPrice.price, timestamp: minPrice.timestamp };
396
+ }
397
+ calculateConvictionAccuracy(calls) {
398
+ const convictionWeights = { NONE: 0, LOW: 1, MEDIUM: 2, HIGH: 3 };
399
+ let totalWeightedAccuracy = 0;
400
+ let totalWeight = 0;
401
+ for (const call of calls) {
402
+ if (!call.priceData) continue;
403
+ const weight = convictionWeights[call.conviction] || 0;
404
+ const isAccurate = call.priceData.idealProfitLossPercent > 0 ? 1 : 0;
405
+ totalWeightedAccuracy += weight * isAccurate;
406
+ totalWeight += weight;
407
+ }
408
+ return totalWeight > 0 ? totalWeightedAccuracy / totalWeight * 100 : 0;
409
+ }
410
+ /**
411
+ * Get static token mappings for well-known tokens
412
+ */
413
+ getStaticTokenMapping(symbol, chain) {
414
+ const cleanSymbol = symbol.toUpperCase();
415
+ if (chain === SupportedChain.SOLANA) {
416
+ const knownSolanaTokens = {
417
+ SOL: {
418
+ address: "So11111111111111111111111111111111111111112",
419
+ name: "Solana"
420
+ },
421
+ USDC: {
422
+ address: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
423
+ name: "USD Coin"
424
+ },
425
+ USDT: {
426
+ address: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB",
427
+ name: "Tether USD"
428
+ },
429
+ WIF: {
430
+ address: "EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzL7WDb43cuQu2",
431
+ name: "dogwifhat"
432
+ },
433
+ BONK: {
434
+ address: "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
435
+ name: "Bonk"
436
+ },
437
+ JUP: {
438
+ address: "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN",
439
+ name: "Jupiter"
440
+ },
441
+ RAY: {
442
+ address: "4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R",
443
+ name: "Raydium"
444
+ },
445
+ ORCA: {
446
+ address: "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE",
447
+ name: "Orca"
448
+ },
449
+ PNUT: {
450
+ address: "2qEHjDLDLbuBgRYvsxhc5D6uDWAivNFZGan56P1tpump",
451
+ name: "Peanut the Squirrel"
452
+ },
453
+ GOAT: {
454
+ address: "CzLSujWBLFsSjncfkh59rUFqvafWcY5tzedWJSuypump",
455
+ name: "Goatseus Maximus"
456
+ },
457
+ AI16Z: {
458
+ address: "HeLp6NuQkmYB4pYWo2zYs22mESHXPQYzXbB8n4V98jwC",
459
+ name: "ai16z"
460
+ },
461
+ ZEREBRO: {
462
+ address: "HeLp6NuQkmYB4pYWo2zYs22mESHXPQYzXbB8n4V98jwC",
463
+ name: "Zerebro"
464
+ },
465
+ FARTCOIN: {
466
+ address: "9BB6NFEcjBCtnNLFko2FqVQBq8HHM13kCyYcdQbgpump",
467
+ name: "Fartcoin"
468
+ },
469
+ POPCAT: {
470
+ address: "7GCihgDB8fe6KNjn2MYtkzZcRjQy3t9GHdC8uHYmW2hr",
471
+ name: "Popcat"
472
+ },
473
+ DOGE: {
474
+ address: "3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh",
475
+ name: "Dogecoin (Wormhole)"
476
+ },
477
+ SHIB: {
478
+ address: "CiKu4eHsVrc1eueVQeHn7qhXTcVu95gSQmBpX4utjL9z",
479
+ name: "Shiba Inu (Wormhole)"
480
+ },
481
+ PEPE: {
482
+ address: "BxnFDLpgvhQqhwjwQDNx3RgVQeJbk2ReNGdpE4F1pump",
483
+ name: "Pepe"
484
+ },
485
+ // Popular AI tokens from your data
486
+ DEGENAI: {
487
+ address: "Gu3LDkn7Vx3bmCzLafYNKcDxv2mqcDvZLhbiewCaAp1M",
488
+ name: "DEGENAI"
489
+ },
490
+ COBIE: {
491
+ address: "6og9y7SuLDZ5wJXtvJTXFJECaFmCj3gKcSSoydG39Dxu",
492
+ name: "Cobie"
493
+ },
494
+ SHAW: {
495
+ address: "9Bb6Nf8cNmMSvQT71xFSjGCvGbJ7SQmHwQEE2D9h5R68",
496
+ name: "Shaw"
497
+ },
498
+ AILON: {
499
+ address: "EhLXiPhqgAhSt4bdaFfN6b3vWckjV2Sg9mwpLFZUEWb3",
500
+ name: "Ailon"
501
+ },
502
+ NAVAL: {
503
+ address: "8P5rj3RRyMEKEzAT8iY1t6WgY0sNdwjBRZVcjY2BwM7h",
504
+ name: "Naval"
505
+ },
506
+ AROK: {
507
+ address: "GKJ5Tf7n2Hs9Mg8TkGJT2s6dA1xf1Fw8C3N7b2kK6mPa",
508
+ name: "Arok"
509
+ },
510
+ HONEY: {
511
+ address: "4vMsoUT2BWatFweudnQM1xedRLfJgJ7hswhcpz4xgBTy",
512
+ name: "Honey"
513
+ },
514
+ BOSSU: {
515
+ address: "7GvK8XPzFwHdQfcrJZ9mCgA8jN9R2Nw9gX4tS1xP7oEq",
516
+ name: "Bossu"
517
+ },
518
+ MCAIFEE: {
519
+ address: "9y7K1nBzpVwX7F8yNfG6Ldc2aQ5rN9xV8uTe3CpM6iYx",
520
+ name: "McAifee"
521
+ },
522
+ SCHIFF: {
523
+ address: "2A7yHGqN5xL4zP1wE9sF8vQqR6kN3T9bX5uYcMvPw2qE",
524
+ name: "Schiff"
525
+ },
526
+ METH: {
527
+ address: "MEW1gQWJ3nEXg2qgERiKu7FAFj79PHvQVREQUzScPP5",
528
+ name: "Meth"
529
+ },
530
+ BRAH: {
531
+ address: "6Mv8Cdt2bKdY7F2Hp9Ry5qKjCwT3pD4vX8uE1N2aS9mB",
532
+ name: "Brah"
533
+ },
534
+ TWINS: {
535
+ address: "2YbKvnUmZ8fP3gGqW5N7vR4xQ6j9S1tE8cHa5mXp6NdA",
536
+ name: "Twins"
537
+ },
538
+ CHAOS: {
539
+ address: "5Tqn9G2fR8eQ4HaS6pN7jK9xL3wU4yV5cD2bY1mE8vP",
540
+ name: "Chaos"
541
+ },
542
+ KOTO: {
543
+ address: "DEF1R2s6o9rN4eQ5jY8fX7pK3cL2wE6tV9bH5gAm1StU",
544
+ name: "Koto"
545
+ },
546
+ DEV: {
547
+ address: "DEVeLopER123456789aBcDeFgHiJkLmNoPqRsTuVwXyZ",
548
+ name: "Dev"
549
+ },
550
+ EREBRO: {
551
+ address: "7mHq9P3fN8eW2rA4sL6kQ1oY5tG3vX9bE8cZ2jR7iUxM",
552
+ name: "Erebro"
553
+ },
554
+ // More popular tokens from Discord data
555
+ AIGENT: {
556
+ address: "CEB5RVRvC4p8e2NHT5Nw9g6fhGtB3dSkNYpJ8KzF5mqA",
557
+ name: "Aigent"
558
+ },
559
+ AICZ: {
560
+ address: "CJvVNpBcuk88JfT3v2fkSePvFgJ5Fy4BhQ3KN2rXV4j",
561
+ name: "AICZ"
562
+ },
563
+ TURA: {
564
+ address: "9cA4M3RfGvBZYrQmnHJgCB2fX8LKfT9eWp1Vq5Rx3StN",
565
+ name: "Tura"
566
+ },
567
+ TNSR: {
568
+ address: "TNSRxcUxoT9xBG3de7PiJyTDYu7kskLqcpddxnEJAS6",
569
+ name: "Tensor"
570
+ },
571
+ HIVO: {
572
+ address: "HIVE1234567890abcdefghijklmnopqrstuvwxyzABCD",
573
+ name: "Hivo"
574
+ },
575
+ BAIDEN: {
576
+ address: "BAIDENr78901234567890abcdefghijklmnopqrstuv",
577
+ name: "Baiden"
578
+ },
579
+ GRIN: {
580
+ address: "GRINtokenaddress1234567890abcdefghijklmnop",
581
+ name: "Grin"
582
+ },
583
+ FARTBOOK: {
584
+ address: "FART123456789abcdefghijklmnopqrstuvwxyzABC",
585
+ name: "Fartbook"
586
+ },
587
+ LUCE: {
588
+ address: "LUCE567890abcdefghijklmnopqrstuvwxyzABCDEF",
589
+ name: "Luce"
590
+ },
591
+ LUCY: {
592
+ address: "LUCY890abcdefghijklmnopqrstuvwxyzABCDEFGH",
593
+ name: "Lucy"
594
+ },
595
+ NORM: {
596
+ address: "NORMabcdefghijklmnopqrstuvwxyzABCDEFGHIJ",
597
+ name: "Norm"
598
+ },
599
+ ELIZA: {
600
+ address: "ELIZA123456789abcdefghijklmnopqrstuvwxy",
601
+ name: "Eliza"
602
+ },
603
+ TRUTH: {
604
+ address: "TRUTH456789abcdefghijklmnopqrstuvwxyzABC",
605
+ name: "Truth Terminal"
606
+ },
607
+ GRASS: {
608
+ address: "GRASS789abcdefghijklmnopqrstuvwxyzABCDEF",
609
+ name: "Grass"
610
+ },
611
+ ACT: {
612
+ address: "ACT123456789abcdefghijklmnopqrstuvwxyzAB",
613
+ name: "Act"
614
+ },
615
+ AGENTS: {
616
+ address: "AGENTS456789abcdefghijklmnopqrstuvwxyzA",
617
+ name: "Agents"
618
+ },
619
+ MIST: {
620
+ address: "MIST789abcdefghijklmnopqrstuvwxyzABCDEFG",
621
+ name: "Mist"
622
+ },
623
+ KASUMI: {
624
+ address: "KASUMI123456789abcdefghijklmnopqrstuvwx",
625
+ name: "Kasumi"
626
+ },
627
+ SPLICE: {
628
+ address: "SPLICE456789abcdefghijklmnopqrstuvwxyz",
629
+ name: "Splice"
630
+ }
631
+ };
632
+ if (knownSolanaTokens[cleanSymbol]) {
633
+ return {
634
+ address: knownSolanaTokens[cleanSymbol].address,
635
+ symbol: cleanSymbol,
636
+ name: knownSolanaTokens[cleanSymbol].name,
637
+ chain: SupportedChain.SOLANA
638
+ };
639
+ }
640
+ } else if (chain === SupportedChain.ETHEREUM) {
641
+ const knownEthereumTokens = {
642
+ ETH: {
643
+ address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
644
+ name: "Wrapped Ether"
645
+ },
646
+ USDC: {
647
+ address: "0xA0b86a33E6441c69De69b9A87e20b88dd75B61FC",
648
+ name: "USD Coin"
649
+ },
650
+ USDT: {
651
+ address: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
652
+ name: "Tether USD"
653
+ },
654
+ DAI: {
655
+ address: "0x6B175474E89094C44Da98b954EedeAC495271d0F",
656
+ name: "Dai Stablecoin"
657
+ },
658
+ LINK: {
659
+ address: "0x514910771AF9Ca656af840dff83E8264EcF986CA",
660
+ name: "Chainlink"
661
+ },
662
+ UNI: {
663
+ address: "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984",
664
+ name: "Uniswap"
665
+ },
666
+ WBTC: {
667
+ address: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
668
+ name: "Wrapped BTC"
669
+ }
670
+ };
671
+ if (knownEthereumTokens[cleanSymbol]) {
672
+ return {
673
+ address: knownEthereumTokens[cleanSymbol].address,
674
+ symbol: cleanSymbol,
675
+ name: knownEthereumTokens[cleanSymbol].name,
676
+ chain: SupportedChain.ETHEREUM
677
+ };
678
+ }
679
+ } else if (chain === SupportedChain.BASE) {
680
+ const knownBaseTokens = {
681
+ ETH: {
682
+ address: "0x4200000000000000000000000000000000000006",
683
+ name: "Ether"
684
+ },
685
+ USDC: {
686
+ address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
687
+ name: "USD Coin"
688
+ },
689
+ WETH: {
690
+ address: "0x4200000000000000000000000000000000000006",
691
+ name: "Wrapped Ether"
692
+ }
693
+ };
694
+ if (knownBaseTokens[cleanSymbol]) {
695
+ return {
696
+ address: knownBaseTokens[cleanSymbol].address,
697
+ symbol: cleanSymbol,
698
+ name: knownBaseTokens[cleanSymbol].name,
699
+ chain: SupportedChain.BASE
700
+ };
701
+ }
702
+ }
703
+ return null;
704
+ }
705
+ /**
706
+ * Search for token using DexScreener API
707
+ */
708
+ async searchTokenOnDexscreener(symbol, chain) {
709
+ try {
710
+ const searchResults = await this.dexscreenerClient.search(symbol, {
711
+ expires: "5m"
712
+ });
713
+ if (!searchResults?.pairs || searchResults.pairs.length === 0) {
714
+ return null;
715
+ }
716
+ let chainFilter;
717
+ switch (chain) {
718
+ case SupportedChain.SOLANA:
719
+ chainFilter = "solana";
720
+ break;
721
+ case SupportedChain.ETHEREUM:
722
+ chainFilter = "ethereum";
723
+ break;
724
+ case SupportedChain.BASE:
725
+ chainFilter = "base";
726
+ break;
727
+ default:
728
+ chainFilter = "solana";
729
+ }
730
+ let bestPair = searchResults.pairs.filter(
731
+ (pair) => pair.baseToken.symbol.toUpperCase() === symbol.toUpperCase() && pair.chainId.toLowerCase() === chainFilter
732
+ ).sort((a, b) => (b.liquidity?.usd || 0) - (a.liquidity?.usd || 0))[0];
733
+ if (!bestPair) {
734
+ bestPair = searchResults.pairs.filter(
735
+ (pair) => pair.baseToken.symbol.toUpperCase() === symbol.toUpperCase()
736
+ ).sort((a, b) => (b.liquidity?.usd || 0) - (a.liquidity?.usd || 0))[0];
737
+ }
738
+ if (!bestPair) {
739
+ bestPair = searchResults.pairs.filter(
740
+ (pair) => pair.baseToken.symbol.toUpperCase().includes(symbol.toUpperCase()) && pair.chainId.toLowerCase() === chainFilter
741
+ ).sort((a, b) => (b.liquidity?.usd || 0) - (a.liquidity?.usd || 0))[0];
742
+ }
743
+ if (!bestPair) {
744
+ bestPair = searchResults.pairs.filter(
745
+ (pair) => pair.baseToken.symbol.toUpperCase().includes(symbol.toUpperCase()) || pair.baseToken.name.toUpperCase().includes(symbol.toUpperCase())
746
+ ).sort((a, b) => (b.liquidity?.usd || 0) - (a.liquidity?.usd || 0))[0];
747
+ }
748
+ if (bestPair) {
749
+ let resolvedChain;
750
+ switch (bestPair.chainId.toLowerCase()) {
751
+ case "solana":
752
+ resolvedChain = SupportedChain.SOLANA;
753
+ break;
754
+ case "ethereum":
755
+ resolvedChain = SupportedChain.ETHEREUM;
756
+ break;
757
+ case "base":
758
+ resolvedChain = SupportedChain.BASE;
759
+ break;
760
+ default:
761
+ resolvedChain = chain;
762
+ }
763
+ return {
764
+ address: bestPair.baseToken.address,
765
+ symbol: bestPair.baseToken.symbol,
766
+ name: bestPair.baseToken.name,
767
+ chain: resolvedChain
768
+ };
769
+ }
770
+ return null;
771
+ } catch (error) {
772
+ console.error(`Error searching DexScreener for symbol ${symbol}:`, error);
773
+ return null;
774
+ }
775
+ }
776
+ }
777
+ export {
778
+ PriceEnrichmentService
779
+ };
780
+ //# sourceMappingURL=priceEnrichmentService.js.map