import asyncio from contextlib import asynccontextmanager class UserLocks: """Per-user asyncio.Lock registry, shared by PLAY and WITHDRAW so a user can never have a bet-build and a withdrawal-build in flight at once (both would otherwise spend from the same UTXO set on the user's dedicated address). Single-process-only by design (an in-memory dict of asyncio.Lock) — this is an accepted MVP constraint; a multi-process deployment would need a DB or Redis lock instead (e.g. a Postgres advisory lock). """ def __init__(self) -> None: self._locks: dict[int, asyncio.Lock] = {} @asynccontextmanager async def acquire(self, user_id: int): lock = self._locks.setdefault(user_id, asyncio.Lock()) async with lock: yield