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
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:
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
63
cli/aitbc_cli/config.py
Normal 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()
|
||||
|
||||
Reference in New Issue
Block a user