@coffer_so/sdk 1.2.0 → 1.3.1

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.
@@ -148,6 +148,9 @@ export interface EpochHistoryEntry {
148
148
  swapXpPerUsdLpFee: number;
149
149
  lpXpPerUsd: number;
150
150
  isCurrent: boolean;
151
+ /** A frozen snapshot of the board at this epoch's end exists (see getLeaderboardEpochSnapshot). */
152
+ finalized: boolean;
153
+ finalizedAt: string | null;
151
154
  }
152
155
 
153
156
  export interface LeaderboardEpochResponse {
@@ -162,7 +165,14 @@ export interface LeaderboardEpochResponse {
162
165
  };
163
166
  currentRates: {
164
167
  swapXpPerUsdLpFee: number;
168
+ /** XP per $1 of liquidity per 3-hour accrual tick. */
165
169
  lpXpPerUsd: number;
170
+ /**
171
+ * XP per $1 of liquidity per DAY (lpXpPerUsd × 8 ticks) — the "+N XP"
172
+ * badge next to APY. Every pool object returned by /api/pools also
173
+ * carries it as `lpXpPerUsdPerDay`, so a pools table needs no second call.
174
+ */
175
+ lpXpPerUsdPerDay: number;
166
176
  };
167
177
  epochs: EpochHistoryEntry[];
168
178
  }
@@ -246,6 +256,114 @@ export interface CampaignTopResponse {
246
256
  data: CampaignTopEntry[];
247
257
  }
248
258
 
259
+ // ── Frozen snapshot types (finished epochs and campaigns) ──
260
+
261
+ /** How an epoch snapshot was produced; `rewind` rows carry a small reconstruction risk, `capture` rows none. */
262
+ export type EpochSnapshotMethod = "capture" | "rewind";
263
+
264
+ export interface EpochSnapshotEntry {
265
+ /** Positional place in the frozen board (same ordering as the live list). */
266
+ place: number;
267
+ address: string;
268
+ /** Cumulative XP at the epoch's end — all epochs so far, referral bonuses included. */
269
+ points: number;
270
+ /** XP earned inside this epoch alone (points minus the previous epoch's frozen points). */
271
+ epochPoints: number;
272
+ }
273
+
274
+ /**
275
+ * The leaderboard exactly as it stood the instant an epoch ended. Frozen
276
+ * once, never recomputed — the live board keeps moving, this does not.
277
+ */
278
+ export interface EpochSnapshotResponse {
279
+ epoch: number;
280
+ start: string;
281
+ /** Exclusive: the instant the next epoch began. */
282
+ end: string;
283
+ finalizedAt: string;
284
+ method: EpochSnapshotMethod;
285
+ multiplier: number;
286
+ /** Everyone on the board at the time, zero-XP users included (as live). */
287
+ totalUsers: number;
288
+ totalXp: number;
289
+ /** Same as totalUsers — pagination total. */
290
+ total: number;
291
+ page: number;
292
+ limit: number;
293
+ data: EpochSnapshotEntry[];
294
+ }
295
+
296
+ export interface EpochSnapshotUser {
297
+ epoch: number;
298
+ place: number;
299
+ address: string;
300
+ points: number;
301
+ epochPoints: number;
302
+ /** Board size, for "N of M". */
303
+ totalUsers: number;
304
+ }
305
+
306
+ export interface CampaignHistoryEntry {
307
+ campaign: string;
308
+ startsAt: string;
309
+ endsAt: string;
310
+ prizePoolUsd: number;
311
+ prizes: CampaignPrizeTier[];
312
+ /** The window has closed. */
313
+ ended: boolean;
314
+ /**
315
+ * Frozen results exist. They appear a grace period (24h) after `endsAt`;
316
+ * until then keep using the live campaign endpoints for this campaign.
317
+ */
318
+ finalized: boolean;
319
+ finalizedAt: string | null;
320
+ /** Null until finalized. */
321
+ totalParticipants: number | null;
322
+ totalRanked: number | null;
323
+ }
324
+
325
+ export interface CampaignSnapshotTopEntry {
326
+ /** Continuous across pages. */
327
+ place: number;
328
+ address: string;
329
+ swapXp: number;
330
+ swapVolumeUsd: number;
331
+ /** Prize for this place under the campaign's tiers — already resolved, no client-side join. */
332
+ prizeUsd: number;
333
+ joinedAt: string;
334
+ }
335
+
336
+ export interface CampaignSnapshotTopResponse {
337
+ campaign: string;
338
+ startsAt: string;
339
+ /** Inclusive. */
340
+ endsAt: string;
341
+ finalizedAt: string;
342
+ prizePoolUsd: number;
343
+ prizes: CampaignPrizeTier[];
344
+ rates: { swapXpPerUsdLpFee: number };
345
+ /** Everyone who joined, ranked or not. */
346
+ totalParticipants: number;
347
+ /** Ranked participants (swap XP > 0) — pagination total. */
348
+ total: number;
349
+ page: number;
350
+ limit: number;
351
+ data: CampaignSnapshotTopEntry[];
352
+ }
353
+
354
+ export interface CampaignSnapshotUserResponse {
355
+ campaign: string;
356
+ participating: boolean;
357
+ /** Null when the wallet did not join or earned no swap XP. */
358
+ place: number | null;
359
+ swapXp: number;
360
+ swapVolumeUsd: number;
361
+ prizeUsd: number;
362
+ joinedAt: string | null;
363
+ /** Table size for "N of M". */
364
+ totalRanked: number;
365
+ }
366
+
249
367
  // ── Platform stats ──
250
368
 
251
369
  export interface PlatformStatsResponse {
@@ -1005,6 +1123,86 @@ export class CofferBackendClient {
1005
1123
  return this.get<CampaignTopResponse>(`/api/campaign/top?${qs.toString()}`);
1006
1124
  }
1007
1125
 
1126
+ // ── Frozen snapshots: finished epochs and campaigns ──
1127
+
1128
+ /**
1129
+ * The leaderboard as it stood the instant `epoch` ended — frozen, never
1130
+ * recomputed. Only finished epochs: the current one is a 404, as is an
1131
+ * epoch whose snapshot is not finalized yet (check
1132
+ * `epochs[].finalized` from getLeaderboardEpoch). Paginated like
1133
+ * getLeaderboard. Safe to cache forever once received.
1134
+ */
1135
+ getLeaderboardEpochSnapshot(
1136
+ epoch: number,
1137
+ page: number = 1,
1138
+ limit: number = 20,
1139
+ ): Promise<SdkResult<EpochSnapshotResponse>> {
1140
+ const qs = new URLSearchParams({
1141
+ page: String(page),
1142
+ limit: String(limit),
1143
+ });
1144
+ return this.get<EpochSnapshotResponse>(
1145
+ `/api/leaderboard/epoch/${epoch}?${qs.toString()}`,
1146
+ );
1147
+ }
1148
+
1149
+ /**
1150
+ * A wallet's frozen place and points at the end of `epoch` — the place
1151
+ * it held in THAT snapshot, not its live rank today. 404 if the wallet
1152
+ * was not on the board at the time. Same envelope as getLeaderboardUser.
1153
+ */
1154
+ getLeaderboardEpochUser(
1155
+ epoch: number,
1156
+ address: string,
1157
+ ): Promise<SdkResult<EpochSnapshotUser>> {
1158
+ return this.getDataField<EpochSnapshotUser>(
1159
+ `/api/leaderboard/epoch/${epoch}/user/${encodeURIComponent(address)}`,
1160
+ );
1161
+ }
1162
+
1163
+ /**
1164
+ * Every campaign, newest first, with whether its frozen results exist.
1165
+ * A campaign is `ended` the moment its window closes and `finalized` a
1166
+ * grace period (24h) later; in between, the live campaign endpoints
1167
+ * still serve its (already fixed) standings.
1168
+ */
1169
+ getCampaignHistory(): Promise<SdkResult<CampaignHistoryEntry[]>> {
1170
+ return this.get<CampaignHistoryEntry[]>("/api/campaign/history");
1171
+ }
1172
+
1173
+ /**
1174
+ * Frozen final standings of a finished campaign, prizes resolved per
1175
+ * row. Same shape as getCampaignTop plus `prizeUsd` and the tiers in
1176
+ * the header, so a results page needs one call. 404 until finalized.
1177
+ */
1178
+ getCampaignSnapshotTop(
1179
+ campaign: string,
1180
+ page: number = 1,
1181
+ limit: number = 20,
1182
+ ): Promise<SdkResult<CampaignSnapshotTopResponse>> {
1183
+ const qs = new URLSearchParams({
1184
+ page: String(page),
1185
+ limit: String(limit),
1186
+ });
1187
+ return this.get<CampaignSnapshotTopResponse>(
1188
+ `/api/campaign/${encodeURIComponent(campaign)}/top?${qs.toString()}`,
1189
+ );
1190
+ }
1191
+
1192
+ /**
1193
+ * A wallet's frozen result in a finished campaign, by address (no auth —
1194
+ * results are public once frozen). Mirrors getCampaignRank. 404 until
1195
+ * the campaign is finalized.
1196
+ */
1197
+ getCampaignSnapshotUser(
1198
+ campaign: string,
1199
+ address: string,
1200
+ ): Promise<SdkResult<CampaignSnapshotUserResponse>> {
1201
+ return this.get<CampaignSnapshotUserResponse>(
1202
+ `/api/campaign/${encodeURIComponent(campaign)}/user/${encodeURIComponent(address)}`,
1203
+ );
1204
+ }
1205
+
1008
1206
  getTokenPrices(mints: string[]): Promise<SdkResult<PriceMap>> {
1009
1207
  const qs = new URLSearchParams({ mints: mints.join(",") });
1010
1208
  return this.get<PriceMap>(`/api/prices?${qs.toString()}`);
package/src/index.ts CHANGED
@@ -201,6 +201,14 @@ export type {
201
201
  CampaignRankResponse,
202
202
  CampaignTopEntry,
203
203
  CampaignTopResponse,
204
+ EpochSnapshotMethod,
205
+ EpochSnapshotEntry,
206
+ EpochSnapshotResponse,
207
+ EpochSnapshotUser,
208
+ CampaignHistoryEntry,
209
+ CampaignSnapshotTopEntry,
210
+ CampaignSnapshotTopResponse,
211
+ CampaignSnapshotUserResponse,
204
212
  PortfolioChartMetric,
205
213
  PortfolioChartRange,
206
214
  PortfolioChartResponse,