Skip to content

Commit

Permalink
feat: fully implemented wallet create & balance
Browse files Browse the repository at this point in the history
  • Loading branch information
mickvandijke committed Oct 29, 2024
1 parent af3c022 commit 1704191
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 38 deletions.
60 changes: 57 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions autonomi-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ autonomi = { path = "../autonomi", version = "0.2.1", features = [
] }
clap = { version = "4.2.1", features = ["derive"] }
color-eyre = "~0.6"
const-hex = "1.13.1"
dirs-next = "~2.0.0"
prettytable = "0.10.0"
thiserror = "1.0"
indicatif = { version = "0.17.5", features = ["tokio"] }
rand = { version = "~0.8.5", features = ["small_rng"] }
Expand Down
2 changes: 1 addition & 1 deletion autonomi-cli/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ pub async fn handle_subcommand(opt: Opt) -> Result<()> {
private_key,
password,
} => wallet::create(no_password, private_key, password),
WalletCmd::Balance => Ok(wallet::balance()?),
WalletCmd::Balance => Ok(wallet::balance().await?),
},
}
}
28 changes: 25 additions & 3 deletions autonomi-cli/src/commands/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
// KIND, either express or implied. Please review the Licences for the specific language governing
// permissions and limitations relating to use of the SAFE Network Software.

use crate::wallet::fs::{get_client_wallet_dir_path, store_private_key};
use crate::wallet::fs::{select_wallet, store_private_key};
use crate::wallet::input::request_password;
use crate::wallet::DUMMY_NETWORK;
use autonomi::Wallet;
use color_eyre::eyre::eyre;
use color_eyre::Result;
use color_eyre::{Result, SectionExt};
use prettytable::{Cell, Row, Table};

const WALLET_PASSWORD_REQUIRED: bool = false;

Expand Down Expand Up @@ -58,6 +59,27 @@ pub fn create(
Ok(())
}

pub fn balance() -> Result<()> {
pub async fn balance() -> Result<()> {
let wallet = select_wallet()?;

let token_balance = wallet.balance_of_tokens().await?;
let gas_balance = wallet.balance_of_gas_tokens().await?;

println!("Wallet balances: {}", wallet.address());

let mut table = Table::new();

table.add_row(Row::new(vec![
Cell::new("Token Balance"),
Cell::new(&token_balance.to_string()),
]));

table.add_row(Row::new(vec![
Cell::new("Gas Balance"),
Cell::new(&gas_balance.to_string()),
]));

table.printstd();

Ok(())
}
40 changes: 40 additions & 0 deletions autonomi-cli/src/wallet/encryption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,43 @@ pub fn decrypt_private_key(encrypted_data: &str, password: &str) -> Result<Strin
// Create secret key from decrypted byte
Ok(String::from_utf8(private_key_bytes.to_vec()).expect("not able to convert private key"))
}

#[cfg(test)]
mod tests {
use super::*;
use autonomi::Wallet;

#[test]
fn test_encrypt_decrypt_private_key() {
let key = Wallet::random_private_key();
let password = "password123".to_string();

let encrypted_key =
encrypt_private_key(&key, &password).expect("Failed to encrypt the private key");

let decrypted_key = decrypt_private_key(&encrypted_key, &password)
.expect("Failed to decrypt the private key");

assert_eq!(
decrypted_key, key,
"Decrypted key does not match the original private key"
);
}

#[test]
fn test_wrong_password() {
let key = Wallet::random_private_key();
let password = "password123".to_string();

let encrypted_key =
encrypt_private_key(&key, &password).expect("Failed to encrypt the private key");

let wrong_password = "password456".to_string();
let result = decrypt_private_key(&encrypted_key, &wrong_password);

assert!(
result.is_err(),
"Decryption should not succeed with a wrong password"
);
}
}
93 changes: 68 additions & 25 deletions autonomi-cli/src/wallet/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ use crate::wallet::encryption::{decrypt_private_key, encrypt_private_key};
use crate::wallet::error::Error;
use crate::wallet::input::{get_password_input, get_wallet_selection_input};
use crate::wallet::DUMMY_NETWORK;
use autonomi::Wallet;
use autonomi::{get_evm_network_from_env, RewardsAddress, Wallet};
use const_hex::traits::FromHex;
use prettytable::{Cell, Row, Table};
use std::ffi::OsString;
use std::io::Read;
use std::path::PathBuf;
Expand Down Expand Up @@ -41,13 +43,17 @@ pub(crate) fn store_private_key(
let encrypted_key = encrypt_private_key(private_key, password)?;
let file_name = format!("{wallet_address}{ENCRYPTED_PRIVATE_KEY_EXT}");
let file_path = wallets_folder.join(file_name);

std::fs::write(file_path.clone(), encrypted_key)
.map_err(|err| Error::FailedToStorePrivateKey(err.to_string()))?;

Ok(file_path.into_os_string())
} else {
let file_path = wallets_folder.join(wallet_address);

std::fs::write(file_path.clone(), private_key)
.map_err(|err| Error::FailedToStorePrivateKey(err.to_string()))?;

Ok(file_path.into_os_string())
}
}
Expand Down Expand Up @@ -88,44 +94,81 @@ pub(crate) fn load_private_key(wallet_address: &str) -> Result<String, Error> {
}
}

pub(crate) fn load_wallet_from_address(wallet_address: &str) -> Result<Wallet, Error> {
let network = get_evm_network_from_env().expect("Could not load EVM network from environment");
let private_key = load_private_key(wallet_address)?;
let wallet =
Wallet::new_from_private_key(network, &private_key).expect("Could not initialize wallet");
Ok(wallet)
}

/// Lists all wallet files together with an index and let the user select one.
/// If only one wallet exists, auto select it.
pub(crate) fn select_wallet() -> Result<String, Error> {
pub(crate) fn select_wallet() -> Result<Wallet, Error> {
let wallets_folder = get_client_wallet_dir_path()?;
let wallet_files = get_wallet_files(&wallets_folder)?;

match wallet_files.len() {
0 => Err(Error::NoWalletsFound),
1 => Ok(wallet_files[0].to_string_lossy().into_owned()),
_ => {
println!("Select a wallet:");

for (index, wallet_file) in wallet_files.iter().enumerate() {
println!("{}: {}", index + 1, wallet_file.display());
}

let selected_index = get_wallet_selection_input()
.parse::<usize>()
.map_err(|_| Error::InvalidSelection)?;

if selected_index >= 1 && selected_index <= wallet_files.len() {
Ok(wallet_files[selected_index - 1]
.to_string_lossy()
.into_owned())
} else {
Err(Error::InvalidSelection)
}
}
1 => load_wallet_from_address(&filter_wallet_file_extension(&wallet_files[0])),
_ => get_wallet_selection(wallet_files),
}
}

fn get_wallet_selection(wallet_files: Vec<String>) -> Result<Wallet, Error> {
list_wallets(&wallet_files);

let selected_index = get_wallet_selection_input("Select by index:")
.parse::<usize>()
.map_err(|_| Error::InvalidSelection)?;

if selected_index < 1 || selected_index > wallet_files.len() {
return Err(Error::InvalidSelection);
}

load_wallet_from_address(&filter_wallet_file_extension(
&wallet_files[selected_index - 1],
))
}

fn get_wallet_files(wallets_folder: &PathBuf) -> Result<Vec<PathBuf>, Error> {
fn list_wallets(wallet_files: &[String]) {
println!("Wallets:");

let mut table = Table::new();

table.add_row(Row::new(vec![
Cell::new("Index"),
Cell::new("Address"),
Cell::new("Encrypted"),
]));

for (index, wallet_file) in wallet_files.iter().enumerate() {
let encrypted = wallet_file.contains(ENCRYPTED_PRIVATE_KEY_EXT);

table.add_row(Row::new(vec![
Cell::new(&(index + 1).to_string()),
Cell::new(&filter_wallet_file_extension(wallet_file)),
Cell::new(&encrypted.to_string()),
]));
}

table.printstd();
}

fn get_wallet_files(wallets_folder: &PathBuf) -> Result<Vec<String>, Error> {
let wallet_files = std::fs::read_dir(wallets_folder)
.map_err(|_| Error::WalletsFolderNotFound)?
.filter_map(Result::ok)
.map(|dir_entry| dir_entry.path())
.filter(|path| path.is_file())
.filter_map(|dir_entry| dir_entry.file_name().into_string().ok())
.filter(|file_name| {
let cleaned_file_name = filter_wallet_file_extension(file_name);
RewardsAddress::from_hex(cleaned_file_name).is_ok()
})
.collect::<Vec<_>>();

Ok(wallet_files)
}

fn filter_wallet_file_extension(wallet_file: &str) -> String {
wallet_file.replace(ENCRYPTED_PRIVATE_KEY_EXT, "")
}
17 changes: 11 additions & 6 deletions autonomi-cli/src/wallet/input.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
pub(crate) fn get_wallet_selection_input() -> String {
let mut input = String::new();
pub(crate) fn get_wallet_selection_input(prompt: &str) -> String {
println!("{prompt}");

std::io::stdin()
.read_line(&mut input)
.expect("Invalid selection");
let mut buffer = String::new();
let stdin = std::io::stdin();

input.trim().to_string()
if stdin.read_line(&mut buffer).is_err() {
// consider if error should process::exit(1) here
return "".to_string();
};

// Remove leading and trailing whitespace
buffer.trim().to_owned()
}

pub(crate) fn get_password_input(prompt: &str) -> String {
Expand Down

0 comments on commit 1704191

Please sign in to comment.