use crate::address;
use crate::constants::ADA_HANDLE_POLICY_ID;
use crate::register::Register;
use hex;
use reqwest::{Client, Error, Response};
use serde::Deserialize;
use serde_json::Value;
#[derive(Deserialize, Debug)]
pub struct BlockchainTip {
pub hash: String,
pub epoch_no: u64,
pub abs_slot: u64,
pub epoch_slot: u64,
pub block_no: u64,
pub block_time: u64,
}
pub async fn tip(network_flag: bool) -> Result<Vec<BlockchainTip>, Error> {
let network: &str = if network_flag { "preprod" } else { "api" };
let url: String = format!("https://{}.koios.rest/api/v1/tip", network);
let response: Vec<BlockchainTip> = reqwest::get(&url)
.await?
.json::<Vec<BlockchainTip>>()
.await?;
Ok(response)
}
#[derive(Debug, Deserialize, Clone, Default)]
pub struct Asset {
pub decimals: u8,
pub quantity: String,
pub policy_id: String,
pub asset_name: String,
pub fingerprint: String,
}
#[derive(Debug, Deserialize, Clone, Default)]
pub struct InlineDatum {
pub bytes: String,
pub value: Value, }
#[derive(Debug, Deserialize, Clone, Default)]
pub struct UtxoResponse {
pub tx_hash: String,
pub tx_index: u64,
pub address: String,
pub value: String,
pub stake_address: Option<String>,
pub payment_cred: String,
pub epoch_no: u64,
pub block_height: u64,
pub block_time: u64,
pub datum_hash: Option<String>,
pub inline_datum: Option<InlineDatum>,
pub reference_script: Option<Value>, pub asset_list: Option<Vec<Asset>>,
pub is_spent: bool,
}
pub async fn credential_utxos(
payment_credential: &str,
network_flag: bool,
) -> Result<Vec<UtxoResponse>, Error> {
let network: &str = if network_flag { "preprod" } else { "api" };
let url: String = format!("https://{}.koios.rest/api/v1/credential_utxos", network);
let client: Client = reqwest::Client::new();
let payload: Value = serde_json::json!({
"_payment_credentials": [payment_credential],
"_extended": true
});
let mut all_utxos: Vec<UtxoResponse> = Vec::new();
let mut offset: i32 = 0;
loop {
let response: Response = client
.post(url.clone())
.header("accept", "application/json")
.header("content-type", "application/json")
.query(&[("offset", offset.to_string())])
.json(&payload)
.send()
.await?;
let mut utxos: Vec<UtxoResponse> = response.json().await?;
if utxos.is_empty() {
break;
}
all_utxos.append(&mut utxos);
offset += 1000;
}
Ok(all_utxos)
}
pub async fn address_utxos(address: &str, network_flag: bool) -> Result<Vec<UtxoResponse>, Error> {
let network: &str = if network_flag { "preprod" } else { "api" };
let url: String = format!("https://{}.koios.rest/api/v1/address_utxos", network);
let client: Client = reqwest::Client::new();
let payload: Value = serde_json::json!({
"_addresses": [address],
"_extended": true
});
let response: Response = client
.post(url)
.header("accept", "application/json")
.header("content-type", "application/json")
.json(&payload)
.send()
.await?;
let utxos: Vec<UtxoResponse> = response.json().await?;
Ok(utxos)
}
pub fn extract_bytes_with_logging(inline_datum: &Option<InlineDatum>) -> Option<Register> {
if let Some(datum) = inline_datum {
if let Value::Object(ref value_map) = datum.value {
if let Some(Value::Array(fields)) = value_map.get("fields") {
if let (Some(first), Some(second)) = (fields.first(), fields.get(1)) {
let first_bytes: String = first.get("bytes")?.as_str()?.to_string();
let second_bytes: String = second.get("bytes")?.as_str()?.to_string();
return Some(Register::new(first_bytes, second_bytes));
} else {
eprintln!("Fields array has fewer than two elements.");
}
} else {
eprintln!("`fields` key is missing or not an array.");
}
} else {
eprintln!("`value` is not an object.");
}
} else {
eprintln!("Inline datum is None.");
}
None
}
pub fn contains_policy_id(asset_list: &Option<Vec<Asset>>, target_policy_id: &str) -> bool {
asset_list
.as_ref() .is_some_and(|assets| {
assets
.iter()
.any(|asset| asset.policy_id == target_policy_id)
})
}
pub async fn evaluate_transaction(tx_cbor: String, network_flag: bool) -> Result<Value, Error> {
let network: &str = if network_flag { "preprod" } else { "api" };
let payload: Value = serde_json::json!({
"jsonrpc": "2.0",
"method": "evaluateTransaction",
"params": {
"transaction": {
"cbor": tx_cbor
}
}
});
let url: String = format!("https://{}.koios.rest/api/v1/ogmios", network);
let client: Client = reqwest::Client::new();
let response: Response = client
.post(url)
.header("accept", "application/json")
.header("content-type", "application/json")
.json(&payload)
.send()
.await?;
response.json().await
}
pub async fn witness_collateral(tx_cbor: String, network_flag: bool) -> Result<Value, Error> {
let network: &str = if network_flag { "preprod" } else { "mainnet" };
let url: String = format!("https://www.giveme.my/{}/collateral/", network);
let client: Client = reqwest::Client::new();
let payload: Value = serde_json::json!({
"tx_body": tx_cbor,
});
let response: Response = client
.post(url)
.header("content-type", "application/json")
.json(&payload)
.send()
.await?;
response.json().await
}
pub async fn submit_tx(tx_cbor: String, network_flag: bool) -> Result<Value, Error> {
let network: &str = if network_flag { "preprod" } else { "api" };
let url: String = format!("https://{}.koios.rest/api/v1/submittx", network);
let client: Client = reqwest::Client::new();
let data: Vec<u8> = hex::decode(&tx_cbor).unwrap();
let response: Response = client
.post(url)
.header("Content-Type", "application/cbor")
.body(data) .send()
.await?;
response.json().await
}
pub async fn ada_handle_address(
asset_name: String,
network_flag: bool,
cip68_flag: bool,
variant: u64,
) -> Result<String, String> {
let network: &str = if network_flag { "preprod" } else { "api" };
let token_name: String = if cip68_flag {
"000de140".to_string() + &hex::encode(asset_name.clone())
} else {
hex::encode(asset_name.clone())
};
let url: String = format!(
"https://{}.koios.rest/api/v1/asset_nft_address?_asset_policy={}&_asset_name={}",
network,
ADA_HANDLE_POLICY_ID,
token_name
);
let client: Client = reqwest::Client::new();
let response: Response = match client
.get(url)
.header("Content-Type", "application/json")
.send()
.await
{
Ok(resp) => resp,
Err(err) => return Err(format!("HTTP request failed: {}", err)),
};
let outcome: Value = response.json().await.unwrap();
let vec_outcome = serde_json::from_value::<Vec<serde_json::Value>>(outcome)
.expect("Failed to parse outcome as Vec<Value>");
let payment_address = match vec_outcome
.first()
.and_then(|obj| obj.get("payment_address"))
.and_then(|val| val.as_str())
{
Some(address) => address,
None => {
if cip68_flag {
return Err("Payment address not found".to_string());
} else {
return Box::pin(ada_handle_address(
asset_name,
network_flag,
!cip68_flag,
variant,
))
.await;
}
}
};
let wallet_addr: String = address::wallet_contract(network_flag, variant)
.to_bech32()
.unwrap();
if payment_address == wallet_addr {
Err("ADA Handle Is In Wallet Address".to_string())
} else {
Ok(payment_address.to_string())
}
}
pub async fn utxo_info(utxo: &str, network_flag: bool) -> Result<Vec<UtxoResponse>, Error> {
let network: &str = if network_flag { "preprod" } else { "api" };
let url: String = format!("https://{}.koios.rest/api/v1/utxo_info", network);
let client: Client = reqwest::Client::new();
let payload: Value = serde_json::json!({
"_utxo_refs": [utxo],
"_extended": true
});
let response: Response = client
.post(url)
.header("accept", "application/json")
.header("content-type", "application/json")
.json(&payload)
.send()
.await?;
let utxos: Vec<UtxoResponse> = response.json().await?;
Ok(utxos)
}
pub async fn nft_utxo(
policy_id: String,
token_name: String,
network_flag: bool,
) -> Result<Vec<UtxoResponse>, Error> {
let network: &str = if network_flag { "preprod" } else { "api" };
let url: String = format!("https://{}.koios.rest/api/v1/asset_utxos", network);
let client: Client = reqwest::Client::new();
let payload: Value = serde_json::json!({
"_asset_list": [[policy_id, token_name]],
"_extended": true
});
let response: Response = client
.post(url)
.header("accept", "application/json")
.header("content-type", "application/json")
.json(&payload)
.send()
.await?;
let utxos: Vec<UtxoResponse> = response.json().await?;
if utxos.len() > 1 {
return Ok(vec![]);
}
Ok(utxos)
}
#[derive(Debug, Deserialize, Clone, Default)]
pub struct ResolvedDatum {
pub datum_hash: Option<String>,
pub creation_tx_hash: String,
pub value: Value,
pub bytes: Option<String>,
}
pub async fn datum_from_datum_hash(
datum_hash: String,
network_flag: bool,
) -> Result<Vec<ResolvedDatum>, Error> {
let network: &str = if network_flag { "preprod" } else { "api" };
let url: String = format!("https://{}.koios.rest/api/v1/datum_info", network);
let client: Client = reqwest::Client::new();
let payload: Value = serde_json::json!({
"_datum_hashes": [datum_hash],
});
let response: Response = client
.post(url)
.header("accept", "application/json")
.header("content-type", "application/json")
.json(&payload)
.send()
.await?;
let datums: Vec<ResolvedDatum> = response.json().await?;
Ok(datums)
}
#[derive(Debug, Deserialize, Clone, Default)]
pub struct History {
pub tx_hash: String,
pub epoch_no: u64,
pub block_height: Option<u64>,
pub block_time: u64,
}
pub async fn asset_history(
policy_id: String,
token_name: String,
network_flag: bool,
limit: u64,
) -> Result<Vec<String>, Error> {
let network: &str = if network_flag { "preprod" } else { "api" };
let url: String = format!(
"https://{}.koios.rest/api/v1/asset_txs?_asset_policy={}&_asset_name={}&_after_block_height=50000&_history=true&limit={}",
network, policy_id, token_name, limit
);
let client: Client = reqwest::Client::new();
let response: Response = client
.get(url)
.header("content-type", "application/json")
.send()
.await?;
let data: Vec<History> = response.json().await.unwrap();
let tx_hashes: Vec<String> = data.iter().map(|h| h.tx_hash.clone()).collect();
Ok(tx_hashes)
}