Skip to main content

Rock-Paper-Scissors with Conditional Transactions

This comprehensive tutorial walks you through building a Rock-Paper-Scissors game that uses Conditional Transactions (CTX) to keep player moves encrypted until both players have committed. The game automatically decrypts moves onchain and determines the winner without any manual reveal phase.
In a traditional Rock-Paper-Scissors game, if Player 1 submits their move first, Player 2 could see it and counter. Using CTX, both moves remain encrypted until both players commit. The network then automatically decrypts them in the next block, ensuring a fair game.

How the Game Works

The game implements a commit-reveal pattern with automatic decryption using CTX: Game Flow:
This tutorial requires access to a CTX-enabled SKALE chain (SKALE Base or SKALE Base Sepolia). Check Programmable Privacy for current feature availability.
Prerequisites:
  • Node.js 18+ and npm/yarn
  • Wallet browser extension
  • Access to a CTX-enabled SKALE chain
  • Basic knowledge of Solidity and React

Step 1: Clone the Project

Clone the Rock-Paper-Scissors demo repository:
Install Dependencies & Build:
Deploy Contracts: The deployment script deploys both the MockSKL token and the RockPaperScissors game contract.
Update Addresses: After deploying, copy the output addresses to your frontend config/contracts.ts.
Expected Output:
Fund Your Wallet: After deployment, the script outputs the token address. Use a block explorer or cast to call mint() on the MockSKL contract to get tokens for testing.
Project Structure:

Step 2: Smart Contract Overview

The core game logic is in RockPaperScissors.sol. Let’s break down the key components. Game State Structure:
Programmable Privacy Precompile Addresses: The contract imports Precompiled.sol to interact with the Programmable Privacy precompiled contracts:
Precompile Addresses:
  • SUBMIT_CTX (0x...1B): Submits encrypted data for automatic decryption
  • ENCRYPT_TE (0x...1C): Encrypts data using threshold encryption (not used directly - frontend encrypts)
Creating a Game:
What happens:
  1. Player 1’s encrypted move is stored onchain
  2. Wager tokens are transferred to the contract
  3. Game enters Created state with a 1-hour join deadline
Joining a Game & Submitting CTX:
Submitting the CTX:
What happens:
  1. Both encrypted moves are packaged into encryptedArgs
  2. The Game ID is passed as plaintext for callback identification
  3. Precompiled.submitCTX() returns the CTX sender address
  4. 0.06 ETH is transferred to cover CTX execution costs
Handling Decryption Callback:
Resolving the Game:

Step 3: Frontend Setup

Install Dependencies: Navigate to the frontend directory and install dependencies:
The frontend uses the @skalenetwork/bite package for encryption:
Frontend Project Structure:
Key Files:

Step 4: CreateGame Component

The CreateGame.tsx component handles game creation. Here are the key parts: Privacy SDK Initialization:
Encryption Flow:
  1. Move value (1-3) is converted to hex: move.toString(16).padStart(2, '0')
  2. Privacy SDK encrypts the hex string using threshold encryption
  3. Encrypted bytes are returned as a hex string for contract submission
Creating the Game:
Flow Summary:
  1. Check if token approval is needed (allowance vs wager amount)
  2. If needed, approve the contract to spend tokens
  3. Encrypt the move using the Privacy SDK
  4. Call createGame with encrypted move, wager amount, and token address

Step 5: JoinGame Component

The JoinGame.tsx component handles joining an existing game. Key differences from CreateGame: Fetching Game Details:
Joining with CTX Gas Payment:
Key Difference: The joinGame call includes value: CTX_GAS_PAYMENT (0.06 ETH) which is required for the CTX execution.

Step 6: Run the Application

Open your browser to http://localhost:5173 and:
  1. Connect Wallet - Connect your wallet to the SKALE Base Sepolia network
  2. Get Test Tokens - Visit the faucet to get gas tokens
  3. Play a Game - Select your move, set wager amount, and create

Resources: