Skip to main content

Integration

This page shows the common setup for using @suigar/sdk inside a frontend, backend, or game aggregator.

If you want a working reference before wiring your own product, use the live SDK playground: playground.suigar.com

What this page covers

This page focuses on the shared integration path:

  • client setup
  • live parameter reads
  • transaction building
  • transport or wallet handoff
  • PvP lobby reads
  • event decoding

Per-game pages explain the inputs and constraints that are specific to one game.

Install

pnpm add @suigar/sdk @mysten/sui @mysten/bcs

Runtime requirements:

  • Node.js ^22.18.0 || >=24
  • ESM project setup ("type": "module")
  • @mysten/sui v2
  • @mysten/bcs v2

This SDK targets Sui TypeScript SDK 2.0+ only. Follow the official Sui 2.0 migration guide if your app still uses the pre-2.0 client API.

Create a Suigar-enabled Sui client

import { SuiGrpcClient } from '@mysten/sui/grpc';
import { suigar } from '@suigar/sdk';

export function createSuigarClient(
network: 'mainnet' | 'testnet',
options: {
name?: string;
partner?: string;
cacheTtl?: number;
} = {},
) {
const baseUrl = network === 'mainnet' ? 'https://fullnode.mainnet.sui.io:443' : 'https://fullnode.testnet.sui.io:443';

return new SuiGrpcClient({
baseUrl,
network,
}).$extend(suigar(options));
}

The SDK uses the connected client network to resolve:

  • Suigar package ids
  • Suigar registry ids
  • supported coin types
  • price info object ids

This means your app usually does not need to keep its own map of package ids or registry ids. The SDK already knows the expected network-specific values for the current supported networks.

The extension registers under client.suigar by default. If your app needs a different property name, pass name during registration:

const client = createSuigarClient('mainnet', {
name: 'games',
});

client.games.tx;
client.games.bcs;

Partner setup

If you are a partner, this setup should be treated as required. Do it when the extension is registered, before any transaction builder is used.

const client = createSuigarClient('mainnet', {
partner: '0xpartner_wallet_address',
});

Do this:

  • partner must be the partner wallet address that should receive attribution onchain
  • the SDK injects the partner wallet into supported metadata automatically
  • this applies to both standard game transactions and PvP Coinflip flows

Do not do this:

  • do not pass a partner slug, brand name, or display label
  • do not wait until createBetTransaction(...) or createPvPCoinflipTransaction(...) to think about partner attribution
  • do not try to override attribution with manual metadata.partner or metadata.referrer

The reason to keep it here:

  • one registration point covers the whole SDK client
  • every supported builder reuses the same partner attribution setup
  • manual partner metadata patching is more error-prone and the reserved keys are ignored with a warning anyway

Build a standard game transaction

If you are a partner, the only extra setup is still the extension registration above. The transaction builder call itself stays clean.

const client = createSuigarClient('mainnet');

const tx = client.suigar.tx.createBetTransaction('coinflip', {
owner: '0x123',
coinType: '0x2::sui::SUI',
stake: 1_000_000_000n,
side: 'heads',
});

What the SDK handles for you:

  • creates the transaction
  • sets the transaction sender from owner and applies the default gas budget
  • normalizes the wallet address and coin type
  • withdraws the coin being wagered
  • injects the correct Suigar package id for the selected game
  • injects the matching price info object for the selected coin
  • transfers the reward object back to owner
  • prepends partner attribution metadata automatically when the extension was registered with partner

What your app still owns:

  • collecting the user’s chosen game inputs
  • validating product-specific rules before build
  • signing and executing the transaction
  • displaying execution state and result data

Serialize for wallet or transport

If you need the built transaction as base64 bytes:

const base64 = await client.suigar.serializeTransactionToBase64(tx);

This is useful when the signing step happens somewhere else.

Common cases:

  • a browser app builds locally, then hands the transaction to a wallet
  • a backend service builds a transaction and forwards the base64 bytes to another signer
  • an integration wants to log or inspect the exact bytes being sent over the wire

Configure onchain read caching

The SDK caches repeated onchain reads such as getGameParameters() results. The default cache TTL is 30 minutes.

const client = createSuigarClient('mainnet', {
cacheTtl: 30 * 60 * 1000,
});

Use a shorter TTL if your product needs fresher parameter refreshes, or pass ignoreCache: true on a specific lookup when you want to force a reload.

Recommended usage:

  • keep the default TTL for backoffice tools, server-rendered docs, and flows where limits do not need to refresh every few seconds
  • use a shorter TTL when your frontend keeps a betting form open and should re-check bounds more aggressively
  • use ignoreCache: true right before a critical action if you want one final fresh read before validating user input

cacheTtl only affects SDK-managed onchain reads such as parsed game parameters.

Inspect the resolved config

const config = client.suigar.getConfig();

console.log(config.packageIds);
console.log(config.registryIds);
console.log(config.coinTypes);
console.log(config.priceInfoObjectIds);

Use this when you want to verify what the SDK resolved for the current network.

Read live game parameters

Use getGameParameters(game, options?) when your app needs current onchain limits before building or validating a bet.

const parameters = await client.suigar.getGameParameters('coinflip', {
coinType: '0x2::sui::SUI',
});

console.log(parameters.min_stake);
console.log(parameters.max_stake);

What this gives you:

  • the SDK resolves the correct SweetHouse settings object for the selected game
  • it resolves the correct Parameters<T> object for the selected coinType
  • it parses the result into the generated SDK type for that game
  • it caches the parsed result so repeated lookups do not keep hitting chain

To bypass the SDK cache and refresh from chain:

const freshParameters = await client.suigar.getGameParameters('coinflip', {
coinType: '0x2::sui::SUI',
ignoreCache: true,
});

For games that return generated Move float structs, such as Limbo or Range, convert those fields before treating them like normal JavaScript numbers:

import { fromMoveFloat } from '@suigar/sdk/utils';

const limboParameters = await client.suigar.getGameParameters('limbo');

const minMultiplier = fromMoveFloat(limboParameters.min_target_multiplier);

A common frontend flow looks like this:

const parameters = await client.suigar.getGameParameters('coinflip', {
coinType,
});

const stake = BigInt(userStakeMist);

if (stake < BigInt(parameters.min_stake)) {
throw new Error('Stake is below the current minimum.');
}

if (stake > BigInt(parameters.max_stake)) {
throw new Error('Stake is above the current maximum.');
}

const tx = client.suigar.tx.createBetTransaction('coinflip', {
owner,
coinType,
stake,
side,
});

Use this pattern when:

  • a frontend needs to disable invalid inputs before submit
  • a backend or relayer wants to validate an incoming request against current onchain bounds
  • a product needs to display min and max stake per coin instead of hardcoding them

What to read from parameters depends on the game:

  • Coinflip, Plinko, and Wheel usually care first about min_stake and max_stake
  • Limbo usually needs stake bounds plus decoded multiplier fields
  • Range usually needs stake bounds plus decoded RTP or interval-related fields
  • PvP Coinflip usually needs stake bounds before opening a lobby

PvP Coinflip helpers

The current SDK also includes runtime helpers for the PvP Coinflip lobby flow:

  • client.suigar.getPvPCoinflipGames(options?)

Example:

const games = await client.suigar.getPvPCoinflipGames({ limit: 20 });
const strictGames = await client.suigar.getPvPCoinflipGames({
limit: 20,
throwOnError: true,
});

getPvPCoinflipGames() reads the unresolved-game registry and bulk-loads the game objects. By default, one stale or unparsable object is skipped so the whole lobby view can still render. Pass throwOnError: true when you want strict failure instead.

Registry membership is the unresolved-state signal. Once a match is joined and resolved, the Move flow removes it from the registry and deletes the live Game object.

Each returned entry includes the parsed game fields plus a derived coin_type string from the underlying Move object type.

For one known live game object, use the generated BCS object helper:

const game = await client.suigar.bcs.PvPCoinflipGame.get({
client,
objectId: '0xGAME_ID',
});

Use these when you need to render open lobbies, inspect a live game, or prepare a join flow from the current onchain state.

A common PvP product flow looks like this:

  1. Read lobbies with getPvPCoinflipGames().
  2. Let the player pick one lobby or paste a known gameId.
  3. If needed, fetch one specific lobby again with PvPCoinflipGame.get(...).
  4. Build the join or cancel transaction from that live state.

Decode Suigar events

The SDK also exposes BCS structs for common events:

const { BetResultEvent, PvPCoinflipGame, PvPCoinflipGameCreatedEvent, PvPCoinflipGameResolvedEvent, PvPCoinflipGameCancelledEvent } =
client.suigar.bcs;

Use PvPCoinflipGame.parse(object.content) for object content you already fetched, or PvPCoinflipGame.get({ client, objectId }) when you want the generated helper to fetch and decode one object.

These helpers decode event payloads and live object content. Prefer them over hand-rolled parsers when you need to inspect Suigar results.

For standard bet result events, derive the game id first, then decode the game details with that id:

import { parseGameEvent, parseGameDetails } from '@suigar/sdk/utils';

const gameEvent = parseGameEvent(event);
if (!gameEvent) {
return null;
}

const decoded = client.suigar.bcs.BetResultEvent.parse(event.bcs);
const details = parseGameDetails(gameEvent.gameId, decoded.game_details);

Use event decoding when you need structured result rows, analytics payloads, or game-specific outcome details after execution. If you only need the transaction digest or success state, decoding is optional.

Public utility exports

Parser helpers, numeric helpers, and SDK constants are exposed through @suigar/sdk/utils:

import {
DEFAULT_GAS_BUDGET_MIST,
DEFAULT_LIMBO_MULTIPLIER_SCALE,
DEFAULT_RANGE_SCALE,
RANGE_POINT_LIMIT,
fromMoveFloat,
fromMoveI64,
parseCoinType,
parseGameEvent,
parseGameDetails,
toBigInt,
toU8,
toU16,
} from '@suigar/sdk/utils';

Prefer these exports instead of copying SDK defaults into an app.

Useful helper behavior:

  • toBigInt(value) accepts bigint, finite number, non-negative integer string, and boolean, then returns a normalized non-negative bigint
  • toU8(value) and toU16(value) reject booleans, fractional values, and out-of-range integers with RangeError
  • parseCoinType(type) throws TypeError when the first generic coin type cannot be parsed from the Move type string
  • parseGameEvent(event) resolves supported Suigar events into the normalized SDK game id and event name
  • parseGameDetails(gameId, gameDetails) preserves the onchain keys and only decodes the byte-array values into the expected string, number, and boolean values

Shared standard-game options

Every standard bet builder shares this base shape:

  • owner: wallet that should receive the reward object back
  • coinType: supported wager coin type
  • stake: logical stake sent to the game contract
  • cashStake: optional withdrawn amount, defaults to stake
  • betCount: optional number of bets, defaults to 1
  • metadata: optional metadata map encoded to byte arrays
  • gasBudget: optional custom gas budget
  • allowGasCoinShortcut: lets the builder use the gas coin path when possible

Notes:

  • owner is the single explicit player identity field used by current builders
  • sender is no longer passed separately
  • partner attribution should come from extension registration, not per-call metadata patching
  • partner integrations should treat suigar({ partner: '0xpartner_wallet_address' }) as the default client setup

How to choose between stake and cashStake:

  • use only stake for the normal case where the logical bet and withdrawn amount are the same
  • use cashStake only when your integration intentionally separates the displayed wager from the withdrawn balance

Supported coins

The SDK currently resolves supported coins from internal config:

  • SUI
  • USDC

If an unsupported coin type is passed, the SDK throws before building the transaction.

Validation and errors

The SDK now has a stricter validation surface. In practice:

  • RangeError is used for unsupported networks, unsupported game ids, unsupported PvP actions, unsupported coin types, and out-of-range validated numeric values.
  • TypeError is used when a helper such as toBigInt(), toU8(), toU16(), or parseCoinType() receives an invalid input shape.
  • getPvPCoinflipGames() skips stale or unparsable objects by default and only throws when throwOnError: true is passed.
  • the extension constructor throws RangeError if the connected client network is outside the SDK-supported Sui networks

If your product accepts user-entered stake, config, or range values, validate them before building the transaction and use live getGameParameters() reads when you need current onchain limits.

Practical examples:

  • toBigInt('1000000') is valid, but toBigInt('-1') throws RangeError
  • toU8('2') is valid for Plinko or Wheel config ids, but toU8(300) throws RangeError
  • toU16('100') is valid for bounded u16 values, but toU16('1.5') throws RangeError
  • passing an unsupported coinType into a transaction builder throws before the Move call is assembled

End-to-end example

This is the most common browser-style integration pattern:

import { SuiGrpcClient } from '@mysten/sui/grpc';
import { suigar } from '@suigar/sdk';

const client = new SuiGrpcClient({
network: 'testnet',
baseUrl: 'https://fullnode.testnet.sui.io:443',
}).$extend(suigar());

const parameters = await client.suigar.getGameParameters('coinflip', {
coinType: '0x2::sui::SUI',
});

const stake = 1_000_000_000n;
if (stake > BigInt(parameters.max_stake)) {
throw new Error('Stake is above the current limit.');
}

const tx = client.suigar.tx.createBetTransaction('coinflip', {
owner: '0x123',
coinType: '0x2::sui::SUI',
stake,
side: 'heads',
});

const transactionBytesBase64 = await client.suigar.serializeTransactionToBase64(tx);

From there, either pass the bytes to a wallet or build and execute with your own signing flow.

Example app and playground

The SDK repository also ships a Next.js integration example app, and the hosted version is available at playground.suigar.com.

Use it when you want to:

  • inspect the current transaction builder inputs and outputs
  • test standard and PvP flows against the current SDK
  • validate partner-oriented integration assumptions with a working reference before wiring your own app

Per-game examples