- Add tier field to User model for plan detection (free/normal/pro)
- Create AVE Cloud API client with all Data API endpoints:
- Token search (GET /v2/tokens)
- Batch prices (POST /v2/tokens/price)
- Token details (GET /v2/tokens/{id})
- Kline data (GET /v2/klines/token/{id})
- Trending tokens (GET /v2/tokens/trending)
- Token risk (GET /v2/contracts/{id})
- Add Trading API endpoints:
- Chain wallet quote (POST /v1/chain/quote)
- Chain wallet swap (POST /v1/chain/swap)
- Add tier gating with upsell messaging for Pro features
- Handle rate limiting gracefully with 429 responses
- Add Pydantic schemas for AVE API requests/responses
Fixes #11
266 lines
8.5 KiB
Python
266 lines
8.5 KiB
Python
from fastapi import APIRouter, Depends, HTTPException, status, Request
|
|
from sqlalchemy.orm import Session
|
|
from typing import Annotated, Optional
|
|
import httpx
|
|
|
|
from .auth import get_current_user
|
|
from ..core.database import get_db
|
|
from ..core.config import get_settings
|
|
from ..db.models import User
|
|
from ..services.ave import AveCloudClient, check_tier_access
|
|
from ..db.schemas import (
|
|
AveBatchPricesRequest,
|
|
AveKlinesRequest,
|
|
AveChainQuoteRequest,
|
|
AveChainSwapRequest,
|
|
)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
def get_ave_client() -> AveCloudClient:
|
|
settings = get_settings()
|
|
return AveCloudClient(api_key=settings.AVE_API_KEY, plan=settings.AVE_API_PLAN)
|
|
|
|
|
|
@router.get("/tokens")
|
|
async def search_tokens(
|
|
query: Optional[str] = None,
|
|
chain: Optional[str] = None,
|
|
limit: int = 20,
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
client = get_ave_client()
|
|
try:
|
|
tokens = await client.get_tokens(query=query, chain=chain, limit=limit)
|
|
return {"tokens": tokens}
|
|
except httpx.HTTPStatusError as e:
|
|
if e.response.status_code == 429:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
|
detail="Rate limit exceeded. Please try again later.",
|
|
)
|
|
raise HTTPException(
|
|
status_code=e.response.status_code,
|
|
detail=f"AVE API error: {e.response.text}",
|
|
)
|
|
except Exception as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Failed to fetch tokens: {str(e)}",
|
|
)
|
|
|
|
|
|
@router.post("/tokens/price")
|
|
async def get_batch_prices(
|
|
request: AveBatchPricesRequest,
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
client = get_ave_client()
|
|
try:
|
|
prices = await client.get_batch_prices(request.token_ids)
|
|
return {"prices": prices}
|
|
except httpx.HTTPStatusError as e:
|
|
if e.response.status_code == 429:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
|
detail="Rate limit exceeded. Please try again later.",
|
|
)
|
|
raise HTTPException(
|
|
status_code=e.response.status_code,
|
|
detail=f"AVE API error: {e.response.text}",
|
|
)
|
|
except Exception as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Failed to fetch batch prices: {str(e)}",
|
|
)
|
|
|
|
|
|
@router.get("/tokens/{token_id}")
|
|
async def get_token_details(
|
|
token_id: str,
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
client = get_ave_client()
|
|
try:
|
|
token = await client.get_token_details(token_id)
|
|
if token is None:
|
|
return {"token": None, "upsell_message": None}
|
|
return {"token": token}
|
|
except httpx.HTTPStatusError as e:
|
|
if e.response.status_code == 429:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
|
detail="Rate limit exceeded. Please try again later.",
|
|
)
|
|
raise HTTPException(
|
|
status_code=e.response.status_code,
|
|
detail=f"AVE API error: {e.response.text}",
|
|
)
|
|
except Exception as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Failed to fetch token details: {str(e)}",
|
|
)
|
|
|
|
|
|
@router.get("/klines/{token_id}")
|
|
async def get_klines(
|
|
token_id: str,
|
|
interval: str = "1h",
|
|
limit: int = 100,
|
|
start_time: Optional[int] = None,
|
|
end_time: Optional[int] = None,
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
client = get_ave_client()
|
|
try:
|
|
klines = await client.get_klines(
|
|
token_id=token_id,
|
|
interval=interval,
|
|
limit=limit,
|
|
start_time=start_time,
|
|
end_time=end_time,
|
|
)
|
|
return {"klines": klines}
|
|
except httpx.HTTPStatusError as e:
|
|
if e.response.status_code == 429:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
|
detail="Rate limit exceeded. Please try again later.",
|
|
)
|
|
raise HTTPException(
|
|
status_code=e.response.status_code,
|
|
detail=f"AVE API error: {e.response.text}",
|
|
)
|
|
except Exception as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Failed to fetch klines: {str(e)}",
|
|
)
|
|
|
|
|
|
@router.get("/tokens/trending")
|
|
async def get_trending_tokens(
|
|
chain: Optional[str] = None,
|
|
limit: int = 20,
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
client = get_ave_client()
|
|
try:
|
|
tokens = await client.get_trending_tokens(chain=chain, limit=limit)
|
|
return {"tokens": tokens}
|
|
except httpx.HTTPStatusError as e:
|
|
if e.response.status_code == 429:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
|
detail="Rate limit exceeded. Please try again later.",
|
|
)
|
|
raise HTTPException(
|
|
status_code=e.response.status_code,
|
|
detail=f"AVE API error: {e.response.text}",
|
|
)
|
|
except Exception as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Failed to fetch trending tokens: {str(e)}",
|
|
)
|
|
|
|
|
|
@router.get("/contracts/{contract_id}")
|
|
async def get_token_risk(
|
|
contract_id: str,
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
client = get_ave_client()
|
|
try:
|
|
risk = await client.get_token_risk(contract_id)
|
|
if risk is None:
|
|
return {"risk": None, "upsell_message": None}
|
|
return {"risk": risk}
|
|
except httpx.HTTPStatusError as e:
|
|
if e.response.status_code == 429:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
|
detail="Rate limit exceeded. Please try again later.",
|
|
)
|
|
raise HTTPException(
|
|
status_code=e.response.status_code,
|
|
detail=f"AVE API error: {e.response.text}",
|
|
)
|
|
except Exception as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Failed to fetch token risk: {str(e)}",
|
|
)
|
|
|
|
|
|
@router.post("/chain/quote")
|
|
async def get_chain_quote(
|
|
request: AveChainQuoteRequest,
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
client = get_ave_client()
|
|
try:
|
|
quote = await client.get_chain_quote(
|
|
chain=request.chain,
|
|
from_token=request.from_token,
|
|
to_token=request.to_token,
|
|
amount=request.amount,
|
|
slippage=request.slippage,
|
|
)
|
|
if quote is None:
|
|
return {"quote": None, "upsell_message": None}
|
|
return {"quote": quote}
|
|
except httpx.HTTPStatusError as e:
|
|
if e.response.status_code == 429:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
|
detail="Rate limit exceeded. Please try again later.",
|
|
)
|
|
raise HTTPException(
|
|
status_code=e.response.status_code,
|
|
detail=f"AVE API error: {e.response.text}",
|
|
)
|
|
except Exception as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Failed to fetch chain quote: {str(e)}",
|
|
)
|
|
|
|
|
|
@router.post("/chain/swap")
|
|
async def get_chain_swap(
|
|
request: AveChainSwapRequest,
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
client = get_ave_client()
|
|
try:
|
|
swap = await client.get_chain_swap(
|
|
chain=request.chain,
|
|
from_token=request.from_token,
|
|
to_token=request.to_token,
|
|
amount=request.amount,
|
|
slippage=request.slippage,
|
|
wallet_address=request.wallet_address,
|
|
)
|
|
if swap is None:
|
|
return {"swap": None, "upsell_message": None}
|
|
return {"swap": swap}
|
|
except httpx.HTTPStatusError as e:
|
|
if e.response.status_code == 429:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
|
detail="Rate limit exceeded. Please try again later.",
|
|
)
|
|
raise HTTPException(
|
|
status_code=e.response.status_code,
|
|
detail=f"AVE API error: {e.response.text}",
|
|
)
|
|
except Exception as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Failed to fetch chain swap: {str(e)}",
|
|
)
|