- Remove standalone explorer-web app (README, HTML, package files) - Add /web endpoint to blockchain-explorer for web interface access - Update .gitignore to exclude application backup archives (*.tar.gz, *.zip) - Add backup documentation files to .gitignore (BACKUP_INDEX.md, README.md) - Consolidate explorer functionality into main blockchain-explorer application
527 lines
18 KiB
Python
Executable File
527 lines
18 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Multi-Chain AITBC Exchange API
|
|
Complete multi-chain trading with chain isolation
|
|
"""
|
|
|
|
import sqlite3
|
|
import json
|
|
import asyncio
|
|
import httpx
|
|
from datetime import datetime, timedelta
|
|
from typing import Dict, List, Optional, Any
|
|
from fastapi import FastAPI, HTTPException, Query, BackgroundTasks
|
|
from pydantic import BaseModel, Field
|
|
import uvicorn
|
|
import os
|
|
|
|
app = FastAPI(title="AITBC Multi-Chain Exchange", version="2.0.0")
|
|
|
|
# Database configuration
|
|
DB_PATH = os.path.join(os.path.dirname(__file__), "exchange_multichain.db")
|
|
|
|
# Supported chains
|
|
SUPPORTED_CHAINS = {
|
|
"ait-devnet": {
|
|
"name": "AITBC Development Network",
|
|
"status": "active",
|
|
"blockchain_url": "http://localhost:8007",
|
|
"token_symbol": "AITBC-DEV"
|
|
},
|
|
"ait-testnet": {
|
|
"name": "AITBC Test Network",
|
|
"status": "inactive",
|
|
"blockchain_url": None,
|
|
"token_symbol": "AITBC-TEST"
|
|
}
|
|
}
|
|
|
|
# Models
|
|
class OrderRequest(BaseModel):
|
|
order_type: str = Field(..., regex="^(BUY|SELL)$")
|
|
amount: float = Field(..., gt=0)
|
|
price: float = Field(..., gt=0)
|
|
chain_id: str = Field(..., regex="^(ait-devnet|ait-testnet)$")
|
|
user_address: str = Field(..., min_length=1)
|
|
|
|
class ChainOrderRequest(BaseModel):
|
|
chain_id: str = Field(..., regex="^(ait-devnet|ait-testnet)$")
|
|
order_type: str = Field(..., regex="^(BUY|SELL)$")
|
|
|
|
class MultiChainTradeRequest(BaseModel):
|
|
buy_order_id: Optional[int] = None
|
|
sell_order_id: Optional[int] = None
|
|
amount: float = Field(..., gt=0)
|
|
chain_id: str = Field(..., regex="^(ait-devnet|ait-testnet)$")
|
|
|
|
# Database functions
|
|
def get_db_connection():
|
|
"""Get database connection with proper configuration"""
|
|
conn = sqlite3.connect(DB_PATH)
|
|
conn.row_factory = sqlite3.Row
|
|
return conn
|
|
|
|
def init_database():
|
|
"""Initialize database with multi-chain schema"""
|
|
try:
|
|
conn = get_db_connection()
|
|
cursor = conn.cursor()
|
|
|
|
# Create chains table if not exists
|
|
cursor.execute('''
|
|
CREATE TABLE IF NOT EXISTS chains (
|
|
chain_id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
status TEXT NOT NULL CHECK(status IN ('active', 'inactive', 'maintenance')),
|
|
blockchain_url TEXT,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
enabled BOOLEAN DEFAULT 1
|
|
)
|
|
''')
|
|
|
|
# Create orders table with chain support
|
|
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,
|
|
chain_id TEXT NOT NULL DEFAULT 'ait-devnet',
|
|
blockchain_tx_hash TEXT,
|
|
chain_status TEXT DEFAULT 'pending' CHECK(chain_status IN ('pending', 'confirmed', 'failed'))
|
|
)
|
|
''')
|
|
|
|
# Create trades table with chain support
|
|
cursor.execute('''
|
|
CREATE TABLE IF NOT EXISTS trades (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
buy_order_id INTEGER,
|
|
sell_order_id INTEGER,
|
|
amount REAL NOT NULL,
|
|
price REAL NOT NULL,
|
|
total REAL NOT NULL,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
chain_id TEXT NOT NULL DEFAULT 'ait-devnet',
|
|
blockchain_tx_hash TEXT,
|
|
chain_status TEXT DEFAULT 'pending' CHECK(chain_status IN ('pending', 'confirmed', 'failed'))
|
|
)
|
|
''')
|
|
|
|
# Insert default chains
|
|
for chain_id, chain_info in SUPPORTED_CHAINS.items():
|
|
cursor.execute('''
|
|
INSERT OR REPLACE INTO chains (chain_id, name, status, blockchain_url)
|
|
VALUES (?, ?, ?, ?)
|
|
''', (chain_id, chain_info["name"], chain_info["status"], chain_info["blockchain_url"]))
|
|
|
|
# Create indexes
|
|
cursor.execute('CREATE INDEX IF NOT EXISTS idx_orders_chain_id ON orders(chain_id)')
|
|
cursor.execute('CREATE INDEX IF NOT EXISTS idx_trades_chain_id ON trades(chain_id)')
|
|
cursor.execute('CREATE INDEX IF NOT EXISTS idx_orders_chain_status ON orders(chain_id, status)')
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
return True
|
|
except Exception as e:
|
|
print(f"Database initialization error: {e}")
|
|
return False
|
|
|
|
# Chain-specific functions
|
|
async def verify_chain_transaction(chain_id: str, tx_hash: str) -> bool:
|
|
"""Verify transaction on specific chain"""
|
|
if chain_id not in SUPPORTED_CHAINS:
|
|
return False
|
|
|
|
chain_info = SUPPORTED_CHAINS[chain_id]
|
|
if chain_info["status"] != "active" or not chain_info["blockchain_url"]:
|
|
return False
|
|
|
|
try:
|
|
async with httpx.AsyncClient() as client:
|
|
response = await client.get(f"{chain_info['blockchain_url']}/api/v1/transactions/{tx_hash}")
|
|
return response.status_code == 200
|
|
except:
|
|
return False
|
|
|
|
async def submit_chain_transaction(chain_id: str, order_data: Dict) -> Optional[str]:
|
|
"""Submit transaction to specific chain"""
|
|
if chain_id not in SUPPORTED_CHAINS:
|
|
return None
|
|
|
|
chain_info = SUPPORTED_CHAINS[chain_id]
|
|
if chain_info["status"] != "active" or not chain_info["blockchain_url"]:
|
|
return None
|
|
|
|
try:
|
|
async with httpx.AsyncClient() as client:
|
|
response = await client.post(
|
|
f"{chain_info['blockchain_url']}/api/v1/transactions",
|
|
json=order_data
|
|
)
|
|
if response.status_code == 200:
|
|
return response.json().get("tx_hash")
|
|
except Exception as e:
|
|
print(f"Chain transaction error: {e}")
|
|
|
|
return None
|
|
|
|
# API Endpoints
|
|
@app.get("/health")
|
|
async def health_check():
|
|
"""Multi-chain health check"""
|
|
chain_status = {}
|
|
for chain_id, chain_info in SUPPORTED_CHAINS.items():
|
|
chain_status[chain_id] = {
|
|
"name": chain_info["name"],
|
|
"status": chain_info["status"],
|
|
"blockchain_url": chain_info["blockchain_url"],
|
|
"connected": False
|
|
}
|
|
|
|
if chain_info["status"] == "active" and chain_info["blockchain_url"]:
|
|
try:
|
|
async with httpx.AsyncClient() as client:
|
|
response = await client.get(f"{chain_info['blockchain_url']}/health", timeout=5.0)
|
|
chain_status[chain_id]["connected"] = response.status_code == 200
|
|
except:
|
|
pass
|
|
|
|
return {
|
|
"status": "ok",
|
|
"service": "multi-chain-exchange",
|
|
"version": "2.0.0",
|
|
"supported_chains": list(SUPPORTED_CHAINS.keys()),
|
|
"chain_status": chain_status,
|
|
"multi_chain": True,
|
|
"timestamp": datetime.now().isoformat()
|
|
}
|
|
|
|
@app.get("/api/v1/chains")
|
|
async def get_chains():
|
|
"""Get all supported chains with their status"""
|
|
chains = []
|
|
for chain_id, chain_info in SUPPORTED_CHAINS.items():
|
|
chains.append({
|
|
"chain_id": chain_id,
|
|
"name": chain_info["name"],
|
|
"status": chain_info["status"],
|
|
"blockchain_url": chain_info["blockchain_url"],
|
|
"token_symbol": chain_info["token_symbol"]
|
|
})
|
|
|
|
return {
|
|
"chains": chains,
|
|
"total_chains": len(chains),
|
|
"active_chains": len([c for c in chains if c["status"] == "active"])
|
|
}
|
|
|
|
@app.post("/api/v1/orders")
|
|
async def create_order(order: OrderRequest, background_tasks: BackgroundTasks):
|
|
"""Create chain-specific order"""
|
|
if order.chain_id not in SUPPORTED_CHAINS:
|
|
raise HTTPException(status_code=400, detail="Unsupported chain")
|
|
|
|
chain_info = SUPPORTED_CHAINS[order.chain_id]
|
|
if chain_info["status"] != "active":
|
|
raise HTTPException(status_code=400, detail=f"Chain {order.chain_id} is not active")
|
|
|
|
try:
|
|
conn = get_db_connection()
|
|
cursor = conn.cursor()
|
|
|
|
# Create order with chain isolation
|
|
cursor.execute('''
|
|
INSERT INTO orders (order_type, amount, price, total, remaining, user_address, chain_id)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
''', (order.order_type, order.amount, order.price, order.total, order.amount, order.user_address, order.chain_id))
|
|
|
|
order_id = cursor.lastrowid
|
|
|
|
# Submit to blockchain in background
|
|
background_tasks.add_task(submit_order_to_blockchain, order_id, order.chain_id)
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
return {
|
|
"success": True,
|
|
"order_id": order_id,
|
|
"chain_id": order.chain_id,
|
|
"status": "created",
|
|
"message": f"Order created on {chain_info['name']}"
|
|
}
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Order creation failed: {str(e)}")
|
|
|
|
async def submit_order_to_blockchain(order_id: int, chain_id: str):
|
|
"""Submit order to blockchain in background"""
|
|
try:
|
|
conn = get_db_connection()
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute("SELECT * FROM orders WHERE id = ?", (order_id,))
|
|
order = cursor.fetchone()
|
|
|
|
if order:
|
|
order_data = {
|
|
"type": "order",
|
|
"order_type": order["order_type"],
|
|
"amount": order["amount"],
|
|
"price": order["price"],
|
|
"user_address": order["user_address"]
|
|
}
|
|
|
|
tx_hash = await submit_chain_transaction(chain_id, order_data)
|
|
if tx_hash:
|
|
cursor.execute('''
|
|
UPDATE orders SET blockchain_tx_hash = ?, chain_status = 'pending'
|
|
WHERE id = ?
|
|
''', (tx_hash, order_id))
|
|
conn.commit()
|
|
|
|
conn.close()
|
|
except Exception as e:
|
|
print(f"Background blockchain submission error: {e}")
|
|
|
|
@app.get("/api/v1/orders/{chain_id}")
|
|
async def get_chain_orders(chain_id: str, status: Optional[str] = None):
|
|
"""Get orders for specific chain"""
|
|
if chain_id not in SUPPORTED_CHAINS:
|
|
raise HTTPException(status_code=400, detail="Unsupported chain")
|
|
|
|
try:
|
|
conn = get_db_connection()
|
|
cursor = conn.cursor()
|
|
|
|
query = "SELECT * FROM orders WHERE chain_id = ?"
|
|
params = [chain_id]
|
|
|
|
if status:
|
|
query += " AND status = ?"
|
|
params.append(status)
|
|
|
|
query += " ORDER BY created_at DESC"
|
|
|
|
cursor.execute(query, params)
|
|
orders = [dict(row) for row in cursor.fetchall()]
|
|
|
|
conn.close()
|
|
|
|
return {
|
|
"chain_id": chain_id,
|
|
"orders": orders,
|
|
"total_orders": len(orders)
|
|
}
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Failed to get orders: {str(e)}")
|
|
|
|
@app.get("/api/v1/orderbook/{chain_id}")
|
|
async def get_chain_orderbook(chain_id: str):
|
|
"""Get order book for specific chain"""
|
|
if chain_id not in SUPPORTED_CHAINS:
|
|
raise HTTPException(status_code=400, detail="Unsupported chain")
|
|
|
|
try:
|
|
conn = get_db_connection()
|
|
cursor = conn.cursor()
|
|
|
|
# Get buy orders (sorted by price descending)
|
|
cursor.execute('''
|
|
SELECT price, SUM(remaining) as volume, COUNT(*) as count
|
|
FROM orders
|
|
WHERE chain_id = ? AND order_type = 'BUY' AND status = 'open'
|
|
GROUP BY price
|
|
ORDER BY price DESC
|
|
''', (chain_id,))
|
|
buy_orders = [dict(row) for row in cursor.fetchall()]
|
|
|
|
# Get sell orders (sorted by price ascending)
|
|
cursor.execute('''
|
|
SELECT price, SUM(remaining) as volume, COUNT(*) as count
|
|
FROM orders
|
|
WHERE chain_id = ? AND order_type = 'SELL' AND status = 'open'
|
|
GROUP BY price
|
|
ORDER BY price ASC
|
|
''', (chain_id,))
|
|
sell_orders = [dict(row) for row in cursor.fetchall()]
|
|
|
|
conn.close()
|
|
|
|
return {
|
|
"chain_id": chain_id,
|
|
"buy_orders": buy_orders,
|
|
"sell_orders": sell_orders,
|
|
"spread": sell_orders[0]["price"] - buy_orders[0]["price"] if buy_orders and sell_orders else None
|
|
}
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Failed to get orderbook: {str(e)}")
|
|
|
|
@app.get("/api/v1/trades/{chain_id}")
|
|
async def get_chain_trades(chain_id: str, limit: int = Query(default=50, le=100)):
|
|
"""Get trades for specific chain"""
|
|
if chain_id not in SUPPORTED_CHAINS:
|
|
raise HTTPException(status_code=400, detail="Unsupported chain")
|
|
|
|
try:
|
|
conn = get_db_connection()
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute('''
|
|
SELECT t.*, o1.order_type as buy_order_type, o2.order_type as sell_order_type
|
|
FROM trades t
|
|
LEFT JOIN orders o1 ON t.buy_order_id = o1.id
|
|
LEFT JOIN orders o2 ON t.sell_order_id = o2.id
|
|
WHERE t.chain_id = ?
|
|
ORDER BY t.created_at DESC
|
|
LIMIT ?
|
|
''', (chain_id, limit))
|
|
|
|
trades = [dict(row) for row in cursor.fetchall()]
|
|
|
|
conn.close()
|
|
|
|
return {
|
|
"chain_id": chain_id,
|
|
"trades": trades,
|
|
"total_trades": len(trades)
|
|
}
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Failed to get trades: {str(e)}")
|
|
|
|
@app.post("/api/v1/trades")
|
|
async def create_trade(trade: MultiChainTradeRequest, background_tasks: BackgroundTasks):
|
|
"""Create chain-specific trade"""
|
|
if trade.chain_id not in SUPPORTED_CHAINS:
|
|
raise HTTPException(status_code=400, detail="Unsupported chain")
|
|
|
|
chain_info = SUPPORTED_CHAINS[trade.chain_id]
|
|
if chain_info["status"] != "active":
|
|
raise HTTPException(status_code=400, detail=f"Chain is not active")
|
|
|
|
try:
|
|
conn = get_db_connection()
|
|
cursor = conn.cursor()
|
|
|
|
# Create trade with chain isolation
|
|
cursor.execute('''
|
|
INSERT INTO trades (buy_order_id, sell_order_id, amount, price, total, chain_id)
|
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
''', (trade.buy_order_id, trade.sell_order_id, trade.amount, trade.price, trade.total, trade.chain_id))
|
|
|
|
trade_id = cursor.lastrowid
|
|
|
|
# Submit to blockchain in background
|
|
background_tasks.add_task(submit_trade_to_blockchain, trade_id, trade.chain_id)
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
return {
|
|
"success": True,
|
|
"trade_id": trade_id,
|
|
"chain_id": trade.chain_id,
|
|
"status": "created",
|
|
"message": f"Trade created on {chain_info['name']}"
|
|
}
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Trade creation failed: {str(e)}")
|
|
|
|
async def submit_trade_to_blockchain(trade_id: int, chain_id: str):
|
|
"""Submit trade to blockchain in background"""
|
|
try:
|
|
conn = get_db_connection()
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute("SELECT * FROM trades WHERE id = ?", (trade_id,))
|
|
trade = cursor.fetchone()
|
|
|
|
if trade:
|
|
trade_data = {
|
|
"type": "trade",
|
|
"buy_order_id": trade["buy_order_id"],
|
|
"sell_order_id": trade["sell_order_id"],
|
|
"amount": trade["amount"],
|
|
"price": trade["price"]
|
|
}
|
|
|
|
tx_hash = await submit_chain_transaction(chain_id, trade_data)
|
|
if tx_hash:
|
|
cursor.execute('''
|
|
UPDATE trades SET blockchain_tx_hash = ?, chain_status = 'pending'
|
|
WHERE id = ?
|
|
''', (tx_hash, trade_id))
|
|
conn.commit()
|
|
|
|
conn.close()
|
|
except Exception as e:
|
|
print(f"Background trade blockchain submission error: {e}")
|
|
|
|
@app.get("/api/v1/stats/{chain_id}")
|
|
async def get_chain_stats(chain_id: str):
|
|
"""Get trading statistics for specific chain"""
|
|
if chain_id not in SUPPORTED_CHAINS:
|
|
raise HTTPException(status_code=400, detail="Unsupported chain")
|
|
|
|
try:
|
|
conn = get_db_connection()
|
|
cursor = conn.cursor()
|
|
|
|
# Get order stats
|
|
cursor.execute('''
|
|
SELECT
|
|
COUNT(*) as total_orders,
|
|
SUM(CASE WHEN status = 'open' THEN 1 ELSE 0 END) as open_orders,
|
|
SUM(CASE WHEN status = 'filled' THEN 1 ELSE 0 END) as filled_orders,
|
|
SUM(amount) as total_volume
|
|
FROM orders WHERE chain_id = ?
|
|
''', (chain_id,))
|
|
order_stats = dict(cursor.fetchone())
|
|
|
|
# Get trade stats
|
|
cursor.execute('''
|
|
SELECT
|
|
COUNT(*) as total_trades,
|
|
SUM(amount) as trade_volume,
|
|
AVG(price) as avg_price,
|
|
MAX(price) as highest_price,
|
|
MIN(price) as lowest_price
|
|
FROM trades WHERE chain_id = ?
|
|
''', (chain_id,))
|
|
trade_stats = dict(cursor.fetchone())
|
|
|
|
conn.close()
|
|
|
|
return {
|
|
"chain_id": chain_id,
|
|
"chain_name": SUPPORTED_CHAINS[chain_id]["name"],
|
|
"orders": order_stats,
|
|
"trades": trade_stats,
|
|
"timestamp": datetime.now().isoformat()
|
|
}
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Failed to get stats: {str(e)}")
|
|
|
|
if __name__ == "__main__":
|
|
# Initialize database
|
|
if init_database():
|
|
print("✅ Multi-chain database initialized successfully")
|
|
else:
|
|
print("❌ Database initialization failed")
|
|
|
|
# Run the server
|
|
uvicorn.run(app, host="0.0.0.0", port=8001)
|