slot-server 1.0.0
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 +949 -0
- package/dist/index.cjs +2944 -0
- package/dist/index.d.cts +2160 -0
- package/dist/index.d.ts +2160 -0
- package/dist/index.js +2897 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,949 @@
|
|
|
1
|
+
# Slot Server
|
|
2
|
+
|
|
3
|
+
A professional server-side companion library for the [Slot Engine client](https://github.com/bakarilevy/slot-engine).
|
|
4
|
+
It provides comprehensive types, game logic managers, and utilities to handle all server-side operations for modern slot games including spins, bonuses, jackpots, gamble features, and regulatory compliance.
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
|
|
8
|
+
### Core Features
|
|
9
|
+
- **Full game state management** - Balance, bet, free spins, bonus tracking
|
|
10
|
+
- **Configurable game data** - Symbols, paytables, reel strips, paylines
|
|
11
|
+
- **Spin evaluation** - Win calculation with payline and ways-to-win support
|
|
12
|
+
- **Bonus system** - Free spins, pick'em games, and extensible bonus framework
|
|
13
|
+
- **History tracking** - Complete spin history with audit trail
|
|
14
|
+
|
|
15
|
+
### Advanced Features
|
|
16
|
+
- **Progressive Jackpots** - Multi-tier jackpot system with contribution tracking
|
|
17
|
+
- **Ways-to-Win** - Support for 243/1024/4096 ways mechanics
|
|
18
|
+
- **Gamble Feature** - Card guess, ladder climb, and wheel mini-games
|
|
19
|
+
- **Buy Bonus** - Direct bonus purchase with configurable limits and cooldowns
|
|
20
|
+
|
|
21
|
+
### Enhanced Gameplay Features (NEW)
|
|
22
|
+
- **Cascading Reels** - Tumbling mechanics with symbol removal, gravity drops, chain reactions, and progressive multipliers
|
|
23
|
+
- **Expanding Wilds** - Wild expansion to cover entire reels with configurable triggers and multiplier stacking
|
|
24
|
+
- **Sticky Wilds** - Persistent wilds that stay in place for multiple spins
|
|
25
|
+
- **Scatter Enhancements** - Advanced scatter mechanics including cluster triggers, collection systems, progressive scatters, transformation mechanics, and multiplier zones
|
|
26
|
+
- **Respins / Hold & Win** - Locking symbols for respin rounds, money symbol collection, reset symbols, jackpot symbols (Mini/Minor/Major/Grand), full screen bonuses, and weighted symbol generation
|
|
27
|
+
|
|
28
|
+
### Compliance & Operations (NEW)
|
|
29
|
+
- **Audit System** - Complete spin audit records for regulatory compliance with export capabilities
|
|
30
|
+
- **Debug Mode** - Comprehensive debugging tools with logging, forced outcomes, and state inspection
|
|
31
|
+
- **Responsible Gaming** - Session limits, reality checks, self-exclusion support
|
|
32
|
+
- **Tournament Mode** - Leaderboards, scoring, and time-limited competitions
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install slot-server
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import {
|
|
44
|
+
ConfigBuilder,
|
|
45
|
+
GameSession,
|
|
46
|
+
sampleConfig,
|
|
47
|
+
JackpotManager,
|
|
48
|
+
WaysEvaluator,
|
|
49
|
+
GambleManager,
|
|
50
|
+
BuyBonusManager,
|
|
51
|
+
CascadingReelsManager,
|
|
52
|
+
ExpandingWildsManager,
|
|
53
|
+
ScatterEnhancementsManager,
|
|
54
|
+
RespinsManager,
|
|
55
|
+
AuditManager,
|
|
56
|
+
DebugManager
|
|
57
|
+
} from 'slot-server';
|
|
58
|
+
|
|
59
|
+
// 1. Build your game configuration
|
|
60
|
+
const config = new ConfigBuilder()
|
|
61
|
+
.setGameId('my-game')
|
|
62
|
+
.setReelCount(5)
|
|
63
|
+
.setRowCount(3)
|
|
64
|
+
.setSymbolMap({ 0: 'symbol0', 1: 'symbol1', ... })
|
|
65
|
+
.setPaytableData({ 0: { x3: 10, x4: 20, x5: 50 }, ... })
|
|
66
|
+
.setReelStrips([[...], ...])
|
|
67
|
+
.setLinePatterns([...])
|
|
68
|
+
.setBetRange(0.1, 100, 0.1, 1)
|
|
69
|
+
.build();
|
|
70
|
+
|
|
71
|
+
// Or use the sample config:
|
|
72
|
+
// const config = sampleConfig;
|
|
73
|
+
|
|
74
|
+
// 2. Implement a balance provider (e.g., using a database)
|
|
75
|
+
const balanceProvider = {
|
|
76
|
+
getBalance: (playerId) => getFromDB(playerId),
|
|
77
|
+
setBalance: (playerId, amount) => updateDB(playerId, amount),
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// 3. Create a game session for a player
|
|
81
|
+
const session = new GameSession(config, 'player-123', balanceProvider);
|
|
82
|
+
|
|
83
|
+
// 4. Send INIT response to client
|
|
84
|
+
const initResponse = session.getInitResponse();
|
|
85
|
+
// Send via WebSocket: ws.send(JSON.stringify({ type: 'INIT', data: initResponse }))
|
|
86
|
+
|
|
87
|
+
// 5. Process a spin request
|
|
88
|
+
const spinRequest = {
|
|
89
|
+
action: 'spin',
|
|
90
|
+
stakePerLine: 1,
|
|
91
|
+
selectedLines: 5,
|
|
92
|
+
totalStake: 5,
|
|
93
|
+
gameMode: 0, // 0 = main game, 1 = bonus/free spins
|
|
94
|
+
};
|
|
95
|
+
const spinResult = session.spin(spinRequest);
|
|
96
|
+
// Send result to client: ws.send(JSON.stringify({ type: 'SPIN', data: spinResult }))
|
|
97
|
+
|
|
98
|
+
// 6. Handle bonus actions (e.g., pick'em games)
|
|
99
|
+
const bonusResult = session.bonusAction('pick', { itemId: 'item-123' });
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Advanced Usage
|
|
103
|
+
|
|
104
|
+
### Progressive Jackpots
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { Random, JackpotManager } from 'slot-server';
|
|
108
|
+
|
|
109
|
+
const rng = new Random();
|
|
110
|
+
const jackpotConfig = {
|
|
111
|
+
enabled: true,
|
|
112
|
+
type: 'standalone' as const,
|
|
113
|
+
tiers: [
|
|
114
|
+
{
|
|
115
|
+
id: 'mini',
|
|
116
|
+
name: 'Mini Jackpot',
|
|
117
|
+
seedAmount: 100,
|
|
118
|
+
currentAmount: 100,
|
|
119
|
+
triggerProbability: 0.001,
|
|
120
|
+
contributionRate: 0.5, // 0.5% of each bet
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: 'major',
|
|
124
|
+
name: 'Major Jackpot',
|
|
125
|
+
seedAmount: 1000,
|
|
126
|
+
currentAmount: 1000,
|
|
127
|
+
triggerProbability: 0.0001,
|
|
128
|
+
contributionRate: 0.3,
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const jackpotManager = new JackpotManager(jackpotConfig, rng);
|
|
134
|
+
|
|
135
|
+
// On each spin, calculate contributions
|
|
136
|
+
const contributions = jackpotManager.calculateContribution(totalStake);
|
|
137
|
+
|
|
138
|
+
// Check for jackpot trigger
|
|
139
|
+
const jackpotWin = jackpotManager.checkTrigger();
|
|
140
|
+
if (jackpotWin) {
|
|
141
|
+
console.log(`Jackpot won: ${jackpotWin.tierName} - $${jackpotWin.winAmount}`);
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Ways-to-Win Evaluation
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import { WaysEvaluator } from 'slot-server';
|
|
149
|
+
|
|
150
|
+
const waysConfig = {
|
|
151
|
+
enabled: true,
|
|
152
|
+
waysCount: 243,
|
|
153
|
+
minMatches: 3,
|
|
154
|
+
direction: 'leftToRight' as const,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const evaluator = new WaysEvaluator(
|
|
158
|
+
waysConfig,
|
|
159
|
+
paytableData,
|
|
160
|
+
reelCount,
|
|
161
|
+
rowCount
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const result = evaluator.evaluate(spinResult, stakePerLine);
|
|
165
|
+
console.log(`Total ways: ${result.ways}, Total win: ${result.totalWin}`);
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Gamble Feature
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
import { Random, GambleManager } from 'slot-server';
|
|
172
|
+
|
|
173
|
+
const rng = new Random();
|
|
174
|
+
const gambleConfig = {
|
|
175
|
+
enabled: true,
|
|
176
|
+
maxGambleAmount: 500,
|
|
177
|
+
maxConsecutiveGambles: 5,
|
|
178
|
+
gameTypes: ['card', 'ladder', 'wheel'] as const,
|
|
179
|
+
autoCollectAt: 1000,
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const gambleManager = new GambleManager(gambleConfig, rng);
|
|
183
|
+
|
|
184
|
+
// Player wants to gamble their win
|
|
185
|
+
if (gambleManager.canGamble(currentWin)) {
|
|
186
|
+
const gambleRequest = {
|
|
187
|
+
action: 'gamble',
|
|
188
|
+
gambleType: 'card',
|
|
189
|
+
currentWin,
|
|
190
|
+
choice: 'red', // or 'black'
|
|
191
|
+
};
|
|
192
|
+
const gambleResult = gambleManager.processGamble(gambleRequest);
|
|
193
|
+
|
|
194
|
+
if (gambleResult.result === 'win') {
|
|
195
|
+
console.log(`Won $${gambleResult.winAmount}!`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Buy Bonus Feature
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
import { BuyBonusManager } from 'slot-server';
|
|
204
|
+
|
|
205
|
+
const buyBonusConfig = {
|
|
206
|
+
enabled: true,
|
|
207
|
+
costMultiplier: 100, // 100x bet
|
|
208
|
+
minBetRequired: 1,
|
|
209
|
+
maxPurchasesPerSession: 3,
|
|
210
|
+
cooldownMs: 5000, // 5 seconds between purchases
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const buyBonusManager = new BuyBonusManager(buyBonusConfig);
|
|
214
|
+
|
|
215
|
+
// Check if player can buy bonus
|
|
216
|
+
const canBuy = buyBonusManager.canBuyBonus(currentBet);
|
|
217
|
+
if (canBuy.canBuy) {
|
|
218
|
+
console.log(`Cost: $${canBuy.cost}`);
|
|
219
|
+
buyBonusManager.recordPurchase();
|
|
220
|
+
// Trigger bonus round
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Cascading Reels
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
import { CascadingReelsManager } from 'slot-server';
|
|
228
|
+
|
|
229
|
+
const cascadeConfig = {
|
|
230
|
+
enabled: true,
|
|
231
|
+
maxCascades: 10,
|
|
232
|
+
initialMultiplier: 1,
|
|
233
|
+
multiplierIncrement: 1, // +1 per cascade
|
|
234
|
+
symbolPool: [...], // Your symbol pool for filling empty positions
|
|
235
|
+
stopOnNoWinIncrease: true,
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const cascadeManager = new CascadingReelsManager(cascadeConfig);
|
|
239
|
+
|
|
240
|
+
// After initial spin evaluation with wins
|
|
241
|
+
const cascadeResult = cascadeManager.executeCascade(gameState, initialWins);
|
|
242
|
+
|
|
243
|
+
console.log(`Total cascades: ${cascadeResult.totalCascades}`);
|
|
244
|
+
console.log(`Final multiplier: ${cascadeResult.finalMultiplier}`);
|
|
245
|
+
console.log(`Total wins from cascades: ${cascadeResult.totalWinsFromCascades}`);
|
|
246
|
+
|
|
247
|
+
// Send cascade steps to client for animation
|
|
248
|
+
cascadeResult.steps.forEach(step => {
|
|
249
|
+
ws.send(JSON.stringify({
|
|
250
|
+
type: 'CASCADE_STEP',
|
|
251
|
+
data: {
|
|
252
|
+
cascadeNumber: step.cascadeNumber,
|
|
253
|
+
removedPositions: step.removedPositions,
|
|
254
|
+
droppedSymbols: step.droppedSymbols,
|
|
255
|
+
newSymbols: step.newSymbols,
|
|
256
|
+
winsInStep: step.winsInStep,
|
|
257
|
+
multiplier: step.multiplier,
|
|
258
|
+
}
|
|
259
|
+
}));
|
|
260
|
+
});
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Expanding & Sticky Wilds
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
import { ExpandingWildsManager } from 'slot-server';
|
|
267
|
+
|
|
268
|
+
const expandConfig = {
|
|
269
|
+
enabled: true,
|
|
270
|
+
triggerCondition: 'any_wild' as const,
|
|
271
|
+
wildMultiplier: 2,
|
|
272
|
+
multiplierStacking: 'multiply' as const,
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const stickyConfig = {
|
|
276
|
+
enabled: true,
|
|
277
|
+
persistSpins: 3,
|
|
278
|
+
maxStickyWilds: 5,
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const wildsManager = new ExpandingWildsManager(expandConfig, stickyConfig);
|
|
282
|
+
|
|
283
|
+
// Process expanding wilds after spin
|
|
284
|
+
const expandResult = wildsManager.processExpandingWilds(gameState);
|
|
285
|
+
|
|
286
|
+
if (expandResult.success) {
|
|
287
|
+
console.log(`Expanded ${expandResult.totalExpanded} positions`);
|
|
288
|
+
console.log(`Applied multiplier: ${expandResult.appliedMultiplier}`);
|
|
289
|
+
|
|
290
|
+
// Send expansion data to client
|
|
291
|
+
ws.send(JSON.stringify({
|
|
292
|
+
type: 'WILDS_EXPANDED',
|
|
293
|
+
data: {
|
|
294
|
+
expandedReels: expandResult.expandedReels,
|
|
295
|
+
expandedPositions: expandResult.expandedPositions,
|
|
296
|
+
modifiedGrid: expandResult.modifiedGrid,
|
|
297
|
+
}
|
|
298
|
+
}));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Manage sticky wilds across spins
|
|
302
|
+
let activeStickyWilds = []; // Persist this between spins
|
|
303
|
+
|
|
304
|
+
const stickyResult = wildsManager.processStickyWilds(gameState, activeStickyWilds);
|
|
305
|
+
activeStickyWilds = stickyResult.activeStickyWilds;
|
|
306
|
+
|
|
307
|
+
// Add new sticky wilds (e.g., from bonus feature)
|
|
308
|
+
const newStickyResult = wildsManager.addStickyWilds(
|
|
309
|
+
gameState,
|
|
310
|
+
[{ reel: 2, row: 1 }, { reel: 3, row: 1 }],
|
|
311
|
+
5 // persist for 5 spins
|
|
312
|
+
);
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Scatter Enhancements
|
|
316
|
+
|
|
317
|
+
```ts
|
|
318
|
+
import { ScatterEnhancementsManager } from 'slot-server';
|
|
319
|
+
|
|
320
|
+
const scatterConfig = {
|
|
321
|
+
enabled: true,
|
|
322
|
+
minScattersForTrigger: 3,
|
|
323
|
+
triggeredFeature: 'FREE_SPINS' as const,
|
|
324
|
+
spinAwardTable: { 3: 10, 4: 15, 5: 20 },
|
|
325
|
+
enableScatterPays: true,
|
|
326
|
+
scatterPayTable: { 3: 5, 4: 20, 5: 100 },
|
|
327
|
+
enableClusterTriggers: true,
|
|
328
|
+
clusterSizeForTrigger: 4,
|
|
329
|
+
enableCollection: true,
|
|
330
|
+
collectionTarget: 10,
|
|
331
|
+
collectionBonusSpins: 15,
|
|
332
|
+
enableTransformation: true,
|
|
333
|
+
transformationType: 'to_wild' as const,
|
|
334
|
+
enableMultiplierZones: true,
|
|
335
|
+
multiplierZones: [
|
|
336
|
+
{ reelStart: 1, reelEnd: 3, rowStart: 1, rowEnd: 1, multiplier: 2 }
|
|
337
|
+
],
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const scatterManager = new ScatterEnhancementsManager(scatterConfig);
|
|
341
|
+
|
|
342
|
+
// Evaluate scatters on current grid
|
|
343
|
+
const scatterResult = scatterManager.evaluateScatters(gameState);
|
|
344
|
+
|
|
345
|
+
if (scatterResult.success) {
|
|
346
|
+
console.log(`Found ${scatterResult.scatterCount} scatters`);
|
|
347
|
+
console.log(`Scatter pays: ${scatterResult.scatterPays}`);
|
|
348
|
+
|
|
349
|
+
// Process triggers
|
|
350
|
+
scatterResult.triggers.forEach(trigger => {
|
|
351
|
+
if (trigger.triggered) {
|
|
352
|
+
console.log(`Triggered: ${trigger.type} - ${trigger.featureType}`);
|
|
353
|
+
|
|
354
|
+
if (trigger.awardedSpins) {
|
|
355
|
+
console.log(`Awarded ${trigger.awardedSpins} free spins`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// Send scatter data to client
|
|
361
|
+
ws.send(JSON.stringify({
|
|
362
|
+
type: 'SCATTER_EVALUATED',
|
|
363
|
+
data: {
|
|
364
|
+
positions: scatterResult.positions,
|
|
365
|
+
triggers: scatterResult.triggers,
|
|
366
|
+
scatterPays: scatterResult.scatterPays,
|
|
367
|
+
}
|
|
368
|
+
}));
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Manage scatter collection system
|
|
372
|
+
let collectedData = { count: 0, values: [], spinsRemaining: 20 };
|
|
373
|
+
const { updated, collectionTrigger } = scatterManager.manageScatterCollection(
|
|
374
|
+
scatterResult,
|
|
375
|
+
collectedData
|
|
376
|
+
);
|
|
377
|
+
collectedData = updated;
|
|
378
|
+
|
|
379
|
+
if (collectionTrigger) {
|
|
380
|
+
console.log('Collection bonus triggered!');
|
|
381
|
+
// Award collection bonus
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### Respins / Hold & Win
|
|
386
|
+
|
|
387
|
+
```ts
|
|
388
|
+
import { RespinsManager } from 'slot-server';
|
|
389
|
+
|
|
390
|
+
const respinsConfig = {
|
|
391
|
+
enabled: true,
|
|
392
|
+
initialRespins: 3,
|
|
393
|
+
triggerType: 'symbol_count' as const,
|
|
394
|
+
minTriggerSymbols: 6,
|
|
395
|
+
symbolWeights: [
|
|
396
|
+
{ symbolId: 'money_1', probability: 0.4, baseValue: 1, isMoneySymbol: true },
|
|
397
|
+
{ symbolId: 'money_5', probability: 0.25, baseValue: 5, isMoneySymbol: true },
|
|
398
|
+
{ symbolId: 'money_10', probability: 0.15, baseValue: 10, isMoneySymbol: true },
|
|
399
|
+
{ symbolId: 'reset', probability: 0.08, isResetSymbol: true },
|
|
400
|
+
{ symbolId: 'jackpot_mini', probability: 0.05, isJackpotSymbol: true, jackpotType: 'mini' as const, baseValue: 25 },
|
|
401
|
+
{ symbolId: 'jackpot_major', probability: 0.02, isJackpotSymbol: true, jackpotType: 'major' as const, baseValue: 100 },
|
|
402
|
+
],
|
|
403
|
+
endOnNoSpecial: true,
|
|
404
|
+
fullScreenBonus: { multiplier: 3 },
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
const respinsManager = new RespinsManager(respinsConfig);
|
|
408
|
+
|
|
409
|
+
// Check if respins should trigger
|
|
410
|
+
const shouldTrigger = respinsManager.checkTrigger(gameState);
|
|
411
|
+
|
|
412
|
+
if (shouldTrigger) {
|
|
413
|
+
console.log('Hold & Win feature triggered!');
|
|
414
|
+
|
|
415
|
+
// Execute the respins feature
|
|
416
|
+
const respinsResult = respinsManager.execute(gameState);
|
|
417
|
+
|
|
418
|
+
console.log(`Final value: ${respinsResult.finalValue}`);
|
|
419
|
+
console.log(`Total respins used: ${respinsResult.totalRespinsUsed}`);
|
|
420
|
+
console.log(`Jackpots collected: ${respinsResult.jackpotsCollected.join(', ')}`);
|
|
421
|
+
|
|
422
|
+
// Send step-by-step updates to client for animation
|
|
423
|
+
respinsResult.steps.forEach((step, index) => {
|
|
424
|
+
ws.send(JSON.stringify({
|
|
425
|
+
type: 'RESPINS_STEP',
|
|
426
|
+
data: {
|
|
427
|
+
stepNumber: index + 1,
|
|
428
|
+
respinsRemaining: step.respinsRemaining,
|
|
429
|
+
lockedPositions: step.lockedPositions,
|
|
430
|
+
newSymbols: step.newSymbols,
|
|
431
|
+
valuesAdded: step.valuesAdded,
|
|
432
|
+
jackpotsAwarded: step.jackpotsAwarded,
|
|
433
|
+
reason: step.reason,
|
|
434
|
+
}
|
|
435
|
+
}));
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// Send final result
|
|
439
|
+
ws.send(JSON.stringify({
|
|
440
|
+
type: 'RESPINS_COMPLETE',
|
|
441
|
+
data: {
|
|
442
|
+
totalValue: respinsResult.finalValue,
|
|
443
|
+
jackpotsCollected: respinsResult.jackpotsCollected,
|
|
444
|
+
totalRespinsUsed: respinsResult.totalRespinsUsed,
|
|
445
|
+
}
|
|
446
|
+
}));
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Calculate potential payout for RTP verification
|
|
450
|
+
const potentialPayout = respinsManager.calculatePotentialPayout(betAmount);
|
|
451
|
+
console.log(`Expected respins payout: ${potentialPayout}`);
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### Audit System
|
|
455
|
+
|
|
456
|
+
```ts
|
|
457
|
+
import { AuditManager, InMemoryAuditStorage } from 'slot-server';
|
|
458
|
+
|
|
459
|
+
// Create audit manager with in-memory storage (use database in production)
|
|
460
|
+
const auditStorage = new InMemoryAuditStorage();
|
|
461
|
+
const auditManager = new AuditManager('my-game', auditStorage, 'operator-123');
|
|
462
|
+
|
|
463
|
+
// Record each spin for compliance
|
|
464
|
+
await auditManager.recordSpin({
|
|
465
|
+
spinId: 'spin-uuid-here',
|
|
466
|
+
playerId: 'player-123',
|
|
467
|
+
sessionId: 'session-456',
|
|
468
|
+
stake: 5.00,
|
|
469
|
+
win: 10.50,
|
|
470
|
+
balanceBefore: 100.00,
|
|
471
|
+
balanceAfter: 105.50,
|
|
472
|
+
rngSeed: 'abc123xyz',
|
|
473
|
+
result: [[...]], // 2D symbol array
|
|
474
|
+
gameMode: 0,
|
|
475
|
+
bonusTriggered: false,
|
|
476
|
+
jackpotContributions: [],
|
|
477
|
+
jackpotWins: [],
|
|
478
|
+
ipAddress: '192.168.1.1',
|
|
479
|
+
deviceInfo: 'Chrome on Windows',
|
|
480
|
+
location: 'US-NJ',
|
|
481
|
+
platform: 'web',
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
// Retrieve player history for dispute resolution
|
|
485
|
+
const records = await auditManager.getPlayerRecords(
|
|
486
|
+
'player-123',
|
|
487
|
+
Date.now() - 24 * 60 * 60 * 1000, // Last 24 hours
|
|
488
|
+
Date.now()
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
// Get activity summary for reporting
|
|
492
|
+
const summary = await auditManager.getPlayerSummary(
|
|
493
|
+
'player-123',
|
|
494
|
+
startTime,
|
|
495
|
+
endTime
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
// Export records for regulatory submission
|
|
499
|
+
const jsonExport = await auditManager.exportRecords(
|
|
500
|
+
'player-123',
|
|
501
|
+
startTime,
|
|
502
|
+
endTime,
|
|
503
|
+
'json' // or 'csv'
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
// Verify RNG integrity
|
|
507
|
+
const verification = await auditManager.verifyRngChain(spinIds);
|
|
508
|
+
if (!verification.valid) {
|
|
509
|
+
console.error('RNG chain verification failed:', verification.errors);
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### Debug Mode
|
|
514
|
+
|
|
515
|
+
```ts
|
|
516
|
+
import { DebugManager } from 'slot-server';
|
|
517
|
+
|
|
518
|
+
// Initialize debug manager
|
|
519
|
+
const debugManager = new DebugManager({
|
|
520
|
+
enabled: true,
|
|
521
|
+
logSpins: true,
|
|
522
|
+
logRngSeeds: true,
|
|
523
|
+
skipBalanceChecks: false,
|
|
524
|
+
verboseLevel: 'debug', // 'none' | 'errors' | 'warnings' | 'info' | 'debug'
|
|
525
|
+
outputFormat: 'console', // 'console' | 'json' | 'file'
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
// Force specific outcomes for testing
|
|
529
|
+
debugManager.configure({
|
|
530
|
+
forcedOutcomes: [
|
|
531
|
+
{
|
|
532
|
+
condition: { type: 'spinNumber', value: 10 },
|
|
533
|
+
result: {
|
|
534
|
+
symbols: [[1, 2, 3], [4, 5, 6], ...],
|
|
535
|
+
winAmount: 100,
|
|
536
|
+
bonusTriggered: true,
|
|
537
|
+
},
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
condition: { type: 'betAmount', value: 50 },
|
|
541
|
+
result: {
|
|
542
|
+
winAmount: 500,
|
|
543
|
+
jackpotWon: 'mini',
|
|
544
|
+
},
|
|
545
|
+
},
|
|
546
|
+
],
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
// Check for forced outcome before spin
|
|
550
|
+
const forcedResult = debugManager.checkForcedOutcome({
|
|
551
|
+
spinNumber: 10,
|
|
552
|
+
rngSeed: 'abc123',
|
|
553
|
+
betAmount: 50,
|
|
554
|
+
playerId: 'player-123',
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
if (forcedResult) {
|
|
558
|
+
console.log('Using forced outcome for testing');
|
|
559
|
+
return forcedResult;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Log spin results
|
|
563
|
+
debugManager.logSpin('spin-uuid', spinResult, 'rng-seed-here');
|
|
564
|
+
|
|
565
|
+
// Get debug state and statistics
|
|
566
|
+
const state = debugManager.getState();
|
|
567
|
+
const stats = debugManager.getStatistics();
|
|
568
|
+
|
|
569
|
+
console.log(`Spins tracked: ${stats.spinsTracked}`);
|
|
570
|
+
console.log(`Forced outcomes applied: ${stats.forcedOutcomesApplied}`);
|
|
571
|
+
console.log(`Errors logged: ${stats.errorCount}`);
|
|
572
|
+
|
|
573
|
+
// Export logs for analysis
|
|
574
|
+
const logs = debugManager.exportLogs();
|
|
575
|
+
|
|
576
|
+
// Reset debug state
|
|
577
|
+
debugManager.reset();
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
## Architecture
|
|
581
|
+
|
|
582
|
+
The library is designed to work seamlessly with the Slot Engine client:
|
|
583
|
+
|
|
584
|
+
```
|
|
585
|
+
┌─────────────────┐ WebSocket ┌──────────────────┐
|
|
586
|
+
│ Slot Server │ ◄──────────────────────► │ Slot Engine │
|
|
587
|
+
│ (This Library) │ │ (Client) │
|
|
588
|
+
├─────────────────┤ ├──────────────────┤
|
|
589
|
+
│ • GameSession │ │ • ReelRenderer │
|
|
590
|
+
│ • SpinEvaluator │ │ • UIManager │
|
|
591
|
+
│ • BonusManager │◄──── Game State ───────► │ • PaylineRender │
|
|
592
|
+
│ • JackpotMgr │ │ • ParticleSys │
|
|
593
|
+
│ • HistoryMgr │ │ • SoundManager │
|
|
594
|
+
│ • CascadeMgr │ │ • CascadeAnim │
|
|
595
|
+
│ • WildsMgr │ │ • WildEffects │
|
|
596
|
+
│ • ScatterMgr │ │ • ScatterFX │
|
|
597
|
+
└─────────────────┘ └──────────────────┘
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
## Message Protocol
|
|
601
|
+
|
|
602
|
+
All communication follows a standard WebSocket message format:
|
|
603
|
+
|
|
604
|
+
```ts
|
|
605
|
+
// Client → Server
|
|
606
|
+
{
|
|
607
|
+
type: 'SPIN',
|
|
608
|
+
data: {
|
|
609
|
+
action: 'spin',
|
|
610
|
+
stakePerLine: 1,
|
|
611
|
+
selectedLines: 5,
|
|
612
|
+
totalStake: 5,
|
|
613
|
+
gameMode: 0
|
|
614
|
+
},
|
|
615
|
+
messageId: 'uuid-here',
|
|
616
|
+
timestamp: 1234567890
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Server → Client
|
|
620
|
+
{
|
|
621
|
+
type: 'SPIN',
|
|
622
|
+
data: {
|
|
623
|
+
result: [[...]],
|
|
624
|
+
totalWin: 10.50,
|
|
625
|
+
winLines: [...],
|
|
626
|
+
balanceAfter: 95.50,
|
|
627
|
+
bonusTriggered: false
|
|
628
|
+
},
|
|
629
|
+
messageId: 'uuid-here',
|
|
630
|
+
timestamp: 1234567890
|
|
631
|
+
}
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
## Responsible Gaming
|
|
635
|
+
|
|
636
|
+
The library includes comprehensive responsible gaming features through the `ResponsibleGamingManager`:
|
|
637
|
+
|
|
638
|
+
- **Session Time Limits** - Track and limit play duration with warnings at 80% threshold
|
|
639
|
+
- **Loss Limits** - Stop play when loss threshold reached with automatic blocking
|
|
640
|
+
- **Win Goals** - Notify players when they reach their win targets
|
|
641
|
+
- **Reality Checks** - Periodic reminders showing time played, stakes, wins, and net result
|
|
642
|
+
- **Self-Exclusion** - Block access during exclusion period with date validation
|
|
643
|
+
- **Bet Limits** - Enforce maximum bet amounts
|
|
644
|
+
- **Activity Monitoring** - Track player statistics and generate alerts
|
|
645
|
+
- **RG Reports** - Export player activity reports for transparency
|
|
646
|
+
|
|
647
|
+
### Usage Example
|
|
648
|
+
|
|
649
|
+
```ts
|
|
650
|
+
import { ResponsibleGamingManager } from 'slot-server';
|
|
651
|
+
|
|
652
|
+
// Initialize with limits and player state
|
|
653
|
+
const limits = {
|
|
654
|
+
sessionTimeLimit: 60, // minutes
|
|
655
|
+
lossLimit: 100,
|
|
656
|
+
winGoal: 500,
|
|
657
|
+
maxBetLimit: 10,
|
|
658
|
+
realityCheckInterval: 15,
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
const playerState = {
|
|
662
|
+
balance: 1000,
|
|
663
|
+
bet: 1,
|
|
664
|
+
gameMode: 0 as const,
|
|
665
|
+
freeSpinsRemaining: 0,
|
|
666
|
+
bonusActive: false,
|
|
667
|
+
history: [],
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
const rgManager = new ResponsibleGamingManager(limits, playerState);
|
|
671
|
+
|
|
672
|
+
// Check if player can place a bet before spinning
|
|
673
|
+
const canBet = rgManager.canPlaceBet(5);
|
|
674
|
+
if (!canBet.success) {
|
|
675
|
+
console.error(`Bet rejected: ${canBet.error}`);
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Apply spin results to tracking
|
|
680
|
+
rgManager.applyResult(stake, win);
|
|
681
|
+
|
|
682
|
+
// Update player state after each spin
|
|
683
|
+
rgManager.updatePlayerState({
|
|
684
|
+
sessionLoss: updatedLoss,
|
|
685
|
+
sessionWin: updatedWin,
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
// Check for reality check
|
|
689
|
+
if (rgManager.shouldShowRealityCheck()) {
|
|
690
|
+
const realityCheck = rgManager.generateRealityCheck();
|
|
691
|
+
// Send to client for display
|
|
692
|
+
ws.send(JSON.stringify({ type: 'REALITY_CHECK', data: realityCheck }));
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// Get active alerts
|
|
696
|
+
const alerts = rgManager.getAlerts();
|
|
697
|
+
alerts.forEach(alert => {
|
|
698
|
+
if (alert.actionRequired) {
|
|
699
|
+
// Critical alert requiring immediate action
|
|
700
|
+
console.warn(`${alert.type}: ${alert.message}`);
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
// Get player activity statistics
|
|
705
|
+
const stats = rgManager.getPlayerActivityStats();
|
|
706
|
+
console.log('Player Stats:', stats);
|
|
707
|
+
|
|
708
|
+
// Export RG report for player download
|
|
709
|
+
const report = rgManager.exportRGReport();
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
### Alert Types
|
|
713
|
+
|
|
714
|
+
The manager generates alerts with different severity levels:
|
|
715
|
+
|
|
716
|
+
- **info** - Win goals reached, general notifications
|
|
717
|
+
- **warning** - Approaching limits (80% threshold)
|
|
718
|
+
- **critical** - Limits reached, self-exclusion active (requires action)
|
|
719
|
+
|
|
720
|
+
### Self-Exclusion
|
|
721
|
+
|
|
722
|
+
```ts
|
|
723
|
+
// Set self-exclusion until a specific date
|
|
724
|
+
rgManager.updateLimits({
|
|
725
|
+
selfExclusionUntil: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), // 30 days
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
// Check exclusion status
|
|
729
|
+
const status = rgManager.getSelfExclusionStatus();
|
|
730
|
+
if (status.isExcluded) {
|
|
731
|
+
console.log(`Excluded until ${status.exclusionUntil}, ${status.remainingDays} days remaining`);
|
|
732
|
+
}
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
### Session Management
|
|
736
|
+
|
|
737
|
+
```ts
|
|
738
|
+
// Reset session tracking when player starts new session
|
|
739
|
+
rgManager.resetSession();
|
|
740
|
+
|
|
741
|
+
// Get session time info
|
|
742
|
+
const timeInfo = rgManager.getSessionTimeInfo();
|
|
743
|
+
console.log(`Playing for ${timeInfo.elapsedMinutes} minutes`);
|
|
744
|
+
console.log(`Limit: ${timeInfo.limitMinutes} minutes`);
|
|
745
|
+
console.log(`Warning reached: ${timeInfo.warningThresholdReached}`);
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
## Audit & Compliance
|
|
749
|
+
|
|
750
|
+
The `AuditManager` provides complete regulatory compliance:
|
|
751
|
+
|
|
752
|
+
```ts
|
|
753
|
+
import { AuditManager, InMemoryAuditStorage } from 'slot-server';
|
|
754
|
+
|
|
755
|
+
const auditStorage = new InMemoryAuditStorage();
|
|
756
|
+
const auditManager = new AuditManager(auditStorage);
|
|
757
|
+
|
|
758
|
+
// Record every spin
|
|
759
|
+
await auditManager.recordSpin({
|
|
760
|
+
spinId: 'spin-123',
|
|
761
|
+
playerId: 'player-456',
|
|
762
|
+
gameId: 'my-slot',
|
|
763
|
+
stake: 5,
|
|
764
|
+
win: 0,
|
|
765
|
+
balanceBefore: 1000,
|
|
766
|
+
balanceAfter: 995,
|
|
767
|
+
rngSeed: 'abc123',
|
|
768
|
+
result: [[...], ...],
|
|
769
|
+
jackpotContributions: [],
|
|
770
|
+
jackpotWins: [],
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
// Retrieve player history for disputes
|
|
774
|
+
const history = await auditManager.getPlayerHistory('player-456', startTime, endTime);
|
|
775
|
+
|
|
776
|
+
// Generate activity summary for reporting
|
|
777
|
+
const summary = await auditManager.getActivitySummary('player-456', lastMonthStart, lastMonthEnd);
|
|
778
|
+
|
|
779
|
+
// Export records for regulators
|
|
780
|
+
const jsonExport = await auditManager.exportRecords('json');
|
|
781
|
+
const csvExport = await auditManager.exportRecords('csv');
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
## Debug Mode
|
|
785
|
+
|
|
786
|
+
The `DebugManager` provides comprehensive testing tools:
|
|
787
|
+
|
|
788
|
+
```ts
|
|
789
|
+
import { DebugManager } from 'slot-server';
|
|
790
|
+
|
|
791
|
+
const debugManager = new DebugManager({
|
|
792
|
+
enabled: true,
|
|
793
|
+
logSpins: true,
|
|
794
|
+
logRngSeeds: true,
|
|
795
|
+
skipBalanceChecks: true, // For testing only
|
|
796
|
+
verboseLevel: 'debug',
|
|
797
|
+
outputFormat: 'console',
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
// Force specific outcomes for testing
|
|
801
|
+
debugManager.setForcedOutcome({
|
|
802
|
+
condition: { type: 'spinNumber', value: 10 },
|
|
803
|
+
result: { bonusTriggered: true, winAmount: 500 },
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
// Log spin results
|
|
807
|
+
debugManager.logSpin(spinRequest, spinResult, 'info');
|
|
808
|
+
|
|
809
|
+
// Get debug statistics
|
|
810
|
+
const stats = debugManager.getStatistics();
|
|
811
|
+
console.log(`Total spins: ${stats.spinCount}`);
|
|
812
|
+
|
|
813
|
+
// Export logs for analysis
|
|
814
|
+
const logs = debugManager.exportLogs('json');
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
## Wallet & Transaction Management
|
|
818
|
+
The `WalletManager` provides a database-agnostic, atomic transaction system designed for real-money gaming. It ensures financial integrity through double-entry ledger logic (recording `balanceBefore` and `balanceAfter` for every operation) and optimistic locking to prevent race conditions.
|
|
819
|
+
|
|
820
|
+
### Key Features
|
|
821
|
+
- Atomic Transactions: All balance updates are wrapped in transaction blocks with rollback support.
|
|
822
|
+
- Optimistic Locking: Prevents concurrent modification issues using version checking.
|
|
823
|
+
- Double-Entry Ledger: Immutable history of every credit and debit.
|
|
824
|
+
- Pluggable Storage: Implements the `IWalletStorage` interface to work with any database (PostgreSQL, MongoDB, Redis, etc.).
|
|
825
|
+
- Precision Handling: Configurable decimal precision for different currencies.
|
|
826
|
+
|
|
827
|
+
### Basic Usage
|
|
828
|
+
```ts
|
|
829
|
+
import { WalletManager, InMemoryWalletStorage } from 'slot-server';
|
|
830
|
+
|
|
831
|
+
// 1. Initialize Storage (Use InMemoryWalletStorage for testing/dev)
|
|
832
|
+
const storage = new InMemoryWalletStorage();
|
|
833
|
+
|
|
834
|
+
// 2. Initialize Manager
|
|
835
|
+
const walletManager = new WalletManager(storage, {
|
|
836
|
+
currency: 'USD',
|
|
837
|
+
precision: 2,
|
|
838
|
+
allowNegativeBalance: false
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
// 3. Create a wallet for a player
|
|
842
|
+
await walletManager.createWallet('player-123', '100.00');
|
|
843
|
+
|
|
844
|
+
// 4. Place a Bet (Debit)
|
|
845
|
+
const betResult = await walletManager.placeBet('player-123', '10.00', {
|
|
846
|
+
referenceId: 'spin-uuid-999',
|
|
847
|
+
gameId: 'slot-game-01'
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
if (!betResult.success) {
|
|
851
|
+
console.error('Bet failed:', betResult.error);
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
console.log(`New Balance: ${betResult.balance}`);
|
|
856
|
+
|
|
857
|
+
// 5. Process a Payout (Credit)
|
|
858
|
+
const winAmount = '25.00';
|
|
859
|
+
const payoutResult = await walletManager.credit('player-123', winAmount, 'payout', {
|
|
860
|
+
referenceId: 'spin-uuid-999',
|
|
861
|
+
description: 'Line hit payout'
|
|
862
|
+
});
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
### Implementing Custom Storage
|
|
866
|
+
To integrate with your production database, implement the `IWalletStorage` interface:
|
|
867
|
+
```ts
|
|
868
|
+
import { IWalletStorage, IWalletBalance, ITransaction } from 'slot-server';
|
|
869
|
+
import { db, userBalances, transactions } from './my-database-schema'; // Your DB schema
|
|
870
|
+
|
|
871
|
+
class PostgresWalletStorage implements IWalletStorage {
|
|
872
|
+
async getBalance(userId: string): Promise<IWalletBalance | null> {
|
|
873
|
+
const result = await db.select().from(userBalances).where({ userId });
|
|
874
|
+
return result[0] || null;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
async saveTransaction(tx: ITransaction): Promise<void> {
|
|
878
|
+
await db.insert(transactions).values({
|
|
879
|
+
id: tx.id,
|
|
880
|
+
userId: tx.userId,
|
|
881
|
+
type: tx.type,
|
|
882
|
+
amount: tx.amount,
|
|
883
|
+
balanceBefore: tx.balanceBefore,
|
|
884
|
+
balanceAfter: tx.balanceAfter,
|
|
885
|
+
referenceId: tx.referenceId,
|
|
886
|
+
createdAt: new Date(tx.createdAt)
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
async updateBalance(userId: string, newBalance: string, version: number): Promise<boolean> {
|
|
891
|
+
// Implement optimistic locking here
|
|
892
|
+
const result = await db
|
|
893
|
+
.update(userBalances)
|
|
894
|
+
.set({ balance: newBalance, updatedAt: new Date() })
|
|
895
|
+
.where({ userId, version }) // 'version' column required for locking
|
|
896
|
+
.returning();
|
|
897
|
+
|
|
898
|
+
return result.length > 0;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// ... implement other required methods
|
|
902
|
+
}
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
### Integration with Game Session
|
|
906
|
+
A common pattern is to wrap the game spin cycle with wallet operations:
|
|
907
|
+
```ts
|
|
908
|
+
async function handleSpin(sessionId: string, playerId: string, betAmount: string) {
|
|
909
|
+
// 1. Debit Bet
|
|
910
|
+
const betTx = await walletManager.placeBet(playerId, betAmount, { referenceId: sessionId });
|
|
911
|
+
if (!betTx.success) throw new Error('Insufficient funds');
|
|
912
|
+
|
|
913
|
+
try {
|
|
914
|
+
// 2. Evaluate Game Logic
|
|
915
|
+
const gameResult = slotEngine.spin(betAmount);
|
|
916
|
+
|
|
917
|
+
// 3. Credit Wins (if any)
|
|
918
|
+
if (gameResult.totalWin > 0) {
|
|
919
|
+
await walletManager.credit(playerId, gameResult.totalWin.toString(), 'payout', {
|
|
920
|
+
referenceId: sessionId
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
return gameResult;
|
|
925
|
+
} catch (error) {
|
|
926
|
+
// 4. Rollback on Critical Error (Optional depending on requirements)
|
|
927
|
+
await walletManager.refund(playerId, betAmount, 'error_refund', { referenceId: sessionId });
|
|
928
|
+
throw error;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
```
|
|
932
|
+
### Transaction Types
|
|
933
|
+
The system supports the following standard transaction types:
|
|
934
|
+
- `bet`: Wager placement
|
|
935
|
+
- `payout`: Standard win payment
|
|
936
|
+
- `jackpot_win`: Progressive jackpot award
|
|
937
|
+
- `bonus_payout`: Feature round winnings
|
|
938
|
+
- `deposit`: External funds added
|
|
939
|
+
- `withdrawal`: External funds added
|
|
940
|
+
- `fee`: House fee or commission
|
|
941
|
+
- `refund`: Reversal of a previous transaction
|
|
942
|
+
|
|
943
|
+
## API Reference
|
|
944
|
+
|
|
945
|
+
See the generated TypeScript definitions (`dist/index.d.ts`) for complete API documentation.
|
|
946
|
+
|
|
947
|
+
## License
|
|
948
|
+
|
|
949
|
+
MIT
|