@provable-games/budokan-sdk 0.1.25 → 0.1.27

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,12 +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
240
  const protocolFeeShare = t.protocolFeeShare ?? t.entryFee?.protocolFeeShare ?? null;
217
- return { ...t, id, tournamentId: id, protocolFeeShare };
241
+ const phase = t.phase ?? tournamentPhase(t);
242
+ return { ...t, id, tournamentId: id, protocolFeeShare, phase };
218
243
  }
219
244
  function fetchOpts(ctx) {
220
245
  return {
@@ -539,7 +564,7 @@ var WSManager = class {
539
564
  // src/chains/constants.ts
540
565
  var CHAINS = {
541
566
  mainnet: {
542
- rpcUrl: "https://api.cartridge.gg/x/starknet/mainnet/rpc/v0_10",
567
+ rpcUrl: "https://rpc.provable.games/rpc",
543
568
  apiBaseUrl: "https://budokan-api-production.up.railway.app",
544
569
  wsUrl: "wss://budokan-api-production.up.railway.app/ws",
545
570
  budokanAddress: "0x0596ced030e74ebc37f33607f07ecd5a62eff22cdc4ae31fe2d724040c1bdc0b",
@@ -952,6 +977,14 @@ function parseTournament(raw, entryCount, protocolFeeShare = null) {
952
977
  entryFeeExtension,
953
978
  entryRequirement,
954
979
  leaderboardConfig: { ascending, gameMustBeOver },
980
+ phase: tournamentPhase({
981
+ createdAtOnchain: createdAtStr,
982
+ registrationStartDelay,
983
+ registrationEndDelay,
984
+ gameStartDelay,
985
+ gameEndDelay,
986
+ submissionDuration
987
+ }),
955
988
  entryCount,
956
989
  prizeCount: 0,
957
990
  // Not available from viewer
@@ -6102,6 +6135,248 @@ function felt252FromShortString(s) {
6102
6135
  }
6103
6136
  return "0x" + value.toString(16);
6104
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;
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
+ }
6105
6380
  function sdkChainId(chain) {
6106
6381
  return chain === "mainnet" ? "SN_MAIN" : "SN_SEPOLIA";
6107
6382
  }
@@ -6170,6 +6445,6 @@ function buildTournamentValidatorConfig(cfg) {
6170
6445
  return [qualifierType, qualifyingMode, topPositions, ...cfg.tournamentIds];
6171
6446
  }
6172
6447
 
6173
- 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, getRawTokenPrizes, getTokenPrizes, getTournament, getTournamentPrizeAggregation, getTournamentPrizes, getTournamentQualifications, getTournamentRegistrations, getTournamentRewardClaims, getTournamentRewardClaimsSummary, getTournaments, getWhitelistedGames, isExtensionPrize, isGameWhitelisted, isMetagameAdaptablePrize, isRawExtensionPrize, isRawTokenPrize, isTokenPrize, normalizeAddress, parseTournamentIdFromReceipt, snakeToCamel, toMetagameExtensionPrize, toMetagamePrize, toMetagamePrizes, toMetagameTokenPrize, toMetagameTokenPrizes, tournamentPageUrl, tryToMetagamePrize, tryToMetagamePrizes, 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 };
6174
6449
  //# sourceMappingURL=index.js.map
6175
6450
  //# sourceMappingURL=index.js.map