partner_chains_dolos_data_sources/client/
minibf.rs1use async_trait::async_trait;
2use blockfrost_openapi::models::{
3 address_transactions_content_inner::AddressTransactionsContentInner,
4 address_utxo_content_inner::AddressUtxoContentInner,
5 asset_addresses_inner::AssetAddressesInner, asset_transactions_inner::AssetTransactionsInner,
6 block_content::BlockContent, epoch_param_content::EpochParamContent,
7 epoch_stake_pool_content_inner::EpochStakePoolContentInner, genesis_content::GenesisContent,
8 pool_history_inner::PoolHistoryInner, pool_list_extended_inner::PoolListExtendedInner,
9 tx_content::TxContent, tx_content_utxo::TxContentUtxo,
10};
11use serde::de::DeserializeOwned;
12use sidechain_domain::*;
13use std::time::Duration;
14use ureq::Agent;
15
16use crate::{
17 DataSourceError,
18 client::api::{McBlockId, McPoolId, MiniBFApi},
19};
20
21#[derive(Clone)]
23pub struct MiniBFClient {
24 agent: ureq::Agent,
25 addr: String,
26}
27
28impl MiniBFClient {
29 pub fn new(addr: &str, timeout: Duration) -> Self {
30 let agent = Agent::config_builder().timeout_per_call(Some(timeout)).build().into();
31 MiniBFClient { agent, addr: addr.strip_suffix("/").unwrap_or(addr).to_string() }
32 }
33
34 async fn request<T: DeserializeOwned + std::fmt::Debug>(
35 &self,
36 method: &str,
37 ) -> Result<T, DataSourceError> {
38 let req = format!("{}/{}", self.addr, method);
39 log::trace!("Dolos request: {req:?}");
40 let resp = self
41 .agent
42 .get(req)
43 .call()
44 .map_err(|e| DataSourceError::DolosCallError(e.to_string()))
45 .and_then(|mut r| {
46 r.body_mut()
47 .read_json()
48 .map_err(|e| DataSourceError::DolosResponseParseError(e.to_string()))
49 });
50 log::trace!("Dolos response: {resp:?}");
51 resp
52 }
53
54 async fn paginated_request<T: DeserializeOwned + std::fmt::Debug>(
55 &self,
56 method: &str,
57 pagination: Pagination,
58 ) -> Result<Vec<T>, DataSourceError> {
59 let mut query_pairs = url::form_urlencoded::Serializer::new(String::new());
60 query_pairs.extend_pairs([
61 ("count", &pagination.count.to_string()),
62 ("page", &pagination.page.to_string()),
63 ("order", &pagination.order.to_string()),
64 ]);
65 if let Some(from) = pagination.from {
66 query_pairs.append_pair("from", &from);
67 }
68 if let Some(to) = pagination.to {
69 query_pairs.append_pair("to", &to);
70 }
71 let mut req_url =
72 url::Url::parse(&format!("{}/{}", self.addr, method)).expect("valid Dolos url");
73 req_url.set_query(Some(&query_pairs.finish()));
74 log::trace!("Dolos request: {req_url:?}");
75 let resp = self
76 .agent
77 .get(req_url.as_str())
78 .call()
79 .map_err(|e| DataSourceError::DolosCallError(e.to_string()))
80 .and_then(|mut r| {
81 r.body_mut()
82 .read_json()
83 .map_err(|e| DataSourceError::DolosResponseParseError(e.to_string()))
84 });
85 log::trace!("Dolos response: {resp:?}");
86 resp
87 }
88
89 async fn paginated_request_all<T: DeserializeOwned + std::fmt::Debug>(
90 &self,
91 method: &str,
92 ) -> Result<Vec<T>, DataSourceError> {
93 let mut pagination: Pagination = Pagination::default();
94 let mut have_all_pages = false;
95 let mut res = Vec::new();
96 while !have_all_pages {
97 let mut resp: Vec<T> = self.paginated_request(method, pagination.clone()).await?;
98 if (resp.len() as i32) < pagination.count {
99 have_all_pages = true
100 }
101 res.append(&mut resp);
102 pagination.page += 1;
103 }
104 Ok(res)
105 }
106}
107
108#[async_trait]
109impl MiniBFApi for MiniBFClient {
110 async fn addresses_utxos(
111 &self,
112 address: MainchainAddress,
113 ) -> Result<Vec<AddressUtxoContentInner>, DataSourceError> {
114 self.paginated_request_all(&format!("addresses/{address}/utxos")).await
115 }
116
117 async fn addresses_transactions(
118 &self,
119 address: MainchainAddress,
120 ) -> Result<Vec<AddressTransactionsContentInner>, DataSourceError> {
121 self.paginated_request_all(&format!("addresses/{address}/transactions")).await
122 }
123
124 async fn assets_transactions(
125 &self,
126 asset_id: AssetId,
127 ) -> Result<Vec<AssetTransactionsInner>, DataSourceError> {
128 let asset_id_str = format_asset_id(&asset_id);
129 self.paginated_request_all(&format!("assets/{asset_id_str}/transactions")).await
130 }
131
132 async fn assets_addresses(
133 &self,
134 asset_id: AssetId,
135 ) -> Result<Vec<AssetAddressesInner>, DataSourceError> {
136 let asset_id_str = format_asset_id(&asset_id);
137 self.paginated_request_all(&format!("assets/{asset_id_str}/addresses")).await
138 }
139
140 async fn blocks_latest(&self) -> Result<BlockContent, DataSourceError> {
141 self.request("blocks/latest").await
142 }
143
144 async fn blocks_by_id(
145 &self,
146 id: impl Into<McBlockId> + Send,
147 ) -> Result<BlockContent, DataSourceError> {
148 let id: McBlockId = id.into();
149 self.request(&format!("blocks/{id}")).await
150 }
151
152 async fn blocks_slot(
153 &self,
154 slot_number: McSlotNumber,
155 ) -> Result<BlockContent, DataSourceError> {
156 self.request(&format!("blocks/slot/{slot_number}")).await
157 }
158
159 async fn blocks_next(
160 &self,
161 id: impl Into<McBlockId> + Send,
162 ) -> Result<Vec<BlockContent>, DataSourceError> {
163 let id: McBlockId = id.into();
164 self.request(&format!("blocks/{id}/next")).await
165 }
166
167 async fn blocks_txs(
168 &self,
169 id: impl Into<McBlockId> + Send,
170 ) -> Result<Vec<String>, DataSourceError> {
171 let id: McBlockId = id.into();
172 self.request(&format!("blocks/{id}/txs")).await
173 }
174
175 async fn epochs_blocks(
176 &self,
177 epoch_number: McEpochNumber,
178 ) -> Result<Vec<String>, DataSourceError> {
179 self.paginated_request_all(&format!("epochs/{epoch_number}/blocks")).await
180 }
181 async fn epochs_parameters(
182 &self,
183 epoch_number: McEpochNumber,
184 ) -> Result<EpochParamContent, DataSourceError> {
185 self.request(&format!("epochs/{epoch_number}/parameters")).await
186 }
187 async fn epochs_stakes_by_pool(
188 &self,
189 epoch_number: McEpochNumber,
190 pool_id: impl Into<McPoolId> + Send,
191 ) -> Result<Vec<EpochStakePoolContentInner>, DataSourceError> {
192 let pool_id: McPoolId = pool_id.into();
193 self.paginated_request_all(&format!("epochs/{epoch_number}/stakes/{pool_id}"))
194 .await
195 }
196
197 async fn pools_history(
198 &self,
199 pool_id: impl Into<McPoolId> + Send,
200 ) -> Result<Vec<PoolHistoryInner>, DataSourceError> {
201 let pool_id: McPoolId = pool_id.into();
202 self.paginated_request_all(&format!("pools/{pool_id}/history")).await
203 }
204 async fn pools_extended(&self) -> Result<Vec<PoolListExtendedInner>, DataSourceError> {
205 self.paginated_request_all("pools/extended").await
206 }
207
208 async fn scripts_datum_hash(
209 &self,
210 datum_hash: &str,
211 ) -> Result<Vec<serde_json::Value>, DataSourceError> {
212 self.request(&format!("scripts/datum/{datum_hash}")).await
213 }
214
215 async fn transaction_by_hash(&self, tx_hash: McTxHash) -> Result<TxContent, DataSourceError> {
216 self.request(&format!("txs/{tx_hash}")).await
217 }
218
219 async fn transactions_utxos(
220 &self,
221 tx_hash: McTxHash,
222 ) -> Result<TxContentUtxo, DataSourceError> {
223 self.request(&format!("txs/{tx_hash}/utxos")).await
224 }
225
226 async fn genesis(&self) -> Result<GenesisContent, DataSourceError> {
227 self.request("genesis").await
228 }
229}
230
231pub fn format_asset_id(asset_id: &AssetId) -> String {
232 let AssetId { policy_id, asset_name } = asset_id;
233 format!("{}{}", &policy_id.to_hex_string()[2..], &asset_name.to_hex_string()[2..])
234}
235
236#[derive(Clone)]
237#[allow(dead_code)]
238enum Order {
239 Asc,
240 Desc,
241}
242
243impl std::fmt::Display for Order {
244 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245 match self {
246 Order::Asc => write!(f, "asc"),
247 Order::Desc => write!(f, "desc"),
248 }
249 }
250}
251
252#[derive(Clone)]
253struct Pagination {
254 count: i32,
255 page: i32,
256 order: Order,
257 from: Option<String>,
258 to: Option<String>,
259}
260
261impl Default for Pagination {
262 fn default() -> Self {
263 Self { count: 100, page: 1, order: Order::Asc, from: None, to: None }
264 }
265}