Blockchain Crash Game Development
Crash—gambling game where multiplier grows from 1x upward and "crashes" at random moment. Player must withdraw stake before crash. Longer wait—higher potential win, higher risk of losing everything.
Key blockchain Crash difference from traditional online casino: result must be provably fair. Player doesn't trust server—can mathematically verify multiplier wasn't determined after their bet via commitment scheme + Chainlink VRF or RANDAO.
Provably Fair Crash Architecture
Commitment + Reveal Scheme
Operator publishes hash of next seed beforehand, reveals seed after bet phase:
contract CrashGame {
struct Round {
bytes32 seedHash; // hash(seed)—published before bets
bytes32 seed; // revealed after bets
uint64 crashPoint; // result (in basis points: 150 = 1.5x)
uint256 totalBets;
uint256 startTime;
RoundStatus status;
}
enum RoundStatus { ACCEPTING_BETS, IN_PROGRESS, CRASHED, CASHOUT_PHASE }
function commitNextRound(bytes32 seedHash) external onlyOperator {
require(rounds[nextRoundId].status == RoundStatus.CRASHED);
rounds[nextRoundId + 1].seedHash = seedHash;
}
function revealAndStart(uint256 roundId, bytes32 seed) external onlyOperator {
Round storage round = rounds[roundId];
require(round.status == RoundStatus.ACCEPTING_BETS);
require(keccak256(abi.encodePacked(seed)) == round.seedHash, "Seed mismatch");
round.seed = seed;
round.crashPoint = _calculateCrashPoint(seed, roundId);
round.status = RoundStatus.IN_PROGRESS;
round.startTime = uint64(block.timestamp);
}
}
Chainlink VRF V2 Plus
function closeAndRequestRandom(uint256 roundId) external onlyOperator {
Round storage round = rounds[roundId];
require(round.status == RoundStatus.ACCEPTING_BETS);
round.status = RoundStatus.IN_PROGRESS;
uint256 requestId = s_vrfCoordinator.requestRandomWords(
VRFV2PlusClient.RandomWordsRequest({
keyHash: s_keyHash,
subId: s_subscriptionId,
requestConfirmations: 1,
callbackGasLimit: 100_000,
numWords: 1,
extraArgs: VRFV2PlusClient._argsToBytes(
VRFV2PlusClient.ExtraArgsV1({nativePayment: false})
)
})
);
roundToVrfRequest[roundId] = requestId;
}
function fulfillRandomWords(
uint256 requestId,
uint256[] calldata randomWords
) internal override {
uint256 roundId = vrfRequestToRound[requestId];
Round storage round = rounds[roundId];
round.crashPoint = _calculateCrashPoint(randomWords[0]);
emit RoundActive(roundId, round.startTime = uint64(block.timestamp));
}
Crash Point Formula
function _calculateCrashPoint(uint256 rand) internal pure returns (uint64) {
uint256 h = rand % 1_000_000_000;
// P(crash >= X) = 0.99/X → X = 990_000_000/h
if (h < 10_000_000) return 100; // 1.00x—instant crash (house edge)
uint256 crashPoint = 990_000_000 * 100 / h;
if (crashPoint < 100) return 100;
if (crashPoint > 100_000) return 100_000;
return uint64(crashPoint);
}
Anyone can verify: take VRF randomWords[0] from on-chain data, reproduce formula, get same crash point.
Betting and Cashout Mechanics
struct Bet {
address player;
uint256 amount;
uint64 autoCashoutAt; // 0 = manual, >0 = auto cashout at multiplier
bool cashedOut;
uint64 cashoutMultiplier;
}
function placeBet(uint256 roundId, uint64 autoCashoutAt) external payable {
Round storage round = rounds[roundId];
require(round.status == RoundStatus.ACCEPTING_BETS);
require(msg.value >= MIN_BET && msg.value <= MAX_BET);
bets[roundId][msg.sender] = Bet({
player: msg.sender,
amount: msg.value,
autoCashoutAt: autoCashoutAt,
cashedOut: false,
cashoutMultiplier: 0
});
}
function cashout(uint256 roundId) external {
Round storage round = rounds[roundId];
Bet storage bet = bets[roundId][msg.sender];
require(round.status == RoundStatus.IN_PROGRESS);
require(bet.amount > 0 && !bet.cashedOut);
uint64 currentMultiplier = _getCurrentMultiplier(round.startTime);
require(currentMultiplier <= round.crashPoint, "Already crashed");
bet.cashedOut = true;
bet.cashoutMultiplier = currentMultiplier;
uint256 payout = bet.amount * currentMultiplier / 100;
payable(msg.sender).transfer(payout);
}
function _getCurrentMultiplier(uint64 startTime) public view returns (uint64) {
uint256 elapsed = block.timestamp - startTime;
uint256 multiplier = 100 + (elapsed * elapsed * 2); // quadratic growth
return uint64(multiplier > 100_000 ? 100_000 : multiplier);
}
Problem: manual cashout on-chain has latency. Player clicks → transaction in mempool → included in block (10–12 sec Ethereum). Round can crash. On L2 more acceptable but not ideal.
Low-latency solution: hybrid. Off-chain cashout: player signs request → game server saves signed timestamp → on-chain settlement game server proves player requested before crash. Requires trusting server but with cryptographic accountability.
Risk Management
function maxAllowedBet() public view returns (uint256) {
return address(this).balance * maxBetPercent / 10_000;
}
function availableCapacity(uint256 roundId) public view returns (uint256) {
uint256 maxExposure = address(this).balance * 500 / 10_000; // 5%
uint256 currentExposure = rounds[roundId].totalBets;
return currentExposure < maxExposure ? maxExposure - currentExposure : 0;
}
Timeline
MVP (Chainlink VRF, manual cashout, basic UI): 4–6 weeks. Production (hybrid cashout, batch settlement, bankroll management, regulatory compliance): 10–14 weeks.







