Players don't trust online slots: the server algorithm can be tweaked, RTP changed overnight. Blockchain slots solve this—every spin is recorded in a smart contract, and the result is immutable. But developing such a contract requires accounting for gas, VRF speed, and bonus mechanics. Here's how we built a classic 5-reel slot with 20 paylines. Order blockchain slot development to attract trust-driven players.
Why blockchain changes the rules in slots
A traditional slot runs on server software with a closed RNG. Players take it on faith. A blockchain slot publishes the contract, each spin calls it, and the result is fixed. However, challenges arise: gas costs, VRF latency, and complexity of bonus mechanics. We solve these by optimizing the contract and choosing the right L2.
How the Slots smart contract works
contract BlockchainSlots is VRFConsumerBaseV2Plus {
// Virtual reel strips
// Index = position on strip, value = symbol (0-8)
uint8[64] public reel1Strip;
uint8[64] public reel2Strip;
uint8[64] public reel3Strip;
uint8[64] public reel4Strip;
uint8[64] public reel5Strip;
// Pay table: symbol combination -> multiplier (in basis points)
mapping(bytes32 => uint256) public payTable;
struct SpinRequest {
address player;
uint256 betAmount;
uint256 lines; // number of active paylines
}
mapping(uint256 => SpinRequest) public pendingSpins;
function spin(uint256 lines) external payable returns (uint256 requestId) {
require(lines >= 1 && lines <= 20, "Invalid lines");
require(msg.value >= MIN_BET * lines, "Insufficient bet");
requestId = _requestRandomWords(3); // 3 random words
pendingSpins[requestId] = SpinRequest({
player: msg.sender,
betAmount: msg.value,
lines: lines
});
}
function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) internal override {
SpinRequest memory spinReq = pendingSpins[requestId];
delete pendingSpins[requestId];
// Determine reel positions from random number
uint8[5] memory reelPositions;
reelPositions[0] = uint8(randomWords[0] % 64);
reelPositions[1] = uint8((randomWords[0] >> 8) % 64);
reelPositions[2] = uint8((randomWords[0] >> 16) % 64);
reelPositions[3] = uint8(randomWords[1] % 64);
reelPositions[4] = uint8((randomWords[1] >> 8) % 64);
// Get symbols for 3 rows of each reel
uint8[5][3] memory grid = _buildGrid(reelPositions);
// Calculate payout across all active paylines
uint256 totalPayout = _calculatePayout(grid, spinReq.betAmount, spinReq.lines);
if (totalPayout > 0) {
payable(spinReq.player).transfer(totalPayout);
}
emit SpinResult(spinReq.player, reelPositions, grid, totalPayout, requestId);
}
function _buildGrid(uint8[5] memory positions) internal view returns (uint8[5][3] memory grid) {
// For each reel, take 3 consecutive symbols (wrap-around)
for (uint i = 0; i < 5; i++) {
uint8 pos = positions[i];
grid[i][0] = _getSymbol(i, (pos + 63) % 64); // row above
grid[i][1] = _getSymbol(i, pos); // middle row
grid[i][2] = _getSymbol(i, (pos + 1) % 64); // row below
}
}
function _calculatePayout(
uint8[5][3] memory grid,
uint256 betAmount,
uint256 activeLines
) internal view returns (uint256 payout) {
uint256 betPerLine = betAmount / activeLines;
// Check each payline
for (uint l = 0; l < activeLines; l++) {
uint8[5] memory line = _getPayline(l, grid);
uint256 lineMultiplier = _getLineMultiplier(line);
if (lineMultiplier > 0) {
payout += (betPerLine * lineMultiplier) / 100;
}
}
}
}
How Chainlink VRF guarantees fairness
Chainlink VRF is an oracle that returns a random number along with a cryptographic proof. The proof is verified in the contract: neither developer nor player can influence the outcome. Unlike traditional PRNG, VRF is fully transparent. In practice, we use VRF v2, which is cheaper and supports subscription-based liquidity. As per the Chainlink VRF v2 documentation, each request includes a proof verifiable on-chain. The average fee per VRF v2 request is about 0.0001 LINK.
What bonus mechanics can be implemented
function _checkBonusFeatures(uint8[5][3] memory grid) internal pure
returns (bool hasFreeSpin, uint256 freeSpinCount, bool hasBonusGame)
{
uint256 scatterCount = 0;
for (uint col = 0; col < 5; col++) {
for (uint row = 0; row < 3; row++) {
if (grid[col][row] == SCATTER_SYMBOL) scatterCount++;
}
}
if (scatterCount >= 3) {
hasFreeSpin = true;
freeSpinCount = scatterCount == 3 ? 10 : scatterCount == 4 ? 15 : 20;
}
// Bonus game on 3+ bonus symbols on payline 1
hasBonusGame = _checkBonusLine(grid);
}
Slots without bonus mechanics are not competitive. Essential features: Free Spins (scatter symbols trigger a series of free spins with increased multiplier), Wild symbols (substitute any symbol to form winning combinations), Multiplier Wilds (with ×2, ×3 multipliers), Expanding Wilds (expand across the entire reel), and Bonus games (pick-me mechanic). All these mechanics are implemented at the smart contract level, eliminating any possibility of manipulation.
Comparison table: traditional vs blockchain slots
| Parameter | Traditional Slot | Blockchain Slot |
|---|---|---|
| Result generation | Server, PRNG | On-chain VRF |
| Transparency | No | Yes (verifiable on blockchain) |
| Manipulation protection | No | Mathematically guaranteed |
| Spin speed | Instant | 3–15 seconds (depends on network) |
| Commission | None (paid by casino) | Network gas ($0.001 to $0.50) |
| Token integration | No | ERC-20/NFT |
Example pay table for symbols
| Symbol | 3 in line | 4 in line | 5 in line |
|---|---|---|---|
| Wild | 100 | 500 | 2500 |
| 7 | 50 | 200 | 1000 |
| BAR | 25 | 100 | 500 |
| Cherry | 10 | 40 | 150 |
Our workflow on the project
- Analytics and math — aligning RTP, pay tables, probabilities.
- Smart contract design — choosing template, VRF, bonus mechanics.
- Solidity development — contract with Foundry, unit test coverage.
- Frontend integration — animations (Pixi.js/Three.js) + Web3 wallet.
- Testing and audit — static analysis (Slither), fuzzing (Echidna), testnet.
- Deployment and launch — choosing L2 (Arbitrum/Polygon), contract verification.
Gas optimization: how we reduce spin cost
The main challenge is gas cost. We use 64-position strips instead of 32, pack multiple random numbers into one uint256, and use a payTable mapping with keccak256 for fast lookup. This saves up to 30% gas compared to a naive implementation. Deploying on L2 (e.g., Polygon or Arbitrum) reduces fees by an order of magnitude. With these measures, the average cost per spin on Polygon is around $0.01.
What's included in the result
- Smart contract with VRF, pay table, and bonus functions.
- Frontend with spin animation and wallet integration.
- Code documentation and deployment instructions.
- Test documentation and audit report.
- Launch support.
Estimated timelines
Development of a full-fledged slot (5 reels, 20 lines, Free Spins, Wild) takes 4 to 6 weeks for the smart contract and another 4–8 weeks for the frontend. Duration depends on the complexity of bonus mechanics and the chosen network.
Our team has years of experience in blockchain development, with over 20 gaming projects deployed on Ethereum, BNB Chain, and Polygon. We guarantee the fairness of the mathematical calculations and contract transparency. Get a consultation — we'll evaluate your project and propose an optimal solution. Contact us to discuss your slot details and find the most efficient architecture.







