koios_sdk/api/
governance.rs

1use crate::{
2    error::Result,
3    models::{
4        governance::{DrepEpochSummary, DrepHistory, DrepInfo, DrepMetadata, DrepUpdate, DrepVote},
5        requests::DrepIdBulkRequest,
6        CommitteeInfo, CommitteeVotes, ProposalList, ProposalVote, ProposalVotingSummary,
7        VoterProposalList,
8    },
9    types::{CcHotId, DrepId, EpochNo, ProposalId, VoterId},
10    Client,
11};
12use urlencoding::encode;
13
14impl Client {
15    /// Get summary of voting power and DRep count for each epoch
16    ///
17    /// # Arguments
18    ///
19    /// * `epoch_no` - Optional epoch number to query
20    ///
21    /// # Examples
22    ///
23    /// ```no_run
24    /// use koios_sdk::Client;
25    /// use koios_sdk::types::EpochNo;
26    ///
27    /// #[tokio::main]
28    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
29    ///     let client = Client::new()?;
30    ///     let summary = client.get_drep_epoch_summary(Some(EpochNo::new("320"))).await?;
31    ///     println!("DRep epoch summary: {:?}", summary);
32    ///     Ok(())
33    /// }
34    /// ```
35    pub async fn get_drep_epoch_summary(
36        &self,
37        epoch_no: Option<EpochNo>,
38    ) -> Result<Vec<DrepEpochSummary>> {
39        let endpoint = if let Some(epoch) = epoch_no {
40            format!("/drep_epoch_summary?_epoch_no={}", encode(epoch.value()))
41        } else {
42            "/drep_epoch_summary".to_string()
43        };
44        self.get(&endpoint).await
45    }
46
47    /// Get list of all active delegated representatives (DReps)
48    ///
49    /// # Examples
50    ///
51    /// ```no_run
52    /// use koios_sdk::Client;
53    ///
54    /// #[tokio::main]
55    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
56    ///     let client = Client::new()?;
57    ///     let dreps = client.get_drep_list().await?;
58    ///     println!("DRep list: {:?}", dreps);
59    ///     Ok(())
60    /// }
61    /// ```
62    pub async fn get_drep_list(&self) -> Result<Vec<DrepInfo>> {
63        self.get("/drep_list").await
64    }
65
66    /// Get detailed information about requested delegated representatives (DReps)
67    ///
68    /// # Arguments
69    ///
70    /// * `drep_ids` - List of DRep IDs to query
71    ///
72    /// # Examples
73    ///
74    /// ```no_run
75    /// use koios_sdk::Client;
76    ///
77    /// #[tokio::main]
78    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
79    ///     let client = Client::new()?;
80    ///     let drep_ids = vec![
81    ///         "drep1arlq6qcjf8qd7kn03k8lqg6gxv7zw9r8684fa03akqfx7qarhgjqka6e5g".to_string()
82    ///     ];
83    ///     let info = client.get_drep_info(&drep_ids).await?;
84    ///     println!("DRep info: {:?}", info);
85    ///     Ok(())
86    /// }
87    /// ```
88    pub async fn get_drep_info(&self, drep_ids: &[String]) -> Result<Vec<DrepInfo>> {
89        let request = DrepIdBulkRequest::new(drep_ids.to_vec());
90        self.post("/drep_info", &request).await
91    }
92
93    /// Get metadata for requested delegated representatives (DReps)
94    ///
95    /// # Arguments
96    ///
97    /// * `drep_ids` - List of DRep IDs to query
98    ///
99    /// # Examples
100    ///
101    /// ```no_run
102    /// use koios_sdk::Client;
103    ///
104    /// #[tokio::main]
105    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
106    ///     let client = Client::new()?;
107    ///     let drep_ids = vec![
108    ///         "drep1arlq6qcjf8qd7kn03k8lqg6gxv7zw9r8684fa03akqfx7qarhgjqka6e5g".to_string()
109    ///     ];
110    ///     let metadata = client.get_drep_metadata(&drep_ids).await?;
111    ///     println!("DRep metadata: {:?}", metadata);
112    ///     Ok(())
113    /// }
114    /// ```
115    pub async fn get_drep_metadata(&self, drep_ids: &[String]) -> Result<Vec<DrepMetadata>> {
116        let request = DrepIdBulkRequest::new(drep_ids.to_vec());
117        self.post("/drep_metadata", &request).await
118    }
119
120    /// Get list of updates for requested (or all) delegated representatives (DReps)
121    ///
122    /// # Arguments
123    ///
124    /// * `drep_id` - Optional DRep ID to filter by
125    ///
126    /// # Examples
127    ///
128    /// ```no_run
129    /// use koios_sdk::Client;
130    /// use koios_sdk::types::DrepId;
131    ///
132    /// #[tokio::main]
133    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
134    ///     let client = Client::new()?;
135    ///     let updates = client.get_drep_updates(
136    ///         Some(DrepId::new("drep1arlq6qcjf8qd7kn03k8lqg6gxv7zw9r8684fa03akqfx7qarhgjqka6e5g"))
137    ///     ).await?;
138    ///     println!("DRep updates: {:?}", updates);
139    ///     Ok(())
140    /// }
141    /// ```
142    pub async fn get_drep_updates(&self, drep_id: Option<DrepId>) -> Result<Vec<DrepUpdate>> {
143        let endpoint = if let Some(id) = drep_id {
144            format!("/drep_updates?_drep_id={}", encode(id.value()))
145        } else {
146            "/drep_updates".to_string()
147        };
148        self.get(&endpoint).await
149    }
150    /// Get history of DReps voting power against each (or requested) epoch
151    ///
152    /// # Arguments
153    ///
154    /// * `epoch_no` - Optional epoch number to query
155    /// * `drep_id` - Optional DRep ID to filter by
156    ///
157    /// # Examples
158    ///
159    /// ```no_run
160    /// use koios_sdk::Client;
161    /// use koios_sdk::types::{EpochNo, DrepId};
162    ///
163    /// #[tokio::main]
164    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
165    ///     let client = Client::new()?;
166    ///     let history = client.get_drep_history(
167    ///         Some(EpochNo::new("320")),
168    ///         Some(DrepId::new("drep1arlq6qcjf8qd7kn03k8lqg6gxv7zw9r8684fa03akqfx7qarhgjqka6e5g"))
169    ///     ).await?;
170    ///     println!("DRep history: {:?}", history);
171    ///     Ok(())
172    /// }
173    /// ```
174    pub async fn get_drep_history(
175        &self,
176        epoch_no: Option<EpochNo>,
177        drep_id: Option<DrepId>,
178    ) -> Result<Vec<DrepHistory>> {
179        let mut params = Vec::new();
180
181        if let Some(epoch) = epoch_no {
182            params.push(format!("_epoch_no={}", encode(epoch.value())));
183        }
184
185        if let Some(id) = drep_id {
186            params.push(format!("_drep_id={}", encode(id.value())));
187        }
188
189        let endpoint = if params.is_empty() {
190            "/drep_history".to_string()
191        } else {
192            format!("/drep_history?{}", params.join("&"))
193        };
194
195        self.get(&endpoint).await
196    }
197
198    /// Get list of all votes casted by requested delegated representative (DRep)
199    ///
200    /// # Arguments
201    ///
202    /// * `drep_id` - DRep ID to query
203    ///
204    /// # Examples
205    ///
206    /// ```no_run
207    /// use koios_sdk::Client;
208    /// use koios_sdk::types::DrepId;
209    ///
210    /// #[tokio::main]
211    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
212    ///     let client = Client::new()?;
213    ///     let votes = client.get_drep_votes(
214    ///         &DrepId::new("drep1arlq6qcjf8qd7kn03k8lqg6gxv7zw9r8684fa03akqfx7qarhgjqka6e5g")
215    ///     ).await?;
216    ///     println!("DRep votes: {:?}", votes);
217    ///     Ok(())
218    /// }
219    /// ```
220    pub async fn get_drep_votes(&self, drep_id: &DrepId) -> Result<Vec<DrepVote>> {
221        self.get(&format!("/drep_votes?_drep_id={}", encode(drep_id.value())))
222            .await
223    }
224
225    /// Get information about active committee and its members
226    ///
227    /// # Examples
228    ///
229    /// ```no_run
230    /// use koios_sdk::Client;
231    ///
232    /// #[tokio::main]
233    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
234    ///     let client = Client::new()?;
235    ///     let info = client.get_committee_info().await?;
236    ///     println!("Committee info: {:?}", info);
237    ///     Ok(())
238    /// }
239    /// ```
240    pub async fn get_committee_info(&self) -> Result<Vec<CommitteeInfo>> {
241        self.get("/committee_info").await
242    }
243
244    /// Get list of all votes cast by given committee member or collective
245    ///
246    /// # Arguments
247    ///
248    /// * `cc_hot_id` - Committee member hot key ID
249    ///
250    /// # Examples
251    ///
252    /// ```no_run
253    /// use koios_sdk::Client;
254    /// use koios_sdk::types::CcHotId;
255    ///
256    /// #[tokio::main]
257    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
258    ///     let client = Client::new()?;
259    ///     let votes = client.get_committee_votes(
260    ///         &CcHotId::new("cc_hot1abcdef1234567890abcdef1234567890abcdef1234567890abcdef123456")
261    ///     ).await?;
262    ///     println!("Committee votes: {:?}", votes);
263    ///     Ok(())
264    /// }
265    /// ```
266    pub async fn get_committee_votes(&self, cc_hot_id: &CcHotId) -> Result<Vec<CommitteeVotes>> {
267        self.get(&format!(
268            "/committee_votes?_cc_hot_id={}",
269            encode(cc_hot_id.value())
270        ))
271        .await
272    }
273
274    /// Get list of all governance proposals
275    ///
276    /// # Examples
277    ///
278    /// ```no_run
279    /// use koios_sdk::Client;
280    ///
281    /// #[tokio::main]
282    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
283    ///     let client = Client::new()?;
284    ///     let proposals = client.get_proposal_list().await?;
285    ///     println!("Proposal list: {:?}", proposals);
286    ///     Ok(())
287    /// }
288    /// ```
289    pub async fn get_proposal_list(&self) -> Result<Vec<ProposalList>> {
290        self.get("/proposal_list").await
291    }
292
293    /// Get list of all governance proposals for specified DRep, SPO or Committee credential
294    ///
295    /// # Arguments
296    ///
297    /// * `voter_id` - Voter ID (DRep, SPO, or Committee credential)
298    ///
299    /// # Examples
300    ///
301    /// ```no_run
302    /// use koios_sdk::Client;
303    /// use koios_sdk::types::VoterId;
304    ///
305    /// #[tokio::main]
306    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
307    ///     let client = Client::new()?;
308    ///     let proposals = client.get_voter_proposal_list(
309    ///         &VoterId::new("drep1arlq6qcjf8qd7kn03k8lqg6gxv7zw9r8684fa03akqfx7qarhgjqka6e5g")
310    ///     ).await?;
311    ///     println!("Voter proposal list: {:?}", proposals);
312    ///     Ok(())
313    /// }
314    /// ```
315    pub async fn get_voter_proposal_list(
316        &self,
317        voter_id: &VoterId,
318    ) -> Result<Vec<VoterProposalList>> {
319        self.get(&format!(
320            "/voter_proposal_list?_voter_id={}",
321            encode(voter_id.value())
322        ))
323        .await
324    }
325
326    /// Get summary of votes for given proposal
327    ///
328    /// # Arguments
329    ///
330    /// * `proposal_id` - Proposal ID to query
331    ///
332    /// # Examples
333    ///
334    /// ```no_run
335    /// use koios_sdk::Client;
336    /// use koios_sdk::types::ProposalId;
337    ///
338    /// #[tokio::main]
339    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
340    ///     let client = Client::new()?;
341    ///     let summary = client.get_proposal_voting_summary(
342    ///         &ProposalId::new("gov_action1abcdef1234567890abcdef1234567890abcdef1234567890abcdef")
343    ///     ).await?;
344    ///     println!("Proposal voting summary: {:?}", summary);
345    ///     Ok(())
346    /// }
347    /// ```
348    pub async fn get_proposal_voting_summary(
349        &self,
350        proposal_id: &ProposalId,
351    ) -> Result<Vec<ProposalVotingSummary>> {
352        self.get(&format!(
353            "/proposal_voting_summary?_proposal_id={}",
354            encode(proposal_id.value())
355        ))
356        .await
357    }
358
359    /// Get list of all votes cast on specified governance action
360    ///
361    /// # Arguments
362    ///
363    /// * `proposal_id` - Proposal ID to query
364    ///
365    /// # Examples
366    ///
367    /// ```no_run
368    /// use koios_sdk::Client;
369    /// use koios_sdk::types::ProposalId;
370    ///
371    /// #[tokio::main]
372    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
373    ///     let client = Client::new()?;
374    ///     let votes = client.get_proposal_votes(
375    ///         &ProposalId::new("gov_action1abcdef1234567890abcdef1234567890abcdef1234567890abcdef")
376    ///     ).await?;
377    ///     println!("Proposal votes: {:?}", votes);
378    ///     Ok(())
379    /// }
380    /// ```
381    pub async fn get_proposal_votes(&self, proposal_id: &ProposalId) -> Result<Vec<ProposalVote>> {
382        self.get(&format!(
383            "/proposal_votes?_proposal_id={}",
384            encode(proposal_id.value())
385        ))
386        .await
387    }
388}
389
390#[cfg(test)]
391mod tests {
392    use super::*;
393    use serde_json::json;
394    use wiremock::matchers::{method, path};
395    use wiremock::{Mock, MockServer, ResponseTemplate};
396
397    #[tokio::test]
398    async fn test_get_drep_list() {
399        let mock_server = MockServer::start().await;
400        let client = Client::builder()
401            .base_url(mock_server.uri())
402            .build()
403            .unwrap();
404
405        let mock_response = json!([{
406            "drep_id": "drep1arlq6qcjf8qd7kn03k8lqg6gxv7zw9r8684fa03akqfx7qarhgjqka6e5g",
407            "hex": "e1faa57619c35f423a461f03c5507826c62b9c121446e95b91fb1b537f",
408            "has_script": false,
409            "registered": true,
410            "deposit": "2000000",
411            "active": true,
412            "expires_epoch_no": null,
413            "amount": "1000000000",
414            "meta_url": null,
415            "meta_hash": null
416        }]);
417
418        Mock::given(method("GET"))
419            .and(path("/drep_list"))
420            .respond_with(ResponseTemplate::new(200).set_body_json(&mock_response))
421            .mount(&mock_server)
422            .await;
423
424        let response = client.get_drep_list().await.unwrap();
425        assert_eq!(response.len(), 1);
426        assert_eq!(
427            response[0].drep_id,
428            "drep1arlq6qcjf8qd7kn03k8lqg6gxv7zw9r8684fa03akqfx7qarhgjqka6e5g"
429        );
430    }
431
432    #[tokio::test]
433    async fn test_get_proposal_list() {
434        let mock_server = MockServer::start().await;
435        let client = Client::builder()
436            .base_url(mock_server.uri())
437            .build()
438            .unwrap();
439
440        let mock_response = json!([{
441            "block_time": 1630106091,
442            "proposal_id": "gov_action1abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
443            "proposal_tx_hash": "6ed09ba58a56c6e946668038ba4d3cef8eb97a20cbf76c5970e1402e8a8d6541",
444            "proposal_index": 0,
445            "proposal_type": "parameter_change",
446            "proposal_description": "Change protocol parameter X",
447            "deposit": "1000000000",
448            "return_address": "addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq946axfcejy5n4x0y99wqpgtp2gd0k09qsgy6pz",
449            "proposed_epoch": 321
450        }]);
451
452        Mock::given(method("GET"))
453            .and(path("/proposal_list"))
454            .respond_with(ResponseTemplate::new(200).set_body_json(&mock_response))
455            .mount(&mock_server)
456            .await;
457
458        let response = client.get_proposal_list().await.unwrap();
459        assert_eq!(response.len(), 1);
460        assert_eq!(response[0].proposal_index, 0);
461        // assert_eq!(response[0].proposal_type, ProposalType::ParameterChange);
462    }
463
464    // Add more tests for other endpoints...
465}