Immersive Poker on the Blockchain: State Channels and NFT Ecosystem
Poker on the blockchain—where hiding cards is critical, but the blockchain is transparent—presents a dilemma for developers: implementing mental poker in a public environment. Without off-chain architecture, gas costs and latency make the game infeasible. Unlike traditional poker where the dealer deals cards face down, every transaction in a smart contract is visible to all nodes. Encryption alone is insufficient; a key controlled by the players is required. Each of the four approaches—mental poker, ZK, TEE, and off-chain server—has its own trade-offs in gas, latency, and trust. We tested all of them and chose the most practical for production: state channels with an off-chain game server and on-chain settlement. This allows processing up to 1000 hands per second with fees under $0.01 per hand on L2—an order of magnitude cheaper than on-chain alternatives.
Fundamental Problem: Hiding Information On-Chain
Approach 1: Mental Poker with Commit-Reveal
The classic cryptographic approach, described by Shamir, Rivest, and Adleman (1979):
- Each player contributes a random seed for shuffling (commit phase).
- The deck is shuffled deterministically from the combination of all seeds.
- Each card is encrypted with each player's key (layered encryption).
- A card is revealed only when all players have revealed their portion of the key.
Problem: With 6 players, each card is encrypted with 6 layers. Revealing requires 6 on-chain transactions. In a 5-street poker hand, that's dozens of transactions per hand. Gas and latency are unacceptable on mainnet.
Approach 2: ZK Proofs for Hidden Cards
ZK-SNARK allows proving "the player holds a card with the required value for payout" without revealing the card itself until showdown. Projects like ZK-Holdem (on Circom) attempt this path. Complexity: ZK circuits for poker rules (full hand evaluation) are non-trivial. Proof generation on mobile devices is slow (seconds); on desktop it's acceptable.
Approach 3: Trusted Execution Environment (TEE)
A dealer service runs in Intel SGX or AWS Nitro Enclave—an isolated environment where even the server operator cannot see the data. Cards are dealt inside the TEE; players see only their own cards through an encrypted channel. Only round results and commitments go on-chain. Trade-off: trust in the TEE manufacturer (Intel). For most gaming applications, this is acceptable—no worse than trusting a casino dealer.
Approach 4: Off-Chain Game Server + On-Chain Settlement
The most practical option for production: a game server maintains game state off-chain, players sign moves (bet, fold, raise) via a state channel, and the final result is recorded on-chain for payout.
Players → [Game Server] → manages hidden cards, game state ↕ signed moves (state channel) ↓ final result + signatures [Settlement Contract] → pays out winners Why State Channels Are Ideal for Poker
State channels are the perfect model for poker. Players open a channel, deposit funds, and all moves are signed off-chain messages. The only on-chain transactions are opening and closing the channel. State channels are 10 times more gas-efficient than on-chain gameplay.
contract PokerStateChannel { struct Channel { address[6] players; uint256[6] deposits; uint256 totalPot; bytes32 stateHash; // hash of current game state uint256 nonce; // move counter ChannelStatus status; } struct PlayerMove { uint8 playerId; MoveType moveType; // BET, RAISE, CALL, FOLD, CHECK uint256 amount; uint256 channelNonce; // must match channel nonce bytes signature; // player signature } function openChannel(address[6] calldata players, uint256[6] calldata deposits) external payable returns (bytes32 channelId) { // Verify all deposits uint256 totalDeposit = 0; for (uint i = 0; i < 6; i++) totalDeposit += deposits[i]; require(msg.value == totalDeposit, "Incorrect deposit"); channelId = keccak256(abi.encode(players, block.timestamp, block.prevrandao)); channels[channelId] = Channel({ players: players, deposits: deposits, totalPot: totalDeposit, stateHash: bytes32(0), nonce: 0, status: ChannelStatus.OPEN }); } function closeChannel( bytes32 channelId, uint256[6] calldata finalBalances, bytes[6] calldata playerSignatures ) external { Channel storage ch = channels[channelId]; require(ch.status == ChannelStatus.OPEN, "Channel not open"); // Verify all player signatures on the final state bytes32 finalStateHash = keccak256(abi.encode(channelId, finalBalances, ch.nonce)); for (uint i = 0; i < 6; i++) { require( ECDSA.recover(ECDSA.toEthSignedMessageHash(finalStateHash), playerSignatures[i]) == ch.players[i], "Invalid player signature" ); } // Payout for (uint i = 0; i < 6; i++) { if (finalBalances[i] > 0) { payable(ch.players[i]).transfer(finalBalances[i]); } } ch.status = ChannelStatus.CLOSED; } } Dispute Mechanism
If the game server disappears or attempts to cheat, the state channel must have dispute resolution. We use a timeout-based mechanism: if a player doesn't receive a response within N blocks, they can initiate a dispute by presenting the last signed state. The counterparty must respond with a newer state. If not, the timeout player wins. We also use forced reveal: at showdown, all active players must reveal cards on-chain within a timeout, otherwise they are considered folded.
Ensuring Honest Shuffling Without Revealing Cards
The game server shuffles the deck. To prove fairness, we use commit-reveal shuffling:
- Before dealing, the server publishes a hash of the seed:
commitment = hash(seed + salt) - Players contribute their entropy
- Final deck = shuffle(seed XOR player_entropy_1 XOR ... XOR player_entropy_N)
- After the game, the server reveals the seed—anyone can verify the shuffle
// Server side const serverSeed = crypto.randomBytes(32) const commitment = keccak256(concat([serverSeed, salt])) await contract.publishCommitment(channelId, commitment) // After all player entropy received: const finalSeed = xorAll([serverSeed, ...playerEntropyContributions]) const deck = shuffleDeck(standardDeck, finalSeed) // deterministic Fisher-Yates // After game: await contract.revealSeed(channelId, serverSeed, salt) // Anyone can verify: hash(serverSeed + salt) == commitment // And: shuffle(standardDeck, serverSeed XOR playerEntropy) == used deck This method provides verifiable fairness without revealing cards until the end of the game.
Game Logic Off-Chain
Poker logic (Texas Hold'em hand evaluation, betting rounds, pot management) is entirely off-chain on the server. The contract only handles: deposit, state commitments, disputes, and payouts.
// Hand evaluator import { Hand } from 'pokersolver' function evaluateHand(holeCards: Card[], communityCards: Card[]): HandResult { const hand = Hand.solve([...holeCards, ...communityCards].map(c => c.toString())) return { rank: hand.rank, name: hand.name, // 'Royal Flush', 'Full House', etc. value: hand.value, cards: hand.cards } } function determineWinner(players: ActivePlayer[], communityCards: Card[]): Winner[] { const hands = players.map(p => ({ player: p, hand: Hand.solve([...p.holeCards, ...communityCards].map(c => c.toString())) })) const winners = Hand.winners(hands.map(h => h.hand)) return winners.map(w => hands.find(h => h.hand === w)!.player) } Which NFT Assets Are in Demand in the Poker Ecosystem?
- Player avatars / profile NFTs. Cosmetic NFTs don't affect gameplay but provide identity and a secondary market. ERC-721 with dynamic metadata (win rate, games played) via tokenURI with on-chain or off-chain data.
- Poker table NFTs. A private table as an NFT: the NFT owner controls table settings (rake %, blinds structure, invite-only) and receives a portion of the rake. Passive income for NFT holders.
- Chip sets and card deck skins. Pure cosmetic, but significant for retention. ERC-1155 for fungible cosmetics.
Tokenomics and Rake
Rake is a commission on each pot, analogous to a casino. 2–5% of the pot is standard. In on-chain poker, rake goes to the protocol treasury. Distribution:
Pot rake (3%) → 50% burn / buyback of game token → 30% staking rewards (stakers = liquidity providers) → 20% development fund Rakeback NFT. Players with a specific NFT receive partial rake back. Incentivizes holding NFT and acts as a token sink (NFTs bought with tokens).
What's Included in the Work
- Architectural documentation (card hiding approach, state channel design)
- Smart contracts (state channel, NFTs, rake) with tests and audit
- Game server on Node.js with poker logic and WebSocket
- Frontend on React with Three.js/Pixi.js and wallet integration (wagmi + WalletConnect v2)
- Integration with TEE or ZK (depending on choice)
- Access to Git repository, deployment to testnet, team training
- 3 months of support after launch
| Stage | Timeline | Result |
|---|---|---|
| Architecture | 1 week | Approach selection, document |
| Smart contracts | 3-4 weeks | Contracts + tests |
| Game server | 3-5 weeks | Server + shuffling |
| Frontend | 4-6 weeks | UI + wallet integration |
| Audit and testnet | 2 weeks | Security review |
| MVP | 3-4 months | Working game |
| Production | 6-9 months | Full ecosystem |
Technology Stack
| Component | Technology |
|---|---|
| Smart contracts | Solidity + Foundry |
| State channels | Nitro Protocol / custom |
| Game server | Node.js + TypeScript |
| Real-time | WebSocket (Socket.io) |
| Frontend | React + Three.js / Pixi.js for table |
| Wallet | wagmi + WalletConnect v2 |
| ZK (if selected) | Circom + snarkjs |
| TEE (if selected) | AWS Nitro Enclaves |
Our team has 5+ years of blockchain development experience and has delivered over 20 projects on Ethereum and L2. Order an MVP from 3 months—let's discuss architecture and timelines. Contact us for a free project assessment.







