npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@b3dotfun/upside-sdk

v0.0.31

Published

Integrating with Upside.win is simple. Your game runs in an iframe on our platform, receives player authentication via JWT, and interacts with the Upside backend through our SDK for bet placement and payout processing.

Readme

Overview

Integrating with Upside.win is simple. Your game runs in an iframe on our platform, receives player authentication via JWT, and interacts with the Upside backend through our SDK for bet placement and payout processing.

Key principle: Your backend handles game logic and state (cards, winners, outcomes), while Upside handles all WIN token transactions.

Integration Flow

```text
Contact: [email protected]
Include: Game name, description, game type, and your backend URL
```
```javascript
import { UpsideProvider } from "@b3dotfun/upside-sdk";

export default function GameApp() {
  return (
    <UpsideProvider>
      <YourGameComponent />
    </UpsideProvider>
  );
}
```
```javascript
{ createB3Client } from "@b3dotfun/upside-sdk/server";

const b3Client = createB3Client();
const betResult = await
b3Client.placeBet(
  "coin-flip", // gameType
  "100000000000000000" // betAmount in wei (1 token = 10^18 wei)
);
```
```javascript
const payoutResult = await b3Client.processPayout(
  "coin-flip",      // gameType
  sessionId,        // unique game session ID
  payoutAmount,     // WIN tokens to award (0 if loss)
  // gameData
  {
    playerChoice: "heads",
    result: "heads",
    outcome: "win"
  }
);
```
The Upside platform automatically:

- Updates the player's WIN balance
- Adds the win/loss to leaderboards
- Sends notifications

SDK Setup

Installation

npm install @b3dotfun/upside-sdk
# or
pnpm add @b3dotfun/upside-sdk
# or
bun install @b3dotfun/upside-sdk

Frontend: UpsideProvider

The UpsideProvider component connects your game frontend to the Upside platform and provides authentication.

import {
  UpsideProvider,
  useUpside,
  useBalance,
  useToken,
  useAuthenticatedFetch,
} from "@b3dotfun/upside-sdk";

export default function CoinFlipGame() {
  return (
    <UpsideProvider>
      <GameContent />
    </UpsideProvider>
  );
}

function GameContent() {
  const { token, balance, showWinModal, showLossModal, refetchBalance } =
    useUpside();

  // Or use individual hooks:
  const balance = useBalance(); // number | null
  const token = useToken(); // string | null
  const authFetch = useAuthenticatedFetch(); // fetch with Authorization header

  return (
    <div>
      <h1>Coin Flip</h1>
      <p>Balance: {(balance / 1e18).toFixed(2)} WIN</p>
      <button onClick={() => playGame(token)}>Play</button>
    </div>
  );
}

Available Hooks:

  • useUpside(): Access full context (token, balance, showWinModal, showLossModal, etc.)
  • useBalance(): Get player balance (number | null)
  • useToken(): Get authentication token (string | null)
  • useRefetchBalance(): Function to request balance refresh
  • useCustomModal(): Function to show custom modals with React components
  • useAuthenticatedFetch(): Fetch function with automatic Bearer token header

Return Values from useUpside():

  • token (string | null): JWT authentication token for backend calls
  • balance (number | null): Current WIN token balance in wei
  • showWinModal(wins: string): Show win modal
  • showLossModal(loss: string): Show loss modal
  • showToast(options): Show toast notification
  • showCustomModal(content): Show custom modal
  • refetchBalance(): Request balance refresh from platform

Making API Calls from Frontend

Use the useAuthenticatedFetch() hook to make authenticated requests:

import {
  useAuthenticatedFetch,
  useBalance,
  useToken,
} from "@b3dotfun/upside-sdk";

function GameComponent() {
  const authFetch = useAuthenticatedFetch();
  const balance = useBalance();
  const token = useToken();

  const playGame = async (prediction) => {
    // Authorization header is automatically added
    const response = await authFetch("/api/game/coin-flip", {
      method: "POST",
      body: JSON.stringify({
        playerId: "player-id",
        prediction,
        betAmount: "1000000000000000000", // 1 WIN in wei
      }),
    });

    const data = await response.json();
    return data;
  };

  return (
    <div>
      <p>Balance: {(balance / 1e18).toFixed(2)} WIN</p>
      <button onClick={() => playGame("heads")}>Predict Heads</button>
    </div>
  );
}

How useAuthenticatedFetch() Works:

  • Automatically includes Authorization: Bearer {token} header
  • Sets Content-Type: application/json by default
  • Merges additional headers you provide
  • Handles token from useToken() automatically

Showing Custom Modals

Use the useCustomModal() hook to display custom modal content in the Upside platform.

import { useCustomModal } from "@b3dotfun/upside-sdk";

function GameComponent() {
  const showCustomModal = useCustomModal();

  const showHelp = () => {
    showCustomModal(
      <div>
        <h2>How to Play</h2>
        <p>Choose heads or tails and place your bet!</p>
        <ul>
          <li>Win: 1.5x your bet</li>
          <li>Loss: Lose your bet</li>
        </ul>
      </div>,
    );
  };

  const showCustomStyledModal = () => {
    showCustomModal({
      content: (
        <div>
          <h2>Special Bonus!</h2>
          <p>You've unlocked a 2x multiplier!</p>
        </div>
      ),
      className: "bonus-modal",
    });
  };

  return (
    <div>
      <button onClick={showHelp}>Show Help</button>
      <button onClick={showCustomStyledModal}>Show Bonus</button>
    </div>
  );
}

Usage:

The hook accepts either:

  1. Simple ReactNode: Pass any React component or JSX directly

    showCustomModal(<div>Simple content</div>);
  2. Options Object: Pass an object with content and optional className

    showCustomModal({
      content: <div>Custom content</div>,
      className: "my-custom-class",
    });

How it works:

  • React components are rendered to static HTML using renderToStaticMarkup
  • The HTML is sent to the parent Upside platform via postMessage
  • The platform displays it in a modal overlay
  • Optional className allows custom styling

Best Practices:

  • Keep modal content simple and focused
  • Use for game rules, achievements, special notifications
  • Avoid complex interactive components (use for display only)
  • CSS classes should match your game's styling theme

Backend: 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}

Testing on Localhost

Quick Testing Setup

You can test your game running on localhost directly in the Upside.win test environment without deploying.

How it works:

  1. Run your game backend on http://localhost:3000 (or any port)
  2. Base64 encode your localhost URL
  3. Navigate to the test URL on upside.win

Step-by-Step

Step 1: Start your game backend

npm run dev
# Game running at http://localhost:3000

Step 2: Base64 encode your URL

Using Node.js:

const url = "http://localhost:3000";
const encoded = Buffer.from(url).toString("base64");
console.log(encoded); // aHR0cDovL2xvY2FsaG9zdDozMDAw

Using command line:

echo -n "http://localhost:3000" | base64
# aHR0cDovL2xvY2FsaG9zdDozMDAw

Online: Use any base64 encoder at https://www.base64encode.org/

Step 3: Test in Upside.win

Visit the test URL:

https://upside.win/test/games/aHR0cDovL2xvY2FsaG9zdDozMDAw

Replace aHR0cDovL2xvY2FsaG9zdDozMDAw with your encoded URL.

Examples

Different localhost URLs:

| URL | Base64 | Test Link | | ----------------------- | ------------------------------ | ------------------------------------------------------------ | | http://localhost:3000 | aHR0cDovL2xvY2FsaG9zdDozMDAw | https://upside.win/test/games/aHR0cDovL2xvY2FsaG9zdDozMDAw | | http://localhost:5000 | aHR0cDovL2xvY2FsaG9zdDo1MDOw | https://upside.win/test/games/aHR0cDovL2xvY2FsaG9zdDo1MDOw | | http://127.0.0.1:3000 | aHR0cDovLzEyNy4wLjAuMTozMDAw | https://upside.win/test/games/aHR0cDovLzEyNy4wLjAuMTozMDAw |

Development Workflow

  1. Create game code - Write your React frontend and Cloudflare Hono backend
  2. Start locally - Run npm run dev on localhost
  3. Generate test URL - Base64 encode your localhost address
  4. Test on Upside - Visit https://upside.win/test/games/<BASE64>
  5. Get JWT & test - Game loads with real JWT for testing
  6. Iterate - Make changes locally and refresh the test URL
  7. Deploy - When ready, deploy backend and update game URL

Tips for Local Testing

  • Use same machine: Keep localhost running while testing
  • Check CORS: Ensure your backend allows requests from upside.win domains

Troubleshooting Local Testing

Problem: "Game not found" or 404

  • Solution: Verify localhost URL is correct and encoding is accurate

Problem: CORS errors

  • Solution: Your backend needs to accept requests from upside.win domain

Problem: JWT errors during testing

  • Solution: Make sure you're using staging API key, not production

Problem: Network requests failing

  • Solution: Check that localhost is running and firewall allows requests

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

Complete Example: Coin Flip Game

Frontend (React)

import { UpsideProvider, useUpsideContext } from "@b3dotfun/upside-sdk";

export default function CoinFlipGame() {
  return (
    <UpsideProvider>
      <CoinFlipContent />
    </UpsideProvider>
  );
}

function CoinFlipContent() {
  const { token, balance, playerId } = useUpsideContext();
  const [gameState, setGameState] = useState("ready"); // ready, playing, won, lost
  const [prediction, setPrediction] = useState(null);
  const [result, setResult] = useState(null);
  const [earnings, setEarnings] = useState(0);

  const playGame = async (playerPrediction) => {
    setPrediction(playerPrediction);
    setGameState("playing");

    try {
      // Call your backend
      const response = await fetch("/api/game/coin-flip", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${token}`,
        },
        body: JSON.stringify({
          playerId,
          prediction: playerPrediction,
          betAmount: "100000000000000000", // 1 token in wei
        }),
      });

      const data = await response.json();

      if (data.outcome === "win") {
        setGameState("won");
        setEarnings(data.payout);
      } else {
        setGameState("lost");
        setEarnings(0);
      }

      setResult(data.result);
    } catch (error) {
      console.error("Game error:", error);
      setGameState("error");
    }
  };

  return (
    <div style={{ textAlign: "center", padding: "40px" }}>
      <h1>Coin Flip</h1>
      <p>Balance: {(balance / 1e18).toFixed(2)} WIN</p>

      {gameState === "ready" && (
        <div>
          <button onClick={() => playGame("heads")}>Predict Heads</button>
          <button onClick={() => playGame("tails")}>Predict Tails</button>
        </div>
      )}

      {gameState === "playing" && <p>Flipping...</p>}

      {gameState === "won" && (
        <div>
          <p>?? You won! Coin landed on {result}</p>
          <p>+{(earnings / 1e18).toFixed(2)} WIN</p>
        </div>
      )}

      {gameState === "lost" && (
        <div>
          <p>? You lost! Coin landed on {result}</p>
          <p>Better luck next time</p>
        </div>
      )}

      {gameState !== "ready" && (
        <button onClick={() => setGameState("ready")}>Play Again</button>
      )}
    </div>
  );
}

Backend (Hono + Cloudflare Workers)

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;
  }
}

Troubleshooting

Common Issues

Problem: placeBet fails with "Insufficient balance"

  • Solution: Check player balance before placing bet, or increase bet amount display in UI

Problem: processPayout returns "session not found"

  • Solution: Verify sessionId matches bet response, check for typos

Problem: Duplicate game sessions or bets

  • Solution: Use same sessionId for retries, implement idempotency on your end

Problem: JWT expires during gameplay

  • Solution: Refresh token before game starts, handle token expiration gracefully

Problem: Game logic runs on client, leading to cheating

  • Solution: Move ALL game logic to backend, client only displays results