Developing a blockchain Crash game is not just a smart contract with randomness. The core dilemma: how to give players absolute confidence in the fairness of the result without sacrificing speed? Classic solutions on Ethereum L1 drown in gas and delays — a cashout transaction takes 12 seconds, while a crash can occur in 100 ms. L2 networks (Arbitrum, Polygon) and hybrid architectures with off-chain signatures solve this. We bet on provably fair via Chainlink VRF and batch settlement — this allows processing millions of rounds with a fee of less than $0.01 per transaction.
Crash is a game where the multiplier grows from 1x upward and randomly "crashes" at some point. The player must withdraw the bet before the crash. In a decentralized version, the round result must be provably fair: the player can mathematically verify that the multiplier was not rigged. This is achieved via a commitment scheme and on-chain randomness from Chainlink. The cashout architecture is critically important — it determines the economy and user experience. Below we break down the key nodes.
How to implement provably fair in a blockchain Crash game?
Commitment + Reveal scheme (without oracle)
Classic scheme: the operator pre-publishes the hash of the next seed, then reveals the seed after bets close.
contract CrashGame {
struct Round {
bytes32 seedHash;
bytes32 seed;
uint64 crashPoint;
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, "Previous not finished");
rounds[nextRoundId + 1].seedHash = seedHash;
}
function revealAndStart(uint256 roundId, bytes32 seed) external onlyOperator {
Round storage round = rounds[roundId];
require(round.status == RoundStatus.ACCEPTING_BETS, "Wrong status");
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);
emit RoundStarted(roundId, round.crashPoint);
}
}
Problem with commitment scheme: the operator knows the seed in advance and may refuse to reveal an unfavorable one (griefing). Solution — VRF.
Chainlink VRF V2 Plus: trustless random
import {VRFConsumerBaseV2Plus} from "@chainlink/contracts/src/v0.8/vrf/dev/VRFConsumerBaseV2Plus.sol";
contract CrashGame is VRFConsumerBaseV2Plus {
uint256 private immutable s_subscriptionId;
bytes32 private immutable s_keyHash;
mapping(uint256 => uint256) public roundToVrfRequest;
mapping(uint256 => uint256) public vrfRequestToRound;
function closeAndRequestRandom(uint256 roundId) external onlyOperator {
Round storage round = rounds[roundId];
require(round.status == RoundStatus.ACCEPTING_BETS, "Wrong status");
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;
vrfRequestToRound[requestId] = roundId;
}
function fulfillRandomWords(
uint256 requestId,
uint256[] calldata randomWords
) internal override {
uint256 roundId = vrfRequestToRound[requestId];
Round storage round = rounds[roundId];
uint256 rand = randomWords[0];
round.crashPoint = _calculateCrashPoint(rand);
round.seed = bytes32(rand);
emit RoundActive(roundId, round.startTime = uint64(block.timestamp));
}
}
Chainlink VRF documentation: "VRF provides cryptographic proofs that the random number was generated using the block data and the oracle's secret key."
Crash point formula
Crash point math with 1% house edge
Target distribution: P(crash >= X) = 0.99/X. Minimum crash = 1.00x.
function _calculateCrashPoint(uint256 rand) internal pure returns (uint64) {
uint256 h = rand % 1_000_000_000;
if (h < 10_000_000) return 100; // 1.00x
uint256 crashPoint = 990_000_000 * 100 / h;
if (crashPoint < 100) return 100;
if (crashPoint > 100_000) return 100_000;
return uint64(crashPoint);
}
Verification: any player can take the VRF randomWords[0] from on-chain data and reproduce the formula — they will get the same crash point.
Why manual cashout is a bottleneck and how to bypass it?
Bet and cashout mechanics
struct Bet {
address player;
uint256 amount;
uint64 autoCashoutAt;
bool cashedOut;
uint64 cashoutMultiplier;
}
mapping(uint256 => mapping(address => Bet)) public bets;
function placeBet(uint256 roundId, uint64 autoCashoutAt) external payable {
Round storage round = rounds[roundId];
require(round.status == RoundStatus.ACCEPTING_BETS, "Not accepting bets");
require(msg.value >= MIN_BET && msg.value <= MAX_BET, "Invalid amount");
require(bets[roundId][msg.sender].amount == 0, "Already bet");
bets[roundId][msg.sender] = Bet({
player: msg.sender,
amount: msg.value,
autoCashoutAt: autoCashoutAt,
cashedOut: false,
cashoutMultiplier: 0
});
rounds[roundId].totalBets += msg.value;
emit BetPlaced(roundId, msg.sender, msg.value, autoCashoutAt);
}
function cashout(uint256 roundId) external {
Round storage round = rounds[roundId];
Bet storage bet = bets[roundId][msg.sender];
require(round.status == RoundStatus.IN_PROGRESS, "Round not active");
require(bet.amount > 0 && !bet.cashedOut, "No active bet");
uint64 currentMultiplier = _getCurrentMultiplier(round.startTime);
require(currentMultiplier <= round.crashPoint, "Round already crashed");
bet.cashedOut = true;
bet.cashoutMultiplier = currentMultiplier;
uint256 payout = bet.amount * currentMultiplier / 100;
payable(msg.sender).transfer(payout);
emit CashedOut(roundId, msg.sender, currentMultiplier, payout);
}
function _getCurrentMultiplier(uint64 startTime) public view returns (uint64) {
uint256 elapsed = block.timestamp - startTime;
uint256 multiplier = 100 + (elapsed * elapsed * 2);
return uint64(multiplier > 100_000 ? 100_000 : multiplier);
}
Manual cashout on-chain has latency. The player clicks cashout in the UI → transaction goes to mempool → gets included in a block (10–12 sec on Ethereum). During that time, the round may crash. On L2 (Arbitrum: 250 ms, Solana: 400 ms) this is more acceptable, but still not ideal. Transaction fee on Arbitrum is less than $0.01, making frequent cashouts economically viable.
Solution for low-latency: hybrid architecture. Off-chain cashout: player signs a cashout request → game server stores the signed timestamp → during on-chain settlement, the game server proves the player requested cashout before the crash. This requires trust in the game server but with cryptographic accountability.
Off-chain batch settlement
struct CashoutRecord {
address player;
uint64 multiplier;
bytes signature;
}
function settleBatch(
uint256 roundId,
CashoutRecord[] calldata cashouts
) external onlyOperator {
Round storage round = rounds[roundId];
require(round.status == RoundStatus.CRASHED, "Round not crashed");
for (uint i = 0; i < cashouts.length; i++) {
CashoutRecord calldata c = cashouts[i];
Bet storage bet = bets[roundId][c.player];
require(!bet.cashedOut, "Already settled");
require(c.multiplier <= round.crashPoint, "Invalid multiplier");
_verifyCashoutSignature(roundId, c.player, c.multiplier, c.signature);
bet.cashedOut = true;
bet.cashoutMultiplier = c.multiplier;
uint256 payout = bet.amount * c.multiplier / 100;
payable(c.player).transfer(payout);
}
}
Network comparison for blockchain Crash
| Network | Block time | Gas cost (typical) | Suitable for MVP | Summary |
|---|---|---|---|---|
| Ethereum L1 | 12-15 sec | High (~$50/tx) | No | Only for high-rollers due to latency and fees |
| Arbitrum One | ~250 ms | Low ($0.01-$0.05) | Yes | Best balance: speed, security, ecosystem |
| Polygon PoS | ~2 sec | Very low ($0.001) | Yes | Even cheaper, but less decentralized |
| Solana | ~400 ms | Minimal ($0.0001) | Yes | Maximum speed, but Rust development is harder |
Hybrid cashout and batch settlement can save up to $5000 monthly on gas with high player activity. Average fee on Arbitrum — $0.01-0.03 per cashout, making microtransactions profitable.
Our work process
- Analysis and specification — defining mechanics, house edge, RNG, latency requirements.
- Smart contract design — architecture, pattern selection (commitment, VRF, batch settlement).
- Contract implementation — Solidity, Foundry, thorough testing (unit, integration, fork).
- Backend and frontend development — Node.js game server, WebSocket, React with real-time graphics.
- Audit and formal verification — Slither, Echidna, Mythril, third-party auditor if needed.
- Deployment and monitoring — contracts on mainnet, Tenderly setup, dashboards.
- Regulatory compliance — document preparation for license, KYC/AML integration.
| Stage | Duration | Result |
|---|---|---|
| Analysis and specification | 1-2 weeks | Requirements document, UI mockups |
| Contract design | 1-2 weeks | Architecture, ERC implementation |
| Contract implementation | 3-4 weeks | Ready contracts, tests, CI |
| Backend and frontend | 4-6 weeks | Game server, WebSocket, UI |
| Audit and verification | 2-3 weeks | Audit report, fixes |
| Deployment and monitoring | 1 week | Contracts on network, monitoring |
What is included in the work (deliverables)
- Source code of smart contracts (Solidity) with full test suite.
- Detailed documentation on architecture, formulas, verification procedure.
- Game server (Node.js) with WebSocket and contract integration.
- Frontend (React) with visualization and wallet (MetaMask, WalletConnect).
- Deployment and management guide (Tenderly, Etherscan).
- Launch support (24/7 for the first week).
Our experience and metrics
Over 5 years, we have developed blockchain solutions for GameFi. Our portfolio includes 10+ projects, including Crash, Dice, and NFT lotteries. Our contracts have been audited by leading firms and processed over 1 million transactions. Order a smart contract audit before release — it reduces risks by 90%. To launch your own Crash game, contact us for a consultation.
Estimated timelines
- MVP (Chainlink VRF, manual on-chain cashout, basic UI): 4–6 weeks.
- Production (hybrid cashout, batch settlement, bankroll management, regulatory compliance): 10–14 weeks.
Cost is calculated individually based on scope and chosen stack. For an accurate estimate, contact us — we will prepare a commercial proposal. Get a consultation before starting: we'll discuss architecture and KPIs.







