repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
public Clawd ADK gateway launch mirror
stars
latest
clone command
git clone gitlawb://did:key:z6Mkq5mY...iFZ5/my-project-publ...git clone gitlawb://did:key:z6Mkq5mY.../my-project-publ...2fa351d6docs: add automaton and perps launch sources16d ago| #1 | """MAWD Solana Trading Agent - Full Featured Agent""" |
| #2 | |
| #3 | import asyncio |
| #4 | import json |
| #5 | import sys |
| #6 | from pathlib import Path |
| #7 | from typing import Optional |
| #8 | from time import perf_counter |
| #9 | |
| #10 | from .llm import LLMClient, Message, ToolCall, LLMResponse |
| #11 | from .config import SolanaAgentConfig, load_config |
| #12 | from .tools.base import Tool, ToolResult |
| #13 | from .tools.solana_tools import create_all_trading_tools, set_solana_clients |
| #14 | from .tools.note_tool import TradingNoteTool, RecallTradingNoteTool, ClearTradingNotesTool |
| #15 | |
| #16 | # Import blockchain clients |
| #17 | sys.path.insert(0, str(Path(__file__).parent.parent)) |
| #18 | from clients.bags_client import BagsClient |
| #19 | from clients.helius_client import HeliusClient |
| #20 | from clients.birdeye_client import BirdeyeClient |
| #21 | try: |
| #22 | from clients.cdp_client import create_cdp_client |
| #23 | CDP_AVAILABLE = True |
| #24 | except ImportError: |
| #25 | CDP_AVAILABLE = False |
| #26 | try: |
| #27 | from clients.hyperliquid_client import HyperliquidClient |
| #28 | HYPERLIQUID_AVAILABLE = True |
| #29 | except ImportError: |
| #30 | HYPERLIQUID_AVAILABLE = False |
| #31 | |
| #32 | |
| #33 | class Colors: |
| #34 | """Terminal colors""" |
| #35 | RESET = "\033[0m" |
| #36 | BOLD = "\033[1m" |
| #37 | DIM = "\033[2m" |
| #38 | RED = "\033[31m" |
| #39 | GREEN = "\033[32m" |
| #40 | YELLOW = "\033[33m" |
| #41 | BLUE = "\033[34m" |
| #42 | MAGENTA = "\033[35m" |
| #43 | CYAN = "\033[36m" |
| #44 | BRIGHT_CYAN = "\033[96m" |
| #45 | BRIGHT_GREEN = "\033[92m" |
| #46 | BRIGHT_YELLOW = "\033[93m" |
| #47 | BRIGHT_RED = "\033[91m" |
| #48 | BRIGHT_BLUE = "\033[94m" |
| #49 | |
| #50 | |
| #51 | SYSTEM_PROMPT = """You are MAWD, an AI-powered Solana trading agent. You help users trade tokens, |
| #52 | check balances, analyze market data, launch new tokens, manage their Solana wallet portfolio, |
| #53 | and trade perpetuals on Hyperliquid DEX. |
| #54 | |
| #55 | ## Your Capabilities |
| #56 | |
| #57 | ### Trading Tools (9 tools): |
| #58 | 1. **get_wallet_balance** - Check SOL and token balances |
| #59 | 2. **get_token_price** - Get current token price |
| #60 | 3. **get_token_info** - Comprehensive token info (price, volume, liquidity, holders) |
| #61 | 4. **get_swap_quote** - Get quote before trading |
| #62 | 5. **buy_token** - ⚠️ Buy tokens with SOL (REAL TRADE) |
| #63 | 6. **sell_token** - ⚠️ Sell tokens for SOL (REAL TRADE) |
| #64 | 7. **get_portfolio** - Complete portfolio with USD values |
| #65 | 8. **get_trending_tokens** - Discover trending tokens |
| #66 | 9. **search_token** - Search tokens by name/symbol |
| #67 | |
| #68 | ### Token Launch Tools (3 tools): |
| #69 | 10. **launch_token** - ⚠️ Launch a new token on Solana via Bags/Meteora |
| #70 | 11. **get_claimable_fees** - Check claimable fee positions |
| #71 | 12. **claim_fees** - ⚠️ Claim accumulated trading fees |
| #72 | |
| #73 | ### Memory Tools (3 tools): |
| #74 | 13. **record_trading_note** - Save trading research, decisions, and observations |
| #75 | 14. **recall_trading_notes** - Recall saved trading information |
| #76 | 15. **clear_trading_notes** - Clear saved notes |
| #77 | |
| #78 | ### CDP Tools (Coinbase Developer Platform) - MAINNET (5 tools): |
| #79 | 16. **cdp_create_account** - Create a new CDP-managed Solana account (mainnet) |
| #80 | 17. **cdp_request_faucet** - Request SOL from faucet (DEVNET ONLY - errors on mainnet) |
| #81 | 18. **cdp_get_balance** - Get SOL balance for an address |
| #82 | 19. **cdp_send_sol** - ⚠️ Send real SOL from CDP account (REAL TRANSACTION) |
| #83 | 20. **cdp_list_accounts** - List all CDP-managed accounts |
| #84 | |
| #85 | ### Hyperliquid DEX Trading Tools (9 tools): |
| #86 | 21. **hyperliquid_get_account** - Get account info, balance, and positions |
| #87 | 22. **hyperliquid_get_price** - Get current prices on Hyperliquid |
| #88 | 23. **hyperliquid_open_long** - ⚠️ Open a LONG perpetual position |
| #89 | 24. **hyperliquid_open_short** - ⚠️ Open a SHORT perpetual position |
| #90 | 25. **hyperliquid_close_position** - ⚠️ Close an open position |
| #91 | 26. **hyperliquid_get_positions** - View all open positions with PnL |
| #92 | 27. **hyperliquid_set_leverage** - Set leverage (cross or isolated margin) |
| #93 | 28. **hyperliquid_get_available_coins** - List all tradeable perpetual markets |
| #94 | 29. **hyperliquid_transfer** - ⚠️ Transfer USDC between perp and spot |
| #95 | |
| #96 | ## Trading Guidelines |
| #97 | |
| #98 | ### Before Trading: |
| #99 | - Always check wallet balance first |
| #100 | - Get a swap quote to show expected output |
| #101 | - Warn about high price impact (>1%) |
| #102 | - Ask for confirmation before executing trades |
| #103 | |
| #104 | ### Token Addresses: |
| #105 | - SOL (wrapped): So11111111111111111111111111111111111111112 |
| #106 | - When users mention symbols, search first to get mint address |
| #107 | |
| #108 | ### For CDP Managed Accounts (MAINNET): |
| #109 | - CDP accounts operate on Solana **mainnet** - uses real SOL |
| #110 | - Faucet does NOT work on mainnet - fund accounts manually |
| #111 | - Always verify addresses before sending transactions |
| #112 | - Transactions are irreversible |
| #113 | |
| #114 | ### For Hyperliquid DEX Trading: |
| #115 | - Trade BTC, ETH, SOL, and many other perpetual contracts |
| #116 | - Uses USDC as collateral |
| #117 | - Check account with **hyperliquid_get_account** first |
| #118 | - Set leverage with **hyperliquid_set_leverage** before trading |
| #119 | - Use cross margin (is_cross=True) for better liquidation protection |
| #120 | - Common coins: BTC, ETH, SOL, DOGE, WIF, PEPE, ARB, OP, SUI |
| #121 | |
| #122 | ### Best Practices: |
| #123 | - Be concise but informative |
| #124 | - Format numbers clearly (commas, 4 decimal places) |
| #125 | - Show transaction signatures after trades |
| #126 | - Record important decisions and research using notes |
| #127 | - Warn about low liquidity or new tokens |
| #128 | |
| #129 | ### Safety: |
| #130 | - Never expose private keys |
| #131 | - Verify token addresses |
| #132 | - Check security info when available |
| #133 | - Warn about rug pull risks |
| #134 | |
| #135 | ## Session Memory |
| #136 | |
| #137 | Use the note tools to remember: |
| #138 | - Token research (category: research) |
| #139 | - Trade results (category: trade) |
| #140 | - Market observations (category: market) |
| #141 | - User preferences (category: preference) |
| #142 | - Watchlist items (category: watchlist) |
| #143 | |
| #144 | This helps maintain context across conversations. |
| #145 | |
| #146 | Ready to trade! What would you like to do? |
| #147 | """ |
| #148 | |
| #149 | |
| #150 | class SolanaAgent: |
| #151 | """Full-featured Solana Trading Agent with all capabilities.""" |
| #152 | |
| #153 | def __init__( |
| #154 | self, |
| #155 | config: SolanaAgentConfig, |
| #156 | system_prompt: str = None, |
| #157 | ): |
| #158 | """ |
| #159 | Initialize the Solana Trading Agent. |
| #160 | |
| #161 | Args: |
| #162 | config: Agent configuration |
| #163 | system_prompt: Custom system prompt (uses default if not provided) |
| #164 | """ |
| #165 | self.config = config |
| #166 | self.system_prompt = system_prompt or SYSTEM_PROMPT |
| #167 | |
| #168 | # Clients |
| #169 | self.bags_client: Optional[BagsClient] = None |
| #170 | self.helius_client: Optional[HeliusClient] = None |
| #171 | self.birdeye_client: Optional[BirdeyeClient] = None |
| #172 | self.llm_client: Optional[LLMClient] = None |
| #173 | |
| #174 | # Tools and messages |
| #175 | self.tools: list[Tool] = [] |
| #176 | self.messages: list[Message] = [] |
| #177 | |
| #178 | # Agent settings |
| #179 | self.max_steps = config.agent.max_steps |
| #180 | self.workspace_dir = Path(config.agent.workspace_dir) |
| #181 | self.workspace_dir.mkdir(parents=True, exist_ok=True) |
| #182 | |
| #183 | # Stats |
| #184 | self.total_steps = 0 |
| #185 | self.total_tool_calls = 0 |
| #186 | self.api_total_tokens = 0 |
| #187 | |
| #188 | async def initialize(self): |
| #189 | """Initialize all clients and tools.""" |
| #190 | print(f"{Colors.CYAN}🚀 Initializing MAWD Solana Trading Agent...{Colors.RESET}") |
| #191 | |
| #192 | # Initialize Bags client |
| #193 | print(f"{Colors.DIM} → Connecting to Bags API...{Colors.RESET}") |
| #194 | self.bags_client = BagsClient( |
| #195 | api_key=self.config.solana.bags_api_key, |
| #196 | config_key=self.config.solana.bags_config_key, |
| #197 | rpc_url=self.config.solana.helius_rpc_url, |
| #198 | private_key=self.config.wallet.private_key, |
| #199 | ) |
| #200 | |
| #201 | # Initialize Helius client |
| #202 | print(f"{Colors.DIM} → Connecting to Helius RPC...{Colors.RESET}") |
| #203 | self.helius_client = HeliusClient( |
| #204 | api_key=self.config.solana.helius_api_key, |
| #205 | rpc_url=self.config.solana.helius_rpc_url, |
| #206 | wss_url=self.config.solana.helius_wss_url, |
| #207 | ) |
| #208 | |
| #209 | # Initialize Birdeye client |
| #210 | print(f"{Colors.DIM} → Connecting to Birdeye API...{Colors.RESET}") |
| #211 | self.birdeye_client = BirdeyeClient( |
| #212 | api_key=self.config.solana.birdeye_api_key, |
| #213 | ) |
| #214 | |
| #215 | # Initialize CDP client if available |
| #216 | cdp_client = None |
| #217 | if CDP_AVAILABLE and hasattr(self.config, 'cdp') and self.config.cdp.api_key_id: |
| #218 | network_name = "mainnet" if self.config.cdp.network == "solana-mainnet" else "devnet" |
| #219 | try: |
| #220 | print(f"{Colors.DIM} → Connecting to Coinbase CDP ({network_name})...{Colors.RESET}") |
| #221 | cdp_client = create_cdp_client( |
| #222 | api_key_id=self.config.cdp.api_key_id, |
| #223 | api_key_secret=self.config.cdp.api_key_secret, |
| #224 | wallet_secret=self.config.cdp.wallet_secret, |
| #225 | rpc_url=self.config.cdp.rpc_url, |
| #226 | network=self.config.cdp.network, |
| #227 | ) |
| #228 | if cdp_client: |
| #229 | print(f"{Colors.GREEN} ✓ CDP connected ({network_name}){Colors.RESET}") |
| #230 | else: |
| #231 | print(f"{Colors.YELLOW} ⚠️ CDP SDK not available{Colors.RESET}") |
| #232 | except Exception as e: |
| #233 | print(f"{Colors.YELLOW} ⚠️ CDP unavailable: {e}{Colors.RESET}") |
| #234 | cdp_client = None |
| #235 | |
| #236 | # Initialize Hyperliquid client if available |
| #237 | hyperliquid_client = None |
| #238 | if HYPERLIQUID_AVAILABLE and hasattr(self.config, 'hyperliquid') and self.config.hyperliquid.wallet: |
| #239 | try: |
| #240 | print(f"{Colors.DIM} → Connecting to Hyperliquid DEX...{Colors.RESET}") |
| #241 | hyperliquid_client = HyperliquidClient( |
| #242 | wallet_address=self.config.hyperliquid.wallet, |
| #243 | private_key=self.config.hyperliquid.private_key, |
| #244 | use_testnet=self.config.hyperliquid.use_testnet, |
| #245 | ) |
| #246 | print(f"{Colors.GREEN} ✓ Hyperliquid connected (perpetuals trading){Colors.RESET}") |
| #247 | except Exception as e: |
| #248 | print(f"{Colors.YELLOW} ⚠️ Hyperliquid unavailable: {e}{Colors.RESET}") |
| #249 | hyperliquid_client = None |
| #250 | |
| #251 | # Set clients for trading tools |
| #252 | set_solana_clients( |
| #253 | bags_client=self.bags_client, |
| #254 | helius_client=self.helius_client, |
| #255 | birdeye_client=self.birdeye_client, |
| #256 | cdp_client=cdp_client, |
| #257 | hyperliquid_client=hyperliquid_client, |
| #258 | ) |
| #259 | |
| #260 | # Create tools |
| #261 | self.tools = [] |
| #262 | |
| #263 | # Add trading tools |
| #264 | if self.config.tools.enable_trading: |
| #265 | trading_tools = create_all_trading_tools() |
| #266 | self.tools.extend(trading_tools) |
| #267 | print(f"{Colors.DIM} → Loaded {len(trading_tools)} trading tools{Colors.RESET}") |
| #268 | |
| #269 | # Add note tools |
| #270 | if self.config.tools.enable_note: |
| #271 | memory_file = str(self.workspace_dir / self.config.tools.memory_file) |
| #272 | note_tools = [ |
| #273 | TradingNoteTool(memory_file=memory_file), |
| #274 | RecallTradingNoteTool(memory_file=memory_file), |
| #275 | ClearTradingNotesTool(memory_file=memory_file), |
| #276 | ] |
| #277 | self.tools.extend(note_tools) |
| #278 | print(f"{Colors.DIM} → Loaded {len(note_tools)} note tools{Colors.RESET}") |
| #279 | |
| #280 | # Initialize LLM client |
| #281 | print(f"{Colors.DIM} → Connecting to LLM ({self.config.llm.model})...{Colors.RESET}") |
| #282 | self.llm_client = LLMClient( |
| #283 | api_key=self.config.llm.api_key, |
| #284 | api_base=self.config.llm.api_base, |
| #285 | model=self.config.llm.model, |
| #286 | max_tokens=self.config.llm.max_tokens, |
| #287 | ) |
| #288 | |
| #289 | # Initialize message history |
| #290 | workspace_info = f"\n\n## Workspace\nWorking directory: `{self.workspace_dir.absolute()}`" |
| #291 | wallet_info = f"\n\n## Wallet\nAddress: `{self.config.wallet.address}`" |
| #292 | full_prompt = self.system_prompt + workspace_info + wallet_info |
| #293 | |
| #294 | self.messages = [Message(role="system", content=full_prompt)] |
| #295 | |
| #296 | # Check wallet |
| #297 | if self.bags_client.wallet_pubkey: |
| #298 | print(f"{Colors.GREEN} ✓ Wallet: {self.bags_client.wallet_pubkey}{Colors.RESET}") |
| #299 | try: |
| #300 | balance = await self.helius_client.get_sol_balance(self.bags_client.wallet_pubkey) |
| #301 | print(f"{Colors.GREEN} ✓ Balance: {balance:.4f} SOL{Colors.RESET}") |
| #302 | except Exception as e: |
| #303 | print(f"{Colors.YELLOW} ⚠️ Could not fetch balance: {e}{Colors.RESET}") |
| #304 | |
| #305 | print(f"{Colors.BRIGHT_GREEN}✓ Agent initialized with {len(self.tools)} tools!{Colors.RESET}\n") |
| #306 | |
| #307 | def add_user_message(self, content: str): |
| #308 | """Add a user message to history.""" |
| #309 | self.messages.append(Message(role="user", content=content)) |
| #310 | |
| #311 | async def run(self, cancel_event: Optional[asyncio.Event] = None) -> str: |
| #312 | """ |
| #313 | Execute agent loop until task is complete or max steps reached. |
| #314 | |
| #315 | Args: |
| #316 | cancel_event: Optional event to cancel execution |
| #317 | |
| #318 | Returns: |
| #319 | Final response content |
| #320 | """ |
| #321 | step = 0 |
| #322 | run_start_time = perf_counter() |
| #323 | |
| #324 | while step < self.max_steps: |
| #325 | if cancel_event and cancel_event.is_set(): |
| #326 | return "Task cancelled." |
| #327 | |
| #328 | step += 1 |
| #329 | self.total_steps += 1 |
| #330 | step_start_time = perf_counter() |
| #331 | |
| #332 | # Step header |
| #333 | print(f"\n{Colors.DIM}╭{'─' * 58}╮{Colors.RESET}") |
| #334 | print(f"{Colors.DIM}│{Colors.RESET} {Colors.BOLD}{Colors.BRIGHT_CYAN}💭 Step {step}/{self.max_steps}{Colors.RESET}{' ' * 40}{Colors.DIM}│{Colors.RESET}") |
| #335 | print(f"{Colors.DIM}╰{'─' * 58}╯{Colors.RESET}") |
| #336 | |
| #337 | # Get LLM response |
| #338 | try: |
| #339 | response = await self.llm_client.generate(self.messages, self.tools) |
| #340 | except Exception as e: |
| #341 | print(f"{Colors.BRIGHT_RED}❌ LLM Error: {e}{Colors.RESET}") |
| #342 | return f"Error: {e}" |
| #343 | |
| #344 | # Track tokens |
| #345 | if response.usage: |
| #346 | self.api_total_tokens = response.usage.get("total_tokens", 0) |
| #347 | |
| #348 | # Add assistant message |
| #349 | self.messages.append(Message( |
| #350 | role="assistant", |
| #351 | content=response.content, |
| #352 | thinking=response.thinking, |
| #353 | tool_calls=response.tool_calls, |
| #354 | )) |
| #355 | |
| #356 | # Print thinking |
| #357 | if response.thinking: |
| #358 | print(f"\n{Colors.BOLD}{Colors.MAGENTA}🧠 Thinking:{Colors.RESET}") |
| #359 | print(f"{Colors.DIM}{response.thinking[:500]}...{Colors.RESET}" if len(response.thinking) > 500 else f"{Colors.DIM}{response.thinking}{Colors.RESET}") |
| #360 | |
| #361 | # Print response |
| #362 | if response.content: |
| #363 | print(f"\n{Colors.BOLD}{Colors.BRIGHT_BLUE}🤖 Assistant:{Colors.RESET}") |
| #364 | print(response.content) |
| #365 | |
| #366 | # If no tool calls, we're done |
| #367 | if not response.tool_calls: |
| #368 | elapsed = perf_counter() - step_start_time |
| #369 | print(f"\n{Colors.DIM}⏱️ Completed in {elapsed:.2f}s{Colors.RESET}") |
| #370 | return response.content |
| #371 | |
| #372 | # Execute tool calls |
| #373 | for tool_call in response.tool_calls: |
| #374 | self.total_tool_calls += 1 |
| #375 | |
| #376 | print(f"\n{Colors.BRIGHT_YELLOW}🔧 Tool Call:{Colors.RESET} {Colors.BOLD}{Colors.CYAN}{tool_call.name}{Colors.RESET}") |
| #377 | |
| #378 | # Truncate arguments for display |
| #379 | args_str = json.dumps(tool_call.arguments, indent=2) |
| #380 | if len(args_str) > 200: |
| #381 | args_str = args_str[:200] + "..." |
| #382 | print(f"{Colors.DIM} Args: {args_str}{Colors.RESET}") |
| #383 | |
| #384 | # Find and execute tool |
| #385 | tool = None |
| #386 | for t in self.tools: |
| #387 | if t.name == tool_call.name: |
| #388 | tool = t |
| #389 | break |
| #390 | |
| #391 | if tool is None: |
| #392 | result = ToolResult(success=False, error=f"Unknown tool: {tool_call.name}") |
| #393 | else: |
| #394 | try: |
| #395 | result = await tool.execute(**tool_call.arguments) |
| #396 | except Exception as e: |
| #397 | result = ToolResult(success=False, error=str(e)) |
| #398 | |
| #399 | # Print result |
| #400 | if result.success: |
| #401 | preview = result.content[:300] + "..." if len(result.content) > 300 else result.content |
| #402 | print(f"{Colors.BRIGHT_GREEN}✓ Result:{Colors.RESET} {preview}") |
| #403 | else: |
| #404 | print(f"{Colors.BRIGHT_RED}✗ Error:{Colors.RESET} {result.error}") |
| #405 | |
| #406 | # Add tool result message |
| #407 | self.messages.append(Message( |
| #408 | role="tool", |
| #409 | content=result.content if result.success else f"Error: {result.error}", |
| #410 | tool_call_id=tool_call.id, |
| #411 | name=tool_call.name, |
| #412 | )) |
| #413 | |
| #414 | elapsed = perf_counter() - step_start_time |
| #415 | total_elapsed = perf_counter() - run_start_time |
| #416 | print(f"\n{Colors.DIM}⏱️ Step {step} in {elapsed:.2f}s (total: {total_elapsed:.2f}s){Colors.RESET}") |
| #417 | |
| #418 | return f"Max steps ({self.max_steps}) reached." |
| #419 | |
| #420 | async def process_message(self, user_message: str) -> str: |
| #421 | """Process a user message and return the response.""" |
| #422 | self.add_user_message(user_message) |
| #423 | return await self.run() |
| #424 | |
| #425 | async def run_interactive(self): |
| #426 | """Run the agent in interactive mode.""" |
| #427 | |
| #428 | print(f"\n{Colors.BOLD}{Colors.CYAN}{'═' * 60}{Colors.RESET}") |
| #429 | print(f"{Colors.BOLD}{Colors.CYAN} 🌊 MAWD - Solana Trading Agent{Colors.RESET}") |
| #430 | print(f"{Colors.BOLD}{Colors.CYAN}{'═' * 60}{Colors.RESET}") |
| #431 | print(f"{Colors.DIM}Type your message and press Enter. Type 'quit' to exit.{Colors.RESET}") |
| #432 | print(f"{Colors.DIM}Commands: /balance, /portfolio, /trending, /notes, /help, /stats{Colors.RESET}\n") |
| #433 | |
| #434 | while True: |
| #435 | try: |
| #436 | user_input = input(f"{Colors.BOLD}{Colors.GREEN}You:{Colors.RESET} ").strip() |
| #437 | |
| #438 | if not user_input: |
| #439 | continue |
| #440 | |
| #441 | if user_input.lower() in ["quit", "exit", "q", "/quit", "/exit", "/q"]: |
| #442 | self._print_stats() |
| #443 | print(f"\n{Colors.CYAN}👋 Goodbye!{Colors.RESET}") |
| #444 | break |
| #445 | |
| #446 | # Handle special commands |
| #447 | if user_input.startswith("/"): |
| #448 | cmd = user_input.lower() |
| #449 | if cmd == "/balance": |
| #450 | user_input = "Check my wallet balance" |
| #451 | elif cmd == "/portfolio": |
| #452 | user_input = "Show my complete portfolio with USD values" |
| #453 | elif cmd == "/trending": |
| #454 | user_input = "What are the top 10 trending tokens right now?" |
| #455 | elif cmd == "/notes": |
| #456 | user_input = "Recall all my trading notes" |
| #457 | elif cmd == "/stats": |
| #458 | self._print_stats() |
| #459 | continue |
| #460 | elif cmd == "/clear": |
| #461 | self.messages = [self.messages[0]] # Keep system prompt |
| #462 | print(f"{Colors.CYAN}✓ Conversation cleared{Colors.RESET}") |
| #463 | continue |
| #464 | elif cmd == "/help": |
| #465 | self._print_help() |
| #466 | continue |
| #467 | |
| #468 | # Process the message |
| #469 | await self.process_message(user_input) |
| #470 | |
| #471 | except KeyboardInterrupt: |
| #472 | self._print_stats() |
| #473 | print(f"\n\n{Colors.CYAN}👋 Goodbye!{Colors.RESET}") |
| #474 | break |
| #475 | except Exception as e: |
| #476 | print(f"{Colors.BRIGHT_RED}Error: {e}{Colors.RESET}") |
| #477 | |
| #478 | def _print_help(self): |
| #479 | """Print help message.""" |
| #480 | print(f""" |
| #481 | {Colors.CYAN}Available Commands:{Colors.RESET} |
| #482 | /balance - Check wallet balance |
| #483 | /portfolio - Show complete portfolio |
| #484 | /trending - Show trending tokens |
| #485 | /notes - Show saved trading notes |
| #486 | /stats - Show session statistics |
| #487 | /clear - Clear conversation history |
| #488 | /help - Show this help |
| #489 | /quit - Exit the agent |
| #490 | |
| #491 | {Colors.CYAN}Example Queries:{Colors.RESET} |
| #492 | "What's the price of BONK?" |
| #493 | "Search for tokens named PEPE" |
| #494 | "Get info on token <mint_address>" |
| #495 | "Buy 0.1 SOL worth of <token_mint>" |
| #496 | "Remember that I'm interested in meme coins" |
| #497 | "What tokens have I researched?" |
| #498 | """) |
| #499 | |
| #500 | def _print_stats(self): |
| #501 | """Print session statistics.""" |
| #502 | print(f""" |
| #503 | {Colors.CYAN}📊 Session Statistics:{Colors.RESET} |
| #504 | Steps: {self.total_steps} |
| #505 | Tool calls: {self.total_tool_calls} |
| #506 | Messages: {len(self.messages)} |
| #507 | Tokens: ~{self.api_total_tokens} |
| #508 | """) |
| #509 | |
| #510 | async def close(self): |
| #511 | """Clean up resources.""" |
| #512 | if self.bags_client: |
| #513 | await self.bags_client.close() |
| #514 | if self.helius_client: |
| #515 | await self.helius_client.close() |
| #516 | if self.birdeye_client: |
| #517 | await self.birdeye_client.close() |
| #518 | if self.llm_client: |
| #519 | await self.llm_client.close() |
| #520 | |
| #521 | |
| #522 | async def main(): |
| #523 | """Main entry point.""" |
| #524 | |
| #525 | # Load configuration |
| #526 | try: |
| #527 | env_path = Path(__file__).parent.parent.parent / ".env.local" |
| #528 | if not env_path.exists(): |
| #529 | env_path = None |
| #530 | |
| #531 | config = load_config(str(env_path) if env_path else None) |
| #532 | except ValueError as e: |
| #533 | print(f"{Colors.BRIGHT_RED}Configuration Error: {e}{Colors.RESET}") |
| #534 | print(f"{Colors.DIM}Please ensure your .env.local file contains all required variables.{Colors.RESET}") |
| #535 | sys.exit(1) |
| #536 | |
| #537 | # Create and run agent |
| #538 | agent = SolanaAgent(config) |
| #539 | |
| #540 | try: |
| #541 | await agent.initialize() |
| #542 | await agent.run_interactive() |
| #543 | finally: |
| #544 | await agent.close() |
| #545 | |
| #546 | |
| #547 | if __name__ == "__main__": |
| #548 | asyncio.run(main()) |
| #549 |