Compare commits
2 Commits
a892a403fb
...
b6f99aa8fe
| Author | SHA1 | Date | |
|---|---|---|---|
| b6f99aa8fe | |||
|
|
3806af3e23 |
265
src/backend/app/api/ave.py
Normal file
265
src/backend/app/api/ave.py
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
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)}",
|
||||||
|
)
|
||||||
@@ -25,6 +25,7 @@ class User(Base):
|
|||||||
id = Column(String, primary_key=True, default=generate_uuid)
|
id = Column(String, primary_key=True, default=generate_uuid)
|
||||||
email = Column(String, unique=True, nullable=False)
|
email = Column(String, unique=True, nullable=False)
|
||||||
password_hash = Column(String, nullable=False)
|
password_hash = Column(String, nullable=False)
|
||||||
|
tier = Column(String, default="free")
|
||||||
created_at = Column(DateTime, default=datetime.utcnow)
|
created_at = Column(DateTime, default=datetime.utcnow)
|
||||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
|
|
||||||
|
|||||||
@@ -144,3 +144,72 @@ class SignalResponse(BaseModel):
|
|||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
from_attributes = True
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
|
class AveTokenSearchResponse(BaseModel):
|
||||||
|
tokens: List[dict]
|
||||||
|
upsell_message: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class AveBatchPricesRequest(BaseModel):
|
||||||
|
token_ids: List[str]
|
||||||
|
|
||||||
|
|
||||||
|
class AveBatchPricesResponse(BaseModel):
|
||||||
|
prices: Dict[str, dict]
|
||||||
|
upsell_message: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class AveTokenDetailsResponse(BaseModel):
|
||||||
|
token: Optional[dict] = None
|
||||||
|
upsell_message: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class AveKlinesRequest(BaseModel):
|
||||||
|
token_id: str
|
||||||
|
interval: str = "1h"
|
||||||
|
limit: int = 100
|
||||||
|
start_time: Optional[int] = None
|
||||||
|
end_time: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
|
class AveKlinesResponse(BaseModel):
|
||||||
|
klines: List[dict]
|
||||||
|
upsell_message: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class AveTrendingTokensResponse(BaseModel):
|
||||||
|
tokens: List[dict]
|
||||||
|
upsell_message: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class AveTokenRiskResponse(BaseModel):
|
||||||
|
risk: Optional[dict] = None
|
||||||
|
upsell_message: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class AveChainQuoteRequest(BaseModel):
|
||||||
|
chain: str
|
||||||
|
from_token: str
|
||||||
|
to_token: str
|
||||||
|
amount: str
|
||||||
|
slippage: float = 0.5
|
||||||
|
|
||||||
|
|
||||||
|
class AveChainQuoteResponse(BaseModel):
|
||||||
|
quote: Optional[dict] = None
|
||||||
|
upsell_message: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class AveChainSwapRequest(BaseModel):
|
||||||
|
chain: str
|
||||||
|
from_token: str
|
||||||
|
to_token: str
|
||||||
|
amount: str
|
||||||
|
slippage: float = 0.5
|
||||||
|
wallet_address: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class AveChainSwapResponse(BaseModel):
|
||||||
|
swap: Optional[dict] = None
|
||||||
|
upsell_message: Optional[str] = None
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from fastapi import FastAPI
|
|||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from slowapi import Limiter
|
from slowapi import Limiter
|
||||||
from slowapi.util import get_remote_address
|
from slowapi.util import get_remote_address
|
||||||
from .api import auth, bots, backtest, simulate, config
|
from .api import auth, bots, backtest, simulate, config, ave
|
||||||
from .core.limiter import limiter
|
from .core.limiter import limiter
|
||||||
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
@@ -26,6 +26,7 @@ app.include_router(bots.router, prefix="/api/bots", tags=["bots"])
|
|||||||
app.include_router(backtest.router, prefix="/api", tags=["backtest"])
|
app.include_router(backtest.router, prefix="/api", tags=["backtest"])
|
||||||
app.include_router(simulate.router, prefix="/api", tags=["simulate"])
|
app.include_router(simulate.router, prefix="/api", tags=["simulate"])
|
||||||
app.include_router(config.router, prefix="/api/config", tags=["config"])
|
app.include_router(config.router, prefix="/api/config", tags=["config"])
|
||||||
|
app.include_router(ave.router, prefix="/api/ave", tags=["ave"])
|
||||||
|
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
|
|||||||
3
src/backend/app/services/ave/__init__.py
Normal file
3
src/backend/app/services/ave/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from .client import AveCloudClient, check_tier_access
|
||||||
|
|
||||||
|
__all__ = ["AveCloudClient", "check_tier_access"]
|
||||||
213
src/backend/app/services/ave/client.py
Normal file
213
src/backend/app/services/ave/client.py
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
import httpx
|
||||||
|
from typing import List, Dict, Any, Optional
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class AveCloudClient:
|
||||||
|
DATA_API_URL = "https://prod.ave-api.com"
|
||||||
|
TRADING_API_URL = "https://bot-api.ave.ai"
|
||||||
|
|
||||||
|
def __init__(self, api_key: str, plan: str = "free"):
|
||||||
|
self.api_key = api_key
|
||||||
|
self.plan = plan
|
||||||
|
|
||||||
|
def _data_headers(self) -> Dict[str, str]:
|
||||||
|
return {"X-API-KEY": self.api_key}
|
||||||
|
|
||||||
|
def _trading_headers(self) -> Dict[str, str]:
|
||||||
|
return {"X-API-KEY": self.api_key, "Content-Type": "application/json"}
|
||||||
|
|
||||||
|
async def get_tokens(
|
||||||
|
self,
|
||||||
|
query: Optional[str] = None,
|
||||||
|
chain: Optional[str] = None,
|
||||||
|
limit: int = 20,
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
url = f"{self.DATA_API_URL}/v2/tokens"
|
||||||
|
params = {"limit": limit}
|
||||||
|
if query:
|
||||||
|
params["query"] = query
|
||||||
|
if chain:
|
||||||
|
params["chain"] = chain
|
||||||
|
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.get(
|
||||||
|
url, headers=self._data_headers(), params=params, timeout=30.0
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
if data.get("status") == 200:
|
||||||
|
return data.get("data", [])
|
||||||
|
raise Exception(f"Failed to fetch tokens: {data}")
|
||||||
|
|
||||||
|
async def get_batch_prices(self, token_ids: List[str]) -> Dict[str, Dict[str, Any]]:
|
||||||
|
url = f"{self.DATA_API_URL}/v2/tokens/price"
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.post(
|
||||||
|
url,
|
||||||
|
headers=self._data_headers(),
|
||||||
|
json={"token_ids": token_ids},
|
||||||
|
timeout=30.0,
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
if data.get("status") == 200:
|
||||||
|
return data.get("data", {})
|
||||||
|
return {}
|
||||||
|
|
||||||
|
async def get_token_details(self, token_id: str) -> Optional[Dict[str, Any]]:
|
||||||
|
url = f"{self.DATA_API_URL}/v2/tokens/{token_id}"
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.get(url, headers=self._data_headers(), timeout=30.0)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
if data.get("status") == 200:
|
||||||
|
return data.get("data")
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_klines(
|
||||||
|
self,
|
||||||
|
token_id: str,
|
||||||
|
interval: str = "1h",
|
||||||
|
limit: int = 100,
|
||||||
|
start_time: Optional[int] = None,
|
||||||
|
end_time: Optional[int] = None,
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
url = f"{self.DATA_API_URL}/v2/klines/token/{token_id}"
|
||||||
|
params = {"interval": interval, "limit": limit}
|
||||||
|
if start_time:
|
||||||
|
params["start_time"] = start_time
|
||||||
|
if end_time:
|
||||||
|
params["end_time"] = end_time
|
||||||
|
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.get(
|
||||||
|
url, headers=self._data_headers(), params=params, timeout=30.0
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
if data.get("status") == 200:
|
||||||
|
return data.get("data", [])
|
||||||
|
raise Exception(f"Failed to fetch klines: {data}")
|
||||||
|
|
||||||
|
async def get_trending_tokens(
|
||||||
|
self, chain: Optional[str] = None, limit: int = 20
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
url = f"{self.DATA_API_URL}/v2/tokens/trending"
|
||||||
|
params = {"limit": limit}
|
||||||
|
if chain:
|
||||||
|
params["chain"] = chain
|
||||||
|
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.get(
|
||||||
|
url, headers=self._data_headers(), params=params, timeout=30.0
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
if data.get("status") == 200:
|
||||||
|
return data.get("data", [])
|
||||||
|
raise Exception(f"Failed to fetch trending tokens: {data}")
|
||||||
|
|
||||||
|
async def get_token_risk(self, contract_id: str) -> Optional[Dict[str, Any]]:
|
||||||
|
url = f"{self.DATA_API_URL}/v2/contracts/{contract_id}"
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.get(url, headers=self._data_headers(), timeout=30.0)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
if data.get("status") == 200:
|
||||||
|
return data.get("data")
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_chain_quote(
|
||||||
|
self,
|
||||||
|
chain: str,
|
||||||
|
from_token: str,
|
||||||
|
to_token: str,
|
||||||
|
amount: str,
|
||||||
|
slippage: float = 0.5,
|
||||||
|
) -> Optional[Dict[str, Any]]:
|
||||||
|
url = f"{self.TRADING_API_URL}/v1/chain/quote"
|
||||||
|
payload = {
|
||||||
|
"chain": chain,
|
||||||
|
"from_token": from_token,
|
||||||
|
"to_token": to_token,
|
||||||
|
"amount": amount,
|
||||||
|
"slippage": slippage,
|
||||||
|
}
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.post(
|
||||||
|
url, headers=self._trading_headers(), json=payload, timeout=30.0
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
if data.get("status") == 200:
|
||||||
|
return data.get("data")
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_chain_swap(
|
||||||
|
self,
|
||||||
|
chain: str,
|
||||||
|
from_token: str,
|
||||||
|
to_token: str,
|
||||||
|
amount: str,
|
||||||
|
slippage: float = 0.5,
|
||||||
|
wallet_address: Optional[str] = None,
|
||||||
|
) -> Optional[Dict[str, Any]]:
|
||||||
|
url = f"{self.TRADING_API_URL}/v1/chain/swap"
|
||||||
|
payload = {
|
||||||
|
"chain": chain,
|
||||||
|
"from_token": from_token,
|
||||||
|
"to_token": to_token,
|
||||||
|
"amount": amount,
|
||||||
|
"slippage": slippage,
|
||||||
|
}
|
||||||
|
if wallet_address:
|
||||||
|
payload["wallet_address"] = wallet_address
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.post(
|
||||||
|
url, headers=self._trading_headers(), json=payload, timeout=60.0
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
if data.get("status") == 200:
|
||||||
|
return data.get("data")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def check_tier_access(user_tier: str, feature: str) -> tuple[bool, Optional[str]]:
|
||||||
|
tier_access = {
|
||||||
|
"free": {
|
||||||
|
"data_rest": True,
|
||||||
|
"websocket": False,
|
||||||
|
"chain_wallet": True,
|
||||||
|
"proxy_wallet": False,
|
||||||
|
},
|
||||||
|
"normal": {
|
||||||
|
"data_rest": True,
|
||||||
|
"websocket": False,
|
||||||
|
"chain_wallet": True,
|
||||||
|
"proxy_wallet": True,
|
||||||
|
},
|
||||||
|
"pro": {
|
||||||
|
"data_rest": True,
|
||||||
|
"websocket": True,
|
||||||
|
"chain_wallet": True,
|
||||||
|
"proxy_wallet": True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if user_tier not in tier_access:
|
||||||
|
user_tier = "free"
|
||||||
|
|
||||||
|
access = tier_access[user_tier]
|
||||||
|
if access.get(feature):
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
upsell_messages = {
|
||||||
|
"websocket": "Upgrade to Pro plan to access WebSocket streaming data. Visit your account settings.",
|
||||||
|
"proxy_wallet": "Upgrade to Normal or Pro plan to access Proxy Wallet functionality. Visit your account settings.",
|
||||||
|
}
|
||||||
|
|
||||||
|
return False, upsell_messages.get(
|
||||||
|
feature, "Upgrade your plan to access this feature."
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user