Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): output selector #1419

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Security -->

## 1.2.0 - 2023-xx-xx

### Added

- `outputs` and `unspent_outputs` print the booked milestone timestamps and sort by them;
- `outputs` and `unspent_outputs` include spent/unspent information;
- `UTC` suffix to the formatted date of `transactions`;

### Changed

- `AccountCommand::Output` accepts either a list index or an `OutputId`;

### Fixed

- `transaction` and `transactions` indexed transactions in opposite order;

## 1.1.0 - 2023-09-29

### Added
Expand Down
2 changes: 1 addition & 1 deletion cli/src/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ pub async fn account_prompt_internal(
}
AccountCommand::NewAddress => new_address_command(account).await,
AccountCommand::NodeInfo => node_info_command(account).await,
AccountCommand::Output { output_id } => output_command(account, output_id).await,
AccountCommand::Output { selector } => output_command(account, selector).await,
AccountCommand::Outputs => outputs_command(account).await,
AccountCommand::Send {
address,
Expand Down
103 changes: 71 additions & 32 deletions cli/src/command/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use iota_sdk::{
},
wallet::{
account::{
types::{AccountAddress, AccountIdentifier},
types::{AccountAddress, AccountIdentifier, OutputData, Transaction},
Account, ConsolidationParams, OutputsToClaim, SyncOptions, TransactionOptions,
},
CreateNativeTokenParams, MintNftParams, SendNativeTokensParams, SendNftParams, SendParams,
Expand Down Expand Up @@ -157,8 +157,9 @@ pub enum AccountCommand {
NodeInfo,
/// Display an output.
Output {
/// Output ID to be displayed.
output_id: String,
/// Selector for output.
/// Either by ID (e.g. 0xbce525324af12eda02bf7927e92cea3a8e8322d0f41966271443e6c3b245a4400000) or index.
selector: OutputSelector,
},
/// List all outputs.
Outputs,
Expand Down Expand Up @@ -281,6 +282,25 @@ impl FromStr for TransactionSelector {
}
}

/// Select by output ID or list index
#[derive(Debug, Copy, Clone)]
pub enum OutputSelector {
Id(OutputId),
Index(usize),
}

impl FromStr for OutputSelector {
type Err = Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(if let Ok(index) = s.parse() {
Self::Index(index)
} else {
Self::Id(s.parse()?)
})
}
}

/// `addresses` command
pub async fn addresses_command(account: &Account) -> Result<(), Error> {
let addresses = account.addresses().await?;
Expand Down Expand Up @@ -644,8 +664,15 @@ pub async fn node_info_command(account: &Account) -> Result<(), Error> {
}

/// `output` command
pub async fn output_command(account: &Account, output_id: String) -> Result<(), Error> {
let output = account.get_output(&OutputId::from_str(&output_id)?).await;
pub async fn output_command(account: &Account, selector: OutputSelector) -> Result<(), Error> {
let output = match selector {
OutputSelector::Id(id) => account.get_output(&id).await,
OutputSelector::Index(index) => {
let mut outputs = account.outputs(None).await?;
DaughterOfMars marked this conversation as resolved.
Show resolved Hide resolved
outputs.sort_unstable_by(outputs_ordering);
outputs.into_iter().nth(index)
}
};

if let Some(output) = output {
println_log_info!("{output:#?}");
Expand All @@ -658,17 +685,7 @@ pub async fn output_command(account: &Account, output_id: String) -> Result<(),

/// `outputs` command
pub async fn outputs_command(account: &Account) -> Result<(), Error> {
let outputs = account.outputs(None).await?;

if outputs.is_empty() {
println_log_info!("No outputs found");
} else {
println_log_info!("Outputs:");
for (i, output_data) in outputs.into_iter().enumerate() {
println_log_info!("{}\t{}\t{}", i, &output_data.output_id, output_data.output.kind_str());
}
}
Ok(())
print_outputs(account.outputs(None).await?, "Outputs:").await
}

// `send` command
Expand Down Expand Up @@ -786,7 +803,7 @@ pub async fn transaction_command(account: &Account, selector: TransactionSelecto
let transaction = match selector {
TransactionSelector::Id(id) => transactions.into_iter().find(|tx| tx.transaction_id == id),
TransactionSelector::Index(index) => {
transactions.sort_by(|a, b| a.timestamp.cmp(&b.timestamp));
transactions.sort_unstable_by(transactions_ordering);
transactions.into_iter().nth(index)
}
};
Expand All @@ -803,17 +820,17 @@ pub async fn transaction_command(account: &Account, selector: TransactionSelecto
/// `transactions` command
pub async fn transactions_command(account: &Account, show_details: bool) -> Result<(), Error> {
let mut transactions = account.transactions().await;
transactions.sort_by(|a, b| a.timestamp.cmp(&b.timestamp));
transactions.sort_unstable_by(transactions_ordering);

if transactions.is_empty() {
println_log_info!("No transactions found");
} else {
for (i, tx) in transactions.into_iter().rev().enumerate() {
for (i, tx) in transactions.into_iter().enumerate() {
if show_details {
println_log_info!("{:#?}", tx);
} else {
let transaction_time = to_utc_date_time(tx.timestamp)?;
let formatted_time = transaction_time.format("%Y-%m-%d %H:%M:%S").to_string();
let formatted_time = transaction_time.format("%Y-%m-%d %H:%M:%S UTC").to_string();

println_log_info!("{:<5}{}\t{}", i, tx.transaction_id, formatted_time);
}
Expand All @@ -825,18 +842,7 @@ pub async fn transactions_command(account: &Account, show_details: bool) -> Resu

/// `unspent-outputs` command
pub async fn unspent_outputs_command(account: &Account) -> Result<(), Error> {
let outputs = account.unspent_outputs(None).await?;

if outputs.is_empty() {
println_log_info!("No outputs found");
} else {
println_log_info!("Unspent outputs:");
for (i, output_data) in outputs.into_iter().enumerate() {
println_log_info!("{}\t{}\t{}", i, &output_data.output_id, output_data.output.kind_str());
}
}

Ok(())
print_outputs(account.unspent_outputs(None).await?, "Unspent outputs:").await
}

pub async fn vote_command(account: &Account, event_id: ParticipationEventId, answers: Vec<u8>) -> Result<(), Error> {
Expand Down Expand Up @@ -992,3 +998,36 @@ async fn print_address(account: &Account, address: &AccountAddress) -> Result<()

Ok(())
}

async fn print_outputs(mut outputs: Vec<OutputData>, title: &str) -> Result<(), Error> {
if outputs.is_empty() {
println_log_info!("No outputs found");
} else {
println_log_info!("{title}");
outputs.sort_unstable_by(outputs_ordering);

for (i, output_data) in outputs.into_iter().enumerate() {
let booked_time = to_utc_date_time(output_data.metadata.milestone_timestamp_booked() as u128 * 1000)?;
let formatted_time = booked_time.format("%Y-%m-%d %H:%M:%S UTC").to_string();

println_log_info!(
"{:<5}{}\t{}\t{}\t{}",
i,
&output_data.output_id,
output_data.output.kind_str(),
formatted_time,
if output_data.is_spent { "Spent" } else { "Unspent" },
);
}
}

Ok(())
}

fn outputs_ordering(a: &OutputData, b: &OutputData) -> std::cmp::Ordering {
(b.metadata.milestone_timestamp_booked(), a.output_id).cmp(&(a.metadata.milestone_timestamp_booked(), b.output_id))
thibault-martinez marked this conversation as resolved.
Show resolved Hide resolved
}

fn transactions_ordering(a: &Transaction, b: &Transaction) -> std::cmp::Ordering {
b.timestamp.cmp(&a.timestamp)
}
Loading