We did a quick demo of walking through the ETHGent.com lucky block demo that uses IC randomness to create a simple draw-to-win game:
Here is what the Lucky block solidity code looks like:
pragma solidity ^0.8.28;
import "./LuckyToken.sol";
import "./ic/ICRandomLib.sol";
contract LuckyBlock {
LuckyToken public immutable luckyToken;
uint256 public immutable ticketPrice;
uint16 public immutable winThresholdBps;
uint256 public currentSpin;
uint256 public pendingTicketId;
address public pendingPlayer;
uint256 public lastPayout;
uint256 public lastRandomWord;
address public lastWinner;
bool public lastSpinWon;
bool public drawPending;
event TicketBought(uint256 indexed spin, address indexed player, uint256 amount, uint256 ticketId);
event DrawSettled(uint256 indexed spin, address indexed player, bool won, uint256 payout, uint256 randomWord);
event DrawRejected(uint256 indexed spin, address indexed player, uint256 refund, uint256 ticketId);
error DrawAlreadyPending();
error DrawUnavailable();
error InvalidAmount();
error InvalidChance();
constructor(address token, uint256 fixedTicketPrice, uint16 fixedWinThresholdBps) {
require(token != address(0), "LuckyBlock: zero token");
if (fixedTicketPrice == 0) revert InvalidAmount();
if (fixedWinThresholdBps == 0 || fixedWinThresholdBps > 10_000) revert InvalidChance();
luckyToken = LuckyToken(token);
ticketPrice = fixedTicketPrice;
winThresholdBps = fixedWinThresholdBps;
}
function currentPot() external view returns (uint256) {
return luckyToken.balanceOf(address(this));
}
function buyTicket(uint256 amount) external returns (uint256 ticketId) {
if (drawPending) revert DrawAlreadyPending();
if (amount != ticketPrice) revert InvalidAmount();
bool ok = luckyToken.transferFrom(msg.sender, address(this), amount);
require(ok, "LuckyBlock: transfer failed");
ticketId = ICRandomLib.rawRand();
currentSpin += 1;
pendingTicketId = ticketId;
pendingPlayer = msg.sender;
drawPending = true;
emit TicketBought(currentSpin, msg.sender, amount, ticketId);
}
function drawStatus() external view returns (uint256 ticketId, bool pending, bool rejected) {
ticketId = pendingTicketId;
if (!drawPending) {
return (ticketId, false, false);
}
(, pending, rejected) = ICRandomLib.getResult(ticketId);
}
function finalizeDraw() external returns (address winner) {
if (!drawPending) revert DrawUnavailable();
uint256 ticketId = pendingTicketId;
address player = pendingPlayer;
(bytes memory result, bool pending, bool rejected) = ICRandomLib.getResult(ticketId);
if (pending) revert DrawUnavailable();
if (rejected) {
drawPending = false;
pendingTicketId = 0;
pendingPlayer = address(0);
bool refundOk = luckyToken.transfer(player, ticketPrice);
require(refundOk, "LuckyBlock: refund failed");
emit DrawRejected(currentSpin, player, ticketPrice, ticketId);
return address(0);
}
uint256 randomWord = uint256(keccak256(result));
uint256 payout = 0;
bool won = randomWord % 10_000 < winThresholdBps;
if (won) {
payout = luckyToken.balanceOf(address(this));
winner = player;
bool ok = luckyToken.transfer(player, payout);
require(ok, "LuckyBlock: payout failed");
} else {
winner = address(0);
}
drawPending = false;
pendingTicketId = 0;
pendingPlayer = address(0);
lastWinner = winner;
lastPayout = payout;
lastRandomWord = randomWord;
lastSpinWon = won;
emit DrawSettled(currentSpin, player, won, payout, randomWord);
}
function winChancePercent() external view returns (uint256) {
return uint256(winThresholdBps) * 100 / 10_000;
}
}