from datetime import datetime, timezone from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.db.models import PendingTransaction, Round from app.tx.confirmation import register_handler async def _on_payout_confirmed(session: AsyncSession, pending: PendingTransaction) -> None: # Resolved by round_id rather than by payout_txid: an RBF-bumped payout confirms # under a different txid than the one first recorded (B-02). round_ = None if pending.round_id is not None: round_ = await session.get(Round, pending.round_id) if round_ is None: round_ = await session.scalar(select(Round).where(Round.payout_txid == pending.current_txid)) if round_ is not None and round_.status == "paying_out": round_.status = "closed" # closed_at is what round_cooldown_seconds counts from (service.py's # open_new_round_if_needed) — re-stamp it here at actual payout # confirmation time rather than leaving it at the earlier "closing" # timestamp, so a short cooldown (e.g. 20s) is a real pause after the # winner's tx confirms, not swallowed by the ~2 block-time draw+payout wait. round_.closed_at = datetime.now(timezone.utc) # The winner's own address is already watched by the Electrum listener, so # their balance is credited by the normal deposit path once this confirms. register_handler("payout", _on_payout_confirmed)