kobe_client/
types.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4pub mod bam_epoch_metrics;
5pub mod bam_validators;
6
7/// Staker rewards response from the API
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct StakerRewardsResponse {
10    pub rewards: Vec<StakerReward>,
11    pub total: Option<u64>,
12}
13
14/// Individual staker reward entry
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct StakerReward {
17    /// The public key of the stake account
18    pub stake_account: String,
19
20    /// The stake authority
21    pub stake_authority: String,
22
23    /// The withdraw authority
24    pub withdraw_authority: String,
25
26    /// Epoch when the reward was earned
27    pub epoch: u64,
28
29    /// MEV rewards in lamports
30    pub mev_rewards: u64,
31
32    /// Priority fee rewards in lamports
33    pub priority_fee_rewards: Option<u64>,
34
35    /// Whether MEV rewards have been claimed
36    pub mev_claimed: bool,
37
38    /// Whether priority fee rewards have been claimed
39    pub priority_fee_claimed: Option<bool>,
40
41    /// Validator vote account
42    pub vote_account: String,
43}
44
45/// Validator rewards response
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct ValidatorRewardsResponse {
48    pub validators: Vec<ValidatorReward>,
49}
50
51/// Validator reward entry
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct ValidatorReward {
54    /// Validator vote account public key
55    pub vote_account: String,
56
57    /// Epoch
58    pub epoch: u64,
59
60    /// MEV commission in basis points (10000 = 100%)
61    pub mev_commission_bps: u16,
62
63    /// Total MEV rewards in lamports
64    pub mev_rewards: u64,
65
66    /// Priority fee commission in basis points
67    pub priority_fee_commission_bps: Option<u16>,
68
69    /// Total priority fee rewards in lamports
70    pub priority_fee_rewards: Option<u64>,
71
72    /// Number of stakers
73    pub num_stakers: Option<u64>,
74
75    /// Total active stake
76    pub active_stake: Option<u64>,
77}
78
79/// Response for validators endpoint
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct ValidatorsResponse {
82    pub validators: Vec<ValidatorInfo>,
83}
84
85/// Validator information
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct ValidatorInfo {
88    /// Validator vote account
89    pub vote_account: String,
90
91    /// MEV commission in basis points
92    pub mev_commission_bps: Option<u16>,
93
94    /// MEV rewards for the epoch (lamports)
95    pub mev_rewards: Option<u64>,
96
97    /// Priority fee commission in basis points
98    pub priority_fee_commission_bps: Option<u16>,
99
100    /// Priority fee rewards (lamports)
101    pub priority_fee_rewards: Option<u64>,
102
103    /// Whether the validator is running Jito
104    pub running_jito: bool,
105
106    /// Whether the validator is running BAM
107    pub running_bam: Option<bool>,
108
109    /// Active stake amount (lamports)
110    pub active_stake: u64,
111}
112
113/// Validator data for a specific epoch
114#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct ValidatorByVoteAccount {
116    /// Epoch
117    pub epoch: u64,
118
119    /// MEV commission in basis points
120    pub mev_commission_bps: u16,
121
122    /// MEV rewards (lamports)
123    pub mev_rewards: u64,
124
125    /// Priority fee commission in basis points
126    pub priority_fee_commission_bps: u16,
127
128    /// Priority fee rewards (lamports)
129    pub priority_fee_rewards: u64,
130}
131
132/// MEV rewards network statistics
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct MevRewards {
135    /// Epoch number
136    pub epoch: u64,
137
138    /// Total network MEV in lamports
139    pub total_network_mev_lamports: u64,
140
141    /// Jito stake weight in lamports
142    pub jito_stake_weight_lamports: u64,
143
144    /// MEV reward per lamport staked
145    pub mev_reward_per_lamport: f64,
146}
147
148/// Daily MEV tips data
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct DailyMevRewards {
151    /// Date of the tips
152    pub day: DateTime<Utc>,
153
154    /// Number of MEV tips
155    pub count_mev_tips: u64,
156
157    /// Jito tips amount (SOL)
158    pub jito_tips: f64,
159
160    /// Number of unique tippers
161    pub tippers: u64,
162
163    /// Validator tips amount (SOL)
164    pub validator_tips: f64,
165}
166
167/// Jito stake over time data
168#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct JitoStakeOverTime {
170    /// Map of epoch to stake ratio
171    pub stake_ratio_over_time: std::collections::HashMap<String, f64>,
172}
173
174/// MEV commission average over time
175#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct MevCommissionAverageOverTime {
177    /// Aggregated MEV rewards
178    pub aggregated_mev_rewards: u64,
179
180    /// MEV rewards time series
181    pub mev_rewards: Vec<TimeSeriesData<u64>>,
182
183    /// Total value locked time series
184    pub tvl: Vec<TimeSeriesData<u64>>,
185
186    /// APY time series
187    pub apy: Vec<TimeSeriesData<f64>>,
188
189    /// Number of validators time series
190    pub num_validators: Vec<TimeSeriesData<u64>>,
191
192    /// JitoSOL supply time series
193    pub supply: Vec<TimeSeriesData<f64>>,
194}
195
196/// Time series data point
197#[derive(Debug, Clone, Serialize, Deserialize)]
198pub struct TimeSeriesData<T> {
199    /// Data value
200    pub data: T,
201
202    /// Timestamp
203    pub date: DateTime<Utc>,
204}
205
206/// JitoSOL to SOL ratio data
207#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct JitoSolRatio {
209    /// Time series of ratio data
210    pub ratios: Vec<TimeSeriesData<f64>>,
211}
212
213/// Stake pool statistics response
214#[derive(Debug, Clone, Serialize, Deserialize)]
215pub struct StakePoolStats {
216    /// Total aggregated MEV rewards across all time periods (lamports)
217    pub aggregated_mev_rewards: u64,
218
219    /// Time series data of MEV rewards
220    pub mev_rewards: Vec<TimeSeriesData<u64>>,
221
222    /// Time series data of Total Value Locked (lamports)
223    pub tvl: Vec<TimeSeriesData<u64>>,
224
225    /// Time series data of Annual Percentage Yield (decimal, e.g., 0.07 = 7%)
226    pub apy: Vec<TimeSeriesData<f64>>,
227
228    /// Time series data of validator count
229    pub num_validators: Vec<TimeSeriesData<u64>>,
230
231    /// Time series data of JitoSOL token supply
232    pub supply: Vec<TimeSeriesData<f64>>,
233}
234
235/// BAM Delegation Blacklist response
236#[derive(Debug, Clone, Serialize, Deserialize)]
237pub struct BamDelegationBlacklistEntry {
238    /// Vote account address
239    pub vote_account: String,
240
241    /// Added epoch
242    pub added_epoch: u64,
243}
244
245// ============================================================================
246// Request Types
247// ============================================================================
248
249/// Request parameters for epoch-based queries
250#[derive(Debug, Clone, Serialize, Deserialize)]
251pub struct EpochRequest {
252    /// Epoch number
253    pub epoch: u64,
254}
255
256/// Range filter for time-based queries
257#[derive(Debug, Clone, Serialize, Deserialize)]
258pub struct RangeFilter {
259    /// Start time (ISO 8601 format)
260    pub start: DateTime<Utc>,
261
262    /// End time (ISO 8601 format)
263    pub end: DateTime<Utc>,
264}
265
266/// Request with range filter
267#[derive(Debug, Clone, Serialize, Deserialize)]
268pub struct RangeRequest {
269    /// Time range filter
270    pub range_filter: RangeFilter,
271}
272
273/// Sort configuration for stake pool stats
274#[derive(Debug, Clone, Serialize, Deserialize)]
275pub struct SortBy {
276    /// Sort field (currently only "BlockTime" is supported)
277    pub field: String,
278
279    /// Sort order: "Asc" or "Desc"
280    pub order: String,
281}
282
283impl Default for SortBy {
284    fn default() -> Self {
285        Self {
286            field: "BlockTime".to_string(),
287            order: "Asc".to_string(),
288        }
289    }
290}
291
292/// Request for stake pool statistics
293#[derive(Debug, Clone, Serialize, Deserialize, Default)]
294pub struct StakePoolStatsRequest {
295    /// Time bucket aggregation type (currently only "Daily" is supported)
296    #[serde(skip_serializing_if = "Option::is_none")]
297    pub bucket_type: Option<String>,
298
299    /// Date range filter
300    #[serde(skip_serializing_if = "Option::is_none")]
301    pub range_filter: Option<RangeFilter>,
302
303    /// Sort configuration
304    #[serde(skip_serializing_if = "Option::is_none")]
305    pub sort_by: Option<SortBy>,
306}
307
308impl StakePoolStatsRequest {
309    /// Create a new request with default values
310    pub fn new() -> Self {
311        Self::default()
312    }
313
314    /// Set bucket type (currently only "Daily" is supported)
315    pub fn with_bucket_type(mut self, bucket_type: impl Into<String>) -> Self {
316        self.bucket_type = Some(bucket_type.into());
317        self
318    }
319
320    /// Set range filter
321    pub fn with_range_filter(mut self, start: DateTime<Utc>, end: DateTime<Utc>) -> Self {
322        self.range_filter = Some(RangeFilter { start, end });
323        self
324    }
325
326    /// Set sort configuration
327    pub fn with_sort_by(mut self, field: impl Into<String>, order: impl Into<String>) -> Self {
328        self.sort_by = Some(SortBy {
329            field: field.into(),
330            order: order.into(),
331        });
332        self
333    }
334
335    /// Set sort order to ascending
336    pub fn sort_asc(mut self) -> Self {
337        if let Some(ref mut sort) = self.sort_by {
338            sort.order = "Asc".to_string();
339        } else {
340            self.sort_by = Some(SortBy {
341                field: "BlockTime".to_string(),
342                order: "Asc".to_string(),
343            });
344        }
345        self
346    }
347
348    /// Set sort order to descending
349    pub fn sort_desc(mut self) -> Self {
350        if let Some(ref mut sort) = self.sort_by {
351            sort.order = "Desc".to_string();
352        } else {
353            self.sort_by = Some(SortBy {
354                field: "BlockTime".to_string(),
355                order: "Desc".to_string(),
356            });
357        }
358        self
359    }
360}
361
362/// Validator history account data
363#[derive(Debug, Clone, Serialize, Deserialize)]
364pub struct ValidatorHistoryAccount {
365    /// Validator vote account
366    pub vote_account: String,
367
368    /// Historical entries
369    pub history: Vec<ValidatorHistoryEntry>,
370}
371
372/// Single validator history entry for an epoch
373#[derive(Debug, Clone, Serialize, Deserialize)]
374pub struct ValidatorHistoryEntry {
375    /// Epoch
376    pub epoch: u64,
377
378    /// Vote credits earned
379    pub vote_credits: Option<u32>,
380
381    /// Validator commission
382    pub commission: Option<u8>,
383
384    /// MEV commission in basis points
385    pub mev_commission_bps: Option<u16>,
386
387    /// Validator version
388    pub version: Option<String>,
389
390    /// Client type
391    pub client_type: Option<String>,
392
393    /// Active stake
394    pub active_stake: Option<u64>,
395
396    /// Stake rank
397    pub stake_rank: Option<u32>,
398
399    /// Whether validator is in superminority
400    pub is_superminority: Option<bool>,
401
402    /// IP address
403    pub ip_address: Option<String>,
404}
405
406/// Steward configuration
407#[derive(Debug, Clone, Serialize, Deserialize)]
408pub struct StewardConfig {
409    /// Stake pool address
410    pub stake_pool: String,
411
412    /// Authority
413    pub authority: String,
414
415    /// Scoring parameters
416    pub scoring_params: ScoringParams,
417}
418
419/// Scoring parameters for validator selection
420#[derive(Debug, Clone, Serialize, Deserialize)]
421pub struct ScoringParams {
422    /// Minimum vote credits
423    pub min_vote_credits: u32,
424
425    /// Maximum commission
426    pub max_commission: u8,
427
428    /// Performance weight
429    pub performance_weight: f64,
430
431    /// Commission weight
432    pub commission_weight: f64,
433
434    /// Stake concentration limit
435    pub stake_concentration_limit: f64,
436}
437
438// ============================================================================
439// Common Types
440// ============================================================================
441
442/// Query parameters for paginated requests
443#[derive(Debug, Clone, Default)]
444pub struct QueryParams {
445    /// Limit number of results
446    pub limit: Option<u32>,
447
448    /// Offset for pagination
449    pub offset: Option<u32>,
450
451    /// Epoch filter
452    pub epoch: Option<u64>,
453
454    /// Sort order (asc/desc)
455    pub sort_order: Option<String>,
456}
457
458impl QueryParams {
459    /// Create new query params with limit
460    pub fn with_limit(limit: u32) -> Self {
461        Self {
462            limit: Some(limit),
463            ..Default::default()
464        }
465    }
466
467    /// Create new query params with epoch
468    pub fn with_epoch(epoch: u64) -> Self {
469        Self {
470            epoch: Some(epoch),
471            ..Default::default()
472        }
473    }
474
475    /// Set limit
476    pub fn limit(mut self, limit: u32) -> Self {
477        self.limit = Some(limit);
478        self
479    }
480
481    /// Set offset
482    pub fn offset(mut self, offset: u32) -> Self {
483        self.offset = Some(offset);
484        self
485    }
486
487    /// Set epoch
488    pub fn epoch(mut self, epoch: u64) -> Self {
489        self.epoch = Some(epoch);
490        self
491    }
492
493    /// Convert to query string
494    pub fn to_query_string(&self) -> String {
495        let mut params = Vec::new();
496
497        if let Some(limit) = self.limit {
498            params.push(format!("limit={}", limit));
499        }
500        if let Some(offset) = self.offset {
501            params.push(format!("offset={}", offset));
502        }
503        if let Some(epoch) = self.epoch {
504            params.push(format!("epoch={}", epoch));
505        }
506        if let Some(ref sort_order) = self.sort_order {
507            params.push(format!("sort_order={}", sort_order));
508        }
509
510        if params.is_empty() {
511            String::new()
512        } else {
513            format!("?{}", params.join("&"))
514        }
515    }
516}
517
518// ============================================================================
519// Error Response Types
520// ============================================================================
521
522/// API error response structure
523#[derive(Debug, Clone, Serialize, Deserialize)]
524pub struct ApiErrorResponse {
525    pub error: String,
526    pub message: Option<String>,
527    pub status_code: Option<u16>,
528}