proof-of-take-sdk 1.0.0

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.
Files changed (90) hide show
  1. package/FINAL_USAGE.md +342 -0
  2. package/LIBRARY_GUIDE.md +400 -0
  3. package/LICENSE +22 -0
  4. package/README.md +370 -0
  5. package/dist/client.d.ts +32 -0
  6. package/dist/client.js +47 -0
  7. package/dist/constants/season.d.ts +18 -0
  8. package/dist/constants/season.js +22 -0
  9. package/dist/getters/getMiztake.d.ts +11 -0
  10. package/dist/getters/getMiztake.js +34 -0
  11. package/dist/getters/getMiztakeStatistics.d.ts +10 -0
  12. package/dist/getters/getMiztakeStatistics.js +20 -0
  13. package/dist/getters/getTokenVault.d.ts +30 -0
  14. package/dist/getters/getTokenVault.js +56 -0
  15. package/dist/getters/getUserStats.d.ts +21 -0
  16. package/dist/getters/getUserStats.js +43 -0
  17. package/dist/getters/index.d.ts +7 -0
  18. package/dist/getters/index.js +23 -0
  19. package/dist/idl/idl.d.ts +2 -0
  20. package/dist/idl/idl.js +8 -0
  21. package/dist/idl/proof_of_take.json +3803 -0
  22. package/dist/index.d.ts +48 -0
  23. package/dist/index.js +92 -0
  24. package/dist/instructions/claimReferralPenaltyForWindow.d.ts +38 -0
  25. package/dist/instructions/claimReferralPenaltyForWindow.js +72 -0
  26. package/dist/instructions/claimWindowRewards.d.ts +48 -0
  27. package/dist/instructions/claimWindowRewards.js +94 -0
  28. package/dist/instructions/confirmedPostOnX.d.ts +51 -0
  29. package/dist/instructions/confirmedPostOnX.js +78 -0
  30. package/dist/instructions/createMiztake.d.ts +90 -0
  31. package/dist/instructions/createMiztake.js +166 -0
  32. package/dist/instructions/initializeEscrowVault.d.ts +15 -0
  33. package/dist/instructions/initializeEscrowVault.js +36 -0
  34. package/dist/instructions/initializeSeasonSettings.d.ts +20 -0
  35. package/dist/instructions/initializeSeasonSettings.js +39 -0
  36. package/dist/instructions/initializeSeasonVault.d.ts +20 -0
  37. package/dist/instructions/initializeSeasonVault.js +43 -0
  38. package/dist/instructions/initializeStatistics.d.ts +32 -0
  39. package/dist/instructions/initializeStatistics.js +73 -0
  40. package/dist/instructions/joinSeason.d.ts +50 -0
  41. package/dist/instructions/joinSeason.js +120 -0
  42. package/dist/instructions/toggleSeasonPause.d.ts +22 -0
  43. package/dist/instructions/toggleSeasonPause.js +42 -0
  44. package/dist/instructions/updateSeasonAdmin.d.ts +23 -0
  45. package/dist/instructions/updateSeasonAdmin.js +43 -0
  46. package/dist/instructions/viewCurrentSeason.d.ts +12 -0
  47. package/dist/instructions/viewCurrentSeason.js +30 -0
  48. package/dist/instructions/viewSeasonMembershipStatus.d.ts +16 -0
  49. package/dist/instructions/viewSeasonMembershipStatus.js +33 -0
  50. package/dist/instructions/viewWindowStatus.d.ts +15 -0
  51. package/dist/instructions/viewWindowStatus.js +28 -0
  52. package/dist/instructions/withdrawSeasonDeposit.d.ts +38 -0
  53. package/dist/instructions/withdrawSeasonDeposit.js +66 -0
  54. package/dist/optimistic/index.d.ts +7 -0
  55. package/dist/optimistic/index.js +33 -0
  56. package/dist/types/accountTypes.d.ts +121 -0
  57. package/dist/types/accountTypes.js +2 -0
  58. package/dist/types/instructionResults.d.ts +44 -0
  59. package/dist/types/instructionResults.js +2 -0
  60. package/dist/types/proof_of_take.d.ts +3809 -0
  61. package/dist/types/proof_of_take.js +2 -0
  62. package/dist/types.d.ts +232 -0
  63. package/dist/types.js +16 -0
  64. package/dist/utils/accountUpdates.d.ts +245 -0
  65. package/dist/utils/accountUpdates.js +611 -0
  66. package/dist/utils/anchorHelpers.d.ts +7 -0
  67. package/dist/utils/anchorHelpers.js +21 -0
  68. package/dist/utils/constants.d.ts +21 -0
  69. package/dist/utils/constants.js +31 -0
  70. package/dist/utils/conversions.d.ts +25 -0
  71. package/dist/utils/conversions.js +53 -0
  72. package/dist/utils/enumHelpers.d.ts +63 -0
  73. package/dist/utils/enumHelpers.js +110 -0
  74. package/dist/utils/index.d.ts +0 -0
  75. package/dist/utils/index.js +2 -0
  76. package/dist/utils/pdaManager.d.ts +106 -0
  77. package/dist/utils/pdaManager.js +89 -0
  78. package/dist/utils/pdas.d.ts +68 -0
  79. package/dist/utils/pdas.js +128 -0
  80. package/dist/utils/programHelpers.d.ts +9 -0
  81. package/dist/utils/programHelpers.js +18 -0
  82. package/dist/utils/signerHelpers.d.ts +17 -0
  83. package/dist/utils/signerHelpers.js +21 -0
  84. package/dist/utils/simulationHelpers.d.ts +121 -0
  85. package/dist/utils/simulationHelpers.js +183 -0
  86. package/dist/utils/tierPenalty.d.ts +9 -0
  87. package/dist/utils/tierPenalty.js +24 -0
  88. package/dist/utils/transactionBuilder.d.ts +77 -0
  89. package/dist/utils/transactionBuilder.js +147 -0
  90. package/package.json +50 -0
@@ -0,0 +1,611 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getCurrentTimestamp = getCurrentTimestamp;
4
+ exports.createEmptyUserStats = createEmptyUserStats;
5
+ exports.initializeUserStats = initializeUserStats;
6
+ exports.updateUserStatsForMiztakeCreation = updateUserStatsForMiztakeCreation;
7
+ exports.updateMiztakeStatisticsForCreation = updateMiztakeStatisticsForCreation;
8
+ exports.initializeUserWindowParticipation = initializeUserWindowParticipation;
9
+ exports.initializeRewardWindow = initializeRewardWindow;
10
+ exports.updateMiztakeForSeasonUse = updateMiztakeForSeasonUse;
11
+ exports.initializeSeasonMembership = initializeSeasonMembership;
12
+ exports.updateSeasonForJoin = updateSeasonForJoin;
13
+ exports.updateWindowForConfirmation = updateWindowForConfirmation;
14
+ exports.updateParticipationForConfirmation = updateParticipationForConfirmation;
15
+ exports.updateMembershipForConfirmation = updateMembershipForConfirmation;
16
+ exports.updateSeasonForConfirmation = updateSeasonForConfirmation;
17
+ exports.updateWindowForFinalization = updateWindowForFinalization;
18
+ exports.updateParticipationForClaim = updateParticipationForClaim;
19
+ exports.updateMembershipForClaim = updateMembershipForClaim;
20
+ exports.initializeReferralPenaltyClaim = initializeReferralPenaltyClaim;
21
+ exports.calculateWithdrawalAmount = calculateWithdrawalAmount;
22
+ exports.updateSeasonSettingsForPauseToggle = updateSeasonSettingsForPauseToggle;
23
+ exports.updateSeasonSettingsForAdminUpdate = updateSeasonSettingsForAdminUpdate;
24
+ const anchor_1 = require("@coral-xyz/anchor");
25
+ const types_1 = require("../types");
26
+ const tierPenalty_1 = require("./tierPenalty");
27
+ const pdas_1 = require("./pdas");
28
+ const enumHelpers_1 = require("./enumHelpers");
29
+ /**
30
+ * Get current Unix timestamp in SECONDS as BN
31
+ *
32
+ * Note:
33
+ * - Returns Unix timestamp in SECONDS (not milliseconds)
34
+ * - Matches Solana's Clock::unix_timestamp format
35
+ * - This is an approximate timestamp; the actual on-chain timestamp
36
+ * will be set by the Solana runtime when the transaction is processed
37
+ *
38
+ * @returns Current Unix timestamp in seconds
39
+ */
40
+ function getCurrentTimestamp(now) {
41
+ if (now)
42
+ return now;
43
+ return new anchor_1.BN(Math.floor(Date.now() / 1000)); // Convert JS milliseconds → seconds
44
+ }
45
+ /**
46
+ * Create an empty/initialized UserStats object
47
+ * Used when initializing a new user stats account
48
+ */
49
+ function createEmptyUserStats(telegramId) {
50
+ return {
51
+ telegramId,
52
+ totalNumberOfMiztakes: new anchor_1.BN(0),
53
+ onchainFirstMiztakeAt: new anchor_1.BN(0),
54
+ onchainLastMiztakeAt: new anchor_1.BN(0),
55
+ bump: 0, // Will be set on-chain
56
+ };
57
+ }
58
+ /**
59
+ * Create a fully initialized UserStats account with PDA bump
60
+ * Mirrors the initialization logic from create_miztake.rs (lines 33-48)
61
+ *
62
+ * This function derives the PDA and returns a UserStats object exactly as it
63
+ * will appear on-chain after initialization.
64
+ *
65
+ * @param telegramId - Telegram ID of the user
66
+ * @returns Initialized UserStats object with proper bump seed
67
+ *
68
+ * @example
69
+ * ```typescript
70
+ * const userStats = initializeUserStats(new BN(123456789));
71
+ * // userStats is ready to be used for local state tracking
72
+ * // and matches exactly what will be created on-chain
73
+ * ```
74
+ */
75
+ function initializeUserStats(telegramId) {
76
+ // Derive the PDA to get the bump seed (matching on-chain derivation)
77
+ const [, bump] = (0, pdas_1.getUserStatsPda)(telegramId);
78
+ // Initialize all fields to 0 as per create_miztake.rs
79
+ return {
80
+ telegramId, // user_stats.telegram_id = telegram_id
81
+ totalNumberOfMiztakes: new anchor_1.BN(0), // user_stats.total_number_of_miztakes = 0
82
+ onchainFirstMiztakeAt: new anchor_1.BN(0), // user_stats.onchain_first_miztake_at = 0
83
+ onchainLastMiztakeAt: new anchor_1.BN(0), // user_stats.onchain_last_miztake_at = 0
84
+ bump, // user_stats.bump = ctx.bumps.user_stats
85
+ };
86
+ }
87
+ /**
88
+ * Update UserStats after creating a miztake
89
+ * Mirrors the increment_miztakes logic from user_stats.rs
90
+ *
91
+ * @param userStats - Current user stats
92
+ * @param telegramId - Telegram ID for initialization check
93
+ * @param timestamp - Timestamp for the update in SECONDS (Unix timestamp)
94
+ * @returns Updated UserStats object
95
+ */
96
+ function updateUserStatsForMiztakeCreation(userStats, telegramId, timestamp = getCurrentTimestamp()) {
97
+ // Check if this is a new UserStats account (needs initialization)
98
+ const isNew = userStats.telegramId.isZero() || !userStats.telegramId.eq(telegramId);
99
+ const stats = isNew ? initializeUserStats(telegramId) : { ...userStats };
100
+ // Apply increment_miztakes logic (lines 48-59 in user_stats.rs)
101
+ stats.totalNumberOfMiztakes = stats.totalNumberOfMiztakes.add(new anchor_1.BN(1));
102
+ // Set first miztake timestamp if this is the first one
103
+ if (stats.onchainFirstMiztakeAt.isZero()) {
104
+ stats.onchainFirstMiztakeAt = timestamp;
105
+ }
106
+ stats.onchainLastMiztakeAt = timestamp;
107
+ return stats;
108
+ }
109
+ /**
110
+ * Update MiztakeStatistics after creating a miztake
111
+ * Mirrors the increment_miztakes logic from miztake_statistics.rs
112
+ *
113
+ * @param statistics - Current miztake statistics
114
+ * @returns Updated MiztakeStatistics object and new miztake ID
115
+ *
116
+ * @throws Error if invalid statistics provided
117
+ */
118
+ function updateMiztakeStatisticsForCreation(statistics) {
119
+ if (!statistics || !statistics.totalMiztakes) {
120
+ throw new Error("updateMiztakeStatisticsForCreation: Invalid statistics object");
121
+ }
122
+ const newMiztakeId = statistics.totalMiztakes.add(new anchor_1.BN(1));
123
+ return {
124
+ statistics: {
125
+ ...statistics,
126
+ totalMiztakes: newMiztakeId,
127
+ },
128
+ newMiztakeId,
129
+ };
130
+ }
131
+ /**
132
+ * Initialize or update UserWindowParticipation
133
+ * Mirrors the logic from create_miztake.rs (lines 109-139)
134
+ *
135
+ * @param user - User's public key
136
+ * @param seasonNumber - Season number
137
+ * @param windowNumber - Window number
138
+ * @param seasonMembershipId - Season membership ID
139
+ * @param miztakeId - The miztake ID to associate
140
+ * @returns Initialized/Updated UserWindowParticipation object
141
+ */
142
+ function initializeUserWindowParticipation(user, seasonNumber, windowNumber, seasonMembershipId, miztakeId) {
143
+ const [, bump] = (0, pdas_1.getUserWindowParticipationPda)(user, seasonNumber, windowNumber, seasonMembershipId);
144
+ return {
145
+ user,
146
+ seasonNumber,
147
+ windowNumber,
148
+ seasonMembershipId,
149
+ postedSuccessfully: false,
150
+ claimedReward: false,
151
+ rewardAmount: new anchor_1.BN(0),
152
+ miztakeId,
153
+ bump,
154
+ };
155
+ }
156
+ /**
157
+ * Initialize RewardWindow
158
+ * Mirrors the logic from season_helpers::initialize_window
159
+ *
160
+ * @param seasonNumber - Season number
161
+ * @param windowNumber - Window number
162
+ * @param season - Current season state
163
+ * @param windowDuration - Duration of each window in seconds
164
+ * @returns Initialized RewardWindow object
165
+ */
166
+ function initializeRewardWindow(seasonNumber, windowNumber, season, windowDuration) {
167
+ const [, bump] = (0, pdas_1.getRewardWindowPda)(seasonNumber, windowNumber);
168
+ const startedAt = season.startedAt.add(windowNumber.mul(windowDuration));
169
+ const endsAt = startedAt.add(windowDuration);
170
+ return {
171
+ seasonNumber,
172
+ windowNumber,
173
+ windowState: types_1.WindowState.Active,
174
+ startedAt,
175
+ endsAt,
176
+ totalMembersInSeason: season.totalMembers,
177
+ penaltyPool: new anchor_1.BN(0),
178
+ successfulPostersCount: new anchor_1.BN(0),
179
+ successfulStakeTotal: new anchor_1.BN(0),
180
+ rewardPerPoster: new anchor_1.BN(0),
181
+ isFinalized: false,
182
+ bump,
183
+ };
184
+ }
185
+ /**
186
+ * Update Miztake to mark it as used in a season/window
187
+ * Mirrors mark_as_used from miztake.rs
188
+ *
189
+ * @param miztake - Current miztake
190
+ * @param seasonNumber - Season number
191
+ * @param windowNumber - Window number
192
+ * @returns Updated Miztake object
193
+ */
194
+ function updateMiztakeForSeasonUse(miztake, seasonNumber, windowNumber) {
195
+ return {
196
+ ...miztake,
197
+ usedInSeason: seasonNumber,
198
+ usedInWindow: windowNumber,
199
+ };
200
+ }
201
+ /**
202
+ * Update SeasonMembership after joining
203
+ * Mirrors the initialization from join_season.rs (lines 84-96)
204
+ *
205
+ * @param user - User's public key
206
+ * @param seasonMembershipId - Membership ID
207
+ * @param seasonNumber - Season number
208
+ * @param depositTier - Tier chosen at join time
209
+ * @param depositAmount - Active deposit amount (participates in rewards/penalties)
210
+ * @param depositEscrowedAmount - Escrowed deposit amount (always refundable)
211
+ * @param joinedAt - Timestamp when joined
212
+ * @param joinedWindowNumber - Window number when joined
213
+ * @returns Initialized SeasonMembership object
214
+ */
215
+ function initializeSeasonMembership(user, seasonMembershipId, seasonNumber, depositTier, referrer, depositAmount, depositEscrowedAmount, joinedAt, joinedWindowNumber, bump) {
216
+ return {
217
+ id: seasonMembershipId,
218
+ owner: user,
219
+ referrer,
220
+ seasonNumber,
221
+ depositTier,
222
+ depositAmount,
223
+ depositEscrowedAmount,
224
+ joinedAt,
225
+ joinedWindowNumber,
226
+ successfulPostsCount: new anchor_1.BN(0),
227
+ totalRewardsClaimed: new anchor_1.BN(0),
228
+ totalPenaltiesPaid: new anchor_1.BN(0),
229
+ depositWithdrawn: false,
230
+ isActive: true,
231
+ bump,
232
+ };
233
+ }
234
+ /**
235
+ * Update Season after a user joins
236
+ * Mirrors the update from join_season.rs (lines 98-104)
237
+ *
238
+ * @param season - Current season
239
+ * @param depositActive - Active deposit amount added to season totals
240
+ * @returns Updated Season object
241
+ */
242
+ function updateSeasonForJoin(season, depositActive, depositTier, joinedWindowNumber, totalWindowsPerSeason) {
243
+ // NOTE: On-chain (join_season.rs) updates eligible_stake_per_window ONLY for windows
244
+ // w >= joined_window_number. Earlier windows are not modified.
245
+ //
246
+ // Also note: older generated IDLs/types in this repo mention 80 windows, but the
247
+ // current Rust program uses a fixed 21-window schedule. We therefore:
248
+ // - Prefer the actual array length present on `season.eligibleStakePerWindow`
249
+ // - Otherwise fall back to `season.totalWindows` / `totalWindowsPerSeason` / 21.
250
+ const seasonLike = season;
251
+ const existingSchedule = Array.isArray(seasonLike.eligibleStakePerWindow)
252
+ ? seasonLike.eligibleStakePerWindow
253
+ : undefined;
254
+ const inferredTotalWindows = typeof totalWindowsPerSeason === "number" &&
255
+ Number.isFinite(totalWindowsPerSeason) &&
256
+ totalWindowsPerSeason > 0
257
+ ? totalWindowsPerSeason
258
+ : typeof seasonLike.totalWindows === "number" &&
259
+ Number.isFinite(seasonLike.totalWindows) &&
260
+ seasonLike.totalWindows > 0
261
+ ? seasonLike.totalWindows
262
+ : 21;
263
+ const scheduleLen = existingSchedule?.length ?? inferredTotalWindows;
264
+ const schedule = existingSchedule
265
+ ? existingSchedule.slice()
266
+ : Array.from({ length: scheduleLen }, () => new anchor_1.BN(0));
267
+ const next = {
268
+ ...season,
269
+ totalMembers: season.totalMembers.add(new anchor_1.BN(1)),
270
+ totalDepositsHeld: season.totalDepositsHeld.add(depositActive),
271
+ eligibleStakePerWindow: schedule,
272
+ };
273
+ const perWindowStake = (0, tierPenalty_1.getTierPenaltyPerWindow)(depositTier);
274
+ const startIdx = joinedWindowNumber.toNumber();
275
+ const endExclusive = Math.min(inferredTotalWindows, next.eligibleStakePerWindow.length);
276
+ // If inputs are out of range, don't try to mutate schedule (avoid throwing in optimistic path).
277
+ const updatedSchedule = next.eligibleStakePerWindow.map((v, i) => {
278
+ if (i >= startIdx && i < endExclusive)
279
+ return v.add(perWindowStake);
280
+ return v;
281
+ });
282
+ const nextWithSchedule = {
283
+ ...next,
284
+ eligibleStakePerWindow: updatedSchedule,
285
+ };
286
+ if ("copper" in depositTier)
287
+ return {
288
+ ...nextWithSchedule,
289
+ copperMembersCount: season.copperMembersCount.add(new anchor_1.BN(1)),
290
+ };
291
+ if ("silver" in depositTier)
292
+ return {
293
+ ...nextWithSchedule,
294
+ silverMembersCount: season.silverMembersCount.add(new anchor_1.BN(1)),
295
+ };
296
+ if ("gold" in depositTier)
297
+ return {
298
+ ...nextWithSchedule,
299
+ goldMembersCount: season.goldMembersCount.add(new anchor_1.BN(1)),
300
+ };
301
+ if ("platinum" in depositTier)
302
+ return {
303
+ ...nextWithSchedule,
304
+ platinumMembersCount: season.platinumMembersCount.add(new anchor_1.BN(1)),
305
+ };
306
+ if ("mithril" in depositTier)
307
+ return {
308
+ ...nextWithSchedule,
309
+ mithrilMembersCount: season.mithrilMembersCount.add(new anchor_1.BN(1)),
310
+ };
311
+ return {
312
+ ...nextWithSchedule,
313
+ };
314
+ }
315
+ /**
316
+ * Update RewardWindow after post confirmation
317
+ * Mirrors confirmed_post_on_x.rs logic
318
+ *
319
+ * @param window - Current window
320
+ * @param isOnTime - Whether the post was on time
321
+ * @param penaltyAmount - Penalty amount if late (REQUIRED when isOnTime=false)
322
+ * @returns Updated RewardWindow object
323
+ *
324
+ * @throws Error if penaltyAmount missing when isOnTime is false
325
+ */
326
+ function updateWindowForConfirmation(window, isOnTime, stakeAmount) {
327
+ if (isOnTime) {
328
+ if (!stakeAmount || stakeAmount.isZero()) {
329
+ throw new Error("updateWindowForConfirmation: stakeAmount is required when isOnTime is true");
330
+ }
331
+ return {
332
+ ...window,
333
+ successfulPostersCount: window.successfulPostersCount.add(new anchor_1.BN(1)),
334
+ successfulStakeTotal: window.successfulStakeTotal.add(stakeAmount),
335
+ };
336
+ }
337
+ else {
338
+ // penaltyPool is derived at finalization as X - Y; do not mutate here
339
+ return { ...window };
340
+ }
341
+ }
342
+ /**
343
+ * Update UserWindowParticipation after post confirmation
344
+ * Mirrors confirmed_post_on_x.rs logic
345
+ *
346
+ * @param participation - Current participation
347
+ * @param isOnTime - Whether the post was on time
348
+ * @returns Updated UserWindowParticipation object
349
+ */
350
+ function updateParticipationForConfirmation(participation, isOnTime) {
351
+ return {
352
+ ...participation,
353
+ postedSuccessfully: isOnTime,
354
+ };
355
+ }
356
+ /**
357
+ * Update SeasonMembership after post confirmation
358
+ * Mirrors confirmed_post_on_x.rs logic
359
+ *
360
+ * @param membership - Current membership
361
+ * @param isOnTime - Whether the post was on time
362
+ * @param penaltyAmount - Penalty amount if late (REQUIRED when isOnTime=false)
363
+ * @returns Updated SeasonMembership object
364
+ *
365
+ * @throws Error if penaltyAmount missing when isOnTime is false
366
+ */
367
+ function updateMembershipForConfirmation(membership, isOnTime, penaltyAmount) {
368
+ if (isOnTime) {
369
+ return {
370
+ ...membership,
371
+ successfulPostsCount: membership.successfulPostsCount.add(new anchor_1.BN(1)),
372
+ };
373
+ }
374
+ else {
375
+ if (!penaltyAmount || penaltyAmount.isZero()) {
376
+ throw new Error("updateMembershipForConfirmation: penaltyAmount is required when isOnTime is false");
377
+ }
378
+ return {
379
+ ...membership,
380
+ totalPenaltiesPaid: membership.totalPenaltiesPaid.add(penaltyAmount),
381
+ };
382
+ }
383
+ }
384
+ /**
385
+ * Update Season after post confirmation
386
+ * Mirrors confirmed_post_on_x.rs logic
387
+ *
388
+ * @param season - Current season
389
+ * @param isOnTime - Whether the post was on time
390
+ * @param penaltyAmount - Penalty amount if late (REQUIRED when isOnTime=false)
391
+ * @returns Updated Season object
392
+ *
393
+ * @throws Error if penaltyAmount missing when isOnTime is false
394
+ */
395
+ function updateSeasonForConfirmation(season, isOnTime, _stakeAmount) {
396
+ if (isOnTime) {
397
+ return {
398
+ ...season,
399
+ totalSuccessfulPosts: season.totalSuccessfulPosts.add(new anchor_1.BN(1)),
400
+ };
401
+ }
402
+ else {
403
+ // totalPenaltiesCollected is updated at window finalization (X - Y), not here
404
+ return { ...season };
405
+ }
406
+ }
407
+ /**
408
+ * Finalize RewardWindow and calculate rewards
409
+ * Mirrors claim_window_rewards.rs finalization logic (lines 22-86)
410
+ *
411
+ * @param window - Current window
412
+ * @returns Updated finalized RewardWindow object
413
+ */
414
+ function updateWindowForFinalization(window, eligibleStakeX) {
415
+ // penalty_pool is derived as X - Y.
416
+ const finalPenaltyPool = eligibleStakeX.gt(window.successfulStakeTotal)
417
+ ? eligibleStakeX.sub(window.successfulStakeTotal)
418
+ : new anchor_1.BN(0);
419
+ // Winners pool is 80% of missed penalties (the other 20% is claimable by referrer/admin).
420
+ const winnersPool = finalPenaltyPool.muln(80).divn(100);
421
+ // Calculate reward per poster
422
+ let rewardPerPoster = new anchor_1.BN(0);
423
+ if (window.successfulPostersCount.gt(new anchor_1.BN(0))) {
424
+ rewardPerPoster = winnersPool.div(window.successfulPostersCount);
425
+ }
426
+ return {
427
+ ...window,
428
+ penaltyPool: finalPenaltyPool,
429
+ rewardPerPoster,
430
+ isFinalized: true,
431
+ windowState: types_1.WindowState.Finalized,
432
+ };
433
+ }
434
+ /**
435
+ * Update UserWindowParticipation after claiming window rewards
436
+ * Mirrors claim_window_rewards.rs logic
437
+ *
438
+ * @param participation - Current participation
439
+ * @param rewardAmount - Reward amount claimed
440
+ * @returns Updated UserWindowParticipation object
441
+ */
442
+ function updateParticipationForClaim(participation, rewardAmount) {
443
+ return {
444
+ ...participation,
445
+ claimedReward: true,
446
+ rewardAmount,
447
+ };
448
+ }
449
+ /**
450
+ * Update SeasonMembership after claiming window rewards
451
+ * Mirrors claim_window_rewards.rs logic (lines 156-165)
452
+ *
453
+ * @param membership - Current membership
454
+ * @param rewardAmount - Total reward amount
455
+ * (Penalties are tier-derived on-chain; settings no longer carry penalty_per_window.)
456
+ * @returns Updated SeasonMembership object
457
+ *
458
+ * @throws Error if invalid inputs provided
459
+ */
460
+ function updateMembershipForClaim(membership, rewardAmount) {
461
+ if (!membership) {
462
+ throw new Error("updateMembershipForClaim: membership is required");
463
+ }
464
+ if (!rewardAmount || rewardAmount.isNeg()) {
465
+ throw new Error("updateMembershipForClaim: Invalid reward amount");
466
+ }
467
+ const tierPenaltyPerWindow = (0, tierPenalty_1.getTierPenaltyPerWindow)(membership.depositTier);
468
+ // Calculate refund to track (up to tier penalty per window)
469
+ const refundToTrack = rewardAmount.gt(tierPenaltyPerWindow)
470
+ ? tierPenaltyPerWindow
471
+ : rewardAmount;
472
+ return {
473
+ ...membership,
474
+ totalRewardsClaimed: membership.totalRewardsClaimed.add(refundToTrack),
475
+ };
476
+ }
477
+ /**
478
+ * Initialize ReferralPenaltyClaim
479
+ * Mirrors claim_referral_penalty_for_window.rs (write fields + amount calc)
480
+ *
481
+ * @param referredUser - The user who missed the window
482
+ * @param seasonNumber - Season number
483
+ * @param windowNumber - Window number
484
+ * @param seasonMembershipId - Referred user's membership id
485
+ * @param claimant - Referrer (or root admin if no referrer)
486
+ * @param perWindowPenalty - Tier penalty per window (base units)
487
+ * @param claimedAt - Unix timestamp in seconds (i64 on-chain, represented as BN in SDK)
488
+ */
489
+ function initializeReferralPenaltyClaim(params) {
490
+ const [, bump] = (0, pdas_1.getReferralPenaltyClaimPda)(params.referredUser, params.seasonNumber, params.windowNumber, params.seasonMembershipId);
491
+ const claimedAt = getCurrentTimestamp(params.claimedAt);
492
+ const amount = params.perWindowPenalty.muln(20).divn(100);
493
+ return {
494
+ referredUser: params.referredUser,
495
+ seasonNumber: params.seasonNumber,
496
+ windowNumber: params.windowNumber,
497
+ seasonMembershipId: params.seasonMembershipId,
498
+ claimant: params.claimant,
499
+ amount,
500
+ claimedAt,
501
+ claimed: true,
502
+ bump,
503
+ };
504
+ }
505
+ // getTierPenaltyPerWindow moved to ./tierPenalty to avoid drift between SDK modules.
506
+ /**
507
+ * Calculate withdrawal amount for season deposit
508
+ * Mirrors withdraw_season_deposit.rs logic (lines 55-87)
509
+ *
510
+ * @param membership - Current membership
511
+ * @param season - Current season
512
+ * @param settings - Season settings
513
+ * @returns Withdrawal amount and updated accounts
514
+ *
515
+ * @throws Error if invalid inputs provided
516
+ */
517
+ function calculateWithdrawalAmount(membership, season, settings) {
518
+ // Input validation
519
+ if (!membership || !season || !settings) {
520
+ throw new Error("calculateWithdrawalAmount: membership, season, and settings are required");
521
+ }
522
+ if (!membership.depositAmount || membership.depositAmount.isNeg()) {
523
+ throw new Error("calculateWithdrawalAmount: Invalid deposit amount");
524
+ }
525
+ // Calculate windows available (match on-chain withdraw_season_deposit):
526
+ // windows_available = total_windows_per_season - joined_window_number
527
+ const totalWindows = new anchor_1.BN(settings.totalWindowsPerSeason);
528
+ const windowsAvailableCapped = totalWindows.gt(membership.joinedWindowNumber)
529
+ ? totalWindows.sub(membership.joinedWindowNumber)
530
+ : new anchor_1.BN(0);
531
+ // Calculate penalties both ways
532
+ // Protect against underflow if successfulPostsCount > windowsAvailable (shouldn't happen but be safe)
533
+ const missedWindows = windowsAvailableCapped.gt(membership.successfulPostsCount)
534
+ ? windowsAvailableCapped.sub(membership.successfulPostsCount)
535
+ : new anchor_1.BN(0);
536
+ const calculatedPenalties = missedWindows.mul((0, tierPenalty_1.getTierPenaltyPerWindow)(membership.depositTier));
537
+ const trackedPenalties = membership.totalPenaltiesPaid;
538
+ // Use larger value (Option C)
539
+ const finalPenalties = calculatedPenalties.gt(trackedPenalties)
540
+ ? calculatedPenalties
541
+ : trackedPenalties;
542
+ // Calculate withdrawal (deposit - penalties - already claimed refunds)
543
+ let withdrawalAmount = membership.depositAmount
544
+ .sub(finalPenalties)
545
+ .sub(membership.totalRewardsClaimed);
546
+ // Clamp to 0 if negative (user forfeited everything to penalties)
547
+ const clampedWithdrawalAmount = withdrawalAmount.gt(new anchor_1.BN(0))
548
+ ? withdrawalAmount
549
+ : new anchor_1.BN(0);
550
+ // Escrowed portion is always refunded in full at season end.
551
+ const totalWithdrawalAmount = clampedWithdrawalAmount.add(membership.depositEscrowedAmount);
552
+ // Update membership
553
+ const updatedMembership = {
554
+ ...membership,
555
+ depositEscrowedAmount: new anchor_1.BN(0),
556
+ depositWithdrawn: true,
557
+ isActive: false,
558
+ };
559
+ // Update season (if ended, finalize it)
560
+ let updatedSeason = season;
561
+ let updatedSettings = settings;
562
+ // Check if season is Active - use helper to handle both formats
563
+ if ((0, enumHelpers_1.isSeasonState)(season.seasonState, types_1.SeasonState.Active)) {
564
+ updatedSeason = {
565
+ ...season,
566
+ seasonState: types_1.SeasonState.Ended,
567
+ totalDepositsHeld: season.totalDepositsHeld.sub(clampedWithdrawalAmount), // Use CLAMPED amount
568
+ };
569
+ updatedSettings = {
570
+ ...settings,
571
+ completedSeasons: settings.completedSeasons.add(new anchor_1.BN(1)),
572
+ };
573
+ }
574
+ else {
575
+ updatedSeason = {
576
+ ...season,
577
+ totalDepositsHeld: season.totalDepositsHeld.sub(clampedWithdrawalAmount), // Use CLAMPED amount
578
+ };
579
+ }
580
+ return {
581
+ withdrawalAmount: totalWithdrawalAmount,
582
+ updatedMembership,
583
+ updatedSeason,
584
+ updatedSettings,
585
+ };
586
+ }
587
+ /**
588
+ * Update SeasonSettings after toggling pause
589
+ *
590
+ * @param settings - Current settings
591
+ * @returns Updated SeasonSettings object
592
+ */
593
+ function updateSeasonSettingsForPauseToggle(settings) {
594
+ return {
595
+ ...settings,
596
+ isPaused: !settings.isPaused,
597
+ };
598
+ }
599
+ /**
600
+ * Update SeasonSettings after admin update
601
+ *
602
+ * @param settings - Current settings
603
+ * @param newAdmin - New admin public key
604
+ * @returns Updated SeasonSettings object
605
+ */
606
+ function updateSeasonSettingsForAdminUpdate(settings, newAdmin) {
607
+ return {
608
+ ...settings,
609
+ admin: newAdmin,
610
+ };
611
+ }
@@ -0,0 +1,7 @@
1
+ import { AnchorProvider, Idl } from "@coral-xyz/anchor";
2
+ import { Commitment, Connection, PublicKey } from "@solana/web3.js";
3
+ export declare function createReadOnlyProvider(connection: Connection, commitment?: Commitment): AnchorProvider;
4
+ /**
5
+ * Ensure the given IDL has a stable `.address` (Anchor Program uses it as programId).
6
+ */
7
+ export declare function withIdlAddress(idl: Idl, programId: PublicKey): Idl;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createReadOnlyProvider = createReadOnlyProvider;
4
+ exports.withIdlAddress = withIdlAddress;
5
+ const anchor_1 = require("@coral-xyz/anchor");
6
+ const web3_js_1 = require("@solana/web3.js");
7
+ /**
8
+ * Anchor v0.32 uses a concrete Wallet class (NodeWallet) that requires a payer Keypair.
9
+ * We keep one in-memory wallet for "read-only" SDK usage (building instructions, `.view()`, etc.).
10
+ */
11
+ const READONLY_WALLET = new anchor_1.Wallet(web3_js_1.Keypair.generate());
12
+ function createReadOnlyProvider(connection, commitment = "confirmed") {
13
+ return new anchor_1.AnchorProvider(connection, READONLY_WALLET, { commitment });
14
+ }
15
+ /**
16
+ * Ensure the given IDL has a stable `.address` (Anchor Program uses it as programId).
17
+ */
18
+ function withIdlAddress(idl, programId) {
19
+ // Anchor v0.32 Program ctor uses `idl.address` as the program id.
20
+ return { ...idl, address: programId.toBase58() };
21
+ }
@@ -0,0 +1,21 @@
1
+ import { PublicKey } from "@solana/web3.js";
2
+ /**
3
+ * Proof of Miztake Program Constants
4
+ */
5
+ export declare const PROGRAM_ID: PublicKey;
6
+ export declare const ADMIN_PUBLIC_KEY: PublicKey;
7
+ export declare const MIZD_TOKEN_MINT: PublicKey;
8
+ export declare const DEFAULT_FEE_RECIPIENT: PublicKey;
9
+ export declare const MIZTAKE_STATISTICS_SEED = "miztake_statistics";
10
+ export declare const TOKEN_VAULT_SEED = "token_vault";
11
+ export declare const USER_STATS_SEED = "user_stats";
12
+ export declare const MIZTAKE_SEED = "miztake";
13
+ export declare const REFERRAL_PENALTY_CLAIM_SEED = "referral_penalty_claim";
14
+ export declare const DEFAULT_MIZTAKE_FEE = 100000;
15
+ export declare const DEFAULT_MAX_CLAIMABLE = 10000000000;
16
+ export declare const MIN_FEE = 10000;
17
+ export declare const MAX_FEE = 10000000;
18
+ export declare const MIN_MAX_CLAIMABLE = 1000000;
19
+ export declare const MAX_MAX_CLAIMABLE = 1000000000000;
20
+ export declare const MAX_USERNAME_LENGTH = 21;
21
+ export declare const MIN_HASH_LENGTH = 32;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MIN_HASH_LENGTH = exports.MAX_USERNAME_LENGTH = exports.MAX_MAX_CLAIMABLE = exports.MIN_MAX_CLAIMABLE = exports.MAX_FEE = exports.MIN_FEE = exports.DEFAULT_MAX_CLAIMABLE = exports.DEFAULT_MIZTAKE_FEE = exports.REFERRAL_PENALTY_CLAIM_SEED = exports.MIZTAKE_SEED = exports.USER_STATS_SEED = exports.TOKEN_VAULT_SEED = exports.MIZTAKE_STATISTICS_SEED = exports.DEFAULT_FEE_RECIPIENT = exports.MIZD_TOKEN_MINT = exports.ADMIN_PUBLIC_KEY = exports.PROGRAM_ID = void 0;
4
+ const web3_js_1 = require("@solana/web3.js");
5
+ /**
6
+ * Proof of Miztake Program Constants
7
+ */
8
+ // Program ID
9
+ exports.PROGRAM_ID = new web3_js_1.PublicKey("takeA82AwosboZgG7HQx8D3dGLbbbPUyQCva9HpLJBr");
10
+ // Admin public key
11
+ exports.ADMIN_PUBLIC_KEY = new web3_js_1.PublicKey("Lude4DE3jDXVViJ3s7bUfPXdXy8Zq7G1stMwHNQrc2s");
12
+ // MIZD Token Mint
13
+ exports.MIZD_TOKEN_MINT = new web3_js_1.PublicKey("mizdS9fDNUKWZcXBeRhJoJQHJJmsTeF4fFRyPdXWv99");
14
+ // Default fee recipient
15
+ exports.DEFAULT_FEE_RECIPIENT = new web3_js_1.PublicKey("Dkhu2VgGPfvXPVuohG2fPfryRacWMpNBJXvRGF1dhzUh");
16
+ // Seeds for PDAs
17
+ exports.MIZTAKE_STATISTICS_SEED = "miztake_statistics";
18
+ exports.TOKEN_VAULT_SEED = "token_vault";
19
+ exports.USER_STATS_SEED = "user_stats";
20
+ exports.MIZTAKE_SEED = "miztake";
21
+ exports.REFERRAL_PENALTY_CLAIM_SEED = "referral_penalty_claim";
22
+ // Default values
23
+ exports.DEFAULT_MIZTAKE_FEE = 100000; // 0.0001 SOL in lamports
24
+ exports.DEFAULT_MAX_CLAIMABLE = 10000000000; // 1000 tokens with 7 decimals
25
+ // Limits
26
+ exports.MIN_FEE = 10000; // 0.00001 SOL
27
+ exports.MAX_FEE = 10000000; // 0.01 SOL
28
+ exports.MIN_MAX_CLAIMABLE = 1000000; // 0.1 tokens
29
+ exports.MAX_MAX_CLAIMABLE = 1000000000000; // 100,000 tokens
30
+ exports.MAX_USERNAME_LENGTH = 21;
31
+ exports.MIN_HASH_LENGTH = 32;