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.







