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/suiv2@mysten/bcsv2
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:
partnermust 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(...)orcreatePvPCoinflipTransaction(...)to think about partner attribution - do not try to override attribution with manual
metadata.partnerormetadata.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
ownerand 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: trueright 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 selectedcoinType - 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_stakeandmax_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:
- Read lobbies with
getPvPCoinflipGames(). - Let the player pick one lobby or paste a known
gameId. - If needed, fetch one specific lobby again with
PvPCoinflipGame.get(...). - 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)acceptsbigint, finitenumber, non-negative integerstring, andboolean, then returns a normalized non-negativebiginttoU8(value)andtoU16(value)reject booleans, fractional values, and out-of-range integers withRangeErrorparseCoinType(type)throwsTypeErrorwhen the first generic coin type cannot be parsed from the Move type stringparseGameEvent(event)resolves supported Suigar events into the normalized SDK game id and event nameparseGameDetails(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 backcoinType: supported wager coin typestake: logical stake sent to the game contractcashStake: optional withdrawn amount, defaults tostakebetCount: optional number of bets, defaults to1metadata: optional metadata map encoded to byte arraysgasBudget: optional custom gas budgetallowGasCoinShortcut: lets the builder use the gas coin path when possible
Notes:
owneris the single explicit player identity field used by current builderssenderis 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
stakefor the normal case where the logical bet and withdrawn amount are the same - use
cashStakeonly when your integration intentionally separates the displayed wager from the withdrawn balance
Supported coins
The SDK currently resolves supported coins from internal config:
SUIUSDC
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:
RangeErroris used for unsupported networks, unsupported game ids, unsupported PvP actions, unsupported coin types, and out-of-range validated numeric values.TypeErroris used when a helper such astoBigInt(),toU8(),toU16(), orparseCoinType()receives an invalid input shape.getPvPCoinflipGames()skips stale or unparsable objects by default and only throws whenthrowOnError: trueis passed.- the extension constructor throws
RangeErrorif 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, buttoBigInt('-1')throwsRangeErrortoU8('2')is valid for Plinko or Wheel config ids, buttoU8(300)throwsRangeErrortoU16('100')is valid for boundedu16values, buttoU16('1.5')throwsRangeError- passing an unsupported
coinTypeinto 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