utility_cli_rs/
common.rs

1use std::collections::VecDeque;
2use std::convert::{TryFrom, TryInto};
3use std::io::Write;
4use std::str::FromStr;
5
6use color_eyre::eyre::{ContextCompat, WrapErr};
7use futures::{StreamExt, TryStreamExt};
8use prettytable::Table;
9use num_rational::Rational32;
10
11use unc_primitives::{hash::CryptoHash, types::BlockReference, views::AccessKeyPermissionView};
12use crate::types::unc_token::UncToken;
13
14pub type CliResult = color_eyre::eyre::Result<()>;
15
16use inquire::{Select, Text};
17use strum::IntoEnumIterator;
18
19use rand::Rng;
20use rsa::pkcs8::{EncodePrivateKey, EncodePublicKey};
21use rsa::{RsaPrivateKey, RsaPublicKey};
22
23pub fn get_unc_exec_path() -> String {
24    std::env::args()
25        .next()
26        .unwrap_or_else(|| "./unc".to_owned())
27}
28
29#[derive(
30    Debug,
31    Clone,
32    strum_macros::IntoStaticStr,
33    strum_macros::EnumString,
34    strum_macros::EnumVariantNames,
35    smart_default::SmartDefault,
36)]
37#[strum(serialize_all = "snake_case")]
38pub enum OutputFormat {
39    #[default]
40    Plaintext,
41    Json,
42}
43
44impl std::fmt::Display for OutputFormat {
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        match self {
47            OutputFormat::Plaintext => write!(f, "plaintext"),
48            OutputFormat::Json => write!(f, "json"),
49        }
50    }
51}
52
53#[derive(Debug, Clone)]
54pub struct BlockHashAsBase58 {
55    pub inner: unc_primitives::hash::CryptoHash,
56}
57
58impl std::str::FromStr for BlockHashAsBase58 {
59    type Err = String;
60    fn from_str(s: &str) -> Result<Self, Self::Err> {
61        Ok(Self {
62            inner: bs58::decode(s)
63                .into_vec()
64                .map_err(|err| format!("base58 block hash sequence is invalid: {}", err))?
65                .as_slice()
66                .try_into()
67                .map_err(|err| format!("block hash could not be collected: {}", err))?,
68        })
69    }
70}
71
72impl std::fmt::Display for BlockHashAsBase58 {
73    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74        write!(f, "BlockHash {}", self.inner)
75    }
76}
77
78pub use unc_gas::UncGas;
79
80#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd)]
81pub struct TransferAmount {
82    amount: unc_token::UncToken,
83}
84
85impl interactive_clap::ToCli for TransferAmount {
86    type CliVariant = unc_token::UncToken;
87}
88
89impl std::fmt::Display for TransferAmount {
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        write!(f, "{}", self.amount)
92    }
93}
94
95impl TransferAmount {
96    pub fn from(
97        amount: unc_token::UncToken,
98        account_transfer_allowance: &AccountTransferAllowance,
99    ) -> color_eyre::eyre::Result<Self> {
100        if amount <= account_transfer_allowance.transfer_allowance() {
101            Ok(Self { amount })
102        } else {
103            Err(color_eyre::Report::msg(
104                "the amount exceeds the transfer allowance",
105            ))
106        }
107    }
108
109    pub fn from_unchecked(amount: unc_token::UncToken) -> Self {
110        Self { amount }
111    }
112
113    pub fn as_attounc(&self) -> u128 {
114        self.amount.as_attounc()
115    }
116}
117
118impl From<TransferAmount> for unc_token::UncToken {
119    fn from(item: TransferAmount) -> Self {
120        item.amount
121    }
122}
123
124#[derive(Debug)]
125pub struct AccountTransferAllowance {
126    account_id: unc_primitives::types::AccountId,
127    account_liquid_balance: unc_token::UncToken,
128    account_locked_balance: unc_token::UncToken,
129    storage_pledge: unc_token::UncToken,
130    pessimistic_transaction_fee: unc_token::UncToken,
131}
132
133impl std::fmt::Display for AccountTransferAllowance {
134    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135        write!(fmt,
136            "\n{} account has {} available for transfer (the total balance is {}, but {} is locked for storage and the transfer transaction fee is ~{})",
137            self.account_id,
138            self.transfer_allowance(),
139            self.account_liquid_balance,
140            self.liquid_storage_pledge(),
141            self.pessimistic_transaction_fee
142        )
143    }
144}
145
146impl AccountTransferAllowance {
147    pub fn liquid_storage_pledge(&self) -> unc_token::UncToken {
148        self.storage_pledge
149            .saturating_sub(self.account_locked_balance)
150    }
151
152    pub fn transfer_allowance(&self) -> unc_token::UncToken {
153        self.account_liquid_balance
154            .saturating_sub(self.liquid_storage_pledge())
155            .saturating_sub(self.pessimistic_transaction_fee)
156    }
157}
158
159pub fn get_account_transfer_allowance(
160    network_config: crate::config::NetworkConfig,
161    account_id: unc_primitives::types::AccountId,
162    block_reference: BlockReference,
163) -> color_eyre::eyre::Result<AccountTransferAllowance> {
164    let account_view = if let Ok(account_view) =
165        get_account_state(network_config.clone(), account_id.clone(), block_reference)
166    {
167        account_view
168    } else if !account_id.get_account_type().is_valid() {
169        return color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!(
170            "Account <{}> does not exist on network <{}>.",
171            account_id,
172            network_config.network_name
173        ));
174    } else {
175        return Ok(AccountTransferAllowance {
176            account_id,
177            account_liquid_balance: unc_token::UncToken::from_unc(0),
178            account_locked_balance: unc_token::UncToken::from_unc(0),
179            storage_pledge: unc_token::UncToken::from_unc(0),
180            pessimistic_transaction_fee: unc_token::UncToken::from_unc(0),
181        });
182    };
183    let storage_amount_per_byte = tokio::runtime::Runtime::new()
184        .unwrap()
185        .block_on(network_config.json_rpc_client().call(
186            unc_jsonrpc_client::methods::EXPERIMENTAL_protocol_config::RpcProtocolConfigRequest {
187                block_reference: unc_primitives::types::Finality::Final.into(),
188            },
189        ))
190        .wrap_err("RpcError")?
191        .runtime_config
192        .storage_amount_per_byte;
193
194    Ok(AccountTransferAllowance {
195        account_id,
196        account_liquid_balance: unc_token::UncToken::from_attounc(account_view.amount),
197        account_locked_balance: unc_token::UncToken::from_attounc(account_view.pledging),
198        storage_pledge: unc_token::UncToken::from_attounc(
199            u128::from(account_view.storage_usage) * storage_amount_per_byte,
200        ),
201        // pessimistic_transaction_fee = 10^21 - this value is set temporarily
202        // In the future, its value will be calculated by the function: fn tx_cost(...)
203        // https://github.com/unc/unccore/blob/8a377fda0b4ce319385c463f1ae46e4b0b29dcd9/runtime/runtime/src/config.rs#L178-L232
204        pessimistic_transaction_fee: unc_token::UncToken::from_milliunc(1),
205    })
206}
207
208pub fn verify_account_access_key(
209    account_id: unc_primitives::types::AccountId,
210    public_key: unc_crypto::PublicKey,
211    network_config: crate::config::NetworkConfig,
212) -> color_eyre::eyre::Result<
213    unc_primitives::views::AccessKeyView,
214    unc_jsonrpc_client::errors::JsonRpcError<unc_jsonrpc_primitives::types::query::RpcQueryError>,
215> {
216    loop {
217        match network_config
218            .json_rpc_client()
219            .blocking_call_view_access_key(
220                &account_id,
221                &public_key,
222                unc_primitives::types::BlockReference::latest(),
223            ) {
224            Ok(rpc_query_response) => {
225                if let unc_jsonrpc_primitives::types::query::QueryResponseKind::AccessKey(result) =
226                    rpc_query_response.kind
227                {
228                    return Ok(result);
229                } else {
230                    return Err(unc_jsonrpc_client::errors::JsonRpcError::TransportError(unc_jsonrpc_client::errors::RpcTransportError::RecvError(
231                        unc_jsonrpc_client::errors::JsonRpcTransportRecvError::UnexpectedServerResponse(
232                            unc_jsonrpc_primitives::message::Message::error(unc_jsonrpc_primitives::errors::RpcError::parse_error("Transport error: unexpected server response".to_string()))
233                        ),
234                    )));
235                }
236            }
237            Err(
238                err @ unc_jsonrpc_client::errors::JsonRpcError::ServerError(
239                    unc_jsonrpc_client::errors::JsonRpcServerError::HandlerError(
240                        unc_jsonrpc_primitives::types::query::RpcQueryError::UnknownAccessKey {
241                            ..
242                        },
243                    ),
244                ),
245            ) => {
246                return Err(err);
247            }
248            Err(unc_jsonrpc_client::errors::JsonRpcError::TransportError(err)) => {
249                eprintln!("\nAccount information ({}) cannot be fetched on <{}> network due to connectivity issue.",
250                    account_id, network_config.network_name
251                );
252                if !need_check_account() {
253                    return Err(unc_jsonrpc_client::errors::JsonRpcError::TransportError(
254                        err,
255                    ));
256                }
257            }
258            Err(unc_jsonrpc_client::errors::JsonRpcError::ServerError(err)) => {
259                eprintln!("\nAccount information ({}) cannot be fetched on <{}> network due to server error.",
260                    account_id, network_config.network_name
261                );
262                if !need_check_account() {
263                    return Err(unc_jsonrpc_client::errors::JsonRpcError::ServerError(err));
264                }
265            }
266        }
267    }
268}
269
270pub fn is_account_exist(
271    networks: &linked_hash_map::LinkedHashMap<String, crate::config::NetworkConfig>,
272    account_id: unc_primitives::types::AccountId,
273) -> bool {
274    for (_, network_config) in networks {
275        if get_account_state(
276            network_config.clone(),
277            account_id.clone(),
278            unc_primitives::types::Finality::Final.into(),
279        )
280        .is_ok()
281        {
282            return true;
283        }
284    }
285    false
286}
287
288pub fn find_network_where_account_exist(
289    context: &crate::GlobalContext,
290    new_account_id: unc_primitives::types::AccountId,
291) -> Option<crate::config::NetworkConfig> {
292    for (_, network_config) in context.config.network_connection.iter() {
293        if get_account_state(
294            network_config.clone(),
295            new_account_id.clone(),
296            unc_primitives::types::BlockReference::latest(),
297        )
298        .is_ok()
299        {
300            return Some(network_config.clone());
301        }
302    }
303    None
304}
305
306pub fn ask_if_different_account_id_wanted() -> color_eyre::eyre::Result<bool> {
307    #[derive(strum_macros::Display, PartialEq)]
308    enum ConfirmOptions {
309        #[strum(to_string = "Yes, I want to enter a new name for account ID.")]
310        Yes,
311        #[strum(to_string = "No, I want to keep using this name for account ID.")]
312        No,
313    }
314    let select_choose_input = Select::new(
315        "Do you want to enter a different name for the new account ID?",
316        vec![ConfirmOptions::Yes, ConfirmOptions::No],
317    )
318    .prompt()?;
319    Ok(select_choose_input == ConfirmOptions::Yes)
320}
321
322pub fn get_account_state(
323    network_config: crate::config::NetworkConfig,
324    account_id: unc_primitives::types::AccountId,
325    block_reference: BlockReference,
326) -> color_eyre::eyre::Result<
327    unc_primitives::views::AccountView,
328    unc_jsonrpc_client::errors::JsonRpcError<unc_jsonrpc_primitives::types::query::RpcQueryError>,
329> {
330    loop {
331        let query_view_method_response = network_config
332            .json_rpc_client()
333            .blocking_call_view_account(&account_id.clone(), block_reference.clone());
334        match query_view_method_response {
335            Ok(rpc_query_response) => {
336                if let unc_jsonrpc_primitives::types::query::QueryResponseKind::ViewAccount(
337                    account_view,
338                ) = rpc_query_response.kind
339                {
340                    return Ok(account_view);
341                } else {
342                    return Err(unc_jsonrpc_client::errors::JsonRpcError::TransportError(unc_jsonrpc_client::errors::RpcTransportError::RecvError(
343                        unc_jsonrpc_client::errors::JsonRpcTransportRecvError::UnexpectedServerResponse(
344                            unc_jsonrpc_primitives::message::Message::error(unc_jsonrpc_primitives::errors::RpcError::parse_error("Transport error: unexpected server response".to_string()))
345                        ),
346                    )));
347                }
348            }
349            Err(
350                err @ unc_jsonrpc_client::errors::JsonRpcError::ServerError(
351                    unc_jsonrpc_client::errors::JsonRpcServerError::HandlerError(
352                        unc_jsonrpc_primitives::types::query::RpcQueryError::UnknownAccount {
353                            ..
354                        },
355                    ),
356                ),
357            ) => {
358                return Err(err);
359            }
360            Err(unc_jsonrpc_client::errors::JsonRpcError::TransportError(err)) => {
361                eprintln!("\nAccount information ({}) cannot be fetched on <{}> network due to connectivity issue.",
362                    account_id, network_config.network_name
363                );
364                if !need_check_account() {
365                    return Err(unc_jsonrpc_client::errors::JsonRpcError::TransportError(
366                        err,
367                    ));
368                }
369            }
370            Err(unc_jsonrpc_client::errors::JsonRpcError::ServerError(err)) => {
371                eprintln!("\nAccount information ({}) cannot be fetched on <{}> network due to server error.",
372                    account_id, network_config.network_name
373                );
374                if !need_check_account() {
375                    return Err(unc_jsonrpc_client::errors::JsonRpcError::ServerError(err));
376                }
377            }
378        }
379    }
380}
381
382fn need_check_account() -> bool {
383    #[derive(strum_macros::Display, PartialEq)]
384    enum ConfirmOptions {
385        #[strum(to_string = "Yes, I want to check the account again.")]
386        Yes,
387        #[strum(to_string = "No, I want to skip the check and use the specified account ID.")]
388        No,
389    }
390    let select_choose_input = Select::new(
391        "Do you want to try again?",
392        vec![ConfirmOptions::Yes, ConfirmOptions::No],
393    )
394    .prompt()
395    .unwrap_or(ConfirmOptions::Yes);
396    select_choose_input == ConfirmOptions::Yes
397}
398
399#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
400pub struct KeyPairProperties {
401    pub seed_phrase_hd_path: crate::types::slip10::BIP32Path,
402    pub master_seed_phrase: String,
403    pub implicit_account_id: unc_primitives::types::AccountId,
404    #[serde(rename = "public_key")]
405    pub public_key_str: String,
406    #[serde(rename = "private_key")]
407    pub secret_keypair_str: String,
408}
409
410pub fn get_key_pair_properties_from_seed_phrase(
411    seed_phrase_hd_path: crate::types::slip10::BIP32Path,
412    master_seed_phrase: String,
413) -> color_eyre::eyre::Result<KeyPairProperties> {
414    let master_seed = bip39::Mnemonic::parse(&master_seed_phrase)?.to_seed("");
415    let derived_private_key = slip10::derive_key_from_path(
416        &master_seed,
417        slip10::Curve::Ed25519,
418        &seed_phrase_hd_path.clone().into(),
419    )
420    .map_err(|err| {
421        color_eyre::Report::msg(format!(
422            "Failed to derive a key from the master key: {}",
423            err
424        ))
425    })?;
426
427    let secret_keypair = {
428        let secret = ed25519_dalek::SecretKey::from_bytes(&derived_private_key.key)?;
429        let public = ed25519_dalek::PublicKey::from(&secret);
430        ed25519_dalek::Keypair { secret, public }
431    };
432
433    let implicit_account_id =
434        unc_primitives::types::AccountId::try_from(hex::encode(secret_keypair.public))?;
435    let public_key_str = format!(
436        "ed25519:{}",
437        bs58::encode(&secret_keypair.public).into_string()
438    );
439    let secret_keypair_str = format!(
440        "ed25519:{}",
441        bs58::encode(secret_keypair.to_bytes()).into_string()
442    );
443    let key_pair_properties: KeyPairProperties = KeyPairProperties {
444        seed_phrase_hd_path,
445        master_seed_phrase,
446        implicit_account_id,
447        public_key_str,
448        secret_keypair_str,
449    };
450    Ok(key_pair_properties)
451}
452
453pub fn get_public_key_from_seed_phrase(
454    seed_phrase_hd_path: slip10::BIP32Path,
455    master_seed_phrase: &str,
456) -> color_eyre::eyre::Result<unc_crypto::PublicKey> {
457    let master_seed = bip39::Mnemonic::parse(master_seed_phrase)?.to_seed("");
458    let derived_private_key =
459        slip10::derive_key_from_path(&master_seed, slip10::Curve::Ed25519, &seed_phrase_hd_path)
460            .map_err(|err| {
461                color_eyre::Report::msg(format!(
462                    "Failed to derive a key from the master key: {}",
463                    err
464                ))
465            })?;
466    let secret_keypair = {
467        let secret = ed25519_dalek::SecretKey::from_bytes(&derived_private_key.key)?;
468        let public = ed25519_dalek::PublicKey::from(&secret);
469        ed25519_dalek::Keypair { secret, public }
470    };
471    let public_key_str = format!(
472        "ed25519:{}",
473        bs58::encode(&secret_keypair.public).into_string()
474    );
475    Ok(unc_crypto::PublicKey::from_str(&public_key_str)?)
476}
477
478pub fn generate_ed25519_keypair() -> color_eyre::eyre::Result<KeyPairProperties> {
479    let generate_keypair: crate::utils_command::generate_keypair_subcommand::CliGenerateKeypair =
480        crate::utils_command::generate_keypair_subcommand::CliGenerateKeypair::default();
481    let (master_seed_phrase, master_seed) =
482        if let Some(master_seed_phrase) = generate_keypair.master_seed_phrase.as_deref() {
483            (
484                master_seed_phrase.to_owned(),
485                bip39::Mnemonic::parse(master_seed_phrase)?.to_seed(""),
486            )
487        } else {
488            let mnemonic =
489                bip39::Mnemonic::generate(generate_keypair.new_master_seed_phrase_words_count)?;
490            let master_seed_phrase = mnemonic.word_iter().collect::<Vec<&str>>().join(" ");
491            (master_seed_phrase, mnemonic.to_seed(""))
492        };
493
494    let derived_private_key = slip10::derive_key_from_path(
495        &master_seed,
496        slip10::Curve::Ed25519,
497        &generate_keypair.seed_phrase_hd_path.clone().into(),
498    )
499    .map_err(|err| {
500        color_eyre::Report::msg(format!(
501            "Failed to derive a key from the master key: {}",
502            err
503        ))
504    })?;
505
506    let secret_keypair = {
507        let secret = ed25519_dalek::SecretKey::from_bytes(&derived_private_key.key)?;
508        let public = ed25519_dalek::PublicKey::from(&secret);
509        ed25519_dalek::Keypair { secret, public }
510    };
511
512    let implicit_account_id =
513        unc_primitives::types::AccountId::try_from(hex::encode(secret_keypair.public))?;
514    let public_key_str = format!(
515        "ed25519:{}",
516        bs58::encode(&secret_keypair.public).into_string()
517    );
518    let secret_keypair_str = format!(
519        "ed25519:{}",
520        bs58::encode(secret_keypair.to_bytes()).into_string()
521    );
522    let key_pair_properties: KeyPairProperties = KeyPairProperties {
523        seed_phrase_hd_path: generate_keypair.seed_phrase_hd_path,
524        master_seed_phrase,
525        implicit_account_id,
526        public_key_str,
527        secret_keypair_str,
528    };
529    Ok(key_pair_properties)
530}
531
532/// The length of an rsa `RsaPublicKey`, in bytes.
533pub const RAW_PUBLIC_KEY_RSA_2048_LENGTH: usize = 294;
534
535// FIXME: generate keypair use trait object
536pub fn generate_rsa2048_keypair() -> color_eyre::eyre::Result<KeyPairProperties> {
537    let generate_keypair: crate::utils_command::generate_keypair_subcommand::CliGenerateKeypair =
538        crate::utils_command::generate_keypair_subcommand::CliGenerateKeypair::default();
539    let (master_seed_phrase, _master_seed) =
540        if let Some(master_seed_phrase) = generate_keypair.master_seed_phrase.as_deref() {
541            (
542                master_seed_phrase.to_owned(),
543                bip39::Mnemonic::parse(master_seed_phrase)?.to_seed(""),
544            )
545        } else {
546            let mnemonic =
547                bip39::Mnemonic::generate(generate_keypair.new_master_seed_phrase_words_count)?;
548            let master_seed_phrase = mnemonic.word_iter().collect::<Vec<&str>>().join(" ");
549            (master_seed_phrase, mnemonic.to_seed(""))
550        };
551
552    let mut rng = rand::thread_rng();
553    let bits = 2048;
554    let priv_key = RsaPrivateKey::new(&mut rng, bits)?;
555    let pub_key = RsaPublicKey::from(&priv_key);
556
557    let implicit_account_id =
558        unc_primitives::types::AccountId::try_from(format!("test{}", rng.gen_range(0..10000)))?;
559
560    let der_pk_encoded = pub_key.to_public_key_der().unwrap();
561    let public_key_str = format!(
562        "rsa2048:{}",
563        bs58::encode(&der_pk_encoded.as_bytes()).into_string()
564    );
565
566    let der_sk_encoded = priv_key.to_pkcs8_der().unwrap().to_bytes();
567    let secret_keypair_str = format!(
568        "rsa2048:{}",
569        bs58::encode(der_sk_encoded.as_slice()).into_string()
570    );
571    let key_pair_properties: KeyPairProperties = KeyPairProperties {
572        seed_phrase_hd_path: generate_keypair.seed_phrase_hd_path,
573        master_seed_phrase,
574        implicit_account_id,
575        public_key_str,
576        secret_keypair_str,
577    };
578    Ok(key_pair_properties)
579}
580
581pub fn print_full_signed_transaction(transaction: unc_primitives::transaction::SignedTransaction) {
582    eprintln!("{:<25} {}\n", "signature:", transaction.signature);
583    crate::common::print_full_unsigned_transaction(transaction.transaction);
584}
585
586pub fn print_full_unsigned_transaction(transaction: unc_primitives::transaction::Transaction) {
587    eprintln!(
588        "Unsigned transaction hash (Base58-encoded SHA-256 hash): {}\n\n",
589        transaction.get_hash_and_size().0
590    );
591
592    eprintln!("{:<13} {}", "public_key:", &transaction.public_key);
593    eprintln!("{:<13} {}", "nonce:", &transaction.nonce);
594    eprintln!("{:<13} {}", "block_hash:", &transaction.block_hash);
595
596    let prepopulated = crate::commands::PrepopulatedTransaction::from(transaction);
597    print_unsigned_transaction(&prepopulated);
598}
599
600pub fn print_unsigned_transaction(transaction: &crate::commands::PrepopulatedTransaction) {
601    eprintln!("{:<13} {}", "signer_id:", &transaction.signer_id);
602    eprintln!("{:<13} {}", "receiver_id:", &transaction.receiver_id);
603    if transaction
604        .actions
605        .iter()
606        .any(|action| matches!(action, unc_primitives::transaction::Action::Delegate(_)))
607    {
608        eprintln!("signed delegate action:");
609    } else {
610        eprintln!("actions:");
611    };
612
613    for action in &transaction.actions {
614        match action {
615            unc_primitives::transaction::Action::CreateAccount(_) => {
616                eprintln!(
617                    "{:>5} {:<20} {}",
618                    "--", "create account:", &transaction.receiver_id
619                )
620            }
621            unc_primitives::transaction::Action::DeployContract(_) => {
622                eprintln!("{:>5} {:<20}", "--", "deploy contract")
623            }
624            unc_primitives::transaction::Action::FunctionCall(function_call_action) => {
625                eprintln!("{:>5} {:<20}", "--", "function call:");
626                eprintln!(
627                    "{:>18} {:<13} {}",
628                    "", "method name:", &function_call_action.method_name
629                );
630                eprintln!(
631                    "{:>18} {:<13} {}",
632                    "",
633                    "args:",
634                    match serde_json::from_slice::<serde_json::Value>(&function_call_action.args) {
635                        Ok(parsed_args) => {
636                            serde_json::to_string_pretty(&parsed_args)
637                                .unwrap_or_else(|_| "".to_string())
638                                .replace('\n', "\n                                 ")
639                        }
640                        Err(_) => {
641                            if let Ok(args) = String::from_utf8(function_call_action.args.clone()) {
642                                args
643                            } else {
644                                format!(
645                                    "<non-printable data ({})>",
646                                    bytesize::ByteSize(function_call_action.args.len() as u64)
647                                )
648                            }
649                        }
650                    }
651                );
652                eprintln!(
653                    "{:>18} {:<13} {}",
654                    "",
655                    "gas:",
656                    crate::common::UncGas::from_gas(function_call_action.gas)
657                );
658                eprintln!(
659                    "{:>18} {:<13} {}",
660                    "",
661                    "deposit:",
662                    crate::types::unc_token::UncToken::from_attounc(function_call_action.deposit)
663                );
664            }
665            unc_primitives::transaction::Action::Transfer(transfer_action) => {
666                eprintln!(
667                    "{:>5} {:<20} {}",
668                    "--",
669                    "transfer deposit:",
670                    crate::types::unc_token::UncToken::from_attounc(transfer_action.deposit)
671                );
672            }
673            unc_primitives::transaction::Action::Pledge(pledge_action) => {
674                eprintln!("{:>5} {:<20}", "--", "pledge:");
675                eprintln!(
676                    "{:>18} {:<13} {}",
677                    "", "public key:", &pledge_action.public_key
678                );
679                eprintln!(
680                    "{:>18} {:<13} {}",
681                    "",
682                    "pledge:",
683                    crate::types::unc_token::UncToken::from_attounc(pledge_action.pledge)
684                );
685            }
686            unc_primitives::transaction::Action::AddKey(add_key_action) => {
687                eprintln!("{:>5} {:<20}", "--", "add access key:");
688                eprintln!(
689                    "{:>18} {:<13} {}",
690                    "", "public key:", &add_key_action.public_key
691                );
692                eprintln!(
693                    "{:>18} {:<13} {}",
694                    "", "nonce:", &add_key_action.access_key.nonce
695                );
696                eprintln!(
697                    "{:>18} {:<13} {:?}",
698                    "", "permission:", &add_key_action.access_key.permission
699                );
700            }
701            unc_primitives::transaction::Action::DeleteKey(delete_key_action) => {
702                eprintln!("{:>5} {:<20}", "--", "delete access key:");
703                eprintln!(
704                    "{:>18} {:<13} {}",
705                    "", "public key:", &delete_key_action.public_key
706                );
707            }
708            unc_primitives::transaction::Action::DeleteAccount(delete_account_action) => {
709                eprintln!(
710                    "{:>5} {:<20} {}",
711                    "--", "delete account:", &transaction.receiver_id
712                );
713                eprintln!(
714                    "{:>5} {:<20} {}",
715                    "", "beneficiary id:", &delete_account_action.beneficiary_id
716                );
717            }
718            unc_primitives::transaction::Action::Delegate(signed_delegate_action) => {
719                let prepopulated_transaction = crate::commands::PrepopulatedTransaction {
720                    signer_id: signed_delegate_action.delegate_action.sender_id.clone(),
721                    receiver_id: signed_delegate_action.delegate_action.receiver_id.clone(),
722                    actions: signed_delegate_action.delegate_action.get_actions(),
723                };
724                print_unsigned_transaction(&prepopulated_transaction);
725            }
726            unc_primitives::transaction::Action::RegisterRsa2048Keys(register_rsa2048_action) => {
727                eprintln!("{:>5} {:<20}", "--", "register rsa2048 key:");
728                eprintln!(
729                    "{:>18} {:<13} {}",
730                    "", "public key:", &register_rsa2048_action.public_key
731                );
732                eprintln!(
733                    "{:>18} {:<13} {}",
734                    "", "op type:", &register_rsa2048_action.operation_type
735                );
736            }
737            unc_primitives::transaction::Action::CreateRsa2048Challenge(
738                create_rsa2048keys_challenge_action,
739            ) => {
740                eprintln!(
741                    "{:>18} {:<13} {}",
742                    "", "public key:", &create_rsa2048keys_challenge_action.public_key
743                );
744                eprintln!(
745                    "{:>18} {:<13} {}",
746                    "",
747                    "args:",
748                    match serde_json::from_slice::<serde_json::Value>(
749                        &create_rsa2048keys_challenge_action.args
750                    ) {
751                        Ok(parsed_args) => {
752                            serde_json::to_string_pretty(&parsed_args)
753                                .unwrap_or_else(|_| "".to_string())
754                                .replace('\n', "\n                                 ")
755                        }
756                        Err(_) => {
757                            format!(
758                                "<non-printable data ({})>",
759                                bytesize::ByteSize(
760                                    create_rsa2048keys_challenge_action.args.len() as u64
761                                )
762                            )
763                        }
764                    }
765                );
766            }
767        }
768    }
769}
770
771fn print_value_successful_transaction(
772    transaction_info: unc_primitives::views::FinalExecutionOutcomeView,
773) {
774    for action in transaction_info.transaction.actions {
775        match action {
776            unc_primitives::views::ActionView::CreateAccount => {
777                eprintln!(
778                    "New account <{}> has been successfully created.",
779                    transaction_info.transaction.receiver_id,
780                );
781            }
782            unc_primitives::views::ActionView::DeployContract { code: _ } => {
783                eprintln!("Contract code has been successfully deployed.",);
784            }
785            unc_primitives::views::ActionView::FunctionCall {
786                method_name,
787                args: _,
788                gas: _,
789                deposit: _,
790            } => {
791                eprintln!(
792                    "The \"{}\" call to <{}> on behalf of <{}> succeeded.",
793                    method_name,
794                    transaction_info.transaction.receiver_id,
795                    transaction_info.transaction.signer_id,
796                );
797            }
798            unc_primitives::views::ActionView::Transfer { deposit } => {
799                eprintln!(
800                    "<{}> has transferred {} to <{}> successfully.",
801                    transaction_info.transaction.signer_id,
802                    crate::types::unc_token::UncToken::from_attounc(deposit),
803                    transaction_info.transaction.receiver_id,
804                );
805            }
806            unc_primitives::views::ActionView::Pledge {
807                pledge,
808                public_key: _,
809            } => {
810                if pledge == 0 {
811                    eprintln!(
812                        "Validator <{}> successfully unpledged.",
813                        transaction_info.transaction.signer_id,
814                    );
815                } else {
816                    eprintln!(
817                        "Validator <{}> has successfully pledged {}.",
818                        transaction_info.transaction.signer_id,
819                        crate::types::unc_token::UncToken::from_attounc(pledge),
820                    );
821                }
822            }
823            unc_primitives::views::ActionView::AddKey {
824                public_key,
825                access_key: _,
826            } => {
827                eprintln!(
828                    "Added access key = {} to {}.",
829                    public_key, transaction_info.transaction.receiver_id,
830                );
831            }
832            unc_primitives::views::ActionView::DeleteKey { public_key } => {
833                eprintln!(
834                    "Access key <{}> for account <{}> has been successfully deleted.",
835                    public_key, transaction_info.transaction.signer_id,
836                );
837            }
838            unc_primitives::views::ActionView::DeleteAccount { beneficiary_id: _ } => {
839                eprintln!(
840                    "Account <{}> has been successfully deleted.",
841                    transaction_info.transaction.signer_id,
842                );
843            }
844            unc_primitives::views::ActionView::Delegate {
845                delegate_action,
846                signature: _,
847            } => {
848                eprintln!(
849                    "Actions delegated for <{}> completed successfully.",
850                    delegate_action.sender_id,
851                );
852            }
853            unc_primitives::views::ActionView::RegisterRsa2048Keys {
854                public_key,
855                operation_type,
856                args: _,
857            } => {
858                eprintln!(
859                    "Rsa2048 key <{}>, op_type <{}> for account <{}> has been successfully registered.",
860                    public_key, operation_type, transaction_info.transaction.signer_id,
861                );
862            }
863            unc_primitives::views::ActionView::CreateRsa2048Challenge {
864                public_key,
865                challenge_key,
866                args: _,
867            } => {
868                eprintln!(
869                    "Rsa2048  <{}> with ChallengeKey <{}> for account <{}> has been successfully challenge created.",
870                    public_key, challenge_key, transaction_info.transaction.signer_id,
871                );
872            }
873        }
874    }
875}
876
877pub fn rpc_transaction_error(
878    err: unc_jsonrpc_client::errors::JsonRpcError<
879        unc_jsonrpc_client::methods::broadcast_tx_commit::RpcTransactionError,
880    >,
881) -> CliResult {
882    match &err {
883        unc_jsonrpc_client::errors::JsonRpcError::TransportError(_rpc_transport_error) => {
884            eprintln!("Transport error transaction.\nPlease wait. The next try to send this transaction is happening right now ...");
885        }
886        unc_jsonrpc_client::errors::JsonRpcError::ServerError(rpc_server_error) => match rpc_server_error {
887            unc_jsonrpc_client::errors::JsonRpcServerError::HandlerError(rpc_transaction_error) => match rpc_transaction_error {
888                unc_jsonrpc_client::methods::broadcast_tx_commit::RpcTransactionError::TimeoutError => {
889                    eprintln!("Timeout error transaction.\nPlease wait. The next try to send this transaction is happening right now ...");
890                }
891                unc_jsonrpc_client::methods::broadcast_tx_commit::RpcTransactionError::InvalidTransaction { context } => {
892                    return handler_invalid_tx_error(context);
893                }
894                unc_jsonrpc_client::methods::broadcast_tx_commit::RpcTransactionError::DoesNotTrackShard => {
895                    return color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("RPC Server Error: {}", err));
896                }
897                unc_jsonrpc_client::methods::broadcast_tx_commit::RpcTransactionError::RequestRouted{transaction_hash} => {
898                    return color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("RPC Server Error for transaction with hash {}\n{}", transaction_hash, err));
899                }
900                unc_jsonrpc_client::methods::broadcast_tx_commit::RpcTransactionError::UnknownTransaction{requested_transaction_hash} => {
901                    return color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("RPC Server Error for transaction with hash {}\n{}", requested_transaction_hash, err));
902                }
903                unc_jsonrpc_client::methods::broadcast_tx_commit::RpcTransactionError::InternalError{debug_info} => {
904                    return color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("RPC Server Error: {}", debug_info));
905                }
906            }
907            unc_jsonrpc_client::errors::JsonRpcServerError::RequestValidationError(rpc_request_validation_error) => {
908                return color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Incompatible request with the server: {:#?}",  rpc_request_validation_error));
909            }
910            unc_jsonrpc_client::errors::JsonRpcServerError::InternalError{ info } => {
911                eprintln!("Internal server error: {}.\nPlease wait. The next try to send this transaction is happening right now ...", info.clone().unwrap_or_default());
912            }
913            unc_jsonrpc_client::errors::JsonRpcServerError::NonContextualError(rpc_error) => {
914                return color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Unexpected response: {}", rpc_error));
915            }
916            unc_jsonrpc_client::errors::JsonRpcServerError::ResponseStatusError(json_rpc_server_response_status_error) => match json_rpc_server_response_status_error {
917                unc_jsonrpc_client::errors::JsonRpcServerResponseStatusError::Unauthorized => {
918                    return color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("JSON RPC server requires authentication. Please, authenticate unc CLI with the JSON RPC server you use."));
919                }
920                unc_jsonrpc_client::errors::JsonRpcServerResponseStatusError::TooManyRequests => {
921                    eprintln!("JSON RPC server is currently busy.\nPlease wait. The next try to send this transaction is happening right now ...");
922                }
923                unc_jsonrpc_client::errors::JsonRpcServerResponseStatusError::Unexpected{status} => {
924                    return color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("JSON RPC server responded with an unexpected status code: {}", status));
925                }
926            }
927        }
928    }
929    Ok(())
930}
931
932pub fn rpc_async_transaction_error(
933    _err: unc_jsonrpc_client::errors::JsonRpcError<
934        unc_jsonrpc_client::methods::broadcast_tx_async::RpcBroadcastTxAsyncError,
935    >,
936) -> CliResult {
937    Ok(())
938}
939
940pub fn print_action_error(action_error: &unc_primitives::errors::ActionError) -> crate::CliResult {
941    match &action_error.kind {
942        unc_primitives::errors::ActionErrorKind::AccountAlreadyExists { account_id } => {
943            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: Create Account action tries to create an account with account ID <{}> which already exists in the storage.", account_id))
944        }
945        unc_primitives::errors::ActionErrorKind::AccountDoesNotExist { account_id } => {
946            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!(
947                "Error: TX receiver ID <{}> doesn't exist (but action is not \"Create Account\").",
948                account_id
949            ))
950        }
951        unc_primitives::errors::ActionErrorKind::CreateAccountNotAllowed {
952            account_id,
953            predecessor_id,
954        } => {
955            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: A newly created account <{}> must be under a namespace of the creator account <{}>.", account_id, predecessor_id))
956        }
957        unc_primitives::errors::ActionErrorKind::ActorNoPermission {
958            account_id: _,
959            actor_id: _,
960        } => {
961            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: Administrative actions can be proceed only if sender=receiver or the first TX action is a \"Create Account\" action."))
962        }
963        unc_primitives::errors::ActionErrorKind::DeleteKeyDoesNotExist {
964            account_id,
965            public_key,
966        } => {
967            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!(
968                "Error: Account <{}>  tries to remove an access key <{}> that doesn't exist.",
969                account_id, public_key
970            ))
971        }
972        unc_primitives::errors::ActionErrorKind::AddKeyAlreadyExists {
973            account_id,
974            public_key,
975        } => {
976            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!(
977                "Error: Public key <{}> is already used for an existing account ID <{}>.",
978                public_key, account_id
979            ))
980        }
981        unc_primitives::errors::ActionErrorKind::DeleteAccountPledging { account_id } => {
982            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!(
983                "Error: Account <{}> is pledging and can not be deleted",
984                account_id
985            ))
986        }
987        unc_primitives::errors::ActionErrorKind::LackBalanceForState { account_id, amount } => {
988            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: Receipt action can't be completed, because the remaining balance will not be enough to cover storage.\nAn account which needs balance: <{}>\nBalance required to complete the action: <{}>",
989                account_id,
990                crate::types::unc_token::UncToken::from_attounc(*amount)
991            ))
992        }
993        unc_primitives::errors::ActionErrorKind::TriesToUnpledge { account_id } => {
994            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!(
995                "Error: Account <{}> is not yet pledged, but tries to unpledge.",
996                account_id
997            ))
998        }
999        unc_primitives::errors::ActionErrorKind::TriesToPledge {
1000            account_id,
1001            pledge,
1002            pledging: _,
1003            balance,
1004        } => {
1005            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!(
1006                "Error: Account <{}> doesn't have enough balance ({}) to increase the pledge ({}).",
1007                account_id,
1008                crate::types::unc_token::UncToken::from_attounc(*balance),
1009                crate::types::unc_token::UncToken::from_attounc(*pledge)
1010            ))
1011        }
1012        unc_primitives::errors::ActionErrorKind::InsufficientPledge {
1013            account_id: _,
1014            pledge,
1015            minimum_pledge,
1016        } => {
1017            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!(
1018                "Error: Insufficient pledge {}.\nThe minimum rate must be {}.",
1019                crate::types::unc_token::UncToken::from_attounc(*pledge),
1020                crate::types::unc_token::UncToken::from_attounc(*minimum_pledge)
1021            ))
1022        }
1023        unc_primitives::errors::ActionErrorKind::FunctionCallError(function_call_error_ser) => {
1024            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: An error occurred during a `FunctionCall` Action, parameter is debug message.\n{:?}", function_call_error_ser))
1025        }
1026        unc_primitives::errors::ActionErrorKind::NewReceiptValidationError(
1027            receipt_validation_error,
1028        ) => {
1029            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: Error occurs when a new `ActionReceipt` created by the `FunctionCall` action fails.\n{:?}", receipt_validation_error))
1030        }
1031        unc_primitives::errors::ActionErrorKind::OnlyImplicitAccountCreationAllowed {
1032            account_id: _,
1033        } => {
1034            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: `CreateAccount` action is called on hex-characters account of length 64.\nSee implicit account creation NEP: https://github.com/unc/NEPs/pull/71"))
1035        }
1036        unc_primitives::errors::ActionErrorKind::DeleteAccountWithLargeState { account_id } => {
1037            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!(
1038                "Error: Delete account <{}> whose state is large is temporarily banned.",
1039                account_id
1040            ))
1041        }
1042        unc_primitives::errors::ActionErrorKind::DelegateActionInvalidSignature => {
1043            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: Invalid Signature on DelegateAction"))
1044        }
1045        unc_primitives::errors::ActionErrorKind::DelegateActionSenderDoesNotMatchTxReceiver {
1046            sender_id,
1047            receiver_id,
1048        } => {
1049            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: Delegate Action sender {sender_id} does not match transaction receiver {receiver_id}"))
1050        }
1051        unc_primitives::errors::ActionErrorKind::DelegateActionExpired => {
1052            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: DelegateAction Expired"))
1053        }
1054        unc_primitives::errors::ActionErrorKind::DelegateActionAccessKeyError(_) => {
1055            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: The given public key doesn't exist for the sender"))
1056        }
1057        unc_primitives::errors::ActionErrorKind::DelegateActionInvalidNonce {
1058            delegate_nonce,
1059            ak_nonce,
1060        } => {
1061            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: DelegateAction Invalid Delegate Nonce: {delegate_nonce} ak_nonce: {ak_nonce}"))
1062        }
1063        unc_primitives::errors::ActionErrorKind::DelegateActionNonceTooLarge {
1064            delegate_nonce,
1065            upper_bound,
1066        } => {
1067            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: DelegateAction Invalid Delegate Nonce: {delegate_nonce} upper bound: {upper_bound}"))
1068        }
1069        unc_primitives::errors::ActionErrorKind::RsaKeysNotFound { account_id, public_key } => {
1070            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: RSA key not found for account <{}> and public key <{}>.", account_id, public_key))
1071        },
1072    }
1073}
1074
1075pub fn handler_invalid_tx_error(
1076    invalid_tx_error: &unc_primitives::errors::InvalidTxError,
1077) -> crate::CliResult {
1078    match invalid_tx_error {
1079        unc_primitives::errors::InvalidTxError::InvalidAccessKeyError(invalid_access_key_error) => {
1080            match invalid_access_key_error {
1081                unc_primitives::errors::InvalidAccessKeyError::AccessKeyNotFound{account_id, public_key} => {
1082                    color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: Public key {} doesn't exist for the account <{}>.", public_key, account_id))
1083                },
1084                unc_primitives::errors::InvalidAccessKeyError::ReceiverMismatch{tx_receiver, ak_receiver} => {
1085                    color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: Transaction for <{}> doesn't match the access key for <{}>.", tx_receiver, ak_receiver))
1086                },
1087                unc_primitives::errors::InvalidAccessKeyError::MethodNameMismatch{method_name} => {
1088                    color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: Transaction method name <{}> isn't allowed by the access key.", method_name))
1089                },
1090                unc_primitives::errors::InvalidAccessKeyError::RequiresFullAccess => {
1091                    color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: Transaction requires a full permission access key."))
1092                },
1093                unc_primitives::errors::InvalidAccessKeyError::NotEnoughAllowance{account_id, public_key, allowance, cost} => {
1094                    color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: Access Key <{}> for account <{}> does not have enough allowance ({}) to cover transaction cost ({}).",
1095                        public_key,
1096                        account_id,
1097                        crate::types::unc_token::UncToken::from_attounc(*allowance),
1098                        crate::types::unc_token::UncToken::from_attounc(*cost)
1099                    ))
1100                },
1101                unc_primitives::errors::InvalidAccessKeyError::DepositWithFunctionCall => {
1102                    color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: Having a deposit with a function call action is not allowed with a function call access key."))
1103                }
1104            }
1105        },
1106        unc_primitives::errors::InvalidTxError::InvalidSignerId { signer_id } => {
1107            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: TX signer ID <{}> is not in a valid format or does not satisfy requirements\nSee \"unc_runtime_utils::utils::is_valid_account_id\".", signer_id))
1108        },
1109        unc_primitives::errors::InvalidTxError::SignerDoesNotExist { signer_id } => {
1110            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: TX signer ID <{}> is not found in the storage.", signer_id))
1111        },
1112        unc_primitives::errors::InvalidTxError::InvalidNonce { tx_nonce, ak_nonce } => {
1113            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: Transaction nonce ({}) must be account[access_key].nonce ({}) + 1.", tx_nonce, ak_nonce))
1114        },
1115        unc_primitives::errors::InvalidTxError::NonceTooLarge { tx_nonce, upper_bound } => {
1116            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: Transaction nonce ({}) is larger than the upper bound ({}) given by the block height.", tx_nonce, upper_bound))
1117        },
1118        unc_primitives::errors::InvalidTxError::InvalidReceiverId { receiver_id } => {
1119            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: TX receiver ID ({}) is not in a valid format or does not satisfy requirements\nSee \"unc_runtime_utils::is_valid_account_id\".", receiver_id))
1120        },
1121        unc_primitives::errors::InvalidTxError::InvalidSignature => {
1122            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: TX signature is not valid"))
1123        },
1124        unc_primitives::errors::InvalidTxError::NotEnoughBalance {signer_id, balance, cost} => {
1125            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: Account <{}> does not have enough balance ({}) to cover TX cost ({}).",
1126                signer_id,
1127                crate::types::unc_token::UncToken::from_attounc(*balance),
1128                crate::types::unc_token::UncToken::from_attounc(*cost)
1129            ))
1130        },
1131        unc_primitives::errors::InvalidTxError::LackBalanceForState {signer_id, amount} => {
1132            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: Signer account <{}> doesn't have enough balance ({}) after transaction.",
1133                signer_id,
1134                crate::types::unc_token::UncToken::from_attounc(*amount)
1135            ))
1136        },
1137        unc_primitives::errors::InvalidTxError::CostOverflow => {
1138            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: An integer overflow occurred during transaction cost estimation."))
1139        },
1140        unc_primitives::errors::InvalidTxError::InvalidChain => {
1141            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: Transaction parent block hash doesn't belong to the current chain."))
1142        },
1143        unc_primitives::errors::InvalidTxError::Expired => {
1144            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: Transaction has expired."))
1145        },
1146        unc_primitives::errors::InvalidTxError::ActionsValidation(actions_validation_error) => {
1147            match actions_validation_error {
1148                unc_primitives::errors::ActionsValidationError::DeleteActionMustBeFinal => {
1149                    color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: The delete action must be the final action in transaction."))
1150                },
1151                unc_primitives::errors::ActionsValidationError::TotalPrepaidGasExceeded {total_prepaid_gas, limit} => {
1152                    color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: The total prepaid gas ({}) for all given actions exceeded the limit ({}).",
1153                    total_prepaid_gas,
1154                    limit
1155                    ))
1156                },
1157                unc_primitives::errors::ActionsValidationError::TotalNumberOfActionsExceeded {total_number_of_actions, limit} => {
1158                    color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: The number of actions ({}) exceeded the given limit ({}).", total_number_of_actions, limit))
1159                },
1160                unc_primitives::errors::ActionsValidationError::AddKeyMethodNamesNumberOfBytesExceeded {total_number_of_bytes, limit} => {
1161                    color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: The total number of bytes ({}) of the method names exceeded the limit ({}) in a Add Key action.", total_number_of_bytes, limit))
1162                },
1163                unc_primitives::errors::ActionsValidationError::AddKeyMethodNameLengthExceeded {length, limit} => {
1164                    color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: The length ({}) of some method name exceeded the limit ({}) in a Add Key action.", length, limit))
1165                },
1166                unc_primitives::errors::ActionsValidationError::IntegerOverflow => {
1167                    color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: Integer overflow."))
1168                },
1169                unc_primitives::errors::ActionsValidationError::InvalidAccountId {account_id} => {
1170                    color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: Invalid account ID <{}>.", account_id))
1171                },
1172                unc_primitives::errors::ActionsValidationError::ContractSizeExceeded {size, limit} => {
1173                    color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: The size ({}) of the contract code exceeded the limit ({}) in a DeployContract action.", size, limit))
1174                },
1175                unc_primitives::errors::ActionsValidationError::FunctionCallMethodNameLengthExceeded {length, limit} => {
1176                    color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: The length ({}) of the method name exceeded the limit ({}) in a Function Call action.", length, limit))
1177                },
1178                unc_primitives::errors::ActionsValidationError::FunctionCallArgumentsLengthExceeded {length, limit} => {
1179                    color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: The length ({}) of the arguments exceeded the limit ({}) in a Function Call action.", length, limit))
1180                },
1181                unc_primitives::errors::ActionsValidationError::UnsuitablePledgingKey {public_key} => {
1182                    color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: An attempt to pledge with a public key <{}> that is not convertible to ristretto.", public_key))
1183                },
1184                unc_primitives::errors::ActionsValidationError::FunctionCallZeroAttachedGas => {
1185                    color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: The attached amount of gas in a FunctionCall action has to be a positive number."))
1186                }
1187                unc_primitives::errors::ActionsValidationError::DelegateActionMustBeOnlyOne => {
1188                    color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: DelegateActionMustBeOnlyOne"))
1189                }
1190                unc_primitives::errors::ActionsValidationError::UnsupportedProtocolFeature { protocol_feature, version } => {
1191                    color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: Protocol Feature {} is unsupported in version {}", protocol_feature, version))
1192                }
1193            }
1194        },
1195        unc_primitives::errors::InvalidTxError::TransactionSizeExceeded { size, limit } => {
1196            color_eyre::eyre::Result::Err(color_eyre::eyre::eyre!("Error: The size ({}) of serialized transaction exceeded the limit ({}).", size, limit))
1197        }
1198    }
1199}
1200
1201pub fn print_transaction_error(
1202    tx_execution_error: &unc_primitives::errors::TxExecutionError,
1203) -> crate::CliResult {
1204    eprintln!("Failed transaction");
1205    match tx_execution_error {
1206        unc_primitives::errors::TxExecutionError::ActionError(action_error) => {
1207            print_action_error(action_error)
1208        }
1209        unc_primitives::errors::TxExecutionError::InvalidTxError(invalid_tx_error) => {
1210            handler_invalid_tx_error(invalid_tx_error)
1211        }
1212    }
1213}
1214
1215pub fn print_transaction_status(
1216    transaction_info: &unc_primitives::views::FinalExecutionOutcomeView,
1217    network_config: &crate::config::NetworkConfig,
1218) -> crate::CliResult {
1219    eprintln!("--- Logs ---------------------------");
1220    for receipt in transaction_info.receipts_outcome.iter() {
1221        if receipt.outcome.logs.is_empty() {
1222            eprintln!("Logs [{}]:   No logs", receipt.outcome.executor_id);
1223        } else {
1224            eprintln!("Logs [{}]:", receipt.outcome.executor_id);
1225            eprintln!("  {}", receipt.outcome.logs.join("\n  "));
1226        };
1227    }
1228    match &transaction_info.status {
1229        unc_primitives::views::FinalExecutionStatus::NotStarted
1230        | unc_primitives::views::FinalExecutionStatus::Started => unreachable!(),
1231        unc_primitives::views::FinalExecutionStatus::Failure(tx_execution_error) => {
1232            return print_transaction_error(tx_execution_error);
1233        }
1234        unc_primitives::views::FinalExecutionStatus::SuccessValue(bytes_result) => {
1235            eprintln!("--- Result -------------------------");
1236            if bytes_result.is_empty() {
1237                eprintln!("Empty result");
1238            } else if let Ok(json_result) =
1239                serde_json::from_slice::<serde_json::Value>(bytes_result)
1240            {
1241                println!("{}", serde_json::to_string_pretty(&json_result)?);
1242            } else if let Ok(string_result) = String::from_utf8(bytes_result.clone()) {
1243                println!("{string_result}");
1244            } else {
1245                eprintln!("The returned value is not printable (binary data)");
1246            }
1247            eprintln!("------------------------------------\n");
1248            print_value_successful_transaction(transaction_info.clone())
1249        }
1250    };
1251    eprintln!("Transaction ID: {id}\nTo see the transaction in the transaction explorer, please open this url in your browser:\n{path}{id}\n",
1252        id=transaction_info.transaction_outcome.id,
1253        path=network_config.explorer_transaction_url
1254    );
1255    Ok(())
1256}
1257
1258pub fn print_async_transaction_status(
1259    tx_hash: &CryptoHash,
1260    network_config: &crate::config::NetworkConfig,
1261) -> crate::CliResult {
1262    eprintln!("--- Logs ---------------------------");
1263    eprintln!("Transaction ID: {id}\nTo see the transaction in the transaction explorer, please open this url in your browser:\n{path}{id}\n",
1264        id=tx_hash,
1265        path=network_config.explorer_transaction_url
1266    );
1267    Ok(())
1268}
1269
1270pub fn save_access_key_to_keychain(
1271    network_config: crate::config::NetworkConfig,
1272    key_pair_properties_buf: &str,
1273    public_key_str: &str,
1274    account_id: &str,
1275) -> color_eyre::eyre::Result<String> {
1276    let service_name = std::borrow::Cow::Owned(format!(
1277        "unc-{}-{}",
1278        network_config.network_name, account_id
1279    ));
1280
1281    keyring::Entry::new(&service_name, &format!("{}:{}", account_id, public_key_str))
1282        .wrap_err("Failed to open keychain")?
1283        .set_password(key_pair_properties_buf)
1284        .wrap_err("Failed to save password to keychain")?;
1285
1286    Ok("The data for the access key is saved in the keychain".to_string())
1287}
1288
1289pub fn save_access_key_to_legacy_keychain(
1290    network_config: crate::config::NetworkConfig,
1291    credentials_home_dir: std::path::PathBuf,
1292    key_pair_properties_buf: &str,
1293    public_key_str: &str,
1294    account_id: &str,
1295) -> color_eyre::eyre::Result<String> {
1296    let dir_name = network_config.network_name.as_str();
1297    let file_with_key_name: std::path::PathBuf =
1298        format!("{}.json", public_key_str.replace(':', "_")).into();
1299    let mut path_with_key_name = std::path::PathBuf::from(&credentials_home_dir);
1300    path_with_key_name.push(dir_name);
1301    path_with_key_name.push(account_id);
1302    std::fs::create_dir_all(&path_with_key_name)?;
1303    path_with_key_name.push(file_with_key_name);
1304    let message_1 = if path_with_key_name.exists() {
1305        format!(
1306            "The file: {} already exists! Therefore it was not overwritten.",
1307            &path_with_key_name.display()
1308        )
1309    } else {
1310        std::fs::File::create(&path_with_key_name)
1311            .wrap_err_with(|| format!("Failed to create file: {:?}", path_with_key_name))?
1312            .write(key_pair_properties_buf.as_bytes())
1313            .wrap_err_with(|| format!("Failed to write to file: {:?}", path_with_key_name))?;
1314        format!(
1315            "The data for the access key is saved in a file {}",
1316            &path_with_key_name.display()
1317        )
1318    };
1319
1320    let file_with_account_name: std::path::PathBuf = format!("{}.json", account_id).into();
1321    let mut path_with_account_name = std::path::PathBuf::from(&credentials_home_dir);
1322    path_with_account_name.push(dir_name);
1323    path_with_account_name.push(file_with_account_name);
1324    if path_with_account_name.exists() {
1325        Ok(format!(
1326            "{}\nThe file: {} already exists! Therefore it was not overwritten.",
1327            message_1,
1328            &path_with_account_name.display()
1329        ))
1330    } else {
1331        std::fs::File::create(&path_with_account_name)
1332            .wrap_err_with(|| format!("Failed to create file: {:?}", path_with_account_name))?
1333            .write(key_pair_properties_buf.as_bytes())
1334            .wrap_err_with(|| format!("Failed to write to file: {:?}", path_with_account_name))?;
1335        Ok(format!(
1336            "{}\nThe data for the access key is saved in a file {}",
1337            message_1,
1338            &path_with_account_name.display()
1339        ))
1340    }
1341}
1342
1343pub fn get_config_toml() -> color_eyre::eyre::Result<crate::config::Config> {
1344    if let Some(mut path_config_toml) = dirs::config_dir() {
1345        path_config_toml.extend(&["unc-cli", "config.toml"]);
1346
1347        if !path_config_toml.is_file() {
1348            write_config_toml(crate::config::Config::default())?;
1349        };
1350        let config_toml = std::fs::read_to_string(&path_config_toml)?;
1351        toml::from_str(&config_toml).or_else(|err| {
1352            eprintln!("Warning: `unc` CLI configuration file stored at {path_config_toml:?} could not be parsed due to: {err}");
1353            eprintln!("Note: The default configuration printed below will be used instead:\n");
1354            let default_config = crate::config::Config::default();
1355            eprintln!("{}", toml::to_string(&default_config)?);
1356            Ok(default_config)
1357        })
1358    } else {
1359        Ok(crate::config::Config::default())
1360    }
1361}
1362pub fn write_config_toml(config: crate::config::Config) -> CliResult {
1363    let config_toml = toml::to_string(&config)?;
1364    let mut path_config_toml = dirs::config_dir().wrap_err("Impossible to get your config dir!")?;
1365    path_config_toml.push("unc-cli");
1366    std::fs::create_dir_all(&path_config_toml)?;
1367    path_config_toml.push("config.toml");
1368    std::fs::File::create(&path_config_toml)
1369        .wrap_err_with(|| format!("Failed to create file: {path_config_toml:?}"))?
1370        .write(config_toml.as_bytes())
1371        .wrap_err_with(|| format!("Failed to write to file: {path_config_toml:?}"))?;
1372    eprintln!("Note: `unc` CLI configuration is stored in {path_config_toml:?}");
1373    Ok(())
1374}
1375
1376pub fn try_external_subcommand_execution(error: clap::Error) -> CliResult {
1377    let (subcommand, args) = {
1378        let mut args = std::env::args().skip(1);
1379        let subcommand = args
1380            .next()
1381            .ok_or_else(|| color_eyre::eyre::eyre!("subcommand is not provided"))?;
1382        (subcommand, args.collect::<Vec<String>>())
1383    };
1384    let is_top_level_command_known = crate::commands::TopLevelCommandDiscriminants::iter()
1385        .map(|x| format!("{:?}", &x).to_lowercase())
1386        .any(|x| x == subcommand);
1387    if is_top_level_command_known {
1388        error.exit()
1389    }
1390    let subcommand_exe = format!("unc-{}{}", subcommand, std::env::consts::EXE_SUFFIX);
1391
1392    let path = path_directories()
1393        .iter()
1394        .map(|dir| dir.join(&subcommand_exe))
1395        .find(|file| is_executable(file));
1396
1397    let command = path.ok_or_else(|| {
1398        color_eyre::eyre::eyre!(
1399            "{} command or {} extension does not exist",
1400            subcommand,
1401            subcommand_exe
1402        )
1403    })?;
1404
1405    let err = match cargo_util::ProcessBuilder::new(command)
1406        .args(&args)
1407        .exec_replace()
1408    {
1409        Ok(()) => return Ok(()),
1410        Err(e) => e,
1411    };
1412
1413    if let Some(perr) = err.downcast_ref::<cargo_util::ProcessError>() {
1414        if let Some(code) = perr.code {
1415            return Err(color_eyre::eyre::eyre!("perror occurred, code: {}", code));
1416        }
1417    }
1418    Err(color_eyre::eyre::eyre!(err))
1419}
1420
1421fn is_executable<P: AsRef<std::path::Path>>(path: P) -> bool {
1422    #[cfg(target_family = "unix")]
1423    {
1424        use std::os::unix::prelude::*;
1425        std::fs::metadata(path)
1426            .map(|metadata| metadata.is_file() && metadata.permissions().mode() & 0o111 != 0)
1427            .unwrap_or(false)
1428    }
1429    #[cfg(target_family = "windows")]
1430    path.as_ref().is_file()
1431}
1432
1433fn path_directories() -> Vec<std::path::PathBuf> {
1434    if let Some(val) = std::env::var_os("PATH") {
1435        std::env::split_paths(&val).collect()
1436    } else {
1437        Vec::new()
1438    }
1439}
1440
1441pub fn get_delegated_validator_list_from_mainnet(
1442    network_connection: &linked_hash_map::LinkedHashMap<String, crate::config::NetworkConfig>,
1443) -> color_eyre::eyre::Result<std::collections::BTreeSet<unc_primitives::types::AccountId>> {
1444    let network_config = network_connection
1445        .get("mainnet")
1446        .wrap_err("There is no 'mainnet' network in your configuration.")?;
1447
1448    let epoch_validator_info = network_config
1449        .json_rpc_client()
1450        .blocking_call(
1451            &unc_jsonrpc_client::methods::validators::RpcValidatorRequest {
1452                epoch_reference: unc_primitives::types::EpochReference::Latest,
1453            },
1454        )
1455        .wrap_err("Failed to get epoch validators information request.")?;
1456
1457    Ok(epoch_validator_info
1458        .current_pledge_proposals
1459        .into_iter()
1460        .map(|current_proposal| current_proposal.take_account_id())
1461        .chain(
1462            epoch_validator_info
1463                .current_validators
1464                .into_iter()
1465                .map(|current_validator| current_validator.account_id),
1466        )
1467        .chain(
1468            epoch_validator_info
1469                .next_validators
1470                .into_iter()
1471                .map(|next_validator| next_validator.account_id),
1472        )
1473        .collect())
1474}
1475
1476pub fn get_used_delegated_validator_list(
1477    config: &crate::config::Config,
1478) -> color_eyre::eyre::Result<VecDeque<unc_primitives::types::AccountId>> {
1479    let used_account_list: VecDeque<UsedAccount> =
1480        get_used_account_list(&config.credentials_home_dir);
1481    let mut delegated_validator_list =
1482        get_delegated_validator_list_from_mainnet(&config.network_connection)?;
1483    let mut used_delegated_validator_list: VecDeque<unc_primitives::types::AccountId> =
1484        VecDeque::new();
1485
1486    for used_account in used_account_list {
1487        if delegated_validator_list.remove(&used_account.account_id) {
1488            used_delegated_validator_list.push_back(used_account.account_id);
1489        }
1490    }
1491
1492    used_delegated_validator_list.extend(delegated_validator_list);
1493    Ok(used_delegated_validator_list)
1494}
1495
1496pub fn input_pledging_pool_validator_account_id(
1497    config: &crate::config::Config,
1498) -> color_eyre::eyre::Result<Option<crate::types::account_id::AccountId>> {
1499    let used_delegated_validator_list = get_used_delegated_validator_list(config)?
1500        .into_iter()
1501        .map(String::from)
1502        .collect::<Vec<_>>();
1503    let validator_account_id_str = match Text::new("What is delegated validator account ID?")
1504        .with_autocomplete(move |val: &str| {
1505            Ok(used_delegated_validator_list
1506                .iter()
1507                .filter(|s| s.contains(val))
1508                .cloned()
1509                .collect())
1510        })
1511        .with_validator(|account_id_str: &str| {
1512            match unc_primitives::types::AccountId::validate(account_id_str) {
1513                Ok(_) => Ok(inquire::validator::Validation::Valid),
1514                Err(err) => Ok(inquire::validator::Validation::Invalid(
1515                    inquire::validator::ErrorMessage::Custom(format!("Invalid account ID: {err}")),
1516                )),
1517            }
1518        })
1519        .prompt()
1520    {
1521        Ok(value) => value,
1522        Err(
1523            inquire::error::InquireError::OperationCanceled
1524            | inquire::error::InquireError::OperationInterrupted,
1525        ) => return Ok(None),
1526        Err(err) => return Err(err.into()),
1527    };
1528    let validator_account_id =
1529        crate::types::account_id::AccountId::from_str(&validator_account_id_str)?;
1530    update_used_account_list_as_non_signer(
1531        &config.credentials_home_dir,
1532        validator_account_id.as_ref(),
1533    );
1534    Ok(Some(validator_account_id))
1535}
1536
1537#[derive(Debug, Clone, PartialEq, Eq)]
1538pub struct PledgingPoolInfo {
1539    pub validator_id: unc_primitives::types::AccountId,
1540    pub fee: Option<RewardFeeFraction>,
1541    pub delegators: Option<u64>,
1542    pub pledge: unc_primitives::types::Balance,
1543}
1544
1545#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
1546pub struct RewardFeeFraction {
1547    pub numerator: u32,
1548    pub denominator: u32,
1549}
1550
1551pub fn get_validator_list(
1552    network_config: &crate::config::NetworkConfig,
1553) -> color_eyre::eyre::Result<Vec<PledgingPoolInfo>> {
1554    let json_rpc_client = network_config.json_rpc_client();
1555
1556    let validators_pledge = get_validators_pledge(&json_rpc_client)?;
1557
1558    let runtime = tokio::runtime::Builder::new_multi_thread()
1559        .enable_all()
1560        .build()?;
1561    let concurrency = 10;
1562
1563    let mut validator_list = runtime.block_on(
1564        futures::stream::iter(validators_pledge.iter())
1565            .map(|(validator_account_id, pledge)| async {
1566                get_pledging_pool_info(
1567                    &json_rpc_client.clone(),
1568                    validator_account_id.clone(),
1569                    *pledge,
1570                )
1571                .await
1572            })
1573            .buffer_unordered(concurrency)
1574            .try_collect::<Vec<_>>(),
1575    )?;
1576    validator_list.sort_by(|a, b| b.pledge.cmp(&a.pledge));
1577    Ok(validator_list)
1578}
1579
1580pub fn get_validators_pledge(
1581    json_rpc_client: &unc_jsonrpc_client::JsonRpcClient,
1582) -> color_eyre::eyre::Result<
1583    std::collections::HashMap<unc_primitives::types::AccountId, unc_primitives::types::Balance>,
1584> {
1585    let epoch_validator_info = json_rpc_client
1586        .blocking_call(
1587            &unc_jsonrpc_client::methods::validators::RpcValidatorRequest {
1588                epoch_reference: unc_primitives::types::EpochReference::Latest,
1589            },
1590        )
1591        .wrap_err("Failed to get epoch validators information request.")?;
1592
1593    Ok(epoch_validator_info
1594        .current_pledge_proposals
1595        .into_iter()
1596        .map(|validator_pledge_view| {
1597            let validator_pledge = validator_pledge_view.into_validator_pledge();
1598            validator_pledge.account_and_pledge()
1599        })
1600        .chain(epoch_validator_info.current_validators.into_iter().map(
1601            |current_epoch_validator_info| {
1602                (
1603                    current_epoch_validator_info.account_id,
1604                    current_epoch_validator_info.pledge,
1605                )
1606            },
1607        ))
1608        .chain(
1609            epoch_validator_info
1610                .next_validators
1611                .into_iter()
1612                .map(|next_epoch_validator_info| {
1613                    (
1614                        next_epoch_validator_info.account_id,
1615                        next_epoch_validator_info.pledge,
1616                    )
1617                }),
1618        )
1619        .collect())
1620}
1621
1622async fn get_pledging_pool_info(
1623    json_rpc_client: &unc_jsonrpc_client::JsonRpcClient,
1624    validator_account_id: unc_primitives::types::AccountId,
1625    pledge: u128,
1626) -> color_eyre::Result<PledgingPoolInfo> {
1627    let fee = match json_rpc_client
1628        .call(unc_jsonrpc_client::methods::query::RpcQueryRequest {
1629            block_reference: unc_primitives::types::Finality::Final.into(),
1630            request: unc_primitives::views::QueryRequest::CallFunction {
1631                account_id: validator_account_id.clone(),
1632                method_name: "get_reward_fee_fraction".to_string(),
1633                args: unc_primitives::types::FunctionArgs::from(vec![]),
1634            },
1635        })
1636        .await
1637    {
1638        Ok(response) => Some(
1639            response
1640                .call_result()?
1641                .parse_result_from_json::<RewardFeeFraction>()
1642                .wrap_err(
1643                    "Failed to parse return value of view function call for RewardFeeFraction.",
1644                )?,
1645        ),
1646        Err(unc_jsonrpc_client::errors::JsonRpcError::ServerError(
1647            unc_jsonrpc_client::errors::JsonRpcServerError::HandlerError(
1648                unc_jsonrpc_client::methods::query::RpcQueryError::NoContractCode { .. }
1649                | unc_jsonrpc_client::methods::query::RpcQueryError::ContractExecutionError {
1650                    ..
1651                },
1652            ),
1653        )) => None,
1654        Err(err) => return Err(err.into()),
1655    };
1656
1657    let delegators = match json_rpc_client
1658        .call(unc_jsonrpc_client::methods::query::RpcQueryRequest {
1659            block_reference: unc_primitives::types::Finality::Final.into(),
1660            request: unc_primitives::views::QueryRequest::CallFunction {
1661                account_id: validator_account_id.clone(),
1662                method_name: "get_number_of_accounts".to_string(),
1663                args: unc_primitives::types::FunctionArgs::from(vec![]),
1664            },
1665        })
1666        .await
1667    {
1668        Ok(response) => Some(
1669            response
1670                .call_result()?
1671                .parse_result_from_json::<u64>()
1672                .wrap_err("Failed to parse return value of view function call for u64.")?,
1673        ),
1674        Err(unc_jsonrpc_client::errors::JsonRpcError::ServerError(
1675            unc_jsonrpc_client::errors::JsonRpcServerError::HandlerError(
1676                unc_jsonrpc_client::methods::query::RpcQueryError::NoContractCode { .. }
1677                | unc_jsonrpc_client::methods::query::RpcQueryError::ContractExecutionError {
1678                    ..
1679                },
1680            ),
1681        )) => None,
1682        Err(err) => return Err(err.into()),
1683    };
1684
1685    Ok(PledgingPoolInfo {
1686        validator_id: validator_account_id.clone(),
1687        fee,
1688        delegators,
1689        pledge,
1690    })
1691}
1692
1693pub fn display_account_info(
1694    viewed_at_block_hash: &CryptoHash,
1695    viewed_at_block_height: &unc_primitives::types::BlockHeight,
1696    account_id: &unc_primitives::types::AccountId,
1697    delegated_pledge: &std::collections::BTreeMap<
1698        unc_primitives::types::AccountId,
1699        unc_token::UncToken,
1700    >,
1701    account_view: &unc_primitives::views::AccountView,
1702    access_keys: &[unc_primitives::views::AccessKeyInfoView],
1703) {
1704    let mut table: Table = Table::new();
1705    table.set_format(*prettytable::format::consts::FORMAT_NO_COLSEP);
1706
1707    profile_table(
1708        viewed_at_block_hash,
1709        viewed_at_block_height,
1710        account_id,
1711        &mut table,
1712    );
1713
1714    table.add_row(prettytable::row![
1715        Fg->"Native account balance",
1716        Fy->unc_token::UncToken::from_attounc(account_view.amount)
1717    ]);
1718    table.add_row(prettytable::row![
1719        Fg->"Validator pledge",
1720        Fy->unc_token::UncToken::from_attounc(account_view.pledging)
1721    ]);
1722
1723    for (validator_id, pledge) in delegated_pledge {
1724        table.add_row(prettytable::row![
1725            Fg->format!("Delegated pledge with <{validator_id}>"),
1726            Fy->pledge
1727        ]);
1728    }
1729
1730    table.add_row(prettytable::row![
1731        Fg->"Storage used by the account",
1732        Fy->bytesize::ByteSize(account_view.storage_usage),
1733    ]);
1734
1735    let contract_status = if account_view.code_hash == CryptoHash::default() {
1736        "No contract code".to_string()
1737    } else {
1738        hex::encode(account_view.code_hash.as_ref())
1739    };
1740    table.add_row(prettytable::row![
1741        Fg->"Contract (SHA-256 checksum hex)",
1742        Fy->contract_status
1743    ]);
1744
1745    let access_keys_summary = if access_keys.is_empty() {
1746        "Account is locked (no access keys)".to_string()
1747    } else {
1748        let full_access_keys_count = access_keys
1749            .iter()
1750            .filter(|access_key| {
1751                matches!(
1752                    access_key.access_key.permission,
1753                    unc_primitives::views::AccessKeyPermissionView::FullAccess
1754                )
1755            })
1756            .count();
1757        format!(
1758            "{} full access keys and {} function-call-only access keys",
1759            full_access_keys_count,
1760            access_keys.len() - full_access_keys_count
1761        )
1762    };
1763    table.add_row(prettytable::row![
1764        Fg->"Access keys",
1765        Fy->access_keys_summary
1766    ]);
1767    table.printstd();
1768}
1769
1770pub fn display_account_profile(
1771    viewed_at_block_hash: &CryptoHash,
1772    viewed_at_block_height: &unc_primitives::types::BlockHeight,
1773    account_id: &unc_primitives::types::AccountId,
1774) {
1775    let mut table = Table::new();
1776    table.set_format(*prettytable::format::consts::FORMAT_NO_COLSEP);
1777    profile_table(
1778        viewed_at_block_hash,
1779        viewed_at_block_height,
1780        account_id,
1781        &mut table,
1782    );
1783    table.printstd();
1784}
1785
1786fn profile_table(
1787    viewed_at_block_hash: &CryptoHash,
1788    viewed_at_block_height: &unc_primitives::types::BlockHeight,
1789    account_id: &unc_primitives::types::AccountId,
1790    table: &mut Table,
1791) {
1792    table.add_row(prettytable::row![
1793        Fy->account_id,
1794        format!("At block #{}\n({})", viewed_at_block_height, viewed_at_block_hash)
1795    ]);
1796}
1797
1798pub fn display_access_key_list(access_keys: &[unc_primitives::views::AccessKeyInfoView]) {
1799    let mut table = Table::new();
1800    table.set_titles(prettytable::row![Fg=>"#", "Public Key", "Nonce", "Permissions"]);
1801
1802    for (index, access_key) in access_keys.iter().enumerate() {
1803        let permissions_message = match &access_key.access_key.permission {
1804            AccessKeyPermissionView::FullAccess => "full access".to_owned(),
1805            AccessKeyPermissionView::FunctionCall {
1806                allowance,
1807                receiver_id,
1808                method_names,
1809            } => {
1810                let allowance_message = match allowance {
1811                    Some(amount) => format!(
1812                        "with an allowance of {}",
1813                        unc_token::UncToken::from_attounc(*amount)
1814                    ),
1815                    None => "with no limit".to_string(),
1816                };
1817                if method_names.is_empty() {
1818                    format!(
1819                        "do any function calls on {} {}",
1820                        receiver_id, allowance_message
1821                    )
1822                } else {
1823                    format!(
1824                        "only do {:?} function calls on {} {}",
1825                        method_names, receiver_id, allowance_message
1826                    )
1827                }
1828            }
1829        };
1830
1831        table.add_row(prettytable::row![
1832            Fg->index + 1,
1833            access_key.public_key,
1834            access_key.access_key.nonce,
1835            permissions_message
1836        ]);
1837    }
1838
1839    table.set_format(*prettytable::format::consts::FORMAT_NO_LINESEP_WITH_TITLE);
1840    table.printstd();
1841}
1842
1843/// Interactive prompt for network name.
1844///
1845/// If account_ids is provided, show the network connections that are more
1846/// relevant at the top of the list.
1847pub fn input_network_name(
1848    config: &crate::config::Config,
1849    account_ids: &[unc_primitives::types::AccountId],
1850) -> color_eyre::eyre::Result<Option<String>> {
1851    if config.network_connection.len() == 1 {
1852        return Ok(config.network_names().pop());
1853    }
1854    let variants = if !account_ids.is_empty() {
1855        let (mut matches, non_matches): (Vec<_>, Vec<_>) = config
1856            .network_connection
1857            .iter()
1858            .partition(|(_, network_config)| {
1859                // We use `linkdrop_account_id` as a heuristic to determine if
1860                // the accounts are on the same network. In the future, we
1861                // might consider to have a better way to do this.
1862                network_config
1863                    .linkdrop_account_id
1864                    .as_ref()
1865                    .map_or(false, |linkdrop_account_id| {
1866                        account_ids.iter().any(|account_id| {
1867                            account_id.as_str().ends_with(linkdrop_account_id.as_str())
1868                        })
1869                    })
1870            });
1871        let variants = if matches.is_empty() {
1872            non_matches
1873        } else {
1874            matches.extend(non_matches);
1875            matches
1876        };
1877        variants.into_iter().map(|(k, _)| k).collect()
1878    } else {
1879        config.network_connection.keys().collect()
1880    };
1881
1882    let select_submit = Select::new("What is the name of the network?", variants).prompt();
1883    match select_submit {
1884        Ok(value) => Ok(Some(value.clone())),
1885        Err(
1886            inquire::error::InquireError::OperationCanceled
1887            | inquire::error::InquireError::OperationInterrupted,
1888        ) => Ok(None),
1889        Err(err) => Err(err.into()),
1890    }
1891}
1892
1893#[easy_ext::ext(JsonRpcClientExt)]
1894pub impl unc_jsonrpc_client::JsonRpcClient {
1895    fn blocking_call<M>(
1896        &self,
1897        method: M,
1898    ) -> unc_jsonrpc_client::MethodCallResult<M::Response, M::Error>
1899    where
1900        M: unc_jsonrpc_client::methods::RpcMethod,
1901    {
1902        tokio::runtime::Runtime::new()
1903            .unwrap()
1904            .block_on(self.call(method))
1905    }
1906
1907    /// A helper function to make a view-funcation call using JSON encoding for the function
1908    /// arguments and function return value.
1909    fn blocking_call_view_function(
1910        &self,
1911        account_id: &unc_primitives::types::AccountId,
1912        method_name: &str,
1913        args: Vec<u8>,
1914        block_reference: unc_primitives::types::BlockReference,
1915    ) -> Result<unc_primitives::views::CallResult, color_eyre::eyre::Error> {
1916        let query_view_method_response = self
1917            .blocking_call(unc_jsonrpc_client::methods::query::RpcQueryRequest {
1918                block_reference,
1919                request: unc_primitives::views::QueryRequest::CallFunction {
1920                    account_id: account_id.clone(),
1921                    method_name: method_name.to_owned(),
1922                    args: unc_primitives::types::FunctionArgs::from(args),
1923                },
1924            })
1925            .wrap_err("Failed to make a view-function call")?;
1926        query_view_method_response.call_result()
1927    }
1928
1929    fn blocking_call_view_access_key(
1930        &self,
1931        account_id: &unc_primitives::types::AccountId,
1932        public_key: &unc_crypto::PublicKey,
1933        block_reference: unc_primitives::types::BlockReference,
1934    ) -> Result<
1935        unc_jsonrpc_primitives::types::query::RpcQueryResponse,
1936        unc_jsonrpc_client::errors::JsonRpcError<
1937            unc_jsonrpc_primitives::types::query::RpcQueryError,
1938        >,
1939    > {
1940        self.blocking_call(unc_jsonrpc_client::methods::query::RpcQueryRequest {
1941            block_reference,
1942            request: unc_primitives::views::QueryRequest::ViewAccessKey {
1943                account_id: account_id.clone(),
1944                public_key: public_key.clone(),
1945            },
1946        })
1947    }
1948
1949    fn blocking_call_view_access_key_list(
1950        &self,
1951        account_id: &unc_primitives::types::AccountId,
1952        block_reference: unc_primitives::types::BlockReference,
1953    ) -> Result<
1954        unc_jsonrpc_primitives::types::query::RpcQueryResponse,
1955        unc_jsonrpc_client::errors::JsonRpcError<
1956            unc_jsonrpc_primitives::types::query::RpcQueryError,
1957        >,
1958    > {
1959        self.blocking_call(unc_jsonrpc_client::methods::query::RpcQueryRequest {
1960            block_reference,
1961            request: unc_primitives::views::QueryRequest::ViewAccessKeyList {
1962                account_id: account_id.clone(),
1963            },
1964        })
1965    }
1966
1967    fn blocking_call_view_account(
1968        &self,
1969        account_id: &unc_primitives::types::AccountId,
1970        block_reference: unc_primitives::types::BlockReference,
1971    ) -> Result<
1972        unc_jsonrpc_primitives::types::query::RpcQueryResponse,
1973        unc_jsonrpc_client::errors::JsonRpcError<
1974            unc_jsonrpc_primitives::types::query::RpcQueryError,
1975        >,
1976    > {
1977        self.blocking_call(unc_jsonrpc_client::methods::query::RpcQueryRequest {
1978            block_reference,
1979            request: unc_primitives::views::QueryRequest::ViewAccount {
1980                account_id: account_id.clone(),
1981            },
1982        })
1983    }
1984}
1985
1986#[easy_ext::ext(RpcQueryResponseExt)]
1987pub impl unc_jsonrpc_primitives::types::query::RpcQueryResponse {
1988    fn access_key_view(&self) -> color_eyre::eyre::Result<unc_primitives::views::AccessKeyView> {
1989        if let unc_jsonrpc_primitives::types::query::QueryResponseKind::AccessKey(access_key_view) =
1990            &self.kind
1991        {
1992            Ok(access_key_view.clone())
1993        } else {
1994            color_eyre::eyre::bail!(
1995                "Internal error: Received unexpected query kind in response to a View Access Key query call",
1996            );
1997        }
1998    }
1999
2000    fn access_key_list_view(
2001        &self,
2002    ) -> color_eyre::eyre::Result<unc_primitives::views::AccessKeyList> {
2003        if let unc_jsonrpc_primitives::types::query::QueryResponseKind::AccessKeyList(
2004            access_key_list,
2005        ) = &self.kind
2006        {
2007            Ok(access_key_list.clone())
2008        } else {
2009            color_eyre::eyre::bail!(
2010                "Internal error: Received unexpected query kind in response to a View Access Key List query call",
2011            );
2012        }
2013    }
2014
2015    fn account_view(&self) -> color_eyre::eyre::Result<unc_primitives::views::AccountView> {
2016        if let unc_jsonrpc_primitives::types::query::QueryResponseKind::ViewAccount(account_view) =
2017            &self.kind
2018        {
2019            Ok(account_view.clone())
2020        } else {
2021            color_eyre::eyre::bail!(
2022                "Internal error: Received unexpected query kind in response to a View Account query call",
2023            );
2024        }
2025    }
2026
2027    fn call_result(&self) -> color_eyre::eyre::Result<unc_primitives::views::CallResult> {
2028        if let unc_jsonrpc_primitives::types::query::QueryResponseKind::CallResult(result) =
2029            &self.kind
2030        {
2031            Ok(result.clone())
2032        } else {
2033            color_eyre::eyre::bail!(
2034                "Internal error: Received unexpected query kind in response to a view-function query call",
2035            );
2036        }
2037    }
2038}
2039
2040#[easy_ext::ext(CallResultExt)]
2041pub impl unc_primitives::views::CallResult {
2042    fn parse_result_from_json<T>(&self) -> Result<T, color_eyre::eyre::Error>
2043    where
2044        T: for<'de> serde::Deserialize<'de>,
2045    {
2046        serde_json::from_slice(&self.result).wrap_err_with(|| {
2047            format!(
2048                "Failed to parse view-function call return value: {}",
2049                String::from_utf8_lossy(&self.result)
2050            )
2051        })
2052    }
2053
2054    fn print_logs(&self) {
2055        eprintln!("--------------");
2056        if self.logs.is_empty() {
2057            eprintln!("No logs")
2058        } else {
2059            eprintln!("Logs:");
2060            eprintln!("  {}", self.logs.join("\n  "));
2061        }
2062        eprintln!("--------------");
2063    }
2064}
2065
2066#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
2067pub struct UsedAccount {
2068    pub account_id: unc_primitives::types::AccountId,
2069    pub used_as_signer: bool,
2070}
2071
2072fn get_used_account_list_path(credentials_home_dir: &std::path::Path) -> std::path::PathBuf {
2073    credentials_home_dir.join("accounts.json")
2074}
2075
2076pub fn create_used_account_list_from_keychain(
2077    credentials_home_dir: &std::path::Path,
2078) -> color_eyre::eyre::Result<()> {
2079    let mut used_account_list: std::collections::BTreeSet<unc_primitives::types::AccountId> =
2080        std::collections::BTreeSet::new();
2081    let read_dir =
2082        |dir: &std::path::Path| dir.read_dir().map(Iterator::flatten).into_iter().flatten();
2083    for network_connection_dir in read_dir(credentials_home_dir) {
2084        for entry in read_dir(&network_connection_dir.path()) {
2085            match (entry.path().file_stem(), entry.path().extension()) {
2086                (Some(file_stem), Some(extension)) if extension == "json" => {
2087                    if let Ok(account_id) = file_stem.to_string_lossy().parse() {
2088                        used_account_list.insert(account_id);
2089                    }
2090                }
2091                _ if entry.path().is_dir() => {
2092                    if let Ok(account_id) = entry.file_name().to_string_lossy().parse() {
2093                        used_account_list.insert(account_id);
2094                    }
2095                }
2096                _ => {}
2097            }
2098        }
2099    }
2100
2101    if !used_account_list.is_empty() {
2102        let used_account_list_path = get_used_account_list_path(credentials_home_dir);
2103        let used_account_list_buf = serde_json::to_string(
2104            &used_account_list
2105                .into_iter()
2106                .map(|account_id| UsedAccount {
2107                    account_id,
2108                    used_as_signer: true,
2109                })
2110                .collect::<Vec<_>>(),
2111        )?;
2112        std::fs::write(&used_account_list_path, used_account_list_buf).wrap_err_with(|| {
2113            format!(
2114                "Failed to write to file: {}",
2115                used_account_list_path.display()
2116            )
2117        })?;
2118    }
2119    Ok(())
2120}
2121
2122pub fn update_used_account_list_as_signer(
2123    credentials_home_dir: &std::path::Path,
2124    account_id: &unc_primitives::types::AccountId,
2125) {
2126    let account_is_signer = true;
2127    update_used_account_list(credentials_home_dir, account_id, account_is_signer);
2128}
2129
2130pub fn update_used_account_list_as_non_signer(
2131    credentials_home_dir: &std::path::Path,
2132    account_id: &unc_primitives::types::AccountId,
2133) {
2134    let account_is_signer = false;
2135    update_used_account_list(credentials_home_dir, account_id, account_is_signer);
2136}
2137
2138fn update_used_account_list(
2139    credentials_home_dir: &std::path::Path,
2140    account_id: &unc_primitives::types::AccountId,
2141    account_is_signer: bool,
2142) {
2143    let mut used_account_list = get_used_account_list(credentials_home_dir);
2144
2145    let used_account = if let Some(mut used_account) = used_account_list
2146        .iter()
2147        .position(|used_account| &used_account.account_id == account_id)
2148        .and_then(|position| used_account_list.remove(position))
2149    {
2150        used_account.used_as_signer |= account_is_signer;
2151        used_account
2152    } else {
2153        UsedAccount {
2154            account_id: account_id.clone(),
2155            used_as_signer: account_is_signer,
2156        }
2157    };
2158    used_account_list.push_front(used_account);
2159
2160    let used_account_list_path = get_used_account_list_path(credentials_home_dir);
2161    if let Ok(used_account_list_buf) = serde_json::to_string(&used_account_list) {
2162        let _ = std::fs::write(used_account_list_path, used_account_list_buf);
2163    }
2164}
2165
2166pub fn get_used_account_list(credentials_home_dir: &std::path::Path) -> VecDeque<UsedAccount> {
2167    let used_account_list_path = get_used_account_list_path(credentials_home_dir);
2168    serde_json::from_str(
2169        std::fs::read_to_string(used_account_list_path)
2170            .as_deref()
2171            .unwrap_or("[]"),
2172    )
2173    .unwrap_or_default()
2174}
2175
2176pub fn is_used_account_list_exist(credentials_home_dir: &std::path::Path) -> bool {
2177    get_used_account_list_path(credentials_home_dir).exists()
2178}
2179
2180pub fn input_signer_account_id_from_used_account_list(
2181    credentials_home_dir: &std::path::Path,
2182    message: &str,
2183) -> color_eyre::eyre::Result<Option<crate::types::account_id::AccountId>> {
2184    let account_is_signer = true;
2185    input_account_id_from_used_account_list(credentials_home_dir, message, account_is_signer)
2186}
2187
2188pub fn input_non_signer_account_id_from_used_account_list(
2189    credentials_home_dir: &std::path::Path,
2190    message: &str,
2191) -> color_eyre::eyre::Result<Option<crate::types::account_id::AccountId>> {
2192    let account_is_signer = false;
2193    input_account_id_from_used_account_list(credentials_home_dir, message, account_is_signer)
2194}
2195
2196fn input_account_id_from_used_account_list(
2197    credentials_home_dir: &std::path::Path,
2198    message: &str,
2199    account_is_signer: bool,
2200) -> color_eyre::eyre::Result<Option<crate::types::account_id::AccountId>> {
2201    let used_account_list = get_used_account_list(credentials_home_dir)
2202        .into_iter()
2203        .filter(|account| !account_is_signer || account.used_as_signer)
2204        .map(|account| account.account_id.to_string())
2205        .collect::<Vec<_>>();
2206    let account_id_str = match Text::new(message)
2207        .with_autocomplete(move |val: &str| {
2208            Ok(used_account_list
2209                .iter()
2210                .filter(|s| s.contains(val))
2211                .cloned()
2212                .collect())
2213        })
2214        .with_validator(|account_id_str: &str| {
2215            match unc_primitives::types::AccountId::validate(account_id_str) {
2216                Ok(_) => Ok(inquire::validator::Validation::Valid),
2217                Err(err) => Ok(inquire::validator::Validation::Invalid(
2218                    inquire::validator::ErrorMessage::Custom(format!("Invalid account ID: {err}")),
2219                )),
2220            }
2221        })
2222        .prompt()
2223    {
2224        Ok(value) => value,
2225        Err(
2226            inquire::error::InquireError::OperationCanceled
2227            | inquire::error::InquireError::OperationInterrupted,
2228        ) => return Ok(None),
2229        Err(err) => return Err(err.into()),
2230    };
2231    let account_id = crate::types::account_id::AccountId::from_str(&account_id_str)?;
2232    update_used_account_list(credentials_home_dir, account_id.as_ref(), account_is_signer);
2233    Ok(Some(account_id))
2234}
2235
2236
2237pub fn find_seat_price(
2238    pledges: Vec<u128>,
2239    max_number_of_seats: u64,
2240    minimum_pledge_ratio: Rational32,
2241    protocol_version: unc_primitives::types::ProtocolVersion,
2242) -> color_eyre::eyre::Result<UncToken> {
2243    if protocol_version < 49 {
2244        return find_seat_price_for_protocol_before_49(pledges, max_number_of_seats);
2245    }
2246    find_seat_price_for_protocol_after_49(pledges, max_number_of_seats, minimum_pledge_ratio)
2247}
2248
2249/// This implementation is ported from unc-api-js:
2250fn find_seat_price_for_protocol_before_49(
2251    pledges: Vec<u128>,
2252    num_seats: u64,
2253) -> color_eyre::eyre::Result<UncToken> {
2254    let pledges_sum: u128 = pledges.iter().sum();
2255    if pledges_sum < num_seats.into() {
2256        return Err(color_eyre::eyre::Report::msg("Pledges are below seats"));
2257    }
2258    let mut left: u128 = 1;
2259    let mut right: u128 = pledges_sum + 1;
2260    while left != (right - 1) {
2261        let mid = left.saturating_add(right) / 2;
2262        let mut found = false;
2263        let mut current_sum: u128 = 0;
2264        for pledge in &pledges {
2265            current_sum = current_sum.saturating_add(pledge.saturating_div(mid));
2266            if current_sum >= num_seats.into() {
2267                left = mid;
2268                found = true;
2269                break;
2270            }
2271        }
2272        if !found {
2273            right = mid;
2274        }
2275    }
2276    Ok(UncToken::from_attounc(left))
2277}
2278
2279/// This implementation is ported from unc-api-js:
2280fn find_seat_price_for_protocol_after_49(
2281    mut pledges: Vec<u128>,
2282    max_number_of_seats: u64,
2283    minimum_pledge_ratio: Rational32,
2284) -> color_eyre::eyre::Result<UncToken> {
2285    let pledges_sum: u128 = pledges.iter().sum();
2286    if u64::try_from(pledges.len()).wrap_err("pledges.len() must fit in u64.")?
2287        < max_number_of_seats
2288    {
2289        return Ok(UncToken::from_attounc(
2290            pledges_sum
2291                .checked_mul(
2292                    (*minimum_pledge_ratio.numer())
2293                        .try_into()
2294                        .wrap_err("minimum_pledge_ratio.numer must be positive.")?,
2295                )
2296                .wrap_err("Can't multiply these numbers")?
2297                .checked_div(
2298                    (*minimum_pledge_ratio.denom())
2299                        .try_into()
2300                        .wrap_err("minimum_pledge_ratio.denom must be positive.")?,
2301                )
2302                .wrap_err("Can't divide these numbers")?,
2303        ));
2304    };
2305    pledges.sort();
2306
2307    Ok(UncToken::from_attounc(pledges[0] + 1))
2308}