Documentation
Complete Tutorial

Complete Tutorial

In this tutorial, we will create a Basic Casino Site using Express.js, which will handle various endpoints for game sessions, balance updates, bet placement, and outcome verification. We will also implement security measures such as request signature verification to ensure the integrity of the API.

Prerequisites

Before getting started, make sure you have the following dependencies installed:

  • Node.js
  • Express.js
  • crypto
  • util
  • tsscmp

You can install these dependencies by running the following command:

npm install express crypto util tsscmp

Setting up the Project

Create a new directory for your project and navigate to it in your terminal. Initialize a new Node.js project by running the following command:

npm init -y

This will create a new package.json file in your project directory.

Next, create a new file called index.js and open it in your preferred code editor. We will write our API code in this file.

Importing Dependencies

To start, let's import the required dependencies at the top of the index.js file:

const http = require("http");
const crypto = require("crypto");
const util = require("util");
const express = require("express");
const tsscmp = require("tsscmp");

Here, we import the http module to create an HTTP server, the crypto module for cryptographic functions, the util module for utility functions, the express module for building the API, and the tsscmp module for secure string comparison.

Initializing Express App and Server

Next, let's initialize our Express app and create an HTTP server using the Express app:

const app = express();
const server = http.createServer(app);

This sets up our Express app and creates an HTTP server using the app.

Configuration Variables

We will define some configuration variables for our API. Update the following placeholders with your own values:

const GAME_PLATFORM_API_URL_SESSION = "<SESSION_URL>";
const GAME_PLATFORM_API_SESSION_SECRET = "<SESSION_SECRET>";
const GAME_PLATFORM_API_WEBHOOK_SECRET = "<WEBHOOK_SECRET>";

Replace <SESSION_URL> with the URL for the game session API, <SESSION_SECRET> with a secret key for session verification, and <WEBHOOK_SECRET> with a secret key for webhook verification. These values will be used later in the code.

Database Mock

For demonstration purposes, let's create a simple in-memory database mock that stores client information, including their balance and bets:

const db = [{ id: "test-client-id", balance: 238, bets: [] }];

This is just a mock implementation. In a real application, you would use a proper database to store and retrieve client data.

Helper Functions

We will define some helper functions that will be used throughout the API implementation. Add the following helper functions:

const timeNow = () => Math.floor(Date.now() / 1000);
 
const sha256 = (body, secret) => {
  const hmac = crypto.createHmac("sha256", body);
  return secret ? hmac.update(secret).digest("hex") : hmac.digest("hex");
};
 
const verifyGameResult = (transactionId, serverSeed, result) => {
  const calculateFloats = (hex) => {
    const CHUNK_SIZE = 8;
    const floats = [];
 
    while (hex.length >= CHUNK_SIZE) {
      const hexChunk = hex.substring(0, CHUNK_SIZE);
 
      hex = hex.substring(CHUNK_SIZE);
 
      const int = parseInt(hexChunk, 16);
 
      const float = int / 2 ** 32;
 
      floats.push(float);
    }
 
    return floats;
  };
 
  const bet = db
    .find((client) =>
      client.bets.find((bet) => bet.transactionId === transactionId)
    )
    .bets.find((bet) => bet.transactionId === transactionId);
 
  if (bet.serverSeedHashed !== sha256(serverSeed)) {
    throw new Error("Server seed hash does not match");
  }
 
  const floats = calculateFloats(
    sha256(`${bet.clientSeed}:${serverSeed}:${bet.operatorSeed}`)
  );
 
  // Game event translation
  const myResult = (floats[0] * 10001) / 100;
 
  if (result !== myResult) {
    throw new Error("Game result does not match");
  }
 
  console.log(`✅ Game [${transactionId}] result verified successfully!`);
};
 
const randomSecureString = async () => {
  const LENGTH = 5;
 
  const promisifiedRandomBytes = util.promisify(crypto.randomBytes);
 
  const buffer = await promisifiedRandomBytes(LENGTH);
 
  return buffer.toString("hex");
};
 
const createGameSignature = (timestamp, body, secret) => {
  const VERSION_NUMBER = "v1";
 
  return `${VERSION_NUMBER}=${sha256(
    [VERSION_NUMBER, String(timestamp), JSON.stringify(body)].join(":"),
    secret
  )}`;
};
 
const verifyGameRequest = (gameSignature, timestamp, body, secret) => {
  if (!gameSignature || !timestamp || !body) {
    throw new Error("Missing parameters");
  }
 
  const mySignature = createGameSignature(timestamp, body, secret);
 
  const FIVE_MINUTES = 60 * 5;
 
  if (Math.abs(timeNow() - timestamp) > FIVE_MINUTES) {
    // Potential replay attack, so let's ignore it.
    throw new Error("Request is no longer valid");
  }
 
  // Using a library to prevent a potential timing-based attack.
  if (!tsscmp(gameSignature, mySignature)) {
    throw new Error("Signature does not match");
  }
};

These functions provide utility for various tasks such as generating timestamps, hashing with SHA256, verifying game results, generating random secure strings, creating and verifying signatures, and handling game request verification.

API Endpoints

Now, let's implement the API endpoints for game sessions, balance updates, bet placement, and outcome verification.

Home Route

Let's define a simple home route that serves an HTML file. Create a file called index.html in the same directory as index.js and add some content to it to show the game properly.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Hexzard Mock Operator</title>
    <style>
      .container {
        display: flex;
        width: 100%;
        height: 100%;
        flex-direction: column;
      }
      html,
      body {
        height: 100vh;
        background-color: #a52a2a;
      }
 
      * {
        margin: 0;
        padding: 0;
      }
 
      #hexzard {
        flex-grow: 1;
        border: none;
        /* RECOMMENDED: set the iframe visibility to hidden by default to hide a white screen flash while loading. */
        visibility: hidden;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h1>Mock Casino Platform</h1>
      <!-- STEP 1: Add iframe to the template -->
      <iframe id="hexzard" src="https://www.hexzard.com/games/dice"></iframe>
    </div>
    <script>
      const GAME_HOST = "https://www.hexzard.com";
 
      // STEP 2: Listen for the ready event from the iframe
      window.addEventListener(
        "message",
        async (event) => {
          if (event.origin !== GAME_HOST) return;
 
          // Handle 'init' event by sending config with session token.
          if (event.data.type === "init") {
            const sessionId = await retrieveSessionToken();
 
            const message = {
              sessionId,
              languageCode: "en",
              currencyCode: "USD",
              minimumBet: 0.01,
              theme: {
                // Optionally pass theme variables here.
              },
            };
 
            event.source.postMessage(message, GAME_HOST);
            // Handle 'ready' event by making the iframe visible.
          } else if (event.data.type === "ready") {
            // Make iframe visible once the theme has been painted.
            document.querySelector("#hexzard").style.visibility = "visible";
          }
        },
        false
      );
 
      // STEP 3: Fetch the session token from your backend
      async function retrieveSessionToken() {
        const res = await fetch("/session", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
        });
 
        const data = await res.json();
 
        const sessionId = data?.result?.data?.json?.createdSession;
 
        if (res.status !== 200 || !sessionId) {
          throw new Error("Failed to retrieve a session id");
        }
 
        return sessionId;
      }
    </script>
  </body>
</html>

Next, add the following route handler to serve the index.html file:

app.get("/", (req, res) => {
  res.sendFile(__dirname + "/index.html");
});

Game Session Endpoint

Let's implement an endpoint for initializing a game session. Add the following route handler:

app.post("/session", async (req, res) => {
  // Retrieve your client's unique identifier
  const clientId = db[0].id;
 
  const createdSession = await initializeGameSession(
    GAME_PLATFORM_API_URL_SESSION,
    clientId
  );
 
  if (!createdSession) throw Error("Unable to create a session");
 
  res.json({
    result: {
      data: {
        json: {
          createdSession,
        },
      },
    },
  });
});

This route handler expects a POST request to /session and retrieves the client's unique identifier from the database mock. It then calls the initializeGameSession function to create a game session using the provided URL and client ID. If the session is created successfully, the response will include the created session data in the JSON format.

Balance Webhook Endpoint

Let's implement an endpoint for handling balance update webhooks. Add the following route handler:

app.post("/balance", (req, res) => {
  verifyGameRequest(
    req.headers["x-hexzard-signature"],
    Number(req.headers["x-hexzard-request-timestamp"]),
    req.body.json,
    GAME_PLATFORM_API_WEBHOOK_SECRET
  );
 
  const { clientId } = req.body.json;
 
  const client = db.find((client) => client.id === clientId);
 
  res.json({
    result: {
      data: {
        json: {
          balance: client.balance,
        },
      },
    },
  });
});

This route handler expects a POST request to /balance and verifies the request signature using the verifyGameRequest function. It then retrieves the client ID from the request body and fetches the corresponding client from the database mock. The response includes the client's balance in the JSON format.

Bet Webhook Endpoint

Let's implement an endpoint for handling bet placement webhooks. Add the following route handler:

app.post("/bet", async (req, res) => {
  verifyGameRequest(
    req.headers["x-hexzard-signature"],
    Number(req.headers["x-hexzard-request-timestamp"]),
    req.body.json,
    GAME_PLATFORM_API_WEBHOOK_SECRET
  );
 
  const {
    betAmount,
    gameName,
    transactionId,
    clientId,
    clientSeed,
    serverSeedHashed,
  } = req.body.json;
 
  const clientIndex = db.findIndex((client) => client.id === clientId);
 
  let betAllowed = false;
  let operatorSeed = null;
 
  if (db[clientIndex].balance >= betAmount) {
    betAllowed = true;
    operatorSeed = await randomSecureString();
    db[clientIndex].balance -= betAmount;
    db[clientIndex].bets.push({
      transactionId,
      betAmount,
      gameName,
      clientSeed,
      serverSeedHashed,
      operatorSeed,
    });
  }
 
  res.json({
    result: {
      data: {
        json: {
          betAllowed,
          operatorSeed,
        },
      },
    },
  });
});

This route handler expects a POST request to /bet and verifies the request signature using the verifyGameRequest function. It then retrieves the necessary information for placing a bet from the request body, including the bet amount, game name, transaction ID, client ID, client seed, and hashed server seed. The handler checks if the client has enough balance to place the bet. If allowed, it generates a secure platform seed and adds the bet to the client's record in the database mock. The response includes the bet allowance status and the generated platform seed in the JSON format.

Outcome Webhook Endpoint

Let's implement an endpoint for handling outcome verification webhooks. Add the following route handler:

app.post("/outcome", (req, res) => {
  verifyGameRequest(
    req.headers["x-hexzard-signature"],
    Number(req.headers["x-hexzard-request-timestamp"]),
    req.body.json,
    GAME_PLATFORM_API_WEBHOOK_SECRET
  );
 
  const { transactionId, multiplier, serverSeed, result } = req.body.json;
 
  verifyGameResult(transactionId, serverSeed, result);
 
  const clientIndex = db.findIndex((client) =>
    client.bets.find((bet) => bet.transactionId === transactionId)
  );
 
  const betIndex = db[clientIndex].bets.findIndex(
    (bet) => bet.transactionId === transactionId
  );
 
  db[clientIndex].balance +=
    db[clientIndex].bets[betIndex].betAmount * multiplier;
  db[clientIndex].bets.splice(betIndex, 1);
 
  res.sendStatus(200);
});

This route handler expects a POST request to /outcome and verifies the request signature using the verifyGameRequest function. It retrieves the transaction ID, multiplier, server seed, and game result from the request body. The handler then calls the verifyGameResult function to verify the game result. If the verification is successful, it updates the client's balance and removes the corresponding bet from the database mock. The response is set to 200 to indicate a successful outcome verification.

Starting the Server

Finally, let's start the server by listening on a specific port. Add the following code at the end of the index.js file:

const port = process.env.PORT || 3000;
 
server.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

This code listens on the port specified by the PORT environment variable. If it's not set, it defaults to port 3000. When the server starts, it logs a message indicating the port it's running on.

Running the API

To run the API, execute the following command in your project directory:

node index.js

This will start the server, and you should see a message indicating the port the server is running on.

Congratulations! You have successfully built a Game Platform API with Express.js. You can now test the API endpoints using tools like Postman or by making HTTP requests from your applications. Remember to adapt the code to your specific use case and connect it to your database for persistent data storage.


Hexzard Docs