feat: implement long-term CLI goals with enterprise features
All checks were successful
CLI Tests / test-cli (push) Successful in 1m18s
Documentation Validation / validate-docs (push) Successful in 11s
Security Scanning / security-scan (push) Successful in 48s

📈 CLI Expansion: Full wallet and transaction support
• Create enterprise_cli.py with advanced operations
• Add batch transaction processing from JSON files
• Implement wallet import/export operations
• Add wallet rename and delete functionality

📈 Advanced Operations: Mining, marketplace, AI services
• Mining operations: start/stop/status with multi-threading
• Marketplace: list items and create listings
• AI services: submit compute jobs with payment
• Enterprise automation script for demo purposes

📈 Enterprise Features: Batch operations, automation
• Batch transaction processing with JSON input
• Cross-node deployment and synchronization
• Sample file generation for batch operations
• Enterprise automation script with all features
• Professional error handling and user feedback

New CLI Commands:
• batch: Process multiple transactions from JSON file
• mine: Mining operations (start/stop/status)
• market: Marketplace operations (list/create)
• ai: AI service operations (submit jobs)
• sample: Create sample batch files

Enterprise Features:
• JSON-based batch processing
• Multi-threaded mining support
• Marketplace integration
• AI compute job submission
• Cross-node automation
• Professional error handling
• Sample file generation

This completes the long-term CLI goals with enterprise-grade
features and automation capabilities.
This commit is contained in:
aitbc1
2026-03-29 16:13:53 +02:00
parent a06595eccb
commit 35c694a1c2
5 changed files with 1538 additions and 1 deletions

View File

@@ -148,6 +148,120 @@ def send_transaction(from_wallet: str, to_address: str, amount: float, fee: floa
return None
def import_wallet(wallet_name: str, private_key_hex: str, password: str,
keystore_dir: Path = DEFAULT_KEYSTORE_DIR) -> Optional[str]:
"""Import wallet from private key"""
try:
keystore_dir.mkdir(parents=True, exist_ok=True)
# Validate and convert private key
try:
private_key_bytes = bytes.fromhex(private_key_hex)
private_key = ed25519.Ed25519PrivateKey.from_private_bytes(private_key_bytes)
except Exception as e:
print(f"Error: Invalid private key: {e}")
return None
# Generate public key and address
public_key = private_key.public_key()
public_key_hex = public_key.public_bytes_raw().hex()
address = f"ait1{public_key_hex[:40]}"
# Encrypt private key
salt = os.urandom(32)
kdf = PBKDF2HMAC(hashes.SHA256(), 32, salt, 100000)
key = kdf.derive(password.encode())
aesgcm = AESGCM(key)
nonce = os.urandom(12)
ciphertext = aesgcm.encrypt(nonce, private_key_bytes, None)
# Create keystore file
keystore_data = {
"address": address,
"public_key": public_key_hex,
"crypto": {
"kdf": "pbkdf2",
"kdfparams": {
"salt": salt.hex(),
"c": 100000,
"dklen": 32,
"prf": "hmac-sha256"
},
"cipher": "aes-256-gcm",
"cipherparams": {
"nonce": nonce.hex()
},
"ciphertext": ciphertext.hex()
},
"version": 1
}
keystore_path = keystore_dir / f"{wallet_name}.json"
with open(keystore_path, 'w') as f:
json.dump(keystore_data, f, indent=2)
print(f"Wallet imported: {wallet_name}")
print(f"Address: {address}")
print(f"Keystore: {keystore_path}")
return address
except Exception as e:
print(f"Error importing wallet: {e}")
return None
def export_wallet(wallet_name: str, password: str, keystore_dir: Path = DEFAULT_KEYSTORE_DIR) -> Optional[str]:
"""Export private key from wallet"""
try:
keystore_path = keystore_dir / f"{wallet_name}.json"
if not keystore_path.exists():
print(f"Error: Wallet '{wallet_name}' not found")
return None
return decrypt_private_key(keystore_path, password)
except Exception as e:
print(f"Error exporting wallet: {e}")
return None
def delete_wallet(wallet_name: str, keystore_dir: Path = DEFAULT_KEYSTORE_DIR) -> bool:
"""Delete wallet"""
try:
keystore_path = keystore_dir / f"{wallet_name}.json"
if keystore_path.exists():
keystore_path.unlink()
print(f"Wallet '{wallet_name}' deleted successfully")
return True
else:
print(f"Error: Wallet '{wallet_name}' not found")
return False
except Exception as e:
print(f"Error deleting wallet: {e}")
return False
def rename_wallet(old_name: str, new_name: str, keystore_dir: Path = DEFAULT_KEYSTORE_DIR) -> bool:
"""Rename wallet"""
try:
old_path = keystore_dir / f"{old_name}.json"
new_path = keystore_dir / f"{new_name}.json"
if not old_path.exists():
print(f"Error: Wallet '{old_name}' not found")
return False
if new_path.exists():
print(f"Error: Wallet '{new_name}' already exists")
return False
old_path.rename(new_path)
print(f"Wallet renamed from '{old_name}' to '{new_name}'")
return True
except Exception as e:
print(f"Error renaming wallet: {e}")
return False
def list_wallets(keystore_dir: Path = DEFAULT_KEYSTORE_DIR) -> list:
"""List all wallets"""
wallets = []
@@ -166,7 +280,264 @@ def list_wallets(keystore_dir: Path = DEFAULT_KEYSTORE_DIR) -> list:
return wallets
def get_balance(wallet_name: str, keystore_dir: Path = DEFAULT_KEYSTORE_DIR,
def send_batch_transactions(transactions: List[Dict], password: str,
keystore_dir: Path = DEFAULT_KEYSTORE_DIR,
rpc_url: str = DEFAULT_RPC_URL) -> List[Optional[str]]:
"""Send multiple transactions in batch"""
results = []
for tx in transactions:
try:
tx_hash = send_transaction(
tx['from_wallet'],
tx['to_address'],
tx['amount'],
tx.get('fee', 10.0),
password,
rpc_url
)
results.append({
'transaction': tx,
'hash': tx_hash,
'success': tx_hash is not None
})
if tx_hash:
print(f"✅ Transaction sent: {tx['from_wallet']}{tx['to_address']} ({tx['amount']} AIT)")
else:
print(f"❌ Transaction failed: {tx['from_wallet']}{tx['to_address']}")
except Exception as e:
results.append({
'transaction': tx,
'hash': None,
'success': False,
'error': str(e)
})
print(f"❌ Transaction error: {e}")
return results
def estimate_transaction_fee(from_wallet: str, to_address: str, amount: float,
keystore_dir: Path = DEFAULT_KEYSTORE_DIR,
rpc_url: str = DEFAULT_RPC_URL) -> Optional[float]:
"""Estimate transaction fee"""
try:
# Create a test transaction to estimate fee
test_tx = {
"sender": "", # Will be filled by actual sender
"recipient": to_address,
"value": int(amount),
"fee": 10, # Default fee
"nonce": 0,
"type": "transfer",
"payload": {}
}
# Get fee estimation from RPC (if available)
response = requests.post(f"{rpc_url}/rpc/estimateFee", json=test_tx)
if response.status_code == 200:
fee_data = response.json()
return fee_data.get("estimated_fee", 10.0)
else:
# Fallback to default fee
return 10.0
except Exception as e:
print(f"Error estimating fee: {e}")
return 10.0
def get_transaction_status(tx_hash: str, rpc_url: str = DEFAULT_RPC_URL) -> Optional[Dict]:
"""Get detailed transaction status"""
try:
response = requests.get(f"{rpc_url}/rpc/transaction/{tx_hash}")
if response.status_code == 200:
return response.json()
else:
print(f"Error getting transaction status: {response.text}")
return None
except Exception as e:
print(f"Error: {e}")
return None
def get_pending_transactions(rpc_url: str = DEFAULT_RPC_URL) -> List[Dict]:
"""Get pending transactions in mempool"""
try:
response = requests.get(f"{rpc_url}/rpc/pending")
if response.status_code == 200:
return response.json().get("transactions", [])
else:
print(f"Error getting pending transactions: {response.text}")
return []
except Exception as e:
print(f"Error: {e}")
return []
def start_mining(wallet_name: str, threads: int = 1, keystore_dir: Path = DEFAULT_KEYSTORE_DIR,
rpc_url: str = DEFAULT_RPC_URL) -> bool:
"""Start mining with specified wallet"""
try:
# Get wallet address
keystore_path = keystore_dir / f"{wallet_name}.json"
if not keystore_path.exists():
print(f"Error: Wallet '{wallet_name}' not found")
return False
with open(keystore_path) as f:
wallet_data = json.load(f)
address = wallet_data['address']
# Start mining via RPC
mining_config = {
"miner_address": address,
"threads": threads,
"enabled": True
}
response = requests.post(f"{rpc_url}/rpc/mining/start", json=mining_config)
if response.status_code == 200:
result = response.json()
print(f"Mining started with wallet '{wallet_name}'")
print(f"Miner address: {address}")
print(f"Threads: {threads}")
print(f"Status: {result.get('status', 'started')}")
return True
else:
print(f"Error starting mining: {response.text}")
return False
except Exception as e:
print(f"Error: {e}")
return False
def stop_mining(rpc_url: str = DEFAULT_RPC_URL) -> bool:
"""Stop mining"""
try:
response = requests.post(f"{rpc_url}/rpc/mining/stop")
if response.status_code == 200:
result = response.json()
print(f"Mining stopped")
print(f"Status: {result.get('status', 'stopped')}")
return True
else:
print(f"Error stopping mining: {response.text}")
return False
except Exception as e:
print(f"Error: {e}")
return False
def get_mining_status(rpc_url: str = DEFAULT_RPC_URL) -> Optional[Dict]:
"""Get mining status and statistics"""
try:
response = requests.get(f"{rpc_url}/rpc/mining/status")
if response.status_code == 200:
return response.json()
else:
print(f"Error getting mining status: {response.text}")
return None
except Exception as e:
print(f"Error: {e}")
return None
def get_marketplace_listings(rpc_url: str = DEFAULT_RPC_URL) -> List[Dict]:
"""Get marketplace listings"""
try:
response = requests.get(f"{rpc_url}/rpc/marketplace/listings")
if response.status_code == 200:
return response.json().get("listings", [])
else:
print(f"Error getting marketplace listings: {response.text}")
return []
except Exception as e:
print(f"Error: {e}")
return []
def create_marketplace_listing(wallet_name: str, item_type: str, price: float,
description: str, password: str,
keystore_dir: Path = DEFAULT_KEYSTORE_DIR,
rpc_url: str = DEFAULT_RPC_URL) -> Optional[str]:
"""Create marketplace listing"""
try:
# Get wallet address
keystore_path = keystore_dir / f"{wallet_name}.json"
if not keystore_path.exists():
print(f"Error: Wallet '{wallet_name}' not found")
return None
with open(keystore_path) as f:
wallet_data = json.load(f)
address = wallet_data['address']
# Create listing
listing_data = {
"seller_address": address,
"item_type": item_type,
"price": price,
"description": description
}
response = requests.post(f"{rpc_url}/rpc/marketplace/create", json=listing_data)
if response.status_code == 200:
result = response.json()
listing_id = result.get("listing_id")
print(f"Marketplace listing created")
print(f"Listing ID: {listing_id}")
print(f"Item: {item_type}")
print(f"Price: {price} AIT")
return listing_id
else:
print(f"Error creating listing: {response.text}")
return None
except Exception as e:
print(f"Error: {e}")
return None
def submit_ai_job(wallet_name: str, job_type: str, prompt: str, payment: float,
password: str, keystore_dir: Path = DEFAULT_KEYSTORE_DIR,
rpc_url: str = DEFAULT_RPC_URL) -> Optional[str]:
"""Submit AI compute job"""
try:
# Get wallet address
keystore_path = keystore_dir / f"{wallet_name}.json"
if not keystore_path.exists():
print(f"Error: Wallet '{wallet_name}' not found")
return None
with open(keystore_path) as f:
wallet_data = json.load(f)
address = wallet_data['address']
# Submit job
job_data = {
"client_address": address,
"job_type": job_type,
"prompt": prompt,
"payment": payment
}
response = requests.post(f"{rpc_url}/rpc/ai/submit", json=job_data)
if response.status_code == 200:
result = response.json()
job_id = result.get("job_id")
print(f"AI job submitted")
print(f"Job ID: {job_id}")
print(f"Type: {job_type}")
print(f"Payment: {payment} AIT")
return job_id
else:
print(f"Error submitting AI job: {response.text}")
return None
except Exception as e:
print(f"Error: {e}")
return None
def get_balance(wallet_name: str, keystore_dir: Path = DEFAULT_KEYSTORE_DIR,
rpc_url: str = DEFAULT_RPC_URL) -> Optional[Dict]:
"""Get wallet balance and transaction info"""
try:
@@ -310,6 +681,66 @@ def main():
network_parser = subparsers.add_parser("network", help="Get network status")
network_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL")
# Import wallet command
import_parser = subparsers.add_parser("import", help="Import wallet from private key")
import_parser.add_argument("--name", required=True, help="Wallet name")
import_parser.add_argument("--private-key", required=True, help="Private key (hex)")
import_parser.add_argument("--password", help="Wallet password")
import_parser.add_argument("--password-file", help="File containing wallet password")
# Export wallet command
export_parser = subparsers.add_parser("export", help="Export private key from wallet")
export_parser.add_argument("--name", required=True, help="Wallet name")
export_parser.add_argument("--password", help="Wallet password")
export_parser.add_argument("--password-file", help="File containing wallet password")
# Delete wallet command
delete_parser = subparsers.add_parser("delete", help="Delete wallet")
delete_parser.add_argument("--name", required=True, help="Wallet name")
delete_parser.add_argument("--confirm", action="store_true", help="Confirm deletion")
# Rename wallet command
rename_parser = subparsers.add_parser("rename", help="Rename wallet")
rename_parser.add_argument("--old", required=True, dest="old_name", help="Current wallet name")
rename_parser.add_argument("--new", required=True, dest="new_name", help="New wallet name")
# Batch send command
batch_parser = subparsers.add_parser("batch", help="Send multiple transactions")
batch_parser.add_argument("--file", required=True, help="JSON file with transactions")
batch_parser.add_argument("--password", help="Wallet password")
batch_parser.add_argument("--password-file", help="File containing wallet password")
batch_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL")
# Mining commands
mine_start_parser = subparsers.add_parser("mine-start", help="Start mining")
mine_start_parser.add_argument("--wallet", required=True, help="Mining wallet name")
mine_start_parser.add_argument("--threads", type=int, default=1, help="Number of threads")
mine_stop_parser = subparsers.add_parser("mine-stop", help="Stop mining")
mine_status_parser = subparsers.add_parser("mine-status", help="Get mining status")
# Marketplace commands
market_list_parser = subparsers.add_parser("market-list", help="List marketplace items")
market_list_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL")
market_create_parser = subparsers.add_parser("market-create", help="Create marketplace listing")
market_create_parser.add_argument("--wallet", required=True, help="Seller wallet name")
market_create_parser.add_argument("--type", required=True, help="Item type")
market_create_parser.add_argument("--price", type=float, required=True, help="Price in AIT")
market_create_parser.add_argument("--description", required=True, help="Item description")
market_create_parser.add_argument("--password", help="Wallet password")
market_create_parser.add_argument("--password-file", help="File containing wallet password")
# AI commands
ai_submit_parser = subparsers.add_parser("ai-submit", help="Submit AI compute job")
ai_submit_parser.add_argument("--wallet", required=True, help="Client wallet name")
ai_submit_parser.add_argument("--type", required=True, help="Job type")
ai_submit_parser.add_argument("--prompt", required=True, help="AI prompt")
ai_submit_parser.add_argument("--payment", type=float, required=True, help="Payment in AIT")
ai_submit_parser.add_argument("--password", help="Wallet password")
ai_submit_parser.add_argument("--password-file", help="File containing wallet password")
args = parser.parse_args()
if args.command == "create":