improve signed reads, crypto hardening, and scraper transport
This commit is contained in:
@@ -8,7 +8,8 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.database import get_db
|
||||
from app.models import BotTrade
|
||||
from app.schemas import BotPerformance
|
||||
from app.services.signed_request import verify_signed_request_any
|
||||
from app.services.signed_request import (
|
||||
SignedReadCreds, signed_read_creds, verify_signed_request_any)
|
||||
|
||||
router = APIRouter()
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -21,8 +22,7 @@ ACTION_VIEW_USER = "view_user"
|
||||
@router.get("/performance", response_model=BotPerformance)
|
||||
async def get_performance(
|
||||
wallet: str = Query(..., description="Wallet address (lower-cased internally)"),
|
||||
ts: int = Query(..., description="Signed timestamp (ms)"),
|
||||
sig: str = Query(..., description="EIP-191 signature"),
|
||||
creds: SignedReadCreds = Depends(signed_read_creds),
|
||||
include_paper: bool = Query(
|
||||
False,
|
||||
description="Include paper (simulated) trades. Default false — this "
|
||||
@@ -35,8 +35,8 @@ async def get_performance(
|
||||
verify_signed_request_any(
|
||||
actions=[ACTION_VIEW_PERFORMANCE, ACTION_VIEW_USER],
|
||||
wallet=wallet,
|
||||
timestamp_ms=ts,
|
||||
signature=sig,
|
||||
timestamp_ms=creds.ts,
|
||||
signature=creds.sig,
|
||||
body=None,
|
||||
allow_replay=True,
|
||||
)
|
||||
|
||||
+12
-13
@@ -35,7 +35,9 @@ from app.database import get_db
|
||||
from app.models import BotTrade, Subscription, iso_utc
|
||||
from app.services.crypto import decrypt_api_key
|
||||
from app.services.price_store import price_store
|
||||
from app.services.signed_request import verify_signed_request, verify_signed_request_any
|
||||
from app.services.signed_request import (
|
||||
SignedReadCreds, signed_read_creds,
|
||||
verify_signed_request, verify_signed_request_any)
|
||||
|
||||
router = APIRouter()
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -144,8 +146,7 @@ def _enrich(trade: BotTrade) -> OpenPosition:
|
||||
@router.get("/positions/open", response_model=OpenPositionsResponse)
|
||||
async def get_open_positions(
|
||||
wallet: str = Query(..., description="Wallet address (lower-cased internally)"),
|
||||
ts: int = Query(..., description="Signed timestamp (ms)"),
|
||||
sig: str = Query(..., description="EIP-191 signature"),
|
||||
creds: SignedReadCreds = Depends(signed_read_creds),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Live open positions for the wallet, with mark-to-market PnL."""
|
||||
@@ -153,8 +154,8 @@ async def get_open_positions(
|
||||
verify_signed_request_any(
|
||||
actions=[ACTION_VIEW_POSITIONS, ACTION_VIEW_USER],
|
||||
wallet=wallet,
|
||||
timestamp_ms=ts,
|
||||
signature=sig,
|
||||
timestamp_ms=creds.ts,
|
||||
signature=creds.sig,
|
||||
body=None,
|
||||
allow_replay=True,
|
||||
)
|
||||
@@ -177,8 +178,7 @@ async def get_open_positions(
|
||||
@router.get("/positions/today", response_model=TodayStatsResponse)
|
||||
async def get_today_stats(
|
||||
wallet: str = Query(...),
|
||||
ts: int = Query(..., description="Signed timestamp (ms)"),
|
||||
sig: str = Query(..., description="EIP-191 signature"),
|
||||
creds: SignedReadCreds = Depends(signed_read_creds),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Today's realized P&L (since UTC midnight) + open count.
|
||||
@@ -190,8 +190,8 @@ async def get_today_stats(
|
||||
verify_signed_request_any(
|
||||
actions=[ACTION_VIEW_POSITIONS, ACTION_VIEW_USER],
|
||||
wallet=wallet,
|
||||
timestamp_ms=ts,
|
||||
signature=sig,
|
||||
timestamp_ms=creds.ts,
|
||||
signature=creds.sig,
|
||||
body=None,
|
||||
allow_replay=True,
|
||||
)
|
||||
@@ -453,8 +453,7 @@ class HLPositionsResponse(BaseModel):
|
||||
@router.get("/positions/hl/{wallet}", response_model=HLPositionsResponse)
|
||||
async def list_hl_positions_endpoint(
|
||||
wallet: str,
|
||||
ts: int = Query(..., description="Signed timestamp (ms)"),
|
||||
sig: str = Query(..., description="EIP-191 signature"),
|
||||
creds: SignedReadCreds = Depends(signed_read_creds),
|
||||
):
|
||||
"""Read the wallet's CURRENT Hyperliquid open positions, annotated with
|
||||
'already adopted' flag. Used by the Adopt picker on the frontend.
|
||||
@@ -469,8 +468,8 @@ async def list_hl_positions_endpoint(
|
||||
verify_signed_request_any(
|
||||
actions=[ACTION_VIEW_POSITIONS, ACTION_VIEW_USER],
|
||||
wallet=wallet,
|
||||
timestamp_ms=ts,
|
||||
signature=sig,
|
||||
timestamp_ms=creds.ts,
|
||||
signature=creds.sig,
|
||||
body=None,
|
||||
allow_replay=True,
|
||||
)
|
||||
|
||||
+3
-4
@@ -1,7 +1,6 @@
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
import httpx
|
||||
from fastapi import APIRouter, HTTPException, Query, Request
|
||||
from fastapi.responses import Response
|
||||
|
||||
@@ -33,9 +32,9 @@ async def fetch_binance_candles(asset: str, tf: str, limit: int) -> List[Candle]
|
||||
symbol = SYMBOL_MAP[asset]
|
||||
interval = BINANCE_INTERVAL[tf]
|
||||
url = f"{settings.binance_rest_url}/api/v3/klines?symbol={symbol}&interval={interval}&limit={limit}"
|
||||
async with httpx.AsyncClient(timeout=15) as client:
|
||||
resp = await client.get(url)
|
||||
resp.raise_for_status()
|
||||
from app.services.http_client import get_client
|
||||
resp = await get_client().get(url, timeout=15)
|
||||
resp.raise_for_status()
|
||||
rows = resp.json()
|
||||
return [
|
||||
Candle(
|
||||
|
||||
+6
-6
@@ -25,7 +25,8 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.config import settings
|
||||
from app.database import get_db
|
||||
from app.models import TelegramBinding, Subscription
|
||||
from app.services.signed_request import verify_signed_request
|
||||
from app.services.signed_request import (
|
||||
SignedReadCreds, optional_signed_read_creds, verify_signed_request)
|
||||
from app.services.telegram import send_test_message
|
||||
from app.services.telegram_bot import issue_binding_code, unbind_wallet
|
||||
|
||||
@@ -144,8 +145,7 @@ async def _build_status(
|
||||
@router.get("/{wallet}/status", response_model=StatusResponse)
|
||||
async def status(
|
||||
wallet: str,
|
||||
timestamp: Optional[int] = None,
|
||||
signature: Optional[str] = None,
|
||||
creds: Optional[SignedReadCreds] = Depends(optional_signed_read_creds),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
) -> StatusResponse:
|
||||
"""Wallet Telegram status.
|
||||
@@ -162,14 +162,14 @@ async def status(
|
||||
# Accepting view_user avoids requiring a separate wallet signature just to
|
||||
# see Telegram status — the frontend reuses its cached view_user envelope.
|
||||
authenticated = False
|
||||
if timestamp is not None and signature:
|
||||
if creds is not None:
|
||||
for _action in ("view_telegram_status", "view_user"):
|
||||
try:
|
||||
verify_signed_request(
|
||||
action=_action,
|
||||
wallet=wallet,
|
||||
timestamp_ms=timestamp,
|
||||
signature=signature,
|
||||
timestamp_ms=creds.ts,
|
||||
signature=creds.sig,
|
||||
body=None,
|
||||
allow_replay=True,
|
||||
)
|
||||
|
||||
+5
-5
@@ -9,7 +9,8 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.database import get_db
|
||||
from app.models import BotTrade, iso_utc
|
||||
from app.schemas import BotTrade as BotTradeSchema
|
||||
from app.services.signed_request import verify_signed_request_any
|
||||
from app.services.signed_request import (
|
||||
SignedReadCreds, signed_read_creds, verify_signed_request_any)
|
||||
|
||||
router = APIRouter()
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -55,8 +56,7 @@ def _trade_to_schema(trade: BotTrade) -> BotTradeSchema:
|
||||
@router.get("/trades", response_model=List[BotTradeSchema])
|
||||
async def get_trades(
|
||||
wallet: str = Query(..., description="Wallet address (lower-cased internally)"),
|
||||
ts: int = Query(..., description="Signed timestamp (ms)"),
|
||||
sig: str = Query(..., description="EIP-191 signature"),
|
||||
creds: SignedReadCreds = Depends(signed_read_creds),
|
||||
limit: int = Query(default=20, ge=1, le=500), # raised from 100: Analytics needs 500 for full history
|
||||
page: int = Query(default=1, ge=1),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
@@ -65,8 +65,8 @@ async def get_trades(
|
||||
verify_signed_request_any(
|
||||
actions=[ACTION_VIEW_TRADES, ACTION_VIEW_USER],
|
||||
wallet=wallet,
|
||||
timestamp_ms=ts,
|
||||
signature=sig,
|
||||
timestamp_ms=creds.ts,
|
||||
signature=creds.sig,
|
||||
body=None,
|
||||
allow_replay=True,
|
||||
)
|
||||
|
||||
+5
-5
@@ -16,7 +16,8 @@ from app.schemas import (
|
||||
)
|
||||
from app.services.crypto import encrypt_api_key
|
||||
from app.services.hyperliquid import HyperliquidTrader
|
||||
from app.services.signed_request import verify_signed_request
|
||||
from app.services.signed_request import (
|
||||
SignedReadCreds, signed_read_creds, verify_signed_request)
|
||||
|
||||
router = APIRouter()
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -132,8 +133,7 @@ async def get_user_public(wallet: str, db: AsyncSession = Depends(get_db)):
|
||||
@router.get("/user/{wallet}", response_model=UserResponse)
|
||||
async def get_user(
|
||||
wallet: str,
|
||||
ts: int = Query(..., description="Signed timestamp (ms)"),
|
||||
sig: str = Query(..., description="EIP-191 signature"),
|
||||
creds: SignedReadCreds = Depends(signed_read_creds),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
wallet = wallet.lower().strip()
|
||||
@@ -143,8 +143,8 @@ async def get_user(
|
||||
verify_signed_request(
|
||||
action=ACTION_VIEW_USER,
|
||||
wallet=wallet,
|
||||
timestamp_ms=ts,
|
||||
signature=sig,
|
||||
timestamp_ms=creds.ts,
|
||||
signature=creds.sig,
|
||||
body=None,
|
||||
allow_replay=True, # idempotent GET — allow sessionStorage caching for UX
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user