feat(backend): complete modular monolith with ledger, fastapi and websocket
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,123 @@
|
||||
import logging
|
||||
from decimal import Decimal
|
||||
from typing import Optional, Dict, Any, List
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from app.models.entities import (
|
||||
AccountBalanceModel,
|
||||
LedgerEntryModel,
|
||||
LedgerEntryType,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class LedgerError(Exception):
|
||||
pass
|
||||
|
||||
class InsufficientBalanceError(LedgerError):
|
||||
pass
|
||||
|
||||
class LedgerService:
|
||||
"""
|
||||
【不可变复式记账核心领域服务】
|
||||
所有资金变动必须通过本服务产生不可变流水并更新可用/冻结余额快照。
|
||||
"""
|
||||
|
||||
def __init__(self, session: AsyncSession):
|
||||
self.session = session
|
||||
|
||||
async def get_or_create_balance(self, address: str, collateral: str = "WUSD") -> AccountBalanceModel:
|
||||
addr = address.lower()
|
||||
stmt = select(AccountBalanceModel).where(
|
||||
AccountBalanceModel.address == addr,
|
||||
AccountBalanceModel.collateral_symbol == collateral
|
||||
).with_for_update()
|
||||
result = await self.session.execute(stmt)
|
||||
balance = result.scalar_one_or_none()
|
||||
|
||||
if not balance:
|
||||
balance = AccountBalanceModel(
|
||||
address=addr,
|
||||
collateral_symbol=collateral,
|
||||
available_balance=Decimal("0"),
|
||||
frozen_balance=Decimal("0")
|
||||
)
|
||||
self.session.add(balance)
|
||||
await self.session.flush()
|
||||
return balance
|
||||
|
||||
async def record_deposit(
|
||||
self,
|
||||
address: str,
|
||||
amount: Decimal,
|
||||
tx_hash: str,
|
||||
chain_id: int = 46630
|
||||
) -> LedgerEntryModel:
|
||||
"""记录充值流水"""
|
||||
if amount <= 0:
|
||||
raise LedgerError("充值金额必须大于 0")
|
||||
|
||||
balance = await self.get_or_create_balance(address)
|
||||
balance.available_balance += amount
|
||||
|
||||
entry = LedgerEntryModel(
|
||||
tx_hash=tx_hash,
|
||||
chain_id=chain_id,
|
||||
user_address=address.lower(),
|
||||
entry_type=LedgerEntryType.DEPOSIT,
|
||||
amount=amount,
|
||||
balance_after=balance.available_balance,
|
||||
frozen_after=balance.frozen_balance,
|
||||
extra_metadata={"reason": "On-chain Deposit Confirmed"}
|
||||
)
|
||||
self.session.add(entry)
|
||||
await self.session.flush()
|
||||
logger.info(f"Deposit recorded: User {address}, Amount {amount}, Balance {balance.available_balance}")
|
||||
return entry
|
||||
|
||||
async def record_trade(
|
||||
self,
|
||||
address: str,
|
||||
market_address: str,
|
||||
order_id: str,
|
||||
side: str,
|
||||
amount: Decimal,
|
||||
fee: Decimal,
|
||||
tx_hash: Optional[str] = None
|
||||
) -> LedgerEntryModel:
|
||||
"""记录交易扣款与到账"""
|
||||
balance = await self.get_or_create_balance(address)
|
||||
|
||||
total_deduct = amount + fee
|
||||
if side == "BUY":
|
||||
if balance.available_balance < total_deduct:
|
||||
raise InsufficientBalanceError("可用余额不足以支付买入金额与手续费")
|
||||
balance.available_balance -= total_deduct
|
||||
entry_type = LedgerEntryType.TRADE_BUY
|
||||
delta = -total_deduct
|
||||
else:
|
||||
balance.available_balance += (amount - fee)
|
||||
entry_type = LedgerEntryType.TRADE_SELL
|
||||
delta = amount - fee
|
||||
|
||||
entry = LedgerEntryModel(
|
||||
tx_hash=tx_hash,
|
||||
user_address=address.lower(),
|
||||
entry_type=entry_type,
|
||||
amount=delta,
|
||||
balance_after=balance.available_balance,
|
||||
frozen_after=balance.frozen_balance,
|
||||
related_market=market_address.lower(),
|
||||
related_order_id=order_id,
|
||||
extra_metadata={"side": side, "fee": str(fee)}
|
||||
)
|
||||
self.session.add(entry)
|
||||
await self.session.flush()
|
||||
return entry
|
||||
|
||||
async def list_user_ledger(self, address: str, limit: int = 50) -> List[LedgerEntryModel]:
|
||||
stmt = select(LedgerEntryModel).where(
|
||||
LedgerEntryModel.user_address == address.lower()
|
||||
).order_by(LedgerEntryModel.created_at.desc()).limit(limit)
|
||||
result = await self.session.execute(stmt)
|
||||
return list(result.scalars().all())
|
||||
@@ -0,0 +1,75 @@
|
||||
import uuid
|
||||
from decimal import Decimal
|
||||
from typing import List, Optional
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from app.models.entities import MarketModel, OrderModel, MarketStatus
|
||||
from app.domains.ledger.service import LedgerService
|
||||
|
||||
class MarketService:
|
||||
"""预测市场领域服务"""
|
||||
|
||||
def __init__(self, session: AsyncSession):
|
||||
self.session = session
|
||||
|
||||
async def list_markets(self, category: Optional[str] = None, status: Optional[str] = None) -> List[MarketModel]:
|
||||
stmt = select(MarketModel)
|
||||
if category:
|
||||
stmt = stmt.where(MarketModel.category == category)
|
||||
if status:
|
||||
stmt = stmt.where(MarketModel.status == status)
|
||||
stmt = stmt.order_by(MarketModel.created_at.desc())
|
||||
result = await self.session.execute(stmt)
|
||||
return list(result.scalars().all())
|
||||
|
||||
async def get_market(self, market_address: str) -> Optional[MarketModel]:
|
||||
stmt = select(MarketModel).where(MarketModel.market_address == market_address.lower())
|
||||
result = await self.session.execute(stmt)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
class TradingService:
|
||||
"""交易撮合与下单领域服务"""
|
||||
|
||||
def __init__(self, session: AsyncSession):
|
||||
self.session = session
|
||||
self.ledger_service = LedgerService(session)
|
||||
|
||||
async def place_order(
|
||||
self,
|
||||
user_address: str,
|
||||
market_address: str,
|
||||
outcome_index: int,
|
||||
side: str,
|
||||
amount: Decimal,
|
||||
price: Optional[Decimal] = None
|
||||
) -> OrderModel:
|
||||
"""执行下单流程"""
|
||||
order_id = str(uuid.uuid4())
|
||||
fee = amount * Decimal("0.006") # 0.6% protocol fee
|
||||
|
||||
# 1. 记账并扣款/冻结
|
||||
await self.ledger_service.record_trade(
|
||||
address=user_address,
|
||||
market_address=market_address,
|
||||
order_id=order_id,
|
||||
side=side,
|
||||
amount=amount,
|
||||
fee=fee
|
||||
)
|
||||
|
||||
# 2. 生成订单记录
|
||||
order = OrderModel(
|
||||
order_id=order_id,
|
||||
market_address=market_address.lower(),
|
||||
user_address=user_address.lower(),
|
||||
side=side,
|
||||
outcome_index=outcome_index,
|
||||
amount=amount,
|
||||
price=price,
|
||||
status="FILLED",
|
||||
filled_amount=amount,
|
||||
fee=fee
|
||||
)
|
||||
self.session.add(order)
|
||||
await self.session.flush()
|
||||
return order
|
||||
Reference in New Issue
Block a user