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}