1use crate::address;
2use crate::constants::ADA_HANDLE_POLICY_ID;
3use crate::register::Register;
4use hex;
5use reqwest::{Client, Error, Response};
6use serde::Deserialize;
7use serde_json::Value;
8
9#[derive(Deserialize, Debug)]
11pub struct BlockchainTip {
12 pub hash: String,
13 pub epoch_no: u64,
14 pub abs_slot: u64,
15 pub epoch_slot: u64,
16 pub block_no: u64,
17 pub block_time: u64,
18}
19
20pub async fn tip(network_flag: bool) -> Result<Vec<BlockchainTip>, Error> {
36 let network: &str = if network_flag { "preprod" } else { "api" };
37 let url: String = format!("https://{}.koios.rest/api/v1/tip", network);
38
39 let response: Vec<BlockchainTip> = reqwest::get(&url)
41 .await?
42 .json::<Vec<BlockchainTip>>()
43 .await?;
44
45 Ok(response)
46}
47
48#[derive(Debug, Deserialize, Clone, Default)]
49pub struct Asset {
50 pub decimals: u8,
51 pub quantity: String,
52 pub policy_id: String,
53 pub asset_name: String,
54 pub fingerprint: String,
55}
56
57#[derive(Debug, Deserialize, Clone, Default)]
58pub struct InlineDatum {
59 pub bytes: String,
60 pub value: Value, }
62
63#[derive(Debug, Deserialize, Clone, Default)]
64pub struct UtxoResponse {
65 pub tx_hash: String,
66 pub tx_index: u64,
67 pub address: String,
68 pub value: String,
69 pub stake_address: Option<String>,
70 pub payment_cred: String,
71 pub epoch_no: u64,
72 pub block_height: u64,
73 pub block_time: u64,
74 pub datum_hash: Option<String>,
75 pub inline_datum: Option<InlineDatum>,
76 pub reference_script: Option<Value>, pub asset_list: Option<Vec<Asset>>,
78 pub is_spent: bool,
79}
80
81pub async fn credential_utxos(
103 payment_credential: &str,
104 network_flag: bool,
105) -> Result<Vec<UtxoResponse>, Error> {
106 let network: &str = if network_flag { "preprod" } else { "api" };
107 let url: String = format!("https://{}.koios.rest/api/v1/credential_utxos", network);
109 let client: Client = reqwest::Client::new();
110
111 let payload: Value = serde_json::json!({
113 "_payment_credentials": [payment_credential],
114 "_extended": true
115 });
116
117 let mut all_utxos: Vec<UtxoResponse> = Vec::new();
118 let mut offset: i32 = 0;
119
120 loop {
121 let response: Response = client
123 .post(url.clone())
124 .header("accept", "application/json")
125 .header("content-type", "application/json")
126 .query(&[("offset", offset.to_string())])
127 .json(&payload)
128 .send()
129 .await?;
130
131 let mut utxos: Vec<UtxoResponse> = response.json().await?;
132 if utxos.is_empty() {
134 break;
135 }
136
137 all_utxos.append(&mut utxos);
139
140 offset += 1000;
142 }
143
144 Ok(all_utxos)
145}
146
147pub async fn address_utxos(address: &str, network_flag: bool) -> Result<Vec<UtxoResponse>, Error> {
169 let network: &str = if network_flag { "preprod" } else { "api" };
170 let url: String = format!("https://{}.koios.rest/api/v1/address_utxos", network);
174 let client: Client = reqwest::Client::new();
175
176 let payload: Value = serde_json::json!({
178 "_addresses": [address],
179 "_extended": true
180 });
181
182 let response: Response = client
184 .post(url)
185 .header("accept", "application/json")
186 .header("content-type", "application/json")
187 .json(&payload)
188 .send()
189 .await?;
190
191 let utxos: Vec<UtxoResponse> = response.json().await?;
192
193 Ok(utxos)
194}
195
196pub fn extract_bytes_with_logging(inline_datum: &Option<InlineDatum>) -> Option<Register> {
220 if let Some(datum) = inline_datum {
221 if let Value::Object(ref value_map) = datum.value {
222 if let Some(Value::Array(fields)) = value_map.get("fields") {
223 if let (Some(first), Some(second)) = (fields.first(), fields.get(1)) {
224 let first_bytes: String = first.get("bytes")?.as_str()?.to_string();
225 let second_bytes: String = second.get("bytes")?.as_str()?.to_string();
226 return Some(Register::new(first_bytes, second_bytes));
227 } else {
228 eprintln!("Fields array has fewer than two elements.");
229 }
230 } else {
231 eprintln!("`fields` key is missing or not an array.");
232 }
233 } else {
234 eprintln!("`value` is not an object.");
235 }
236 } else {
237 eprintln!("Inline datum is None.");
238 }
239 None
240}
241
242pub fn contains_policy_id(asset_list: &Option<Vec<Asset>>, target_policy_id: &str) -> bool {
263 asset_list
264 .as_ref() .is_some_and(|assets| {
266 assets
267 .iter()
268 .any(|asset| asset.policy_id == target_policy_id)
269 })
270}
271
272pub async fn evaluate_transaction(tx_cbor: String, network_flag: bool) -> Result<Value, Error> {
295 let network: &str = if network_flag { "preprod" } else { "api" };
296
297 let payload: Value = serde_json::json!({
299 "jsonrpc": "2.0",
300 "method": "evaluateTransaction",
301 "params": {
302 "transaction": {
303 "cbor": tx_cbor
304 }
305 }
306 });
307
308 let url: String = format!("https://{}.koios.rest/api/v1/ogmios", network);
309 let client: Client = reqwest::Client::new();
310
311 let response: Response = client
313 .post(url)
314 .header("accept", "application/json")
315 .header("content-type", "application/json")
316 .json(&payload)
317 .send()
318 .await?;
319
320 response.json().await
321}
322
323pub async fn witness_collateral(tx_cbor: String, network_flag: bool) -> Result<Value, Error> {
345 let network: &str = if network_flag { "preprod" } else { "mainnet" };
346 let url: String = format!("https://www.giveme.my/{}/collateral/", network);
347 let client: Client = reqwest::Client::new();
348
349 let payload: Value = serde_json::json!({
350 "tx_body": tx_cbor,
351 });
352
353 let response: Response = client
355 .post(url)
356 .header("content-type", "application/json")
357 .json(&payload)
358 .send()
359 .await?;
360
361 response.json().await
362}
363
364pub async fn submit_tx(tx_cbor: String, network_flag: bool) -> Result<Value, Error> {
387 let network: &str = if network_flag { "preprod" } else { "api" };
388 let url: String = format!("https://{}.koios.rest/api/v1/submittx", network);
389 let client: Client = reqwest::Client::new();
390
391 let data: Vec<u8> = hex::decode(&tx_cbor).unwrap();
393
394 let response: Response = client
395 .post(url)
396 .header("Content-Type", "application/cbor")
397 .body(data) .send()
399 .await?;
400
401 response.json().await
402}
403
404pub async fn ada_handle_address(
405 asset_name: String,
406 network_flag: bool,
407 cip68_flag: bool,
408 variant: u64,
409) -> Result<String, String> {
410 let network: &str = if network_flag { "preprod" } else { "api" };
411 let token_name: String = if cip68_flag {
412 "000de140".to_string() + &hex::encode(asset_name.clone())
413 } else {
414 hex::encode(asset_name.clone())
415 };
416 let url: String = format!(
417 "https://{}.koios.rest/api/v1/asset_nft_address?_asset_policy={}&_asset_name={}",
418 network,
419 ADA_HANDLE_POLICY_ID,
420 token_name
422 );
423 let client: Client = reqwest::Client::new();
424
425 let response: Response = match client
426 .get(url)
427 .header("Content-Type", "application/json")
428 .send()
429 .await
430 {
431 Ok(resp) => resp,
432 Err(err) => return Err(format!("HTTP request failed: {}", err)),
433 };
434
435 let outcome: Value = response.json().await.unwrap();
436 let vec_outcome = serde_json::from_value::<Vec<serde_json::Value>>(outcome)
437 .expect("Failed to parse outcome as Vec<Value>");
438
439 let payment_address = match vec_outcome
441 .first()
442 .and_then(|obj| obj.get("payment_address"))
443 .and_then(|val| val.as_str())
444 {
445 Some(address) => address,
446 None => {
447 if cip68_flag {
448 return Err("Payment address not found".to_string());
449 } else {
450 return Box::pin(ada_handle_address(
451 asset_name,
452 network_flag,
453 !cip68_flag,
454 variant,
455 ))
456 .await;
457 }
458 }
459 };
460
461 let wallet_addr: String = address::wallet_contract(network_flag, variant)
462 .to_bech32()
463 .unwrap();
464
465 if payment_address == wallet_addr {
466 Err("ADA Handle Is In Wallet Address".to_string())
467 } else {
468 Ok(payment_address.to_string())
469 }
470}
471
472pub async fn utxo_info(utxo: &str, network_flag: bool) -> Result<Vec<UtxoResponse>, Error> {
473 let network: &str = if network_flag { "preprod" } else { "api" };
474 let url: String = format!("https://{}.koios.rest/api/v1/utxo_info", network);
478 let client: Client = reqwest::Client::new();
479
480 let payload: Value = serde_json::json!({
482 "_utxo_refs": [utxo],
483 "_extended": true
484 });
485
486 let response: Response = client
488 .post(url)
489 .header("accept", "application/json")
490 .header("content-type", "application/json")
491 .json(&payload)
492 .send()
493 .await?;
494
495 let utxos: Vec<UtxoResponse> = response.json().await?;
496
497 Ok(utxos)
498}
499
500pub async fn nft_utxo(
502 policy_id: String,
503 token_name: String,
504 network_flag: bool,
505) -> Result<Vec<UtxoResponse>, Error> {
506 let network: &str = if network_flag { "preprod" } else { "api" };
507 let url: String = format!("https://{}.koios.rest/api/v1/asset_utxos", network);
508 let client: Client = reqwest::Client::new();
509
510 let payload: Value = serde_json::json!({
512 "_asset_list": [[policy_id, token_name]],
513 "_extended": true
514 });
515
516 let response: Response = client
518 .post(url)
519 .header("accept", "application/json")
520 .header("content-type", "application/json")
521 .json(&payload)
522 .send()
523 .await?;
524
525 let utxos: Vec<UtxoResponse> = response.json().await?;
526
527 if utxos.len() > 1 {
528 return Ok(vec![]);
529 }
530
531 Ok(utxos)
532}
533
534#[derive(Debug, Deserialize, Clone, Default)]
535pub struct ResolvedDatum {
536 pub datum_hash: Option<String>,
537 pub creation_tx_hash: String,
538 pub value: Value,
539 pub bytes: Option<String>,
540}
541
542pub async fn datum_from_datum_hash(
543 datum_hash: String,
544 network_flag: bool,
545) -> Result<Vec<ResolvedDatum>, Error> {
546 let network: &str = if network_flag { "preprod" } else { "api" };
547 let url: String = format!("https://{}.koios.rest/api/v1/datum_info", network);
548 let client: Client = reqwest::Client::new();
549
550 let payload: Value = serde_json::json!({
552 "_datum_hashes": [datum_hash],
553 });
554
555 let response: Response = client
557 .post(url)
558 .header("accept", "application/json")
559 .header("content-type", "application/json")
560 .json(&payload)
561 .send()
562 .await?;
563
564 let datums: Vec<ResolvedDatum> = response.json().await?;
565 Ok(datums)
566}
567
568#[derive(Debug, Deserialize, Clone, Default)]
569pub struct History {
570 pub tx_hash: String,
571 pub epoch_no: u64,
572 pub block_height: Option<u64>,
573 pub block_time: u64,
574}
575
576pub async fn asset_history(
577 policy_id: String,
578 token_name: String,
579 network_flag: bool,
580 limit: u64,
581) -> Result<Vec<String>, Error> {
582 let network: &str = if network_flag { "preprod" } else { "api" };
583 let url: String = format!(
584 "https://{}.koios.rest/api/v1/asset_txs?_asset_policy={}&_asset_name={}&_after_block_height=50000&_history=true&limit={}",
585 network, policy_id, token_name, limit
586 );
587 let client: Client = reqwest::Client::new();
588
589 let response: Response = client
591 .get(url)
592 .header("content-type", "application/json")
593 .send()
594 .await?;
595
596 let data: Vec<History> = response.json().await.unwrap();
597 let tx_hashes: Vec<String> = data.iter().map(|h| h.tx_hash.clone()).collect();
598 Ok(tx_hashes)
599}