@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.
- package/dist/clients.d.ts +354 -0
- package/dist/clients.d.ts.map +1 -0
- package/dist/clients.js +670 -0
- package/dist/clients.js.map +1 -0
- package/dist/config.d.ts +144 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +122 -0
- package/dist/config.js.map +1 -0
- package/dist/events.d.ts +5 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +426 -0
- package/dist/events.js.map +1 -0
- package/dist/frontend/LeaderboardView.helpers.d.ts +6 -0
- package/dist/frontend/LeaderboardView.helpers.d.ts.map +1 -0
- package/dist/frontend/LeaderboardView.helpers.js +59 -0
- package/dist/frontend/LeaderboardView.helpers.js.map +1 -0
- package/dist/frontend/SocialAlphaSpatialView.d.ts +52 -0
- package/dist/frontend/SocialAlphaSpatialView.d.ts.map +1 -0
- package/dist/frontend/SocialAlphaSpatialView.js +72 -0
- package/dist/frontend/SocialAlphaSpatialView.js.map +1 -0
- package/dist/frontend/SocialAlphaView.d.ts +35 -0
- package/dist/frontend/SocialAlphaView.d.ts.map +1 -0
- package/dist/frontend/SocialAlphaView.js +125 -0
- package/dist/frontend/SocialAlphaView.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +73 -0
- package/dist/index.js.map +1 -0
- package/dist/mockPriceService.d.ts +22 -0
- package/dist/mockPriceService.d.ts.map +1 -0
- package/dist/mockPriceService.js +21 -0
- package/dist/mockPriceService.js.map +1 -0
- package/dist/providers/socialAlphaProvider.d.ts +15 -0
- package/dist/providers/socialAlphaProvider.d.ts.map +1 -0
- package/dist/providers/socialAlphaProvider.js +261 -0
- package/dist/providers/socialAlphaProvider.js.map +1 -0
- package/dist/register-terminal-view.d.ts +15 -0
- package/dist/register-terminal-view.d.ts.map +1 -0
- package/dist/register-terminal-view.js +21 -0
- package/dist/register-terminal-view.js.map +1 -0
- package/dist/register.d.ts +10 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +5 -0
- package/dist/register.js.map +1 -0
- package/dist/reports.d.ts +57 -0
- package/dist/reports.d.ts.map +1 -0
- package/dist/reports.js +455 -0
- package/dist/reports.js.map +1 -0
- package/dist/routes.d.ts +3 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +59 -0
- package/dist/routes.js.map +1 -0
- package/dist/schemas.d.ts +151 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +258 -0
- package/dist/schemas.js.map +1 -0
- package/dist/service.d.ts +306 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +3078 -0
- package/dist/service.js.map +1 -0
- package/dist/services/balancedTrustScoreCalculator.d.ts +61 -0
- package/dist/services/balancedTrustScoreCalculator.d.ts.map +1 -0
- package/dist/services/balancedTrustScoreCalculator.js +207 -0
- package/dist/services/balancedTrustScoreCalculator.js.map +1 -0
- package/dist/services/historicalPriceService.d.ts +59 -0
- package/dist/services/historicalPriceService.d.ts.map +1 -0
- package/dist/services/historicalPriceService.js +291 -0
- package/dist/services/historicalPriceService.js.map +1 -0
- package/dist/services/index.d.ts +12 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +17 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/priceEnrichmentService.d.ts +109 -0
- package/dist/services/priceEnrichmentService.d.ts.map +1 -0
- package/dist/services/priceEnrichmentService.js +780 -0
- package/dist/services/priceEnrichmentService.js.map +1 -0
- package/dist/services/simulationActorsV2.d.ts +54 -0
- package/dist/services/simulationActorsV2.d.ts.map +1 -0
- package/dist/services/simulationActorsV2.js +362 -0
- package/dist/services/simulationActorsV2.js.map +1 -0
- package/dist/services/simulationRunner.d.ts +113 -0
- package/dist/services/simulationRunner.d.ts.map +1 -0
- package/dist/services/simulationRunner.js +771 -0
- package/dist/services/simulationRunner.js.map +1 -0
- package/dist/services/tokenSimulationService.d.ts +34 -0
- package/dist/services/tokenSimulationService.d.ts.map +1 -0
- package/dist/services/tokenSimulationService.js +297 -0
- package/dist/services/tokenSimulationService.js.map +1 -0
- package/dist/services/trustScoreOptimizer.d.ts +110 -0
- package/dist/services/trustScoreOptimizer.d.ts.map +1 -0
- package/dist/services/trustScoreOptimizer.js +635 -0
- package/dist/services/trustScoreOptimizer.js.map +1 -0
- package/dist/simulationActors.d.ts +35 -0
- package/dist/simulationActors.d.ts.map +1 -0
- package/dist/simulationActors.js +160 -0
- package/dist/simulationActors.js.map +1 -0
- package/dist/social-alpha-view-bundle.d.ts +2 -0
- package/dist/social-alpha-view-bundle.d.ts.map +1 -0
- package/dist/social-alpha-view-bundle.js +5 -0
- package/dist/social-alpha-view-bundle.js.map +1 -0
- package/dist/types.d.ts +937 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +46 -0
- package/dist/types.js.map +1 -0
- package/dist/views/brand/background/clouds_background.jpg +0 -0
- package/dist/views/brand/banners/eliza_banner.svg +20 -0
- package/dist/views/brand/banners/elizacloud_banner.svg +20 -0
- package/dist/views/brand/banners/elizaos_banner.svg +20 -0
- package/dist/views/brand/concepts/billboard_concept_1200.jpg +0 -0
- package/dist/views/brand/concepts/chibi_usb_concept_900.jpg +0 -0
- package/dist/views/brand/concepts/concept_minipc_900.jpg +0 -0
- package/dist/views/brand/concepts/concept_phone_800.jpg +0 -0
- package/dist/views/brand/concepts/concept_usbdrive_900.jpg +0 -0
- package/dist/views/brand/favicons/android-chrome-192x192.png +0 -0
- package/dist/views/brand/favicons/android-chrome-512x512.png +0 -0
- package/dist/views/brand/favicons/apple-touch-icon.png +0 -0
- package/dist/views/brand/favicons/favicon-16x16.png +0 -0
- package/dist/views/brand/favicons/favicon-32x32.png +0 -0
- package/dist/views/brand/favicons/favicon.ico +0 -0
- package/dist/views/brand/favicons/favicon.svg +17 -0
- package/dist/views/brand/logos/elizaOS_text_black.svg +3 -0
- package/dist/views/brand/logos/elizaOS_text_white.svg +3 -0
- package/dist/views/brand/logos/eliza_logotext.svg +26 -0
- package/dist/views/brand/logos/eliza_logotext_black.svg +26 -0
- package/dist/views/brand/logos/eliza_text_black.svg +3 -0
- package/dist/views/brand/logos/eliza_text_white.svg +3 -0
- package/dist/views/brand/logos/elizacloud_logotext.svg +26 -0
- package/dist/views/brand/logos/elizacloud_logotext_black.svg +26 -0
- package/dist/views/brand/logos/elizacloud_text_black.svg +3 -0
- package/dist/views/brand/logos/elizacloud_text_white.svg +3 -0
- package/dist/views/brand/logos/elizaos_logotext.svg +26 -0
- package/dist/views/brand/logos/elizaos_logotext_black.svg +26 -0
- package/dist/views/brand/logos/logo_blue_blackbg.svg +18 -0
- package/dist/views/brand/logos/logo_blue_nobg.svg +17 -0
- package/dist/views/brand/logos/logo_orange_blackbg.svg +18 -0
- package/dist/views/brand/logos/logo_orange_nobg.svg +17 -0
- package/dist/views/brand/logos/logo_white_blackbg.svg +25 -0
- package/dist/views/brand/logos/logo_white_bluebg.svg +25 -0
- package/dist/views/brand/logos/logo_white_graybg.svg +18 -0
- package/dist/views/brand/logos/logo_white_nobg.svg +24 -0
- package/dist/views/brand/logos/logo_white_orangebg.svg +25 -0
- package/dist/views/brand/ogembeds/eliza_ogembed.png +0 -0
- package/dist/views/brand/ogembeds/eliza_ogembed.svg +20 -0
- package/dist/views/brand/ogembeds/elizacloud_ogembed.png +0 -0
- package/dist/views/brand/ogembeds/elizacloud_ogembed.svg +20 -0
- package/dist/views/brand/ogembeds/elizaos_ogembed.png +0 -0
- package/dist/views/brand/ogembeds/elizaos_ogembed.svg +20 -0
- package/dist/views/bundle.js +268 -0
- package/dist/views/bundle.js.map +1 -0
- package/dist/views/site.webmanifest +19 -0
- 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
|