use crate::assets::{Asset, Assets, string_to_u64};
use crate::constants::{MAXIMUM_TOKENS_PER_UTXO, MAXIMUM_WALLET_UTXOS};
use crate::transaction::wallet_minimum_lovelace_with_assets;
use anyhow::{Context, Result, anyhow, bail};
use blstrs::Scalar;
use hex;
use seedelf_crypto::register::Register;
use seedelf_koios::koios::{
UtxoResponse, address_utxos, contains_policy_id, credential_utxos, extract_bytes_with_logging,
};
pub async fn get_credential_utxos(
wallet_contract_hash: [u8; 28],
network_flag: bool,
) -> Result<Vec<UtxoResponse>> {
let utxos: Vec<UtxoResponse> =
credential_utxos(hex::encode(wallet_contract_hash).as_str(), network_flag)
.await
.context("Failed To Get Credential UTxOs")?;
Ok(utxos)
}
pub async fn get_address_utxos(address: &str, network_flag: bool) -> Result<Vec<UtxoResponse>> {
let utxos: Vec<UtxoResponse> = address_utxos(address, network_flag)
.await
.context("Failed To Get Address UTxOs")?;
Ok(utxos)
}
pub fn collect_all_wallet_utxos(
sk: Scalar,
seedelf_policy_id: &str,
utxos: Vec<UtxoResponse>,
) -> Result<Vec<UtxoResponse>> {
let mut all_utxos: Vec<UtxoResponse> = Vec::new();
for utxo in utxos {
if let Some(inline_datum) = extract_bytes_with_logging(&utxo.inline_datum) {
if inline_datum
.is_owned(sk)
.context("Failed To Construct Points")?
{
if !contains_policy_id(&utxo.asset_list, seedelf_policy_id) {
all_utxos.push(utxo.clone());
}
}
}
}
Ok(all_utxos)
}
pub fn find_seedelf_and_wallet_utxos(
sk: Scalar,
seedelf: String,
seedelf_policy_id: &str,
utxos: Vec<UtxoResponse>,
) -> Result<(Option<Register>, Vec<UtxoResponse>)> {
let mut usable_utxos: Vec<UtxoResponse> = Vec::new();
let mut number_of_utxos: u64 = 0;
let mut seedelf_datum: Option<Register> = None;
let mut found_seedelf: bool = false;
for utxo in utxos {
if let Some(inline_datum) = extract_bytes_with_logging(&utxo.inline_datum) {
if !found_seedelf && contains_policy_id(&utxo.asset_list, seedelf_policy_id) {
let asset_name = utxo
.asset_list
.as_ref()
.and_then(|vec| {
vec.iter()
.find(|asset| asset.policy_id == seedelf_policy_id)
.map(|asset| &asset.asset_name)
})
.context("Can't Produce Asset Name")?;
if asset_name == &seedelf {
found_seedelf = true;
seedelf_datum = Some(inline_datum.clone());
}
}
if inline_datum
.is_owned(sk)
.context("Failed To Construct Points")?
{
if !contains_policy_id(&utxo.asset_list, seedelf_policy_id) {
if number_of_utxos >= MAXIMUM_WALLET_UTXOS {
break;
}
usable_utxos.push(utxo);
number_of_utxos += 1;
}
}
}
}
Ok((seedelf_datum, usable_utxos))
}
pub fn find_seedelf_utxo(
seedelf: String,
seedelf_policy_id: &str,
utxos: Vec<UtxoResponse>,
) -> Result<Option<UtxoResponse>> {
for utxo in utxos {
if contains_policy_id(&utxo.asset_list, seedelf_policy_id) {
let asset_name = utxo
.asset_list
.as_ref()
.and_then(|vec| {
vec.iter()
.find(|asset| asset.policy_id == seedelf_policy_id)
.map(|asset| &asset.asset_name)
})
.context("Can't Produce Asset Name")?;
if asset_name == &seedelf {
return Ok(Some(utxo));
}
}
}
Ok(None)
}
pub fn collect_wallet_utxos(
sk: Scalar,
seedelf_policy_id: &str,
utxos: Vec<UtxoResponse>,
) -> Result<Vec<UtxoResponse>> {
let mut number_of_utxos: u64 = 0;
let mut usable_utxos: Vec<UtxoResponse> = Vec::new();
for utxo in utxos {
if let Some(inline_datum) = extract_bytes_with_logging(&utxo.inline_datum) {
if inline_datum
.is_owned(sk)
.context("Failed To Construct Points")?
{
if !contains_policy_id(&utxo.asset_list, seedelf_policy_id) {
if number_of_utxos >= MAXIMUM_WALLET_UTXOS {
break;
}
usable_utxos.push(utxo);
number_of_utxos += 1;
}
}
}
}
Ok(usable_utxos)
}
pub fn collect_address_utxos(utxos: Vec<UtxoResponse>) -> Result<Vec<UtxoResponse>> {
let mut usable_utxos: Vec<UtxoResponse> = Vec::new();
for utxo in utxos {
let lovelace: u64 = utxo.value.parse::<u64>().context("Invalid Lovelace")?;
if let Some(assets) = &utxo.asset_list {
if assets.is_empty() && lovelace == 5_000_000 {
} else {
usable_utxos.push(utxo);
}
}
}
Ok(usable_utxos)
}
pub fn select(
utxos: Vec<UtxoResponse>,
lovelace: u64,
tokens: Assets,
) -> Result<Vec<UtxoResponse>> {
do_select(utxos, lovelace, tokens, lovelace).context("Do Select Failed")
}
pub fn do_select(
mut utxos: Vec<UtxoResponse>,
lovelace: u64,
tokens: Assets,
lovelace_goal: u64,
) -> Result<Vec<UtxoResponse>> {
let mut selected_utxos: Vec<UtxoResponse> = Vec::new();
let mut current_lovelace_sum: u64 = 0;
let mut found_enough: bool = false;
let mut found_assets: Assets = Assets::new();
utxos.sort_by(|a, b| {
let a_group_key = a.asset_list.as_ref().is_some_and(|list| list.is_empty());
let b_group_key = b.asset_list.as_ref().is_some_and(|list| list.is_empty());
b_group_key.cmp(&a_group_key).then_with(|| {
string_to_u64(b.value.clone())
.into_iter()
.cmp(string_to_u64(a.value.clone()))
})
});
for utxo in utxos.clone() {
let value: u64 = string_to_u64(utxo.value.clone()).context("Invalid UTxO Value")?;
let mut utxo_assets: Assets = Assets::new();
let mut added: bool = false;
if let Some(assets) = utxo.clone().asset_list {
if !assets.is_empty() {
for token in assets.clone() {
utxo_assets = utxo_assets
.add(
Asset::new(
token.policy_id,
token.asset_name,
string_to_u64(token.quantity).context("Invalid Asset Amount")?,
)
.context("Invalid Asset")?,
)
.context("Can't Add Assets")?;
}
if utxo_assets.any(tokens.clone()) && !found_assets.contains(tokens.clone()) {
selected_utxos.push(utxo.clone());
current_lovelace_sum += value;
found_assets = found_assets
.merge(utxo_assets.clone())
.context("Can't Merge Assets")?;
added = true;
}
} else {
if current_lovelace_sum < lovelace {
selected_utxos.push(utxo.clone());
current_lovelace_sum += value;
added = true;
}
}
}
if !added && current_lovelace_sum < lovelace && found_assets.contains(tokens.clone()) {
selected_utxos.push(utxo.clone());
current_lovelace_sum += value;
found_assets = found_assets
.merge(utxo_assets)
.context("Can't Merge Assets")?;
}
if current_lovelace_sum >= lovelace && found_assets.contains(tokens.clone()) {
let change_assets: Assets = found_assets
.separate(tokens.clone())
.context("Can't Separate Assets")?;
let number_of_change_assets: u64 = change_assets.len();
let minimum: u64 = wallet_minimum_lovelace_with_assets(change_assets.clone())
.context("Invalid Minimum Lovelace")?;
let multiplier: u64 = if number_of_change_assets > MAXIMUM_TOKENS_PER_UTXO {
(number_of_change_assets / MAXIMUM_TOKENS_PER_UTXO) + 1
} else {
1
};
if current_lovelace_sum - multiplier * minimum >= lovelace_goal {
found_enough = true;
break;
} else {
return do_select(
utxos.clone(),
lovelace + multiplier * minimum,
tokens.clone(),
lovelace_goal,
);
}
}
}
if found_enough {
Ok(selected_utxos)
} else {
Ok(Vec::new())
}
}
pub fn assets_of(utxos: Vec<UtxoResponse>) -> Result<(u64, Assets)> {
let mut found_assets: Assets = Assets::new();
let mut current_lovelace_sum: u64 = 0;
for utxo in utxos.clone() {
let value: u64 = string_to_u64(utxo.value.clone()).context("Invalid UTxO Value")?;
current_lovelace_sum += value;
if let Some(assets) = utxo.clone().asset_list {
if !assets.is_empty() {
let mut utxo_assets: Assets = Assets::new();
for token in assets.clone() {
let new_asset = Asset::new(
token.policy_id,
token.asset_name,
string_to_u64(token.quantity).context("Invalid Token Quantity")?,
)
.context("Fail To Construct Asset")?;
utxo_assets = utxo_assets.add(new_asset).context("Can't Add Assets")?;
}
found_assets = found_assets
.merge(utxo_assets.clone())
.context("Can't Merge Assets")?;
}
}
}
Ok((current_lovelace_sum, found_assets))
}
pub fn find_all_seedelfs(
label: String,
seedelf_policy_id: &str,
utxos: Vec<UtxoResponse>,
) -> Result<Vec<String>> {
let mut matches = Vec::new();
for utxo in utxos {
if contains_policy_id(&utxo.asset_list, seedelf_policy_id) {
let asset_name = utxo
.asset_list
.as_ref()
.and_then(|vec| {
vec.iter()
.find(|asset| asset.policy_id == seedelf_policy_id)
.map(|asset| &asset.asset_name)
})
.context("Can't Produce Asset Name")?;
if asset_name.to_lowercase().contains(&label.to_lowercase()) {
matches.push(asset_name.to_string());
}
}
}
Ok(matches)
}
pub fn count_lovelace_and_utxos(
seedelf_policy_id: &str,
utxos: Vec<UtxoResponse>,
) -> Result<(usize, u64, u64)> {
let mut total_lovelace: u64 = 0;
let mut total_seedelfs: u64 = 0;
for utxo in utxos.clone() {
if contains_policy_id(&utxo.asset_list, seedelf_policy_id) {
total_seedelfs += 1;
}
let value: u64 =
string_to_u64(utxo.value.clone()).context("failed to parse lovelace value")?;
total_lovelace += value;
}
Ok((utxos.len(), total_lovelace, total_seedelfs))
}
pub fn parse_tx_utxos(utxos: Vec<String>) -> Result<Vec<(String, u64)>> {
utxos
.into_iter()
.map(|s| {
let parts: Vec<&str> = s.split('#').collect();
if parts.len() != 2 {
bail!("Invalid input format: {s}");
}
let tx_hash = parts[0].to_string();
let index = parts[1]
.parse::<u64>()
.map_err(|_| anyhow!("Invalid index in input: {s}"))?;
Ok((tx_hash, index))
})
.collect()
}
pub fn filter_utxos(utxos: Vec<UtxoResponse>, targets: Vec<(String, u64)>) -> Vec<UtxoResponse> {
use std::collections::HashSet;
let target_set: HashSet<(String, u64)> = targets.into_iter().collect();
utxos
.into_iter()
.filter(|u| target_set.contains(&(u.tx_hash.clone(), u.tx_index)))
.collect()
}