seedelf_cli/utxos.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387
use crate::assets::{string_to_u64, Asset, Assets};
use crate::constants::{
MAXIMUM_TOKENS_PER_UTXO, MAXIMUM_WALLET_UTXOS, SEEDELF_POLICY_ID, WALLET_CONTRACT_HASH,
};
use crate::display::seedelf_label;
use crate::koios::{
address_utxos, contains_policy_id, credential_utxos, extract_bytes_with_logging, UtxoResponse,
};
use crate::register::Register;
use crate::transaction::wallet_minimum_lovelace_with_assets;
use blstrs::Scalar;
use colored::Colorize;
/// collects all the wallet utxos owned by some scalar.
pub async fn collect_all_wallet_utxos(sk: Scalar, network_flag: bool) -> Vec<UtxoResponse> {
let mut all_utxos: Vec<UtxoResponse> = Vec::new();
match credential_utxos(WALLET_CONTRACT_HASH, network_flag).await {
Ok(utxos) => {
for utxo in utxos {
if let Some(inline_datum) = extract_bytes_with_logging(&utxo.inline_datum) {
// utxo must be owned by this secret scaler
if inline_datum.is_owned(sk) {
// its owned but lets not count the seedelf in the balance
if !contains_policy_id(&utxo.asset_list, SEEDELF_POLICY_ID) {
all_utxos.push(utxo.clone());
}
}
}
}
}
Err(err) => {
eprintln!(
"Failed to fetch UTxOs: {}\nWait a few moments and try again.",
err
);
}
}
all_utxos
}
/// Find a specific seedelf's datum and all the utxos owned by a scalar. The maximum amount of utxos is limited by a upper bound.
pub async fn find_seedelf_and_wallet_utxos(
sk: Scalar,
seedelf: String,
network_flag: bool,
) -> (Option<Register>, Vec<UtxoResponse>) {
let mut usuable_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;
match credential_utxos(WALLET_CONTRACT_HASH, network_flag).await {
Ok(utxos) => {
for utxo in utxos {
// Extract bytes
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)
})
.unwrap();
if asset_name == &seedelf {
found_seedelf = true;
seedelf_datum = Some(inline_datum.clone());
}
}
// utxo must be owned by this secret scaler
if inline_datum.is_owned(sk) {
// its owned but it can't hold a seedelf
if !contains_policy_id(&utxo.asset_list, SEEDELF_POLICY_ID) {
if number_of_utxos >= MAXIMUM_WALLET_UTXOS {
// we hit the max utxos allowed in a single tx
println!("Maximum UTxOs");
break;
}
usuable_utxos.push(utxo);
number_of_utxos += 1;
}
}
}
}
}
Err(err) => {
eprintln!(
"Failed to fetch UTxOs: {}\nWait a few moments and try again.",
err
);
}
}
(seedelf_datum, usuable_utxos)
}
/// Find a specific seedelf.
pub async fn find_seedelf_utxo(seedelf: String, network_flag: bool) -> Option<UtxoResponse> {
match credential_utxos(WALLET_CONTRACT_HASH, network_flag).await {
Ok(utxos) => {
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)
})
.unwrap();
if asset_name == &seedelf {
// we found it so stop searching
return Some(utxo);
}
}
}
}
Err(err) => {
eprintln!(
"Failed to fetch UTxOs: {}\nWait a few moments and try again.",
err
);
}
}
None
}
// Find wallet utxos owned by some scalar. The maximum amount of utxos is limited by a upper bound.
pub async fn collect_wallet_utxos(sk: Scalar, network_flag: bool) -> Vec<UtxoResponse> {
let mut number_of_utxos: u64 = 0;
let mut usuable_utxos: Vec<UtxoResponse> = Vec::new();
match credential_utxos(WALLET_CONTRACT_HASH, network_flag).await {
Ok(utxos) => {
for utxo in utxos {
// Extract bytes
if let Some(inline_datum) = extract_bytes_with_logging(&utxo.inline_datum) {
// utxo must be owned by this secret scaler
if inline_datum.is_owned(sk) {
// its owned but it can't hold a seedelf
if !contains_policy_id(&utxo.asset_list, SEEDELF_POLICY_ID) {
if number_of_utxos >= MAXIMUM_WALLET_UTXOS {
// we hit the max utxos allowed in a single tx
println!("Maximum UTxOs");
break;
}
usuable_utxos.push(utxo);
number_of_utxos += 1;
}
}
}
}
}
Err(err) => {
eprintln!(
"Failed to fetch UTxOs: {}\nWait a few moments and try again.",
err
);
}
}
usuable_utxos
}
/// Collect all the address utxos that are not an assumed collateral utxo.
pub async fn collect_address_utxos(address: &str, network_flag: bool) -> Vec<UtxoResponse> {
let mut usuable_utxos: Vec<UtxoResponse> = Vec::new();
// This should probably be some generalized function later
match address_utxos(address, network_flag).await {
Ok(utxos) => {
// loop all the utxos found from the address
for utxo in utxos {
// get the lovelace on this utxo
let lovelace: u64 = utxo.value.parse::<u64>().expect("Invalid Lovelace");
if let Some(assets) = &utxo.asset_list {
if assets.is_empty() && lovelace == 5_000_000 {
// its probably a collateral utxo
} else {
// its probably not a collateral utxo
usuable_utxos.push(utxo);
}
}
}
}
Err(err) => {
eprintln!(
"Failed to fetch UTxOs: {}\nWait a few moments and try again.",
err
);
}
}
usuable_utxos
}
// lets assume that the lovelace here initially accounts for the estimated fee, like 1 ada or something
// use largest first algo but account for change
pub fn select(mut utxos: Vec<UtxoResponse>, lovelace: u64, tokens: Assets) -> Vec<UtxoResponse> {
let mut selected_utxos: Vec<UtxoResponse> = Vec::new();
let mut current_lovelace_sum: u64 = 0;
let mut found_enough: bool = false;
// all the found assets
let mut found_assets: Assets = Assets::new();
// sort by largest ada first
// (empty asset lists first)
utxos.sort_by(|a, b| {
let a_group_key = a.asset_list.as_ref().map_or(false, |list| list.is_empty());
let b_group_key = b.asset_list.as_ref().map_or(false, |list| list.is_empty());
b_group_key
.cmp(&a_group_key)
.then_with(|| string_to_u64(b.value.clone()).cmp(&string_to_u64(a.value.clone())))
});
for utxo in utxos.clone() {
// the value from koios is the lovelace
let value: u64 = string_to_u64(utxo.value.clone()).unwrap();
let mut utxo_assets: Assets = Assets::new();
let mut added: bool = false;
// lets keep track if we found any assets while searching
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).unwrap(),
));
}
// if this utxo has the assets we need but we haven't found it all yet then add it
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());
added = true;
}
} else {
// no tokens here just lovelace so add it
if current_lovelace_sum < lovelace {
selected_utxos.push(utxo.clone());
current_lovelace_sum += value;
added = true;
}
}
}
// the utxo is not pure ada and doesnt contain what you need but you need ada because you already found the tokens so add it
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);
}
// we know we found enough lovelace and assets
if current_lovelace_sum >= lovelace && found_assets.contains(tokens.clone()) {
// but is it enough to account for the min ada for the token change as we will assume there will always be a change utxo
let change_assets: Assets = found_assets.separate(tokens.clone());
let number_of_change_assets: u64 = change_assets.len();
let minimum: u64 = wallet_minimum_lovelace_with_assets(change_assets.clone());
// we need to calculate how many multiple change utxos we need
let multiplier: u64 = if number_of_change_assets > MAXIMUM_TOKENS_PER_UTXO {
// add one due to floor division
(number_of_change_assets / MAXIMUM_TOKENS_PER_UTXO) + 1
} else {
1
};
// we need lovelace for the goal and the change here
if current_lovelace_sum - multiplier * minimum >= lovelace {
// it is!
found_enough = true;
break;
} else {
// its not, try again but increase the lovelace by the minimum we would need
select(
utxos.clone(),
lovelace + multiplier * minimum,
tokens.clone(),
);
}
}
}
if found_enough {
// we found enough utxos to pay for it
selected_utxos
} else {
// not enough utxos to pay for what you are trying to do so return the empty utxo set
Vec::new()
}
}
/// Calculate the total assets of a list of utxos.
pub fn assets_of(utxos: Vec<UtxoResponse>) -> (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()).unwrap();
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() {
utxo_assets = utxo_assets.add(Asset::new(
token.policy_id,
token.asset_name,
string_to_u64(token.quantity).unwrap(),
));
}
found_assets = found_assets.merge(utxo_assets.clone());
}
}
}
(current_lovelace_sum, found_assets)
}
/// Find a seedelf that contains the label and print the match.
pub async fn find_and_print_all_seedelfs(label: String, network_flag: bool) {
match credential_utxos(WALLET_CONTRACT_HASH, network_flag).await {
Ok(utxos) => {
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)
})
.unwrap();
if asset_name.to_lowercase().contains(&label.to_lowercase()) {
// we found it so print it
println!(
"\n{}: {}",
"Found Match:".bright_cyan(),
asset_name.bright_white()
);
seedelf_label(asset_name.to_string());
}
}
}
}
Err(err) => {
eprintln!(
"Failed to fetch UTxOs: {}\nWait a few moments and try again.",
err
);
}
}
}
/// Find a seedelf that contains the label and print the match.
pub async fn count_lovelace_and_utxos(network_flag: bool) {
match credential_utxos(WALLET_CONTRACT_HASH, network_flag).await {
Ok(utxos) => {
let mut total_lovelace: u64 = 0;
for utxo in utxos.clone() {
let value: u64 = string_to_u64(utxo.value.clone()).unwrap();
total_lovelace += value;
}
println!(
"\nBalance: {} ₳",
format!("{:.6}", total_lovelace as f64 / 1_000_000.0).bright_yellow()
);
println!(
"Contract Has {} UTxOs",
utxos.len().to_string().bright_yellow()
);
}
Err(err) => {
eprintln!(
"Failed to fetch UTxOs: {}\nWait a few moments and try again.",
err
);
}
}
}