
- Created Alembic migrations for SQLite database - Set up database initialization on app startup - Fixed linting issues with Ruff - Updated README with comprehensive documentation - Configured startup tasks and health checks
166 lines
5.9 KiB
Python
166 lines
5.9 KiB
Python
import logging
|
|
import time
|
|
from typing import Dict, List, Optional, Any
|
|
import httpx
|
|
|
|
from app.services.dex.base import BaseDexService
|
|
from app.services.solana import USDC_TOKEN_ADDRESS
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Jupiter API V6 endpoints
|
|
JUPITER_API_BASE = "https://quote-api.jup.ag/v6"
|
|
PRICE_ENDPOINT = f"{JUPITER_API_BASE}/price"
|
|
QUOTE_ENDPOINT = f"{JUPITER_API_BASE}/quote"
|
|
|
|
|
|
class JupiterDexService(BaseDexService):
|
|
"""Service for Jupiter DEX price monitoring"""
|
|
|
|
def __init__(self):
|
|
super().__init__("jupiter")
|
|
self.http_client = httpx.AsyncClient(timeout=10.0)
|
|
|
|
async def get_token_price(self, token_address: str, quote_token_address: Optional[str] = None) -> Dict[str, Any]:
|
|
"""Get token price from Jupiter"""
|
|
if not quote_token_address:
|
|
quote_token_address = USDC_TOKEN_ADDRESS
|
|
|
|
try:
|
|
params = {
|
|
"ids": token_address,
|
|
"vsToken": quote_token_address
|
|
}
|
|
|
|
response = await self.http_client.get(PRICE_ENDPOINT, params=params)
|
|
response.raise_for_status()
|
|
|
|
data = response.json()
|
|
if "data" in data and token_address in data["data"]:
|
|
price_data = data["data"][token_address]
|
|
return {
|
|
"price": float(price_data["price"]),
|
|
"liquidity": float(price_data.get("liquidity", 0)),
|
|
"timestamp": int(time.time()),
|
|
"metadata": {
|
|
"raw_data": price_data
|
|
}
|
|
}
|
|
else:
|
|
logger.warning(f"No price data returned from Jupiter for {token_address}")
|
|
return {
|
|
"price": 0.0,
|
|
"liquidity": 0.0,
|
|
"timestamp": int(time.time()),
|
|
"metadata": {
|
|
"error": "No price data returned"
|
|
}
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error getting Jupiter price for {token_address}: {str(e)}")
|
|
return {
|
|
"price": 0.0,
|
|
"liquidity": 0.0,
|
|
"timestamp": int(time.time()),
|
|
"metadata": {
|
|
"error": str(e)
|
|
}
|
|
}
|
|
|
|
async def get_token_prices(self, token_addresses: List[str], quote_token_address: Optional[str] = None) -> Dict[str, Dict[str, Any]]:
|
|
"""Get prices for multiple tokens from Jupiter"""
|
|
if not quote_token_address:
|
|
quote_token_address = USDC_TOKEN_ADDRESS
|
|
|
|
try:
|
|
params = {
|
|
"ids": ",".join(token_addresses),
|
|
"vsToken": quote_token_address
|
|
}
|
|
|
|
response = await self.http_client.get(PRICE_ENDPOINT, params=params)
|
|
response.raise_for_status()
|
|
|
|
data = response.json()
|
|
result = {}
|
|
|
|
if "data" in data:
|
|
price_data = data["data"]
|
|
timestamp = int(time.time())
|
|
|
|
for token_address in token_addresses:
|
|
if token_address in price_data:
|
|
token_price_data = price_data[token_address]
|
|
result[token_address] = {
|
|
"price": float(token_price_data["price"]),
|
|
"liquidity": float(token_price_data.get("liquidity", 0)),
|
|
"timestamp": timestamp,
|
|
"metadata": {
|
|
"raw_data": token_price_data
|
|
}
|
|
}
|
|
else:
|
|
result[token_address] = {
|
|
"price": 0.0,
|
|
"liquidity": 0.0,
|
|
"timestamp": timestamp,
|
|
"metadata": {
|
|
"error": "No price data returned"
|
|
}
|
|
}
|
|
|
|
return result
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error getting Jupiter prices: {str(e)}")
|
|
timestamp = int(time.time())
|
|
return {
|
|
token_address: {
|
|
"price": 0.0,
|
|
"liquidity": 0.0,
|
|
"timestamp": timestamp,
|
|
"metadata": {
|
|
"error": str(e)
|
|
}
|
|
}
|
|
for token_address in token_addresses
|
|
}
|
|
|
|
async def get_swap_quote(self, input_token: str, output_token: str, amount: float, slippage_bps: int = 50) -> Dict[str, Any]:
|
|
"""
|
|
Get a swap quote from Jupiter
|
|
|
|
Args:
|
|
input_token: Address of the input token
|
|
output_token: Address of the output token
|
|
amount: Amount of input token to swap
|
|
slippage_bps: Slippage tolerance in basis points (1 bps = 0.01%)
|
|
|
|
Returns:
|
|
Quote data or error
|
|
"""
|
|
try:
|
|
# Convert amount to raw format
|
|
raw_amount = self.parse_token_amount(input_token, amount)
|
|
|
|
params = {
|
|
"inputMint": input_token,
|
|
"outputMint": output_token,
|
|
"amount": str(raw_amount),
|
|
"slippageBps": slippage_bps,
|
|
"onlyDirectRoutes": False,
|
|
"asLegacyTransaction": False,
|
|
}
|
|
|
|
response = await self.http_client.get(QUOTE_ENDPOINT, params=params)
|
|
response.raise_for_status()
|
|
|
|
data = response.json()
|
|
return data
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error getting Jupiter swap quote: {str(e)}")
|
|
return {
|
|
"error": str(e)
|
|
} |