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