@provable-games/metagame-sdk 0.1.0 → 0.1.2

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 ADDED
@@ -0,0 +1,1071 @@
1
+ 'use strict';
2
+
3
+ var starknet = require('starknet');
4
+
5
+ // src/types/extensions.ts
6
+ var QualifyingMode = /* @__PURE__ */ ((QualifyingMode2) => {
7
+ QualifyingMode2[QualifyingMode2["AtLeastOne"] = 0] = "AtLeastOne";
8
+ QualifyingMode2[QualifyingMode2["CumulativePerTournament"] = 1] = "CumulativePerTournament";
9
+ QualifyingMode2[QualifyingMode2["All"] = 2] = "All";
10
+ QualifyingMode2[QualifyingMode2["CumulativePerEntry"] = 3] = "CumulativePerEntry";
11
+ QualifyingMode2[QualifyingMode2["AllParticipateAnyWin"] = 4] = "AllParticipateAnyWin";
12
+ QualifyingMode2[QualifyingMode2["AllWithCumulative"] = 5] = "AllWithCumulative";
13
+ return QualifyingMode2;
14
+ })(QualifyingMode || {});
15
+
16
+ // src/utils/address.ts
17
+ function indexAddress(address) {
18
+ return address.replace(/^0x0+/, "0x");
19
+ }
20
+ function padAddress(address) {
21
+ if (address && address !== "") {
22
+ const length = address.length;
23
+ const neededLength = 66 - length;
24
+ let zeros = "";
25
+ for (let i = 0; i < neededLength; i++) {
26
+ zeros += "0";
27
+ }
28
+ return address.substring(0, 2) + zeros + address.substring(2);
29
+ }
30
+ return "";
31
+ }
32
+ function displayAddress(address) {
33
+ if (address === void 0) return "unknown";
34
+ return address.substring(0, 6) + "..." + address.substring(address.length - 4);
35
+ }
36
+ function bigintToHex(v) {
37
+ if (!v) return "0x0";
38
+ return `0x${BigInt(v).toString(16)}`;
39
+ }
40
+
41
+ // src/utils/formatting.ts
42
+ function formatNumber(num) {
43
+ if (Math.abs(num) >= 1e6) {
44
+ return parseFloat((num / 1e6).toFixed(2)) + "m";
45
+ } else if (Math.abs(num) >= 1e3) {
46
+ return parseFloat((num / 1e3).toFixed(2)) + "k";
47
+ } else if (Math.abs(num) >= 1) {
48
+ return num.toFixed(0);
49
+ } else if (Math.abs(num) >= 0.1) {
50
+ return num.toFixed(1);
51
+ } else if (Math.abs(num) >= 0.01) {
52
+ return num.toFixed(2);
53
+ } else if (Math.abs(num) >= 1e-3) {
54
+ return num.toFixed(3);
55
+ } else if (num === 0) {
56
+ return "0";
57
+ } else {
58
+ return num.toFixed(4);
59
+ }
60
+ }
61
+ function formatPrizeAmount(num) {
62
+ if (Math.abs(num) >= 1e6) {
63
+ return parseFloat((num / 1e6).toFixed(2)) + "m";
64
+ } else if (Math.abs(num) >= 1e3) {
65
+ return parseFloat((num / 1e3).toFixed(2)) + "k";
66
+ } else if (Math.abs(num) >= 100) {
67
+ return num.toFixed(1);
68
+ } else if (Math.abs(num) >= 10) {
69
+ return num.toFixed(2);
70
+ } else if (Math.abs(num) >= 1) {
71
+ return num.toFixed(2);
72
+ } else if (Math.abs(num) >= 0.1) {
73
+ return num.toFixed(2);
74
+ } else if (Math.abs(num) >= 0.01) {
75
+ return num.toFixed(3);
76
+ } else if (Math.abs(num) >= 1e-3) {
77
+ return num.toFixed(4);
78
+ } else if (num === 0) {
79
+ return "0";
80
+ } else {
81
+ return num.toFixed(5);
82
+ }
83
+ }
84
+ function formatUsdValue(value) {
85
+ if (value === 0) return "0.00";
86
+ if (value < 0.01 && value > 0) return "<0.01";
87
+ return value.toFixed(2);
88
+ }
89
+ function formatScore(num) {
90
+ if (Math.abs(num) >= 1e6) {
91
+ return parseFloat((num / 1e6).toFixed(2)) + "m";
92
+ } else if (Math.abs(num) >= 1e3) {
93
+ return parseFloat((num / 1e3).toFixed(2)) + "k";
94
+ } else if (Math.abs(num) >= 10) {
95
+ return num.toFixed(0);
96
+ } else if (Math.abs(num) > 0) {
97
+ return num.toFixed(0);
98
+ } else {
99
+ return "0";
100
+ }
101
+ }
102
+ function formatTime(seconds) {
103
+ const minutes = Math.floor(seconds / 60);
104
+ const hours = Math.floor(minutes / 60);
105
+ const days = Math.floor(hours / 24);
106
+ if (days > 0) {
107
+ return `${days} Day${days > 1 ? "s" : ""}`;
108
+ } else if (hours > 0) {
109
+ return `${hours} Hour${hours > 1 ? "s" : ""}`;
110
+ } else if (minutes > 0) {
111
+ return `${minutes} Min${minutes > 1 ? "s" : ""}`;
112
+ } else {
113
+ return `${seconds.toFixed(0)} Sec${seconds > 1 ? "s" : ""}`;
114
+ }
115
+ }
116
+ function getOrdinalSuffix(position) {
117
+ const formatPosition = isNaN(position) ? 0 : position;
118
+ if (formatPosition % 10 === 1 && formatPosition !== 11) return "st";
119
+ if (formatPosition % 10 === 2 && formatPosition !== 12) return "nd";
120
+ if (position % 10 === 3 && position !== 13) return "rd";
121
+ return "th";
122
+ }
123
+ function calculatePayouts(totalPlaces, weightingFactor) {
124
+ const weights = [];
125
+ for (let i = 1; i <= totalPlaces; i++) {
126
+ weights.push(1 / Math.pow(i, weightingFactor));
127
+ }
128
+ const totalWeight = weights.reduce((sum, weight) => sum + weight, 0);
129
+ const payouts = weights.map(
130
+ (weight) => Math.floor(weight / totalWeight * 100)
131
+ );
132
+ const totalPayout = payouts.reduce((sum, payout) => sum + payout, 0);
133
+ let remaining = 100 - totalPayout;
134
+ let index = 0;
135
+ while (remaining > 0) {
136
+ payouts[index] += 1;
137
+ remaining -= 1;
138
+ index = (index + 1) % totalPlaces;
139
+ }
140
+ return payouts;
141
+ }
142
+ function calculateDistribution(positions, weight, creatorFee, gameFee, refundShare, distributionType = "exponential") {
143
+ if (positions <= 0) {
144
+ return [];
145
+ }
146
+ const safeCreatorFee = creatorFee ?? 0;
147
+ const safeGameFee = gameFee ?? 0;
148
+ const safeRefundShare = refundShare ?? 0;
149
+ const prizePoolPercentage = 100 - safeCreatorFee - safeGameFee - safeRefundShare;
150
+ if (prizePoolPercentage <= 0) {
151
+ return Array(positions).fill(0);
152
+ }
153
+ let rawDistributions = [];
154
+ if (distributionType === "uniform") {
155
+ rawDistributions = Array(positions).fill(1);
156
+ } else if (distributionType === "linear") {
157
+ for (let i = 0; i < positions; i++) {
158
+ const positionValue = positions - i;
159
+ const share = 1 + (positionValue - 1) * (weight / 10);
160
+ rawDistributions.push(share);
161
+ }
162
+ } else {
163
+ for (let i = 0; i < positions; i++) {
164
+ const share = Math.pow(1 - i / positions, weight);
165
+ rawDistributions.push(share);
166
+ }
167
+ }
168
+ const total = rawDistributions.reduce((a, b) => a + b, 0);
169
+ if (total === 0) {
170
+ return Array(positions).fill(0);
171
+ }
172
+ const basisPointShares = rawDistributions.map((d) => {
173
+ const ratio = d / total;
174
+ const basisPoints = ratio * 1e4;
175
+ return Math.floor(basisPoints);
176
+ });
177
+ const totalBasisPoints = basisPointShares.reduce((a, b) => a + b, 0);
178
+ const remainingBasisPoints = 1e4 - totalBasisPoints;
179
+ if (remainingBasisPoints !== 0 && positions > 0) {
180
+ basisPointShares[0] = basisPointShares[0] + remainingBasisPoints;
181
+ }
182
+ return basisPointShares.map((bp) => bp / 100);
183
+ }
184
+
185
+ // src/utils/prizes.ts
186
+ function groupPrizesByToken(prizes) {
187
+ return prizes.reduce(
188
+ (acc, prize) => {
189
+ const key = prize.tokenAddress;
190
+ if (!acc[key]) {
191
+ acc[key] = {
192
+ type: prize.tokenType,
193
+ address: prize.tokenAddress,
194
+ totalAmount: "0"
195
+ };
196
+ }
197
+ acc[key].totalAmount = String(
198
+ BigInt(acc[key].totalAmount) + BigInt(prize.amount)
199
+ );
200
+ return acc;
201
+ },
202
+ {}
203
+ );
204
+ }
205
+ function calculatePrizeValue(amount, decimals, price) {
206
+ return Number(amount) / 10 ** decimals * price;
207
+ }
208
+
209
+ // src/utils/status.ts
210
+ function computeStatus(timestamps, now) {
211
+ const currentTimestamp = now ?? Math.floor(Date.now() / 1e3);
212
+ if (timestamps.unlocked === false) {
213
+ return {
214
+ label: "Locked",
215
+ variant: "locked",
216
+ isActive: false,
217
+ countdown: null
218
+ };
219
+ }
220
+ if (timestamps.completed) {
221
+ return {
222
+ label: "Completed",
223
+ variant: "completed",
224
+ isActive: false,
225
+ countdown: null
226
+ };
227
+ }
228
+ const { registrationStart, registrationEnd, start, end, submissionEnd } = timestamps;
229
+ if (registrationStart && registrationEnd) {
230
+ if (currentTimestamp < registrationStart) {
231
+ return {
232
+ label: "Upcoming",
233
+ variant: "upcoming",
234
+ isActive: false,
235
+ countdown: {
236
+ targetTimestamp: registrationStart,
237
+ label: "Registration"
238
+ }
239
+ };
240
+ }
241
+ if (currentTimestamp >= registrationStart && currentTimestamp < registrationEnd) {
242
+ return {
243
+ label: "Registration",
244
+ variant: "registration",
245
+ isActive: true,
246
+ countdown: { targetTimestamp: registrationEnd, label: "Closes" }
247
+ };
248
+ }
249
+ }
250
+ if (currentTimestamp < start) {
251
+ return {
252
+ label: "Upcoming",
253
+ variant: "upcoming",
254
+ isActive: false,
255
+ countdown: { targetTimestamp: start, label: "Starts" }
256
+ };
257
+ }
258
+ if (currentTimestamp >= start && currentTimestamp < end) {
259
+ return {
260
+ label: "Live",
261
+ variant: "active",
262
+ isActive: true,
263
+ countdown: { targetTimestamp: end, label: "Ends" }
264
+ };
265
+ }
266
+ if (submissionEnd && currentTimestamp >= end && currentTimestamp < submissionEnd) {
267
+ return {
268
+ label: "Submission",
269
+ variant: "submission",
270
+ isActive: true,
271
+ countdown: { targetTimestamp: submissionEnd, label: "Submit" }
272
+ };
273
+ }
274
+ return {
275
+ label: "Ended",
276
+ variant: "ended",
277
+ isActive: false,
278
+ countdown: null
279
+ };
280
+ }
281
+
282
+ // src/utils/time.ts
283
+ function isBefore(date1, date2) {
284
+ return date1.getTime() < date2.getTime();
285
+ }
286
+
287
+ // src/utils/entryFee.ts
288
+ function calculateEntryFeeBreakdown(feePerEntry, entryCount, shares) {
289
+ const totalCollected = feePerEntry * BigInt(entryCount);
290
+ if (totalCollected === 0n) {
291
+ return {
292
+ totalCollected: 0n,
293
+ creatorAmount: 0n,
294
+ gameCreatorAmount: 0n,
295
+ refundAmount: 0n,
296
+ prizePoolAmount: 0n
297
+ };
298
+ }
299
+ const creatorAmount = shares.creatorShare > 0 ? totalCollected * BigInt(shares.creatorShare) / 10000n : 0n;
300
+ const gameCreatorAmount = shares.gameCreatorShare > 0 ? totalCollected * BigInt(shares.gameCreatorShare) / 10000n : 0n;
301
+ const refundAmount = shares.refundShare > 0 ? totalCollected * BigInt(shares.refundShare) / 10000n : 0n;
302
+ const prizePoolBps = 1e4 - shares.creatorShare - shares.gameCreatorShare - shares.refundShare;
303
+ const prizePoolAmount = prizePoolBps > 0 ? totalCollected * BigInt(prizePoolBps) / 10000n : 0n;
304
+ return {
305
+ totalCollected,
306
+ creatorAmount,
307
+ gameCreatorAmount,
308
+ refundAmount,
309
+ prizePoolAmount
310
+ };
311
+ }
312
+ function distributePool(prizePoolAmount, percentages) {
313
+ return percentages.map((pct, index) => ({
314
+ position: index + 1,
315
+ amount: prizePoolAmount * BigInt(Math.floor(pct * 100)) / 10000n
316
+ })).filter((entry) => entry.amount > 0n);
317
+ }
318
+
319
+ // src/utils/prizeAggregation.ts
320
+ function aggregatePrizesByPosition(prizes) {
321
+ const positionMap = /* @__PURE__ */ new Map();
322
+ for (const prize of prizes) {
323
+ if (!positionMap.has(prize.position)) {
324
+ positionMap.set(prize.position, {
325
+ erc20Map: /* @__PURE__ */ new Map(),
326
+ erc721Map: /* @__PURE__ */ new Map()
327
+ });
328
+ }
329
+ const group = positionMap.get(prize.position);
330
+ if (prize.tokenType === "erc20") {
331
+ const current = group.erc20Map.get(prize.tokenAddress) ?? 0n;
332
+ group.erc20Map.set(prize.tokenAddress, current + BigInt(prize.amount));
333
+ } else {
334
+ const ids = group.erc721Map.get(prize.tokenAddress) ?? [];
335
+ ids.push(prize.id);
336
+ group.erc721Map.set(prize.tokenAddress, ids);
337
+ }
338
+ }
339
+ return Array.from(positionMap.entries()).sort(([a], [b]) => a - b).map(([position, { erc20Map, erc721Map }]) => ({
340
+ position,
341
+ erc20: Array.from(erc20Map.entries()).map(([tokenAddress, totalAmount]) => ({
342
+ tokenAddress,
343
+ totalAmount
344
+ })),
345
+ erc721: Array.from(erc721Map.entries()).map(([tokenAddress, tokenIds]) => ({
346
+ tokenAddress,
347
+ tokenIds
348
+ }))
349
+ }));
350
+ }
351
+ function aggregatePrizesBySponsor(prizes) {
352
+ const sponsorMap = /* @__PURE__ */ new Map();
353
+ for (const prize of prizes) {
354
+ if (!prize.sponsorAddress || prize.sponsorAddress === "0x0") continue;
355
+ if (!sponsorMap.has(prize.sponsorAddress)) {
356
+ sponsorMap.set(prize.sponsorAddress, {
357
+ erc20Map: /* @__PURE__ */ new Map(),
358
+ nftMap: /* @__PURE__ */ new Map(),
359
+ nftCount: 0,
360
+ totalCount: 0
361
+ });
362
+ }
363
+ const sponsor = sponsorMap.get(prize.sponsorAddress);
364
+ sponsor.totalCount++;
365
+ if (prize.tokenType === "erc20") {
366
+ const current = sponsor.erc20Map.get(prize.tokenAddress) ?? {
367
+ amount: 0n,
368
+ count: 0
369
+ };
370
+ sponsor.erc20Map.set(prize.tokenAddress, {
371
+ amount: current.amount + BigInt(prize.amount),
372
+ count: current.count + 1
373
+ });
374
+ } else {
375
+ sponsor.nftCount++;
376
+ const ids = sponsor.nftMap.get(prize.tokenAddress) ?? [];
377
+ ids.push(prize.amount);
378
+ sponsor.nftMap.set(prize.tokenAddress, ids);
379
+ }
380
+ }
381
+ return Array.from(sponsorMap.entries()).map(([sponsorAddress, { erc20Map, nftMap, nftCount, totalCount }]) => ({
382
+ sponsorAddress,
383
+ tokens: Array.from(erc20Map.entries()).map(
384
+ ([tokenAddress, { amount, count }]) => ({
385
+ tokenAddress,
386
+ totalAmount: amount,
387
+ prizeCount: count
388
+ })
389
+ ),
390
+ nftCollections: Array.from(nftMap.entries()).map(
391
+ ([tokenAddress, tokenIds]) => ({
392
+ tokenAddress,
393
+ tokenIds
394
+ })
395
+ ),
396
+ nftCount,
397
+ totalPrizeCount: totalCount
398
+ })).sort((a, b) => b.totalPrizeCount - a.totalPrizeCount);
399
+ }
400
+ function filterClaimablePrizes(prizes, claimedIds) {
401
+ return prizes.filter((prize) => !claimedIds.has(prize.id));
402
+ }
403
+ function filterZeroPrizes(prizes) {
404
+ return prizes.filter((prize) => {
405
+ if (prize.tokenType === "erc20") {
406
+ return BigInt(prize.amount) > 0n;
407
+ }
408
+ return true;
409
+ });
410
+ }
411
+
412
+ // src/utils/nftInput.ts
413
+ function parseNFTBulkInput(input) {
414
+ const trimmed = input.trim();
415
+ if (!trimmed) return { entries: [], errors: [] };
416
+ if (trimmed.startsWith("[")) {
417
+ return parseJsonFormat(trimmed);
418
+ }
419
+ return parseKeyValueFormat(trimmed);
420
+ }
421
+ function parseJsonFormat(input) {
422
+ const errors = [];
423
+ try {
424
+ const parsed = JSON.parse(input);
425
+ if (!Array.isArray(parsed)) {
426
+ return { entries: [], errors: ["Expected a JSON array"] };
427
+ }
428
+ const entries = [];
429
+ for (let i = 0; i < parsed.length; i++) {
430
+ const item = parsed[i];
431
+ const tokenId = item.tokenId ?? item.token_id ?? item.id;
432
+ const position = item.position ?? item.pos;
433
+ if (tokenId == null || position == null) {
434
+ errors.push(
435
+ `Item ${i + 1}: missing tokenId or position`
436
+ );
437
+ continue;
438
+ }
439
+ const posNum = Number(position);
440
+ if (!Number.isFinite(posNum) || posNum < 1) {
441
+ errors.push(`Item ${i + 1}: position must be >= 1`);
442
+ continue;
443
+ }
444
+ entries.push({ tokenId: String(tokenId), position: posNum });
445
+ }
446
+ return { entries, errors };
447
+ } catch {
448
+ return { entries: [], errors: ["Invalid JSON format"] };
449
+ }
450
+ }
451
+ function parseKeyValueFormat(input) {
452
+ const errors = [];
453
+ const entries = [];
454
+ const lines = input.split(/[\n,]/).map((s) => s.trim()).filter((s) => s.length > 0);
455
+ for (let i = 0; i < lines.length; i++) {
456
+ const line = lines[i];
457
+ const parts = line.split(":").map((s) => s.trim());
458
+ if (parts.length !== 2) {
459
+ errors.push(`Line ${i + 1}: expected "tokenId:position" format`);
460
+ continue;
461
+ }
462
+ const [tokenIdStr, posStr] = parts;
463
+ const position = Number(posStr);
464
+ if (!tokenIdStr) {
465
+ errors.push(`Line ${i + 1}: missing tokenId`);
466
+ continue;
467
+ }
468
+ if (!Number.isFinite(position) || position < 1) {
469
+ errors.push(`Line ${i + 1}: position must be >= 1`);
470
+ continue;
471
+ }
472
+ entries.push({ tokenId: tokenIdStr, position });
473
+ }
474
+ return { entries, errors };
475
+ }
476
+
477
+ // src/utils/extensions.ts
478
+ var EXTENSION_ADDRESSES = {
479
+ SN_SEPOLIA: {
480
+ tournamentValidator: "0x04c9ba290bbebd47f87141801623748884af610230cdaf69232007b4135be3f1",
481
+ erc20BalanceValidator: "0x05437210fd8231893da2317971983ae1f810d8d500344ad09e5b258b5820a908",
482
+ opusTrovesValidator: "0x0317f96eeff41d1badffd9bda126d36c1806523d8c91a5b440f9bdf2aa2b9fe7",
483
+ snapshotValidator: "0x02628045f0688663ecacbaefdbbc46c2e315b2da39271419e8a3443fe4120f61",
484
+ zkPassportValidator: "0x025b65137c5297e03491830635f4c5d76eb17e5b2db3bf49dbee34bba94ad3e6",
485
+ governanceValidator: "0x0585e6b1aa8daac7711004a92a8d0d3b5a81eaac8c9a07db24fc34b1a4f2322c"
486
+ },
487
+ SN_MAIN: {
488
+ tournamentValidator: "0x0771b57c0709fc4407ff8b63d573f302b96fb03638364032fad734e3c310b9e0",
489
+ erc20BalanceValidator: "0x051fc2681f65ee18e99dab3cc2ca2eca1b4532c735e752f575ace91ed30f17b7",
490
+ opusTrovesValidator: "0x0604bc0d54727f439786c65d7dc6f7d46de1aba7e129ef9caf65fef111a1644e",
491
+ snapshotValidator: "0x03e6820e9e1cfb5c22465a86f469c651355f05397e29fc94de8e832d5f3d8ede",
492
+ zkPassportValidator: "0x01a25f04c8a39da7bb37adcc53d595fde9e60702611d15b1c498df0657001479",
493
+ governanceValidator: ""
494
+ // placeholder until deployed
495
+ }
496
+ };
497
+ var OPUS_SUPPORTED_ASSETS = {
498
+ SN_SEPOLIA: [],
499
+ SN_MAIN: [
500
+ "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
501
+ // ETH
502
+ "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d",
503
+ // STRK
504
+ "0x0124aeb495b947201f5fac96fd1138e326ad86195b98df6dec9009158a533b49",
505
+ // LORDS
506
+ "0x075afe6402ad5a5c20dd25e10ec3b3986acaa647b77e4ae24b0cbc9a54a27a87",
507
+ // EKUBO
508
+ "0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac"
509
+ // WBTC
510
+ ]
511
+ };
512
+ function getExtensionAddresses(chainId) {
513
+ return EXTENSION_ADDRESSES[chainId] ?? {};
514
+ }
515
+ function getOpusSupportedAssets(chainId) {
516
+ return OPUS_SUPPORTED_ASSETS[chainId] ?? [];
517
+ }
518
+ function getWhitelistedExtensionAddresses(chainId) {
519
+ const addresses = EXTENSION_ADDRESSES[chainId] ?? {};
520
+ const set = /* @__PURE__ */ new Set();
521
+ for (const addr of Object.values(addresses)) {
522
+ if (addr) {
523
+ set.add(normalizeAddr(addr));
524
+ }
525
+ }
526
+ return set;
527
+ }
528
+ function isWhitelistedExtension(extensionAddress, chainId) {
529
+ const whitelist = getWhitelistedExtensionAddresses(chainId);
530
+ return whitelist.has(normalizeAddr(extensionAddress));
531
+ }
532
+ function normalizeAddr(address) {
533
+ if (!address) return "";
534
+ const hex = address.toLowerCase().replace(/^0x0*/, "");
535
+ return hex;
536
+ }
537
+ function identifyExtensionType(extensionAddress, chainId) {
538
+ const addresses = getExtensionAddresses(chainId);
539
+ const normalized = normalizeAddr(extensionAddress);
540
+ if (addresses.tournamentValidator && normalizeAddr(addresses.tournamentValidator) === normalized) {
541
+ return "tournament";
542
+ }
543
+ if (addresses.erc20BalanceValidator && normalizeAddr(addresses.erc20BalanceValidator) === normalized) {
544
+ return "erc20Balance";
545
+ }
546
+ if (addresses.opusTrovesValidator && normalizeAddr(addresses.opusTrovesValidator) === normalized) {
547
+ return "opusTroves";
548
+ }
549
+ if (addresses.snapshotValidator && normalizeAddr(addresses.snapshotValidator) === normalized) {
550
+ return "snapshot";
551
+ }
552
+ if (addresses.zkPassportValidator && normalizeAddr(addresses.zkPassportValidator) === normalized) {
553
+ return "zkPassport";
554
+ }
555
+ if (addresses.governanceValidator && normalizeAddr(addresses.governanceValidator) === normalized) {
556
+ return "governance";
557
+ }
558
+ return "unknown";
559
+ }
560
+ function parseTournamentValidatorConfig(config) {
561
+ if (!config || config.length < 3) return null;
562
+ return {
563
+ requirementType: String(config[0]) === "1" ? "won" : "participated",
564
+ qualifyingMode: Number(config[1]),
565
+ topPositions: Number(config[2]),
566
+ tournamentIds: config.slice(3).map((id) => String(BigInt(id)))
567
+ };
568
+ }
569
+ function parseERC20BalanceValidatorConfig(config) {
570
+ if (!config || config.length < 8) return null;
571
+ const tokenAddress = String(config[0]);
572
+ const minThreshold = BigInt(config[2]) << 128n | BigInt(config[1]);
573
+ const maxThreshold = BigInt(config[4]) << 128n | BigInt(config[3]);
574
+ const valuePerEntry = BigInt(config[6]) << 128n | BigInt(config[5]);
575
+ const maxEntries = Number(config[7]);
576
+ return {
577
+ tokenAddress,
578
+ minThreshold,
579
+ maxThreshold,
580
+ valuePerEntry,
581
+ maxEntries
582
+ };
583
+ }
584
+ function parseOpusTrovesValidatorConfig(config) {
585
+ if (!config || config.length < 4) return null;
586
+ const assetCount = Number(config[0]);
587
+ const assetAddresses = config.slice(1, assetCount + 1).map(String);
588
+ const threshold = BigInt(config[assetCount + 1] ?? "0");
589
+ const valuePerEntry = BigInt(config[assetCount + 2] ?? "0");
590
+ const maxEntries = Number(config[assetCount + 3] ?? "0");
591
+ return {
592
+ assetCount,
593
+ assetAddresses,
594
+ threshold,
595
+ valuePerEntry,
596
+ maxEntries
597
+ };
598
+ }
599
+ function parseSnapshotValidatorConfig(config) {
600
+ if (!config || config.length === 0) return null;
601
+ return {
602
+ snapshotId: String(Number(BigInt(config[0] ?? "0")))
603
+ };
604
+ }
605
+ function parseZkPassportValidatorConfig(config) {
606
+ if (!config || config.length < 6) return null;
607
+ return {
608
+ verifierAddress: String(config[0]),
609
+ serviceScope: BigInt(config[1]),
610
+ subscope: BigInt(config[2]),
611
+ paramCommitment: BigInt(config[3]),
612
+ maxProofAge: Number(config[4]),
613
+ nullifierType: Number(config[5])
614
+ };
615
+ }
616
+ function parseGovernanceValidatorConfig(config) {
617
+ if (!config || config.length < 11) return null;
618
+ return {
619
+ governorAddress: String(config[0]),
620
+ governanceTokenAddress: String(config[1]),
621
+ balanceThreshold: BigInt(config[3]) << 128n | BigInt(config[2]),
622
+ proposalId: BigInt(config[5]) << 128n | BigInt(config[4]),
623
+ checkVoted: String(config[6]) === "1" || BigInt(config[6]) === 1n,
624
+ votesThreshold: BigInt(config[8]) << 128n | BigInt(config[7]),
625
+ votesPerEntry: BigInt(config[10]) << 128n | BigInt(config[9])
626
+ };
627
+ }
628
+ function parseExtensionConfig(extensionAddress, config, chainId) {
629
+ const extensionType = identifyExtensionType(extensionAddress, chainId);
630
+ switch (extensionType) {
631
+ case "tournament": {
632
+ const parsed = parseTournamentValidatorConfig(config);
633
+ return parsed ? { type: "tournament", config: parsed } : null;
634
+ }
635
+ case "erc20Balance": {
636
+ const parsed = parseERC20BalanceValidatorConfig(config);
637
+ return parsed ? { type: "erc20Balance", config: parsed } : null;
638
+ }
639
+ case "opusTroves": {
640
+ const parsed = parseOpusTrovesValidatorConfig(config);
641
+ return parsed ? { type: "opusTroves", config: parsed } : null;
642
+ }
643
+ case "snapshot": {
644
+ const parsed = parseSnapshotValidatorConfig(config);
645
+ return parsed ? { type: "snapshot", config: parsed } : null;
646
+ }
647
+ case "zkPassport": {
648
+ const parsed = parseZkPassportValidatorConfig(config);
649
+ return parsed ? { type: "zkPassport", config: parsed } : null;
650
+ }
651
+ case "governance": {
652
+ const parsed = parseGovernanceValidatorConfig(config);
653
+ return parsed ? { type: "governance", config: parsed } : null;
654
+ }
655
+ default:
656
+ return { type: "unknown", config: null };
657
+ }
658
+ }
659
+ function getQualifyingModeInfo(mode) {
660
+ switch (mode) {
661
+ case 0:
662
+ return {
663
+ label: "At Least One",
664
+ description: "Qualify in at least one tournament"
665
+ };
666
+ case 1:
667
+ return {
668
+ label: "Cumulative per Tournament",
669
+ description: "Track entry limits separately for each tournament"
670
+ };
671
+ case 2:
672
+ return {
673
+ label: "All",
674
+ description: "Must qualify in all tournaments"
675
+ };
676
+ case 3:
677
+ return {
678
+ label: "Cumulative per Entry",
679
+ description: "Track entries per qualifying token ID"
680
+ };
681
+ case 4:
682
+ return {
683
+ label: "All Participate, Any Win",
684
+ description: "Must participate in all tournaments, but only need to win in any one"
685
+ };
686
+ case 5:
687
+ return {
688
+ label: "All With Cumulative",
689
+ description: "Must participate in all tournaments, entries multiply by tournament count"
690
+ };
691
+ default:
692
+ return { label: "Unknown", description: "" };
693
+ }
694
+ }
695
+ function formatTokenAmount(value, decimals) {
696
+ if (value === 0n) return "0";
697
+ const divisor = 10n ** BigInt(decimals);
698
+ const integerPart = value / divisor;
699
+ const remainder = value % divisor;
700
+ if (remainder === 0n) return integerPart.toString();
701
+ const decimalStr = remainder.toString().padStart(decimals, "0");
702
+ const trimmed = decimalStr.replace(/0+$/, "");
703
+ return trimmed ? `${integerPart}.${trimmed}` : integerPart.toString();
704
+ }
705
+ function formatCashToUSD(value) {
706
+ return formatTokenAmount(value, 18);
707
+ }
708
+
709
+ // src/utils/qualification.ts
710
+ function evaluateTokenQualification(input) {
711
+ if (input.ownedTokenIds.length === 0) {
712
+ return {
713
+ meetsRequirements: false,
714
+ bestProof: null,
715
+ qualifications: [],
716
+ totalEntriesLeft: 0
717
+ };
718
+ }
719
+ const qualifications = [];
720
+ for (const tokenId of input.ownedTokenIds) {
721
+ const usedCount = input.usedEntryCounts[tokenId] ?? 0;
722
+ const remaining = input.entryLimit > 0 ? input.entryLimit - usedCount : Infinity;
723
+ if (remaining > 0) {
724
+ qualifications.push({
725
+ id: `token-${tokenId}`,
726
+ entriesLeft: remaining,
727
+ proof: { type: "token", tokenId },
728
+ label: `Token #${tokenId}`,
729
+ metadata: { tokenId }
730
+ });
731
+ }
732
+ }
733
+ const totalEntriesLeft = qualifications.reduce(
734
+ (sum, q) => sum + (q.entriesLeft === Infinity ? 1 : q.entriesLeft),
735
+ 0
736
+ );
737
+ const best = qualifications.length > 0 ? qualifications.reduce(
738
+ (a, b) => b.entriesLeft > a.entriesLeft ? b : a
739
+ ) : null;
740
+ return {
741
+ meetsRequirements: qualifications.length > 0,
742
+ bestProof: best?.proof ?? null,
743
+ qualifications,
744
+ totalEntriesLeft
745
+ };
746
+ }
747
+ function evaluateExtensionQualification(input) {
748
+ const qualifications = input.checkedQualifications.filter((q) => q.entriesLeft > 0).map((q) => ({
749
+ id: q.id,
750
+ entriesLeft: q.entriesLeft,
751
+ proof: {
752
+ type: "extension",
753
+ extensionProof: q.proof
754
+ },
755
+ label: q.label,
756
+ metadata: q.metadata
757
+ }));
758
+ const totalEntriesLeft = qualifications.reduce(
759
+ (sum, q) => sum + q.entriesLeft,
760
+ 0
761
+ );
762
+ const best = qualifications.length > 0 ? qualifications.reduce(
763
+ (a, b) => b.entriesLeft > a.entriesLeft ? b : a
764
+ ) : null;
765
+ return {
766
+ meetsRequirements: qualifications.length > 0,
767
+ bestProof: best?.proof ?? null,
768
+ qualifications,
769
+ totalEntriesLeft
770
+ };
771
+ }
772
+ function evaluateQualification(variant, input) {
773
+ if (variant === "none" || input.type === "none") {
774
+ return {
775
+ meetsRequirements: true,
776
+ bestProof: null,
777
+ qualifications: [
778
+ {
779
+ id: "no-requirement",
780
+ entriesLeft: Infinity,
781
+ proof: { type: "none" }
782
+ }
783
+ ],
784
+ totalEntriesLeft: Infinity
785
+ };
786
+ }
787
+ switch (input.type) {
788
+ case "token":
789
+ return evaluateTokenQualification(input.data);
790
+ case "extension":
791
+ return evaluateExtensionQualification(input.data);
792
+ default:
793
+ return {
794
+ meetsRequirements: false,
795
+ bestProof: null,
796
+ qualifications: [],
797
+ totalEntriesLeft: 0
798
+ };
799
+ }
800
+ }
801
+ function buildQualificationProof(proof) {
802
+ if (!proof) {
803
+ return new starknet.CairoOption(starknet.CairoOptionVariant.None);
804
+ }
805
+ switch (proof.type) {
806
+ case "token":
807
+ return buildNFTProof(proof.tokenId ?? "0");
808
+ case "extension":
809
+ return buildExtensionProof(proof.extensionProof ?? []);
810
+ case "none":
811
+ return new starknet.CairoOption(starknet.CairoOptionVariant.None);
812
+ default:
813
+ return new starknet.CairoOption(starknet.CairoOptionVariant.None);
814
+ }
815
+ }
816
+ function buildNFTProof(tokenId) {
817
+ return new starknet.CairoOption(
818
+ starknet.CairoOptionVariant.Some,
819
+ new starknet.CairoCustomEnum({
820
+ NFT: {
821
+ token_id: {
822
+ low: tokenId,
823
+ high: "0"
824
+ }
825
+ },
826
+ Extension: void 0
827
+ })
828
+ );
829
+ }
830
+ function buildTournamentExtensionProof(tournamentId, tokenId, position) {
831
+ return buildExtensionProof([
832
+ tournamentId.toString(),
833
+ tokenId.toString(),
834
+ position.toString()
835
+ ]);
836
+ }
837
+ function buildExtensionProof(proofData) {
838
+ return new starknet.CairoOption(
839
+ starknet.CairoOptionVariant.Some,
840
+ new starknet.CairoCustomEnum({
841
+ NFT: void 0,
842
+ Extension: proofData
843
+ })
844
+ );
845
+ }
846
+
847
+ // src/utils/tournamentValidator.ts
848
+ function buildParticipationMap(registrations, requiredTournamentIds) {
849
+ const requiredSet = new Set(requiredTournamentIds);
850
+ const map = {};
851
+ for (const reg of registrations) {
852
+ if (!requiredSet.has(reg.tournamentId)) continue;
853
+ if (!map[reg.tournamentId]) map[reg.tournamentId] = [];
854
+ map[reg.tournamentId].push(reg.gameTokenId);
855
+ }
856
+ return map;
857
+ }
858
+ function buildWinMap(leaderboards, ownedGameTokenIds, topPositions = 0) {
859
+ const ownedSet = new Set(ownedGameTokenIds);
860
+ const map = {};
861
+ for (const lb of leaderboards) {
862
+ if (!lb.isFinalized) continue;
863
+ const limit = topPositions > 0 ? topPositions : lb.tokenIds.length;
864
+ for (let i = 0; i < Math.min(limit, lb.tokenIds.length); i++) {
865
+ if (ownedSet.has(lb.tokenIds[i])) {
866
+ if (!map[lb.tournamentId]) map[lb.tournamentId] = [];
867
+ map[lb.tournamentId].push({
868
+ tokenId: lb.tokenIds[i],
869
+ position: i + 1
870
+ });
871
+ }
872
+ }
873
+ }
874
+ return map;
875
+ }
876
+ function resolveTournamentQualifications(config, participationMap, winMap, tournamentNames) {
877
+ const inputs = [];
878
+ if (config.requirementType === "won") {
879
+ for (const tournamentId of config.tournamentIds) {
880
+ const wonEntries = winMap[tournamentId];
881
+ if (!wonEntries) continue;
882
+ for (const entry of wonEntries) {
883
+ inputs.push({
884
+ tournamentId,
885
+ tokenId: entry.tokenId,
886
+ position: entry.position,
887
+ tournamentName: tournamentNames?.[tournamentId]
888
+ });
889
+ }
890
+ }
891
+ } else {
892
+ for (const tournamentId of config.tournamentIds) {
893
+ const tokenIds = participationMap[tournamentId];
894
+ if (!tokenIds) continue;
895
+ for (const tokenId of tokenIds) {
896
+ inputs.push({
897
+ tournamentId,
898
+ tokenId,
899
+ position: 1,
900
+ // Participation = position 1
901
+ tournamentName: tournamentNames?.[tournamentId]
902
+ });
903
+ }
904
+ }
905
+ }
906
+ return inputs;
907
+ }
908
+
909
+ // src/utils/opusTroves.ts
910
+ function calculateOpusTrovesEntries(debt, config) {
911
+ let allowed = 0;
912
+ if (config.valuePerEntry > 0n) {
913
+ if (debt > config.threshold) {
914
+ allowed = Number((debt - config.threshold) / config.valuePerEntry);
915
+ }
916
+ } else {
917
+ if (debt >= config.threshold && config.maxEntries > 0) {
918
+ allowed = config.maxEntries;
919
+ }
920
+ }
921
+ if (config.maxEntries > 0) {
922
+ allowed = Math.min(allowed, config.maxEntries);
923
+ }
924
+ return allowed;
925
+ }
926
+ function findBannableEntries(registeredTokenIds, entriesAllowed) {
927
+ const bannable = /* @__PURE__ */ new Set();
928
+ const bannableCount = Math.max(0, registeredTokenIds.length - entriesAllowed);
929
+ if (bannableCount > 0) {
930
+ const sorted = [...registeredTokenIds].sort((a, b) => {
931
+ return Number(BigInt(a)) - Number(BigInt(b));
932
+ });
933
+ for (let i = 0; i < bannableCount; i++) {
934
+ bannable.add(sorted[i]);
935
+ }
936
+ }
937
+ return bannable;
938
+ }
939
+ function findAllBannableEntries(players, config) {
940
+ const allBannable = /* @__PURE__ */ new Set();
941
+ for (const [, { debt, registeredTokenIds }] of players) {
942
+ const allowed = calculateOpusTrovesEntries(debt, config);
943
+ const bannable = findBannableEntries(registeredTokenIds, allowed);
944
+ for (const id of bannable) {
945
+ allBannable.add(id);
946
+ }
947
+ }
948
+ return allBannable;
949
+ }
950
+
951
+ // src/utils/prizePool.ts
952
+ function buildEntryFeePrizes(entryFee, entryCount) {
953
+ if (!entryFee.amount || entryCount === 0 || entryFee.distributionCount <= 0) {
954
+ return [];
955
+ }
956
+ const shares = {
957
+ creatorShare: entryFee.tournamentCreatorShare,
958
+ gameCreatorShare: entryFee.gameCreatorShare,
959
+ refundShare: entryFee.refundShare
960
+ };
961
+ const breakdown = calculateEntryFeeBreakdown(
962
+ BigInt(entryFee.amount),
963
+ entryCount,
964
+ shares
965
+ );
966
+ if (breakdown.prizePoolAmount <= 0n) return [];
967
+ const dist = entryFee.distribution;
968
+ let distributionType = "uniform";
969
+ let weight = 10;
970
+ if (dist?.type === "linear" || dist?.Linear !== void 0) {
971
+ distributionType = "linear";
972
+ weight = Number(dist.weight ?? dist.Linear ?? 100) / 10;
973
+ } else if (dist?.type === "exponential" || dist?.Exponential !== void 0) {
974
+ distributionType = "exponential";
975
+ weight = Number(dist.weight ?? dist.Exponential ?? 100) / 10;
976
+ }
977
+ const percentages = calculateDistribution(
978
+ entryFee.distributionCount,
979
+ weight,
980
+ 0,
981
+ 0,
982
+ 0,
983
+ distributionType
984
+ );
985
+ const positions = distributePool(breakdown.prizePoolAmount, percentages);
986
+ return positions.map((pos) => ({
987
+ id: `entry_fee_${pos.position}`,
988
+ position: pos.position,
989
+ tokenAddress: entryFee.tokenAddress,
990
+ tokenType: "erc20",
991
+ amount: pos.amount.toString(),
992
+ sponsorAddress: ""
993
+ }));
994
+ }
995
+ function calculateTotalPrizeValueUSD(prizes, prices, decimals, normalizeAddress = (addr) => addr) {
996
+ let total = 0;
997
+ for (const prize of prizes) {
998
+ if (prize.tokenType !== "erc20") continue;
999
+ const addr = normalizeAddress(prize.tokenAddress);
1000
+ const price = prices[addr];
1001
+ if (price === void 0) continue;
1002
+ const dec = decimals[addr] ?? 18;
1003
+ total += calculatePrizeValue(prize.amount, dec, price);
1004
+ }
1005
+ return total;
1006
+ }
1007
+ function calculatePaidPlaces(sponsoredPrizes, entryFeeDistributionCount) {
1008
+ const maxSponsoredPosition = sponsoredPrizes.reduce(
1009
+ (max, p) => Math.max(max, p.position),
1010
+ 0
1011
+ );
1012
+ return Math.max(maxSponsoredPosition, entryFeeDistributionCount);
1013
+ }
1014
+
1015
+ exports.QualifyingMode = QualifyingMode;
1016
+ exports.aggregatePrizesByPosition = aggregatePrizesByPosition;
1017
+ exports.aggregatePrizesBySponsor = aggregatePrizesBySponsor;
1018
+ exports.bigintToHex = bigintToHex;
1019
+ exports.buildEntryFeePrizes = buildEntryFeePrizes;
1020
+ exports.buildExtensionProof = buildExtensionProof;
1021
+ exports.buildNFTProof = buildNFTProof;
1022
+ exports.buildParticipationMap = buildParticipationMap;
1023
+ exports.buildQualificationProof = buildQualificationProof;
1024
+ exports.buildTournamentExtensionProof = buildTournamentExtensionProof;
1025
+ exports.buildWinMap = buildWinMap;
1026
+ exports.calculateDistribution = calculateDistribution;
1027
+ exports.calculateEntryFeeBreakdown = calculateEntryFeeBreakdown;
1028
+ exports.calculateOpusTrovesEntries = calculateOpusTrovesEntries;
1029
+ exports.calculatePaidPlaces = calculatePaidPlaces;
1030
+ exports.calculatePayouts = calculatePayouts;
1031
+ exports.calculatePrizeValue = calculatePrizeValue;
1032
+ exports.calculateTotalPrizeValueUSD = calculateTotalPrizeValueUSD;
1033
+ exports.computeStatus = computeStatus;
1034
+ exports.displayAddress = displayAddress;
1035
+ exports.distributePool = distributePool;
1036
+ exports.evaluateExtensionQualification = evaluateExtensionQualification;
1037
+ exports.evaluateQualification = evaluateQualification;
1038
+ exports.evaluateTokenQualification = evaluateTokenQualification;
1039
+ exports.filterClaimablePrizes = filterClaimablePrizes;
1040
+ exports.filterZeroPrizes = filterZeroPrizes;
1041
+ exports.findAllBannableEntries = findAllBannableEntries;
1042
+ exports.findBannableEntries = findBannableEntries;
1043
+ exports.formatCashToUSD = formatCashToUSD;
1044
+ exports.formatNumber = formatNumber;
1045
+ exports.formatPrizeAmount = formatPrizeAmount;
1046
+ exports.formatScore = formatScore;
1047
+ exports.formatTime = formatTime;
1048
+ exports.formatTokenAmount = formatTokenAmount;
1049
+ exports.formatUsdValue = formatUsdValue;
1050
+ exports.getExtensionAddresses = getExtensionAddresses;
1051
+ exports.getOpusSupportedAssets = getOpusSupportedAssets;
1052
+ exports.getOrdinalSuffix = getOrdinalSuffix;
1053
+ exports.getQualifyingModeInfo = getQualifyingModeInfo;
1054
+ exports.getWhitelistedExtensionAddresses = getWhitelistedExtensionAddresses;
1055
+ exports.groupPrizesByToken = groupPrizesByToken;
1056
+ exports.identifyExtensionType = identifyExtensionType;
1057
+ exports.indexAddress = indexAddress;
1058
+ exports.isBefore = isBefore;
1059
+ exports.isWhitelistedExtension = isWhitelistedExtension;
1060
+ exports.padAddress = padAddress;
1061
+ exports.parseERC20BalanceValidatorConfig = parseERC20BalanceValidatorConfig;
1062
+ exports.parseExtensionConfig = parseExtensionConfig;
1063
+ exports.parseGovernanceValidatorConfig = parseGovernanceValidatorConfig;
1064
+ exports.parseNFTBulkInput = parseNFTBulkInput;
1065
+ exports.parseOpusTrovesValidatorConfig = parseOpusTrovesValidatorConfig;
1066
+ exports.parseSnapshotValidatorConfig = parseSnapshotValidatorConfig;
1067
+ exports.parseTournamentValidatorConfig = parseTournamentValidatorConfig;
1068
+ exports.parseZkPassportValidatorConfig = parseZkPassportValidatorConfig;
1069
+ exports.resolveTournamentQualifications = resolveTournamentQualifications;
1070
+ //# sourceMappingURL=index.cjs.map
1071
+ //# sourceMappingURL=index.cjs.map