@suigar/sdk 2.0.0-beta.1 → 2.0.0-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,31 +5,92 @@ TypeScript SDK for building Suigar v2 game transactions on Sui.
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- npm install @suigar/sdk
8
+ npm install --save @suigar/sdk @mysten/sui @mysten/bcs
9
9
  ```
10
10
 
11
11
  Runtime requirements:
12
12
 
13
13
  - Node.js `>=22`
14
- - `@mysten/sui`
14
+ - ESM project configuration (`"type": "module"`)
15
+ - `@mysten/sui` v2
16
+ - `@mysten/bcs` v2
17
+
18
+ This SDK targets Sui TypeScript SDK 2.0+ only. Follow the official [Sui 2.0 migration guide](https://sdk.mystenlabs.com/sui/migrations/sui-2.0) if your app still uses the pre-2.0 client API.
15
19
 
16
20
  ## What This Package Exposes
17
21
 
18
- The package root currently exposes the extension factory:
22
+ The package ships three public entrypoints:
23
+
24
+ - `@suigar/sdk` for the extension factory and runtime client class
25
+ - `@suigar/sdk/games` for game-specific public types
26
+ - `@suigar/sdk/utils` for public parser, constants, and numeric helpers
27
+
28
+ The package root exposes the extension factory and client class:
19
29
 
20
30
  ```ts
21
- import { suigar } from '@suigar/sdk';
31
+ import { suigar, SuigarClient } from '@suigar/sdk';
22
32
  ```
23
33
 
24
34
  It does not export the individual transaction builders from the package root.
25
- It also does not export `SuigarClient` as a public root symbol.
35
+ Those stay on the registered extension instance under `client.suigar.tx`.
36
+
37
+ Utility exports are available from the utils subpath:
38
+
39
+ ```ts
40
+ import {
41
+ DEFAULT_GAS_BUDGET_MIST,
42
+ DEFAULT_LIMBO_MULTIPLIER_SCALE,
43
+ DEFAULT_RANGE_SCALE,
44
+ fromMoveFloat,
45
+ fromMoveI64,
46
+ parseCoinType,
47
+ parseGameDetails,
48
+ RANGE_POINT_LIMIT,
49
+ toBigInt,
50
+ toU8,
51
+ toU16,
52
+ } from '@suigar/sdk/utils';
53
+ ```
54
+
55
+ Numeric helper behavior:
56
+
57
+ - `toBigInt(value)` accepts `bigint`, finite `number`, non-negative integer
58
+ `string`, and `boolean` inputs and returns a normalized non-negative `bigint`
59
+ - `toU8(value)` accepts a finite integer `number` or plain integer `string` in
60
+ the inclusive `0..255` range and rejects booleans or fractional values
61
+ - `toU16(value)` accepts a finite integer `number` or plain integer `string`
62
+ in the inclusive `0..65535` range and rejects booleans or fractional values
63
+ - `fromMoveI64(value)` converts a generated Move `i64` wrapper into a
64
+ JavaScript `number`
65
+ - `fromMoveFloat(value)` converts a generated Move float struct into a
66
+ JavaScript `number`
67
+ - `parseGameDetails(gameDetails)` decodes standard `BetResultEvent.game_details`
68
+ byte arrays into the expected string, number, and boolean values while
69
+ preserving the original onchain keys
70
+
71
+ Game-specific type exports are available from the dedicated `games` subpath:
72
+
73
+ ```ts
74
+ import type {
75
+ BuildCoinflipTransactionOptions,
76
+ BuildCreatePvPCoinflipTransactionOptions,
77
+ CoinSide,
78
+ PvPCoinflipAction,
79
+ } from '@suigar/sdk/games';
80
+ ```
81
+
82
+ Current game-type subpath exports:
83
+
84
+ - `@suigar/sdk/games`: `CoinSide`, `PvPCoinflipAction`, `BuildCoinflipTransactionOptions`, `BuildLimboTransactionOptions`, `BuildPlinkoTransactionOptions`, `BuildRangeTransactionOptions`, `BuildWheelTransactionOptions`, `BuildCreatePvPCoinflipTransactionOptions`, `BuildJoinPvPCoinflipTransactionOptions`, `BuildCancelPvPCoinflipTransactionOptions`
26
85
 
27
86
  What you actually use at runtime is the registered extension instance:
28
87
 
29
88
  ```ts
30
- const client = new SuiClient({ url }).$extend(suigar());
89
+ const client = new SuiGrpcClient({ baseUrl, network }).$extend(suigar());
31
90
 
32
91
  client.suigar.serializeTransactionToBase64(...);
92
+ client.suigar.getConfig();
93
+ client.suigar.getPvPCoinflipGames(...);
33
94
  client.suigar.bcs;
34
95
  client.suigar.tx;
35
96
  ```
@@ -37,22 +98,16 @@ client.suigar.tx;
37
98
  ## Quick Start
38
99
 
39
100
  ```ts
40
- import { getFullnodeUrl, SuiClient } from '@mysten/sui/client';
101
+ import { SuiGrpcClient } from '@mysten/sui/grpc';
41
102
  import { suigar } from '@suigar/sdk';
42
103
 
43
- const client = new SuiClient({
44
- url: getFullnodeUrl('testnet'),
45
- }).$extend(
46
- suigar({
47
- pyth: {
48
- suiPriceInfoObjectId: '0xPYTH_SUI_PRICE_INFO',
49
- usdcPriceInfoObjectId: '0xPYTH_USDC_PRICE_INFO',
50
- },
51
- }),
52
- );
104
+ const client = new SuiGrpcClient({
105
+ baseUrl: 'https://fullnode.testnet.sui.io:443',
106
+ network: 'testnet',
107
+ }).$extend(suigar());
53
108
 
54
109
  const tx = client.suigar.tx.createBetTransaction('coinflip', {
55
- owner: '0x123',
110
+ playerAddress: '0x123',
56
111
  coinType: '0x2::sui::SUI',
57
112
  stake: 1_000_000_000n,
58
113
  side: 'heads',
@@ -67,78 +122,114 @@ const base64 = await client.suigar.serializeTransactionToBase64(tx);
67
122
 
68
123
  Creates a named Sui client extension. By default, it registers under `client.suigar`.
69
124
 
125
+ ### Partner Setup
126
+
127
+ > **Important:** `partner` is the partner wallet address. Configure it once
128
+ > when you register the extension so the SDK can prepend that wallet address to
129
+ > supported bet metadata automatically.
130
+
70
131
  ```ts
71
- const client = new SuiClient({ url }).$extend(suigar());
132
+ const client = new SuiGrpcClient({ baseUrl, network }).$extend(
133
+ suigar({ partner: '0xpartner_wallet_address' }),
134
+ );
72
135
 
73
136
  client.suigar;
74
137
  ```
75
138
 
139
+ Do not pass a partner slug, label, or display name here. Use the wallet
140
+ address that should receive partner attribution onchain.
141
+
76
142
  You can rename the extension:
77
143
 
78
144
  ```ts
79
- const client = new SuiClient({ url }).$extend(suigar({ name: 'casino' }));
145
+ const client = new SuiGrpcClient({ baseUrl, network }).$extend(
146
+ suigar({ name: 'games' }),
147
+ );
80
148
 
81
- client.casino.tx;
82
- client.casino.bcs;
149
+ client.games.tx;
150
+ client.games.bcs;
83
151
  ```
84
152
 
85
153
  ## Config
86
154
 
87
155
  `suigar(options?)` resolves config from:
88
156
 
89
- - the connected Sui network
90
- - internal default package ids
91
- - internal default SweetHouse package id by network
92
- - default coin types for `SUI`, `USDC`, and FlowX `USDC`
93
- - user overrides
157
+ - internal package ids by network
158
+ - internal supported coin types by network
159
+ - internal price info object ids by network
160
+ - the connected client network
161
+ - the extension name
94
162
 
95
163
  Supported override areas:
96
164
 
97
165
  - `name`
98
- - `sweetHousePackageId`
99
- - `coinTypes.sui`
100
- - `coinTypes.usdc`
101
- - `coinTypes.usdcFlowx`
102
- - `gamesPackageId.coinflip`
103
- - `gamesPackageId.limbo`
104
- - `gamesPackageId.plinko`
105
- - `gamesPackageId['pvp-coinflip']`
106
- - `gamesPackageId.range`
107
- - `gamesPackageId.wheel`
108
- - `pyth.packageId`
109
- - `pyth.suiPriceInfoObjectId`
110
- - `pyth.usdcPriceInfoObjectId`
111
- - `pyth.priceInfoObjectIds[coinType]`
112
-
113
- Example:
166
+ - `partner`
167
+ - `cacheTtl`
114
168
 
115
- ```ts
116
- const client = new SuiClient({ url }).$extend(
117
- suigar({
118
- sweetHousePackageId: '0xsweethouse',
119
- pyth: {
120
- suiPriceInfoObjectId: '0xsui',
121
- usdcPriceInfoObjectId: '0xusdc',
122
- priceInfoObjectIds: {
123
- '0x123::custom::TOKEN': '0xprice',
124
- },
125
- },
126
- gamesPackageId: {
127
- coinflip: '0xcoinflip',
128
- wheel: '0xwheel',
129
- },
130
- }),
131
- );
132
- ```
169
+ If `partner` is configured, the SDK automatically writes that partner wallet
170
+ address into the onchain metadata vec-map. Transaction builder options may also
171
+ include `metadata`, but reserved keys such as `partner` and `referrer` are
172
+ ignored with a warning when provided manually.
173
+
174
+ `cacheTtl` controls the SDK cache for onchain reads such as parsed game
175
+ parameters. It is expressed in milliseconds and defaults to 30 minutes.
133
176
 
134
177
  ## Runtime Surface
135
178
 
136
- The registered extension instance exposes three main areas:
179
+ The registered extension instance exposes the main runtime surface:
137
180
 
181
+ - `getConfig()`
182
+ - `getGameParameters(game, options?)`
138
183
  - `serializeTransactionToBase64(transaction, options?)`
184
+ - `getPvPCoinflipGames(options?)`
139
185
  - `bcs`
140
186
  - `tx`
141
187
 
188
+ ### `getConfig()`
189
+
190
+ Returns the resolved SDK configuration for the connected network.
191
+
192
+ This is intended mainly for debugging and inspection, for example to verify the
193
+ resolved package ids or supported coin mappings for the active client network.
194
+
195
+ It includes:
196
+
197
+ - `packageIds`
198
+ - `registryIds`
199
+ - `coinTypes`
200
+ - `priceInfoObjectIds`
201
+
202
+ ```ts
203
+ const config = client.suigar.getConfig();
204
+ console.log(config.packageIds);
205
+ ```
206
+
207
+ ### `getGameParameters(game, options?)`
208
+
209
+ Returns the onchain `Parameters<T>` object for any supported game and coin type.
210
+ The return type is inferred from `game`.
211
+
212
+ The SDK first reads the selected game's settings object from the configured
213
+ SweetHouse object, then reads that game's coin-specific `Parameters<T>` object.
214
+ This is useful for displaying or validating current limits such as min/max
215
+ stake, house edge, or game-specific config bounds. The parsed result is cached
216
+ using the extension `cacheTtl`.
217
+
218
+ When a parameter field is a generated Move float struct, such as
219
+ `min_target_multiplier`, `max_target_multiplier`, `min_rtp`, or `max_rtp`, use
220
+ `fromMoveFloat()` before treating it as a normal JavaScript number.
221
+
222
+ ```ts
223
+ const parameters = await client.suigar.getGameParameters('coinflip', {
224
+ coinType: '0x2::sui::SUI',
225
+ });
226
+
227
+ console.log(parameters.min_stake);
228
+ ```
229
+
230
+ Pass `ignoreCache: true` to refresh the onchain read and replace the cached
231
+ value.
232
+
142
233
  ### `serializeTransactionToBase64(transaction, options?)`
143
234
 
144
235
  Builds a transaction with the configured Sui client and returns base64-encoded transaction bytes.
@@ -149,6 +240,42 @@ Use this when you need a transport-safe payload for a wallet, API, or external s
149
240
  const base64 = await client.suigar.serializeTransactionToBase64(tx);
150
241
  ```
151
242
 
243
+ ### `getPvPCoinflipGames(options?)`
244
+
245
+ Lists unresolved PvP coinflip games from the configured PvP registry.
246
+
247
+ This reads the registry dynamic fields for the active network and resolves each
248
+ entry into parsed game state through a bulk `client.core.getObjects()` lookup.
249
+ Registry membership is the unresolved-state signal: once a match is joined and
250
+ resolved, the Move flow removes it from the registry and deletes the live
251
+ `Game` object.
252
+
253
+ Use this when a product needs the current set of open PvP coinflip matches for
254
+ browsing or lobby views.
255
+
256
+ By default, per-object fetch or parse failures are skipped so one broken or
257
+ already-deleted registry entry does not reject the full lookup. Pass
258
+ `throwOnError: true` if you want the call to reject instead.
259
+
260
+ Any supported `listDynamicFields()` options such as `limit`, `cursor`, or
261
+ `signal` can be passed through `options`.
262
+
263
+ ```ts
264
+ const games = await client.suigar.getPvPCoinflipGames({ limit: 20 });
265
+
266
+ for (const game of games) {
267
+ console.log(game.id);
268
+ console.log(game.coinType);
269
+ }
270
+ ```
271
+
272
+ ```ts
273
+ const games = await client.suigar.getPvPCoinflipGames({
274
+ limit: 20,
275
+ throwOnError: true,
276
+ });
277
+ ```
278
+
152
279
  ## `tx`
153
280
 
154
281
  Transaction builders live under `client.suigar.tx`.
@@ -165,7 +292,7 @@ Use `createBetTransaction(gameId, options)` for:
165
292
 
166
293
  ```ts
167
294
  const tx = client.suigar.tx.createBetTransaction('coinflip', {
168
- owner: '0x123',
295
+ playerAddress: '0x123',
169
296
  coinType: '0x2::sui::SUI',
170
297
  stake: 1_000_000_000n,
171
298
  side: 'tails',
@@ -174,14 +301,13 @@ const tx = client.suigar.tx.createBetTransaction('coinflip', {
174
301
 
175
302
  Shared option shape:
176
303
 
177
- - `owner: string`
304
+ - `playerAddress: string`
178
305
  - `coinType: string`
179
306
  - `stake: number | bigint`
180
307
  - `cashStake?: number | bigint`
181
308
  - `betCount?: number | bigint`
182
309
  - `metadata?: Record<string, string | number | boolean | bigint | Uint8Array | number[] | null | undefined>`
183
310
  - `gasBudget?: number | bigint`
184
- - `sender?: string`
185
311
  - `allowGasCoinShortcut?: boolean`
186
312
 
187
313
  Shared behavior:
@@ -189,10 +315,11 @@ Shared behavior:
189
315
  - `stake` is the logical stake passed into the Move call
190
316
  - `cashStake` controls the withdrawn balance and defaults to `stake`
191
317
  - `betCount` defaults to `1`
192
- - `sender` overrides the transaction sender
193
318
  - `metadata` is encoded into `keys` and `values` byte arrays
194
- - the SDK resolves the Pyth price info object from the configured coin mapping
195
- - the reward object is transferred back to `owner`
319
+ - `partner` configured via `suigar({ partner })` is prepended automatically to metadata as the partner wallet address
320
+ - `metadata.partner` and `metadata.referrer` are reserved and ignored with a warning
321
+ - the SDK resolves the price info object from the configured supported-coin mapping
322
+ - the reward object is transferred back to `playerAddress`
196
323
 
197
324
  Per-game options:
198
325
 
@@ -206,27 +333,35 @@ Examples:
206
333
 
207
334
  ```ts
208
335
  const limboTx = client.suigar.tx.createBetTransaction('limbo', {
209
- owner: '0x123',
336
+ playerAddress: '0x123',
210
337
  coinType: '0x2::sui::SUI',
211
338
  stake: 1_000_000_000n,
212
339
  targetMultiplier: 2.5,
213
340
  });
214
341
 
215
342
  const rangeTx = client.suigar.tx.createBetTransaction('range', {
216
- owner: '0x123',
343
+ playerAddress: '0x123',
217
344
  coinType: '0x2::sui::SUI',
218
345
  stake: 1_000_000_000n,
219
- leftPoint: 0.95,
220
- rightPoint: 1.05,
346
+ leftPoint: 25,
347
+ rightPoint: 75,
221
348
  outOfRange: false,
222
349
  });
223
350
  ```
224
351
 
225
- Notes:
352
+ > **Note:**
353
+ >
354
+ > - limbo converts `targetMultiplier` with `Math.round(targetMultiplier * scale)`
355
+ > - with the default limbo scale `100`, exposed as `DEFAULT_LIMBO_MULTIPLIER_SCALE`, a target multiplier of `2.5` becomes `250` onchain
356
+ > - range converts each point with `Math.round(value * scale)`
357
+ > - range points are bounded by the contract limit exposed as `RANGE_POINT_LIMIT`
358
+ > - with the default range scale `1_000_000`, exposed as `DEFAULT_RANGE_SCALE`, valid UI values are `0` to `100`
359
+ > - plinko and wheel `configId` must fit in `u8`
226
360
 
227
- - limbo converts `targetMultiplier` with `Math.round(targetMultiplier * scale)`
228
- - range converts each point with `Math.round(value * scale)`
229
- - plinko and wheel `configId` must fit in `u8`
361
+ > **Tip:**
362
+ >
363
+ > - if you set `scale` to `10_000_000`, valid UI values become `0` to `10`
364
+ > - do not pre-scale range points before passing them to the SDK; pass the human value and let the SDK scale it once
230
365
 
231
366
  ### PvP Coinflip
232
367
 
@@ -240,7 +375,7 @@ Create:
240
375
 
241
376
  ```ts
242
377
  const tx = client.suigar.tx.createPvPCoinflipTransaction('create', {
243
- owner: '0x123',
378
+ playerAddress: '0x123',
244
379
  coinType: '0x2::sui::SUI',
245
380
  stake: 1_000_000_000n,
246
381
  side: 'heads',
@@ -252,11 +387,9 @@ Join:
252
387
 
253
388
  ```ts
254
389
  const tx = client.suigar.tx.createPvPCoinflipTransaction('join', {
255
- owner: '0x123',
390
+ playerAddress: '0x123',
256
391
  coinType: '0x2::sui::SUI',
257
392
  gameId: '0xGAME_ID',
258
- extraObjectId: '0xEXTRA_OBJECT_ID',
259
- stake: 1_000_000_000n,
260
393
  });
261
394
  ```
262
395
 
@@ -264,25 +397,27 @@ Cancel:
264
397
 
265
398
  ```ts
266
399
  const tx = client.suigar.tx.createPvPCoinflipTransaction('cancel', {
267
- owner: '0x123',
400
+ playerAddress: '0x123',
268
401
  coinType: '0x2::sui::SUI',
269
402
  gameId: '0xGAME_ID',
270
403
  });
271
404
  ```
272
405
 
406
+ Join derives the stake from `gameId` and uses the configured price info object
407
+ id for `coinType`.
408
+
273
409
  PvP shared options:
274
410
 
275
- - `owner: string`
411
+ - `playerAddress: string`
276
412
  - `coinType: string`
277
- - `metadata?: ...`
413
+ - `metadata?: Record<string, string | number | boolean | bigint | Uint8Array | number[] | null | undefined>`
278
414
  - `gasBudget?: number | bigint`
279
- - `sender?: string`
280
415
  - `allowGasCoinShortcut?: boolean`
281
416
 
282
417
  Action-specific options:
283
418
 
284
419
  - `create`: `stake`, `side`, `isPrivate?`
285
- - `join`: `gameId`, `extraObjectId`, `stake`
420
+ - `join`: `gameId`
286
421
  - `cancel`: `gameId`
287
422
 
288
423
  ## `bcs`
@@ -291,24 +426,70 @@ BCS helpers live under `client.suigar.bcs`.
291
426
 
292
427
  Current exposed helpers:
293
428
 
429
+ - `PvPCoinflipGame`
294
430
  - `BetResultEvent`
295
- - `PvPCoinflipGameCreated`
296
- - `PvPCoinflipGameResolved`
297
- - `PvPCoinflipGameCancelled`
431
+ - `PvPCoinflipGameCreatedEvent`
432
+ - `PvPCoinflipGameResolvedEvent`
433
+ - `PvPCoinflipGameCancelledEvent`
434
+
435
+ These are generated Move event decoders. Use them to parse Suigar event payloads from transaction results. The `@suigar/sdk/utils` subpath also exposes parser helpers for generated BCS values:
436
+
437
+ - `PvPCoinflipGame` parses a PvP coinflip game object's `content`
438
+ - `fromMoveI64(float.exp)` converts a generated Move `i64` exponent to a JavaScript number
439
+ - `fromMoveFloat(float)` converts a generated Move `Float` struct to a JavaScript number
440
+ - `parseCoinType(type)` extracts the normalized coin type from generic Move object type strings such as PvP coinflip `Game<T>`
441
+ - `parseGameDetails(game_details)` decodes `BetResultEvent.game_details` entries into the expected string, number, and boolean values
442
+
443
+ ### Parse PvP Coinflip Game Object Data
444
+
445
+ Use the generated BCS helper when you want to fetch and parse a game object:
446
+
447
+ ```ts
448
+ const game = await client.suigar.bcs.PvPCoinflipGame.get({
449
+ client,
450
+ objectId: '0xGAME_ID',
451
+ });
298
452
 
299
- These are generated Move struct/event decoders. Use them to parse Suigar event payloads and structured onchain content.
453
+ console.log(game.json);
454
+ ```
300
455
 
301
456
  ### Parse Standard Bet Result Data
302
457
 
303
458
  ```ts
304
- const { object } = await client.core.getObject({
305
- objectId: '0xOBJECT_ID',
459
+ const executeResult = await client.core.executeTransaction({
460
+ transaction: transactionBytes,
461
+ signatures: [signature],
306
462
  include: {
307
- content: true,
463
+ events: true,
308
464
  },
309
465
  });
310
466
 
311
- const decoded = client.suigar.bcs.BetResultEvent.parse(object.content);
467
+ const finalResult = await client.core.waitForTransaction({
468
+ result: executeResult,
469
+ include: {
470
+ effects: true,
471
+ events: true,
472
+ },
473
+ });
474
+
475
+ if (finalResult.$kind === 'FailedTransaction') {
476
+ throw new Error(finalResult.FailedTransaction.status.error?.message);
477
+ }
478
+
479
+ console.log(finalResult.Transaction.digest);
480
+
481
+ const transactionResult = finalResult.Transaction;
482
+
483
+ const betResults = [];
484
+
485
+ for (const event of transactionResult.events ?? []) {
486
+ try {
487
+ const decoded = client.suigar.bcs.BetResultEvent.parse(event.bcs);
488
+ betResults.push(decoded);
489
+ } catch {
490
+ // Ignore non-BetResultEvent payloads.
491
+ }
492
+ }
312
493
  ```
313
494
 
314
495
  Parsed fields include:
@@ -322,36 +503,68 @@ Parsed fields include:
322
503
  - `game_details`
323
504
  - `metadata`
324
505
 
325
- `game_details` and `metadata` decode as `VecMap<string, vector<u8>>`-shaped data, so values come back as byte arrays.
506
+ `game_details` and `metadata` decode as `VecMap<string, vector<u8>>`-shaped data, so values come back as byte arrays. Use `parseGameDetails` from `@suigar/sdk/utils` to decode `game_details` with the SDK's known game-detail schemas.
326
507
 
327
508
  ```ts
328
- const textDecoder = new TextDecoder();
509
+ import { parseGameDetails } from '@suigar/sdk/utils';
329
510
 
330
- const metadata = new Map(
331
- decoded.metadata.contents.map(({ key, value }) => [
332
- key,
333
- textDecoder.decode(new Uint8Array(value)),
334
- ]),
335
- );
511
+ const decoded = client.suigar.bcs.BetResultEvent.parse(event.bcs);
512
+ const gameDetails = parseGameDetails(decoded.game_details);
336
513
  ```
337
514
 
338
- Important:
515
+ `parseGameDetails` preserves the onchain keys and only changes the value representation. For example, coinflip details keep keys such as `player_bet` and `coin_outcome`; range details keep keys such as `roll_value`, `win`, and `payout_multiplier`.
516
+
517
+ When the extension is configured with `partner`, decoded event `metadata` will
518
+ contain that partner wallet address under the `partner` entry.
519
+
520
+ > **Important:**
521
+ >
522
+ > - execute or wait for the transaction with `include: { events: true }`
523
+ > - unwrap the core API union with `result.$kind`, `result.Transaction`, and `result.FailedTransaction`
524
+ > - parse emitted events from the unwrapped transaction result
525
+ > - use `event.bcs` for consistent decoding across transports
526
+ > - use `parseGameDetails(decoded.game_details)` instead of hand-decoding standard game detail byte arrays
339
527
 
340
- - use `content`, not `objectBcs`, with these generated parsers
341
- - the generated parser expects the struct payload, not a full object envelope
528
+ > **Tip:**
529
+ >
530
+ > - `waitForTransaction({ result, include: { effects: true, events: true } })` is useful when you want the finalized transaction result before decoding
531
+ > - these helpers decode the event payload itself, not a full transaction response
342
532
 
343
533
  ### Parse PvP Coinflip Event Data
344
534
 
345
- Use the matching helper for the PvP coinflip event payload you fetched from chain:
535
+ Use the matching helper for each PvP coinflip event payload found in `transactionResult.events`:
346
536
 
347
- - `client.suigar.bcs.PvPCoinflipGameCreated`
348
- - `client.suigar.bcs.PvPCoinflipGameResolved`
349
- - `client.suigar.bcs.PvPCoinflipGameCancelled`
537
+ - `client.suigar.bcs.PvPCoinflipGameCreatedEvent`
538
+ - `client.suigar.bcs.PvPCoinflipGameResolvedEvent`
539
+ - `client.suigar.bcs.PvPCoinflipGameCancelledEvent`
350
540
 
351
541
  ## Development
352
542
 
353
543
  ```bash
354
- npm run build
355
- npm run typecheck
356
- npm test
544
+ pnpm --dir packages/sdk build
545
+ pnpm --dir packages/sdk typecheck
546
+ pnpm --dir packages/sdk test
547
+ ```
548
+
549
+ ## Example App
550
+
551
+ This repository includes a Next.js integration playground in [apps/playground](../../apps/playground).
552
+
553
+ It demonstrates:
554
+
555
+ - standard game transactions through `client.suigar.tx.createBetTransaction(...)`
556
+ - PvP coinflip create, join, and cancel flows through `client.suigar.tx.createPvPCoinflipTransaction(...)`, exposed in the example through a PvP coinflip action selector
557
+ - unresolved PvP lobby browsing through `client.suigar.getPvPCoinflipGames(...)`, including public join cards while disconnected, an optional private-lobby join toggle, and connected-wallet filtering for cancel
558
+ - wallet connection and execution with `@mysten/dapp-kit-core` and `@mysten/dapp-kit-react`
559
+ - supported coin selection from `client.suigar.getConfig()`
560
+ - connected-wallet balance display for each supported coin in the example app
561
+ - privacy badges and copyable PvP game ids in the lobby UI
562
+ - decoding `BetResultEvent` and PvP events into a persistent event log
563
+ - parsing `BetResultEvent.game_details` with `parseGameDetails`
564
+
565
+ Run it from the repo root with:
566
+
567
+ ```bash
568
+ pnpm install
569
+ pnpm turbo run dev --filter='./apps/playground'
357
570
  ```