shufflecom-calculations 6.12.1 → 6.14.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shufflecom-calculations",
3
- "version": "6.12.1",
3
+ "version": "6.14.0",
4
4
  "description": "",
5
5
  "types": "lib/index.d.ts",
6
6
  "main": "lib/index.js",
@@ -19,5 +19,5 @@
19
19
  },
20
20
  "author": "",
21
21
  "license": "ISC",
22
- "gitHead": "150cf4b9aac66f07c03577e943656c61b9daf3b3"
22
+ "gitHead": "bb40b80986c6a996592546bb531b86a6992fa184"
23
23
  }
@@ -33,4 +33,4 @@ export const STONKS_CHECKPOINT_INTERVAL_TICKS = 500;
33
33
 
34
34
  export const STONKS_PRICE_BAR_INTERVAL_TICKS = 5_000 / STONKS_TICK_DURATION_MS;
35
35
 
36
- export const STONKS_PROFIT_FEE_RATE = BigNumber(0.05);
36
+ export const STONKS_PAYOUT_FEE_RATE = BigNumber(0.02);
@@ -44,15 +44,22 @@ describe('StonksRules', () => {
44
44
  it('inverts multiplierAtPrice without depending on amount', () => {
45
45
  const bet = openBet({ amount: number(0), leverage: number(10) });
46
46
 
47
- const impliedPrice = StonksRules.impliedPriceForMultiplier(bet, number(1.5));
47
+ const impliedPrice = StonksRules.impliedPriceForMultiplier(bet, number('1.47'));
48
48
 
49
- expect(StonksRules.multiplierAtPrice(bet, impliedPrice)).toBigNumberEqual(1.5);
49
+ expect(StonksRules.multiplierAtPrice(bet, impliedPrice)).toBigNumberEqual('1.47');
50
50
  });
51
51
 
52
52
  it('moves in the opposite price direction for DOWN bets', () => {
53
53
  const bet = openBet({ direction: StonksDirection.DOWN, leverage: number(10) });
54
54
 
55
- expect(StonksRules.impliedPriceForMultiplier(bet, number(1.5))).toBigNumberEqual(95);
55
+ expect(StonksRules.impliedPriceForMultiplier(bet, number('1.47'))).toBigNumberEqual(95);
56
+ });
57
+
58
+ it('treats the multiplier as net of the fee, so 1x leverage needs more than a 100% move to reach 2x', () => {
59
+ const bet = openBet({ leverage: number(1) });
60
+
61
+ expect(StonksRules.impliedPriceForMultiplier(bet, number('1.96'))).toBigNumberEqual(200);
62
+ expect(StonksRules.impliedPriceForMultiplier(bet, number(2))).toBigNumberGreaterThan(200);
56
63
  });
57
64
  });
58
65
 
@@ -122,12 +129,22 @@ describe('StonksRules', () => {
122
129
  });
123
130
  });
124
131
 
125
- describe('hasReachedTargetPrice', () => {
126
- it('is inclusive of an exact match, in the direction of profit', () => {
127
- expect(StonksRules.hasReachedTargetPrice(StonksDirection.UP, number(110), number(110))).toBe(true);
128
- expect(StonksRules.hasReachedTargetPrice(StonksDirection.UP, number(109), number(110))).toBe(false);
129
- expect(StonksRules.hasReachedTargetPrice(StonksDirection.DOWN, number(90), number(90))).toBe(true);
130
- expect(StonksRules.hasReachedTargetPrice(StonksDirection.DOWN, number(91), number(90))).toBe(false);
132
+ describe('hasReachedTakeProfit', () => {
133
+ it('compares the net multiplier at the price against the take-profit, inclusive, in the direction of profit', () => {
134
+ const up = openBet({ direction: StonksDirection.UP, leverage: number(10) });
135
+ const down = openBet({ direction: StonksDirection.DOWN, leverage: number(10) });
136
+
137
+ expect(StonksRules.hasReachedTakeProfit(up, number(105), number('1.47'))).toBe(true);
138
+ expect(StonksRules.hasReachedTakeProfit(up, number('104.99'), number('1.47'))).toBe(false);
139
+ expect(StonksRules.hasReachedTakeProfit(down, number(95), number('1.47'))).toBe(true);
140
+ expect(StonksRules.hasReachedTakeProfit(down, number('95.01'), number('1.47'))).toBe(false);
141
+ });
142
+
143
+ it('needs the price to clear the fee: a 10% move at 10x reaches 1.96x, not 2x', () => {
144
+ const bet = openBet({ leverage: number(10) });
145
+
146
+ expect(StonksRules.hasReachedTakeProfit(bet, number(110), number('1.96'))).toBe(true);
147
+ expect(StonksRules.hasReachedTakeProfit(bet, number(110), number(2))).toBe(false);
131
148
  });
132
149
  });
133
150
 
@@ -141,10 +158,11 @@ describe('StonksRules', () => {
141
158
  });
142
159
 
143
160
  describe('multiplierAtPrice', () => {
144
- it('is 1 at entry and floors at zero once loss would exceed the wager', () => {
161
+ it('is net of the fee at entry and floors at zero once loss would exceed the wager', () => {
145
162
  const bet = openBet({ entryPrice: number(100), leverage: number(10), direction: StonksDirection.UP });
146
163
 
147
- expect(StonksRules.multiplierAtPrice(bet, number(100))).toBigNumberEqual(1);
164
+ expect(StonksRules.multiplierAtPrice(bet, number(100))).toBigNumberEqual('0.98');
165
+ expect(StonksRules.multiplierAtPrice(bet, number(110))).toBigNumberEqual('1.96');
148
166
  expect(StonksRules.multiplierAtPrice(bet, number(80))).toBigNumberEqual(0);
149
167
  });
150
168
  });
@@ -185,11 +203,11 @@ describe('StonksRules', () => {
185
203
  });
186
204
  });
187
205
 
188
- it('fires MAX_PAYOUT once the gross payout reaches the live cap, even overshooting it', () => {
206
+ it('fires MAX_PAYOUT once the net payout reaches the live cap, even overshooting it', () => {
189
207
  const bet = openBet({ leverage: number(100) });
190
208
  const cap = number(150);
191
209
 
192
- // At price 101 the gross payout (200) overshoots the cap (150) — MAX_PAYOUT still fires,
210
+ // At price 101 the net payout (196) overshoots the cap (150) — MAX_PAYOUT still fires,
193
211
  // and the caller settles at this real tick price regardless, not a price back-solved from the cap.
194
212
  expect(StonksRules.evaluateTriggers(bet, number(101), cap)).toEqual({
195
213
  fired: true,
@@ -212,7 +230,7 @@ describe('StonksRules', () => {
212
230
  const reason = { kind: StonksBetTerminalReason.MAX_PAYOUT, appliedCap: cap } as const;
213
231
 
214
232
  expect(StonksRules.evaluateTriggers(bet, impliedPrice, cap)).toEqual({ fired: true, reason });
215
- expect(StonksRules.grossPayoutForReason(bet, reason, impliedPrice)).toBigNumberEqual(250);
233
+ expect(StonksRules.netPayoutForReason(bet, reason, impliedPrice)).toBigNumberEqual(250);
216
234
  });
217
235
 
218
236
  // The mirror of the case above, and the reason the cap cannot simply be checked before the
@@ -222,12 +240,12 @@ describe('StonksRules', () => {
222
240
  const takeProfitMultiplier = number(2);
223
241
  const bet = openBet({ takeProfitMultiplier });
224
242
  const cap = number(250);
225
- // One tick jumps to 130, well past the TP-implied 110: the price-derived payout there is 400
226
- // and clears the cap, but the agreed take-profit payout is only 100 × 2 = 200, under it.
243
+ // One tick jumps to 130, well past the TP-implied ~110.4: the price-derived net payout there
244
+ // is 392 and clears the cap, but the agreed take-profit payout is only 100 × 2 = 200, under it.
227
245
  const reason = { kind: StonksBetTerminalReason.TAKE_PROFIT, takeProfitMultiplier } as const;
228
246
 
229
247
  expect(StonksRules.evaluateTriggers(bet, number(130), cap)).toEqual({ fired: true, reason });
230
- expect(StonksRules.grossPayoutForReason(bet, reason, number(130))).toBigNumberEqual(200);
248
+ expect(StonksRules.netPayoutForReason(bet, reason, number(130))).toBigNumberEqual(200);
231
249
  });
232
250
 
233
251
  it('fires TAKE_PROFIT, not MAX_PAYOUT, when the agreed take-profit payout exactly equals the live cap', () => {
@@ -254,38 +272,32 @@ describe('StonksRules', () => {
254
272
  });
255
273
  });
256
274
 
257
- describe('grossPayoutForReason', () => {
275
+ describe('netPayoutForReason', () => {
258
276
  const bet = openBet({ leverage: number(10) });
259
277
 
260
278
  it('TAKE_PROFIT credits wager × multiplier exactly, ignoring price', () => {
261
279
  const takeProfitMultiplier = number('1.5');
262
280
 
263
281
  expect(
264
- StonksRules.grossPayoutForReason(bet, { kind: StonksBetTerminalReason.TAKE_PROFIT, takeProfitMultiplier }, number(999)),
282
+ StonksRules.netPayoutForReason(bet, { kind: StonksBetTerminalReason.TAKE_PROFIT, takeProfitMultiplier }, number(999)),
265
283
  ).toBigNumberEqual(150);
266
284
  });
267
285
 
268
286
  it('LIQUIDATION is exact zero regardless of price', () => {
269
- expect(StonksRules.grossPayoutForReason(bet, { kind: StonksBetTerminalReason.LIQUIDATION }, number(999))).toBigNumberEqual(0);
287
+ expect(StonksRules.netPayoutForReason(bet, { kind: StonksBetTerminalReason.LIQUIDATION }, number(999))).toBigNumberEqual(0);
270
288
  });
271
289
 
272
290
  it('MAX_PAYOUT is exactly the applied cap, ignoring price', () => {
273
291
  const appliedCap = number(150);
274
292
 
275
293
  expect(
276
- StonksRules.grossPayoutForReason(bet, { kind: StonksBetTerminalReason.MAX_PAYOUT, appliedCap }, number(999)),
294
+ StonksRules.netPayoutForReason(bet, { kind: StonksBetTerminalReason.MAX_PAYOUT, appliedCap }, number(999)),
277
295
  ).toBigNumberEqual(150);
278
296
  });
279
297
 
280
- it('CASHOUT and ROUND_END fall back to the price-derived gross payout', () => {
281
- const priceDerivedPayout = StonksRules.grossPayoutAtPrice(bet, number(110));
282
-
283
- expect(StonksRules.grossPayoutForReason(bet, { kind: StonksBetTerminalReason.CASHOUT }, number(110))).toBigNumberEqual(
284
- priceDerivedPayout,
285
- );
286
- expect(StonksRules.grossPayoutForReason(bet, { kind: StonksBetTerminalReason.ROUND_END }, number(110))).toBigNumberEqual(
287
- priceDerivedPayout,
288
- );
298
+ it('CASHOUT and ROUND_END fall back to the price-derived payout, net of the fee', () => {
299
+ expect(StonksRules.netPayoutForReason(bet, { kind: StonksBetTerminalReason.CASHOUT }, number(110))).toBigNumberEqual(196);
300
+ expect(StonksRules.netPayoutForReason(bet, { kind: StonksBetTerminalReason.ROUND_END }, number(110))).toBigNumberEqual(196);
289
301
  });
290
302
  });
291
303
 
@@ -302,34 +314,29 @@ describe('StonksRules', () => {
302
314
  it.each([
303
315
  [StonksDirection.UP, number(101)],
304
316
  [StonksDirection.DOWN, number(99)],
305
- ])('every other reason falls back to the price-derived multiplier, independently of wager (%s)', (direction, price) => {
317
+ ])('every other reason falls back to the price-derived net multiplier, independently of wager (%s)', (direction, price) => {
306
318
  const bet = openBet({ direction, amount: number(0) });
307
319
 
308
- expect(StonksRules.multiplierForReason(bet, { kind: StonksBetTerminalReason.CASHOUT }, price)).toBigNumberEqual(1.1);
309
- expect(StonksRules.multiplierForReason(bet, { kind: StonksBetTerminalReason.LIQUIDATION }, price)).toBigNumberEqual(1.1);
320
+ expect(StonksRules.multiplierForReason(bet, { kind: StonksBetTerminalReason.CASHOUT }, price)).toBigNumberEqual('1.078');
321
+ expect(StonksRules.multiplierForReason(bet, { kind: StonksBetTerminalReason.LIQUIDATION }, price)).toBigNumberEqual('1.078');
310
322
  });
311
323
  });
312
324
 
313
- describe('netPayout', () => {
314
- const wager = number(100);
315
-
316
- it('passes losses and breakeven payouts through untaxed', () => {
317
- expect(StonksRules.netPayout(number(90), wager)).toBigNumberEqual(90);
318
- expect(StonksRules.netPayout(number(0), wager)).toBigNumberEqual(0);
319
- expect(StonksRules.netPayout(wager, wager)).toBigNumberEqual(wager);
325
+ describe('netOfFee / grossOfFee', () => {
326
+ it('takes a flat 2% fee on every payout, including losses and breakeven', () => {
327
+ expect(StonksRules.netOfFee(number(90))).toBigNumberEqual('88.2');
328
+ expect(StonksRules.netOfFee(number(100))).toBigNumberEqual(98);
329
+ expect(StonksRules.netOfFee(number(101))).toBigNumberEqual('98.98');
330
+ expect(StonksRules.netOfFee(number(200))).toBigNumberEqual(196);
320
331
  });
321
332
 
322
- it('taxes only the profit portion at 5%, never the wager', () => {
323
- // gross 101 = wager + 1 profit; only the 1 is taxed, so net = 100 + 0.95 = 100.95
324
- expect(StonksRules.netPayout(number(101), wager)).toBigNumberEqual('100.95');
325
- // gross 200 = wager + 100 profit; net = 100 + 95 = 195
326
- expect(StonksRules.netPayout(number(200), wager)).toBigNumberEqual(195);
333
+ it('leaves a liquidated payout at zero', () => {
334
+ expect(StonksRules.netOfFee(number(0))).toBigNumberEqual(0);
327
335
  });
328
336
 
329
- it('never drops a nominal win below the wager, however small the gross profit is', () => {
330
- const net = StonksRules.netPayout(number('100.01'), wager);
331
-
332
- expect(net).toBigNumberGreaterThan(wager);
337
+ it('grossOfFee inverts netOfFee', () => {
338
+ expect(StonksRules.grossOfFee(number(98))).toBigNumberEqual(100);
339
+ expect(StonksRules.grossOfFee(StonksRules.netOfFee(number('123.456')))).toBigNumberEqual('123.456');
333
340
  });
334
341
  });
335
342
  });
@@ -4,7 +4,7 @@ import {
4
4
  STONKS_INTERNAL_PRICE_DECIMAL_PLACES,
5
5
  STONKS_MAX_STALE_CLIENT_TICKS,
6
6
  STONKS_MIN_TAKE_PROFIT_MULTIPLIER,
7
- STONKS_PROFIT_FEE_RATE,
7
+ STONKS_PAYOUT_FEE_RATE,
8
8
  STONKS_ROUND_DURATION_MS,
9
9
  STONKS_ROUND_TICK_COUNT,
10
10
  STONKS_TICK_DURATION_MS,
@@ -38,7 +38,6 @@ export interface StonksTerminalBetState extends StonksOpenBetState {
38
38
  readonly terminalTick: number;
39
39
  readonly terminalPrice: BigNumber;
40
40
  readonly multiplier: BigNumber;
41
- readonly grossPayout: BigNumber;
42
41
  readonly netPayout: BigNumber;
43
42
  }
44
43
 
@@ -67,7 +66,7 @@ export class StonksRules {
67
66
  targetMultiplier: BigNumber,
68
67
  ): BigNumber {
69
68
  const directionSign = bet.direction === StonksDirection.UP ? 1 : -1;
70
- const multiplierDelta = targetMultiplier.minus(1);
69
+ const multiplierDelta = StonksRules.grossOfFee(targetMultiplier).minus(1);
71
70
  const priceChange = BigNumber(directionSign).multipliedBy(multiplierDelta).dividedBy(bet.leverage);
72
71
  return bet.entryPrice.multipliedBy(BigNumber(1).plus(priceChange));
73
72
  }
@@ -114,8 +113,12 @@ export class StonksRules {
114
113
  return new Date(startedAtMs + tick * STONKS_TICK_DURATION_MS);
115
114
  }
116
115
 
117
- static hasReachedTargetPrice(direction: StonksDirection, currentPrice: BigNumber, targetPrice: BigNumber): boolean {
118
- return direction === StonksDirection.UP ? currentPrice.isGreaterThanOrEqualTo(targetPrice) : currentPrice.isLessThanOrEqualTo(targetPrice);
116
+ static hasReachedTakeProfit(
117
+ bet: Pick<StonksOpenBetState, 'leverage' | 'entryPrice' | 'direction'>,
118
+ price: BigNumber,
119
+ takeProfitMultiplier: BigNumber,
120
+ ): boolean {
121
+ return StonksRules.multiplierAtPrice(bet, price).isGreaterThanOrEqualTo(takeProfitMultiplier);
119
122
  }
120
123
 
121
124
  static hasReachedBust(direction: StonksDirection, price: BigNumber, bustPrice: BigNumber): boolean {
@@ -127,11 +130,7 @@ export class StonksRules {
127
130
 
128
131
  if (bet.takeProfitMultiplier != null) {
129
132
  const takeProfitMultiplier = bet.takeProfitMultiplier;
130
- const hitTakeProfit = StonksRules.hasReachedTargetPrice(
131
- bet.direction,
132
- tickPrice,
133
- StonksRules.impliedPriceForMultiplier(bet, takeProfitMultiplier),
134
- );
133
+ const hitTakeProfit = StonksRules.hasReachedTakeProfit(bet, tickPrice, takeProfitMultiplier);
135
134
  if (hitTakeProfit && hitBust) {
136
135
  throw new Error('StonksRules.evaluateTriggers: take profit and liquidation both fired');
137
136
  }
@@ -141,7 +140,7 @@ export class StonksRules {
141
140
  // A take-profit is the one reason whose payout is a fixed wager × multiplier rather than
142
141
  // something the tick price bounds, and the cap is read live every batch — so it can be
143
142
  // lowered under an already-agreed multiplier. Nothing downstream re-checks it.
144
- if (StonksRules.grossPayoutForReason(bet, reason, tickPrice).isGreaterThan(liveMaxPayoutCap)) {
143
+ if (StonksRules.netPayoutForReason(bet, reason, tickPrice).isGreaterThan(liveMaxPayoutCap)) {
145
144
  return { fired: true, reason: { kind: StonksBetTerminalReason.MAX_PAYOUT, appliedCap: liveMaxPayoutCap } };
146
145
  }
147
146
  return { fired: true, reason };
@@ -152,19 +151,19 @@ export class StonksRules {
152
151
  return { fired: true, reason: { kind: StonksBetTerminalReason.LIQUIDATION } };
153
152
  }
154
153
 
155
- if (StonksRules.grossPayoutAtPrice(bet, tickPrice).isGreaterThanOrEqualTo(liveMaxPayoutCap)) {
154
+ if (StonksRules.netPayoutAtPrice(bet, tickPrice).isGreaterThanOrEqualTo(liveMaxPayoutCap)) {
156
155
  return { fired: true, reason: { kind: StonksBetTerminalReason.MAX_PAYOUT, appliedCap: liveMaxPayoutCap } };
157
156
  }
158
157
  return { fired: false };
159
158
  }
160
159
 
161
- static multiplierAtPrice(bet: StonksOpenBetState, price: BigNumber): BigNumber {
160
+ static multiplierAtPrice(bet: Pick<StonksOpenBetState, 'leverage' | 'entryPrice' | 'direction'>, price: BigNumber): BigNumber {
162
161
  const directionSign = bet.direction === StonksDirection.UP ? 1 : -1;
163
162
  const percentagePriceChange = price.minus(bet.entryPrice).dividedBy(bet.entryPrice);
164
163
  const leveragedPriceChange = BigNumber(directionSign).multipliedBy(bet.leverage).multipliedBy(percentagePriceChange);
165
164
 
166
- const multiplier = BigNumber(1).plus(leveragedPriceChange);
167
- return BigNumber.maximum(0, multiplier);
165
+ const grossMultiplier = BigNumber(1).plus(leveragedPriceChange);
166
+ return StonksRules.netOfFee(BigNumber.maximum(0, grossMultiplier));
168
167
  }
169
168
 
170
169
  static grossPayoutAtPrice(bet: StonksOpenBetState, price: BigNumber): BigNumber {
@@ -173,7 +172,11 @@ export class StonksRules {
173
172
  return BigNumber.maximum(0, bet.amount.plus(pnl));
174
173
  }
175
174
 
176
- static grossPayoutForReason(bet: StonksOpenBetState, reason: StonksSettlementReason, price: BigNumber): BigNumber {
175
+ static netPayoutAtPrice(bet: StonksOpenBetState, price: BigNumber): BigNumber {
176
+ return StonksRules.netOfFee(StonksRules.grossPayoutAtPrice(bet, price));
177
+ }
178
+
179
+ static netPayoutForReason(bet: StonksOpenBetState, reason: StonksSettlementReason, price: BigNumber): BigNumber {
177
180
  switch (reason.kind) {
178
181
  case StonksBetTerminalReason.TAKE_PROFIT:
179
182
  return calculatePayoutBN(bet.amount, reason.takeProfitMultiplier);
@@ -183,10 +186,10 @@ export class StonksRules {
183
186
  return reason.appliedCap;
184
187
  case StonksBetTerminalReason.CASHOUT:
185
188
  case StonksBetTerminalReason.ROUND_END:
186
- return StonksRules.grossPayoutAtPrice(bet, price);
189
+ return StonksRules.netPayoutAtPrice(bet, price);
187
190
  default:
188
191
  reason satisfies never;
189
- throw new Error(`StonksRules.grossPayoutForReason: unhandled reason`);
192
+ throw new Error(`StonksRules.netPayoutForReason: unhandled reason`);
190
193
  }
191
194
  }
192
195
 
@@ -197,11 +200,11 @@ export class StonksRules {
197
200
  return StonksRules.multiplierAtPrice(bet, price);
198
201
  }
199
202
 
200
- static netPayout(grossPayout: BigNumber, amount: BigNumber): BigNumber {
201
- if (grossPayout.lte(amount)) {
202
- return grossPayout;
203
- }
204
- const profit = grossPayout.minus(amount);
205
- return amount.plus(profit.multipliedBy(BigNumber(1).minus(STONKS_PROFIT_FEE_RATE)));
203
+ static netOfFee(gross: BigNumber): BigNumber {
204
+ return gross.multipliedBy(BigNumber(1).minus(STONKS_PAYOUT_FEE_RATE));
205
+ }
206
+
207
+ static grossOfFee(net: BigNumber): BigNumber {
208
+ return net.dividedBy(BigNumber(1).minus(STONKS_PAYOUT_FEE_RATE));
206
209
  }
207
210
  }
@@ -147,6 +147,7 @@ export interface SportsBetCashoutInfoV3 {
147
147
  /** Non-null exactly when playerCashoutEnabled is ENABLED. */
148
148
  cashoutOddsDecimal: string | null;
149
149
  playerCashoutEnabled: SportsBetCashoutButtonState;
150
+ partialCashoutEnabled: boolean;
150
151
  }
151
152
 
152
153
  export interface CashoutSportsBetPayloadV2 {
@@ -154,7 +155,10 @@ export interface CashoutSportsBetPayloadV2 {
154
155
  currency: Currency;
155
156
  amount: string;
156
157
  status: SportsBetStatus;
157
- settlement: { payoutOddsDecimal: string; payout: string };
158
+ // null after a partial cashout, the bet stays pending until the remaining stake is resulted
159
+ settlement: { payoutOddsDecimal: string; payout: string } | null;
160
+ partialCashoutBps: number | null;
161
+ partialCashoutPayout: string | null;
158
162
  }
159
163
 
160
164
  export interface SportsBetUpdatedPayloadV2 {
@@ -103,6 +103,7 @@ export enum SportsCashoutFailedReason {
103
103
  SPORTS_CASHOUT_DISABLED = 'SPORTS_CASHOUT_DISABLED',
104
104
  SPORTS_MAINTENANCE = 'SPORTS_MAINTENANCE',
105
105
  CASHOUT_MARGIN_CAP_HIT = 'CASHOUT_MARGIN_CAP_HIT',
106
+ ALREADY_PARTIALLY_CASHED_OUT = 'ALREADY_PARTIALLY_CASHED_OUT',
106
107
  }
107
108
 
108
109
  export enum SportsBetCashoutButtonState {