kobe_client/
types.rs

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