Skip to main content

createB3Client

Initialize the B3 client in your backend to interact with Upside’s API. Framework Support: Currently supports Hono with Cloudflare Workers.
import { createB3Client } from "@b3dotfun/upside-sdk/server";

// In your Hono route handler
app.post("/api/game/play", async c => {
  // Create B3Client from Hono context
  // Automatically extracts auth token from Authorization header
  const b3Client = createB3Client(c);

  // Now use b3Client for game operations
  const betResult = await b3Client.placeBet("coin-flip", "1000000000000000000");

  return c.json({ success: true, sessionId: betResult.sessionId });
});
Parameters:
  • context (Hono Context): The Hono context object containing:
    • req.header(name: string): Method to extract request headers
    • env (optional): Cloudflare Workers environment
Returns:
  • B3Client instance with authentication automatically configured from the Authorization header
Authentication: The Authorization header is automatically extracted from the incoming request:
Authorization: Bearer {player_token}

Core Functions

placeBet

Start a game session by placing a bet. This locks in the wager and creates a game session.
const betResult = await b3Client.placeBet(
  gameType, // string: "coin-flip", "dice", etc.
  betAmount, // string: bet in wei (e.g., "100000000000000000" = 1 token)
);

// Response:
// {
//   sessionId: "session_abc123",
//   gameId: "game_xyz789",
//   status: "active",
//   createdAt: "2024-01-15T10:30:00Z"
// }
Parameters:
  • gameType (string): Your game’s identifier (e.g., “coin-flip”, “dice-roll”)
  • betAmount (string): Bet amount in wei (1 token = 10^18 wei, e.g., “100000000000000000”)
Returns:
  • sessionId: Unique identifier for this game session
  • gameId: Game record identifier
  • status: Current session status (“active”, “completed”, “failed”)
  • createdAt: ISO timestamp of bet creation
Errors:
  • Insufficient player balance
  • Invalid game type
  • Game not active/enabled
  • Invalid bet amount

processPayout

Complete the game and credit the player’s winnings.
const payoutResult = await b3Client.processPayout(
  gameType, // string: "coin-flip", etc.
  sessionId, // string: from placeBet response
  payoutAmount, // string: WIN to award in wei (0 for loss)
  {
    playerChoice: "heads", // what player chose/predicted
    result: "heads", // actual game result
    outcome: "win", // "win" or "loss"
  },
);

// Response:
// {
//   status: "completed",
//   payoutAmount: "150000000000000000",
//   newBalance: "1350000000000000000",
//   updatedAt: "2024-01-15T10:30:30Z"
// }
Parameters:
  • gameType (string): Same game type from placeBet
  • sessionId (string): Session ID from placeBet response
  • payoutAmount (string): WIN tokens to credit in wei (0 for losses, e.g., “150000000000000000” = 1.5 tokens)
  • metadata (object):
    • playerChoice: What the player chose/predicted
    • result: The actual game outcome
    • outcome: “win” or “loss”
Returns:
  • status: “completed”, “failed”, etc.
  • payoutAmount: Amount credited in wei
  • newBalance: Player’s updated WIN balance in wei
  • updatedAt: ISO timestamp of completion
Errors:
  • Session not found
  • Session already completed (duplicate request)
  • Payout exceeds pool limits
  • Invalid bet amount format

Backend Example

import { Hono } from "hono";
import { createB3Client } from "@b3dotfun/upside-sdk/server";

const app = new Hono();

app.post("/api/game/coin-flip", async c => {
  const { prediction, betAmount } = await c.req.json();

  try {
    // Create B3Client from Hono context
    // Automatically extracts auth token from Authorization header
    const b3Client = createB3Client(c);

    // Step 1: Place the bet (amount in wei)
    const betResult = await b3Client.placeBet("coin-flip", betAmount);

    if (!betResult.sessionId) {
      return c.json({ error: "Failed to place bet" }, 400);
    }

    // Step 2: Game logic - flip coin
    const coin = Math.random() < 0.5 ? "heads" : "tails";
    const isWin = coin === prediction;
    // Calculate payout: 50% profit on win (betAmount * 1.5, in wei)
    const payout = isWin ? (BigInt(betAmount) * BigInt(150)) / BigInt(100) : "0";

    // Step 3: Store game in your database (D1, etc.)
    // await db.execute(
    //   "INSERT INTO games (sessionId, prediction, result, betAmount, payout) VALUES (?, ?, ?, ?, ?)",
    //   [betResult.sessionId, prediction, coin, betAmount, payout.toString()]
    // );

    // Step 4: Process payout
    const payoutResult = await b3Client.processPayout("coin-flip", betResult.sessionId, payout.toString(), {
      playerChoice: prediction,
      result: coin,
      outcome: isWin ? "win" : "loss",
    });

    // Step 5: Return result to frontend
    return c.json({
      sessionId: betResult.sessionId,
      prediction,
      result: coin,
      outcome: isWin ? "win" : "loss",
      payout: isWin ? payout.toString() : "0",
      newBalance: payoutResult.newBalance,
    });
  } catch (error) {
    console.error("Game error:", error);
    return c.json({ error: error.message }, 500);
  }
});

export default app;
Key Differences from Express:
  • createB3Client(c) extracts auth automatically from Hono context
  • Runs on Cloudflare Workers (serverless)
  • No need for manual middleware - Hono context handles everything
  • Response uses c.json() instead of res.json()
Environment Setup (wrangler.toml):
[env.production]
name = "upside-games"
route = "example.com/api/*"
zone_id = "..."
account_id = "..."

[[env.production.env.vars]]
# Add any environment variables here

Best Practices

Bet Placement

  • Always validate amounts: Check bet is within player balance
  • Use idempotency: Retry failed placeBet calls with the same sessionId
  • Lock immediately: Once placeBet succeeds, prevent player from placing another bet

Game Logic

  • Backend is source of truth: Never trust client-side game outcomes
  • Store everything: Log all game events for audits and disputes
  • Validate results: Ensure game outcome matches expected range
  • Timeout games: Cancel bets if no payout is processed within 5 minutes

Payout Processing

  • Process once: Only call processPayout once per game session
  • Use correct amounts: Verify payout calculation before sending
  • Handle duplicates: If processPayout returns “already completed”, that’s OK
  • Handle failures: Retry failed payouts, but check if already paid first

Security

  • Verify tokens: Always validate JWT in every backend request
  • Use HTTPS: All communication must be encrypted
  • Validate game types: Only allow known, approved game types
  • Rate limit: Implement rate limiting to prevent abuse

Error Handling

async function safePlayGame(gameType, betAmount) {
  try {
    // Place bet
    let betResult;
    try {
      betResult = await b3Client.placeBet(gameType, betAmount);
    } catch (error) {
      if (error.message.includes("Insufficient balance")) {
        return { error: "Player balance too low" };
      }
      throw error;
    }

    // Run game logic
    const outcome = await runGameLogic();

    // Process payout
    try {
      const payout = outcome.isWin ? betAmount * 1.5 : "0";
      await b3Client.processPayout(gameType, betResult.sessionId, payout, {
        playerChoice: outcome.choice,
        result: outcome.result,
        outcome: outcome.isWin ? "win" : "loss",
      });
    } catch (error) {
      if (error.message.includes("already completed")) {
        console.log("Payout already processed for session");
      } else {
        throw error;
      }
    }

    return outcome;
  } catch (error) {
    console.error("Game error:", error);
    throw error;
  }
}