dg_xch_cli_lib/
lib.rs

1use crate::cli::ProgramOutput;
2use crate::wallet_commands::{
3    create_cold_wallet, get_plotnft_ready_state, migrate_plot_nft, migrate_plot_nft_with_owner_key,
4};
5use crate::wallets::plotnft_utils::{get_plotnft_by_launcher_id, scrounge_for_plotnfts};
6use blst::min_pk::SecretKey;
7use clap::Parser;
8use cli::{prompt_for_mnemonic, Cli, RootCommands, WalletAction};
9use dg_logger::DruidGardenLogger;
10use dg_xch_clients::api::full_node::{FullnodeAPI, FullnodeExtAPI};
11use dg_xch_clients::api::pool::create_pool_login_url;
12use dg_xch_clients::rpc::full_node::FullnodeClient;
13use dg_xch_clients::ClientSSLConfig;
14use dg_xch_core::blockchain::sized_bytes::{Bytes32, Bytes48};
15use dg_xch_core::blockchain::spend_bundle::SpendBundle;
16use dg_xch_core::clvm::assemble::{assemble_text, is_hex};
17use dg_xch_core::clvm::program::SerializedProgram;
18use dg_xch_core::clvm::utils::INFINITE_COST;
19use dg_xch_core::consensus::constants::{CONSENSUS_CONSTANTS_MAP, MAINNET};
20use dg_xch_keys::{
21    encode_puzzle_hash, key_from_mnemonic, master_sk_to_farmer_sk, master_sk_to_pool_sk,
22    master_sk_to_wallet_sk, master_sk_to_wallet_sk_unhardened,
23};
24use dg_xch_puzzles::clvm_puzzles::launcher_id_to_p2_puzzle_hash;
25use dg_xch_puzzles::p2_delegated_puzzle_or_hidden_puzzle::puzzle_hash_for_pk;
26use dg_xch_serialize::{ChiaProtocolVersion, ChiaSerialize};
27use hex::{decode, encode};
28use log::{error, info, Level};
29use std::env;
30use std::io::{Cursor, Error, ErrorKind};
31use std::path::Path;
32use std::str::FromStr;
33use std::sync::Arc;
34
35pub mod cli;
36pub mod commands;
37pub mod simulator;
38pub mod wallet_commands;
39pub mod wallets;
40
41#[allow(clippy::too_many_lines)]
42#[allow(clippy::cast_sign_loss)]
43pub async fn run_cli() -> Result<(), Error> {
44    let cli = Cli::parse();
45    let _logger = DruidGardenLogger::build()
46        .use_colors(true)
47        .current_level(Level::Info)
48        .init()
49        .map_err(|e| Error::other(format!("{e:?}")))?;
50    let host = cli
51        .fullnode_host
52        .unwrap_or(env::var("FULLNODE_HOST").unwrap_or("localhost".to_string()));
53    let port = cli.fullnode_port.unwrap_or(
54        env::var("FULLNODE_PORT")
55            .map(|s| s.parse().unwrap_or(8555))
56            .unwrap_or(8555),
57    );
58    let timeout = cli.timeout.unwrap_or(60);
59    let ssl = cli.ssl_path.map(|v| ClientSSLConfig {
60        ssl_crt_path: format!("{}/{}", v, "full_node/private_full_node.crt"),
61        ssl_key_path: format!("{}/{}", v, "full_node/private_full_node.crt"),
62        ssl_ca_crt_path: format!("{}/{}", v, "full_node/private_full_node.crt"),
63    });
64    let constants = if let Some(network) = cli.network {
65        CONSENSUS_CONSTANTS_MAP
66            .get(&network)
67            .cloned()
68            .unwrap_or_else(|| MAINNET.clone())
69    } else {
70        MAINNET.clone()
71    };
72    match cli.action {
73        RootCommands::PrintPlottingInfo { launcher_id } => {
74            let client = Arc::new(FullnodeClient::new(&host, port, timeout, ssl, &None)?);
75            let master_key = key_from_mnemonic(&prompt_for_mnemonic()?)?;
76            let mut page = 0;
77            let mut plotnfts = vec![];
78            if let Some(launcher_id) = launcher_id {
79                info!("Searching for NFT with LauncherID: {launcher_id}");
80                if let Some(plotnft) =
81                    get_plotnft_by_launcher_id(client.clone(), launcher_id, None).await?
82                {
83                    plotnfts.push(plotnft);
84                } else {
85                    return Err(Error::new(
86                        ErrorKind::NotFound,
87                        "Failed to find a plotNFT with LauncherID: {launcher_id}",
88                    ));
89                }
90            } else {
91                info!("No LauncherID Specified, Searching for PlotNFTs...");
92                while page < 50 && plotnfts.is_empty() {
93                    let mut puzzle_hashes = vec![];
94                    for index in page * 50..(page + 1) * 50 {
95                        let wallet_sk = master_sk_to_wallet_sk_unhardened(&master_key, index)
96                            .map_err(|e| {
97                                Error::new(
98                                    ErrorKind::InvalidInput,
99                                    format!("Failed to parse Wallet SK: {e:?}"),
100                                )
101                            })?;
102                        let pub_key: Bytes48 = wallet_sk.sk_to_pk().to_bytes().into();
103                        puzzle_hashes.push(puzzle_hash_for_pk(pub_key)?);
104                        let hardened_wallet_sk = master_sk_to_wallet_sk(&master_key, index)
105                            .map_err(|e| {
106                                Error::new(
107                                    ErrorKind::InvalidInput,
108                                    format!("Failed to parse Wallet SK: {e:?}"),
109                                )
110                            })?;
111                        let pub_key: Bytes48 = hardened_wallet_sk.sk_to_pk().to_bytes().into();
112                        puzzle_hashes.push(puzzle_hash_for_pk(pub_key)?);
113                    }
114                    plotnfts.extend(scrounge_for_plotnfts(client.clone(), &puzzle_hashes).await?);
115                    page += 1;
116                }
117            }
118            let farmer_key =
119                Bytes48::from(master_sk_to_farmer_sk(&master_key)?.sk_to_pk().to_bytes());
120            let pool_key = Bytes48::from(master_sk_to_pool_sk(&master_key)?.sk_to_pk().to_bytes());
121            info!("{{");
122            info!("\tFarmerPublicKey(All Plots): {farmer_key},");
123            info!("\tPoolPublicKey(OG Plots): {pool_key},");
124            info!("\tPlotNfts(NFT Plots): {{");
125            let total = plotnfts.len();
126            for (index, plot_nft) in plotnfts.into_iter().enumerate() {
127                info!("\t  {{");
128                info!("\t    LauncherID: {},", plot_nft.launcher_id);
129                info!(
130                    "\t    ContractAddress: {}",
131                    encode_puzzle_hash(
132                        &launcher_id_to_p2_puzzle_hash(
133                            plot_nft.launcher_id,
134                            plot_nft.delay_time as u64,
135                            plot_nft.delay_puzzle_hash,
136                        )?,
137                        "xch"
138                    )?
139                );
140                info!("\t  }}{}", if index == total - 1 { "" } else { "," });
141            }
142            info!("\t}}");
143            info!("}}");
144        }
145        RootCommands::GetBlockchainState => {
146            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
147            let results = client.get_blockchain_state().await?;
148            match serde_json::to_string_pretty(&results) {
149                Ok(json) => {
150                    println!("{json}");
151                }
152                Err(e) => {
153                    error!("Failed to convert value to JSON: {e:?}");
154                }
155            }
156        }
157        RootCommands::GetBlock { header_hash } => {
158            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
159            let results = client.get_block(&header_hash).await?;
160            match serde_json::to_string_pretty(&results) {
161                Ok(json) => {
162                    info!("{json}");
163                }
164                Err(e) => {
165                    error!("Failed to convert value to JSON: {e:?}");
166                }
167            }
168        }
169        RootCommands::GetBlockCountMetrics => {
170            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
171            let results = client.get_block_count_metrics().await?;
172            match serde_json::to_string_pretty(&results) {
173                Ok(json) => {
174                    info!("{json}");
175                }
176                Err(e) => {
177                    error!("Failed to convert value to JSON: {e:?}");
178                }
179            }
180        }
181        RootCommands::GetBlocks {
182            start,
183            end,
184            exclude_header_hash,
185            exclude_reorged,
186        } => {
187            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
188            let results = client
189                .get_blocks(start, end, exclude_header_hash, exclude_reorged)
190                .await?;
191            match serde_json::to_string_pretty(&results) {
192                Ok(json) => {
193                    info!("{json}");
194                }
195                Err(e) => {
196                    error!("Failed to convert value to JSON: {e:?}");
197                }
198            }
199        }
200        RootCommands::GetAllBlocks { start, end } => {
201            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
202            let results = client.get_all_blocks(start, end).await?;
203            match serde_json::to_string_pretty(&results) {
204                Ok(json) => {
205                    info!("{json}");
206                }
207                Err(e) => {
208                    error!("Failed to convert value to JSON: {e:?}");
209                }
210            }
211        }
212        RootCommands::GetBlockRecord { header_hash } => {
213            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
214            let results = client.get_block_record(&header_hash).await?;
215            match serde_json::to_string_pretty(&results) {
216                Ok(json) => {
217                    info!("{json}");
218                }
219                Err(e) => {
220                    error!("Failed to convert value to JSON: {e:?}");
221                }
222            }
223        }
224        RootCommands::GetBlockRecordByHeight { height } => {
225            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
226            let results = client.get_block_record_by_height(height).await?;
227            match serde_json::to_string_pretty(&results) {
228                Ok(json) => {
229                    info!("{json}");
230                }
231                Err(e) => {
232                    error!("Failed to convert value to JSON: {e:?}");
233                }
234            }
235        }
236        RootCommands::GetBlockRecords { start, end } => {
237            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
238            let results = client.get_block_records(start, end).await?;
239            match serde_json::to_string_pretty(&results) {
240                Ok(json) => {
241                    info!("{json}");
242                }
243                Err(e) => {
244                    error!("Failed to convert value to JSON: {e:?}");
245                }
246            }
247        }
248        RootCommands::GetUnfinishedBlocks => {
249            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
250            let results = client.get_unfinished_block_headers().await?;
251            match serde_json::to_string_pretty(&results) {
252                Ok(json) => {
253                    info!("{json}");
254                }
255                Err(e) => {
256                    error!("Failed to convert value to JSON: {e:?}");
257                }
258            }
259        }
260        RootCommands::GetNetworkSpace {
261            older_block_header_hash,
262            newer_block_header_hash,
263        } => {
264            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
265            let results = client
266                .get_network_space(&older_block_header_hash, &newer_block_header_hash)
267                .await?;
268            match serde_json::to_string_pretty(&results) {
269                Ok(json) => {
270                    info!("{json}");
271                }
272                Err(e) => {
273                    error!("Failed to convert value to JSON: {e:?}");
274                }
275            }
276        }
277        RootCommands::GetNetworkSpaceaByHeight { start, end } => {
278            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
279            let results = client.get_network_space_by_height(start, end).await?;
280            match serde_json::to_string_pretty(&results) {
281                Ok(json) => {
282                    info!("{json}");
283                }
284                Err(e) => {
285                    error!("Failed to convert value to JSON: {e:?}");
286                }
287            }
288        }
289        RootCommands::GetAdditionsAndRemovals { header_hash } => {
290            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
291            let results = client.get_additions_and_removals(&header_hash).await?;
292            match serde_json::to_string_pretty(&results) {
293                Ok(json) => {
294                    info!("{json}");
295                }
296                Err(e) => {
297                    error!("Failed to convert value to JSON: {e:?}");
298                }
299            }
300        }
301        RootCommands::GetInitialFreezePeriod => {
302            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
303            let results = client.get_initial_freeze_period().await?;
304            match serde_json::to_string_pretty(&results) {
305                Ok(json) => {
306                    info!("{json}");
307                }
308                Err(e) => {
309                    error!("Failed to convert value to JSON: {e:?}");
310                }
311            }
312        }
313        RootCommands::GetNetworkInfo => {
314            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
315            let results = client.get_network_info().await?;
316            match serde_json::to_string_pretty(&results) {
317                Ok(json) => {
318                    info!("{json}");
319                }
320                Err(e) => {
321                    error!("Failed to convert value to JSON: {e:?}");
322                }
323            }
324        }
325        RootCommands::GetSignagePointOrEOS {
326            sp_hash,
327            challenge_hash,
328        } => {
329            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
330            let results = client
331                .get_recent_signage_point_or_eos(sp_hash.as_ref(), challenge_hash.as_ref())
332                .await?;
333            match serde_json::to_string_pretty(&results) {
334                Ok(json) => {
335                    info!("{json}");
336                }
337                Err(e) => {
338                    error!("Failed to convert value to JSON: {e:?}");
339                }
340            }
341        }
342        RootCommands::GetCoinRecords {
343            puzzle_hashes,
344            include_spent_coins,
345            start_height,
346            end_height,
347        } => {
348            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
349            let results = client
350                .get_coin_records_by_puzzle_hashes(
351                    &puzzle_hashes,
352                    include_spent_coins,
353                    start_height,
354                    end_height,
355                )
356                .await?;
357            match serde_json::to_string_pretty(&results) {
358                Ok(json) => {
359                    info!("{json}");
360                }
361                Err(e) => {
362                    error!("Failed to convert value to JSON: {e:?}");
363                }
364            }
365        }
366        RootCommands::GetCoinRecordByName { name } => {
367            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
368            let results = client.get_coin_record_by_name(&name).await?;
369            match serde_json::to_string_pretty(&results) {
370                Ok(json) => {
371                    info!("{json}");
372                }
373                Err(e) => {
374                    error!("Failed to convert value to JSON: {e:?}");
375                }
376            }
377        }
378        RootCommands::GetCoinRecordsByNames {
379            names,
380            include_spent_coins,
381            start_height,
382            end_height,
383        } => {
384            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
385            let results = client
386                .get_coin_records_by_names(
387                    &names,
388                    Some(include_spent_coins),
389                    Some(start_height),
390                    Some(end_height),
391                )
392                .await?;
393            match serde_json::to_string_pretty(&results) {
394                Ok(json) => {
395                    info!("{json}");
396                }
397                Err(e) => {
398                    error!("Failed to convert value to JSON: {e:?}");
399                }
400            }
401        }
402        RootCommands::GetCoinRecordsByParentIds {
403            parent_ids,
404            include_spent_coins,
405            start_height,
406            end_height,
407        } => {
408            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
409            let results = client
410                .get_coin_records_by_parent_ids(
411                    &parent_ids,
412                    Some(include_spent_coins),
413                    Some(start_height),
414                    Some(end_height),
415                )
416                .await?;
417            match serde_json::to_string_pretty(&results) {
418                Ok(json) => {
419                    info!("{json}");
420                }
421                Err(e) => {
422                    error!("Failed to convert value to JSON: {e:?}");
423                }
424            }
425        }
426        RootCommands::GetCoinRecordsByhint {
427            hint,
428            include_spent_coins,
429            start_height,
430            end_height,
431        } => {
432            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
433            let results = client
434                .get_coin_records_by_hint(
435                    &hint,
436                    Some(include_spent_coins),
437                    Some(start_height),
438                    Some(end_height),
439                )
440                .await?;
441            match serde_json::to_string_pretty(&results) {
442                Ok(json) => {
443                    info!("{json}");
444                }
445                Err(e) => {
446                    error!("Failed to convert value to JSON: {e:?}");
447                }
448            }
449        }
450        RootCommands::GetPuzzleAndSolution { coin_id, height } => {
451            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
452            let results = client.get_puzzle_and_solution(&coin_id, height).await?;
453            match serde_json::to_string_pretty(&results) {
454                Ok(json) => {
455                    info!("{json}");
456                }
457                Err(e) => {
458                    error!("Failed to convert value to JSON: {e:?}");
459                }
460            }
461        }
462        RootCommands::GetCoinSpend { coin_id, height } => {
463            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
464            let results = client.get_puzzle_and_solution(&coin_id, height).await?;
465            match serde_json::to_string_pretty(&results) {
466                Ok(json) => {
467                    info!("{json}");
468                }
469                Err(e) => {
470                    error!("Failed to convert value to JSON: {e:?}");
471                }
472            }
473        }
474        RootCommands::GetAllMempoolTxIds => {
475            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
476            let results = client.get_all_mempool_tx_ids().await?;
477            match serde_json::to_string_pretty(&results) {
478                Ok(json) => {
479                    info!("{json}");
480                }
481                Err(e) => {
482                    error!("Failed to convert value to JSON: {e:?}");
483                }
484            }
485        }
486        RootCommands::GetAllMempoolItems => {
487            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
488            let results = client.get_all_mempool_items().await?;
489            match serde_json::to_string_pretty(&results) {
490                Ok(json) => {
491                    info!("{json}");
492                }
493                Err(e) => {
494                    error!("Failed to convert value to JSON: {e:?}");
495                }
496            }
497        }
498        RootCommands::GetMempoolItemByTxID { tx_id } => {
499            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
500            let results = client.get_mempool_item_by_tx_id(&tx_id).await?;
501            match serde_json::to_string_pretty(&results) {
502                Ok(json) => {
503                    info!("{json}");
504                }
505                Err(e) => {
506                    error!("Failed to convert value to JSON: {e:?}");
507                }
508            }
509        }
510        RootCommands::GetMempoolItemByName { coin_name } => {
511            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
512            let results = client.get_mempool_items_by_coin_name(&coin_name).await?;
513            match serde_json::to_string_pretty(&results) {
514                Ok(json) => {
515                    info!("{json}");
516                }
517                Err(e) => {
518                    error!("Failed to convert value to JSON: {e:?}");
519                }
520            }
521        }
522        RootCommands::GetFeeEstimate {
523            cost,
524            spend_bundle,
525            spend_type,
526            target_times,
527        } => {
528            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
529            let results = client
530                .get_fee_estimate(
531                    cost,
532                    spend_bundle.map(|s| {
533                        if s.starts_with("0x") {
534                            let mut cur = Cursor::new(
535                                decode(s).expect("String is not valid SpendBundle Hex"),
536                            );
537                            SpendBundle::from_bytes(&mut cur, ChiaProtocolVersion::default())
538                                .expect("String is not valid SpendBundle Hex")
539                        } else {
540                            serde_json::from_str(&s).expect("String is not a valid SpendBundle")
541                        }
542                    }),
543                    spend_type,
544                    &target_times,
545                )
546                .await?;
547            match serde_json::to_string_pretty(&results) {
548                Ok(json) => {
549                    info!("{json}");
550                }
551                Err(e) => {
552                    error!("Failed to convert value to JSON: {e:?}");
553                }
554            }
555        }
556        //End Fullnode API, Start of Extended Fullnode API
557        RootCommands::GetSingletonByLauncherId { launcher_id } => {
558            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
559            let results = client.get_singleton_by_launcher_id(&launcher_id).await?;
560            match serde_json::to_string_pretty(&results) {
561                Ok(json) => {
562                    info!("{json}");
563                }
564                Err(e) => {
565                    error!("Failed to convert value to JSON: {e:?}");
566                }
567            }
568        }
569        RootCommands::GetAdditionsAndRemovalsWithHints { header_hash } => {
570            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
571            let results = client
572                .get_additions_and_removals_with_hints(&header_hash)
573                .await?;
574            match serde_json::to_string_pretty(&results) {
575                Ok(json) => {
576                    info!("{json}");
577                }
578                Err(e) => {
579                    error!("Failed to convert value to JSON: {e:?}");
580                }
581            }
582        }
583        RootCommands::GetCoinRecordsByHints {
584            hints,
585            include_spent_coins,
586            start_height,
587            end_height,
588        } => {
589            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
590            let results = client
591                .get_coin_records_by_hints(
592                    &hints,
593                    Some(include_spent_coins),
594                    Some(start_height),
595                    Some(end_height),
596                )
597                .await?;
598            match serde_json::to_string_pretty(&results) {
599                Ok(json) => {
600                    info!("{json}");
601                }
602                Err(e) => {
603                    error!("Failed to convert value to JSON: {e:?}");
604                }
605            }
606        }
607        RootCommands::GetCoinRecordsByHintsPaginated {
608            hints,
609            include_spent_coins,
610            start_height,
611            end_height,
612            page_size,
613            last_id,
614        } => {
615            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
616            let results = client
617                .get_coin_records_by_hints_paginated(
618                    &hints,
619                    include_spent_coins,
620                    start_height,
621                    end_height,
622                    page_size,
623                    last_id,
624                )
625                .await?;
626            match serde_json::to_string_pretty(&results) {
627                Ok(json) => {
628                    info!("{json}");
629                }
630                Err(e) => {
631                    error!("Failed to convert value to JSON: {e:?}");
632                }
633            }
634        }
635        RootCommands::GetCoinRecordsByPuzzleHashesPaginated {
636            puzzle_hashes,
637            include_spent_coins,
638            start_height,
639            end_height,
640            page_size,
641            last_id,
642        } => {
643            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
644            let results = client
645                .get_coin_records_by_puzzle_hashes_paginated(
646                    &puzzle_hashes,
647                    include_spent_coins,
648                    start_height,
649                    end_height,
650                    page_size,
651                    last_id,
652                )
653                .await?;
654            match serde_json::to_string_pretty(&results) {
655                Ok(json) => {
656                    info!("{json}");
657                }
658                Err(e) => {
659                    error!("Failed to convert value to JSON: {e:?}");
660                }
661            }
662        }
663        RootCommands::GetHintsByCoinIds { coin_ids } => {
664            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
665            let results = client.get_hints_by_coin_ids(&coin_ids).await?;
666            match serde_json::to_string_pretty(&results) {
667                Ok(json) => {
668                    info!("{json}");
669                }
670                Err(e) => {
671                    error!("Failed to convert value to JSON: {e:?}");
672                }
673            }
674        }
675        RootCommands::GetPuzzleAndSoultionsByNames {
676            names,
677            include_spent_coins,
678            start_height,
679            end_height,
680        } => {
681            let client = FullnodeClient::new(&host, port, timeout, ssl, &None)?;
682            let results = client
683                .get_puzzles_and_solutions_by_names(
684                    &names,
685                    include_spent_coins,
686                    start_height,
687                    end_height,
688                )
689                .await?;
690            match serde_json::to_string_pretty(&results) {
691                Ok(json) => {
692                    info!("{json}");
693                }
694                Err(e) => {
695                    error!("Failed to convert value to JSON: {e:?}");
696                }
697            }
698        }
699        //End Extended Fullnode API
700        RootCommands::MovePlotNFT {
701            target_pool,
702            launcher_id,
703            target_address,
704            mnemonic,
705            fee,
706        } => {
707            let client = Arc::new(FullnodeClient::new(&host, port, timeout, ssl, &None)?);
708            migrate_plot_nft(
709                client,
710                &target_pool,
711                launcher_id,
712                target_address,
713                &mnemonic,
714                constants.clone(),
715                fee.unwrap_or_default(),
716            )
717            .await?;
718        }
719        RootCommands::MovePlotNFTWithOwnerKey {
720            target_pool,
721            launcher_id,
722            target_address,
723            owner_key,
724        } => {
725            let client = Arc::new(FullnodeClient::new(&host, port, timeout, ssl, &None)?);
726            let owner_key = SecretKey::from_bytes(Bytes32::from_str(&owner_key)?.as_ref())
727                .expect("Failed to Parse Owner Secret Key");
728            migrate_plot_nft_with_owner_key(
729                client,
730                &target_pool,
731                launcher_id,
732                target_address,
733                &owner_key,
734            )
735            .await?;
736        }
737        RootCommands::GetPlotnftState { launcher_id } => {
738            let client = Arc::new(FullnodeClient::new(&host, port, timeout, ssl, &None)?);
739            get_plotnft_ready_state(client, launcher_id, None)
740                .await
741                .map(|_| ())?;
742        }
743        RootCommands::CreatePoolLoginLink {
744            target_pool,
745            launcher_id,
746            auth_key,
747        } => {
748            let url =
749                create_pool_login_url(&target_pool, &[(auth_key.into(), launcher_id)]).await?;
750            println!("{url}");
751        }
752        RootCommands::CreateWallet { action } => match action {
753            WalletAction::WithNFT { .. } => {}
754            WalletAction::Cold => create_cold_wallet()?,
755        },
756        RootCommands::Curry {
757            program,
758            args,
759            output,
760        } => {
761            let prog_as_path = Path::new(&program);
762            let asrg_as_path = Path::new(&args);
763            let program = if prog_as_path.exists() {
764                SerializedProgram::from_file(prog_as_path)
765                    .await?
766                    .to_program()
767            } else if is_hex(program.as_bytes()) {
768                SerializedProgram::from_bytes(program.as_bytes()).to_program()
769            } else {
770                assemble_text(&program)?.to_program()
771            };
772            let args = if asrg_as_path.exists() {
773                SerializedProgram::from_file(asrg_as_path)
774                    .await?
775                    .to_program()
776            } else if is_hex(args.as_bytes()) {
777                SerializedProgram::from_bytes(args.as_bytes()).to_program()
778            } else {
779                assemble_text(&args)?.to_program()
780            };
781            let curried_program = program.curry(&args.as_list())?;
782            match output.unwrap_or_default() {
783                ProgramOutput::Hex => {
784                    println!("{}", encode(&curried_program.serialized))
785                }
786                ProgramOutput::String => {
787                    println!("{curried_program}")
788                }
789            }
790        }
791        RootCommands::Run {
792            program,
793            args,
794            output,
795        } => {
796            let prog_as_path = Path::new(&program);
797            let asrg_as_path = Path::new(&args);
798            let program = if prog_as_path.exists() {
799                SerializedProgram::from_file(prog_as_path)
800                    .await?
801                    .to_program()
802            } else if is_hex(program.as_bytes()) {
803                SerializedProgram::from_bytes(program.as_bytes()).to_program()
804            } else {
805                assemble_text(&program)?.to_program()
806            };
807            let args = if asrg_as_path.exists() {
808                SerializedProgram::from_file(asrg_as_path)
809                    .await?
810                    .to_program()
811            } else if is_hex(args.as_bytes()) {
812                SerializedProgram::from_bytes(args.as_bytes()).to_program()
813            } else {
814                assemble_text(&args)?.to_program()
815            };
816            let (_cost, program_output) = program.run(INFINITE_COST, 0, &args)?;
817            match output.unwrap_or_default() {
818                ProgramOutput::Hex => {
819                    println!("{}", encode(&program_output.serialized))
820                }
821                ProgramOutput::String => {
822                    println!("{program_output}")
823                }
824            }
825        }
826    }
827    Ok(())
828}