A fully decentralized lottery system built on Ethereum, featuring provably fair number generation using Chainlink VRF, token staking mechanics, and automated gift distribution.
- Overview
- Features
- System Architecture
- Smart Contracts
- Getting Started
- Deployment
- Usage
- Security Features
- Testing
- Contributing
- License
This lottery system implements a decentralized gambling platform where users stake platform tokens to participate in lottery rounds. The system features:
- Provably Fair: Uses Chainlink VRF for verifiable random number generation
- Staking-Based: Users must stake tokens to participate, creating long-term engagement
- Automated Gifts: Regular gift distribution to active participants and creators
- Multi-Tier Winnings: 5-tier prize structure based on number matches
- Administrative Controls: Timelock-protected administrative functions
- 5-minute lottery rounds with automatic progression
- Players select 5 numbers from 1-49
- Multiple bet sizes supported (minimum 1 PTK)
- Provably fair random number generation via Chainlink VRF
- Multi-tier prize structure (2-5 matches)
- PlatformToken (PTK): ERC20 token with staking capabilities
- Minimum Stake: 10 PTK required for betting eligibility
- Staking Benefits: Higher staking weight increases gift eligibility
- Burn Mechanism: 5% of winnings burned for deflationary pressure
- Creator Gifts: Platform creator receives gifts each round
- User Gifts: Random selection of eligible users receive gifts
- Eligibility: Requires 3+ consecutive rounds of participation
- Cooldown: 24-hour cooldown between gifts per user
- Access Control: Role-based permissions for different functions
- Timelock: 24-hour timelock for critical parameter changes
- Emergency Controls: Pause/unpause functionality
- Reentrancy Protection: Full reentrancy guards on critical functions
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ PlatformToken │ │ LotteryGameCore │ │ LotteryGift │
│ │ │ │ │ │
│ • Staking │◄──►│ • Betting │◄──►│ • Distribution │
│ • Burning │ │ • Rounds │ │ • Selection │
│ • Rewards │ │ • Claiming │ │ • Cooldowns │
└─────────────────┘ └─────────────────┘ └─────────────────┘
▲ ▲ ▲
│ │ │
└───────────────────────┼───────────────────────┘
▼
┌─────────────────┐ ┌─────────────────┐
│ LotteryAdmin │ │ Chainlink VRF │
│ │ │ │
│ • Governance │ │ • RNG Service │
│ • Timelocks │ │ • Verification │
│ • Emergency │ │ • Callbacks │
└─────────────────┘ └─────────────────┘
| Contract | Description | Key Functions |
|---|---|---|
PlatformToken.sol |
ERC20 token with staking | stake(), unstake(), burnFrom() |
LotteryGameCore.sol |
Main lottery logic | placeBet(), claimWinnings(), endRound() |
LotteryGift.sol |
Gift distribution system | distributeGifts(), fundGiftReserve() |
LotteryAdmin.sol |
Administrative functions | setMaxPayoutPerRound(), pause() |
VRFConsumer.sol |
Chainlink VRF integration | _requestRandomWords(), fulfillRandomWords() |
- PlatformToken:
[Your existing address] - LotteryGameCore:
[Deploy using guide] - LotteryGift:
[Deploy using guide] - LotteryAdmin:
[Deploy using guide]
- PlatformToken:
[To be deployed] - LotteryGameCore:
[To be deployed] - LotteryGift:
[To be deployed] - LotteryAdmin:
[To be deployed]
- Foundry
- Node.js 16+ and npm/yarn
- Git
# Clone the repository
git clone https://github.com/agwara/crystalChain
cd crystalChain
# Install Foundry dependencies
forge install
Create a .env file:
# Network Configuration
SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_INFURA_KEY
# Deployment
PRIVATE_KEY=your_private_key_here
PLATFORM_TOKEN_ADDRESS=your_existing_token_address
# Chainlink VRF
VRF_SUBSCRIPTION_ID=your_subscription_id
# Verification
ETHERSCAN_API_KEY=your_etherscan_api_key# Deploy all contracts
forge script script/DeployLottery.s.sol \
--rpc-url $SEPOLIA_RPC_URL \
--broadcast \
--verify \
--etherscan-api-key $ETHERSCAN_API_KEY
# Set up VRF subscription
# Visit https://vrf.chain.link and add your Core contract as consumer
# Configure token permissions
cast send $PLATFORM_TOKEN_ADDRESS \
"setAuthorizedBurner(address,bool)" \
$CORE_CONTRACT_ADDRESS true \
--rpc-url $SEPOLIA_RPC_URL \
--private-key $PRIVATE_KEYFor detailed deployment instructions, see Deployment Guide.
-
Stake Tokens: Minimum 10 PTK required
platformToken.stake(10 * 10**18);
-
Place Bets: Select 5 unique numbers (1-49)
lotteryCore.placeBet([1,15,23,35,42], betAmount);
-
Claim Winnings: After round ends and numbers are drawn
lotteryCore.claimWinnings(roundId, betIndices);
-
Manage Settings: Update payout limits (with timelock)
adminContract.scheduleMaxPayoutChange(newAmount); // Wait 24 hours adminContract.setMaxPayoutPerRound(newAmount);
-
Emergency Controls: Pause system if needed
adminContract.pause(); -
Gift Distribution: Distribute gifts after each round
giftContract.distributeGifts(roundId);
| Matches | Payout Multiplier | Example (1 PTK bet) |
|---|---|---|
| 5/5 | 800x | 800 PTK |
| 4/5 | 80x | 80 PTK |
| 3/5 | 8x | 8 PTK |
| 2/5 | 2x | 2 PTK |
| 0-1/5 | 0x | 0 PTK |
All payouts subject to 5% house edge
- Role-based permissions for different contract functions
- Multi-signature support for critical operations
- Timelock protection for parameter changes
- Minimum stake requirements prevent spam
- Maximum bet limits prevent manipulation
- Burn mechanisms create deflationary pressure
- Reentrancy guards on all external calls
- Input validation for all user inputs
- Overflow protection using Solidity 0.8+
- Pausable contracts for emergency stops
- Chainlink VRF for verifiable randomness
- Request validation prevents manipulation
- Emergency fallback for VRF failures
# Run all tests
forge test
# Run with verbosity
forge test -vvv
# Run with gas reporting
forge test --gas-report# Generate coverage report
forge coverage
# Generate HTML coverage report
forge coverage --report lcov
genhtml lcov.info --output-directory coveragefunction placeBet(uint256[5] calldata numbers, uint256 amount) external
function claimWinnings(uint256 roundId, uint256[] calldata betIndices) external
function getCurrentRound() external view returns (Round memory)
function getUserStats(address user) external view returns (UserStats memory)function stake(uint256 amount) external
function unstake(uint256 amount) external
function getStakingWeight(address user) external view returns (uint256)
function isEligibleForBenefits(address user) external view returns (bool)function distributeGifts(uint256 roundId) external
function fundGiftReserve(uint256 amount) external
function getGiftReserveStatus() external view returns (uint256, uint256)For complete API documentation, see API Reference.
// Lottery Parameters
uint256 public constant ROUND_DURATION = 5 minutes;
uint256 public constant MIN_BET_AMOUNT = 1 * 10**18; // 1 PTK
uint256 public constant MAX_BET_PER_USER_PER_ROUND = 1000 * 10**18; // 1000 PTK
// Token Parameters
uint256 public constant MIN_STAKE_AMOUNT = 10 * 10**18; // 10 PTK
uint256 public constant MIN_STAKE_DURATION = 24 hours;
// Gift Parameters
uint256 public constant CONSECUTIVE_PLAY_REQUIREMENT = 3;
uint256 public constant GIFT_COOLDOWN = 24 hours;- Maximum payout per round
- Gift recipient count
- Gift amounts (creator and user)
- House edge percentage
- Round Statistics: Participation, total bets, prize pools
- User Engagement: Consecutive rounds, staking levels
- Economic Health: Token supply, burn rate, gift distribution
- Technical Performance: VRF response times, gas usage
event RoundStarted(uint256 indexed roundId, uint256 startTime, uint256 endTime);
event BetPlaced(uint256 indexed roundId, address indexed user, uint256[5] numbers, uint256 amount);
event NumbersDrawn(uint256 indexed roundId, uint256[5] winningNumbers);
event GiftDistributed(uint256 indexed roundId, address indexed recipient, uint256 amount, bool isCreator);-
VRF Not Responding
- Check subscription funding
- Verify consumer registration
- Check gas limits
-
Betting Fails
- Ensure minimum stake requirement met
- Check token allowance
- Verify round is active
-
Gift Distribution Fails
- Check gift reserve balance
- Verify eligible participants
- Ensure round numbers are drawn
# Pause the system
cast send $ADMIN_CONTRACT "pause()"
# Emergency withdraw funds
cast send $ADMIN_CONTRACT "emergencyWithdraw(uint256)" $AMOUNT
# Enable emergency unstaking
cast send $PLATFORM_TOKEN "toggleEmergencyWithdrawal(bool)" trueWe welcome contributions! Please see CONTRIBUTING.md for guidelines.
- Fork the repository
- Create a feature branch
- Make changes with tests
- Submit a pull request
- Follow Solidity style guide
- Include comprehensive tests
- Add documentation for new features
- Use semantic commit messages
This project is licensed under the MIT License - see the LICENSE file for details.
- OpenZeppelin for secure contract libraries
- Chainlink for VRF services
- Foundry for development framework
This software is provided "as is" without warranty. Users should conduct their own security audits before deploying to mainnet. Gambling may be subject to legal restrictions in your jurisdiction.