
- Add detailed logging throughout the Solana client and scanner - Improve error handling in RPC client methods - Add debug endpoints to validate Solana connection - Add message field to scan status responses - Enhance health endpoint with RPC connectivity status - Handle invalid block ranges and API rate limits
270 lines
9.4 KiB
Python
270 lines
9.4 KiB
Python
from typing import Dict, List
|
|
|
|
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.db.session import get_db
|
|
from app.models.models import ArbitrageEvent
|
|
from app.schemas.schemas import (ArbitrageEventResponse, ScanRequest,
|
|
ScanStatusResponse)
|
|
from app.services.scanner import BlockScanner
|
|
from app.services.solana_client import SolanaClient
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.post("/scan", response_model=ScanStatusResponse)
|
|
async def scan_for_arbitrage(
|
|
background_tasks: BackgroundTasks,
|
|
scan_request: ScanRequest,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
Scan the blockchain for arbitrage transactions
|
|
"""
|
|
scanner = BlockScanner(db)
|
|
|
|
# Check if scan is already in progress
|
|
if scanner.scan_in_progress:
|
|
return ScanStatusResponse(
|
|
last_scanned_block=scanner.get_last_scanned_block() or 0,
|
|
last_scan_time=scanner.get_scan_status()["last_scan_time"],
|
|
arbitrage_events_count=scanner.get_scan_status()["arbitrage_events_count"],
|
|
scan_in_progress=True,
|
|
message="Scan already in progress, please wait for it to complete"
|
|
)
|
|
|
|
try:
|
|
# Verify Solana connection before starting scan
|
|
client = SolanaClient()
|
|
latest_block = client.get_latest_block()
|
|
if not latest_block:
|
|
return ScanStatusResponse(
|
|
last_scanned_block=scanner.get_last_scanned_block() or 0,
|
|
last_scan_time=scanner.get_scan_status()["last_scan_time"],
|
|
arbitrage_events_count=scanner.get_scan_status()["arbitrage_events_count"],
|
|
scan_in_progress=False,
|
|
message="Failed to connect to Solana RPC. Please check your connection and try again."
|
|
)
|
|
except Exception as e:
|
|
return ScanStatusResponse(
|
|
last_scanned_block=scanner.get_last_scanned_block() or 0,
|
|
last_scan_time=scanner.get_scan_status()["last_scan_time"],
|
|
arbitrage_events_count=scanner.get_scan_status()["arbitrage_events_count"],
|
|
scan_in_progress=False,
|
|
message=f"Error connecting to Solana RPC: {str(e)}"
|
|
)
|
|
|
|
# Add scanning task to background tasks
|
|
background_tasks.add_task(
|
|
scanner.scan_blocks,
|
|
num_blocks=scan_request.num_blocks,
|
|
start_slot=scan_request.start_slot,
|
|
)
|
|
|
|
# Return current scan status
|
|
status = scanner.get_scan_status()
|
|
status["message"] = "Scan started successfully"
|
|
return ScanStatusResponse(**status)
|
|
|
|
|
|
@router.get("/status", response_model=ScanStatusResponse)
|
|
async def get_scan_status(
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
Get the current status of the arbitrage scanner
|
|
"""
|
|
scanner = BlockScanner(db)
|
|
status = scanner.get_scan_status()
|
|
|
|
# Add helpful message based on status
|
|
if status["scan_in_progress"]:
|
|
status["message"] = "Scan is currently in progress"
|
|
elif status["last_scanned_block"] == 0:
|
|
status["message"] = "No blocks have been scanned yet. Use the /scan endpoint to start scanning."
|
|
else:
|
|
status["message"] = f"Last scan completed at {status['last_scan_time']}. Found {status['arbitrage_events_count']} arbitrage events."
|
|
|
|
return ScanStatusResponse(**status)
|
|
|
|
|
|
@router.get("/events", response_model=List[ArbitrageEventResponse])
|
|
async def get_arbitrage_events(
|
|
skip: int = 0,
|
|
limit: int = 100,
|
|
min_confidence: float = 0.0,
|
|
token_address: str = None,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
Get detected arbitrage events
|
|
"""
|
|
query = db.query(ArbitrageEvent)
|
|
|
|
# Apply filters
|
|
if min_confidence > 0:
|
|
query = query.filter(ArbitrageEvent.confidence_score >= min_confidence)
|
|
|
|
if token_address:
|
|
query = query.filter(ArbitrageEvent.profit_token_address == token_address)
|
|
|
|
# Get paginated results
|
|
events = query.order_by(ArbitrageEvent.detected_at.desc()).offset(skip).limit(limit).all()
|
|
|
|
# Convert to response format
|
|
result = []
|
|
for event in events:
|
|
# Get transaction signature and block slot
|
|
transaction = event.transaction
|
|
block_slot = transaction.block_id if transaction else 0
|
|
|
|
result.append(
|
|
ArbitrageEventResponse(
|
|
id=event.id,
|
|
transaction_signature=transaction.signature if transaction else "",
|
|
profit_token_address=event.profit_token_address,
|
|
profit_amount=event.profit_amount,
|
|
profit_usd=event.profit_usd,
|
|
path=event.path,
|
|
confidence_score=event.confidence_score,
|
|
detected_at=event.detected_at,
|
|
block_slot=block_slot,
|
|
)
|
|
)
|
|
|
|
return result
|
|
|
|
|
|
@router.get("/events/{event_id}", response_model=ArbitrageEventResponse)
|
|
async def get_arbitrage_event(
|
|
event_id: int,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
Get a specific arbitrage event by ID
|
|
"""
|
|
event = db.query(ArbitrageEvent).filter(ArbitrageEvent.id == event_id).first()
|
|
|
|
if not event:
|
|
raise HTTPException(status_code=404, detail="Arbitrage event not found")
|
|
|
|
# Get transaction signature and block slot
|
|
transaction = event.transaction
|
|
block_slot = transaction.block_id if transaction else 0
|
|
|
|
return ArbitrageEventResponse(
|
|
id=event.id,
|
|
transaction_signature=transaction.signature if transaction else "",
|
|
profit_token_address=event.profit_token_address,
|
|
profit_amount=event.profit_amount,
|
|
profit_usd=event.profit_usd,
|
|
path=event.path,
|
|
confidence_score=event.confidence_score,
|
|
detected_at=event.detected_at,
|
|
block_slot=block_slot,
|
|
)
|
|
|
|
|
|
@router.get("/debug/solana-connection", response_model=Dict)
|
|
async def debug_solana_connection():
|
|
"""
|
|
Debug endpoint to test Solana RPC connection
|
|
"""
|
|
client = SolanaClient()
|
|
results = {}
|
|
|
|
try:
|
|
# Test getting latest blockhash
|
|
latest_block = client.get_latest_block()
|
|
results["latest_blockhash"] = latest_block
|
|
|
|
# Try to get a slot
|
|
slot_response = client.client.get_slot()
|
|
if "result" in slot_response:
|
|
results["current_slot"] = slot_response["result"]
|
|
|
|
# Try to get a block
|
|
try:
|
|
block_data = client.get_block(slot_response["result"])
|
|
results["block_data"] = {"has_data": block_data is not None}
|
|
if block_data:
|
|
results["block_data"]["parent_slot"] = block_data.get("parentSlot")
|
|
results["block_data"]["transactions_count"] = len(block_data.get("transactions", []))
|
|
except Exception as e:
|
|
results["block_error"] = str(e)
|
|
|
|
except Exception as e:
|
|
results["error"] = str(e)
|
|
|
|
# Add RPC URL info
|
|
results["rpc_url"] = client.rpc_url
|
|
|
|
return results
|
|
|
|
|
|
@router.get("/validate-connection", response_model=Dict)
|
|
async def validate_solana_connection():
|
|
"""
|
|
Validate the Solana RPC connection and return detailed status
|
|
"""
|
|
client = SolanaClient()
|
|
result = {
|
|
"status": "success",
|
|
"rpc_url": client.rpc_url,
|
|
"connection_valid": False,
|
|
"message": "",
|
|
}
|
|
|
|
try:
|
|
# Test 1: Get latest blockhash
|
|
latest_blockhash = client.get_latest_block()
|
|
result["blockhash_test"] = {
|
|
"success": latest_blockhash is not None,
|
|
"data": latest_blockhash if latest_blockhash else "Failed to retrieve"
|
|
}
|
|
|
|
# Test 2: Get current slot
|
|
try:
|
|
slot_response = client.client.get_slot()
|
|
current_slot = slot_response.get("result") if "result" in slot_response else None
|
|
result["slot_test"] = {
|
|
"success": current_slot is not None,
|
|
"data": current_slot if current_slot else "Failed to retrieve"
|
|
}
|
|
|
|
# If we have a slot, try to get a block
|
|
if current_slot:
|
|
try:
|
|
block = client.get_block(current_slot)
|
|
result["block_test"] = {
|
|
"success": block is not None,
|
|
"data": {
|
|
"has_transactions": block is not None and "transactions" in block,
|
|
"transaction_count": len(block.get("transactions", [])) if block else 0
|
|
}
|
|
}
|
|
except Exception as e:
|
|
result["block_test"] = {
|
|
"success": False,
|
|
"error": str(e)
|
|
}
|
|
except Exception as e:
|
|
result["slot_test"] = {
|
|
"success": False,
|
|
"error": str(e)
|
|
}
|
|
|
|
# Determine overall status
|
|
if result.get("blockhash_test", {}).get("success", False) and result.get("slot_test", {}).get("success", False):
|
|
result["connection_valid"] = True
|
|
result["message"] = "Solana RPC connection is valid and functioning properly."
|
|
else:
|
|
result["status"] = "error"
|
|
result["message"] = "Solana RPC connection is not fully functional."
|
|
|
|
except Exception as e:
|
|
result["status"] = "error"
|
|
result["message"] = f"Error validating Solana RPC connection: {str(e)}"
|
|
|
|
return result |