Sui.

Bài viết

Chia sẻ kiến thức của bạn.

article banner.
harry phan.
Apr 10, 2025
Bài Viết

Xây dựng DApp xổ số NFT thế hệ tiếp theo với Sui Move và giao diện người dùng hiện đại

🧩 Xây dựng DApp xổ số NFT thế hệ tiếp theo với Sui Move & giao diện người dùng hiện đại

Đây là hướng dẫn cơ bản của bạn để xây dựng DApp xổ số được chơi game hóa, hỗ trợ NFT sử dụngSui Move, với hỗ trợ nhiều vòng, hệ thống giới thiệu, quản trị DAO và hệ thống thiết kế mà Gen Z sẽ yêu thích. Từ kiến trúc hợp đồng đến quy trình giao diện người dùng — hãy cùng tham gia.


📦 Phân tích giai đoạn

Giai đoạn 1 — Xổ số cốt lõi

  • Trò chơi nhiều vòng
  • Bán vé NFT
  • Hệ thống phần thưởng giới thiệu
  • Bỏ phiếu DAO cơ bản

Giai đoạn 2 - Thị trường & Trò chơi hóa

  • Tích hợp thị trường NFT
  • Boosters (tăng cơ hội chiến thắng)
  • Hệ thống Jackpot
  • Airdrop ẩn

Giai đoạn 3 - DAO & Multichain

  • Khả năng tương thích chuỗi chÉO
  • DAO với các đề xuất nâng cao
  • Định giá năng động
  • Phân tích trên chuỗi

🧠 Hợp đồng thông minh Tìm hiểu sâu về Sui Move

Cấu trúc hợp đồng

module nft_lottery_x::nft_lottery_x {
    use sui::object;
    use sui::balance::{Balance, zero};
    use sui::coin::{Self, Coin};
    use sui::clock::Clock;
    use sui::random::Random;
    use sui::event::emit;
    use sui::transfer;
    use sui::tx_context::TxContext;
    use std::option;
    use std::signer;

    const EGameNotStarted: u64 = 1000;
    const EGameAlreadyFinished: u64 = 1001;
    const EInvalidPayment: u64 = 1002;
    const ENoTickets: u64 = 1003;
    const EWinnerAlreadyChosen: u64 = 1004;
    const ENotWinner: u64 = 1005;

    public struct Game has key {
        id: UID,
        ticket_price: u64,
        start_time: u64,
        end_time: u64,
        total_tickets: u32,
        round: u32,
        winner: Option<u32>,
        balance: Balance<FAUCET_COIN>,
        referral_bonus: u64,
    }

    public struct Ticket has key {
        id: UID,
        game_id: ID,
        ticket_number: u32,
        buyer: address,
        referrer: Option<address>,
    }

    public struct GameCreated has copy, drop {
        game_id: ID,
        start_time: u64,
        end_time: u64,
        ticket_price: u64,
    }

    public struct TicketBought has copy, drop {
        game_id: ID,
        ticket_number: u32,
        buyer: address,
        referrer: Option<address>,
    }

    public struct WinnerAnnounced has copy, drop {
        game_id: ID,
        winner_ticket: u32,
        round: u32,
    }

    public struct RewardClaimed has copy, drop {
        game_id: ID,
        ticket_number: u32,
        amount: u64,
    }

    public fun create_game(
        start_time: u64,
        end_time: u64,
        ticket_price: u64,
        referral_bonus: u64,
        ctx: &mut TxContext
    ) {
        let game = Game {
            id: object::new(ctx),
            ticket_price,
            start_time,
            end_time,
            total_tickets: 0,
            round: 1,
            winner: option::none(),
            balance: zero(),
            referral_bonus,
        };

        emit<GameCreated>(GameCreated {
            game_id: object::id(&game),
            start_time,
            end_time,
            ticket_price,
        });

        transfer::share_object(game);
    }

    public fun buy_ticket(
        game: &mut Game,
        coin: Coin<FAUCET_COIN>,
        clock: &Clock,
        referrer: Option<address>,
        ctx: &mut TxContext
    ): Ticket {
        assert!(clock.timestamp_ms() >= game.start_time, EGameNotStarted);
        assert!(clock.timestamp_ms() < game.end_time, EGameAlreadyFinished);
        assert!(coin.value() == game.ticket_price, EInvalidPayment);

        game.total_tickets = game.total_tickets + 1;
        coin::put(&mut game.balance, coin);

        let ticket = Ticket {
            id: object::new(ctx),
            game_id: object::id(game),
            ticket_number: game.total_tickets,
            buyer: signer::address_of(ctx.sender()),
            referrer,
        };

        emit<TicketBought>(TicketBought {
            game_id: object::id(game),
            ticket_number: ticket.ticket_number,
            buyer: ticket.buyer,
            referrer: ticket.referrer,
        });

        ticket
    }

    public entry fun determine_winner(
        game: &mut Game,
        rand: &Random,
        clock: &Clock,
        ctx: &mut TxContext
    ) {
        assert!(clock.timestamp_ms() >= game.end_time, EGameNotStarted);
        assert!(game.winner.is_none(), EWinnerAlreadyChosen);
        assert!(game.total_tickets > 0, ENoTickets);

        let mut generator = rand.new_generator(ctx);
        let winning_ticket = generator.generate_u32_in_range(1, game.total_tickets);
        game.winner = option::some(winning_ticket);

        emit<WinnerAnnounced>(WinnerAnnounced {
            game_id: object::id(game),
            winner_ticket: winning_ticket,
            round: game.round,
        });
    }

    public fun claim_reward(
        ticket: Ticket,
        game: Game,
        ctx: &mut TxContext
    ): Coin<FAUCET_COIN> {
        assert!(object::id(&game) == ticket.game_id, EInvalidPayment);
        let ticket_num = ticket.ticket_number;
        assert!(game.winner.contains(&ticket_num), ENotWinner);

        let amount = game.balance.value();
        let reward = game.balance.into_coin(ctx);

        emit<RewardClaimed>(RewardClaimed {
            game_id: object::id(&game),
            ticket_number: ticket.ticket_number,
            amount,
        });

        object::delete(object::id(&game));

        reward
    }
}

Những điểm rút ra chính:

  • Balance<FAUCET_COIN>đảm bảo an toàn kiểu và xử lý tiền xu thích hợp
  • ✅ báo Option<u32>hiệu rõ ràng nếu người chiến thắng đã được chọn
  • ✅ Sự kiện cung cấp khả năng truy xuất nguồn gốc cho người giao diện và nhà thám hiểm

🛠 Lệnh Sui CLI

sui client call   --package <PACKAGE_ID>   --module nft_lottery_x   --function create_game   --args <START_TIME> <END_TIME> <TICKET_PRICE> <REFERRAL_BONUS>   --gas-budget 10000000

Để mua vé, xác định người chiến thắng hoặc nhận phần thưởng, hãy làm theo các quy trình CLI tương tự.


🔮 Bổ sung trong tương lai

  • Logic tự động đặt lại cho vòng tiếp theo claim_reward
  • Phát ra nhiều sự kiện hơn như ReferralRewardDistributed
  • Tái cấu trúc giải đặc biệt và giới thiệu thành các mô-đun con

Hãy cho tôi biết nếu bạn muốn phần 2 để xây dựng giao diện người dùng và tích hợp trên Sui testnet!

  • Sui
3
Chia sẻ
Bình luận
.
Chúng tôi sử dụng cookie để đảm bảo bạn có trải nghiệm tốt nhất trên trang web của chúng tôi.
Thêm thông tin