1use anyhow::{bail, Result};
3
4use crate::agent::Agent;
5
6use super::*;
7
8impl Agent {
10 pub fn get_latest_block_height(&self) -> Result<u32> {
15 let url = format!("{}/{}/block/height/latest", self.base_url(), self.network());
16 match self.client().get(&url).call()?.into_json() {
17 Ok(height) => Ok(height),
18 Err(error) => bail!("Failed to parse the latest block height: {error}"),
19 }
20 }
21
22 pub fn get_latest_block_hash(&self) -> Result<BlockHash> {
27 let url = format!("{}/{}/block/hash/latest", self.base_url(), self.network());
28 match self.client().get(&url).call()?.into_json() {
29 Ok(hash) => Ok(hash),
30 Err(error) => bail!("Failed to parse the latest block hash: {error}"),
31 }
32 }
33
34 pub fn get_latest_block(&self) -> Result<Block> {
39 let url = format!("{}/{}/latest/block/height", self.base_url(), self.network());
40 match self.client().get(&url).call()?.into_json() {
41 Ok(block) => Ok(block),
42 Err(error) => bail!("Failed to parse the latest block: {error}"),
43 }
44 }
45
46 pub fn get_block_of_height(&self, height: u32) -> Result<Block> {
54 let url = format!("{}/{}/block/{height}", self.base_url(), self.network());
55 match self.client().get(&url).call()?.into_json() {
56 Ok(block) => Ok(block),
57 Err(error) => bail!("Failed to parse block {height}: {error}"),
58 }
59 }
60
61 pub fn get_transactions_of_height(&self, height: u32) -> Result<Transactions> {
69 let url = format!(
70 "{}/{}/block/{height}/transactions",
71 self.base_url(),
72 self.network()
73 );
74 match self.client().get(&url).call()?.into_json() {
75 Ok(block) => Ok(block),
76 Err(error) => bail!("Failed to parse block {height}: {error}"),
77 }
78 }
79
80 pub fn get_blocks_in_range(&self, start_height: u32, end_height: u32) -> Result<Vec<Block>> {
90 if start_height >= end_height {
91 bail!("Start height must be less than end height");
92 }
93
94 if end_height - start_height > 50 {
95 bail!("The range of blocks must be less than 50");
96 }
97
98 let url = format!(
99 "{}/{}/blocks?start={start_height}&end={end_height}",
100 self.base_url(),
101 self.network()
102 );
103 match self.client().get(&url).call()?.into_json() {
104 Ok(blocks) => Ok(blocks),
105 Err(error) => {
106 bail!("Failed to parse blocks {start_height} (inclusive) to {end_height} (exclusive): {error}")
107 }
108 }
109 }
110
111 pub fn get_transaction(&self, transaction_id: &str) -> Result<Transaction> {
119 let url = format!(
120 "{}/{}/transaction/{}",
121 self.base_url(),
122 self.network(),
123 transaction_id
124 ).replace('"', "");
125 match self.client().get(&url).call()?.into_json() {
126 Ok(transaction) => Ok(transaction),
127 Err(error) => bail!("Failed to parse transaction '{transaction_id}': {error}"),
128 }
129 }
130
131 pub fn get_confirmed_transaction(&self, transaction_id: &str) -> Result<ConfirmedTransaction> {
139 let url = format!(
140 "{}/{}/transaction/confirmed/{}",
141 self.base_url(),
142 self.network(),
143 transaction_id
144 ).replace('"', "");
145 match self.client().get(&url).call()?.into_json() {
146 Ok(transaction) => Ok(transaction),
147 Err(error) => bail!("Failed to parse transaction '{transaction_id}': {error}"),
148 }
149 }
150
151 pub fn broadcast_transaction(&self, transaction: &Transaction) -> Result<String> {
175 let url = format!(
176 "{}/{}/transaction/broadcast",
177 self.base_url(),
178 self.network()
179 );
180 match self.client().post(&url).send_json(transaction) {
181 Ok(response) => match response.into_string() {
182 Ok(success_response) => {
183 Ok(success_response)
184 }
185 Err(error) => bail!("❌ Transaction response was malformed {}", error),
186 },
187 Err(error) => {
188 let error_message = match error {
189 ureq::Error::Status(code, response) => {
190 format!("(status code {code}: {:?})", response.into_string()?)
191 }
192 ureq::Error::Transport(err) => format!("({err})"),
193 };
194
195 match transaction {
196 Transaction::Deploy(..) => {
197 bail!("❌ Failed to deploy program to {}: {}", &url, error_message)
198 }
199 Transaction::Execute(..) => {
200 bail!(
201 "❌ Failed to broadcast execution to {}: {}",
202 &url,
203 error_message
204 )
205 }
206 Transaction::Fee(..) => {
207 bail!(
208 "❌ Failed to broadcast fee execution to {}: {}",
209 &url,
210 error_message
211 )
212 }
213 }
214 }
215 }
216 }
217
218 pub fn find_block_hash_by_transaction_id(
226 &self,
227 transaction_id: &TransactionID,
228 ) -> Result<BlockHash> {
229 let url = format!(
230 "{}/{}/find/blockHash/{}",
231 self.base_url(),
232 self.network(),
233 transaction_id
234 ).replace('"', "");
235 match self.client().get(&url).call()?.into_json() {
236 Ok(hash) => Ok(hash),
237 Err(error) => bail!("Failed to parse block hash: {error}"),
238 }
239 }
240
241 pub fn find_transition_id_by_input_or_output_id(
249 &self,
250 input_or_output_id: Field,
251 ) -> Result<TransitionID> {
252 let url = format!(
253 "{}/{}/find/transitionID/{input_or_output_id}",
254 self.base_url(),
255 self.network()
256 );
257 match self.client().get(&url).call()?.into_json() {
258 Ok(transition_id) => Ok(transition_id),
259 Err(error) => bail!("Failed to parse transition ID: {error}"),
260 }
261 }
262}
263
264#[cfg(test)]
265mod test{
266 use std::str::FromStr;
267 use snarkvm::prelude::AleoID;
268 use super::*;
269
270 #[test]
271 fn test_find_transition_id_by_private_input_id() {
272 let agent = Agent::default();
273 let input_id = Field::from_str("4264902728006919131372754675851572663419744328988144775456736509806946463832field").unwrap();
274 let res = agent
275 .find_transition_id_by_input_or_output_id(input_id)
276 .expect("Failed to find transition ID by input ID");
277 assert_eq!(res, AleoID::from_str("au175l9ljm7k0r7rgp3dxkr9upp5xdlxp97zsv0gq9h4f73w9y835rs5svnk9").unwrap())
278 }
279
280 #[test]
281 fn test_find_transition_id_by_public_input_id() {
282 let agent = Agent::default();
283 let input_id = Field::from_str("1189069982776259983430750750378210517138284966096341396999865832791709072861field").unwrap();
284 let res = agent
285 .find_transition_id_by_input_or_output_id(input_id)
286 .expect("Failed to find transition ID by input ID");
287 assert_eq!(res, AleoID::from_str("au175l9ljm7k0r7rgp3dxkr9upp5xdlxp97zsv0gq9h4f73w9y835rs5svnk9").unwrap())
288 }
289
290 #[test]
291 fn test_find_transition_id_by_output_id() {
292 let agent = Agent::default();
293 let input_or_output_id = Field::from_str("6367764211666803735912595104467722517989468495803138547640524449862797481066field").unwrap();
294 let res = agent
295 .find_transition_id_by_input_or_output_id(input_or_output_id)
296 .expect("Failed to find transition ID by input ID");
297 assert_eq!(res, AleoID::from_str("au175l9ljm7k0r7rgp3dxkr9upp5xdlxp97zsv0gq9h4f73w9y835rs5svnk9").unwrap())
298 }
299
300 #[test]
301 fn test_find_block_hash_by_transaction_id() {
302 let agent = Agent::default();
303 let transaction_id = TransactionID::from_str("at1dtzwtj4kucw5wvyrdjse5quuu5kzy0psrqqlqv5zu7nxhutpdyxqwhte0h").unwrap();
304 let res = agent
305 .find_block_hash_by_transaction_id(&transaction_id)
306 .expect("Failed to find block hash by transaction ID");
307 assert_eq!(res, BlockHash::from_str("ab192jlmwheq53yd8xmezx5vr3m2pc00vafcnwrf25jv0qgt23p6sxsju0ntk").unwrap())
308 }
309
310 #[test]
318 fn test_get_transaction_by_id(){
319 let agent = Agent::default();
320 let transaction_id = "at1dtzwtj4kucw5wvyrdjse5quuu5kzy0psrqqlqv5zu7nxhutpdyxqwhte0h";
321 let res = agent.get_transaction(transaction_id).expect("Failed to get transaction by id");
322 assert_eq!(res.id(), TransactionID::from_str(transaction_id).unwrap())
323 }
324
325 #[test]
326 fn test_get_confirmed_transaction_by_id() {
327 let agent = Agent::default();
328 let transaction_id = "at1dtzwtj4kucw5wvyrdjse5quuu5kzy0psrqqlqv5zu7nxhutpdyxqwhte0h";
329 let res = agent.get_confirmed_transaction(transaction_id).expect("Failed to get confirmed transaction by id");
330 assert_eq!(res.id(), TransactionID::from_str(transaction_id).unwrap())
331 }
332}