feat: migrate wallet daemon and CLI to use centralized aitbc package utilities
Some checks failed
API Endpoint Tests / test-api-endpoints (push) Successful in 9s
CLI Tests / test-cli (push) Failing after 3s
Integration Tests / test-service-integration (push) Successful in 41s
Python Tests / test-python (push) Failing after 18s
Security Scanning / security-scan (push) Failing after 2m0s

- Migrate simple_daemon.py from mock data to real keystore and blockchain RPC integration
- Add httpx for async HTTP client in wallet daemon
- Implement real wallet listing from keystore directory
- Implement blockchain balance queries via RPC
- Update CLI to use aitbc.AITBCHTTPClient instead of requests
- Add aitbc imports: constants, http_client, exceptions, logging, paths, validation
- Add address and amount validation in
This commit is contained in:
aitbc
2026-04-24 22:05:55 +02:00
parent 154627cdfa
commit cbd8700984
13 changed files with 724 additions and 455 deletions

View File

@@ -12,8 +12,8 @@ import asyncio
from datetime import datetime
from decimal import Decimal
from typing import Optional, List
from cli.utils import output, error, success, info, warning
from cli.aitbc_cli.utils.island_credentials import (
from ..utils import output, error, success, info, warning
from ..utils.island_credentials import (
load_island_credentials, get_rpc_endpoint, get_chain_id,
get_island_id, get_island_name
)

View File

@@ -9,10 +9,22 @@ import yaml
from pathlib import Path
from typing import Optional, Dict, Any, List
from datetime import datetime, timedelta
from ..utils import output, error, success, encrypt_value, decrypt_value
from ..utils import output, error, success
import getpass
def encrypt_value(value: str, password: str) -> str:
"""Simple encryption for wallet data (placeholder)"""
# For now, return the value as-is since daemon mode doesn't need this
return value
def decrypt_value(encrypted: str, password: str) -> str:
"""Simple decryption for wallet data (placeholder)"""
# For now, return the value as-is since daemon mode doesn't need this
return encrypted
def _get_wallet_password(wallet_name: str) -> str:
"""Get or prompt for wallet encryption password"""
# Try to get from keyring first
@@ -84,12 +96,26 @@ def _load_wallet(wallet_path: Path, wallet_name: str) -> Dict[str, Any]:
@click.option(
"--wallet-path", help="Direct path to wallet file (overrides --wallet-name)"
)
@click.option("--use-daemon", is_flag=True, default=True, help="Use wallet daemon for operations")
@click.pass_context
def wallet(ctx, wallet_name: Optional[str], wallet_path: Optional[str]):
def wallet(ctx, wallet_name: Optional[str], wallet_path: Optional[str], use_daemon: bool):
"""Manage your AITBC wallets and transactions"""
# Ensure wallet object exists
ctx.ensure_object(dict)
# Set daemon mode
ctx.obj["use_daemon"] = use_daemon
# Initialize dual-mode adapter
from ..config import get_config
import sys
sys.path.insert(0, '/opt/aitbc/cli')
from utils.dual_mode_wallet_adapter import DualModeWalletAdapter
config = get_config()
adapter = DualModeWalletAdapter(config, use_daemon=use_daemon)
ctx.obj["wallet_adapter"] = adapter
# If direct wallet path is provided, use it
if wallet_path:
wp = Path(wallet_path)
@@ -217,32 +243,43 @@ def create(ctx, name: str, wallet_type: str, no_encrypt: bool):
@click.pass_context
def list(ctx):
"""List all wallets"""
wallet_dir = ctx.obj["wallet_dir"]
config_file = Path.home() / ".aitbc" / "config.yaml"
# Get active wallet
active_wallet = "default"
if config_file.exists():
with open(config_file, "r") as f:
config = yaml.safe_load(f)
active_wallet = config.get("active_wallet", "default")
wallets = []
for wallet_file in wallet_dir.glob("*.json"):
with open(wallet_file, "r") as f:
wallet_data = json.load(f)
wallet_info = {
"name": wallet_data["wallet_id"],
"type": wallet_data.get("type", "simple"),
"address": wallet_data["address"],
"created_at": wallet_data["created_at"],
"active": wallet_data["wallet_id"] == active_wallet,
}
if wallet_data.get("encrypted"):
wallet_info["encrypted"] = True
wallets.append(wallet_info)
output(wallets, ctx.obj.get("output_format", "table"))
adapter = ctx.obj["wallet_adapter"]
use_daemon = ctx.obj["use_daemon"]
# Check if using daemon mode and daemon is available
if use_daemon and not adapter.is_daemon_available():
error("Wallet daemon is not available. Falling back to file-based wallet listing.")
# Switch to file mode
from ..config import get_config
import sys
sys.path.insert(0, '/opt/aitbc/cli')
from utils.dual_mode_wallet_adapter import DualModeWalletAdapter
config = get_config()
adapter = DualModeWalletAdapter(config, use_daemon=False)
try:
wallets = adapter.list_wallets()
if not wallets:
output("No wallets found")
return
# Format output
output_format = ctx.obj.get("output_format", "table")
if output_format == "json":
import json
output(json.dumps(wallets, indent=2))
elif output_format == "yaml":
import yaml
output(yaml.dump(wallets, default_flow_style=False))
else:
# Table format
for wallet in wallets:
wallet_name = wallet.get("wallet_name", wallet.get("name", "unknown"))
wallet_address = wallet.get("address", "")
output(f"{wallet_name}: {wallet_address}")
except Exception as e:
error(f"Failed to list wallets: {str(e)}")
@wallet.command()

63
cli/aitbc_cli/config.py Normal file
View File

@@ -0,0 +1,63 @@
"""Configuration module for AITBC CLI"""
import os
from pathlib import Path
from typing import Optional
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
from aitbc.config import BaseAITBCConfig
from aitbc.constants import BLOCKCHAIN_RPC_PORT, BLOCKCHAIN_P2P_PORT
class CLIConfig(BaseAITBCConfig):
"""CLI-specific configuration inheriting from shared BaseAITBCConfig"""
model_config = SettingsConfigDict(
env_file=str(Path("/etc/aitbc/.env")),
env_file_encoding="utf-8",
case_sensitive=False,
extra="ignore"
)
# CLI-specific settings
app_name: str = Field(default="AITBC CLI", description="CLI application name")
app_version: str = Field(default="2.1.0", description="CLI version")
# Service URLs
coordinator_url: str = Field(default="http://localhost:8000", description="Coordinator API URL")
wallet_daemon_url: str = Field(default="http://localhost:8003", description="Wallet daemon URL")
wallet_url: str = Field(default="http://localhost:8003", description="Wallet daemon URL (alias for compatibility)")
blockchain_rpc_url: str = Field(default=f"http://localhost:{BLOCKCHAIN_RPC_PORT}", description="Blockchain RPC URL")
# Authentication
api_key: Optional[str] = Field(default=None, description="API key for authentication")
# Request settings
timeout: int = Field(default=30, description="Request timeout in seconds")
# Config file path (for backward compatibility)
config_file: Optional[str] = Field(default=None, description="Path to config file")
def get_config(config_file: Optional[str] = None) -> CLIConfig:
"""Load CLI configuration from shared config system"""
# For backward compatibility, allow config_file override
if config_file:
config_path = Path(config_file)
if config_path.exists():
import yaml
with open(config_path) as f:
config_data = yaml.safe_load(f) or {}
# Override with config file values
return CLIConfig(
coordinator_url=config_data.get("coordinator_url", "http://localhost:8000"),
wallet_daemon_url=config_data.get("wallet_url", "http://localhost:8003"),
api_key=config_data.get("api_key"),
timeout=config_data.get("timeout", 30)
)
# Use shared config system with environment variables
return CLIConfig()