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