Technical breakdown of the architecture, authentication flow, and verifiable randomness behind **SCAI Lucky Loop—a coin-based daily lottery built during my Web3 internship at **EtherAuthority.
Over the past few months, I've been building SCAI Lucky Loop: a daily, coin-based lottery delivered as a Telegram Mini App (with a browser fallback), where players earn in-app coins, purchase ticket slots, and withdraw eligible winnings as LLT, an ERC-20 token deployed on SCAI Mainnet.
Rather than focusing on the user experience, this article explains the engineering decisions behind the application: authentication, backend architecture, commit–reveal randomness, and the trust model that makes the system verifiable.
🎥 Project Demo
Watch SCAI Lucky Loop in action:
Technology Stack & Deployment
The project is split into three independently deployable components.
| Component | Responsibility | Deployment |
|---|---|---|
| Frontend | React + Vite client, Telegram integration, wallet connection | Vercel |
| Backend | Express API, SQLite database, cron jobs, withdrawals | Render |
| Smart Contracts | LLT ERC-20 contract, Hardhat tests | SCAI Mainnet |
The frontend is built using React 18, TypeScript, and Vite, while the backend runs on Express.js with SQLite acting as the system of record for users, tickets, draws, referrals, and withdrawals.
Wallet connectivity is handled using Reown AppKit with the Ethers adapter, configured exclusively for SCAI Mainnet (Chain ID 34).
One important architectural decision is that ticket purchases are currently off-chain. Players spend in-app coins rather than sending blockchain transactions. Wallets are connected only when withdrawing LLT rewards. This separation significantly reduces gas costs and simplifies the user experience while still enabling blockchain payouts.
Authentication
The application supports two independent authentication mechanisms.
Telegram Mini App Authentication
The primary authentication flow is native to Telegram.
- Telegram launches the Mini App.
- The client receives
window.Telegram.WebApp.initData. - The frontend sends the signed payload to the backend.
- The backend verifies Telegram's signature using the bot token.
- If valid, the backend creates (or loads) the user and returns a JWT.
- Future requests authenticate using the JWT.
The important security boundary is the server-side verification of Telegram's signed payload. Simply copying the JSON payload is insufficient because the signature cannot be forged without Telegram's secret.
This prevents ordinary browser sessions from impersonating Telegram users.
Wallet-Only Authentication
To support users outside Telegram, I also implemented wallet-based login.
Instead of relying on Telegram identity:
- The backend generates a unique nonce.
- The wallet signs that nonce.
- The backend verifies the signature using
ethers.verifyMessage(). - A JWT session is issued.
Supporting this required modifying the database schema so telegram_id became nullable while ensuring every account contains at least one valid identity through database constraints.
Additionally, every authenticated request reloads the user directly from SQLite instead of trusting cached JWT information. This ensures administrative changes, bans, or balance updates take effect immediately.
Verifiable Randomness with Commit–Reveal
Lottery systems often ask users to trust that draws are random.
Lucky Loop instead uses a commit–reveal mechanism that allows anyone to independently verify every draw.
Before ticket sales close:
- The backend generates a cryptographically secure 32-byte random seed.
- The SHA-256 hash of that seed is published immediately.
- The seed itself remains secret.
Once ticket sales end:
- The original seed is revealed.
- SHA-256 is computed again.
- The first four bytes become an unsigned integer.
- That integer is reduced using modulo arithmetic against the number of tickets.
Because the hash was published beforehand, the backend cannot secretly generate multiple seeds after ticket sales close until it finds a favorable winner.
Either:
- the revealed seed matches the published hash,
or
- it doesn't.
There is no middle ground.
Public Verification
Lucky Loop exposes a public verification endpoint.
Given a draw date, anyone can independently verify:
- the published commitment hash,
- the revealed seed,
- the computed winning ticket,
- and the recorded winner.
No trust in the backend is required—the mathematics speaks for itself.
Handling Zero-Ticket Draws
One interesting production issue appeared during testing.
Originally, a draw record only existed if someone purchased a ticket.
On days with zero ticket sales:
- no draw record existed,
- the scheduled draw job executed,
- the cron process failed because there was nothing to draw.
The fix was straightforward.
When ticket sales close, the scheduler now creates an empty draw if one does not already exist.
This allows every scheduled draw to execute successfully—even if nobody played that day.
Admin System
Administrative privileges are determined through three independent mechanisms.
An account becomes an administrator if any of the following is true:
- Telegram ID exists inside
ADMIN_TELEGRAM_IDS - Wallet address exists inside
ADMIN_WALLET_ADDRESSES - Database column
is_adminis true
Environment variables provide the initial bootstrap administrator.
After that, existing administrators can promote or demote users directly from the admin panel without redeploying the backend.
This avoids the common problem of requiring infrastructure changes whenever administrator access changes.
LLT Withdrawals
Blockchain interaction occurs only during withdrawals.
The process is:
- Connect wallet.
- Switch to SCAI Mainnet.
- Submit withdrawal.
- Backend validates:
- minimum coin balance,
- referral requirements,
- withdrawal eligibility.
- Backend signs an LLT transaction using the server payout wallet.
- Transaction hash and explorer link are returned.
The payout wallet is the application's most sensitive secret.
It is stored securely using Render environment secrets and is never exposed to the frontend.
A critical distinction when building Vite applications is remembering that every environment variable beginning with VITE_ becomes public inside the browser bundle.
Private keys should never appear there.
Future Improvements
The next major milestone is replacing off-chain ticket purchases with native SCAI payments.
Doing so would move the entire lottery lifecycle onto the blockchain rather than only withdrawals.
Other planned improvements include:
- migrating from SQLite to PostgreSQL,
- replacing local rate limiting with Redis,
- supporting horizontal backend scaling,
- expanding transparency around draw verification.
Conclusion
Building SCAI Lucky Loop taught me considerably more than writing React components or Solidity contracts.
It required thinking carefully about authentication boundaries, backend trust, scheduled systems, wallet integration, cryptographic randomness, and operational edge cases that only appear after deployment.
The commit–reveal pattern ended up being one of the most valuable engineering techniques I implemented. It's relatively simple to build, easy for users to verify independently, and significantly increases trust in any application that depends on server-generated randomness.
If you're building a Telegram Mini App, a Web3 application, or any system where fairness matters, I highly recommend considering commit–reveal as part of your design.
Demo
🎥 https://vimeo.com/1215772223
SCAI Lucky Loop's smart contract has been audited by EtherAuthority. This project was built during my Web3 internship at EtherAuthority.
#React #TypeScript #ExpressJS #Solidity #Ethereum #Web3 #Blockchain #TelegramMiniApps #SmartContracts #Cryptography #OpenSource #SCAI #EtherAuthority
Top comments (0)