shufflecom-calculations 3.3.9 → 3.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shufflecom-calculations",
3
- "version": "3.3.9",
3
+ "version": "3.3.11",
4
4
  "description": "",
5
5
  "types": "lib/index.d.ts",
6
6
  "main": "lib/index.js",
@@ -14,5 +14,5 @@
14
14
  },
15
15
  "author": "",
16
16
  "license": "ISC",
17
- "gitHead": "55c15194f5b26382e3793140c4a843196c3d82cf"
17
+ "gitHead": "ef4532ce504fc76df3fda095044475231b91cf19"
18
18
  }
@@ -17,5 +17,9 @@ export const TIMEZONE_OFFSET_REGEX = /^(?:Z|[+-](?:2[0-3]|[01][0-9]):[0-5][0-9])
17
17
  export const TXN_URL_REGEX = /^https:\/\/.*\{txId\}.*$/;
18
18
 
19
19
  export const ADDRESS_URL_REGEX = /^https:\/\/.*\{address\}.*$/;
20
- export const VALID_NAME_REGEX = /^[^0-9!@#$%^&*`~()+=[{\]};:"\\|<>\/?\u3164]*$/;
20
+ export const VALID_NAME_REGEX = /^[^0-9!@#$%^&*`~()+=[{\]};:"\\|<>\/?]*$/;
21
+ // Rejects invisible/blank Unicode chars commonly used to spoof empty or hidden names:
22
+ // U+2800 Braille Blank, U+FFA0 Halfwidth Hangul Filler, U+115F Hangul Choseong Filler,
23
+ // U+1160 Hangul Jungseong Filler, U+3164 Hangul Filler
24
+ export const NO_INVISIBLE_CHAR_REGEX = /^[^\u2800\uFFA0\u115F\u1160\u3164]*$/;
21
25
  export const US_PHONE_NUMBER_REGEX = /^\+1\d{10}$/;
@@ -7,6 +7,7 @@ import {
7
7
  ADDRESS_URL_REGEX,
8
8
  VALID_NAME_REGEX,
9
9
  US_PHONE_NUMBER_REGEX,
10
+ NO_INVISIBLE_CHAR_REGEX,
10
11
  } from '.';
11
12
 
12
13
  describe('regex', () => {
@@ -21,10 +22,9 @@ describe('regex', () => {
21
22
  });
22
23
 
23
24
  it('should allow passwords between 8-24 characters', () => {
24
- expect(PWD_REGEX.test('12345678')).toBe(true);
25
- expect(PWD_REGEX.test('abcdefgh')).toBe(true);
26
- expect(PWD_REGEX.test('Password123!')).toBe(true);
27
-
25
+ expect(PWD_REGEX.test('12345678')).toBe(true);
26
+ expect(PWD_REGEX.test('abcdefgh')).toBe(true);
27
+ expect(PWD_REGEX.test('Password123!')).toBe(true);
28
28
  });
29
29
  });
30
30
 
@@ -139,9 +139,44 @@ describe('regex', () => {
139
139
  it('should allow for empty string', () => {
140
140
  expect(VALID_NAME_REGEX.test('')).toBe(true);
141
141
  });
142
+ });
143
+
144
+ describe('NO_INVISIBLE_CHAR_REGEX', () => {
145
+ const BRAILLE_BLANK = String.fromCodePoint(0x2800);
146
+ const HALFWIDTH_HANGUL_FILLER = String.fromCodePoint(0xffa0);
147
+ const HANGUL_CHOSEONG_FILLER = String.fromCodePoint(0x115f);
148
+ const HANGUL_JUNGSEONG_FILLER = String.fromCodePoint(0x1160);
149
+ const HANGUL_FILLER = String.fromCodePoint(0x3164);
150
+
151
+ it('should not allow for U+2800 (Braille Blank) unicode character', () => {
152
+ expect(NO_INVISIBLE_CHAR_REGEX.test(BRAILLE_BLANK)).toBe(false);
153
+ });
154
+
155
+ it('should not allow for U+FFA0 (Halfwidth Hangul Filler) unicode character', () => {
156
+ expect(NO_INVISIBLE_CHAR_REGEX.test(HALFWIDTH_HANGUL_FILLER)).toBe(false);
157
+ });
158
+
159
+ it('should not allow for U+115F (Hangul Choseong Filler) unicode character', () => {
160
+ expect(NO_INVISIBLE_CHAR_REGEX.test(HANGUL_CHOSEONG_FILLER)).toBe(false);
161
+ });
162
+
163
+ it('should not allow for U+1160 (Hangul Jungseong Filler) unicode character', () => {
164
+ expect(NO_INVISIBLE_CHAR_REGEX.test(HANGUL_JUNGSEONG_FILLER)).toBe(false);
165
+ });
166
+
167
+ it('should not allow for U+3164 (Hangul Filler) unicode character', () => {
168
+ expect(NO_INVISIBLE_CHAR_REGEX.test(HANGUL_FILLER)).toBe(false);
169
+ });
170
+
171
+ it('should not allow strings with multiple invisible chars', () => {
172
+ expect(NO_INVISIBLE_CHAR_REGEX.test(`${BRAILLE_BLANK}${HANGUL_FILLER}${HALFWIDTH_HANGUL_FILLER}`)).toBe(false);
173
+ });
142
174
 
143
- it('should not allow for U+3164(Hangul Filler) unicode character', () => {
144
- expect(VALID_NAME_REGEX.test('')).toBe(false); // there is an invisible unicode character in here
175
+ it('should allow clean strings without invisible chars', () => {
176
+ expect(NO_INVISIBLE_CHAR_REGEX.test('superman')).toBe(true);
177
+ expect(NO_INVISIBLE_CHAR_REGEX.test('SuPERman 123!')).toBe(true);
178
+ expect(NO_INVISIBLE_CHAR_REGEX.test('我喜欢bingchiling')).toBe(true);
179
+ expect(NO_INVISIBLE_CHAR_REGEX.test("m_r's don-nut.")).toBe(true);
145
180
  });
146
181
  });
147
182
 
@@ -1,6 +1,14 @@
1
1
  import BigNumber from 'bignumber.js';
2
2
  import { limitTotalOddsDecimalPlaces, oddsFractionToDecimal } from './odds';
3
- import { OutcomeResult, SportsBetSelectionInterface, SportsBetSelectionStatus, SportsMarketSelectionInterface } from './sports.types';
3
+ import {
4
+ OutcomeResult,
5
+ SportsBetLegInterface,
6
+ SportsBetLegType,
7
+ SportsBetSelectionInterface,
8
+ SportsBetSelectionStatus,
9
+ SportsBetType,
10
+ SportsMarketSelectionInterface,
11
+ } from './sports.types';
4
12
  import { SportsSystemBetType, SportsSystemBetTypeCombinations, SportsSystemBetTypeSelectionCountsToBetCounts } from './system-bet.types';
5
13
 
6
14
  export const MARGIN_MIN_BPS = 100; // hard limit min 1% for cashout
@@ -73,6 +81,9 @@ export function getSystemBetSubBetSelections(
73
81
  return subBetSelectionsAndIndex.map(({ subBetSelection }) => subBetSelection);
74
82
  }
75
83
 
84
+ // Selection-shaped façade over `calculateSportsPayoutOddsFromLegs`. Builds legs from the input
85
+ // selections, maps subBetSelections to subBetLegs, and delegates. Callers that already have legs
86
+ // should prefer `calculateSportsPayoutOddsFromLegs` directly to skip the bridging cost.
76
87
  export function calculateSportsPayoutOdds(
77
88
  selections: SportsBetSelectionInterface[],
78
89
  marketSelections: SportsMarketSelectionInterface[],
@@ -82,119 +93,30 @@ export function calculateSportsPayoutOdds(
82
93
  isEstimation: boolean; // treats PENDING leg as WON
83
94
  },
84
95
  ): BigNumber {
85
- const sportsCustomBetTotalOddsDecimal = opt?.sportsCustomBetTotalOddsDecimal;
86
- const subBetSelections = opt?.subBetSelections;
87
- if (sportsCustomBetTotalOddsDecimal) {
88
- if (selections.some(({ status }) => CUSTOM_BET_VOIDED_STATUSES.includes(status))) {
89
- return new BigNumber(1);
90
- } else if (selections.some(({ status }) => status === SportsBetSelectionStatus.LOST)) {
91
- return new BigNumber(0);
92
- } else {
93
- return limitTotalOddsDecimalPlaces(sportsCustomBetTotalOddsDecimal);
94
- }
95
- }
96
-
97
- const marketSelectionMap = marketSelections.reduce(
98
- (map, selection) => {
99
- map[selection.id] = selection;
100
- return map;
101
- },
102
- {} as Record<string, SportsMarketSelectionInterface>,
103
- );
96
+ const sportsBetType = opt.sportsCustomBetTotalOddsDecimal !== null ? SportsBetType.CUSTOM_BET : SportsBetType.REGULAR;
97
+ const legs = deriveLegsFromSelections(sportsBetType, selections, opt.sportsCustomBetTotalOddsDecimal ?? new BigNumber(1));
98
+ const subBetLegs = opt.subBetSelections ? mapSubBetSelectionsToLegs(opt.subBetSelections, legs) : null;
99
+ return calculateSportsPayoutOddsFromLegs(legs, marketSelections, { subBetLegs, isEstimation: opt.isEstimation });
100
+ }
104
101
 
105
- if (subBetSelections) {
106
- let totalOddsDecimal = new BigNumber(0);
107
- for (const subBetSelection of subBetSelections) {
108
- const subBetMarketSelections = subBetSelection.map(selection => marketSelectionMap[selection.marketSelectionId]);
109
- if (subBetMarketSelections.some(marketSelection => !marketSelection)) {
110
- throw new Error('Sub bet market selections not found');
111
- }
112
- const subBetOdds = calculateSportsPayoutOdds(subBetSelection, subBetMarketSelections as SportsMarketSelectionInterface[], {
113
- sportsCustomBetTotalOddsDecimal: null,
114
- subBetSelections: null,
115
- isEstimation: opt?.isEstimation,
116
- });
117
- totalOddsDecimal = totalOddsDecimal.plus(subBetOdds);
102
+ function mapSubBetSelectionsToLegs(
103
+ subBetSelections: SportsBetSelectionInterface[][],
104
+ legs: SportsBetLegInterface[],
105
+ ): SportsBetLegInterface[][] {
106
+ const legByMarketSelectionId = new Map<string, SportsBetLegInterface>();
107
+ for (const leg of legs) {
108
+ for (const selection of leg.selections) {
109
+ legByMarketSelectionId.set(selection.marketSelectionId, leg);
118
110
  }
119
-
120
- return limitTotalOddsDecimalPlaces(totalOddsDecimal.dividedBy(subBetSelections.length));
121
111
  }
122
-
123
- return limitTotalOddsDecimalPlaces(
124
- selections.reduce((totalOddsDecimal, selection): BigNumber => {
125
- // https://geniussports.atlassian.net/wiki/spaces/BID/pages/1239941172/Back-End+Integrations#Back-EndIntegrations-Whatareselectionresultingvalues%3F
126
- const selectionOddsDecimal = oddsFractionToDecimal(selection.oddsNumerator, selection.oddsDenominator);
127
- const marketSelection = marketSelectionMap[selection.marketSelectionId];
128
-
129
- if (!marketSelection) {
130
- throw new Error('Market selection not found');
131
- }
132
-
133
- if (selection.status === SportsBetSelectionStatus.LOST) {
134
- return BigNumber(0);
135
- }
136
-
137
- if (selection.status === SportsBetSelectionStatus.WON) {
138
- return totalOddsDecimal.multipliedBy(selectionOddsDecimal);
139
- }
140
-
141
- if (selection.status === SportsBetSelectionStatus.PARTIAL) {
142
- if (marketSelection.resultAdditionalData) {
143
- if ('percentageWin' in marketSelection.resultAdditionalData && 'percentagePush' in marketSelection.resultAdditionalData) {
144
- return totalOddsDecimal.multipliedBy(
145
- selectionOddsDecimal
146
- .multipliedBy(BigNumber(marketSelection.resultAdditionalData?.percentageWin).dividedBy(100))
147
- .plus(BigNumber(marketSelection.resultAdditionalData?.percentagePush).dividedBy(100)),
148
- );
149
- } else if ('voidFactor' in marketSelection.resultAdditionalData && 'outcome' in marketSelection.resultAdditionalData) {
150
- if (marketSelection.resultAdditionalData.outcome === OutcomeResult.WINNING) {
151
- return totalOddsDecimal.multipliedBy(
152
- selectionOddsDecimal
153
- .multipliedBy(BigNumber(marketSelection.resultAdditionalData?.voidFactor))
154
- .plus(BigNumber(marketSelection.resultAdditionalData?.voidFactor)),
155
- );
156
- }
157
-
158
- return totalOddsDecimal.multipliedBy(marketSelection.resultAdditionalData.voidFactor || 0);
159
- } else if ('deadHeatFactor' in marketSelection.resultAdditionalData) {
160
- return totalOddsDecimal.multipliedBy(selectionOddsDecimal.multipliedBy(marketSelection.resultAdditionalData.deadHeatFactor || 0));
161
- }
162
- }
163
-
164
- return totalOddsDecimal;
112
+ return subBetSelections.map(subBet =>
113
+ subBet.map(selection => {
114
+ const leg = legByMarketSelectionId.get(selection.marketSelectionId);
115
+ if (!leg) {
116
+ throw new Error('Sub bet leg not found');
165
117
  }
166
-
167
- if (selection.status === SportsBetSelectionStatus.PLACED) {
168
- if (marketSelection.resultAdditionalData && 'countInPlace' in marketSelection.resultAdditionalData) {
169
- return totalOddsDecimal.multipliedBy(selectionOddsDecimal.dividedBy(marketSelection.resultAdditionalData.countInPlace));
170
- }
171
-
172
- return totalOddsDecimal;
173
- }
174
-
175
- if (selection.status === SportsBetSelectionStatus.PUSHED) {
176
- return totalOddsDecimal;
177
- }
178
-
179
- if (selection.status === SportsBetSelectionStatus.VOIDED || selection.status == SportsBetSelectionStatus.PROVIDER_VOIDED) {
180
- return totalOddsDecimal;
181
- }
182
-
183
- if (selection.status === SportsBetSelectionStatus.CASHED_OUT) {
184
- return calculateSelectionCashoutOdds(totalOddsDecimal, selectionOddsDecimal, marketSelection);
185
- }
186
-
187
- if (selection.status === SportsBetSelectionStatus.PENDING) {
188
- if (opt?.isEstimation) {
189
- return totalOddsDecimal.multipliedBy(selectionOddsDecimal);
190
- }
191
- return calculateSelectionCashoutOdds(totalOddsDecimal, selectionOddsDecimal, marketSelection);
192
- }
193
-
194
- selection.status satisfies never;
195
-
196
- return totalOddsDecimal;
197
- }, BigNumber(1)),
118
+ return leg;
119
+ }),
198
120
  );
199
121
  }
200
122
 
@@ -273,3 +195,153 @@ function calculateSelectionCashoutOdds(
273
195
  .multipliedBy(selectionOddsDecimal)
274
196
  .dividedBy(oddsFractionToDecimal(marketSelection.oddsNumerator, marketSelection.oddsDenominator));
275
197
  }
198
+
199
+ // Structural mapping from (bet type, selections) → legs. The placement writer and the backfill
200
+ // service both consume this so that the leg-shape rule for each bet type lives in one place.
201
+ export function deriveLegsFromSelections(
202
+ sportsBetType: SportsBetType,
203
+ selections: SportsBetSelectionInterface[],
204
+ customBetTotalOddsDecimal: BigNumber,
205
+ ): SportsBetLegInterface[] {
206
+ if (sportsBetType === SportsBetType.CUSTOM_BET) {
207
+ return [{ type: SportsBetLegType.CUSTOM, oddsDecimal: customBetTotalOddsDecimal, selections }];
208
+ }
209
+ return selections.map(selection => ({
210
+ type: SportsBetLegType.REGULAR,
211
+ oddsDecimal: oddsFractionToDecimal(selection.oddsNumerator, selection.oddsDenominator),
212
+ selections: [selection],
213
+ }));
214
+ }
215
+
216
+ export function getSystemBetSubBetLegs<TLeg>(legs: TLeg[], sportsSystemBetType: SportsSystemBetType): TLeg[][] {
217
+ const systemBetSizes = SportsSystemBetTypeCombinations[sportsSystemBetType];
218
+ if (!systemBetSizes) {
219
+ throw new Error('Invalid system bet type');
220
+ }
221
+ if (!SportsSystemBetTypeSelectionCountsToBetCounts[legs.length]?.[sportsSystemBetType]) {
222
+ throw new Error('Invalid selection count');
223
+ }
224
+ const combinations = systemBetSizes.flatMap(size => generateSystemBetIndexCombinations(legs.length, size));
225
+ return combinations.map(combination => combination.map(index => legs[index]));
226
+ }
227
+
228
+ export function calculateSportsPayoutOddsFromLegs(
229
+ legs: SportsBetLegInterface[],
230
+ marketSelections: SportsMarketSelectionInterface[],
231
+ opt: {
232
+ subBetLegs: SportsBetLegInterface[][] | null;
233
+ isEstimation: boolean;
234
+ },
235
+ ): BigNumber {
236
+ const subBetLegs = opt?.subBetLegs;
237
+
238
+ const marketSelectionMap = marketSelections.reduce(
239
+ (map, selection) => {
240
+ map[selection.id] = selection;
241
+ return map;
242
+ },
243
+ {} as Record<string, SportsMarketSelectionInterface>,
244
+ );
245
+
246
+ if (subBetLegs) {
247
+ let totalOddsDecimal = new BigNumber(0);
248
+ for (const subBetLeg of subBetLegs) {
249
+ const subBetOdds = calculateSportsPayoutOddsFromLegs(subBetLeg, marketSelections, {
250
+ subBetLegs: null,
251
+ isEstimation: opt?.isEstimation,
252
+ });
253
+ totalOddsDecimal = totalOddsDecimal.plus(subBetOdds);
254
+ }
255
+ return limitTotalOddsDecimalPlaces(totalOddsDecimal.dividedBy(subBetLegs.length));
256
+ }
257
+
258
+ return limitTotalOddsDecimalPlaces(
259
+ legs.reduce((totalOddsDecimal, leg) => totalOddsDecimal.multipliedBy(getLegPayoutOddsDecimal(leg, marketSelectionMap, opt.isEstimation)), new BigNumber(1)),
260
+ );
261
+ }
262
+
263
+ function getLegPayoutOddsDecimal(
264
+ leg: SportsBetLegInterface,
265
+ marketSelectionMap: Record<string, SportsMarketSelectionInterface>,
266
+ isEstimation: boolean,
267
+ ): BigNumber {
268
+ if (leg.type === SportsBetLegType.CUSTOM) {
269
+ if (leg.selections.some(({ status }) => CUSTOM_BET_VOIDED_STATUSES.includes(status))) {
270
+ return new BigNumber(1);
271
+ }
272
+ if (leg.selections.some(({ status }) => status === SportsBetSelectionStatus.LOST)) {
273
+ return new BigNumber(0);
274
+ }
275
+ return leg.oddsDecimal;
276
+ }
277
+
278
+ // REGULAR leg: exactly one selection. Mirrors the per-selection reducer in calculateSportsPayoutOdds.
279
+ const [selection] = leg.selections;
280
+ const selectionOddsDecimal = oddsFractionToDecimal(selection.oddsNumerator, selection.oddsDenominator);
281
+ const marketSelection = marketSelectionMap[selection.marketSelectionId];
282
+
283
+ if (!marketSelection) {
284
+ throw new Error('Market selection not found');
285
+ }
286
+
287
+ if (selection.status === SportsBetSelectionStatus.LOST) {
288
+ return BigNumber(0);
289
+ }
290
+
291
+ if (selection.status === SportsBetSelectionStatus.WON) {
292
+ return selectionOddsDecimal;
293
+ }
294
+
295
+ if (selection.status === SportsBetSelectionStatus.PARTIAL) {
296
+ if (marketSelection.resultAdditionalData) {
297
+ if ('percentageWin' in marketSelection.resultAdditionalData && 'percentagePush' in marketSelection.resultAdditionalData) {
298
+ return selectionOddsDecimal
299
+ .multipliedBy(BigNumber(marketSelection.resultAdditionalData?.percentageWin).dividedBy(100))
300
+ .plus(BigNumber(marketSelection.resultAdditionalData?.percentagePush).dividedBy(100));
301
+ } else if ('voidFactor' in marketSelection.resultAdditionalData && 'outcome' in marketSelection.resultAdditionalData) {
302
+ if (marketSelection.resultAdditionalData.outcome === OutcomeResult.WINNING) {
303
+ return selectionOddsDecimal
304
+ .multipliedBy(BigNumber(marketSelection.resultAdditionalData?.voidFactor))
305
+ .plus(BigNumber(marketSelection.resultAdditionalData?.voidFactor));
306
+ }
307
+
308
+ return new BigNumber(marketSelection.resultAdditionalData.voidFactor || 0);
309
+ } else if ('deadHeatFactor' in marketSelection.resultAdditionalData) {
310
+ return selectionOddsDecimal.multipliedBy(marketSelection.resultAdditionalData.deadHeatFactor || 0);
311
+ }
312
+ }
313
+
314
+ return new BigNumber(1);
315
+ }
316
+
317
+ if (selection.status === SportsBetSelectionStatus.PLACED) {
318
+ if (marketSelection.resultAdditionalData && 'countInPlace' in marketSelection.resultAdditionalData) {
319
+ return selectionOddsDecimal.dividedBy(marketSelection.resultAdditionalData.countInPlace);
320
+ }
321
+
322
+ return new BigNumber(1);
323
+ }
324
+
325
+ if (selection.status === SportsBetSelectionStatus.PUSHED) {
326
+ return new BigNumber(1);
327
+ }
328
+
329
+ if (selection.status === SportsBetSelectionStatus.VOIDED || selection.status === SportsBetSelectionStatus.PROVIDER_VOIDED) {
330
+ return new BigNumber(1);
331
+ }
332
+
333
+ if (selection.status === SportsBetSelectionStatus.CASHED_OUT) {
334
+ return calculateSelectionCashoutOdds(new BigNumber(1), selectionOddsDecimal, marketSelection);
335
+ }
336
+
337
+ if (selection.status === SportsBetSelectionStatus.PENDING) {
338
+ if (isEstimation) {
339
+ return selectionOddsDecimal;
340
+ }
341
+ return calculateSelectionCashoutOdds(new BigNumber(1), selectionOddsDecimal, marketSelection);
342
+ }
343
+
344
+ selection.status satisfies never;
345
+
346
+ return new BigNumber(1);
347
+ }
@@ -0,0 +1,168 @@
1
+ import { VipLevel } from '../types/vip-level.type';
2
+ import { Currency } from '../utils/currency';
3
+ import {
4
+ MatchStatusParts,
5
+ Sports,
6
+ SportsBetLegType,
7
+ SportsBetSelectionStatus,
8
+ SportsBetStatus,
9
+ SportsBetType,
10
+ SportsFixtureBannerType,
11
+ SportsFixtureStatus,
12
+ SportsMarketProductId,
13
+ SportsMarketProvider,
14
+ SportsMarketSelectionResultStatus,
15
+ SportsMarketSelectionStatus,
16
+ SportsMarketStatus,
17
+ SportsMatchPhaseHomeAway,
18
+ SportsMatchPhaseSummary,
19
+ SportsMatchStatistics,
20
+ } from './sports.types';
21
+ import { SportsSystemBetType } from './system-bet.types';
22
+
23
+ export interface SportsBetSelectionCategoryV3 {
24
+ id: string;
25
+ slug: string;
26
+ }
27
+
28
+ export interface SportsBetSelectionCompetitionV3 {
29
+ id: string;
30
+ slug: string;
31
+ name: string;
32
+ }
33
+
34
+ export interface SportsBetSelectionFixtureV3 {
35
+ provider: SportsMarketProvider;
36
+ id: string;
37
+ slug: string;
38
+ name: string;
39
+ startTime: string;
40
+ inPlayAllowed: boolean;
41
+ status: SportsFixtureStatus;
42
+ bannerType: SportsFixtureBannerType | null;
43
+ shortName: string;
44
+ }
45
+
46
+ export interface SportsBetSelectionCompetitorV3 {
47
+ isHome: boolean;
48
+ countryCode: string | null;
49
+ iconPath: string | null;
50
+ displayName: string;
51
+ abbreviation: string;
52
+ }
53
+
54
+ export interface SportsBetSelectionMarketV3 {
55
+ provider: SportsMarketProvider;
56
+ id: string;
57
+ name: string;
58
+ isOtc: boolean;
59
+ inPlay: boolean;
60
+ expiryTime: string | null;
61
+ status: SportsMarketStatus;
62
+ productId: SportsMarketProductId | null;
63
+ fullName: string;
64
+ lineValue: string | null;
65
+ }
66
+
67
+ export interface SportsBetSelectionMarketSelectionV3 {
68
+ id: string;
69
+ oddsNumerator: string;
70
+ oddsDenominator: string;
71
+ status: SportsMarketSelectionStatus;
72
+ resultStatus: SportsMarketSelectionResultStatus | null;
73
+ formattedName: string;
74
+ }
75
+
76
+ export interface SportsBetSelectionMatchSummaryV3 {
77
+ matchStatusDisplay: MatchStatusParts[];
78
+ homeScore: string | null;
79
+ awayScore: string | null;
80
+ possession: SportsMatchPhaseHomeAway | null;
81
+ currentRound: string | null;
82
+ timeRemaining: number | null;
83
+ stoppageTime: number | null;
84
+ stoppageTimeAnnounced: number | null;
85
+ timeElapsed: number | null;
86
+ clockRunning: boolean | null;
87
+ providerMessageTimestamp: string | null;
88
+ currentPhase: SportsMatchPhaseSummary | null;
89
+ phases: SportsMatchPhaseSummary[] | null;
90
+ statistics: SportsMatchStatistics | null;
91
+ }
92
+
93
+ export interface SportsBetSelectionMatchStateV3 {
94
+ matchSummary: SportsBetSelectionMatchSummaryV3 | null;
95
+ }
96
+
97
+ export interface SportsBetSelectionV3 {
98
+ id: string;
99
+ oddsNumerator: string;
100
+ oddsDenominator: string;
101
+ inPlay: boolean;
102
+ status: SportsBetSelectionStatus;
103
+ displayStatus: SportsBetSelectionStatus;
104
+ updatedAt: string;
105
+ createdAt: string;
106
+ sports: Sports;
107
+ streamExists: boolean;
108
+ category: SportsBetSelectionCategoryV3;
109
+ competition: SportsBetSelectionCompetitionV3;
110
+ fixture: SportsBetSelectionFixtureV3;
111
+ competitors: SportsBetSelectionCompetitorV3[];
112
+ marketSelection: SportsBetSelectionMarketSelectionV3;
113
+ market: SportsBetSelectionMarketV3;
114
+ matchState: SportsBetSelectionMatchStateV3 | null;
115
+ }
116
+
117
+ export interface SportsBetLegV3 {
118
+ id: string;
119
+ type: SportsBetLegType;
120
+ oddsDecimal: string;
121
+ updatedAt: string;
122
+ createdAt: string;
123
+ selections: SportsBetSelectionV3[];
124
+ }
125
+
126
+ export interface SportsBetSettlementV3 {
127
+ id: string;
128
+ payoutOddsDecimal: string;
129
+ payout: string;
130
+ createdAt: string;
131
+ }
132
+
133
+ export interface SportsBetCashoutAvailableV3 {
134
+ canCashout: boolean;
135
+ reason: string | null;
136
+ }
137
+
138
+ export interface SportsBetUserV3 {
139
+ id: string;
140
+ username: string;
141
+ vipLevel: VipLevel;
142
+ }
143
+
144
+ export interface SportsBetPayloadV3 {
145
+ id: string; // bs58
146
+ currency: Currency;
147
+ amount: string;
148
+ originalAmount: string | null;
149
+ totalOddsDecimal: string;
150
+ status: SportsBetStatus;
151
+ type: SportsBetType;
152
+ systemBetType: SportsSystemBetType | null;
153
+ isOtc: boolean;
154
+ updatedAt: string;
155
+ createdAt: string;
156
+ actualOddsDecimal: string;
157
+ cashoutOddsDecimal: string | null;
158
+ providerCashoutDisabled: boolean;
159
+ settlement: SportsBetSettlementV3 | null;
160
+ cashoutAvailable: SportsBetCashoutAvailableV3;
161
+ user: SportsBetUserV3 | null;
162
+ legs: SportsBetLegV3[];
163
+ }
164
+
165
+ export interface PaginatedSportsBetPayloadV3 {
166
+ nodes: SportsBetPayloadV3[];
167
+ nextCursor: string | null;
168
+ }
@@ -46,8 +46,8 @@ export enum SportsFixtureStatus {
46
46
  export enum SportsMarketStatus {
47
47
  OPEN = 'OPEN',
48
48
  CLOSED = 'CLOSED', // gets reverted back to OPEN very often with the radar line market cases
49
- RESULTED = 'RESULTED', // aka, finalized, no longer used in any query + index, updated by update-market-status job
50
49
  SUSPENDED = 'SUSPENDED',
50
+ RESULTED = 'RESULTED', // aka, finalized, no longer used in any query + index, updated by update-market-status job
51
51
  /**
52
52
  * @deprecated
53
53
  */
@@ -135,6 +135,17 @@ export interface SportsBetSelectionInterface {
135
135
  oddsDenominator: BigNumber;
136
136
  }
137
137
 
138
+ export enum SportsBetLegType {
139
+ REGULAR = 'REGULAR',
140
+ CUSTOM = 'CUSTOM',
141
+ }
142
+
143
+ export interface SportsBetLegInterface {
144
+ type: SportsBetLegType;
145
+ oddsDecimal: BigNumber;
146
+ selections: SportsBetSelectionInterface[];
147
+ }
148
+
138
149
  export interface SportsFixtureInterface {
139
150
  id: string;
140
151
  status: SportsFixtureStatus;
@@ -539,6 +550,8 @@ export enum SportsMarketGroup {
539
550
  LAST_TO_MARKETS = 'LAST_TO_MARKETS',
540
551
  OTHER_MARKETS = 'OTHER_MARKETS', // this doesnt really exist in db, for query purpose only
541
552
  SAME_GAME_MULTI_MARKETS = 'SAME_GAME_MULTI_MARKETS',
553
+ QUICK_COMBOS = 'QUICK_COMBOS', // for managing tab visibility only — no SportsMarketGrouping rows. queries for tab content happen through quickCombos query, not sportsMarketInfo
554
+
542
555
  KILL_MARKETS = 'KILL_MARKETS',
543
556
  ROSHAN_MARKETS = 'ROSHAN_MARKETS',
544
557
  TURRET_MARKETS = 'TURRET_MARKETS',