Architecture
How the smart contract, backend, and frontend work together to create a trustless, provably fair spin wheel game.
System Overview
Lucky Spin is a three-tier application: a Solidity smart contract for on-chain logic, a Node.js backend for seed management and blockchain interaction, and a React frontend with a 3D Three.js wheel for the user interface.
Component Roles
Frontend (React + Three.js)
The frontend handles all user-facing functionality:
- Wallet connection — Uses
useQtumWallethook to connect MetaMask via qtum-wallet-connector - Seed generation — Generates 32 random bytes via
crypto.getRandomValues()and hashes with keccak256 - ABI encoding — Encodes
placeSpin(bytes32, bytes32)calls using ethers.js AbiCoder - Transaction submission — Sends 1 QTUM to the SpinGame contract via the snap
- Polling — Polls backend for on-chain confirmation and spin resolution
- 3D visualization — Three.js wheel with bloom effects, particle bursts, and animated landing
- Recovery — Stores pending spin data in localStorage for page reload recovery
Backend (Node.js + Express)
The backend serves as the trusted house operator:
- House seed generation — Generates cryptographically random seeds and stores them in memory
- Contract monitoring — Polls the blockchain every 5 seconds for
SpinPlacedandSpinResolvedevents - Spin registration — When a
SpinPlacedevent is detected, links it to the stored house seed - Reveal coordination — When the player submits their seed, calls
revealAndResolve()on-chain - QTUM RPC — Wraps
sendtocontract,callcontract, andsearchlogsRPC calls
Smart Contract (SpinGame.sol)
The contract is the source of truth for all game logic:
- Spin placement — Accepts 1 QTUM, stores seed hashes, locks funds for max payout
- Seed verification — Verifies that revealed seeds match committed hashes
- Segment calculation — Computes
keccak256(houseSeed + playerSeed + spinId) % 8 - Automatic payouts — Sends prize amount directly to the player's address
- Refund mechanism — Players can claim a refund if their spin is not resolved within 1 hour
Provably Fair Scheme
The commit-reveal pattern is the foundation of trustless gaming. It ensures that neither party can influence the outcome after both have committed:
If the house chose the result after seeing the player's seed, it could cheat. If the player chose after seeing the house seed, they could cheat. By having both parties commit (hash) their seeds before revealing them, neither can manipulate the result.
Detailed Flow
House Seed Generation
Backend generates 32 random bytes (houseSeed), computes keccak256(houseSeed) = houseSeedHash, stores the seed, and returns the hash to the frontend.
Player Seed Generation
Frontend generates 32 random bytes (playerSeed) via crypto.getRandomValues(), computes keccak256(playerSeed) = playerSeedHash.
On-Chain Commit
Player calls placeSpin(playerSeedHash, houseSeedHash) with 1 QTUM. Both hashes are now permanently recorded on-chain — neither party can change their seed.
Reveal & Resolve
Player sends playerSeed to backend. Backend calls revealAndResolve(spinId, houseSeed, playerSeed). The contract verifies both seeds match their hashes, then computes the result:
segment = uint8(uint256(keccak256(houseSeed, playerSeed, spinId)) % 8)
payout = prizes[segment]
Data Flow
Security Measures
Reentrancy Guard
revealAndResolve and claimRefund use a nonReentrant modifier to prevent recursive calls during fund transfers.
Locked Funds
The contract tracks lockedFunds to ensure it always reserves enough to cover the maximum payout (2 QTUM) for each pending spin.
Owner-Only Reveal
Only the contract owner can call revealAndResolve(), preventing unauthorized resolution attempts.
Player Refunds
If a spin is not resolved within 1 hour, the player can call claimRefund() to recover their 1 QTUM.