@@ -414,8 +414,10 @@ async def run_daily_sync(self, db: AsyncSession) -> Dict:
414414 adjustment_factor = str (adj_factor )
415415 )
416416
417- # Get current balance to check if already refreshed today
418- balance = await credits_crud .get_or_create_balance (db , user_id )
417+ # Get current balance with row lock to ensure we modify a
418+ # session-tracked object (bypasses Redis cache) and prevent
419+ # concurrent updates from racing with us.
420+ balance = await credits_crud .get_or_create_balance (db , user_id , for_update = True )
419421
420422 # Check if already refreshed today - skip balance update but still update wallet stakes
421423 if balance .staking_refresh_date == today :
@@ -431,7 +433,9 @@ async def run_daily_sync(self, db: AsyncSession) -> Dict:
431433
432434 idempotency_key = f"staking_sync:{ user_id } :{ today .isoformat ()} "
433435
434- # Create ledger entry for staking refresh (transaction record)
436+ # Create ledger entry for staking refresh (transaction record).
437+ # Use auto_commit=False so the ledger row and the balance
438+ # update below are committed in a single atomic transaction.
435439 if daily_amount > 0 :
436440 await credits_crud .create_ledger_entry (
437441 db = db ,
@@ -442,6 +446,7 @@ async def run_daily_sync(self, db: AsyncSession) -> Dict:
442446 amount_paid = Decimal ("0" ),
443447 amount_staking = daily_amount , # Positive for credit (in USD)
444448 description = f"Daily staking rewards: { stake_in_mor :.4f} MOR staked ({ stake_share * 100 :.4f} % share), earned { mor_earned :.6f} MOR @ ${ mor_price :.2f} " ,
449+ auto_commit = False ,
445450 )
446451
447452 # Update user's staking balance
@@ -452,7 +457,7 @@ async def run_daily_sync(self, db: AsyncSession) -> Dict:
452457 balance .is_staker = daily_amount > 0
453458 balance .updated_at = datetime .utcnow ()
454459
455- # Commit all changes for this user atomically
460+ # Commit ledger entry + balance update atomically
456461 await db .commit ()
457462
458463 users_processed += 1
0 commit comments