chore: refactor logging module, update genesis timestamp, remove model relationships, and reorganize routers

- Rename logging.py to logger.py and update import paths in poa.py and main.py
- Update devnet genesis timestamp to 1766828620
- Remove SQLModel Relationship declarations from Block, Transaction, and Receipt models
- Add SessionDep type alias and get_session dependency in coordinator-api deps
- Reorganize coordinator-api routers: replace explorer/registry with exchange, users, marketplace
This commit is contained in:
oib
2025-12-28 21:05:53 +01:00
parent cdaf1122c3
commit ff5486fe08
146 changed files with 33301 additions and 219 deletions

View File

@ -0,0 +1,175 @@
#!/usr/bin/env python3
"""
Bitcoin Wallet Integration for AITBC Trade Exchange
"""
import os
import json
import hashlib
import hmac
import time
from typing import Dict, Optional, Tuple
from dataclasses import dataclass
import requests
@dataclass
class BitcoinWallet:
"""Bitcoin wallet configuration"""
address: str
private_key: Optional[str] = None
testnet: bool = True
class BitcoinProcessor:
"""Bitcoin payment processor"""
def __init__(self, config: Dict):
self.config = config
self.testnet = config.get('testnet', True)
self.api_key = config.get('api_key')
self.webhook_secret = config.get('webhook_secret')
def generate_payment_address(self, user_id: str, amount_btc: float) -> str:
"""Generate a unique payment address for each transaction"""
# In production, use HD wallet to generate unique addresses
# For demo, we'll use a fixed address with payment tracking
# Create payment hash
payment_data = f"{user_id}:{amount_btc}:{int(time.time())}"
hash_bytes = hashlib.sha256(payment_data.encode()).hexdigest()
# For demo, return the main wallet address
# In production, generate unique address from HD wallet
return self.config['main_address']
def check_payment(self, address: str, amount_btc: float) -> Tuple[bool, float]:
"""Check if payment has been received"""
# In production, integrate with blockchain API
# For demo, simulate payment check
# Mock API call to check blockchain
if self.testnet:
# Testnet blockchain API
api_url = f"https://blockstream.info/testnet/api/address/{address}"
else:
# Mainnet blockchain API
api_url = f"https://blockstream.info/api/address/{address}"
try:
response = requests.get(api_url, timeout=5)
if response.status_code == 200:
data = response.json()
# Check recent transactions
# In production, implement proper transaction verification
return False, 0.0
except Exception as e:
print(f"Error checking payment: {e}")
return False, 0.0
def verify_webhook(self, payload: str, signature: str) -> bool:
"""Verify webhook signature from payment processor"""
if not self.webhook_secret:
return True # Skip verification if no secret
expected_signature = hmac.new(
self.webhook_secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected_signature, signature)
class WalletManager:
"""Manages Bitcoin wallet operations"""
def __init__(self):
self.config = self.load_config()
self.processor = BitcoinProcessor(self.config)
def load_config(self) -> Dict:
"""Load wallet configuration"""
return {
'testnet': os.getenv('BITCOIN_TESTNET', 'true').lower() == 'true',
'main_address': os.getenv('BITCOIN_ADDRESS', 'tb1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh'),
'private_key': os.getenv('BITCOIN_PRIVATE_KEY'),
'api_key': os.getenv('BLOCKCHAIN_API_KEY'),
'webhook_secret': os.getenv('WEBHOOK_SECRET'),
'min_confirmations': int(os.getenv('MIN_CONFIRMATIONS', '1')),
'exchange_rate': float(os.getenv('BTC_TO_AITBC_RATE', '100000')) # 1 BTC = 100,000 AITBC
}
def create_payment_request(self, user_id: str, aitbc_amount: float) -> Dict:
"""Create a new payment request"""
btc_amount = aitbc_amount / self.config['exchange_rate']
payment_request = {
'user_id': user_id,
'aitbc_amount': aitbc_amount,
'btc_amount': btc_amount,
'payment_address': self.processor.generate_payment_address(user_id, btc_amount),
'created_at': int(time.time()),
'status': 'pending',
'expires_at': int(time.time()) + 3600 # 1 hour expiry
}
# Save payment request
self.save_payment_request(payment_request)
return payment_request
def save_payment_request(self, request: Dict):
"""Save payment request to storage"""
payments_file = 'payments.json'
payments = []
if os.path.exists(payments_file):
with open(payments_file, 'r') as f:
payments = json.load(f)
payments.append(request)
with open(payments_file, 'w') as f:
json.dump(payments, f, indent=2)
def get_payment_status(self, payment_id: str) -> Optional[Dict]:
"""Get payment status"""
payments_file = 'payments.json'
if not os.path.exists(payments_file):
return None
with open(payments_file, 'r') as f:
payments = json.load(f)
for payment in payments:
if payment.get('payment_id') == payment_id:
return payment
return None
def update_payment_status(self, payment_id: str, status: str, tx_hash: str = None):
"""Update payment status"""
payments_file = 'payments.json'
if not os.path.exists(payments_file):
return False
with open(payments_file, 'r') as f:
payments = json.load(f)
for payment in payments:
if payment.get('payment_id') == payment_id:
payment['status'] = status
payment['updated_at'] = int(time.time())
if tx_hash:
payment['tx_hash'] = tx_hash
with open(payments_file, 'w') as f:
json.dump(payments, f, indent=2)
return True
return False
# Global wallet manager
wallet_manager = WalletManager()

View File

@ -0,0 +1,888 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AITBC Trade Exchange - Buy AITBC with Bitcoin</title>
<base href="/Exchange/">
<link rel="stylesheet" href="/assets/css/aitbc.css">
<script src="/assets/js/axios.min.js"></script>
<script src="/assets/js/lucide.js"></script>
<style>
.gradient-bg {
background: linear-gradient(135deg, #f97316 0%, #ea580c 100%);
}
.card-hover {
transition: all 0.3s ease;
}
.card-hover:hover {
transform: translateY(-4px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.pulse-animation {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
/* Fix navigation button styling */
.nav-button {
background: transparent !important;
color: white !important;
padding: 0.5rem 0.75rem;
border-radius: 0.5rem;
font-weight: 500;
transition: all 0.2s ease;
}
.nav-button:hover {
background: rgba(255, 255, 255, 0.1) !important;
color: white !important;
}
.nav-button:focus {
outline: none;
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.3);
}
</style>
</head>
<body class="bg-gray-50 dark:bg-gray-900 transition-colors duration-300">
<!-- Header -->
<header class="gradient-bg text-white shadow-lg">
<div class="container mx-auto px-4 py-6">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-3">
<i data-lucide="trending-up" class="w-8 h-8"></i>
<h1 class="text-2xl font-bold">AITBC Trade Exchange</h1>
</div>
<nav class="flex items-center space-x-6">
<button onclick="showSection('trade')" class="nav-button">Trade</button>
<button onclick="showSection('marketplace')" class="nav-button">Marketplace</button>
<button onclick="showSection('wallet')" class="nav-button">Wallet</button>
<button onclick="toggleDarkMode()" class="nav-button" title="Toggle dark mode">
<i data-lucide="moon" class="w-5 h-5" id="darkModeIcon"></i>
</button>
<button id="navConnectBtn" onclick="connectWallet()" class="bg-white text-orange-600 px-4 py-2 rounded-lg hover:bg-orange-100 transition">
<i data-lucide="wallet" class="w-4 h-4 inline mr-2"></i>Connect Wallet
</button>
<div id="navUserInfo" class="hidden flex items-center space-x-3">
<span class="text-sm text-white" id="navUsername">-</span>
<button onclick="showSection('wallet')" class="nav-button">
<i data-lucide="user" class="w-5 h-5"></i>
</button>
<button onclick="logout()" class="nav-button">
<i data-lucide="log-out" class="w-5 h-5"></i>
</button>
</div>
</nav>
</div>
</div>
</header>
<!-- Price Ticker -->
<section class="bg-white dark:bg-gray-800 border-b dark:border-gray-700">
<div class="container mx-auto px-4 py-4">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-6">
<div class="flex items-center space-x-2">
<span class="text-gray-600 dark:text-gray-400">AITBC/BTC:</span>
<span class="text-2xl font-bold text-green-600 dark:text-green-400" id="aitbcBtcPrice">0.00001</span>
<span class="text-sm text-green-500 dark:text-green-400">+5.2%</span>
</div>
<div class="flex items-center space-x-2">
<span class="text-gray-600 dark:text-gray-400">24h Volume:</span>
<span class="font-semibold text-gray-900 dark:text-white">1,234 AITBC</span>
</div>
</div>
<div class="text-sm text-gray-500 dark:text-gray-400">
Last updated: <span id="lastUpdated">Just now</span>
</div>
</div>
</div>
</section>
<!-- Main Content -->
<main class="container mx-auto px-4 py-8">
<!-- Trade Section -->
<section id="tradeSection" class="section">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<!-- Buy AITBC -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6">
<h2 class="text-xl font-bold mb-6 flex items-center text-gray-900 dark:text-white">
<i data-lucide="arrow-down-left" class="w-5 h-5 mr-2 text-green-600 dark:text-green-400"></i>
Buy AITBC with Bitcoin
</h2>
<div id="tradeConnectPrompt" class="bg-orange-50 dark:bg-orange-900/20 border border-orange-200 dark:border-orange-800 rounded-lg p-4 mb-4">
<p class="text-sm text-orange-800 dark:text-orange-200 mb-3">
<i data-lucide="wallet" class="w-4 h-4 inline mr-1"></i>
Connect your wallet to start trading
</p>
<button onclick="connectWallet()" class="bg-orange-600 text-white px-4 py-2 rounded-lg hover:bg-orange-700 transition">
<i data-lucide="wallet" class="w-4 h-4 inline mr-2"></i>Connect Wallet
</button>
</div>
<div id="tradeForm" class="hidden">
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4 mb-4">
<p class="text-sm text-blue-800 dark:text-blue-200">
<i data-lucide="info" class="w-4 h-4 inline mr-1"></i>
Send Bitcoin to the generated address. Your AITBC will be credited after 1 confirmation.
</p>
</div>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Pay with Bitcoin</label>
<div class="relative">
<input type="number" id="btcAmount" class="w-full border dark:border-gray-600 rounded-lg px-4 py-3 pr-12 bg-white dark:bg-gray-700 text-gray-900 dark:text-white" placeholder="0.001" step="0.00001">
<span class="absolute right-3 top-3 text-gray-500 dark:text-gray-400">BTC</span>
</div>
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">Available: 0.12345 BTC</p>
</div>
<div class="flex justify-center">
<button onclick="swapCurrencies()" class="p-2 bg-gray-100 dark:bg-gray-700 rounded-full hover:bg-gray-200 dark:hover:bg-gray-600 transition">
<i data-lucide="arrow-up-down" class="w-5 h-5 text-gray-700 dark:text-gray-300"></i>
</button>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">You will receive</label>
<div class="relative">
<input type="number" id="aitbcAmount" class="w-full border dark:border-gray-600 rounded-lg px-4 py-3 pr-16 bg-white dark:bg-gray-700 text-gray-900 dark:text-white" placeholder="100" step="0.01">
<span class="absolute right-3 top-3 text-gray-500 dark:text-gray-400">AITBC</span>
</div>
</div>
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
<div class="flex justify-between text-sm mb-2 text-gray-700 dark:text-gray-300">
<span>Price</span>
<span>0.00001 BTC/AITBC</span>
</div>
<div class="flex justify-between text-sm mb-2 text-gray-700 dark:text-gray-300">
<span>Fee (0.5%)</span>
<span>0.000005 BTC</span>
</div>
<div class="flex justify-between text-sm font-semibold text-gray-900 dark:text-white">
<span>Total</span>
<span>0.001005 BTC</span>
</div>
</div>
<button onclick="createPaymentRequest()" class="w-full bg-green-600 text-white py-3 rounded-lg hover:bg-green-700 transition font-semibold">
Create Payment Request
</button>
</div>
</div>
</div>
<!-- Order Book -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6">
<h2 class="text-xl font-bold mb-6 flex items-center justify-between text-gray-900 dark:text-white">
<span class="flex items-center">
<i data-lucide="book-open" class="w-5 h-5 mr-2 text-blue-600 dark:text-blue-400"></i>
Order Book
</span>
<div class="flex space-x-2">
<button onclick="refreshOrderBook()" class="p-1 hover:bg-gray-100 dark:hover:bg-gray-700 rounded">
<i data-lucide="refresh-cw" class="w-4 h-4 text-gray-700 dark:text-gray-300"></i>
</button>
</div>
</h2>
<div class="grid grid-cols-2 gap-4">
<!-- Sell Orders -->
<div>
<h3 class="text-sm font-semibold text-red-600 dark:text-red-400 mb-3">Sell Orders</h3>
<div class="space-y-1" id="sellOrders">
<div class="flex justify-between text-sm">
<span class="text-red-600 dark:text-red-400">0.00001</span>
<span class="text-gray-900 dark:text-white">500</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-red-600 dark:text-red-400">0.000011</span>
<span class="text-gray-900 dark:text-white">300</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-red-600 dark:text-red-400">0.000012</span>
<span class="text-gray-900 dark:text-white">200</span>
</div>
</div>
</div>
<!-- Buy Orders -->
<div>
<h3 class="text-sm font-semibold text-green-600 dark:text-green-400 mb-3">Buy Orders</h3>
<div class="space-y-1" id="buyOrders">
<div class="flex justify-between text-sm">
<span class="text-green-600 dark:text-green-400">0.000009</span>
<span class="text-gray-900 dark:text-white">150</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-green-600 dark:text-green-400">0.000008</span>
<span class="text-gray-900 dark:text-white">200</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-green-600 dark:text-green-400">0.000007</span>
<span class="text-gray-900 dark:text-white">300</span>
</div>
</div>
</div>
</div>
<!-- Recent Trades -->
<div class="mt-6 pt-6 border-t dark:border-gray-700">
<h3 class="text-sm font-semibold text-gray-600 dark:text-gray-400 mb-3">Recent Trades</h3>
<div class="space-y-1" id="recentTrades">
<div class="flex justify-between text-sm">
<span class="text-gray-900 dark:text-white">0.000010</span>
<span class="text-gray-600 dark:text-gray-400">100</span>
<span class="text-green-600 dark:text-green-400">Buy</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-gray-900 dark:text-white">0.000011</span>
<span class="text-red-600 dark:text-red-400">50</span>
<span class="text-gray-500 dark:text-gray-400">5 min ago</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-gray-900 dark:text-white">0.00001</span>
<span class="text-green-600 dark:text-green-400">200</span>
<span class="text-gray-500 dark:text-gray-400">8 min ago</span>
</div>
</div>
</div>
</div>
</div>
<!-- GPU Marketplace Link -->
<div class="mt-8 bg-gradient-to-r from-purple-600 to-blue-600 rounded-lg p-8 text-white">
<div class="flex items-center justify-between">
<div>
<h2 class="text-2xl font-bold mb-2">Ready to Use Your AITBC?</h2>
<p class="mb-4">Purchase GPU compute time for AI workloads on our decentralized marketplace</p>
<button onclick="showSection('marketplace')" class="bg-white text-purple-600 px-6 py-3 rounded-lg hover:bg-purple-100 transition font-semibold">
Browse GPU Marketplace
</button>
</div>
<i data-lucide="cpu" class="w-24 h-24 opacity-50"></i>
</div>
</div>
</section>
<!-- Marketplace Section -->
<section id="marketplaceSection" class="section hidden">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold">Available GPU Compute</h2>
<button onclick="showSection('trade')" class="bg-orange-600 text-white px-4 py-2 rounded-lg hover:bg-orange-700 transition">
<i data-lucide="arrow-left" class="w-4 h-4 inline mr-2"></i>Back to Trading
</button>
</div>
<div id="gpuList" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<!-- GPU cards will be inserted here -->
</div>
</section>
<!-- Wallet Section -->
<section id="walletSection" class="section hidden">
<div class="max-w-4xl mx-auto">
<h2 class="text-2xl font-bold mb-6">Your Profile</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<!-- User Profile Card -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-8">
<h3 class="text-lg font-semibold mb-4 flex items-center">
<i data-lucide="user" class="w-5 h-5 mr-2 text-blue-500"></i>
User Profile
</h3>
<div id="notLoggedIn" class="space-y-4">
<p class="text-gray-600 dark:text-gray-400">Please connect your wallet to access your profile</p>
<button onclick="connectWallet()" id="connectWalletBtn" class="w-full bg-orange-600 text-white px-4 py-2 rounded-lg hover:bg-orange-700 transition">
<i data-lucide="wallet" class="w-4 h-4 inline mr-2"></i>Connect Wallet
</button>
</div>
<div id="userProfile" class="hidden space-y-4">
<div>
<p class="text-sm text-gray-500 dark:text-gray-400">Username</p>
<p class="font-semibold text-gray-900 dark:text-white" id="userUsername">-</p>
</div>
<div>
<p class="text-sm text-gray-500 dark:text-gray-400">User ID</p>
<p class="font-mono text-xs text-gray-700 dark:text-gray-300" id="userId">-</p>
</div>
<div>
<p class="text-sm text-gray-500 dark:text-gray-400">Member Since</p>
<p class="text-sm text-gray-700 dark:text-gray-300" id="userCreated">-</p>
</div>
<button onclick="logout()" class="w-full bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 px-4 py-2 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition">
<i data-lucide="log-out" class="w-4 h-4 inline mr-2"></i>Logout
</button>
</div>
</div>
<!-- AITBC Wallet -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-8">
<h3 class="text-lg font-semibold mb-4 flex items-center">
<i data-lucide="coins" class="w-5 h-5 mr-2 text-purple-500"></i>
AITBC Wallet
</h3>
<div class="space-y-3">
<div>
<p class="text-sm text-gray-500 dark:text-gray-400">Address</p>
<p class="font-mono text-sm text-gray-700 dark:text-gray-300" id="aitbcAddress">Not connected</p>
</div>
<div>
<p class="text-sm text-gray-500 dark:text-gray-400">Balance</p>
<p class="text-2xl font-bold text-gray-900 dark:text-white" id="aitbcBalance">0 AITBC</p>
</div>
</div>
</div>
</div>
<!-- Transaction History -->
<div class="mt-12 bg-white dark:bg-gray-800 rounded-lg shadow-lg p-8">
<h3 class="text-lg font-semibold mb-6 text-gray-900 dark:text-white">Transaction History</h3>
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="border-b dark:border-gray-700">
<th class="text-left py-3 text-gray-700 dark:text-gray-300">Time</th>
<th class="text-left py-3 text-gray-700 dark:text-gray-300">Type</th>
<th class="text-left py-3 text-gray-700 dark:text-gray-300">Amount</th>
<th class="text-left py-3 text-gray-700 dark:text-gray-300">Status</th>
</tr>
</thead>
<tbody id="transactionHistory">
<tr class="border-b dark:border-gray-700">
<td class="py-3 text-gray-700 dark:text-gray-300">2025-12-28 10:30</td>
<td class="py-2">
<span class="text-green-600 dark:text-green-400">Buy AITBC</span>
</td>
<td class="py-3 text-gray-700 dark:text-gray-300">+100 AITBC</td>
<td class="py-2">
<span class="bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-300 px-2 py-1 rounded text-xs">Completed</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</section>
</main>
<!-- QR Code Modal -->
<div id="qrModal" class="fixed inset-0 bg-black bg-opacity-50 hidden flex items-center justify-center z-50">
<div class="bg-white dark:bg-gray-800 rounded-lg p-8 max-w-md w-full">
<h3 class="text-xl font-bold mb-4 text-gray-900 dark:text-white">Send Bitcoin to Complete Purchase</h3>
<div class="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg mb-4">
<img id="paymentQR" src="" alt="Payment QR Code" class="mx-auto">
</div>
<div class="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-3 mb-4">
<p class="text-sm text-yellow-800 dark:text-yellow-200">
<strong>Payment Address:</strong>
</p>
<p class="font-mono text-xs break-all text-gray-700 dark:text-gray-300" id="paymentAddress"></p>
</div>
<div class="grid grid-cols-2 gap-4 mb-4 text-sm">
<div>
<p class="text-gray-600 dark:text-gray-400">Amount to Send:</p>
<p class="font-semibold text-gray-900 dark:text-white" id="paymentAmount">0 BTC</p>
</div>
<div>
<p class="text-gray-600 dark:text-gray-400">You'll Receive:</p>
<p class="font-semibold text-green-600 dark:text-green-400" id="receiveAmount">0 AITBC</p>
</div>
</div>
<div class="flex items-center justify-center mb-4">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-green-600" id="paymentSpinner"></div>
<span class="ml-2 text-sm text-gray-600 dark:text-gray-400">Waiting for payment...</span>
</div>
<div class="flex space-x-3">
<button onclick="closeQRModal()" class="flex-1 bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 py-2 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition">
Cancel
</button>
<button onclick="checkPaymentStatus()" class="flex-1 bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 transition">
Check Payment
</button>
</div>
</div>
</div>
<script>
// API Configuration
const API_BASE = window.location.origin + '/api';
const BLOCKCHAIN_API = window.location.origin + '/rpc';
const EXCHANGE_RATE = 0.00001; // 1 AITBC = 0.00001 BTC
let walletAddress = null;
let currentUser = null;
let sessionToken = null;
let aitbcBalance = 0;
// Initialize
document.addEventListener('DOMContentLoaded', () => {
lucide.createIcons();
updatePrices();
loadGPUOffers();
// Auto-refresh prices every 30 seconds
setInterval(updatePrices, 30000);
// Input handlers
document.getElementById('btcAmount').addEventListener('input', updateAITBCAmount);
document.getElementById('aitbcAmount').addEventListener('input', updateBTCAmount);
// Check for saved dark mode preference
if (localStorage.getItem('darkMode') === 'true' ||
(!localStorage.getItem('darkMode') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
updateDarkModeIcon(true);
}
});
// Dark mode toggle
function toggleDarkMode() {
const isDark = document.documentElement.classList.toggle('dark');
localStorage.setItem('darkMode', isDark);
updateDarkModeIcon(isDark);
}
function updateDarkModeIcon(isDark) {
const icon = document.getElementById('darkModeIcon');
if (isDark) {
icon.setAttribute('data-lucide', 'sun');
} else {
icon.setAttribute('data-lucide', 'moon');
}
lucide.createIcons();
}
// Section Navigation
function showSection(section) {
document.querySelectorAll('.section').forEach(s => s.classList.add('hidden'));
document.getElementById(section + 'Section').classList.remove('hidden');
if (section === 'marketplace') {
loadGPUOffers();
}
}
// Connect Wallet
async function connectWallet() {
try {
// For demo, create a new wallet
const walletId = 'wallet-' + Math.random().toString(36).substr(2, 9);
const address = 'aitbc1' + walletId + 'x'.repeat(40 - walletId.length);
// Login or register user
const response = await axios.post(`${API_BASE}/users/login`, {
wallet_address: address
});
const user = response.data;
currentUser = user;
sessionToken = user.session_token;
walletAddress = address;
// Update UI
document.getElementById('aitbcAddress').textContent = address;
document.getElementById('userUsername').textContent = user.username;
document.getElementById('userId').textContent = user.user_id;
document.getElementById('userCreated').textContent = new Date(user.created_at).toLocaleDateString();
// Update navigation
document.getElementById('navConnectBtn').classList.add('hidden');
document.getElementById('navUserInfo').classList.remove('hidden');
document.getElementById('navUsername').textContent = user.username;
// Show trade form, hide connect prompt
document.getElementById('tradeConnectPrompt').classList.add('hidden');
document.getElementById('tradeForm').classList.remove('hidden');
// Show profile, hide login prompt
document.getElementById('notLoggedIn').classList.add('hidden');
document.getElementById('userProfile').classList.remove('hidden');
showToast('Wallet connected: ' + address.substring(0, 20) + '...');
// Load user balance
await loadUserBalance();
} catch (error) {
console.error('Failed to connect wallet:', error);
showToast('Failed to connect wallet', 'error');
}
}
// Update Prices
function updatePrices() {
// Simulate price updates
const variation = (Math.random() - 0.5) * 0.000001;
const newPrice = EXCHANGE_RATE + variation;
document.getElementById('aitbcBtcPrice').textContent = newPrice.toFixed(5);
document.getElementById('lastUpdated').textContent = 'Just now';
}
// Currency Conversion
function updateAITBCAmount() {
const btcAmount = parseFloat(document.getElementById('btcAmount').value) || 0;
const aitbcAmount = btcAmount / EXCHANGE_RATE;
document.getElementById('aitbcAmount').value = aitbcAmount.toFixed(2);
}
function updateBTCAmount() {
const aitbcAmount = parseFloat(document.getElementById('aitbcAmount').value) || 0;
const btcAmount = aitbcAmount * EXCHANGE_RATE;
document.getElementById('btcAmount').value = btcAmount.toFixed(5);
}
function swapCurrencies() {
const btcInput = document.getElementById('btcAmount');
const aitbcInput = document.getElementById('aitbcAmount');
const temp = btcInput.value;
btcInput.value = aitbcInput.value;
aitbcInput.value = temp;
updateAITBCFromBTC();
}
// Create Payment Request
async function createPaymentRequest() {
const btcAmount = parseFloat(document.getElementById('btcAmount').value) || 0;
const aitbcAmount = parseFloat(document.getElementById('aitbcAmount').value) || 0;
if (btcAmount <= 0 || aitbcAmount <= 0) {
showToast('Please enter a valid amount', 'error');
return;
}
if (!currentUser || !sessionToken) {
showToast('Please connect your wallet first', 'error');
return;
}
try {
// Create payment request
const response = await axios.post(`${API_BASE}/exchange/create-payment`, {
user_id: currentUser.user_id,
aitbc_amount: aitbcAmount,
btc_amount: btcAmount
}, {
headers: { 'X-Session-Token': sessionToken }
});
const payment = response.data;
showPaymentModal(payment);
// Start checking payment status
startPaymentMonitoring(payment.payment_id);
} catch (error) {
console.error('Failed to create payment:', error);
showToast('Failed to create payment request', 'error');
}
}
// Show Payment Modal
function showPaymentModal(payment) {
// Update modal with payment details
document.getElementById('paymentAddress').textContent = payment.payment_address;
document.getElementById('paymentAmount').textContent = payment.btc_amount + ' BTC';
document.getElementById('receiveAmount').textContent = payment.aitbc_amount + ' AITBC';
// Generate QR code
const qrData = `bitcoin:${payment.payment_address}?amount=${payment.btc_amount}`;
document.getElementById('paymentQR').src = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(qrData)}`;
// Store payment ID for checking
window.currentPaymentId = payment.payment_id;
// Show modal
document.getElementById('qrModal').classList.remove('hidden');
}
// Start Payment Monitoring
function startPaymentMonitoring(paymentId) {
const checkInterval = setInterval(async () => {
try {
const response = await axios.get(`${API_BASE}/exchange/payment-status/${paymentId}`);
const payment = response.data;
if (payment.status === 'confirmed') {
clearInterval(checkInterval);
handlePaymentConfirmed(payment);
} else if (payment.status === 'expired') {
clearInterval(checkInterval);
showToast('Payment expired. Please try again.', 'error');
closeQRModal();
}
} catch (error) {
console.error('Error checking payment:', error);
}
}, 10000); // Check every 10 seconds
}
// Check Payment Status
async function checkPaymentStatus() {
if (!window.currentPaymentId) return;
try {
const response = await axios.get(`${API_BASE}/exchange/payment-status/${window.currentPaymentId}`);
const payment = response.data;
if (payment.status === 'confirmed') {
handlePaymentConfirmed(payment);
} else {
showToast('Payment not yet detected. Please wait.', 'info');
}
} catch (error) {
console.error('Error checking payment:', error);
showToast('Error checking payment status', 'error');
}
}
// Handle Payment Confirmed
function handlePaymentConfirmed(payment) {
closeQRModal();
showToast(`Payment confirmed! ${payment.aitbc_amount} AITBC credited to your wallet.`, 'success');
// Update wallet balance
updateWalletBalance();
// Add to transaction history
addTransaction('Buy AITBC', `+${payment.aitbc_amount} AITBC`, 'Completed');
// Clear form
document.getElementById('btcAmount').value = '';
document.getElementById('aitbcAmount').value = '';
}
// Close QR Modal
function closeQRModal() {
document.getElementById('qrModal').classList.add('hidden');
window.currentPaymentId = null;
}
// Mint AITBC (simulated)
async function mintAITBC(address, amount) {
try {
const response = await axios.post(`${BLOCKCHAIN_API}/admin/mintFaucet`, {
address: address,
amount: amount
});
console.log('Minted AITBC:', response.data);
} catch (error) {
console.error('Failed to mint AITBC:', error);
}
}
// Logout
async function logout() {
if (!sessionToken) return;
try {
await axios.post(`${API_BASE}/users/logout`, {}, {
headers: { 'X-Session-Token': sessionToken }
});
} catch (error) {
console.error('Logout error:', error);
}
// Clear local data
currentUser = null;
sessionToken = null;
walletAddress = null;
aitbcBalance = 0;
// Update UI
document.getElementById('notLoggedIn').classList.remove('hidden');
document.getElementById('userProfile').classList.add('hidden');
document.getElementById('aitbcAddress').textContent = 'Not connected';
document.getElementById('aitbcBalance').textContent = '0 AITBC';
// Update navigation
document.getElementById('navConnectBtn').classList.remove('hidden');
document.getElementById('navUserInfo').classList.add('hidden');
// Hide trade form, show connect prompt
document.getElementById('tradeConnectPrompt').classList.remove('hidden');
document.getElementById('tradeForm').classList.add('hidden');
showToast('Logged out successfully');
}
// Load User Balance
async function loadUserBalance() {
if (!currentUser || !sessionToken) return;
try {
const response = await axios.get(
`${API_BASE}/users/${currentUser.user_id}/balance`,
{ headers: { 'X-Session-Token': sessionToken } }
);
const balance = response.data;
aitbcBalance = balance.balance;
document.getElementById('aitbcBalance').textContent = aitbcBalance.toFixed(2);
} catch (error) {
console.error('Failed to load balance:', error);
}
}
// Update Wallet Balance (legacy)
async function updateWalletBalance() {
if (!walletAddress) return;
try {
const response = await axios.get(`${BLOCKCHAIN_API}/getBalance/${walletAddress}`);
aitbcBalance = response.data.balance;
document.getElementById('aitbcBalance').textContent = aitbcBalance + ' AITBC';
} catch (error) {
// Demo balance
aitbcBalance = 1000;
document.getElementById('aitbcBalance').textContent = aitbcBalance + ' AITBC';
}
}
// Load GPU Offers
async function loadGPUOffers() {
try {
const response = await axios.get(`${API_BASE}/marketplace/offers`);
displayGPUOffers(response.data);
} catch (error) {
// Display demo offers
displayGPUOffers([{
id: '1',
provider: 'miner_dev_key_1',
capacity: 1,
price: 50,
attributes: {
gpu_model: 'NVIDIA RTX 4060 Ti',
gpu_memory_gb: 16,
cuda_version: '12.4',
supported_models: ['stable-diffusion', 'llama2-7b']
}
}]);
}
}
// Display GPU Offers
function displayGPUOffers(offers) {
const container = document.getElementById('gpuList');
if (offers.length === 0) {
container.innerHTML = '<div class="col-span-full text-center py-12 text-gray-500">No GPU offers available at the moment.</div>';
return;
}
container.innerHTML = offers.map(offer => {
const attrs = offer.attributes || {};
return `
<div class="bg-white rounded-lg shadow-lg p-6 card-hover">
<div class="flex justify-between items-start mb-4">
<h3 class="text-lg font-semibold">${attrs.gpu_model || 'GPU'}</h3>
<span class="bg-green-100 text-green-800 px-2 py-1 rounded text-sm">Available</span>
</div>
<div class="space-y-2 text-sm text-gray-600 mb-4">
<p><i data-lucide="monitor" class="w-4 h-4 inline mr-1"></i>Memory: ${attrs.gpu_memory_gb || 'N/A'} GB</p>
<p><i data-lucide="zap" class="w-4 h-4 inline mr-1"></i>CUDA: ${attrs.cuda_version || 'N/A'}</p>
<p><i data-lucide="cpu" class="w-4 h-4 inline mr-1"></i>Capacity: ${offer.capacity || 1} GPU(s)</p>
</div>
<div class="flex justify-between items-center">
<span class="text-2xl font-bold text-purple-600">${offer.price || '50'} AITBC/hr</span>
<button onclick="purchaseGPU('${offer.id}')" class="bg-purple-600 text-white px-4 py-2 rounded hover:bg-purple-700 transition">
Purchase
</button>
</div>
</div>
`;
}).join('');
lucide.createIcons();
}
// Purchase GPU
async function purchaseGPU(offerId) {
if (!walletAddress) {
showToast('Please connect your wallet first', 'error');
return;
}
if (aitbcBalance < 100) {
showToast('Insufficient AITBC balance. Please purchase more tokens.', 'error');
showSection('trade');
return;
}
showToast('GPU time purchased successfully!');
addTransaction('GPU Purchase', '-100 AITBC', 'Completed');
updateWalletBalance();
}
// Refresh Order Book
function refreshOrderBook() {
// Simulate order book refresh
showToast('Order book refreshed');
}
// Transaction Management
function addTransaction(type, amount, status) {
const tbody = document.getElementById('transactionHistory');
const time = new Date().toLocaleString();
const row = document.createElement('tr');
row.className = 'border-b';
row.innerHTML = `
<td class="py-2">${time}</td>
<td class="py-2">
<span class="${amount.startsWith('+') ? 'text-green-600' : 'text-red-600'}">${type}</span>
</td>
<td class="py-2">${amount}</td>
<td class="py-2">
<span class="bg-green-100 text-green-800 px-2 py-1 rounded text-xs">${status}</span>
</td>
`;
tbody.insertBefore(row, tbody.firstChild);
}
// QR Modal
function showQRModal() {
document.getElementById('qrModal').classList.remove('hidden');
}
function closeQRModal() {
document.getElementById('qrModal').classList.add('hidden');
}
// Toast Notification
function showToast(message, type = 'success') {
const toast = document.createElement('div');
toast.className = `fixed bottom-4 right-4 px-6 py-3 rounded-lg shadow-lg transform transition-transform duration-300 ${
type === 'error' ? 'bg-red-500' : 'bg-green-500'
} text-white`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.style.transform = 'translateY(0)';
}, 100);
setTimeout(() => {
toast.style.transform = 'translateY(100%)';
setTimeout(() => toast.remove(), 300);
}, 3000);
}
</script>
</body>
</html>

View File

@ -0,0 +1,620 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AITBC Trade Exchange - Buy AITBC with Bitcoin</title>
<base href="/Exchange/">
<!-- Production: Use local assets -->
<script src="/assets/js/tailwind.js"></script>
<script>
tailwind.config = {
darkMode: 'class'
}
</script>
<script src="/assets/js/axios.min.js"></script>
<script src="/assets/js/lucide.js"></script>
<style>
.gradient-bg {
background: linear-gradient(135deg, #f97316 0%, #ea580c 100%);
}
.card-hover {
transition: all 0.3s ease;
}
.card-hover:hover {
transform: translateY(-4px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.pulse-animation {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
</style>
</head>
<body class="bg-gray-50 dark:bg-gray-900 transition-colors duration-300">
<!-- Header -->
<header class="gradient-bg text-white shadow-lg">
<div class="container mx-auto px-4 py-6">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-3">
<i data-lucide="trending-up" class="w-8 h-8"></i>
<h1 class="text-2xl font-bold">AITBC Trade Exchange</h1>
</div>
<nav class="flex items-center space-x-6">
<button onclick="showSection('trade')" class="hover:text-orange-200 transition">Trade</button>
<button onclick="showSection('marketplace')" class="hover:text-orange-200 transition">Marketplace</button>
<button onclick="showSection('wallet')" class="hover:text-orange-200 transition">Wallet</button>
<button onclick="toggleDarkMode()" class="hover:text-orange-200 transition" title="Toggle dark mode">
<i data-lucide="moon" class="w-5 h-5" id="darkModeIcon"></i>
</button>
<button id="navConnectBtn" onclick="connectWallet()" class="bg-white text-orange-600 px-4 py-2 rounded-lg hover:bg-orange-100 transition">
<i data-lucide="wallet" class="w-4 h-4 inline mr-2"></i>Connect Wallet
</button>
<div id="navUserInfo" class="hidden flex items-center space-x-3">
<span class="text-sm" id="navUsername">-</span>
<button onclick="showSection('wallet')" class="text-white hover:text-orange-200 transition">
<i data-lucide="user" class="w-5 h-5"></i>
</button>
<button onclick="logout()" class="text-white hover:text-orange-200 transition">
<i data-lucide="log-out" class="w-5 h-5"></i>
</button>
</div>
</nav>
</div>
</div>
</header>
<!-- Price Ticker -->
<section class="bg-white dark:bg-gray-800 border-b dark:border-gray-700">
<div class="container mx-auto px-4 py-4">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-6">
<div class="flex items-center space-x-2">
<span class="text-gray-600 dark:text-gray-400">AITBC/BTC:</span>
<span class="text-2xl font-bold text-green-600 dark:text-green-400" id="aitbcBtcPrice">0.00001</span>
<span class="text-sm text-green-500 dark:text-green-400">+5.2%</span>
</div>
<div class="flex items-center space-x-2">
<span class="text-gray-600 dark:text-gray-400">24h Volume:</span>
<span class="font-semibold text-gray-900 dark:text-white">1,234 AITBC</span>
</div>
</div>
<div class="text-sm text-gray-500 dark:text-gray-400">
Last updated: <span id="lastUpdated">Just now</span>
</div>
</div>
</div>
</section>
<!-- Main Content -->
<main class="container mx-auto px-4 py-8">
<!-- Trade Section -->
<section id="tradeSection" class="section">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<!-- Buy AITBC -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6">
<h2 class="text-xl font-bold mb-6 flex items-center text-gray-900 dark:text-white">
<i data-lucide="arrow-down-left" class="w-5 h-5 mr-2 text-green-600 dark:text-green-400"></i>
Buy AITBC with Bitcoin
</h2>
<div id="tradeConnectPrompt" class="bg-orange-50 dark:bg-orange-900/20 border border-orange-200 dark:border-orange-800 rounded-lg p-4 mb-4">
<p class="text-sm text-orange-800 dark:text-orange-200 mb-3">
<i data-lucide="wallet" class="w-4 h-4 inline mr-1"></i>
Connect your wallet to start trading
</p>
<button onclick="connectWallet()" class="bg-orange-600 text-white px-4 py-2 rounded-lg hover:bg-orange-700 transition">
<i data-lucide="wallet" class="w-4 h-4 inline mr-2"></i>Connect Wallet
</button>
</div>
<div id="tradeForm" class="hidden">
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4 mb-4">
<p class="text-sm text-blue-800 dark:text-blue-200">
<i data-lucide="info" class="w-4 h-4 inline mr-1"></i>
Send Bitcoin to the generated address. Your AITBC will be credited after 1 confirmation.
</p>
</div>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Pay with Bitcoin</label>
<div class="relative">
<input type="number" id="btcAmount" class="w-full border dark:border-gray-600 rounded-lg px-4 py-3 pr-12 bg-white dark:bg-gray-700 text-gray-900 dark:text-white" placeholder="0.001" step="0.00001">
<span class="absolute right-3 top-3 text-gray-500 dark:text-gray-400">BTC</span>
</div>
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">Available: 0.12345 BTC</p>
</div>
<div class="flex justify-center">
<button onclick="swapCurrencies()" class="p-2 bg-gray-100 dark:bg-gray-700 rounded-full hover:bg-gray-200 dark:hover:bg-gray-600 transition">
<i data-lucide="arrow-up-down" class="w-5 h-5 text-gray-700 dark:text-gray-300"></i>
</button>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">You will receive</label>
<div class="relative">
<input type="number" id="aitbcAmount" class="w-full border dark:border-gray-600 rounded-lg px-4 py-3 pr-16 bg-white dark:bg-gray-700 text-gray-900 dark:text-white" placeholder="100" step="0.01">
<span class="absolute right-3 top-3 text-gray-500 dark:text-gray-400">AITBC</span>
</div>
</div>
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
<div class="flex justify-between text-sm mb-2 text-gray-700 dark:text-gray-300">
<span>Price</span>
<span>0.00001 BTC/AITBC</span>
</div>
<div class="flex justify-between text-sm mb-2 text-gray-700 dark:text-gray-300">
<span>Fee (0.5%)</span>
<span>0.000005 BTC</span>
</div>
<div class="flex justify-between text-sm font-semibold text-gray-900 dark:text-white">
<span>Total</span>
<span>0.001005 BTC</span>
</div>
</div>
<button onclick="createPaymentRequest()" class="w-full bg-green-600 text-white py-3 rounded-lg hover:bg-green-700 transition font-semibold">
Create Payment Request
</button>
</div>
</div>
<!-- Order Book -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6">
<h2 class="text-xl font-bold mb-6 flex items-center text-gray-900 dark:text-white">
<i data-lucide="book-open" class="w-5 h-5 mr-2 text-blue-600 dark:text-blue-400"></i>
Order Book
</h2>
<div class="space-y-4">
<div class="border-b dark:border-gray-700 pb-2">
<h3 class="text-sm font-semibold text-gray-600 dark:text-gray-400 mb-2">Buy Orders</h3>
<div class="space-y-1">
<div class="flex justify-between text-sm">
<span class="text-green-600 dark:text-green-400">100 AITBC</span>
<span class="text-gray-700 dark:text-gray-300">0.001 BTC</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-green-600 dark:text-green-400">50 AITBC</span>
<span class="text-gray-700 dark:text-gray-300">0.0005 BTC</span>
</div>
</div>
</div>
<div>
<h3 class="text-sm font-semibold text-gray-600 dark:text-gray-400 mb-2">Sell Orders</h3>
<div class="space-y-1">
<div class="flex justify-between text-sm">
<span class="text-red-600 dark:text-red-400">200 AITBC</span>
<span class="text-gray-700 dark:text-gray-300">0.002 BTC</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-red-600 dark:text-red-400">150 AITBC</span>
<span class="text-gray-700 dark:text-gray-300">0.0015 BTC</span>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Marketplace Section -->
<section id="marketplaceSection" class="section hidden">
<h2 class="text-2xl font-bold mb-6 text-gray-900 dark:text-white">GPU Marketplace</h2>
<div id="gpuOffers" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<!-- GPU offers will be loaded here -->
</div>
</section>
<!-- Wallet Section -->
<section id="walletSection" class="section hidden">
<h2 class="text-2xl font-bold mb-6 text-gray-900 dark:text-white">My Wallet</h2>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h3 class="text-lg font-semibold mb-4 text-gray-900 dark:text-white">Wallet Information</h3>
<div class="space-y-3">
<div>
<span class="text-sm text-gray-600 dark:text-gray-400">Address:</span>
<p class="font-mono text-sm bg-gray-100 dark:bg-gray-700 p-2 rounded" id="walletAddress">-</p>
</div>
<div>
<span class="text-sm text-gray-600 dark:text-gray-400">Username:</span>
<p id="walletUsername">-</p>
</div>
</div>
</div>
<div>
<h3 class="text-lg font-semibold mb-4 text-gray-900 dark:text-white">Balances</h3>
<div class="space-y-3">
<div>
<span class="text-sm text-gray-600 dark:text-gray-400">AITBC Balance:</span>
<p class="text-2xl font-bold text-green-600 dark:text-green-400" id="aitbcBalance">0 AITBC</p>
</div>
</div>
</div>
</div>
</div>
</section>
</main>
<!-- QR Code Modal -->
<div id="qrModal" class="fixed inset-0 bg-black bg-opacity-50 hidden flex items-center justify-center z-50">
<div class="bg-white dark:bg-gray-800 rounded-lg p-6 max-w-md w-full mx-4">
<h3 class="text-lg font-bold mb-4 text-gray-900 dark:text-white">Send Bitcoin to this Address</h3>
<div class="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg mb-4">
<div id="qrCode" class="w-64 h-64 mx-auto mb-4 bg-white rounded"></div>
<p class="font-mono text-sm break-all" id="paymentAddress">-</p>
</div>
<div class="grid grid-cols-2 gap-4 mb-4 text-sm">
<div>
<p class="text-gray-600 dark:text-gray-400">Amount to Send:</p>
<p class="font-semibold" id="paymentAmount">0 BTC</p>
</div>
<div>
<p class="text-gray-600 dark:text-gray-400">You'll Receive:</p>
<p class="font-semibold text-green-600" id="receiveAmount">0 AITBC</p>
</div>
</div>
<div class="flex items-center justify-center mb-4">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-green-600" id="paymentSpinner"></div>
<span class="ml-2 text-sm text-gray-600">Waiting for payment...</span>
</div>
<div class="flex space-x-3">
<button onclick="closeQRModal()" class="flex-1 bg-gray-200 text-gray-800 py-2 rounded-lg hover:bg-gray-300 transition">
Cancel
</button>
<button onclick="checkPaymentStatus()" class="flex-1 bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 transition">
Check Payment
</button>
</div>
</div>
</div>
<script>
// API Configuration
const API_BASE = window.location.origin + '/api';
const BLOCKCHAIN_API = window.location.origin + '/rpc';
const EXCHANGE_RATE = 0.00001; // 1 AITBC = 0.00001 BTC
let walletAddress = null;
let currentUser = null;
let sessionToken = null;
let aitbcBalance = 0;
// Initialize
document.addEventListener('DOMContentLoaded', () => {
lucide.createIcons();
updatePrices();
loadGPUOffers();
// Auto-refresh prices every 30 seconds
setInterval(updatePrices, 30000);
// Input handlers
document.getElementById('btcAmount').addEventListener('input', updateAITBCAmount);
document.getElementById('aitbcAmount').addEventListener('input', updateBTCAmount);
// Check for saved dark mode preference
if (localStorage.getItem('darkMode') === 'true' ||
(!localStorage.getItem('darkMode') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
updateDarkModeIcon(true);
}
});
// Dark mode toggle
function toggleDarkMode() {
const isDark = document.documentElement.classList.toggle('dark');
localStorage.setItem('darkMode', isDark);
updateDarkModeIcon(isDark);
}
function updateDarkModeIcon(isDark) {
const icon = document.getElementById('darkModeIcon');
if (isDark) {
icon.setAttribute('data-lucide', 'sun');
} else {
icon.setAttribute('data-lucide', 'moon');
}
lucide.createIcons();
}
// Section Navigation
function showSection(section) {
document.querySelectorAll('.section').forEach(s => s.classList.add('hidden'));
document.getElementById(section + 'Section').classList.remove('hidden');
if (section === 'marketplace') {
loadGPUOffers();
}
}
// Connect Wallet
async function connectWallet() {
try {
// Generate a random wallet address for demo
walletAddress = 'aitbc1' + Array(39).fill(0).map(() => Math.random().toString(36).substr(2, 1)).join('');
// Login or register via API
const response = await axios.post(`${API_BASE}/users/login`, {
wallet_address: walletAddress
});
currentUser = response.data;
sessionToken = response.data.session_token;
// Update UI
document.getElementById('tradeConnectPrompt').classList.add('hidden');
document.getElementById('tradeForm').classList.remove('hidden');
document.getElementById('navConnectBtn').classList.add('hidden');
document.getElementById('navUserInfo').classList.remove('hidden');
document.getElementById('navUsername').textContent = currentUser.username;
document.getElementById('walletAddress').textContent = walletAddress;
document.getElementById('walletUsername').textContent = currentUser.username;
// Load wallet balance
await loadWalletBalance();
showToast('Wallet connected successfully!', 'success');
} catch (error) {
console.error('Failed to connect wallet:', error);
showToast('Failed to connect wallet', 'error');
}
}
// Load Wallet Balance
async function loadWalletBalance() {
try {
const response = await axios.get(
`${API_BASE}/users/${currentUser.user_id}/balance`,
{ headers: { 'Authorization': `Bearer ${sessionToken}` } }
);
aitbcBalance = response.data.balance || 0;
document.getElementById('aitbcBalance').textContent = aitbcBalance + ' AITBC';
} catch (error) {
console.error('Failed to load balance:', error);
}
}
// Logout
async function logout() {
try {
if (sessionToken) {
await axios.post(`${API_BASE}/users/logout`, {}, {
headers: { 'Authorization': `Bearer ${sessionToken}` }
});
}
} catch (error) {
console.error('Logout error:', error);
}
// Reset state
walletAddress = null;
currentUser = null;
sessionToken = null;
aitbcBalance = 0;
// Update UI
document.getElementById('tradeConnectPrompt').classList.remove('hidden');
document.getElementById('tradeForm').classList.add('hidden');
document.getElementById('navConnectBtn').classList.remove('hidden');
document.getElementById('navUserInfo').classList.add('hidden');
showToast('Logged out successfully', 'success');
}
// Update Prices
function updatePrices() {
// Simulate price updates
const variation = (Math.random() - 0.5) * 0.000001;
const newPrice = EXCHANGE_RATE + variation;
document.getElementById('aitbcBtcPrice').textContent = newPrice.toFixed(5);
document.getElementById('lastUpdated').textContent = 'Just now';
}
// Currency Conversion
function updateAITBCAmount() {
const btcAmount = parseFloat(document.getElementById('btcAmount').value) || 0;
const aitbcAmount = btcAmount / EXCHANGE_RATE;
document.getElementById('aitbcAmount').value = aitbcAmount.toFixed(2);
}
function updateBTCAmount() {
const aitbcAmount = parseFloat(document.getElementById('aitbcAmount').value) || 0;
const btcAmount = aitbcAmount * EXCHANGE_RATE;
document.getElementById('btcAmount').value = btcAmount.toFixed(5);
}
function swapCurrencies() {
const btcInput = document.getElementById('btcAmount');
const aitbcInput = document.getElementById('aitbcAmount');
const temp = btcInput.value;
btcInput.value = aitbcInput.value;
aitbcInput.value = temp;
}
// Create Payment Request
async function createPaymentRequest() {
const btcAmount = document.getElementById('btcAmount').value;
if (!btcAmount || btcAmount <= 0) {
showToast('Please enter a valid amount', 'error');
return;
}
try {
const response = await axios.post(`${API_BASE}/exchange/create-payment`, {
amount: parseFloat(btcAmount),
currency: 'BTC'
}, {
headers: { 'Authorization': `Bearer ${sessionToken}` }
});
const payment = response.data;
showQRModal(payment);
} catch (error) {
console.error('Failed to create payment:', error);
showToast('Failed to create payment request', 'error');
}
}
// Show QR Modal
function showQRModal(payment) {
const modal = document.getElementById('qrModal');
const addressEl = document.getElementById('paymentAddress');
const amountEl = document.getElementById('paymentAmount');
const receiveEl = document.getElementById('receiveAmount');
addressEl.textContent = payment.address;
amountEl.textContent = payment.amount + ' BTC';
receiveEl.textContent = (payment.amount / EXCHANGE_RATE).toFixed(2) + ' AITBC';
// Generate QR code (simplified - in production use a proper QR library)
const qrDiv = document.getElementById('qrCode');
qrDiv.innerHTML = `
<div class="w-full h-full flex items-center justify-center border-2 border-gray-300 rounded">
<div class="text-center">
<i data-lucide="qr-code" class="w-32 h-32 mx-auto mb-2"></i>
<p class="text-sm">QR Code for ${payment.address}</p>
</div>
</div>
`;
lucide.createIcons();
modal.classList.remove('hidden');
window.currentPaymentId = payment.payment_id;
}
// Close QR Modal
function closeQRModal() {
document.getElementById('qrModal').classList.add('hidden');
window.currentPaymentId = null;
}
// Check Payment Status
async function checkPaymentStatus() {
if (!window.currentPaymentId) return;
try {
const response = await axios.get(
`${API_BASE}/exchange/payment-status/${window.currentPaymentId}`,
{ headers: { 'Authorization': `Bearer ${sessionToken}` } }
);
const status = response.data.status;
if (status === 'completed') {
showToast('Payment received! AITBC credited to your wallet.', 'success');
closeQRModal();
await loadWalletBalance();
} else if (status === 'pending') {
showToast('Payment still pending...', 'info');
} else {
showToast('Payment not found', 'error');
}
} catch (error) {
console.error('Failed to check payment:', error);
showToast('Failed to check payment status', 'error');
}
}
// Load GPU Offers
async function loadGPUOffers() {
try {
const response = await axios.get(`${API_BASE}/marketplace/offers`);
displayGPUOffers(response.data);
} catch (error) {
console.error('Failed to load GPU offers:', error);
// Display demo offers
displayGPUOffers([
{
id: 'demo-1',
provider: 'Demo Provider 1',
capacity: 'RTX 4090',
price: 0.01,
status: 'available'
},
{
id: 'demo-2',
provider: 'Demo Provider 2',
capacity: 'A100 80GB',
price: 0.05,
status: 'available'
}
]);
}
}
// Display GPU Offers
function displayGPUOffers(offers) {
const container = document.getElementById('gpuOffers');
if (offers.length === 0) {
container.innerHTML = '<p class="text-gray-500 dark:text-gray-400">No GPU offers available at the moment.</p>';
return;
}
container.innerHTML = offers.map(offer => `
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 card-hover">
<div class="flex items-center justify-between mb-4">
<span class="text-sm font-semibold text-blue-600 dark:text-blue-400">${offer.capacity}</span>
<span class="text-xs px-2 py-1 bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 rounded">
${offer.status || 'Available'}
</span>
</div>
<h3 class="font-semibold mb-2 text-gray-900 dark:text-white">${offer.provider}</h3>
<p class="text-2xl font-bold text-gray-900 dark:text-white mb-4">${offer.price} BTC/hour</p>
<button onclick="rentGPU('${offer.id}')" class="w-full bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 transition">
Rent Now
</button>
</div>
`).join('');
}
// Rent GPU
function rentGPU(gpuId) {
if (!currentUser) {
showToast('Please connect your wallet first', 'error');
showSection('trade');
return;
}
showToast(`Renting GPU ${gpuId}...`, 'info');
}
// Toast Notification
function showToast(message, type = 'info') {
const toast = document.createElement('div');
toast.className = `fixed bottom-4 right-4 px-6 py-3 rounded-lg shadow-lg transform transition-all duration-300 z-50`;
if (type === 'success') {
toast.classList.add('bg-green-500', 'text-white');
} else if (type === 'error') {
toast.classList.add('bg-red-500', 'text-white');
} else {
toast.classList.add('bg-blue-500', 'text-white');
}
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.classList.add('translate-y-full', 'opacity-0');
setTimeout(() => toast.remove(), 300);
}, 3000);
}
</script>
</body>
</html>

54
apps/trade-exchange/server.py Executable file
View File

@ -0,0 +1,54 @@
#!/usr/bin/env python3
"""
Simple HTTP server for the AITBC Trade Exchange
"""
import os
import sys
from http.server import HTTPServer, SimpleHTTPRequestHandler
import argparse
class CORSHTTPRequestHandler(SimpleHTTPRequestHandler):
def end_headers(self):
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
self.send_header('Access-Control-Allow-Headers', 'Content-Type, X-Api-Key')
super().end_headers()
def do_OPTIONS(self):
self.send_response(200)
self.end_headers()
def run_server(port=3002, directory=None):
"""Run the HTTP server"""
if directory:
os.chdir(directory)
server_address = ('', port)
httpd = HTTPServer(server_address, CORSHTTPRequestHandler)
print(f"""
╔═══════════════════════════════════════╗
║ AITBC Trade Exchange Server ║
╠═══════════════════════════════════════╣
║ Server running at: ║
║ http://localhost:{port}
║ ║
║ Buy AITBC with Bitcoin! ║
║ Press Ctrl+C to stop ║
╚═══════════════════════════════════════╝
""")
try:
httpd.serve_forever()
except KeyboardInterrupt:
print("\nShutting down server...")
httpd.server_close()
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Run the AITBC Trade Exchange server')
parser.add_argument('--port', type=int, default=3002, help='Port to run the server on')
parser.add_argument('--dir', type=str, default='.', help='Directory to serve from')
args = parser.parse_args()
run_server(port=args.port, directory=args.dir)