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