Files
aitbc/apps/exchange/simple_exchange_api.py
aitbc 745f791eda refactor: improve error handling and remove hardcoded credentials
- Changed bare except clauses to specific exception types in web3_utils.py, testing.py, messages.py, and message_storage.py
- Replaced print() calls with logger in testing.py, agent_discovery.py, compliance_agent.py, coordinator.py, trading_agent.py, keys.py, escrow.py, persistent_spending_tracker.py, sync_cli.py, and client.py
- Added logger initialization using get_logger(__name__) in compliance_agent.py, coordinator.py, trading_agent.py, keys.py, escrow.py, persistent_spending_tracker.py, and client.py
- Removed hardcoded secret
2026-05-12 17:01:57 +02:00

870 lines
32 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Simple FastAPI backend for the AITBC Trade Exchange (Python 3.13 compatible)
"""
import sqlite3
import json
from datetime import datetime, timezone, timedelta
from http.server import HTTPServer, BaseHTTPRequestHandler
import urllib.parse
import random
from aitbc.constants import DATA_DIR
from aitbc import get_logger
logger = get_logger(__name__)
# Database setup
def get_db_path():
"""Get database path and ensure directory exists"""
import os
db_path = os.getenv("EXCHANGE_DATABASE_URL", f"sqlite:///{DATA_DIR}/data/exchange/exchange.db").replace("sqlite://///", "")
# Create directory if it doesn't exist
db_dir = os.path.dirname(db_path)
if db_dir and not os.path.exists(db_dir):
os.makedirs(db_dir, exist_ok=True)
return db_path
def init_db():
"""Initialize SQLite database"""
db_path = get_db_path()
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Create tables
cursor.execute('''
CREATE TABLE IF NOT EXISTS trades (
id INTEGER PRIMARY KEY AUTOINCREMENT,
amount REAL NOT NULL,
price REAL NOT NULL,
total REAL NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
order_type TEXT NOT NULL CHECK(order_type IN ('BUY', 'SELL')),
amount REAL NOT NULL,
price REAL NOT NULL,
total REAL NOT NULL,
filled REAL DEFAULT 0,
remaining REAL NOT NULL,
status TEXT DEFAULT 'open' CHECK(status IN ('open', 'filled', 'cancelled')),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
user_address TEXT,
tx_hash TEXT
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS marketplace_offers (
id TEXT PRIMARY KEY,
item TEXT NOT NULL,
item_type TEXT NOT NULL,
price REAL NOT NULL,
wallet TEXT,
status TEXT DEFAULT 'active',
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS marketplace_orders (
id TEXT PRIMARY KEY,
order_type TEXT NOT NULL,
item TEXT NOT NULL,
price REAL NOT NULL,
wallet TEXT,
status TEXT DEFAULT 'open',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# Add columns if they don't exist (for existing databases)
try:
cursor.execute('ALTER TABLE orders ADD COLUMN user_address TEXT')
except Exception:
pass
try:
cursor.execute('ALTER TABLE orders ADD COLUMN tx_hash TEXT')
except Exception:
pass
conn.commit()
conn.close()
def create_mock_trades():
"""Create some mock trades"""
db_path = get_db_path()
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Check if we have trades
cursor.execute('SELECT COUNT(*) FROM trades')
if cursor.fetchone()[0] > 0:
conn.close()
return
# Create mock trades
now = datetime.now(UTC)
for i in range(20):
amount = random.uniform(10, 500)
price = random.uniform(0.000009, 0.000012)
total = amount * price
created_at = now - timedelta(minutes=random.randint(0, 60))
cursor.execute('''
INSERT INTO trades (amount, price, total, created_at)
VALUES (?, ?, ?, ?)
''', (amount, price, total, created_at))
conn.commit()
conn.close()
class ExchangeAPIHandler(BaseHTTPRequestHandler):
def do_GET(self):
"""Handle GET requests"""
# Validate path to prevent SSRF
if not self.path or self.path.startswith(('//', '\\\\', '..')):
self.send_error(400, "Invalid path")
return
parsed = urllib.parse.urlparse(self.path)
path = parsed.path
if path == '/health' or path == '/api/health':
self.health_check()
elif path.startswith('/api/trades/recent'):
self.get_recent_trades(parsed)
elif path.startswith('/api/orders/orderbook'):
self.get_orderbook()
elif path.startswith('/api/wallet/balance'):
self.handle_wallet_balance()
elif path == '/api/total-supply':
self.handle_total_supply()
elif path == '/api/treasury-balance':
self.handle_treasury_balance()
elif path == '/v1/marketplace/offers':
self.handle_marketplace_offers(parsed)
elif path.startswith('/v1/marketplace/offers/'):
self.handle_marketplace_offer(path)
elif path == '/v1/marketplace/orders':
self.handle_marketplace_orders(parsed)
else:
self.send_error(404, "Not Found")
def do_POST(self):
"""Handle POST requests"""
parsed = urllib.parse.urlparse(self.path)
path = parsed.path
if path == '/api/orders':
self.handle_place_order()
elif path == '/api/wallet/connect':
self.handle_wallet_connect()
elif path == '/v1/marketplace/offers':
self.handle_marketplace_create_offer()
elif path.startswith('/v1/marketplace/offers/') and path.endswith('/book'):
self.handle_marketplace_book_offer(path)
else:
self.send_error(404, "Not Found")
def do_DELETE(self):
parsed = urllib.parse.urlparse(self.path)
path = parsed.path
if path.startswith('/v1/marketplace/orders/'):
self.handle_marketplace_delete_order(path)
elif path.startswith('/v1/marketplace/offers/'):
self.handle_marketplace_delete_offer(path)
else:
self.send_error(404, "Not Found")
def _read_json_body(self):
content_length = int(self.headers.get('Content-Length', 0))
if content_length <= 0:
return {}
post_data = self.rfile.read(content_length)
if not post_data:
return {}
return json.loads(post_data.decode('utf-8'))
def _new_marketplace_id(self, prefix):
return f"{prefix}_{int(datetime.now(UTC).timestamp() * 1000)}{random.randint(100, 999)}"
def _marketplace_offer_row(self, row):
return {
"id": row[0],
"address": row[0],
"item": row[1],
"item_type": row[2],
"model": row[2],
"price": row[3],
"price_per_hour": row[3],
"wallet": row[4],
"status": row[5],
"description": row[6],
"created_at": row[7],
"deployed_at": row[7],
}
def _marketplace_order_row(self, row):
return {
"id": row[0],
"order_type": row[1],
"item": row[2],
"price": row[3],
"wallet": row[4],
"status": row[5],
"created_at": row[6],
}
def handle_marketplace_offers(self, parsed):
query = urllib.parse.parse_qs(parsed.query)
status_filter = query.get('status', [None])[0]
db_path = get_db_path()
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
if status_filter:
cursor.execute('''
SELECT id, item, item_type, price, wallet, status, description, created_at
FROM marketplace_offers
WHERE status = ?
ORDER BY created_at DESC
''', (status_filter,))
else:
cursor.execute('''
SELECT id, item, item_type, price, wallet, status, description, created_at
FROM marketplace_offers
ORDER BY created_at DESC
''')
offers = [self._marketplace_offer_row(row) for row in cursor.fetchall()]
conn.close()
self.send_json_response(offers)
def handle_marketplace_offer(self, path):
offer_id = urllib.parse.unquote(path.rsplit('/', 1)[-1])
db_path = get_db_path()
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute('''
SELECT id, item, item_type, price, wallet, status, description, created_at
FROM marketplace_offers
WHERE id = ?
''', (offer_id,))
row = cursor.fetchone()
conn.close()
if row:
self.send_json_response(self._marketplace_offer_row(row))
else:
self.send_error(404, "Offer not found")
def handle_marketplace_create_offer(self):
try:
data = self._read_json_body()
item = data.get('item') or data.get('item_type') or 'service'
item_type = data.get('item_type') or item
price = float(data.get('price') or data.get('price_per_hour') or 0)
wallet = data.get('wallet')
description = data.get('description', '')
offer_id = self._new_marketplace_id('offer')
order_id = self._new_marketplace_id('order')
db_path = get_db_path()
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute('''
INSERT INTO marketplace_offers (id, item, item_type, price, wallet, status, description)
VALUES (?, ?, ?, ?, ?, 'active', ?)
''', (offer_id, item, item_type, price, wallet, description))
cursor.execute('''
INSERT INTO marketplace_orders (id, order_type, item, price, wallet, status)
VALUES (?, 'SELL', ?, ?, ?, 'open')
''', (order_id, item, price, wallet))
conn.commit()
cursor.execute('''
SELECT id, item, item_type, price, wallet, status, description, created_at
FROM marketplace_offers
WHERE id = ?
''', (offer_id,))
offer = self._marketplace_offer_row(cursor.fetchone())
conn.close()
offer["order_id"] = order_id
self.send_json_response(offer, status=201)
except Exception as e:
self.send_json_response({"success": False, "error": str(e)}, status=400)
def handle_marketplace_book_offer(self, path):
try:
offer_id = urllib.parse.unquote(path[len('/v1/marketplace/offers/'): -len('/book')])
data = self._read_json_body()
wallet = data.get('wallet')
db_path = get_db_path()
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute('''
SELECT item, price
FROM marketplace_offers
WHERE id = ? OR item = ?
''', (offer_id, offer_id))
row = cursor.fetchone()
item = row[0] if row else offer_id
price = float(data.get('price') or (row[1] if row else 0) or 0)
order_id = self._new_marketplace_id('order')
cursor.execute('''
INSERT INTO marketplace_orders (id, order_type, item, price, wallet, status)
VALUES (?, 'BUY', ?, ?, ?, 'open')
''', (order_id, item, price, wallet))
conn.commit()
cursor.execute('''
SELECT id, order_type, item, price, wallet, status, created_at
FROM marketplace_orders
WHERE id = ?
''', (order_id,))
order = self._marketplace_order_row(cursor.fetchone())
conn.close()
self.send_json_response({"success": True, "order": order, "order_id": order_id}, status=201)
except Exception as e:
self.send_json_response({"success": False, "error": str(e)}, status=400)
def handle_marketplace_orders(self, parsed):
query = urllib.parse.parse_qs(parsed.query)
wallet = query.get('wallet', [None])[0]
db_path = get_db_path()
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
if wallet:
cursor.execute('''
SELECT id, order_type, item, price, wallet, status, created_at
FROM marketplace_orders
WHERE wallet = ?
ORDER BY created_at DESC
''', (wallet,))
else:
cursor.execute('''
SELECT id, order_type, item, price, wallet, status, created_at
FROM marketplace_orders
ORDER BY created_at DESC
''')
orders = [self._marketplace_order_row(row) for row in cursor.fetchall()]
conn.close()
self.send_json_response({"orders": orders})
def handle_marketplace_delete_order(self, path):
order_id = urllib.parse.unquote(path.rsplit('/', 1)[-1])
db_path = get_db_path()
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute("UPDATE marketplace_orders SET status = 'cancelled' WHERE id = ?", (order_id,))
conn.commit()
deleted = cursor.rowcount
conn.close()
self.send_json_response({"success": True, "order_id": order_id, "deleted": deleted})
def handle_marketplace_delete_offer(self, path):
offer_id = urllib.parse.unquote(path.rsplit('/', 1)[-1])
db_path = get_db_path()
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute("UPDATE marketplace_offers SET status = 'cancelled' WHERE id = ?", (offer_id,))
conn.commit()
deleted = cursor.rowcount
conn.close()
self.send_json_response({"success": True, "offer_id": offer_id, "deleted": deleted})
def get_recent_trades(self, parsed):
"""Get recent trades"""
query = urllib.parse.parse_qs(parsed.query)
limit = int(query.get('limit', [20])[0])
db_path = get_db_path()
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute('''
SELECT id, amount, price, total, created_at
FROM trades
ORDER BY created_at DESC
LIMIT ?
''', (limit,))
trades = []
for row in cursor.fetchall():
trades.append({
'id': row[0],
'amount': row[1],
'price': row[2],
'total': row[3],
'created_at': row[4]
})
conn.close()
self.send_json_response(trades)
def get_orderbook(self):
"""Get order book"""
db_path = get_db_path()
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Get sell orders
cursor.execute('''
SELECT id, order_type, amount, price, total, filled, remaining, status, created_at
FROM orders
WHERE order_type = 'SELL' AND status = 'open'
ORDER BY price ASC
LIMIT 20
''')
sells = []
for row in cursor.fetchall():
sells.append({
'id': row[0],
'order_type': row[1],
'amount': row[2],
'price': row[3],
'total': row[4],
'filled': row[5],
'remaining': row[6],
'status': row[7],
'created_at': row[8]
})
# Get buy orders
cursor.execute('''
SELECT id, order_type, amount, price, total, filled, remaining, status, created_at
FROM orders
WHERE order_type = 'BUY' AND status = 'open'
ORDER BY price DESC
LIMIT 20
''')
buys = []
for row in cursor.fetchall():
buys.append({
'id': row[0],
'order_type': row[1],
'amount': row[2],
'price': row[3],
'total': row[4],
'filled': row[5],
'remaining': row[6],
'status': row[7],
'created_at': row[8]
})
conn.close()
self.send_json_response({'buys': buys, 'sells': sells})
def handle_place_order(self):
"""Place a new order on the blockchain"""
content_length = int(self.headers.get('Content-Length', 0))
post_data = self.rfile.read(content_length)
try:
data = json.loads(post_data.decode('utf-8'))
order_type = data.get('order_type')
amount = data.get('amount')
price = data.get('price')
user_address = data.get('user_address')
if not all([order_type, amount, price, user_address]):
self.send_error(400, "Missing required fields")
return
if order_type not in ['BUY', 'SELL']:
self.send_error(400, "Invalid order type")
return
# Create order transaction on blockchain
try:
import urllib.request
import urllib.parse
# Prepare transaction data
tx_data = {
"from": user_address,
"type": "ORDER",
"order_type": order_type,
"amount": str(amount),
"price": str(price),
"nonce": 0 # Would get actual nonce from wallet
}
# Send transaction to blockchain
tx_url = "http://localhost:9080/rpc/sendTx"
encoded_data = urllib.parse.urlencode(tx_data).encode('utf-8')
req = urllib.request.Request(
tx_url,
data=encoded_data,
headers={'Content-Type': 'application/x-www-form-urlencoded'}
)
with urllib.request.urlopen(req) as response:
tx_result = json.loads(response.read().decode())
# Store order in local database for orderbook
total = amount * price
db_path = get_db_path()
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute('''
INSERT INTO orders (order_type, amount, price, total, remaining, user_address, tx_hash)
VALUES (?, ?, ?, ?, ?, ?, ?)
''', (order_type, amount, price, total, amount, user_address, tx_result.get('tx_hash', '')))
order_id = cursor.lastrowid
conn.commit()
# Get the created order
cursor.execute('SELECT * FROM orders WHERE id = ?', (order_id,))
row = cursor.fetchone()
order = {
'id': row[0],
'order_type': row[1],
'amount': row[2],
'price': row[3],
'total': row[4],
'filled': row[5],
'remaining': row[6],
'status': row[7],
'created_at': row[8],
'user_address': row[9],
'tx_hash': row[10]
}
conn.close()
# Try to match orders
self.match_orders(order)
self.send_json_response(order)
except Exception as e:
# Fallback to database-only if blockchain is down
total = amount * price
db_path = get_db_path()
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute('''
INSERT INTO orders (order_type, amount, price, total, remaining, user_address)
VALUES (?, ?, ?, ?, ?, ?)
''', (order_type, amount, price, total, amount, user_address))
order_id = cursor.lastrowid
conn.commit()
# Get the created order
cursor.execute('SELECT * FROM orders WHERE id = ?', (order_id,))
row = cursor.fetchone()
order = {
'id': row[0],
'order_type': row[1],
'amount': row[2],
'price': row[3],
'total': row[4],
'filled': row[5],
'remaining': row[6],
'status': row[7],
'created_at': row[8],
'user_address': row[9] if len(row) > 9 else None
}
conn.close()
# Try to match orders
self.match_orders(order)
self.send_json_response(order)
except Exception as e:
# Fallback to hardcoded values if blockchain is down
self.send_json_response({
"total_supply": "21000000",
"circulating_supply": "1000000",
"treasury_balance": "0",
"source": "fallback",
"error": str(e)
})
# Match with sell orders
cursor.execute('''
SELECT * FROM orders
WHERE order_type = 'SELL' AND status = 'open' AND price <= ?
ORDER BY price ASC, created_at ASC
''', (new_order['price'],))
else:
# Match with buy orders
cursor.execute('''
SELECT * FROM orders
WHERE order_type = 'BUY' AND status = 'open' AND price >= ?
ORDER BY price DESC, created_at ASC
''', (new_order['price'],))
matching_orders = cursor.fetchall()
for order_row in matching_orders:
if new_order['remaining'] <= 0:
break
# Calculate trade amount
trade_amount = min(new_order['remaining'], order_row[6]) # remaining
if trade_amount > 0:
# Create trade on blockchain
try:
import urllib.request
import urllib.parse
trade_price = order_row[3] # Use the existing order's price
trade_data = {
"type": "TRADE",
"buy_order": new_order['id'] if new_order['order_type'] == 'BUY' else order_row[0],
"sell_order": order_row[0] if new_order['order_type'] == 'BUY' else new_order['id'],
"amount": str(trade_amount),
"price": str(trade_price)
}
# Record trade in database
cursor.execute('''
INSERT INTO trades (amount, price, total)
VALUES (?, ?, ?)
''', (trade_amount, trade_price, trade_amount * trade_price))
# Update orders
new_order['remaining'] -= trade_amount
new_order['filled'] = new_order.get('filled', 0) + trade_amount
# Update matching order
new_remaining = order_row[6] - trade_amount
cursor.execute('''
UPDATE orders SET remaining = ?, filled = filled + ?
WHERE id = ?
''', (new_remaining, trade_amount, order_row[0]))
# Close order if fully filled
if new_remaining <= 0:
cursor.execute('''
UPDATE orders SET status = 'filled' WHERE id = ?
''', (order_row[0],))
except Exception as e:
logger.error(f"Failed to create trade on blockchain: {e}")
# Still record the trade in database
cursor.execute('''
INSERT INTO trades (amount, price, total)
VALUES (?, ?, ?)
''', (trade_amount, order_row[3], trade_amount * order_row[3]))
# Update new order in database
if new_order['remaining'] <= 0:
cursor.execute('''
UPDATE orders SET status = 'filled', remaining = 0, filled = ?
WHERE id = ?
''', (new_order['filled'], new_order['id']))
else:
cursor.execute('''
UPDATE orders SET remaining = ?, filled = ?
WHERE id = ?
''', (new_order['remaining'], new_order['filled'], new_order['id']))
conn.commit()
conn.close()
def handle_treasury_balance(self):
"""Get exchange treasury balance from blockchain"""
try:
import urllib.request
import json
# Treasury address from genesis
treasury_address = "aitbcexchange00000000000000000000000000000000"
blockchain_url = f"http://localhost:9080/rpc/getBalance/{treasury_address}"
try:
with urllib.request.urlopen(blockchain_url) as response:
balance_data = json.loads(response.read().decode())
treasury_balance = balance_data.get('balance', 0)
self.send_json_response({
"address": treasury_address,
"balance": str(treasury_balance),
"available_for_sale": str(treasury_balance), # All treasury tokens available
"source": "blockchain"
})
except Exception as e:
# If blockchain query fails, show the genesis amount
self.send_json_response({
"address": treasury_address,
"balance": "10000000000000", # 10 million in smallest units
"available_for_sale": "10000000000000",
"source": "genesis",
"note": "Genesis amount - blockchain may need restart"
})
except Exception as e:
self.send_error(500, str(e))
def health_check(self):
"""Health check"""
self.send_json_response({
'status': 'ok',
'timestamp': datetime.now(UTC).isoformat()
})
def handle_wallet_balance(self):
"""Handle wallet balance request"""
from urllib.parse import urlparse, parse_qs
parsed = urlparse(self.path)
params = parse_qs(parsed.query)
address = params.get('address', [''])[0]
if not address:
self.send_json_response({
"btc": "0.00000000",
"aitbc": "0.00",
"address": "unknown"
})
return
try:
# Query real blockchain for balance
import urllib.request
import json
# Get AITBC balance from blockchain
blockchain_url = f"http://localhost:9080/rpc/getBalance/{address}"
with urllib.request.urlopen(blockchain_url) as response:
balance_data = json.loads(response.read().decode())
# For BTC, we'll query a Bitcoin API (simplified for now)
# In production, you'd integrate with a real Bitcoin node API
btc_balance = "0.00000000" # Placeholder - would query real Bitcoin network
self.send_json_response({
"btc": btc_balance,
"aitbc": str(balance_data.get('balance', 0)),
"address": address,
"nonce": balance_data.get('nonce', 0)
})
except Exception as e:
# Fallback to error if blockchain is down
self.send_json_response({
"btc": "0.00000000",
"aitbc": "0.00",
"address": address,
"error": "Failed to fetch balance from blockchain"
})
def handle_wallet_connect(self):
"""Handle wallet connection request"""
import secrets
content_length = int(self.headers.get('Content-Length', 0))
post_data = self.rfile.read(content_length)
mock_address = "aitbc" + secrets.token_hex(20)
self.send_json_response({
"address": mock_address,
"status": "connected"
})
def send_json_response(self, data, status=200):
"""Send JSON response"""
self.send_response(status)
self.send_header('Content-Type', 'application/json')
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')
self.end_headers()
self.wfile.write(json.dumps(data, default=str).encode())
def do_OPTIONS(self):
"""Handle OPTIONS requests for CORS"""
self.send_response(200)
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')
self.end_headers()
class WalletAPIHandler(BaseHTTPRequestHandler):
"""Handle wallet API requests"""
def do_GET(self):
"""Handle GET requests"""
if self.path.startswith('/api/wallet/balance'):
# Parse address from query params
from urllib.parse import urlparse, parse_qs
parsed = urlparse(self.path)
params = parse_qs(parsed.query)
address = params.get('address', [''])[0]
# Return mock balance for now
self.send_json_response({
"btc": "0.12345678",
"aitbc": "1000.50",
"address": address or "unknown"
})
else:
self.send_error(404)
def do_POST(self):
"""Handle POST requests"""
if self.path == '/wallet/connect':
import secrets
mock_address = "aitbc" + secrets.token_hex(20)
self.send_json_response({
"address": mock_address,
"status": "connected"
})
else:
self.send_error(404)
def send_json_response(self, data, status=200):
"""Send JSON response"""
self.send_response(status)
self.send_header('Content-Type', 'application/json')
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')
self.end_headers()
self.wfile.write(json.dumps(data, default=str).encode())
def do_OPTIONS(self):
"""Handle OPTIONS requests for CORS"""
self.send_response(200)
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')
self.end_headers()
def run_server(port=8001):
"""Run the server"""
init_db()
# Removed mock trades - now using only real blockchain data
server = HTTPServer(('localhost', port), ExchangeAPIHandler)
logger.info("AITBC Exchange API Server started", port=port, url=f"http://localhost:{port}")
try:
server.serve_forever()
except KeyboardInterrupt:
logger.info("Shutting down server...")
server.shutdown()
server.server_close()
if __name__ == "__main__":
run_server()