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

@edge_protocol/bankroll-sdk

v1.9.17

Published

TypeScript SDK for Edge Protocol Bankroll - Build games without writing Solana programs

Readme

@edge_protocol/bankroll-sdk

TypeScript SDK for the Edge Protocol Bankroll program on Solana. Build casino-style games without deploying your own Solana program.

Installation

npm install @edge_protocol/bankroll-sdk

Package Exports

| Import path | Use in | Description | |---|---|---| | @edge_protocol/bankroll-sdk | Browser + Server | Core client, types, PDA helpers, wager ID generation | | @edge_protocol/bankroll-sdk/server | Server only | NodeWallet, connection/keypair singletons, getSdkClient() | | @edge_protocol/bankroll-sdk/routes | Server only | Ready-made Next.js route handlers for place-bet, settle-bet, pool-info |

Quick Start (Next.js Game)

A new game needs three things: environment variables, three API route files, and a game hook that uses BankrollApiClient.

1. Environment Variables

# Solana / Pool Configuration
NEXT_PUBLIC_RPC_ENDPOINT=https://api.devnet.solana.com
NEXT_PUBLIC_POOL_ADDRESS=<pool-pubkey>
NEXT_PUBLIC_GAME_CONFIG_ADDRESS=<game-config-pubkey>
NEXT_PUBLIC_USDC_MINT=<usdc-mint-pubkey>
NEXT_PUBLIC_FEE_TREASURY=<fee-treasury-pubkey>
GAME_KEYPAIR_SECRET=<base58-encoded-game-secret-key>

# Game Identity (required)
EDGE_GAME_NAME=<your-game-name>    # e.g. "dice", "slots", "roulette"

EDGE_GAME_NAME is required. This identifies your game to the Edge Protocol team for support and troubleshooting. Use a short, lowercase, unique name for your game (e.g. dice, slots, roulette).

If you're using EdgeBankrollClient directly instead of the route handlers, pass gameName in the constructor:

const client = new EdgeBankrollClient({
  gameKeypair,
  bankrollProgramId: program.programId,
  program,
  gameName: "my-game",
});

2. API Routes

// src/app/api/place-bet/route.ts
import { handlePlaceBet } from "@edge_protocol/bankroll-sdk/routes";
export async function POST(request: Request) { return handlePlaceBet(request); }
// src/app/api/settle-bet/route.ts
import { handleSettleBet } from "@edge_protocol/bankroll-sdk/routes";
export async function POST(request: Request) { return handleSettleBet(request); }
// src/app/api/pool-info/route.ts
import { handlePoolInfo } from "@edge_protocol/bankroll-sdk/routes";
export async function GET(request: Request) { return handlePoolInfo(request); }

3. Browser-Side Client

import { BankrollApiClient, generateWagerId } from "@edge_protocol/bankroll-sdk";

const bankroll = new BankrollApiClient();

// Fetch pool info (max bet, utilization, etc.)
const poolInfo = await bankroll.getMaxBet();

// Place a bet — returns a partially-signed transaction for the player's wallet to co-sign
const wagerId = generateWagerId("dice");
const tx = await bankroll.placeBet({
  player: walletPublicKey,
  amount: 1_000_000, // 1 USDC (6 decimals)
  wagerId,
});

// Sign and send. Use sendAndConfirmIdempotent to avoid the "already been
// processed" error caused by React Strict Mode, double-clicks, or stale
// tx closures resubmitting the same signed transaction.
import { sendAndConfirmIdempotent } from "@edge_protocol/bankroll-sdk";
const signed = await wallet.signTransaction(tx);
const sig    = await sendAndConfirmIdempotent(connection, signed);

// Settle — fully server-side, auto-batches if multiple settlements
await bankroll.settleBet({
  player: walletPublicKey,
  wagerId,
  betAmount: 1_000_000,
  payoutAmount: 2_000_000, // player won 2x
  gameData: JSON.stringify({ result: "win", roll: 18 }),
});

4. Local Dev with MockBankrollClient

Use MockBankrollClient to develop without a Solana connection. It has the same interface as BankrollApiClient.

import { BankrollApiClient, MockBankrollClient } from "@edge_protocol/bankroll-sdk";

const isDevnet = process.env.NEXT_PUBLIC_RPC_ENDPOINT?.includes("devnet");

const bankroll = isDevnet
  ? new BankrollApiClient()
  : new MockBankrollClient({
      reserveMultiplier: 3,   // e.g. 3:1 max payout
      poolTotalAssets: 10_000_000_000,
      maxBetBps: 500,          // 5% of pool
      maxUtilizationBps: 8000, // 80%
    });

API Reference

Core (@edge_protocol/bankroll-sdk)

EdgeBankrollClient

Low-level client for direct on-chain interaction. Used server-side.

import { EdgeBankrollClient, createBankrollProgram } from "@edge_protocol/bankroll-sdk";

const program = createBankrollProgram(connection, wallet);
const client = new EdgeBankrollClient({
  gameKeypair,
  bankrollProgramId: program.programId,
  program,
  gameName: "my-game",
});

Methods:

| Method | Description | |---|---| | placeBet(params) | Build, sign, and submit a place-bet transaction | | settleBet(params) | Build, sign, and submit a settle-bet transaction | | settleBets(params) | Settle multiple bets, auto-batching into transactions (max 2 per tx due to Solana size limit) | | buildPlaceBetInstruction(params) | Build a place-bet instruction without signing (for partial-sign flows) | | buildSettleBetInstruction(params) | Build a settle-bet instruction without signing (for batching) | | getGameConfig() | Fetch the on-chain game config | | getPool() | Fetch the pool account | | getActiveBet(wagerId) | Get an active bet by wager ID (null if not found) | | getMaxBet() | Calculate the maximum bet the pool can support for this game |

settleBets

Auto-batches settle instructions into multiple transactions (max 2 instructions per tx) to stay under Solana's 1232-byte transaction limit. Returns an array of transaction signatures.

const signatures = await client.settleBets({
  settlements: [
    { player, playerTokenAccount, wagerId: "bj_1", betAmount: new BN(1000000), payoutAmount: new BN(2000000), gameData: '{"result":"win"}' },
    { player, playerTokenAccount, wagerId: "bj_2", betAmount: new BN(500000), payoutAmount: new BN(0), gameData: '{"result":"bust"}' },
    { player, playerTokenAccount, wagerId: "bj_3", betAmount: new BN(1000000), payoutAmount: new BN(1000000), gameData: '{"result":"push"}' },
  ],
  payer: gameKeypair,
});
// signatures.length === 2 (first tx has 2 settlements, second has 1)

getMaxBet

Returns MaxBetInfo with the maximum bet amount considering:

  • Game config max_bet_bps (% of pool)
  • Pool utilization and reserved assets
  • Reserve multiplier calculated from payout tiers
const info = await client.getMaxBet();
// info.maxBet: BN — max bet in token smallest units
// info.reserveMultiplier: number — payout tier multiplier
// info.poolTotalAssets: BN
// info.poolReservedAssets: BN
// info.poolAvailableLiquidity: BN
// info.poolUtilizationBps: number
// info.isPaused: boolean
// info.isGameActive: boolean

BankrollApiClient

Browser-side client that calls your game's API routes (/api/place-bet, /api/settle-bet, /api/pool-info).

| Method | Returns | Description | |---|---|---| | placeBet(params) | Transaction | Partially-signed transaction for the player's wallet | | placeBets(player, bets[]) | Transaction | Multiple place-bet instructions in one transaction | | settleBet(params) | string | Transaction signature | | settleBets(player, settlements[]) | string | Last transaction signature | | getMaxBet() | PoolInfo | Pool info (max bet, utilization, etc.) | | generateWagerId(prefix) | string | Unique wager ID (max 32 chars) |

MockBankrollClient

Drop-in replacement for BankrollApiClient for local development. Tracks bets in memory with configurable pool parameters.

new MockBankrollClient({
  reserveMultiplier?: number,    // default: 1
  poolTotalAssets?: number,      // default: 10_000_000_000
  maxBetBps?: number,            // default: 500 (5%)
  maxUtilizationBps?: number,    // default: 8000 (80%)
})

generateWagerId(prefix?)

Generate a unique wager ID (max 32 chars). Available as standalone function and as a method on both client classes.

import { generateWagerId } from "@edge_protocol/bankroll-sdk";
const id = generateWagerId("poker"); // e.g. "poker_1709654321_a1b2"

createBankrollProgram(connection, wallet?)

Create an Anchor Program instance with the bundled IDL. No manual IDL loading needed.

sendAndConfirmIdempotent(connection, signedTx, opts?)

Idempotent send + confirm for a pre-signed transaction. Use this in any code path that calls connection.sendRawTransaction on a pre-signed tx — it eliminates the "Transaction simulation failed: This transaction has already been processed" error that surfaces when the same signed tx is submitted twice.

When duplicate sends happen:

  • React Strict Mode double-invoke during dev
  • Double-clicks before an isLoading flag flips
  • A tx reference captured in a stale closure
  • Wallet-adapter retries
  • Hot-reload re-running an effect

How it works:

  1. Solana signatures are deterministic over (blockhash + feePayer + instructions + signers), so a fully-signed tx already carries its final on-chain signature in its first-signer slot.
  2. Pre-checks getSignatureStatus(sig, { searchTransactionHistory: true }) — if the tx already landed, returns the signature without re-sending.
  3. Otherwise sends + confirms.
  4. If a concurrent send races us into "already been processed" between pre-check and submit, recovers the deterministic signature and confirms it.

Drop-in replacement for the typical sign + send pattern:

import { sendAndConfirmIdempotent } from "@edge_protocol/bankroll-sdk";

// Before — vulnerable to duplicate submits:
const signed = await wallet.signTransaction(partialTx);
const sig    = await connection.sendRawTransaction(signed.serialize());
await connection.confirmTransaction(sig, "confirmed");

// After — idempotent:
const signed = await wallet.signTransaction(partialTx);
const sig    = await sendAndConfirmIdempotent(connection, signed, {
  commitment: "confirmed", // optional, defaults to 'confirmed'
});

Inside a React signAndSend hook:

const signAndSend = useCallback(
  async (partialTx) => {
    if (!partialTx) return "mock";
    if (!signTransaction) throw new Error("Wallet does not support signing");
    const signed = await signTransaction(partialTx);
    return sendAndConfirmIdempotent(connection, signed);
  },
  [signTransaction, connection]
);

Where to use it:

| Code path | Use? | Why | |---|---|---| | Client-side signAndSend in React hooks | Yes | Strict Mode, double-clicks, and stale closures cause the most dups here | | Anywhere you call connection.sendRawTransaction(rawTx) with a pre-signed tx | Yes | One extra getSignatureStatus is cheap insurance | | EdgeBankrollClient.placeBet / settleBet (server-side) | Not needed by default | They use Anchor's provider.sendAndConfirm, which signs internally and doesn't auto-retry. Wrap manually only if you have your own retry logic above. | | BankrollApiClient.placeBet (browser → server route) | No | This just fetches a partial tx; the actual send happens in your signAndSend | | Server route handlers (/api/place-bet, /api/settle-bet) | No (build) / Yes (/api/settle-bet if you wrap retries) | /api/place-bet only builds; /api/settle-bet calls EdgeBankrollClient.settleBets |

Options:

sendAndConfirmIdempotent(connection, signedTx, {
  commitment: "finalized", // confirmation level — defaults to 'confirmed'
});

Throws if the tx isn't signed (no first-signer signature to derive from).

Don't disable preflight. The default preflight simulation is what surfaces program errors (insufficient funds, account constraint failures, decoded Anchor errors) before the tx is broadcast — and Sight uses those simulation logs for the failed-tx enrichment path. Pass sendOptions only if you understand what you're turning off.

PDA Helpers

import {
  findPoolPda,
  findGameConfigPda,
  findActiveBetPda,
  findBetEscrowPda,
  findSettledBetPda,
  findVaultPda,
  findPlayerAccountPda,
  findPlayerEscrowTokenPda,
  findMetadataPda,
} from "@edge_protocol/bankroll-sdk";

BankrollIDL

The bundled Bankroll IDL JSON. Import directly if needed for custom Anchor usage.

Server (@edge_protocol/bankroll-sdk/server)

Server-side utilities for Next.js API routes. Uses module-level singletons for connection, keypair, and program instances.

| Export | Description | |---|---| | NodeWallet | Minimal wallet adapter that works with Next.js ESM bundling | | decodeKeypair(base58) | Decode a base58-encoded secret key into a Keypair | | getConnection() | Singleton Connection from NEXT_PUBLIC_RPC_ENDPOINT | | getGameKeypair() | Singleton Keypair from GAME_KEYPAIR_SECRET | | getProgram() | Singleton Anchor Program with bundled IDL | | getPoolAddress() | PublicKey from NEXT_PUBLIC_POOL_ADDRESS | | getGameConfigAddress() | PublicKey from NEXT_PUBLIC_GAME_CONFIG_ADDRESS | | getAssetMint() | PublicKey from NEXT_PUBLIC_USDC_MINT | | getFeeTreasury() | PublicKey from NEXT_PUBLIC_FEE_TREASURY | | getSdkClient() | Singleton EdgeBankrollClient ready to use |

Routes (@edge_protocol/bankroll-sdk/routes)

Pre-built route handlers using standard web Request/Response (no Next.js dependency). Wire them directly into Next.js App Router routes.

handlePlaceBet(request: Request): Promise<Response>

POST /api/place-bet

Request body (single bet):

{ "player": "<pubkey>", "wagerId": "dice_123", "amount": 1000000 }

Request body (multiple bets):

{ "player": "<pubkey>", "bets": [{ "wagerId": "bj_1", "amount": 1000000 }, { "wagerId": "bj_2", "amount": 500000 }] }

Returns { "transaction": "<base64>" } — a partially-signed transaction for the player to co-sign.

handleSettleBet(request: Request): Promise<Response>

POST /api/settle-bet

Request body (single settlement):

{ "player": "<pubkey>", "wagerId": "dice_123", "betAmount": 1000000, "payoutAmount": 2000000, "gameData": "{\"result\":\"win\"}" }

Request body (batch):

{ "player": "<pubkey>", "settlements": [{ "wagerId": "bj_1", "betAmount": 1000000, "payoutAmount": 0, "gameData": "{}" }] }

Returns { "signature": "<tx-sig>", "signatures": ["<tx-sig-1>", "<tx-sig-2>"] }. Auto-batches into multiple transactions if needed.

handlePoolInfo(request: Request): Promise<Response>

GET /api/pool-info

Returns:

{
  "maxBet": 180000,
  "reserveMultiplier": 3,
  "totalAssets": 1805155080000,
  "reservedAssets": 0,
  "availableLiquidity": 1805155080000,
  "utilizationBps": 0,
  "isPaused": false,
  "isGameActive": true
}

Architecture

Browser (React)                          Server (Next.js API Routes)
┌─────────────────────┐                  ┌──────────────────────────┐
│  BankrollApiClient   │─── /api/place-bet ──▶│  handlePlaceBet()       │
│  or MockBankrollClient│─── /api/settle-bet ─▶│  handleSettleBet()      │
│                     │─── /api/pool-info ──▶│  handlePoolInfo()       │
└─────────────────────┘                  └────────────┬─────────────┘
                                                      │
                                                      │ EdgeBankrollClient
                                                      │ (signs with game keypair)
                                                      ▼
                                         ┌──────────────────────────┐
                                         │  Edge Bankroll Program   │
                                         │  (on-chain Solana)       │
                                         │  - Manages reserves      │
                                         │  - Enforces house edge   │
                                         │  - Handles payouts       │
                                         └────────────┬─────────────┘
                                                      │
                                                      ▼
                                         ┌──────────────────────────┐
                                         │  Liquidity Pool          │
                                         │  - LP token holders      │
                                         │  - Insurance fund        │
                                         └──────────────────────────┘

Place bet flow: Browser builds request → server builds transaction and partial-signs with game keypair → browser receives transaction → player's wallet co-signs and submits.

Settle bet flow: Browser sends settlement data → server builds + fully signs + submits transaction(s) via settleBets() auto-batching.

Security

  • Game keypair is your authentication credential. Store it in environment variables, never expose client-side.
  • Place-bet transactions are partially signed by the game keypair server-side. The player's wallet signs and submits.
  • Settle-bet transactions are fully signed server-side by the game keypair. Only your server can settle bets for your game.

License

MIT