Разработка лотереи на блокчейне

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка лотереи на блокчейне
Средняя
~5 рабочих дней
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1258
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1170
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    873
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1092
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    563
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    830

Разработка лотереи на блокчейне

Blockchain лотерея — один из наиболее естественных use cases для smart contracts: прозрачное хранение призового фонда, верифицируемый розыгрыш через Chainlink VRF, автоматические выплаты без посредников. В отличие от традиционных лотерей — никто не может манипулировать результатом, фонд нельзя украсть, выплата гарантирована кодом.

Типы лотерей

Fixed prize (Powerball-style): фиксированный джекпот, игрок выбирает числа, выигрывает при совпадении. Фонд берётся из продажи билетов + резерва оператора.

Prize pool lottery (Raffle): весь призовой фонд = сумма всех ставок (минус комиссия). Больше участников = больше приз. Один winner takes all или несколько призовых мест.

No-loss lottery (PoolTogether-style): участники вносят в yield-генерирующий протокол (AAVE), накопленный процент идёт winner, депозиты возвращаются всем. «Играешь бесплатно, рискуешь только доходностью».

Smart contract — раффл лотерея

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {VRFConsumerBaseV2Plus} from "@chainlink/contracts/src/v0.8/vrf/dev/VRFConsumerBaseV2Plus.sol";
import {VRFV2PlusClient} from "@chainlink/contracts/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol";

contract BlockchainLottery is VRFConsumerBaseV2Plus {
    struct Lottery {
        uint256 ticketPrice;
        uint256 startTime;
        uint256 endTime;
        uint256 maxTickets;
        uint256 ticketsSold;
        address[] participants; // array для weighted selection
        uint256 prizePool;
        LotteryStatus status;
        uint256 vrfRequestId;
        address winner;
        uint256[] prizeDistribution; // [7000, 2000, 1000] = 70%, 20%, 10%
    }
    
    enum LotteryStatus { OPEN, DRAWING, CLOSED, CANCELLED }
    
    mapping(uint256 => Lottery) public lotteries;
    uint256 public lotteryCount;
    
    // Комиссия оператора
    uint256 public operatorFee = 300; // 3% в basis points
    
    function createLottery(
        uint256 ticketPrice,
        uint256 duration,
        uint256 maxTickets,
        uint256[] calldata prizeDistribution
    ) external onlyOwner returns (uint256 lotteryId) {
        require(_validateDistribution(prizeDistribution), "Invalid distribution");
        
        lotteryId = ++lotteryCount;
        lotteries[lotteryId] = Lottery({
            ticketPrice: ticketPrice,
            startTime: block.timestamp,
            endTime: block.timestamp + duration,
            maxTickets: maxTickets,
            ticketsSold: 0,
            participants: new address[](0),
            prizePool: 0,
            status: LotteryStatus.OPEN,
            vrfRequestId: 0,
            winner: address(0),
            prizeDistribution: prizeDistribution,
        });
    }
    
    function buyTickets(uint256 lotteryId, uint256 amount) external payable {
        Lottery storage lottery = lotteries[lotteryId];
        require(lottery.status == LotteryStatus.OPEN, "Not open");
        require(block.timestamp < lottery.endTime, "Lottery ended");
        require(lottery.ticketsSold + amount <= lottery.maxTickets, "Not enough tickets");
        require(msg.value == lottery.ticketPrice * amount, "Wrong payment");
        
        // Добавляем участника amount раз (weighted: больше билетов = больше шансов)
        for (uint256 i = 0; i < amount; i++) {
            lottery.participants.push(msg.sender);
        }
        
        lottery.ticketsSold += amount;
        
        uint256 fee = (msg.value * operatorFee) / 10000;
        lottery.prizePool += msg.value - fee;
        
        emit TicketsPurchased(lotteryId, msg.sender, amount);
    }
    
    function drawWinners(uint256 lotteryId) external {
        Lottery storage lottery = lotteries[lotteryId];
        require(
            block.timestamp >= lottery.endTime || lottery.ticketsSold == lottery.maxTickets,
            "Lottery not ended"
        );
        require(lottery.status == LotteryStatus.OPEN, "Wrong status");
        require(lottery.ticketsSold > 0, "No participants");
        
        lottery.status = LotteryStatus.DRAWING;
        
        // Запрашиваем N случайных чисел (по количеству призовых мест)
        uint256 numWinners = lottery.prizeDistribution.length;
        uint256 requestId = s_vrfCoordinator.requestRandomWords(
            VRFV2PlusClient.RandomWordsRequest({
                keyHash: KEY_HASH,
                subId: SUBSCRIPTION_ID,
                requestConfirmations: 3,
                callbackGasLimit: 500_000,
                numWords: uint32(numWinners),
                extraArgs: VRFV2PlusClient._argsToBytes(
                    VRFV2PlusClient.ExtraArgsV1({nativePayment: false})
                )
            })
        );
        
        lottery.vrfRequestId = requestId;
        vrfToLottery[requestId] = lotteryId;
    }
    
    function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) 
        internal override 
    {
        uint256 lotteryId = vrfToLottery[requestId];
        Lottery storage lottery = lotteries[lotteryId];
        
        uint256 participantCount = lottery.participants.length;
        address[] memory winners = new address[](randomWords.length);
        bool[] memory isSelected = new bool[](participantCount);
        
        for (uint256 i = 0; i < randomWords.length; i++) {
            uint256 idx = randomWords[i] % participantCount;
            
            // Если участник уже выбран — ищем следующего
            while (isSelected[idx]) {
                idx = (idx + 1) % participantCount;
            }
            
            isSelected[idx] = true;
            winners[i] = lottery.participants[idx];
            
            // Выплачиваем приз
            uint256 prize = (lottery.prizePool * lottery.prizeDistribution[i]) / 10000;
            payable(winners[i]).transfer(prize);
            
            emit WinnerPaid(lotteryId, winners[i], i + 1, prize);
        }
        
        lottery.status = LotteryStatus.CLOSED;
        emit LotteryDrawn(lotteryId, winners);
    }
}

No-loss lottery (PoolTogether модель)

contract NoLossLottery {
    IERC20 public depositToken;    // USDC
    IAAVE public aavePool;         // AAVE lending pool
    IERC20 public aToken;          // aUSDC (yield bearing)
    
    mapping(address => uint256) public deposits;
    uint256 public totalDeposited;
    
    function deposit(uint256 amount) external {
        depositToken.transferFrom(msg.sender, address(this), amount);
        
        // Депозит в AAVE для yield
        depositToken.approve(address(aavePool), amount);
        aavePool.deposit(address(depositToken), amount, address(this), 0);
        
        deposits[msg.sender] += amount;
        totalDeposited += amount;
        
        emit Deposited(msg.sender, amount);
    }
    
    function triggerDraw() external {
        // Общая стоимость aTokens > totalDeposited = накопленный yield
        uint256 totalWithYield = aToken.balanceOf(address(this));
        uint256 yieldEarned = totalWithYield - totalDeposited;
        
        require(yieldEarned > MIN_PRIZE, "Not enough yield");
        
        // Запрашиваем VRF для выбора победителя
        _requestRandomWinner(yieldEarned);
    }
    
    // Withdraw: пользователь получает полный депозит назад
    function withdraw(uint256 amount) external {
        require(deposits[msg.sender] >= amount, "Insufficient balance");
        
        deposits[msg.sender] -= amount;
        totalDeposited -= amount;
        
        // Вывод из AAVE
        aavePool.withdraw(address(depositToken), amount, msg.sender);
        
        emit Withdrawn(msg.sender, amount);
    }
}

Chainlink Automation для автоматического розыгрыша

import {AutomationCompatibleInterface} from "@chainlink/contracts/src/v0.8/automation/AutomationCompatible.sol";

contract AutoLottery is BlockchainLottery, AutomationCompatibleInterface {
    // Chainlink Automation вызывает checkUpkeep каждый блок
    function checkUpkeep(bytes calldata) external view override 
        returns (bool upkeepNeeded, bytes memory performData) 
    {
        for (uint256 i = 1; i <= lotteryCount; i++) {
            Lottery storage lottery = lotteries[i];
            if (
                lottery.status == LotteryStatus.OPEN &&
                block.timestamp >= lottery.endTime &&
                lottery.ticketsSold > 0
            ) {
                return (true, abi.encode(i));
            }
        }
        return (false, "");
    }
    
    // Автоматически вызывается когда upkeepNeeded = true
    function performUpkeep(bytes calldata performData) external override {
        uint256 lotteryId = abi.decode(performData, (uint256));
        drawWinners(lotteryId);
    }
}

Разработка базовой раффл лотереи — 2-3 недели. No-loss lottery с AAVE интеграцией — 4-5 недель. Chainlink Automation для автоматизации — добавить 1 неделю.