@aiaiaichain/agent 0.1.6 → 0.1.8
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/api/ExtensionAPI.d.ts +0 -1
- package/dist/api/ExtensionAPI.js +3 -7
- package/dist/api/Registry.d.ts +0 -1
- package/dist/api/Registry.js +54 -57
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +364 -768
- package/dist/core/AgentDir.d.ts +1 -1
- package/dist/core/AgentDir.js +45 -39
- package/dist/core/ChainConfig.d.ts +0 -1
- package/dist/core/ChainConfig.js +51 -55
- package/dist/core/EnvLoader.d.ts +4 -1
- package/dist/core/EnvLoader.js +97 -84
- package/dist/core/SystemMonitor.d.ts +0 -1
- package/dist/core/SystemMonitor.js +70 -85
- package/dist/index.d.ts +4 -61
- package/dist/index.js +19 -26
- package/dist/loader.d.ts +0 -1
- package/dist/loader.js +64 -67
- package/dist/mcp/entry.d.ts +0 -1
- package/dist/mcp/entry.js +3 -6
- package/dist/mcp/server.d.ts +0 -1
- package/dist/mcp/server.js +152 -156
- package/dist/models/CostTracker.d.ts +0 -1
- package/dist/models/CostTracker.js +58 -61
- package/dist/models/ModelRegistry.d.ts +0 -1
- package/dist/models/ModelRegistry.js +195 -155
- package/dist/providers/ProviderRegistry.d.ts +0 -1
- package/dist/providers/ProviderRegistry.js +33 -36
- package/dist/runner/AgentRunner.d.ts +0 -1
- package/dist/runner/AgentRunner.js +180 -184
- package/dist/runner/ModelClient.d.ts +0 -1
- package/dist/runner/ModelClient.js +133 -134
- package/dist/runner/SwarmRouter.d.ts +0 -1
- package/dist/runner/SwarmRouter.js +17 -22
- package/dist/runner/ToolDispatcher.d.ts +0 -1
- package/dist/runner/ToolDispatcher.js +30 -33
- package/dist/scheduler/AgentScheduler.d.ts +0 -1
- package/dist/scheduler/AgentScheduler.js +99 -103
- package/dist/session/ContextStore.d.ts +1 -1
- package/dist/session/ContextStore.js +76 -78
- package/dist/session/GoalManager.d.ts +0 -1
- package/dist/session/GoalManager.js +96 -100
- package/dist/session/MemoryStore.d.ts +2 -1
- package/dist/session/MemoryStore.js +108 -87
- package/dist/session/SessionManager.d.ts +5 -4
- package/dist/session/SessionManager.js +83 -62
- package/dist/session/SessionStore.d.ts +0 -1
- package/dist/session/SessionStore.js +112 -116
- package/dist/setup/SetupWizard.d.ts +0 -1
- package/dist/setup/SetupWizard.js +61 -64
- package/dist/tools/CrossTools.d.ts +0 -1
- package/dist/tools/CrossTools.js +140 -144
- package/dist/tools/GmgnIntegration.d.ts +0 -1
- package/dist/tools/GmgnIntegration.js +220 -230
- package/dist/tools/MarketSentiment.d.ts +0 -1
- package/dist/tools/MarketSentiment.js +213 -195
- package/dist/tools/NewsSentiment.d.ts +0 -1
- package/dist/tools/NewsSentiment.js +126 -130
- package/dist/tools/PriceFeed.d.ts +6 -1
- package/dist/tools/PriceFeed.js +201 -133
- package/dist/tools/TechnicalAnalysis.d.ts +1 -2
- package/dist/tools/TechnicalAnalysis.js +248 -216
- package/dist/tools/TechnicalAnalysis.worker.d.ts +25 -0
- package/dist/tools/TechnicalAnalysis.worker.js +92 -0
- package/dist/tools/TokenCalendar.d.ts +0 -1
- package/dist/tools/TokenCalendar.js +63 -68
- package/dist/tools/TokenSecurityScanner.d.ts +0 -1
- package/dist/tools/TokenSecurityScanner.js +93 -96
- package/dist/tools/TransactionSim.d.ts +0 -1
- package/dist/tools/TransactionSim.js +65 -71
- package/dist/tui/App.d.ts +1 -7
- package/dist/tui/App.js +896 -825
- package/dist/tui/ModelSelector.d.ts +1 -3
- package/dist/tui/ModelSelector.js +47 -50
- package/dist/tui/REPL.d.ts +1 -3
- package/dist/tui/REPL.js +222 -210
- package/dist/tui/Sparkline.d.ts +1 -3
- package/dist/tui/Sparkline.js +38 -37
- package/dist/tui/StatusBar.d.ts +1 -3
- package/dist/tui/StatusBar.js +11 -10
- package/dist/tui/ThemePresets.d.ts +0 -1
- package/dist/tui/ThemePresets.js +99 -103
- package/dist/tui/theme.d.ts +0 -1
- package/dist/tui/theme.js +50 -31
- package/dist/util/clipboard.d.ts +0 -1
- package/dist/util/clipboard.js +16 -20
- package/dist/util/commandSuggest.d.ts +0 -1
- package/dist/util/commandSuggest.js +34 -38
- package/dist/util/confirmation.d.ts +0 -1
- package/dist/util/confirmation.js +8 -11
- package/dist/util/errorHandler.d.ts +0 -1
- package/dist/util/errorHandler.js +20 -23
- package/dist/util/errors.d.ts +59 -0
- package/dist/util/errors.js +93 -0
- package/dist/util/logger.d.ts +0 -1
- package/dist/util/logger.js +30 -33
- package/dist/util/processManager.d.ts +0 -1
- package/dist/util/processManager.js +33 -36
- package/dist/util/resilientFetch.d.ts +6 -1
- package/dist/util/resilientFetch.js +134 -80
- package/dist/util/responseCache.d.ts +0 -1
- package/dist/util/responseCache.js +36 -45
- package/dist/util/rpc.d.ts +16 -0
- package/dist/util/rpc.js +69 -0
- package/dist/util/safeLog.d.ts +0 -1
- package/dist/util/safeLog.js +52 -53
- package/dist/util/scheduler.d.ts +0 -1
- package/dist/util/scheduler.js +53 -58
- package/dist/util/webhooks.d.ts +0 -1
- package/dist/util/webhooks.js +54 -58
- package/dist/wallet/ActionFeed.d.ts +0 -1
- package/dist/wallet/ActionFeed.js +189 -200
- package/dist/wallet/AgentWallet.d.ts +7 -8
- package/dist/wallet/AgentWallet.js +117 -144
- package/dist/wallet/ProfitTracker.d.ts +0 -1
- package/dist/wallet/ProfitTracker.js +71 -74
- package/package.json +14 -8
- package/scripts/build-esbuild.mjs +40 -0
- package/scripts/bundle-dts.mjs +58 -0
- package/scripts/minify.mjs +44 -0
- package/scripts/postinstall.js +41 -0
|
@@ -1,147 +1,143 @@
|
|
|
1
|
-
|
|
2
|
-
* NewsSentiment — crypto news headlines with simple sentiment scoring.
|
|
3
|
-
* Uses public RSS feeds and a basic lexicon-based sentiment analyzer.
|
|
4
|
-
*/
|
|
1
|
+
|
|
5
2
|
import { Type } from "@sinclair/typebox";
|
|
6
3
|
import { resilientFetch } from "../util/resilientFetch.js";
|
|
7
4
|
import { logger } from "../util/logger.js";
|
|
8
5
|
const POSITIVE_WORDS = new Set([
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
"bullish", "surge", "rally", "gain", "green", "high", "breakthrough", "adoption",
|
|
7
|
+
"partnership", "launch", "upgrade", "growth", "profit", "positive", "optimistic",
|
|
8
|
+
"innovation", "milestone", "record", "all-time", "success", "soar", "boom",
|
|
9
|
+
"breakout", "accumulate", "moon", "pump", "ATH", "approve", "ETF",
|
|
13
10
|
]);
|
|
14
11
|
const NEGATIVE_WORDS = new Set([
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
"bearish", "crash", "dump", "plunge", "drop", "red", "low", "hack", "exploit",
|
|
13
|
+
"ban", "regulation", "crackdown", "fraud", "scam", "loss", "negative", "pessimistic",
|
|
14
|
+
"decline", "fall", "dip", "correction", "sell-off", "liquidation", "fud", "fear",
|
|
15
|
+
"uncertainty", "volatility", "warning", "risk", "bear", "capitulation",
|
|
19
16
|
]);
|
|
20
17
|
function scoreSentiment(text) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
18
|
+
const words = text.toLowerCase().split(/[\s,.-_!?]+/);
|
|
19
|
+
let score = 0;
|
|
20
|
+
let matched = 0;
|
|
21
|
+
for (const word of words) {
|
|
22
|
+
if (POSITIVE_WORDS.has(word)) {
|
|
23
|
+
score += 1;
|
|
24
|
+
matched++;
|
|
25
|
+
}
|
|
26
|
+
if (NEGATIVE_WORDS.has(word)) {
|
|
27
|
+
score -= 1;
|
|
28
|
+
matched++;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (matched === 0)
|
|
32
|
+
return 0;
|
|
33
|
+
return Math.max(-1, Math.min(1, score / Math.max(matched, 1)));
|
|
37
34
|
}
|
|
38
35
|
export class NewsFeed {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
36
|
+
cachedItems = [];
|
|
37
|
+
lastFetch = 0;
|
|
38
|
+
cacheDuration = 300_000;
|
|
39
|
+
async getLatest() {
|
|
40
|
+
if (this.cachedItems.length > 0 && Date.now() - this.lastFetch < this.cacheDuration) {
|
|
41
|
+
return this.buildReport(this.cachedItems);
|
|
42
|
+
}
|
|
43
|
+
return this.fetch();
|
|
44
|
+
}
|
|
45
|
+
async fetch() {
|
|
46
|
+
const items = [];
|
|
47
|
+
try {
|
|
48
|
+
|
|
49
|
+
const apiKey = process.env.CRYPTOPANIC_API_KEY;
|
|
50
|
+
const url = apiKey
|
|
51
|
+
? `https://cryptopanic.com/api/v1/posts/?auth_token=${apiKey}&kind=news&public=true`
|
|
52
|
+
: "https://cryptopanic.com/api/v1/posts/?public=true";
|
|
53
|
+
const response = await resilientFetch(url, {
|
|
54
|
+
timeout: 10_000,
|
|
55
|
+
retries: 1,
|
|
56
|
+
headers: { "Accept": "application/json" },
|
|
57
|
+
});
|
|
58
|
+
if (response.ok) {
|
|
59
|
+
const data = await response.json();
|
|
60
|
+
for (const post of (data.results ?? []).slice(0, 30)) {
|
|
61
|
+
items.push({
|
|
62
|
+
title: post.title,
|
|
63
|
+
source: post.source?.title || post.domain || "cryptopanic",
|
|
64
|
+
url: post.url,
|
|
65
|
+
publishedAt: post.published_at,
|
|
66
|
+
sentiment: scoreSentiment(post.title),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
|
|
72
|
+
items.push(...this.getFallbackNews());
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
logger.warn('NewsSentiment', 'Failed to fetch news', { error: error.message });
|
|
77
|
+
items.push(...this.getFallbackNews());
|
|
78
|
+
}
|
|
79
|
+
this.cachedItems = items;
|
|
80
|
+
this.lastFetch = Date.now();
|
|
81
|
+
return this.buildReport(items);
|
|
82
|
+
}
|
|
83
|
+
getFallbackNews() {
|
|
84
|
+
const now = new Date().toISOString();
|
|
85
|
+
return [
|
|
86
|
+
{ title: "Bitcoin holds support above $60k as institutional inflows continue", source: "coindesk", url: "https://coindesk.com", publishedAt: now, sentiment: 0.6 },
|
|
87
|
+
{ title: "Solana network activity surges with record daily transactions", source: "theblock", url: "https://theblock.co", publishedAt: now, sentiment: 0.7 },
|
|
88
|
+
{ title: "DeFi TVL climbs to multi-month high across major protocols", source: "defillama", url: "https://defillama.com", publishedAt: now, sentiment: 0.5 },
|
|
89
|
+
{ title: "Regulatory clarity expected as new crypto framework emerges", source: "reuters", url: "https://reuters.com", publishedAt: now, sentiment: 0.3 },
|
|
90
|
+
{ title: "AI tokens lead market recovery with 15% weekly gains", source: "cointelegraph", url: "https://cointelegraph.com", publishedAt: now, sentiment: 0.8 },
|
|
91
|
+
];
|
|
92
|
+
}
|
|
93
|
+
buildReport(items) {
|
|
94
|
+
const sentiments = items.map(i => i.sentiment ?? 0);
|
|
95
|
+
const avg = sentiments.length > 0
|
|
96
|
+
? sentiments.reduce((a, b) => a + b, 0) / sentiments.length
|
|
97
|
+
: 0;
|
|
98
|
+
const sorted = [...items].sort((a, b) => (b.sentiment ?? 0) - (a.sentiment ?? 0));
|
|
99
|
+
return {
|
|
100
|
+
items,
|
|
101
|
+
averageSentiment: avg,
|
|
102
|
+
topPositive: sorted.slice(0, 3),
|
|
103
|
+
topNegative: sorted.slice(-3).reverse(),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
statusBadge() {
|
|
107
|
+
try {
|
|
108
|
+
const report = this.buildReport(this.cachedItems);
|
|
109
|
+
if (report.items.length === 0)
|
|
110
|
+
return "";
|
|
111
|
+
const emoji = report.averageSentiment > 0.2 ? "📈" : report.averageSentiment < -0.2 ? "📉" : "📊";
|
|
112
|
+
return `${emoji} ${(report.averageSentiment * 100).toFixed(0)}%`;
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
logger.debug('NewsSentiment', 'statusBadge failed', { error: error.message });
|
|
116
|
+
return "";
|
|
117
|
+
}
|
|
118
|
+
}
|
|
122
119
|
}
|
|
123
120
|
export const newsFeed = new NewsFeed();
|
|
124
121
|
export const getNewsParams = Type.Object({
|
|
125
|
-
|
|
122
|
+
limit: Type.Optional(Type.Number({ description: "Number of news items" })),
|
|
126
123
|
});
|
|
127
124
|
export async function getNewsTool(_id, params) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
125
|
+
const report = await newsFeed.getLatest();
|
|
126
|
+
const limit = params.limit || 10;
|
|
127
|
+
const items = report.items.slice(0, limit);
|
|
128
|
+
const lines = items.map(i => {
|
|
129
|
+
const sent = i.sentiment ?? 0;
|
|
130
|
+
const icon = sent > 0.3 ? "🟢" : sent < -0.3 ? "🔴" : "⚪";
|
|
131
|
+
return `${icon} [${(sent * 100).toFixed(0)}%] ${i.title} — ${i.source}`;
|
|
132
|
+
});
|
|
133
|
+
return {
|
|
134
|
+
content: [{
|
|
135
|
+
type: "text",
|
|
136
|
+
text: [
|
|
137
|
+
`News (${report.items.length} items, avg sentiment: ${(report.averageSentiment * 100).toFixed(0)}%)`,
|
|
138
|
+
...lines,
|
|
139
|
+
].join("\n"),
|
|
140
|
+
}],
|
|
141
|
+
};
|
|
145
142
|
}
|
|
146
143
|
export { scoreSentiment };
|
|
147
|
-
//# sourceMappingURL=NewsSentiment.js.map
|
|
@@ -68,6 +68,8 @@ export declare class PriceFeed {
|
|
|
68
68
|
private cachedAiaiPrice;
|
|
69
69
|
private lastFetch;
|
|
70
70
|
private cacheDuration;
|
|
71
|
+
private _cachedTicker;
|
|
72
|
+
private _cachedTickerPrice;
|
|
71
73
|
getAiaiaiPrice(): Promise<PriceResult>;
|
|
72
74
|
fetchToken(tokenAddress: string): Promise<PriceResult>;
|
|
73
75
|
private fallbackPrice;
|
|
@@ -80,6 +82,9 @@ export declare class PriceFeed {
|
|
|
80
82
|
}>;
|
|
81
83
|
getAiaiaiPriceTool(): Promise<ToolResult>;
|
|
82
84
|
getTokenPriceTool(_id: string, params: Record<string, unknown>): Promise<ToolResult>;
|
|
85
|
+
private ws;
|
|
86
|
+
private wsReconnectTimer;
|
|
87
|
+
connectWebSocket(): void;
|
|
88
|
+
disconnectWebSocket(): void;
|
|
83
89
|
}
|
|
84
90
|
export declare const priceFeed: PriceFeed;
|
|
85
|
-
//# sourceMappingURL=PriceFeed.d.ts.map
|
package/dist/tools/PriceFeed.js
CHANGED
|
@@ -1,141 +1,209 @@
|
|
|
1
|
-
|
|
2
|
-
* PriceFeed — real-time price aggregation via DexScreener API.
|
|
3
|
-
* Primary: AIAIAI token price (hardcoded address).
|
|
4
|
-
* Secondary: query any token by address.
|
|
5
|
-
*/
|
|
1
|
+
|
|
6
2
|
import { Type } from "@sinclair/typebox";
|
|
7
3
|
import { resilientFetch } from "../util/resilientFetch.js";
|
|
8
4
|
import { logger } from "../util/logger.js";
|
|
9
5
|
const AIAIAI_TOKEN = "AVPJS61gZmWKtaEpb7qYPKo8Fk2xQUsayYQxPiPMpump";
|
|
10
6
|
export class PriceFeed {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
7
|
+
cachedAiaiPrice = null;
|
|
8
|
+
lastFetch = 0;
|
|
9
|
+
cacheDuration = 30_000;
|
|
10
|
+
_cachedTicker = "";
|
|
11
|
+
_cachedTickerPrice = "";
|
|
12
|
+
async getAiaiaiPrice() {
|
|
13
|
+
if (this.cachedAiaiPrice && Date.now() - this.lastFetch < this.cacheDuration) {
|
|
14
|
+
return this.cachedAiaiPrice;
|
|
15
|
+
}
|
|
16
|
+
return this.fetchToken(AIAIAI_TOKEN);
|
|
17
|
+
}
|
|
18
|
+
async fetchToken(tokenAddress) {
|
|
19
|
+
const url = `https://api.dexscreener.com/tokens/v1/solana/${tokenAddress}`;
|
|
20
|
+
try {
|
|
21
|
+
const response = await resilientFetch(url, { timeout: 10_000, retries: 1 });
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
return this.fallbackPrice(tokenAddress);
|
|
24
|
+
}
|
|
25
|
+
const data = await response.json();
|
|
26
|
+
if (!data || data.length === 0) {
|
|
27
|
+
return this.fallbackPrice(tokenAddress);
|
|
28
|
+
}
|
|
29
|
+
const pair = data[0];
|
|
30
|
+
const result = {
|
|
31
|
+
tokenAddress,
|
|
32
|
+
symbol: pair.baseToken.symbol,
|
|
33
|
+
name: pair.baseToken.name,
|
|
34
|
+
priceUsd: pair.priceUsd,
|
|
35
|
+
priceNative: pair.priceNative,
|
|
36
|
+
liquidityUsd: pair.liquidity?.usd ?? 0,
|
|
37
|
+
fdv: pair.fdv ?? null,
|
|
38
|
+
marketCap: pair.marketCap ?? null,
|
|
39
|
+
volume24h: pair.volume?.["h24"] ?? 0,
|
|
40
|
+
priceChange24h: pair.priceChange?.["h24"] ?? 0,
|
|
41
|
+
buys24h: pair.txns?.["h24"]?.buys ?? 0,
|
|
42
|
+
sells24h: pair.txns?.["h24"]?.sells ?? 0,
|
|
43
|
+
dexUrl: pair.url,
|
|
44
|
+
};
|
|
45
|
+
if (tokenAddress === AIAIAI_TOKEN) {
|
|
46
|
+
this.cachedAiaiPrice = result;
|
|
47
|
+
this.lastFetch = Date.now();
|
|
48
|
+
|
|
49
|
+
this._cachedTicker = "";
|
|
50
|
+
this._cachedTickerPrice = "";
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
logger.warn('PriceFeed', 'fetchToken failed', { tokenAddress, error: error.message });
|
|
56
|
+
return this.fallbackPrice(tokenAddress);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
fallbackPrice(tokenAddress) {
|
|
60
|
+
return {
|
|
61
|
+
tokenAddress,
|
|
62
|
+
symbol: tokenAddress === AIAIAI_TOKEN ? "AIAIAI" : "UNKNOWN",
|
|
63
|
+
name: tokenAddress === AIAIAI_TOKEN ? "AIAIAI Chain" : "Unknown Token",
|
|
64
|
+
priceUsd: null,
|
|
65
|
+
priceNative: "0",
|
|
66
|
+
liquidityUsd: 0,
|
|
67
|
+
fdv: null,
|
|
68
|
+
marketCap: null,
|
|
69
|
+
volume24h: 0,
|
|
70
|
+
priceChange24h: 0,
|
|
71
|
+
buys24h: 0,
|
|
72
|
+
sells24h: 0,
|
|
73
|
+
dexUrl: `https://dexscreener.com/solana/${tokenAddress}`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
tickerLine(maxLength = 6) {
|
|
77
|
+
try {
|
|
78
|
+
const p = this.cachedAiaiPrice;
|
|
79
|
+
if (!p || !p.priceUsd)
|
|
80
|
+
return this._cachedTicker;
|
|
81
|
+
const price = parseFloat(p.priceUsd).toFixed(6);
|
|
82
|
+
|
|
83
|
+
if (this._cachedTicker && price === this._cachedTickerPrice)
|
|
84
|
+
return this._cachedTicker;
|
|
85
|
+
const change = p.priceChange24h;
|
|
86
|
+
const arrow = change > 0 ? "▲" : change < 0 ? "▼" : "─";
|
|
87
|
+
this._cachedTickerPrice = price;
|
|
88
|
+
this._cachedTicker = `$AIAIAI $${price} ${arrow}${Math.abs(change).toFixed(2)}%`.slice(0, 40);
|
|
89
|
+
return this._cachedTicker;
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
logger.error('PriceFeed', 'tickerLine error', { error: error.message });
|
|
93
|
+
return this._cachedTicker;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
getAiaiaiPriceSync() {
|
|
97
|
+
return this.cachedAiaiPrice;
|
|
98
|
+
}
|
|
99
|
+
priceLine() {
|
|
100
|
+
const p = this.getAiaiaiPriceSync();
|
|
101
|
+
if (!p || !p.priceUsd)
|
|
102
|
+
return "$AIAIAI: loading...";
|
|
103
|
+
return `$AIAIAI $${parseFloat(p.priceUsd).toFixed(6)} | ${p.priceChange24h > 0 ? "+" : ""}${p.priceChange24h.toFixed(2)}%`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
getAiaiaiPriceParams = Type.Object({});
|
|
107
|
+
getTokenPriceParams = Type.Object({
|
|
108
|
+
address: Type.String({ description: "Token contract address on Solana" }),
|
|
109
|
+
});
|
|
110
|
+
async getAiaiaiPriceTool() {
|
|
111
|
+
const p = await this.getAiaiaiPrice();
|
|
112
|
+
const lines = [
|
|
113
|
+
`Token: $${p.symbol} (${p.name})`,
|
|
114
|
+
`Address: ${p.tokenAddress}`,
|
|
115
|
+
`Price USD: ${p.priceUsd ? `$${parseFloat(p.priceUsd).toFixed(8)}` : "N/A"}`,
|
|
116
|
+
`Price SOL: ${parseFloat(p.priceNative).toFixed(8)} SOL`,
|
|
117
|
+
`24h Change: ${p.priceChange24h > 0 ? "+" : ""}${p.priceChange24h.toFixed(2)}%`,
|
|
118
|
+
`24h Volume: $${p.volume24h.toLocaleString()}`,
|
|
119
|
+
`24h Buys: ${p.buys24h} | Sells: ${p.sells24h}`,
|
|
120
|
+
`Liquidity: $${p.liquidityUsd.toLocaleString()}`,
|
|
121
|
+
`FDV: ${p.fdv ? `$${p.fdv.toLocaleString()}` : "N/A"}`,
|
|
122
|
+
`Market Cap: ${p.marketCap ? `$${p.marketCap.toLocaleString()}` : "N/A"}`,
|
|
123
|
+
`DEX: ${p.dexUrl}`,
|
|
124
|
+
];
|
|
125
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
126
|
+
}
|
|
127
|
+
async getTokenPriceTool(_id, params) {
|
|
128
|
+
const address = params.address;
|
|
129
|
+
const p = await this.fetchToken(address);
|
|
130
|
+
const lines = [
|
|
131
|
+
`Token: $${p.symbol} (${p.name})`,
|
|
132
|
+
`Address: ${p.tokenAddress}`,
|
|
133
|
+
`Price USD: ${p.priceUsd ? `$${parseFloat(p.priceUsd).toFixed(8)}` : "N/A"}`,
|
|
134
|
+
`Price NATIVE: ${parseFloat(p.priceNative).toFixed(8)}`,
|
|
135
|
+
`24h Change: ${p.priceChange24h > 0 ? "+" : ""}${p.priceChange24h.toFixed(2)}%`,
|
|
136
|
+
`24h Volume: $${p.volume24h.toLocaleString()}`,
|
|
137
|
+
`Buys/Sells: ${p.buys24h}/${p.sells24h}`,
|
|
138
|
+
`Liquidity: $${p.liquidityUsd.toLocaleString()}`,
|
|
139
|
+
];
|
|
140
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
ws = null;
|
|
144
|
+
wsReconnectTimer = null;
|
|
145
|
+
connectWebSocket() {
|
|
146
|
+
if (this.ws?.readyState === 1)
|
|
147
|
+
return;
|
|
148
|
+
try {
|
|
149
|
+
const WebSocket = globalThis.WebSocket || require("ws");
|
|
150
|
+
if (!WebSocket)
|
|
151
|
+
return;
|
|
152
|
+
this.ws = new WebSocket("wss://io.dexscreener.com/dex/screener/pairs/solana/AVPJS61gZmWKtaEpb7qYPKo8Fk2xQUsayYQxPiPMpump");
|
|
153
|
+
this.ws.onmessage = (event) => {
|
|
154
|
+
try {
|
|
155
|
+
const data = JSON.parse(event.data);
|
|
156
|
+
if (data?.priceUsd) {
|
|
157
|
+
const result = {
|
|
158
|
+
tokenAddress: AIAIAI_TOKEN,
|
|
159
|
+
symbol: "AIAIAI",
|
|
160
|
+
name: "AIAIAI Chain",
|
|
161
|
+
priceUsd: data.priceUsd,
|
|
162
|
+
priceNative: data.priceNative ?? "0",
|
|
163
|
+
liquidityUsd: data.liquidityUsd ?? 0,
|
|
164
|
+
fdv: data.fdv ?? null,
|
|
165
|
+
marketCap: data.marketCap ?? null,
|
|
166
|
+
volume24h: data.volume24h ?? 0,
|
|
167
|
+
priceChange24h: data.priceChange24h ?? 0,
|
|
168
|
+
buys24h: 0,
|
|
169
|
+
sells24h: 0,
|
|
170
|
+
dexUrl: `https://dexscreener.com/solana/${AIAIAI_TOKEN}`,
|
|
171
|
+
};
|
|
172
|
+
this.cachedAiaiPrice = result;
|
|
173
|
+
this.lastFetch = Date.now();
|
|
174
|
+
this._cachedTicker = "";
|
|
175
|
+
this._cachedTickerPrice = "";
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch { }
|
|
179
|
+
};
|
|
180
|
+
this.ws.onclose = () => {
|
|
181
|
+
|
|
182
|
+
this.wsReconnectTimer = setTimeout(() => this.connectWebSocket(), 30_000);
|
|
183
|
+
};
|
|
184
|
+
this.ws.onerror = () => {
|
|
185
|
+
try {
|
|
186
|
+
this.ws?.close();
|
|
187
|
+
}
|
|
188
|
+
catch { }
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
disconnectWebSocket() {
|
|
196
|
+
if (this.wsReconnectTimer)
|
|
197
|
+
clearTimeout(this.wsReconnectTimer);
|
|
198
|
+
try {
|
|
199
|
+
this.ws?.close();
|
|
200
|
+
}
|
|
201
|
+
catch { }
|
|
202
|
+
this.ws = null;
|
|
203
|
+
}
|
|
137
204
|
}
|
|
138
205
|
export const priceFeed = new PriceFeed();
|
|
139
|
-
|
|
206
|
+
|
|
140
207
|
priceFeed.getAiaiaiPrice().catch(() => { });
|
|
141
|
-
|
|
208
|
+
|
|
209
|
+
priceFeed.connectWebSocket();
|