@provable-games/budokan-sdk 0.1.25 → 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,12 +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
242
  const protocolFeeShare = t.protocolFeeShare ?? t.entryFee?.protocolFeeShare ?? null;
219
- return { ...t, id, tournamentId: id, protocolFeeShare };
243
+ const phase = t.phase ?? tournamentPhase(t);
244
+ return { ...t, id, tournamentId: id, protocolFeeShare, phase };
220
245
  }
221
246
  function fetchOpts(ctx) {
222
247
  return {
@@ -541,7 +566,7 @@ var WSManager = class {
541
566
  // src/chains/constants.ts
542
567
  var CHAINS = {
543
568
  mainnet: {
544
- rpcUrl: "https://api.cartridge.gg/x/starknet/mainnet/rpc/v0_10",
569
+ rpcUrl: "https://rpc.provable.games/rpc",
545
570
  apiBaseUrl: "https://budokan-api-production.up.railway.app",
546
571
  wsUrl: "wss://budokan-api-production.up.railway.app/ws",
547
572
  budokanAddress: "0x0596ced030e74ebc37f33607f07ecd5a62eff22cdc4ae31fe2d724040c1bdc0b",
@@ -954,6 +979,14 @@ function parseTournament(raw, entryCount, protocolFeeShare = null) {
954
979
  entryFeeExtension,
955
980
  entryRequirement,
956
981
  leaderboardConfig: { ascending, gameMustBeOver },
982
+ phase: tournamentPhase({
983
+ createdAtOnchain: createdAtStr,
984
+ registrationStartDelay,
985
+ registrationEndDelay,
986
+ gameStartDelay,
987
+ gameEndDelay,
988
+ submissionDuration
989
+ }),
957
990
  entryCount,
958
991
  prizeCount: 0,
959
992
  // Not available from viewer
@@ -6104,6 +6137,248 @@ function felt252FromShortString(s) {
6104
6137
  }
6105
6138
  return "0x" + value.toString(16);
6106
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
+ }
6107
6382
  function sdkChainId(chain) {
6108
6383
  return chain === "mainnet" ? "SN_MAIN" : "SN_SEPOLIA";
6109
6384
  }
@@ -6184,6 +6459,7 @@ exports.RpcError = RpcError;
6184
6459
  exports.TournamentNotFoundError = TournamentNotFoundError;
6185
6460
  exports.WSManager = WSManager;
6186
6461
  exports.buildAddPrizeCall = buildAddPrizeCall;
6462
+ exports.buildClaimCalls = buildClaimCalls;
6187
6463
  exports.buildClaimRewardCall = buildClaimRewardCall;
6188
6464
  exports.buildCreateTournamentCall = buildCreateTournamentCall;
6189
6465
  exports.buildEnterTournamentCall = buildEnterTournamentCall;
@@ -6195,6 +6471,9 @@ exports.buildSubmitScoreCall = buildSubmitScoreCall;
6195
6471
  exports.buildTournamentValidatorConfig = buildTournamentValidatorConfig;
6196
6472
  exports.camelToSnake = camelToSnake;
6197
6473
  exports.createBudokanClient = createBudokanClient;
6474
+ exports.distributionPercentages = distributionPercentages;
6475
+ exports.entryFeePositionPayout = entryFeePositionPayout;
6476
+ exports.entryFeeSplit = entryFeeSplit;
6198
6477
  exports.explorerAddressUrl = explorerAddressUrl;
6199
6478
  exports.explorerBaseUrl = explorerBaseUrl;
6200
6479
  exports.explorerTxUrl = explorerTxUrl;
@@ -6202,6 +6481,7 @@ exports.extensionAddressFor = extensionAddressFor;
6202
6481
  exports.findWhitelistedGame = findWhitelistedGame;
6203
6482
  exports.getActivityStats = getActivityStats;
6204
6483
  exports.getChainConfig = getChainConfig;
6484
+ exports.getClaimableRewards = getClaimableRewards;
6205
6485
  exports.getGameDefaults = getGameDefaults;
6206
6486
  exports.getGameStats = getGameStats;
6207
6487
  exports.getGameTournaments = getGameTournaments;
@@ -6224,14 +6504,18 @@ exports.isRawExtensionPrize = isRawExtensionPrize;
6224
6504
  exports.isRawTokenPrize = isRawTokenPrize;
6225
6505
  exports.isTokenPrize = isTokenPrize;
6226
6506
  exports.normalizeAddress = normalizeAddress;
6507
+ exports.parseDistribution = parseDistribution;
6227
6508
  exports.parseTournamentIdFromReceipt = parseTournamentIdFromReceipt;
6509
+ exports.prizeDistribution = prizeDistribution;
6228
6510
  exports.snakeToCamel = snakeToCamel;
6511
+ exports.sponsorPrizePayout = sponsorPrizePayout;
6229
6512
  exports.toMetagameExtensionPrize = toMetagameExtensionPrize;
6230
6513
  exports.toMetagamePrize = toMetagamePrize;
6231
6514
  exports.toMetagamePrizes = toMetagamePrizes;
6232
6515
  exports.toMetagameTokenPrize = toMetagameTokenPrize;
6233
6516
  exports.toMetagameTokenPrizes = toMetagameTokenPrizes;
6234
6517
  exports.tournamentPageUrl = tournamentPageUrl;
6518
+ exports.tournamentPhase = tournamentPhase;
6235
6519
  exports.tryToMetagamePrize = tryToMetagamePrize;
6236
6520
  exports.tryToMetagamePrizes = tryToMetagamePrizes;
6237
6521
  exports.u256ToLowHigh = u256ToLowHigh;