1use alloc::string::String;
2use thiserror_no_std::Error;
3
4#[cfg(feature = "std")]
5mod std_cli;
6
7#[cfg(feature = "std")]
8pub use std_cli::run;
9
10pub const DEFAULT_MAINNET_URL: &str = "https://xrplcluster.com/";
12
13pub const DEFAULT_TESTNET_URL: &str = "https://s.altnet.rippletest.net:51234";
15
16pub const DEFAULT_WEBSOCKET_URL: &str = "wss://xrplcluster.com/";
18
19pub const DEFAULT_PAGINATION_LIMIT: u32 = 10;
21
22#[cfg_attr(feature = "std", derive(clap::Parser))]
24#[derive(Debug, Clone)]
25#[cfg_attr(
26 feature = "std",
27 command(name = "xrpl", about = "XRPL command line utility")
28)]
29pub struct Cli {
30 #[cfg_attr(feature = "std", command(subcommand))]
31 pub command: Commands,
32}
33
34#[cfg_attr(feature = "std", derive(clap::Subcommand))]
35#[derive(Debug, Clone)]
36pub enum Commands {
37 #[cfg_attr(feature = "std", command(subcommand))]
39 Wallet(WalletCommands),
40
41 #[cfg_attr(feature = "std", command(subcommand))]
43 Account(AccountCommands),
44
45 #[cfg_attr(feature = "std", command(subcommand))]
47 Transaction(TransactionCommands),
48
49 #[cfg_attr(feature = "std", command(subcommand))]
51 Server(ServerCommands),
52
53 #[cfg_attr(feature = "std", command(subcommand))]
55 Ledger(LedgerCommands),
56}
57
58#[cfg_attr(feature = "std", derive(clap::Subcommand))]
59#[derive(Debug, Clone)]
60pub enum WalletCommands {
61 Generate {
63 #[cfg_attr(feature = "std", arg(long))]
65 save: bool,
66
67 #[cfg_attr(feature = "std", arg(long))]
69 mnemonic: bool,
70
71 #[cfg_attr(feature = "std", arg(long, default_value_t = 12))]
73 words: usize,
74 },
75
76 FromSeed {
78 #[cfg_attr(feature = "std", arg(long))]
80 seed: String,
81 #[cfg_attr(feature = "std", arg(long, default_value = "0"))]
83 sequence: u64,
84 },
85
86 #[cfg(feature = "std")]
88 Faucet {
89 #[cfg_attr(feature = "std", arg(long, default_value_t = DEFAULT_TESTNET_URL.into()))]
91 url: String,
92 },
93
94 Validate {
96 #[cfg_attr(feature = "std", arg(long))]
98 address: String,
99 },
100}
101
102#[cfg_attr(feature = "std", derive(clap::Subcommand))]
103#[derive(Debug, Clone)]
104pub enum AccountCommands {
105 Info {
107 #[cfg_attr(feature = "std", arg(long))]
109 address: String,
110 #[cfg_attr(feature = "std", arg(long, default_value_t = DEFAULT_MAINNET_URL.into()))]
112 url: String,
113 },
114
115 Tx {
117 #[cfg_attr(feature = "std", arg(long))]
119 address: String,
120 #[cfg_attr(feature = "std", arg(long, default_value_t = DEFAULT_MAINNET_URL.into()))]
122 url: String,
123 #[cfg_attr(feature = "std", arg(long, default_value_t = DEFAULT_PAGINATION_LIMIT))]
125 limit: u32,
126 },
127
128 Objects {
130 #[cfg_attr(feature = "std", arg(long))]
132 address: String,
133 #[cfg_attr(feature = "std", arg(long, default_value_t = DEFAULT_MAINNET_URL.into()))]
135 url: String,
136 #[cfg_attr(feature = "std", arg(long))]
138 type_filter: Option<String>,
139 #[cfg_attr(feature = "std", arg(long, default_value_t = DEFAULT_PAGINATION_LIMIT as u16))]
141 limit: u16,
142 },
143
144 Channels {
146 #[cfg_attr(feature = "std", arg(long))]
148 address: String,
149 #[cfg_attr(feature = "std", arg(long, default_value_t = DEFAULT_MAINNET_URL.into()))]
151 url: String,
152 #[cfg_attr(feature = "std", arg(long))]
154 destination_account: Option<String>,
155 #[cfg_attr(feature = "std", arg(long, default_value_t = DEFAULT_PAGINATION_LIMIT as u16))]
157 limit: u16,
158 },
159
160 Currencies {
162 #[cfg_attr(feature = "std", arg(long))]
164 address: String,
165 #[cfg_attr(feature = "std", arg(long, default_value_t = DEFAULT_MAINNET_URL.into()))]
167 url: String,
168 },
169
170 Lines {
172 #[cfg_attr(feature = "std", arg(long))]
174 address: String,
175 #[cfg_attr(feature = "std", arg(long, default_value_t = DEFAULT_MAINNET_URL.into()))]
177 url: String,
178 #[cfg_attr(feature = "std", arg(long))]
180 peer: Option<String>,
181 #[cfg_attr(feature = "std", arg(long, default_value_t = DEFAULT_PAGINATION_LIMIT as u16))]
183 limit: u16,
184 },
185
186 Nfts {
188 #[cfg_attr(feature = "std", arg(long))]
190 address: String,
191 #[cfg_attr(feature = "std", arg(long, default_value_t = DEFAULT_MAINNET_URL.into()))]
193 url: String,
194 },
195
196 SetFlag {
198 #[cfg_attr(feature = "std", arg(short, long))]
200 seed: String,
201 #[cfg_attr(feature = "std", arg(short, long))]
203 flag: String,
204 #[cfg_attr(feature = "std", arg(short, long, default_value_t = DEFAULT_MAINNET_URL.into()))]
206 url: String,
207 },
208
209 ClearFlag {
211 #[cfg_attr(feature = "std", arg(short, long))]
213 seed: String,
214 #[cfg_attr(feature = "std", arg(short, long))]
216 flag: String,
217 #[cfg_attr(feature = "std", arg(short, long, default_value_t = DEFAULT_MAINNET_URL.into()))]
219 url: String,
220 },
221}
222
223#[cfg_attr(feature = "std", derive(clap::Subcommand))]
224#[derive(Debug, Clone)]
225pub enum TransactionCommands {
226 Sign {
228 #[cfg_attr(feature = "std", arg(short, long))]
230 seed: String,
231 #[cfg_attr(feature = "std", arg(short, long))]
233 r#type: String,
234 #[cfg_attr(feature = "std", arg(short, long))]
236 json: String,
237 },
238
239 Submit {
241 #[cfg_attr(feature = "std", arg(short, long))]
243 tx_blob: String,
244 #[cfg_attr(feature = "std", arg(short, long, default_value_t = DEFAULT_MAINNET_URL.into()))]
246 url: String,
247 },
248
249 TrustSet {
251 #[cfg_attr(feature = "std", arg(short, long))]
253 seed: String,
254 #[cfg_attr(feature = "std", arg(short, long))]
256 issuer: String,
257 #[cfg_attr(feature = "std", arg(short, long))]
259 currency: String,
260 #[cfg_attr(feature = "std", arg(short, long))]
262 limit: String,
263 #[cfg_attr(feature = "std", arg(short, long, default_value_t = DEFAULT_MAINNET_URL.into()))]
265 url: String,
266 },
267
268 NftMint {
270 #[cfg_attr(feature = "std", arg(short, long))]
272 seed: String,
273 #[cfg_attr(feature = "std", arg(long))]
275 uri: String,
276 #[cfg_attr(feature = "std", arg(long))]
278 flags: Option<u32>,
279 #[cfg_attr(feature = "std", arg(long))]
281 transfer_fee: Option<u16>,
282 #[cfg_attr(feature = "std", arg(short, long, default_value_t = DEFAULT_MAINNET_URL.into()))]
284 url: String,
285 },
286
287 NftBurn {
289 #[cfg_attr(feature = "std", arg(short, long))]
291 seed: String,
292 #[cfg_attr(feature = "std", arg(short, long))]
294 nftoken_id: String,
295 #[cfg_attr(feature = "std", arg(short, long, default_value_t = DEFAULT_MAINNET_URL.into()))]
297 url: String,
298 },
299}
300
301#[cfg_attr(feature = "std", derive(clap::Subcommand))]
302#[derive(Debug, Clone)]
303pub enum ServerCommands {
304 Fee {
306 #[cfg_attr(feature = "std", arg(long, default_value_t = DEFAULT_MAINNET_URL.into()))]
308 url: String,
309 },
310
311 Info {
313 #[cfg_attr(feature = "std", arg(long, default_value_t = DEFAULT_MAINNET_URL.into()))]
315 url: String,
316 },
317
318 Subscribe {
320 #[cfg_attr(feature = "std", arg(long, default_value_t = DEFAULT_WEBSOCKET_URL.into()))]
322 url: String,
323 #[cfg_attr(feature = "std", arg(long, default_value = "ledger"))]
325 stream: String,
326 #[cfg_attr(feature = "std", arg(long, default_value_t = DEFAULT_PAGINATION_LIMIT))]
328 limit: u32,
329 },
330}
331
332#[cfg_attr(feature = "std", derive(clap::Subcommand))]
333#[derive(Debug, Clone)]
334pub enum LedgerCommands {
335 Data {
337 #[cfg_attr(feature = "std", arg(long, default_value_t = DEFAULT_MAINNET_URL.into()))]
339 url: String,
340 #[cfg_attr(feature = "std", arg(long))]
342 ledger_index: Option<String>,
343 #[cfg_attr(feature = "std", arg(long))]
345 ledger_hash: Option<String>,
346 #[cfg_attr(feature = "std", arg(long, default_value_t = DEFAULT_PAGINATION_LIMIT as u16))]
348 limit: u16,
349 },
350}
351
352#[derive(Debug, Error)]
354pub enum CliError {
355 #[error("Wallet error: {0}")]
356 WalletError(#[from] crate::wallet::exceptions::XRPLWalletException),
357 #[error("Client error: {0}")]
358 ClientError(#[from] crate::asynch::clients::exceptions::XRPLClientException),
359 #[error("URL parse error: {0}")]
360 UrlParseError(#[from] url::ParseError),
361 #[error("JSON error: {0}")]
362 JsonError(#[from] serde_json::Error),
363 #[error("IO error: {0}")]
364 IoError(#[from] std::io::Error),
365 #[error("Helper error: {0}")]
366 HelperError(#[from] crate::asynch::exceptions::XRPLHelperException),
367 #[error("Core error: {0}")]
368 CoreError(#[from] crate::core::exceptions::XRPLCoreException),
369 #[error("Other error: {0}")]
370 Other(String),
371}
372
373#[cfg(feature = "std")]
375fn parse_url(url_str: &str) -> Result<url::Url, CliError> {
376 url_str.parse().map_err(CliError::UrlParseError)
377}
378
379#[cfg(feature = "std")]
381fn create_json_rpc_client(
382 url_str: &str,
383) -> Result<crate::clients::json_rpc::JsonRpcClient, CliError> {
384 use crate::clients::json_rpc::JsonRpcClient;
385 Ok(JsonRpcClient::connect(parse_url(url_str)?))
386}
387
388#[cfg(feature = "std")]
390fn handle_response<T: core::fmt::Debug>(
391 result: Result<T, crate::asynch::clients::exceptions::XRPLClientException>,
392 response_type: &str,
393) -> Result<(), CliError> {
394 match result {
395 Ok(response) => {
396 alloc::println!("{}: {:#?}", response_type, response);
397 Ok(())
398 }
399 Err(e) => Err(CliError::ClientError(e)),
400 }
401}
402
403#[cfg(feature = "std")]
405fn get_or_create_runtime() -> Result<tokio::runtime::Runtime, CliError> {
406 use tokio::runtime::Runtime;
407 Runtime::new().map_err(CliError::IoError)
408}
409
410#[cfg(feature = "std")]
412fn encode_and_print_tx<T: serde::Serialize>(tx: &T) -> Result<String, CliError> {
413 let tx_blob = crate::core::binarycodec::encode(tx)?;
414 alloc::println!("Signed transaction blob: {}", tx_blob);
415 Ok(tx_blob)
416}
417
418pub fn execute_command(command: &Commands) -> Result<(), CliError> {
419 match command {
420 Commands::Wallet(wallet_cmd) => match wallet_cmd {
421 WalletCommands::Generate {
422 save,
423 mnemonic,
424 words,
425 } => {
426 if *mnemonic {
427 use bip39::{Language, Mnemonic};
428
429 let mut rng = rand::thread_rng();
430 let mnemonic = Mnemonic::generate_in_with(&mut rng, Language::English, *words)
431 .expect("Invalid word count (must be 12, 15, 18, 21, or 24)");
432 let seed = mnemonic.to_seed(""); let phrase = mnemonic.words().collect::<Vec<_>>().join(" ");
434
435 alloc::println!(
436 "Generated wallet with mnemonic:\nMnemonic: {}\nSeed: {}",
437 phrase,
438 hex::encode(seed)
439 );
440 } else {
442 let wallet = crate::wallet::Wallet::create(None)?;
443 alloc::println!("Generated wallet: {:#?}", wallet);
444 if *save {
445 alloc::println!("Saving wallet functionality not implemented yet");
446 }
447 }
448 Ok(())
449 }
450 WalletCommands::FromSeed { seed, sequence } => {
451 let wallet = crate::wallet::Wallet::new(seed, *sequence)?;
452 alloc::println!("Wallet from seed: {:#?}", wallet);
453 Ok(())
454 }
455 #[cfg(feature = "std")]
456 WalletCommands::Faucet { url } => {
457 use crate::asynch::clients::AsyncJsonRpcClient;
458 use crate::asynch::wallet::generate_faucet_wallet;
459
460 let rt = get_or_create_runtime()?;
462
463 let result = rt.block_on(async {
465 let client = AsyncJsonRpcClient::connect(url.parse()?);
466 generate_faucet_wallet(&client, None, None, None, None).await
467 });
468
469 match result {
470 Ok(wallet) => {
471 alloc::println!("Generated faucet wallet: {:#?}", wallet);
472 Ok(())
473 }
474 Err(e) => Err(CliError::Other(format!(
475 "Failed to generate faucet wallet: {}",
476 e
477 ))),
478 }
479 }
480 WalletCommands::Validate { address } => {
481 use crate::core::addresscodec::{is_valid_classic_address, is_valid_xaddress};
482
483 let is_valid_classic = is_valid_classic_address(&address);
484 let is_valid_x = is_valid_xaddress(&address);
485
486 if is_valid_classic {
487 alloc::println!("Valid classic address: {}", address);
488 Ok(())
489 } else if is_valid_x {
490 use crate::core::addresscodec::xaddress_to_classic_address;
491 let (classic_address, tag, is_test) = xaddress_to_classic_address(&address)?;
492 alloc::println!("Valid X-address: {}", address);
493 alloc::println!(" Classic address: {}", classic_address);
494 alloc::println!(" Destination tag: {:?}", tag);
495 alloc::println!(" Test network: {}", is_test);
496 Ok(())
497 } else {
498 Err(CliError::Other(format!("Invalid address: {}", address)))
499 }
500 }
501 },
502
503 Commands::Account(account_cmd) => match account_cmd {
504 #[cfg(feature = "std")]
505 AccountCommands::Info { address, url } => {
506 use crate::clients::XRPLSyncClient;
507 use crate::models::requests::account_info::AccountInfo;
508
509 let client = create_json_rpc_client(url)?;
511
512 let account_info = AccountInfo::new(
514 None, address.clone().into(), None, None, None, None, None, );
522
523 handle_response(client.request(account_info.into()), "Account info")
525 }
526 #[cfg(feature = "std")]
527 AccountCommands::Tx {
528 address,
529 url,
530 limit,
531 } => {
532 use crate::clients::XRPLSyncClient;
533 use crate::models::requests::account_tx::AccountTx;
534
535 let client = create_json_rpc_client(url)?;
537
538 let account_tx = AccountTx::new(
540 None,
541 address.clone().into(),
542 None,
543 None,
544 None,
545 None,
546 Some(*limit),
547 None,
548 None,
549 None,
550 );
551
552 handle_response(client.request(account_tx.into()), "Account transactions")
554 }
555 #[cfg(feature = "std")]
556 AccountCommands::Objects {
557 address,
558 url,
559 type_filter,
560 limit,
561 } => {
562 use std::str::FromStr;
563
564 use crate::clients::XRPLSyncClient;
565 use crate::models::requests::account_objects::{AccountObjectType, AccountObjects};
566
567 let object_type = if let Some(filter) = type_filter.as_deref() {
569 match AccountObjectType::from_str(filter) {
570 Ok(obj_type) => Some(obj_type),
571 Err(_) => {
572 return Err(CliError::Other(format!(
573 "Invalid object type: {}",
574 filter
575 )));
576 }
577 }
578 } else {
579 None
580 };
581
582 let client = create_json_rpc_client(url)?;
584
585 let account_objects = AccountObjects::new(
587 None,
588 address.clone().into(),
589 None,
590 None,
591 object_type,
592 None,
593 Some(*limit),
594 None,
595 );
596
597 handle_response(client.request(account_objects.into()), "Account objects")
599 }
600 #[cfg(feature = "std")]
601 AccountCommands::Channels {
602 address,
603 url,
604 destination_account,
605 limit,
606 } => {
607 use crate::clients::XRPLSyncClient;
608 use crate::models::requests::account_channels::AccountChannels;
609
610 let client = create_json_rpc_client(url)?;
612
613 let account_channels = AccountChannels::new(
615 None,
616 address.clone().into(),
617 destination_account.as_deref().map(Into::into),
618 None,
619 None,
620 Some(*limit),
621 None,
622 );
623
624 handle_response(client.request(account_channels.into()), "Account channels")
626 }
627 #[cfg(feature = "std")]
628 AccountCommands::Currencies { address, url } => {
629 use crate::clients::XRPLSyncClient;
630 use crate::models::requests::account_currencies::AccountCurrencies;
631
632 let client = create_json_rpc_client(url)?;
634
635 let account_currencies =
637 AccountCurrencies::new(None, address.clone().into(), None, None, None);
638
639 handle_response(
641 client.request(account_currencies.into()),
642 "Account currencies",
643 )
644 }
645 #[cfg(feature = "std")]
646 AccountCommands::Lines {
647 address,
648 url,
649 peer,
650 limit,
651 } => {
652 use crate::clients::XRPLSyncClient;
653 use crate::models::requests::account_lines::AccountLines;
654
655 let client = create_json_rpc_client(url)?;
657
658 let account_lines = AccountLines::new(
660 None,
661 address.clone().into(),
662 None,
663 None,
664 Some(*limit),
665 peer.as_deref().map(Into::into),
666 );
667
668 handle_response(client.request(account_lines.into()), "Account trust lines")
670 }
671 #[cfg(feature = "std")]
672 AccountCommands::Nfts { address, url } => {
673 use crate::clients::XRPLSyncClient;
674 use crate::models::requests::account_nfts::AccountNfts;
675
676 let client = create_json_rpc_client(url)?;
677 let req = AccountNfts::new(
678 None, address.clone().into(), None, None, );
683
684 handle_response(client.request(req.into()), "Account NFTs")
685 }
686 #[cfg(feature = "std")]
687 AccountCommands::SetFlag { seed, flag, url } => {
688 use alloc::borrow::Cow;
689 use core::str::FromStr;
690
691 use crate::asynch::transaction::sign;
692 use crate::models::transactions::account_set::{AccountSet, AccountSetFlag};
693 use crate::wallet::Wallet;
694
695 let wallet = Wallet::new(seed, 0)?;
696 let flag_enum = AccountSetFlag::from_str(flag)
697 .map_err(|_| CliError::Other(format!("Invalid flag: {}", flag)))?;
698
699 let mut tx = AccountSet::new(
700 Cow::Owned(wallet.classic_address.clone()),
701 None, None, None, None, None, None, None, None, None, None, None, None, None, Some(flag_enum), None, None, None, );
719
720 sign(&mut tx, &wallet, false)?;
721 let tx_blob = encode_and_print_tx(&tx)?;
722
723 alloc::println!(
724 "To submit, use: xrpl transaction submit --tx-blob {} --url {}",
725 tx_blob,
726 url
727 );
728 Ok(())
729 }
730
731 #[cfg(feature = "std")]
732 AccountCommands::ClearFlag { seed, flag, url } => {
733 use alloc::borrow::Cow;
734 use core::str::FromStr;
735
736 use crate::asynch::transaction::sign;
737 use crate::models::transactions::account_set::{AccountSet, AccountSetFlag};
738 use crate::wallet::Wallet;
739
740 let wallet = Wallet::new(seed, 0)?;
741 let flag_enum = AccountSetFlag::from_str(flag)
742 .map_err(|_| CliError::Other(format!("Invalid flag: {}", flag)))?;
743
744 let mut tx = AccountSet::new(
745 Cow::Owned(wallet.classic_address.clone()),
746 None, None, None, None, None, None, None, None, None, None, None, None, None, Some(flag_enum), None, None, None, );
764
765 sign(&mut tx, &wallet, false)?;
766 let tx_blob = encode_and_print_tx(&tx)?;
767
768 alloc::println!(
769 "To submit, use: xrpl transaction submit --tx-blob {} --url {}",
770 tx_blob,
771 url
772 );
773 Ok(())
774 }
775 },
776
777 Commands::Transaction(tx_cmd) => match tx_cmd {
778 #[cfg(feature = "std")]
779 TransactionCommands::Sign { seed, r#type, json } => {
780 use serde_json::Value;
781
782 use crate::models::transactions::{
783 account_set::AccountSet, offer_cancel::OfferCancel, offer_create::OfferCreate,
784 payment::Payment, trust_set::TrustSet,
785 };
786 use crate::wallet::Wallet;
787
788 let wallet = Wallet::new(&seed, 0)?;
790
791 let json_value: Value = serde_json::from_str(&json)?;
793
794 use crate::asynch::transaction::sign;
795
796 match r#type.to_lowercase().as_str() {
798 "payment" => {
799 let mut tx: Payment = serde_json::from_value(json_value)?;
800 sign(&mut tx, &wallet, false)?;
801 encode_and_print_tx(&tx)?;
802 }
803 "accountset" => {
804 let mut tx: AccountSet = serde_json::from_value(json_value)?;
805 sign(&mut tx, &wallet, false)?;
806 encode_and_print_tx(&tx)?;
807 }
808 "offercreate" => {
809 let mut tx: OfferCreate = serde_json::from_value(json_value)?;
810 sign(&mut tx, &wallet, false)?;
811 encode_and_print_tx(&tx)?;
812 }
813 "offercancel" => {
814 let mut tx: OfferCancel = serde_json::from_value(json_value)?;
815 sign(&mut tx, &wallet, false)?;
816 encode_and_print_tx(&tx)?;
817 }
818 "trustset" => {
819 let mut tx: TrustSet = serde_json::from_value(json_value)?;
820 sign(&mut tx, &wallet, false)?;
821 encode_and_print_tx(&tx)?;
822 }
823 _ => {
824 return Err(CliError::Other(format!(
825 "Unsupported transaction type: {}",
826 r#type
827 )));
828 }
829 }
830
831 Ok(())
832 }
833 #[cfg(feature = "std")]
834 TransactionCommands::Submit { tx_blob, url } => {
835 use crate::clients::XRPLSyncClient;
836 use crate::models::requests::submit::Submit;
837
838 let client = create_json_rpc_client(url)?;
840
841 let submit_request = Submit::new(None, tx_blob.into(), None);
843
844 handle_response(
846 client.request(submit_request.into()),
847 "Transaction submission result",
848 )
849 }
850 #[cfg(feature = "std")]
851 TransactionCommands::TrustSet {
852 seed,
853 issuer,
854 currency,
855 limit,
856 url,
857 } => {
858 use alloc::borrow::Cow;
859
860 use crate::models::IssuedCurrencyAmount;
861 use crate::models::transactions::trust_set::TrustSet;
862 use crate::wallet::Wallet;
863
864 let wallet = Wallet::new(seed, 0)?;
866
867 let amount = IssuedCurrencyAmount::new(
869 currency.clone().into(), issuer.clone().into(), limit.clone().into(), );
873
874 let mut tx = TrustSet::new(
876 Cow::Owned(wallet.classic_address.clone()),
877 None, None, None, None, None, None, None, None, None, amount,
887 None, None, );
890
891 use crate::asynch::transaction::sign;
893 sign(&mut tx, &wallet, false)?;
894
895 let tx_blob = encode_and_print_tx(&tx)?;
897
898 alloc::println!(
899 "To submit, use: xrpl transaction submit --tx-blob {} --url {}",
900 tx_blob,
901 url
902 );
903
904 Ok(())
905 }
906 #[cfg(feature = "std")]
907 TransactionCommands::NftMint {
908 seed,
909 uri,
910 flags,
911 transfer_fee,
912 url,
913 } => {
914 use crate::asynch::transaction::sign;
915 use crate::models::transactions::nftoken_mint::{NFTokenMint, NFTokenMintFlag};
916 use crate::wallet::Wallet;
917 use alloc::borrow::Cow;
918
919 let wallet = Wallet::new(seed, 0)?;
920
921 let flag_collection = flags.map(|f| NFTokenMintFlag::from_bits(f).into());
923
924 let mut tx = NFTokenMint::new(
925 Cow::Owned(wallet.classic_address.clone()),
926 None, None, flag_collection,
929 None, None, None, None, None, None, 0, None, transfer_fee.map(|v| v as u32),
938 Some(uri.clone().into()),
939 );
940
941 sign(&mut tx, &wallet, false)?;
942 let tx_blob = encode_and_print_tx(&tx)?;
943
944 alloc::println!(
945 "To submit, use: xrpl transaction submit --tx-blob {} --url {}",
946 tx_blob,
947 url
948 );
949 Ok(())
950 }
951
952 #[cfg(feature = "std")]
953 TransactionCommands::NftBurn {
954 seed,
955 nftoken_id,
956 url,
957 } => {
958 use crate::asynch::transaction::sign;
959 use crate::models::transactions::nftoken_burn::NFTokenBurn;
960 use crate::wallet::Wallet;
961 use alloc::borrow::Cow;
962
963 let wallet = Wallet::new(seed, 0)?;
964
965 let mut tx = NFTokenBurn::new(
966 Cow::Owned(wallet.classic_address.clone()),
967 None, None, None, None, None, None, None, None, nftoken_id.clone().into(),
976 None, );
978
979 sign(&mut tx, &wallet, false)?;
980 let tx_blob = encode_and_print_tx(&tx)?;
981
982 alloc::println!(
983 "To submit, use: xrpl transaction submit --tx-blob {} --url {}",
984 tx_blob,
985 url
986 );
987 Ok(())
988 }
989 },
990
991 Commands::Server(server_cmd) => match server_cmd {
992 #[cfg(feature = "std")]
993 ServerCommands::Fee { url } => {
994 use crate::ledger::{FeeType, get_fee};
995
996 let rt = get_or_create_runtime()?;
998 let client = create_json_rpc_client(url)?;
999
1000 match rt.block_on(async { get_fee(&client, None, Some(FeeType::Open)) }) {
1002 Ok(fee) => {
1003 alloc::println!("Current network fee: {} drops", fee);
1004 Ok(())
1005 }
1006 Err(e) => Err(CliError::HelperError(e)),
1007 }
1008 }
1009 #[cfg(feature = "std")]
1010 ServerCommands::Info { url } => {
1011 use crate::clients::XRPLSyncClient;
1012 use crate::models::requests::server_info::ServerInfo;
1013
1014 let client = create_json_rpc_client(url)?;
1016
1017 let server_info = ServerInfo::new(None);
1019
1020 handle_response(client.request(server_info.into()), "Server info")
1022 }
1023 #[cfg(feature = "std")]
1024 ServerCommands::Subscribe { url, stream, limit } => {
1025 use crate::clients::websocket::WebSocketClient;
1026 use crate::clients::{SingleExecutorMutex, XRPLSyncWebsocketIO};
1027 use crate::models::requests::subscribe::{StreamParameter, Subscribe};
1028
1029 let stream_param = match stream.to_lowercase().as_str() {
1031 "ledger" => StreamParameter::Ledger,
1032 "transactions" => StreamParameter::Transactions,
1033 "validations" => StreamParameter::Validations,
1034 _ => return Err(CliError::Other(format!("Unknown stream type: {}", stream))),
1035 };
1036
1037 let mut websocket: WebSocketClient<SingleExecutorMutex, _> =
1039 WebSocketClient::open(parse_url(url)?)?;
1040
1041 let subscribe = Subscribe::new(
1043 None,
1044 None,
1045 None,
1046 None,
1047 Some(vec![stream_param]),
1048 None,
1049 None,
1050 None,
1051 );
1052
1053 websocket.xrpl_send(subscribe.into())?;
1054
1055 let mut count = 0;
1057 loop {
1058 if *limit > 0 && count >= *limit {
1059 break;
1060 }
1061
1062 match websocket.xrpl_receive() {
1063 Ok(Some(response)) => {
1064 alloc::println!("Received: {:#?}", response);
1065 count += 1;
1066 }
1067 Ok(None) => {
1068 std::thread::sleep(std::time::Duration::from_millis(100));
1069 }
1070 Err(e) => {
1071 return Err(CliError::ClientError(e));
1072 }
1073 }
1074 }
1075
1076 Ok(())
1077 }
1078 },
1079
1080 Commands::Ledger(ledger_cmd) => match ledger_cmd {
1081 #[cfg(feature = "std")]
1082 LedgerCommands::Data {
1083 url,
1084 ledger_index,
1085 ledger_hash,
1086 limit,
1087 } => {
1088 use crate::clients::XRPLSyncClient;
1089 use crate::models::requests::ledger_data::LedgerData;
1090
1091 let client = create_json_rpc_client(url)?;
1093
1094 let ledger_data = LedgerData::new(
1096 None,
1097 None,
1098 ledger_index.as_deref().map(Into::into),
1099 ledger_hash.as_deref().map(Into::into),
1100 Some(*limit),
1101 None,
1102 );
1103
1104 handle_response(client.request(ledger_data.into()), "Ledger data")
1106 }
1107 },
1108 }
1109}