Add deposit crediting from confirmed UTXOs
Credits a user's internal balance once a UTXO on their deposit address reaches 1 confirmation, keeping utxo_events as the source of truth and cached_balance_sats as a derived read cache. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.audit.log import write_audit_log
|
||||
from app.db.models import UtxoEvent
|
||||
from app.wallet.balance import recompute_balance
|
||||
|
||||
|
||||
async def credit_confirmed_utxos(session: AsyncSession, user_id: int, entries: list[dict]) -> int:
|
||||
"""Insert utxo_events for newly-confirmed entries from an Electrum
|
||||
`listunspent` response (idempotent on txid+vout), refresh the user's cached
|
||||
balance. Returns the number of newly-credited UTXOs.
|
||||
|
||||
entries: [{"tx_hash": ..., "tx_pos": ..., "height": ..., "value": ...}, ...]
|
||||
height <= 0 means unconfirmed (mempool) per the Electrum protocol convention —
|
||||
skipped, since the spec requires 1 confirmation before crediting.
|
||||
"""
|
||||
existing_keys = {
|
||||
(txid, vout)
|
||||
for txid, vout in (
|
||||
await session.execute(select(UtxoEvent.txid, UtxoEvent.vout).where(UtxoEvent.user_id == user_id))
|
||||
).all()
|
||||
}
|
||||
|
||||
newly_credited = 0
|
||||
for entry in entries:
|
||||
if entry["height"] <= 0:
|
||||
continue
|
||||
key = (entry["tx_hash"], entry["tx_pos"])
|
||||
if key in existing_keys:
|
||||
continue
|
||||
session.add(
|
||||
UtxoEvent(
|
||||
user_id=user_id,
|
||||
txid=entry["tx_hash"],
|
||||
vout=entry["tx_pos"],
|
||||
amount_sats=entry["value"],
|
||||
confirmed_height=entry["height"],
|
||||
)
|
||||
)
|
||||
await write_audit_log(
|
||||
session,
|
||||
"deposit_credited",
|
||||
{"txid": entry["tx_hash"], "vout": entry["tx_pos"], "amount_sats": entry["value"]},
|
||||
user_id=user_id,
|
||||
)
|
||||
newly_credited += 1
|
||||
|
||||
if newly_credited:
|
||||
await session.flush()
|
||||
await recompute_balance(session, user_id)
|
||||
await session.commit()
|
||||
|
||||
return newly_credited
|
||||
Reference in New Issue
Block a user