accumulate_api/
lib.rs

1use async_trait::async_trait;
2use serde::{de::DeserializeOwned, Deserialize, Serialize};
3use std::time::Duration;
4use std::time::SystemTime;
5use std::{fmt, sync::Arc};
6
7pub mod error;
8pub mod types;
9pub use error::Error;
10
11pub mod utils;
12pub use utils::now_millis;
13
14pub mod blocks;
15
16
17////////////////////////////////////////////////////////////////////////////////
18// USEFUL DEFAULTS
19
20pub const ACCUMULATE_MAINNET_RPC_URL: &str = "https://mainnet.accumulatenetwork.io";
21pub const ACCUMULATE_TESTNET_KERMIT_RPC_URL: &str = "https://api-gateway.accumulate.defidevs.io";
22pub const ACCUMULATE_TESTNET_FOZZIE_RPC_URL: &str = "https://fozzie.accumulatenetwork.io";
23pub const ACCUMULATE_EDGE_RPC_URL: &str = "https://api.accumulate.chainfold.io";
24pub const ACCUMULATE_DEFAULT_RPC_URL: &str = "http://127.0.0.1:4467"; // if none speficied, use devnet
25
26/// Default API version number
27pub const ACCUMULATE_API_VER: u8 = 3; // "2" for older requests
28
29/// Default timeout for API requests
30pub const DEFAULT_TIMEOUT: u64 = 120;
31
32/// JSON RPC version
33pub const JSON_RPC: &str = "2.0";
34
35////////////////////////////////////////////////////////////////////////////////
36// Client Logic
37
38// Response type
39pub type Result<T = ()> = std::result::Result<T, Error>;
40
41#[derive(Clone, Debug)]
42pub struct Client {
43	base_url: String,
44	version: u8,
45	client: reqwest::Client,
46}
47
48impl Default for Client {
49	fn default() -> Self {
50		Self::new_with_base_url(ACCUMULATE_DEFAULT_RPC_URL.to_string())
51	}
52}
53
54impl Client {
55	pub fn new() -> Self {
56		let client = reqwest::Client::builder()
57			//.gzip(true)
58			.timeout(Duration::from_secs(DEFAULT_TIMEOUT))
59			.build()
60			.unwrap();
61		Self {
62			base_url: ACCUMULATE_DEFAULT_RPC_URL.to_string(),
63			version: ACCUMULATE_API_VER,
64			client,
65		}
66	}
67
68	/// Create a new client using a given base URL and a default
69	/// timeout. The library will use absoluate paths based on this
70	/// base_url.
71	pub fn new_with_base_url(base_url: String) -> Self {
72		Self::new_with_timeout(base_url, ACCUMULATE_API_VER, DEFAULT_TIMEOUT)
73	}
74
75	/// Create a new client using a given base URL, and request
76	/// timeout value.  The library will use absoluate paths based on
77	/// the given base_url.
78	pub fn new_with_timeout(base_url: String, version: u8, timeout: u64) -> Self {
79		let client = reqwest::Client::builder()
80			//.gzip(true)
81			.timeout(Duration::from_secs(timeout))
82			.build()
83			.unwrap();
84		Self {
85			base_url,
86			version,
87			client,
88		}
89	}
90
91	pub fn new_with_version(base_url: String, version: u8) -> Self {
92		let client = reqwest::Client::builder()
93			//.gzip(true)
94			.timeout(Duration::from_secs(DEFAULT_TIMEOUT))
95			.build()
96			.unwrap();
97		Self {
98			base_url,
99			version,
100			client,
101		}
102	}
103
104	async fn post<T: DeserializeOwned, D: Serialize>(&self, path: &str, data: D) -> Result<T> {
105		#[derive(Clone, Serialize, Deserialize, Debug)]
106		#[serde(untagged)]
107		pub(crate) enum Response<T> {
108			Data { result: T, id: String },
109			Error { id: String, error: Error },
110		}
111
112		#[derive(Clone, Serialize, Deserialize, Debug)]
113		pub(crate) struct Error {
114			message: String,
115			code: isize,
116		}
117
118		let request_url = format!("{}{}", self.base_url, path);
119		println!("1// request URL: {}", request_url);
120		let request = self.client.post(&request_url).json(&data);
121		let requestV = self.client.post(&request_url).json(&data);
122		println!("2//");
123
124		match requestV.build() {
125			Ok(req) => {
126				// Inspect the request
127				println!("{:?}", req);
128				//println!("{:?}", &data);
129
130				// Send the request
131				//let response = client.execute(req).await?;
132				// Handle the response...
133			}
134			Err(e) => {
135				// Handle the error
136				eprintln!("Error building the request: {:?}", e);
137			}
138		}
139
140		let response = request.send().await?;
141		println!("3//");
142		let body = response.text().await?;
143		println!("body: {}", body);
144		let v: Response<T> = serde_json::from_str(&body)?;
145		match v {
146			Response::Data { result, .. } => Ok(result),
147			Response::Error { error, .. } => Err(error::Error::ApiError(error.message, error.code)),
148		}
149	}
150}
151
152#[derive(Clone, Deserialize, Debug, Serialize)]
153#[serde(tag = "method")]
154//#[serde(rename_all = "snake_case")]
155enum Method {
156	#[serde(rename(serialize = "metrics"))]
157	Metrics,
158	#[serde(rename(serialize = "consensus-status"))]
159	ConsensusStatus,
160	#[serde(rename(serialize = "find-service"))]
161	FindService,
162	#[serde(rename(serialize = "query"))]
163	MajorBlocksGet { params: QueryBlockMajorListParams },
164	#[serde(rename(serialize = "query"))]
165	MinorBlocksGet { params: QueryBlockMinorListParams },
166
167	#[serde(rename(serialize = "query"))]
168	MajorBlockGet { params: BlockParams },
169	#[serde(rename(serialize = "query"))]
170	MinorBlockGet { params: BlockParams },
171
172	#[serde(rename(serialize = "query"))]
173	Query { params: QueryParams },
174}
175
176#[derive(Clone, Deserialize, Debug, Serialize)]
177pub(crate) struct ApiCall {
178	jsonrpc: String,
179	id: String,
180	#[serde(flatten)]
181	method: Method,
182}
183
184impl ApiCall {
185	fn new(request: Method) -> ApiCall {
186		ApiCall {
187			jsonrpc: JSON_RPC.to_string(),
188			id: "0".to_string(), //now_millis(),
189			method: request,
190		}
191	}
192
193	pub(crate) fn query_major_blocks(url: String, start: u64, count: u64, expand: bool, from_end: bool) -> Self {
194		Self::new(Method::MajorBlocksGet {
195			params: QueryBlockMajorListParams {
196				scope: url,
197				query: {
198					BlockMajorListParams {
199						query_type: "block".to_string(),
200						major_range: BlockListRangeParams {
201							start,
202							count,
203							expand,
204							from_end,
205						},
206					}
207				},
208			},
209		})
210	}
211
212	pub(crate) fn query_major_block(url: String, height: u64) -> Self {
213		Self::new(Method::MajorBlockGet {
214			params: BlockParams { scope: url, height },
215		})
216	}
217
218	pub(crate) fn query_minor_blocks(url: String, start: u64, count: u64, expand: bool, from_end: bool) -> Self {
219		Self::new(Method::MinorBlocksGet {
220			params: QueryBlockMinorListParams {
221				scope: url,
222				query: {
223					BlockMinorListParams {
224						query_type: "block".to_string(),
225						major_range: BlockListRangeParams {
226							start,
227							count,
228							expand,
229							from_end,
230						},
231					}
232				},
233			},
234		})
235	}
236}
237
238#[derive(Clone, Deserialize, Debug, Serialize)]
239struct QueryParams {
240	scope: String,
241}
242
243#[derive(Clone, Deserialize, Debug, Serialize)]
244struct BlockParams {
245	scope: String,
246	height: u64,
247}
248
249#[derive(Clone, Deserialize, Debug, Serialize)]
250struct QueryBlockMajorListParams {
251	scope: String,
252	query: BlockMajorListParams,
253}
254
255#[derive(Clone, Deserialize, Debug, Serialize)]
256struct BlockMajorListParams {
257	#[serde(rename(serialize = "queryType"))]
258	query_type: String,
259	#[serde(rename(serialize = "majorRange"))]
260	major_range: BlockListRangeParams,
261}
262
263#[derive(Clone, Deserialize, Debug, Serialize)]
264struct BlockListRangeParams {
265	start: u64,
266	count: u64,
267	expand: bool,
268	#[serde(rename(serialize = "fromEnd"))]
269	from_end: bool,
270}
271
272#[derive(Clone, Deserialize, Debug, Serialize)]
273struct QueryBlockMinorListParams {
274	scope: String,
275	query: BlockMinorListParams,
276}
277
278#[derive(Clone, Deserialize, Debug, Serialize)]
279struct BlockMinorListParams {
280	#[serde(rename(serialize = "queryType"))]
281	query_type: String,
282	#[serde(rename(serialize = "minorRange"))]
283	major_range: BlockListRangeParams,
284}
285
286#[cfg(test)]
287mod test {
288	use super::*;
289	use tokio::test;
290
291	#[test]
292	async fn get_last_major_block() {
293		let client = Client::new_with_version(ACCUMULATE_MAINNET_RPC_URL.to_string(), 3);
294		let resp = blocks::get_range_major(&client, "acc://bvn-apollo.acme", 1, 100, true, true).await;
295
296		if let Err(err) = resp {
297			// can be omitted if
298			println!("{}", err);
299			panic!("{err}"); // only the Ok result
300		}
301
302		assert!(1 > 0);
303	}
304}