@aiaiaichain/agent 0.1.4 → 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.
Files changed (58) hide show
  1. package/dist/cli.js +218 -5
  2. package/dist/core/ChainConfig.js +1 -1
  3. package/dist/core/SystemMonitor.d.ts +1 -4
  4. package/dist/core/SystemMonitor.js +20 -46
  5. package/dist/index.d.ts +10 -0
  6. package/dist/index.js +11 -0
  7. package/dist/models/ModelRegistry.js +12 -4
  8. package/dist/runner/AgentRunner.d.ts +2 -0
  9. package/dist/runner/AgentRunner.js +18 -1
  10. package/dist/runner/ModelClient.js +109 -48
  11. package/dist/session/SessionManager.d.ts +1 -0
  12. package/dist/session/SessionManager.js +8 -2
  13. package/dist/session/SessionStore.js +8 -3
  14. package/dist/tools/CrossTools.js +13 -5
  15. package/dist/tools/MarketSentiment.js +22 -13
  16. package/dist/tools/NewsSentiment.js +9 -3
  17. package/dist/tools/PriceFeed.js +11 -4
  18. package/dist/tools/TechnicalAnalysis.js +2 -1
  19. package/dist/tools/TokenCalendar.d.ts +24 -0
  20. package/dist/tools/TokenCalendar.js +81 -0
  21. package/dist/tools/TokenSecurityScanner.d.ts +22 -0
  22. package/dist/tools/TokenSecurityScanner.js +102 -0
  23. package/dist/tools/TransactionSim.d.ts +17 -0
  24. package/dist/tools/TransactionSim.js +78 -0
  25. package/dist/tui/App.js +143 -21
  26. package/dist/tui/REPL.js +2 -2
  27. package/dist/tui/Sparkline.d.ts +21 -0
  28. package/dist/tui/Sparkline.js +44 -0
  29. package/dist/tui/ThemePresets.d.ts +25 -0
  30. package/dist/tui/ThemePresets.js +117 -0
  31. package/dist/util/clipboard.d.ts +9 -0
  32. package/dist/util/clipboard.js +26 -0
  33. package/dist/util/commandSuggest.d.ts +7 -0
  34. package/dist/util/commandSuggest.js +44 -0
  35. package/dist/util/confirmation.d.ts +6 -0
  36. package/dist/util/confirmation.js +16 -0
  37. package/dist/util/errorHandler.d.ts +3 -0
  38. package/dist/util/errorHandler.js +28 -0
  39. package/dist/util/logger.d.ts +11 -0
  40. package/dist/util/logger.js +43 -0
  41. package/dist/util/processManager.d.ts +5 -0
  42. package/dist/util/processManager.js +39 -0
  43. package/dist/util/resilientFetch.d.ts +21 -0
  44. package/dist/util/resilientFetch.js +94 -0
  45. package/dist/util/responseCache.d.ts +27 -0
  46. package/dist/util/responseCache.js +54 -0
  47. package/dist/util/safeLog.d.ts +4 -5
  48. package/dist/util/safeLog.js +68 -30
  49. package/dist/util/scheduler.d.ts +14 -0
  50. package/dist/util/scheduler.js +75 -0
  51. package/dist/util/webhooks.d.ts +9 -0
  52. package/dist/util/webhooks.js +75 -0
  53. package/dist/wallet/ActionFeed.js +12 -4
  54. package/dist/wallet/AgentWallet.d.ts +1 -0
  55. package/dist/wallet/AgentWallet.js +30 -5
  56. package/dist/wallet/ProfitTracker.d.ts +30 -0
  57. package/dist/wallet/ProfitTracker.js +93 -0
  58. package/package.json +1 -1
@@ -1,38 +1,76 @@
1
1
  /**
2
- * safeLog — wraps console.log/error/warn to avoid corrupting Ink TUI output.
2
+ * src/util/safeLog.tsIntercepts console output, masks secrets, routes to file
3
3
  */
4
- const queue = [];
5
- let notifyFn = null;
6
- let scheduled = false;
7
- export function wireNotify(fn) {
8
- notifyFn = fn;
9
- // Drain any queued messages
10
- if (queue.length > 0) {
11
- for (const msg of queue)
12
- fn(msg);
13
- queue.length = 0;
4
+ import { existsSync, appendFileSync, mkdirSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import { homedir } from "node:os";
7
+ const AIAIAI_HOME = process.env.AIAIAI_HOME ?? join(homedir(), '.aiaiai');
8
+ const LOG_DIR = join(AIAIAI_HOME, '.logs');
9
+ const LOG_FILE = join(LOG_DIR, 'agent.log');
10
+ // Patterns that indicate sensitive values
11
+ const SENSITIVE_PATTERNS = [
12
+ /sk-[a-zA-Z0-9]{20,}/g, // API keys
13
+ /[A-Za-z0-9]{32,}/g, // Hex keys
14
+ /Bearer\s+[A-Za-z0-9\-._~+/]+/g, // Bearer tokens
15
+ /api[_-]?key["\s:=]+["']?[^\s"']+/gi, // Key assignments
16
+ /secret["\s:=]+["']?[^\s"']+/gi, // Secrets
17
+ ];
18
+ const SENSITIVE_ENV_NAMES = [
19
+ 'OPENROUTER_API_KEY', 'ANTHROPIC_API_KEY', 'CRYPTOPANIC_API_KEY',
20
+ 'GMGN_API_KEY', 'SOLANA_RPC_URL', 'HELIUS_API_KEY', 'PRIVY_KEY',
21
+ ];
22
+ const REDACTED = '[REDACTED]';
23
+ function maskSensitive(text) {
24
+ let masked = text;
25
+ for (const pattern of SENSITIVE_PATTERNS) {
26
+ masked = masked.replace(pattern, (match) => {
27
+ // Keep first 4 chars for identification
28
+ if (match.length > 8)
29
+ return match.slice(0, 4) + '…' + REDACTED;
30
+ return REDACTED;
31
+ });
32
+ }
33
+ return masked;
34
+ }
35
+ function ensureLogDir() {
36
+ if (!existsSync(LOG_DIR)) {
37
+ mkdirSync(LOG_DIR, { recursive: true, mode: 0o700 });
38
+ }
39
+ }
40
+ function writeToFile(text) {
41
+ try {
42
+ ensureLogDir();
43
+ appendFileSync(LOG_FILE, text + '\n', { encoding: 'utf-8' });
14
44
  }
45
+ catch { /* non-fatal — prevents logging failures from crashing agent */ }
15
46
  }
16
- function safeLogFn(...args) {
17
- const msg = args.map(a => (typeof a === "string" ? a : JSON.stringify(a))).join(" ");
18
- if (notifyFn) {
19
- notifyFn(msg);
47
+ export function wireNotify(fn) {
48
+ // Wire to notification callback if provided
49
+ if (fn) {
50
+ // placeholder for notification integration
20
51
  }
21
- else {
22
- queue.push(msg);
23
- if (!scheduled) {
24
- scheduled = true;
25
- process.nextTick(() => {
26
- // If no notifyFn registered by next tick, just write to stderr
27
- if (!notifyFn && queue.length > 0) {
28
- for (const m of queue)
29
- process.stderr.write(m + "\n");
30
- queue.length = 0;
31
- }
32
- scheduled = false;
33
- });
34
- }
52
+ }
53
+ export function safeLog(...args) {
54
+ const raw = args.map(a => typeof a === 'string' ? a : String(a)).join(' ');
55
+ const masked = maskSensitive(raw);
56
+ const timestamp = new Date().toISOString();
57
+ const line = `[${timestamp}] ${masked}`;
58
+ try {
59
+ writeToFile(line);
35
60
  }
61
+ catch { /* non-fatal */ }
62
+ process.stdout.write(line + '\n');
63
+ }
64
+ // Override console methods
65
+ export function installSafeLogging() {
66
+ console.log = (...args) => safeLog(...args);
67
+ console.warn = (...args) => {
68
+ const masked = maskSensitive(args.map(String).join(' '));
69
+ process.stderr.write(`[WARN] ${masked}\n`);
70
+ };
71
+ console.error = (...args) => {
72
+ const masked = maskSensitive(args.map(String).join(' '));
73
+ process.stderr.write(`[ERROR] ${masked}\n`);
74
+ };
36
75
  }
37
- export const safeLog = safeLogFn;
38
76
  //# sourceMappingURL=safeLog.js.map
@@ -0,0 +1,14 @@
1
+ /**
2
+ * src/util/scheduler.ts — Centralized interval scheduler with pause/resume
3
+ */
4
+ export declare function addTask(name: string, intervalMs: number, fn: () => void): void;
5
+ export declare function removeTask(name: string): void;
6
+ export declare function pauseTask(name: string): void;
7
+ export declare function resumeTask(name: string): void;
8
+ export declare function pauseAll(): void;
9
+ export declare function resumeAll(): void;
10
+ /**
11
+ * Start the scheduler loop. Returns a stop function.
12
+ */
13
+ export declare function startScheduler(): () => void;
14
+ //# sourceMappingURL=scheduler.d.ts.map
@@ -0,0 +1,75 @@
1
+ /**
2
+ * src/util/scheduler.ts — Centralized interval scheduler with pause/resume
3
+ */
4
+ const MAX_TASKS = 50;
5
+ const tasks = [];
6
+ let running = false;
7
+ export function addTask(name, intervalMs, fn) {
8
+ // Replace existing task with same name
9
+ const existing = tasks.findIndex(t => t.name === name);
10
+ if (existing >= 0) {
11
+ tasks[existing] = { name, intervalMs, fn, lastRun: 0, paused: false };
12
+ }
13
+ else {
14
+ if (tasks.length >= MAX_TASKS) {
15
+ // Evict oldest non-paused task
16
+ const evictIdx = tasks.findIndex(t => !t.paused);
17
+ if (evictIdx >= 0)
18
+ tasks.splice(evictIdx, 1);
19
+ else
20
+ return; // All tasks paused, refuse to add
21
+ }
22
+ tasks.push({ name, intervalMs, fn, lastRun: 0, paused: false });
23
+ }
24
+ }
25
+ export function removeTask(name) {
26
+ const idx = tasks.findIndex(t => t.name === name);
27
+ if (idx >= 0)
28
+ tasks.splice(idx, 1);
29
+ }
30
+ export function pauseTask(name) {
31
+ const task = tasks.find(t => t.name === name);
32
+ if (task)
33
+ task.paused = true;
34
+ }
35
+ export function resumeTask(name) {
36
+ const task = tasks.find(t => t.name === name);
37
+ if (task)
38
+ task.paused = false;
39
+ }
40
+ export function pauseAll() {
41
+ tasks.forEach(t => (t.paused = true));
42
+ }
43
+ export function resumeAll() {
44
+ tasks.forEach(t => (t.paused = false));
45
+ }
46
+ /**
47
+ * Start the scheduler loop. Returns a stop function.
48
+ */
49
+ export function startScheduler() {
50
+ if (running)
51
+ return () => { };
52
+ running = true;
53
+ const tick = () => {
54
+ if (!running)
55
+ return;
56
+ const now = Date.now();
57
+ for (const task of tasks) {
58
+ if (task.paused)
59
+ continue;
60
+ if (now - task.lastRun >= task.intervalMs) {
61
+ task.lastRun = now;
62
+ try {
63
+ task.fn();
64
+ }
65
+ catch { /* task errors don't crash scheduler */ }
66
+ }
67
+ }
68
+ setTimeout(tick, 1000);
69
+ };
70
+ tick();
71
+ return () => {
72
+ running = false;
73
+ };
74
+ }
75
+ //# sourceMappingURL=scheduler.js.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * src/util/webhooks.ts — Discord & Telegram notification webhooks.
3
+ * Configure via ~/.aiaiai/.env with WEBHOOK_DISCORD and/or WEBHOOK_TELEGRAM.
4
+ */
5
+ export declare function sendDiscord(message: string): Promise<boolean>;
6
+ export declare function sendTelegram(message: string): Promise<boolean>;
7
+ export declare function notify(message: string): Promise<void>;
8
+ export declare function hasWebhooks(): boolean;
9
+ //# sourceMappingURL=webhooks.d.ts.map
@@ -0,0 +1,75 @@
1
+ /**
2
+ * src/util/webhooks.ts — Discord & Telegram notification webhooks.
3
+ * Configure via ~/.aiaiai/.env with WEBHOOK_DISCORD and/or WEBHOOK_TELEGRAM.
4
+ */
5
+ import { existsSync, readFileSync } from "node:fs";
6
+ import { join } from "node:path";
7
+ import { homedir } from "node:os";
8
+ import { logger } from "./logger.js";
9
+ const AIAIAI_HOME = process.env.AIAIAI_HOME ?? join(homedir(), ".aiaiai");
10
+ function getWebhookUrl(service) {
11
+ const envFile = join(AIAIAI_HOME, ".env");
12
+ if (!existsSync(envFile))
13
+ return null;
14
+ const content = readFileSync(envFile, "utf-8");
15
+ const key = service === "discord" ? "WEBHOOK_DISCORD" : "WEBHOOK_TELEGRAM";
16
+ const match = new RegExp(`^${key}=(.+)$`, "m").exec(content);
17
+ return match?.[1] ?? null;
18
+ }
19
+ export async function sendDiscord(message) {
20
+ const url = getWebhookUrl("discord");
21
+ if (!url)
22
+ return false;
23
+ try {
24
+ const res = await fetch(url, {
25
+ method: "POST",
26
+ headers: { "Content-Type": "application/json" },
27
+ body: JSON.stringify({ content: `🤖 **AIAIAI Agent**\n${message}` }),
28
+ });
29
+ return res.ok;
30
+ }
31
+ catch (error) {
32
+ logger.warn("Webhooks", "Discord send failed", { error: error.message });
33
+ return false;
34
+ }
35
+ }
36
+ export async function sendTelegram(message) {
37
+ const url = getWebhookUrl("telegram");
38
+ if (!url)
39
+ return false;
40
+ // Telegram Bot API format: https://api.telegram.org/bot<TOKEN>/sendMessage
41
+ try {
42
+ const res = await fetch(url, {
43
+ method: "POST",
44
+ headers: { "Content-Type": "application/json" },
45
+ body: JSON.stringify({
46
+ chat_id: extractTelegramChatId(url),
47
+ text: `🤖 AIAIAI Agent\n${message}`,
48
+ parse_mode: "HTML",
49
+ }),
50
+ });
51
+ return res.ok;
52
+ }
53
+ catch (error) {
54
+ logger.warn("Webhooks", "Telegram send failed", { error: error.message });
55
+ return false;
56
+ }
57
+ }
58
+ function extractTelegramChatId(url) {
59
+ // URL format: https://api.telegram.org/bot<TOKEN>/sendMessage?chat_id=<ID>
60
+ const match = /chat_id=([^&]+)/.exec(url);
61
+ return match?.[1] ?? "";
62
+ }
63
+ export async function notify(message) {
64
+ const [discordOk, telegramOk] = await Promise.all([
65
+ sendDiscord(message),
66
+ sendTelegram(message),
67
+ ]);
68
+ if (!discordOk && !telegramOk) {
69
+ logger.debug("Webhooks", "No webhooks configured or all failed");
70
+ }
71
+ }
72
+ export function hasWebhooks() {
73
+ return !!(getWebhookUrl("discord") || getWebhookUrl("telegram"));
74
+ }
75
+ //# sourceMappingURL=webhooks.js.map
@@ -5,18 +5,22 @@
5
5
  */
6
6
  import { Type } from "@sinclair/typebox";
7
7
  import { ACTION_WALLET, AIAIAI_MINT } from "./AgentWallet.js";
8
+ import { resilientFetch } from "../util/resilientFetch.js";
9
+ import { logger } from "../util/logger.js";
8
10
  function getRpcUrl() {
9
11
  return process.env.SOLANA_RPC_URL || "https://api.mainnet-beta.solana.com";
10
12
  }
11
13
  async function rpcCall(method, params = []) {
12
- const response = await fetch(getRpcUrl(), {
14
+ const response = await resilientFetch(getRpcUrl(), {
15
+ timeout: 15_000,
16
+ retries: 2,
13
17
  method: "POST",
14
18
  headers: { "Content-Type": "application/json" },
15
19
  body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
16
20
  });
17
21
  const data = await response.json();
18
22
  if (data.error)
19
- throw new Error(data.error.message);
23
+ throw new Error(data.error.message || 'Unknown RPC error');
20
24
  return data.result;
21
25
  }
22
26
  export class ActionFeed {
@@ -46,7 +50,9 @@ export class ActionFeed {
46
50
  this.lastSignature = signatures[0].signature;
47
51
  }
48
52
  }
49
- catch { /* non-fatal */ }
53
+ catch (error) {
54
+ logger.debug('ActionFeed', 'refresh failed', { error: error.message });
55
+ }
50
56
  }
51
57
  async processTransaction(signature) {
52
58
  try {
@@ -100,7 +106,9 @@ export class ActionFeed {
100
106
  if (this.actions.length > 50)
101
107
  this.actions = this.actions.slice(0, 50);
102
108
  }
103
- catch { /* skip this tx */ }
109
+ catch (error) {
110
+ logger.debug('ActionFeed', 'Failed to parse tx', { error: error.message });
111
+ }
104
112
  }
105
113
  setPrice(p) {
106
114
  if (p > 0)
@@ -33,6 +33,7 @@ export declare class AgentWallet {
33
33
  getColdWallet(): Promise<WalletBalance>;
34
34
  getActionWallet(): Promise<WalletBalance>;
35
35
  getDepositWallet(): Promise<WalletBalance>;
36
+ getSolPrice(): Promise<number>;
36
37
  getAgentBalanceParams: import("@sinclair/typebox").TObject<{}>;
37
38
  getDepositBalanceParams: import("@sinclair/typebox").TObject<{}>;
38
39
  getAgentBalanceTool(): Promise<ToolResult>;
@@ -8,6 +8,8 @@
8
8
  * Signer: GmFrDZT2cdrqykgTikVdXbe8EtCgzUDM9VsDhQnwsUsG (authority)
9
9
  */
10
10
  import { Type } from "@sinclair/typebox";
11
+ import { resilientFetch } from "../util/resilientFetch.js";
12
+ import { logger } from "../util/logger.js";
11
13
  export const COLD_WALLET = "A11iZoqEt6hU7HyggqC67ee4AtYmaJjwKCvJLerJRV2J";
12
14
  export const ACTION_WALLET = "BygDYM1ZXLQNC1HXLhnd1rHZ7E5XjioqT3vPjJFfjnU2";
13
15
  export const DEPOSIT_WALLET = "FBMDYpG9WXKy4SgxuATQdB2sCyzHsJWPrEr45z3TgL2e";
@@ -19,14 +21,21 @@ function getRpcUrl() {
19
21
  return process.env.SOLANA_RPC_URL || "https://api.mainnet-beta.solana.com";
20
22
  }
21
23
  async function rpcCall(method, params = []) {
22
- const response = await fetch(getRpcUrl(), {
24
+ const response = await resilientFetch(getRpcUrl(), {
25
+ timeout: 15_000,
26
+ retries: 2,
23
27
  method: "POST",
24
28
  headers: { "Content-Type": "application/json" },
25
29
  body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
26
30
  });
27
31
  const data = await response.json();
28
- if (data.error)
29
- throw new Error(data.error.message);
32
+ if (data.error) {
33
+ const msg = data.error.message || 'Unknown RPC error';
34
+ if (data.error.code === -32005 || msg.includes('rate limit')) {
35
+ logger.warn('AgentWallet', 'RPC rate limited', { method, code: data.error.code });
36
+ }
37
+ throw new Error(msg);
38
+ }
30
39
  return data.result;
31
40
  }
32
41
  async function getBalance(address) {
@@ -34,7 +43,8 @@ async function getBalance(address) {
34
43
  const result = await rpcCall("getBalance", [address]);
35
44
  return (result?.value ?? 0) / 1e9;
36
45
  }
37
- catch {
46
+ catch (error) {
47
+ logger.warn('AgentWallet', 'getBalance failed', { address, error: error.message });
38
48
  return 0;
39
49
  }
40
50
  }
@@ -50,7 +60,8 @@ async function getTokenBalance(address, mint) {
50
60
  return 0;
51
61
  return parseFloat(accounts[0]?.account?.data?.parsed?.info?.tokenAmount?.uiAmount ?? "0");
52
62
  }
53
- catch {
63
+ catch (error) {
64
+ logger.warn('AgentWallet', 'getTokenBalance failed', { address, mint, error: error.message });
54
65
  return 0;
55
66
  }
56
67
  }
@@ -62,6 +73,9 @@ export class AgentWallet {
62
73
  if (this.cachedWallets && Date.now() - this.lastFetch < this.cacheDuration) {
63
74
  return this.cachedWallets;
64
75
  }
76
+ // Parallel: 3 SOL balances + 6 token balances (9 concurrent queries)
77
+ const wallets = [COLD_WALLET, ACTION_WALLET, DEPOSIT_WALLET];
78
+ const mints = [AIAIAI_MINT, USDC_MINT];
65
79
  const [coldSol, coldAiai, coldUsdc, actionSol, actionAiai, actionUsdc, depSol, depAiai, depUsdc] = await Promise.all([
66
80
  getBalance(COLD_WALLET),
67
81
  getTokenBalance(COLD_WALLET, AIAIAI_MINT),
@@ -90,6 +104,17 @@ export class AgentWallet {
90
104
  async getDepositWallet() {
91
105
  return (await this.getAll()).deposit;
92
106
  }
107
+ // Get live SOL price from PriceFeed
108
+ async getSolPrice() {
109
+ try {
110
+ const { priceFeed } = await import("../tools/PriceFeed.js");
111
+ const result = await priceFeed.fetchToken("So11111111111111111111111111111111111111112");
112
+ return parseFloat(result.priceUsd ?? "150");
113
+ }
114
+ catch {
115
+ return 150; // fallback
116
+ }
117
+ }
93
118
  // ── Tools ───────────────────────────────────────────────────────────────
94
119
  getAgentBalanceParams = Type.Object({});
95
120
  getDepositBalanceParams = Type.Object({});
@@ -0,0 +1,30 @@
1
+ /**
2
+ /ProfitTracker.ts - Track cost basis and P&L for agent wallet trades
3
+ */
4
+ export interface Trade {
5
+ timestamp: number;
6
+ type: "buy" | "sell" | "burn";
7
+ tokenAmount: number;
8
+ solAmount: number;
9
+ pricePerToken: number;
10
+ txSignature?: string;
11
+ }
12
+ export interface ProfitState {
13
+ trades: Trade[];
14
+ totalSpent: number;
15
+ totalReceived: number;
16
+ avgCostBasis: number;
17
+ }
18
+ export interface PLSummary {
19
+ avgCostBasis: number;
20
+ totalSpent: number;
21
+ totalReceived: number;
22
+ realizedPL: number;
23
+ unrealizedPL: number;
24
+ roi: number;
25
+ }
26
+ export declare function recordTrade(type: "buy" | "sell" | "burn", tokenAmount: number, solAmount: number, txSignature?: string): void;
27
+ export declare function getPLSummary(currentTokenPrice: number): PLSummary;
28
+ export declare function getTradeHistory(): Trade[];
29
+ export declare function exportCSV(): string;
30
+ //# sourceMappingURL=ProfitTracker.d.ts.map
@@ -0,0 +1,93 @@
1
+ /**
2
+ /ProfitTracker.ts - Track cost basis and P&L for agent wallet trades
3
+ */
4
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import { homedir } from "node:os";
7
+ import { logger } from "../util/logger.js";
8
+ const AIAIAI_HOME = process.env.AIAIAI_HOME ?? join(homedir(), ".aiaiai");
9
+ const TRACKER_DIR = join(AIAIAI_HOME, ".tracker");
10
+ const TRACKER_FILE = join(TRACKER_DIR, "pl.json");
11
+ function loadState() {
12
+ try {
13
+ const raw = readFileSync(TRACKER_FILE, "utf-8");
14
+ return JSON.parse(raw);
15
+ }
16
+ catch {
17
+ return { trades: [], totalSpent: 0, totalReceived: 0, avgCostBasis: 0 };
18
+ }
19
+ }
20
+ function saveState(state) {
21
+ try {
22
+ if (!existsSync(TRACKER_DIR)) {
23
+ mkdirSync(TRACKER_DIR, { recursive: true, mode: 0o700 });
24
+ }
25
+ writeFileSync(TRACKER_FILE, JSON.stringify(state, null, 2), "utf-8");
26
+ }
27
+ catch (error) {
28
+ logger.warn("ProfitTracker", "Failed to save state", { error: error.message });
29
+ }
30
+ }
31
+ export function recordTrade(type, tokenAmount, solAmount, txSignature) {
32
+ const state = loadState();
33
+ const pricePerToken = tokenAmount > 0 ? solAmount / tokenAmount : 0;
34
+ const trade = {
35
+ timestamp: Date.now(),
36
+ type,
37
+ tokenAmount,
38
+ solAmount,
39
+ pricePerToken,
40
+ txSignature,
41
+ };
42
+ state.trades.push(trade);
43
+ if (type === "buy") {
44
+ state.totalSpent += solAmount;
45
+ const totalTokensInvested = state.trades
46
+ .filter(t => t.type === "buy")
47
+ .reduce((sum, t) => sum + t.tokenAmount, 0);
48
+ state.avgCostBasis = totalTokensInvested > 0 ? state.totalSpent / totalTokensInvested : 0;
49
+ }
50
+ else if (type === "sell") {
51
+ state.totalReceived += solAmount;
52
+ }
53
+ saveState(state);
54
+ logger.info("ProfitTracker", "Recorded " + type, { tokenAmount, solAmount, pricePerToken });
55
+ }
56
+ export function getPLSummary(currentTokenPrice) {
57
+ const state = loadState();
58
+ const buyTrades = state.trades.filter(t => t.type === "buy");
59
+ const totalBought = buyTrades.reduce((sum, t) => sum + t.tokenAmount, 0);
60
+ const totalSoldOrBurned = state.trades
61
+ .filter(t => t.type === "sell" || t.type === "burn")
62
+ .reduce((sum, t) => sum + t.tokenAmount, 0);
63
+ const remainingTokens = totalBought - totalSoldOrBurned;
64
+ const realizedPL = state.totalReceived - (state.totalSpent * (totalSoldOrBurned / Math.max(totalBought, 1)));
65
+ const unrealizedPL = remainingTokens * currentTokenPrice;
66
+ const totalPL = realizedPL + unrealizedPL;
67
+ const roi = state.totalSpent > 0 ? (totalPL / state.totalSpent) * 100 : 0;
68
+ return {
69
+ avgCostBasis: state.avgCostBasis,
70
+ totalSpent: state.totalSpent,
71
+ totalReceived: state.totalReceived,
72
+ realizedPL,
73
+ unrealizedPL,
74
+ roi,
75
+ };
76
+ }
77
+ export function getTradeHistory() {
78
+ return loadState().trades;
79
+ }
80
+ export function exportCSV() {
81
+ const state = loadState();
82
+ const header = "timestamp,type,tokenAmount,solAmount,pricePerToken,txSignature";
83
+ const rows = state.trades.map(t => t.timestamp + "," + t.type + "," + t.tokenAmount + "," + t.solAmount + "," + t.pricePerToken + "," + (t.txSignature ?? ""));
84
+ const csv = header + "\n" + rows.join("\n");
85
+ const csvPath = join(TRACKER_DIR, "pl-export.csv");
86
+ if (!existsSync(TRACKER_DIR)) {
87
+ mkdirSync(TRACKER_DIR, { recursive: true, mode: 0o700 });
88
+ }
89
+ writeFileSync(csvPath, csv, "utf-8");
90
+ logger.info("ProfitTracker", "Exported CSV", { path: csvPath });
91
+ return csvPath;
92
+ }
93
+ //# sourceMappingURL=ProfitTracker.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiaiaichain/agent",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "AIAIAI Chain Agent — Solana-native AI agent for decentralized AI governance. Ticker: $AIAIAI",
5
5
  "author": "AIAIAI Foundation",
6
6
  "license": "MIT",