Skip to content

Commit

Permalink
More refactoring and docs (#86)
Browse files Browse the repository at this point in the history
* print_address as info log

* Docs pages

* Change package name and version

* Build from source doc

* Add Rust installation

* Reorder files

* Init doc

* Reorganize account manager creation

* Make command optional

* Change account prompt

* Clean up account args matching

* Print error on account not found

* Simplify main match

* Remove change address boolean from logs
  • Loading branch information
thibault-martinez authored May 25, 2022
1 parent 595d609 commit 64bc616
Show file tree
Hide file tree
Showing 12 changed files with 186 additions and 136 deletions.
30 changes: 15 additions & 15 deletions Cargo.lock

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

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "stardust-cli-wallet"
version = "0.1.0-alpha"
name = "cli-wallet"
version = "1.0.0-alpha.1"
authors = [ "IOTA Stiftung" ]
edition = "2021"
homepage = "https://iota.org"
Expand Down
1 change: 1 addition & 0 deletions documentation/docs/00_introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The `cli-wallet` is a stateful Command Line Interface wallet that allows you to interact with the ledger.
17 changes: 17 additions & 0 deletions documentation/docs/01_installation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# From release

# From source

## 1. Install Rust

https://www.rust-lang.org/tools/install

## 2. Compile

```sh
git clone [email protected]:iotaledger/cli-wallet.git -b develop
cd cli-wallet
cargo build --profile production
```

Resulting binary will be located at `./target/production/wallet`.
65 changes: 65 additions & 0 deletions documentation/docs/02_account_manager.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
The account manager interface allows you to:
- Initialise the wallet with a mnemonic;
- Create new accounts;
- Select the account to use;
- Synchronise the accounts;

# Commands

## `help`

### Parameters

### Example(s)

## `init`

The wallet can only be initialised once.

### Parameters

| Name | Optional | Default |Example |
| ----------- | --------- | ------------------------- |--------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mnemonic` || Randomly generated | "aunt middle impose faith ramp kid olive good practice motor grab ready group episode oven matrix silver rhythm avocado assume humble tiger shiver hurt" |
| `node` || "http://localhost:14265/" | "http://localhost:14265/" |

### Example(s)

Initialise the wallet with a randomly generated mnemonic and the default node.
```sh
./wallet init
```

Initialise the wallet with a given mnemonic and the default node.
```sh
./wallet init --mnemonic "aunt middle impose faith ramp kid olive good practice motor grab ready group episode oven matrix silver rhythm avocado assume humble tiger shiver hurt"
```

Initialise the wallet with a a randomly generated mnemonic and a given node.
```sh
./wallet init --node "http://localhost:14265/"
```

## `new`

### Parameters

### Example(s)

## `select`

### Parameters

### Example(s)

## `set-node`

### Parameters

### Example(s)

## `sync`

### Parameters

### Example(s)
Empty file.
62 changes: 51 additions & 11 deletions src/account_manager.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,65 @@
// Copyright 2020-2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use iota_wallet::account_manager::AccountManager;
use std::env::var_os;

use clap::Parser;
use iota_wallet::{
account_manager::AccountManager,
secret::{stronghold::StrongholdSecretManager, SecretManager},
ClientOptions,
};

use crate::{
command::account_manager::{
init_command, new_command, select_command, set_node_command, sync_command, AccountManagerCli,
AccountManagerCommand,
},
error::Error,
helper::get_password,
};

pub async fn match_account_manager_command(
account_manager: &AccountManager,
account_manager_cli: AccountManagerCli,
) -> Result<(), Error> {
match account_manager_cli.command {
AccountManagerCommand::Init(mnemonic_url) => init_command(account_manager, mnemonic_url).await,
AccountManagerCommand::New { alias } => new_command(account_manager, alias).await,
AccountManagerCommand::Select { identifier } => select_command(account_manager, identifier).await,
AccountManagerCommand::SetNode { url } => set_node_command(account_manager, url).await,
AccountManagerCommand::Sync => sync_command(account_manager).await,
pub async fn new_account_manager() -> Result<(AccountManager, Option<String>), Error> {
let storage_path = var_os("WALLET_DATABASE_PATH")
.map(|os_str| os_str.into_string().expect("invalid WALLET_DATABASE_PATH"))
.unwrap_or_else(|| "./stardust-cli-wallet-db".to_string());
let stronghold_path = std::path::Path::new("./stardust-cli-wallet.stronghold");

match AccountManagerCli::try_parse() {
Ok(account_manager_cli) => {
let password = get_password(stronghold_path)?;
let secret_manager = SecretManager::Stronghold(
StrongholdSecretManager::builder()
.password(&password)
.snapshot_path(stronghold_path.to_path_buf())
.build(),
);
let account_manager = AccountManager::builder()
.with_secret_manager(secret_manager)
.with_client_options(
ClientOptions::new()
.with_node("http://localhost:14265")?
.with_node_sync_disabled(),
)
.with_storage_path(&storage_path)
.finish()
.await?;

if let Some(command) = account_manager_cli.command {
match command {
AccountManagerCommand::Init(mnemonic_url) => init_command(&account_manager, mnemonic_url).await,
AccountManagerCommand::New { alias } => new_command(&account_manager, alias).await,
AccountManagerCommand::Select { identifier } => select_command(&account_manager, identifier).await,
AccountManagerCommand::SetNode { url } => set_node_command(&account_manager, url).await,
AccountManagerCommand::Sync => sync_command(&account_manager).await,
}?;
}

Ok((account_manager, account_manager_cli.account))
}
Err(e) => {
println!("{e}");
Err(Error::Help)
}
}
}
12 changes: 7 additions & 5 deletions src/command/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,21 +294,23 @@ fn print_transaction(transaction: &Transaction) {
}

pub async fn print_address(account_handle: &AccountHandle, address: &AccountAddress) -> Result<(), Error> {
println!("ADDRESS {:?}", address.address().to_bech32());
println!("--- Index: {}", address.key_index());
let mut log = format!("Address {}: {}\n", address.key_index(), address.address().to_bech32());

if *address.internal() {
println!("--- Change address: {}", address.internal());
log = format!("{log}Change address");
}

let addresses_with_balance = account_handle.list_addresses_with_unspent_outputs().await?;

if let Ok(index) = addresses_with_balance.binary_search_by_key(&(address.key_index(), address.internal()), |a| {
(a.key_index(), a.internal())
}) {
println!("--- Address balance: {}", addresses_with_balance[index].amount());
println!("--- Address outputs: {:#?}", addresses_with_balance[index].output_ids());
log = format!("{log}Balance: {}\n", addresses_with_balance[index].amount());
log = format!("{log}Outputs: {:#?}", addresses_with_balance[index].output_ids());
}

log::info!("{log}");

Ok(())
}

Expand Down
5 changes: 3 additions & 2 deletions src/command/account_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ use crate::{account::account_prompt, error::Error};
#[clap(propagate_version = true)]
pub struct AccountManagerCli {
#[clap(subcommand)]
pub command: AccountManagerCommand,
pub command: Option<AccountManagerCommand>,
pub account: Option<String>,
}

#[derive(Debug, Subcommand)]
Expand Down Expand Up @@ -54,7 +55,7 @@ pub async fn init_command(manager: &AccountManager, mnemonic_url: MnemonicAndUrl
};
log::info!("IMPORTANT: write this mnemonic phrase in a safe place.");
log::info!(
"It is the only way to recover your account if you ever forget your password/lose the .stronghold file."
"It is the only way to recover your account if you ever forget your password and/or lose the .stronghold file."
);
log::info!("{mnemonic}");

Expand Down
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ pub enum Error {
Block(#[from] BlockError),
#[error("client error: {0}")]
Client(#[from] ClientError),
#[error("help command")]
Help,
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("logger error: {0}")]
Expand Down
32 changes: 8 additions & 24 deletions src/helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@

use std::path::Path;

use clap::Parser;
use dialoguer::{console::Term, theme::ColorfulTheme, Password, Select};
use iota_wallet::account::AccountHandle;
use iota_wallet::account_manager::AccountManager;

use crate::{error::Error, AccountManagerCli};
use crate::error::Error;

pub fn get_password(path: &Path) -> Result<String, Error> {
let mut prompt = Password::new();
Expand All @@ -22,34 +21,19 @@ pub fn get_password(path: &Path) -> Result<String, Error> {
Ok(prompt.interact()?)
}

pub async fn pick_account(accounts: Vec<AccountHandle>) -> Option<usize> {
pub async fn pick_account(manager: &AccountManager) -> Result<u32, Error> {
let accounts = manager.get_accounts().await?;
let mut items = Vec::new();

for account_handle in accounts {
items.push(account_handle.read().await.alias().clone());
}

Select::with_theme(&ColorfulTheme::default())
.with_prompt("Select an account to manipulate")
let index = Select::with_theme(&ColorfulTheme::default())
.with_prompt("Select an account:")
.items(&items)
.default(0)
.interact_on_opt(&Term::stderr())
.unwrap_or_default()
}
.interact_on(&Term::stderr())?;

pub fn help_command() {
if let Err(r) = AccountManagerCli::try_parse() {
// If only one argument from the user is provided, try to use it as identifier.
let mut iter = std::env::args();

// The first element is the path of the executable.
iter.next();
if let Some(input) = iter.next() {
if input == "help" {
// this prints the help output
r.print().expect("Error writing Error");
std::process::exit(0);
}
}
}
Ok(index as u32)
}
Loading

0 comments on commit 64bc616

Please sign in to comment.