@aiaiaichain/agent 0.1.3 → 0.1.5
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/README.md +2 -2
- package/dist/cli.js +274 -26
- package/dist/core/ChainConfig.js +1 -1
- package/dist/core/SystemMonitor.d.ts +32 -0
- package/dist/core/SystemMonitor.js +89 -0
- package/dist/index.d.ts +17 -2
- package/dist/index.js +18 -1
- package/dist/models/ModelRegistry.js +12 -4
- package/dist/runner/AgentRunner.d.ts +2 -0
- package/dist/runner/AgentRunner.js +18 -1
- package/dist/runner/ModelClient.js +109 -48
- package/dist/session/SessionManager.d.ts +1 -0
- package/dist/session/SessionManager.js +8 -2
- package/dist/session/SessionStore.d.ts +45 -0
- package/dist/session/SessionStore.js +128 -0
- package/dist/tools/CrossTools.d.ts +52 -0
- package/dist/tools/CrossTools.js +190 -0
- package/dist/tools/MarketSentiment.js +22 -13
- package/dist/tools/NewsSentiment.js +9 -3
- package/dist/tools/PriceFeed.js +11 -4
- package/dist/tools/TechnicalAnalysis.js +2 -1
- package/dist/tools/TokenCalendar.d.ts +24 -0
- package/dist/tools/TokenCalendar.js +81 -0
- package/dist/tools/TokenSecurityScanner.d.ts +22 -0
- package/dist/tools/TokenSecurityScanner.js +102 -0
- package/dist/tools/TransactionSim.d.ts +17 -0
- package/dist/tools/TransactionSim.js +78 -0
- package/dist/tui/App.d.ts +4 -3
- package/dist/tui/App.js +371 -118
- package/dist/tui/REPL.d.ts +2 -1
- package/dist/tui/REPL.js +190 -16
- package/dist/tui/Sparkline.d.ts +21 -0
- package/dist/tui/Sparkline.js +44 -0
- package/dist/tui/StatusBar.d.ts +5 -1
- package/dist/tui/StatusBar.js +6 -4
- package/dist/tui/ThemePresets.d.ts +25 -0
- package/dist/tui/ThemePresets.js +117 -0
- package/dist/util/clipboard.d.ts +9 -0
- package/dist/util/clipboard.js +26 -0
- package/dist/util/commandSuggest.d.ts +7 -0
- package/dist/util/commandSuggest.js +44 -0
- package/dist/util/confirmation.d.ts +6 -0
- package/dist/util/confirmation.js +16 -0
- package/dist/util/errorHandler.d.ts +3 -0
- package/dist/util/errorHandler.js +28 -0
- package/dist/util/logger.d.ts +11 -0
- package/dist/util/logger.js +43 -0
- package/dist/util/processManager.d.ts +5 -0
- package/dist/util/processManager.js +39 -0
- package/dist/util/resilientFetch.d.ts +21 -0
- package/dist/util/resilientFetch.js +94 -0
- package/dist/util/responseCache.d.ts +27 -0
- package/dist/util/responseCache.js +54 -0
- package/dist/util/safeLog.d.ts +4 -5
- package/dist/util/safeLog.js +68 -30
- package/dist/util/scheduler.d.ts +14 -0
- package/dist/util/scheduler.js +75 -0
- package/dist/util/webhooks.d.ts +9 -0
- package/dist/util/webhooks.js +75 -0
- package/dist/wallet/ActionFeed.js +12 -4
- package/dist/wallet/AgentWallet.d.ts +2 -0
- package/dist/wallet/AgentWallet.js +31 -5
- package/dist/wallet/ProfitTracker.d.ts +30 -0
- package/dist/wallet/ProfitTracker.js +93 -0
- package/docs/COMMANDS.md +40 -9
- package/docs/README.md +2 -0
- package/package.json +5 -3
- package/scripts/postinstall.js +34 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CrossTools — automation tools that combine multiple data sources.
|
|
3
|
+
* Watch tokens, compare, portfolio tracking, price alerts.
|
|
4
|
+
*/
|
|
5
|
+
import { Type } from "@sinclair/typebox";
|
|
6
|
+
import { priceFeed } from "../tools/PriceFeed.js";
|
|
7
|
+
import { AIAIAI_TOKEN, agentWallet } from "../wallet/AgentWallet.js";
|
|
8
|
+
import { logger } from "../util/logger.js";
|
|
9
|
+
const alerts = [];
|
|
10
|
+
export function addAlert(token, type, price) {
|
|
11
|
+
const alert = {
|
|
12
|
+
id: `alert-${Date.now()}-${Math.random().toString(36).slice(2, 5)}`,
|
|
13
|
+
token,
|
|
14
|
+
type,
|
|
15
|
+
price,
|
|
16
|
+
active: true,
|
|
17
|
+
createdAt: Date.now(),
|
|
18
|
+
};
|
|
19
|
+
alerts.push(alert);
|
|
20
|
+
return alert;
|
|
21
|
+
}
|
|
22
|
+
export function checkAlerts(currentPrice, token) {
|
|
23
|
+
const triggered = [];
|
|
24
|
+
for (const alert of alerts) {
|
|
25
|
+
if (!alert.active || alert.token !== token)
|
|
26
|
+
continue;
|
|
27
|
+
if (alert.type === 'above' && currentPrice >= alert.price) {
|
|
28
|
+
alert.active = false;
|
|
29
|
+
alert.triggeredAt = Date.now();
|
|
30
|
+
triggered.push(alert);
|
|
31
|
+
}
|
|
32
|
+
else if (alert.type === 'below' && currentPrice <= alert.price) {
|
|
33
|
+
alert.active = false;
|
|
34
|
+
alert.triggeredAt = Date.now();
|
|
35
|
+
triggered.push(alert);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return triggered;
|
|
39
|
+
}
|
|
40
|
+
export function getActiveAlerts() {
|
|
41
|
+
return alerts.filter(a => a.active);
|
|
42
|
+
}
|
|
43
|
+
// ── Watch List ───────────────────────────────────────────────────────────────
|
|
44
|
+
const watchList = new Set();
|
|
45
|
+
export function addToWatchList(tokenAddress) {
|
|
46
|
+
watchList.add(tokenAddress);
|
|
47
|
+
}
|
|
48
|
+
export function removeFromWatchList(tokenAddress) {
|
|
49
|
+
watchList.delete(tokenAddress);
|
|
50
|
+
}
|
|
51
|
+
export function getWatchList() {
|
|
52
|
+
return [...watchList];
|
|
53
|
+
}
|
|
54
|
+
// ── Agent Tools ──────────────────────────────────────────────────────────────
|
|
55
|
+
export const watchTokenParams = Type.Object({
|
|
56
|
+
address: Type.String({ description: "Token contract address to watch" }),
|
|
57
|
+
});
|
|
58
|
+
export const removeWatchParams = Type.Object({
|
|
59
|
+
address: Type.String({ description: "Token contract address to remove" }),
|
|
60
|
+
});
|
|
61
|
+
export const listWatchParams = Type.Object({});
|
|
62
|
+
export const addAlertParams = Type.Object({
|
|
63
|
+
token: Type.String({ description: "Token address or 'AIAIAI'" }),
|
|
64
|
+
type: Type.String({ description: "Alert type: above or below" }),
|
|
65
|
+
price: Type.Number({ description: "Price threshold in USD" }),
|
|
66
|
+
});
|
|
67
|
+
export const checkAlertsParams = Type.Object({});
|
|
68
|
+
export const compareTokensParams = Type.Object({
|
|
69
|
+
address1: Type.String({ description: "First token address" }),
|
|
70
|
+
address2: Type.String({ description: "Second token address" }),
|
|
71
|
+
});
|
|
72
|
+
export async function watchTokenTool(_id, params) {
|
|
73
|
+
const addr = params.address;
|
|
74
|
+
addToWatchList(addr);
|
|
75
|
+
return { content: [{ type: "text", text: `👁️ Watching ${addr.slice(0, 8)}…${addr.slice(-6)}. You'll get alerts on significant moves.` }] };
|
|
76
|
+
}
|
|
77
|
+
export async function removeWatchTool(_id, params) {
|
|
78
|
+
const addr = params.address;
|
|
79
|
+
removeFromWatchList(addr);
|
|
80
|
+
return { content: [{ type: "text", text: `Removed ${addr.slice(0, 8)}… from watch list.` }] };
|
|
81
|
+
}
|
|
82
|
+
export async function listWatchTool() {
|
|
83
|
+
const list = getWatchList();
|
|
84
|
+
if (list.length === 0)
|
|
85
|
+
return { content: [{ type: "text", text: "Watch list is empty." }] };
|
|
86
|
+
const lines = list.map((addr, i) => ` ${i + 1}. ${addr.slice(0, 8)}…${addr.slice(-6)}`);
|
|
87
|
+
return { content: [{ type: "text", text: `Watch List (${list.length}):\n${lines.join("\n")}` }] };
|
|
88
|
+
}
|
|
89
|
+
export async function addAlertTool(_id, params) {
|
|
90
|
+
const token = params.token;
|
|
91
|
+
const type = params.type;
|
|
92
|
+
const price = params.price;
|
|
93
|
+
const alert = addAlert(token, type, price);
|
|
94
|
+
return { content: [{ type: "text", text: `🔔 Alert set: ${type} $${price} for ${token.slice(0, 8)}… (${alert.id})` }] };
|
|
95
|
+
}
|
|
96
|
+
export async function checkAlertsTool() {
|
|
97
|
+
const active = getActiveAlerts();
|
|
98
|
+
if (active.length === 0)
|
|
99
|
+
return { content: [{ type: "text", text: "No active alerts." }] };
|
|
100
|
+
const lines = active.map(a => ` ${a.id} — ${a.type} $${a.price} (${a.token.slice(0, 8)}…)`);
|
|
101
|
+
return { content: [{ type: "text", text: `Active Alerts (${active.length}):\n${lines.join("\n")}` }] };
|
|
102
|
+
}
|
|
103
|
+
export async function compareTokensTool(_id, params) {
|
|
104
|
+
const addr1 = params.address1;
|
|
105
|
+
const addr2 = params.address2;
|
|
106
|
+
let price1 = null, price2 = null;
|
|
107
|
+
try {
|
|
108
|
+
if (addr1 === AIAIAI_TOKEN || addr1.toLowerCase() === 'aiaiai') {
|
|
109
|
+
price1 = await priceFeed.getAiaiaiPrice();
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
price1 = await priceFeed.fetchToken(addr1);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
logger.warn('CrossTools', 'compareTokens: failed to fetch price1', { address: addr1, error: error.message });
|
|
117
|
+
price1 = null;
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
if (addr2 === AIAIAI_TOKEN || addr2.toLowerCase() === 'aiaiai') {
|
|
121
|
+
price2 = await priceFeed.getAiaiaiPrice();
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
price2 = await priceFeed.fetchToken(addr2);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
logger.warn('CrossTools', 'compareTokens: failed to fetch price2', { address: addr2, error: error.message });
|
|
129
|
+
price2 = null;
|
|
130
|
+
}
|
|
131
|
+
const lines = [
|
|
132
|
+
'── Token Comparison ──────────────────────────',
|
|
133
|
+
'',
|
|
134
|
+
`Token 1: ${addr1.slice(0, 8)}…`,
|
|
135
|
+
price1 ? ` Price: $${price1.priceUsd ?? 'N/A'}` : ' Price: N/A',
|
|
136
|
+
price1 ? ` 24h: ${price1.priceChange24h > 0 ? '+' : ''}${price1.priceChange24h.toFixed(2)}%` : ' 24h: N/A',
|
|
137
|
+
price1 ? ` Liq: $${(price1.liquidityUsd / 1000).toFixed(1)}k` : ' Liq: N/A',
|
|
138
|
+
price1 ? ` MCap: $${price1.marketCap ? (price1.marketCap / 1000).toFixed(1) + 'k' : 'N/A'}` : ' MCap: N/A',
|
|
139
|
+
'',
|
|
140
|
+
`Token 2: ${addr2.slice(0, 8)}…`,
|
|
141
|
+
price2 ? ` Price: $${price2.priceUsd ?? 'N/A'}` : ' Price: N/A',
|
|
142
|
+
price2 ? ` 24h: ${price2.priceChange24h > 0 ? '+' : ''}${price2.priceChange24h.toFixed(2)}%` : ' 24h: N/A',
|
|
143
|
+
price2 ? ` Liq: $${(price2.liquidityUsd / 1000).toFixed(1)}k` : ' Liq: N/A',
|
|
144
|
+
price2 ? ` MCap: $${price2.marketCap ? (price2.marketCap / 1000).toFixed(1) + 'k' : 'N/A'}` : ' MCap: N/A',
|
|
145
|
+
];
|
|
146
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
147
|
+
}
|
|
148
|
+
// ── Portfolio tracking ─────────────────────────────────────────────────────────────
|
|
149
|
+
export const portfolioParams = Type.Object({
|
|
150
|
+
address: Type.String({ description: "Wallet address to analyze" }),
|
|
151
|
+
});
|
|
152
|
+
export async function portfolioTool(_id, params) {
|
|
153
|
+
const address = params.address;
|
|
154
|
+
// Use agentWallet's cached balances if it's one of our wallets
|
|
155
|
+
let result = null;
|
|
156
|
+
try {
|
|
157
|
+
const all = await agentWallet.getAll();
|
|
158
|
+
if (address === COLD_WALLET)
|
|
159
|
+
result = all.cold;
|
|
160
|
+
else if (address === ACTION_WALLET)
|
|
161
|
+
result = all.action;
|
|
162
|
+
else if (address === DEPOSIT_WALLET)
|
|
163
|
+
result = all.deposit;
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
logger.warn('CrossTools', 'portfolioTool: wallet lookup failed', { address, error: error.message });
|
|
167
|
+
}
|
|
168
|
+
if (result) {
|
|
169
|
+
let solPrice = await new Promise(res => {
|
|
170
|
+
agentWallet.getSolPrice().then(res).catch(() => res(150));
|
|
171
|
+
});
|
|
172
|
+
const lines = [
|
|
173
|
+
`📊 Portfolio: ${address.slice(0, 8)}…${address.slice(-6)}`,
|
|
174
|
+
'',
|
|
175
|
+
` SOL: ${result.sol.toFixed(4)} ($${(result.sol * solPrice).toFixed(2)} est)`,
|
|
176
|
+
` $AIAIAI: ${result.aiaiai.toLocaleString(undefined, { maximumFractionDigits: 0 })}`,
|
|
177
|
+
` USDC: ${result.usdc.toFixed(2)}`,
|
|
178
|
+
'',
|
|
179
|
+
` Total value (est): $${(result.sol * solPrice + result.usdc).toFixed(2)} + $AIAIAI`,
|
|
180
|
+
];
|
|
181
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
content: [{ type: "text", text: `📊 Analyzed: ${address.slice(0, 8)}…${address.slice(-6)}\nUse /wallet or /deposit for tracked wallet balances.` }],
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
export const COLD_WALLET = "A11iZoqEt6hU7HyggqC67ee4AtYmaJjwKCvJLerJRV2J";
|
|
188
|
+
export const ACTION_WALLET = "BygDYM1ZXLQNC1HXLhnd1rHZ7E5XjioqT3vPjJFfjnU2";
|
|
189
|
+
export const DEPOSIT_WALLET = "FBMDYpG9WXKy4SgxuATQdB2sCyzHsJWPrEr45z3TgL2e";
|
|
190
|
+
//# sourceMappingURL=CrossTools.js.map
|
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
* Fear & Greed Index, Binance Funding Rates, BTC Mempool, DeFi TVL, Solana Stats.
|
|
4
4
|
*/
|
|
5
5
|
import { Type } from "@sinclair/typebox";
|
|
6
|
+
import { resilientFetch } from "../util/resilientFetch.js";
|
|
7
|
+
import { logger } from "../util/logger.js";
|
|
6
8
|
// ── Fear & Greed Index ──────────────────────────────────────────────────────
|
|
7
9
|
export const fearGreedParams = Type.Object({});
|
|
8
10
|
export async function getFearGreedTool() {
|
|
9
11
|
try {
|
|
10
|
-
const response = await
|
|
12
|
+
const response = await resilientFetch("https://api.alternative.me/fng/?limit=7", { timeout: 10_000, retries: 1 });
|
|
11
13
|
if (!response.ok)
|
|
12
14
|
return fallbackFearGreed();
|
|
13
15
|
const data = await response.json();
|
|
@@ -28,7 +30,8 @@ export async function getFearGreedTool() {
|
|
|
28
30
|
}],
|
|
29
31
|
};
|
|
30
32
|
}
|
|
31
|
-
catch {
|
|
33
|
+
catch (error) {
|
|
34
|
+
logger.warn('MarketSentiment', 'Fear & Greed fetch failed', { error: error.message });
|
|
32
35
|
return fallbackFearGreed();
|
|
33
36
|
}
|
|
34
37
|
}
|
|
@@ -47,7 +50,7 @@ export const fundingRatesParams = Type.Object({
|
|
|
47
50
|
export async function getFundingRatesTool(_id, params) {
|
|
48
51
|
const limit = params.limit || 10;
|
|
49
52
|
try {
|
|
50
|
-
const response = await
|
|
53
|
+
const response = await resilientFetch("https://fapi.binance.com/fapi/v1/premiumIndex", { timeout: 10_000, retries: 1 });
|
|
51
54
|
if (!response.ok)
|
|
52
55
|
return fallbackFundingRates();
|
|
53
56
|
const data = await response.json();
|
|
@@ -67,7 +70,8 @@ export async function getFundingRatesTool(_id, params) {
|
|
|
67
70
|
}],
|
|
68
71
|
};
|
|
69
72
|
}
|
|
70
|
-
catch {
|
|
73
|
+
catch (error) {
|
|
74
|
+
logger.warn('MarketSentiment', 'Funding rates fetch failed', { error: error.message });
|
|
71
75
|
return fallbackFundingRates();
|
|
72
76
|
}
|
|
73
77
|
}
|
|
@@ -84,8 +88,8 @@ export const btcMempoolParams = Type.Object({});
|
|
|
84
88
|
export async function getBtcMempoolTool() {
|
|
85
89
|
try {
|
|
86
90
|
const [feeResp, mempoolResp] = await Promise.all([
|
|
87
|
-
|
|
88
|
-
|
|
91
|
+
resilientFetch("https://mempool.space/api/v1/fees/recommended", { timeout: 10_000 }),
|
|
92
|
+
resilientFetch("https://mempool.space/api/mempool", { timeout: 10_000 }),
|
|
89
93
|
]);
|
|
90
94
|
const fees = await feeResp.json();
|
|
91
95
|
const mempool = await mempoolResp.json();
|
|
@@ -107,7 +111,8 @@ export async function getBtcMempoolTool() {
|
|
|
107
111
|
}],
|
|
108
112
|
};
|
|
109
113
|
}
|
|
110
|
-
catch {
|
|
114
|
+
catch (error) {
|
|
115
|
+
logger.warn('MarketSentiment', 'BTC mempool fetch failed', { error: error.message });
|
|
111
116
|
return {
|
|
112
117
|
content: [{ type: "text", text: "BTC Mempool: Data temporarily unavailable." }],
|
|
113
118
|
};
|
|
@@ -121,7 +126,7 @@ export async function getDefiTvlTool(_id, params) {
|
|
|
121
126
|
const chain = params.chain?.toLowerCase();
|
|
122
127
|
try {
|
|
123
128
|
if (chain) {
|
|
124
|
-
const response = await
|
|
129
|
+
const response = await resilientFetch(`https://api.llama.fi/v2/historicalChainTvl/${chain}`, { timeout: 10_000 });
|
|
125
130
|
if (!response.ok)
|
|
126
131
|
return fallbackTvl(chain);
|
|
127
132
|
const data = await response.json();
|
|
@@ -134,7 +139,7 @@ export async function getDefiTvlTool(_id, params) {
|
|
|
134
139
|
};
|
|
135
140
|
}
|
|
136
141
|
else {
|
|
137
|
-
const response = await
|
|
142
|
+
const response = await resilientFetch("https://api.llama.fi/v2/chains", { timeout: 10_000 });
|
|
138
143
|
if (!response.ok)
|
|
139
144
|
return fallbackTvl("all");
|
|
140
145
|
const data = await response.json();
|
|
@@ -148,7 +153,8 @@ export async function getDefiTvlTool(_id, params) {
|
|
|
148
153
|
};
|
|
149
154
|
}
|
|
150
155
|
}
|
|
151
|
-
catch {
|
|
156
|
+
catch (error) {
|
|
157
|
+
logger.warn('MarketSentiment', 'DeFi TVL fetch failed', { chain, error: error.message });
|
|
152
158
|
return fallbackTvl(chain ?? "all");
|
|
153
159
|
}
|
|
154
160
|
}
|
|
@@ -168,7 +174,9 @@ export async function getSolanaStatsTool() {
|
|
|
168
174
|
method: "getEpochInfo",
|
|
169
175
|
params: [],
|
|
170
176
|
});
|
|
171
|
-
const response = await
|
|
177
|
+
const response = await resilientFetch(rpcUrl, {
|
|
178
|
+
timeout: 10_000,
|
|
179
|
+
retries: 1,
|
|
172
180
|
method: "POST",
|
|
173
181
|
headers: { "Content-Type": "application/json" },
|
|
174
182
|
body,
|
|
@@ -179,7 +187,7 @@ export async function getSolanaStatsTool() {
|
|
|
179
187
|
const epoch = data.result;
|
|
180
188
|
const epochProgress = ((epoch.slotIndex / epoch.slotsInEpoch) * 100).toFixed(1);
|
|
181
189
|
// Also get SOL price
|
|
182
|
-
const priceResp = await
|
|
190
|
+
const priceResp = await resilientFetch("https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd&include_24hr_change=true", { timeout: 8_000 });
|
|
183
191
|
let solPrice = "";
|
|
184
192
|
if (priceResp.ok) {
|
|
185
193
|
const priceData = await priceResp.json();
|
|
@@ -199,7 +207,8 @@ export async function getSolanaStatsTool() {
|
|
|
199
207
|
}],
|
|
200
208
|
};
|
|
201
209
|
}
|
|
202
|
-
catch {
|
|
210
|
+
catch (error) {
|
|
211
|
+
logger.warn('MarketSentiment', 'Solana stats fetch failed', { error: error.message });
|
|
203
212
|
return fallbackSolanaStats();
|
|
204
213
|
}
|
|
205
214
|
}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Uses public RSS feeds and a basic lexicon-based sentiment analyzer.
|
|
4
4
|
*/
|
|
5
5
|
import { Type } from "@sinclair/typebox";
|
|
6
|
+
import { resilientFetch } from "../util/resilientFetch.js";
|
|
7
|
+
import { logger } from "../util/logger.js";
|
|
6
8
|
const POSITIVE_WORDS = new Set([
|
|
7
9
|
"bullish", "surge", "rally", "gain", "green", "high", "breakthrough", "adoption",
|
|
8
10
|
"partnership", "launch", "upgrade", "growth", "profit", "positive", "optimistic",
|
|
@@ -51,7 +53,9 @@ export class NewsFeed {
|
|
|
51
53
|
const url = apiKey
|
|
52
54
|
? `https://cryptopanic.com/api/v1/posts/?auth_token=${apiKey}&kind=news&public=true`
|
|
53
55
|
: "https://cryptopanic.com/api/v1/posts/?public=true";
|
|
54
|
-
const response = await
|
|
56
|
+
const response = await resilientFetch(url, {
|
|
57
|
+
timeout: 10_000,
|
|
58
|
+
retries: 1,
|
|
55
59
|
headers: { "Accept": "application/json" },
|
|
56
60
|
});
|
|
57
61
|
if (response.ok) {
|
|
@@ -71,7 +75,8 @@ export class NewsFeed {
|
|
|
71
75
|
items.push(...this.getFallbackNews());
|
|
72
76
|
}
|
|
73
77
|
}
|
|
74
|
-
catch {
|
|
78
|
+
catch (error) {
|
|
79
|
+
logger.warn('NewsSentiment', 'Failed to fetch news', { error: error.message });
|
|
75
80
|
items.push(...this.getFallbackNews());
|
|
76
81
|
}
|
|
77
82
|
this.cachedItems = items;
|
|
@@ -109,7 +114,8 @@ export class NewsFeed {
|
|
|
109
114
|
const emoji = report.averageSentiment > 0.2 ? "📈" : report.averageSentiment < -0.2 ? "📉" : "📊";
|
|
110
115
|
return `${emoji} ${(report.averageSentiment * 100).toFixed(0)}%`;
|
|
111
116
|
}
|
|
112
|
-
catch {
|
|
117
|
+
catch (error) {
|
|
118
|
+
logger.debug('NewsSentiment', 'statusBadge failed', { error: error.message });
|
|
113
119
|
return "";
|
|
114
120
|
}
|
|
115
121
|
}
|
package/dist/tools/PriceFeed.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* Secondary: query any token by address.
|
|
5
5
|
*/
|
|
6
6
|
import { Type } from "@sinclair/typebox";
|
|
7
|
+
import { resilientFetch } from "../util/resilientFetch.js";
|
|
8
|
+
import { logger } from "../util/logger.js";
|
|
7
9
|
const AIAIAI_TOKEN = "AVPJS61gZmWKtaEpb7qYPKo8Fk2xQUsayYQxPiPMpump";
|
|
8
10
|
export class PriceFeed {
|
|
9
11
|
cachedAiaiPrice = null;
|
|
@@ -18,7 +20,7 @@ export class PriceFeed {
|
|
|
18
20
|
async fetchToken(tokenAddress) {
|
|
19
21
|
const url = `https://api.dexscreener.com/tokens/v1/solana/${tokenAddress}`;
|
|
20
22
|
try {
|
|
21
|
-
const response = await
|
|
23
|
+
const response = await resilientFetch(url, { timeout: 10_000, retries: 1 });
|
|
22
24
|
if (!response.ok) {
|
|
23
25
|
return this.fallbackPrice(tokenAddress);
|
|
24
26
|
}
|
|
@@ -48,7 +50,8 @@ export class PriceFeed {
|
|
|
48
50
|
}
|
|
49
51
|
return result;
|
|
50
52
|
}
|
|
51
|
-
catch {
|
|
53
|
+
catch (error) {
|
|
54
|
+
logger.warn('PriceFeed', 'fetchToken failed', { tokenAddress, error: error.message });
|
|
52
55
|
return this.fallbackPrice(tokenAddress);
|
|
53
56
|
}
|
|
54
57
|
}
|
|
@@ -71,7 +74,7 @@ export class PriceFeed {
|
|
|
71
74
|
}
|
|
72
75
|
tickerLine(maxLength = 6) {
|
|
73
76
|
try {
|
|
74
|
-
const p = this.cachedAiaiPrice
|
|
77
|
+
const p = this.cachedAiaiPrice;
|
|
75
78
|
if (!p || !p.priceUsd)
|
|
76
79
|
return "";
|
|
77
80
|
const price = parseFloat(p.priceUsd).toFixed(6);
|
|
@@ -79,7 +82,9 @@ export class PriceFeed {
|
|
|
79
82
|
const arrow = change > 0 ? "▲" : change < 0 ? "▼" : "─";
|
|
80
83
|
return `$AIAIAI $${price} ${arrow}${Math.abs(change).toFixed(2)}%`.slice(0, 40);
|
|
81
84
|
}
|
|
82
|
-
catch {
|
|
85
|
+
catch (error) {
|
|
86
|
+
// Log error in case of debug needed
|
|
87
|
+
console.error('Error in tickerLine:', error);
|
|
83
88
|
return "";
|
|
84
89
|
}
|
|
85
90
|
}
|
|
@@ -131,4 +136,6 @@ export class PriceFeed {
|
|
|
131
136
|
}
|
|
132
137
|
}
|
|
133
138
|
export const priceFeed = new PriceFeed();
|
|
139
|
+
// Eagerly initialize the price cache so sidebar shows price immediately
|
|
140
|
+
priceFeed.getAiaiaiPrice().catch(() => { });
|
|
134
141
|
//# sourceMappingURL=PriceFeed.js.map
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Works with OHLCV candle data. Tools for the agent to query.
|
|
4
4
|
*/
|
|
5
5
|
import { Type } from "@sinclair/typebox";
|
|
6
|
+
import { resilientFetch } from "../util/resilientFetch.js";
|
|
6
7
|
// ── SMA ──────────────────────────────────────────────────────────────────────
|
|
7
8
|
export function sma(data, period) {
|
|
8
9
|
const result = [];
|
|
@@ -197,7 +198,7 @@ export async function getCandlesTool(_id, params) {
|
|
|
197
198
|
const interval = params.interval || "1d";
|
|
198
199
|
const limit = params.limit || 50;
|
|
199
200
|
try {
|
|
200
|
-
const response = await
|
|
201
|
+
const response = await resilientFetch(`https://api.binance.com/api/v3/klines?symbol=${symbol}&interval=${interval}&limit=${limit}`, { timeout: 10_000, retries: 1 });
|
|
201
202
|
if (!response.ok) {
|
|
202
203
|
return { content: [{ type: "text", text: `Failed to fetch candles for ${symbol}: ${response.status}` }] };
|
|
203
204
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tools/TokenCalendar.ts — Token launch calendar tracker.
|
|
3
|
+
* Uses DexScreener's latest tokens endpoint to find recent launches.
|
|
4
|
+
* Caches results for 5 minutes.
|
|
5
|
+
*/
|
|
6
|
+
import type { ToolResult } from "../api/ExtensionAPI.js";
|
|
7
|
+
export interface LaunchEvent {
|
|
8
|
+
address: string;
|
|
9
|
+
symbol: string;
|
|
10
|
+
name: string;
|
|
11
|
+
priceUsd: string | null;
|
|
12
|
+
volume24h: number;
|
|
13
|
+
liquidityUsd: number;
|
|
14
|
+
age: string;
|
|
15
|
+
chain: string;
|
|
16
|
+
}
|
|
17
|
+
export declare const launchCalendarParams: import("@sinclair/typebox").TObject<{
|
|
18
|
+
chain: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
19
|
+
limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
|
|
20
|
+
}>;
|
|
21
|
+
export declare function launchCalendarTool(_id: string, params: Record<string, unknown>): Promise<ToolResult>;
|
|
22
|
+
/** Get sidebar-ready summary */
|
|
23
|
+
export declare function getLaunchSummary(): string;
|
|
24
|
+
//# sourceMappingURL=TokenCalendar.d.ts.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tools/TokenCalendar.ts — Token launch calendar tracker.
|
|
3
|
+
* Uses DexScreener's latest tokens endpoint to find recent launches.
|
|
4
|
+
* Caches results for 5 minutes.
|
|
5
|
+
*/
|
|
6
|
+
import { Type } from "@sinclair/typebox";
|
|
7
|
+
import { resilientFetch } from "../util/resilientFetch.js";
|
|
8
|
+
import { logger } from "../util/logger.js";
|
|
9
|
+
export const launchCalendarParams = Type.Object({
|
|
10
|
+
chain: Type.Optional(Type.String({ description: "Chain: solana, ethereum, bsc, base", default: "solana" })),
|
|
11
|
+
limit: Type.Optional(Type.Number({ description: "Max entries", default: 10 })),
|
|
12
|
+
});
|
|
13
|
+
let cachedLaunches = [];
|
|
14
|
+
let lastFetch = 0;
|
|
15
|
+
const CACHE_DURATION = 300_000; // 5 min
|
|
16
|
+
export async function launchCalendarTool(_id, params) {
|
|
17
|
+
const chain = params.chain || "solana";
|
|
18
|
+
const limit = params.limit || 10;
|
|
19
|
+
if (Date.now() - lastFetch < CACHE_DURATION && cachedLaunches.length > 0) {
|
|
20
|
+
const filtered = cachedLaunches.filter(l => l.chain === chain).slice(0, limit);
|
|
21
|
+
return formatResult(filtered, chain);
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
// DexScreener "latest" endpoint per chain
|
|
25
|
+
const url = `https://api.dexscreener.com/latest/dex/tokens/${chain}`;
|
|
26
|
+
const response = await resilientFetch(url, { timeout: 10_000, retries: 1 });
|
|
27
|
+
if (!response.ok) {
|
|
28
|
+
return { content: [{ type: "text", text: `Token launch calendar unavailable for ${chain}.` }] };
|
|
29
|
+
}
|
|
30
|
+
const data = await response.json();
|
|
31
|
+
const pairs = data?.pairs ?? [];
|
|
32
|
+
cachedLaunches = pairs.map((p) => {
|
|
33
|
+
const createdAt = p.pairCreatedAt ? new Date(p.pairCreatedAt) : null;
|
|
34
|
+
const now = Date.now();
|
|
35
|
+
const diffMs = createdAt ? now - createdAt.getTime() : Infinity;
|
|
36
|
+
const age = createdAt
|
|
37
|
+
? diffMs < 3600000 ? `${Math.floor(diffMs / 60000)}m ago`
|
|
38
|
+
: diffMs < 86400000 ? `${Math.floor(diffMs / 3600000)}h ago`
|
|
39
|
+
: `${Math.floor(diffMs / 86400000)}d ago`
|
|
40
|
+
: "unknown";
|
|
41
|
+
return {
|
|
42
|
+
address: p.baseToken?.address ?? "",
|
|
43
|
+
symbol: p.baseToken?.symbol ?? "???",
|
|
44
|
+
name: p.baseToken?.name ?? "Unknown",
|
|
45
|
+
priceUsd: p.priceUsd ?? null,
|
|
46
|
+
volume24h: p.volume?.h24 ?? 0,
|
|
47
|
+
liquidityUsd: p.liquidity?.usd ?? 0,
|
|
48
|
+
age,
|
|
49
|
+
chain,
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
lastFetch = Date.now();
|
|
53
|
+
const filtered = cachedLaunches.slice(0, limit);
|
|
54
|
+
return formatResult(filtered, chain);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
logger.warn("TokenCalendar", "Failed to fetch launches", { chain, error: error.message });
|
|
58
|
+
return { content: [{ type: "text", text: `Token launch calendar: Data temporarily unavailable.` }] };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function formatResult(launches, chain) {
|
|
62
|
+
if (launches.length === 0) {
|
|
63
|
+
return { content: [{ type: "text", text: `No recent launches found on ${chain}.` }] };
|
|
64
|
+
}
|
|
65
|
+
const lines = [`🚀 Recent Token Launches — ${chain.toUpperCase()}`, ""];
|
|
66
|
+
for (const l of launches.slice(0, 10)) {
|
|
67
|
+
const price = l.priceUsd ? `$${parseFloat(l.priceUsd).toFixed(8)}` : "N/A";
|
|
68
|
+
const vol = l.volume24h > 0 ? `$${(l.volume24h / 1000).toFixed(1)}k` : "no vol";
|
|
69
|
+
const liq = l.liquidityUsd > 0 ? `$${(l.liquidityUsd / 1000).toFixed(1)}k` : "no liq";
|
|
70
|
+
lines.push(` ${l.symbol.padEnd(10)} ${price.padEnd(16)} ${vol.padEnd(10)} ${l.age.padEnd(10)} ${l.address.slice(0, 6)}…`);
|
|
71
|
+
}
|
|
72
|
+
return { content: [{ type: "text", text: lines.join("\n") }], details: { launches } };
|
|
73
|
+
}
|
|
74
|
+
/** Get sidebar-ready summary */
|
|
75
|
+
export function getLaunchSummary() {
|
|
76
|
+
if (cachedLaunches.length === 0)
|
|
77
|
+
return "Loading…";
|
|
78
|
+
const recent = cachedLaunches.slice(0, 3);
|
|
79
|
+
return recent.map(l => l.symbol).join(" · ");
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=TokenCalendar.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tools/TokenSecurityScanner.ts - honeypot/rugpull detection for Solana tokens
|
|
3
|
+
*/
|
|
4
|
+
import type { ToolResult } from "../api/ExtensionAPI.js";
|
|
5
|
+
export interface TokenSecurityResult {
|
|
6
|
+
address: string;
|
|
7
|
+
symbol: string;
|
|
8
|
+
name: string;
|
|
9
|
+
isHoneypot: boolean;
|
|
10
|
+
isRugRisk: boolean;
|
|
11
|
+
mintAuthorityRenounced: boolean;
|
|
12
|
+
freezeAuthorityRenounced: boolean;
|
|
13
|
+
liquidityLocked: boolean;
|
|
14
|
+
top10HolderPct: number;
|
|
15
|
+
warnings: string[];
|
|
16
|
+
score: number;
|
|
17
|
+
}
|
|
18
|
+
export declare const tokenSecurityParams: import("@sinclair/typebox").TObject<{
|
|
19
|
+
address: import("@sinclair/typebox").TString;
|
|
20
|
+
}>;
|
|
21
|
+
export declare function scanTokenSecurityTool(_id: string, params: Record<string, unknown>): Promise<ToolResult>;
|
|
22
|
+
//# sourceMappingURL=TokenSecurityScanner.d.ts.map
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tools/TokenSecurityScanner.ts - honeypot/rugpull detection for Solana tokens
|
|
3
|
+
*/
|
|
4
|
+
import { Type } from "@sinclair/typebox";
|
|
5
|
+
import { resilientFetch } from "../util/resilientFetch.js";
|
|
6
|
+
import { logger } from "../util/logger.js";
|
|
7
|
+
export const tokenSecurityParams = Type.Object({
|
|
8
|
+
address: Type.String({ description: "Token mint address on Solana" }),
|
|
9
|
+
});
|
|
10
|
+
export async function scanTokenSecurityTool(_id, params) {
|
|
11
|
+
const address = params.address;
|
|
12
|
+
if (!address || address.length < 32 || address.length > 44) {
|
|
13
|
+
return {
|
|
14
|
+
content: [{ type: "text", text: "Invalid token address format." }],
|
|
15
|
+
isError: true,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const warnings = [];
|
|
19
|
+
let score = 100;
|
|
20
|
+
let isHoneypot = false;
|
|
21
|
+
let isRugRisk = false;
|
|
22
|
+
let dexData = null;
|
|
23
|
+
try {
|
|
24
|
+
const resp = await resilientFetch(`https://api.dexscreener.com/tokens/v1/solana/${address}`, {
|
|
25
|
+
timeout: 10_000,
|
|
26
|
+
retries: 1,
|
|
27
|
+
});
|
|
28
|
+
if (resp.ok) {
|
|
29
|
+
const data = await resp.json();
|
|
30
|
+
if (data && data.length > 0)
|
|
31
|
+
dexData = data[0];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
logger.warn("TokenSecurity", "DexScreener lookup failed", { address, error: error.message });
|
|
36
|
+
}
|
|
37
|
+
if (!dexData) {
|
|
38
|
+
warnings.push("No DEX liquidity found - token may be dead or not traded");
|
|
39
|
+
score -= 50;
|
|
40
|
+
isRugRisk = true;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
const liquidity = dexData.liquidity?.usd ?? 0;
|
|
44
|
+
if (liquidity < 1000) {
|
|
45
|
+
warnings.push(`Low liquidity: $${liquidity.toFixed(0)}`);
|
|
46
|
+
score -= 30;
|
|
47
|
+
isRugRisk = true;
|
|
48
|
+
}
|
|
49
|
+
else if (liquidity < 10000) {
|
|
50
|
+
warnings.push(`Moderate liquidity: $${liquidity.toFixed(0)}`);
|
|
51
|
+
score -= 10;
|
|
52
|
+
}
|
|
53
|
+
const volume = dexData.volume?.h24 ?? 0;
|
|
54
|
+
if (volume < 100) {
|
|
55
|
+
warnings.push(`Very low 24h volume: $${volume.toFixed(0)}`);
|
|
56
|
+
score -= 20;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const rugResp = await resilientFetch(`https://api.rugcheck.xyz/v1/tokens/${address}/report`, {
|
|
61
|
+
timeout: 8_000,
|
|
62
|
+
retries: 0,
|
|
63
|
+
});
|
|
64
|
+
if (rugResp.ok) {
|
|
65
|
+
const report = await rugResp.json();
|
|
66
|
+
if (report.status === "WARNING" || report.status === "BLOCKED") {
|
|
67
|
+
warnings.push(`RugCheck status: ${report.status}`);
|
|
68
|
+
score -= 40;
|
|
69
|
+
isRugRisk = true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
logger.warn('TokenSecurity', 'RugCheck lookup failed (optional)', { address, error: error.message });
|
|
75
|
+
}
|
|
76
|
+
const symbol = dexData?.baseToken?.symbol ?? "???";
|
|
77
|
+
const name = dexData?.baseToken?.name ?? "Unknown";
|
|
78
|
+
const scoreLabel = score >= 70 ? "SAFE" : score >= 40 ? "CAUTION" : "DANGEROUS";
|
|
79
|
+
const lines = [
|
|
80
|
+
"Token Security Scan",
|
|
81
|
+
"",
|
|
82
|
+
`Token: ${name} ($${symbol})`,
|
|
83
|
+
`Address: ${address.slice(0, 8)}...${address.slice(-6)}`,
|
|
84
|
+
`Score: ${scoreLabel} (${score}/100)`,
|
|
85
|
+
"",
|
|
86
|
+
];
|
|
87
|
+
if (warnings.length > 0) {
|
|
88
|
+
lines.push("Warnings:");
|
|
89
|
+
for (const w of warnings)
|
|
90
|
+
lines.push(" - " + w);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
lines.push("No immediate risks detected.");
|
|
94
|
+
}
|
|
95
|
+
lines.push("");
|
|
96
|
+
lines.push("This is not financial advice. Always verify before trading.");
|
|
97
|
+
return {
|
|
98
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
99
|
+
details: { address, symbol, name, score, isHoneypot, isRugRisk, warnings },
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=TokenSecurityScanner.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tools/TransactionSim.ts — Simulate Solana transactions before execution.
|
|
3
|
+
* Uses public RPC simulateTransaction for pre-flight checks.
|
|
4
|
+
*/
|
|
5
|
+
import type { ToolResult } from "../api/ExtensionAPI.js";
|
|
6
|
+
export interface SimResult {
|
|
7
|
+
ok: boolean;
|
|
8
|
+
logs: string[];
|
|
9
|
+
unitsConsumed: number;
|
|
10
|
+
fee: number;
|
|
11
|
+
error?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const simulateTxParams: import("@sinclair/typebox").TObject<{
|
|
14
|
+
transaction: import("@sinclair/typebox").TString;
|
|
15
|
+
}>;
|
|
16
|
+
export declare function simulateTxTool(_id: string, params: Record<string, unknown>): Promise<ToolResult>;
|
|
17
|
+
//# sourceMappingURL=TransactionSim.d.ts.map
|