1use crate::consts::{
2 DB_FOLDER, IGNITION_CONFIG_FOLDER_NAME, LOCAL_CONFIG_FOLDER_NAME, TESTNET_CONFIG_FOLDER_NAME,
3};
4use anyhow::{anyhow, Result};
5use dialoguer::{theme::ColorfulTheme, Confirm, Input, Password};
6use forc_util::user_forc_directory;
7use fuel_crypto::{
8 rand::{prelude::StdRng, SeedableRng},
9 SecretKey,
10};
11use libp2p_identity::{secp256k1, Keypair, PeerId};
12use semver::Version;
13use serde::{Deserialize, Serialize};
14use std::{
15 fmt::Display,
16 path::PathBuf,
17 process::{Command, Stdio},
18};
19use std::{
20 io::{Read, Write},
21 ops::Deref,
22};
23
24pub enum DbConfig {
25 Local,
26 Testnet,
27 Ignition,
28}
29
30#[derive(Serialize, Deserialize, Debug)]
31pub struct KeyPair {
32 pub peer_id: String,
33 pub secret: String,
34}
35
36pub struct HumanReadableCommand<'a>(&'a Command);
50
51impl From<DbConfig> for PathBuf {
52 fn from(value: DbConfig) -> Self {
53 let user_db_dir = user_forc_directory().join(DB_FOLDER);
54 match value {
55 DbConfig::Local => user_db_dir.join(LOCAL_CONFIG_FOLDER_NAME),
56 DbConfig::Testnet => user_db_dir.join(TESTNET_CONFIG_FOLDER_NAME),
57 DbConfig::Ignition => user_db_dir.join(IGNITION_CONFIG_FOLDER_NAME),
58 }
59 }
60}
61
62impl Display for HumanReadableCommand<'_> {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 let dbg_out = format!("{:?}", self.0);
65 let parsed = dbg_out
67 .replace("\" \"", " ") .replace("\"", ""); write!(f, "{parsed}")
70 }
71}
72
73impl<'a> From<&'a Command> for HumanReadableCommand<'a> {
74 fn from(value: &'a Command) -> Self {
75 Self(value)
76 }
77}
78
79impl KeyPair {
80 pub fn random() -> Self {
81 let mut rng = StdRng::from_entropy();
82 let secret = SecretKey::random(&mut rng);
83
84 let mut bytes = *secret.deref();
85 let p2p_secret = secp256k1::SecretKey::try_from_bytes(&mut bytes)
86 .expect("Should be a valid private key");
87 let p2p_keypair = secp256k1::Keypair::from(p2p_secret);
88 let libp2p_keypair = Keypair::from(p2p_keypair);
89 let peer_id = PeerId::from_public_key(&libp2p_keypair.public());
90 Self {
91 peer_id: format!("{peer_id}"),
92 secret: format!("{secret}"),
93 }
94 }
95}
96
97pub(crate) fn ask_user_yes_no_question(question: &str) -> anyhow::Result<bool> {
98 let answer = Confirm::with_theme(&ColorfulTheme::default())
99 .with_prompt(question)
100 .default(false)
101 .show_default(false)
102 .interact()?;
103 Ok(answer)
104}
105
106pub(crate) fn ask_user_discreetly(question: &str) -> anyhow::Result<String> {
107 let discrete = Password::with_theme(&ColorfulTheme::default())
108 .with_prompt(question)
109 .interact()?;
110 Ok(discrete)
111}
112
113pub(crate) fn ask_user_string(question: &str) -> anyhow::Result<String> {
114 let response = Input::with_theme(&ColorfulTheme::default())
115 .with_prompt(question)
116 .interact_text()?;
117 Ok(response)
118}
119
120pub(crate) fn display_string_discreetly(
122 discreet_string: &str,
123 continue_message: &str,
124) -> Result<()> {
125 use termion::screen::IntoAlternateScreen;
126 let mut screen = std::io::stdout().into_alternate_screen()?;
127 writeln!(screen, "{discreet_string}")?;
128 screen.flush()?;
129 println!("{continue_message}");
130 wait_for_keypress();
131 Ok(())
132}
133
134pub(crate) fn wait_for_keypress() {
135 let mut single_key = [0u8];
136 std::io::stdin().read_exact(&mut single_key).unwrap();
137}
138
139pub(crate) fn ask_user_keypair() -> Result<KeyPair> {
142 let has_keypair = ask_user_yes_no_question("Do you have a keypair in hand?")?;
143 if has_keypair {
144 let peer_id = ask_user_string("Peer Id:")?;
146 let secret = ask_user_discreetly("Secret:")?;
147 Ok(KeyPair { peer_id, secret })
148 } else {
149 println!("Generating new keypair...");
150 let pair = KeyPair::random();
151 display_string_discreetly(
152 &format!(
153 "Generated keypair:\n PeerID: {}, secret: {}",
154 pair.peer_id, pair.secret
155 ),
156 "### Do not share or lose this private key! Press any key to complete. ###",
157 )?;
158 Ok(pair)
159 }
160}
161
162pub(crate) fn get_fuel_core_version() -> anyhow::Result<Version> {
164 let version_cmd = Command::new("fuel-core")
165 .arg("--version")
166 .stdout(Stdio::piped())
167 .output()
168 .expect("failed to run fuel-core, make sure that it is installed.");
169
170 let version_output = String::from_utf8_lossy(&version_cmd.stdout).to_string();
171
172 let version = version_output
175 .split_whitespace()
176 .last()
177 .ok_or_else(|| anyhow!("fuel-core version parse failed"))?;
178 let version_semver = Version::parse(version)?;
179
180 Ok(version_semver)
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use std::process::Command;
187
188 #[test]
189 fn test_basic_command() {
190 let mut command = Command::new("fuel-core");
191 command.arg("run");
192 let human_readable = HumanReadableCommand(&command);
193 assert_eq!(format!("{human_readable}"), "fuel-core run");
194 }
195
196 #[test]
197 fn test_command_with_multiple_args() {
198 let mut command = Command::new("fuel-core");
199 command.arg("run");
200 command.arg("--config");
201 command.arg("config.toml");
202 let human_readable = HumanReadableCommand(&command);
203 assert_eq!(
204 format!("{human_readable}"),
205 "fuel-core run --config config.toml"
206 );
207 }
208
209 #[test]
210 fn test_command_no_args() {
211 let command = Command::new("fuel-core");
212 let human_readable = HumanReadableCommand(&command);
213 assert_eq!(format!("{human_readable}"), "fuel-core");
214 }
215
216 #[test]
217 fn test_command_with_path() {
218 let mut command = Command::new("fuel-core");
219 command.arg("--config");
220 command.arg("/path/to/config.toml");
221 let human_readable = HumanReadableCommand(&command);
222 assert_eq!(
223 format!("{human_readable}"),
224 "fuel-core --config /path/to/config.toml"
225 );
226 }
227}