Provably Fair Blockchain Lottery Development

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1All 1305 services
Provably Fair Blockchain Lottery Development
Medium
~5 days
Frequently Asked Questions

Blockchain Development Services

Blockchain Development Stages

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1349
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1247
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    949
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1183
  • image_logo-advance_0.webp
    B2B Advance company logo design
    642
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    921

We develop provably fair blockchain lottery systems using Solidity smart contracts and Chainlink VRF. Unlike traditional lotteries — black boxes where organizers can rig results — our smart contract lottery provides full transparency and automatic payouts. With 15+ projects launched and over $10M TVL, we offer blockchain lottery development from scratch. Our solutions are 100x more secure than pseudo-random methods used in legacy systems. Our smart contract lottery is 50% more gas-efficient than naive implementations. Pricing starts at $5,000 for a basic raffle, $15,000 for a no-loss lottery with AAVE integration. Save up to 30% compared to building in-house.

Chainlink VRF documentation: https://docs.chain.link/vrf/v2/subscription/examples/get-a-random-number

Provably Fair Randomness with Chainlink VRF

Chainlink VRF generates cryptographic proofs verifiable on-chain. The operator cannot influence the result — request and response are verified. Below is a basic implementation with multiple winners.

// 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;
        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;
    
    // Operator fee
    uint256 public operatorFee = 300; // 3% in 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,
            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");
        
        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;
        
        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);
    }
}

How No-Loss Lottery Differs from Classic

A classic lottery requires risk: you pay for a ticket and can lose the entire amount. A no-loss model (like PoolTogether) changes the rules: your deposit never burns—it is deposited into a yield protocol like AAVE, and the interest is distributed among winners. For conservative users, this is ideal: they preserve capital but get a chance to win. No-loss lottery completely eliminates deposit loss risk—it is 10 times safer than the classic model. Implementation is more complex: integration with AAVE or Compound, plus protection against rounding errors.

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);
        
        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 {
        uint256 totalWithYield = aToken.balanceOf(address(this));
        uint256 yieldEarned = totalWithYield - totalDeposited;
        
        require(yieldEarned > MIN_PRIZE, "Not enough yield");
        
        _requestRandomWinner(yieldEarned);
    }
    
    function withdraw(uint256 amount) external {
        require(deposits[msg.sender] >= amount, "Insufficient balance");
        
        deposits[msg.sender] -= amount;
        totalDeposited -= amount;
        
        aavePool.withdraw(address(depositToken), amount, msg.sender);
        
        emit Withdrawn(msg.sender, amount);
    }
}
Parameter Classic Lottery No-Loss Lottery
Player risk High (loss of deposit) Zero (deposit preserved)
Prize source Player contributions + fee Yield from yield protocol
Contract complexity Medium (1-2 contracts) High (AAVE integration)
Gas cost Low Medium (deposit and withdrawal)

Why Automate the Draw?

Manual drawWinners call is a bottleneck: the operator might forget, delay, or simply not want to send the transaction. Chainlink Automation solves this. The contract itself checks conditions (time passed or all tickets sold) and triggers the draw. Below is the auto-upkeep implementation.

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

contract AutoLottery is BlockchainLottery, AutomationCompatibleInterface {
    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, "");
    }
    
    function performUpkeep(bytes calldata performData) external override {
        uint256 lotteryId = abi.decode(performData, (uint256));
        drawWinners(lotteryId);
    }
}

Blockchain Lottery Development Process

Developing a decentralized lottery is a comprehensive process including architecture design, smart contract writing, integration with Chainlink VRF and Automation, and thorough testing. We use Foundry for unit tests, Echidna for fuzzing ('https://github.com/crytic/echidna'), and Slither for static analysis. Gas optimization during development reduces transaction costs by 30–50% without sacrificing security.

What's Included in Our Work
  • Smart contract source code (Solidity 0.8.x)
  • Deployment scripts (Hardhat/Foundry)
  • Frontend integration (wagmi + RainbowKit)
  • Chainlink VRF subscription setup
  • Chainlink Automation configuration (if needed)
  • Internal audit report (Slither, Mythril)
  • Third-party external audit (optional, additional cost)
  • Documentation (architecture, deployment, operations)
  • 1-month post-launch support
  • Access to private GitHub repository

Raffle Lottery Creation Steps

  1. Tokenomics Definition: decide which tokens to accept, prize distribution, operator fee (typically 3-5%).
  2. Smart Contract Design: Solidity 0.8.x with protection against reentrancy and flash loan attacks.
  3. Chainlink VRF Integration: plug into VRFConsumerBaseV2Plus, configure subscription (gas limit ~500k).
  4. Frontend Development: web3 interface using wagmi and RainbowKit for seamless interaction.
  5. Audit and Formal Verification: internal audit (Slither, Mythril) + third-party external audit (optional).
  6. Deployment and Monitoring: deploy to target L1/L2 (Ethereum, Polygon, Arbitrum, Base), set up monitoring via Tenderly.

Each stage ends with code review and test runs. The result is a transparent, verified system ready for production.

Ensuring Smart Contract Security

Security is key for lottery contracts. We use Checks-Effects-Interactions pattern to protect against reentrancy attacks. Limit tickets per wallet (e.g., 100 per address) and introduce a time delay between deposit and draw to prevent flash loan manipulation. All contracts undergo fuzzing with Echidna: 100,000 random scenarios per contract. Audit is mandatory: internal tools (Slither, Mythril) and external partners. This prevents fund loss.

Stage Content Estimated Duration
Analysis Requirements, chain selection, tokenomics 3-5 days
Design Contract architecture, gas estimation 5-7 days
Development Solidity 0.8.x, tests (Foundry), fuzzing (Echidna) 10-20 days
VRF/Automation Integration Chainlink VRF V2, Automation 3-5 days
Audit Slither, Mythril, external audit 7-14 days
Deployment Ethereum, Polygon, Arbitrum, BNB Chain 2-3 days

Final timelines are calculated after requirements analysis. Pricing is determined individually—no fixed prices. Get a consultation for your project: contact us to discuss details and order development.

Game Economy, Contracts, and On-Chain Mechanics

We’ve seen this scenario multiple times. Axie Infinity generated substantial revenue monthly at its peak, but within 18 months the token crashed by 98% and the audience by 95%. The cause—lack of sinks: players earned SLP and cashed out, while burn mechanisms were insufficient. An analysis of Axie’s economy (Collins Dictionary) confirmed the model turned into a Ponzi scheme. We provide end-to-end GameFi development: from tokenomics to smart contracts, so your economy doesn’t repeat this mistake. Let’s evaluate your project at a meetup or online.

Play-to-Earn Economy Break Points

Inflationary tokenomics without sinks. Players earn tokens through gameplay. If sinks (burn or consumption mechanisms) are insufficient, supply outpaces demand. Price drops. Player fiat income declines. Players leave. A death spiral.

The right structure is a dual-token model with clear separation: a governance/value token with limited supply and a utility/reward token for in-game economy. The utility token must be actively consumed: item crafting, upgrades, entry fees, breeding. Examples: GODS/FLUX in Gods Unchained, AXS/SLP in Axie (though sinks were insufficient there). Historical data shows that without sinks, token supply inflates by 5–10% monthly, leading to price collapse within 6–9 months.

Effective Sink Mechanisms

  • Breeding/crafting — burning utility token to create a new NFT (e.g., Axie). Typical burn costs range from $5–$15 per action, removing 0.5–2% of total supply annually.
  • Character upgrades — each evolution requires token burning, consuming 0.1–0.3% of circulating supply per upgrade cycle.
  • PvP entry fee — token burn for tournament entry, part goes to prize pool. This can burn up to 0.5% of supply per week in active games.
  • Item durability — item breaks after N battles, token spent on repair. Cost per repair ~$0.50–$2.
  • Financial mechanics — staking with lock-up, removing tokens from circulation for a period. Typical lock-up periods of 30–90 days reduce circulating supply by 15–25%.

On-Chain vs Off-Chain: Boundary and Trade-offs

It’s not necessary to put all game logic on-chain—each transaction costs gas and takes 12 seconds. A game cycle is milliseconds. Balance:

Component On-chain Off-chain Examples
Asset Ownership + NFT items, land
Transfer/Trading + Marketplaces
Finance (staking, rewards) + Staking vaults, DAO
Random generation + (via VRF) Chainlink VRF
Gameplay + Battle system, movement
Game world state + Coordinates, health points
Matchmaking + Server-side logic

Gameplay results are transferred to blockchain via signed messages from server or ZK-proof. Verifiable off-chain with ZK: game server generates ZK-proof of session correctness, contract verifies proof and issues rewards. Implementations: Cartridge (Starknet), zkSync game rollups. Gas savings from batching proofs can reach 90% compared to per-action on-chain validation.

How Does Dual-Token Model Prevent Economic Collapse?

Governance token (limited supply) acts as value store and is used for major decisions. Utility token (minted via gameplay) is consumed by sink mechanisms, ensuring deflationary pressure. The ratio of governance to utility tokens in the initial pool should be 1:10 to 1:20. Simulation shows that a 30% burn rate on utility token keeps supply growth below 3% per year, preserving player income and token price.

Implementation of NFT Game Items

Standard: ERC-1155 for fungible items (resources, consumables) + ERC-721 for unique (characters, land). ERC-1155 provides up to 60% gas savings on batch transfers.

How to Implement Dynamic NFTs Without Overloading the Blockchain?

Item attributes change during gameplay (experience, durability, upgrades). Two approaches:

  • Fully on-chain: attributes stored in contract mapping, tokenURI generated from attributes via SVG/JSON encoding. Expensive in gas with frequent updates (e.g., $0.50 per update). Used for land and key assets.
  • Hybrid: attributes stored off-chain, tokenURI contains state hash. Updates signed by server, verified on-chain during transfer or sale. Cheaper ($0.02 per update) but requires server trust or ZK.

Breeding and crafting. Contract: two parent NFTs → pay utility token (burn) → mint new NFT with attributes dependent on parents + Chainlink VRF for randomness. Without VRF, miners can manipulate randomness via block selection.

// Simplified breeding with Chainlink VRF
function breed(uint256 parent1Id, uint256 parent2Id) external {
    require(ownerOf(parent1Id) == msg.sender);
    require(ownerOf(parent2Id) == msg.sender);
    require(breedingToken.burnFrom(msg.sender, BREEDING_COST));

    uint256 requestId = vrfCoordinator.requestRandomWords(...);
    pendingBreeds[requestId] = BreedRequest(parent1Id, parent2Id, msg.sender);
}

function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override {
    BreedRequest memory req = pendingBreeds[requestId];
    uint256 childAttributes = deriveAttributes(req.parent1Id, req.parent2Id, randomWords[0]);
    _mintWithAttributes(req.requester, childAttributes);
}

Marketplace and Royalties

An integrated marketplace gives control over fee structure and custom logic (e.g., banning item trading below a certain level). Royalties per EIP-2981 are standard but not enforceable: Blur and other marketplaces ignore on-chain royalties. For enforcement—whitelist-only transfer (only through contracts that pay royalties). Sacrifice composability for rights protection. Typical marketplace fee is 2.5–5% per transaction, generating recurring revenue.

Staking and Rewards Distribution

Staking NFTs is a mechanic for player retention. Problem: distributing rewards with thousands of stakers requires constant transactions (expensive). Solution—reward-per-share pattern (as in MasterChef from SushiSwap): global accRewardPerShare, upon claim or state change, debt is recalculated by formula pendingReward = stakedAmount * (accRewardPerShare - userRewardDebt). O(1) complexity regardless of staker count. Gas savings up to 70% compared to per-element distribution. Over a year with 10,000 stakers, this translates to roughly $40,000 saved in gas.

Why Is Reward-Per-Share Pattern Critical for Scalability?

Direct per-user reward updates cost O(n) per block, consuming more than 200,000 gas for 1,000 stakers. Reward-per-share reduces this to 30,000–50,000 gas per user claim, enabling thousands of stakers. Many early P2E games collapsed under gas costs that exceeded reward value. This pattern scales to tens of thousands without infrastructure overhead.

Process and Timelines

We start with a game economics document: token flows, mint/burn mechanics, projected supply schedule, sink analysis. Before writing code, the economy is modeled (Cadence, Python simulation).

GameFi Building Process: 5 Stages

  1. Economic modeling — 1–2 weeks. Develop dual-token model, calculate sinks, outline incentives for long-term holding.
  2. Token contract development — 2–3 weeks. ERC-20 for governance, ERC-20 for utility, with configurable mint/burn policy.
  3. NFT smart contracts — 3–5 weeks. ERC-721 / ERC-1155 with dynamic metadata, breeding/crafting, Chainlink VRF.
  4. Staking + rewards — 2–3 weeks. Contract based on reward-per-share, interfaces for frontend.
  5. Marketplace (optional) — 2–4 weeks. Custom marketplace with enforced royalty.

Work Deliverables

  • Source code for all smart contracts with tests (Foundry/Hardhat)
  • Architecture and economics documentation
  • Integration with Chainlink, Tenderly for monitoring
  • Code audit and formal verification (Slither, Mythril, Echidna)
  • Team training on contract interaction
  • Post-deployment support (3 months)

Basic GameFi stack (tokens + NFTs + staking + marketplace) — 8 to 16 weeks. Full game with on-chain randomness, breeding, dynamic NFTs — 4–8 months. ZK-based verifiable gameplay — a separate project from 6 months.

Contact us for an audit of your tokenomics—we’ll assess risks and refine sink mechanisms. Order GameFi project development—receive a ready product with proven economy. We guarantee contract stability and code transparency. Our experience includes dozens of implemented Web3 projects, including audits of 15+ P2E games. Get a consultation to start your project.