Introduction
Project Name: ChainLotto: Verifiable Decentralized Lottery
Concept: Traditional lotteries suffer from lack of transparency - players must trust that the operator is fair and that the winning numbers aren't manipulated. This project creates a completely transparent lottery system using blockchain technology, where every aspect of the lottery is publicly verifiable and the winning number is generated on-chain using cryptographic principles.
The Blockchain Solution: Instead of trusting a central authority to pick winning numbers, we use blockchain's immutability and transparency. Every ticket purchase is recorded on-chain, the winning number is generated using a verifiable random function (VRF) combining multiple on-chain data points, and all prize distributions are automatic and transparent. Anyone can verify the fairness of the entire process.
Features
- 🎲 Verifiable Randomness: Winning numbers are generated on-chain using cryptographic hashing
- 📝 Transparent Tickets: All ticket purchases are recorded and publicly viewable
- 💰 Automatic Prizes: Winners are automatically determined and can claim prizes
- 🔍 Verifiable History: Every lottery round can be audited by anyone
- ⏰ Scheduled Draws: Automatic draws at predetermined times
- 📊 Real-time Updates: Live display of ticket sales and jackpot amount
- ✅ Provably Fair: All randomness can be verified after the draw
- 🏆 Multiple Prize Tiers: Support for different winning combinations
Project File Structure
decentralized-lottery/ │ ├── index.php # Main application UI ├── style.css # Custom styling ├── script.js # Frontend JavaScript ├── api/ │ ├── buy_ticket.php # Purchase lottery tickets │ ├── draw_lottery.php # Trigger lottery draw (admin/auto) │ ├── get_lottery_status.php # Get current lottery info │ ├── get_history.php # Get past lottery results │ ├── verify_ticket.php # Verify if a ticket won │ └── claim_prize.php # Claim winnings ├── includes/ │ ├── config.php # Database configuration │ └── LotteryChain.php # Core blockchain logic for lottery ├── database/ │ └── schema.sql # Database schema ├── cron/ │ └── auto_draw.php # Automated draws via cron job └── README.md # Project documentation
Database Setup (MySQL)
1. Create a database named chainlotto_db.
2. Run the database/schema.sql script:
CREATE DATABASE IF NOT EXISTS chainlotto_db; USE chainlotto_db; -- Lottery rounds table CREATE TABLE lottery_rounds ( id INT AUTO_INCREMENT PRIMARY KEY, round_number INT NOT NULL UNIQUE, ticket_price DECIMAL(10, 2) NOT NULL DEFAULT 1.00, jackpot_amount DECIMAL(15, 2) NOT NULL DEFAULT 0, start_time BIGINT NOT NULL, end_time BIGINT NOT NULL, draw_time BIGINT NULL, winning_numbers VARCHAR(50) NULL, winning_hash VARCHAR(64) NULL, status ENUM('active', 'drawing', 'completed', 'cancelled') DEFAULT 'active', total_tickets INT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_status (status), INDEX idx_round (round_number) ); -- Lottery tickets table (blockchain) CREATE TABLE lottery_tickets ( id INT AUTO_INCREMENT PRIMARY KEY, block_index INT NOT NULL, round_number INT NOT NULL, ticket_number VARCHAR(20) NOT NULL, buyer_name VARCHAR(100) NOT NULL, buyer_email VARCHAR(255), numbers VARCHAR(50) NOT NULL, -- Format: "n1,n2,n3,n4,n5,n6" ticket_hash VARCHAR(64) NOT NULL UNIQUE, previous_hash VARCHAR(64) NOT NULL, timestamp BIGINT NOT NULL, nonce INT NOT NULL, is_winner BOOLEAN DEFAULT FALSE, prize_amount DECIMAL(15, 2) DEFAULT 0, claimed BOOLEAN DEFAULT FALSE, claimed_at BIGINT NULL, FOREIGN KEY (round_number) REFERENCES lottery_rounds(round_number), INDEX idx_round (round_number), INDEX idx_winner (is_winner), INDEX idx_ticket (ticket_number) ); -- Winners table CREATE TABLE lottery_winners ( id INT AUTO_INCREMENT PRIMARY KEY, round_number INT NOT NULL, ticket_id INT NOT NULL, ticket_number VARCHAR(20) NOT NULL, buyer_name VARCHAR(100) NOT NULL, numbers VARCHAR(50) NOT NULL, matched_numbers INT NOT NULL, prize_tier INT NOT NULL, prize_amount DECIMAL(15, 2) NOT NULL, claimed BOOLEAN DEFAULT FALSE, claimed_at BIGINT NULL, transaction_hash VARCHAR(64) NULL, FOREIGN KEY (round_number) REFERENCES lottery_rounds(round_number), FOREIGN KEY (ticket_id) REFERENCES lottery_tickets(id) ); -- Randomness seeds table (for verifiable randomness) CREATE TABLE randomness_seeds ( id INT AUTO_INCREMENT PRIMARY KEY, round_number INT NOT NULL, seed_type VARCHAR(20) NOT NULL, -- 'commit', 'reveal', 'final' seed_value VARCHAR(256) NOT NULL, timestamp BIGINT NOT NULL, block_height INT, FOREIGN KEY (round_number) REFERENCES lottery_rounds(round_number) ); -- Insert genesis block for tickets INSERT INTO lottery_tickets (block_index, round_number, ticket_number, buyer_name, numbers, ticket_hash, previous_hash, timestamp, nonce) VALUES (0, 0, 'GENESIS', 'SYSTEM', '0,0,0,0,0,0', SHA2(CONCAT('0', '0', 'GENESIS', 'SYSTEM', '0,0,0,0,0,0', UNIX_TIMESTAMP(), '0', '0'), 256), '0', UNIX_TIMESTAMP(), 0); -- Insert first lottery round INSERT INTO lottery_rounds (round_number, ticket_price, jackpot_amount, start_time, end_time, status) VALUES (1, 1.00, 100.00, UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + 86400, 'active'); Code Implementation
1. includes/config.php (Database Configuration)
<?php define('DB_HOST', 'localhost'); define('DB_USER', 'root'); define('DB_PASS', ''); define('DB_NAME', 'chainlotto_db'); function getDBConnection() { $conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME); if ($conn->connect_error) { die(json_encode(['success' => false, 'message' => 'Database connection failed: ' . $conn->connect_error])); } return $conn; } // Lottery configuration define('TICKET_PRICE', 1.00); define('MAX_NUMBER', 49); define('NUMBERS_PER_TICKET', 6); define('JACKPOT_MULTIPLIER', 0.5); // 50% of ticket sales go to jackpot define('DRAW_INTERVAL', 86400); // 24 hours in seconds date_default_timezone_set('UTC'); ?> 2. includes/LotteryChain.php (Core Blockchain Logic)
<?php require_once 'config.php'; class LotteryChain { private $conn; private $difficulty = 2; // Proof of work difficulty for tickets public function __construct() { $this->conn = getDBConnection(); } /** * Calculate hash for a ticket block */ public static function calculateHash($index, $roundNumber, $ticketNumber, $buyerName, $numbers, $timestamp, $previousHash, $nonce) { $data = $index . $roundNumber . $ticketNumber . $buyerName . $numbers . $timestamp . $previousHash . $nonce; return hash('sha256', $data); } /** * Get the last ticket block */ public function getLastBlock() { $sql = "SELECT * FROM lottery_tickets ORDER BY block_index DESC LIMIT 1"; $result = $this->conn->query($sql); if ($result && $result->num_rows > 0) { return $result->fetch_assoc(); } return null; } /** * Get current active lottery round */ public function getCurrentRound() { $sql = "SELECT * FROM lottery_rounds WHERE status = 'active' ORDER BY round_number DESC LIMIT 1"; $result = $this->conn->query($sql); if ($result && $result->num_rows > 0) { return $result->fetch_assoc(); } return null; } /** * Generate unique ticket number */ private function generateTicketNumber() { $prefix = 'TKT'; $timestamp = time(); $random = rand(1000, 9999); return $prefix . $timestamp . $random; } /** * Validate lottery numbers */ public function validateNumbers($numbers) { $numArray = explode(',', $numbers); if (count($numArray) != NUMBERS_PER_TICKET) { return false; } foreach ($numArray as $num) { if (!is_numeric($num) || $num < 1 || $num > MAX_NUMBER) { return false; } } // Check for duplicates if (count(array_unique($numArray)) != count($numArray)) { return false; } return true; } /** * Buy a lottery ticket */ public function buyTicket($buyerName, $buyerEmail, $numbers) { try { // Validate numbers if (!$this->validateNumbers($numbers)) { return ['success' => false, 'message' => 'Invalid numbers. Please select 6 unique numbers between 1 and ' . MAX_NUMBER]; } // Get current round $currentRound = $this->getCurrentRound(); if (!$currentRound) { return ['success' => false, 'message' => 'No active lottery round']; } // Check if round is still active if (time() > $currentRound['end_time']) { return ['success' => false, 'message' => 'Lottery round has ended']; } // Get last block $lastBlock = $this->getLastBlock(); if (!$lastBlock) { return ['success' => false, 'message' => 'Ticket chain corrupted']; } $newIndex = $lastBlock['block_index'] + 1; $timestamp = time(); $previousHash = $lastBlock['hash']; $ticketNumber = $this->generateTicketNumber(); // Sort numbers for consistency $numArray = explode(',', $numbers); sort($numArray); $sortedNumbers = implode(',', $numArray); // Proof of Work $nonce = 0; $prefix = str_repeat('0', $this->difficulty); do { $hash = $this->calculateHash($newIndex, $currentRound['round_number'], $ticketNumber, $buyerName, $sortedNumbers, $timestamp, $previousHash, $nonce); $nonce++; if ($nonce > 1000000) { throw new Exception("Proof of work timeout"); } } while (substr($hash, 0, $this->difficulty) !== $prefix); // Start transaction $this->conn->begin_transaction(); // Insert ticket $sql = "INSERT INTO lottery_tickets (block_index, round_number, ticket_number, buyer_name, buyer_email, numbers, ticket_hash, previous_hash, timestamp, nonce) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; $stmt = $this->conn->prepare($sql); $stmt->bind_param("iissssssii", $newIndex, $currentRound['round_number'], $ticketNumber, $buyerName, $buyerEmail, $sortedNumbers, $hash, $previousHash, $timestamp, $nonce - 1 ); if (!$stmt->execute()) { throw new Exception("Failed to insert ticket: " . $stmt->error); } // Update round statistics $newJackpot = $currentRound['jackpot_amount'] + (TICKET_PRICE * JACKPOT_MULTIPLIER); $sql2 = "UPDATE lottery_rounds SET total_tickets = total_tickets + 1, jackpot_amount = ? WHERE round_number = ?"; $stmt2 = $this->conn->prepare($sql2); $stmt2->bind_param("di", $newJackpot, $currentRound['round_number']); if (!$stmt2->execute()) { throw new Exception("Failed to update round"); } $this->conn->commit(); // Get ticket ID $ticketId = $this->conn->insert_id; return [ 'success' => true, 'message' => 'Ticket purchased successfully!', 'ticket' => [ 'ticket_number' => $ticketNumber, 'numbers' => $sortedNumbers, 'round' => $currentRound['round_number'], 'block_index' => $newIndex, 'hash' => $hash, 'timestamp' => date('Y-m-d H:i:s', $timestamp) ] ]; } catch (Exception $e) { $this->conn->rollback(); return ['success' => false, 'message' => 'Error purchasing ticket: ' . $e->getMessage()]; } } /** * Generate winning numbers using verifiable randomness */ public function generateWinningNumbers($roundNumber) { try { // Get round info $sql = "SELECT * FROM lottery_rounds WHERE round_number = ?"; $stmt = $this->conn->prepare($sql); $stmt->bind_param("i", $roundNumber); $stmt->execute(); $result = $stmt->get_result(); $round = $result->fetch_assoc(); if (!$round) { throw new Exception("Round not found"); } if ($round['status'] != 'active') { throw new Exception("Round is not active"); } // Get all tickets for this round $sql = "SELECT * FROM lottery_tickets WHERE round_number = ? AND ticket_number != 'GENESIS'"; $stmt = $this->conn->prepare($sql); $stmt->bind_param("i", $roundNumber); $stmt->execute(); $tickets = $stmt->get_result(); // Create randomness seed $seedData = ''; while ($ticket = $tickets->fetch_assoc()) { $seedData .= $ticket['ticket_hash'] . $ticket['timestamp']; } // Add round data and current time $seedData .= $round['round_number'] . $round['start_time'] . time(); // Generate random numbers using hash $hash = hash('sha256', $seedData); // Generate 6 unique winning numbers $winningNumbers = []; $hashParts = str_split($hash, 8); foreach ($hashParts as $part) { if (count($winningNumbers) >= NUMBERS_PER_TICKET) break; // Convert hex to decimal and map to 1-MAX_NUMBER $num = hexdec(substr($part, 0, 4)) % MAX_NUMBER + 1; if (!in_array($num, $winningNumbers)) { $winningNumbers[] = $num; } } // If we don't have enough unique numbers, fill remaining while (count($winningNumbers) < NUMBERS_PER_TICKET) { $num = rand(1, MAX_NUMBER); if (!in_array($num, $winningNumbers)) { $winningNumbers[] = $num; } } sort($winningNumbers); $winningNumbersStr = implode(',', $winningNumbers); // Store randomness seed $sql = "INSERT INTO randomness_seeds (round_number, seed_type, seed_value, timestamp) VALUES (?, 'final', ?, ?)"; $stmt = $this->conn->prepare($sql); $stmt->bind_param("isi", $roundNumber, $hash, time()); $stmt->execute(); return [ 'success' => true, 'winning_numbers' => $winningNumbersStr, 'seed_hash' => $hash ]; } catch (Exception $e) { return ['success' => false, 'message' => $e->getMessage()]; } } /** * Determine winners for a round */ public function determineWinners($roundNumber, $winningNumbers) { try { // Get all tickets for this round $sql = "SELECT * FROM lottery_tickets WHERE round_number = ? AND ticket_number != 'GENESIS'"; $stmt = $this->conn->prepare($sql); $stmt->bind_param("i", $roundNumber); $stmt->execute(); $result = $stmt->get_result(); $winningArray = explode(',', $winningNumbers); $winners = []; $prizePool = 0; // Calculate prize pool (80% of jackpot) $sql = "SELECT jackpot_amount FROM lottery_rounds WHERE round_number = ?"; $stmt = $this->conn->prepare($sql); $stmt->bind_param("i", $roundNumber); $stmt->execute(); $round = $stmt->get_result()->fetch_assoc(); $prizePool = $round['jackpot_amount'] * 0.8; // Prize tiers $tiers = [ 6 => 0.5, // 50% for jackpot (6 numbers) 5 => 0.2, // 20% for 5 numbers 4 => 0.15, // 15% for 4 numbers 3 => 0.1, // 10% for 3 numbers 2 => 0.05 // 5% for 2 numbers ]; // Count matches per tier $matchCounts = [6 => 0, 5 => 0, 4 => 0, 3 => 0, 2 => 0]; $ticketWinners = []; $this->conn->begin_transaction(); while ($ticket = $result->fetch_assoc()) { $ticketNumbers = explode(',', $ticket['numbers']); $matches = array_intersect($winningArray, $ticketNumbers); $matchCount = count($matches); if ($matchCount >= 2) { $matchCounts[$matchCount]++; $ticketWinners[] = [ 'ticket' => $ticket, 'matches' => $matchCount ]; // Mark ticket as winner $sql = "UPDATE lottery_tickets SET is_winner = TRUE WHERE id = ?"; $stmt = $this->conn->prepare($sql); $stmt->bind_param("i", $ticket['id']); $stmt->execute(); } } // Calculate and distribute prizes foreach ($ticketWinners as $winner) { $matches = $winner['matches']; if (isset($tiers[$matches]) && $matchCounts[$matches] > 0) { $prizeAmount = ($prizePool * $tiers[$matches]) / $matchCounts[$matches]; // Insert winner record $sql = "INSERT INTO lottery_winners (round_number, ticket_id, ticket_number, buyer_name, numbers, matched_numbers, prize_tier, prize_amount) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"; $stmt = $this->conn->prepare($sql); $stmt->bind_param("iisssiid", $roundNumber, $winner['ticket']['id'], $winner['ticket']['ticket_number'], $winner['ticket']['buyer_name'], $winner['ticket']['numbers'], $matches, $matches, $prizeAmount ); $stmt->execute(); // Update ticket with prize amount $sql = "UPDATE lottery_tickets SET prize_amount = ? WHERE id = ?"; $stmt = $this->conn->prepare($sql); $stmt->bind_param("di", $prizeAmount, $winner['ticket']['id']); $stmt->execute(); } } // Update round status $sql = "UPDATE lottery_rounds SET status = 'completed', winning_numbers = ?, draw_time = ? WHERE round_number = ?"; $stmt = $this->conn->prepare($sql); $stmt->bind_param("sii", $winningNumbers, time(), $roundNumber); $stmt->execute(); // Create next round $nextRound = $roundNumber + 1; $startTime = time(); $endTime = $startTime + DRAW_INTERVAL; $initialJackpot = 100.00; // Starting jackpot $sql = "INSERT INTO lottery_rounds (round_number, ticket_price, jackpot_amount, start_time, end_time, status) VALUES (?, ?, ?, ?, ?, 'active')"; $stmt = $this->conn->prepare($sql); $stmt->bind_param("iddii", $nextRound, TICKET_PRICE, $initialJackpot, $startTime, $endTime); $stmt->execute(); $this->conn->commit(); return [ 'success' => true, 'message' => 'Winners determined successfully', 'winning_numbers' => $winningNumbers, 'total_winners' => count($ticketWinners), 'prize_pool' => $prizePool ]; } catch (Exception $e) { $this->conn->rollback(); return ['success' => false, 'message' => $e->getMessage()]; } } /** * Draw lottery (complete process) */ public function drawLottery($roundNumber = null) { try { if (!$roundNumber) { $current = $this->getCurrentRound(); if ($current) { $roundNumber = $current['round_number']; } else { throw new Exception("No active round found"); } } // Generate winning numbers $numbersResult = $this->generateWinningNumbers($roundNumber); if (!$numbersResult['success']) { throw new Exception($numbersResult['message']); } // Determine winners $winnersResult = $this->determineWinners($roundNumber, $numbersResult['winning_numbers']); return $winnersResult; } catch (Exception $e) { return ['success' => false, 'message' => $e->getMessage()]; } } /** * Verify if a ticket won */ public function verifyTicket($ticketNumber) { $sql = "SELECT t.*, r.winning_numbers, r.round_number FROM lottery_tickets t JOIN lottery_rounds r ON t.round_number = r.round_number WHERE t.ticket_number = ?"; $stmt = $this->conn->prepare($sql); $stmt->bind_param("s", $ticketNumber); $stmt->execute(); $result = $stmt->get_result(); if ($result && $result->num_rows > 0) { $ticket = $result->fetch_assoc(); if ($ticket['winning_numbers']) { $winning = explode(',', $ticket['winning_numbers']); $player = explode(',', $ticket['numbers']); $matches = array_intersect($winning, $player); return [ 'success' => true, 'ticket' => $ticket, 'winning_numbers' => $ticket['winning_numbers'], 'matches' => count($matches), 'is_winner' => $ticket['is_winner'], 'prize_amount' => $ticket['prize_amount'], 'claimed' => $ticket['claimed'] ]; } else { return [ 'success' => true, 'ticket' => $ticket, 'message' => 'Draw not completed yet', 'is_winner' => false ]; } } return ['success' => false, 'message' => 'Ticket not found']; } /** * Claim prize */ public function claimPrize($ticketNumber, $claimantName) { try { $sql = "SELECT * FROM lottery_tickets WHERE ticket_number = ? AND is_winner = TRUE AND claimed = FALSE"; $stmt = $this->conn->prepare($sql); $stmt->bind_param("s", $ticketNumber); $stmt->execute(); $result = $stmt->get_result(); if ($result && $result->num_rows > 0) { $ticket = $result->fetch_assoc(); $this->conn->begin_transaction(); // Update ticket $sql = "UPDATE lottery_tickets SET claimed = TRUE, claimed_at = ? WHERE id = ?"; $stmt = $this->conn->prepare($sql); $stmt->bind_param("ii", time(), $ticket['id']); $stmt->execute(); // Update winners table $sql = "UPDATE lottery_winners SET claimed = TRUE, claimed_at = ? WHERE ticket_id = ?"; $stmt = $this->conn->prepare($sql); $stmt->bind_param("ii", time(), $ticket['id']); $stmt->execute(); $this->conn->commit(); return [ 'success' => true, 'message' => 'Prize claimed successfully!', 'amount' => $ticket['prize_amount'] ]; } return ['success' => false, 'message' => 'No unclaimed winning ticket found']; } catch (Exception $e) { $this->conn->rollback(); return ['success' => false, 'message' => $e->getMessage()]; } } /** * Get lottery history */ public function getHistory($limit = 10) { $sql = "SELECT * FROM lottery_rounds WHERE status = 'completed' ORDER BY round_number DESC LIMIT ?"; $stmt = $this->conn->prepare($sql); $stmt->bind_param("i", $limit); $stmt->execute(); $result = $stmt->get_result(); $history = []; while ($row = $result->fetch_assoc()) { // Get winners for this round $sql2 = "SELECT COUNT(*) as winner_count, SUM(prize_amount) as total_prizes FROM lottery_winners WHERE round_number = ?"; $stmt2 = $this->conn->prepare($sql2); $stmt2->bind_param("i", $row['round_number']); $stmt2->execute(); $stats = $stmt2->get_result()->fetch_assoc(); $row['winner_count'] = $stats['winner_count'] ?? 0; $row['total_prizes'] = $stats['total_prizes'] ?? 0; $row['formatted_date'] = date('Y-m-d H:i:s', $row['draw_time']); $history[] = $row; } return $history; } /** * Get lottery status */ public function getStatus() { $current = $this->getCurrentRound(); if ($current) { $timeLeft = $current['end_time'] - time(); // Get recent tickets $sql = "SELECT * FROM lottery_tickets WHERE round_number = ? AND ticket_number != 'GENESIS' ORDER BY block_index DESC LIMIT 10"; $stmt = $this->conn->prepare($sql); $stmt->bind_param("i", $current['round_number']); $stmt->execute(); $tickets = $stmt->get_result(); $recentTickets = []; while ($ticket = $tickets->fetch_assoc()) { $recentTickets[] = [ 'ticket_number' => $ticket['ticket_number'], 'buyer_name' => substr($ticket['buyer_name'], 0, 3) . '***', 'time' => date('H:i:s', $ticket['timestamp']) ]; } return [ 'success' => true, 'active' => true, 'round' => $current['round_number'], 'jackpot' => $current['jackpot_amount'], 'tickets_sold' => $current['total_tickets'], 'time_remaining' => $timeLeft, 'end_time' => date('Y-m-d H:i:s', $current['end_time']), 'recent_tickets' => $recentTickets ]; } else { return [ 'success' => true, 'active' => false, 'message' => 'No active lottery round' ]; } } public function __destruct() { $this->conn->close(); } } ?> 3. api/buy_ticket.php (Purchase Ticket Endpoint)
<?php header('Content-Type: application/json'); header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: POST'); header('Access-Control-Allow-Headers: Content-Type'); require_once '../includes/LotteryChain.php'; if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); echo json_encode(['success' => false, 'message' => 'Method not allowed']); exit; } $input = json_decode(file_get_contents('php://input'), true); if (!$input) { $buyerName = $_POST['buyer_name'] ?? ''; $buyerEmail = $_POST['buyer_email'] ?? ''; $numbers = $_POST['numbers'] ?? ''; } else { $buyerName = $input['buyer_name'] ?? ''; $buyerEmail = $input['buyer_email'] ?? ''; $numbers = $input['numbers'] ?? ''; } if (empty($buyerName) || empty($numbers)) { echo json_encode(['success' => false, 'message' => 'Name and numbers are required']); exit; } $lottery = new LotteryChain(); $result = $lottery->buyTicket($buyerName, $buyerEmail, $numbers); echo json_encode($result); ?> 4. api/draw_lottery.php (Trigger Lottery Draw)
<?php header('Content-Type: application/json'); header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: POST'); header('Access-Control-Allow-Headers: Content-Type'); require_once '../includes/LotteryChain.php'; // Simple authentication for admin actions $adminKey = $_POST['admin_key'] ?? ''; if ($adminKey !== 'CHANGEME_SECRET_KEY') { // Change this in production http_response_code(403); echo json_encode(['success' => false, 'message' => 'Unauthorized']); exit; } $roundNumber = $_POST['round_number'] ?? null; $lottery = new LotteryChain(); $result = $lottery->drawLottery($roundNumber); echo json_encode($result); ?> 5. api/get_lottery_status.php (Get Current Status)
<?php header('Content-Type: application/json'); header('Access-Control-Allow-Origin: *'); require_once '../includes/LotteryChain.php'; $lottery = new LotteryChain(); $status = $lottery->getStatus(); echo json_encode($status); ?> 6. api/get_history.php (Get Lottery History)
<?php header('Content-Type: application/json'); header('Access-Control-Allow-Origin: *'); require_once '../includes/LotteryChain.php'; $limit = $_GET['limit'] ?? 10; $lottery = new LotteryChain(); $history = $lottery->getHistory($limit); echo json_encode([ 'success' => true, 'history' => $history ]); ?> 7. api/verify_ticket.php (Verify Ticket)
<?php header('Content-Type: application/json'); header('Access-Control-Allow-Origin: *'); require_once '../includes/LotteryChain.php'; $ticketNumber = $_GET['ticket'] ?? ''; if (empty($ticketNumber)) { echo json_encode(['success' => false, 'message' => 'Ticket number required']); exit; } $lottery = new LotteryChain(); $result = $lottery->verifyTicket($ticketNumber); echo json_encode($result); ?> 8. api/claim_prize.php (Claim Winnings)
<?php header('Content-Type: application/json'); header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: POST'); header('Access-Control-Allow-Headers: Content-Type'); require_once '../includes/LotteryChain.php'; if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); echo json_encode(['success' => false, 'message' => 'Method not allowed']); exit; } $input = json_decode(file_get_contents('php://input'), true); if (!$input) { $ticketNumber = $_POST['ticket_number'] ?? ''; $claimantName = $_POST['claimant_name'] ?? ''; } else { $ticketNumber = $input['ticket_number'] ?? ''; $claimantName = $input['claimant_name'] ?? ''; } if (empty($ticketNumber) || empty($claimantName)) { echo json_encode(['success' => false, 'message' => 'Ticket number and claimant name required']); exit; } $lottery = new LotteryChain(); $result = $lottery->claimPrize($ticketNumber, $claimantName); echo json_encode($result); ?> 9. cron/auto_draw.php (Automated Draws)
<?php // This file should be run by cron job every hour // Example cron: 0 * * * * php /path/to/cron/auto_draw.php require_once '../includes/LotteryChain.php'; $lottery = new LotteryChain(); $status = $lottery->getStatus(); if ($status['active'] && $status['time_remaining'] <= 0) { // Time's up, draw the lottery $result = $lottery->drawLottery($status['round']); // Log the result $logFile = '../logs/lottery_draw.log'; $logEntry = date('Y-m-d H:i:s') . " - Round {$status['round']}: " . json_encode($result) . PHP_EOL; file_put_contents($logFile, $logEntry, FILE_APPEND); echo "Lottery draw completed for round {$status['round']}\n"; } else { $timeLeft = $status['time_remaining'] ?? 0; echo "No draw needed. Next draw in {$timeLeft} seconds\n"; } ?> 10. index.php (Main Application UI)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>ChainLotto | Decentralized Lottery</title> <script src="https://cdn.tailwindcss.com"></script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> <style> @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;600;700;800;900&display=swap'); body { font-family: 'Orbitron', sans-serif; background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%); min-height: 100vh; } .number-ball { width: 50px; height: 50px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 1.2rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); transition: all 0.3s ease; } .number-ball:hover { transform: scale(1.1); box-shadow: 0 0 20px rgba(102, 126, 234, 0.8); } .number-ball.selected { background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); box-shadow: 0 0 30px rgba(245, 87, 108, 0.8); } .jackpot-glow { animation: jackpotPulse 2s ease-in-out infinite; } @keyframes jackpotPulse { 0%, 100% { text-shadow: 0 0 10px #ffd700, 0 0 20px #ffd700, 0 0 30px #ffd700; } 50% { text-shadow: 0 0 20px #ffd700, 0 0 40px #ffd700, 0 0 60px #ffd700; } } .ticket-card { background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.2); transition: all 0.3s ease; } .ticket-card:hover { transform: translateY(-5px); border-color: #667eea; } .countdown { font-size: 2.5rem; font-weight: 800; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } .blockchain-badge { background: rgba(0, 0, 0, 0.5); border-left: 4px solid #667eea; } .winner-announcement { animation: slideIn 0.5s ease-out; } @keyframes slideIn { from { transform: translateY(-100%); opacity: 0; } to { transform: translateY(0); opacity: 1; } } </style> </head> <body class="text-white"> <div class="max-w-7xl mx-auto px-4 py-8"> <!-- Header --> <div class="text-center mb-8"> <div class="flex items-center justify-center mb-4"> <i class="fas fa-dice-d6 text-6xl text-yellow-400 mr-4"></i> <h1 class="text-6xl font-black bg-gradient-to-r from-yellow-400 via-purple-400 to-pink-400 bg-clip-text text-transparent"> ChainLotto </h1> </div> <p class="text-xl text-gray-300 mb-2">Decentralized • Transparent • Provably Fair</p> <p class="text-gray-400 max-w-2xl mx-auto"> Every ticket is recorded on the blockchain. Winning numbers are generated on-chain using verifiable randomness. No trust required. </p> </div> <!-- Main Jackpot Display --> <div class="text-center mb-12"> <div class="inline-block p-8 rounded-3xl bg-gradient-to-r from-purple-900/50 to-indigo-900/50 backdrop-blur-sm border border-purple-500/30"> <p class="text-sm text-gray-400 mb-2">Current Jackpot</p> <div class="text-7xl font-black text-yellow-400 jackpot-glow mb-2"> $<span id="jackpotAmount">0</span> </div> <div class="flex justify-center items-center space-x-4 text-sm"> <span class="bg-purple-600/30 px-3 py-1 rounded-full"> <i class="fas fa-ticket-alt mr-1"></i> <span id="ticketsSold">0</span> tickets sold </span> <span class="bg-indigo-600/30 px-3 py-1 rounded-full"> <i class="fas fa-clock mr-1"></i> <span id="timeRemaining">00:00:00</span> </span> </div> </div> </div> <div class="grid grid-cols-1 lg:grid-cols-3 gap-8"> <!-- Left Column: Number Selection --> <div class="lg:col-span-2"> <div class="bg-white/10 backdrop-blur-lg rounded-2xl p-6 border border-white/20"> <h2 class="text-2xl font-bold mb-6 flex items-center"> <i class="fas fa-dice-d20 text-purple-400 mr-3"></i> Pick Your Numbers </h2> <div class="grid grid-cols-7 gap-3 mb-6"> <?php for ($i = 1; $i <= 49; $i++): ?> <div class="number-ball cursor-pointer" onclick="toggleNumber(<?php echo $i; ?>)"> <?php echo $i; ?> </div> <?php endfor; ?> </div> <div class="flex space-x-4 mb-6"> <button onclick="randomNumbers()" class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-lg transition flex items-center"> <i class="fas fa-random mr-2"></i> Lucky Dip </button> <button onclick="clearNumbers()" class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-lg transition flex items-center"> <i class="fas fa-undo mr-2"></i> Clear </button> </div> <div class="bg-black/30 rounded-lg p-4 mb-4"> <p class="text-sm text-gray-400 mb-2">Your selected numbers:</p> <div id="selectedNumbers" class="flex space-x-2"> <span class="text-gray-500">None selected</span> </div> </div> <!-- Purchase Form --> <div class="grid grid-cols-2 gap-4"> <div> <label class="block text-sm font-medium text-gray-300 mb-2">Your Name</label> <input type="text" id="buyerName" placeholder="Enter your name" class="w-full bg-black/30 border border-gray-600 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-purple-500"> </div> <div> <label class="block text-sm font-medium text-gray-300 mb-2">Email (for notifications)</label> <input type="email" id="buyerEmail" placeholder="optional" class="w-full bg-black/30 border border-gray-600 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-purple-500"> </div> </div> <button onclick="buyTicket()" class="w-full mt-6 bg-gradient-to-r from-green-400 to-blue-500 text-white font-bold py-4 px-6 rounded-xl text-xl hover:opacity-90 transition transform hover:scale-105"> <i class="fas fa-ticket-alt mr-2"></i> Buy Ticket - $1.00 </button> </div> <!-- Recent Winners --> <div class="mt-8 bg-white/10 backdrop-blur-lg rounded-2xl p-6 border border-white/20"> <h2 class="text-xl font-bold mb-4 flex items-center"> <i class="fas fa-trophy text-yellow-400 mr-2"></i> Recent Winners </h2> <div id="recentWinners" class="space-y-3"> <!-- Winners will be loaded here --> <div class="text-center text-gray-500 py-4"> <i class="fas fa-spinner fa-spin mr-2"></i> Loading winners... </div> </div> </div> </div> <!-- Right Column: Lottery Info & Tickets --> <div class="lg:col-span-1"> <!-- Current Round Info --> <div class="bg-white/10 backdrop-blur-lg rounded-2xl p-6 border border-white/20 mb-6"> <h2 class="text-xl font-bold mb-4 flex items-center"> <i class="fas fa-info-circle text-blue-400 mr-2"></i> Round <span id="roundNumber">1</span> </h2> <div class="space-y-4"> <div> <p class="text-sm text-gray-400">Draw Time</p> <p id="drawTime" class="text-lg font-semibold">Loading...</p> </div> <div> <p class="text-sm text-gray-400">Time Remaining</p> <div class="countdown" id="countdown">00:00:00</div> </div> <div class="blockchain-badge p-3 rounded-lg"> <p class="text-xs text-gray-400 mb-1">Blockchain Status</p> <p class="text-sm"> <i class="fas fa-circle text-green-400 text-xs mr-2"></i> Active & Valid </p> </div> </div> </div> <!-- Recent Tickets --> <div class="bg-white/10 backdrop-blur-lg rounded-2xl p-6 border border-white/20"> <h2 class="text-xl font-bold mb-4 flex items-center"> <i class="fas fa-history text-purple-400 mr-2"></i> Recent Tickets </h2> <div id="recentTickets" class="space-y-3 max-h-96 overflow-y-auto pr-2"> <!-- Tickets will be loaded here --> <div class="text-center text-gray-500 py-4"> <i class="fas fa-spinner fa-spin mr-2"></i> Loading tickets... </div> </div> </div> <!-- Verify Ticket --> <div class="mt-6 bg-white/10 backdrop-blur-lg rounded-2xl p-6 border border-white/20"> <h2 class="text-xl font-bold mb-4 flex items-center"> <i class="fas fa-search text-green-400 mr-2"></i> Verify Ticket </h2> <div class="flex space-x-2"> <input type="text" id="verifyTicket" placeholder="Enter ticket number" class="flex-1 bg-black/30 border border-gray-600 rounded-lg px-3 py-2 text-sm text-white focus:outline-none focus:ring-2 focus:ring-green-500"> <button onclick="verifyTicket()" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg transition"> <i class="fas fa-check"></i> </button> </div> <div id="verificationResult" class="mt-3 text-sm hidden"></div> </div> </div> </div> <!-- Past Results --> <div class="mt-12 bg-white/10 backdrop-blur-lg rounded-2xl p-6 border border-white/20"> <h2 class="text-2xl font-bold mb-6 flex items-center"> <i class="fas fa-history text-purple-400 mr-3"></i> Past Lottery Results </h2> <div id="pastResults" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> <!-- Results will be loaded here --> <div class="text-center text-gray-500 py-8 col-span-3"> <i class="fas fa-spinner fa-spin mr-2"></i> Loading history... </div> </div> </div> </div> <!-- Winner Announcement Modal --> <div id="winnerModal" class="fixed inset-0 bg-black bg-opacity-75 hidden items-center justify-center z-50"> <div class="bg-gradient-to-r from-purple-900 to-indigo-900 rounded-2xl p-8 max-w-md text-center transform scale-0 transition-transform duration-300"> <i class="fas fa-trophy text-6xl text-yellow-400 mb-4"></i> <h2 class="text-3xl font-bold mb-2">Congratulations!</h2> <p id="winnerMessage" class="text-xl mb-4"></p> <button onclick="closeWinnerModal()" class="bg-white text-purple-900 px-6 py-2 rounded-lg font-bold hover:bg-gray-100 transition"> Claim Prize </button> </div> </div> <script src="script.js"></script> </body> </html> 11. script.js (Frontend JavaScript)
// script.js let selectedNumbers = []; let countdownInterval; let currentRound = null; // Load data on page load document.addEventListener('DOMContentLoaded', function() { loadLotteryStatus(); loadPastResults(); // Start countdown countdownInterval = setInterval(updateCountdown, 1000); // Refresh status every 30 seconds setInterval(loadLotteryStatus, 30000); }); // Toggle number selection function toggleNumber(num) { const index = selectedNumbers.indexOf(num); if (index === -1) { if (selectedNumbers.length >= 6) { showNotification('You can only select 6 numbers', 'error'); return; } selectedNumbers.push(num); } else { selectedNumbers.splice(index, 1); } // Update UI updateNumberDisplay(); updateSelectedNumbersDisplay(); } // Update number ball display function updateNumberDisplay() { document.querySelectorAll('.number-ball').forEach(ball => { const num = parseInt(ball.textContent); if (selectedNumbers.includes(num)) { ball.classList.add('selected'); } else { ball.classList.remove('selected'); } }); } // Update selected numbers display function updateSelectedNumbersDisplay() { const container = document.getElementById('selectedNumbers'); if (selectedNumbers.length === 0) { container.innerHTML = '<span class="text-gray-500">None selected</span>'; return; } let html = ''; selectedNumbers.sort((a, b) => a - b).forEach(num => { html += `<span class="number-ball !w-10 !h-10 !text-sm">${num}</span>`; }); container.innerHTML = html; } // Random number selection function randomNumbers() { selectedNumbers = []; while (selectedNumbers.length < 6) { const num = Math.floor(Math.random() * 49) + 1; if (!selectedNumbers.includes(num)) { selectedNumbers.push(num); } } updateNumberDisplay(); updateSelectedNumbersDisplay(); } // Clear selection function clearNumbers() { selectedNumbers = []; updateNumberDisplay(); updateSelectedNumbersDisplay(); } // Buy ticket async function buyTicket() { if (selectedNumbers.length !== 6) { showNotification('Please select 6 numbers', 'error'); return; } const buyerName = document.getElementById('buyerName').value; if (!buyerName) { showNotification('Please enter your name', 'error'); return; } const numbers = selectedNumbers.sort((a, b) => a - b).join(','); const email = document.getElementById('buyerEmail').value; const formData = { buyer_name: buyerName, buyer_email: email, numbers: numbers }; try { const response = await fetch('api/buy_ticket.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData) }); const data = await response.json(); if (data.success) { showNotification(data.message, 'success'); clearNumbers(); document.getElementById('buyerName').value = ''; document.getElementById('buyerEmail').value = ''; loadLotteryStatus(); // Show ticket details alert(`Your ticket number: ${data.ticket.ticket_number}\nHash: ${data.ticket.hash.substring(0, 20)}...`); } else { showNotification('Error: ' + data.message, 'error'); } } catch (error) { console.error('Error:', error); showNotification('Failed to purchase ticket', 'error'); } } // Load lottery status async function loadLotteryStatus() { try { const response = await fetch('api/get_lottery_status.php'); const data = await response.json(); if (data.success && data.active) { currentRound = data; document.getElementById('jackpotAmount').textContent = data.jackpot.toFixed(2); document.getElementById('ticketsSold').textContent = data.tickets_sold; document.getElementById('roundNumber').textContent = data.round; document.getElementById('drawTime').textContent = data.end_time; // Update recent tickets displayRecentTickets(data.recent_tickets); } } catch (error) { console.error('Error loading status:', error); } } // Display recent tickets function displayRecentTickets(tickets) { const container = document.getElementById('recentTickets'); if (!tickets || tickets.length === 0) { container.innerHTML = '<div class="text-center text-gray-500 py-4">No tickets yet</div>'; return; } let html = ''; tickets.forEach(ticket => { html += ` <div class="ticket-card p-3 rounded-lg"> <div class="flex justify-between items-start"> <div> <p class="font-mono text-xs text-purple-400">${ticket.ticket_number}</p> <p class="text-sm">${ticket.buyer_name}</p> </div> <span class="text-xs text-gray-400">${ticket.time}</span> </div> </div> `; }); container.innerHTML = html; } // Update countdown function updateCountdown() { if (!currentRound) return; const timeRemaining = currentRound.time_remaining; if (timeRemaining <= 0) { document.getElementById('countdown').textContent = 'DRAWING...'; document.getElementById('timeRemaining').textContent = 'Drawing soon'; return; } const hours = Math.floor(timeRemaining / 3600); const minutes = Math.floor((timeRemaining % 3600) / 60); const seconds = timeRemaining % 60; document.getElementById('countdown').textContent = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; document.getElementById('timeRemaining').textContent = `${hours}h ${minutes}m ${seconds}s`; } // Load past results async function loadPastResults() { try { const response = await fetch('api/get_history.php?limit=6'); const data = await response.json(); if (data.success) { displayPastResults(data.history); } } catch (error) { console.error('Error loading history:', error); } } // Display past results function displayPastResults(results) { const container = document.getElementById('pastResults'); if (!results || results.length === 0) { container.innerHTML = '<div class="text-center text-gray-500 py-8 col-span-3">No past results</div>'; return; } let html = ''; results.forEach(result => { const winningNumbers = result.winning_numbers ? result.winning_numbers.split(',') : []; html += ` <div class="ticket-card p-4 rounded-xl"> <div class="flex justify-between items-center mb-3"> <h3 class="font-bold">Round #${result.round_number}</h3> <span class="text-xs text-gray-400">${result.formatted_date}</span> </div> <div class="mb-3"> <p class="text-sm text-gray-400 mb-2">Winning Numbers</p> <div class="flex space-x-1"> ${winningNumbers.map(num => `<span class="number-ball !w-8 !h-8 !text-xs">${num}</span>` ).join('')} </div> </div> <div class="grid grid-cols-2 gap-2 text-sm"> <div> <span class="text-gray-400">Jackpot:</span> <span class="text-yellow-400">$${result.jackpot_amount.toFixed(2)}</span> </div> <div> <span class="text-gray-400">Winners:</span> <span class="text-green-400">${result.winner_count}</span> </div> </div> </div> `; }); container.innerHTML = html; } // Verify ticket async function verifyTicket() { const ticketNumber = document.getElementById('verifyTicket').value; if (!ticketNumber) { showNotification('Please enter a ticket number', 'error'); return; } try { const response = await fetch(`api/verify_ticket.php?ticket=${encodeURIComponent(ticketNumber)}`); const data = await response.json(); const resultDiv = document.getElementById('verificationResult'); resultDiv.classList.remove('hidden'); if (data.success) { if (data.is_winner) { resultDiv.className = 'mt-3 p-3 bg-green-600/20 border border-green-500 rounded-lg text-green-400'; resultDiv.innerHTML = ` <div class="flex items-center"> <i class="fas fa-trophy mr-2"></i> <div> <p class="font-bold">WINNER!</p> <p>Matched ${data.matches} numbers</p> <p class="text-lg font-bold">Prize: $${data.prize_amount.toFixed(2)}</p> ${!data.claimed ? '<button onclick="claimPrize(\'' + ticketNumber + '\')" class="mt-2 bg-green-600 text-white px-3 py-1 rounded text-sm">Claim Prize</button>' : '<p class="text-sm text-gray-400">Already claimed</p>'} </div> </div> `; } else { resultDiv.className = 'mt-3 p-3 bg-yellow-600/20 border border-yellow-500 rounded-lg text-yellow-400'; resultDiv.innerHTML = ` <div class="flex items-center"> <i class="fas fa-times-circle mr-2"></i> <div> <p class="font-bold">Not a winner</p> <p class="text-sm">Better luck next time!</p> </div> </div> `; } } else { resultDiv.className = 'mt-3 p-3 bg-red-600/20 border border-red-500 rounded-lg text-red-400'; resultDiv.innerHTML = ` <div class="flex items-center"> <i class="fas fa-exclamation-circle mr-2"></i> <div> <p class="font-bold">Error</p> <p class="text-sm">${data.message}</p> </div> </div> `; } } catch (error) { console.error('Error:', error); showNotification('Verification failed', 'error'); } } // Claim prize async function claimPrize(ticketNumber) { const claimantName = prompt('Enter your name to claim prize:'); if (!claimantName) return; try { const response = await fetch('api/claim_prize.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ticket_number: ticketNumber, claimant_name: claimantName }) }); const data = await response.json(); if (data.success) { showNotification(`Prize claimed: $${data.amount.toFixed(2)}`, 'success'); verifyTicket(); // Refresh verification } else { showNotification('Error: ' + data.message, 'error'); } } catch (error) { console.error('Error:', error); showNotification('Failed to claim prize', 'error'); } } // Show winner modal function showWinnerModal(ticketNumber, amount) { const modal = document.getElementById('winnerModal'); document.getElementById('winnerMessage').textContent = `Ticket ${ticketNumber} won $${amount.toFixed(2)}!`; modal.classList.remove('hidden'); setTimeout(() => { modal.querySelector('div').classList.add('scale-100'); }, 10); } // Close winner modal function closeWinnerModal() { const modal = document.getElementById('winnerModal'); modal.querySelector('div').classList.remove('scale-100'); setTimeout(() => { modal.classList.add('hidden'); }, 300); } // Helper: Show notification function showNotification(message, type = 'info') { const notification = document.createElement('div'); notification.className = `fixed top-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 animate-bounce ${ type === 'success' ? 'bg-green-600' : type === 'error' ? 'bg-red-600' : 'bg-blue-600' } text-white`; notification.textContent = message; document.body.appendChild(notification); setTimeout(() => { notification.remove(); }, 3000); } // Helper: Escape HTML function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } 12. style.css (Additional Styles)
/* style.css - Additional custom styles */ /* Custom scrollbar for recent tickets */ #recentTickets::-webkit-scrollbar { width: 6px; } #recentTickets::-webkit-scrollbar-track { background: rgba(255, 255, 255, 0.1); border-radius: 3px; } #recentTickets::-webkit-scrollbar-thumb { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 3px; } /* Number ball animations */ @keyframes selectPulse { 0%, 100% { box-shadow: 0 0 10px #667eea; } 50% { box-shadow: 0 0 30px #667eea; } } .number-ball.selected { animation: selectPulse 2s infinite; } /* Jackpot counter animation */ @keyframes countUp { from { transform: translateY(10px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } #jackpotAmount { animation: countUp 0.5s ease-out; } /* Ticket card hover effects */ .ticket-card { position: relative; overflow: hidden; } .ticket-card::before { content: ''; position: absolute; top: -50%; left: -50%; width: 200%; height: 200%; background: linear-gradient( 45deg, transparent, rgba(255, 255, 255, 0.1), transparent ); transform: rotate(45deg); transition: all 0.5s ease; opacity: 0; } .ticket-card:hover::before { opacity: 1; transform: rotate(45deg) translate(50%, 50%); } /* Countdown timer animation */ @keyframes countdownPulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } .countdown { animation: countdownPulse 2s infinite; } /* Winner announcement */ .winner-glow { animation: winnerGlow 2s ease-in-out infinite; } @keyframes winnerGlow { 0%, 100% { box-shadow: 0 0 20px gold; } 50% { box-shadow: 0 0 50px gold; } } /* Blockchain verification badge */ .blockchain-verified { background: linear-gradient(90deg, #10b981, #059669); padding: 2px 8px; border-radius: 9999px; font-size: 0.7rem; display: inline-flex; align-items: center; } /* Loading spinner */ .lottery-spinner { border: 4px solid rgba(102, 126, 234, 0.3); border-radius: 50%; border-top: 4px solid #667eea; width: 40px; height: 40px; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } How to Run
- Setup Environment:
- Ensure XAMPP/WAMP/Laragon is installed with PHP 7.4+ and MySQL
- Start Apache and MySQL services
- Database Setup:
- Open phpMyAdmin (http://localhost/phpmyadmin)
- Create database
chainlotto_db - Import
database/schema.sql
- Configure Database:
- Update
includes/config.phpwith your database credentials
- Set Admin Key:
- In
api/draw_lottery.php, change the admin key from 'CHANGEME_SECRET_KEY' to a secure value
- Setup Cron Job (Optional):
- For automated draws, add a cron job to run
cron/auto_draw.phpevery hour:0 * * * * php /path/to/your/project/cron/auto_draw.php
- Place Files:
- Create folder
decentralized-lotteryin web root - Copy all files maintaining the structure
- Access Application:
- Open browser and navigate to:
http://localhost/decentralized-lottery/
Testing the Lottery System
- Purchase Tickets:
- Select 6 numbers by clicking on number balls
- Enter your name
- Click "Buy Ticket"
- Save your ticket number for verification
- Monitor Lottery:
- Watch jackpot grow as tickets are sold
- See countdown to next draw
- View recent ticket purchases
- Test Verification:
- Enter your ticket number in the verify field
- Check if you won (after draw)
- Admin Functions:
- To trigger a manual draw (for testing):
curl -X POST -d "admin_key=CHANGEME_SECRET_KEY" http://localhost/decentralized-lottery/api/draw_lottery.php
- Verify Transparency:
- All tickets are recorded with their hashes
- Winning numbers are generated from ticket data
- Anyone can verify the randomness by checking the seed hash
Security Features
- Verifiable Randomness: Winning numbers are derived from all ticket hashes
- Immutable Tickets: Each ticket is part of a blockchain
- Transparent Process: All draws can be audited
- Proof of Work: Prevents spam ticket purchases
- Automatic Payouts: Winners can claim directly
Prize Structure
- 6 numbers (Jackpot): 50% of prize pool
- 5 numbers: 20% of prize pool
- 4 numbers: 15% of prize pool
- 3 numbers: 10% of prize pool
- 2 numbers: 5% of prize pool
This project demonstrates a fully transparent, verifiable lottery system where trust is replaced by mathematics and blockchain technology. No central authority can manipulate the results, and every aspect is publicly auditable.