logoCheckmate
Reference

Troubleshooting

Common issues and solutions when using the Chess Game SDK

Troubleshooting

This guide covers common issues you might encounter when integrating the Chess Game SDK and their solutions.

Common Error Codes

Game State Errors

GameNotWaiting (6003)

Error: Game is not in waiting state

Cause: Trying to join a game that has already started or finished

Solution:

// Check game status before joining
const gameAccount = await fetchGameAccount(rpc, gameAccountPDA);
if (gameAccount.data.gameStatus !== GameStatus.WaitingForPlayer) {
  console.log("Game is not available for joining");
  return;
}

GameNotActive (6005)

Error: Game is not active

Cause: Trying to make moves in a game that hasn't started or has finished

Solution:

// Verify game is in progress
if (gameData.gameStatus !== GameStatus.InProgress) {
  throw new Error("Cannot make moves - game is not active");
}

GameAlreadyFinished (6008)

Error: Game already finished

Cause: Attempting actions on a completed game

Solution:

// Check if game is still ongoing
if (gameData.gameStatus === GameStatus.Finished) {
  console.log("Game has already ended");
  // Redirect to game results or claim winnings
  return;
}

Player and Turn Errors

NotPlayerTurn (6006)

Error: Not player's turn

Cause: Player trying to move when it's opponent's turn

Solution:

// Validate turn before making move
const isPlayerTurn = (
  (gameData.currentTurn === PieceColor.White && player.equals(gameData.whitePlayer)) ||
  (gameData.currentTurn === PieceColor.Black && player.equals(gameData.blackPlayer))
);

if (!isPlayerTurn) {
  console.log("Wait for your turn");
  return;
}

NotGamePlayer (6004)

Error: Player is not part of this game

Cause: Non-participant trying to interact with the game

Solution:

// Verify player participation
const isParticipant = (
  player.equals(gameData.whitePlayer) || 
  player.equals(gameData.blackPlayer)
);

if (!isParticipant) {
  throw new Error("You are not a player in this game");
}

CannotJoinOwnGame (6015)

Error: Cannot join your own game

Cause: Game creator trying to join their own game

Solution:

// Check if player is the creator
if (player.equals(gameData.whitePlayer)) {
  console.log("Cannot join your own game");
  return;
}

Move Validation Errors

InvalidMove (6007)

Error: Invalid move

Cause: Move violates chess rules or game constraints

Solution:

import { ChessUtils } from "@sendarcade/checkmate";

const utils = new ChessUtils();

// Validate move before sending transaction
const isValidMove = utils.isValidMove(
  gameData.board,
  fromSquare,
  toSquare,
  gameData.currentTurn
);

if (!isValidMove) {
  console.log("Invalid move - please try again");
  return;
}

MoveExposesKing (6012)

Error: Move would leave king in check

Cause: Attempted move would put own king in check

Solution:

// Check if move leaves king in check
const wouldExposeKing = utils.wouldExposeKing(
  gameData.board,
  fromSquare,
  toSquare,
  gameData.currentTurn
);

if (wouldExposeKing) {
  console.log("Cannot make move - would expose king to check");
  return;
}

NoPieceAtSquare (6017)

Error: No piece at source square

Cause: Trying to move from an empty square

Solution:

// Verify piece exists at source square
const piece = utils.getPieceAt(gameData.board, fromSquare);
if (!piece) {
  console.log("No piece at selected square");
  return;
}

NotYourPiece (6018)

Error: Not your piece

Cause: Trying to move opponent's piece

Solution:

// Check piece ownership
const piece = utils.getPieceAt(gameData.board, fromSquare);
if (piece && piece.color !== gameData.currentTurn) {
  console.log("Cannot move opponent's piece");
  return;
}

Economic Errors

InsufficientFunds (6002)

Error: Insufficient funds to create game

Cause: Player doesn't have enough tokens for entry fee

Solution:

// Check token balance before creating game
const tokenAccount = await getAssociatedTokenAddress(
  tokenMint,
  player.publicKey
);

const balance = await connection.getTokenAccountBalance(tokenAccount);
if (balance.value.uiAmount < entryFeeAmount) {
  console.log("Insufficient funds for entry fee");
  return;
}

EntryFeeTooHigh (6001)

Error: Entry fee is too high

Cause: Entry fee exceeds platform limits

Solution:

// Validate entry fee against platform limits
const MAX_ENTRY_FEE = 1000 * 1e6; // 1000 USDC
if (entryFee > MAX_ENTRY_FEE) {
  console.log("Entry fee exceeds maximum allowed");
  return;
}

AlreadyClaimed (6025)

Error: Player has already claimed winnings

Cause: Attempting to claim winnings multiple times

Solution:

// Check if winnings already claimed
if (gameData.winningsClaimed) {
  console.log("Winnings have already been claimed");
  return;
}

Draw System Errors

DrawOffersNotAllowed (6026)

Error: Draw offers are not allowed in this game

Cause: Game was created with draw offers disabled

Solution:

// Check if draw offers are allowed
if (!gameData.allowDrawOffers) {
  console.log("Draw offers are not allowed in this game");
  return;
}

NoDrawOfferPending (6029)

Error: No draw offer pending

Cause: Trying to accept/reject when no draw offer exists

Solution:

// Check for pending draw offer
if (!gameData.drawOffer.pending) {
  console.log("No draw offer to respond to");
  return;
}

Network and RPC Issues

Magicblock Ephemeral Rollup

Issue: Cannot fetch game data during active gameplay

Cause: Game is delegated to Ephemeral Rollup but using wrong RPC

Solution:

// Use appropriate RPC based on game status
const getGameData = async (gameAccountPDA: Address) => {
  try {
    // Try standard RPC first
    const gameAccount = await fetchGameAccount(rpc, gameAccountPDA);
    
    if (gameAccount.exists && gameAccount.data.gameStatus === GameStatus.InProgress) {
      // Game is in Ephemeral Rollup, use ER RPC
      return await fetchGameAccount(erRpc, gameAccountPDA);
    }
    
    return gameAccount;
  } catch (error) {
    console.error("Failed to fetch game data:", error);
    throw error;
  }
};

Transaction Failures

Issue: Transactions failing with "Transaction simulation failed"

Cause: Various reasons including insufficient funds, invalid state, or network issues

Solution:

const buildAndSendTransaction = async (
  rpc: Rpc,
  instructions: TransactionInstruction[],
  signer: TransactionSigner
) => {
  try {
    // Build transaction with recent blockhash
    const { blockhash } = await rpc.getLatestBlockhash().send();
    
    const transaction = pipe(
      createTransaction({ version: 0 }),
      (tx) => setTransactionFeePayer(signer.address, tx),
      (tx) => setTransactionLifetimeUsingBlockhash(blockhash, tx),
      (tx) => appendTransactionInstructions(instructions, tx)
    );

    // Sign and send
    const signedTransaction = await signTransaction([signer], transaction);
    const signature = await rpc.sendTransaction(signedTransaction).send();
    
    // Wait for confirmation
    await rpc.confirmTransaction(signature, { commitment: "confirmed" }).send();
    
    return signature;
  } catch (error) {
    console.error("Transaction failed:", error);
    
    // Retry logic for network issues
    if (error.message.includes("blockhash not found")) {
      console.log("Retrying with fresh blockhash...");
      // Implement retry logic
    }
    
    throw error;
  }
};

SDK Integration Issues

Import Errors

Issue: Cannot import SDK modules

Solution:

// Correct import syntax
import { ChessGameSDK, ChessUtils } from "@sendarcade/checkmate";
import { address, Rpc } from "@solana/kit";

// For CommonJS environments
const { ChessGameSDK } = require("@sendarcade/checkmate");

Type Errors

Issue: TypeScript compilation errors

Solution:

// Ensure proper type imports
import type { 
  GameAccount, 
  PieceColor, 
  GameStatus,
  TransactionSigner 
} from "@sendarcade/checkmate";

// Use proper type assertions
const gameId = BigInt(1); // Not just 1
const integratorId = address("YourIntegratorId"); // Use address() helper

Debugging Strategies

Enable Detailed Logging

// Add comprehensive logging
const makeMove = async (fromSquare: string, toSquare: string) => {
  console.log(`Attempting move: ${fromSquare} -> ${toSquare}`);
  console.log(`Current turn: ${gameData.currentTurn}`);
  console.log(`Player: ${player.toString()}`);
  
  try {
    const { instruction } = await chessSDK.makeMoveIx({
      player,
      gameId,
      integratorId,
      fromSquare,
      toSquare
    });
    
    console.log("Instruction created successfully");
    
    const signature = await buildAndSendTransaction(rpc, [instruction], signer);
    console.log(`Move successful: ${signature}`);
    
  } catch (error) {
    console.error("Move failed:", {
      error: error.message,
      fromSquare,
      toSquare,
      gameId: gameId.toString(),
      player: player.toString()
    });
    throw error;
  }
};

Game State Validation

// Comprehensive game state checker
const validateGameState = (gameData: GameAccount) => {
  const issues = [];
  
  if (!gameData.whitePlayer) {
    issues.push("Missing white player");
  }
  
  if (!gameData.blackPlayer && gameData.gameStatus !== GameStatus.WaitingForPlayer) {
    issues.push("Missing black player for active game");
  }
  
  if (gameData.moveCount < 0 || gameData.moveCount > 1000) {
    issues.push(`Invalid move count: ${gameData.moveCount}`);
  }
  
  if (issues.length > 0) {
    console.warn("Game state issues:", issues);
  }
  
  return issues.length === 0;
};

Error Recovery Patterns

// Implement retry logic for transient failures
const withRetry = async <T>(
  operation: () => Promise<T>,
  maxRetries: number = 3,
  delay: number = 1000
): Promise<T> => {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error) {
      if (attempt === maxRetries) {
        throw error;
      }
      
      console.log(`Attempt ${attempt} failed, retrying in ${delay}ms...`);
      await new Promise(resolve => setTimeout(resolve, delay));
      delay *= 2; // Exponential backoff
    }
  }
  
  throw new Error("Max retries exceeded");
};

// Usage
const gameData = await withRetry(() => 
  fetchGameAccount(rpc, gameAccountPDA)
);

Performance Optimization

Efficient State Updates

// Batch multiple state updates
const updateGameState = async (gameAccountPDA: Address) => {
  const [gameAccount, playerStats] = await Promise.all([
    fetchGameAccount(rpc, gameAccountPDA),
    fetchPlayerStats(player)
  ]);
  
  // Update UI with both pieces of data
  updateGameUI(gameAccount.data);
  updatePlayerUI(playerStats);
};

Caching Strategies

// Simple game state cache
class GameStateCache {
  private cache = new Map<string, { data: GameAccount; timestamp: number }>();
  private readonly TTL = 5000; // 5 seconds
  
  async getGameState(gameAccountPDA: Address): Promise<GameAccount> {
    const key = gameAccountPDA.toString();
    const cached = this.cache.get(key);
    
    if (cached && Date.now() - cached.timestamp < this.TTL) {
      return cached.data;
    }
    
    const gameAccount = await fetchGameAccount(rpc, gameAccountPDA);
    this.cache.set(key, {
      data: gameAccount.data,
      timestamp: Date.now()
    });
    
    return gameAccount.data;
  }
}

Getting Help

If you're still experiencing issues:

  1. Check the Console: Look for detailed error messages and stack traces
  2. Verify Network: Ensure you're using the correct RPC endpoints
  3. Update Dependencies: Make sure you're using the latest SDK version
  4. Review Examples: Check the SDK Examples for reference implementations
  5. Community Support: Join our Discord or GitHub discussions for community help

Common Gotchas

  • Always use BigInt() for game IDs and numeric values
  • Check game status before performing actions
  • Use the correct RPC endpoint based on game state
  • Validate moves client-side before sending transactions
  • Handle both chess rule violations and blockchain errors
  • Remember that some operations require specific player roles (creator vs joiner)