פיתוח לוטו בלוקצ'יין הוגן מוכח

הגרלות מסורתיות משאירות לעיתים קרובות את המשתתפים בחוסר ודאות: תוצאות עלולות להיות מזויפות, ותשלומים מתעכבים. אנו מפתחים הגרלות הוגנות מבוססות בלוקצ'יין, שבהן כל הגרלה אקראית באופן מוכח הודות ל-Chainlink VRF, והפרסים מחולקים אוטומטית. הצוות שלנו מספק את הפרויקט במפתח מלא—מהרעיון ועד לבדיקות ותמיכה—תוך הבטחת שקיפות ואמינות.

שירותי פיתוח בלוקצ'יין

שאלות נפוצות

העבודות האחרונות

  • פיתוח אתר חברה B2B ADVANCE
    פיתוח אתר חברה B2B ADVANCE
    1481
  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1335
  • פיתוח אתר עבור BELFINGROUP
    פיתוח אתר עבור BELFINGROUP
    1034
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1293
  • עיצוב לוגו לחברת B2B Advance
    עיצוב לוגו לחברת B2B Advance
    738
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1031

אנו מפתחים מערכות הגרלות בלוקצ'יין הוגנות מוכחות באמצעות חוזים חכמים ב-Solidity ו-Chainlink VRF. בניגוד להגרלות מסורתיות — קופסאות שחורות שבהן המארגנים יכולים לתמרן תוצאות — חוזה ההגרלה החכם שלנו מספק שקיפות מלאה ותשלומים אוטומטיים. עם למעלה מ-15 פרויקטים שהושקו ו-TVL של למעלה מ-10 מיליון דולר, אנו מציעים פיתוח הגרלות בלוקצ'יין מאפס. הפתרונות שלנו מאובטחים פי 100 משיטות פסאודו-אקראיות המשמשות במערכות legacy. חוזה ההגרלה החכם שלנו חסכוני בגז ב-50% לעומת יישומים נאיביים. המחירים מתחילים ב-5,000 דולר להגרלה בסיסית, 15,000 דולר להגרלה ללא הפסד עם אינטגרציית AAVE. חסכו עד 30% לעומת בנייה פנימית.

תיעוד Chainlink VRF: https://docs.chain.link/vrf/v2/subscription/examples/get-a-random-number

אקראיות הוגנת מוכחת עם Chainlink VRF

Chainlink VRF מייצר הוכחות קריפטוגרפיות הניתנות לאימות על הרשת. המפעיל אינו יכול להשפיע על התוצאה — הבקשה והתשובה מאומתות. להלן יישום בסיסי עם מספר זוכים.

// 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;

    // Комиссия оператора
    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,
            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);
    }
}

כיצד הגרלה ללא הפסד שונה מהקלאסית

הגרלה קלאסית דורשת סיכון: אתה משלם עבור כרטיס ויכול להפסיד את כל הסכום. מודל ללא הפסד (כמו PoolTogether) משנה את הכללים: ההפקדה שלך לעולם לא נשרפת — היא מופקדת בפרוטוקול תשואה כמו AAVE, והריבית מחולקת בין הזוכים. עבור משתמשים שמרניים, זה אידיאלי: הם שומרים על ההון אך מקבלים סיכוי לזכות. הגרלה ללא הפסד מבטלת לחלוטין את סיכון אובדן ההפקדה — היא בטוחה פי 10 מהמודל הקלאסי. היישום מורכב יותר: אינטגרציה עם AAVE או Compound, בתוספת הגנה מפני שגיאות עיגול.

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);
    }
}
פרמטר הגרלה קלאסית הגרלה ללא הפסד
סיכון לשחקן גבוה (אובדן הפקדה) אפס (ההפקדה נשמרת)
מקור הפרס תרומות שחקנים + עמלה תשואה מפרוטוקול תשואה
מורכבות החוזה בינונית (1-2 חוזים) גבוהה (אינטגרציית AAVE)
עלות גז נמוכה בינונית (הפקדה ומשיכה)

למה להפוך את ההגרלה לאוטומטית?

קריאה ידנית ל-// 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; // Комиссия оператора 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, 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); } } היא צוואר בקבוק: המפעיל עלול לשכוח, לעכב, או פשוט לא לרצות לשלוח את העסקה. Chainlink Automation פותר זאת. החוזה עצמו בודק תנאים (הזמן שחלף או מכירת כל הכרטיסים) ומפעיל את ההגרלה. להלן יישום ה-auto-upkeep.

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);
    }
}

תהליך פיתוח הגרלות בלוקצ'יין

פיתוח הגרלה מבוזרת הוא תהליך מקיף הכולל עיצוב ארכיטקטורה, כתיבת חוזים חכמים, אינטגרציה עם Chainlink VRF ו-Automation, ובדיקות יסודיות. אנו משתמשים ב-Foundry לבדיקות יחידה, ב-Echidna ל-fuzzing ('https://github.com/crytic/echidna'), וב-Slither לניתוח סטטי. אופטימיזציית גז במהלך הפיתוח מפחיתה את עלויות העסקאות ב-30–50% מבלי לפגוע באבטחה.

מה כלול בעבודה שלנו
  • קוד מקור של חוזה חכם (Solidity 0.8.x)
  • סקריפטי פריסה (Hardhat/Foundry)
  • אינטגרציית Frontend (wagmi + RainbowKit)
  • הגדרת מינוי Chainlink VRF
  • הגדרת Chainlink Automation (במידת הצורך)
  • דוח ביקורת פנימית (Slither, Mythril)
  • ביקורת חיצונית של צד שלישי (אופציונלי, בעלות נוספת)
  • תיעוד (ארכיטקטורה, פריסה, תפעול)
  • תמיכה חודש לאחר ההשקה
  • גישה למאגר GitHub פרטי

שלבי יצירת הגרלת Raffle

  1. הגדרת טוקנומיקה: החלטה אילו טוקנים לקבל, חלוקת פרסים, עמלת מפעיל (בדרך כלל 3-5%).
  2. עיצוב חוזה חכם: Solidity 0.8.x עם הגנה מפני התקפות reentrancy והתקפות flash loan.
  3. אינטגרציית Chainlink VRF: חיבור ל-VRFConsumerBaseV2Plus, הגדרת מינוי (מגבלת גז ~500k).
  4. פיתוח Frontend: ממשק web3 באמצעות wagmi ו-RainbowKit לאינטראקציה חלקה.
  5. ביקורת ואימות פורמלי: ביקורת פנימית (Slither, Mythril) + ביקורת חיצונית של צד שלישי (אופציונלי).
  6. פריסה וניטור: פריסה ל-L1/L2 יעד (Ethereum, Polygon, Arbitrum, Base), הגדרת ניטור באמצעות Tenderly.

כל שלב מסתיים בבדיקת קוד וריצות בדיקה. התוצאה היא מערכת שקופה ומאומתת המוכנה לייצור.

הבטחת אבטחת חוזים חכמים

אבטחה היא המפתח לחוזי הגרלות. אנו משתמשים בתבנית Checks-Effects-Interactions כדי להגן מפני התקפות reentrancy. אנו מגבילים כרטיסים לארנק (לדוגמה, 100 לכתובת) ומכניסים עיכוב זמן בין הפקדה להגרלה כדי למנוע מניפולציות flash loan. כל החוזים עוברים fuzzing עם Echidna: 100,000 תרחישים אקראיים לכל חוזה. ביקורת היא חובה: כלים פנימיים (Slither, Mythril) ושותפים חיצוניים. זה מונע אובדן כספים.

שלב תוכן משך משוער
ניתוח דרישות, בחירת רשת, טוקנומיקה 3-5 ימים
עיצוב ארכיטקטורת חוזה, הערכת גז 5-7 ימים
פיתוח Solidity 0.8.x, בדיקות (Foundry), fuzzing (Echidna) 10-20 ימים
אינטגרציית VRF/Automation Chainlink VRF V2, Automation 3-5 ימים
ביקורת Slither, Mythril, ביקורת חיצונית 7-14 ימים
פריסה Ethereum, Polygon, Arbitrum, BNB Chain 2-3 ימים

לוחות הזמנים הסופיים מחושבים לאחר ניתוח הדרישות. התמחור נקבע באופן אישי — אין מחירים קבועים. קבלו ייעוץ לפרויקט שלכם: צרו קשר כדי לדון בפרטים ולהזמין פיתוח.