Repository navigation
Money Safety Invariants
What the platform guarantees at the SQL layer, not just in business logic.
Every debit / credit lives inside a BEGIN IMMEDIATE block. SQLite's advisory lock makes the read-modify-write atomic with respect to any other writer.
A negative delta uses
UPDATE users SET balance = balance - ?
WHERE id = ? AND balance >= ?and checks rowcount. Overdraft is impossible at the SQL layer. Even a buggy caller cannot drive a balance negative.
The balance debit and the INSERT INTO withdrawals happen in the same transaction. A user hammering the "Withdraw" button on two devices cannot create two payout rows against the same funds — the second one is rejected with rowcount==0.
payments.payload TEXT UNIQUE. Replayed webhooks, retried Telegram callbacks, re-runs of the hold sweeper — all no-op once the row is in.
When we issue an invoice, we append a secrets.token_urlsafe(8) nonce to the payload. Two top-ups of the same amount by the same user no longer collide on the UNIQUE constraint and never get silently dropped.
cancel_task_with_refund flips status and credits the advertiser balance in one transaction, with a guaranteed task_refund_{id} UNIQUE so re-running cancel is a no-op.
UPDATE tasks SET done_slots = done_slots + 1 WHERE id = ? AND done_slots < total_slots. A race past the quota is rejected; we never pay for a slot the advertiser hasn't reserved.
Stars are integers. TON is REAL but always rounded at boundaries; we never accumulate fractional drift inside the platform.
Giveaway picker, jackpot winner picker, Lucky Spin all use secrets.SystemRandom. Mersenne Twister is deterministic from its seed and unsuitable for payouts.
Telegram Mini App initData is HMAC-verified and has a 24h freshness check on auth_date. A stolen initData blob cannot replay financial endpoints (/miniapp/api/jackpot/buy, /miniapp/api/staking/open, ...) past its expiry.