feat: add marketplace API endpoints and CLI handlers for offers and orders
Some checks failed
CLI Tests / test-cli (push) Failing after 6s
Deploy to Testnet / deploy-testnet (push) Successful in 1m16s
API Endpoint Tests / test-api-endpoints (push) Failing after 3h2m28s
Multi-Node Stress Testing / stress-test (push) Has been cancelled
Node Failover Simulation / failover-test (push) Has been cancelled
Python Tests / test-python (push) Has been cancelled
Security Scanning / security-scan (push) Has been cancelled
Integration Tests / test-service-integration (push) Successful in 1h59m22s
Cross-Node Transaction Testing / transaction-test (push) Has been cancelled

Added comprehensive marketplace functionality to Exchange API and CLI:
- Created marketplace_offers and marketplace_orders database tables
- Implemented REST endpoints: GET/POST/DELETE for offers and orders
- Added marketplace CLI handlers with proper URL resolution and auth
- Support for creating offers, booking offers, listing orders, and cancellations
- Fixed order status values from 'OPEN'/'FILLED' to 'open'/'filled'
This commit is contained in:
aitbc
2026-05-04 13:21:54 +02:00
parent 7709afa3ba
commit cb509f62fc
10 changed files with 505 additions and 99 deletions

View File

@@ -5,7 +5,7 @@ Simple FastAPI backend for the AITBC Trade Exchange (Python 3.13 compatible)
import sqlite3
import json
from datetime import datetime, UTC
from datetime import datetime, UTC, timedelta
from http.server import HTTPServer, BaseHTTPRequestHandler
import urllib.parse
import random
@@ -57,6 +57,31 @@ def init_db():
)
''')
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')
@@ -84,7 +109,7 @@ def create_mock_trades():
return
# Create mock trades
now = datetime.now(datetime.UTC)
now = datetime.now(UTC)
for i in range(20):
amount = random.uniform(10, 500)
price = random.uniform(0.000009, 0.000012)
@@ -107,31 +132,248 @@ class ExchangeAPIHandler(BaseHTTPRequestHandler):
self.send_error(400, "Invalid path")
return
if self.path == '/health' or self.path == '/api/health':
parsed = urllib.parse.urlparse(self.path)
path = parsed.path
if path == '/health' or path == '/api/health':
self.health_check()
elif self.path.startswith('/api/trades/recent'):
parsed = urllib.parse.urlparse(self.path)
elif path.startswith('/api/trades/recent'):
self.get_recent_trades(parsed)
elif self.path.startswith('/api/orders/orderbook'):
elif path.startswith('/api/orders/orderbook'):
self.get_orderbook()
elif self.path.startswith('/api/wallet/balance'):
elif path.startswith('/api/wallet/balance'):
self.handle_wallet_balance()
elif self.path == '/api/total-supply':
elif path == '/api/total-supply':
self.handle_total_supply()
elif self.path == '/api/treasury-balance':
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"""
if self.path == '/api/orders':
parsed = urllib.parse.urlparse(self.path)
path = parsed.path
if path == '/api/orders':
self.handle_place_order()
elif self.path == '/api/wallet/connect':
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)
@@ -172,7 +414,7 @@ class ExchangeAPIHandler(BaseHTTPRequestHandler):
cursor.execute('''
SELECT id, order_type, amount, price, total, filled, remaining, status, created_at
FROM orders
WHERE order_type = 'SELL' AND status = 'OPEN'
WHERE order_type = 'SELL' AND status = 'open'
ORDER BY price ASC
LIMIT 20
''')
@@ -195,7 +437,7 @@ class ExchangeAPIHandler(BaseHTTPRequestHandler):
cursor.execute('''
SELECT id, order_type, amount, price, total, filled, remaining, status, created_at
FROM orders
WHERE order_type = 'BUY' AND status = 'OPEN'
WHERE order_type = 'BUY' AND status = 'open'
ORDER BY price DESC
LIMIT 20
''')
@@ -358,14 +600,14 @@ class ExchangeAPIHandler(BaseHTTPRequestHandler):
# Match with sell orders
cursor.execute('''
SELECT * FROM orders
WHERE order_type = 'SELL' AND status = 'OPEN' AND price <= ?
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 >= ?
WHERE order_type = 'BUY' AND status = 'open' AND price >= ?
ORDER BY price DESC, created_at ASC
''', (new_order['price'],))
@@ -413,7 +655,7 @@ class ExchangeAPIHandler(BaseHTTPRequestHandler):
# Close order if fully filled
if new_remaining <= 0:
cursor.execute('''
UPDATE orders SET status = 'FILLED' WHERE id = ?
UPDATE orders SET status = 'filled' WHERE id = ?
''', (order_row[0],))
except Exception as e:
@@ -427,7 +669,7 @@ class ExchangeAPIHandler(BaseHTTPRequestHandler):
# Update new order in database
if new_order['remaining'] <= 0:
cursor.execute('''
UPDATE orders SET status = 'FILLED', remaining = 0, filled = ?
UPDATE orders SET status = 'filled', remaining = 0, filled = ?
WHERE id = ?
''', (new_order['filled'], new_order['id']))
else:
@@ -477,7 +719,7 @@ class ExchangeAPIHandler(BaseHTTPRequestHandler):
"""Health check"""
self.send_json_response({
'status': 'ok',
'timestamp': datetime.now(datetime.UTC).isoformat()
'timestamp': datetime.now(UTC).isoformat()
})
def handle_wallet_balance(self):