shufflecom-calculations 3.3.10 → 3.3.12

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.
@@ -42,4 +42,5 @@ declare const CHECKED: unique symbol;
42
42
  export type CheckedCurrency = Currency & {
43
43
  readonly [CHECKED]: true;
44
44
  };
45
+ export declare function unsafeMintCheckedCurrency(currency: Currency): CheckedCurrency;
45
46
  export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CurrencyType = exports.Currency = void 0;
3
+ exports.unsafeMintCheckedCurrency = exports.CurrencyType = exports.Currency = void 0;
4
4
  var Currency;
5
5
  (function (Currency) {
6
6
  Currency["BTC"] = "BTC";
@@ -68,4 +68,8 @@ CurrencyType.TYPE_MAP = {
68
68
  };
69
69
  CurrencyType.CRYPTO_CURRENCIES = Object.keys(CurrencyType.TYPE_MAP).filter((c) => CurrencyType.kindOf(c) === 'crypto');
70
70
  CurrencyType.FIAT_CURRENCIES = Object.keys(CurrencyType.TYPE_MAP).filter((c) => CurrencyType.kindOf(c) === 'fiat');
71
+ function unsafeMintCheckedCurrency(currency) {
72
+ return currency;
73
+ }
74
+ exports.unsafeMintCheckedCurrency = unsafeMintCheckedCurrency;
71
75
  //# sourceMappingURL=currency.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"currency.js","sourceRoot":"","sources":["../../src/utils/currency.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"currency.js","sourceRoot":"","sources":["../../src/utils/currency.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shufflecom-calculations",
3
- "version": "3.3.10",
3
+ "version": "3.3.12",
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": "7d9ea4668009e9c62e2f8a5abb00be87cf1ec11d"
17
+ "gitHead": "72c940719346f25366185d52c9742917410bffa7"
18
18
  }
package/src/index.ts CHANGED
@@ -45,4 +45,5 @@ export { Tower, TowerDifficulty, TOWER_DIFFICULTY_TO_CONFIG, TOWER_TOTAL_ROWS }
45
45
  export { Wheel, WheelRiskLevel, WheelSegments, WHEEL_MULTIPLIERS, WHEEL_SEGMENTS_TO_NUMBER } from './games/wheel';
46
46
  export { formatSelectionName } from './sports/sports-name';
47
47
  export { SportsContentWithFixturesInfo } from './sports/sports-content-fixture.types';
48
+ export { SportsProviderMaxBet, BetradarOverrideLimitType, BETRADAR_OVERRIDE_LIMIT_TO_MAX_BET } from './sports/override-limit-max-bet';
48
49
  export { Blitz, BlitzResult, BLITZ_MIN_PICKS, BLITZ_MAX_PICKS } from './games/blitz';
@@ -81,6 +81,9 @@ export function getSystemBetSubBetSelections(
81
81
  return subBetSelectionsAndIndex.map(({ subBetSelection }) => subBetSelection);
82
82
  }
83
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.
84
87
  export function calculateSportsPayoutOdds(
85
88
  selections: SportsBetSelectionInterface[],
86
89
  marketSelections: SportsMarketSelectionInterface[],
@@ -90,119 +93,30 @@ export function calculateSportsPayoutOdds(
90
93
  isEstimation: boolean; // treats PENDING leg as WON
91
94
  },
92
95
  ): BigNumber {
93
- const sportsCustomBetTotalOddsDecimal = opt?.sportsCustomBetTotalOddsDecimal;
94
- const subBetSelections = opt?.subBetSelections;
95
- if (sportsCustomBetTotalOddsDecimal) {
96
- if (selections.some(({ status }) => CUSTOM_BET_VOIDED_STATUSES.includes(status))) {
97
- return new BigNumber(1);
98
- } else if (selections.some(({ status }) => status === SportsBetSelectionStatus.LOST)) {
99
- return new BigNumber(0);
100
- } else {
101
- return limitTotalOddsDecimalPlaces(sportsCustomBetTotalOddsDecimal);
102
- }
103
- }
104
-
105
- const marketSelectionMap = marketSelections.reduce(
106
- (map, selection) => {
107
- map[selection.id] = selection;
108
- return map;
109
- },
110
- {} as Record<string, SportsMarketSelectionInterface>,
111
- );
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
+ }
112
101
 
113
- if (subBetSelections) {
114
- let totalOddsDecimal = new BigNumber(0);
115
- for (const subBetSelection of subBetSelections) {
116
- const subBetMarketSelections = subBetSelection.map(selection => marketSelectionMap[selection.marketSelectionId]);
117
- if (subBetMarketSelections.some(marketSelection => !marketSelection)) {
118
- throw new Error('Sub bet market selections not found');
119
- }
120
- const subBetOdds = calculateSportsPayoutOdds(subBetSelection, subBetMarketSelections as SportsMarketSelectionInterface[], {
121
- sportsCustomBetTotalOddsDecimal: null,
122
- subBetSelections: null,
123
- isEstimation: opt?.isEstimation,
124
- });
125
- 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);
126
110
  }
127
-
128
- return limitTotalOddsDecimalPlaces(totalOddsDecimal.dividedBy(subBetSelections.length));
129
111
  }
130
-
131
- return limitTotalOddsDecimalPlaces(
132
- selections.reduce((totalOddsDecimal, selection): BigNumber => {
133
- // https://geniussports.atlassian.net/wiki/spaces/BID/pages/1239941172/Back-End+Integrations#Back-EndIntegrations-Whatareselectionresultingvalues%3F
134
- const selectionOddsDecimal = oddsFractionToDecimal(selection.oddsNumerator, selection.oddsDenominator);
135
- const marketSelection = marketSelectionMap[selection.marketSelectionId];
136
-
137
- if (!marketSelection) {
138
- throw new Error('Market selection not found');
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');
139
117
  }
140
-
141
- if (selection.status === SportsBetSelectionStatus.LOST) {
142
- return BigNumber(0);
143
- }
144
-
145
- if (selection.status === SportsBetSelectionStatus.WON) {
146
- return totalOddsDecimal.multipliedBy(selectionOddsDecimal);
147
- }
148
-
149
- if (selection.status === SportsBetSelectionStatus.PARTIAL) {
150
- if (marketSelection.resultAdditionalData) {
151
- if ('percentageWin' in marketSelection.resultAdditionalData && 'percentagePush' in marketSelection.resultAdditionalData) {
152
- return totalOddsDecimal.multipliedBy(
153
- selectionOddsDecimal
154
- .multipliedBy(BigNumber(marketSelection.resultAdditionalData?.percentageWin).dividedBy(100))
155
- .plus(BigNumber(marketSelection.resultAdditionalData?.percentagePush).dividedBy(100)),
156
- );
157
- } else if ('voidFactor' in marketSelection.resultAdditionalData && 'outcome' in marketSelection.resultAdditionalData) {
158
- if (marketSelection.resultAdditionalData.outcome === OutcomeResult.WINNING) {
159
- return totalOddsDecimal.multipliedBy(
160
- selectionOddsDecimal
161
- .multipliedBy(BigNumber(marketSelection.resultAdditionalData?.voidFactor))
162
- .plus(BigNumber(marketSelection.resultAdditionalData?.voidFactor)),
163
- );
164
- }
165
-
166
- return totalOddsDecimal.multipliedBy(marketSelection.resultAdditionalData.voidFactor || 0);
167
- } else if ('deadHeatFactor' in marketSelection.resultAdditionalData) {
168
- return totalOddsDecimal.multipliedBy(selectionOddsDecimal.multipliedBy(marketSelection.resultAdditionalData.deadHeatFactor || 0));
169
- }
170
- }
171
-
172
- return totalOddsDecimal;
173
- }
174
-
175
- if (selection.status === SportsBetSelectionStatus.PLACED) {
176
- if (marketSelection.resultAdditionalData && 'countInPlace' in marketSelection.resultAdditionalData) {
177
- return totalOddsDecimal.multipliedBy(selectionOddsDecimal.dividedBy(marketSelection.resultAdditionalData.countInPlace));
178
- }
179
-
180
- return totalOddsDecimal;
181
- }
182
-
183
- if (selection.status === SportsBetSelectionStatus.PUSHED) {
184
- return totalOddsDecimal;
185
- }
186
-
187
- if (selection.status === SportsBetSelectionStatus.VOIDED || selection.status == SportsBetSelectionStatus.PROVIDER_VOIDED) {
188
- return totalOddsDecimal;
189
- }
190
-
191
- if (selection.status === SportsBetSelectionStatus.CASHED_OUT) {
192
- return calculateSelectionCashoutOdds(totalOddsDecimal, selectionOddsDecimal, marketSelection);
193
- }
194
-
195
- if (selection.status === SportsBetSelectionStatus.PENDING) {
196
- if (opt?.isEstimation) {
197
- return totalOddsDecimal.multipliedBy(selectionOddsDecimal);
198
- }
199
- return calculateSelectionCashoutOdds(totalOddsDecimal, selectionOddsDecimal, marketSelection);
200
- }
201
-
202
- selection.status satisfies never;
203
-
204
- return totalOddsDecimal;
205
- }, BigNumber(1)),
118
+ return leg;
119
+ }),
206
120
  );
207
121
  }
208
122
 
@@ -298,3 +212,136 @@ export function deriveLegsFromSelections(
298
212
  selections: [selection],
299
213
  }));
300
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,30 @@
1
+ export enum SportsProviderMaxBet {
2
+ ON = 'ON',
3
+ NEUTRAL = 'NEUTRAL',
4
+ OFF = 'OFF',
5
+ }
6
+
7
+ export enum BetradarOverrideLimitType {
8
+ SUPER_VIP = 'SUPER_VIP',
9
+ VIP = 'VIP',
10
+ SAFE = 'SAFE',
11
+ HIGH_RISK = 'HIGH_RISK',
12
+ POTENTIAL_VIP = 'POTENTIAL_VIP',
13
+ ENHANCED = 'ENHANCED',
14
+ BAD_ACCOUNTS = 'BAD_ACCOUNTS',
15
+ DEFAULT = 'DEFAULT',
16
+ UNWANTED_ACCOUNTS = 'UNWANTED_ACCOUNTS',
17
+ MARKETING_ACCOUNTS = 'MARKETING_ACCOUNTS',
18
+ }
19
+
20
+ export const BETRADAR_OVERRIDE_LIMIT_TO_MAX_BET: Partial<Record<BetradarOverrideLimitType, SportsProviderMaxBet | undefined>> = {
21
+ [BetradarOverrideLimitType.BAD_ACCOUNTS]: SportsProviderMaxBet.OFF,
22
+ [BetradarOverrideLimitType.UNWANTED_ACCOUNTS]: SportsProviderMaxBet.OFF,
23
+ [BetradarOverrideLimitType.HIGH_RISK]: SportsProviderMaxBet.OFF,
24
+ [BetradarOverrideLimitType.SAFE]: SportsProviderMaxBet.OFF,
25
+ [BetradarOverrideLimitType.SUPER_VIP]: SportsProviderMaxBet.NEUTRAL,
26
+ [BetradarOverrideLimitType.VIP]: SportsProviderMaxBet.NEUTRAL,
27
+ [BetradarOverrideLimitType.ENHANCED]: SportsProviderMaxBet.NEUTRAL,
28
+ [BetradarOverrideLimitType.POTENTIAL_VIP]: SportsProviderMaxBet.NEUTRAL,
29
+ [BetradarOverrideLimitType.DEFAULT]: SportsProviderMaxBet.NEUTRAL,
30
+ };
@@ -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
+ }
@@ -650,6 +650,7 @@ export class DisplayGroup extends BaseDisplayGroup {
650
650
  isDefaultMarket: Nullable<boolean>;
651
651
  groupDisplayType: SportsGroupedSelectionsDisplayType;
652
652
  playerMarkets: PlayerMarket[] | null;
653
+ groups: Partial<Record<SportsMarketGroup, number>> | null; // For SGM sub-groups
653
654
  }
654
655
 
655
656
  export class SelectionGroup {
@@ -106,3 +106,21 @@ declare const CHECKED: unique symbol;
106
106
  export type CheckedCurrency = Currency & {
107
107
  readonly [CHECKED]: true;
108
108
  };
109
+
110
+ /**
111
+ * Escape hatch for minting a CheckedCurrency without running
112
+ * CurrencyEligibilityService.check().
113
+ *
114
+ * Example use cases:
115
+ * - Webhooks crediting an already-settled payment (eligibility ran at
116
+ * initiation; re-checking could refuse money that's already been taken).
117
+ * - Backfill/migration scripts over historical rows with no live eligibility
118
+ * context.
119
+ * - Hot-path bet settlement where the currency was already established upstream.
120
+ *
121
+ * For any normal account-credit path (bonuses, etc.) call check() even when it's
122
+ * a no-op for crypto, so the brand stays meaningful.
123
+ */
124
+ export function unsafeMintCheckedCurrency(currency: Currency): CheckedCurrency {
125
+ return currency as CheckedCurrency;
126
+ }