chore: remove configuration files and enhance blockchain explorer with advanced search, analytics, and export features
- Delete .aitbc.yaml.example CLI configuration template - Delete .lycheeignore link checker exclusion rules - Delete .nvmrc Node.js version specification - Add advanced search panel with filters for address, amount range, transaction type, time range, and validator - Add analytics dashboard with transaction volume, active addresses, and block time metrics - Add Chart.js integration
This commit is contained in:
494
cli/aitbc_cli/commands/marketplace_cmd.py
Normal file
494
cli/aitbc_cli/commands/marketplace_cmd.py
Normal file
@@ -0,0 +1,494 @@
|
||||
"""Global chain marketplace commands for AITBC CLI"""
|
||||
|
||||
import click
|
||||
import asyncio
|
||||
import json
|
||||
from decimal import Decimal
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from ..core.config import load_multichain_config
|
||||
from ..core.marketplace import (
|
||||
GlobalChainMarketplace, ChainType, MarketplaceStatus,
|
||||
TransactionStatus
|
||||
)
|
||||
from ..utils import output, error, success
|
||||
|
||||
@click.group()
|
||||
def marketplace():
|
||||
"""Global chain marketplace commands"""
|
||||
pass
|
||||
|
||||
@marketplace.command()
|
||||
@click.argument('chain_id')
|
||||
@click.argument('chain_name')
|
||||
@click.argument('chain_type')
|
||||
@click.argument('description')
|
||||
@click.argument('seller_id')
|
||||
@click.argument('price')
|
||||
@click.option('--currency', default='ETH', help='Currency for pricing')
|
||||
@click.option('--specs', help='Chain specifications (JSON string)')
|
||||
@click.option('--metadata', help='Additional metadata (JSON string)')
|
||||
@click.pass_context
|
||||
def list(ctx, chain_id, chain_name, chain_type, description, seller_id, price, currency, specs, metadata):
|
||||
"""List a chain for sale in the marketplace"""
|
||||
try:
|
||||
config = load_multichain_config()
|
||||
marketplace = GlobalChainMarketplace(config)
|
||||
|
||||
# Parse chain type
|
||||
try:
|
||||
chain_type_enum = ChainType(chain_type)
|
||||
except ValueError:
|
||||
error(f"Invalid chain type: {chain_type}")
|
||||
error(f"Valid types: {[t.value for t in ChainType]}")
|
||||
raise click.Abort()
|
||||
|
||||
# Parse price
|
||||
try:
|
||||
price_decimal = Decimal(price)
|
||||
except:
|
||||
error("Invalid price format")
|
||||
raise click.Abort()
|
||||
|
||||
# Parse specifications
|
||||
chain_specs = {}
|
||||
if specs:
|
||||
try:
|
||||
chain_specs = json.loads(specs)
|
||||
except json.JSONDecodeError:
|
||||
error("Invalid JSON specifications")
|
||||
raise click.Abort()
|
||||
|
||||
# Parse metadata
|
||||
metadata_dict = {}
|
||||
if metadata:
|
||||
try:
|
||||
metadata_dict = json.loads(metadata)
|
||||
except json.JSONDecodeError:
|
||||
error("Invalid JSON metadata")
|
||||
raise click.Abort()
|
||||
|
||||
# Create listing
|
||||
listing_id = asyncio.run(marketplace.create_listing(
|
||||
chain_id, chain_name, chain_type_enum, description,
|
||||
seller_id, price_decimal, currency, chain_specs, metadata_dict
|
||||
))
|
||||
|
||||
if listing_id:
|
||||
success(f"Chain listed successfully! Listing ID: {listing_id}")
|
||||
|
||||
listing_data = {
|
||||
"Listing ID": listing_id,
|
||||
"Chain ID": chain_id,
|
||||
"Chain Name": chain_name,
|
||||
"Type": chain_type,
|
||||
"Price": f"{price} {currency}",
|
||||
"Seller": seller_id,
|
||||
"Status": "active",
|
||||
"Created": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
}
|
||||
|
||||
output(listing_data, ctx.obj.get('output_format', 'table'))
|
||||
else:
|
||||
error("Failed to create listing")
|
||||
raise click.Abort()
|
||||
|
||||
except Exception as e:
|
||||
error(f"Error creating listing: {str(e)}")
|
||||
raise click.Abort()
|
||||
|
||||
@marketplace.command()
|
||||
@click.argument('listing_id')
|
||||
@click.argument('buyer_id')
|
||||
@click.option('--payment', default='crypto', help='Payment method')
|
||||
@click.pass_context
|
||||
def buy(ctx, listing_id, buyer_id, payment):
|
||||
"""Purchase a chain from the marketplace"""
|
||||
try:
|
||||
config = load_multichain_config()
|
||||
marketplace = GlobalChainMarketplace(config)
|
||||
|
||||
# Purchase chain
|
||||
transaction_id = asyncio.run(marketplace.purchase_chain(listing_id, buyer_id, payment))
|
||||
|
||||
if transaction_id:
|
||||
success(f"Purchase initiated! Transaction ID: {transaction_id}")
|
||||
|
||||
transaction_data = {
|
||||
"Transaction ID": transaction_id,
|
||||
"Listing ID": listing_id,
|
||||
"Buyer": buyer_id,
|
||||
"Payment Method": payment,
|
||||
"Status": "pending",
|
||||
"Created": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
}
|
||||
|
||||
output(transaction_data, ctx.obj.get('output_format', 'table'))
|
||||
else:
|
||||
error("Failed to purchase chain")
|
||||
raise click.Abort()
|
||||
|
||||
except Exception as e:
|
||||
error(f"Error purchasing chain: {str(e)}")
|
||||
raise click.Abort()
|
||||
|
||||
@marketplace.command()
|
||||
@click.argument('transaction_id')
|
||||
@click.argument('transaction_hash')
|
||||
@click.pass_context
|
||||
def complete(ctx, transaction_id, transaction_hash):
|
||||
"""Complete a marketplace transaction"""
|
||||
try:
|
||||
config = load_multichain_config()
|
||||
marketplace = GlobalChainMarketplace(config)
|
||||
|
||||
# Complete transaction
|
||||
success = asyncio.run(marketplace.complete_transaction(transaction_id, transaction_hash))
|
||||
|
||||
if success:
|
||||
success(f"Transaction {transaction_id} completed successfully!")
|
||||
|
||||
transaction_data = {
|
||||
"Transaction ID": transaction_id,
|
||||
"Transaction Hash": transaction_hash,
|
||||
"Status": "completed",
|
||||
"Completed": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
}
|
||||
|
||||
output(transaction_data, ctx.obj.get('output_format', 'table'))
|
||||
else:
|
||||
error(f"Failed to complete transaction {transaction_id}")
|
||||
raise click.Abort()
|
||||
|
||||
except Exception as e:
|
||||
error(f"Error completing transaction: {str(e)}")
|
||||
raise click.Abort()
|
||||
|
||||
@marketplace.command()
|
||||
@click.option('--type', help='Filter by chain type')
|
||||
@click.option('--min-price', help='Minimum price')
|
||||
@click.option('--max-price', help='Maximum price')
|
||||
@click.option('--seller', help='Filter by seller ID')
|
||||
@click.option('--status', help='Filter by listing status')
|
||||
@click.option('--format', type=click.Choice(['table', 'json']), default='table', help='Output format')
|
||||
@click.pass_context
|
||||
def search(ctx, type, min_price, max_price, seller, status, format):
|
||||
"""Search chain listings in the marketplace"""
|
||||
try:
|
||||
config = load_multichain_config()
|
||||
marketplace = GlobalChainMarketplace(config)
|
||||
|
||||
# Parse filters
|
||||
chain_type = None
|
||||
if type:
|
||||
try:
|
||||
chain_type = ChainType(type)
|
||||
except ValueError:
|
||||
error(f"Invalid chain type: {type}")
|
||||
raise click.Abort()
|
||||
|
||||
min_price_dec = None
|
||||
if min_price:
|
||||
try:
|
||||
min_price_dec = Decimal(min_price)
|
||||
except:
|
||||
error("Invalid minimum price format")
|
||||
raise click.Abort()
|
||||
|
||||
max_price_dec = None
|
||||
if max_price:
|
||||
try:
|
||||
max_price_dec = Decimal(max_price)
|
||||
except:
|
||||
error("Invalid maximum price format")
|
||||
raise click.Abort()
|
||||
|
||||
listing_status = None
|
||||
if status:
|
||||
try:
|
||||
listing_status = MarketplaceStatus(status)
|
||||
except ValueError:
|
||||
error(f"Invalid status: {status}")
|
||||
raise click.Abort()
|
||||
|
||||
# Search listings
|
||||
listings = asyncio.run(marketplace.search_listings(
|
||||
chain_type, min_price_dec, max_price_dec, seller, listing_status
|
||||
))
|
||||
|
||||
if not listings:
|
||||
output("No listings found matching your criteria", ctx.obj.get('output_format', 'table'))
|
||||
return
|
||||
|
||||
# Format output
|
||||
listing_data = [
|
||||
{
|
||||
"Listing ID": listing.listing_id,
|
||||
"Chain ID": listing.chain_id,
|
||||
"Chain Name": listing.chain_name,
|
||||
"Type": listing.chain_type.value,
|
||||
"Price": f"{listing.price} {listing.currency}",
|
||||
"Seller": listing.seller_id,
|
||||
"Status": listing.status.value,
|
||||
"Created": listing.created_at.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"Expires": listing.expires_at.strftime("%Y-%m-%d %H:%M:%S")
|
||||
}
|
||||
for listing in listings
|
||||
]
|
||||
|
||||
output(listing_data, ctx.obj.get('output_format', format), title="Marketplace Listings")
|
||||
|
||||
except Exception as e:
|
||||
error(f"Error searching listings: {str(e)}")
|
||||
raise click.Abort()
|
||||
|
||||
@marketplace.command()
|
||||
@click.argument('chain_id')
|
||||
@click.option('--format', type=click.Choice(['table', 'json']), default='table', help='Output format')
|
||||
@click.pass_context
|
||||
def economy(ctx, chain_id, format):
|
||||
"""Get economic metrics for a specific chain"""
|
||||
try:
|
||||
config = load_multichain_config()
|
||||
marketplace = GlobalChainMarketplace(config)
|
||||
|
||||
# Get chain economy
|
||||
economy = asyncio.run(marketplace.get_chain_economy(chain_id))
|
||||
|
||||
if not economy:
|
||||
error(f"No economic data available for chain {chain_id}")
|
||||
raise click.Abort()
|
||||
|
||||
# Format output
|
||||
economy_data = [
|
||||
{"Metric": "Chain ID", "Value": economy.chain_id},
|
||||
{"Metric": "Total Value Locked", "Value": f"{economy.total_value_locked} ETH"},
|
||||
{"Metric": "Daily Volume", "Value": f"{economy.daily_volume} ETH"},
|
||||
{"Metric": "Market Cap", "Value": f"{economy.market_cap} ETH"},
|
||||
{"Metric": "Transaction Count", "Value": economy.transaction_count},
|
||||
{"Metric": "Active Users", "Value": economy.active_users},
|
||||
{"Metric": "Agent Count", "Value": economy.agent_count},
|
||||
{"Metric": "Governance Tokens", "Value": f"{economy.governance_tokens}"},
|
||||
{"Metric": "Staking Rewards", "Value": f"{economy.staking_rewards}"},
|
||||
{"Metric": "Last Updated", "Value": economy.last_updated.strftime("%Y-%m-%d %H:%M:%S")}
|
||||
]
|
||||
|
||||
output(economy_data, ctx.obj.get('output_format', format), title=f"Chain Economy: {chain_id}")
|
||||
|
||||
except Exception as e:
|
||||
error(f"Error getting chain economy: {str(e)}")
|
||||
raise click.Abort()
|
||||
|
||||
@marketplace.command()
|
||||
@click.argument('user_id')
|
||||
@click.option('--role', type=click.Choice(['buyer', 'seller', 'both']), default='both', help='User role')
|
||||
@click.option('--format', type=click.Choice(['table', 'json']), default='table', help='Output format')
|
||||
@click.pass_context
|
||||
def transactions(ctx, user_id, role, format):
|
||||
"""Get transactions for a specific user"""
|
||||
try:
|
||||
config = load_multichain_config()
|
||||
marketplace = GlobalChainMarketplace(config)
|
||||
|
||||
# Get user transactions
|
||||
transactions = asyncio.run(marketplace.get_user_transactions(user_id, role))
|
||||
|
||||
if not transactions:
|
||||
output(f"No transactions found for user {user_id}", ctx.obj.get('output_format', 'table'))
|
||||
return
|
||||
|
||||
# Format output
|
||||
transaction_data = [
|
||||
{
|
||||
"Transaction ID": transaction.transaction_id,
|
||||
"Listing ID": transaction.listing_id,
|
||||
"Chain ID": transaction.chain_id,
|
||||
"Price": f"{transaction.price} {transaction.currency}",
|
||||
"Role": "buyer" if transaction.buyer_id == user_id else "seller",
|
||||
"Counterparty": transaction.seller_id if transaction.buyer_id == user_id else transaction.buyer_id,
|
||||
"Status": transaction.status.value,
|
||||
"Created": transaction.created_at.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"Completed": transaction.completed_at.strftime("%Y-%m-%d %H:%M:%S") if transaction.completed_at else "N/A"
|
||||
}
|
||||
for transaction in transactions
|
||||
]
|
||||
|
||||
output(transaction_data, ctx.obj.get('output_format', format), title=f"Transactions for {user_id}")
|
||||
|
||||
except Exception as e:
|
||||
error(f"Error getting user transactions: {str(e)}")
|
||||
raise click.Abort()
|
||||
|
||||
@marketplace.command()
|
||||
@click.option('--format', type=click.Choice(['table', 'json']), default='table', help='Output format')
|
||||
@click.pass_context
|
||||
def overview(ctx, format):
|
||||
"""Get comprehensive marketplace overview"""
|
||||
try:
|
||||
config = load_multichain_config()
|
||||
marketplace = GlobalChainMarketplace(config)
|
||||
|
||||
# Get marketplace overview
|
||||
overview = asyncio.run(marketplace.get_marketplace_overview())
|
||||
|
||||
if not overview:
|
||||
error("No marketplace data available")
|
||||
raise click.Abort()
|
||||
|
||||
# Marketplace metrics
|
||||
if "marketplace_metrics" in overview:
|
||||
metrics = overview["marketplace_metrics"]
|
||||
metrics_data = [
|
||||
{"Metric": "Total Listings", "Value": metrics["total_listings"]},
|
||||
{"Metric": "Active Listings", "Value": metrics["active_listings"]},
|
||||
{"Metric": "Total Transactions", "Value": metrics["total_transactions"]},
|
||||
{"Metric": "Total Volume", "Value": f"{metrics['total_volume']} ETH"},
|
||||
{"Metric": "Average Price", "Value": f"{metrics['average_price']} ETH"},
|
||||
{"Metric": "Market Sentiment", "Value": f"{metrics['market_sentiment']:.2f}"}
|
||||
]
|
||||
|
||||
output(metrics_data, ctx.obj.get('output_format', format), title="Marketplace Metrics")
|
||||
|
||||
# Volume 24h
|
||||
if "volume_24h" in overview:
|
||||
volume_data = [
|
||||
{"Metric": "24h Volume", "Value": f"{overview['volume_24h']} ETH"}
|
||||
]
|
||||
|
||||
output(volume_data, ctx.obj.get('output_format', format), title="24-Hour Volume")
|
||||
|
||||
# Top performing chains
|
||||
if "top_performing_chains" in overview:
|
||||
chains = overview["top_performing_chains"]
|
||||
if chains:
|
||||
chain_data = [
|
||||
{
|
||||
"Chain ID": chain["chain_id"],
|
||||
"Volume": f"{chain['volume']} ETH",
|
||||
"Transactions": chain["transactions"]
|
||||
}
|
||||
for chain in chains[:5] # Top 5
|
||||
]
|
||||
|
||||
output(chain_data, ctx.obj.get('output_format', format), title="Top Performing Chains")
|
||||
|
||||
# Chain types distribution
|
||||
if "chain_types_distribution" in overview:
|
||||
distribution = overview["chain_types_distribution"]
|
||||
if distribution:
|
||||
dist_data = [
|
||||
{"Chain Type": chain_type, "Count": count}
|
||||
for chain_type, count in distribution.items()
|
||||
]
|
||||
|
||||
output(dist_data, ctx.obj.get('output_format', format), title="Chain Types Distribution")
|
||||
|
||||
# User activity
|
||||
if "user_activity" in overview:
|
||||
activity = overview["user_activity"]
|
||||
activity_data = [
|
||||
{"Metric": "Active Buyers (7d)", "Value": activity["active_buyers_7d"]},
|
||||
{"Metric": "Active Sellers (7d)", "Value": activity["active_sellers_7d"]},
|
||||
{"Metric": "Total Unique Users", "Value": activity["total_unique_users"]},
|
||||
{"Metric": "Average Reputation", "Value": f"{activity['average_reputation']:.3f}"}
|
||||
]
|
||||
|
||||
output(activity_data, ctx.obj.get('output_format', format), title="User Activity")
|
||||
|
||||
# Escrow summary
|
||||
if "escrow_summary" in overview:
|
||||
escrow = overview["escrow_summary"]
|
||||
escrow_data = [
|
||||
{"Metric": "Active Escrows", "Value": escrow["active_escrows"]},
|
||||
{"Metric": "Released Escrows", "Value": escrow["released_escrows"]},
|
||||
{"Metric": "Total Escrow Value", "Value": f"{escrow['total_escrow_value']} ETH"},
|
||||
{"Metric": "Escrow Fees Collected", "Value": f"{escrow['escrow_fee_collected']} ETH"}
|
||||
]
|
||||
|
||||
output(escrow_data, ctx.obj.get('output_format', format), title="Escrow Summary")
|
||||
|
||||
except Exception as e:
|
||||
error(f"Error getting marketplace overview: {str(e)}")
|
||||
raise click.Abort()
|
||||
|
||||
@marketplace.command()
|
||||
@click.option('--realtime', is_flag=True, help='Real-time monitoring')
|
||||
@click.option('--interval', default=30, help='Update interval in seconds')
|
||||
@click.pass_context
|
||||
def monitor(ctx, realtime, interval):
|
||||
"""Monitor marketplace activity"""
|
||||
try:
|
||||
config = load_multichain_config()
|
||||
marketplace = GlobalChainMarketplace(config)
|
||||
|
||||
if realtime:
|
||||
# Real-time monitoring
|
||||
from rich.console import Console
|
||||
from rich.live import Live
|
||||
from rich.table import Table
|
||||
import time
|
||||
|
||||
console = Console()
|
||||
|
||||
def generate_monitor_table():
|
||||
try:
|
||||
overview = asyncio.run(marketplace.get_marketplace_overview())
|
||||
|
||||
table = Table(title=f"Marketplace Monitor - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
table.add_column("Metric", style="cyan")
|
||||
table.add_column("Value", style="green")
|
||||
|
||||
if "marketplace_metrics" in overview:
|
||||
metrics = overview["marketplace_metrics"]
|
||||
table.add_row("Total Listings", str(metrics["total_listings"]))
|
||||
table.add_row("Active Listings", str(metrics["active_listings"]))
|
||||
table.add_row("Total Transactions", str(metrics["total_transactions"]))
|
||||
table.add_row("Total Volume", f"{metrics['total_volume']} ETH")
|
||||
table.add_row("Market Sentiment", f"{metrics['market_sentiment']:.2f}")
|
||||
|
||||
if "volume_24h" in overview:
|
||||
table.add_row("24h Volume", f"{overview['volume_24h']} ETH")
|
||||
|
||||
if "user_activity" in overview:
|
||||
activity = overview["user_activity"]
|
||||
table.add_row("Active Users (7d)", str(activity["active_buyers_7d"] + activity["active_sellers_7d"]))
|
||||
|
||||
return table
|
||||
except Exception as e:
|
||||
return f"Error getting marketplace data: {e}"
|
||||
|
||||
with Live(generate_monitor_table(), refresh_per_second=1) as live:
|
||||
try:
|
||||
while True:
|
||||
live.update(generate_monitor_table())
|
||||
time.sleep(interval)
|
||||
except KeyboardInterrupt:
|
||||
console.print("\n[yellow]Monitoring stopped by user[/yellow]")
|
||||
else:
|
||||
# Single snapshot
|
||||
overview = asyncio.run(marketplace.get_marketplace_overview())
|
||||
|
||||
monitor_data = []
|
||||
|
||||
if "marketplace_metrics" in overview:
|
||||
metrics = overview["marketplace_metrics"]
|
||||
monitor_data.extend([
|
||||
{"Metric": "Total Listings", "Value": metrics["total_listings"]},
|
||||
{"Metric": "Active Listings", "Value": metrics["active_listings"]},
|
||||
{"Metric": "Total Transactions", "Value": metrics["total_transactions"]},
|
||||
{"Metric": "Total Volume", "Value": f"{metrics['total_volume']} ETH"},
|
||||
{"Metric": "Market Sentiment", "Value": f"{metrics['market_sentiment']:.2f}"}
|
||||
])
|
||||
|
||||
if "volume_24h" in overview:
|
||||
monitor_data.append({"Metric": "24h Volume", "Value": f"{overview['volume_24h']} ETH"})
|
||||
|
||||
if "user_activity" in overview:
|
||||
activity = overview["user_activity"]
|
||||
monitor_data.append({"Metric": "Active Users (7d)", "Value": activity["active_buyers_7d"] + activity["active_sellers_7d"]})
|
||||
|
||||
output(monitor_data, ctx.obj.get('output_format', 'table'), title="Marketplace Monitor")
|
||||
|
||||
except Exception as e:
|
||||
error(f"Error during monitoring: {str(e)}")
|
||||
raise click.Abort()
|
||||
Reference in New Issue
Block a user