@provable-games/budokan-sdk 0.1.22 → 0.1.23

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/react.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode } from 'react';
3
- import { i as BudokanClientConfig, B as BudokanClient, T as Tournament, b as PaginatedResult, e as TournamentListParams, m as Phase, l as LeaderboardEntry, R as Registration, c as RewardClaim, d as RewardClaimSummary, g as PrizeStats, a as Prize, P as PrizeAggregation, Q as QualificationEntry, f as PlatformStats, o as WSEventMessage, n as WSChannel, C as ConnectionMode } from './client-DlXvzneQ.cjs';
3
+ import { i as BudokanClientConfig, B as BudokanClient, T as Tournament, b as PaginatedResult, e as TournamentListParams, m as Phase, l as LeaderboardEntry, R as Registration, c as RewardClaim, d as RewardClaimSummary, g as PrizeStats, a as Prize, P as PrizeAggregation, Q as QualificationEntry, f as PlatformStats, o as PlayerRewards, q as WSEventMessage, p as WSChannel, C as ConnectionMode } from './player-BUynfv7D.cjs';
4
4
  import 'starknet';
5
5
  import '@provable-games/metagame-sdk';
6
6
 
@@ -200,6 +200,49 @@ interface UseActivityStatsResult {
200
200
  */
201
201
  declare function useActivityStats(): UseActivityStatsResult;
202
202
 
203
+ interface UsePlayerRewardsResult {
204
+ rewards: PlayerRewards | null;
205
+ loading: boolean;
206
+ error: Error | null;
207
+ refetch: () => Promise<void>;
208
+ }
209
+ /**
210
+ * Aggregate a player's placement/earnings data across finalized Budokan
211
+ * tournaments they currently hold tokens in.
212
+ *
213
+ * Composition:
214
+ *
215
+ * denshokan useTokens (current ownership)
216
+ * ↓ group by contextId (= tournament id)
217
+ * budokan useTournaments({ tournamentIds })
218
+ * ↓ filter to finalized
219
+ * for each finalized tournament (parallel):
220
+ * budokan getTournamentPrizes — full prize records
221
+ * budokan getTournamentRewardClaims — paginated, all pages
222
+ * denshokan getTokenRanks (bulk) — final ranks for owned tokens
223
+ * ↓ filter ranks to paid positions (max position derived from prizes
224
+ * + entry-fee distribution_count)
225
+ *
226
+ * Per-tournament failures are tolerated: a 5xx on one tournament's prize/
227
+ * claim/rank fetch skips that tournament and continues, rather than
228
+ * failing the whole hook. Failures are logged via `console.warn`.
229
+ *
230
+ * Why source ownership from denshokan: PR #243 dropped
231
+ * `registrations.player_address` because the indexed value goes stale on
232
+ * transfer. The contract keys registrations by token_id only, so the only
233
+ * trustworthy answer to "what tournaments has this wallet placed in" is
234
+ * to ask denshokan who currently holds Budokan-minted tokens.
235
+ *
236
+ * Staleness: the hook only re-fetches when the player's owned token set
237
+ * changes (transfers/new mints) or when the underlying tournament list
238
+ * changes. It does *not* observe time-based finalization transitions on
239
+ * its own — if a tournament's submission window closes while the
240
+ * consumer is mounted, call `refetch()` to pick up the new placement.
241
+ *
242
+ * Pass `undefined` to skip fetching.
243
+ */
244
+ declare function usePlayerRewards(address: string | undefined): UsePlayerRewardsResult;
245
+
203
246
  interface UseSubscriptionResult {
204
247
  lastMessage: WSEventMessage | null;
205
248
  isConnected: boolean;
@@ -223,4 +266,4 @@ declare function useConnectionStatus(): {
223
266
  datasourceMode: ConnectionMode;
224
267
  };
225
268
 
226
- export { BudokanProvider, type BudokanProviderProps, type UseActivityStatsResult, type UseLeaderboardResult, type UsePrizeAggregationResult, type UsePrizeStatsResult, type UsePrizesResult, type UseQualificationsResult, type UseRegistrationsResult, type UseRewardClaimsResult, type UseRewardClaimsSummaryResult, type UseSubscriptionResult, type UseTournamentCountResult, type UseTournamentResult, type UseTournamentsResult, useActivityStats, useBudokanClient, useConnectionStatus, useLeaderboard, usePrizeAggregation, usePrizeStats, usePrizes, useQualifications, useRegistrations, useRegistrationsByOwner, useRewardClaims, useRewardClaimsSummary, useSubscription, useTournament, useTournamentCount, useTournaments, useTournamentsByOwner, useTournamentsByOwnerCount };
269
+ export { BudokanProvider, type BudokanProviderProps, type UseActivityStatsResult, type UseLeaderboardResult, type UsePlayerRewardsResult, type UsePrizeAggregationResult, type UsePrizeStatsResult, type UsePrizesResult, type UseQualificationsResult, type UseRegistrationsResult, type UseRewardClaimsResult, type UseRewardClaimsSummaryResult, type UseSubscriptionResult, type UseTournamentCountResult, type UseTournamentResult, type UseTournamentsResult, useActivityStats, useBudokanClient, useConnectionStatus, useLeaderboard, usePlayerRewards, usePrizeAggregation, usePrizeStats, usePrizes, useQualifications, useRegistrations, useRegistrationsByOwner, useRewardClaims, useRewardClaimsSummary, useSubscription, useTournament, useTournamentCount, useTournaments, useTournamentsByOwner, useTournamentsByOwnerCount };
package/dist/react.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode } from 'react';
3
- import { i as BudokanClientConfig, B as BudokanClient, T as Tournament, b as PaginatedResult, e as TournamentListParams, m as Phase, l as LeaderboardEntry, R as Registration, c as RewardClaim, d as RewardClaimSummary, g as PrizeStats, a as Prize, P as PrizeAggregation, Q as QualificationEntry, f as PlatformStats, o as WSEventMessage, n as WSChannel, C as ConnectionMode } from './client-DlXvzneQ.js';
3
+ import { i as BudokanClientConfig, B as BudokanClient, T as Tournament, b as PaginatedResult, e as TournamentListParams, m as Phase, l as LeaderboardEntry, R as Registration, c as RewardClaim, d as RewardClaimSummary, g as PrizeStats, a as Prize, P as PrizeAggregation, Q as QualificationEntry, f as PlatformStats, o as PlayerRewards, q as WSEventMessage, p as WSChannel, C as ConnectionMode } from './player-BUynfv7D.js';
4
4
  import 'starknet';
5
5
  import '@provable-games/metagame-sdk';
6
6
 
@@ -200,6 +200,49 @@ interface UseActivityStatsResult {
200
200
  */
201
201
  declare function useActivityStats(): UseActivityStatsResult;
202
202
 
203
+ interface UsePlayerRewardsResult {
204
+ rewards: PlayerRewards | null;
205
+ loading: boolean;
206
+ error: Error | null;
207
+ refetch: () => Promise<void>;
208
+ }
209
+ /**
210
+ * Aggregate a player's placement/earnings data across finalized Budokan
211
+ * tournaments they currently hold tokens in.
212
+ *
213
+ * Composition:
214
+ *
215
+ * denshokan useTokens (current ownership)
216
+ * ↓ group by contextId (= tournament id)
217
+ * budokan useTournaments({ tournamentIds })
218
+ * ↓ filter to finalized
219
+ * for each finalized tournament (parallel):
220
+ * budokan getTournamentPrizes — full prize records
221
+ * budokan getTournamentRewardClaims — paginated, all pages
222
+ * denshokan getTokenRanks (bulk) — final ranks for owned tokens
223
+ * ↓ filter ranks to paid positions (max position derived from prizes
224
+ * + entry-fee distribution_count)
225
+ *
226
+ * Per-tournament failures are tolerated: a 5xx on one tournament's prize/
227
+ * claim/rank fetch skips that tournament and continues, rather than
228
+ * failing the whole hook. Failures are logged via `console.warn`.
229
+ *
230
+ * Why source ownership from denshokan: PR #243 dropped
231
+ * `registrations.player_address` because the indexed value goes stale on
232
+ * transfer. The contract keys registrations by token_id only, so the only
233
+ * trustworthy answer to "what tournaments has this wallet placed in" is
234
+ * to ask denshokan who currently holds Budokan-minted tokens.
235
+ *
236
+ * Staleness: the hook only re-fetches when the player's owned token set
237
+ * changes (transfers/new mints) or when the underlying tournament list
238
+ * changes. It does *not* observe time-based finalization transitions on
239
+ * its own — if a tournament's submission window closes while the
240
+ * consumer is mounted, call `refetch()` to pick up the new placement.
241
+ *
242
+ * Pass `undefined` to skip fetching.
243
+ */
244
+ declare function usePlayerRewards(address: string | undefined): UsePlayerRewardsResult;
245
+
203
246
  interface UseSubscriptionResult {
204
247
  lastMessage: WSEventMessage | null;
205
248
  isConnected: boolean;
@@ -223,4 +266,4 @@ declare function useConnectionStatus(): {
223
266
  datasourceMode: ConnectionMode;
224
267
  };
225
268
 
226
- export { BudokanProvider, type BudokanProviderProps, type UseActivityStatsResult, type UseLeaderboardResult, type UsePrizeAggregationResult, type UsePrizeStatsResult, type UsePrizesResult, type UseQualificationsResult, type UseRegistrationsResult, type UseRewardClaimsResult, type UseRewardClaimsSummaryResult, type UseSubscriptionResult, type UseTournamentCountResult, type UseTournamentResult, type UseTournamentsResult, useActivityStats, useBudokanClient, useConnectionStatus, useLeaderboard, usePrizeAggregation, usePrizeStats, usePrizes, useQualifications, useRegistrations, useRegistrationsByOwner, useRewardClaims, useRewardClaimsSummary, useSubscription, useTournament, useTournamentCount, useTournaments, useTournamentsByOwner, useTournamentsByOwnerCount };
269
+ export { BudokanProvider, type BudokanProviderProps, type UseActivityStatsResult, type UseLeaderboardResult, type UsePlayerRewardsResult, type UsePrizeAggregationResult, type UsePrizeStatsResult, type UsePrizesResult, type UseQualificationsResult, type UseRegistrationsResult, type UseRewardClaimsResult, type UseRewardClaimsSummaryResult, type UseSubscriptionResult, type UseTournamentCountResult, type UseTournamentResult, type UseTournamentsResult, useActivityStats, useBudokanClient, useConnectionStatus, useLeaderboard, usePlayerRewards, usePrizeAggregation, usePrizeStats, usePrizes, useQualifications, useRegistrations, useRegistrationsByOwner, useRewardClaims, useRewardClaimsSummary, useSubscription, useTournament, useTournamentCount, useTournaments, useTournamentsByOwner, useTournamentsByOwnerCount };
package/dist/react.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createContext, useMemo, useRef, useEffect, useContext, useState, useCallback } from 'react';
2
2
  import { num, CairoCustomEnum } from 'starknet';
3
3
  import { jsx } from 'react/jsx-runtime';
4
- import { useTokens } from '@provable-games/denshokan-sdk/react';
4
+ import { useTokens, useDenshokanClient } from '@provable-games/denshokan-sdk/react';
5
5
 
6
6
  // src/react/context.tsx
7
7
 
@@ -4869,7 +4869,6 @@ function useOwnedTournamentIds(owner, contextId) {
4869
4869
  enabled ? {
4870
4870
  owner,
4871
4871
  minterAddress: budokanAddress,
4872
- hasContext: true,
4873
4872
  ...{},
4874
4873
  limit: MAX_OWNED_TOKENS
4875
4874
  } : void 0
@@ -4985,7 +4984,11 @@ function useRegistrationsByOwner(tournamentId, owner, params) {
4985
4984
  }, [enabled, tokensResult]);
4986
4985
  const inner = useRegistrations(
4987
4986
  ownedGameTokenIds && ownedGameTokenIds.length > 0 ? tournamentId : void 0,
4988
- ownedGameTokenIds && ownedGameTokenIds.length > 0 ? { ...params, gameTokenIds: ownedGameTokenIds } : void 0
4987
+ ownedGameTokenIds && ownedGameTokenIds.length > 0 ? {
4988
+ limit: Math.min(ownedGameTokenIds.length, MAX_OWNED_TOKENS),
4989
+ ...params,
4990
+ gameTokenIds: ownedGameTokenIds
4991
+ } : void 0
4989
4992
  );
4990
4993
  if (ownedGameTokenIds !== null && ownedGameTokenIds.length === 0) {
4991
4994
  return {
@@ -5174,6 +5177,200 @@ function useActivityStats() {
5174
5177
  }, [fetch2]);
5175
5178
  return { stats, loading, error, refetch: fetch2 };
5176
5179
  }
5180
+ function usePlayerRewards(address) {
5181
+ const budokan = useBudokanClient();
5182
+ const denshokan = useDenshokanClient();
5183
+ const budokanAddress = budokan.clientConfig.budokanAddress;
5184
+ const [rewards, setRewards] = useState(null);
5185
+ const [aggregating, setAggregating] = useState(false);
5186
+ const [error, setError] = useState(null);
5187
+ useResetOnClient(budokan, setRewards, setError);
5188
+ const tokensEnabled = !!address && !!budokanAddress;
5189
+ const { data: tokensResult, isLoading: tokensLoading } = useTokens(
5190
+ tokensEnabled ? {
5191
+ owner: address,
5192
+ minterAddress: budokanAddress,
5193
+ limit: 1e3
5194
+ } : void 0
5195
+ );
5196
+ const tokensByTournament = useMemo(() => {
5197
+ if (!tokensResult?.data) return null;
5198
+ const map = /* @__PURE__ */ new Map();
5199
+ for (const t of tokensResult.data) {
5200
+ if (t.contextId == null || !t.tokenId) continue;
5201
+ const tid = String(t.contextId);
5202
+ let list = map.get(tid);
5203
+ if (!list) {
5204
+ list = [];
5205
+ map.set(tid, list);
5206
+ }
5207
+ list.push(t.tokenId);
5208
+ }
5209
+ return map;
5210
+ }, [tokensResult]);
5211
+ const tournamentIds = useMemo(
5212
+ () => tokensByTournament ? Array.from(tokensByTournament.keys()) : [],
5213
+ [tokensByTournament]
5214
+ );
5215
+ const { tournaments: tournamentsPage, loading: tournamentsLoading } = useTournaments(
5216
+ tournamentIds.length > 0 ? { tournamentIds, limit: 1e3 } : void 0
5217
+ );
5218
+ const tournamentIdsKey = tournamentIds.join(",");
5219
+ const fetch2 = useCallback(async () => {
5220
+ if (!tokensEnabled) {
5221
+ setRewards(null);
5222
+ return;
5223
+ }
5224
+ if (!tokensByTournament) return;
5225
+ if (tokensByTournament.size === 0) {
5226
+ setRewards(emptyRewards());
5227
+ return;
5228
+ }
5229
+ if (!tournamentsPage?.data) return;
5230
+ const now = Math.floor(Date.now() / 1e3);
5231
+ const finalized = tournamentsPage.data.filter((t) => {
5232
+ const sub = Number(t.submissionEndTime ?? 0);
5233
+ return sub > 0 && sub <= now;
5234
+ });
5235
+ if (finalized.length === 0) {
5236
+ setRewards(emptyRewards());
5237
+ return;
5238
+ }
5239
+ setAggregating(true);
5240
+ setError(null);
5241
+ try {
5242
+ const settled = await Promise.allSettled(
5243
+ finalized.map(async (t) => {
5244
+ const tid = t.id;
5245
+ const tokenIds = tokensByTournament.get(tid) ?? [];
5246
+ if (tokenIds.length === 0) return null;
5247
+ const [prizes, allClaims, ranksResult] = await Promise.all([
5248
+ budokan.getTournamentPrizes(tid),
5249
+ fetchAllRewardClaims(budokan, tid),
5250
+ denshokan.getTokenRanks(tokenIds, {
5251
+ contextId: Number(tid),
5252
+ minterAddress: budokanAddress
5253
+ })
5254
+ ]);
5255
+ let maxPaid = 0;
5256
+ for (const p of prizes) {
5257
+ if ((p.payoutPosition ?? 0) > 0) {
5258
+ maxPaid = Math.max(maxPaid, p.payoutPosition);
5259
+ }
5260
+ if ((p.distributionCount ?? 0) > 0) {
5261
+ maxPaid = Math.max(maxPaid, p.distributionCount);
5262
+ }
5263
+ }
5264
+ const efDistCount = Number(t.entryFee?.distributionCount ?? 0);
5265
+ if (efDistCount > 0) maxPaid = Math.max(maxPaid, efDistCount);
5266
+ if (maxPaid === 0) return null;
5267
+ const placements = ranksResult.data.filter((r) => r.rank > 0 && r.rank <= maxPaid).map((r) => ({
5268
+ tournamentId: tid,
5269
+ tokenId: r.tokenId,
5270
+ position: r.rank,
5271
+ score: String(r.score ?? "0")
5272
+ }));
5273
+ if (placements.length === 0) return null;
5274
+ return {
5275
+ tournament: t,
5276
+ prizes,
5277
+ rewardClaims: allClaims,
5278
+ placements
5279
+ };
5280
+ })
5281
+ );
5282
+ const valid = settled.map((s, i) => {
5283
+ if (s.status === "fulfilled") return s.value;
5284
+ console.warn(
5285
+ `usePlayerRewards: tournament ${finalized[i].id} fetch failed; skipping`,
5286
+ s.reason
5287
+ );
5288
+ return null;
5289
+ }).filter((r) => r !== null);
5290
+ const allPlacements = valid.flatMap(
5291
+ (r) => r.placements
5292
+ );
5293
+ const wins = allPlacements.length;
5294
+ const bestPlacement = wins > 0 ? Math.min(...allPlacements.map((p) => p.position)) : null;
5295
+ const tournamentsList = valid.map((r) => r.tournament);
5296
+ const prizesList = valid.flatMap((r) => r.prizes);
5297
+ const rewardClaimsList = valid.flatMap(
5298
+ (r) => r.rewardClaims
5299
+ );
5300
+ setRewards({
5301
+ wins,
5302
+ bestPlacement,
5303
+ placements: allPlacements,
5304
+ tournaments: tournamentsList,
5305
+ prizes: prizesList,
5306
+ rewardClaims: rewardClaimsList
5307
+ });
5308
+ } catch (e) {
5309
+ setError(e);
5310
+ } finally {
5311
+ setAggregating(false);
5312
+ }
5313
+ }, [
5314
+ tokensEnabled,
5315
+ tokensByTournament,
5316
+ tournamentsPage,
5317
+ tournamentIdsKey,
5318
+ budokan,
5319
+ denshokan,
5320
+ budokanAddress
5321
+ ]);
5322
+ const lastRunRef = useRef("");
5323
+ useEffect(() => {
5324
+ if (!tokensEnabled) {
5325
+ setRewards(null);
5326
+ return;
5327
+ }
5328
+ if (!tokensByTournament || !tournamentsPage?.data) return;
5329
+ const fingerprint = `${address}|${tournamentIdsKey}|${tournamentsPage.data.length}`;
5330
+ if (fingerprint === lastRunRef.current) return;
5331
+ lastRunRef.current = fingerprint;
5332
+ fetch2();
5333
+ }, [
5334
+ address,
5335
+ tokensEnabled,
5336
+ tokensByTournament,
5337
+ tournamentsPage,
5338
+ tournamentIdsKey,
5339
+ fetch2
5340
+ ]);
5341
+ const loading = tokensEnabled && (tokensLoading || tournamentsLoading) || aggregating;
5342
+ return { rewards, loading, error, refetch: fetch2 };
5343
+ }
5344
+ function emptyRewards() {
5345
+ return {
5346
+ wins: 0,
5347
+ bestPlacement: null,
5348
+ placements: [],
5349
+ tournaments: [],
5350
+ prizes: [],
5351
+ rewardClaims: []
5352
+ };
5353
+ }
5354
+ async function fetchAllRewardClaims(client, tournamentId) {
5355
+ const first = await client.getTournamentRewardClaims(tournamentId, {
5356
+ limit: 100
5357
+ });
5358
+ const accumulated = [...first.data];
5359
+ const total = first.total ?? accumulated.length;
5360
+ if (accumulated.length >= total) return accumulated;
5361
+ const pageSize = Math.max(first.data.length, 1);
5362
+ let offset = accumulated.length;
5363
+ while (offset < total) {
5364
+ const next = await client.getTournamentRewardClaims(tournamentId, {
5365
+ limit: pageSize,
5366
+ offset
5367
+ });
5368
+ if (next.data.length === 0) break;
5369
+ accumulated.push(...next.data);
5370
+ offset += next.data.length;
5371
+ }
5372
+ return accumulated;
5373
+ }
5177
5374
  function useSubscription(channels, tournamentIds) {
5178
5375
  const client = useBudokanClient();
5179
5376
  const [lastMessage, setLastMessage] = useState(null);
@@ -5219,6 +5416,6 @@ function useConnectionStatus() {
5219
5416
  return { isConnected, datasourceMode };
5220
5417
  }
5221
5418
 
5222
- export { BudokanProvider, useActivityStats, useBudokanClient, useConnectionStatus, useLeaderboard, usePrizeAggregation, usePrizeStats, usePrizes, useQualifications, useRegistrations, useRegistrationsByOwner, useRewardClaims, useRewardClaimsSummary, useSubscription, useTournament, useTournamentCount, useTournaments, useTournamentsByOwner, useTournamentsByOwnerCount };
5419
+ export { BudokanProvider, useActivityStats, useBudokanClient, useConnectionStatus, useLeaderboard, usePlayerRewards, usePrizeAggregation, usePrizeStats, usePrizes, useQualifications, useRegistrations, useRegistrationsByOwner, useRewardClaims, useRewardClaimsSummary, useSubscription, useTournament, useTournamentCount, useTournaments, useTournamentsByOwner, useTournamentsByOwnerCount };
5223
5420
  //# sourceMappingURL=react.js.map
5224
5421
  //# sourceMappingURL=react.js.map