Phase 2.1-2.3: Add exchange endpoints

Add exchange trading endpoints:
- POST /api/exchange: Create exchange trade
  - Validates slot, price staleness, EUR amount limits
  - Calculates sats from EUR and agreed price
  - Direction-specific premium (buy=+5%, sell=-5%)

- GET /api/trades: List user's exchanges
- POST /api/trades/{id}/cancel: Cancel user's exchange

Add schemas:
- ExchangeRequest, ExchangeResponse
- ExchangeUserContact, AdminExchangeResponse (for Phase 2.4)
- PaginatedExchanges, PaginatedAdminExchanges
This commit is contained in:
counterweight 2025-12-22 18:28:56 +01:00
parent d88d69cf9f
commit ce9159c5b0
Signed by: counterweight
GPG key ID: 883EDBAA726BD96C
3 changed files with 382 additions and 15 deletions

View file

@ -234,6 +234,74 @@ class PriceHistoryResponse(BaseModel):
created_at: datetime
# =============================================================================
# Exchange Schemas
# =============================================================================
class ExchangeRequest(BaseModel):
"""Request to create an exchange trade."""
slot_start: datetime
direction: str # "buy" or "sell"
eur_amount: int # EUR cents (e.g., 10000 = €100)
class ExchangeResponse(BaseModel):
"""Response model for an exchange trade."""
id: int
user_id: int
user_email: str
slot_start: datetime
slot_end: datetime
direction: str
eur_amount: int # EUR cents
sats_amount: int # Satoshis
market_price_eur: float
agreed_price_eur: float
premium_percentage: int
status: str
created_at: datetime
cancelled_at: datetime | None
completed_at: datetime | None
class ExchangeUserContact(BaseModel):
"""User contact info for admin view."""
email: str
contact_email: str | None
telegram: str | None
signal: str | None
nostr_npub: str | None
class AdminExchangeResponse(BaseModel):
"""Response model for admin exchange view (includes user contact)."""
id: int
user_id: int
user_email: str
user_contact: ExchangeUserContact
slot_start: datetime
slot_end: datetime
direction: str
eur_amount: int
sats_amount: int
market_price_eur: float
agreed_price_eur: float
premium_percentage: int
status: str
created_at: datetime
cancelled_at: datetime | None
completed_at: datetime | None
PaginatedExchanges = PaginatedResponse[ExchangeResponse]
PaginatedAdminExchanges = PaginatedResponse[AdminExchangeResponse]
# =============================================================================
# Meta/Constants Schemas
# =============================================================================