```
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:
175
apps/trade-exchange/bitcoin-wallet.py
Normal file
175
apps/trade-exchange/bitcoin-wallet.py
Normal 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()
|
||||
888
apps/trade-exchange/index.html
Normal file
888
apps/trade-exchange/index.html
Normal 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>
|
||||
620
apps/trade-exchange/index.prod.html
Normal file
620
apps/trade-exchange/index.prod.html
Normal 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
54
apps/trade-exchange/server.py
Executable 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)
|
||||
Reference in New Issue
Block a user