@provable-games/budokan-sdk 0.1.24 → 0.1.26

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/dist/index.cjs CHANGED
@@ -211,11 +211,37 @@ function camelToSnake(obj) {
211
211
  return obj;
212
212
  }
213
213
 
214
+ // src/phase/index.ts
215
+ function tournamentPhase(t, nowSeconds) {
216
+ const createdAt = Number(t.createdAtOnchain ?? NaN);
217
+ if (!Number.isFinite(createdAt)) return null;
218
+ const regStartDelay = Number(t.registrationStartDelay ?? t.schedule?.registrationStartDelay ?? 0);
219
+ const regEndDelay = Number(t.registrationEndDelay ?? t.schedule?.registrationEndDelay ?? 0);
220
+ const gameStartDelay = Number(t.gameStartDelay ?? t.schedule?.gameStartDelay ?? 0);
221
+ const gameEndDelay = Number(t.gameEndDelay ?? t.schedule?.gameEndDelay ?? 0);
222
+ const submissionDuration = Number(t.submissionDuration ?? t.schedule?.submissionDuration ?? 0);
223
+ const now = nowSeconds ?? Math.floor(Date.now() / 1e3);
224
+ const hasReg = regStartDelay > 0 || regEndDelay > 0;
225
+ const regStart = createdAt + regStartDelay;
226
+ const regEnd = regStart + regEndDelay;
227
+ const gameStart = createdAt + gameStartDelay;
228
+ const gameEnd = gameStart + gameEndDelay;
229
+ const subEnd = gameEnd + submissionDuration;
230
+ if (hasReg && now < regStart) return "scheduled";
231
+ if (hasReg && now < regEnd) return "registration";
232
+ if (now < gameStart) return "staging";
233
+ if (now < gameEnd) return "live";
234
+ if (now < subEnd) return "submission";
235
+ return "finalized";
236
+ }
237
+
214
238
  // src/api/tournaments.ts
215
239
  function normalizeTournament(raw) {
216
240
  const t = snakeToCamel(raw);
217
241
  const id = t.id ?? t.tournamentId;
218
- return { ...t, id, tournamentId: id };
242
+ const protocolFeeShare = t.protocolFeeShare ?? t.entryFee?.protocolFeeShare ?? null;
243
+ const phase = t.phase ?? tournamentPhase(t);
244
+ return { ...t, id, tournamentId: id, protocolFeeShare, phase };
219
245
  }
220
246
  function fetchOpts(ctx) {
221
247
  return {
@@ -540,7 +566,7 @@ var WSManager = class {
540
566
  // src/chains/constants.ts
541
567
  var CHAINS = {
542
568
  mainnet: {
543
- rpcUrl: "https://api.cartridge.gg/x/starknet/mainnet/rpc/v0_10",
569
+ rpcUrl: "https://rpc.provable.games/rpc",
544
570
  apiBaseUrl: "https://budokan-api-production.up.railway.app",
545
571
  wsUrl: "wss://budokan-api-production.up.railway.app/ws",
546
572
  budokanAddress: "0x0596ced030e74ebc37f33607f07ecd5a62eff22cdc4ae31fe2d724040c1bdc0b",
@@ -832,7 +858,7 @@ function phaseToRpcArg(phase) {
832
858
  }
833
859
  return new starknet.CairoCustomEnum(variants);
834
860
  }
835
- function parseTournament(raw, entryCount) {
861
+ function parseTournament(raw, entryCount, protocolFeeShare = null) {
836
862
  const obj = raw;
837
863
  const id = String(obj.id ?? "0");
838
864
  const createdAt = Number(obj.created_at ?? 0);
@@ -930,6 +956,8 @@ function parseTournament(raw, entryCount) {
930
956
  leaderboardGameMustBeOver: gameMustBeOver,
931
957
  entryFeeToken,
932
958
  entryFeeAmount,
959
+ // Protocol-fee bps snapshot, surfaced by the viewer's TournamentFullState.
960
+ protocolFeeShare,
933
961
  hasEntryRequirement,
934
962
  schedule: {
935
963
  registrationStartDelay,
@@ -951,6 +979,14 @@ function parseTournament(raw, entryCount) {
951
979
  entryFeeExtension,
952
980
  entryRequirement,
953
981
  leaderboardConfig: { ascending, gameMustBeOver },
982
+ phase: tournamentPhase({
983
+ createdAtOnchain: createdAtStr,
984
+ registrationStartDelay,
985
+ registrationEndDelay,
986
+ gameStartDelay,
987
+ gameEndDelay,
988
+ submissionDuration
989
+ }),
954
990
  entryCount,
955
991
  prizeCount: 0,
956
992
  // Not available from viewer
@@ -1096,7 +1132,8 @@ function parseFilterResult(raw) {
1096
1132
  function parseTournamentFullState(raw) {
1097
1133
  const obj = raw;
1098
1134
  const entryCount = Number(obj.entry_count ?? 0);
1099
- return parseTournament(obj.tournament, entryCount);
1135
+ const protocolFeeShare = obj.protocol_fee_bps != null ? Number(obj.protocol_fee_bps) : null;
1136
+ return parseTournament(obj.tournament, entryCount, protocolFeeShare);
1100
1137
  }
1101
1138
  async function viewerTournaments(contract, offset, limit) {
1102
1139
  return wrapRpcCall(async () => {
@@ -1272,6 +1309,9 @@ function translateCairoRewardType(rewardType) {
1272
1309
  if (subVariant === "GameCreator" || subBag?.GameCreator !== void 0) {
1273
1310
  return rewardClaim({ claimKind: "entry_fee_game_creator" });
1274
1311
  }
1312
+ if (subVariant === "ProtocolFee" || subBag?.ProtocolFee !== void 0) {
1313
+ return rewardClaim({ claimKind: "entry_fee_protocol_fee" });
1314
+ }
1275
1315
  if (subVariant === "Refund" || subBag?.Refund !== void 0) {
1276
1316
  return rewardClaim({
1277
1317
  claimKind: "entry_fee_refund",
@@ -1744,6 +1784,10 @@ var budokanViewer_default = [
1744
1784
  {
1745
1785
  name: "phase",
1746
1786
  type: "budokan_interfaces::budokan::Phase"
1787
+ },
1788
+ {
1789
+ name: "protocol_fee_bps",
1790
+ type: "core::integer::u16"
1747
1791
  }
1748
1792
  ]
1749
1793
  },
@@ -2014,6 +2058,10 @@ var budokanViewer_default = [
2014
2058
  {
2015
2059
  name: "Refund",
2016
2060
  type: "core::felt252"
2061
+ },
2062
+ {
2063
+ name: "ProtocolFee",
2064
+ type: "()"
2017
2065
  }
2018
2066
  ]
2019
2067
  },
@@ -3392,6 +3440,10 @@ var budokan_default = [
3392
3440
  {
3393
3441
  name: "Refund",
3394
3442
  type: "core::felt252"
3443
+ },
3444
+ {
3445
+ name: "ProtocolFee",
3446
+ type: "()"
3395
3447
  }
3396
3448
  ]
3397
3449
  },
@@ -4680,6 +4732,11 @@ var budokan_default = [
4680
4732
  name: "entry_requirement",
4681
4733
  type: "core::option::Option::<game_components_interfaces::entry_requirement::EntryRequirement>",
4682
4734
  kind: "data"
4735
+ },
4736
+ {
4737
+ name: "protocol_fee_bps",
4738
+ type: "core::integer::u16",
4739
+ kind: "data"
4683
4740
  }
4684
4741
  ]
4685
4742
  },
@@ -4902,6 +4959,108 @@ var budokan_default = [
4902
4959
  name: "QualificationEntriesUpdated",
4903
4960
  type: "budokan::events::QualificationEntriesUpdated",
4904
4961
  kind: "nested"
4962
+ },
4963
+ {
4964
+ name: "ProtocolFeeBpsUpdated",
4965
+ type: "budokan::events::ProtocolFeeBpsUpdated",
4966
+ kind: "nested"
4967
+ },
4968
+ {
4969
+ name: "ProtocolFeeRecipientUpdated",
4970
+ type: "budokan::events::ProtocolFeeRecipientUpdated",
4971
+ kind: "nested"
4972
+ }
4973
+ ]
4974
+ },
4975
+ {
4976
+ type: "interface",
4977
+ name: "budokan::budokan::Budokan::IBudokanProtocolFeeAdmin",
4978
+ items: [
4979
+ {
4980
+ type: "function",
4981
+ name: "set_protocol_fee_bps",
4982
+ inputs: [
4983
+ {
4984
+ name: "bps",
4985
+ type: "core::integer::u16"
4986
+ }
4987
+ ],
4988
+ outputs: [],
4989
+ state_mutability: "external"
4990
+ },
4991
+ {
4992
+ type: "function",
4993
+ name: "set_protocol_fee_recipient",
4994
+ inputs: [
4995
+ {
4996
+ name: "recipient",
4997
+ type: "core::starknet::contract_address::ContractAddress"
4998
+ }
4999
+ ],
5000
+ outputs: [],
5001
+ state_mutability: "external"
5002
+ },
5003
+ {
5004
+ type: "function",
5005
+ name: "protocol_fee_bps",
5006
+ inputs: [],
5007
+ outputs: [
5008
+ {
5009
+ type: "core::integer::u16"
5010
+ }
5011
+ ],
5012
+ state_mutability: "view"
5013
+ },
5014
+ {
5015
+ type: "function",
5016
+ name: "protocol_fee_recipient",
5017
+ inputs: [],
5018
+ outputs: [
5019
+ {
5020
+ type: "core::starknet::contract_address::ContractAddress"
5021
+ }
5022
+ ],
5023
+ state_mutability: "view"
5024
+ },
5025
+ {
5026
+ type: "function",
5027
+ name: "tournament_protocol_fee_bps",
5028
+ inputs: [
5029
+ {
5030
+ name: "tournament_id",
5031
+ type: "core::integer::u64"
5032
+ }
5033
+ ],
5034
+ outputs: [
5035
+ {
5036
+ type: "core::integer::u16"
5037
+ }
5038
+ ],
5039
+ state_mutability: "view"
5040
+ }
5041
+ ]
5042
+ },
5043
+ {
5044
+ type: "event",
5045
+ name: "budokan::events::ProtocolFeeBpsUpdated",
5046
+ kind: "struct",
5047
+ members: [
5048
+ {
5049
+ name: "bps",
5050
+ type: "core::integer::u16",
5051
+ kind: "data"
5052
+ }
5053
+ ]
5054
+ },
5055
+ {
5056
+ type: "event",
5057
+ name: "budokan::events::ProtocolFeeRecipientUpdated",
5058
+ kind: "struct",
5059
+ members: [
5060
+ {
5061
+ name: "recipient",
5062
+ type: "core::starknet::contract_address::ContractAddress",
5063
+ kind: "key"
4905
5064
  }
4906
5065
  ]
4907
5066
  }
@@ -5369,6 +5528,185 @@ function createBudokanClient(config) {
5369
5528
  return new BudokanClient(config);
5370
5529
  }
5371
5530
 
5531
+ // src/utils/prizes.ts
5532
+ function isNonEmptyString(value) {
5533
+ return typeof value === "string" && value.length > 0;
5534
+ }
5535
+ function isNonNegativeIntegerString(value) {
5536
+ if (!isNonEmptyString(value) || value.trim() !== value) return false;
5537
+ if (!/^\d+$/.test(value)) return false;
5538
+ try {
5539
+ return BigInt(value) >= 0n;
5540
+ } catch {
5541
+ return false;
5542
+ }
5543
+ }
5544
+ function isNonNegativeIntegerNumber(value) {
5545
+ return typeof value === "number" && Number.isInteger(value) && value >= 0;
5546
+ }
5547
+ function isPositiveIntegerNumber(value) {
5548
+ return typeof value === "number" && Number.isInteger(value) && value > 0;
5549
+ }
5550
+ function isDistributionType(value) {
5551
+ return value === "linear" || value === "exponential" || value === "uniform" || value === "custom";
5552
+ }
5553
+ function hasNoDistributionFields(prize) {
5554
+ return prize.distributionType === null && prize.distributionWeight === null && prize.distributionShares === null && prize.distributionCount === null;
5555
+ }
5556
+ function hasValidDistributionFields(prize) {
5557
+ if (prize.distributionType === null) return hasNoDistributionFields(prize);
5558
+ if (!isDistributionType(prize.distributionType)) return false;
5559
+ if (!isPositiveIntegerNumber(prize.distributionCount)) return false;
5560
+ if (prize.distributionType === "custom") {
5561
+ return prize.distributionWeight === null && Array.isArray(prize.distributionShares) && prize.distributionShares.length === prize.distributionCount && prize.distributionShares.every(isNonNegativeIntegerNumber) && prize.distributionShares.reduce((sum, share) => sum + share, 0) === 1e4;
5562
+ }
5563
+ if (prize.distributionType === "uniform") {
5564
+ return prize.distributionWeight === null && prize.distributionShares === null;
5565
+ }
5566
+ return isNonNegativeIntegerNumber(prize.distributionWeight) && prize.distributionShares === null;
5567
+ }
5568
+ function hasBasePrizeFields(prize) {
5569
+ return isNonNegativeIntegerString(prize.prizeId) && isNonNegativeIntegerString(prize.tournamentId) && Number.isInteger(prize.payoutPosition) && prize.payoutPosition >= 0 && isNonEmptyString(prize.sponsorAddress);
5570
+ }
5571
+ function hasExtensionConfig(value) {
5572
+ return value === null || Array.isArray(value) && value.every((entry) => typeof entry === "string");
5573
+ }
5574
+ function describePrize(prize) {
5575
+ const prizeId = isNonEmptyString(prize.prizeId) ? prize.prizeId : "<invalid>";
5576
+ return `prizeId=${prizeId}, tokenType=${prize.tokenType}`;
5577
+ }
5578
+ function malformedTokenPrizeError(action, prize) {
5579
+ return new TypeError(
5580
+ `Cannot ${action} malformed Budokan token prize (${describePrize(prize)})`
5581
+ );
5582
+ }
5583
+ function malformedExtensionPrizeError(action, prize) {
5584
+ return new TypeError(
5585
+ `Cannot ${action} malformed Budokan extension prize (${describePrize(prize)})`
5586
+ );
5587
+ }
5588
+ function assertMetagameTokenPosition(prize) {
5589
+ if (Number.isInteger(prize.payoutPosition) && prize.payoutPosition > 0) {
5590
+ return;
5591
+ }
5592
+ if (prize.payoutPosition !== 0) {
5593
+ throw new TypeError(
5594
+ `Cannot adapt Budokan token prize with invalid payout position (${describePrize(prize)})`
5595
+ );
5596
+ }
5597
+ throw new TypeError(
5598
+ `Cannot adapt Budokan token prize with unhydrated payout position (${describePrize(prize)})`
5599
+ );
5600
+ }
5601
+ function assertMetagameExtensionPosition(prize) {
5602
+ if (Number.isInteger(prize.payoutPosition) && prize.payoutPosition > 0) {
5603
+ return;
5604
+ }
5605
+ if (prize.payoutPosition !== 0) {
5606
+ throw new TypeError(
5607
+ `Cannot adapt Budokan extension prize with invalid payout position (${describePrize(prize)})`
5608
+ );
5609
+ }
5610
+ throw new TypeError(
5611
+ `Cannot adapt Budokan extension prize with unhydrated payout position (${describePrize(prize)})`
5612
+ );
5613
+ }
5614
+ function hasHydratedPayoutPosition(prize) {
5615
+ return prize.payoutPosition > 0;
5616
+ }
5617
+ function isRawTokenPrize(prize) {
5618
+ if (!hasBasePrizeFields(prize) || !isNonEmptyString(prize.tokenAddress)) {
5619
+ return false;
5620
+ }
5621
+ return prize.tokenType === "erc20" ? isNonNegativeIntegerString(prize.amount) && prize.tokenId === null && prize.extensionAddress === null && prize.extensionConfig === null && hasValidDistributionFields(prize) : prize.tokenType === "erc721" && prize.amount === null && isNonNegativeIntegerString(prize.tokenId) && prize.extensionAddress === null && prize.extensionConfig === null && hasNoDistributionFields(prize);
5622
+ }
5623
+ function isRawExtensionPrize(prize) {
5624
+ return hasBasePrizeFields(prize) && prize.tokenType === "extension" && prize.tokenAddress === null && prize.amount === null && prize.tokenId === null && hasNoDistributionFields(prize) && isNonEmptyString(prize.extensionAddress) && hasExtensionConfig(prize.extensionConfig);
5625
+ }
5626
+ function isTokenPrize(prize) {
5627
+ return isRawTokenPrize(prize) && hasHydratedPayoutPosition(prize);
5628
+ }
5629
+ function isExtensionPrize(prize) {
5630
+ return isRawExtensionPrize(prize) && hasHydratedPayoutPosition(prize);
5631
+ }
5632
+ function isMetagameAdaptablePrize(prize) {
5633
+ return isTokenPrize(prize) || isExtensionPrize(prize);
5634
+ }
5635
+ function getRawTokenPrizes(prizes) {
5636
+ return prizes.flatMap((prize) => {
5637
+ if (isRawTokenPrize(prize)) return [prize];
5638
+ if (prize.tokenType === "extension") return [];
5639
+ throw malformedTokenPrizeError("read", prize);
5640
+ });
5641
+ }
5642
+ function getTokenPrizes(prizes) {
5643
+ return prizes.flatMap((prize) => {
5644
+ if (isTokenPrize(prize)) return [prize];
5645
+ if (isRawTokenPrize(prize) || prize.tokenType === "extension") return [];
5646
+ throw malformedTokenPrizeError("read", prize);
5647
+ });
5648
+ }
5649
+ function toMetagameTokenPrize(prize) {
5650
+ assertMetagameTokenPosition(prize);
5651
+ if (!isRawTokenPrize(prize)) {
5652
+ throw malformedTokenPrizeError("adapt", prize);
5653
+ }
5654
+ const adapted = {
5655
+ id: prize.prizeId,
5656
+ position: prize.payoutPosition,
5657
+ tokenAddress: prize.tokenAddress,
5658
+ tokenType: prize.tokenType,
5659
+ // Metagame token prizes use `amount` as the token id for ERC721 entries.
5660
+ amount: prize.tokenType === "erc20" ? prize.amount : prize.tokenId,
5661
+ sponsorAddress: prize.sponsorAddress
5662
+ };
5663
+ return adapted;
5664
+ }
5665
+ function toMetagameExtensionPrize(prize) {
5666
+ assertMetagameExtensionPosition(prize);
5667
+ if (!isRawExtensionPrize(prize)) {
5668
+ throw malformedExtensionPrizeError("adapt", prize);
5669
+ }
5670
+ const adapted = {
5671
+ id: prize.prizeId,
5672
+ position: prize.payoutPosition,
5673
+ tokenAddress: null,
5674
+ tokenType: "extension",
5675
+ amount: null,
5676
+ sponsorAddress: prize.sponsorAddress,
5677
+ extensionAddress: prize.extensionAddress,
5678
+ extensionConfig: prize.extensionConfig
5679
+ };
5680
+ return adapted;
5681
+ }
5682
+ function toMetagamePrize(prize) {
5683
+ if (isRawTokenPrize(prize)) return toMetagameTokenPrize(prize);
5684
+ if (isRawExtensionPrize(prize)) return toMetagameExtensionPrize(prize);
5685
+ throw new TypeError(
5686
+ `Cannot adapt malformed Budokan prize (${describePrize(prize)})`
5687
+ );
5688
+ }
5689
+ function tryToMetagamePrize(prize) {
5690
+ if (!isMetagameAdaptablePrize(prize)) return null;
5691
+ return prize.tokenType === "extension" ? toMetagameExtensionPrize(prize) : toMetagameTokenPrize(prize);
5692
+ }
5693
+ function toMetagamePrizes(prizes) {
5694
+ return prizes.map(toMetagamePrize);
5695
+ }
5696
+ function tryToMetagamePrizes(prizes) {
5697
+ return prizes.flatMap((prize) => {
5698
+ const adapted = tryToMetagamePrize(prize);
5699
+ return adapted ? [adapted] : [];
5700
+ });
5701
+ }
5702
+ function toMetagameTokenPrizes(prizes) {
5703
+ return prizes.flatMap((prize) => {
5704
+ if (isRawTokenPrize(prize)) return [toMetagameTokenPrize(prize)];
5705
+ if (prize.tokenType === "extension") return [];
5706
+ throw malformedTokenPrizeError("adapt", prize);
5707
+ });
5708
+ }
5709
+
5372
5710
  // src/games/whitelist.ts
5373
5711
  var STRK = "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d";
5374
5712
  var MAINNET_GAMES_RAW = [
@@ -5755,6 +6093,9 @@ function pushRewardTypeFelts(out, reward) {
5755
6093
  case "entry_fee_refund":
5756
6094
  out.push("0x1", "0x0", "0x3", starknet.num.toHex(reward.tokenId));
5757
6095
  return;
6096
+ case "entry_fee_protocol_fee":
6097
+ out.push("0x1", "0x0", "0x4");
6098
+ return;
5758
6099
  case "entry_fee_extension": {
5759
6100
  out.push("0x1", "0x1");
5760
6101
  if (reward.tokenId !== void 0) {
@@ -5796,6 +6137,248 @@ function felt252FromShortString(s) {
5796
6137
  }
5797
6138
  return "0x" + value.toString(16);
5798
6139
  }
6140
+
6141
+ // src/distribution/index.ts
6142
+ var BASIS_POINTS = 10000n;
6143
+ function calculateDistribution(positions, weight, distributionType) {
6144
+ if (positions <= 0) return [];
6145
+ let raw;
6146
+ if (distributionType === "uniform") {
6147
+ raw = Array(positions).fill(1);
6148
+ } else if (distributionType === "linear") {
6149
+ raw = [];
6150
+ for (let i = 0; i < positions; i++) {
6151
+ const positionValue = positions - i;
6152
+ raw.push(1 + (positionValue - 1) * (weight / 10));
6153
+ }
6154
+ } else {
6155
+ raw = [];
6156
+ for (let i = 0; i < positions; i++) {
6157
+ raw.push(Math.pow(1 - i / positions, weight));
6158
+ }
6159
+ }
6160
+ const total = raw.reduce((a, b) => a + b, 0);
6161
+ if (total === 0) return Array(positions).fill(0);
6162
+ const bpShares = raw.map((d) => Math.floor(d / total * 1e4));
6163
+ const remaining = 1e4 - bpShares.reduce((a, b) => a + b, 0);
6164
+ if (remaining !== 0) bpShares[0] = bpShares[0] + remaining;
6165
+ return bpShares.map((bp) => bp / 100);
6166
+ }
6167
+ var KNOWN_KEYS = {
6168
+ linear: "linear",
6169
+ exponential: "exponential",
6170
+ uniform: "uniform",
6171
+ custom: "custom"
6172
+ };
6173
+ function parseDistribution(dist) {
6174
+ if (!dist || typeof dist !== "object") {
6175
+ return { type: "unknown", weight: 0 };
6176
+ }
6177
+ const explicit = dist;
6178
+ if (typeof explicit.type === "string") {
6179
+ const kind = KNOWN_KEYS[explicit.type.toLowerCase()] ?? "unknown";
6180
+ return { type: kind, weight: Number(explicit.weight ?? 0) };
6181
+ }
6182
+ const bag = dist.variant ?? dist;
6183
+ for (const [rawKey, value] of Object.entries(bag)) {
6184
+ if (value === void 0 || value === null) continue;
6185
+ const kind = KNOWN_KEYS[rawKey.toLowerCase()];
6186
+ if (!kind) continue;
6187
+ if (kind === "uniform") return { type: "uniform", weight: 0 };
6188
+ if (kind === "custom") {
6189
+ const arr = Array.isArray(value) ? value.map((v) => Number(v)) : [];
6190
+ return { type: "custom", weight: 0, customWeights: arr };
6191
+ }
6192
+ if (typeof value === "number" || typeof value === "string" || typeof value === "bigint") {
6193
+ return { type: kind, weight: Number(value) };
6194
+ }
6195
+ if (typeof value === "object" && value !== null && "Some" in value) {
6196
+ return { type: kind, weight: Number(value.Some) };
6197
+ }
6198
+ return { type: kind, weight: 0 };
6199
+ }
6200
+ return { type: "unknown", weight: 0 };
6201
+ }
6202
+ function prizeDistribution(prize) {
6203
+ const type = KNOWN_KEYS[String(prize.distributionType ?? "uniform").toLowerCase()] ?? "uniform";
6204
+ if (type === "custom") {
6205
+ return { type, weight: 0, customWeights: prize.distributionShares ?? [] };
6206
+ }
6207
+ return { type, weight: prize.distributionWeight ?? 10 };
6208
+ }
6209
+ function distributionPercentages(dist, count) {
6210
+ if (count <= 0) return [];
6211
+ if (dist.type === "custom") {
6212
+ const cw = dist.customWeights ?? [];
6213
+ if (cw.length === count) return cw.map((bp) => bp / 100);
6214
+ return calculateDistribution(count, 1, "uniform");
6215
+ }
6216
+ const distType = dist.type === "linear" || dist.type === "exponential" || dist.type === "uniform" ? dist.type : "uniform";
6217
+ return calculateDistribution(count, dist.weight / 10, distType);
6218
+ }
6219
+ function bps(total, share) {
6220
+ const b = Number(share ?? 0);
6221
+ if (b <= 0) return 0n;
6222
+ return total * BigInt(b) / BASIS_POINTS;
6223
+ }
6224
+ function entryFeeSplit(input) {
6225
+ const total = BigInt(input.amount ?? 0) * BigInt(input.entryCount ?? 0);
6226
+ const creator = Number(input.tournamentCreatorShare ?? 0);
6227
+ const game = Number(input.gameCreatorShare ?? 0);
6228
+ const refund = Number(input.refundShare ?? 0);
6229
+ const protocol = Number(input.protocolFeeShare ?? 0);
6230
+ const availableShareBps = Math.max(0, 1e4 - creator - game - refund - protocol);
6231
+ return {
6232
+ total,
6233
+ positionPool: total * BigInt(availableShareBps) / BASIS_POINTS,
6234
+ tournamentCreator: bps(total, creator),
6235
+ gameCreator: bps(total, game),
6236
+ refund: bps(total, refund),
6237
+ protocolFee: bps(total, protocol),
6238
+ availableShareBps
6239
+ };
6240
+ }
6241
+ function entryFeePositionPayout(input, position) {
6242
+ const distCount = Number(input.distributionCount ?? 0);
6243
+ if (distCount <= 0 || position < 1 || position > distCount) return 0n;
6244
+ const split = entryFeeSplit(input);
6245
+ if (split.positionPool <= 0n) return 0n;
6246
+ const pcts = distributionPercentages(parseDistribution(input.distribution), distCount);
6247
+ const pct = pcts[position - 1] ?? 0;
6248
+ if (pct <= 0) return 0n;
6249
+ return split.positionPool * BigInt(Math.floor(pct * 1e4)) / 1000000n;
6250
+ }
6251
+ function sponsorPrizePayout(prize, position) {
6252
+ if (prize.tokenType !== "erc20") return 0n;
6253
+ const distCount = Number(prize.distributionCount ?? 0);
6254
+ if (distCount <= 0 || position < 1 || position > distCount) return 0n;
6255
+ const pcts = distributionPercentages(prizeDistribution(prize), distCount);
6256
+ const pct = pcts[position - 1] ?? 0;
6257
+ if (pct <= 0) return 0n;
6258
+ return BigInt(prize.amount ?? "0") * BigInt(Math.floor(pct * 1e4)) / 1000000n;
6259
+ }
6260
+
6261
+ // src/rewards/index.ts
6262
+ function getClaimableRewards(input) {
6263
+ const tournamentsById = /* @__PURE__ */ new Map();
6264
+ for (const t of input.tournaments) tournamentsById.set(t.id, t);
6265
+ const prizesByTournament = /* @__PURE__ */ new Map();
6266
+ for (const p of input.prizes) {
6267
+ let list = prizesByTournament.get(p.tournamentId);
6268
+ if (!list) prizesByTournament.set(p.tournamentId, list = []);
6269
+ list.push(p);
6270
+ }
6271
+ const claimedKeys = /* @__PURE__ */ new Set();
6272
+ for (const c of input.existingClaims) {
6273
+ if (!c.claimed) continue;
6274
+ const key = rewardClaimKey(c);
6275
+ if (key) claimedKeys.add(`${c.tournamentId}:${key}`);
6276
+ }
6277
+ const out = [];
6278
+ for (const placement of input.placements) {
6279
+ const tournament = tournamentsById.get(placement.tournamentId);
6280
+ if (!tournament) continue;
6281
+ const tournamentName = tournament.name || `#${tournament.id}`;
6282
+ const pos = placement.position;
6283
+ const ef = tournament.entryFee;
6284
+ const efDistCount = Number(ef?.distributionCount ?? 0);
6285
+ if (ef && ef.tokenAddress && efDistCount >= pos) {
6286
+ const claimKey = `${placement.tournamentId}:EntryFee.Position.${pos}`;
6287
+ if (!claimedKeys.has(claimKey)) {
6288
+ const amount = entryFeePositionPayout(
6289
+ {
6290
+ amount: ef.amount ?? "0",
6291
+ entryCount: tournament.entryCount ?? 0,
6292
+ tournamentCreatorShare: ef.tournamentCreatorShare,
6293
+ gameCreatorShare: ef.gameCreatorShare,
6294
+ refundShare: ef.refundShare,
6295
+ protocolFeeShare: tournament.protocolFeeShare,
6296
+ distribution: ef.distribution,
6297
+ distributionCount: efDistCount
6298
+ },
6299
+ pos
6300
+ );
6301
+ if (amount > 0n) {
6302
+ out.push({
6303
+ tournamentId: placement.tournamentId,
6304
+ tournamentName,
6305
+ source: "entry_fee_position",
6306
+ position: pos,
6307
+ tokenAddress: ef.tokenAddress,
6308
+ tokenType: "erc20",
6309
+ amount,
6310
+ reward: { kind: "entry_fee_position", position: pos }
6311
+ });
6312
+ }
6313
+ }
6314
+ }
6315
+ for (const prize of prizesByTournament.get(placement.tournamentId) ?? []) {
6316
+ if (!isRawTokenPrize(prize)) continue;
6317
+ const dc = prize.distributionCount ?? 0;
6318
+ const pp = prize.payoutPosition ?? 0;
6319
+ const isSingle = pp === pos && dc === 0;
6320
+ const isDistributed = dc >= pos && pp === 0;
6321
+ if (!isSingle && !isDistributed) continue;
6322
+ const tokenType = prize.tokenType === "erc721" ? "erc721" : "erc20";
6323
+ if (isSingle) {
6324
+ const claimKey2 = `${placement.tournamentId}:Prize.Single.${prize.prizeId}`;
6325
+ if (claimedKeys.has(claimKey2)) continue;
6326
+ out.push({
6327
+ tournamentId: placement.tournamentId,
6328
+ tournamentName,
6329
+ source: "sponsor_single",
6330
+ position: pos,
6331
+ tokenAddress: prize.tokenAddress,
6332
+ tokenType,
6333
+ amount: tokenType === "erc20" ? BigInt(prize.amount ?? "0") : void 0,
6334
+ tokenId: prize.tokenId,
6335
+ reward: { kind: "prize_single", prizeId: prize.prizeId }
6336
+ });
6337
+ continue;
6338
+ }
6339
+ const payoutIndex = pos - 1;
6340
+ const claimKey = `${placement.tournamentId}:Prize.Distributed.${prize.prizeId}.${payoutIndex}`;
6341
+ if (claimedKeys.has(claimKey)) continue;
6342
+ const amount = sponsorPrizePayout(prize, pos);
6343
+ if (amount <= 0n) continue;
6344
+ out.push({
6345
+ tournamentId: placement.tournamentId,
6346
+ tournamentName,
6347
+ source: "sponsor_distributed",
6348
+ position: pos,
6349
+ tokenAddress: prize.tokenAddress,
6350
+ tokenType: "erc20",
6351
+ amount,
6352
+ reward: {
6353
+ kind: "prize_distributed",
6354
+ prizeId: prize.prizeId,
6355
+ payoutPosition: payoutIndex
6356
+ }
6357
+ });
6358
+ }
6359
+ }
6360
+ return out;
6361
+ }
6362
+ function buildClaimCalls(rewards, budokanAddress) {
6363
+ return rewards.map(
6364
+ (r) => buildClaimRewardCall(budokanAddress, {
6365
+ tournamentId: r.tournamentId,
6366
+ reward: r.reward
6367
+ })
6368
+ );
6369
+ }
6370
+ function rewardClaimKey(c) {
6371
+ switch (c.claimKind) {
6372
+ case "prize_single":
6373
+ return c.prizeId ? `Prize.Single.${c.prizeId}` : null;
6374
+ case "prize_distributed":
6375
+ return c.prizeId != null && c.payoutIndex != null ? `Prize.Distributed.${c.prizeId}.${c.payoutIndex}` : null;
6376
+ case "entry_fee_position":
6377
+ return c.position != null ? `EntryFee.Position.${c.position}` : null;
6378
+ default:
6379
+ return null;
6380
+ }
6381
+ }
5799
6382
  function sdkChainId(chain) {
5800
6383
  return chain === "mainnet" ? "SN_MAIN" : "SN_SEPOLIA";
5801
6384
  }
@@ -5876,6 +6459,7 @@ exports.RpcError = RpcError;
5876
6459
  exports.TournamentNotFoundError = TournamentNotFoundError;
5877
6460
  exports.WSManager = WSManager;
5878
6461
  exports.buildAddPrizeCall = buildAddPrizeCall;
6462
+ exports.buildClaimCalls = buildClaimCalls;
5879
6463
  exports.buildClaimRewardCall = buildClaimRewardCall;
5880
6464
  exports.buildCreateTournamentCall = buildCreateTournamentCall;
5881
6465
  exports.buildEnterTournamentCall = buildEnterTournamentCall;
@@ -5887,6 +6471,9 @@ exports.buildSubmitScoreCall = buildSubmitScoreCall;
5887
6471
  exports.buildTournamentValidatorConfig = buildTournamentValidatorConfig;
5888
6472
  exports.camelToSnake = camelToSnake;
5889
6473
  exports.createBudokanClient = createBudokanClient;
6474
+ exports.distributionPercentages = distributionPercentages;
6475
+ exports.entryFeePositionPayout = entryFeePositionPayout;
6476
+ exports.entryFeeSplit = entryFeeSplit;
5890
6477
  exports.explorerAddressUrl = explorerAddressUrl;
5891
6478
  exports.explorerBaseUrl = explorerBaseUrl;
5892
6479
  exports.explorerTxUrl = explorerTxUrl;
@@ -5894,10 +6481,13 @@ exports.extensionAddressFor = extensionAddressFor;
5894
6481
  exports.findWhitelistedGame = findWhitelistedGame;
5895
6482
  exports.getActivityStats = getActivityStats;
5896
6483
  exports.getChainConfig = getChainConfig;
6484
+ exports.getClaimableRewards = getClaimableRewards;
5897
6485
  exports.getGameDefaults = getGameDefaults;
5898
6486
  exports.getGameStats = getGameStats;
5899
6487
  exports.getGameTournaments = getGameTournaments;
5900
6488
  exports.getPrizeStats = getPrizeStats;
6489
+ exports.getRawTokenPrizes = getRawTokenPrizes;
6490
+ exports.getTokenPrizes = getTokenPrizes;
5901
6491
  exports.getTournament = getTournament;
5902
6492
  exports.getTournamentPrizeAggregation = getTournamentPrizeAggregation;
5903
6493
  exports.getTournamentPrizes = getTournamentPrizes;
@@ -5907,11 +6497,27 @@ exports.getTournamentRewardClaims = getTournamentRewardClaims;
5907
6497
  exports.getTournamentRewardClaimsSummary = getTournamentRewardClaimsSummary;
5908
6498
  exports.getTournaments = getTournaments;
5909
6499
  exports.getWhitelistedGames = getWhitelistedGames;
6500
+ exports.isExtensionPrize = isExtensionPrize;
5910
6501
  exports.isGameWhitelisted = isGameWhitelisted;
6502
+ exports.isMetagameAdaptablePrize = isMetagameAdaptablePrize;
6503
+ exports.isRawExtensionPrize = isRawExtensionPrize;
6504
+ exports.isRawTokenPrize = isRawTokenPrize;
6505
+ exports.isTokenPrize = isTokenPrize;
5911
6506
  exports.normalizeAddress = normalizeAddress;
6507
+ exports.parseDistribution = parseDistribution;
5912
6508
  exports.parseTournamentIdFromReceipt = parseTournamentIdFromReceipt;
6509
+ exports.prizeDistribution = prizeDistribution;
5913
6510
  exports.snakeToCamel = snakeToCamel;
6511
+ exports.sponsorPrizePayout = sponsorPrizePayout;
6512
+ exports.toMetagameExtensionPrize = toMetagameExtensionPrize;
6513
+ exports.toMetagamePrize = toMetagamePrize;
6514
+ exports.toMetagamePrizes = toMetagamePrizes;
6515
+ exports.toMetagameTokenPrize = toMetagameTokenPrize;
6516
+ exports.toMetagameTokenPrizes = toMetagameTokenPrizes;
5914
6517
  exports.tournamentPageUrl = tournamentPageUrl;
6518
+ exports.tournamentPhase = tournamentPhase;
6519
+ exports.tryToMetagamePrize = tryToMetagamePrize;
6520
+ exports.tryToMetagamePrizes = tryToMetagamePrizes;
5915
6521
  exports.u256ToLowHigh = u256ToLowHigh;
5916
6522
  exports.withRetry = withRetry;
5917
6523
  //# sourceMappingURL=index.cjs.map