The first rule of building anything where a player can win something of value: the device in their hand is not on your side. It can be decompiled, patched, run on a rooted phone and fed fabricated responses. Any outcome the client computes is an outcome the client can choose.
The client renders, the server decides
In our scratch and spin titles the app never determines a result. It asks the server to open a card; the server picks the outcome, writes it to an append-only log, applies it to the balance, and returns what to draw. The animation is theatre over a decision that has already been made and recorded.
Make the balance reconstructable
A balance stored as a single mutable number is a number you cannot defend. Every change should be an entry - what happened, when, why, and what the balance was afterwards. When support asks why a player is short, the answer is a query rather than a guess.
- Every credit and debit is an event with a reason code
- The current balance is derived, and reconciled against the log on a schedule
- Payouts reference the exact events they settle
Assume the request is hostile
Rate-limit per account and per device, not just per IP. Make each open idempotent so a replayed request cannot double-credit. Sign anything you hand to the client and expect back. And log the requests you rejected - the pattern of failed attempts is usually the first sign of a new exploit.
Keep the economy out of the binary
Prize tables, drop rates and streak rules live in configuration the server reads, not constants compiled into the app. That means tuning does not need a release, and - just as importantly - it means the numbers are not sitting in a file anyone can extract from the APK.