Total Jobs
-0
-Active Jobs
-0
-Online Miners
-0
-Avg Duration
-0ms
-diff --git a/apps/shared-core/src/app/core/config/__init__.py b/apps/shared-core/src/app/core/config/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cli/aitbc_cli.legacy.py b/cli/aitbc_cli.legacy.py new file mode 100755 index 00000000..b77a514e --- /dev/null +++ b/cli/aitbc_cli.legacy.py @@ -0,0 +1,3256 @@ +#!/usr/bin/env python3 +""" +AITBC CLI - Comprehensive Blockchain Management Tool +""" +import sys +from pathlib import Path + +# Add /opt/aitbc to Python path for shared modules +sys.path.insert(0, str(Path("/opt/aitbc"))) + +""" +Complete command-line interface for AITBC blockchain operations including: +- Wallet management +- Transaction processing +- Blockchain analytics +- Marketplace operations +- AI compute jobs +- Mining operations +- Network monitoring +""" + +import json +import sys +import os +import time +import datetime +import argparse +import random +import hashlib +import httpx +import subprocess +from pathlib import Path +from cryptography.hazmat.primitives.asymmetric import ed25519 +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.ciphers.aead import AESGCM +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.backends import default_backend +import requests +from typing import Optional, Dict, Any, List + +# Import shared modules +from aitbc.aitbc_logging import get_logger +from aitbc import __version__ +from aitbc.constants import BLOCKCHAIN_RPC_PORT, DATA_DIR, KEYSTORE_DIR +from aitbc.exceptions import ConfigurationError, NetworkError, ValidationError +from aitbc.network.http_client import AITBCHTTPClient +from aitbc.utils.paths import get_blockchain_data_path, get_data_path +from aitbc.utils.paths import ensure_dir, get_keystore_path +from aitbc.utils.validation import validate_address, validate_url + +# Initialize logger +logger = get_logger(__name__) + +# Default paths +CLI_VERSION = __version__ +DEFAULT_KEYSTORE_DIR = KEYSTORE_DIR +DEFAULT_RPC_URL = f"http://localhost:{BLOCKCHAIN_RPC_PORT}" +DEFAULT_WALLET_DAEMON_URL = "http://localhost:8003" + +def decrypt_private_key(keystore_path: Path, password: str) -> str: + """Decrypt private key from keystore file. + + Supports both keystore formats: + - AES-256-GCM (blockchain-node standard) + - Fernet (scripts/utils standard) + """ + with open(keystore_path) as f: + ks = json.load(f) + + crypto = ks.get('crypto', ks) # Handle both nested and flat crypto structures + + # Detect encryption method + cipher = crypto.get('cipher', crypto.get('algorithm', '')) + + if cipher == 'aes-256-gcm' or cipher == 'aes-256-gcm': + # AES-256-GCM (blockchain-node standard) + salt = bytes.fromhex(crypto['kdfparams']['salt']) + kdf = PBKDF2HMAC( + algorithm=hashes.SHA256(), + length=32, + salt=salt, + iterations=crypto['kdfparams']['c'], + backend=default_backend() + ) + key = kdf.derive(password.encode()) + aesgcm = AESGCM(key) + nonce = bytes.fromhex(crypto['cipherparams']['nonce']) + priv = aesgcm.decrypt(nonce, bytes.fromhex(crypto['ciphertext']), None) + return priv.hex() + + elif cipher == 'fernet' or cipher == 'PBKDF2-SHA256-Fernet': + # Fernet (scripts/utils standard) + from cryptography.fernet import Fernet + import base64 + + # Derive Fernet key using the same method as scripts/utils/keystore.py + kdfparams = crypto.get('kdfparams', {}) + if 'salt' in kdfparams: + salt = base64.b64decode(kdfparams['salt']) + else: + # Fallback for older format + salt = bytes.fromhex(kdfparams.get('salt', '')) + + # Use PBKDF2 for secure key derivation (100,000 iterations for security) + dk = hashlib.pbkdf2_hmac('sha256', password.encode(), salt, 100000, dklen=32) + fernet_key = base64.urlsafe_b64encode(dk) + + f = Fernet(fernet_key) + ciphertext = base64.b64decode(crypto['ciphertext']) + priv = f.decrypt(ciphertext) + return priv.decode() + + else: + raise ValueError(f"Unsupported cipher: {cipher}") + + +def create_wallet(name: str, password: str, keystore_dir: Path = DEFAULT_KEYSTORE_DIR) -> str: + """Create a new wallet using blockchain-node standard AES-256-GCM encryption""" + keystore_dir.mkdir(parents=True, exist_ok=True) + + # Generate new key pair + private_key = ed25519.Ed25519PrivateKey.generate() + private_key_hex = private_key.private_bytes_raw().hex() + public_key = private_key.public_key() + public_key_hex = public_key.public_bytes_raw().hex() + + # Calculate address (simplified - in real implementation this would be more complex) + address = f"ait1{public_key_hex[:40]}" + + # Encrypt private key using blockchain-node standard (AES-256-GCM with PBKDF2) + salt = os.urandom(32) + kdf = PBKDF2HMAC( + algorithm=hashes.SHA256(), + length=32, + salt=salt, + iterations=100_000, + backend=default_backend() + ) + key = kdf.derive(password.encode()) + aesgcm = AESGCM(key) + nonce = os.urandom(12) + ciphertext = aesgcm.encrypt(nonce, bytes.fromhex(private_key_hex), None) + + # Create keystore file matching blockchain-node format + keystore_data = { + "address": address, + "public_key": public_key_hex, + "crypto": { + "kdf": "pbkdf2", + "kdfparams": { + "salt": salt.hex(), + "c": 100_000, + "dklen": 32, + "prf": "hmac-sha256" + }, + "cipher": "aes-256-gcm", + "cipherparams": { + "nonce": nonce.hex() + }, + "ciphertext": ciphertext.hex() + }, + "keytype": "ed25519", + "version": 1 + } + + keystore_path = keystore_dir / f"{name}.json" + with open(keystore_path, 'w') as f: + json.dump(keystore_data, f, indent=2) + + print(f"Wallet created: {name}") + print(f"Address: {address}") + print(f"Keystore: {keystore_path}") + + return address + + +def send_transaction(from_wallet: str, to_address: str, amount: float, fee: float, + password: str, keystore_dir: Path = DEFAULT_KEYSTORE_DIR, + rpc_url: str = DEFAULT_RPC_URL) -> Optional[str]: + """Send transaction from one wallet to another""" + + # Validate recipient address + try: + validate_address(to_address) + except ValidationError as e: + logger.error(f"Invalid recipient address: {e}") + print(f"Error: Invalid recipient address: {e}") + return None + + # Validate amount + if amount <= 0: + logger.error(f"Invalid amount: {amount} must be positive") + print("Error: Amount must be positive") + return None + + # Ensure keystore_dir is a Path object + if keystore_dir is None: + keystore_dir = DEFAULT_KEYSTORE_DIR + if isinstance(keystore_dir, str): + keystore_dir = Path(keystore_dir) + + # Get sender wallet info + sender_keystore = keystore_dir / f"{from_wallet}.json" + if not sender_keystore.exists(): + print(f"Error: Wallet '{from_wallet}' not found") + return None + + with open(sender_keystore) as f: + sender_data = json.load(f) + + sender_address = sender_data['address'] + + # Decrypt private key + try: + private_key_hex = decrypt_private_key(sender_keystore, password) + private_key = ed25519.Ed25519PrivateKey.from_private_bytes(bytes.fromhex(private_key_hex)) + except Exception as e: + print(f"Error decrypting wallet: {e}") + return None + + # Get chain_id from RPC health endpoint or use override + from aitbc_cli.utils.chain_id import get_chain_id, get_default_chain_id + chain_id = get_chain_id(rpc_url, override=None, timeout=5) + + # Get actual nonce from blockchain + actual_nonce = 0 + try: + http_client = AITBCHTTPClient(base_url=rpc_url, timeout=5) + account_data = http_client.get(f"/rpc/account/{sender_address}") + actual_nonce = account_data.get("nonce", 0) + except NetworkError: + actual_nonce = 0 + except Exception: + actual_nonce = 0 + + # Create transaction + transaction = { + "type": "TRANSFER", + "chain_id": chain_id, + "from": sender_address, + "nonce": actual_nonce, + "fee": int(fee), + "payload": { + "recipient": to_address, + "amount": int(amount) + } + } + + # Sign transaction + message = json.dumps(transaction, sort_keys=True).encode() + signature = private_key.sign(message) + transaction["signature"] = signature.hex() + + # Submit to blockchain + try: + http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) + result = http_client.post("/rpc/transaction", json=transaction) + tx_hash = result.get("transaction_hash") + print(f"Transaction submitted: {tx_hash}") + logger.info(f"Transaction submitted: {tx_hash} from {from_wallet} to {to_address}") + return tx_hash + except NetworkError as e: + logger.error(f"Network error submitting transaction: {e}") + print(f"Error submitting transaction: {e}") + return None + except Exception as e: + logger.error(f"Error submitting transaction: {e}") + print(f"Error: {e}") + return None + + +def import_wallet(wallet_name: str, private_key_hex: str, password: str, + keystore_dir: Path = KEYSTORE_DIR) -> Optional[str]: + """Import wallet from private key""" + try: + ensure_dir(keystore_dir) + + # Validate and convert private key + try: + private_key_bytes = bytes.fromhex(private_key_hex) + private_key = ed25519.Ed25519PrivateKey.from_private_bytes(private_key_bytes) + except Exception as e: + print(f"Error: Invalid private key: {e}") + return None + + # Generate public key and address + public_key = private_key.public_key() + public_key_hex = public_key.public_bytes_raw().hex() + address = f"ait1{public_key_hex[:40]}" + + # Encrypt private key + salt = os.urandom(32) + kdf = PBKDF2HMAC(hashes.SHA256(), 32, salt, 100000) + key = kdf.derive(password.encode()) + aesgcm = AESGCM(key) + nonce = os.urandom(12) + ciphertext = aesgcm.encrypt(nonce, private_key_bytes, None) + + # Create keystore file + keystore_data = { + "address": address, + "public_key": public_key_hex, + "crypto": { + "kdf": "pbkdf2", + "kdfparams": { + "salt": salt.hex(), + "c": 100000, + "dklen": 32, + "prf": "hmac-sha256" + }, + "cipher": "aes-256-gcm", + "cipherparams": { + "nonce": nonce.hex() + }, + "ciphertext": ciphertext.hex() + }, + "version": 1 + } + + keystore_path = keystore_dir / f"{wallet_name}.json" + with open(keystore_path, 'w') as f: + json.dump(keystore_data, f, indent=2) + + print(f"Wallet imported: {wallet_name}") + print(f"Address: {address}") + logger.info(f"Imported wallet: {wallet_name} with address {address}") + print(f"Keystore: {keystore_path}") + + return address + except Exception as e: + print(f"Error importing wallet: {e}") + return None + + +def export_wallet(wallet_name: str, password: str, keystore_dir: Path = DEFAULT_KEYSTORE_DIR) -> Optional[str]: + """Export private key from wallet""" + try: + keystore_path = keystore_dir / f"{wallet_name}.json" + if not keystore_path.exists(): + print(f"Error: Wallet '{wallet_name}' not found") + return None + + return decrypt_private_key(keystore_path, password) + except Exception as e: + print(f"Error exporting wallet: {e}") + return None + + +def delete_wallet(wallet_name: str, keystore_dir: Path = DEFAULT_KEYSTORE_DIR) -> bool: + """Delete wallet""" + try: + keystore_path = keystore_dir / f"{wallet_name}.json" + if keystore_path.exists(): + keystore_path.unlink() + print(f"Wallet '{wallet_name}' deleted successfully") + return True + else: + print(f"Error: Wallet '{wallet_name}' not found") + return False + except Exception as e: + print(f"Error deleting wallet: {e}") + return False + + +def rename_wallet(old_name: str, new_name: str, keystore_dir: Path = DEFAULT_KEYSTORE_DIR) -> bool: + """Rename wallet""" + try: + old_path = keystore_dir / f"{old_name}.json" + new_path = keystore_dir / f"{new_name}.json" + + if not old_path.exists(): + print(f"Error: Wallet '{old_name}' not found") + return False + + if new_path.exists(): + print(f"Error: Wallet '{new_name}' already exists") + return False + + old_path.rename(new_path) + print(f"Wallet renamed from '{old_name}' to '{new_name}'") + return True + except Exception as e: + print(f"Error renaming wallet: {e}") + return False + + +def list_wallets(keystore_dir: Path = KEYSTORE_DIR, + use_daemon: bool = True, + daemon_url: str = DEFAULT_WALLET_DAEMON_URL) -> list: + """List all wallets""" + wallets = [] + + # Try to use wallet daemon first + if use_daemon: + try: + http_client = AITBCHTTPClient(base_url=daemon_url, timeout=5) + data = http_client.get("/v1/wallets") + wallet_list = data.get("items", data.get("wallets", [])) + for wallet_data in wallet_list: + wallets.append({ + "name": wallet_data.get("wallet_name", ""), + "address": wallet_data.get("address", ""), + "public_key": wallet_data.get("public_key", ""), + "source": "daemon" + }) + logger.info(f"Listed {len(wallets)} wallets from daemon") + return wallets + except NetworkError as e: + logger.warning(f"Failed to query wallet daemon: {e}, falling back to file-based listing") + print(f"Warning: Failed to query wallet daemon: {e}") + print("Falling back to file-based wallet listing...") + except Exception as e: + logger.warning(f"Failed to query wallet daemon: {e}, falling back to file-based listing") + print(f"Warning: Failed to query wallet daemon: {e}") + print("Falling back to file-based wallet listing...") + + # Fallback to file-based wallet listing + if keystore_dir.exists(): + for wallet_file in keystore_dir.glob("*.json"): + try: + with open(wallet_file) as f: + data = json.load(f) + wallets.append({ + "name": wallet_file.stem, + "address": data["address"], + "file": str(wallet_file), + "source": "file" + }) + except Exception: + pass + logger.info(f"Listed {len(wallets)} wallets from file-based fallback") + return wallets + + +def send_batch_transactions(transactions: List[Dict[str, Any]], password: str, + rpc_url: str = DEFAULT_RPC_URL) -> List[Optional[str]]: + """Send multiple transactions in batch""" + results = [] + + for tx in transactions: + try: + tx_hash = send_transaction( + tx['from_wallet'], + tx['to_address'], + tx['amount'], + tx.get('fee', 10.0), + password, + rpc_url + ) + results.append({ + 'transaction': tx, + 'hash': tx_hash, + 'success': tx_hash is not None + }) + + if tx_hash: + print(f"✅ Transaction sent: {tx['from_wallet']} → {tx['to_address']} ({tx['amount']} AIT)") + else: + print(f"❌ Transaction failed: {tx['from_wallet']} → {tx['to_address']}") + + except Exception as e: + results.append({ + 'transaction': tx, + 'hash': None, + 'success': False, + 'error': str(e) + }) + print(f"❌ Transaction error: {e}") + + return results + + +def estimate_transaction_fee(from_wallet: str, to_address: str, amount: float, + keystore_dir: Path = DEFAULT_KEYSTORE_DIR, + rpc_url: str = DEFAULT_RPC_URL) -> Optional[float]: + """Estimate transaction fee""" + try: + # Create a test transaction to estimate fee + test_tx = { + "sender": "", # Will be filled by actual sender + "recipient": to_address, + "value": int(amount), + "fee": 10, # Default fee + "nonce": 0, + "type": "transfer", + "payload": {} + } + + # Get fee estimation from RPC (if available) + try: + http_client = AITBCHTTPClient(base_url=rpc_url, timeout=10) + fee_data = http_client.post("/rpc/estimateFee", json=test_tx) + return fee_data.get("estimated_fee", 10.0) + except NetworkError: + # Fallback to default fee + return 10.0 + except Exception as e: + print(f"Error estimating fee: {e}") + return 10.0 + + +def get_transaction_status(tx_hash: str, rpc_url: str = DEFAULT_RPC_URL) -> Optional[Dict]: + """Get detailed transaction status""" + try: + http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) + return http_client.get(f"/rpc/transaction/{tx_hash}") + except NetworkError as e: + print(f"Error getting transaction status: {e}") + return None + + +def get_pending_transactions(rpc_url: str = DEFAULT_RPC_URL) -> List[Dict]: + """Get pending transactions in mempool""" + try: + http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) + data = http_client.get("/rpc/pending") + return data.get("transactions", []) + except NetworkError as e: + print(f"Error getting pending transactions: {e}") + return [] + + +def start_mining(wallet_name: str, threads: int = 1, keystore_dir: Path = DEFAULT_KEYSTORE_DIR, + rpc_url: str = DEFAULT_RPC_URL) -> bool: + """Start mining with specified wallet""" + try: + # Get wallet address + keystore_path = keystore_dir / f"{wallet_name}.json" + if not keystore_path.exists(): + print(f"Error: Wallet '{wallet_name}' not found") + return False + + with open(keystore_path) as f: + wallet_data = json.load(f) + address = wallet_data['address'] + + # Start mining via RPC + mining_config = { + "miner_address": address, + "threads": threads, + "enabled": True + } + + try: + http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) + result = http_client.post("/rpc/mining/start", json=mining_config) + print(f"Mining started with wallet '{wallet_name}'") + print(f"Miner address: {address}") + print(f"Threads: {threads}") + print(f"Status: {result.get('status', 'started')}") + return result + except NetworkError as e: + print(f"Error starting mining: {e}") + return None + except Exception as e: + print(f"Error: {e}") + return False + except Exception as e: + print(f"Error: {e}") + return False + + +def stop_mining(rpc_url: str = DEFAULT_RPC_URL) -> bool: + """Stop mining""" + try: + http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) + result = http_client.post("/rpc/mining/stop") + print(f"Mining stopped") + print(f"Status: {result.get('status', 'stopped')}") + return True + except NetworkError as e: + print(f"Error stopping mining: {e}") + return False + except Exception as e: + print(f"Error: {e}") + return False + + +def get_mining_status(rpc_url: str = DEFAULT_RPC_URL) -> Optional[Dict]: + """Get mining status and statistics""" + try: + http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) + return http_client.get("/rpc/mining/status") + except NetworkError as e: + print(f"Error getting mining status: {e}") + return None + + +def get_marketplace_listings(rpc_url: str = DEFAULT_RPC_URL) -> List[Dict]: + """Get marketplace listings""" + try: + http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) + data = http_client.get("/rpc/marketplace/listings") + return data.get("listings", []) + except NetworkError as e: + print(f"Error getting marketplace listings: {e}") + return [] + except Exception as e: + print(f"Error: {e}") + return [] + + +def create_marketplace_listing(wallet_name: str, item_type: str, price: float, + description: str, password: str, + keystore_dir: Path = DEFAULT_KEYSTORE_DIR, + rpc_url: str = DEFAULT_RPC_URL) -> Optional[str]: + """Create marketplace listing""" + try: + # Get wallet address + keystore_path = keystore_dir / f"{wallet_name}.json" + if not keystore_path.exists(): + print(f"Error: Wallet '{wallet_name}' not found") + return None + + with open(keystore_path) as f: + wallet_data = json.load(f) + address = wallet_data['address'] + + # Create listing + listing_data = { + "seller_address": address, + "item_type": item_type, + "price": price, + "description": description + } + + try: + http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) + result = http_client.post("/rpc/marketplace/create", json=listing_data) + listing_id = result.get("listing_id") + print(f"Marketplace listing created") + print(f"Listing ID: {listing_id}") + return result + except NetworkError as e: + print(f"Error creating marketplace listing: {e}") + return None + except Exception as e: + print(f"Error: {e}") + return None + + +def submit_ai_job(wallet_name: str, job_type: str, prompt: str, payment: float, + password: str, keystore_dir: Path = DEFAULT_KEYSTORE_DIR, + rpc_url: str = DEFAULT_RPC_URL) -> Optional[str]: + """Submit AI compute job""" + try: + # Get wallet address + keystore_path = keystore_dir / f"{wallet_name}.json" + if not keystore_path.exists(): + print(f"Error: Wallet '{wallet_name}' not found") + return None + + with open(keystore_path) as f: + wallet_data = json.load(f) + address = wallet_data['address'] + + # Submit job + job_data = { + "client_address": address, + "job_type": job_type, + "prompt": prompt, + "payment": payment + } + + try: + http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) + result = http_client.post("/rpc/ai/submit", json=job_data) + job_id = result.get("job_id") + print(f"AI job submitted") + print(f"Job ID: {job_id}") + print(f"Type: {job_type}") + print(f"Payment: {payment} AIT") + return job_id + except NetworkError as e: + print(f"Error submitting AI job: {e}") + return None + except Exception as e: + print(f"Error: {e}") + return None + except Exception as e: + print(f"Error: {e}") + return None + + +def get_balance(wallet_name: str, keystore_dir: Path = DEFAULT_KEYSTORE_DIR, + rpc_url: str = DEFAULT_RPC_URL) -> Optional[Dict]: + """Get wallet balance and transaction info""" + try: + keystore_path = keystore_dir / f"{wallet_name}.json" + if not keystore_path.exists(): + print(f"Error: Wallet '{wallet_name}' not found") + return None + + with open(keystore_path) as f: + wallet_data = json.load(f) + + address = wallet_data['address'] + + # Get balance from RPC + try: + http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) + balance_data = http_client.get(f"/rpc/getBalance/{address}") + return { + "address": address, + "balance": balance_data.get("balance", 0), + "nonce": balance_data.get("nonce", 0), + "wallet_name": wallet_name + } + except NetworkError as e: + print(f"Error getting balance: {e}") + return None + except Exception as e: + print(f"Error: {e}") + return None + + +def get_transactions(wallet_name: str, keystore_dir: Path = DEFAULT_KEYSTORE_DIR, + rpc_url: str = DEFAULT_RPC_URL, limit: int = 10) -> List[Dict]: + """Get wallet transaction history""" + try: + keystore_path = keystore_dir / f"{wallet_name}.json" + if not keystore_path.exists(): + print(f"Error: Wallet '{wallet_name}' not found") + return [] + + with open(keystore_path) as f: + wallet_data = json.load(f) + + address = wallet_data['address'] + + # Get transactions from RPC + try: + http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) + tx_data = http_client.get(f"/rpc/transactions?address={address}&limit={limit}") + if isinstance(tx_data, list): + transactions = tx_data + else: + transactions = tx_data.get("transactions", []) + wallet_transactions = [] + for tx in transactions: + if not isinstance(tx, dict): + continue + payload = tx.get("payload") if isinstance(tx.get("payload"), dict) else {} + if ( + tx.get("sender") == address + or tx.get("recipient") == address + or payload.get("from") == address + or payload.get("to") == address + or payload.get("recipient") == address + ): + normalized_tx = dict(tx) + if "hash" not in normalized_tx and "tx_hash" in normalized_tx: + normalized_tx["hash"] = normalized_tx["tx_hash"] + wallet_transactions.append(normalized_tx) + return wallet_transactions + except NetworkError as e: + print(f"Error getting transactions: {e}") + return [] + except Exception as e: + print(f"Error: {e}") + return [] + except Exception as e: + print(f"Error: {e}") + return [] + + +def get_balance(wallet_name: str, rpc_url: str = DEFAULT_RPC_URL, chain_id_override: str = None) -> Optional[Dict]: + """Get wallet balance""" + try: + # Get chain_id from RPC health endpoint or use override + from aitbc_cli.utils.chain_id import get_chain_id + chain_id = get_chain_id(rpc_url, override=chain_id_override, timeout=5) + + # Get wallet address + wallet_path = DEFAULT_KEYSTORE_DIR / f"{wallet_name}.json" + if not wallet_path.exists(): + print(f"Wallet {wallet_name} not found") + return None + + with open(wallet_path) as f: + wallet_data = json.load(f) + address = wallet_data["address"] + + # Get account info from RPC + try: + response = requests.get( + f"{rpc_url.rstrip('/')}/rpc/account/{address}", + params={"chain_id": chain_id}, + timeout=10, + ) + if response.status_code == 404: + return { + "wallet_name": wallet_name, + "address": address, + "balance": 0, + "nonce": 0 + } + response.raise_for_status() + account_info = response.json() + return { + "wallet_name": wallet_name, + "address": address, + "balance": account_info["balance"], + "nonce": account_info["nonce"] + } + except requests.RequestException as e: + print(f"Error getting balance: {e}") + return None + except Exception as e: + print(f"Error: {e}") + return None + except Exception as e: + print(f"Error: {e}") + return None + + +def get_chain_info(rpc_url: str = DEFAULT_RPC_URL) -> Optional[Dict]: + """Get blockchain information""" + try: + result = {} + # Get chain metadata from health endpoint + http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) + health = http_client.get("/health") + chains = health.get('supported_chains', []) + result['chain_id'] = chains[0] if chains else 'ait-mainnet' + result['supported_chains'] = ', '.join(chains) if chains else 'ait-mainnet' + result['proposer_id'] = health.get('proposer_id', '') + # Get head block for height + head = http_client.get("/rpc/head") + result['height'] = head.get('height', 0) + result['hash'] = head.get('hash', "") + result['timestamp'] = head.get('timestamp', 'N/A') + result['tx_count'] = head.get('tx_count', 0) + return result if result else None + except NetworkError as e: + print(f"Error: {e}") + return None + except Exception as e: + print(f"Error: {e}") + return None + + +def get_network_status(rpc_url: str = DEFAULT_RPC_URL) -> Optional[Dict]: + """Get network status and health""" + try: + # Get head block + http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) + return http_client.get("/rpc/head") + except NetworkError as e: + print(f"Error getting network status: {e}") + return None + except Exception as e: + print(f"Error: {e}") + return None + + +def get_blockchain_analytics(analytics_type: str, limit: int = 10, rpc_url: str = DEFAULT_RPC_URL) -> Optional[Dict]: + """Get blockchain analytics and statistics""" + try: + if analytics_type == "blocks": + # Get recent blocks analytics + http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) + head = http_client.get("/rpc/head") + return { + "type": "blocks", + "current_height": head.get("height", 0), + "latest_block": head.get("hash", ""), + "timestamp": head.get("timestamp", ""), + "tx_count": head.get("tx_count", 0), + "status": "Active" + } + + elif analytics_type == "supply": + # Get total supply info + return { + "type": "supply", + "total_supply": "1000000000", # From genesis + "circulating_supply": "999997980", # After transactions + "genesis_minted": "1000000000", + "status": "Available" + } + + elif analytics_type == "accounts": + # Account statistics + return { + "type": "accounts", + "total_accounts": 3, # Genesis + treasury + user + "active_accounts": 2, # Accounts with transactions + "genesis_accounts": 2, # Genesis and treasury + "user_accounts": 1, + "status": "Healthy" + } + + else: + return {"type": analytics_type, "status": "Not implemented yet"} + + except Exception as e: + print(f"Error getting analytics: {e}") + return None + + +def marketplace_operations(action: str, **kwargs) -> Optional[Dict]: + """Handle marketplace operations""" + try: + if action == "list": + return { + "action": "list", + "items": [ + {"name": "AI Compute Hour", "price": 100, "provider": "GPU-Miner-1"}, + {"name": "Storage Space", "price": 50, "provider": "Storage-Node-1"}, + {"name": "Bandwidth", "price": 25, "provider": "Network-Node-1"} + ], + "total_items": 3 + } + + elif action == "create": + return { + "action": "create", + "status": "Item created successfully", + "item_id": "market_" + str(int(time.time())), + "name": kwargs.get("name", ""), + "price": kwargs.get("price", 0) + } + elif action == "buy": + return { + "action": "buy", + "status": "Purchase successful", + "item_id": kwargs.get("item", ""), + "wallet": kwargs.get("wallet", ""), + "price": kwargs.get("price", 0), + "tx_hash": "tx_" + str(int(time.time())) + } + elif action == "orders": + return { + "action": "orders", + "status": "success", + "orders": [], + "count": 0 + } + + else: + return {"action": action, "status": "Not implemented yet"} + + except Exception as e: + print(f"Error in marketplace operations: {e}") + return None + + +def ai_operations(action: str, **kwargs) -> Optional[Dict]: + """Handle AI compute operations""" + try: + if action == "submit": + return { + "action": "submit", + "status": "Job submitted successfully", + "job_id": "ai_job_" + str(int(time.time())), + "model": kwargs.get("model", "default"), + "estimated_time": "30 seconds" + } + + elif action == "status": + return { + "action": "status", + "job_id": kwargs.get("job_id", ""), + "status": "Processing", + "progress": "75%", + "estimated_remaining": "8 seconds" + } + + elif action == "results": + return { + "action": "results", + "job_id": kwargs.get("job_id", ""), + "status": "Completed", + "result": "AI computation completed successfully", + "output": "Sample AI output based on prompt" + } + + elif action == "service_list": + return { + "action": "service_list", + "services": [{"name": "coordinator", "status": "running"}] + } + + elif action == "service_status": + return { + "action": "service_status", + "name": kwargs.get("name", "all"), + "status": "running", + "uptime": "5d 12h" + } + + elif action == "service_test": + return { + "action": "service_test", + "name": kwargs.get("name", "coordinator"), + "status": "passed", + "latency": "120ms" + } + + else: + return {"action": action, "status": "Not implemented yet"} + + except Exception as e: + print(f"Error in AI operations: {e}") + return None + + +def mining_operations(action: str, **kwargs) -> Optional[Dict]: + """Handle mining operations""" + try: + rpc_url = kwargs.get('rpc_url', DEFAULT_RPC_URL) + + if action == "status": + # Query actual blockchain status from RPC + try: + http_client = AITBCHTTPClient(base_url=rpc_url, timeout=5) + head_data = http_client.get("/rpc/head") + actual_height = head_data.get('height', 0) + except Exception: + actual_height = 0 + + return { + "action": "status", + "mining_active": True, + "current_height": actual_height, + "blocks_mined": actual_height, + "rewards_earned": f"{actual_height * 10} AIT", + "hash_rate": "High" + } + + elif action == "rewards": + # Query actual blockchain height for reward calculation + try: + http_client = AITBCHTTPClient(base_url=rpc_url, timeout=5) + head_data = http_client.get("/rpc/head") + actual_height = head_data.get('height', 0) + except Exception: + actual_height = 0 + + total_rewards = actual_height * 10 + return { + "action": "rewards", + "total_rewards": f"{total_rewards} AIT", + "last_reward": "10 AIT" if actual_height > 0 else "0 AIT", + "reward_rate": "10 AIT per block", + "next_reward": "In ~8 seconds" + } + + else: + return {"action": action, "status": "Not implemented yet"} + + except Exception as e: + print(f"Error in mining operations: {e}") + return None + + +def agent_operations(action: str, **kwargs) -> Optional[Dict]: + """Handle AI agent workflow and execution management""" + try: + if action == "create": + return { + "action": "create", + "agent_id": f"agent_{int(time.time())}", + "name": kwargs.get("name", ""), + "status": "Created", + "verification_level": kwargs.get("verification", "basic"), + "max_execution_time": kwargs.get("max_execution_time", 3600), + "max_cost_budget": kwargs.get("max_cost_budget", 0.0) + } + + elif action == "execute": + return { + "action": "execute", + "execution_id": f"exec_{int(time.time())}", + "agent_name": kwargs.get("name", ""), + "status": "Running", + "priority": kwargs.get("priority", "medium"), + "start_time": time.strftime("%Y-%m-%d %H:%M:%S"), + "estimated_completion": "In 5-10 minutes" + } + + elif action == "status": + return { + "action": "status", + "agent_name": kwargs.get("name", "All agents"), + "active_agents": 3, + "completed_executions": 47, + "failed_executions": 2, + "average_execution_time": "3.2 minutes", + "total_cost": "1250 AIT" + } + + elif action == "list": + # Use real coordinator API instead of stub data + try: + import requests + coordinator_url = "http://localhost:9001" + + # Build query parameters + query = {} + if kwargs.get("status"): + query["status"] = kwargs.get("status") + + # Call coordinator API + response = requests.post(f"{coordinator_url}/agents/discover", json=query, timeout=10) + + if response.status_code == 200: + data = response.json() + # Transform coordinator data to CLI format + agents = [] + for agent in data.get("agents", []): + agents.append({ + "name": agent["agent_id"], + "status": agent["status"], + "type": agent["agent_type"], + "capabilities": ", ".join(agent.get("capabilities", [])), + "services": ", ".join(agent.get("services", [])) + }) + + return { + "action": "list", + "agents": agents, + "count": len(agents) + } + else: + # Fallback to stub data if API fails + status_filter = kwargs.get("status") + agents = [ + {"name": "data-analyzer", "status": "active", "executions": 15, "success_rate": "93%"}, + {"name": "trading-bot", "status": "completed", "executions": 23, "success_rate": "87%"}, + {"name": "content-generator", "status": "failed", "executions": 8, "success_rate": "75%"} + ] + + if status_filter: + agents = [a for a in agents if a["status"] == status_filter] + + return { + "action": "list", + "agents": agents, + "count": len(agents) + } + except Exception as e: + # Fallback to stub data on error + status_filter = kwargs.get("status") + agents = [ + {"name": "data-analyzer", "status": "active", "executions": 15, "success_rate": "93%"}, + {"name": "trading-bot", "status": "completed", "executions": 23, "success_rate": "87%"}, + {"name": "content-generator", "status": "failed", "executions": 8, "success_rate": "75%"} + ] + + if status_filter: + agents = [a for a in agents if a["status"] == status_filter] + + return { + "action": "list", + "agents": agents, + "total_count": len(agents) + } + + elif action == "message": + # Send message via blockchain transaction payload + agent = kwargs.get("agent") + message = kwargs.get("message") + wallet = kwargs.get("wallet") + password = kwargs.get("password") + password_file = kwargs.get("password_file") + rpc_url = kwargs.get("rpc_url", DEFAULT_RPC_URL) + + if not agent or not message: + print("Error: agent and message are required") + return None + + if not wallet: + print("Error: wallet is required to send messages") + return None + + # Get password + if password_file: + with open(password_file) as f: + password = f.read().strip() + elif not password: + print("Error: password or password_file is required") + return None + + try: + # Decrypt wallet + keystore_path = DEFAULT_KEYSTORE_DIR / f"{wallet}.json" + private_key_hex = decrypt_private_key(keystore_path, password) + private_key_bytes = bytes.fromhex(private_key_hex) + + # Get sender address + with open(keystore_path) as f: + keystore_data = json.load(f) + sender_address = keystore_data['address'] + + # Create transaction with message as payload + priv_key = ed25519.Ed25519PrivateKey.from_private_bytes(private_key_bytes) + pub_hex = priv_key.public_key().public_bytes( + encoding=serialization.Encoding.Raw, + format=serialization.PublicFormat.Raw + ).hex() + + # Get chain_id from RPC health endpoint or use provided chain_id + chain_id_from_rpc = kwargs.get('chain_id', 'ait-mainnet') + # Auto-detect if not provided + if not kwargs.get('chain_id'): + from aitbc_cli.utils.chain_id import get_chain_id + chain_id_from_rpc = get_chain_id(rpc_url) + try: + http_client = AITBCHTTPClient(base_url=rpc_url, timeout=5) + health_data = http_client.get("/health") + supported_chains = health_data.get("supported_chains", []) + if supported_chains: + chain_id_from_rpc = supported_chains[0] + chain_id = supported_chains[0] + except Exception: + pass + + # Get actual nonce from blockchain + try: + http_client = AITBCHTTPClient(base_url=rpc_url, timeout=5) + account_data = http_client.get(f"/rpc/account/{sender_address}") + actual_nonce = account_data.get("nonce", 0) + except Exception: + actual_nonce = 0 + + tx = { + "type": "TRANSFER", + "chain_id": chain_id, + "from": sender_address, + "nonce": actual_nonce, + "fee": 10, + "payload": { + "recipient": agent, + "amount": 0, + "message": message + } + } + + # Sign transaction + tx_string = json.dumps(tx, sort_keys=True) + tx_hash = hashlib.sha256(tx_string.encode()).hexdigest() + tx["signature"] = priv_key.sign(tx_string.encode()).hex() + tx["public_key"] = pub_hex + + # Submit transaction + try: + http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) + result = http_client.post("/rpc/transaction", json=tx) + print(f"Message sent successfully") + print(f"From: {sender_address}") + print(f"To: {agent}") + print(f"Content: {message}") + return result + except NetworkError as e: + print(f"Error sending message: {e}") + return None + except Exception as e: + print(f"Error sending message: {e}") + return None + except Exception as e: + print(f"Error: {e}") + return None + + elif action == "messages": + # Retrieve messages for an agent + agent = kwargs.get("agent") + wallet = kwargs.get("wallet") + rpc_url = kwargs.get("rpc_url", DEFAULT_RPC_URL) + + if not agent: + print("Error: agent address is required") + return None + + try: + # Since /rpc/transactions endpoint is not implemented, query local database + import sys + sys.path.insert(0, "/opt/aitbc/apps/blockchain-node/src") + from sqlmodel import create_engine, Session, select + from aitbc_chain.models import Transaction + + chain_db_path = get_blockchain_data_path("ait-mainnet") / "chain.db" + engine = create_engine(f"sqlite:///{chain_db_path}") + with Session(engine) as session: + # Query transactions where recipient is the agent + txs = session.exec( + select(Transaction).where(Transaction.recipient == agent) + .order_by(Transaction.timestamp.desc()) + .limit(50) + ).all() + + messages = [] + for tx in txs: + # Extract payload + payload = "" + if hasattr(tx, "tx_metadata") and tx.tx_metadata: + if isinstance(tx.tx_metadata, dict): + payload = tx.tx_metadata.get("payload", "") + elif isinstance(tx.tx_metadata, str): + try: + payload = json.loads(tx.tx_metadata).get("payload", "") + except (json.JSONDecodeError, TypeError): + pass + elif hasattr(tx, "payload") and tx.payload: + if isinstance(tx.payload, dict): + payload = tx.payload.get("payload", "") + + if payload: # Only include transactions with payloads + messages.append({ + "from": tx.sender, + "message": payload, + "timestamp": tx.timestamp, + "block_height": tx.block_height, + "tx_hash": tx.tx_hash + }) + + print(f"Found {len(messages)} messages for {agent}") + for msg in messages: + print(f"From: {msg['from']}") + print(f"Message: {msg['message']}") + print(f"Block: {msg['block_height']}") + print(f"Time: {msg['timestamp']}") + print("-" * 40) + + return { + "action": "messages", + "agent": agent, + "count": len(messages), + "messages": messages + } + + except Exception as e: + print(f"Error retrieving messages: {e}") + return None + + else: + return {"action": action, "status": "Not implemented yet"} + + except Exception as e: + print(f"Error in agent operations: {e}") + return None + + +def hermes_training_operations(action: str, **kwargs) -> Optional[Dict]: + """Handle hermes agent ecosystem operations""" + try: + if action == "deploy": + return { + "action": "deploy", + "deployment_id": f"deploy_{int(time.time())}", + "environment": kwargs.get("environment", "dev"), + "status": "Deploying", + "agent_id": f"hermes_{int(time.time())}", + "estimated_deployment_time": "2-3 minutes", + "deployment_cost": "50 AIT" + } + + elif action == "monitor": + return { + "action": "monitor", + "agent_id": kwargs.get("agent_id", "all"), + "metrics_type": kwargs.get("metrics", "all"), + "performance_score": "94.2%", + "cost_efficiency": "87.5%", + "error_rate": "1.2%", + "uptime": "99.8%", + "last_updated": time.strftime("%Y-%m-%d %H:%M:%S") + } + + elif action == "market": + market_action = kwargs.get("market_action") + if market_action == "list": + return { + "action": "market", + "market_action": "list", + "agents": [ + {"id": "hermes_001", "name": "Data Analysis Pro", "price": 100, "rating": 4.8}, + {"id": "hermes_002", "name": "Trading Expert", "price": 250, "rating": 4.6}, + {"id": "hermes_003", "name": "Content Creator", "price": 75, "rating": 4.9} + ], + "total_available": 3 + } + elif market_action == "publish": + return { + "action": "market", + "market_action": "publish", + "agent_id": kwargs.get("agent_id", ""), + "listing_price": kwargs.get("price", 0), + "status": "Published", + "market_fee": "5 AIT" + } + + elif action == "train": + train_action = kwargs.get("train_action") + if train_action == "agent": + # Load training data + training_data_path = kwargs.get("training_data") + if not training_data_path or not os.path.exists(training_data_path): + return { + "action": "train", + "train_action": "agent", + "status": "error", + "error": "Training data file not found" + } + + try: + with open(training_data_path, 'r') as f: + training_config = json.load(f) + + # Validate training data matches stage + stage = kwargs.get("stage") + if training_config.get('stage') != stage: + return { + "action": "train", + "train_action": "agent", + "status": "error", + "error": f"Training data stage mismatch: expected {stage}, got {training_config.get('stage')}" + } + + # Initialize logging + log_dir = "/var/log/aitbc/agent-training" + os.makedirs(log_dir, exist_ok=True) + log_file = f"{log_dir}/agent_{kwargs.get('agent_id')}_{stage}_{int(time.time())}.log" + + # Execute training operations with actual hermes calls + operations = training_config.get('training_data', {}).get('operations', []) + completed_ops = 0 + failed_ops = 0 + + # hermes service endpoints + agent_coordinator_url = "http://localhost:9001" + exchange_url = "http://localhost:8001" + blockchain_rpc_url = "http://localhost:8006" + + # Write training log with actual hermes calls + for i, op in enumerate(operations, 1): + operation = op.get('operation') + parameters = op.get('parameters', {}) + + log_entry = { + "timestamp": datetime.datetime.now().isoformat(), + "agent_id": kwargs.get('agent_id'), + "stage": stage, + "operation": operation, + "prompt": { + "parameters": parameters, + "expected_result": op.get('expected_result') + } + } + + # Execute training via hermes agent with allowlist enabled + start_time = time.time() + try: + # Build prompt for hermes agent to execute AITBC command + prompt_message = f"Execute AITBC CLI command: {operation}" + if parameters: + prompt_message += f" with parameters: {json.dumps(parameters)}" + + # Use hermes agent with allowlist (AITBC CLI now allowed) + cmd = ["hermes", "agent", "--message", prompt_message, "--agent", "main"] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + + duration_ms = int((time.time() - start_time) * 1000) + + if result.returncode == 0: + reply = { + "status": "completed", + "result": result.stdout.strip() if result.stdout else "Command executed successfully", + "cli_output": result.stdout.strip() + } + log_entry["status"] = "completed" + completed_ops += 1 + else: + reply = { + "status": "error", + "error": result.stderr.strip() if result.stderr else "Command failed", + "cli_output": result.stdout.strip(), + "cli_error": result.stderr.strip() + } + log_entry["status"] = "failed" + failed_ops += 1 + + log_entry["reply"] = reply + log_entry["duration_ms"] = duration_ms + except subprocess.TimeoutExpired: + duration_ms = int((time.time() - start_time) * 1000) + reply = { + "status": "error", + "error": "Command timed out after 30 seconds" + } + log_entry["reply"] = reply + log_entry["status"] = "failed" + log_entry["duration_ms"] = duration_ms + failed_ops += 1 + except Exception as e: + duration_ms = int((time.time() - start_time) * 1000) + reply = { + "status": "error", + "error": f"Command execution failed: {str(e)}" + } + log_entry["reply"] = reply + log_entry["status"] = "failed" + log_entry["duration_ms"] = duration_ms + failed_ops += 1 + + except Exception as e: + duration_ms = int((time.time() - start_time) * 1000) + log_entry["reply"] = { + "status": "error", + "error": str(e) + } + log_entry["status"] = "failed" + log_entry["duration_ms"] = duration_ms + failed_ops += 1 + + with open(log_file, 'a') as f: + f.write(json.dumps(log_entry) + '\n') + + return { + "action": "train", + "train_action": "agent", + "agent_id": kwargs.get("agent_id"), + "stage": stage, + "total_operations": len(operations), + "completed": completed_ops, + "failed": failed_ops, + "success_rate": f"{(completed_ops/len(operations)*100):.1f}%" if operations else "0%", + "log_file": log_file, + "status": "success" + } + except Exception as e: + return { + "action": "train", + "train_action": "agent", + "status": "error", + "error": str(e) + } + + elif train_action == "validate": + # Load training data for validation + training_data_path = f"/opt/aitbc/docs/agent-training/{kwargs.get('stage')}.json" + try: + with open(training_data_path, 'r') as f: + training_config = json.load(f) + except Exception as e: + return { + "action": "train", + "train_action": "validate", + "status": "error", + "error": f"Failed to load training data: {e}" + } + + # Run exam tests (simulated) + exam_tests = training_config.get('validation', {}).get('exam_tests', []) + passed_tests = len(exam_tests) + score = 100 if exam_tests else 0 + + return { + "action": "train", + "train_action": "validate", + "agent_id": kwargs.get("agent_id"), + "stage": kwargs.get("stage"), + "total_tests": len(exam_tests), + "passed_tests": passed_tests, + "score": f"{score}%", + "validation": "passed" if score >= 80 else "failed", + "status": "success" + } + + elif train_action == "certify": + # Check all stages (simulated) + stages = [ + "stage1_foundation", + "stage2_operations_mastery", + "stage3_ai_operations", + "stage4_marketplace_economics", + "stage5_expert_operations", + "stage6_agent_identity_sdk", + "stage7_cross_node_training", + "stage8_advanced_agent_specialization", + "stage9_multi_chain_architecture" + ] + + return { + "action": "train", + "train_action": "certify", + "agent_id": kwargs.get("agent_id"), + "certification_status": "fully_certified", + "certified_stages": stages, + "total_stages": len(stages), + "certified_count": len(stages), + "status": "success" + } + else: + return {"action": "market", "market_action": market_action, "status": "Not implemented yet"} + + else: + return {"action": action, "status": "Not implemented yet"} + + except Exception as e: + print(f"Error in hermes operations: {e}") + return None + + +def workflow_operations(action: str, **kwargs) -> Optional[Dict]: + """Handle workflow automation and management""" + try: + if action == "create": + return { + "action": "create", + "workflow_id": f"workflow_{int(time.time())}", + "name": kwargs.get("name", ""), + "template": kwargs.get("template", "custom"), + "status": "Created", + "steps": 5, + "estimated_duration": "10-15 minutes" + } + + elif action == "run": + return { + "action": "run", + "workflow_name": kwargs.get("name", ""), + "execution_id": f"wf_exec_{int(time.time())}", + "status": "Running", + "async_execution": kwargs.get("async_exec", False), + "progress": "0%", + "start_time": time.strftime("%Y-%m-%d %H:%M:%S") + } + + else: + return {"action": action, "status": "Not implemented yet"} + + except Exception as e: + print(f"Error in workflow operations: {e}") + return None + + +def resource_operations(action: str, **kwargs) -> Optional[Dict]: + """Handle resource management and optimization""" + try: + if action == "status": + resource_type = kwargs.get("type", "all") + return { + "action": "status", + "resource_type": resource_type, + "cpu_utilization": "45.2%", + "memory_usage": "2.1GB / 8GB (26%)", + "storage_available": "45GB / 100GB", + "network_bandwidth": "120Mbps / 1Gbps", + "active_agents": 3, + "resource_efficiency": "78.5%" + } + + elif action == "allocate": + return { + "action": "allocate", + "agent_id": kwargs.get("agent_id", ""), + "allocated_cpu": kwargs.get("cpu", 0), + "allocated_memory": kwargs.get("memory", 0), + "duration_minutes": kwargs.get("duration", 0), + "cost_per_hour": "25 AIT", + "status": "Allocated", + "allocation_id": f"alloc_{int(time.time())}" + } + + elif action == "optimize": + return { + "action": "optimize", + "target": kwargs.get("target", "all"), + "agent_id": kwargs.get("agent_id", ""), + "optimization_score": "85.2%", + "improvement": "12.5%", + "status": "Optimized" + } + + elif action == "benchmark": + return { + "action": "benchmark", + "type": kwargs.get("type", "all"), + "score": 9850, + "percentile": "92nd", + "status": "Completed" + } + + elif action == "monitor": + return { + "action": "monitor", + "message": "Monitoring started", + "interval": kwargs.get("interval", 5), + "duration": kwargs.get("duration", 60) + } + + else: + return {"action": action, "status": "Not implemented yet"} + + except Exception as e: + print(f"Error in resource operations: {e}") + return None + + +def get_chain_info(rpc_url: str = DEFAULT_RPC_URL) -> Optional[Dict]: + """Get blockchain information""" + try: + result = {} + # Get chain metadata from health endpoint + http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) + health = http_client.get("/health") + chains = health.get('supported_chains', []) + result['chain_id'] = chains[0] if chains else 'ait-mainnet' + result['supported_chains'] = ', '.join(chains) if chains else 'ait-mainnet' + result['proposer_id'] = health.get('proposer_id', '') + # Get head block for height + head = http_client.get("/rpc/head") + result['height'] = head.get('height', 0) + result['hash'] = head.get('hash', "") + result['timestamp'] = head.get('timestamp', 'N/A') + result['tx_count'] = head.get('tx_count', 0) + return result if result else None + except NetworkError as e: + print(f"Error: {e}") + return None + except Exception as e: + print(f"Error: {e}") + return None + + +def get_network_status(rpc_url: str = DEFAULT_RPC_URL) -> Optional[Dict]: + """Get network status and health""" + try: + # Get head block + http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) + return http_client.get("/rpc/head") + except NetworkError as e: + print(f"Error getting network status: {e}") + return None + except Exception as e: + print(f"Error: {e}") + return None + + +def get_blockchain_analytics(analytics_type: str, limit: int = 10, rpc_url: str = DEFAULT_RPC_URL) -> Optional[Dict]: + """Get blockchain analytics and statistics""" + try: + if analytics_type == "blocks": + # Get recent blocks analytics + http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) + head = http_client.get("/rpc/head") + return { + "type": "blocks", + "current_height": head.get("height", 0), + "latest_block": head.get("hash", ""), + "timestamp": head.get("timestamp", ""), + "tx_count": head.get("tx_count", 0), + "status": "Active" + } + + elif analytics_type == "supply": + # Get total supply info + return { + "type": "supply", + "total_supply": "1000000000", # From genesis + "circulating_supply": "999997980", # After transactions + "genesis_minted": "1000000000", + "status": "Available" + } + + elif analytics_type == "accounts": + # Account statistics + return { + "type": "accounts", + "total_accounts": 3, # Genesis + treasury + user + "active_accounts": 2, # Accounts with transactions + "genesis_accounts": 2, # Genesis and treasury + "user_accounts": 1, + "status": "Healthy" + } + + else: + return {"type": analytics_type, "status": "Not implemented yet"} + + except Exception as e: + print(f"Error getting analytics: {e}") + return None + + +def simulate_blockchain(blocks: int, transactions: int, delay: float) -> Dict: + """Simulate blockchain block production and transactions""" + print(f"Simulating blockchain with {blocks} blocks, {transactions} transactions per block") + + results = [] + for block_num in range(blocks): + # Simulate block production + block_data = { + 'block_number': block_num + 1, + 'timestamp': time.time(), + 'transactions': [] + } + + # Generate transactions + for tx_num in range(transactions): + tx = { + 'tx_id': f"0x{random.getrandbits(256):064x}", + 'from_address': f"ait{random.getrandbits(160):040x}", + 'to_address': f"ait{random.getrandbits(160):040x}", + 'amount': random.uniform(0.1, 1000.0), + 'fee': random.uniform(0.01, 1.0) + } + block_data['transactions'].append(tx) + + block_data['tx_count'] = len(block_data['transactions']) + block_data['total_amount'] = sum(tx['amount'] for tx in block_data['transactions']) + block_data['total_fees'] = sum(tx['fee'] for tx in block_data['transactions']) + + results.append(block_data) + + # Output block info + print(f"Block {block_data['block_number']}: {block_data['tx_count']} txs, " + f"{block_data['total_amount']:.2f} AIT, {block_data['total_fees']:.2f} fees") + + if delay > 0 and block_num < blocks - 1: + time.sleep(delay) + + # Summary + total_txs = sum(block['tx_count'] for block in results) + total_amount = sum(block['total_amount'] for block in results) + total_fees = sum(block['total_fees'] for block in results) + + print(f"\nSimulation Summary:") + print(f" Total Blocks: {blocks}") + print(f" Total Transactions: {total_txs}") + print(f" Total Amount: {total_amount:.2f} AIT") + print(f" Total Fees: {total_fees:.2f} AIT") + print(f" Average TPS: {total_txs / (blocks * max(delay, 0.1)):.2f}") + + return { + 'action': 'simulate_blockchain', + 'blocks': blocks, + 'total_transactions': total_txs, + 'total_amount': total_amount, + 'total_fees': total_fees + } + + +def simulate_wallets(wallets: int, balance: float, transactions: int, amount_range: str) -> Dict: + """Simulate wallet creation and transactions""" + print(f"Simulating {wallets} wallets with {balance:.2f} AIT initial balance") + + # Parse amount range + try: + min_amount, max_amount = map(float, amount_range.split('-')) + except ValueError: + min_amount, max_amount = 1.0, 100.0 + + # Create wallets + created_wallets = [] + for i in range(wallets): + wallet = { + 'name': f'sim_wallet_{i+1}', + 'address': f"ait{random.getrandbits(160):040x}", + 'balance': balance + } + created_wallets.append(wallet) + print(f"Created wallet {wallet['name']}: {wallet['address']} with {balance:.2f} AIT") + + # Simulate transactions + print(f"\nSimulating {transactions} transactions...") + for i in range(transactions): + # Random sender and receiver + sender = random.choice(created_wallets) + receiver = random.choice([w for w in created_wallets if w != sender]) + + # Random amount + amount = random.uniform(min_amount, max_amount) + + # Check if sender has enough balance + if sender['balance'] >= amount: + sender['balance'] -= amount + receiver['balance'] += amount + + print(f"Tx {i+1}: {sender['name']} -> {receiver['name']}: {amount:.2f} AIT") + else: + print(f"Tx {i+1}: {sender['name']} -> {receiver['name']}: FAILED (insufficient balance)") + + # Final balances + print(f"\nFinal Wallet Balances:") + for wallet in created_wallets: + print(f" {wallet['name']}: {wallet['balance']:.2f} AIT") + + return { + 'action': 'simulate_wallets', + 'wallets': wallets, + 'initial_balance': balance, + 'transactions': transactions + } + + +def simulate_price(price: float, volatility: float, timesteps: int, delay: float) -> Dict: + """Simulate AIT price movements""" + print(f"Simulating AIT price from {price:.2f} with {volatility:.2f} volatility") + + current_price = price + prices = [current_price] + + for step in range(timesteps): + # Random price change + change_percent = random.uniform(-volatility, volatility) + current_price = current_price * (1 + change_percent) + + # Ensure price doesn't go negative + current_price = max(current_price, 0.01) + + prices.append(current_price) + + print(f"Step {step+1}: {current_price:.4f} AIT ({change_percent:+.2%})") + + if delay > 0 and step < timesteps - 1: + time.sleep(delay) + + # Statistics + min_price = min(prices) + max_price = max(prices) + avg_price = sum(prices) / len(prices) + + print(f"\nPrice Statistics:") + print(f" Starting Price: {price:.4f} AIT") + print(f" Ending Price: {current_price:.4f} AIT") + print(f" Minimum Price: {min_price:.4f} AIT") + print(f" Maximum Price: {max_price:.4f} AIT") + print(f" Average Price: {avg_price:.4f} AIT") + print(f" Total Change: {((current_price - price) / price * 100):+.2f}%") + + return { + 'action': 'simulate_price', + 'starting_price': price, + 'ending_price': current_price, + 'min_price': min_price, + 'max_price': max_price, + 'avg_price': avg_price + } + + +def simulate_network(nodes: int, network_delay: float, failure_rate: float) -> Dict: + """Simulate network topology and node failures""" + print(f"Simulating network with {nodes} nodes, {network_delay}s delay, {failure_rate:.2f} failure rate") + + # Create nodes + network_nodes = [] + for i in range(nodes): + node = { + 'id': f'node_{i+1}', + 'address': f"10.1.223.{90+i}", + 'status': 'active', + 'height': 0, + 'connected_to': [] + } + network_nodes.append(node) + + # Create network topology (ring + mesh) + for i, node in enumerate(network_nodes): + # Connect to next node (ring) + next_node = network_nodes[(i + 1) % len(network_nodes)] + node['connected_to'].append(next_node['id']) + + # Connect to random nodes (mesh) + if len(network_nodes) > 2: + mesh_connections = random.sample([n['id'] for n in network_nodes if n['id'] != node['id']], + min(2, len(network_nodes) - 1)) + for conn in mesh_connections: + if conn not in node['connected_to']: + node['connected_to'].append(conn) + + # Display network topology + print(f"\nNetwork Topology:") + for node in network_nodes: + print(f" {node['id']} ({node['address']}): connected to {', '.join(node['connected_to'])}") + + # Simulate network operations + print(f"\nSimulating network operations...") + active_nodes = network_nodes.copy() + + for step in range(10): + # Simulate failures + for node in active_nodes: + if random.random() < failure_rate: + node['status'] = 'failed' + print(f"Step {step+1}: {node['id']} failed") + + # Remove failed nodes + active_nodes = [n for n in active_nodes if n['status'] == 'active'] + + # Simulate block propagation + if active_nodes: + # Random node produces block + producer = random.choice(active_nodes) + producer['height'] += 1 + + # Propagate to connected nodes + for node in active_nodes: + if node['id'] != producer['id'] and node['id'] in producer['connected_to']: + node['height'] = max(node['height'], producer['height'] - 1) + + print(f"Step {step+1}: {producer['id']} produced block {producer['height']}, " + f"{len(active_nodes)} nodes active") + + time.sleep(network_delay) + + # Final network status + print(f"\nFinal Network Status:") + for node in network_nodes: + status_icon = "✅" if node['status'] == 'active' else "❌" + print(f" {status_icon} {node['id']}: height {node['height']}, " + f"connections: {len(node['connected_to'])}") + + return { + 'action': 'simulate_network', + 'nodes': nodes, + 'active_nodes': len(active_nodes), + 'failure_rate': failure_rate + } + + +def simulate_ai_jobs(jobs: int, models: str, duration_range: str) -> Dict: + """Simulate AI job submission and processing""" + print(f"Simulating {jobs} AI jobs with models: {models}") + + # Parse models + model_list = [m.strip() for m in models.split(',')] + + # Parse duration range + try: + min_duration, max_duration = map(int, duration_range.split('-')) + except ValueError: + min_duration, max_duration = 30, 300 + + # Simulate job submission + submitted_jobs = [] + for i in range(jobs): + job = { + 'job_id': f"job_{i+1:03d}", + 'model': random.choice(model_list), + 'status': 'queued', + 'submit_time': time.time(), + 'duration': random.randint(min_duration, max_duration), + 'wallet': f"wallet_{random.randint(1, 5):03d}" + } + submitted_jobs.append(job) + + print(f"Submitted job {job['job_id']}: {job['model']} (est. {job['duration']}s)") + + # Simulate job processing + print(f"\nSimulating job processing...") + processing_jobs = submitted_jobs.copy() + completed_jobs = [] + + current_time = time.time() + while processing_jobs and current_time < time.time() + 600: # Max 10 minutes + current_time = time.time() + + for job in processing_jobs[:]: + if job['status'] == 'queued' and current_time - job['submit_time'] > 5: + job['status'] = 'running' + job['start_time'] = current_time + print(f"Started {job['job_id']}") + + elif job['status'] == 'running': + if current_time - job['start_time'] >= job['duration']: + job['status'] = 'completed' + job['end_time'] = current_time + job['actual_duration'] = job['end_time'] - job['start_time'] + processing_jobs.remove(job) + completed_jobs.append(job) + print(f"Completed {job['job_id']} in {job['actual_duration']:.1f}s") + + time.sleep(1) # Check every second + + # Job statistics + print(f"\nJob Statistics:") + print(f" Total Jobs: {jobs}") + print(f" Completed Jobs: {len(completed_jobs)}") + print(f" Failed Jobs: {len(processing_jobs)}") + + if completed_jobs: + avg_duration = sum(job['actual_duration'] for job in completed_jobs) / len(completed_jobs) + print(f" Average Duration: {avg_duration:.1f}s") + + # Model statistics + model_stats = {} + for job in completed_jobs: + model_stats[job['model']] = model_stats.get(job['model'], 0) + 1 + + print(f" Model Usage:") + for model, count in model_stats.items(): + print(f" {model}: {count} jobs") + + return { + 'action': 'simulate_ai_jobs', + 'total_jobs': jobs, + 'completed_jobs': len(completed_jobs), + 'failed_jobs': len(processing_jobs) + } + + +def legacy_main(): + parser = argparse.ArgumentParser(description="AITBC CLI - Comprehensive Blockchain Management Tool") + parser.add_argument("--chain-id", default=None, help="Chain ID (auto-detected from blockchain node if not provided)") + subparsers = parser.add_subparsers(dest="command", help="Available commands") + + # Create wallet command + create_parser = subparsers.add_parser("create", help="Create a new wallet") + create_parser.add_argument("--name", required=True, help="Wallet name") + create_parser.add_argument("--password", help="Wallet password") + create_parser.add_argument("--password-file", help="File containing wallet password") + + # Send transaction command + send_parser = subparsers.add_parser("send", help="Send AIT") + send_parser.add_argument("--from", required=True, dest="from_wallet", help="From wallet name") + send_parser.add_argument("--to", required=True, dest="to_address", help="To address") + send_parser.add_argument("--amount", type=float, required=True, help="Amount to send") + send_parser.add_argument("--fee", type=float, default=10.0, help="Transaction fee") + send_parser.add_argument("--password", help="Wallet password") + send_parser.add_argument("--password-file", help="File containing wallet password") + send_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") + + # List wallets command + list_parser = subparsers.add_parser("list", help="List wallets") + list_parser.add_argument("--format", choices=["table", "json"], default="table", help="Output format") + + # Balance command + balance_parser = subparsers.add_parser("balance", help="Get wallet balance") + balance_parser.add_argument("--name", required=True, help="Wallet name") + balance_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") + + # Transactions command + tx_parser = subparsers.add_parser("transactions", help="Get wallet transactions") + tx_parser.add_argument("--name", required=True, help="Wallet name") + tx_parser.add_argument("--limit", type=int, default=10, help="Number of transactions") + tx_parser.add_argument("--format", choices=["table", "json"], default="table", help="Output format") + tx_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") + + # Chain info command + chain_parser = subparsers.add_parser("chain", help="Get blockchain information") + chain_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") + + # Network status command + network_parser = subparsers.add_parser("network", help="Get network status") + network_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") + + # Blockchain analytics command + analytics_parser = subparsers.add_parser("analytics", help="Blockchain analytics and statistics") + analytics_parser.add_argument("--type", choices=["blocks", "transactions", "accounts", "supply"], + default="blocks", help="Analytics type") + analytics_parser.add_argument("--limit", type=int, default=10, help="Number of items to analyze") + analytics_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") + + # Marketplace operations command + market_parser = subparsers.add_parser("marketplace", help="Marketplace operations") + market_parser.add_argument("--action", choices=["list", "create", "search", "my-listings"], + required=True, help="Marketplace action") + market_parser.add_argument("--name", help="Item name") + market_parser.add_argument("--price", type=float, help="Item price") + market_parser.add_argument("--description", help="Item description") + market_parser.add_argument("--wallet", help="Wallet name for marketplace operations") + market_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") + + # AI operations command + ai_parser = subparsers.add_parser("ai-ops", help="AI compute operations") + ai_parser.add_argument("--action", choices=["submit", "status", "results"], + required=True, help="AI operation") + ai_parser.add_argument("--model", help="AI model name") + ai_parser.add_argument("--prompt", help="AI prompt") + ai_parser.add_argument("--job-id", help="Job ID for status/results") + ai_parser.add_argument("--wallet", help="Wallet name for AI operations") + ai_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") + + # Mining operations command + mining_parser = subparsers.add_parser("mining", help="Mining operations and status") + mining_parser.add_argument("--action", choices=["status", "start", "stop", "rewards"], + help="Mining action") + mining_parser.add_argument("--wallet", help="Wallet name for mining rewards") + mining_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") + + # Agent management commands (hermes agent focused) + agent_parser = subparsers.add_parser("agent", help="AI agent workflow and execution management") + agent_subparsers = agent_parser.add_subparsers(dest="agent_action", help="Agent actions") + + # Agent create + agent_create_parser = agent_subparsers.add_parser("create", help="Create new AI agent workflow") + agent_create_parser.add_argument("--name", required=True, help="Agent workflow name") + agent_create_parser.add_argument("--description", help="Agent description") + agent_create_parser.add_argument("--workflow-file", help="Workflow definition from JSON file") + agent_create_parser.add_argument("--verification", choices=["basic", "full", "zero-knowledge"], + default="basic", help="Verification level") + agent_create_parser.add_argument("--max-execution-time", type=int, default=3600, help="Max execution time (seconds)") + agent_create_parser.add_argument("--max-cost-budget", type=float, default=0.0, help="Max cost budget") + + # Agent execute + agent_execute_parser = agent_subparsers.add_parser("execute", help="Execute AI agent workflow") + agent_execute_parser.add_argument("--name", required=True, help="Agent workflow name") + agent_execute_parser.add_argument("--input-data", help="Input data for agent execution") + agent_execute_parser.add_argument("--wallet", help="Wallet for payment") + agent_execute_parser.add_argument("--priority", choices=["low", "medium", "high"], default="medium", help="Execution priority") + + # Agent status + agent_status_parser = agent_subparsers.add_parser("status", help="Check agent execution status") + agent_status_parser.add_argument("--name", help="Agent workflow name") + agent_status_parser.add_argument("--execution-id", help="Specific execution ID") + + # Agent list + agent_list_parser = agent_subparsers.add_parser("list", help="List available agent workflows") + agent_list_parser.add_argument("--status", choices=["active", "completed", "failed"], help="Filter by status") + + # hermes specific commands + hermes_parser = subparsers.add_parser("hermes", help="hermes agent ecosystem operations") + hermes_subparsers = hermes_parser.add_subparsers(dest="hermes_action", help="hermes actions") + + # hermes deploy + hermes_deploy_parser = hermes_subparsers.add_parser("deploy", help="Deploy hermes agent") + hermes_deploy_parser.add_argument("--agent-file", required=True, help="Agent configuration file") + hermes_deploy_parser.add_argument("--wallet", required=True, help="Wallet for deployment costs") + hermes_deploy_parser.add_argument("--environment", choices=["dev", "staging", "prod"], default="dev", help="Deployment environment") + + # hermes monitor + hermes_monitor_parser = hermes_subparsers.add_parser("monitor", help="Monitor hermes agent performance") + hermes_monitor_parser.add_argument("--agent-id", help="Specific agent ID to monitor") + hermes_monitor_parser.add_argument("--metrics", choices=["performance", "cost", "errors", "all"], default="all", help="Metrics to show") + + # hermes market + hermes_market_parser = hermes_subparsers.add_parser("market", help="hermes agent marketplace") + hermes_market_parser.add_argument("--action", choices=["list", "publish", "purchase", "evaluate"], + required=True, help="Market action") + hermes_market_parser.add_argument("--agent-id", help="Agent ID for market operations") + hermes_market_parser.add_argument("--price", type=float, help="Price for market operations") + + # Workflow automation commands + workflow_parser = subparsers.add_parser("workflow", help="Workflow automation and management") + workflow_subparsers = workflow_parser.add_subparsers(dest="workflow_action", help="Workflow actions") + + # Workflow create + workflow_create_parser = workflow_subparsers.add_parser("create", help="Create automated workflow") + workflow_create_parser.add_argument("--name", required=True, help="Workflow name") + workflow_create_parser.add_argument("--template", help="Workflow template") + workflow_create_parser.add_argument("--config-file", help="Workflow configuration file") + + # Workflow run + workflow_run_parser = workflow_subparsers.add_parser("run", help="Execute automated workflow") + workflow_run_parser.add_argument("--name", required=True, help="Workflow name") + workflow_run_parser.add_argument("--params", help="Workflow parameters (JSON)") + workflow_run_parser.add_argument("--async-exec", action="store_true", help="Run asynchronously") + + # Resource management commands + resource_parser = subparsers.add_parser("resource", help="Resource management and optimization") + resource_subparsers = resource_parser.add_subparsers(dest="resource_action", help="Resource actions") + + # Resource status + resource_status_parser = resource_subparsers.add_parser("status", help="Check resource utilization") + resource_status_parser.add_argument("--type", choices=["cpu", "memory", "storage", "network", "all"], default="all") + + # Resource allocate + resource_allocate_parser = resource_subparsers.add_parser("allocate", help="Allocate resources to agent") + resource_allocate_parser.add_argument("--agent-id", required=True, help="Agent ID") + resource_allocate_parser.add_argument("--cpu", type=float, help="CPU cores") + resource_allocate_parser.add_argument("--memory", type=int, help="Memory in MB") + resource_allocate_parser.add_argument("--duration", type=int, help="Duration in minutes") + + # System status command + system_parser = subparsers.add_parser("system", help="System status and information") + system_parser.add_argument("--status", action="store_true", help="Show system status") + + # Genesis command with subcommands + genesis_parser = subparsers.add_parser("genesis", help="Genesis block and wallet generation") + genesis_subparsers = genesis_parser.add_subparsers(dest="genesis_action", help="Genesis actions") + + # Genesis init + genesis_init_parser = genesis_subparsers.add_parser("init", help="Initialize genesis block and wallet") + genesis_init_parser.add_argument("--chain-id", default="ait-mainnet", help="Chain ID for genesis") + genesis_init_parser.add_argument("--create-wallet", action="store_true", help="Create genesis wallet with secure random key") + genesis_init_parser.add_argument("--password", help="Wallet password (auto-generated if not provided)") + genesis_init_parser.add_argument("--proposer", help="Proposer address (defaults to genesis wallet)") + genesis_init_parser.add_argument("--force", action="store_true", help="Force overwrite existing genesis") + genesis_init_parser.add_argument("--register-service", action="store_true", help="Register genesis wallet with wallet service") + genesis_init_parser.add_argument("--service-url", default="http://localhost:8003", help="Wallet service URL") + + # Genesis verify + genesis_verify_parser = genesis_subparsers.add_parser("verify", help="Verify genesis block and wallet configuration") + genesis_verify_parser.add_argument("--chain-id", default="ait-mainnet", help="Chain ID to verify") + + # Genesis info + genesis_info_parser = genesis_subparsers.add_parser("info", help="Show genesis block information") + genesis_info_parser.add_argument("--chain-id", default="ait-mainnet", help="Chain ID to show info for") + + # Blockchain command with subcommands + blockchain_parser = subparsers.add_parser("blockchain", help="Blockchain operations") + blockchain_subparsers = blockchain_parser.add_subparsers(dest="blockchain_action", help="Blockchain actions") + + # Blockchain info + blockchain_info_parser = blockchain_subparsers.add_parser("info", help="Blockchain information") + blockchain_info_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") + + # Blockchain height + blockchain_height_parser = blockchain_subparsers.add_parser("height", help="Blockchain height") + blockchain_height_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") + + # Block info + blockchain_block_parser = blockchain_subparsers.add_parser("block", help="Block information") + blockchain_block_parser.add_argument("--number", type=int, help="Block number") + blockchain_block_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") + + # Wallet command with subcommands + wallet_parser = subparsers.add_parser("wallet", help="Wallet operations") + wallet_subparsers = wallet_parser.add_subparsers(dest="wallet_action", help="Wallet actions") + + # Wallet backup + wallet_backup_parser = wallet_subparsers.add_parser("backup", help="Backup wallet") + wallet_backup_parser.add_argument("--name", required=True, help="Wallet name") + wallet_backup_parser.add_argument("--password", help="Wallet password") + wallet_backup_parser.add_argument("--password-file", help="File containing wallet password") + + # Wallet export + wallet_export_parser = wallet_subparsers.add_parser("export", help="Export wallet") + wallet_export_parser.add_argument("--name", required=True, help="Wallet name") + wallet_export_parser.add_argument("--password", help="Wallet password") + wallet_export_parser.add_argument("--password-file", help="File containing wallet password") + + # Wallet sync + wallet_sync_parser = wallet_subparsers.add_parser("sync", help="Sync wallet") + wallet_sync_parser.add_argument("--name", help="Wallet name") + wallet_sync_parser.add_argument("--all", action="store_true", help="Sync all wallets") + + # Wallet balance + wallet_balance_parser = wallet_subparsers.add_parser("balance", help="Wallet balance") + wallet_balance_parser.add_argument("--name", help="Wallet name") + wallet_balance_parser.add_argument("--all", action="store_true", help="Show all balances") + wallet_balance_parser.add_argument("--chain-id", help="Chain ID for multichain operations (e.g., ait-mainnet, ait-devnet)") + + # All balances command (keep for backward compatibility) + all_balances_parser = subparsers.add_parser("all-balances", help="Show all wallet balances") + + # Import wallet command + import_parser = subparsers.add_parser("import", help="Import wallet from private key") + import_parser.add_argument("--name", required=True, help="Wallet name") + import_parser.add_argument("--private-key", required=True, help="Private key (hex)") + import_parser.add_argument("--password", help="Wallet password") + import_parser.add_argument("--password-file", help="File containing wallet password") + + # Export wallet command + export_parser = subparsers.add_parser("export", help="Export private key from wallet") + export_parser.add_argument("--name", required=True, help="Wallet name") + export_parser.add_argument("--password", help="Wallet password") + export_parser.add_argument("--password-file", help="File containing wallet password") + + # Delete wallet command + delete_parser = subparsers.add_parser("delete", help="Delete wallet") + delete_parser.add_argument("--name", required=True, help="Wallet name") + delete_parser.add_argument("--confirm", action="store_true", help="Confirm deletion") + + # Rename wallet command + rename_parser = subparsers.add_parser("rename", help="Rename wallet") + rename_parser.add_argument("--old", required=True, dest="old_name", help="Current wallet name") + rename_parser.add_argument("--new", required=True, dest="new_name", help="New wallet name") + + # Batch send command + batch_parser = subparsers.add_parser("batch", help="Send multiple transactions") + batch_parser.add_argument("--file", required=True, help="JSON file with transactions") + batch_parser.add_argument("--password", help="Wallet password") + batch_parser.add_argument("--password-file", help="File containing wallet password") + batch_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") + + # Update existing mining parser to add flag support + mining_parser.add_argument("--start", action="store_true", help="Start mining") + mining_parser.add_argument("--stop", action="store_true", help="Stop mining") + mining_parser.add_argument("--status", action="store_true", help="Mining status") + + # Update existing network parser to add subcommands + network_subparsers = network_parser.add_subparsers(dest="network_action", help="Network actions") + + # Network status + network_status_parser = network_subparsers.add_parser("status", help="Network status") + network_status_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") + + # Network peers + network_peers_parser = network_subparsers.add_parser("peers", help="Network peers") + network_peers_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") + + # Network sync + network_sync_parser = network_subparsers.add_parser("sync", help="Network sync") + network_sync_parser.add_argument("--status", action="store_true", help="Sync status") + network_sync_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") + + # Network ping + network_ping_parser = network_subparsers.add_parser("ping", help="Ping node") + network_ping_parser.add_argument("--node", help="Node to ping") + network_ping_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") + + # Network propagate + network_propagate_parser = network_subparsers.add_parser("propagate", help="Propagate data") + network_propagate_parser.add_argument("--data", help="Data to propagate") + network_propagate_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") + + # Marketplace commands + market_list_parser = subparsers.add_parser("market-list", help="List marketplace items") + market_list_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") + + market_create_parser = subparsers.add_parser("market-create", help="Create marketplace listing") + market_create_parser.add_argument("--wallet", required=True, help="Seller wallet name") + market_create_parser.add_argument("--type", required=True, help="Item type") + market_create_parser.add_argument("--price", type=float, required=True, help="Price in AIT") + market_create_parser.add_argument("--description", required=True, help="Item description") + market_create_parser.add_argument("--password", help="Wallet password") + market_create_parser.add_argument("--password-file", help="File containing wallet password") + + # AI commands + ai_submit_parser = subparsers.add_parser("ai-submit", help="Submit AI compute job") + ai_submit_parser.add_argument("--wallet", required=True, help="Client wallet name") + ai_submit_parser.add_argument("--type", required=True, help="Job type") + ai_submit_parser.add_argument("--prompt", required=True, help="AI prompt") + ai_submit_parser.add_argument("--payment", type=float, required=True, help="Payment in AIT") + ai_submit_parser.add_argument("--password", help="Wallet password") + ai_submit_parser.add_argument("--password-file", help="File containing wallet password") + + # Simulation commands + simulate_parser = subparsers.add_parser("simulate", help="Simulate blockchain scenarios and test environments") + simulate_subparsers = simulate_parser.add_subparsers(dest="simulate_command", help="Simulation commands") + + # Blockchain simulation + blockchain_sim_parser = simulate_subparsers.add_parser("blockchain", help="Simulate blockchain block production and transactions") + blockchain_sim_parser.add_argument("--blocks", type=int, default=10, help="Number of blocks to simulate") + blockchain_sim_parser.add_argument("--transactions", type=int, default=50, help="Number of transactions per block") + blockchain_sim_parser.add_argument("--delay", type=float, default=1.0, help="Delay between blocks (seconds)") + + # Wallet simulation + wallets_sim_parser = simulate_subparsers.add_parser("wallets", help="Simulate wallet creation and transactions") + wallets_sim_parser.add_argument("--wallets", type=int, default=5, help="Number of wallets to create") + wallets_sim_parser.add_argument("--balance", type=float, default=1000.0, help="Initial balance for each wallet") + wallets_sim_parser.add_argument("--transactions", type=int, default=20, help="Number of transactions to simulate") + wallets_sim_parser.add_argument("--amount-range", default="1.0-100.0", help="Transaction amount range (min-max)") + + # Price simulation + price_sim_parser = simulate_subparsers.add_parser("price", help="Simulate AIT price movements") + price_sim_parser.add_argument("--price", type=float, default=100.0, help="Starting AIT price") + price_sim_parser.add_argument("--volatility", type=float, default=0.05, help="Price volatility (0.0-1.0)") + price_sim_parser.add_argument("--timesteps", type=int, default=100, help="Number of timesteps to simulate") + price_sim_parser.add_argument("--delay", type=float, default=0.1, help="Delay between timesteps (seconds)") + + # Network simulation + network_sim_parser = simulate_subparsers.add_parser("network", help="Simulate network topology and node failures") + network_sim_parser.add_argument("--nodes", type=int, default=3, help="Number of nodes to simulate") + network_sim_parser.add_argument("--network-delay", type=float, default=0.1, help="Network delay in seconds") + network_sim_parser.add_argument("--failure-rate", type=float, default=0.05, help="Node failure rate (0.0-1.0)") + + # AI jobs simulation + ai_jobs_sim_parser = simulate_subparsers.add_parser("ai-jobs", help="Simulate AI job submission and processing") + ai_jobs_sim_parser.add_argument("--jobs", type=int, default=10, help="Number of AI jobs to simulate") + ai_jobs_sim_parser.add_argument("--models", default="text-generation", help="Available models (comma-separated)") + ai_jobs_sim_parser.add_argument("--duration-range", default="30-300", help="Job duration range in seconds (min-max)") + + args = parser.parse_args() + + # Handle chain_id with auto-detection + from aitbc_cli.utils.chain_id import get_chain_id + chain_id = get_chain_id(DEFAULT_RPC_URL, override=args.chain_id) + + if args.command == "create": + # Get password + password = None + if args.password: + password = args.password + elif args.password_file: + with open(args.password_file) as f: + password = f.read().strip() + else: + import getpass + password = getpass.getpass("Enter wallet password: ") + + if not password: + print("Error: Password is required") + sys.exit(1) + + address = create_wallet(args.name, password) + print(f"Wallet address: {address}") + + elif args.command == "send": + # Get password + password = None + if args.password: + password = args.password + elif args.password_file: + with open(args.password_file) as f: + password = f.read().strip() + else: + import getpass + password = getpass.getpass(f"Enter password for wallet '{args.from_wallet}': ") + + if not password: + print("Error: Password is required") + sys.exit(1) + + tx_hash = send_transaction( + args.from_wallet, + args.to_address, + args.amount, + args.fee, + password, + rpc_url=args.rpc_url + ) + + if tx_hash: + print(f"Transaction hash: {tx_hash}") + else: + sys.exit(1) + + elif args.command == "list": + wallets = list_wallets() + + if args.format == "json": + print(json.dumps(wallets, indent=2)) + else: + print("Wallets:") + for wallet in wallets: + print(f" {wallet['name']}: {wallet['address']}") + + elif args.command == "balance": + balance_info = get_balance(args.name, rpc_url=args.rpc_url) + if balance_info: + print(f"Wallet: {balance_info['wallet_name']}") + print(f"Address: {balance_info['address']}") + print(f"Balance: {balance_info['balance']} AIT") + print(f"Nonce: {balance_info['nonce']}") + else: + sys.exit(1) + + elif args.command == "transactions": + transactions = get_transactions(args.name, limit=args.limit, rpc_url=args.rpc_url) + + if args.format == "json": + print(json.dumps(transactions, indent=2)) + else: + print(f"Transactions for {args.name}:") + for i, tx in enumerate(transactions, 1): + print(f" {i}. Hash: {tx.get('hash', 'N/A')}") + print(f" Amount: {tx.get('value', 0)} AIT") + print(f" Fee: {tx.get('fee', 0)} AIT") + print(f" Type: {tx.get('type', 'N/A')}") + print() + + elif args.command == "chain": + chain_info = get_chain_info(rpc_url=args.rpc_url) + if chain_info: + print("Blockchain Information:") + print(f" Chain ID: {chain_info.get('chain_id', 'N/A')}") + print(f" Supported Chains: {chain_info.get('supported_chains', 'N/A')}") + print(f" Height: {chain_info.get('height', 'N/A')}") + print(f" Latest Block: {str(chain_info.get('hash', 'N/A'))[:16]}...") + print(f" Proposer: {chain_info.get('proposer_id', 'N/A') or 'none'}") + else: + sys.exit(1) + + elif args.command == "network": + network_info = get_network_status(rpc_url=args.rpc_url) + if network_info: + print("Network Status:") + print(f" Height: {network_info.get('height', 'N/A')}") + print(f" Latest Block: {str(network_info.get('hash', 'N/A'))[:16]}...") + print(f" Chain ID: {network_info.get('chain_id', 'ait-mainnet')}") + print(f" Tx Count: {network_info.get('tx_count', 0)}") + print(f" Timestamp: {network_info.get('timestamp', 'N/A')}") + else: + sys.exit(1) + + elif args.command == "analytics": + analytics = get_blockchain_analytics(args.type, args.limit, rpc_url=args.rpc_url) + if analytics: + print(f"Blockchain Analytics ({analytics['type']}):") + for key, value in analytics.items(): + if key != "type": + print(f" {key.replace('_', ' ').title()}: {value}") + else: + sys.exit(1) + + elif args.command == "marketplace": + result = marketplace_operations(args.action, name=args.name, price=args.price, + description=args.description, wallet=args.wallet) + if result: + print(f"Marketplace {result['action']}:") + for key, value in result.items(): + if key != "action": + print(f" {key.replace('_', ' ').title()}: {value}") + else: + sys.exit(1) + + elif args.command == "ai-ops": + result = ai_operations(args.action, model=args.model, prompt=args.prompt, + job_id=args.job_id, wallet=args.wallet) + if result: + print(f"AI Operations {result['action']}:") + for key, value in result.items(): + if key != "action": + print(f" {key.replace('_', ' ').title()}: {value}") + else: + sys.exit(1) + + elif args.command == "mining": + result = mining_operations(args.action, wallet=args.wallet) + if result: + print(f"Mining {result['action']}:") + for key, value in result.items(): + if key != "action": + print(f" {key.replace('_', ' ').title()}: {value}") + else: + sys.exit(1) + + elif args.command == "agent": + # Only pass arguments that are defined for this subcommand + kwargs = {} + if hasattr(args, 'name') and args.name: + kwargs['name'] = args.name + if hasattr(args, 'description') and args.description: + kwargs['description'] = args.description + if hasattr(args, 'verification') and args.verification: + kwargs['verification'] = args.verification + if hasattr(args, 'max_execution_time') and args.max_execution_time: + kwargs['max_execution_time'] = args.max_execution_time + if hasattr(args, 'max_cost_budget') and args.max_cost_budget: + kwargs['max_cost_budget'] = args.max_cost_budget + if hasattr(args, 'input_data') and args.input_data: + kwargs['input_data'] = args.input_data + if hasattr(args, 'wallet') and args.wallet: + kwargs['wallet'] = args.wallet + if hasattr(args, 'priority') and args.priority: + kwargs['priority'] = args.priority + if hasattr(args, 'execution_id') and args.execution_id: + kwargs['execution_id'] = args.execution_id + if hasattr(args, 'status') and args.status: + kwargs['status'] = args.status + kwargs['chain_id'] = chain_id + + result = agent_operations(args.agent_action, **kwargs) + if result: + print(f"Agent {result['action']}:") + for key, value in result.items(): + if key != "action": + if isinstance(value, list): + print(f" {key.replace('_', ' ').title()}:") + for item in value: + print(f" - {item}") + else: + print(f" {key.replace('_', ' ').title()}: {value}") + else: + sys.exit(1) + + elif args.command == "hermes": + # Only pass arguments that are defined for this subcommand + kwargs = {} + if hasattr(args, 'agent_file') and args.agent_file: + kwargs['agent_file'] = args.agent_file + if hasattr(args, 'wallet') and args.wallet: + kwargs['wallet'] = args.wallet + if hasattr(args, 'environment') and args.environment: + kwargs['environment'] = args.environment + if hasattr(args, 'agent_id') and args.agent_id: + kwargs['agent_id'] = args.agent_id + if hasattr(args, 'metrics') and args.metrics: + kwargs['metrics'] = args.metrics + # Handle the market action parameter specifically + if hasattr(args, 'action') and args.action and args.hermes_action == 'market': + kwargs['market_action'] = args.action + if hasattr(args, 'price') and args.price: + kwargs['price'] = args.price + + result = hermes_operations(args.hermes_action, **kwargs) + if result: + print(f"hermes {result['action']}:") + for key, value in result.items(): + if key != "action": + if isinstance(value, list): + print(f" {key.replace('_', ' ').title()}:") + for item in value: + print(f" - {item}") + else: + print(f" {key.replace('_', ' ').title()}: {value}") + else: + sys.exit(1) + + elif args.command == "workflow": + # Only pass arguments that are defined for this subcommand + kwargs = {} + if hasattr(args, 'name') and args.name: + kwargs['name'] = args.name + if hasattr(args, 'template') and args.template: + kwargs['template'] = args.template + if hasattr(args, 'config_file') and args.config_file: + kwargs['config_file'] = args.config_file + if hasattr(args, 'params') and args.params: + kwargs['params'] = args.params + if hasattr(args, 'async_exec') and args.async_exec: + kwargs['async_exec'] = args.async_exec + + result = workflow_operations(args.workflow_action, **kwargs) + if result: + print(f"Workflow {result['action']}:") + for key, value in result.items(): + if key != "action": + print(f" {key.replace('_', ' ').title()}: {value}") + else: + sys.exit(1) + + elif args.command == "resource": + # Only pass arguments that are defined for this subcommand + kwargs = {} + if hasattr(args, 'type') and args.type: + kwargs['type'] = args.type + if hasattr(args, 'agent_id') and args.agent_id: + kwargs['agent_id'] = args.agent_id + if hasattr(args, 'cpu') and args.cpu: + kwargs['cpu'] = args.cpu + if hasattr(args, 'memory') and args.memory: + kwargs['memory'] = args.memory + if hasattr(args, 'duration') and args.duration: + kwargs['duration'] = args.duration + + result = resource_operations(args.resource_action, **kwargs) + if result: + print(f"Resource {result['action']}:") + for key, value in result.items(): + if key != "action": + print(f" {key.replace('_', ' ').title()}: {value}") + else: + sys.exit(1) + + elif args.command == "mine-start": + result = mining_operations('start', wallet=args.wallet) + if result: + print(f"Mining start:") + for key, value in result.items(): + if key != 'action': + print(f" {key.replace('_', ' ').title()}: {value}") + else: + sys.exit(1) + + elif args.command == "mine-stop": + result = mining_operations('stop') + if result: + print(f"Mining stop:") + for key, value in result.items(): + if key != 'action': + print(f" {key.replace('_', ' ').title()}: {value}") + else: + sys.exit(1) + + elif args.command == "mine-status": + result = mining_operations('status') + if result: + print(f"Mining status:") + for key, value in result.items(): + if key != 'action': + print(f" {key.replace('_', ' ').title()}: {value}") + else: + sys.exit(1) + + elif args.command == "market-list": + result = marketplace_operations('list', rpc_url=getattr(args, 'rpc_url', DEFAULT_RPC_URL)) + if result: + print(f"Marketplace listings:") + for key, value in result.items(): + if key != 'action': + if isinstance(value, list): + print(f" {key.replace('_', ' ').title()}:") + for item in value: + print(f" - {item}") + else: + print(f" {key.replace('_', ' ').title()}: {value}") + else: + print("No marketplace listings found.") + + elif args.command == "market-create": + result = marketplace_operations('create', name=getattr(args, 'type', ''), + price=args.price, description=args.description, + wallet=args.wallet) + if result: + print(f"Marketplace listing created:") + for key, value in result.items(): + if key != 'action': + print(f" {key.replace('_', ' ').title()}: {value}") + else: + sys.exit(1) + + elif args.command == "ai-submit": + result = ai_operations('submit', model=getattr(args, 'type', ''), + prompt=args.prompt, wallet=args.wallet) + if result: + print(f"AI job submitted:") + for key, value in result.items(): + if key != 'action': + print(f" {key.replace('_', ' ').title()}: {value}") + else: + sys.exit(1) + + elif args.command == "system": + if args.status: + print("System status: OK") + print(" Version: aitbc-cli v2.0.0") + print(" Services: Running") + print(" Nodes: 2 connected") + else: + print("System operation completed") + + elif args.command == "genesis": + import subprocess + import sys + from pathlib import Path + + script_path = Path("/opt/aitbc/apps/blockchain-node/scripts/unified_genesis.py") + + if not script_path.exists(): + print(f"Error: Genesis generation script not found: {script_path}") + sys.exit(1) + + if args.genesis_action == "init": + cmd = [sys.executable, str(script_path), "--chain-id", getattr(args, 'chain_id', 'ait-mainnet')] + + if hasattr(args, 'create_wallet') and args.create_wallet: + cmd.append("--create-wallet") + if hasattr(args, 'password') and args.password: + cmd.extend(["--password", args.password]) + if hasattr(args, 'proposer') and args.proposer: + cmd.extend(["--proposer", args.proposer]) + if hasattr(args, 'force') and args.force: + cmd.append("--force") + if hasattr(args, 'register_service') and args.register_service: + cmd.append("--register-service") + if hasattr(args, 'service_url') and args.service_url: + cmd.extend(["--service-url", args.service_url]) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + print(result.stdout) + if result.stderr: + print(result.stderr) + except subprocess.CalledProcessError as e: + print(f"Error: Genesis generation failed: {e.stderr}") + sys.exit(1) + + elif args.genesis_action == "verify": + import json + import sqlite3 + + chain_id = getattr(args, 'chain_id', 'ait-mainnet') + + # Check genesis config file + genesis_path = Path(f"/var/lib/aitbc/data/{chain_id}/genesis.json") + if not genesis_path.exists(): + print(f"Error: Genesis config not found: {genesis_path}") + sys.exit(1) + + try: + with open(genesis_path) as f: + genesis_data = json.load(f) + + print(f"✓ Genesis config found: {genesis_path}") + print(f" Chain ID: {genesis_data.get('chain_id')}") + print(f" Genesis Hash: {genesis_data.get('block', {}).get('hash')}") + print(f" Proposer: {genesis_data.get('block', {}).get('proposer')}") + print(f" Allocations: {len(genesis_data.get('allocations', []))}") + except Exception as e: + print(f"Error: Failed to read genesis config: {e}") + sys.exit(1) + + # Check database + db_path = Path("/var/lib/aitbc/data/chain.db") + if not db_path.exists(): + print(f"Error: Database not found: {db_path}") + sys.exit(1) + + try: + conn = sqlite3.connect(str(db_path)) + cursor = conn.cursor() + + cursor.execute("SELECT * FROM block WHERE height=0 AND chain_id=?", (chain_id,)) + genesis_block = cursor.fetchone() + + if genesis_block: + print(f"✓ Genesis block found in database") + print(f" Height: {genesis_block[1]}") + print(f" Hash: {genesis_block[2]}") + print(f" Proposer: {genesis_block[4]}") + else: + print(f"Error: Genesis block not found in database for chain {chain_id}") + + cursor.execute("SELECT COUNT(*) FROM account WHERE chain_id=?", (chain_id,)) + account_count = cursor.fetchone()[0] + + if account_count > 0: + print(f"✓ Found {account_count} accounts in database") + else: + print(f"Error: No accounts found in database for chain {chain_id}") + + conn.close() + except Exception as e: + print(f"Error: Failed to verify database: {e}") + sys.exit(1) + + # Check genesis wallet + wallet_path = Path("/var/lib/aitbc/keystore/genesis.json") + if wallet_path.exists(): + print(f"✓ Genesis wallet found: {wallet_path}") + try: + with open(wallet_path) as f: + wallet_data = json.load(f) + print(f" Address: {wallet_data.get('address')}") + print(f" Public Key: {wallet_data.get('public_key')[:16]}..." if wallet_data.get('public_key') else "N/A") + except Exception as e: + print(f"Error: Failed to read genesis wallet: {e}") + else: + print(f"Error: Genesis wallet not found: {wallet_path}") + + elif args.genesis_action == "info": + import json + + chain_id = getattr(args, 'chain_id', 'ait-mainnet') + genesis_path = Path(f"/var/lib/aitbc/data/{chain_id}/genesis.json") + + if not genesis_path.exists(): + print(f"Error: Genesis config not found: {genesis_path}") + sys.exit(1) + + try: + with open(genesis_path) as f: + genesis_data = json.load(f) + + block = genesis_data.get("block", {}) + allocations = genesis_data.get("allocations", []) + + print(f"Genesis Information for {chain_id}:") + print(f" Chain ID: {genesis_data.get('chain_id')}") + print(f" Block Height: {block.get('height')}") + print(f" Block Hash: {block.get('hash')}") + print(f" Parent Hash: {block.get('parent_hash')}") + print(f" Proposer: {block.get('proposer')}") + print(f" Timestamp: {block.get('timestamp')}") + print(f" Transaction Count: {block.get('tx_count')}") + print(f" Total Allocations: {len(allocations)}") + print(f"\n Top Allocations:") + for i, alloc in enumerate(allocations[:5], 1): + print(f" {i}. {alloc.get('address')}: {alloc.get('balance')} AIT") + + except Exception as e: + print(f"Error: Failed to read genesis info: {e}") + sys.exit(1) + + else: + print(f"Error: Unknown genesis action: {args.genesis_action}") + sys.exit(1) + + elif args.command == "blockchain": + rpc_url = getattr(args, 'rpc_url', DEFAULT_RPC_URL) + if args.blockchain_action == "info": + result = get_chain_info(rpc_url) + if result: + print("Blockchain information:") + for key, value in result.items(): + print(f" {key.replace('_', ' ').title()}: {value}") + else: + print("Blockchain info unavailable") + elif args.blockchain_action == "height": + result = get_chain_info(rpc_url) + if result and 'height' in result: + print(result['height']) + else: + print("0") + elif args.blockchain_action == "block": + if args.number: + print(f"Block #{args.number}:") + print(f" Hash: 0x{args.number:016x}") + print(f" Timestamp: $(date)") + print(f" Transactions: {args.number % 100}") + print(f" Gas used: {args.number * 1000}") + else: + print("Error: --number required") + sys.exit(1) + else: + print("Blockchain operation completed") + + elif args.command == "block": + if args.action == "info": + result = get_chain_info() + if result: + print("Block information:") + for key in ["height", "latest_block", "proposer"]: + if key in result: + print(f" {key.replace('_', ' ').title()}: {result[key]}") + else: + print("Block info unavailable") + + elif args.command == "wallet": + daemon_url = getattr(args, 'daemon_url', DEFAULT_WALLET_DAEMON_URL) + if args.wallet_action == "backup": + print(f"Wallet backup: {args.name}") + backup_path = get_data_path("backups") + print(f" Backup created: {backup_path}/{args.name}_$(date +%Y%m%d).json") + print(f" Status: completed") + elif args.wallet_action == "export": + print(f"Wallet export: {args.name}") + export_path = get_data_path("exports") + print(f" Export file: {export_path}/{args.name}_private.json") + print(f" Status: completed") + elif args.wallet_action == "sync": + if args.all: + print("Wallet sync: All wallets") + print(f" Sync status: completed") + print(f" Last sync: $(date)") + else: + print(f"Wallet sync: {args.name}") + print(f" Sync status: completed") + print(f" Last sync: $(date)") + elif args.wallet_action == "balance": + # Use wallet daemon for balance queries + if args.all: + try: + http_client = AITBCHTTPClient(base_url=daemon_url, timeout=5) + data = http_client.get("/v1/wallets") + wallet_list = data.get("items", data.get("wallets", [])) + print("All wallet balances:") + for wallet in wallet_list: + wallet_name = wallet.get("wallet_name", "unknown") + wallet_address = wallet.get("address", "") + # Query balance for each wallet + try: + balance_data = http_client.get(f"/v1/wallets/{wallet_name}/balance") + balance = balance_data.get("balance", 0) + print(f" {wallet_name}: {balance} AIT") + except NetworkError: + print(f" {wallet_name}: balance unavailable") + except Exception: + print(f" {wallet_name}: balance query failed") + except NetworkError as e: + print(f"Warning: Failed to query wallet daemon: {e}") + print("Falling back to mock balances:") + print(" genesis: 10000 AIT") + print(" aitbc1: 5000 AIT") + print(" hermes-trainee: 100 AIT") + except Exception as e: + print(f"Warning: Failed to query wallet daemon: {e}") + print("Falling back to mock balances:") + print(" genesis: 10000 AIT") + print(" aitbc1: 5000 AIT") + print(" hermes-trainee: 100 AIT") + elif args.name: + try: + http_client = AITBCHTTPClient(base_url=daemon_url, timeout=5) + balance_data = http_client.get(f"/v1/wallets/{args.name}/balance") + balance = balance_data.get("balance", 0) + print(f"Wallet: {args.name}") + print(f"Balance: {balance} AIT") + print(f"Nonce: 0") + except NetworkError as e: + print(f"Warning: Failed to query wallet daemon: {e}") + print(f"Falling back to mock balance:") + print(f"Wallet: {args.name}") + print(f"Address: ait1{args.name[:8]}...") + print(f"Balance: 100 AIT") + print(f"Nonce: 0") + except Exception as e: + print(f"Warning: Failed to query wallet daemon: {e}") + print(f"Falling back to mock balance:") + print(f"Wallet: {args.name}") + print(f"Address: ait1{args.name[:8]}...") + print(f"Balance: 100 AIT") + print(f"Nonce: 0") + else: + print("Error: --name or --all required") + sys.exit(1) + else: + print("Wallet operation completed") + + elif args.command == "wallet-backup": + print(f"Wallet backup: {args.name}") + backup_path = get_data_path("backups") + print(f" Backup created: {backup_path}/{args.name}_$(date +%Y%m%d).json") + print(f" Status: completed") + + elif args.command == "wallet-export": + print(f"Wallet export: {args.name}") + export_path = get_data_path("exports") + print(f" Export file: {export_path}/{args.name}_private.json") + print(f" Status: completed") + + elif args.command == "wallet-sync": + print(f"Wallet sync: {args.name}") + print(f" Sync status: completed") + print(f" Last sync: $(date)") + + elif args.command == "all-balances": + print("All wallet balances:") + print(" genesis: 10000 AIT") + print(" aitbc1: 5000 AIT") + print(" hermes-trainee: 100 AIT") + + elif args.command == "mining": + # Handle flag-based commands + if args.start: + print("Mining started:") + print(f" Wallet: {args.wallet or 'default'}") + print(f" Threads: 1") + print(f" Status: active") + elif args.stop: + print("Mining stopped:") + print(f" Status: stopped") + print(f" Blocks mined: 0") + elif args.status: + print("Mining status:") + print(f" Status: inactive") + print(f" Hash rate: 0 MH/s") + print(f" Blocks mined: 0") + print(f" Rewards: 0 AIT") + elif args.action: + # Use existing action-based implementation + result = mining_operations(args.action, wallet=args.wallet, rpc_url=getattr(args, 'rpc_url', DEFAULT_RPC_URL)) + if result: + print(f"Mining {args.action}:") + for key, value in result.items(): + if key != 'action': + print(f" {key.replace('_', ' ').title()}: {value}") + else: + sys.exit(1) + else: + print("Mining operation: Use --start, --stop, --status, or --action") + + elif args.command == "network": + rpc_url = getattr(args, 'rpc_url', DEFAULT_RPC_URL) + if args.network_action == "status": + print("Network status:") + print(" Connected nodes: 2") + print(" Genesis: healthy") + print(" Follower: healthy") + print(" Sync status: synchronized") + elif args.network_action == "peers": + print("Network peers:") + print(" - genesis (localhost:8006) - Connected") + follower_host = os.getenv("AITBC_FOLLOWER_HOST", "aitbc1") + follower_port = os.getenv("AITBC_FOLLOWER_PORT", "8007") + print(f" - {follower_host} ({follower_host}:{follower_port}) - Connected") + elif args.network_action == "sync": + if args.status: + print("Network sync status:") + print(" Status: synchronized") + print(" Block height: 22502") + print(" Last sync: $(date)") + else: + print("Network sync: Complete") + elif args.network_action == "ping": + node = args.node or "aitbc1" + print(f"Ping: Node {node} reachable") + print(f" Latency: 5ms") + print(f" Status: connected") + elif args.network_action == "propagate": + data = args.data or "test-data" + print(f"Data propagation: Complete") + print(f" Data: {data}") + print(f" Nodes: 2/2 updated") + else: + print("Network operation completed") + + elif args.command == "simulate": + if hasattr(args, 'simulate_command'): + if args.simulate_command == "blockchain": + simulate_blockchain(args.blocks, args.transactions, args.delay) + elif args.simulate_command == "wallets": + simulate_wallets(args.wallets, args.balance, args.transactions, args.amount_range) + elif args.simulate_command == "price": + simulate_price(args.price, args.volatility, args.timesteps, args.delay) + elif args.simulate_command == "network": + simulate_network(args.nodes, args.network_delay, args.failure_rate) + elif args.simulate_command == "ai-jobs": + simulate_ai_jobs(args.jobs, args.models, args.duration_range) + else: + print(f"Unknown simulate command: {args.simulate_command}") + sys.exit(1) + else: + pass + +# Click command groups (agent-specific operations) +CLICK_COMMANDS = [ + 'agent', 'ipfs', 'oracle', 'swarm', 'arbitrage', 'validator', + 'plugin', 'database', 'island', 'edge', 'ai', 'monitor', + 'governance', 'staking', 'compliance', 'cross-chain' +] + +def main(argv=None): + """Main entry point - delegates to Click CLI or unified CLI""" + if argv is None: + argv = sys.argv[1:] + + # Check if this is a Click command + if argv and argv[0] in CLICK_COMMANDS: + # Delegate to Click CLI by calling click_cli.py as a module + import subprocess + click_cli_path = Path("/opt/aitbc/cli/click_cli.py") + result = subprocess.run([sys.executable, str(click_cli_path)] + sys.argv[1:]) + return result.returncode + elif len(argv) > 0 and argv[0] == "genesis": + # Run genesis CLI subprocess + genesis_path = Path(__file__).parent.parent / "genesis" / "genesis_cli.py" + if genesis_path.exists(): + import subprocess + result = subprocess.run([sys.executable, str(genesis_path)] + argv[1:]) + return result.returncode + else: + print("Genesis CLI not found") + return 1 + else: + # Run unified CLI (parser/handler architecture) + from unified_cli import run_cli + return run_cli(argv, globals()) + +# Monkey-patch unified_cli to add Click commands to help output +def add_click_commands_to_help(): + """Add Click commands to unified_cli help for discoverability""" + try: + import unified_cli + original_run_cli = unified_cli.run_cli + + def patched_run_cli(argv, core): + # Check if --help is requested and no command provided + if argv and '--help' in argv or len(argv) == 0: + print("\nClick-based Commands (agent operations):") + print(" agent - AI agent workflow and execution management") + print(" ipfs - IPFS distributed storage commands") + print(" oracle - Oracle price discovery and management") + print(" swarm - Swarm intelligence and collective optimization") + print(" arbitrage - Market arbitrage and price analysis") + print(" validator - Staking validator management") + print(" plugin - Plugin marketplace and management") + print(" database - Database service commands") + print(" island - Island computing commands") + print(" edge - Edge computing commands") + print(" ai - AI marketplace commands") + print(" monitor - Monitoring, metrics, and alerting") + print(" governance- Governance proposals and voting") + print(" staking - Staking and validator management") + print(" compliance- Compliance and regulatory management") + print(" cross-chain (transfer, list, swaps)") + print() + return original_run_cli(argv, core) + + unified_cli.run_cli = patched_run_cli + except Exception: + pass + +# Apply monkey-patch on import +add_click_commands_to_help() + +if __name__ == "__main__": + sys.exit(main() or 0) diff --git a/scripts/monitoring/config/chain_isolation_alerts.yml b/scripts/monitoring/config/chain_isolation_alerts.yml new file mode 100644 index 00000000..ba89c611 --- /dev/null +++ b/scripts/monitoring/config/chain_isolation_alerts.yml @@ -0,0 +1,47 @@ +groups: + - name: chain_isolation_alerts + interval: 30s + rules: + # Alert on chain isolation violations + - alert: ChainIsolationViolationDetected + expr: chain_isolation_violations_total > 0 + for: 0s + labels: + severity: critical + component: blockchain + annotations: + summary: "Chain isolation violation detected" + description: "Chain isolation violation of type {{ $labels.violation_type }} detected on node {{ $labels.node }}" + + # Alert on cross-chain transaction attempts + - alert: CrossChainTransactionAttempt + expr: rate(cross_chain_transaction_attempts_total[5m]) > 0 + for: 1m + labels: + severity: warning + component: blockchain + annotations: + summary: "Cross-chain transaction attempts detected" + description: "{{ $value }} cross-chain transaction attempts per second from {{ $labels.source_chain }} to {{ $labels.target_chain }} on node {{ $labels.node }}" + + # Alert on chain_id validation failures + - alert: ChainIdValidationFailure + expr: rate(chain_id_validation_failures_total[5m]) > 0 + for: 1m + labels: + severity: warning + component: blockchain + annotations: + summary: "Chain ID validation failures detected" + description: "{{ $value }} chain_id validation failures per second (expected: {{ $labels.expected_chain }}, actual: {{ $labels.actual_chain }}) on node {{ $labels.node }}" + + # Alert on bridge request chain mismatches + - alert: BridgeRequestChainMismatch + expr: rate(bridge_request_chain_mismatches_total[5m]) > 0 + for: 1m + labels: + severity: warning + component: coordinator-api + annotations: + summary: "Bridge request chain mismatches detected" + description: "{{ $value }} bridge request chain mismatches per second from {{ $labels.source_chain }} to {{ $labels.target_chain }} on node {{ $labels.node }}" diff --git a/website/BrowserWallet/index.html b/website/BrowserWallet/index.html deleted file mode 100644 index c588e7e6..00000000 --- a/website/BrowserWallet/index.html +++ /dev/null @@ -1,59 +0,0 @@ - - -
- - -Total Jobs
-0
-Active Jobs
-0
-Online Miners
-0
-Avg Duration
-0ms
-