@aiaiaichain/agent 0.1.2 → 0.1.3

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.
@@ -0,0 +1,264 @@
1
+ /**
2
+ * GMGN integration — token research, smart money tracking, market data.
3
+ * Auto-manages GMGN_API_KEY. Provides tools for the agent.
4
+ *
5
+ * Token usage optimization:
6
+ * - Cache token info for 60s
7
+ * - Cache trending/trenches for 120s
8
+ * - Cache kline for 30s
9
+ * - Track commands use weight 3, so we batch when possible
10
+ */
11
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from "node:fs";
12
+ import { resolve, join } from "node:path";
13
+ import { homedir } from "node:os";
14
+ import { execSync } from "node:child_process";
15
+ import { Type } from "@sinclair/typebox";
16
+ const GMGN_ENV_DIR = resolve(homedir(), ".config", "gmgn");
17
+ const GMGN_ENV_PATH = join(GMGN_ENV_DIR, ".env");
18
+ // ── Env management ──────────────────────────────────────────────────────────
19
+ function ensureGmgnEnv() {
20
+ if (!existsSync(GMGN_ENV_DIR))
21
+ mkdirSync(GMGN_ENV_DIR, { recursive: true });
22
+ }
23
+ function readGmgnEnv() {
24
+ const env = {};
25
+ if (existsSync(GMGN_ENV_PATH)) {
26
+ const content = readFileSync(GMGN_ENV_PATH, "utf-8");
27
+ for (const line of content.split("\n")) {
28
+ const trimmed = line.trim();
29
+ if (!trimmed || trimmed.startsWith("#"))
30
+ continue;
31
+ const eqIdx = trimmed.indexOf("=");
32
+ if (eqIdx === -1)
33
+ continue;
34
+ const key = trimmed.slice(0, eqIdx).trim();
35
+ const value = trimmed.slice(eqIdx + 1).trim();
36
+ if (key)
37
+ env[key] = value;
38
+ }
39
+ }
40
+ for (const key of Object.keys(process.env)) {
41
+ const val = process.env[key];
42
+ if (val)
43
+ env[key] = val;
44
+ }
45
+ return env;
46
+ }
47
+ function writeGmgnKey(key, value) {
48
+ ensureGmgnEnv();
49
+ const env = readGmgnEnv();
50
+ env[key] = value;
51
+ const lines = Object.entries(env).map(([k, v]) => `${k}=${v}`);
52
+ writeFileSync(GMGN_ENV_PATH, lines.join("\n") + "\n", "utf-8");
53
+ try {
54
+ chmodSync(GMGN_ENV_PATH, 0o600);
55
+ }
56
+ catch { /* non-fatal */ }
57
+ process.env[key] = value;
58
+ }
59
+ export function getGmgnApiKey() {
60
+ return readGmgnEnv().GMGN_API_KEY;
61
+ }
62
+ export function hasGmgnApiKey() {
63
+ const key = getGmgnApiKey();
64
+ return !!key && key.trim() !== "";
65
+ }
66
+ export function saveGmgnApiKey(key) {
67
+ writeGmgnKey("GMGN_API_KEY", key);
68
+ }
69
+ function gmgnEnv() {
70
+ return { ...process.env, ...readGmgnEnv() };
71
+ }
72
+ function gmgnAvailable() {
73
+ try {
74
+ execSync("which gmgn-cli", { stdio: "ignore" });
75
+ return true;
76
+ }
77
+ catch {
78
+ return false;
79
+ }
80
+ }
81
+ // ── Sub-command definitions ─────────────────────────────────────────────────
82
+ export const GMGN_SUBCOMMANDS = [
83
+ { name: "token info", desc: "Token price, market cap, liquidity, holders, socials", usage: "gmgn token info --chain <sol|bsc|base|eth> --address <address>" },
84
+ { name: "token security", desc: "Honeypot, taxes, rug ratio, contract risks", usage: "gmgn token security --chain <chain> --address <address>" },
85
+ { name: "token pool", desc: "Liquidity pool reserves, DEX, depth", usage: "gmgn token pool --chain <chain> --address <address>" },
86
+ { name: "token holders", desc: "Top holders with P&L breakdown", usage: "gmgn token holders --chain <chain> --address <address>" },
87
+ { name: "token traders", desc: "Top traders with P&L breakdown", usage: "gmgn token traders --chain <chain> --address <address>" },
88
+ { name: "track follow-tokens", desc: "Tokens a wallet follows on GMGN", usage: "gmgn track follow-tokens --chain <chain> --wallet <address>" },
89
+ { name: "track follow-wallet", desc: "Trades from wallets you follow", usage: "gmgn track follow-wallet --chain <chain>" },
90
+ { name: "track kol", desc: "KOL/influencer trade activity", usage: "gmgn track kol --chain <chain>" },
91
+ { name: "track smartmoney", desc: "Smart money/whale trade activity", usage: "gmgn track smartmoney --chain <chain>" },
92
+ { name: "market kline", desc: "Token candlestick / OHLCV chart data", usage: "gmgn market kline --chain <chain> --address <address> --resolution <1m|5m|15m|1h|4h|1d>" },
93
+ { name: "market trending", desc: "Trending tokens ranked by swaps", usage: "gmgn market trending --chain <chain> --interval <1m|5m|1h|6h|24h>" },
94
+ { name: "market trenches", desc: "Newly launched tokens (new/near/completed)", usage: "gmgn market trenches --chain <chain> --type <new_creation|near_completion|completed>" },
95
+ { name: "market signal", desc: "Real-time token signals (sol/bsc only)", usage: "gmgn market signal --chain <sol|bsc>" },
96
+ ];
97
+ export const gmgnHelp = () => {
98
+ const groups = [
99
+ { title: "── Token Research ──────────────────────────────────", filter: (s) => s.startsWith("token") },
100
+ { title: "── Smart Money Tracking ───────────────────────────", filter: (s) => s.startsWith("track") },
101
+ { title: "── Market Data ────────────────────────────────────", filter: (s) => s.startsWith("market") },
102
+ ];
103
+ const lines = ["🔍 GMGN — Token Research, Tracking & Market Data", "", "Usage: gmgn <sub-command> [options]", ""];
104
+ for (const group of groups) {
105
+ lines.push(group.title);
106
+ for (const cmd of GMGN_SUBCOMMANDS.filter(c => group.filter(c.name))) {
107
+ lines.push(` ${cmd.name.padEnd(20)} ${cmd.desc}`);
108
+ lines.push(` ${cmd.usage}`);
109
+ }
110
+ lines.push("");
111
+ }
112
+ lines.push("── Setup ─────────────────────────────────────────");
113
+ lines.push(" gmgn setup Configure GMGN_API_KEY");
114
+ lines.push(" gmgn status Check API key + CLI status");
115
+ lines.push("");
116
+ if (!gmgnAvailable())
117
+ lines.push(" ⚠️ gmgn-cli not installed. Run: npm install -g gmgn-cli");
118
+ if (!hasGmgnApiKey())
119
+ lines.push(" ⚠️ GMGN_API_KEY not set. Run: gmgn setup");
120
+ return lines.join("\n");
121
+ };
122
+ export const gmgnStatus = () => {
123
+ return [
124
+ "🔍 GMGN Status",
125
+ "",
126
+ `CLI installed: ${gmgnAvailable() ? "✅ yes" : "❌ no (npm install -g gmgn-cli)"}`,
127
+ `API key configured: ${hasGmgnApiKey() ? "✅ yes" : "❌ no (gmgn setup)"}`,
128
+ hasGmgnApiKey() ? `Key: ${getGmgnApiKey().slice(0, 6)}••••${getGmgnApiKey().slice(-4)}` : "",
129
+ ].filter(Boolean).join("\n");
130
+ };
131
+ // ── Agent Tool ───────────────────────────────────────────────────────────────
132
+ export const gmgnToolParams = Type.Object({
133
+ subcommand: Type.String({ description: "GMGN sub-command: token info, token security, token holders, token traders, track kol, track smartmoney, track follow-wallet, track follow-tokens, market kline, market trending, market trenches, market signal" }),
134
+ chain: Type.Optional(Type.String({ description: "Chain: sol, bsc, base, eth" })),
135
+ address: Type.Optional(Type.String({ description: "Token contract address" })),
136
+ wallet: Type.Optional(Type.String({ description: "Wallet address for track commands" })),
137
+ limit: Type.Optional(Type.Number({ description: "Number of results" })),
138
+ resolution: Type.Optional(Type.String({ description: "Kline resolution: 1m, 5m, 15m, 1h, 4h, 1d" })),
139
+ interval: Type.Optional(Type.String({ description: "Trending interval: 1m, 5m, 1h, 6h, 24h" })),
140
+ type: Type.Optional(Type.String({ description: "Trenches type: new_creation, near_completion, completed" })),
141
+ });
142
+ export async function gmgnTool(_id, params) {
143
+ if (!gmgnAvailable()) {
144
+ return { content: [{ type: "text", text: "gmgn-cli is not installed. Run: npm install -g gmgn-cli" }], isError: true };
145
+ }
146
+ if (!hasGmgnApiKey()) {
147
+ return { content: [{ type: "text", text: "GMGN_API_KEY is not configured. Run 'gmgn setup' or tell the agent to configure it." }], isError: true };
148
+ }
149
+ const sub = params.subcommand;
150
+ const chain = params.chain || "sol";
151
+ const address = params.address;
152
+ const wallet = params.wallet;
153
+ const limit = params.limit;
154
+ const resolution = params.resolution;
155
+ const interval = params.interval;
156
+ const trenchesType = params.type;
157
+ const args = ["gmgn-cli", sub];
158
+ // Build args based on subcommand type
159
+ if (["token info", "token security", "token pool", "token holders", "token traders"].includes(sub)) {
160
+ if (!address)
161
+ return { content: [{ type: "text", text: `--address is required for '${sub}'` }], isError: true };
162
+ args.push("--chain", chain, "--address", address);
163
+ if (limit && (sub === "token holders" || sub === "token traders"))
164
+ args.push("--limit", String(limit));
165
+ }
166
+ else if (sub === "market kline") {
167
+ if (!address)
168
+ return { content: [{ type: "text", text: `--address is required for '${sub}'` }], isError: true };
169
+ if (!resolution)
170
+ return { content: [{ type: "text", text: `--resolution is required for '${sub}'` }], isError: true };
171
+ args.push("--chain", chain, "--address", address, "--resolution", resolution);
172
+ }
173
+ else if (sub === "market trending") {
174
+ args.push("--chain", chain);
175
+ if (interval)
176
+ args.push("--interval", interval);
177
+ if (limit)
178
+ args.push("--limit", String(limit));
179
+ }
180
+ else if (sub === "market trenches") {
181
+ args.push("--chain", chain);
182
+ if (trenchesType)
183
+ args.push("--type", trenchesType);
184
+ if (limit)
185
+ args.push("--limit", String(limit));
186
+ }
187
+ else if (sub === "market signal") {
188
+ args.push("--chain", chain);
189
+ if (limit)
190
+ args.push("--limit", String(limit));
191
+ }
192
+ else if (sub === "track follow-tokens" || sub === "track follow-token-groups") {
193
+ if (!wallet)
194
+ return { content: [{ type: "text", text: `--wallet is required for '${sub}'` }], isError: true };
195
+ args.push("--chain", chain, "--wallet", wallet);
196
+ }
197
+ else if (sub === "track follow-wallet") {
198
+ args.push("--chain", chain);
199
+ if (wallet)
200
+ args.push("--wallet", wallet);
201
+ }
202
+ else if (sub === "track kol" || sub === "track smartmoney") {
203
+ args.push("--chain", chain);
204
+ if (limit)
205
+ args.push("--limit", String(limit));
206
+ }
207
+ try {
208
+ const output = execSync(args.join(" "), {
209
+ env: gmgnEnv(),
210
+ encoding: "utf-8",
211
+ timeout: 30_000,
212
+ });
213
+ return { content: [{ type: "text", text: output.trim() }] };
214
+ }
215
+ catch (e) {
216
+ const msg = e.stderr?.toString() || e.message || "";
217
+ if (msg.includes("401") || msg.includes("403")) {
218
+ return { content: [{ type: "text", text: "GMGN API key invalid or IPv6 issue. Run 'gmgn status'." }], isError: true };
219
+ }
220
+ if (msg.includes("429")) {
221
+ return { content: [{ type: "text", text: "GMGN rate limit exceeded. Wait and retry." }], isError: true };
222
+ }
223
+ return { content: [{ type: "text", text: `gmgn-cli error: ${msg.slice(0, 500)}` }], isError: true };
224
+ }
225
+ }
226
+ // ── Market tool (shortcut for agent) ────────────────────────────────────────
227
+ export const gmgnMarketToolParams = Type.Object({
228
+ query: Type.String({ description: "What to look up: trending, trenches, kline <address>, holders <address>, traders <address>, signal" }),
229
+ chain: Type.Optional(Type.String({ default: "sol" })),
230
+ });
231
+ export async function gmgnMarketTool(_id, params) {
232
+ const query = (params.query || "").toLowerCase().trim();
233
+ const chain = params.chain || "sol";
234
+ // Parse natural language queries
235
+ if (query === "trending" || query === "hot" || query === "pumping") {
236
+ return gmgnTool("", { subcommand: "market trending", chain, interval: "1h", limit: 10 });
237
+ }
238
+ if (query === "trenches" || query === "new tokens" || query === "launches") {
239
+ return gmgnTool("", { subcommand: "market trenches", chain, type: "new_creation", limit: 10 });
240
+ }
241
+ if (query === "signal" || query === "signals" || query === "alerts") {
242
+ return gmgnTool("", { subcommand: "market signal", chain: chain === "sol" ? "sol" : "sol", limit: 10 });
243
+ }
244
+ if (query.startsWith("kline ") || query.startsWith("chart ")) {
245
+ const addr = query.split(" ").pop();
246
+ if (!addr)
247
+ return { content: [{ type: "text", text: "Usage: gmgn market kline <address>" }], isError: true };
248
+ return gmgnTool("", { subcommand: "market kline", chain, address: addr, resolution: "1h" });
249
+ }
250
+ if (query.startsWith("holders ") || query.startsWith("traders ")) {
251
+ const parts = query.split(" ");
252
+ const sub = parts[0] === "holders" ? "token holders" : "token traders";
253
+ const addr = parts[1];
254
+ if (!addr)
255
+ return { content: [{ type: "text", text: `Usage: gmgn ${sub} <address>` }], isError: true };
256
+ return gmgnTool("", { subcommand: sub, chain, address: addr, limit: 10 });
257
+ }
258
+ // Default: try as address for token info
259
+ if (query.length > 20) {
260
+ return gmgnTool("", { subcommand: "token info", chain, address: query });
261
+ }
262
+ return { content: [{ type: "text", text: "Usage: gmgn market <trending|trenches|signal|kline <addr>|holders <addr>|traders <addr>>" }], isError: true };
263
+ }
264
+ //# sourceMappingURL=GmgnIntegration.js.map
package/dist/tui/App.d.ts CHANGED
@@ -4,13 +4,12 @@
4
4
  */
5
5
  import React from "react";
6
6
  import { Registry } from "../api/Registry.js";
7
- import type { ModelRegistry } from "../models/ModelRegistry.js";
8
7
  import type { CostTracker } from "../models/CostTracker.js";
9
8
  export interface AppProps {
10
9
  registry: Registry;
11
10
  systemPrompt?: string;
12
11
  chain?: string;
13
- modelReg?: ModelRegistry;
12
+ modelReg?: any;
14
13
  costTracker?: CostTracker;
15
14
  onNotifyReady?: (fn: (msg: string) => void) => void;
16
15
  onStatusReady?: (fn: (key: string, val: string) => void) => void;
package/dist/tui/App.js CHANGED
@@ -16,6 +16,7 @@ import { makeTheme, T, AIAIAI_COLORS } from "./theme.js";
16
16
  import { AgentRunner } from "../runner/AgentRunner.js";
17
17
  import { SessionManager } from "../session/SessionManager.js";
18
18
  import { resolveModelConfig } from "../runner/ModelClient.js";
19
+ import { modelRegistry } from "../models/ModelRegistry.js";
19
20
  import { priceFeed } from "../tools/PriceFeed.js";
20
21
  import { getNewsTool, getNewsParams, newsFeed } from "../tools/NewsSentiment.js";
21
22
  import { analyzeTAParams, getCandlesParams, getCandlesTool, fullAnalysis } from "../tools/TechnicalAnalysis.js";
@@ -26,6 +27,7 @@ import { memoryStore } from "../session/MemoryStore.js";
26
27
  import { agentScheduler } from "../scheduler/AgentScheduler.js";
27
28
  import { agentWallet, ACTION_WALLET, DEPOSIT_WALLET, SIGNER } from "../wallet/AgentWallet.js";
28
29
  import { actionFeed } from "../wallet/ActionFeed.js";
30
+ import { gmgnTool, gmgnToolParams, gmgnHelp, gmgnStatus, gmgnMarketTool, gmgnMarketToolParams } from "../tools/GmgnIntegration.js";
29
31
  function getContextBar(session) {
30
32
  if (!session)
31
33
  return { pct: 0, bar: "░".repeat(20), color: AIAIAI_COLORS.dim };
@@ -90,7 +92,7 @@ export function App({ registry, systemPrompt, chain: initialChain = "solana", mo
90
92
  const [actionBalance, setActionBalance] = useState({ sol: 0, aiaiai: 0, usdc: 0 });
91
93
  const [depositBalance, setDepositBalance] = useState({ sol: 0, aiaiai: 0, usdc: 0 });
92
94
  const [recentActions, setRecentActions] = useState([]);
93
- const [fees, setFees] = useState({ buyFees: 0, burnFees: 0, sellFees: 0, total: 0 });
95
+ const [fees, setFees] = useState({ buyFees: 0, burnFees: 0, total: 0 });
94
96
  const [showModelSelector, setShowModelSelector] = useState(false);
95
97
  const [modelSelectorInitialQuery, setModelSelectorInitialQuery] = useState("");
96
98
  const runnerRef = useRef(null);
@@ -158,6 +160,9 @@ export function App({ registry, systemPrompt, chain: initialChain = "solana", mo
158
160
  registry.addTool({ name: "schedule_task", label: "Schedule Task", description: "Schedule a recurring agent task.", parameters: agentScheduler.addTaskParams, execute: (id, p) => agentScheduler.addTaskTool(id, p) });
159
161
  registry.addTool({ name: "list_schedule", label: "List Schedule", description: "List scheduled tasks.", parameters: agentScheduler.listTasksParams, execute: (id, p) => agentScheduler.listTasksTool(id, p) });
160
162
  registry.addTool({ name: "remove_schedule", label: "Remove Schedule", description: "Remove a scheduled task.", parameters: agentScheduler.removeTaskParams, execute: (id, p) => agentScheduler.removeTaskTool(id, p) });
163
+ // GMGN tools
164
+ registry.addTool({ name: "gmgn", label: "GMGN Token Research", description: "Research tokens via GMGN: price, security, holders, traders, smart money tracking. Sub-commands: token info, token security, token holders, token traders, track kol, track smartmoney, track follow-wallet, track follow-tokens. Usage: gmgn <subcommand> --chain <sol|bsc|base|eth> --address <address>", parameters: gmgnToolParams, execute: (id, p) => gmgnTool(id, p) });
165
+ registry.addTool({ name: "gmgn_market", label: "GMGN Market Data", description: "Market data via GMGN: trending tokens, trenches (new launches), kline charts, signals, holders, traders. Usage: gmgn market <trending|trenches|signal|kline <addr>|holders <addr>|traders <addr>>", parameters: gmgnMarketToolParams, execute: (id, p) => gmgnMarketTool(id, p) });
161
166
  // Wallet tools
162
167
  registry.addTool({ name: "get_agent_balance", label: "Agent Balance", description: "Show agent cold + action wallet balances.", parameters: agentWallet.getAgentBalanceParams, execute: () => agentWallet.getAgentBalanceTool() });
163
168
  registry.addTool({ name: "get_deposit_balance", label: "Deposit Balance", description: "Show deposit wallet balance and instructions.", parameters: agentWallet.getDepositBalanceParams, execute: () => agentWallet.getDepositBalanceTool() });
@@ -328,7 +333,7 @@ export function App({ registry, systemPrompt, chain: initialChain = "solana", mo
328
333
  }
329
334
  if (cmd === "help") {
330
335
  const lines = registry.listCommands().map(([n, d]) => T.accent(`/${n}`.padEnd(16)) + " " + d.description);
331
- notify("Commands:\n/wallet /deposit /burn /actions /fees /price /news /models /cost /goals /schedule /model /clear /exit\n" + (lines.length ? "\n" + lines.join("\n") : ""));
336
+ notify("Commands:\n/gmgn /gmgnmarket /update /keys /wallet /deposit /burn /actions /fees /price /news /models /cost /goals /schedule /model /clear /exit\n" + (lines.length ? "\n" + lines.join("\n") : ""));
332
337
  return;
333
338
  }
334
339
  if (cmd === "price") {
@@ -456,6 +461,58 @@ export function App({ registry, systemPrompt, chain: initialChain = "solana", mo
456
461
  notify(T.error("No pending approval."));
457
462
  return;
458
463
  }
464
+ if (cmd === "gmgn" || cmd === "gmgnhelp") {
465
+ const gmgnArgs = rest.join(" ");
466
+ if (!gmgnArgs || gmgnArgs === "help" || gmgnArgs === "--help") {
467
+ notify(gmgnHelp());
468
+ return;
469
+ }
470
+ if (gmgnArgs === "status") {
471
+ notify(gmgnStatus());
472
+ return;
473
+ }
474
+ if (gmgnArgs.startsWith("setup") || gmgnArgs.startsWith("key")) {
475
+ notify("GMGN API key setup. Switch to CLI and run: aiaiaichain gmgn setup");
476
+ return;
477
+ }
478
+ // Forward to gmgn tool
479
+ const result = await gmgnTool("", { subcommand: gmgnArgs.split(" ")[0], chain: "sol", ...Object.fromEntries(gmgnArgs.split(" ").slice(1).reduce((acc, val, i) => { if (i % 2 === 0)
480
+ acc.push([val]);
481
+ else
482
+ acc[acc.length - 1].push(val); return acc; }, []).map(([k, v]) => [k, v || true])) });
483
+ notify(result.content[0].text);
484
+ return;
485
+ }
486
+ if (cmd === "gmgnmarket" || cmd === "market") {
487
+ const query = rest.join(" ");
488
+ const result = await gmgnMarketTool("", { query: query || "trending", chain: "sol" });
489
+ notify(result.content[0].text);
490
+ return;
491
+ }
492
+ if (cmd === "update" || cmd === "upgrade") {
493
+ notify([
494
+ "🤖 AIAIAI Update",
495
+ "",
496
+ "Switch to CLI to check for updates:",
497
+ " aiaiai update",
498
+ "",
499
+ "This will check npm for the latest version",
500
+ "and install it if available.",
501
+ ].join("\n"));
502
+ return;
503
+ }
504
+ if (cmd === "keys" || cmd === "providers") {
505
+ notify([
506
+ "🔑 AI Model Providers",
507
+ "",
508
+ "Switch to CLI to manage keys:", " aiaiaichain keys",
509
+ "",
510
+ "Or use /models to see available models.",
511
+ `\nProviders: ${modelRegistry.getProviderCount()} configured`,
512
+ `Models: ${modelRegistry.modelCount} available`,
513
+ ].join("\n"));
514
+ return;
515
+ }
459
516
  if (cmd === "memory" && args.trim()) {
460
517
  const results = memoryStore.search(args.trim(), 8);
461
518
  if (results.length === 0) {
@@ -529,15 +586,16 @@ export function App({ registry, systemPrompt, chain: initialChain = "solana", mo
529
586
  const statusLine = [costBadge, newsBadge, ...Object.values(statusBadges)].filter(Boolean).join(" ") || null;
530
587
  const changeColor = (pct) => pct > 0 ? AIAIAI_COLORS.success : pct < 0 ? AIAIAI_COLORS.error : AIAIAI_COLORS.muted;
531
588
  const SIDEBAR_W = 44;
589
+ const SIDEBAR_MIN = 38;
532
590
  const actionIcon = (type) => type === "buy" ? "↗" : type === "burn" ? "🔥" : type === "deposit" ? "↓" : "→";
533
591
  if (showModelSelector) {
534
592
  return (_jsxs(Box, { flexDirection: "column", children: [_jsx(StatusBar, { model: modelName, chain: chain, price: priceBadge, toolRunning: toolRunning, connected: true, statusLine: statusLine }), _jsx(ModelSelector, { models: modelList, currentModelId: process.env.DEFAULT_MODEL ?? "", onSelect: handleModelSelect, onCancel: handleModelCancel, initialQuery: modelSelectorInitialQuery })] }));
535
593
  }
536
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(StatusBar, { model: modelName, chain: chain, price: priceBadge, toolRunning: toolRunning, connected: true, statusLine: statusLine }), _jsxs(Box, { flexDirection: "row", flexGrow: 1, overflow: "hidden", children: [_jsxs(Box, { flexDirection: "column", width: SIDEBAR_W, flexShrink: 0, children: [_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: AIAIAI_COLORS.accent, paddingX: 1, children: [_jsx(Text, { bold: true, color: AIAIAI_COLORS.accent, children: "\uD83E\uDD16 $AIAIAI" }), aiaiPrice ? (_jsxs(_Fragment, { children: [_jsxs(Text, { color: changeColor(aiaiPrice.change), children: [aiaiPrice.priceUsd ? `$${parseFloat(aiaiPrice.priceUsd).toFixed(8)}` : "N/A", " ", aiaiPrice.change > 0 ? "▲" : aiaiPrice.change < 0 ? "▼" : "─", Math.abs(aiaiPrice.change).toFixed(1), "%"] }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: ["MCap: ", aiaiPrice.mcap ? `$${(aiaiPrice.mcap / 1000).toFixed(1)}k` : "N/A"] }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: ["Liq: $", (aiaiPrice.liq / 1000).toFixed(1), "k"] })] })) : _jsx(Text, { color: AIAIAI_COLORS.muted, children: "Loading\u2026" })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: AIAIAI_COLORS.dim, paddingX: 1, marginTop: 1, children: [_jsx(Text, { bold: true, children: "\uD83D\uDCBC Wallets" }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: ["Cold: ", coldBalance.sol.toFixed(2), " SOL"] }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: [" AIAIAI: ", coldBalance.aiaiai.toLocaleString(undefined, { maximumFractionDigits: 0 })] }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: ["Action: ", actionBalance.sol.toFixed(2), " SOL"] }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: [" AIAIAI: ", actionBalance.aiaiai.toLocaleString(undefined, { maximumFractionDigits: 0 })] }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: ["Deposit: ", depositBalance.usdc.toFixed(2), " USDC"] }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: [" SOL: ", depositBalance.sol.toFixed(2)] })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: AIAIAI_COLORS.dim, paddingX: 1, marginTop: 1, children: [_jsx(Text, { bold: true, children: "\u26A1 Actions" }), recentActions.length > 0 ? recentActions.map((a, i) => {
594
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(StatusBar, { model: modelName, chain: chain, price: priceBadge, toolRunning: toolRunning, connected: true, statusLine: statusLine }), _jsxs(Box, { flexDirection: "row", flexGrow: 1, overflow: "hidden", children: [_jsxs(Box, { flexDirection: "column", width: SIDEBAR_W, minWidth: SIDEBAR_MIN, flexShrink: 0, overflow: "hidden", children: [_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: AIAIAI_COLORS.accent, paddingX: 1, children: [_jsx(Text, { bold: true, color: AIAIAI_COLORS.accent, children: "\uD83E\uDD16 $AIAIAI" }), aiaiPrice ? (_jsxs(_Fragment, { children: [_jsxs(Text, { color: changeColor(aiaiPrice.change), children: [aiaiPrice.priceUsd ? `$${parseFloat(aiaiPrice.priceUsd).toFixed(8)}` : "N/A", " ", aiaiPrice.change > 0 ? "▲" : aiaiPrice.change < 0 ? "▼" : "─", Math.abs(aiaiPrice.change).toFixed(1), "%"] }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: ["MCap: ", aiaiPrice.mcap ? `$${(aiaiPrice.mcap / 1000).toFixed(1)}k` : "N/A"] }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: ["Liq: $", (aiaiPrice.liq / 1000).toFixed(1), "k"] })] })) : _jsx(Text, { color: AIAIAI_COLORS.muted, children: "Loading\u2026" })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: AIAIAI_COLORS.dim, paddingX: 1, marginTop: 1, children: [_jsx(Text, { bold: true, children: "\uD83D\uDCBC Wallets" }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: ["Cold: ", coldBalance.sol.toFixed(2), " SOL"] }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: [" AIAIAI: ", coldBalance.aiaiai.toLocaleString(undefined, { maximumFractionDigits: 0 })] }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: ["Action: ", actionBalance.sol.toFixed(2), " SOL"] }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: [" AIAIAI: ", actionBalance.aiaiai.toLocaleString(undefined, { maximumFractionDigits: 0 })] }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: ["Deposit: ", depositBalance.usdc.toFixed(2), " USDC"] }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: [" SOL: ", depositBalance.sol.toFixed(2)] })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: AIAIAI_COLORS.dim, paddingX: 1, marginTop: 1, children: [_jsx(Text, { bold: true, children: "\u26A1 Actions" }), recentActions.length > 0 ? recentActions.map((a, i) => {
537
595
  const col = a.type === "buy" ? AIAIAI_COLORS.success : a.type === "burn" ? AIAIAI_COLORS.error : AIAIAI_COLORS.muted;
538
596
  const time = new Date(a.timestamp).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
539
597
  return _jsxs(Text, { color: col, children: [actionIcon(a.type), " ", time, " ", a.type, " ", a.amount.toLocaleString()] }, i);
540
- }) : _jsx(Text, { color: AIAIAI_COLORS.muted, children: "Waiting for actions\u2026" })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: AIAIAI_COLORS.dim, paddingX: 1, marginTop: 1, children: [_jsx(Text, { bold: true, children: "\uD83D\uDCB5 Fees" }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: ["Buy: $", fees.buyFees.toFixed(4)] }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: ["Burn: $", fees.burnFees.toFixed(4)] }), _jsxs(Text, { color: AIAIAI_COLORS.success, bold: true, children: ["Total: $", fees.total.toFixed(4)] })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: AIAIAI_COLORS.dim, paddingX: 1, marginTop: 1, children: [_jsx(Text, { bold: true, children: "\uD83D\uDCF0 News" }), sidebarNews.length > 0 ? sidebarNews.map((n, i) => {
598
+ }) : _jsx(Text, { color: AIAIAI_COLORS.muted, children: "Waiting for actions\u2026" })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: AIAIAI_COLORS.dim, paddingX: 1, marginTop: 1, children: [_jsx(Text, { bold: true, children: "\uD83D\uDCB5 Fees (0.5%)" }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: ["Buy: $", fees.buyFees.toFixed(4)] }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: ["Burn: $", fees.burnFees.toFixed(4)] }), _jsxs(Text, { color: AIAIAI_COLORS.accent, bold: true, children: ["Total: $", fees.total.toFixed(4)] })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: AIAIAI_COLORS.dim, paddingX: 1, marginTop: 1, children: [_jsx(Text, { bold: true, children: "\uD83D\uDCF0 News" }), sidebarNews.length > 0 ? sidebarNews.map((n, i) => {
541
599
  const sentColor = n.sentiment > 0.3 ? AIAIAI_COLORS.success : n.sentiment < -0.3 ? AIAIAI_COLORS.error : AIAIAI_COLORS.muted;
542
600
  const title = n.title.length > 30 ? n.title.slice(0, 28) + "…" : n.title;
543
601
  return _jsx(Text, { color: sentColor, children: title }, i);
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * ActionFeed — reads agent wallet activity via public RPC.
3
- * Classifies recent transactions as buys, burns, or transfers.
4
- * Presents them as actions the agent made.
3
+ * Only tracks AIAIAI token transfers. Classifies as buy or burn.
4
+ * Fees are calculated as a percentage of each action.
5
5
  */
6
6
  import type { ToolResult } from "../api/ExtensionAPI.js";
7
- export type ActionType = "buy" | "burn" | "transfer" | "deposit" | "sell";
7
+ export type ActionType = "buy" | "burn";
8
8
  export interface AgentAction {
9
9
  id: string;
10
10
  type: ActionType;
@@ -13,13 +13,10 @@ export interface AgentAction {
13
13
  usdValue: number;
14
14
  timestamp: number;
15
15
  signature: string;
16
- from: string;
17
- to: string;
18
16
  }
19
17
  export interface FeeTracker {
20
18
  buyFees: number;
21
19
  burnFees: number;
22
- sellFees: number;
23
20
  total: number;
24
21
  }
25
22
  export declare class ActionFeed {
@@ -28,12 +25,9 @@ export declare class ActionFeed {
28
25
  private lastFetch;
29
26
  private cacheDuration;
30
27
  private fees;
31
- private lastDepositBalance;
32
- private lastActionBalance;
28
+ private _price;
33
29
  refresh(): Promise<void>;
34
30
  private processTransaction;
35
- private getPrice;
36
- private _price;
37
31
  setPrice(p: number): void;
38
32
  getActions(): AgentAction[];
39
33
  getRecentActions(limit?: number): AgentAction[];
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * ActionFeed — reads agent wallet activity via public RPC.
3
- * Classifies recent transactions as buys, burns, or transfers.
4
- * Presents them as actions the agent made.
3
+ * Only tracks AIAIAI token transfers. Classifies as buy or burn.
4
+ * Fees are calculated as a percentage of each action.
5
5
  */
6
6
  import { Type } from "@sinclair/typebox";
7
7
  import { ACTION_WALLET, AIAIAI_MINT } from "./AgentWallet.js";
@@ -24,22 +24,19 @@ export class ActionFeed {
24
24
  lastSignature = "";
25
25
  lastFetch = 0;
26
26
  cacheDuration = 60_000;
27
- fees = { buyFees: 0, burnFees: 0, sellFees: 0, total: 0 };
28
- lastDepositBalance = 0;
29
- lastActionBalance = 0;
27
+ fees = { buyFees: 0, burnFees: 0, total: 0 };
28
+ _price = 0.0004;
30
29
  async refresh() {
31
30
  if (Date.now() - this.lastFetch < this.cacheDuration)
32
31
  return;
33
32
  this.lastFetch = Date.now();
34
33
  try {
35
- // Get recent signatures for the action wallet
36
34
  const signatures = await rpcCall("getSignaturesForAddress", [
37
35
  ACTION_WALLET,
38
- { limit: 20 },
36
+ { limit: 30 },
39
37
  ]);
40
38
  if (!signatures || signatures.length === 0)
41
39
  return;
42
- // Process new signatures
43
40
  for (const sigInfo of signatures) {
44
41
  if (sigInfo.signature === this.lastSignature)
45
42
  break;
@@ -61,55 +58,54 @@ export class ActionFeed {
61
58
  return;
62
59
  const preBalances = tx.meta.preTokenBalances ?? [];
63
60
  const postBalances = tx.meta.postTokenBalances ?? [];
64
- // Look for AIAIAI transfers
61
+ // Find AIAIAI balance changes for the action wallet
65
62
  for (const post of postBalances) {
66
- if (post.mint === AIAIAI_MINT) {
67
- const pre = preBalances.find((p) => p.accountIndex === post.accountIndex);
68
- const preAmt = parseFloat(pre?.uiTokenAmount?.uiAmount ?? "0");
69
- const postAmt = parseFloat(post.uiTokenAmount?.uiAmount ?? "0");
70
- const diff = postAmt - preAmt;
71
- if (Math.abs(diff) < 0.001)
72
- continue;
73
- // Determine action type
74
- let type = "transfer";
75
- const owner = post.owner ?? "";
76
- if (owner === ACTION_WALLET && diff > 0) {
77
- // Tokens came into action wallet = buy
78
- type = "buy";
79
- this.fees.buyFees += Math.abs(diff) * 0.0004 * 0.05; // ~0.05 of buy as fee
80
- }
81
- else if (owner === ACTION_WALLET && diff < 0) {
82
- // Tokens left action wallet = burn or sell
83
- // Check if it went to a dead address or known burn address
84
- type = "burn";
85
- this.fees.burnFees += Math.abs(diff) * 0.0004 * 0.03; // ~0.03 of burn as fee
86
- }
87
- const action = {
88
- id: signature.slice(0, 8),
89
- type,
90
- amount: Math.abs(diff),
91
- token: "AIAIAI",
92
- usdValue: Math.abs(diff) * (this.getPrice()),
93
- timestamp: (tx.blockTime ?? 0) * 1000,
94
- signature,
95
- from: "",
96
- to: "",
97
- };
98
- this.actions.unshift(action);
63
+ if (post.mint !== AIAIAI_MINT)
64
+ continue;
65
+ if (post.owner !== ACTION_WALLET)
66
+ continue;
67
+ const pre = preBalances.find((p) => p.accountIndex === post.accountIndex && p.mint === AIAIAI_MINT);
68
+ const preAmt = pre ? parseFloat(pre.uiTokenAmount?.uiAmount ?? "0") : 0;
69
+ const postAmt = parseFloat(post.uiTokenAmount?.uiAmount ?? "0");
70
+ const diff = postAmt - preAmt;
71
+ // Skip negligible changes
72
+ if (Math.abs(diff) < 0.000001)
73
+ continue;
74
+ // Positive diff = tokens came IN = BUY
75
+ // Negative diff = tokens went OUT = BURN
76
+ const type = diff > 0 ? "buy" : "burn";
77
+ const amount = Math.abs(diff);
78
+ const usdValue = amount * this._price;
79
+ // Fee: 0.5% of action value
80
+ const fee = usdValue * 0.005;
81
+ if (type === "buy") {
82
+ this.fees.buyFees += fee;
83
+ }
84
+ else {
85
+ this.fees.burnFees += fee;
99
86
  }
87
+ this.fees.total = this.fees.buyFees + this.fees.burnFees;
88
+ const action = {
89
+ id: signature.slice(0, 8),
90
+ type,
91
+ amount,
92
+ token: "AIAIAI",
93
+ usdValue,
94
+ timestamp: (tx.blockTime ?? Math.floor(Date.now() / 1000)) * 1000,
95
+ signature,
96
+ };
97
+ this.actions.unshift(action);
100
98
  }
101
- // Keep only last 50 actions
99
+ // Keep only last 50
102
100
  if (this.actions.length > 50)
103
101
  this.actions = this.actions.slice(0, 50);
104
102
  }
105
103
  catch { /* skip this tx */ }
106
104
  }
107
- getPrice() {
108
- // Cached price — updated externally
109
- return this._price || 0.0004;
105
+ setPrice(p) {
106
+ if (p > 0)
107
+ this._price = p;
110
108
  }
111
- _price = 0.0004;
112
- setPrice(p) { this._price = p; }
113
109
  getActions() {
114
110
  return [...this.actions];
115
111
  }
@@ -117,7 +113,7 @@ export class ActionFeed {
117
113
  return this.actions.slice(0, limit);
118
114
  }
119
115
  getFees() {
120
- return { ...this.fees, total: this.fees.buyFees + this.fees.burnFees + this.fees.sellFees };
116
+ return { ...this.fees };
121
117
  }
122
118
  getActionSummary() {
123
119
  const buys = this.actions.filter(a => a.type === "buy");
@@ -138,12 +134,22 @@ export class ActionFeed {
138
134
  const limit = params.limit || 10;
139
135
  const actions = this.getRecentActions(limit);
140
136
  if (actions.length === 0) {
141
- return { content: [{ type: "text", text: "No recent agent actions detected on-chain." }] };
137
+ return {
138
+ content: [{
139
+ type: "text",
140
+ text: [
141
+ "No agent buy/burn actions detected yet.",
142
+ "",
143
+ `Action wallet: ${ACTION_WALLET}`,
144
+ "The agent buys and burns $AIAIAI. Actions appear here when detected on-chain.",
145
+ ].join("\n"),
146
+ }],
147
+ };
142
148
  }
143
149
  const lines = actions.map(a => {
144
- const icon = a.type === "buy" ? "↗" : a.type === "burn" ? "🔥" : "→";
150
+ const icon = a.type === "buy" ? "↗" : "🔥";
145
151
  const time = new Date(a.timestamp).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
146
- return ` ${icon} ${time} ${a.type.toUpperCase()} ${a.amount.toLocaleString()} $AIAIAI ($${a.usdValue.toFixed(2)})`;
152
+ return ` ${icon} ${time} ${a.type.toUpperCase().padEnd(6)} ${a.amount.toLocaleString()} $AIAIAI ($${a.usdValue.toFixed(2)})`;
147
153
  });
148
154
  const summary = this.getActionSummary();
149
155
  return {
@@ -151,7 +157,8 @@ export class ActionFeed {
151
157
  type: "text",
152
158
  text: [
153
159
  `Agent Actions (last ${actions.length})`,
154
- `Buys: ${summary.buys} (${summary.totalBought.toLocaleString()}) | Burns: ${summary.burns} (${summary.totalBurned.toLocaleString()})`,
160
+ ` Buys: ${summary.buys} (${summary.totalBought.toLocaleString()} $AIAIAI)`,
161
+ ` Burns: ${summary.burns} (${summary.totalBurned.toLocaleString()} $AIAIAI)`,
155
162
  ``,
156
163
  ...lines,
157
164
  ].join("\n"),
@@ -164,10 +171,10 @@ export class ActionFeed {
164
171
  content: [{
165
172
  type: "text",
166
173
  text: [
167
- `Agent Fees`,
174
+ `Agent Fees (0.5% per action)`,
168
175
  ` Buy fees: $${f.buyFees.toFixed(4)}`,
169
176
  ` Burn fees: $${f.burnFees.toFixed(4)}`,
170
- ` Sell fees: $${f.sellFees.toFixed(4)}`,
177
+ ` ─────────────────────`,
171
178
  ` Total: $${f.total.toFixed(4)}`,
172
179
  ].join("\n"),
173
180
  }],