from fastapi import APIRouter, Depends, Request, Response, status from pydantic import BaseModel from sqlalchemy.ext.asyncio import AsyncSession from app.api.errors import from_api_error, http_error from app.auth.dependencies import get_current_user from app.bets.service import BetError, place_bet from app.db.models import User from app.db.session import get_session router = APIRouter(prefix="/bets", tags=["bets"]) class BetResponse(BaseModel): round_id: int bet_txid: str bet_amount_sats: int status: str @router.post("", response_model=BetResponse, status_code=status.HTTP_201_CREATED) async def create_bet( request: Request, response: Response, user: User = Depends(get_current_user), session: AsyncSession = Depends(get_session), ) -> BetResponse: listener = request.app.state.electrum_listener async with request.app.state.user_locks.acquire(user.id): # B-96 (BET-02): read after the lock is taken — a wait on it across a reconnection # must not hand place_bet the connection that closed while this request queued. client = listener.client if client is None: raise http_error( status.HTTP_503_SERVICE_UNAVAILABLE, "network_unavailable", "not connected to the network, try again shortly", ) try: participant = await place_bet(session, client, user, network=listener) except BetError as exc: # A rejected broadcast isn't the client's fault — it's the network # refusing our transaction, so it answers 502 rather than 400 (B-07). code = ( status.HTTP_502_BAD_GATEWAY if exc.code == "broadcast_failed" else status.HTTP_503_SERVICE_UNAVAILABLE # B-88: write lock not granted, nothing sent if exc.code == "service_busy" else status.HTTP_400_BAD_REQUEST ) raise from_api_error(code, exc) from exc if participant.status == "building": # B-76 (TX-20, BET-23): the outcome of the broadcast is uncertain — the tx may # already be on the network — so the bet is accepted for processing, not # confirmed as sent, and the reconciler settles it. response.status_code = status.HTTP_202_ACCEPTED return BetResponse( round_id=participant.round_id, bet_txid=participant.bet_txid, bet_amount_sats=participant.bet_amount_sats, status=participant.status, )