"""Cross-chain trading commands for AITBC CLI""" import click import httpx import json from typing import Optional from tabulate import tabulate from ..config import get_config from ..utils import success, error, output @click.group() def cross_chain(): """Cross-chain trading operations""" pass @cross_chain.command() @click.option("--from-chain", help="Source chain ID") @click.option("--to-chain", help="Target chain ID") @click.option("--from-token", help="Source token symbol") @click.option("--to-token", help="Target token symbol") @click.pass_context def rates(ctx, from_chain: Optional[str], to_chain: Optional[str], from_token: Optional[str], to_token: Optional[str]): """Get cross-chain exchange rates""" config = ctx.obj['config'] try: with httpx.Client() as client: # Get rates from cross-chain exchange response = client.get( f"http://localhost:8001/api/v1/cross-chain/rates", timeout=10 ) if response.status_code == 200: rates_data = response.json() rates = rates_data.get('rates', {}) if from_chain and to_chain: # Get specific rate pair_key = f"{from_chain}-{to_chain}" if pair_key in rates: success(f"Exchange rate {from_chain} → {to_chain}: {rates[pair_key]}") else: error(f"No rate available for {from_chain} → {to_chain}") else: # Show all rates success("Cross-chain exchange rates:") rate_table = [] for pair, rate in rates.items(): chains = pair.split('-') rate_table.append([chains[0], chains[1], f"{rate:.6f}"]) if rate_table: headers = ["From Chain", "To Chain", "Rate"] print(tabulate(rate_table, headers=headers, tablefmt="grid")) else: output("No cross-chain rates available") else: error(f"Failed to get cross-chain rates: {response.status_code}") except Exception as e: error(f"Network error: {e}") @cross_chain.command() @click.option("--from-chain", required=True, help="Source chain ID") @click.option("--to-chain", required=True, help="Target chain ID") @click.option("--from-token", required=True, help="Source token symbol") @click.option("--to-token", required=True, help="Target token symbol") @click.option("--amount", type=float, required=True, help="Amount to swap") @click.option("--min-amount", type=float, help="Minimum amount to receive") @click.option("--slippage", type=float, default=0.01, help="Slippage tolerance (0-0.1)") @click.option("--address", help="User wallet address") @click.pass_context def swap(ctx, from_chain: str, to_chain: str, from_token: str, to_token: str, amount: float, min_amount: Optional[float], slippage: float, address: Optional[str]): """Create cross-chain swap""" config = ctx.obj['config'] # Validate inputs if from_chain == to_chain: error("Source and target chains must be different") return if amount <= 0: error("Amount must be greater than 0") return # Use default address if not provided if not address: address = config.get('default_address', '0x1234567890123456789012345678901234567890') # Calculate minimum amount if not provided if not min_amount: # Get rate first try: with httpx.Client() as client: response = client.get( f"http://localhost:8001/api/v1/cross-chain/rates", timeout=10 ) if response.status_code == 200: rates_data = response.json() pair_key = f"{from_chain}-{to_chain}" rate = rates_data.get('rates', {}).get(pair_key, 1.0) min_amount = amount * rate * (1 - slippage) * 0.97 # Account for fees else: min_amount = amount * 0.95 # Conservative fallback except: min_amount = amount * 0.95 swap_data = { "from_chain": from_chain, "to_chain": to_chain, "from_token": from_token, "to_token": to_token, "amount": amount, "min_amount": min_amount, "user_address": address, "slippage_tolerance": slippage } try: with httpx.Client() as client: response = client.post( f"http://localhost:8001/api/v1/cross-chain/swap", json=swap_data, timeout=30 ) if response.status_code == 200: swap_result = response.json() success("Cross-chain swap created successfully!") output({ "Swap ID": swap_result.get('swap_id'), "From Chain": swap_result.get('from_chain'), "To Chain": swap_result.get('to_chain'), "Amount": swap_result.get('amount'), "Expected Amount": swap_result.get('expected_amount'), "Rate": swap_result.get('rate'), "Total Fees": swap_result.get('total_fees'), "Status": swap_result.get('status') }, ctx.obj['output_format']) # Show swap ID for tracking success(f"Track swap with: aitbc cross-chain status {swap_result.get('swap_id')}") else: error(f"Failed to create swap: {response.status_code}") if response.text: error(f"Details: {response.text}") except Exception as e: error(f"Network error: {e}") @cross_chain.command() @click.argument("swap_id") @click.pass_context def status(ctx, swap_id: str): """Check cross-chain swap status""" try: with httpx.Client() as client: response = client.get( f"http://localhost:8001/api/v1/cross-chain/swap/{swap_id}", timeout=10 ) if response.status_code == 200: swap_data = response.json() success(f"Swap Status: {swap_data.get('status', 'unknown')}") # Display swap details details = { "Swap ID": swap_data.get('swap_id'), "From Chain": swap_data.get('from_chain'), "To Chain": swap_data.get('to_chain'), "From Token": swap_data.get('from_token'), "To Token": swap_data.get('to_token'), "Amount": swap_data.get('amount'), "Expected Amount": swap_data.get('expected_amount'), "Actual Amount": swap_data.get('actual_amount'), "Status": swap_data.get('status'), "Created At": swap_data.get('created_at'), "Completed At": swap_data.get('completed_at'), "Bridge Fee": swap_data.get('bridge_fee'), "From Tx Hash": swap_data.get('from_tx_hash'), "To Tx Hash": swap_data.get('to_tx_hash') } output(details, ctx.obj['output_format']) # Show additional status info if swap_data.get('status') == 'completed': success("✅ Swap completed successfully!") elif swap_data.get('status') == 'failed': error("❌ Swap failed") if swap_data.get('error_message'): error(f"Error: {swap_data['error_message']}") elif swap_data.get('status') == 'pending': success("⏳ Swap is pending...") elif swap_data.get('status') == 'executing': success("🔄 Swap is executing...") elif swap_data.get('status') == 'refunded': success("💰 Swap was refunded") else: error(f"Failed to get swap status: {response.status_code}") except Exception as e: error(f"Network error: {e}") @cross_chain.command() @click.option("--user-address", help="Filter by user address") @click.option("--status", help="Filter by status") @click.option("--limit", type=int, default=10, help="Number of swaps to show") @click.pass_context def swaps(ctx, user_address: Optional[str], status: Optional[str], limit: int): """List cross-chain swaps""" params = {} if user_address: params['user_address'] = user_address if status: params['status'] = status try: with httpx.Client() as client: response = client.get( f"http://localhost:8001/api/v1/cross-chain/swaps", params=params, timeout=10 ) if response.status_code == 200: swaps_data = response.json() swaps = swaps_data.get('swaps', []) if swaps: success(f"Found {len(swaps)} cross-chain swaps:") # Create table swap_table = [] for swap in swaps[:limit]: swap_table.append([ swap.get('swap_id', '')[:8] + '...', swap.get('from_chain', ''), swap.get('to_chain', ''), swap.get('amount', 0), swap.get('status', ''), swap.get('created_at', '')[:19] ]) table(["ID", "From", "To", "Amount", "Status", "Created"], swap_table) if len(swaps) > limit: success(f"Showing {limit} of {len(swaps)} total swaps") else: success("No cross-chain swaps found") else: error(f"Failed to get swaps: {response.status_code}") except Exception as e: error(f"Network error: {e}") @cross_chain.command() @click.option("--source-chain", required=True, help="Source chain ID") @click.option("--target-chain", required=True, help="Target chain ID") @click.option("--token", required=True, help="Token to bridge") @click.option("--amount", type=float, required=True, help="Amount to bridge") @click.option("--recipient", help="Recipient address") @click.pass_context def bridge(ctx, source_chain: str, target_chain: str, token: str, amount: float, recipient: Optional[str]): """Create cross-chain bridge transaction""" config = ctx.obj['config'] # Validate inputs if source_chain == target_chain: error("Source and target chains must be different") return if amount <= 0: error("Amount must be greater than 0") return # Use default recipient if not provided if not recipient: recipient = config.get('default_address', '0x1234567890123456789012345678901234567890') bridge_data = { "source_chain": source_chain, "target_chain": target_chain, "token": token, "amount": amount, "recipient_address": recipient } try: with httpx.Client() as client: response = client.post( f"http://localhost:8001/api/v1/cross-chain/bridge", json=bridge_data, timeout=30 ) if response.status_code == 200: bridge_result = response.json() success("Cross-chain bridge created successfully!") output({ "Bridge ID": bridge_result.get('bridge_id'), "Source Chain": bridge_result.get('source_chain'), "Target Chain": bridge_result.get('target_chain'), "Token": bridge_result.get('token'), "Amount": bridge_result.get('amount'), "Bridge Fee": bridge_result.get('bridge_fee'), "Status": bridge_result.get('status') }, ctx.obj['output_format']) # Show bridge ID for tracking success(f"Track bridge with: aitbc cross-chain bridge-status {bridge_result.get('bridge_id')}") else: error(f"Failed to create bridge: {response.status_code}") if response.text: error(f"Details: {response.text}") except Exception as e: error(f"Network error: {e}") @cross_chain.command() @click.argument("bridge_id") @click.pass_context def bridge_status(ctx, bridge_id: str): """Check cross-chain bridge status""" try: with httpx.Client() as client: response = client.get( f"http://localhost:8001/api/v1/cross-chain/bridge/{bridge_id}", timeout=10 ) if response.status_code == 200: bridge_data = response.json() success(f"Bridge Status: {bridge_data.get('status', 'unknown')}") # Display bridge details details = { "Bridge ID": bridge_data.get('bridge_id'), "Source Chain": bridge_data.get('source_chain'), "Target Chain": bridge_data.get('target_chain'), "Token": bridge_data.get('token'), "Amount": bridge_data.get('amount'), "Recipient Address": bridge_data.get('recipient_address'), "Status": bridge_data.get('status'), "Created At": bridge_data.get('created_at'), "Completed At": bridge_data.get('completed_at'), "Bridge Fee": bridge_data.get('bridge_fee'), "Source Tx Hash": bridge_data.get('source_tx_hash'), "Target Tx Hash": bridge_data.get('target_tx_hash') } output(details, ctx.obj['output_format']) # Show additional status info if bridge_data.get('status') == 'completed': success("✅ Bridge completed successfully!") elif bridge_data.get('status') == 'failed': error("❌ Bridge failed") if bridge_data.get('error_message'): error(f"Error: {bridge_data['error_message']}") elif bridge_data.get('status') == 'pending': success("⏳ Bridge is pending...") elif bridge_data.get('status') == 'locked': success("🔒 Bridge is locked...") elif bridge_data.get('status') == 'transferred': success("🔄 Bridge is transferring...") else: error(f"Failed to get bridge status: {response.status_code}") except Exception as e: error(f"Network error: {e}") @cross_chain.command() @click.pass_context def pools(ctx): """Show cross-chain liquidity pools""" try: with httpx.Client() as client: response = client.get( f"http://localhost:8001/api/v1/cross-chain/pools", timeout=10 ) if response.status_code == 200: pools_data = response.json() pools = pools_data.get('pools', []) if pools: success(f"Found {len(pools)} cross-chain liquidity pools:") # Create table pool_table = [] for pool in pools: pool_table.append([ pool.get('pool_id', ''), pool.get('token_a', ''), pool.get('token_b', ''), pool.get('chain_a', ''), pool.get('chain_b', ''), f"{pool.get('reserve_a', 0):.2f}", f"{pool.get('reserve_b', 0):.2f}", f"{pool.get('total_liquidity', 0):.2f}", f"{pool.get('apr', 0):.2%}" ]) table(["Pool ID", "Token A", "Token B", "Chain A", "Chain B", "Reserve A", "Reserve B", "Liquidity", "APR"], pool_table) else: success("No cross-chain liquidity pools found") else: error(f"Failed to get pools: {response.status_code}") except Exception as e: error(f"Network error: {e}") @cross_chain.command() @click.pass_context def stats(ctx): """Show cross-chain trading statistics""" try: with httpx.Client() as client: response = client.get( f"http://localhost:8001/api/v1/cross-chain/stats", timeout=10 ) if response.status_code == 200: stats_data = response.json() success("Cross-Chain Trading Statistics:") # Show swap stats swap_stats = stats_data.get('swap_stats', []) if swap_stats: success("Swap Statistics:") swap_table = [] for stat in swap_stats: swap_table.append([ stat.get('status', ''), stat.get('count', 0), f"{stat.get('volume', 0):.2f}" ]) table(["Status", "Count", "Volume"], swap_table) # Show bridge stats bridge_stats = stats_data.get('bridge_stats', []) if bridge_stats: success("Bridge Statistics:") bridge_table = [] for stat in bridge_stats: bridge_table.append([ stat.get('status', ''), stat.get('count', 0), f"{stat.get('volume', 0):.2f}" ]) table(["Status", "Count", "Volume"], bridge_table) # Show overall stats success("Overall Statistics:") output({ "Total Volume": f"{stats_data.get('total_volume', 0):.2f}", "Supported Chains": ", ".join(stats_data.get('supported_chains', [])), "Last Updated": stats_data.get('timestamp', '') }, ctx.obj['output_format']) else: error(f"Failed to get stats: {response.status_code}") except Exception as e: error(f"Network error: {e}")