Skip to content

Latest commit

 

History

History
386 lines (275 loc) · 10.2 KB

File metadata and controls

386 lines (275 loc) · 10.2 KB

Chess Game SDK: Integration Guide

This guide explains how to integrate the Solana Chess Game SDK into your own game client or dApp — from installing the SDK to creating games, joining, making moves, and handling results.

1. Install the SDK

Install the SDK package in your TypeScript / JavaScript project:

npm install @checkmate/sdk @solana/kit @solana-program/token

Or with Yarn:

yarn add @checkmate/sdk @solana/kit @solana-program/token

Or with pnpm:

pnpm add @checkmate/sdk @solana/kit @solana-program/token

2. Import and Initialize

Import the SDK and create an instance:

import { ChessGameSDK } from "@checkmate/sdk";
import { Rpc, KeyPairSigner, createNoopSigner, address } from "@solana/kit";

// Create RPC clients for both standard Solana and Ephemeral Rollup
const rpc = new Rpc("https://api.mainnet-beta.solana.com");
const erRpc = new Rpc("https://api.rollup.example.com"); // Ephemeral Rollup RPC

// Create a signer from your wallet
const signer = /* your wallet's signer implementation */;

// Initialize the SDK
const chessSDK = new ChessGameSDK();

Tip: In production, integrate with your Solana wallet adapter that supports @solana/kit.


3. Initialize an Integrator (Admin Only)

The admin must run this once to set up an integrator configuration:

import { address } from "@solana/kit";

const params = {
  integrator: signer, // TransactionSigner
  integratorId: address("YourIntegratorId"), // Address
  feeBasisPoints: 500, // 5%
  feeVault: address("YourFeeVaultAddress"), // Address
};

const { instruction, integratorConfigPDA } =
  await chessSDK.initializeIntegratorIx(params);

// Build and send the transaction
const signature = await buildAndSendTransaction(rpc, [instruction], signer);

console.log(`Integrator config created at: ${integratorConfigPDA}`);

Note: Only run this ONCE per integrator setup.


4. Create a New Game

Any player can create a new game:

import { address } from "@solana/kit";

const createParams = {
  rpc, // Rpc<GetAccountInfoApi>
  creator: signer, // TransactionSigner
  integratorId: address("YourIntegratorId"), // Address
  tokenMint: address("TokenMintAddress"), // Address, USDC address
  entryFee: BigInt(1_000_000), // 1 USDC as bigint
  timeControl: {
    initialTime: 600, // 10 minutes
    increment: 5, // 5 seconds increment
    moveTimeLimit: null,
  },
  ratedGame: true,
  allowDrawOffers: true,
};

const { instruction, gameId, gameAccountAddress } = await chessSDK.createGameIx(
  createParams
);

// Build and send the transaction
const signature = await buildAndSendTransaction(rpc, [instruction], signer);

console.log(`Game created with ID: ${gameId}`);
console.log(`Game account address: ${gameAccountAddress}`);

5. Join an Existing Game

Players join open games by ID:

import { address } from "@solana/kit";

const joinParams = {
  player: signer, // TransactionSigner
  gameId: BigInt(1), // bigint
  integratorId: address("YourIntegratorId"), // Address
  tokenMint: address("TokenMintAddress"), // Address
};

const { instruction, gameAccountAddress } = await chessSDK.joinGameIx(
  joinParams
);

// Build and send the transaction
const signature = await buildAndSendTransaction(rpc, [instruction], signer);

console.log("Joined game as black player");

6. Make a Move

Your client should handle player turns. Example: Move pawn from e2 to e4

import { none, some } from "@solana/kit";

const moveParams = {
  player: signer, // TransactionSigner
  gameId: BigInt(1), // bigint
  integratorId: address("YourIntegratorId"), // Address
  fromSquare: "e2", // Can be string notation or number index
  toSquare: "e4", // Can be string notation or number index
  promotionPiece: none(), // Optional: some(PieceType.Queen) for promotion
};

const { instruction } = await chessSDK.makeMoveIx(moveParams);

// Build and send the transaction
const signature = await buildAndSendTransaction(rpc, [instruction], signer);

7. Claim Winnings

When a game ends, the winner (or both, if drawn) can claim the prize pool:

const claimParams = {
  rpc, // Rpc<GetAccountInfoApi>
  player: signer, // TransactionSigner
  gameId: BigInt(1), // bigint
  integratorId: address("YourIntegratorId"), // Address
  tokenMint: address("TokenMintAddress"), // Address
};

const { instruction } = await chessSDK.claimWinningsIx(claimParams);

// Build and send the transaction
const signature = await buildAndSendTransaction(rpc, [instruction], signer);

console.log("Winnings claimed!");

8. Draw Offers & Forfeits

  • Offer Draw:
const drawParams = {
  player: signer, // TransactionSigner
  gameId: BigInt(1), // bigint
  integratorId: address("YourIntegratorId"), // Address
};

const { instruction } = await chessSDK.offerDrawIx(drawParams);

// Build and send the transaction
const signature = await buildAndSendTransaction(rpc, [instruction], signer);
  • Accept Draw:
const acceptParams = {
  player: signer, // TransactionSigner
  gameId: BigInt(1), // bigint
  integratorId: address("YourIntegratorId"), // Address
};

const { instruction } = await chessSDK.acceptDrawIx(acceptParams);

// Build and send the transaction
const signature = await buildAndSendTransaction(rpc, [instruction], signer);
  • Reject Draw:
const rejectParams = {
  player: signer, // TransactionSigner
  gameId: BigInt(1), // bigint
  integratorId: address("YourIntegratorId"), // Address
};

const { instruction } = await chessSDK.rejectDrawIx(rejectParams);

// Build and send the transaction
const signature = await buildAndSendTransaction(rpc, [instruction], signer);
  • Forfeit Game:
const forfeitParams = {
  player: signer, // TransactionSigner
  gameId: BigInt(1), // bigint
  integratorId: address("YourIntegratorId"), // Address
};

const { instruction } = await chessSDK.forfeitGameIx(forfeitParams);

// Build and send the transaction
const signature = await buildAndSendTransaction(rpc, [instruction], signer);

9. Get Game State & Display

Fetch state to show the board:

import { getGameAccountPDA, fetchGameAccount } from "@checkmate/sdk";

// Get the game account PDA
const [gameAccountPDA] = await getGameAccountPDA(
  address("YourIntegratorId"),
  BigInt(1)
);

// Fetch the game account data
// Use the appropriate RPC based on game status (see Magicblock Note section)
const gameAccount = await fetchGameAccount(rpc, gameAccountPDA);

// Access the game data
const gameData = gameAccount.data;

console.log("Game status:", gameData.gameStatus);
console.log("Current turn:", gameData.currentTurn);
console.log("Board state:", gameData.board);

Combine this with your frontend chess engine to render the current position.


10. Magicblock Note

  • For a smooth gaming experience, our program integrates with Magicblock's Ephemeral Rollup. When the second player joins the game, we call and execute the joinGameIx instruction. After that, the game status changes to InProgress, and the GameAccount is delegated to the Ephemeral Rollup environment. To read data from the GameAccount, you need to switch your RPC to the Ephemeral Rollup RPC.
// Check game status and use appropriate RPC
const [gameAccountPDA] = await getGameAccountPDA(
  address("YourIntegratorId"),
  BigInt(1)
);

// First try with standard RPC
const maybeGameAccount = await fetchGameAccount(rpc, gameAccountPDA);

// If game is in progress, use Ephemeral Rollup RPC
if (
  maybeGameAccount.exists &&
  maybeGameAccount.data.gameStatus === GameStatus.InProgress
) {
  // Use Ephemeral Rollup RPC
  const gameAccount = await fetchGameAccount(erRpc, gameAccountPDA);
  // Process game data...
} else {
  // Use standard RPC data
  // Process game data...
}
  • When the game finishes (for example, when one player achieves checkmate or a draw offer is accepted), the game status changes to Finished. The GameAccount will then be committed and undelegated back to Solana. To read GameAccount data at this point, you can use the standard RPC.
  • Integrators should always check the game status to determine whether the GameAccount is in the Ephemeral Rollup environment or not, in order to choose the correct RPC endpoint for reading data.

11. Validate Legal Moves (Optional)

To show possible moves for a selected piece:

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

// Create a chess utils instance
const utils = new ChessUtils();

// Get legal moves from the current board state
const legalMoves = utils.getLegalMoves(gameData.board, gameData.currentTurn);

// Get legal moves for a specific piece (e2 square)
const e2Index = utils.squareToIndex("e2");
const legalMovesFromE2 = legalMoves.filter((move) => move.from === e2Index);

console.log(
  "Legal moves from e2:",
  legalMovesFromE2.map((m) => utils.indexToSquare(m.to))
);

12. Use Utilities

Use built-in ChessUtils:

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

const utils = new ChessUtils();

// Convert between square notation and indices
const index = utils.squareToIndex("e4"); // 28
const notation = utils.indexToSquare(28); // "e4"

// Get piece information
const piece = utils.getPieceAt(gameData.board, index);
console.log(`Piece at e4: ${piece?.type} (${piece?.color})`);

// Display board in console
utils.displayBoard(gameData.board, "Current Position");

13. Handle Errors

Wrap calls in try/catch and use the improved error handling in @solana/kit:

import { ChessGameError, GameNotFoundError } from "@checkmate/sdk";

try {
  const { instruction } = await chessSDK.makeMoveIx({
    player: signer,
    gameId: BigInt(1),
    integratorId: address("YourIntegratorId"),
    fromSquare: "e2",
    toSquare: "e5", // Invalid move
  });

  // Build and send transaction...
} catch (error) {
  if (error instanceof GameNotFoundError) {
    console.error("Game not found:", error.message);
  } else if (error instanceof ChessGameError) {
    console.error("Chess game error:", error.message);
  } else {
    console.error("Unknown error:", error);
  }
}