diff --git a/apps/blockchain-node/src/aitbc_chain/rpc/router.py b/apps/blockchain-node/src/aitbc_chain/rpc/router.py index 92ee2f01..77764257 100755 --- a/apps/blockchain-node/src/aitbc_chain/rpc/router.py +++ b/apps/blockchain-node/src/aitbc_chain/rpc/router.py @@ -18,6 +18,7 @@ from ..models import Account, Block, Receipt, Transaction from ..logger import get_logger from ..sync import ChainSync from ..contracts.agent_messaging_contract import messaging_contract +from .contract_service import contract_service _logger = get_logger(__name__) @@ -636,6 +637,71 @@ async def deploy_messaging_contract(deploy_data: dict) -> Dict[str, Any]: contract_address = "0xagent_messaging_001" return {"success": True, "contract_address": contract_address, "status": "deployed"} +@router.get("/contracts", summary="List deployed contracts") +async def list_contracts() -> Dict[str, Any]: + """List all deployed contracts""" + return contract_service.list_contracts() + +@router.post("/contracts/deploy", summary="Deploy a smart contract") +async def deploy_contract(deploy_data: dict) -> Dict[str, Any]: + """Deploy a new smart contract to the blockchain""" + contract_name = deploy_data.get("name") + contract_type = deploy_data.get("type", "zk-verifier") + + if not contract_name: + return {"success": False, "error": "Contract name is required"} + + # Generate a mock contract address for now + contract_address = f"0x{contract_name.lower()}_{int(time.time())}" + + return { + "success": True, + "contract_address": contract_address, + "name": contract_name, + "type": contract_type, + "status": "deployed", + "deployed_at": datetime.now(UTC).isoformat() + } + +@router.post("/contracts/call", summary="Call a contract method") +async def call_contract(call_data: dict) -> Dict[str, Any]: + """Call a method on a deployed contract""" + contract_address = call_data.get("address") + method = call_data.get("method") + params = call_data.get("params") + + if not contract_address: + return {"success": False, "error": "Contract address is required"} + if not method: + return {"success": False, "error": "Method name is required"} + + # Mock call result for now + return { + "success": True, + "result": f"Called {method} on {contract_address}", + "address": contract_address, + "method": method + } + +@router.post("/contracts/verify", summary="Verify a ZK proof") +async def verify_contract(verify_data: dict) -> Dict[str, Any]: + """Verify a ZK proof against a contract""" + contract_address = verify_data.get("address") + proof = verify_data.get("proof") + + if not contract_address: + return {"success": False, "error": "Contract address is required"} + + # Mock verification result for now + return { + "success": True, + "result": { + "valid": True, + "receipt_hash": "0xmock_receipt_hash", + "address": contract_address + } + } + @router.get("/contracts/messaging/state", summary="Get messaging contract state") async def get_messaging_contract_state() -> Dict[str, Any]: """Get the current state of the messaging contract""" diff --git a/cli/handlers/contract.py b/cli/handlers/contract.py new file mode 100644 index 00000000..d9eda482 --- /dev/null +++ b/cli/handlers/contract.py @@ -0,0 +1,175 @@ +"""Contract command handlers for AITBC CLI""" + +import requests +from typing import Optional, Dict, Any + + +def handle_contract_list(args, default_rpc_url: str): + """Handle contract list command""" + rpc_url = args.rpc_url if hasattr(args, 'rpc_url') and args.rpc_url else default_rpc_url + + try: + response = requests.get(f"{rpc_url}/rpc/contracts", timeout=30) + if response.status_code == 200: + data = response.json() + # Handle both response formats: with or without "success" field + if data.get("success") is not False: + contracts = data.get("contracts", []) + if contracts: + print(f"Deployed contracts ({len(contracts)}):") + for contract in contracts: + print(f" - Address: {contract.get('address', 'N/A')}") + print(f" Type: {contract.get('type', 'N/A')}") + print(f" Deployed: {contract.get('deployed_at', 'N/A')}") + else: + print("No contracts deployed") + else: + print(f"Error: {data.get('error', 'Unknown error')}") + else: + print(f"Error: RPC returned {response.status_code}") + except Exception as e: + print(f"Error listing contracts: {e}") + + +def handle_contract_deploy(args, default_rpc_url: str, read_password, render_mapping): + """Handle contract deploy command""" + rpc_url = args.rpc_url if hasattr(args, 'rpc_url') and args.rpc_url else default_rpc_url + contract_name = getattr(args, 'name', None) + contract_type = getattr(args, 'type', 'zk-verifier') + + if not contract_name: + print("Error: Contract name is required (--name)") + return + + password = read_password(args) + if not password: + print("Error: Wallet password is required (--password or --password-file)") + return + + try: + payload = { + "name": contract_name, + "type": contract_type + } + + response = requests.post( + f"{rpc_url}/rpc/contracts/deploy", + json=payload, + headers={"X-Wallet-Password": password}, + timeout=60 + ) + + if response.status_code == 200: + data = response.json() + if data.get("success"): + render_mapping("Contract deployed successfully", data) + else: + print(f"Error: {data.get('error', 'Unknown error')}") + else: + print(f"Error: RPC returned {response.status_code}") + except Exception as e: + print(f"Error deploying contract: {e}") + + +def handle_contract_call(args, default_rpc_url: str, read_password): + """Handle contract call command""" + rpc_url = args.rpc_url if hasattr(args, 'rpc_url') and args.rpc_url else default_rpc_url + contract_address = getattr(args, 'address', None) + method = getattr(args, 'method', None) + + if not contract_address: + print("Error: Contract address is required (--address)") + return + + if not method: + print("Error: Method name is required (--method)") + return + + password = read_password(args) + + try: + payload = { + "address": contract_address, + "method": method + } + + # Add optional parameters + if hasattr(args, 'params') and args.params: + payload["params"] = args.params + + headers = {} + if password: + headers["X-Wallet-Password"] = password + + response = requests.post( + f"{rpc_url}/rpc/contracts/call", + json=payload, + headers=headers, + timeout=60 + ) + + if response.status_code == 200: + data = response.json() + if data.get("success"): + result = data.get("result") + print(f"Contract call result:") + print(f" Address: {contract_address}") + print(f" Method: {method}") + print(f" Result: {result}") + else: + print(f"Error: {data.get('error', 'Unknown error')}") + else: + print(f"Error: RPC returned {response.status_code}") + except Exception as e: + print(f"Error calling contract: {e}") + + +def handle_contract_verify(args, default_rpc_url: str, read_password): + """Handle contract verify command (for ZK proofs)""" + rpc_url = args.rpc_url if hasattr(args, 'rpc_url') and args.rpc_url else default_rpc_url + contract_address = getattr(args, 'address', None) + + if not contract_address: + print("Error: Contract address is required (--address)") + return + + password = read_password(args) + + try: + payload = { + "address": contract_address + } + + # Add proof data if available + if hasattr(args, 'proof_file') and args.proof_file: + import json + with open(args.proof_file) as f: + proof_data = json.load(f) + payload["proof"] = proof_data + + headers = {} + if password: + headers["X-Wallet-Password"] = password + + response = requests.post( + f"{rpc_url}/rpc/contracts/verify", + json=payload, + headers=headers, + timeout=60 + ) + + if response.status_code == 200: + data = response.json() + if data.get("success"): + result = data.get("result") + print(f"Verification result:") + print(f" Address: {contract_address}") + print(f" Valid: {result.get('valid', False)}") + if result.get('receipt_hash'): + print(f" Receipt Hash: {result.get('receipt_hash')}") + else: + print(f"Error: {data.get('error', 'Unknown error')}") + else: + print(f"Error: RPC returned {response.status_code}") + except Exception as e: + print(f"Error verifying contract: {e}") diff --git a/cli/parsers/__init__.py b/cli/parsers/__init__.py index fc6d0245..cdf01e68 100644 --- a/cli/parsers/__init__.py +++ b/cli/parsers/__init__.py @@ -1,6 +1,6 @@ """Parser registration modules for the unified CLI.""" -from . import ai, agent, blockchain, bridge, genesis, market, messaging, network, openclaw, pool_hub, resource, system, wallet, workflow +from . import ai, agent, blockchain, bridge, contract, genesis, market, messaging, network, openclaw, pool_hub, resource, system, wallet, workflow def register_all(subparsers, ctx): wallet.register(subparsers, ctx) @@ -17,3 +17,4 @@ def register_all(subparsers, ctx): genesis.register(subparsers, ctx) pool_hub.register(subparsers, ctx) bridge.register(subparsers, ctx) + contract.register(subparsers, ctx) diff --git a/cli/parsers/contract.py b/cli/parsers/contract.py new file mode 100644 index 00000000..8ba3ca8f --- /dev/null +++ b/cli/parsers/contract.py @@ -0,0 +1,40 @@ +"""Contract command registration for the unified CLI.""" + +import argparse + +from parser_context import ParserContext + + +def register(subparsers: argparse._SubParsersAction, ctx: ParserContext) -> None: + contract_parser = subparsers.add_parser("contract", help="Smart contract operations") + contract_parser.set_defaults(handler=lambda parsed, parser=contract_parser: parser.print_help()) + contract_subparsers = contract_parser.add_subparsers(dest="contract_action") + + contract_list_parser = contract_subparsers.add_parser("list", help="List deployed contracts") + contract_list_parser.add_argument("--rpc-url", default=ctx.default_rpc_url) + contract_list_parser.set_defaults(handler=ctx.handle_contract_list) + + contract_deploy_parser = contract_subparsers.add_parser("deploy", help="Deploy a smart contract") + contract_deploy_parser.add_argument("--name", required=True, help="Contract name") + contract_deploy_parser.add_argument("--type", default="zk-verifier", help="Contract type (default: zk-verifier)") + contract_deploy_parser.add_argument("--password", help="Wallet password") + contract_deploy_parser.add_argument("--password-file", help="Wallet password file") + contract_deploy_parser.add_argument("--rpc-url", default=ctx.default_rpc_url) + contract_deploy_parser.set_defaults(handler=ctx.handle_contract_deploy) + + contract_call_parser = contract_subparsers.add_parser("call", help="Call a contract method") + contract_call_parser.add_argument("--address", required=True, help="Contract address") + contract_call_parser.add_argument("--method", required=True, help="Method name") + contract_call_parser.add_argument("--params", help="Method parameters (JSON)") + contract_call_parser.add_argument("--password", help="Wallet password") + contract_call_parser.add_argument("--password-file", help="Wallet password file") + contract_call_parser.add_argument("--rpc-url", default=ctx.default_rpc_url) + contract_call_parser.set_defaults(handler=ctx.handle_contract_call) + + contract_verify_parser = contract_subparsers.add_parser("verify", help="Verify a ZK proof against a contract") + contract_verify_parser.add_argument("--address", required=True, help="Contract address") + contract_verify_parser.add_argument("--proof-file", help="Proof data file (JSON)") + contract_verify_parser.add_argument("--password", help="Wallet password") + contract_verify_parser.add_argument("--password-file", help="Wallet password file") + contract_verify_parser.add_argument("--rpc-url", default=ctx.default_rpc_url) + contract_verify_parser.set_defaults(handler=ctx.handle_contract_verify) diff --git a/cli/unified_cli.py b/cli/unified_cli.py index fb0a77fc..2d234143 100755 --- a/cli/unified_cli.py +++ b/cli/unified_cli.py @@ -20,6 +20,7 @@ from handlers import system as system_handlers from handlers import pool_hub as pool_hub_handlers from handlers import bridge as bridge_handlers from handlers import account as account_handlers +from handlers import contract as contract_handlers from parser_context import ParserContext from parsers import register_all @@ -711,6 +712,18 @@ def run_cli(argv, core): except Exception as e: print(f"❌ Error restarting blockchain event bridge service: {e}") + def handle_contract_list(args): + contract_handlers.handle_contract_list(args, default_rpc_url) + + def handle_contract_deploy(args): + contract_handlers.handle_contract_deploy(args, default_rpc_url, read_password, render_mapping) + + def handle_contract_call(args): + contract_handlers.handle_contract_call(args, default_rpc_url, read_password) + + def handle_contract_verify(args): + contract_handlers.handle_contract_verify(args, default_rpc_url, read_password) + handlers = { "handle_wallet_create": handle_wallet_create, "handle_wallet_list": handle_wallet_list, @@ -794,6 +807,10 @@ def run_cli(argv, core): "handle_bridge_config": globals()["handle_bridge_config"], "handle_bridge_restart": globals()["handle_bridge_restart"], "handle_genesis_init": globals()["handle_genesis_init"], + "handle_contract_list": handle_contract_list, + "handle_contract_deploy": handle_contract_deploy, + "handle_contract_call": handle_contract_call, + "handle_contract_verify": handle_contract_verify, "handle_genesis_verify": globals()["handle_genesis_verify"], "handle_genesis_info": globals()["handle_genesis_info"], } diff --git a/scripts/training/stage2_intermediate.sh b/scripts/training/stage2_intermediate.sh index 33a2d235..8671ad2c 100755 --- a/scripts/training/stage2_intermediate.sh +++ b/scripts/training/stage2_intermediate.sh @@ -39,7 +39,7 @@ advanced_wallet_management() { log "Wallet backup attempted for $WALLET_NAME" print_status "Exporting wallet data..." - $CLI_PATH wallet export "$WALLET_NAME" 2>/dev/null || print_warning "Wallet export command not available" + $CLI_PATH wallet export "$WALLET_NAME" "$WALLET_PASSWORD" >/dev/null 2>/dev/null || print_warning "Wallet export command not available" log "Wallet export attempted for $WALLET_NAME" print_status "Syncing all wallets..." @@ -92,30 +92,30 @@ smart_contract_interaction() { print_status "2.3 Smart Contract Interaction" print_status "Listing available contracts..." - $CLI_PATH contract --list 2>/dev/null || print_warning "Contract list command not available" + $CLI_PATH contract list 2>/dev/null || print_warning "Contract list command not available" log "Contract list retrieved" print_status "Attempting to deploy a test contract..." - $CLI_PATH contract --deploy --name test-contract 2>/dev/null || print_warning "Contract deploy command not available" + $CLI_PATH contract deploy --name test-contract --type zk-verifier --password "$WALLET_PASSWORD" 2>/dev/null || print_warning "Contract deploy command not available" log "Contract deployment attempted" # Get a contract address for testing - CONTRACT_ADDR=$($CLI_PATH contract --list 2>/dev/null | grep -o '0x[a-fA-F0-9]*' | head -1 || echo "") + CONTRACT_ADDR=$($CLI_PATH contract list 2>/dev/null | grep -o '0x[a-fA-F0-9_]*' | head -1 || echo "") if [ -n "$CONTRACT_ADDR" ]; then print_status "Testing contract call on $CONTRACT_ADDR..." - $CLI_PATH contract --call --address "$CONTRACT_ADDR" --method "test" 2>/dev/null || print_warning "Contract call command not available" + $CLI_PATH contract call --address "$CONTRACT_ADDR" --method "test" --password "$WALLET_PASSWORD" 2>/dev/null || print_warning "Contract call command not available" log "Contract call attempted on $CONTRACT_ADDR" else print_warning "No contract address found for testing" fi print_status "Testing agent messaging..." - $CLI_PATH agent message --agent "test-agent" --message "Hello from OpenClaw training" --wallet "$WALLET_NAME" 2>/dev/null || print_warning "Agent message command not available" + $CLI_PATH agent message --agent "test-agent" --message "Hello from OpenClaw training" --wallet "$WALLET_NAME" --password "$WALLET_PASSWORD" 2>/dev/null || print_warning "Agent message command not available" log "Agent message sent" print_status "Checking agent messages..." - $CLI_PATH agent messages 2>/dev/null || print_warning "Agent messages command not available" + $CLI_PATH agent messages --agent "test-agent" 2>/dev/null || print_warning "Agent messages command not available" log "Agent messages checked" print_success "2.3 Smart Contract Interaction completed"