seedelf_cli/
utxos.rs

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