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.
Install the SDK package in your TypeScript / JavaScript project:
npm install @checkmate/sdk @solana/kit @solana-program/tokenOr with Yarn:
yarn add @checkmate/sdk @solana/kit @solana-program/tokenOr with pnpm:
pnpm add @checkmate/sdk @solana/kit @solana-program/tokenImport 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.
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.
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}`);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");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);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!");- 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);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.
- 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
joinGameIxinstruction. After that, the game status changes to InProgress, and theGameAccountis delegated to the Ephemeral Rollup environment. To read data from theGameAccount, 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
GameAccountwill then be committed and undelegated back to Solana. To readGameAccountdata at this point, you can use the standard RPC. - Integrators should always check the game status to determine whether the
GameAccountis in the Ephemeral Rollup environment or not, in order to choose the correct RPC endpoint for reading data.
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))
);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");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);
}
}