cabalspy-id 0.0.0-stage → 0.5.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +146 -0
  2. package/LICENSE +21 -0
  3. package/README.md +504 -2
  4. package/dist/api.cjs +74 -0
  5. package/dist/api.cjs.map +1 -0
  6. package/dist/api.d.cts +193 -0
  7. package/dist/api.d.ts +193 -0
  8. package/dist/api.js +74 -0
  9. package/dist/api.js.map +1 -0
  10. package/dist/chunk-A2ZAFAWC.cjs +1 -0
  11. package/dist/chunk-A2ZAFAWC.cjs.map +1 -0
  12. package/dist/chunk-ABFIPCBY.cjs +109 -0
  13. package/dist/chunk-ABFIPCBY.cjs.map +1 -0
  14. package/dist/chunk-CJFIOVBA.js +1 -0
  15. package/dist/chunk-CJFIOVBA.js.map +1 -0
  16. package/dist/chunk-DBYCRBVS.js +400 -0
  17. package/dist/chunk-DBYCRBVS.js.map +1 -0
  18. package/dist/chunk-PKFIMBPW.cjs +444 -0
  19. package/dist/chunk-PKFIMBPW.cjs.map +1 -0
  20. package/dist/chunk-ULEBNY2A.cjs +1301 -0
  21. package/dist/chunk-ULEBNY2A.cjs.map +1 -0
  22. package/dist/chunk-VDFCMYLR.cjs +400 -0
  23. package/dist/chunk-VDFCMYLR.cjs.map +1 -0
  24. package/dist/chunk-VOTYIQAY.js +109 -0
  25. package/dist/chunk-VOTYIQAY.js.map +1 -0
  26. package/dist/chunk-XLBO2RSL.js +1301 -0
  27. package/dist/chunk-XLBO2RSL.js.map +1 -0
  28. package/dist/chunk-Z23B5TY6.js +444 -0
  29. package/dist/chunk-Z23B5TY6.js.map +1 -0
  30. package/dist/index.cjs +105 -0
  31. package/dist/index.cjs.map +1 -0
  32. package/dist/index.d.cts +21 -0
  33. package/dist/index.d.ts +21 -0
  34. package/dist/index.js +105 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/react.cjs +361 -0
  37. package/dist/react.cjs.map +1 -0
  38. package/dist/react.d.cts +315 -0
  39. package/dist/react.d.ts +315 -0
  40. package/dist/react.js +333 -0
  41. package/dist/react.js.map +1 -0
  42. package/dist/thesis.cjs +76 -0
  43. package/dist/thesis.cjs.map +1 -0
  44. package/dist/thesis.d.cts +221 -0
  45. package/dist/thesis.d.ts +221 -0
  46. package/dist/thesis.js +76 -0
  47. package/dist/thesis.js.map +1 -0
  48. package/dist/types-UZeY9n1e.d.cts +689 -0
  49. package/dist/types-UZeY9n1e.d.ts +689 -0
  50. package/dist/users-3DAw88_U.d.cts +767 -0
  51. package/dist/users-B4r33i4O.d.ts +767 -0
  52. package/dist/widget.cjs +233 -0
  53. package/dist/widget.cjs.map +1 -0
  54. package/dist/widget.d.cts +226 -0
  55. package/dist/widget.d.ts +226 -0
  56. package/dist/widget.js +233 -0
  57. package/dist/widget.js.map +1 -0
  58. package/package.json +145 -4
@@ -0,0 +1,689 @@
1
+ /**
2
+ * Response shapes of the CabalSpy ID Data API, derived from the server source.
3
+ *
4
+ * Every object type keeps an index signature-free shape for editor help, but
5
+ * the server may add fields later; nothing is stripped at runtime.
6
+ *
7
+ * Timestamps are ISO 8601 UTC strings ("2026-10-12T08:00:00Z"), except
8
+ * `PnlCalendarDay.date`, which is "DD-MM-YY" like the CabalSpy API.
9
+ */
10
+ declare const CHAINS: readonly ["solana", "bnb", "base", "eth", "rh"];
11
+ /** solana, bnb (BNB Chain), base, eth (Ethereum), rh (Robinhood Chain). */
12
+ type Chain = (typeof CHAINS)[number];
13
+ declare const PERIODS: readonly ["6h", "1d", "7d", "30d"];
14
+ type Period = (typeof PERIODS)[number];
15
+ declare const LEADERBOARD_SORTS: readonly ["volume", "pnl", "pnl_usd", "win_rate", "trades"];
16
+ type LeaderboardSort = (typeof LEADERBOARD_SORTS)[number];
17
+ type Currency = 'SOL' | 'BNB' | 'ETH';
18
+ type Scope = 'instance' | 'global';
19
+ declare const CURRENCY_BY_CHAIN: Record<Chain, Currency>;
20
+ interface ResponseMeta {
21
+ request_id: string;
22
+ cached: boolean;
23
+ cache_age_seconds: number;
24
+ version: string;
25
+ timestamp: string;
26
+ }
27
+ interface Pagination {
28
+ limit: number;
29
+ /** Absent on /wallets/history, `null` on /transactions/latest. */
30
+ total?: number | null;
31
+ has_more: boolean;
32
+ next_cursor: string | null;
33
+ }
34
+ /** Shape of every successful Data API response. */
35
+ interface ApiResponse<T> {
36
+ success: true;
37
+ data: T;
38
+ pagination?: Pagination;
39
+ meta: ResponseMeta;
40
+ }
41
+ interface HealthResponse {
42
+ status: 'ok' | 'degraded';
43
+ /** Seconds since the last trade sync; `null` when unknown. */
44
+ trade_sync_age_seconds: number | null;
45
+ }
46
+ interface MeResponse {
47
+ /** The registrable domain of a live instance, else the client_id. */
48
+ instance: string;
49
+ client_id: string;
50
+ mode: 'sandbox' | 'development' | 'live';
51
+ /** free | starter | pro | business | enterprise, or "development" for non-live apps. */
52
+ plan: string;
53
+ /** Requests per second; `null` = unlimited. */
54
+ rate_limit_rps: number | null;
55
+ /** Whether the key may open the live stream (Pro plan and up). */
56
+ websockets: boolean;
57
+ /** Whether `scope=global` is available (Enterprise). */
58
+ global_scope: boolean;
59
+ users_with_data: number;
60
+ }
61
+ interface VerifiedWallet {
62
+ address: string;
63
+ /** "solana" or "evm". */
64
+ family: string;
65
+ chains: Chain[];
66
+ verified_at: string | null;
67
+ }
68
+ interface Profile {
69
+ /** The user's id: the `sub` of their ID token. */
70
+ csid_id: string;
71
+ /** X handle; for people without X `tg.<telegram name>` (signed in with Telegram) or `id.<hex>` (signed in with Google). X handles never contain a dot. */
72
+ username: string;
73
+ name: string | null;
74
+ image_url: string | null;
75
+ bio: string | null;
76
+ /** https://x.com/<handle>; `null` for people without X. */
77
+ twitter: string | null;
78
+ /** https://t.me/<name> when Telegram is connected, else an empty string. */
79
+ telegram: string;
80
+ /** X stats; `null` for people who signed in with Telegram or Google and have not connected X. */
81
+ x: {
82
+ id: string;
83
+ followers: number | null;
84
+ following: number | null;
85
+ verified: boolean | null;
86
+ verified_type: string | null;
87
+ } | null;
88
+ kol: {
89
+ badge: boolean;
90
+ since: string | null;
91
+ };
92
+ profile_url: string;
93
+ wallets: VerifiedWallet[];
94
+ joined_at: string | null;
95
+ }
96
+ interface WalletLookupResponse {
97
+ address: string;
98
+ verified: true;
99
+ wallet: VerifiedWallet | null;
100
+ owner: Omit<Profile, 'wallets'>;
101
+ }
102
+ /** Trader block on trading responses. */
103
+ interface TraderProfile {
104
+ csid_id: string;
105
+ /** X handle; for people without X `tg.<telegram name>` (signed in with Telegram) or `id.<hex>` (signed in with Google). X handles never contain a dot. */
106
+ username: string;
107
+ name: string | null;
108
+ image_url: string | null;
109
+ /** https://x.com/<handle>; `null` for people without X. */
110
+ twitter: string | null;
111
+ /** https://t.me/<name> when Telegram is connected, else an empty string. */
112
+ telegram: string;
113
+ blockchain: Chain;
114
+ currency: Currency;
115
+ type: 'csid';
116
+ kol: boolean;
117
+ /** e.g. "13:00-22:00 UTC"; empty when there were no trades in 30 days. */
118
+ active_hours: string;
119
+ profile_url: string;
120
+ wallet_address?: string;
121
+ }
122
+ interface PeriodStats {
123
+ period: Period;
124
+ total_buy: number;
125
+ total_buy_usd: number;
126
+ total_sell: number;
127
+ total_sell_usd: number;
128
+ volume: number;
129
+ volume_usd: number;
130
+ /** Cash flow inside the window: sells minus buys. */
131
+ realized_pnl: number;
132
+ realized_pnl_usd: number;
133
+ realized_pnl_percentage: number;
134
+ buy_txn: number;
135
+ sell_txn: number;
136
+ largest_buy: number;
137
+ best_trade_pnl: number;
138
+ worst_trade_pnl: number;
139
+ win_count: number;
140
+ loss_count: number;
141
+ avg_hold_time_minutes: number;
142
+ }
143
+ interface WinRateBuckets {
144
+ above_500: number;
145
+ above_100_to_500: number;
146
+ above_zero_to_100: number;
147
+ below_zero: number;
148
+ }
149
+ interface WinRateDistribution extends WinRateBuckets {
150
+ win_count: number;
151
+ closed_count: number;
152
+ win_rate_percentage: number;
153
+ }
154
+ interface ActiveTokens {
155
+ active_tokens_count: number;
156
+ avg_position_size: number;
157
+ still_holding_count: number;
158
+ }
159
+ interface HistoryToken {
160
+ mint: string;
161
+ token_name: string | null;
162
+ token_symbol: string | null;
163
+ token_logo: string | null;
164
+ buy_value: number;
165
+ buy_value_usd: number;
166
+ sell_value: number;
167
+ sell_value_usd: number;
168
+ buy_tokens: number;
169
+ sell_tokens: number;
170
+ realized_pnl: number;
171
+ realized_pnl_usd: number;
172
+ realized_pnl_percentage: number;
173
+ currency: Currency;
174
+ remaining_tokens: number;
175
+ }
176
+ interface PnlChartPoint {
177
+ timestamp: string;
178
+ realized_pnl: number;
179
+ realized_pnl_usd: number;
180
+ }
181
+ interface TrackerTrade {
182
+ mint: string;
183
+ token_name: string | null;
184
+ token_logo: string | null;
185
+ transaction_type: 'buy' | 'sell';
186
+ buy_token: null;
187
+ sell_token: null;
188
+ buy_value?: number;
189
+ buy_value_usd?: number;
190
+ sell_value?: number;
191
+ sell_value_usd?: number;
192
+ token_amount: number | null;
193
+ created_at: string;
194
+ tx_signature: string;
195
+ }
196
+ interface TrackerResponse {
197
+ wallet: string;
198
+ profile: TraderProfile;
199
+ period_stats: PeriodStats;
200
+ period_win_rate_distribution: WinRateDistribution;
201
+ period_active_tokens: ActiveTokens;
202
+ period_history_tokens: HistoryToken[];
203
+ /** Cumulative realized PnL, starts at 0, one point every 15 minutes. */
204
+ period_realized_pnl_chart: PnlChartPoint[];
205
+ period_trades: TrackerTrade[];
206
+ }
207
+ interface LifetimeStats {
208
+ buy: number;
209
+ sell: number;
210
+ realized_pnl: number;
211
+ realized_pnl_percentage: number;
212
+ buy_txn: number;
213
+ sell_txn: number;
214
+ total_tx_count: number;
215
+ win_count: number;
216
+ total_sold: number;
217
+ win_rate: number;
218
+ active_tokens_count: number;
219
+ largest_buy: number;
220
+ best_trade_pnl: number;
221
+ worst_trade_pnl: number;
222
+ still_holding_count: number;
223
+ }
224
+ interface TokenOverview {
225
+ mint: string;
226
+ token_name: string | null;
227
+ token_logo: string | null;
228
+ invested: number;
229
+ sold: number;
230
+ realized_pnl: number;
231
+ realized_pnl_percentage: number;
232
+ currency: Currency;
233
+ buy_txn: number;
234
+ sell_txn: number;
235
+ first_seen: string;
236
+ last_seen: string;
237
+ still_holding: boolean;
238
+ }
239
+ interface HistoryTrade {
240
+ tx_signature: string;
241
+ mint: string;
242
+ token_name: string | null;
243
+ token_logo: string | null;
244
+ transaction_type: 'buy' | 'sell';
245
+ value: number;
246
+ value_usd: number;
247
+ currency: Currency;
248
+ created_at: string;
249
+ }
250
+ interface HistoryResponse {
251
+ lifetime_stats: LifetimeStats;
252
+ win_rate_distribution: WinRateBuckets;
253
+ token_overview: TokenOverview[];
254
+ /** One page of trades, newest first. */
255
+ trades: HistoryTrade[];
256
+ profile: TraderProfile;
257
+ }
258
+ interface HoldingToken {
259
+ token_address: string | null;
260
+ token_name: string | null;
261
+ symbol: string | null;
262
+ logo: string | null;
263
+ amount: string;
264
+ amount_formatted: string;
265
+ decimals: number | null;
266
+ price_usd: number;
267
+ value_usd: number;
268
+ verified_contract: boolean | null;
269
+ avg_buy_price_usd?: number | null;
270
+ unrealized_pnl_usd?: number | null;
271
+ }
272
+ interface HoldingsResponse {
273
+ wallet: string;
274
+ blockchain: Chain;
275
+ profile: TraderProfile;
276
+ active_holdings: {
277
+ tokens: HoldingToken[];
278
+ count: number;
279
+ total_value_usd: number;
280
+ last_updated: string;
281
+ stale: boolean;
282
+ loading: boolean;
283
+ };
284
+ }
285
+ interface PnlCalendarDay {
286
+ /** "DD-MM-YY" */
287
+ date: string;
288
+ realized_pnl: number;
289
+ realized_pnl_usd: number;
290
+ buy_txn: number;
291
+ sell_txn: number;
292
+ txn_total: number;
293
+ tokens_traded: number;
294
+ volume: number;
295
+ total_buy: number;
296
+ total_sell: number;
297
+ total_buy_usd: number;
298
+ total_sell_usd: number;
299
+ }
300
+ interface PnlCalendarMonth {
301
+ month: number;
302
+ year: number;
303
+ dates: PnlCalendarDay[];
304
+ realized_pnl: number;
305
+ realized_pnl_usd: number;
306
+ volume: number;
307
+ total_buy: number;
308
+ total_buy_usd: number;
309
+ total_sell: number;
310
+ total_sell_usd: number;
311
+ buy_txn: number;
312
+ sell_txn: number;
313
+ txn_total: number;
314
+ tokens_traded: number;
315
+ positive_streak: number;
316
+ negative_streak: number;
317
+ /** Only on the current month. */
318
+ current_positive_streak?: number;
319
+ current_negative_streak?: number;
320
+ }
321
+ interface PnlCalendarResponse {
322
+ wallet: string;
323
+ profile: TraderProfile;
324
+ /** Oldest first. */
325
+ months: PnlCalendarMonth[];
326
+ /** Days of the current month. */
327
+ dates: PnlCalendarDay[];
328
+ month: PnlCalendarMonth | null;
329
+ }
330
+ interface LeaderboardEntry {
331
+ rank: number;
332
+ wallet: string;
333
+ profile: {
334
+ csid_id: string;
335
+ username: string;
336
+ name: string | null;
337
+ image_url: string | null;
338
+ /** https://x.com/<handle>; `null` for people without X. */
339
+ twitter: string | null;
340
+ /** https://t.me/<name> when Telegram is connected, else an empty string. */
341
+ telegram: string;
342
+ blockchain: Chain;
343
+ currency: Currency;
344
+ wallet_type: 'csid';
345
+ kol: boolean;
346
+ public_trading: boolean;
347
+ };
348
+ period_stats: {
349
+ period: Period;
350
+ total_buy: number;
351
+ total_buy_usd: number;
352
+ total_sell: number;
353
+ total_sell_usd: number;
354
+ total_volume: number;
355
+ total_volume_usd: number;
356
+ realized_pnl: number;
357
+ realized_pnl_usd: number;
358
+ realized_pnl_percentage: number;
359
+ avg_hold_time_minutes: number;
360
+ total_txn: number;
361
+ buy_txn: number;
362
+ sell_txn: number;
363
+ };
364
+ period_win_rate_distribution: {
365
+ win_count: number;
366
+ closed_count: number;
367
+ win_rate_percentage: number;
368
+ };
369
+ period_history_tokens: {
370
+ total_tokens: number;
371
+ };
372
+ }
373
+ interface LeaderboardResponse {
374
+ blockchain: Chain;
375
+ type: 'csid';
376
+ scope: Scope;
377
+ period: Period;
378
+ sort: LeaderboardSort;
379
+ currency: Currency;
380
+ /** The leaderboard paginates inside `data`. */
381
+ pagination: Pagination;
382
+ leaderboard: LeaderboardEntry[];
383
+ }
384
+ /** Trader summary attached to trades. */
385
+ interface TradeOwner {
386
+ csid_id: string;
387
+ username: string;
388
+ name: string | null;
389
+ image_url: string | null;
390
+ kol: boolean;
391
+ }
392
+ /** One trade, from /transactions/latest and the live stream. */
393
+ interface Trade {
394
+ signature: string;
395
+ blockchain: Chain;
396
+ wallet: string;
397
+ mint: string;
398
+ transaction_type: 'buy' | 'sell';
399
+ token_symbol: string | null;
400
+ token_name: string | null;
401
+ token_logo: string | null;
402
+ token_amount: number | null;
403
+ value: number;
404
+ value_usd: number;
405
+ currency: Currency;
406
+ timestamp: string;
407
+ trader: TradeOwner | null;
408
+ }
409
+ /**
410
+ * `thesis` lets your app post theses and replies, react and report in the
411
+ * user's name (Data API writes with `csid_id`, or the public thesis API and
412
+ * the thesis widget with the user's access token).
413
+ */
414
+ type OidcScope = 'openid' | 'profile' | 'wallets' | 'onchain' | 'thesis' | 'offline_access';
415
+ interface XProfileClaim {
416
+ id: string;
417
+ username: string;
418
+ followers: number | null;
419
+ following: number | null;
420
+ posts: number | null;
421
+ listed: number | null;
422
+ verified: boolean | null;
423
+ verified_type: string | null;
424
+ created_at: string | null;
425
+ fetched_at: string;
426
+ }
427
+ interface WalletClaim {
428
+ address: string;
429
+ family: string;
430
+ chains: Chain[];
431
+ verified_at: string | null;
432
+ }
433
+ /**
434
+ * Claims of an ID token or a userinfo response.
435
+ *
436
+ * When the platform is over its plan's user limit, `sub` is an anonymous
437
+ * `anon_…` id, `limited` is true and no profile or wallet data is included.
438
+ */
439
+ interface CabalSpyIDClaims {
440
+ sub: string;
441
+ iss?: string;
442
+ aud?: string;
443
+ azp?: string;
444
+ exp?: number;
445
+ iat?: number;
446
+ auth_time?: number;
447
+ nonce?: string;
448
+ name?: string | null;
449
+ /** X handle; for people without X `tg.<telegram name>` (signed in with Telegram) or `id.<hex>` (signed in with Google). X handles never contain a dot. */
450
+ preferred_username?: string;
451
+ picture?: string | null;
452
+ /** https://id.cabalspy.xyz/u/<username> */
453
+ profile?: string;
454
+ kol_badge?: boolean;
455
+ /**
456
+ * Full X profile, userinfo only (scope `profile`). `null` for people who signed
457
+ * in with Telegram or Google and have not connected X. See hasX().
458
+ */
459
+ x?: XProfileClaim | null;
460
+ /** Verified wallets, userinfo only (scope `wallets`). */
461
+ wallets?: WalletClaim[];
462
+ /** Latest 7-day on-chain volume, userinfo only (scope `onchain`). */
463
+ onchain?: {
464
+ volume_7d_usd: number;
465
+ checked_at: string;
466
+ } | null;
467
+ /** A made-up sandbox identity; never trust it in production. */
468
+ sandbox?: boolean;
469
+ /** True when the platform is over its plan's user limit. */
470
+ limited?: boolean;
471
+ reason?: 'plan_user_limit' | string;
472
+ message?: string;
473
+ [claim: string]: unknown;
474
+ }
475
+ interface TokenResponse {
476
+ access_token: string;
477
+ token_type: 'Bearer';
478
+ /** Seconds. Access and ID tokens live 15 minutes. */
479
+ expires_in: number;
480
+ id_token: string;
481
+ scope: string;
482
+ /** Only with the `offline_access` scope. Rotates on every refresh. */
483
+ refresh_token?: string;
484
+ }
485
+ interface DiscoveryDocument {
486
+ issuer: string;
487
+ authorization_endpoint: string;
488
+ token_endpoint: string;
489
+ userinfo_endpoint: string;
490
+ jwks_uri: string;
491
+ revocation_endpoint: string;
492
+ scopes_supported: string[];
493
+ response_types_supported: string[];
494
+ grant_types_supported: string[];
495
+ id_token_signing_alg_values_supported: string[];
496
+ token_endpoint_auth_methods_supported: string[];
497
+ code_challenge_methods_supported: string[];
498
+ claims_supported: string[];
499
+ [key: string]: unknown;
500
+ }
501
+ type WebhookEventType = 'wallet.verified' | 'wallet.removed' | 'profile.updated' | 'kol.granted' | 'consent.revoked' | 'thesis.created' | 'thesis.reply' | 'thesis.reaction' | 'webhook.test';
502
+ interface WebhookEvent<D = Record<string, unknown>> {
503
+ /** Event id; the same on every retry. Dedupe on it. */
504
+ id: string;
505
+ type: WebhookEventType | string;
506
+ created_at: string;
507
+ sandbox: boolean;
508
+ /** Always carries `sub` (the user's CabalSpy ID), except for webhook.test. */
509
+ data: D & {
510
+ sub?: string;
511
+ };
512
+ }
513
+ /** `data` of a `thesis.created` webhook: one of your users posted a thesis. */
514
+ interface ThesisCreatedData {
515
+ sub: string;
516
+ thesis: Thesis;
517
+ }
518
+ /** `data` of a `thesis.reply` webhook: one of your users replied to a thesis. */
519
+ interface ThesisReplyData {
520
+ sub: string;
521
+ reply: Thesis;
522
+ }
523
+ /** `data` of a `thesis.reaction` webhook: one of your users added or removed a reaction. */
524
+ interface ThesisReactionData {
525
+ sub: string;
526
+ thesis_id: string;
527
+ chain: Chain;
528
+ mint: string;
529
+ emoji: ThesisEmoji | string;
530
+ /** true: added, false: removed. */
531
+ on: boolean;
532
+ /** The new counts. */
533
+ reactions: Partial<Record<ThesisEmoji | string, number>>;
534
+ }
535
+ type ThesisStance = 'bull' | 'bear';
536
+ type ThesisSort = 'new' | 'top';
537
+ /** Reactions: `like` plus a fixed emoji set. */
538
+ declare const THESIS_EMOJIS: readonly ["like", "🔥", "🚀", "💎", "👀", "😂", "🤝", "💀", "🐂", "🐻"];
539
+ type ThesisEmoji = (typeof THESIS_EMOJIS)[number];
540
+ declare const THESIS_REPORT_REASONS: readonly ["spam", "scam", "abuse", "impersonation", "other"];
541
+ type ThesisReportReason = (typeof THESIS_REPORT_REASONS)[number];
542
+ /** Maximum thesis / reply length in characters. */
543
+ declare const THESIS_MAX_BODY = 1000;
544
+ interface ThesisAuthor {
545
+ csid_id: string;
546
+ /** X handle; for people without X `tg.<telegram name>` (signed in with Telegram) or `id.<hex>` (signed in with Google). X handles never contain a dot. */
547
+ username: string | null;
548
+ name: string | null;
549
+ image_url: string | null;
550
+ kol_badge: boolean;
551
+ profile_url: string | null;
552
+ }
553
+ interface ThesisPnl {
554
+ invested_usd: number;
555
+ realized_usd: number;
556
+ holding_value_usd: number | null;
557
+ pnl_usd: number | null;
558
+ pnl_percent: number | null;
559
+ }
560
+ /** The author's real position in the token, when they show their trading publicly. */
561
+ interface ThesisPositionPublic {
562
+ /** What was bought and sold when the thesis was posted. */
563
+ at_post: ThesisPnl & {
564
+ tokens_held: number | null;
565
+ first_buy: string | null;
566
+ };
567
+ /** Live: PnL now, and whether the author still holds or sold. */
568
+ now: (ThesisPnl & {
569
+ tokens_held: number;
570
+ status: 'holding' | 'sold';
571
+ }) | null;
572
+ }
573
+ /** The author keeps their trading private: only that they hold the token. */
574
+ interface ThesisPositionPrivate {
575
+ private: true;
576
+ }
577
+ type ThesisPosition = ThesisPositionPublic | ThesisPositionPrivate;
578
+ /** Who reacted recently (newest first). */
579
+ interface ThesisReactor {
580
+ emoji: ThesisEmoji | string;
581
+ csid_id: string;
582
+ username: string | null;
583
+ at: string;
584
+ }
585
+ /** A thesis (`kind: 'thesis'`) or a reply to one (`kind: 'reply'`). */
586
+ interface Thesis {
587
+ id: string;
588
+ chain: Chain;
589
+ mint: string;
590
+ kind: 'thesis' | 'reply';
591
+ /** The thesis a reply belongs to. */
592
+ parent_id: string | null;
593
+ body: string;
594
+ stance: ThesisStance | null;
595
+ author: ThesisAuthor;
596
+ likes: number;
597
+ replies: number;
598
+ /** Count per reaction, e.g. { like: 3, '🚀': 1 }. */
599
+ reactions: Partial<Record<ThesisEmoji | string, number>>;
600
+ /** The latest reactions with who reacted, newest first. */
601
+ reacted_by: ThesisReactor[];
602
+ /** The viewer's own reactions (pass `csid_id` / an access token to get them). */
603
+ my_reactions: string[];
604
+ /** Name of the app it was posted through. */
605
+ via: string | null;
606
+ created_at: string;
607
+ is_mine: boolean;
608
+ /** Theses only (not replies). */
609
+ position?: ThesisPosition;
610
+ /** Theses only: a preview of the newest replies, oldest first. */
611
+ latest_replies?: Thesis[];
612
+ }
613
+ interface ThesisToken {
614
+ chain: Chain;
615
+ mint: string;
616
+ name: string | null;
617
+ symbol: string | null;
618
+ logo: string | null;
619
+ price_usd: number | null;
620
+ }
621
+ interface ThesisCounts {
622
+ theses: number;
623
+ bull: number;
624
+ bear: number;
625
+ authors: number;
626
+ }
627
+ interface ThesisSummary extends ThesisCounts {
628
+ token: ThesisToken;
629
+ }
630
+ interface ThesisEligibility {
631
+ /** Holders only: one of the user's verified wallets bought and still holds the token. */
632
+ can_post: boolean;
633
+ can_reply: boolean;
634
+ holder: boolean | null;
635
+ /** Posting paused after several hidden posts. */
636
+ paused: boolean;
637
+ /** Data API: why the user can't write for your app (consent_required, plan_user_limit, sandbox_user). */
638
+ reason?: string;
639
+ }
640
+ /** Moderation notification kinds. */
641
+ type ThesisNotificationKind = 'post_hidden' | 'paused' | 'banned' | 'post_removed' | 'post_restored' | 'appeal_accepted' | 'appeal_rejected' | 'reporting_paused';
642
+ /** A moderation notification (warning, hidden post, pause, appeal result …). */
643
+ interface ThesisNotification {
644
+ id: number;
645
+ kind: ThesisNotificationKind | string;
646
+ /** Ready-to-show English text. */
647
+ text: string;
648
+ /** Details, e.g. thesis_id, until, note. */
649
+ data: Record<string, unknown>;
650
+ created_at: string;
651
+ read: boolean;
652
+ /** Can be appealed (within 30 days). */
653
+ appealable: boolean;
654
+ /** Appeal status when appealed: pending | accepted | rejected; null otherwise. */
655
+ appeal: 'pending' | 'accepted' | 'rejected' | string | null;
656
+ }
657
+ interface ThesisNotifications {
658
+ unread: number;
659
+ items: ThesisNotification[];
660
+ }
661
+ /** Unread moderation notices of the signed-in user (public API `me()`). */
662
+ interface ThesisNotices {
663
+ unread: number;
664
+ /** Text of the newest notice when there are unread ones. */
665
+ latest: string | null;
666
+ /** Where the user reads all notices on id.cabalspy.xyz. */
667
+ url: string;
668
+ }
669
+ interface ThesisReactions {
670
+ id: string;
671
+ reactions: Partial<Record<ThesisEmoji | string, number>>;
672
+ }
673
+ /** Stream / live payload of a new thesis or reply. */
674
+ type ThesisLiveItem = Thesis & {
675
+ blockchain?: Chain;
676
+ };
677
+ /** Stream / live payload of a removed (deleted or hidden) post. */
678
+ interface ThesisRemoved {
679
+ id: string;
680
+ blockchain?: Chain;
681
+ mint?: string;
682
+ }
683
+ /** Stream / live payload of changed reaction counts. */
684
+ type ThesisReactionUpdate = ThesisReactions & {
685
+ blockchain?: Chain;
686
+ mint?: string;
687
+ };
688
+
689
+ export { type ThesisReactor as $, type ActiveTokens as A, type ThesisCounts as B, CHAINS as C, type DiscoveryDocument as D, type ThesisCreatedData as E, type ThesisEligibility as F, type ThesisEmoji as G, type HealthResponse as H, type ThesisLiveItem as I, type ThesisNotices as J, type ThesisNotification as K, LEADERBOARD_SORTS as L, type MeResponse as M, type ThesisNotificationKind as N, type OidcScope as O, PERIODS as P, type ThesisNotifications as Q, type ResponseMeta as R, type Scope as S, THESIS_EMOJIS as T, type ThesisPnl as U, type ThesisPosition as V, type ThesisPositionPrivate as W, type ThesisPositionPublic as X, type ThesisReactionData as Y, type ThesisReactionUpdate as Z, type ThesisReactions as _, type ApiResponse as a, type ThesisRemoved as a0, type ThesisReplyData as a1, type ThesisReportReason as a2, type ThesisSort as a3, type ThesisStance as a4, type ThesisSummary as a5, type ThesisToken as a6, type TokenOverview as a7, type TokenResponse as a8, type TrackerResponse as a9, type TrackerTrade as aa, type Trade as ab, type TradeOwner as ac, type TraderProfile as ad, type VerifiedWallet as ae, type WalletClaim as af, type WalletLookupResponse as ag, type WebhookEvent as ah, type WebhookEventType as ai, type WinRateBuckets as aj, type WinRateDistribution as ak, type XProfileClaim as al, CURRENCY_BY_CHAIN as b, type CabalSpyIDClaims as c, type Chain as d, type Currency as e, type HistoryResponse as f, type HistoryToken as g, type HistoryTrade as h, type HoldingToken as i, type HoldingsResponse as j, type LeaderboardEntry as k, type LeaderboardResponse as l, type LeaderboardSort as m, type LifetimeStats as n, type Pagination as o, type Period as p, type PeriodStats as q, type PnlCalendarDay as r, type PnlCalendarMonth as s, type PnlCalendarResponse as t, type PnlChartPoint as u, type Profile as v, THESIS_MAX_BODY as w, THESIS_REPORT_REASONS as x, type Thesis as y, type ThesisAuthor as z };