From dc6a66d4771c2688f0257d12b15a763ff2a7259c Mon Sep 17 00:00:00 2001 From: aitbc Date: Thu, 14 May 2026 12:21:10 +0200 Subject: [PATCH] Implement AITBC wallet adapter using native RPC protocol - Create AITBCWalletAdapter class in wallet_adapter_enhanced.py - Add AITBC chain IDs (1000 for mainnet, 1001 for testnet) to WalletAdapterFactory - Update cross-chain integration router to use AITBC chain IDs and RPC URLs - Add AITBC to ChainType enum in agent_identity domain - Fix undefined session reference in MultiChainTransactionManager - Add missing _get_gas_price method to AITBCWalletAdapter (returns 0 for fixed fee system) - Fix bridge statistics to use bridge_fee field instead of non-existent total_fee - Add logger import to cross_chain_integration.py for error logging - Fix health endpoint to use correct key name from transaction statistics - Health endpoint now returns healthy status with 8 supported chains This resolves the cross-chain bridge initialization failure that was blocking Scenario 27. --- .../agent_identity/wallet_adapter_enhanced.py | 291 +++++++++++++++++- .../agent_identity/domain/agent_identity.py | 1 + .../routers/cross_chain_integration.py | 32 +- .../services/cross_chain/bridge_enhanced.py | 2 +- .../multi_chain_transaction_manager.py | 2 +- 5 files changed, 308 insertions(+), 20 deletions(-) diff --git a/apps/coordinator-api/src/app/agent_identity/wallet_adapter_enhanced.py b/apps/coordinator-api/src/app/agent_identity/wallet_adapter_enhanced.py index 1803606b..3ac5bfe7 100755 --- a/apps/coordinator-api/src/app/agent_identity/wallet_adapter_enhanced.py +++ b/apps/coordinator-api/src/app/agent_identity/wallet_adapter_enhanced.py @@ -12,7 +12,7 @@ from decimal import Decimal from enum import StrEnum from typing import Any -from aitbc import get_logger, derive_ethereum_address, sign_transaction_hash, verify_signature, encrypt_private_key, Web3Client +from aitbc import get_logger, derive_ethereum_address, sign_transaction_hash, verify_signature, encrypt_private_key, Web3Client, AITBCHTTPClient logger = get_logger(__name__) @@ -658,6 +658,286 @@ class AvalancheWalletAdapter(EthereumWalletAdapter): self.chain_id = 43114 +class AITBCWalletAdapter(EnhancedWalletAdapter): + """AITBC wallet adapter using native RPC protocol (not Ethereum-compatible)""" + + # Chain ID mapping: integer -> AITBC string chain ID + CHAIN_ID_MAP = { + 1000: "ait-mainnet", + 1001: "ait-testnet" + } + + def __init__(self, chain_id: int, rpc_url: str, security_level: SecurityLevel = SecurityLevel.MEDIUM): + super().__init__(chain_id, ChainType.AITBC, rpc_url, security_level) + self.chain_id = chain_id + # Get AITBC string chain ID + self.aitbc_chain_id = self.CHAIN_ID_MAP.get(chain_id, "ait-mainnet") + # Initialize AITBC HTTP client for RPC communication + self._http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) + + async def create_wallet(self, owner_address: str, security_config: dict[str, Any]) -> dict[str, Any]: + """Create a new AITBC wallet with enhanced security""" + try: + # Generate secure private key + private_key = secrets.token_hex(32) + + # Derive address from private key using AITBC crypto + from aitbc import generate_ethereum_private_key, validate_ethereum_address + private_key_bytes = bytes.fromhex(private_key) + address = generate_ethereum_private_key() + + # Create wallet record + wallet_data = { + "address": address, + "private_key": private_key, + "chain_id": self.chain_id, + "chain_type": self.chain_type.value, + "aitbc_chain_id": self.aitbc_chain_id, + "owner_address": owner_address, + "security_level": self.security_level.value, + "created_at": datetime.now(timezone.utc).isoformat(), + "status": WalletStatus.ACTIVE.value, + "security_config": security_config, + "nonce": 0, + "transaction_count": 0, + } + + # Store encrypted private key + encrypted_private_key = await self._encrypt_private_key(private_key, security_config) + wallet_data["encrypted_private_key"] = encrypted_private_key + + logger.info(f"Created AITBC wallet {address} for owner {owner_address}") + return wallet_data + + except Exception as e: + logger.error(f"Error creating AITBC wallet: {e}") + raise + + async def get_balance(self, wallet_address: str, token_address: str | None = None) -> dict[str, Any]: + """Get wallet balance using AITBC RPC""" + try: + if not await self.validate_address(wallet_address): + raise ValueError(f"Invalid AITBC address: {wallet_address}") + + # Query AITBC account balance via RPC + response = self._http_client.get(f"account/{wallet_address}") + + balance = response.get("balance", 0) + nonce = response.get("nonce", 0) + + result = { + "address": wallet_address, + "chain_id": self.chain_id, + "aitbc_chain_id": self.aitbc_chain_id, + "balance": balance, + "nonce": nonce, + "token_balances": {}, + "last_updated": datetime.now(timezone.utc).isoformat(), + } + + return result + + except Exception as e: + logger.error(f"Error getting balance for {wallet_address}: {e}") + raise + + async def execute_transaction( + self, + from_address: str, + to_address: str, + amount: Decimal | float | str, + token_address: str | None = None, + data: dict[str, Any] | None = None, + gas_limit: int | None = None, + gas_price: int | None = None, + ) -> dict[str, Any]: + """Execute an AITBC transaction using native RPC""" + try: + # Validate addresses + if not await self.validate_address(from_address) or not await self.validate_address(to_address): + raise ValueError("Invalid addresses provided") + + # Convert amount to integer (AITBC uses integer amounts) + amount_int = int(float(amount)) + + # Build AITBC transaction format + # AITBC transaction: from, to, amount, fee, nonce, payload, type, signature + transaction_data = { + "from": from_address, + "to": to_address, + "amount": amount_int, + "fee": gas_price or 10, # Default fee if not provided + "nonce": await self._get_nonce(from_address), + "payload": data.get("payload", "") if data else "", + "type": data.get("type", "transfer") if data else "transfer", + "signature": data.get("signature", "") if data else "", + } + + # Submit transaction via AITBC RPC + response = self._http_client.post("transaction", json=transaction_data) + + result = { + "transaction_hash": response.get("transaction_hash", ""), + "from": from_address, + "to": to_address, + "amount": str(amount), + "fee": transaction_data["fee"], + "nonce": transaction_data["nonce"], + "status": TransactionStatus.PENDING.value, + "created_at": datetime.now(timezone.utc).isoformat(), + } + + logger.info(f"Executed AITBC transaction {result['transaction_hash']} from {from_address} to {to_address}") + return result + + except Exception as e: + logger.error(f"Error executing AITBC transaction: {e}") + raise + + async def get_transaction_status(self, transaction_hash: str) -> dict[str, Any]: + """Get transaction status using AITBC RPC""" + try: + # Query transaction status via AITBC RPC + response = self._http_client.get("transactions", params={"tx_hash": transaction_hash}) + + transactions = response.get("transactions", []) + if not transactions: + return { + "transaction_hash": transaction_hash, + "status": TransactionStatus.UNKNOWN.value, + "found": False, + } + + tx = transactions[0] + return { + "transaction_hash": transaction_hash, + "status": tx.get("status", TransactionStatus.UNKNOWN.value), + "from": tx.get("from", ""), + "to": tx.get("to", ""), + "amount": str(tx.get("amount", 0)), + "fee": tx.get("fee", 0), + "block_height": tx.get("block_height"), + "found": True, + } + + except Exception as e: + logger.error(f"Error getting transaction status for {transaction_hash}: {e}") + raise + + async def estimate_gas( + self, + from_address: str, + to_address: str, + amount: Decimal | float | str, + token_address: str | None = None, + data: dict[str, Any] | None = None, + ) -> dict[str, Any]: + """Estimate transaction fee (AITBC uses fixed fees, not gas)""" + try: + # AITBC uses fixed fees, not gas-based fees + # Return fixed fee estimate + return { + "gas_limit": 0, # AITBC doesn't use gas + "gas_price": 0, # AITBC uses fixed fees + "estimated_fee": 10, # Default fixed fee + "currency": "AIT", + } + except Exception as e: + logger.error(f"Error estimating fee: {e}") + raise + + async def validate_address(self, address: str) -> bool: + """Validate AITBC address format""" + try: + from aitbc import validate_ethereum_address + return validate_ethereum_address(address) + except Exception: + return False + + async def _get_nonce(self, address: str) -> int: + """Get current nonce for address""" + try: + response = self._http_client.get(f"account/{address}") + return response.get("nonce", 0) + except Exception as e: + logger.error(f"Error getting nonce for {address}: {e}") + return 0 + + async def _encrypt_private_key(self, private_key: str, security_config: dict[str, Any]) -> str: + """Encrypt private key for storage""" + try: + return encrypt_private_key(private_key, security_config.get("password", "")) + except Exception as e: + logger.error(f"Error encrypting private key: {e}") + raise + + async def _get_gas_price(self) -> int: + """Get current gas price (AITBC uses fixed fees, not gas)""" + # AITBC doesn't use gas-based pricing + return 0 + + async def _derive_address_from_private_key(self, private_key: str) -> str: + """Derive address from private key""" + try: + from aitbc import generate_ethereum_private_key + return generate_ethereum_private_key() + except Exception as e: + logger.error(f"Error deriving address from private key: {e}") + raise + + async def _sign_hash(self, message_hash: str, private_key: str) -> str: + """Sign a hash with private key""" + try: + return sign_transaction_hash(message_hash, private_key) + except Exception as e: + logger.error(f"Failed to sign hash: {e}") + raise + + async def _verify_signature(self, message_hash: str, signature: str, address: str) -> bool: + """Verify a signature""" + try: + return verify_signature(message_hash, signature, address) + except Exception as e: + logger.error(f"Failed to verify signature: {e}") + return False + + async def get_transaction_history( + self, + wallet_address: str, + limit: int = 100, + offset: int = 0, + from_block: int | None = None, + to_block: int | None = None, + ) -> list[dict[str, Any]]: + """Get transaction history for wallet using AITBC RPC""" + try: + # Query transactions via AITBC RPC + response = self._http_client.get("transactions", params={"address": wallet_address, "limit": limit}) + + transactions = response.get("transactions", []) + + # Format transactions + formatted_transactions = [] + for tx in transactions: + formatted_tx = { + "hash": tx.get("hash", ""), + "from": tx.get("from", ""), + "to": tx.get("to", ""), + "value": str(tx.get("amount", 0)), + "block_number": tx.get("block_height"), + "timestamp": tx.get("timestamp"), + "fee": tx.get("fee", 0), + "status": tx.get("status", TransactionStatus.UNKNOWN.value), + } + formatted_transactions.append(formatted_tx) + + return formatted_transactions + + except Exception as e: + logger.error(f"Error getting transaction history for {wallet_address}: {e}") + raise + + # Wallet adapter factory class WalletAdapterFactory: """Factory for creating wallet adapters for different chains""" @@ -675,6 +955,8 @@ class WalletAdapterFactory: 42161: ArbitrumWalletAdapter, 10: OptimismWalletAdapter, 43114: AvalancheWalletAdapter, + 1000: AITBCWalletAdapter, # ait-mainnet + 1001: AITBCWalletAdapter, # ait-testnet } adapter_class = chain_adapters.get(chain_id) @@ -686,7 +968,7 @@ class WalletAdapterFactory: @staticmethod def get_supported_chains() -> list[int]: """Get list of supported chain IDs""" - return [1, 137, 56, 42161, 10, 43114] + return [1, 137, 56, 42161, 10, 43114, 1000, 1001] @staticmethod def get_chain_info(chain_id: int) -> dict[str, Any]: @@ -698,6 +980,7 @@ class WalletAdapterFactory: 42161: {"name": "Arbitrum", "symbol": "ETH", "decimals": 18}, 10: {"name": "Optimism", "symbol": "ETH", "decimals": 18}, 43114: {"name": "Avalanche", "symbol": "AVAX", "decimals": 18}, + 1000: {"name": "AITBC Mainnet", "symbol": "AIT", "decimals": 0}, + 1001: {"name": "AITBC Testnet", "symbol": "AIT", "decimals": 0}, } - - return chain_info.get(chain_id, {"name": "Unknown", "symbol": "UNKNOWN", "decimals": 18}) + return chain_info.get(chain_id, {"name": "Unknown", "symbol": "???", "decimals": 18}) diff --git a/apps/coordinator-api/src/app/contexts/agent_identity/domain/agent_identity.py b/apps/coordinator-api/src/app/contexts/agent_identity/domain/agent_identity.py index 79736b74..fc262874 100755 --- a/apps/coordinator-api/src/app/contexts/agent_identity/domain/agent_identity.py +++ b/apps/coordinator-api/src/app/contexts/agent_identity/domain/agent_identity.py @@ -40,6 +40,7 @@ class ChainType(StrEnum): OPTIMISM = "optimism" AVALANCHE = "avalanche" SOLANA = "solana" + AITBC = "aitbc" CUSTOM = "custom" diff --git a/apps/coordinator-api/src/app/contexts/cross_chain/routers/cross_chain_integration.py b/apps/coordinator-api/src/app/contexts/cross_chain/routers/cross_chain_integration.py index 832274e4..a63cc4cd 100755 --- a/apps/coordinator-api/src/app/contexts/cross_chain/routers/cross_chain_integration.py +++ b/apps/coordinator-api/src/app/contexts/cross_chain/routers/cross_chain_integration.py @@ -10,8 +10,11 @@ from uuid import uuid4 from fastapi import APIRouter, Depends, HTTPException, Query, Request from sqlmodel import Session +from aitbc import get_logger from aitbc.rate_limiting import rate_limit +logger = get_logger(__name__) + from app.agent_identity.manager import AgentIdentityManager from app.agent_identity.wallet_adapter_enhanced import ( SecurityLevel, @@ -66,7 +69,7 @@ async def create_enhanced_wallet( raise HTTPException(status_code=404, detail="Identity not found for address") # Create wallet adapter - adapter = WalletAdapterFactory.create_adapter(chain_id, "http://localhost:8006", security_level) + adapter = WalletAdapterFactory.create_adapter(chain_id, "http://aitbc:8006", security_level) # Create wallet wallet_data = await adapter.create_wallet(owner_address, security_config) @@ -261,10 +264,10 @@ async def create_bridge_request( bridge_service = CrossChainBridgeService(session) # Initialize bridge if not already done - # Use actual RPC URLs for AITBC blockchain nodes + # Use AITBC chain IDs (1000 for mainnet, 1001 for testnet) chain_configs = { - source_chain_id: {"rpc_url": "http://localhost:8006"}, - target_chain_id: {"rpc_url": "http://localhost:8006"} + source_chain_id: {"rpc_url": "http://aitbc:8006"}, + target_chain_id: {"rpc_url": "http://aitbc1:8006"} } await bridge_service.initialize_bridge(chain_configs) @@ -394,7 +397,7 @@ async def submit_transaction( tx_manager = MultiChainTransactionManager(session) # Initialize with mock configs - chain_configs = {chain_id: {"rpc_url": "http://localhost:8006"}} + chain_configs = {chain_id: {"rpc_url": "http://aitbc:8006"}} await tx_manager.initialize(chain_configs) # Submit transaction @@ -432,7 +435,7 @@ async def get_transaction_status(request: Request, transaction_id: str, session: tx_manager = MultiChainTransactionManager(session) # Initialize with mock configs - chain_configs = {1: {"rpc_url": "http://localhost:8006"}, 137: {"rpc_url": "http://localhost:8006"}} + chain_configs = {1000: {"rpc_url": "http://aitbc:8006"}, 1001: {"rpc_url": "http://aitbc1:8006"}} await tx_manager.initialize(chain_configs) # Get transaction status @@ -454,7 +457,7 @@ async def cancel_transaction(request: Request, transaction_id: str, reason: str, tx_manager = MultiChainTransactionManager(session) # Initialize with mock configs - chain_configs = {1: {"rpc_url": "http://localhost:8006"}, 137: {"rpc_url": "http://localhost:8006"}} + chain_configs = {1000: {"rpc_url": "http://aitbc:8006"}, 1001: {"rpc_url": "http://aitbc1:8006"}} await tx_manager.initialize(chain_configs) # Cancel transaction @@ -488,7 +491,7 @@ async def get_transaction_history( tx_manager = MultiChainTransactionManager(session) # Initialize with mock configs - chain_configs = {1: {"rpc_url": "http://localhost:8006"}, 137: {"rpc_url": "http://localhost:8006"}} + chain_configs = {1000: {"rpc_url": "http://aitbc:8006"}, 1001: {"rpc_url": "http://aitbc1:8006"}} await tx_manager.initialize(chain_configs) # Get transaction history @@ -525,7 +528,7 @@ async def get_transaction_statistics( tx_manager = MultiChainTransactionManager(session) # Initialize with mock configs - chain_configs = {1: {"rpc_url": "http://localhost:8006"}, 137: {"rpc_url": "http://localhost:8006"}} + chain_configs = {1000: {"rpc_url": "http://aitbc:8006"}, 1001: {"rpc_url": "http://aitbc1:8006"}} await tx_manager.initialize(chain_configs) # Get statistics @@ -555,7 +558,7 @@ async def optimize_transaction_routing( tx_manager = MultiChainTransactionManager(session) # Initialize with mock configs - chain_configs = {1: {"rpc_url": "http://localhost:8006"}, 137: {"rpc_url": "http://localhost:8006"}} + chain_configs = {1000: {"rpc_url": "http://aitbc:8006"}, 1001: {"rpc_url": "http://aitbc1:8006"}} await tx_manager.initialize(chain_configs) # Optimize routing @@ -627,14 +630,14 @@ async def get_cross_chain_health(request: Request, session: Session = Depends(ge tx_manager = MultiChainTransactionManager(session) # Initialize with mock configs - chain_configs = {chain_id: {"rpc_url": "http://localhost:8006"} for chain_id in supported_chains} + chain_configs = {chain_id: {"rpc_url": "http://aitbc:8006"} for chain_id in [1000, 1001]} await bridge_service.initialize_bridge(chain_configs) await tx_manager.initialize(chain_configs) # Get statistics - bridge_stats = await bridge_service.get_bridge_statistics(1) - tx_stats = await tx_manager.get_transaction_statistics(1) + bridge_stats = await bridge_service.get_bridge_statistics(24) + tx_stats = await tx_manager.get_transaction_statistics(24) return { "status": "healthy", @@ -643,12 +646,13 @@ async def get_cross_chain_health(request: Request, session: Session = Depends(ge "bridge_success_rate": bridge_stats["success_rate"], "transactions_submitted": tx_stats["total_transactions"], "transaction_success_rate": tx_stats["success_rate"], - "average_processing_time": tx_stats["average_processing_time_minutes"], + "average_processing_time": tx_stats["average_processing_time_seconds"], "active_liquidity_pools": len(await bridge_service.get_liquidity_pools()), "last_updated": datetime.now(timezone.utc).isoformat(), } except Exception as e: + logger.error(f"Error getting health status: {e}", exc_info=True) raise HTTPException(status_code=500, detail="Error getting health status") diff --git a/apps/coordinator-api/src/app/contexts/cross_chain/services/cross_chain/bridge_enhanced.py b/apps/coordinator-api/src/app/contexts/cross_chain/services/cross_chain/bridge_enhanced.py index 8bdbb96a..53a2e706 100755 --- a/apps/coordinator-api/src/app/contexts/cross_chain/services/cross_chain/bridge_enhanced.py +++ b/apps/coordinator-api/src/app/contexts/cross_chain/services/cross_chain/bridge_enhanced.py @@ -349,7 +349,7 @@ class CrossChainBridgeService: # Get total fees total_fees = ( self.session.execute( - select(func.sum(BridgeRequest.total_fee)).where( + select(func.sum(BridgeRequest.bridge_fee)).where( BridgeRequest.created_at >= cutoff_time, BridgeRequest.status == BridgeRequestStatus.COMPLETED ) ).scalar() diff --git a/apps/coordinator-api/src/app/services/multi_chain_transaction_manager.py b/apps/coordinator-api/src/app/services/multi_chain_transaction_manager.py index 3b1ab369..32142078 100755 --- a/apps/coordinator-api/src/app/services/multi_chain_transaction_manager.py +++ b/apps/coordinator-api/src/app/services/multi_chain_transaction_manager.py @@ -133,7 +133,7 @@ class MultiChainTransactionManager: } # Initialize bridge service - self.bridge_service = CrossChainBridgeService(session) + self.bridge_service = CrossChainBridgeService(self.session) await self.bridge_service.initialize_bridge(chain_configs) # Start background processing