From 6617d8172f7e4bafa80039249e4a4a43e7d9e395 Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Thu, 27 Jul 2023 10:17:23 +0200 Subject: [PATCH 01/22] Enable CI workflows (#941) --- .github/workflows/bindings-nodejs.yml | 4 ++-- .github/workflows/bindings-python.yml | 4 ++-- .github/workflows/bindings-wallet-nodejs.yml | 4 ++-- .github/workflows/bindings-wasm.yml | 4 ++-- .github/workflows/build-and-test.yml | 4 ++-- .github/workflows/coverage.yml | 2 +- .github/workflows/lint.yml | 4 ++-- .github/workflows/private-tangle-tests.yml | 4 ++-- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/bindings-nodejs.yml b/.github/workflows/bindings-nodejs.yml index b6c3339bef..d5034e37de 100644 --- a/.github/workflows/bindings-nodejs.yml +++ b/.github/workflows/bindings-nodejs.yml @@ -2,7 +2,7 @@ name: Nodejs bindings checks on: push: - branches: [develop, production] + branches: [develop, production, 1.1] paths: - ".github/actions/**" - "**.rs" # Include all rust files @@ -15,7 +15,7 @@ on: - "bindings/nodejs/**" - ".github/workflows/bindings-nodejs.yml" pull_request: - branches: [develop, production] + branches: [develop, production, 1.1] paths: - ".github/actions/**" - "**.rs" # Include all rust files diff --git a/.github/workflows/bindings-python.yml b/.github/workflows/bindings-python.yml index 4b1181a8e8..d61ac6d32c 100644 --- a/.github/workflows/bindings-python.yml +++ b/.github/workflows/bindings-python.yml @@ -2,7 +2,7 @@ name: Python bindings checks on: push: - branches: [develop, production] + branches: [develop, production, 1.1] paths: - ".github/actions/**" - "**.rs" # Include all rust files @@ -15,7 +15,7 @@ on: - "bindings/python/**" - ".github/workflows/bindings-python.yml" pull_request: - branches: [develop, production] + branches: [develop, production, 1.1] paths: - ".github/actions/**" - "**.rs" # Include all rust files diff --git a/.github/workflows/bindings-wallet-nodejs.yml b/.github/workflows/bindings-wallet-nodejs.yml index 8941116bd8..6766830147 100644 --- a/.github/workflows/bindings-wallet-nodejs.yml +++ b/.github/workflows/bindings-wallet-nodejs.yml @@ -2,7 +2,7 @@ name: Nodejs bindings checks (wallet) on: push: - branches: [develop, production] + branches: [develop, production, 1.1] paths: - ".github/actions/**" - "**.rs" # Include all rust files @@ -16,7 +16,7 @@ on: - ".github/workflows/bindings-wallet-nodejs.yml" - ".patches/*" pull_request: - branches: [develop, production] + branches: [develop, production, 1.1] paths: - ".github/actions/**" - "**.rs" # Include all rust files diff --git a/.github/workflows/bindings-wasm.yml b/.github/workflows/bindings-wasm.yml index f7cbb1cb31..1b8389d0f4 100644 --- a/.github/workflows/bindings-wasm.yml +++ b/.github/workflows/bindings-wasm.yml @@ -1,7 +1,7 @@ name: Wasm bindings checks on: push: - branches: [develop, production] + branches: [develop, production, 1.1] paths: - ".github/actions/**" - "**.rs" # Include all rust files @@ -15,7 +15,7 @@ on: - "bindings/nodejs/**" - ".github/workflows/bindings-wasm.yml" pull_request: - branches: [develop, production] + branches: [develop, production, 1.1] paths: - ".github/actions/**" - "**.rs" # Include all rust files diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index cb5069fe06..edab827f48 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -2,7 +2,7 @@ name: Build and Run Tests on: push: - branches: [develop, production] + branches: [develop, production, 1.1] paths: - ".github/workflows/build-and-test.yml" - ".github/actions/**" @@ -11,7 +11,7 @@ on: - "**Cargo.lock" # Include all Cargo.lock files - "!**/bindings/**" # Exclude all bindings pull_request: - branches: [develop, production] + branches: [develop, production, 1.1] paths: - ".github/workflows/build-and-test.yml" - ".github/actions/**" diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index ce216b583c..a508e51ef7 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -2,7 +2,7 @@ name: Test coverage on: push: - branches: [develop, production] + branches: [develop, production, 1.1] paths: - ".github/workflows/coverage.yml" - ".github/actions/**" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 88442548b8..50b3518b50 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,7 +1,7 @@ name: Linting on: push: - branches: [develop, production] + branches: [develop, production, 1.1] paths: - ".github/workflows/lint.yml" - ".github/actions/**" @@ -10,7 +10,7 @@ on: - "**Cargo.lock" # Include all Cargo.lock files - "!**/bindings/**" # Exclude all bindings pull_request: - branches: [develop, production] + branches: [develop, production, 1.1] paths: - ".github/workflows/lint.yml" - ".github/actions/**" diff --git a/.github/workflows/private-tangle-tests.yml b/.github/workflows/private-tangle-tests.yml index bc04e75e86..2ba7040647 100644 --- a/.github/workflows/private-tangle-tests.yml +++ b/.github/workflows/private-tangle-tests.yml @@ -2,7 +2,7 @@ name: Build and run specific tests on a private tangle on: push: - branches: [develop, production] + branches: [develop, production, 1.1] paths: - ".github/workflows/private-tangle-tests.yml" - ".github/actions/**" @@ -13,7 +13,7 @@ on: - "**Cargo.lock" - "!cli/**" # Exclude CLI pull_request: - branches: [develop, production] + branches: [develop, production, 1.1] paths: - ".github/workflows/private-tangle-tests.yml" - ".github/actions/**" From bbc8341160226ffc6775ac34ca7e6fc44cba2527 Mon Sep 17 00:00:00 2001 From: Alexandcoats Date: Thu, 27 Jul 2023 12:08:46 -0400 Subject: [PATCH 02/22] Simplify `StrongholdAdapterBuilder` (#940) * Fix StrongholdAdapterBuilder * changelog * Enable CI workflows (#941) * tweaks and fix test --------- Co-authored-by: Thibault Martinez --- Cargo.lock | 73 ------------------ sdk/CHANGELOG.md | 6 ++ sdk/Cargo.toml | 3 - sdk/src/client/stronghold/mod.rs | 127 +++++++++++++++---------------- 4 files changed, 67 insertions(+), 142 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6820f925ad..b7ec25a422 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -719,41 +719,6 @@ dependencies = [ "syn 2.0.27", ] -[[package]] -name = "darling" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 1.0.109", -] - -[[package]] -name = "darling_macro" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" -dependencies = [ - "darling_core", - "quote", - "syn 1.0.109", -] - [[package]] name = "data-encoding" version = "2.4.0" @@ -782,37 +747,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "derive_builder" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" -dependencies = [ - "derive_builder_macro", -] - -[[package]] -name = "derive_builder_core" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "derive_builder_macro" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" -dependencies = [ - "derive_builder_core", - "syn 1.0.109", -] - [[package]] name = "derive_more" version = "0.99.17" @@ -1494,12 +1428,6 @@ dependencies = [ "tokio-rustls", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" version = "0.4.0" @@ -1652,7 +1580,6 @@ dependencies = [ "bech32 0.9.1", "bitflags 2.3.3", "bytemuck", - "derive_builder", "derive_more", "dotenvy", "fern-logger", diff --git a/sdk/CHANGELOG.md b/sdk/CHANGELOG.md index ee99c119e8..76b8c9337c 100644 --- a/sdk/CHANGELOG.md +++ b/sdk/CHANGELOG.md @@ -19,6 +19,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Security --> +## 1.1.0 - 2023-MM-DD + +### Changed + +- `StrongholdAdapterBuilder` updated to be slightly more ergonomic; + ## 1.0.1 - 2023-07-25 ### Fixed diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 29115cf0dd..3cfc845dc7 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -58,7 +58,6 @@ serde_json = { version = "1.0.103", default-features = false, features = [ # Optional dependencies anymap = { version = "0.12.1", default-features = false, optional = true } async-trait = { version = "0.1.72", default-features = false, optional = true } -derive_builder = { version = "0.12.0", default-features = false, optional = true } fern-logger = { version = "0.5.0", default-features = false, optional = true } futures = { version = "0.3.28", default-features = false, features = [ "thread-pool", @@ -162,7 +161,6 @@ std = [ "bitflags/std", "rand?/std_rng", "regex?/std", - "derive_builder?/std", "iota_stronghold?/std", "iota-crypto/std", "once_cell?/std", @@ -177,7 +175,6 @@ storage = [ ] stronghold = [ "iota_stronghold", - "derive_builder", "iota-crypto/chacha", "dep:time", "dep:anymap", diff --git a/sdk/src/client/stronghold/mod.rs b/sdk/src/client/stronghold/mod.rs index 9e4d3f02bf..79c6fe16a3 100644 --- a/sdk/src/client/stronghold/mod.rs +++ b/sdk/src/client/stronghold/mod.rs @@ -52,13 +52,13 @@ mod migration; mod secret; mod storage; +use alloc::sync::Weak; use std::{ path::{Path, PathBuf}, sync::Arc, time::Duration, }; -use derive_builder::Builder; use iota_stronghold::{KeyProvider, SnapshotPath, Stronghold}; use log::{debug, error, warn}; use tokio::{ @@ -74,11 +74,9 @@ use super::{storage::StorageAdapter, utils::Password}; /// A wrapper on [Stronghold]. /// /// See the [module-level documentation](self) for more details. -#[derive(Builder, Debug)] -#[builder(pattern = "owned", build_fn(skip))] +#[derive(Debug)] pub struct StrongholdAdapter { /// A stronghold instance. - #[builder(field(type = "Option"))] stronghold: Arc>, /// A key to open the Stronghold vault. @@ -88,8 +86,6 @@ pub struct StrongholdAdapter { /// derive a key from it. /// /// [`password()`]: self::StrongholdAdapterBuilder::password() - #[builder(setter(custom))] - #[builder(field(type = "Option"))] key_provider: Arc>>, /// An interval of time, after which `key` will be cleared from the memory. @@ -98,18 +94,12 @@ pub struct StrongholdAdapter { /// timer will be spawned in the background to clear ([zeroize]) the key after `timeout`. /// /// If a [`StrongholdAdapter`] is destroyed (dropped), then the timer will stop too. - #[builder(setter(strip_option))] timeout: Option, /// A handle to the timeout task. - /// - /// Note that this field doesn't actually have a custom setter; `setter(custom)` is only for skipping the setter - /// generation. - #[builder(setter(custom))] - timeout_task: Arc>>>, + timeout_task: Arc>>, /// The path to a Stronghold snapshot file. - #[builder(setter(skip))] pub(crate) snapshot_path: PathBuf, } @@ -146,15 +136,34 @@ fn check_or_create_snapshot( Ok(()) } +#[derive(Default, Debug)] +pub struct StrongholdAdapterBuilder { + stronghold: Option, + key_provider: Option, + timeout: Option, +} + /// Extra / custom builder method implementations. impl StrongholdAdapterBuilder { - /// Use an user-input password string to derive a key to use Stronghold. - pub fn password(mut self, password: impl Into) -> Self { - let password = password.into(); + pub fn stronghold(mut self, stronghold: impl Into>) -> Self { + self.stronghold = stronghold.into(); + self + } + + pub fn key_provider(mut self, key_provider: impl Into>) -> Self { + self.key_provider = key_provider.into(); + self + } - // Note that derive_builder always adds another layer of Option. - self.key_provider = Some(self::common::key_provider_from_password(password)); + pub fn timeout(mut self, timeout: impl Into>) -> Self { + self.timeout = timeout.into(); + self + } + /// Use an user-input password string to derive a key to use Stronghold. + pub fn password(mut self, password: impl Into) -> Self { + self.key_provider + .replace(self::common::key_provider_from_password(password.into())); self } @@ -172,7 +181,7 @@ impl StrongholdAdapterBuilder { /// /// [`password()`]: Self::password() /// [`timeout()`]: Self::timeout() - pub fn build>(mut self, snapshot_path: P) -> Result { + pub fn build>(self, snapshot_path: P) -> Result { // In any case, Stronghold - as a necessary component - needs to be present at this point. let stronghold = self.stronghold.unwrap_or_default(); @@ -186,39 +195,25 @@ impl StrongholdAdapterBuilder { let has_key_provider = self.key_provider.is_some(); let key_provider = Arc::new(Mutex::new(self.key_provider)); let stronghold = Arc::new(Mutex::new(stronghold)); + let timeout_task = Arc::new(Mutex::new(None)); // If both `key` and `timeout` are set, then we spawn the task and keep its join handle. - if let (true, Some(Some(timeout))) = (has_key_provider, self.timeout) { - let timeout_task = Arc::new(Mutex::new(None)); - - // The key clearing task, with the data it owns. - let task_self = timeout_task.clone(); - let key_provider = key_provider.clone(); - - // To keep this function synchronous (`fn`), we spawn a task that spawns the key clearing task here. It'll - // however panic when this function is not in a Tokio runtime context (usually in an `async fn`), albeit it - // itself is a `fn`. There is also a small delay from the return of this function to the task actually being - // spawned and set in the `struct`. - let stronghold_clone = stronghold.clone(); - tokio::spawn(async move { - *task_self.lock().await = Some(tokio::spawn(task_key_clear( - task_self.clone(), // LHS moves task_self - stronghold_clone, - key_provider, - timeout, - ))); - }); - - // Keep the task handle in the builder; the code below checks this. - self.timeout_task = Some(timeout_task); + if let (true, Some(timeout)) = (has_key_provider, self.timeout) { + let weak = Arc::downgrade(&timeout_task); + *timeout_task.try_lock().unwrap() = Some(tokio::spawn(task_key_clear( + weak, + stronghold.clone(), + key_provider.clone(), + timeout, + ))); } // Create the adapter as per configuration and return it. Ok(StrongholdAdapter { stronghold, key_provider, - timeout: self.timeout.unwrap_or(None), - timeout_task: self.timeout_task.unwrap_or_else(|| Arc::new(Mutex::new(None))), + timeout: self.timeout, + timeout_task, snapshot_path: snapshot_path.as_ref().to_path_buf(), }) } @@ -269,12 +264,10 @@ impl StrongholdAdapter { timeout_task.abort(); } - // The key clearing task, with the data it owns. - let task_self = self.timeout_task.clone(); let key_provider = self.key_provider.clone(); *self.timeout_task.lock().await = Some(tokio::spawn(task_key_clear( - task_self, + Arc::downgrade(&self.timeout_task), self.stronghold.clone(), key_provider, timeout, @@ -329,12 +322,10 @@ impl StrongholdAdapter { // Recover: restart the key clearing task if let Some(timeout) = self.timeout { - // The key clearing task, with the data it owns. - let task_self = self.timeout_task.clone(); let key_provider = self.key_provider.clone(); *self.timeout_task.lock().await = Some(tokio::spawn(task_key_clear( - task_self, + Arc::downgrade(&self.timeout_task), self.stronghold.clone(), key_provider, timeout, @@ -372,12 +363,10 @@ impl StrongholdAdapter { // Recover: restart key clearing task if let Some(timeout) = self.timeout { - // The key clearing task, with the data it owns. - let task_self = self.timeout_task.clone(); let key_provider = self.key_provider.clone(); *self.timeout_task.lock().await = Some(tokio::spawn(task_key_clear( - task_self, + Arc::downgrade(&self.timeout_task), self.stronghold.clone(), key_provider, timeout, @@ -393,12 +382,10 @@ impl StrongholdAdapter { // Restart the key clearing task. if let Some(timeout) = self.timeout { - // The key clearing task, with the data it owns. - let task_self = self.timeout_task.clone(); let key_provider = self.key_provider.clone(); *self.timeout_task.lock().await = Some(tokio::spawn(task_key_clear( - task_self, + Arc::downgrade(&self.timeout_task), self.stronghold.clone(), key_provider, timeout, @@ -453,12 +440,10 @@ impl StrongholdAdapter { // If a new timeout is set and the key is still in the memory, spawn a new task; otherwise we do nothing. if let (Some(_), Some(timeout)) = (self.key_provider.lock().await.as_ref(), self.timeout) { - // The key clearing task, with the data it owns. - let task_self = self.timeout_task.clone(); let key_provider = self.key_provider.clone(); *self.timeout_task.lock().await = Some(tokio::spawn(task_key_clear( - task_self, + Arc::downgrade(&self.timeout_task), self.stronghold.clone(), key_provider, timeout, @@ -540,23 +525,33 @@ impl StrongholdAdapter { } } +type TaskHandle = JoinHandle<()>; + /// The asynchronous key clearing task purging `key` after `timeout` spent in Tokio. async fn task_key_clear( - task_self: Arc>>>, + task: Weak>>, stronghold: Arc>, key_provider: Arc>>, timeout: Duration, ) { tokio::time::sleep(timeout).await; - debug!("StrongholdAdapter is purging the key"); - key_provider.lock().await.take(); + // If the weak pointer cannot upgrade, that means the secret manager has been dropped, + // so we can just exit. + if let Some(task) = task.upgrade() { + // Take the join handle, but hold the lock until we're done + let mut lock = task.lock().await; + lock.take(); - // TODO handle error - stronghold.lock().await.clear().unwrap(); + debug!("StrongholdAdapter is purging the key"); + key_provider.lock().await.take(); - // Take self, but do nothing (we're exiting anyways). - task_self.lock().await.take(); + if let Err(e) = stronghold.lock().await.clear() { + log::error!("Failed to clear stronghold keys: {e}"); + } + + drop(lock); + } } #[cfg(test)] From 2a9a1bfb6c7dab1c088476cb7b140985409a1227 Mon Sep 17 00:00:00 2001 From: /alex/ Date: Fri, 28 Jul 2023 09:07:40 +0200 Subject: [PATCH 03/22] feat(cli): Add cli command to list wallet accounts (#950) * add cli command to list accounts * add list-accounts as wallet command * review suggestions * some fixes * update changelog * nit * also print account indexes * format as table * undo breaking change * nit --- cli/CHANGELOG.md | 6 +++ cli/src/account.rs | 91 ++++++++++++++++++++--------------- cli/src/account_completion.rs | 3 +- cli/src/command/wallet.rs | 16 ++++++ cli/src/helper.rs | 4 +- cli/src/main.rs | 9 ++-- cli/src/wallet.rs | 8 ++- sdk/src/wallet/core/mod.rs | 6 +-- 8 files changed, 90 insertions(+), 53 deletions(-) diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index bba6480056..38bd56cfd9 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -19,6 +19,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Security --> +## 1.1.0 - 2023-MM-DD + +### Added + +`WalletCommand::Accounts` variant to list all available accounts in a wallet; + ## 1.0.0 - 2023-07-27 First release of the `cli-wallet`. diff --git a/cli/src/account.rs b/cli/src/account.rs index 15a08f25ec..f5a63acfa1 100644 --- a/cli/src/account.rs +++ b/cli/src/account.rs @@ -4,7 +4,7 @@ use clap::Parser; use colored::Colorize; use dialoguer::Input; -use iota_sdk::wallet::Account; +use iota_sdk::wallet::{Account, Wallet}; use crate::{ account_completion::ACCOUNT_COMPLETION, @@ -25,10 +25,10 @@ use crate::{ }; // loop on the account prompt -pub async fn account_prompt(account: Account) -> Result<(), Error> { +pub async fn account_prompt(wallet: &Wallet, account: &Account) -> Result<(), Error> { let mut history = AccountHistory::default(); loop { - match account_prompt_internal(account.clone(), &mut history).await { + match account_prompt_internal(wallet, account, &mut history).await { Ok(true) => { return Ok(()); } @@ -41,7 +41,11 @@ pub async fn account_prompt(account: Account) -> Result<(), Error> { } // loop on the account prompt -pub async fn account_prompt_internal(account: Account, history: &mut AccountHistory) -> Result { +pub async fn account_prompt_internal( + wallet: &Wallet, + account: &Account, + history: &mut AccountHistory, +) -> Result { let alias = { let account = account.details().await; account.alias().clone() @@ -52,11 +56,20 @@ pub async fn account_prompt_internal(account: Account, history: &mut AccountHist .completion_with(&ACCOUNT_COMPLETION) .interact_text()?; match command.as_str() { - "h" => print_account_help(), - "clear" => { + "h" | "help" => print_account_help(), + "c" | "clear" => { // Clear console let _ = std::process::Command::new("clear").status(); } + "accounts" => { + // List all accounts + let accounts = wallet.get_accounts().await?; + println!("INDEX\tALIAS"); + for account in accounts { + let details = &*account.details().await; + println!("{}\t{}", details.index(), details.alias()); + } + } _ => { // Prepend `Account: ` so the parsing will be correct let command = format!("Account: {}", command.trim()); @@ -68,16 +81,16 @@ pub async fn account_prompt_internal(account: Account, history: &mut AccountHist } }; if let Err(err) = match account_cli.command { - AccountCommand::Addresses => addresses_command(&account).await, - AccountCommand::Balance { addresses } => balance_command(&account, addresses).await, + AccountCommand::Addresses => addresses_command(account).await, + AccountCommand::Balance { addresses } => balance_command(account, addresses).await, AccountCommand::BurnNativeToken { token_id, amount } => { - burn_native_token_command(&account, token_id, amount).await + burn_native_token_command(account, token_id, amount).await } - AccountCommand::BurnNft { nft_id } => burn_nft_command(&account, nft_id).await, - AccountCommand::Claim { output_id } => claim_command(&account, output_id).await, - AccountCommand::ClaimableOutputs => claimable_outputs_command(&account).await, - AccountCommand::Consolidate => consolidate_command(&account).await, - AccountCommand::CreateAliasOutput => create_alias_outputs_command(&account).await, + AccountCommand::BurnNft { nft_id } => burn_nft_command(account, nft_id).await, + AccountCommand::Claim { output_id } => claim_command(account, output_id).await, + AccountCommand::ClaimableOutputs => claimable_outputs_command(account).await, + AccountCommand::Consolidate => consolidate_command(account).await, + AccountCommand::CreateAliasOutput => create_alias_outputs_command(account).await, AccountCommand::CreateNativeToken { circulating_supply, maximum_supply, @@ -85,24 +98,24 @@ pub async fn account_prompt_internal(account: Account, history: &mut AccountHist foundry_metadata_file, } => { create_native_token_command( - &account, + account, circulating_supply, maximum_supply, bytes_from_hex_or_file(foundry_metadata_hex, foundry_metadata_file).await?, ) .await } - AccountCommand::DestroyAlias { alias_id } => destroy_alias_command(&account, alias_id).await, - AccountCommand::DestroyFoundry { foundry_id } => destroy_foundry_command(&account, foundry_id).await, + AccountCommand::DestroyAlias { alias_id } => destroy_alias_command(account, alias_id).await, + AccountCommand::DestroyFoundry { foundry_id } => destroy_foundry_command(account, foundry_id).await, AccountCommand::Exit => { return Ok(true); } - AccountCommand::Faucet { address, url } => faucet_command(&account, address, url).await, + AccountCommand::Faucet { address, url } => faucet_command(account, address, url).await, AccountCommand::MeltNativeToken { token_id, amount } => { - melt_native_token_command(&account, token_id, amount).await + melt_native_token_command(account, token_id, amount).await } AccountCommand::MintNativeToken { token_id, amount } => { - mint_native_token(&account, token_id, amount).await + mint_native_token(account, token_id, amount).await } AccountCommand::MintNft { address, @@ -115,7 +128,7 @@ pub async fn account_prompt_internal(account: Account, history: &mut AccountHist issuer, } => { mint_nft_command( - &account, + account, address, bytes_from_hex_or_file(immutable_metadata_hex, immutable_metadata_file).await?, bytes_from_hex_or_file(metadata_hex, metadata_file).await?, @@ -125,10 +138,10 @@ pub async fn account_prompt_internal(account: Account, history: &mut AccountHist ) .await } - 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::Outputs => outputs_command(&account).await, + 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::Outputs => outputs_command(account).await, AccountCommand::Send { address, amount, @@ -142,7 +155,7 @@ pub async fn account_prompt_internal(account: Account, history: &mut AccountHist allow_micro_amount }; send_command( - &account, + account, address, amount, return_address, @@ -156,22 +169,22 @@ pub async fn account_prompt_internal(account: Account, history: &mut AccountHist token_id, amount, gift_storage_deposit, - } => send_native_token_command(&account, address, token_id, amount, gift_storage_deposit).await, - AccountCommand::SendNft { address, nft_id } => send_nft_command(&account, address, nft_id).await, - AccountCommand::Sync => sync_command(&account).await, - AccountCommand::Transaction { transaction_id } => transaction_command(&account, &transaction_id).await, - AccountCommand::Transactions { show_details } => transactions_command(&account, show_details).await, - AccountCommand::UnspentOutputs => unspent_outputs_command(&account).await, - AccountCommand::Vote { event_id, answers } => vote_command(&account, event_id, answers).await, - AccountCommand::StopParticipating { event_id } => stop_participating_command(&account, event_id).await, + } => send_native_token_command(account, address, token_id, amount, gift_storage_deposit).await, + AccountCommand::SendNft { address, nft_id } => send_nft_command(account, address, nft_id).await, + AccountCommand::Sync => sync_command(account).await, + AccountCommand::Transaction { transaction_id } => transaction_command(account, &transaction_id).await, + AccountCommand::Transactions { show_details } => transactions_command(account, show_details).await, + AccountCommand::UnspentOutputs => unspent_outputs_command(account).await, + AccountCommand::Vote { event_id, answers } => vote_command(account, event_id, answers).await, + AccountCommand::StopParticipating { event_id } => stop_participating_command(account, event_id).await, AccountCommand::ParticipationOverview { event_ids } => { let event_ids = (!event_ids.is_empty()).then_some(event_ids); - participation_overview_command(&account, event_ids).await + participation_overview_command(account, event_ids).await } - AccountCommand::VotingPower => voting_power_command(&account).await, - AccountCommand::IncreaseVotingPower { amount } => increase_voting_power_command(&account, amount).await, - AccountCommand::DecreaseVotingPower { amount } => decrease_voting_power_command(&account, amount).await, - AccountCommand::VotingOutput => voting_output_command(&account).await, + AccountCommand::VotingPower => voting_power_command(account).await, + AccountCommand::IncreaseVotingPower { amount } => increase_voting_power_command(account, amount).await, + AccountCommand::DecreaseVotingPower { amount } => decrease_voting_power_command(account, amount).await, + AccountCommand::VotingOutput => voting_output_command(account).await, } { println_log_error!("{err}"); } diff --git a/cli/src/account_completion.rs b/cli/src/account_completion.rs index 8c05db9e31..1bdfdd54d6 100644 --- a/cli/src/account_completion.rs +++ b/cli/src/account_completion.rs @@ -4,11 +4,12 @@ use dialoguer::Completion; pub(crate) struct AccountCompletion<'a> { - options: [&'a str; 37], + options: [&'a str; 38], } pub(crate) const ACCOUNT_COMPLETION: AccountCompletion = AccountCompletion { options: [ + "accounts", "addresses", "balance", "burn-native-token", diff --git a/cli/src/command/wallet.rs b/cli/src/command/wallet.rs index ff74bc9f76..0200637b7a 100644 --- a/cli/src/command/wallet.rs +++ b/cli/src/command/wallet.rs @@ -46,6 +46,8 @@ pub struct WalletCli { #[derive(Debug, Clone, Subcommand)] pub enum WalletCommand { + /// List all accounts. + Accounts, /// Create a stronghold backup file. Backup { /// Path of the created stronghold backup file. @@ -107,6 +109,20 @@ impl Default for InitParameters { } } +pub async fn accounts_command(storage_path: &Path, snapshot_path: &Path) -> Result<(), Error> { + let password = get_password("Stronghold password", false)?; + let wallet = unlock_wallet(storage_path, snapshot_path, password).await?; + let accounts = wallet.get_accounts().await?; + + println!("INDEX\tALIAS"); + for account in accounts { + let details = &*account.details().await; + println!("{}\t{}", details.index(), details.alias()); + } + + Ok(()) +} + pub async fn backup_command(storage_path: &Path, snapshot_path: &Path, backup_path: &Path) -> Result<(), Error> { let password = get_password("Stronghold password", !snapshot_path.exists())?; let wallet = unlock_wallet(storage_path, snapshot_path, password.clone()).await?; diff --git a/cli/src/helper.rs b/cli/src/helper.rs index f0915e31d9..05f84293f2 100644 --- a/cli/src/helper.rs +++ b/cli/src/helper.rs @@ -77,11 +77,11 @@ pub async fn pick_account(wallet: &Wallet) -> Result, Error> { 1 => Ok(Some(accounts.swap_remove(0))), _ => { // fetch all available account aliases to display to the user - let aliases = wallet.get_account_aliases().await?; + let account_aliases = wallet.get_account_aliases().await?; let index = Select::with_theme(&ColorfulTheme::default()) .with_prompt("Select an account:") - .items(&aliases) + .items(&account_aliases) .default(0) .interact_on(&Term::stderr())?; diff --git a/cli/src/main.rs b/cli/src/main.rs index d6b055e10d..0c4386a9a9 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -48,12 +48,9 @@ fn logger_init(cli: &WalletCli) -> Result<(), Error> { } async fn run(cli: WalletCli) -> Result<(), Error> { - let (wallet, account) = new_wallet(cli).await?; - - if let Some(wallet) = wallet { - if let Some(account) = account { - account::account_prompt(wallet.get_account(account).await?).await?; - } + if let (Some(wallet), Some(account)) = new_wallet(cli).await? { + let account = wallet.get_account(account).await?; + account::account_prompt(&wallet, &account).await?; } Ok(()) diff --git a/cli/src/wallet.rs b/cli/src/wallet.rs index 07fe35f6c4..8dc2662f0e 100644 --- a/cli/src/wallet.rs +++ b/cli/src/wallet.rs @@ -7,7 +7,7 @@ use iota_sdk::wallet::Wallet; use crate::{ command::wallet::{ - add_account, backup_command, change_password_command, init_command, + accounts_command, add_account, backup_command, change_password_command, init_command, migrate_stronghold_snapshot_v2_to_v3_command, mnemonic_command, new_account_command, node_info_command, restore_command, set_node_url_command, sync_command, unlock_wallet, InitParameters, WalletCli, WalletCommand, }, @@ -22,6 +22,10 @@ pub async fn new_wallet(cli: WalletCli) -> Result<(Option, Option { + accounts_command(storage_path, snapshot_path).await?; + return Ok((None, None)); + } WalletCommand::Init(init_parameters) => { let wallet = init_command(storage_path, snapshot_path, init_parameters).await?; (Some(wallet), None) @@ -108,7 +112,7 @@ async fn create_initial_account(wallet: Wallet) -> Result<(Option, Optio if get_decision("Create initial account?")? { let alias = get_account_alias("New account alias", &wallet).await?; let alias = add_account(&wallet, Some(alias)).await?; - println_log_info!("Created initial account. Type `help` to see all available commands."); + println_log_info!("Created initial account.\nType `help` to see all available account commands."); Ok((Some(wallet), Some(alias))) } else { Ok((Some(wallet), None)) diff --git a/sdk/src/wallet/core/mod.rs b/sdk/src/wallet/core/mod.rs index f007ac896a..170570d6c0 100644 --- a/sdk/src/wallet/core/mod.rs +++ b/sdk/src/wallet/core/mod.rs @@ -97,11 +97,11 @@ where /// Get all account aliases pub async fn get_account_aliases(&self) -> crate::wallet::Result> { let accounts = self.accounts.read().await; - let mut aliases = Vec::with_capacity(accounts.len()); + let mut account_aliases = Vec::with_capacity(accounts.len()); for handle in accounts.iter() { - aliases.push(handle.details().await.alias().clone()); + account_aliases.push(handle.details().await.alias().clone()); } - Ok(aliases) + Ok(account_aliases) } /// Removes the latest account (account with the largest account index). From 67cb327c592420bb4378da5d1fbae19442358200 Mon Sep 17 00:00:00 2001 From: /alex/ Date: Fri, 28 Jul 2023 10:15:04 +0200 Subject: [PATCH 04/22] chore(logging): change some debug logs to warnings and errors (#958) * change some debug logs to warnings and errors * revert some --- bindings/nodejs/src/client.rs | 2 +- bindings/nodejs/src/secret_manager.rs | 2 +- bindings/nodejs/src/wallet.rs | 2 +- sdk/src/client/node_manager/syncing.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bindings/nodejs/src/client.rs b/bindings/nodejs/src/client.rs index 643fead784..13697a3c0c 100644 --- a/bindings/nodejs/src/client.rs +++ b/bindings/nodejs/src/client.rs @@ -53,7 +53,7 @@ impl ClientMethodHandler { (msg, is_err) } Err(e) => { - log::debug!("{:?}", e); + log::error!("{:?}", e); (format!("Couldn't parse to method with error - {e:?}"), true) } } diff --git a/bindings/nodejs/src/secret_manager.rs b/bindings/nodejs/src/secret_manager.rs index cf0b6d8019..7c5531638f 100644 --- a/bindings/nodejs/src/secret_manager.rs +++ b/bindings/nodejs/src/secret_manager.rs @@ -56,7 +56,7 @@ impl SecretManagerMethodHandler { (msg, is_err) } Err(e) => { - log::debug!("{:?}", e); + log::error!("{:?}", e); (format!("Couldn't parse to method with error - {e:?}"), true) } } diff --git a/bindings/nodejs/src/wallet.rs b/bindings/nodejs/src/wallet.rs index adc85c6f02..ad3ae44380 100644 --- a/bindings/nodejs/src/wallet.rs +++ b/bindings/nodejs/src/wallet.rs @@ -58,7 +58,7 @@ impl WalletMethodHandler { (msg, is_err) } Err(e) => { - log::debug!("{:?}", e); + log::error!("{:?}", e); ( serde_json::to_string(&Response::Error(e.into())).expect("json to string error"), true, diff --git a/sdk/src/client/node_manager/syncing.rs b/sdk/src/client/node_manager/syncing.rs index 988e98d761..444a9f063e 100644 --- a/sdk/src/client/node_manager/syncing.rs +++ b/sdk/src/client/node_manager/syncing.rs @@ -83,7 +83,7 @@ impl ClientInner { } } } else { - log::debug!("{} is not healthy: {:?}", node.url, info); + log::warn!("{} is not healthy: {:?}", node.url, info); } } Err(err) => { From 83a8c12643f32347ce8b651a841ef187899564b8 Mon Sep 17 00:00:00 2001 From: Pawel Date: Fri, 28 Jul 2023 12:24:22 +0200 Subject: [PATCH 05/22] feat(cli): adds hex version of address to `addresses` command (#960) * feat: add hex version of address to `addresses` output --- cli/CHANGELOG.md | 3 ++- cli/src/command/account.rs | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 38bd56cfd9..b063c4b294 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -23,7 +23,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -`WalletCommand::Accounts` variant to list all available accounts in a wallet; +- `WalletCommand::Accounts` variant to list all available accounts in a wallet; +- `addresses` now additionally prints the hex version of the address; ## 1.0.0 - 2023-07-27 diff --git a/cli/src/command/account.rs b/cli/src/command/account.rs index eea411c7ae..1fbf578c0e 100644 --- a/cli/src/command/account.rs +++ b/cli/src/command/account.rs @@ -869,7 +869,14 @@ pub async fn voting_output_command(account: &Account) -> Result<(), Error> { } async fn print_address(account: &Account, address: &AccountAddress) -> Result<(), Error> { - let mut log = format!("Address {}: {}", address.key_index(), address.address()); + let mut log = format!( + "Address {}:\n {:<10}{}\n {:<10}{:?}", + address.key_index(), + "Bech32:", + address.address(), + "Hex:", + address.address().inner() + ); if *address.internal() { log = format!("{log}\nChange address"); From 571baede0e3fb4d60c1ece20a5a46da7ea6529b8 Mon Sep 17 00:00:00 2001 From: /alex/ Date: Sat, 29 Jul 2023 12:06:41 +0200 Subject: [PATCH 06/22] Return error on secret manager mismatch (#946) * Enable CI workflows (#941) * return error on mismatch * update changelog --------- Co-authored-by: Thibault Martinez --- sdk/CHANGELOG.md | 1 + sdk/examples/client/stronghold.rs | 2 +- sdk/src/wallet/core/operations/stronghold.rs | 34 ++++++++++++-------- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/sdk/CHANGELOG.md b/sdk/CHANGELOG.md index 76b8c9337c..f2657b58eb 100644 --- a/sdk/CHANGELOG.md +++ b/sdk/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - `StrongholdAdapterBuilder` updated to be slightly more ergonomic; +- `Wallet::{set_stronghold_password, change_stronghold_password, set_stronghold_password_clear_interval, store_mnemonic}` return an `Err` instead of `Ok` in case of a non-stronghold secret manager; ## 1.0.1 - 2023-07-25 diff --git a/sdk/examples/client/stronghold.rs b/sdk/examples/client/stronghold.rs index a16b52ad35..50ed797069 100644 --- a/sdk/examples/client/stronghold.rs +++ b/sdk/examples/client/stronghold.rs @@ -5,7 +5,7 @@ //! //! Rename `.env.example` to `.env` first, then run the command: //! ```sh -//! cargo run --release --all-features --example client_stronghold +//! cargo run --release --all-features --example stronghold //! ``` use iota_sdk::{ diff --git a/sdk/src/wallet/core/operations/stronghold.rs b/sdk/src/wallet/core/operations/stronghold.rs index b3d997fc03..d3c72db45c 100644 --- a/sdk/src/wallet/core/operations/stronghold.rs +++ b/sdk/src/wallet/core/operations/stronghold.rs @@ -17,8 +17,10 @@ impl Wallet { if let SecretManager::Stronghold(stronghold) = &mut *self.secret_manager.write().await { stronghold.set_password(password).await?; + Ok(()) + } else { + Err(crate::client::Error::SecretManagerMismatch.into()) } - Ok(()) } /// Change the Stronghold password to another one and also re-encrypt the values in the loaded snapshot with it. @@ -33,44 +35,50 @@ impl Wallet { if let SecretManager::Stronghold(stronghold) = &mut *self.secret_manager.write().await { stronghold.set_password(current_password).await?; stronghold.change_password(new_password).await?; + Ok(()) + } else { + Err(crate::client::Error::SecretManagerMismatch.into()) } - Ok(()) } /// Sets the Stronghold password clear interval pub async fn set_stronghold_password_clear_interval(&self, timeout: Option) -> crate::wallet::Result<()> { if let SecretManager::Stronghold(stronghold) = &mut *self.secret_manager.write().await { stronghold.set_timeout(timeout).await; + Ok(()) + } else { + Err(crate::client::Error::SecretManagerMismatch.into()) } - Ok(()) } /// Stores a mnemonic into the Stronghold vault pub async fn store_mnemonic(&self, mnemonic: Mnemonic) -> crate::wallet::Result<()> { if let SecretManager::Stronghold(stronghold) = &mut *self.secret_manager.write().await { stronghold.store_mnemonic(mnemonic).await?; + Ok(()) + } else { + Err(crate::client::Error::SecretManagerMismatch.into()) } - Ok(()) } /// Clears the Stronghold password from memory. pub async fn clear_stronghold_password(&self) -> crate::wallet::Result<()> { log::debug!("[clear_stronghold_password]"); - let mut secret_manager = self.secret_manager.write().await; - match &mut *secret_manager { - SecretManager::Stronghold(stronghold) => stronghold.clear_key().await, - _ => return Err(crate::client::Error::SecretManagerMismatch.into()), + if let SecretManager::Stronghold(stronghold) = &mut *self.secret_manager.write().await { + stronghold.clear_key().await; + Ok(()) + } else { + Err(crate::client::Error::SecretManagerMismatch.into()) } - Ok(()) } /// Checks if the Stronghold password is available. pub async fn is_stronghold_password_available(&self) -> crate::wallet::Result { log::debug!("[is_stronghold_password_available]"); - let mut secret_manager = self.secret_manager.write().await; - match &mut *secret_manager { - SecretManager::Stronghold(stronghold) => Ok(stronghold.is_key_available().await), - _ => Err(crate::client::Error::SecretManagerMismatch.into()), + if let SecretManager::Stronghold(stronghold) = &*self.secret_manager.write().await { + Ok(stronghold.is_key_available().await) + } else { + Err(crate::client::Error::SecretManagerMismatch.into()) } } } From 35c81781283c13e8fdd0af950a65838d7b50c41d Mon Sep 17 00:00:00 2001 From: Pawel Date: Mon, 31 Jul 2023 10:44:49 +0200 Subject: [PATCH 07/22] cli: add types and enumeration when listing Outputs (#955) * feat(wallet): add types to list of outputs * remove vim-typo * remove named parameters * feat: add kind_str for Output * fix kind_str * lint * changelog * lint * Update sdk/src/types/block/output/mod.rs * remove ghost code * Update cli/src/command/wallet.rs * Update cli/src/command/wallet.rs * add empty line * feat: extend list of outputs for `unspent-outputs` --------- Co-authored-by: Thibault Martinez --- cli/CHANGELOG.md | 1 + cli/src/command/account.rs | 13 ++++++++----- sdk/CHANGELOG.md | 4 ++++ sdk/src/types/block/output/mod.rs | 11 +++++++++++ 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index b063c4b294..172c5ad0d6 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `WalletCommand::Accounts` variant to list all available accounts in a wallet; - `addresses` now additionally prints the hex version of the address; +- `outputs`, `unspent-outputs` print a list that includes number and type of the output; ## 1.0.0 - 2023-07-27 diff --git a/cli/src/command/account.rs b/cli/src/command/account.rs index 1fbf578c0e..6192099b14 100644 --- a/cli/src/command/account.rs +++ b/cli/src/command/account.rs @@ -627,10 +627,11 @@ pub async fn outputs_command(account: &Account) -> Result<(), Error> { if outputs.is_empty() { println_log_info!("No outputs found"); } else { - let output_ids: Vec = outputs.iter().map(|o| o.output_id).collect(); - println_log_info!("Outputs: {output_ids:#?}"); + 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(()) } @@ -786,8 +787,10 @@ pub async fn unspent_outputs_command(account: &Account) -> Result<(), Error> { if outputs.is_empty() { println_log_info!("No outputs found"); } else { - let output_ids: Vec = outputs.iter().map(|o| o.output_id).collect(); - println_log_info!("Unspent outputs: {output_ids:#?}"); + 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(()) diff --git a/sdk/CHANGELOG.md b/sdk/CHANGELOG.md index f2657b58eb..ffc2e4309b 100644 --- a/sdk/CHANGELOG.md +++ b/sdk/CHANGELOG.md @@ -21,6 +21,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 1.1.0 - 2023-MM-DD +### Added + +- `Output::kind_str()` method; + ### Changed - `StrongholdAdapterBuilder` updated to be slightly more ergonomic; diff --git a/sdk/src/types/block/output/mod.rs b/sdk/src/types/block/output/mod.rs index 4c6cb7d6f7..6cf6365424 100644 --- a/sdk/src/types/block/output/mod.rs +++ b/sdk/src/types/block/output/mod.rs @@ -164,6 +164,17 @@ impl Output { } } + /// Returns the output kind of an [`Output`] as a string. + pub fn kind_str(&self) -> &str { + match self { + Self::Alias(_) => "Alias", + Self::Basic(_) => "Basic", + Self::Foundry(_) => "Foundry", + Self::Nft(_) => "Nft", + Self::Treasury(_) => "Treasury", + } + } + /// Returns the amount of an [`Output`]. pub fn amount(&self) -> u64 { match self { From 862cdfdd2ec6d8e31fbf083ed669676d099e0e18 Mon Sep 17 00:00:00 2001 From: Alexandcoats Date: Thu, 3 Aug 2023 04:49:20 -0400 Subject: [PATCH 08/22] Add `get_or_create_account` (#971) * Add `get_or_create_account` * clippy * changelog --- sdk/CHANGELOG.md | 1 + sdk/examples/wallet/accounts.rs | 15 +++------------ sdk/examples/wallet/background_syncing.rs | 8 +------- sdk/examples/wallet/events.rs | 8 +------- sdk/examples/wallet/ledger_nano.rs | 7 +------ sdk/examples/wallet/logger.rs | 8 +------- sdk/examples/wallet/spammer.rs | 11 +---------- sdk/examples/wallet/split_funds.rs | 11 +---------- sdk/examples/wallet/storage.rs | 11 +---------- sdk/examples/wallet/wallet.rs | 12 +----------- sdk/src/types/block/output/feature/mod.rs | 4 ++-- sdk/src/types/block/output/native_token.rs | 4 ++-- .../types/block/output/unlock_condition/mod.rs | 4 ++-- .../types/block/payload/milestone/option/mod.rs | 4 ++-- sdk/src/wallet/core/operations/get_account.rs | 13 +++++++++++++ 15 files changed, 33 insertions(+), 88 deletions(-) diff --git a/sdk/CHANGELOG.md b/sdk/CHANGELOG.md index ffc2e4309b..5006828099 100644 --- a/sdk/CHANGELOG.md +++ b/sdk/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- `Wallet::get_or_create_account` convenience method; - `Output::kind_str()` method; ### Changed diff --git a/sdk/examples/wallet/accounts.rs b/sdk/examples/wallet/accounts.rs index b7a432175a..82ce5e4a6c 100644 --- a/sdk/examples/wallet/accounts.rs +++ b/sdk/examples/wallet/accounts.rs @@ -17,7 +17,7 @@ use iota_sdk::{ secret::{mnemonic::MnemonicSecretManager, SecretManager}, utils::request_funds_from_faucet, }, - wallet::{Account, ClientOptions, Result, Wallet}, + wallet::{ClientOptions, Result, Wallet}, }; // The number of addresses to generate @@ -41,11 +41,11 @@ async fn main() -> Result<()> { .await?; // Get or create first account - let _ = get_or_create_account(&wallet, "Alice").await?; + let _ = wallet.get_or_create_account("Alice").await?; // Get or create second account let alias2 = "Bob"; - let account2 = get_or_create_account(&wallet, alias2).await?; + let account2 = wallet.get_or_create_account("Alice").await?; let accounts = wallet.get_accounts().await?; println!("WALLET ACCOUNTS:"); @@ -94,12 +94,3 @@ async fn main() -> Result<()> { Ok(()) } - -async fn get_or_create_account(wallet: &Wallet, alias: &str) -> Result { - Ok(if let Ok(account) = wallet.get_account(alias).await { - account - } else { - println!("Creating account '{alias}'"); - wallet.create_account().with_alias(alias).finish().await? - }) -} diff --git a/sdk/examples/wallet/background_syncing.rs b/sdk/examples/wallet/background_syncing.rs index 70e5ea3499..eda91075d0 100644 --- a/sdk/examples/wallet/background_syncing.rs +++ b/sdk/examples/wallet/background_syncing.rs @@ -34,13 +34,7 @@ async fn main() -> Result<()> { .await?; // Get or create new account - let alias = "Alice"; - let account = if let Ok(account) = wallet.get_account(alias).await { - account - } else { - println!("Creating account '{alias}'"); - wallet.create_account().with_alias(alias).finish().await? - }; + let account = wallet.get_or_create_account("Alice").await?; let addresses = account.addresses().await?; // Manually sync to ensure we have the correct funds to start with diff --git a/sdk/examples/wallet/events.rs b/sdk/examples/wallet/events.rs index ceedf02433..01abf19c8d 100644 --- a/sdk/examples/wallet/events.rs +++ b/sdk/examples/wallet/events.rs @@ -50,13 +50,7 @@ async fn main() -> Result<()> { .await; // Get or create an account - let alias = "Alice"; - let account = if let Ok(account) = wallet.get_account(alias).await { - account - } else { - println!("Creating account '{alias}'"); - wallet.create_account().with_alias(alias).finish().await? - }; + let account = wallet.get_or_create_account("Alice").await?; let balance = account.sync(None).await?; println!("Balance BEFORE:\n{:#?}", balance.base_coin()); diff --git a/sdk/examples/wallet/ledger_nano.rs b/sdk/examples/wallet/ledger_nano.rs index 820dfbdfd5..3842bc9f88 100644 --- a/sdk/examples/wallet/ledger_nano.rs +++ b/sdk/examples/wallet/ledger_nano.rs @@ -49,12 +49,7 @@ async fn main() -> Result<()> { println!("{:?}", wallet.get_ledger_nano_status().await?); // Get or create a new account - let account = if let Ok(account) = wallet.get_account(ACCOUNT_ALIAS).await { - account - } else { - println!("Creating account '{ACCOUNT_ALIAS}'"); - wallet.create_account().with_alias(ACCOUNT_ALIAS).finish().await? - }; + let account = wallet.get_or_create_account(ACCOUNT_ALIAS).await?; println!("Generating {NUM_ADDRESSES_TO_GENERATE} addresses..."); let now = tokio::time::Instant::now(); diff --git a/sdk/examples/wallet/logger.rs b/sdk/examples/wallet/logger.rs index 775a89c4c0..4cb69e54ca 100644 --- a/sdk/examples/wallet/logger.rs +++ b/sdk/examples/wallet/logger.rs @@ -46,13 +46,7 @@ async fn main() -> Result<()> { .await?; // Get or create a new account - let alias = "Alice"; - let account = if let Ok(account) = wallet.get_account(alias).await { - account - } else { - println!("Creating account '{alias}'"); - wallet.create_account().with_alias(alias).finish().await? - }; + let account = wallet.get_or_create_account("Alice").await?; println!("Generating {NUM_ADDRESSES_TO_GENERATE} addresses..."); let _ = account diff --git a/sdk/examples/wallet/spammer.rs b/sdk/examples/wallet/spammer.rs index a83058c888..997a21c6cf 100644 --- a/sdk/examples/wallet/spammer.rs +++ b/sdk/examples/wallet/spammer.rs @@ -45,7 +45,7 @@ async fn main() -> Result<()> { .with_coin_type(SHIMMER_COIN_TYPE) .finish() .await?; - let account = get_or_create_account(&wallet, ACCOUNT_ALIAS).await?; + let account = wallet.get_or_create_account(ACCOUNT_ALIAS).await?; let recv_address = *account.addresses().await?[0].address(); println!("Recv address: {}", recv_address); @@ -141,15 +141,6 @@ async fn main() -> Result<()> { Ok(()) } -async fn get_or_create_account(wallet: &Wallet, alias: &str) -> Result { - Ok(if let Ok(account) = wallet.get_account(alias).await { - account - } else { - println!("Creating account '{alias}'"); - wallet.create_account().with_alias(alias).finish().await? - }) -} - async fn ensure_enough_funds(account: &Account, bech32_address: &Bech32Address) -> Result<()> { let balance = account.sync(None).await?; let available_funds = balance.base_coin().available(); diff --git a/sdk/examples/wallet/split_funds.rs b/sdk/examples/wallet/split_funds.rs index 262d418426..963ca287cf 100644 --- a/sdk/examples/wallet/split_funds.rs +++ b/sdk/examples/wallet/split_funds.rs @@ -43,7 +43,7 @@ async fn main() -> Result<()> { .await?; // Get account or create a new one - let account = create_account(&wallet, "Alice").await?; + let account = wallet.get_or_create_account("Alice").await?; let _ = ensure_enough_addresses(&account, ADDRESSES_TO_SPLIT_FUNDS).await?; @@ -109,15 +109,6 @@ async fn main() -> Result<()> { Ok(()) } -async fn create_account(wallet: &Wallet, alias: &str) -> Result { - Ok(if let Ok(account) = wallet.get_account(alias).await { - account - } else { - println!("Creating account '{alias}'"); - wallet.create_account().with_alias(alias).finish().await? - }) -} - async fn sync_print_balance(account: &Account) -> Result<()> { let alias = account.alias().await; let now = tokio::time::Instant::now(); diff --git a/sdk/examples/wallet/storage.rs b/sdk/examples/wallet/storage.rs index 677bfaeb9c..3ea3fe36e5 100644 --- a/sdk/examples/wallet/storage.rs +++ b/sdk/examples/wallet/storage.rs @@ -37,7 +37,7 @@ async fn main() -> Result<()> { .await?; // Get account or create a new one - let account = get_or_create_account(&wallet, "Alice").await?; + let account = wallet.get_or_create_account("Alice").await?; let addresses = generate_max_addresses(&account, MAX_ADDRESSES_TO_GENERATE).await?; let bech32_addresses = addresses @@ -57,15 +57,6 @@ async fn main() -> Result<()> { Ok(()) } -async fn get_or_create_account(wallet: &Wallet, alias: &str) -> Result { - Ok(if let Ok(account) = wallet.get_account(alias).await { - account - } else { - println!("Creating account '{alias}'"); - wallet.create_account().with_alias(alias).finish().await? - }) -} - async fn generate_max_addresses(account: &Account, max: usize) -> Result> { let alias = account.alias().await; if account.addresses().await?.len() < max { diff --git a/sdk/examples/wallet/wallet.rs b/sdk/examples/wallet/wallet.rs index 3e4adf1c77..0e2dcdb191 100644 --- a/sdk/examples/wallet/wallet.rs +++ b/sdk/examples/wallet/wallet.rs @@ -39,7 +39,7 @@ async fn main() -> Result<()> { let wallet = create_wallet().await?; - let account = get_or_create_account(&wallet, "Alice").await?; + let account = wallet.get_or_create_account("Alice").await?; print_accounts(&wallet).await?; generate_addresses(&account, MAX_ADDRESSES_TO_GENERATE).await?; @@ -72,16 +72,6 @@ async fn create_wallet() -> Result { .await } -async fn get_or_create_account(wallet: &Wallet, alias: &str) -> Result { - let account = if let Ok(account) = wallet.get_account(alias).await { - account - } else { - println!("Creating account '{alias}'"); - wallet.create_account().with_alias(alias).finish().await? - }; - Ok(account) -} - async fn print_accounts(wallet: &Wallet) -> Result<()> { let accounts = wallet.get_accounts().await?; println!("Accounts:"); diff --git a/sdk/src/types/block/output/feature/mod.rs b/sdk/src/types/block/output/feature/mod.rs index 47bc273e43..274bc81b2b 100644 --- a/sdk/src/types/block/output/feature/mod.rs +++ b/sdk/src/types/block/output/feature/mod.rs @@ -38,12 +38,12 @@ pub enum Feature { impl PartialOrd for Feature { fn partial_cmp(&self, other: &Self) -> Option { - self.kind().partial_cmp(&other.kind()) + Some(self.cmp(other)) } } impl Ord for Feature { fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.partial_cmp(other).unwrap() + self.kind().cmp(&other.kind()) } } diff --git a/sdk/src/types/block/output/native_token.rs b/sdk/src/types/block/output/native_token.rs index bb07db9812..610ff52c82 100644 --- a/sdk/src/types/block/output/native_token.rs +++ b/sdk/src/types/block/output/native_token.rs @@ -53,12 +53,12 @@ impl NativeToken { impl PartialOrd for NativeToken { fn partial_cmp(&self, other: &Self) -> Option { - self.token_id.partial_cmp(&other.token_id) + Some(self.cmp(other)) } } impl Ord for NativeToken { fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.partial_cmp(other).unwrap() + self.token_id.cmp(&other.token_id) } } diff --git a/sdk/src/types/block/output/unlock_condition/mod.rs b/sdk/src/types/block/output/unlock_condition/mod.rs index b92b9a02f7..4b044b00be 100644 --- a/sdk/src/types/block/output/unlock_condition/mod.rs +++ b/sdk/src/types/block/output/unlock_condition/mod.rs @@ -55,12 +55,12 @@ pub enum UnlockCondition { impl PartialOrd for UnlockCondition { fn partial_cmp(&self, other: &Self) -> Option { - self.kind().partial_cmp(&other.kind()) + Some(self.cmp(other)) } } impl Ord for UnlockCondition { fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.partial_cmp(other).unwrap() + self.kind().cmp(&other.kind()) } } diff --git a/sdk/src/types/block/payload/milestone/option/mod.rs b/sdk/src/types/block/payload/milestone/option/mod.rs index 30e7fdf856..0b02056f95 100644 --- a/sdk/src/types/block/payload/milestone/option/mod.rs +++ b/sdk/src/types/block/payload/milestone/option/mod.rs @@ -46,12 +46,12 @@ impl MilestoneOption { impl PartialOrd for MilestoneOption { fn partial_cmp(&self, other: &Self) -> Option { - self.kind().partial_cmp(&other.kind()) + Some(self.cmp(other)) } } impl Ord for MilestoneOption { fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.partial_cmp(other).unwrap() + self.kind().cmp(&other.kind()) } } diff --git a/sdk/src/wallet/core/operations/get_account.rs b/sdk/src/wallet/core/operations/get_account.rs index 16a9bd7d67..463207333e 100644 --- a/sdk/src/wallet/core/operations/get_account.rs +++ b/sdk/src/wallet/core/operations/get_account.rs @@ -44,3 +44,16 @@ impl Wallet { )?)) } } + +impl Wallet +where + crate::wallet::Error: From, +{ + pub async fn get_or_create_account(&self, alias: impl Into + Send) -> crate::wallet::Result> { + let alias = alias.into(); + match self.get_account(&alias).await { + Err(crate::wallet::Error::AccountNotFound(_)) => self.create_account().with_alias(alias).finish().await, + res => res, + } + } +} From b8a06c4e65ae43671d3bf65ac35e954a15b1e8ee Mon Sep 17 00:00:00 2001 From: Brord van Wierst Date: Fri, 4 Aug 2023 16:27:18 +0200 Subject: [PATCH 09/22] Feat/cli: added mnemonic gen from console only (#983) * added mnemonic gen from console only * made range more readable * changelog * undecisive person options moved to give a second try --------- Co-authored-by: Alexandcoats --- cli/CHANGELOG.md | 4 ++++ cli/src/command/account.rs | 3 ++- cli/src/command/wallet.rs | 16 +++++++++----- cli/src/helper.rs | 45 ++++++++++++++++++++++++++------------ cli/src/wallet.rs | 7 ++++-- 5 files changed, 53 insertions(+), 22 deletions(-) diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 172c5ad0d6..a742c457d3 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -21,6 +21,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 1.1.0 - 2023-MM-DD +### Changed + +- `WalletCommand::Mnemonic` now takes 2 optional arguments to avoid user interaction; + ### Added - `WalletCommand::Accounts` variant to list all available accounts in a wallet; diff --git a/cli/src/command/account.rs b/cli/src/command/account.rs index 6192099b14..99473f6c53 100644 --- a/cli/src/command/account.rs +++ b/cli/src/command/account.rs @@ -182,7 +182,8 @@ pub enum AccountCommand { token_id: String, /// Amount to send, e.g. 1000000. amount: String, - /// Whether to gift the storage deposit for the output or not, e.g. ` true`. + /// Whether to gift the storage deposit for the output or not, e.g. `true`. + #[arg(value_parser = clap::builder::BoolishValueParser::new())] gift_storage_deposit: Option, }, /// Send an NFT. diff --git a/cli/src/command/wallet.rs b/cli/src/command/wallet.rs index 0200637b7a..9b9d386219 100644 --- a/cli/src/command/wallet.rs +++ b/cli/src/command/wallet.rs @@ -3,7 +3,7 @@ use std::path::Path; -use clap::{Args, Parser, Subcommand}; +use clap::{builder::BoolishValueParser, Args, Parser, Subcommand}; use iota_sdk::{ client::{ constants::SHIMMER_COIN_TYPE, @@ -63,7 +63,14 @@ pub enum WalletCommand { path: Option, }, /// Generate a random mnemonic. - Mnemonic, + Mnemonic { + // Output the mnemonic to the specified file. + #[arg(long)] + output_file_name: Option, + // Output the mnemonic to the stdout. + #[arg(long, num_args = 0..=1, default_missing_value = Some("true"), value_parser = BoolishValueParser::new())] + output_stdout: Option, + }, /// Create a new account. NewAccount { /// Account alias, next available account index if not provided. @@ -194,9 +201,8 @@ pub async fn migrate_stronghold_snapshot_v2_to_v3_command(path: Option) Ok(()) } -pub async fn mnemonic_command() -> Result<(), Error> { - generate_mnemonic().await?; - +pub async fn mnemonic_command(output_file_name: Option, output_stdout: Option) -> Result<(), Error> { + generate_mnemonic(output_file_name, output_stdout).await?; Ok(()) } diff --git a/cli/src/helper.rs b/cli/src/helper.rs index 05f84293f2..9449ad3302 100644 --- a/cli/src/helper.rs +++ b/cli/src/helper.rs @@ -121,7 +121,7 @@ pub async fn enter_or_generate_mnemonic() -> Result { .interact_on(&Term::stderr())?; let mnemnonic = match selected_choice { - 0 => generate_mnemonic().await?, + 0 => generate_mnemonic(None, None).await?, 1 => enter_mnemonic()?, _ => unreachable!(), }; @@ -129,28 +129,45 @@ pub async fn enter_or_generate_mnemonic() -> Result { Ok(mnemnonic) } -pub async fn generate_mnemonic() -> Result { +pub async fn generate_mnemonic( + output_file_name: Option, + output_stdout: Option, +) -> Result { let mnemonic = iota_sdk::client::generate_mnemonic()?; println_log_info!("Mnemonic has been generated."); - let choices = [ - "Write it to the console only", - "Write it to a file only", - "Write it to the console and a file", - ]; - let selected_choice = Select::with_theme(&ColorfulTheme::default()) - .with_prompt("Select how to proceed with it") - .items(&choices) - .default(0) - .interact_on(&Term::stderr())?; + let selected_choice = match (&output_file_name, &output_stdout) { + // Undecided, we give the user a choice + (None, None) | (None, Some(false)) => { + let choices = [ + "Write it to the console only", + "Write it to a file only", + "Write it to the console and a file", + ]; + + Select::with_theme(&ColorfulTheme::default()) + .with_prompt("Select how to proceed with it") + .items(&choices) + .default(0) + .interact_on(&Term::stderr())? + } + // Only console + (None, Some(true)) => 0, + // Only file + (Some(_), Some(false)) | (Some(_), None) => 1, + // File and console + (Some(_), Some(true)) => 2, + }; if [0, 2].contains(&selected_choice) { println!("YOUR MNEMONIC:"); println!("{}", mnemonic.as_ref()); } if [1, 2].contains(&selected_choice) { - write_mnemonic_to_file(DEFAULT_MNEMONIC_FILE_PATH, &mnemonic).await?; - println_log_info!("Mnemonic has been written to '{DEFAULT_MNEMONIC_FILE_PATH}'."); + let file_path = output_file_name.unwrap_or(DEFAULT_MNEMONIC_FILE_PATH.to_string()); + + write_mnemonic_to_file(&file_path, &mnemonic).await?; + println_log_info!("Mnemonic has been written to '{file_path}'."); } println_log_info!("IMPORTANT:"); diff --git a/cli/src/wallet.rs b/cli/src/wallet.rs index 8dc2662f0e..6713515e12 100644 --- a/cli/src/wallet.rs +++ b/cli/src/wallet.rs @@ -58,8 +58,11 @@ pub async fn new_wallet(cli: WalletCli) -> Result<(Option, Option { - mnemonic_command().await?; + WalletCommand::Mnemonic { + output_file_name, + output_stdout, + } => { + mnemonic_command(output_file_name, output_stdout).await?; return Ok((None, None)); } WalletCommand::NodeInfo => { From 7c40a44f066b55fd55ba7dd019c133a466bfb067 Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Wed, 16 Aug 2023 15:28:46 +0200 Subject: [PATCH 10/22] Fix wallet accounts example alias (#1022) --- sdk/examples/wallet/accounts.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/examples/wallet/accounts.rs b/sdk/examples/wallet/accounts.rs index 82ce5e4a6c..21d9541bbe 100644 --- a/sdk/examples/wallet/accounts.rs +++ b/sdk/examples/wallet/accounts.rs @@ -45,7 +45,7 @@ async fn main() -> Result<()> { // Get or create second account let alias2 = "Bob"; - let account2 = wallet.get_or_create_account("Alice").await?; + let account2 = wallet.get_or_create_account(alias2).await?; let accounts = wallet.get_accounts().await?; println!("WALLET ACCOUNTS:"); From af19a14fb9e9ba54877d46f5a7ec776ba2cce344 Mon Sep 17 00:00:00 2001 From: Alexandcoats Date: Thu, 17 Aug 2023 11:25:08 -0400 Subject: [PATCH 11/22] Allow selecting transactions by index (#1033) * Allow selecting transactions by index * Update cli/src/command/account.rs Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> * changelog * more changelog * Update cli/src/command/account.rs Co-authored-by: Thibault Martinez --------- Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> Co-authored-by: Thibault Martinez --- cli/CHANGELOG.md | 9 ++++---- cli/src/account.rs | 2 +- cli/src/command/account.rs | 46 ++++++++++++++++++++++++++++---------- 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index a742c457d3..46bb8e0c8c 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -21,16 +21,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 1.1.0 - 2023-MM-DD -### Changed - -- `WalletCommand::Mnemonic` now takes 2 optional arguments to avoid user interaction; - ### Added - `WalletCommand::Accounts` variant to list all available accounts in a wallet; - `addresses` now additionally prints the hex version of the address; - `outputs`, `unspent-outputs` print a list that includes number and type of the output; +### Changed + +- `WalletCommand::Mnemonic` now takes 2 optional arguments to avoid user interaction; +- `AccountCommand::Transaction` now accepts either an index or an ID; + ## 1.0.0 - 2023-07-27 First release of the `cli-wallet`. diff --git a/cli/src/account.rs b/cli/src/account.rs index f5a63acfa1..8491b370a1 100644 --- a/cli/src/account.rs +++ b/cli/src/account.rs @@ -172,7 +172,7 @@ pub async fn account_prompt_internal( } => send_native_token_command(account, address, token_id, amount, gift_storage_deposit).await, AccountCommand::SendNft { address, nft_id } => send_nft_command(account, address, nft_id).await, AccountCommand::Sync => sync_command(account).await, - AccountCommand::Transaction { transaction_id } => transaction_command(account, &transaction_id).await, + AccountCommand::Transaction { selector } => transaction_command(account, selector).await, AccountCommand::Transactions { show_details } => transactions_command(account, show_details).await, AccountCommand::UnspentOutputs => unspent_outputs_command(account).await, AccountCommand::Vote { event_id, answers } => vote_command(account, event_id, answers).await, diff --git a/cli/src/command/account.rs b/cli/src/command/account.rs index 99473f6c53..fddb6dc806 100644 --- a/cli/src/command/account.rs +++ b/cli/src/command/account.rs @@ -195,11 +195,12 @@ pub enum AccountCommand { }, /// Synchronize the account. Sync, - /// Show the details of the transaction. + /// Show the details of a transaction. #[clap(visible_alias = "tx")] Transaction { - /// Transaction ID to be displayed e.g. 0x84fe6b1796bddc022c9bc40206f0a692f4536b02aa8c13140264e2e01a3b7e4b. - transaction_id: String, + /// Selector for transaction. + /// Either by ID (e.g. 0x84fe6b1796bddc022c9bc40206f0a692f4536b02aa8c13140264e2e01a3b7e4b) or index. + selector: TransactionSelector, }, /// List the account transactions. #[clap(visible_alias = "txs")] @@ -246,6 +247,25 @@ pub enum AccountCommand { VotingOutput, } +/// Select by transaction ID or list index +#[derive(Debug, Copy, Clone)] +pub enum TransactionSelector { + Id(TransactionId), + Index(usize), +} + +impl FromStr for TransactionSelector { + type Err = Error; + + fn from_str(s: &str) -> Result { + 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?; @@ -741,15 +761,17 @@ pub async fn sync_command(account: &Account) -> Result<(), Error> { } /// `transaction` command -pub async fn transaction_command(account: &Account, transaction_id_str: &str) -> Result<(), Error> { - let transaction_id = TransactionId::from_str(transaction_id_str)?; - let maybe_transaction = account - .transactions() - .await - .into_iter() - .find(|tx| tx.transaction_id == transaction_id); - - if let Some(tx) = maybe_transaction { +pub async fn transaction_command(account: &Account, selector: TransactionSelector) -> Result<(), Error> { + let mut transactions = account.transactions().await; + 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.into_iter().nth(index) + } + }; + + if let Some(tx) = transaction { println_log_info!("{:#?}", tx); } else { println_log_info!("No transaction found"); From 6eb0b6a02436ea873b7da8c83354733a97f93e22 Mon Sep 17 00:00:00 2001 From: Pawel Date: Fri, 18 Aug 2023 18:45:08 +0200 Subject: [PATCH 12/22] Add lint check for examples (#1003) * ci: add lint check for python bindings --- .github/workflows/bindings-python.yml | 17 ++++++++++++----- bindings/python/examples/.pylintrc | 10 ++++++++++ .../python/examples/client/04_get_output.py | 7 ++++--- .../examples/client/05_get_address_balance.py | 12 +++++++----- .../python/examples/client/06_simple_block.py | 6 ++++-- .../examples/client/07_get_block_data.py | 8 +++++--- .../python/examples/client/08_data_block.py | 8 +++++--- .../python/examples/client/09_transaction.py | 6 ++++-- bindings/python/examples/client/10_mint_nft.py | 8 ++++++-- bindings/python/examples/client/build_alias.py | 9 +++++++-- bindings/python/examples/client/build_basic.py | 15 ++++++++++----- .../python/examples/client/build_foundry.py | 7 +++++-- bindings/python/examples/client/build_nft.py | 8 ++++++-- .../python/examples/client/custom_plugin.py | 8 +++++--- .../python/examples/client/get_raw_block.py | 6 ++++-- bindings/python/examples/client/logger.py | 9 ++++++--- bindings/python/examples/client/mqtt.py | 10 +++++++--- .../python/examples/client/post_raw_block.py | 6 ++++-- .../examples/client/submit_and_read_block.py | 14 ++++++++------ .../examples/exchange/1_create_account.py | 7 +++++-- .../examples/exchange/2_generate_address.py | 8 +++++--- .../examples/exchange/3_check_balance.py | 8 +++++--- .../examples/exchange/4_listen_events.py | 11 ++++++++--- .../python/examples/exchange/5_send_amount.py | 6 ++++-- .../accounts_and_addresses/check_balance.py | 6 ++++-- .../consolidate_outputs.py | 11 +++++------ .../accounts_and_addresses/create_account.py | 6 ++++-- .../accounts_and_addresses/create_address.py | 8 +++++--- .../accounts_and_addresses/list_accounts.py | 6 ++++-- .../accounts_and_addresses/list_addresses.py | 6 ++++-- .../accounts_and_addresses/list_outputs.py | 6 ++++-- .../list_transactions.py | 6 ++++-- .../advanced_transaction.py | 16 +++++++++++++--- .../advanced_transactions/claim_transaction.py | 8 +++++--- .../send_micro_transaction.py | 6 ++++-- .../python/examples/how_tos/alias/create.py | 6 ++++-- .../python/examples/how_tos/alias/destroy.py | 6 ++++-- .../how_tos/alias_wallet/request_funds.py | 6 ++++-- .../how_tos/alias_wallet/transaction.py | 11 +++++++---- .../examples/how_tos/client/get_health.py | 6 ++++-- .../python/examples/how_tos/client/get_info.py | 6 ++++-- .../examples/how_tos/client/get_outputs.py | 6 ++++-- .../examples/how_tos/native_tokens/burn.py | 6 ++++-- .../examples/how_tos/native_tokens/create.py | 6 ++++-- .../how_tos/native_tokens/destroy_foundry.py | 6 ++++-- .../examples/how_tos/native_tokens/melt.py | 6 ++++-- .../examples/how_tos/native_tokens/mint.py | 6 ++++-- .../examples/how_tos/native_tokens/send.py | 6 ++++-- .../nft_collection/00_mint_issuer_nft.py | 6 ++++-- .../nft_collection/01_mint_collection_nft.py | 13 ++++++++----- .../python/examples/how_tos/nfts/burn_nft.py | 6 ++++-- .../python/examples/how_tos/nfts/mint_nft.py | 6 ++++-- .../python/examples/how_tos/nfts/send_nft.py | 6 ++++-- .../examples/how_tos/outputs/features.py | 16 ++++++++++++++-- .../how_tos/outputs/unlock_conditions.py | 18 ++++++++++++++++-- .../sign_and_verify_ed25519/sign_ed25519.py | 7 +++++-- .../examples/how_tos/sign_evm/sign_evm.py | 7 +++++-- .../simple_transaction/request_funds.py | 6 ++++-- .../simple_transaction/simple_transaction.py | 6 ++++-- .../secret_manager/generate_addresses.py | 6 ++++-- .../examples/secret_manager/ledger_nano.py | 4 ++-- .../examples/secret_manager/stronghold.py | 6 ++++-- .../examples/wallet/12-prepare_output.py | 6 ++++-- .../wallet/13-check-unlock-conditions.py | 7 +++++-- bindings/python/examples/wallet/backup.py | 8 +++++--- .../python/examples/wallet/create_alias.py | 6 ++++-- bindings/python/examples/wallet/get_client.py | 6 ++++-- .../python/examples/wallet/getting-started.py | 7 +++++-- bindings/python/examples/wallet/logger.py | 8 ++++++-- .../migrate-stronghold-snapshot-v2-to-v3.py | 11 +++++++---- .../offline_signing/0_generate_addresses.py | 8 +++++--- .../offline_signing/1_prepare_transaction.py | 13 ++++++++----- .../offline_signing/2_sign_transaction.py | 12 +++++++----- .../offline_signing/3_send_transaction.py | 10 ++++++---- .../python/examples/wallet/recover_accounts.py | 6 ++++-- .../python/examples/wallet/restore_backup.py | 8 +++++--- .../examples/wallet/transaction_options.py | 7 +++++-- bindings/python/tox.ini | 15 ++++++++++++--- 78 files changed, 431 insertions(+), 202 deletions(-) create mode 100644 bindings/python/examples/.pylintrc diff --git a/.github/workflows/bindings-python.yml b/.github/workflows/bindings-python.yml index d61ac6d32c..922e2635a6 100644 --- a/.github/workflows/bindings-python.yml +++ b/.github/workflows/bindings-python.yml @@ -40,7 +40,7 @@ concurrency: jobs: lint: - name: PEP8 style check + name: Python PEP8 format runs-on: ubuntu-latest strategy: fail-fast: false @@ -63,12 +63,13 @@ jobs: python3 -m pip install --upgrade setuptools pip wheel python3 -m pip install tox-gh-actions - - name: Run Tox + - name: Run tox format check working-directory: bindings/python - run: tox -e lint + run: tox -e format + test: - name: Test + name: Linter & Tests needs: lint if: ${{ ! github.event.schedule }} runs-on: ${{ matrix.os }} @@ -111,6 +112,12 @@ jobs: sudo apt-get update sudo apt-get install libudev-dev libusb-1.0-0-dev - - name: Run Tox + - name: Run linter for examples + if: ${{ startsWith(matrix.os, 'ubuntu-latest') }} + working-directory: bindings/python + run: tox -e lint-examples + + - name: Run tests working-directory: bindings/python run: tox + diff --git a/bindings/python/examples/.pylintrc b/bindings/python/examples/.pylintrc new file mode 100644 index 0000000000..b87e66105a --- /dev/null +++ b/bindings/python/examples/.pylintrc @@ -0,0 +1,10 @@ +[MESSAGES CONTROL] +disable=missing-module-docstring, + line-too-long, # enforced by PEP8 format checker + broad-exception-raised, + invalid-name, # files that starts with number + duplicate-code, + consider-using-with, + consider-using-f-string + + diff --git a/bindings/python/examples/client/04_get_output.py b/bindings/python/examples/client/04_get_output.py index 007a9eae8f..e0eadf27af 100644 --- a/bindings/python/examples/client/04_get_output.py +++ b/bindings/python/examples/client/04_get_output.py @@ -1,9 +1,10 @@ -from iota_sdk import Client -from dotenv import load_dotenv - import json import os +from dotenv import load_dotenv + +from iota_sdk import Client + load_dotenv() node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') diff --git a/bindings/python/examples/client/05_get_address_balance.py b/bindings/python/examples/client/05_get_address_balance.py index 1d5ba92666..1c52cb8c99 100644 --- a/bindings/python/examples/client/05_get_address_balance.py +++ b/bindings/python/examples/client/05_get_address_balance.py @@ -1,7 +1,9 @@ -from iota_sdk import Client, NodeIndexerAPI -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Client, NodeIndexerAPI + load_dotenv() node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') @@ -9,9 +11,9 @@ # Create a Client instance client = Client(nodes=[node_url]) -address = 'rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy' +ADDRESS = 'rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy' query_parameters = NodeIndexerAPI.QueryParameters( - address, + ADDRESS, has_expiration=False, has_timelock=False, has_storage_deposit_return=False @@ -37,4 +39,4 @@ native_tokens.append(output.nativeTokens) print( - f'Outputs controlled by {address} have {total_amount} glow and native tokens: {native_tokens}') + f'Outputs controlled by {ADDRESS} have {total_amount} glow and native tokens: {native_tokens}') diff --git a/bindings/python/examples/client/06_simple_block.py b/bindings/python/examples/client/06_simple_block.py index 62658bcbdf..74605a098c 100644 --- a/bindings/python/examples/client/06_simple_block.py +++ b/bindings/python/examples/client/06_simple_block.py @@ -1,7 +1,9 @@ -from iota_sdk import Client -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Client + load_dotenv() node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') diff --git a/bindings/python/examples/client/07_get_block_data.py b/bindings/python/examples/client/07_get_block_data.py index 7a1280e867..595ac43fa0 100644 --- a/bindings/python/examples/client/07_get_block_data.py +++ b/bindings/python/examples/client/07_get_block_data.py @@ -1,8 +1,10 @@ -from dataclasses import asdict -from iota_sdk import Client -from dotenv import load_dotenv import json import os +from dataclasses import asdict + +from dotenv import load_dotenv + +from iota_sdk import Client load_dotenv() diff --git a/bindings/python/examples/client/08_data_block.py b/bindings/python/examples/client/08_data_block.py index 3a52a41f66..1becf183d8 100644 --- a/bindings/python/examples/client/08_data_block.py +++ b/bindings/python/examples/client/08_data_block.py @@ -1,8 +1,10 @@ -from dataclasses import asdict -from iota_sdk import Client, utf8_to_hex, hex_to_utf8 -from dotenv import load_dotenv import json import os +from dataclasses import asdict + +from dotenv import load_dotenv + +from iota_sdk import Client, hex_to_utf8, utf8_to_hex load_dotenv() diff --git a/bindings/python/examples/client/09_transaction.py b/bindings/python/examples/client/09_transaction.py index 45369a6fa5..47f9c9f215 100644 --- a/bindings/python/examples/client/09_transaction.py +++ b/bindings/python/examples/client/09_transaction.py @@ -1,7 +1,9 @@ -from iota_sdk import Client, MnemonicSecretManager, AddressAndAmount -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import AddressAndAmount, Client, MnemonicSecretManager + load_dotenv() node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') diff --git a/bindings/python/examples/client/10_mint_nft.py b/bindings/python/examples/client/10_mint_nft.py index 415eb8e336..c571c46584 100644 --- a/bindings/python/examples/client/10_mint_nft.py +++ b/bindings/python/examples/client/10_mint_nft.py @@ -1,7 +1,11 @@ -from iota_sdk import * -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import (AddressUnlockCondition, Client, Ed25519Address, + MetadataFeature, MnemonicSecretManager, Utils, + utf8_to_hex) + load_dotenv() node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') diff --git a/bindings/python/examples/client/build_alias.py b/bindings/python/examples/client/build_alias.py index bc8dbfe9e0..ae49d40dba 100644 --- a/bindings/python/examples/client/build_alias.py +++ b/bindings/python/examples/client/build_alias.py @@ -1,8 +1,13 @@ -from iota_sdk import * -from dotenv import load_dotenv import json import os +from dotenv import load_dotenv + +from iota_sdk import (Client, Ed25519Address, GovernorAddressUnlockCondition, + IssuerFeature, MetadataFeature, SenderFeature, + StateControllerAddressUnlockCondition, Utils, + utf8_to_hex) + load_dotenv() node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') diff --git a/bindings/python/examples/client/build_basic.py b/bindings/python/examples/client/build_basic.py index 5529bfedd7..43ec8db0d2 100644 --- a/bindings/python/examples/client/build_basic.py +++ b/bindings/python/examples/client/build_basic.py @@ -1,8 +1,13 @@ -from iota_sdk import * -from dotenv import load_dotenv import json import os +from dotenv import load_dotenv + +from iota_sdk import (AddressUnlockCondition, Client, Ed25519Address, + ExpirationUnlockCondition, MetadataFeature, + SenderFeature, StorageDepositReturnUnlockCondition, + TagFeature, TimelockUnlockCondition, Utils, utf8_to_hex) + load_dotenv() node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') @@ -43,7 +48,7 @@ unlock_conditions=[ address_unlock_condition, StorageDepositReturnUnlockCondition( - return_address=Ed25519Address(hex_address), + returnAddress=Ed25519Address(hex_address), amount=1000000 ) ], @@ -56,8 +61,8 @@ unlock_conditions=[ address_unlock_condition, ExpirationUnlockCondition( - return_address=Ed25519Address(hex_address), - unix_time=1 + returnAddress=Ed25519Address(hex_address), + unixTime=1 ) ], amount=1000000, diff --git a/bindings/python/examples/client/build_foundry.py b/bindings/python/examples/client/build_foundry.py index 25b1cd9857..c9b0e79b1c 100644 --- a/bindings/python/examples/client/build_foundry.py +++ b/bindings/python/examples/client/build_foundry.py @@ -1,8 +1,11 @@ -from iota_sdk import * -from dotenv import load_dotenv import json import os +from dotenv import load_dotenv + +from iota_sdk import (AliasAddress, Client, + ImmutableAliasAddressUnlockCondition, SimpleTokenScheme) + load_dotenv() node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') diff --git a/bindings/python/examples/client/build_nft.py b/bindings/python/examples/client/build_nft.py index 0a0c542d1c..e0f3cb631e 100644 --- a/bindings/python/examples/client/build_nft.py +++ b/bindings/python/examples/client/build_nft.py @@ -1,8 +1,12 @@ -from iota_sdk import * -from dotenv import load_dotenv import json import os +from dotenv import load_dotenv + +from iota_sdk import (AddressUnlockCondition, Client, Ed25519Address, + IssuerFeature, MetadataFeature, SenderFeature, + TagFeature, Utils, utf8_to_hex) + load_dotenv() node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') diff --git a/bindings/python/examples/client/custom_plugin.py b/bindings/python/examples/client/custom_plugin.py index 37938d04ca..79292ce7c8 100644 --- a/bindings/python/examples/client/custom_plugin.py +++ b/bindings/python/examples/client/custom_plugin.py @@ -1,9 +1,11 @@ -from iota_sdk import Client, init_logger -from dotenv import load_dotenv - import json import os +from dotenv import load_dotenv + +# pylint: disable=no-name-in-module +from iota_sdk import Client, init_logger + load_dotenv() log_config = { 'name': 'client.log', diff --git a/bindings/python/examples/client/get_raw_block.py b/bindings/python/examples/client/get_raw_block.py index 2f5b507d81..51006f60ec 100644 --- a/bindings/python/examples/client/get_raw_block.py +++ b/bindings/python/examples/client/get_raw_block.py @@ -1,7 +1,9 @@ -from iota_sdk import Client -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Client + load_dotenv() node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') diff --git a/bindings/python/examples/client/logger.py b/bindings/python/examples/client/logger.py index e40fcb2e5e..3d33d60ec8 100644 --- a/bindings/python/examples/client/logger.py +++ b/bindings/python/examples/client/logger.py @@ -1,7 +1,10 @@ -from iota_sdk import Client, init_logger -from dotenv import load_dotenv -import os import json +import os + +from dotenv import load_dotenv + +# pylint: disable=no-name-in-module +from iota_sdk import Client, init_logger load_dotenv() diff --git a/bindings/python/examples/client/mqtt.py b/bindings/python/examples/client/mqtt.py index e73e5a669c..c948af27cd 100644 --- a/bindings/python/examples/client/mqtt.py +++ b/bindings/python/examples/client/mqtt.py @@ -3,11 +3,13 @@ # This example shows how to listen to MQTT events of a node. -from iota_sdk import Client -from dotenv import load_dotenv +import json import os import threading -import json + +from dotenv import load_dotenv + +from iota_sdk import Client load_dotenv() @@ -22,8 +24,10 @@ def callback(event): + """Callback function for the MQTT listener""" event_dict = json.loads(event) print(event_dict) + # pylint: disable=global-statement global received_events received_events += 1 if received_events > 10: diff --git a/bindings/python/examples/client/post_raw_block.py b/bindings/python/examples/client/post_raw_block.py index 102bb33234..05a986a22f 100644 --- a/bindings/python/examples/client/post_raw_block.py +++ b/bindings/python/examples/client/post_raw_block.py @@ -1,7 +1,9 @@ -from iota_sdk import Client, MnemonicSecretManager -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Client + load_dotenv() node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') diff --git a/bindings/python/examples/client/submit_and_read_block.py b/bindings/python/examples/client/submit_and_read_block.py index fac01b9397..7aaa33ea8f 100644 --- a/bindings/python/examples/client/submit_and_read_block.py +++ b/bindings/python/examples/client/submit_and_read_block.py @@ -6,11 +6,13 @@ # 2) Submit the block to the Shimmer test network # 3) Use the block ID to read the payload back from the network +import os + +from dotenv import load_dotenv + # Import the python iota client # Make sure you have first installed it with `pip install iota_sdk` from iota_sdk import Client, hex_to_utf8, utf8_to_hex -from dotenv import load_dotenv -import os load_dotenv() @@ -40,7 +42,7 @@ tag_hex = utf8_to_hex(tag) message_hex = utf8_to_hex(message) -print(f'\nYour prepared block content is:') +print('\nYour prepared block content is:') print(f' tag: {tag}') print(f' tag converted to hex: {tag_hex}') print(f' message: {message}') @@ -61,10 +63,10 @@ block_id = blockIdAndBlock[0] block = blockIdAndBlock[1] -print(f'\nThe block ID for your submitted block is:') +print('\nThe block ID for your submitted block is:') print(f' {block_id}') -print(f'\nMetadata for your submitted block is:') +print('\nMetadata for your submitted block is:') print(f' {block}') ######################################################## @@ -86,7 +88,7 @@ # Unpackage the payload (from hex to text) message_out = hex_to_utf8(message_hex_out) -print(f'\nYour message, read from the Shimmer network:') +print('\nYour message, read from the Shimmer network:') print(f' {message_out}') # Or see the message online, with the testnet explorer. diff --git a/bindings/python/examples/exchange/1_create_account.py b/bindings/python/examples/exchange/1_create_account.py index 2ab4707e80..1b6347f6e5 100644 --- a/bindings/python/examples/exchange/1_create_account.py +++ b/bindings/python/examples/exchange/1_create_account.py @@ -3,10 +3,13 @@ # This example creates a new database and account. -from iota_sdk import Wallet, StrongholdSecretManager, SyncOptions, CoinType, ClientOptions -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import (ClientOptions, CoinType, StrongholdSecretManager, + SyncOptions, Wallet) + # This example uses secrets in environment variables for simplicity which # should not be done in production. load_dotenv() diff --git a/bindings/python/examples/exchange/2_generate_address.py b/bindings/python/examples/exchange/2_generate_address.py index 55ed957199..afd9257e37 100644 --- a/bindings/python/examples/exchange/2_generate_address.py +++ b/bindings/python/examples/exchange/2_generate_address.py @@ -3,10 +3,12 @@ # This example generates an address for an account. -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + # This example uses secrets in environment variables for simplicity which # should not be done in production. load_dotenv() @@ -23,4 +25,4 @@ account = wallet.get_account('Alice') address = account.generate_ed25519_addresses(1)[0] -print(f'Address:', address.address) +print('Address:', address.address) diff --git a/bindings/python/examples/exchange/3_check_balance.py b/bindings/python/examples/exchange/3_check_balance.py index c5a3430e1c..1cef817598 100644 --- a/bindings/python/examples/exchange/3_check_balance.py +++ b/bindings/python/examples/exchange/3_check_balance.py @@ -3,10 +3,12 @@ # This example gets the balance of an account. -from iota_sdk import Wallet, SyncOptions -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import SyncOptions, Wallet + # This example uses secrets in environment variables for simplicity which # should not be done in production. load_dotenv() @@ -19,7 +21,7 @@ account = wallet.get_account('Alice') addresses = account.addresses() -print(f'Addresses:', addresses) +print('Addresses:', addresses) # Set sync_only_most_basic_outputs to True if not interested in outputs that are timelocked, # have a storage deposit return, expiration or are nft/alias/foundry outputs. diff --git a/bindings/python/examples/exchange/4_listen_events.py b/bindings/python/examples/exchange/4_listen_events.py index d466d2d600..32d5cf5b42 100644 --- a/bindings/python/examples/exchange/4_listen_events.py +++ b/bindings/python/examples/exchange/4_listen_events.py @@ -3,12 +3,15 @@ # This example listens to the NewOutput event. -from iota_sdk import Wallet, SyncOptions, WalletEventType -from dotenv import load_dotenv import json import os +import sys import time +from dotenv import load_dotenv + +from iota_sdk import SyncOptions, Wallet, WalletEventType + # This example uses secrets in environment variables for simplicity which # should not be done in production. load_dotenv() @@ -24,11 +27,13 @@ def callback(event): + """Callback function for the event listener""" event_dict = json.loads(event) print('AccountIndex:', event_dict["accountIndex"]) print('Event:', event_dict["event"]) # Exit after receiving an event. + # pylint: disable=global-statement global received_event received_event = True @@ -47,7 +52,7 @@ def callback(event): # Sync every 5 seconds until the faucet transaction gets confirmed. for _ in range(100): if received_event: - exit() + sys.exit() time.sleep(5) # Sync to detect new outputs diff --git a/bindings/python/examples/exchange/5_send_amount.py b/bindings/python/examples/exchange/5_send_amount.py index 6e0dedbcba..c2e1a65cae 100644 --- a/bindings/python/examples/exchange/5_send_amount.py +++ b/bindings/python/examples/exchange/5_send_amount.py @@ -3,10 +3,12 @@ # This example sends tokens to an address. -from iota_sdk import Wallet, SyncOptions -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import SyncOptions, Wallet + # This example uses secrets in environment variables for simplicity which # should not be done in production. load_dotenv() diff --git a/bindings/python/examples/how_tos/accounts_and_addresses/check_balance.py b/bindings/python/examples/how_tos/accounts_and_addresses/check_balance.py index 6623b5b814..42e6c08c5a 100644 --- a/bindings/python/examples/how_tos/accounts_and_addresses/check_balance.py +++ b/bindings/python/examples/how_tos/accounts_and_addresses/check_balance.py @@ -1,8 +1,10 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import json import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + # This example checks the balance of an account. # This example uses secrets in environment variables for simplicity which diff --git a/bindings/python/examples/how_tos/accounts_and_addresses/consolidate_outputs.py b/bindings/python/examples/how_tos/accounts_and_addresses/consolidate_outputs.py index 33e2252392..cd0480a98c 100644 --- a/bindings/python/examples/how_tos/accounts_and_addresses/consolidate_outputs.py +++ b/bindings/python/examples/how_tos/accounts_and_addresses/consolidate_outputs.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet, Utils, ConsolidationParams -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import ConsolidationParams, Utils, Wallet + # In this example we will consolidate basic outputs from an account with only an AddressUnlockCondition by sending # them to the same address again. @@ -53,10 +55,7 @@ block_id = account.retry_transaction_until_included(transaction.transactionId) print( - 'Transaction included: {}/block/{}'.format( - os.environ['EXPLORER_URL'], - block_id - ) + f'Transaction included: {os.environ["EXPLORER_ID"]}/block/{block_id}' ) # Sync account diff --git a/bindings/python/examples/how_tos/accounts_and_addresses/create_account.py b/bindings/python/examples/how_tos/accounts_and_addresses/create_account.py index be327ae9cd..34b26d5866 100644 --- a/bindings/python/examples/how_tos/accounts_and_addresses/create_account.py +++ b/bindings/python/examples/how_tos/accounts_and_addresses/create_account.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet, StrongholdSecretManager, CoinType, ClientOptions -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import ClientOptions, CoinType, StrongholdSecretManager, Wallet + load_dotenv() # This example creates a new database and account diff --git a/bindings/python/examples/how_tos/accounts_and_addresses/create_address.py b/bindings/python/examples/how_tos/accounts_and_addresses/create_address.py index db1e5445a1..77a63e98ad 100644 --- a/bindings/python/examples/how_tos/accounts_and_addresses/create_address.py +++ b/bindings/python/examples/how_tos/accounts_and_addresses/create_address.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + load_dotenv() # This example generates a new address. @@ -16,4 +18,4 @@ account = wallet.get_account('Alice') address = account.generate_ed25519_addresses(1) -print(f'Generated address:', address[0].address) +print('Generated address:', address[0].address) diff --git a/bindings/python/examples/how_tos/accounts_and_addresses/list_accounts.py b/bindings/python/examples/how_tos/accounts_and_addresses/list_accounts.py index 2f5028b91c..abbb5756e9 100644 --- a/bindings/python/examples/how_tos/accounts_and_addresses/list_accounts.py +++ b/bindings/python/examples/how_tos/accounts_and_addresses/list_accounts.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + # This example lists all accounts in the wallet. # This example uses secrets in environment variables for simplicity which diff --git a/bindings/python/examples/how_tos/accounts_and_addresses/list_addresses.py b/bindings/python/examples/how_tos/accounts_and_addresses/list_addresses.py index 81e4f48814..38cb28430f 100644 --- a/bindings/python/examples/how_tos/accounts_and_addresses/list_addresses.py +++ b/bindings/python/examples/how_tos/accounts_and_addresses/list_addresses.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + # This example lists all addresses in the account. # This example uses secrets in environment variables for simplicity which diff --git a/bindings/python/examples/how_tos/accounts_and_addresses/list_outputs.py b/bindings/python/examples/how_tos/accounts_and_addresses/list_outputs.py index 6fe5e08284..bff985dfe6 100644 --- a/bindings/python/examples/how_tos/accounts_and_addresses/list_outputs.py +++ b/bindings/python/examples/how_tos/accounts_and_addresses/list_outputs.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + # In this example we will get outputs stored in the account # This example uses secrets in environment variables for simplicity which diff --git a/bindings/python/examples/how_tos/accounts_and_addresses/list_transactions.py b/bindings/python/examples/how_tos/accounts_and_addresses/list_transactions.py index e40d80efb1..cf45623a83 100644 --- a/bindings/python/examples/how_tos/accounts_and_addresses/list_transactions.py +++ b/bindings/python/examples/how_tos/accounts_and_addresses/list_transactions.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + # In this example we will list transactions # This example uses secrets in environment variables for simplicity which diff --git a/bindings/python/examples/how_tos/advanced_transactions/advanced_transaction.py b/bindings/python/examples/how_tos/advanced_transactions/advanced_transaction.py index 8a9e95e25a..25d64e04be 100644 --- a/bindings/python/examples/how_tos/advanced_transactions/advanced_transaction.py +++ b/bindings/python/examples/how_tos/advanced_transactions/advanced_transaction.py @@ -1,8 +1,18 @@ -from iota_sdk import * -from dotenv import load_dotenv +import datetime import os import time -import datetime + +from dotenv import load_dotenv + +from iota_sdk import ( + AddressUnlockCondition, + Client, + Ed25519Address, + Wallet, + Utils, + TimelockUnlockCondition, +) + load_dotenv() diff --git a/bindings/python/examples/how_tos/advanced_transactions/claim_transaction.py b/bindings/python/examples/how_tos/advanced_transactions/claim_transaction.py index f2956745f6..c1672ab01d 100644 --- a/bindings/python/examples/how_tos/advanced_transactions/claim_transaction.py +++ b/bindings/python/examples/how_tos/advanced_transactions/claim_transaction.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + load_dotenv() # In this example we will claim outputs that have additional unlock @@ -22,7 +24,7 @@ # Only the unspent outputs in the account output_ids = account.claimable_outputs('All') -print(f'Available outputs to claim:') +print('Available outputs to claim:') for output_id in output_ids: print(f'{output_id}') diff --git a/bindings/python/examples/how_tos/advanced_transactions/send_micro_transaction.py b/bindings/python/examples/how_tos/advanced_transactions/send_micro_transaction.py index 9d58fd6d9b..53f54117b9 100644 --- a/bindings/python/examples/how_tos/advanced_transactions/send_micro_transaction.py +++ b/bindings/python/examples/how_tos/advanced_transactions/send_micro_transaction.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + load_dotenv() # In this example we will send an amount below the minimum storage deposit diff --git a/bindings/python/examples/how_tos/alias/create.py b/bindings/python/examples/how_tos/alias/create.py index 4f9ac12bcb..599982ceb2 100644 --- a/bindings/python/examples/how_tos/alias/create.py +++ b/bindings/python/examples/how_tos/alias/create.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + load_dotenv() # In this example we will create an alias ouput diff --git a/bindings/python/examples/how_tos/alias/destroy.py b/bindings/python/examples/how_tos/alias/destroy.py index fb3e998caa..944fab3d83 100644 --- a/bindings/python/examples/how_tos/alias/destroy.py +++ b/bindings/python/examples/how_tos/alias/destroy.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + load_dotenv() # In this example we will destroy an alias output diff --git a/bindings/python/examples/how_tos/alias_wallet/request_funds.py b/bindings/python/examples/how_tos/alias_wallet/request_funds.py index e19b79a99e..b6bb9a44cd 100644 --- a/bindings/python/examples/how_tos/alias_wallet/request_funds.py +++ b/bindings/python/examples/how_tos/alias_wallet/request_funds.py @@ -1,8 +1,10 @@ -from iota_sdk import Wallet, Utils, SyncOptions, AliasSyncOptions -from dotenv import load_dotenv import os import time +from dotenv import load_dotenv + +from iota_sdk import AliasSyncOptions, SyncOptions, Utils, Wallet + # In this example we request funds to an alias wallet. load_dotenv() diff --git a/bindings/python/examples/how_tos/alias_wallet/transaction.py b/bindings/python/examples/how_tos/alias_wallet/transaction.py index 93c24344ae..d0e6786511 100644 --- a/bindings/python/examples/how_tos/alias_wallet/transaction.py +++ b/bindings/python/examples/how_tos/alias_wallet/transaction.py @@ -1,7 +1,10 @@ -from iota_sdk import Wallet, Utils, NodeIndexerAPI, SyncOptions, AliasSyncOptions, SendParams -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import (AliasSyncOptions, NodeIndexerAPI, SendParams, + SyncOptions, Utils, Wallet) + # In this example we send funds from an alias wallet. load_dotenv() @@ -31,14 +34,14 @@ # Find first output unlockable by the alias address query_parameters = NodeIndexerAPI.QueryParameters(alias_address) -input = wallet.get_client().basic_output_ids(query_parameters).items[0] +transaction_input = wallet.get_client().basic_output_ids(query_parameters).items[0] params = [SendParams( address='rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu', amount=1000000, )] options = { - 'mandatoryInputs': [input], + 'mandatoryInputs': [transaction_input], } transaction = account.send_with_params(params, options) account.retry_transaction_until_included(transaction.transactionId, None, None) diff --git a/bindings/python/examples/how_tos/client/get_health.py b/bindings/python/examples/how_tos/client/get_health.py index 90df03d3f5..b66fad5f44 100644 --- a/bindings/python/examples/how_tos/client/get_health.py +++ b/bindings/python/examples/how_tos/client/get_health.py @@ -1,7 +1,9 @@ -from iota_sdk import Client -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Client + load_dotenv() node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') diff --git a/bindings/python/examples/how_tos/client/get_info.py b/bindings/python/examples/how_tos/client/get_info.py index 99c453c8c1..6934ac5247 100644 --- a/bindings/python/examples/how_tos/client/get_info.py +++ b/bindings/python/examples/how_tos/client/get_info.py @@ -1,9 +1,11 @@ -from iota_sdk import Client import dataclasses -from dotenv import load_dotenv import json import os +from dotenv import load_dotenv + +from iota_sdk import Client + load_dotenv() node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') diff --git a/bindings/python/examples/how_tos/client/get_outputs.py b/bindings/python/examples/how_tos/client/get_outputs.py index 4d8561a64c..79f2c2fee0 100644 --- a/bindings/python/examples/how_tos/client/get_outputs.py +++ b/bindings/python/examples/how_tos/client/get_outputs.py @@ -1,8 +1,10 @@ -from iota_sdk import Client, NodeIndexerAPI -from dotenv import load_dotenv import json import os +from dotenv import load_dotenv + +from iota_sdk import Client, NodeIndexerAPI + load_dotenv() node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') diff --git a/bindings/python/examples/how_tos/native_tokens/burn.py b/bindings/python/examples/how_tos/native_tokens/burn.py index cf48b79f88..991d0a7739 100644 --- a/bindings/python/examples/how_tos/native_tokens/burn.py +++ b/bindings/python/examples/how_tos/native_tokens/burn.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet, HexStr -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + load_dotenv() # In this example we will burn native tokens diff --git a/bindings/python/examples/how_tos/native_tokens/create.py b/bindings/python/examples/how_tos/native_tokens/create.py index e35bbeecb9..4e3038616c 100644 --- a/bindings/python/examples/how_tos/native_tokens/create.py +++ b/bindings/python/examples/how_tos/native_tokens/create.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet, utf8_to_hex, CreateNativeTokenParams -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import CreateNativeTokenParams, Wallet, utf8_to_hex + load_dotenv() # In this example we will create native tokens diff --git a/bindings/python/examples/how_tos/native_tokens/destroy_foundry.py b/bindings/python/examples/how_tos/native_tokens/destroy_foundry.py index f8eba3785b..b8b5bf73cc 100644 --- a/bindings/python/examples/how_tos/native_tokens/destroy_foundry.py +++ b/bindings/python/examples/how_tos/native_tokens/destroy_foundry.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet, HexStr -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + load_dotenv() # In this example we will destroy a foundry diff --git a/bindings/python/examples/how_tos/native_tokens/melt.py b/bindings/python/examples/how_tos/native_tokens/melt.py index 18992f6782..79d7365641 100644 --- a/bindings/python/examples/how_tos/native_tokens/melt.py +++ b/bindings/python/examples/how_tos/native_tokens/melt.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet, HexStr -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + load_dotenv() # In this example we will decrease the native token supply diff --git a/bindings/python/examples/how_tos/native_tokens/mint.py b/bindings/python/examples/how_tos/native_tokens/mint.py index 7f08492e74..38624d241e 100644 --- a/bindings/python/examples/how_tos/native_tokens/mint.py +++ b/bindings/python/examples/how_tos/native_tokens/mint.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + load_dotenv() # In this example we will mint native tokens diff --git a/bindings/python/examples/how_tos/native_tokens/send.py b/bindings/python/examples/how_tos/native_tokens/send.py index 7aabebfd27..00d195cc07 100644 --- a/bindings/python/examples/how_tos/native_tokens/send.py +++ b/bindings/python/examples/how_tos/native_tokens/send.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet, SendNativeTokensParams -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import SendNativeTokensParams, Wallet + load_dotenv() # In this example we will send native tokens diff --git a/bindings/python/examples/how_tos/nft_collection/00_mint_issuer_nft.py b/bindings/python/examples/how_tos/nft_collection/00_mint_issuer_nft.py index cc1ed0cc09..64d7bc1570 100644 --- a/bindings/python/examples/how_tos/nft_collection/00_mint_issuer_nft.py +++ b/bindings/python/examples/how_tos/nft_collection/00_mint_issuer_nft.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet, Utils, utf8_to_hex, MintNftParams -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import MintNftParams, Utils, Wallet, utf8_to_hex + load_dotenv() # In this example we will mint the issuer NFT for the NFT collection. diff --git a/bindings/python/examples/how_tos/nft_collection/01_mint_collection_nft.py b/bindings/python/examples/how_tos/nft_collection/01_mint_collection_nft.py index e8774d900a..a21154736b 100644 --- a/bindings/python/examples/how_tos/nft_collection/01_mint_collection_nft.py +++ b/bindings/python/examples/how_tos/nft_collection/01_mint_collection_nft.py @@ -1,8 +1,10 @@ -from iota_sdk import Wallet, Utils, utf8_to_hex, MintNftParams -from dotenv import load_dotenv +import json import os import sys -import json + +from dotenv import load_dotenv + +from iota_sdk import MintNftParams, Utils, Wallet, utf8_to_hex load_dotenv() @@ -34,7 +36,8 @@ issuer = Utils.nft_id_to_bech32(issuer_nft_id, bech32_hrp) -def get_immutable_metadata(index: int, issuer_nft_id: str) -> str: +def get_immutable_metadata(index: int, collection_id: str) -> str: + """Returns the immutable metadata for the NFT with the given index""" data = { "standard": "IRC27", "version": "v1.0", @@ -43,7 +46,7 @@ def get_immutable_metadata(index: int, issuer_nft_id: str) -> str: "name": "Shimmer OG NFT #" + str(index), "description": "The Shimmer OG NFT was handed out 1337 times by the IOTA Foundation to celebrate the official launch of the Shimmer Network.", "issuerName": "IOTA Foundation", - "collectionId": issuer_nft_id, + "collectionId": collection_id, "collectionName": "Shimmer OG" } return json.dumps(data, separators=(',', ':')) diff --git a/bindings/python/examples/how_tos/nfts/burn_nft.py b/bindings/python/examples/how_tos/nfts/burn_nft.py index 39bcdf15be..dc0e2b395f 100644 --- a/bindings/python/examples/how_tos/nfts/burn_nft.py +++ b/bindings/python/examples/how_tos/nfts/burn_nft.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + load_dotenv() # In this example we will burn an NFT diff --git a/bindings/python/examples/how_tos/nfts/mint_nft.py b/bindings/python/examples/how_tos/nfts/mint_nft.py index a1bea911bb..6b0042f587 100644 --- a/bindings/python/examples/how_tos/nfts/mint_nft.py +++ b/bindings/python/examples/how_tos/nfts/mint_nft.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet, utf8_to_hex, MintNftParams -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import MintNftParams, Wallet, utf8_to_hex + load_dotenv() # In this example we will mint an nft diff --git a/bindings/python/examples/how_tos/nfts/send_nft.py b/bindings/python/examples/how_tos/nfts/send_nft.py index 06b0b37fe9..7415082d2a 100644 --- a/bindings/python/examples/how_tos/nfts/send_nft.py +++ b/bindings/python/examples/how_tos/nfts/send_nft.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet, SendNftParams -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import SendNftParams, Wallet + load_dotenv() # In this example we will send an nft diff --git a/bindings/python/examples/how_tos/outputs/features.py b/bindings/python/examples/how_tos/outputs/features.py index f7d6108485..c8fc009baa 100644 --- a/bindings/python/examples/how_tos/outputs/features.py +++ b/bindings/python/examples/how_tos/outputs/features.py @@ -1,7 +1,19 @@ -from iota_sdk import * +import json from dataclasses import asdict + from dotenv import load_dotenv -import json + +from iota_sdk import ( + AddressUnlockCondition, + Client, + Ed25519Address, + Utils, + SenderFeature, + IssuerFeature, + MetadataFeature, + TagFeature, + utf8_to_hex, +) load_dotenv() diff --git a/bindings/python/examples/how_tos/outputs/unlock_conditions.py b/bindings/python/examples/how_tos/outputs/unlock_conditions.py index 8c78b12599..c7e38ffbdf 100644 --- a/bindings/python/examples/how_tos/outputs/unlock_conditions.py +++ b/bindings/python/examples/how_tos/outputs/unlock_conditions.py @@ -1,7 +1,21 @@ -from iota_sdk import * -from dotenv import load_dotenv import json +from dotenv import load_dotenv +from iota_sdk import ( + AddressUnlockCondition, + AliasAddress, + Client, + Ed25519Address, + Utils, + ExpirationUnlockCondition, + SimpleTokenScheme, + StorageDepositReturnUnlockCondition, + TimelockUnlockCondition, + GovernorAddressUnlockCondition, + StateControllerAddressUnlockCondition, + ImmutableAliasAddressUnlockCondition, +) + load_dotenv() client = Client() diff --git a/bindings/python/examples/how_tos/sign_and_verify_ed25519/sign_ed25519.py b/bindings/python/examples/how_tos/sign_and_verify_ed25519/sign_ed25519.py index 33d1acaeab..9146928111 100644 --- a/bindings/python/examples/how_tos/sign_and_verify_ed25519/sign_ed25519.py +++ b/bindings/python/examples/how_tos/sign_and_verify_ed25519/sign_ed25519.py @@ -1,7 +1,10 @@ -from iota_sdk import Client, StrongholdSecretManager, SecretManager, Bip44, CoinType, Utils, utf8_to_hex -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import (Bip44, CoinType, SecretManager, + StrongholdSecretManager, Utils, utf8_to_hex) + load_dotenv() # In this example we will sign with Ed25519. diff --git a/bindings/python/examples/how_tos/sign_evm/sign_evm.py b/bindings/python/examples/how_tos/sign_evm/sign_evm.py index 60691726e9..1cc85a43a1 100644 --- a/bindings/python/examples/how_tos/sign_evm/sign_evm.py +++ b/bindings/python/examples/how_tos/sign_evm/sign_evm.py @@ -1,7 +1,10 @@ -from iota_sdk import Client, StrongholdSecretManager, SecretManager, Bip44, CoinType, Utils, utf8_to_hex -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import (Bip44, CoinType, SecretManager, StrongholdSecretManager, + utf8_to_hex) + load_dotenv() # In this example we will sign with secp256k1. diff --git a/bindings/python/examples/how_tos/simple_transaction/request_funds.py b/bindings/python/examples/how_tos/simple_transaction/request_funds.py index 2f530ef1f3..583a167d87 100644 --- a/bindings/python/examples/how_tos/simple_transaction/request_funds.py +++ b/bindings/python/examples/how_tos/simple_transaction/request_funds.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + # This example requests funds from the faucet load_dotenv() diff --git a/bindings/python/examples/how_tos/simple_transaction/simple_transaction.py b/bindings/python/examples/how_tos/simple_transaction/simple_transaction.py index bdd7bcb4b8..550d0b495b 100644 --- a/bindings/python/examples/how_tos/simple_transaction/simple_transaction.py +++ b/bindings/python/examples/how_tos/simple_transaction/simple_transaction.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet, SendParams -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import SendParams, Wallet + load_dotenv() # This example sends a transaction. diff --git a/bindings/python/examples/secret_manager/generate_addresses.py b/bindings/python/examples/secret_manager/generate_addresses.py index 8ccd9daee8..899c8d12b7 100644 --- a/bindings/python/examples/secret_manager/generate_addresses.py +++ b/bindings/python/examples/secret_manager/generate_addresses.py @@ -1,7 +1,9 @@ -from iota_sdk import MnemonicSecretManager, CoinType, SecretManager -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import CoinType, MnemonicSecretManager, SecretManager + load_dotenv() if 'MNEMONIC' not in os.environ: diff --git a/bindings/python/examples/secret_manager/ledger_nano.py b/bindings/python/examples/secret_manager/ledger_nano.py index 3e1f3baa35..a9f2f9420a 100644 --- a/bindings/python/examples/secret_manager/ledger_nano.py +++ b/bindings/python/examples/secret_manager/ledger_nano.py @@ -1,6 +1,6 @@ -from iota_sdk import LedgerNanoSecretManager, SecretManager from dotenv import load_dotenv -import os + +from iota_sdk import LedgerNanoSecretManager, SecretManager load_dotenv() diff --git a/bindings/python/examples/secret_manager/stronghold.py b/bindings/python/examples/secret_manager/stronghold.py index 71c29e9edc..db6abf25e1 100644 --- a/bindings/python/examples/secret_manager/stronghold.py +++ b/bindings/python/examples/secret_manager/stronghold.py @@ -1,7 +1,9 @@ -from iota_sdk import StrongholdSecretManager, SecretManager -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import SecretManager, StrongholdSecretManager + load_dotenv() if 'MNEMONIC' not in os.environ: diff --git a/bindings/python/examples/wallet/12-prepare_output.py b/bindings/python/examples/wallet/12-prepare_output.py index 25b0ddcd37..a1bd8ef31d 100644 --- a/bindings/python/examples/wallet/12-prepare_output.py +++ b/bindings/python/examples/wallet/12-prepare_output.py @@ -1,8 +1,10 @@ -from iota_sdk import Wallet, OutputParams, Unlocks -from dotenv import load_dotenv import json import os +from dotenv import load_dotenv + +from iota_sdk import OutputParams, Unlocks, Wallet + load_dotenv() # In this example we will prepare an output with an address and expiration diff --git a/bindings/python/examples/wallet/13-check-unlock-conditions.py b/bindings/python/examples/wallet/13-check-unlock-conditions.py index b3c81c00cc..65711461d3 100644 --- a/bindings/python/examples/wallet/13-check-unlock-conditions.py +++ b/bindings/python/examples/wallet/13-check-unlock-conditions.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet, Utils, OutputParams -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import OutputParams, Utils, Wallet + load_dotenv() # In this example we check if an output has only an address unlock @@ -19,6 +21,7 @@ def hexAddress(address): + """Converts an address to hex""" return Utils.bech32_to_hex(address.address) diff --git a/bindings/python/examples/wallet/backup.py b/bindings/python/examples/wallet/backup.py index 3460a5ca3f..45f197e4ce 100644 --- a/bindings/python/examples/wallet/backup.py +++ b/bindings/python/examples/wallet/backup.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet, StrongholdSecretManager, CoinType, ClientOptions -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import ClientOptions, CoinType, StrongholdSecretManager, Wallet + load_dotenv() # This example creates a new database and account @@ -31,4 +33,4 @@ accounts = wallet.create_account('Alice') wallet.backup("backup.stronghold", os.environ['STRONGHOLD_PASSWORD']) -print(f'Created backup') +print('Created backup') diff --git a/bindings/python/examples/wallet/create_alias.py b/bindings/python/examples/wallet/create_alias.py index 951ea54b88..4700e3ee62 100644 --- a/bindings/python/examples/wallet/create_alias.py +++ b/bindings/python/examples/wallet/create_alias.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + load_dotenv() # In this example we will create an alias output diff --git a/bindings/python/examples/wallet/get_client.py b/bindings/python/examples/wallet/get_client.py index 6ae4facc70..0e3c8bde03 100644 --- a/bindings/python/examples/wallet/get_client.py +++ b/bindings/python/examples/wallet/get_client.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + load_dotenv() # This example gets a client from the wallet. diff --git a/bindings/python/examples/wallet/getting-started.py b/bindings/python/examples/wallet/getting-started.py index 1bdd4e056d..abfafbc117 100644 --- a/bindings/python/examples/wallet/getting-started.py +++ b/bindings/python/examples/wallet/getting-started.py @@ -1,10 +1,13 @@ # Copyright 2023 IOTA Stiftung # SPDX-License-Identifier: Apache-2.0 -from iota_sdk import Wallet, StrongholdSecretManager, CoinType, ClientOptions, Utils -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import (ClientOptions, CoinType, StrongholdSecretManager, Utils, + Wallet) + load_dotenv() # A name to associate with the created account. diff --git a/bindings/python/examples/wallet/logger.py b/bindings/python/examples/wallet/logger.py index a1e659104b..7a411a966c 100644 --- a/bindings/python/examples/wallet/logger.py +++ b/bindings/python/examples/wallet/logger.py @@ -1,8 +1,12 @@ -from iota_sdk import Wallet, StrongholdSecretManager, init_logger, CoinType, ClientOptions -from dotenv import load_dotenv import json import os +from dotenv import load_dotenv + +# pylint: disable=no-name-in-module +from iota_sdk import (ClientOptions, CoinType, StrongholdSecretManager, Wallet, + init_logger) + load_dotenv() # This example creates a new database and account and write debug logs in diff --git a/bindings/python/examples/wallet/migrate-stronghold-snapshot-v2-to-v3.py b/bindings/python/examples/wallet/migrate-stronghold-snapshot-v2-to-v3.py index c7dd538497..2af7aa5aa4 100644 --- a/bindings/python/examples/wallet/migrate-stronghold-snapshot-v2-to-v3.py +++ b/bindings/python/examples/wallet/migrate-stronghold-snapshot-v2-to-v3.py @@ -1,8 +1,11 @@ -import iota_sdk -from iota_sdk import Wallet, StrongholdSecretManager, CoinType, ClientOptions -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +# pylint: disable=no-name-in-module +from iota_sdk import (ClientOptions, CoinType, StrongholdSecretManager, Wallet, + migrate_stronghold_snapshot_v2_to_v3) + load_dotenv() v2_path = "../../../sdk/tests/wallet/fixtures/v2.stronghold" @@ -22,7 +25,7 @@ except ValueError as e: print(e) -iota_sdk.migrate_stronghold_snapshot_v2_to_v3( +migrate_stronghold_snapshot_v2_to_v3( v2_path, "current_password", "wallet.rs", diff --git a/bindings/python/examples/wallet/offline_signing/0_generate_addresses.py b/bindings/python/examples/wallet/offline_signing/0_generate_addresses.py index 86e8ac4e48..cf7bf74efa 100644 --- a/bindings/python/examples/wallet/offline_signing/0_generate_addresses.py +++ b/bindings/python/examples/wallet/offline_signing/0_generate_addresses.py @@ -4,11 +4,13 @@ # In this example we create an account and store its addresses in a file which will be used later to find # inputs. -from iota_sdk import Wallet, StrongholdSecretManager, CoinType, ClientOptions -from dotenv import load_dotenv import json import os +from dotenv import load_dotenv + +from iota_sdk import ClientOptions, CoinType, StrongholdSecretManager, Wallet + load_dotenv() OFFLINE_WALLET_DB_PATH = "./wallet/offline_signing/example-offline-walletdb" @@ -43,6 +45,6 @@ json_data = json.dumps(list(map(lambda x: x.__dict__, addresses)), indent=4) print(f"example.addresses.json:\n{json_data}") -f = open(ADDRESSES_FILE_PATH, "w") +f = open(ADDRESSES_FILE_PATH, "w", encoding="utf-8") f.write(json_data) f.close() diff --git a/bindings/python/examples/wallet/offline_signing/1_prepare_transaction.py b/bindings/python/examples/wallet/offline_signing/1_prepare_transaction.py index 174d3012a1..7976f96a51 100644 --- a/bindings/python/examples/wallet/offline_signing/1_prepare_transaction.py +++ b/bindings/python/examples/wallet/offline_signing/1_prepare_transaction.py @@ -3,12 +3,15 @@ # In this example we will get inputs and prepare a transaction. -from iota_sdk import Wallet, CoinType, ClientOptions, SendParams, AccountAddress -from dotenv import load_dotenv -from dacite import from_dict import json import os +from dacite import from_dict +from dotenv import load_dotenv + +from iota_sdk import (AccountAddress, ClientOptions, CoinType, SendParams, + Wallet) + load_dotenv() ONLINE_WALLET_DB_PATH = "./wallet/offline_signing/example-online-walletdb" @@ -23,7 +26,7 @@ params = [SendParams(RECV_ADDRESS, str(SEND_AMOUNT))] # Recovers addresses from example `0_address_generation`. -addresses_data = json.load(open(ADDRESSES_FILE_PATH, "r")) +addresses_data = json.load(open(ADDRESSES_FILE_PATH, "r", encoding="utf-8")) addresses = list(map(lambda x: from_dict(AccountAddress, x), addresses_data)) if 'NODE_URL' not in os.environ: @@ -45,6 +48,6 @@ prepared_transaction.prepared_transaction_data().as_dict(), indent=4) print(f"example.prepared_transaction.json:\n{json_data}") -f = open(PREPARED_TRANSACTION_FILE_PATH, "w") +f = open(PREPARED_TRANSACTION_FILE_PATH, "w", encoding="utf-8") f.write(json_data) f.close() diff --git a/bindings/python/examples/wallet/offline_signing/2_sign_transaction.py b/bindings/python/examples/wallet/offline_signing/2_sign_transaction.py index 66a05c5822..37c614a612 100644 --- a/bindings/python/examples/wallet/offline_signing/2_sign_transaction.py +++ b/bindings/python/examples/wallet/offline_signing/2_sign_transaction.py @@ -3,12 +3,14 @@ # In this example we sign the prepared transaction. -from iota_sdk import Wallet, PreparedTransactionData -from dotenv import load_dotenv -from dacite import from_dict import json import os +from dacite import from_dict +from dotenv import load_dotenv + +from iota_sdk import PreparedTransactionData, Wallet + load_dotenv() OFFLINE_WALLET_DB_PATH = "./wallet/offline_signing/example-offline-walletdb" @@ -18,7 +20,7 @@ prepared_transaction_data = json.load( - open(PREPARED_TRANSACTION_FILE_PATH, "r")) + open(PREPARED_TRANSACTION_FILE_PATH, "r", encoding="utf-8")) prepared_transaction_data = from_dict( PreparedTransactionData, prepared_transaction_data) @@ -39,6 +41,6 @@ json_data = json.dumps(signed_transaction_data.as_dict(), indent=4) print(f"example.signed_transaction.json:\n{json_data}") -f = open(SIGNED_TRANSACTION_FILE_PATH, "w") +f = open(SIGNED_TRANSACTION_FILE_PATH, "w", encoding="utf-8") f.write(json_data) f.close() diff --git a/bindings/python/examples/wallet/offline_signing/3_send_transaction.py b/bindings/python/examples/wallet/offline_signing/3_send_transaction.py index 2ad5e6e760..59c911862c 100644 --- a/bindings/python/examples/wallet/offline_signing/3_send_transaction.py +++ b/bindings/python/examples/wallet/offline_signing/3_send_transaction.py @@ -3,12 +3,14 @@ # In this example we send the signed transaction in a block. -from iota_sdk import Wallet, SignedTransactionData -from dotenv import load_dotenv -from dacite import from_dict import json import os +from dacite import from_dict +from dotenv import load_dotenv + +from iota_sdk import SignedTransactionData, Wallet + load_dotenv() ONLINE_WALLET_DB_PATH = "./wallet/offline_signing/example-online-walletdb" @@ -22,7 +24,7 @@ account = wallet.get_account("Alice") signed_transaction_data = json.load( - open(SIGNED_TRANSACTION_FILE_PATH, "r")) + open(SIGNED_TRANSACTION_FILE_PATH, "r", encoding="utf-8")) signed_transaction_data = from_dict( SignedTransactionData, signed_transaction_data) diff --git a/bindings/python/examples/wallet/recover_accounts.py b/bindings/python/examples/wallet/recover_accounts.py index 5ed9160074..665e840fa4 100644 --- a/bindings/python/examples/wallet/recover_accounts.py +++ b/bindings/python/examples/wallet/recover_accounts.py @@ -1,8 +1,10 @@ -from iota_sdk import Wallet, StrongholdSecretManager, CoinType, ClientOptions -from dotenv import load_dotenv import json import os +from dotenv import load_dotenv + +from iota_sdk import ClientOptions, CoinType, StrongholdSecretManager, Wallet + load_dotenv() # This example searches for accounts with unspent outputs. diff --git a/bindings/python/examples/wallet/restore_backup.py b/bindings/python/examples/wallet/restore_backup.py index 11c86e85fb..4d726fea05 100644 --- a/bindings/python/examples/wallet/restore_backup.py +++ b/bindings/python/examples/wallet/restore_backup.py @@ -1,8 +1,10 @@ -from iota_sdk import Wallet, CoinType, ClientOptions -from dotenv import load_dotenv -from dataclasses import asdict import json import os +from dataclasses import asdict + +from dotenv import load_dotenv + +from iota_sdk import ClientOptions, CoinType, Wallet load_dotenv() diff --git a/bindings/python/examples/wallet/transaction_options.py b/bindings/python/examples/wallet/transaction_options.py index e5075f3e53..0c5e852678 100644 --- a/bindings/python/examples/wallet/transaction_options.py +++ b/bindings/python/examples/wallet/transaction_options.py @@ -1,7 +1,10 @@ -from iota_sdk import Wallet, TransactionOptions, TaggedDataPayload, utf8_to_hex, RemainderValueStrategy -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import (RemainderValueStrategy, TaggedDataPayload, + TransactionOptions, Wallet, utf8_to_hex) + load_dotenv() # This example sends a transaction with a tagged data payload. diff --git a/bindings/python/tox.ini b/bindings/python/tox.ini index f34ce91b6e..b8a8c96a45 100644 --- a/bindings/python/tox.ini +++ b/bindings/python/tox.ini @@ -17,8 +17,17 @@ commands = pip install . pytest -[testenv:lint] -description = Run linter {basepython} +[testenv:format] +description = Run format checker {basepython} deps = autopep8 commands = - autopep8 --exclude .venv --diff --recursive --aggressive --exit-code . + autopep8 --exclude .venv --diff --recursive --aggressive --max-line-length 120 --exit-code . + +[testenv:lint-examples] +description = Run pylint {basepython} on examples +deps = + -r requirements-dev.txt + pylint +commands = + pip install . + pylint --rcfile=examples/.pylintrc examples/**/*.py From 60aed54cae009ed92a39a21dcdcb544cfa1f371a Mon Sep 17 00:00:00 2001 From: Alexandcoats Date: Fri, 18 Aug 2023 14:09:05 -0400 Subject: [PATCH 13/22] Add switch CLI command to change accounts (#1043) * Add switch CLI command to change accounts * completion and cleanup * clippy --- cli/CHANGELOG.md | 1 + cli/src/account.rs | 38 ++++++++---- cli/src/account_completion.rs | 92 +++++++++++++--------------- cli/src/command/account.rs | 10 ++- cli/src/command/wallet.rs | 10 +-- cli/src/main.rs | 2 +- cli/src/wallet.rs | 17 +++-- sdk/src/client/secret/ledger_nano.rs | 2 +- sdk/src/pow/miner.rs | 2 +- sdk/src/wallet/account/types/mod.rs | 11 +++- 10 files changed, 106 insertions(+), 79 deletions(-) diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 46bb8e0c8c..eedef8b1df 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `WalletCommand::Accounts` variant to list all available accounts in a wallet; - `addresses` now additionally prints the hex version of the address; - `outputs`, `unspent-outputs` print a list that includes number and type of the output; +- `Account::switch` command to allow changing accounts quickly; ### Changed diff --git a/cli/src/account.rs b/cli/src/account.rs index 8491b370a1..dfca2563a7 100644 --- a/cli/src/account.rs +++ b/cli/src/account.rs @@ -7,7 +7,7 @@ use dialoguer::Input; use iota_sdk::wallet::{Account, Wallet}; use crate::{ - account_completion::ACCOUNT_COMPLETION, + account_completion::AccountCompletion, account_history::AccountHistory, command::account::{ addresses_command, balance_command, burn_native_token_command, burn_nft_command, claim_command, @@ -25,27 +25,38 @@ use crate::{ }; // loop on the account prompt -pub async fn account_prompt(wallet: &Wallet, account: &Account) -> Result<(), Error> { +pub async fn account_prompt(wallet: &Wallet, mut account: Account) -> Result<(), Error> { let mut history = AccountHistory::default(); loop { - match account_prompt_internal(wallet, account, &mut history).await { - Ok(true) => { - return Ok(()); - } + match account_prompt_internal(wallet, &account, &mut history).await { + Ok(res) => match res { + AccountPromptResponse::Reprompt => (), + AccountPromptResponse::Done => { + return Ok(()); + } + AccountPromptResponse::Switch(new_account) => { + account = new_account; + } + }, Err(e) => { println_log_error!("{e}"); } - _ => {} } } } +pub enum AccountPromptResponse { + Reprompt, + Done, + Switch(Account), +} + // loop on the account prompt pub async fn account_prompt_internal( wallet: &Wallet, account: &Account, history: &mut AccountHistory, -) -> Result { +) -> Result { let alias = { let account = account.details().await; account.alias().clone() @@ -53,7 +64,7 @@ pub async fn account_prompt_internal( let command: String = Input::new() .with_prompt(format!("Account \"{}\"", alias).green().to_string()) .history_with(history) - .completion_with(&ACCOUNT_COMPLETION) + .completion_with(&AccountCompletion) .interact_text()?; match command.as_str() { "h" | "help" => print_account_help(), @@ -77,7 +88,7 @@ pub async fn account_prompt_internal( Ok(account_cli) => account_cli, Err(err) => { println!("{err}"); - return Ok(false); + return Ok(AccountPromptResponse::Reprompt); } }; if let Err(err) = match account_cli.command { @@ -108,7 +119,7 @@ pub async fn account_prompt_internal( AccountCommand::DestroyAlias { alias_id } => destroy_alias_command(account, alias_id).await, AccountCommand::DestroyFoundry { foundry_id } => destroy_foundry_command(account, foundry_id).await, AccountCommand::Exit => { - return Ok(true); + return Ok(AccountPromptResponse::Done); } AccountCommand::Faucet { address, url } => faucet_command(account, address, url).await, AccountCommand::MeltNativeToken { token_id, amount } => { @@ -171,6 +182,9 @@ pub async fn account_prompt_internal( gift_storage_deposit, } => send_native_token_command(account, address, token_id, amount, gift_storage_deposit).await, AccountCommand::SendNft { address, nft_id } => send_nft_command(account, address, nft_id).await, + AccountCommand::Switch { account_id } => { + return Ok(AccountPromptResponse::Switch(wallet.get_account(account_id).await?)); + } AccountCommand::Sync => sync_command(account).await, AccountCommand::Transaction { selector } => transaction_command(account, selector).await, AccountCommand::Transactions { show_details } => transactions_command(account, show_details).await, @@ -191,5 +205,5 @@ pub async fn account_prompt_internal( } } - Ok(false) + Ok(AccountPromptResponse::Reprompt) } diff --git a/cli/src/account_completion.rs b/cli/src/account_completion.rs index 1bdfdd54d6..98a000aae1 100644 --- a/cli/src/account_completion.rs +++ b/cli/src/account_completion.rs @@ -3,57 +3,53 @@ use dialoguer::Completion; -pub(crate) struct AccountCompletion<'a> { - options: [&'a str; 38], -} +pub(crate) struct AccountCompletion; -pub(crate) const ACCOUNT_COMPLETION: AccountCompletion = AccountCompletion { - options: [ - "accounts", - "addresses", - "balance", - "burn-native-token", - "burn-nft", - "claim", - "claimable-outputs", - "consolidate", - "create-alias-output", - "create-native-token", - "destroy-alias", - "destroy-foundry", - "exit", - "faucet", - "melt-native-token", - "mint-native-token", - "mint-nft", - "new-address", - "node-info", - "output", - "outputs", - "send", - "send-native-token", - "send-nft", - "sync", - "transaction", - "transactions", - "tx", - "txs", - "unspent-outputs", - "vote", - "stop-participating", - "participation-overview", - "voting-power", - "increase-voting-power", - "decrease-voting-power", - "voting-output", - "help", - ], -}; +pub(crate) const ACCOUNT_COMPLETION: &[&str] = &[ + "accounts", + "addresses", + "balance", + "burn-native-token", + "burn-nft", + "claim", + "claimable-outputs", + "consolidate", + "create-alias-output", + "create-native-token", + "destroy-alias", + "destroy-foundry", + "exit", + "faucet", + "melt-native-token", + "mint-native-token", + "mint-nft", + "new-address", + "node-info", + "output", + "outputs", + "send", + "send-native-token", + "send-nft", + "switch", + "sync", + "transaction", + "transactions", + "tx", + "txs", + "unspent-outputs", + "vote", + "stop-participating", + "participation-overview", + "voting-power", + "increase-voting-power", + "decrease-voting-power", + "voting-output", + "help", +]; -impl<'a> Completion for AccountCompletion<'a> { +impl Completion for AccountCompletion { fn get(&self, input: &str) -> Option { - let matches = self - .options + let matches = ACCOUNT_COMPLETION .iter() .filter(|option| option.starts_with(input)) .collect::>(); diff --git a/cli/src/command/account.rs b/cli/src/command/account.rs index fddb6dc806..baa336fe34 100644 --- a/cli/src/command/account.rs +++ b/cli/src/command/account.rs @@ -19,7 +19,10 @@ use iota_sdk::{ }, }, wallet::{ - account::{types::AccountAddress, Account, ConsolidationParams, OutputsToClaim, TransactionOptions}, + account::{ + types::{AccountAddress, AccountIdentifier}, + Account, ConsolidationParams, OutputsToClaim, TransactionOptions, + }, CreateNativeTokenParams, MintNftParams, SendNativeTokensParams, SendNftParams, SendParams, }, U256, @@ -193,6 +196,11 @@ pub enum AccountCommand { /// NFT ID to be sent, e.g. 0xecadf10e6545aa82da4df2dfd2a496b457c8850d2cab49b7464cb273d3dffb07. nft_id: String, }, + /// Switch to a different account. + Switch { + /// The identifier (alias or index) of the account you want to switch to. + account_id: AccountIdentifier, + }, /// Synchronize the account. Sync, /// Show the details of a transaction. diff --git a/cli/src/command/wallet.rs b/cli/src/command/wallet.rs index 9b9d386219..bb71ca596f 100644 --- a/cli/src/command/wallet.rs +++ b/cli/src/command/wallet.rs @@ -11,7 +11,7 @@ use iota_sdk::{ stronghold::StrongholdAdapter, utils::Password, }, - wallet::{ClientOptions, Wallet}, + wallet::{account::types::AccountIdentifier, ClientOptions, Wallet}, }; use log::LevelFilter; @@ -36,7 +36,7 @@ pub struct WalletCli { #[arg(long, value_name = "PATH", env = "STRONGHOLD_SNAPSHOT_PATH", default_value = DEFAULT_STRONGHOLD_SNAPSHOT_PATH)] pub stronghold_snapshot_path: String, /// Set the account to enter. - pub account: Option, + pub account: Option, /// Set the log level. #[arg(short, long, default_value = DEFAULT_LOG_LEVEL)] pub log_level: LevelFilter, @@ -210,7 +210,7 @@ pub async fn new_account_command( storage_path: &Path, snapshot_path: &Path, alias: Option, -) -> Result<(Wallet, String), Error> { +) -> Result<(Wallet, AccountIdentifier), Error> { let password = get_password("Stronghold password", !snapshot_path.exists())?; let wallet = unlock_wallet(storage_path, snapshot_path, password).await?; @@ -304,7 +304,7 @@ pub async fn unlock_wallet( Ok(maybe_wallet?) } -pub async fn add_account(wallet: &Wallet, alias: Option) -> Result { +pub async fn add_account(wallet: &Wallet, alias: Option) -> Result { let mut account_builder = wallet.create_account(); if let Some(alias) = alias { @@ -312,7 +312,7 @@ pub async fn add_account(wallet: &Wallet, alias: Option) -> Result Result<(), Error> { async fn run(cli: WalletCli) -> Result<(), Error> { if let (Some(wallet), Some(account)) = new_wallet(cli).await? { let account = wallet.get_account(account).await?; - account::account_prompt(&wallet, &account).await?; + account::account_prompt(&wallet, account).await?; } Ok(()) diff --git a/cli/src/wallet.rs b/cli/src/wallet.rs index 6713515e12..f7e5ddfb5e 100644 --- a/cli/src/wallet.rs +++ b/cli/src/wallet.rs @@ -3,7 +3,7 @@ use std::path::Path; -use iota_sdk::wallet::Wallet; +use iota_sdk::wallet::{account::types::AccountIdentifier, Wallet}; use crate::{ command::wallet::{ @@ -16,11 +16,11 @@ use crate::{ println_log_error, println_log_info, }; -pub async fn new_wallet(cli: WalletCli) -> Result<(Option, Option), Error> { +pub async fn new_wallet(cli: WalletCli) -> Result<(Option, Option), Error> { let storage_path = Path::new(&cli.wallet_db_path); let snapshot_path = Path::new(&cli.stronghold_snapshot_path); - let (wallet, account) = if let Some(command) = cli.command { + let (wallet, account_id) = if let Some(command) = cli.command { match command { WalletCommand::Accounts => { accounts_command(storage_path, snapshot_path).await?; @@ -81,8 +81,7 @@ pub async fn new_wallet(cli: WalletCli) -> Result<(Option, Option Result<(Option, Option Result<(Option, Option), Error> { +async fn create_initial_account(wallet: Wallet) -> Result<(Option, Option), Error> { // Ask the user whether an initial account should be created. if get_decision("Create initial account?")? { let alias = get_account_alias("New account alias", &wallet).await?; - let alias = add_account(&wallet, Some(alias)).await?; + let account_id = add_account(&wallet, Some(alias)).await?; println_log_info!("Created initial account.\nType `help` to see all available account commands."); - Ok((Some(wallet), Some(alias))) + Ok((Some(wallet), Some(account_id))) } else { Ok((Some(wallet), None)) } diff --git a/sdk/src/client/secret/ledger_nano.rs b/sdk/src/client/secret/ledger_nano.rs index 0b57b8a36d..6baa8a4a1d 100644 --- a/sdk/src/client/secret/ledger_nano.rs +++ b/sdk/src/client/secret/ledger_nano.rs @@ -548,7 +548,7 @@ fn merge_unlocks( // address already at this point, because the reference index needs to be lower // than the current block index if !input_address.is_ed25519() { - return Err(Error::MissingInputWithEd25519Address)?; + return Err(Error::MissingInputWithEd25519Address); } let unlock = unlocks.next().ok_or(Error::MissingInputWithEd25519Address)?; diff --git a/sdk/src/pow/miner.rs b/sdk/src/pow/miner.rs index 44c23f1e97..25d0e22d5f 100644 --- a/sdk/src/pow/miner.rs +++ b/sdk/src/pow/miner.rs @@ -81,7 +81,7 @@ impl MinerBuilder { pub fn finish(self) -> Miner { Miner { num_workers: self.num_workers.unwrap_or_else(num_cpus::get), - cancel: self.cancel.unwrap_or_else(MinerCancel::new), + cancel: self.cancel.unwrap_or_default(), } } } diff --git a/sdk/src/wallet/account/types/mod.rs b/sdk/src/wallet/account/types/mod.rs index 9da8afe6dd..2fe2a1b927 100644 --- a/sdk/src/wallet/account/types/mod.rs +++ b/sdk/src/wallet/account/types/mod.rs @@ -311,7 +311,7 @@ impl<'de> Deserialize<'de> for AccountIdentifier { None => { let alias_or_index_str = v .as_str() - .ok_or_else(|| D::Error::custom("accountIdentifier is no number or string"))?; + .ok_or_else(|| D::Error::custom("account identifier is not a number or string"))?; Self::from(alias_or_index_str) } }) @@ -343,3 +343,12 @@ impl From for AccountIdentifier { Self::Index(value) } } + +impl core::fmt::Display for AccountIdentifier { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Alias(alias) => alias.fmt(f), + Self::Index(index) => index.fmt(f), + } + } +} From 38118333b74dad0d8cd79d129cb3c45c4b472ec1 Mon Sep 17 00:00:00 2001 From: Brord van Wierst Date: Wed, 23 Aug 2023 16:16:20 +0200 Subject: [PATCH 14/22] Feat: Added Conflict Reasons (#1069) * added reasons * tryfrom back * py format * changelog * lint * changelog edit --- bindings/python/CHANGELOG.md | 7 +++++ bindings/python/iota_sdk/types/block.py | 18 ++++++++++++ sdk/CHANGELOG.md | 1 + sdk/src/types/block/semantic.rs | 38 +++++++++++++++++++++++-- 4 files changed, 61 insertions(+), 3 deletions(-) diff --git a/bindings/python/CHANGELOG.md b/bindings/python/CHANGELOG.md index 9937cfed8e..d8be1117e2 100644 --- a/bindings/python/CHANGELOG.md +++ b/bindings/python/CHANGELOG.md @@ -19,6 +19,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Security --> + +## 1.1.0 - 2023-MM-DD + +### Added + +- `ConflictReason` display implementation with an explanation of the conflict; + ## 1.0.0 - 2023-07-24 ### Added diff --git a/bindings/python/iota_sdk/types/block.py b/bindings/python/iota_sdk/types/block.py index 7e969e51dc..1b89ffddec 100644 --- a/bindings/python/iota_sdk/types/block.py +++ b/bindings/python/iota_sdk/types/block.py @@ -92,6 +92,24 @@ class ConflictReason(Enum): semanticValidationFailed = 255, +CONFLICT_REASON_STRINGS = { + ConflictReason.none: 'The block has no conflict', + ConflictReason.inputUTXOAlreadySpent: 'The referenced UTXO was already spent', + ConflictReason.inputUTXOAlreadySpentInThisMilestone: 'The referenced UTXO was already spent while confirming this milestone', + ConflictReason.inputUTXONotFound: 'The referenced UTXO cannot be found', + ConflictReason.inputOutputSumMismatch: 'The sum of the inputs and output values does not match', + ConflictReason.invalidSignature: 'The unlock block signature is invalid', + ConflictReason.invalidTimelock: 'The configured timelock is not yet expired', + ConflictReason.invalidNativeTokens: 'The native tokens are invalid', + ConflictReason.returnAmountMismatch: 'The return amount in a transaction is not fulfilled by the output side', + ConflictReason.invalidInputUnlock: 'The input unlock is invalid', + ConflictReason.invalidInputsCommitment: 'The inputs commitment is invalid', + ConflictReason.invalidSender: ' The output contains a Sender with an ident (address) which is not unlocked', + ConflictReason.invalidChainState: 'The chain state transition is invalid', + ConflictReason.semanticValidationFailed: 'The semantic validation failed' +} + + @dataclass class BlockMetadata: """Block Metadata. diff --git a/sdk/CHANGELOG.md b/sdk/CHANGELOG.md index 5006828099..e5122c3f52 100644 --- a/sdk/CHANGELOG.md +++ b/sdk/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Wallet::get_or_create_account` convenience method; - `Output::kind_str()` method; +- `ConflictReason` display implementation with an explanation of the conflict; ### Changed diff --git a/sdk/src/types/block/semantic.rs b/sdk/src/types/block/semantic.rs index 891840b58c..4d8995e0a2 100644 --- a/sdk/src/types/block/semantic.rs +++ b/sdk/src/types/block/semantic.rs @@ -76,9 +76,35 @@ pub enum ConflictReason { SemanticValidationFailed = 255, } -impl Default for ConflictReason { - fn default() -> Self { - Self::None +impl fmt::Display for ConflictReason { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::None => write!(f, "The block has no conflict"), + Self::InputUtxoAlreadySpent => write!(f, "The referenced UTXO was already spent"), + Self::InputUtxoAlreadySpentInThisMilestone => write!( + f, + "The referenced UTXO was already spent while confirming this milestone" + ), + Self::InputUtxoNotFound => write!(f, "The referenced UTXO cannot be found"), + Self::CreatedConsumedAmountMismatch => { + write!(f, "The sum of the inputs and output values does not match") + } + Self::InvalidSignature => write!(f, "The unlock block signature is invalid"), + Self::TimelockNotExpired => write!(f, "The configured timelock is not yet expired"), + Self::InvalidNativeTokens => write!(f, "The native tokens are invalid"), + Self::StorageDepositReturnUnfulfilled => write!( + f, + "The return amount in a transaction is not fulfilled by the output side" + ), + Self::InvalidUnlock => write!(f, "The input unlock is invalid"), + Self::InputsCommitmentsMismatch => write!(f, "The inputs commitment is invalid"), + Self::UnverifiedSender => write!( + f, + " The output contains a Sender with an ident (address) which is not unlocked" + ), + Self::InvalidChainStateTransition => write!(f, "The chain state transition is invalid"), + Self::SemanticValidationFailed => write!(f, "The semantic validation failed"), + } } } @@ -106,6 +132,12 @@ impl TryFrom for ConflictReason { } } +impl Default for ConflictReason { + fn default() -> Self { + Self::None + } +} + /// pub struct ValidationContext<'a> { /// From f1a6ec55635f41151a0a190c1fd606bb7b9743ef Mon Sep 17 00:00:00 2001 From: Brord van Wierst Date: Wed, 30 Aug 2023 10:32:21 +0200 Subject: [PATCH 15/22] Fix: foundry destroy example (#1087) * enhanced foundry destroy example * updated example * review * Nits * Use burn instead of prepare --------- Co-authored-by: Thibault Martinez --- sdk/CHANGELOG.md | 1 + .../how_tos/native_tokens/destroy_foundry.rs | 47 +++++++++++++++++-- .../types/block/output/token_scheme/mod.rs | 12 +++++ 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/sdk/CHANGELOG.md b/sdk/CHANGELOG.md index 44d675986e..85448cbf4d 100644 --- a/sdk/CHANGELOG.md +++ b/sdk/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Wallet::get_or_create_account` convenience method; - `Output::kind_str()` method; - `ConflictReason` display implementation with an explanation of the conflict; +- `TokenScheme` methods `is_simple` and `as_simple`; ### Changed diff --git a/sdk/examples/how_tos/native_tokens/destroy_foundry.rs b/sdk/examples/how_tos/native_tokens/destroy_foundry.rs index ef51b7d795..fca8c58984 100644 --- a/sdk/examples/how_tos/native_tokens/destroy_foundry.rs +++ b/sdk/examples/how_tos/native_tokens/destroy_foundry.rs @@ -12,7 +12,7 @@ //! cargo run --release --all-features --example destroy_foundry //! ``` -use iota_sdk::{wallet::Result, Wallet}; +use iota_sdk::{types::block::output::TokenId, wallet::Result, Wallet}; #[tokio::main] async fn main() -> Result<()> { @@ -29,17 +29,55 @@ async fn main() -> Result<()> { // May want to ensure the account is synced before sending a transaction. let balance = account.sync(None).await?; + let foundry_count = balance.foundries().len(); + println!("Foundries before destroying: {foundry_count}"); + // We try to destroy the first foundry in the account if let Some(foundry_id) = balance.foundries().first() { - let foundry_count = balance.foundries().len(); - println!("Foundries before destroying: {foundry_count}"); + let token_id = TokenId::from(*foundry_id); // Set the stronghold password wallet .set_stronghold_password(std::env::var("STRONGHOLD_PASSWORD").unwrap()) .await?; + // Find the native tokens balance for this foundry if one exists. + let native_tokens = balance + .native_tokens() + .iter() + .find(|native_token| *native_token.token_id() == token_id); + if let Some(native_token) = native_tokens { + let output = account.get_foundry_output(token_id).await?; + // Check if all tokens are melted. + if native_token.available() != output.as_foundry().token_scheme().as_simple().circulating_supply() { + // We are not able to melt all tokens, because we don't own them or they are not unlocked. + println!("We don't own all remaining tokens, aborting foundry destruction."); + return Ok(()); + } + + println!("Melting remaining tokens.."); + // Melt all tokens so we can destroy the foundry. + let transaction = account + .melt_native_token(token_id, native_token.available(), None) + .await?; + println!("Transaction sent: {}", transaction.transaction_id); + + let block_id = account + .retry_transaction_until_included(&transaction.transaction_id, None, None) + .await?; + println!( + "Block included: {}/block/{}", + std::env::var("EXPLORER_URL").unwrap(), + block_id + ); + + // Sync to make the foundry output available again, because it was used in the melting transaction. + account.sync(None).await?; + } + println!("Destroying foundry.."); + let transaction = account.burn(*foundry_id, None).await?; + println!("Transaction sent: {}", transaction.transaction_id); let block_id = account @@ -51,6 +89,9 @@ async fn main() -> Result<()> { block_id ); + // Resync to update the foundries list. + let balance = account.sync(None).await?; + let foundry_count = balance.foundries().len(); println!("Foundries after destroying: {foundry_count}"); } else { diff --git a/sdk/src/types/block/output/token_scheme/mod.rs b/sdk/src/types/block/output/token_scheme/mod.rs index c25bb26e62..a726254d86 100644 --- a/sdk/src/types/block/output/token_scheme/mod.rs +++ b/sdk/src/types/block/output/token_scheme/mod.rs @@ -31,6 +31,18 @@ impl TokenScheme { Self::Simple(_) => SimpleTokenScheme::KIND, } } + + /// Checks whether the token scheme is a [`SimpleTokenScheme`]. + pub fn is_simple(&self) -> bool { + matches!(self, Self::Simple(_)) + } + + /// Gets the token scheme as an actual [`SimpleTokenScheme`]. + /// PANIC: do not call on a non-simple token scheme. + pub fn as_simple(&self) -> &SimpleTokenScheme { + let Self::Simple(scheme) = self; + scheme + } } #[cfg(feature = "serde")] From 1be824f207dba5004896ca9656cec1ae78bf2021 Mon Sep 17 00:00:00 2001 From: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> Date: Wed, 30 Aug 2023 14:32:33 +0200 Subject: [PATCH 16/22] Add python send wrapper methods (#1104) * Add send wrapper methods * Fix example, update comment --- bindings/core/src/method/account.rs | 2 +- bindings/nodejs/lib/wallet/account.ts | 2 +- bindings/python/CHANGELOG.md | 1 + .../consolidate_outputs.py | 3 +- .../python/examples/how_tos/alias/create.py | 2 +- .../how_tos/alias_wallet/transaction.py | 3 +- .../examples/how_tos/native_tokens/create.py | 2 +- .../examples/how_tos/native_tokens/melt.py | 2 +- .../examples/how_tos/native_tokens/mint.py | 4 +- .../examples/how_tos/native_tokens/send.py | 2 +- .../nft_collection/00_mint_issuer_nft.py | 3 +- .../nft_collection/01_mint_collection_nft.py | 3 +- .../python/examples/how_tos/nfts/mint_nft.py | 2 +- .../python/examples/how_tos/nfts/send_nft.py | 2 +- .../python/examples/wallet/create_alias.py | 2 +- bindings/python/iota_sdk/wallet/account.py | 66 +++++++++++++++++++ .../high_level/burning_melting/mod.rs | 2 +- 17 files changed, 84 insertions(+), 19 deletions(-) diff --git a/bindings/core/src/method/account.rs b/bindings/core/src/method/account.rs index 65f7c2fcec..4a1ca46b2a 100644 --- a/bindings/core/src/method/account.rs +++ b/bindings/core/src/method/account.rs @@ -129,7 +129,7 @@ pub enum AccountMethod { /// Returns all pending transactions of the account /// Expected response: [`Transactions`](crate::Response::Transactions) PendingTransactions, - /// A generic `burn()` function that can be used to burn native tokens, nfts, foundries and aliases. + /// A generic function that can be used to burn native tokens, nfts, foundries and aliases. /// /// Note that burning **native tokens** doesn't require the foundry output which minted them, but will not /// increase the foundries `melted_tokens` field, which makes it impossible to destroy the foundry output. diff --git a/bindings/nodejs/lib/wallet/account.ts b/bindings/nodejs/lib/wallet/account.ts index 4e46bb7bba..8366c2f357 100644 --- a/bindings/nodejs/lib/wallet/account.ts +++ b/bindings/nodejs/lib/wallet/account.ts @@ -117,7 +117,7 @@ export class Account { } /** - * A generic `burn()` function that can be used to prepare to burn native tokens, nfts, foundries and aliases. + * A generic function that can be used to prepare to burn native tokens, nfts, foundries and aliases. * @param burn The outputs to burn * @param transactionOptions The options to define a `RemainderValueStrategy` * or custom inputs. diff --git a/bindings/python/CHANGELOG.md b/bindings/python/CHANGELOG.md index ef2ee4fb09..62f858f5fe 100644 --- a/bindings/python/CHANGELOG.md +++ b/bindings/python/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `ConflictReason` display implementation with an explanation of the conflict; +- `Account::{burn(), consolidate_outputs(), create_alias_output(), create_native_token(), melt_native_token(), mint_native_token(), mint_nfts(), send_transaction(), send_native_tokens(), send_nft()}` methods; ## 1.0.1 - 2023-08-23 diff --git a/bindings/python/examples/how_tos/accounts_and_addresses/consolidate_outputs.py b/bindings/python/examples/how_tos/accounts_and_addresses/consolidate_outputs.py index cd0480a98c..674602e73a 100644 --- a/bindings/python/examples/how_tos/accounts_and_addresses/consolidate_outputs.py +++ b/bindings/python/examples/how_tos/accounts_and_addresses/consolidate_outputs.py @@ -47,8 +47,7 @@ # Consolidate unspent outputs and print the consolidation transaction ID # Set `force` to true to force the consolidation even though the # `output_threshold` isn't reached. -transaction = account.prepare_consolidate_outputs( - ConsolidationParams(force=True)).send() +transaction = account.consolidate_outputs(ConsolidationParams(force=True)) print('Transaction sent: ', transaction.transactionId) # Wait for the consolidation transaction to get confirmed diff --git a/bindings/python/examples/how_tos/alias/create.py b/bindings/python/examples/how_tos/alias/create.py index 599982ceb2..2f2838eef1 100644 --- a/bindings/python/examples/how_tos/alias/create.py +++ b/bindings/python/examples/how_tos/alias/create.py @@ -21,5 +21,5 @@ wallet.set_stronghold_password(os.environ["STRONGHOLD_PASSWORD"]) # Send transaction. -transaction = account.prepare_create_alias_output(None, None).send() +transaction = account.create_alias_output(None, None) print(f'Block sent: {os.environ["EXPLORER_URL"]}/block/{transaction.blockId}') diff --git a/bindings/python/examples/how_tos/alias_wallet/transaction.py b/bindings/python/examples/how_tos/alias_wallet/transaction.py index d0e6786511..53c27f086f 100644 --- a/bindings/python/examples/how_tos/alias_wallet/transaction.py +++ b/bindings/python/examples/how_tos/alias_wallet/transaction.py @@ -34,7 +34,8 @@ # Find first output unlockable by the alias address query_parameters = NodeIndexerAPI.QueryParameters(alias_address) -transaction_input = wallet.get_client().basic_output_ids(query_parameters).items[0] +transaction_input = wallet.get_client( +).basic_output_ids(query_parameters).items[0] params = [SendParams( address='rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu', diff --git a/bindings/python/examples/how_tos/native_tokens/create.py b/bindings/python/examples/how_tos/native_tokens/create.py index 4e3038616c..d04c55d07d 100644 --- a/bindings/python/examples/how_tos/native_tokens/create.py +++ b/bindings/python/examples/how_tos/native_tokens/create.py @@ -25,7 +25,7 @@ # existing one. if not balance.aliases: # If we don't have an alias, we need to create one - transaction = account.prepare_create_alias_output(None, None).send() + transaction = account.create_alias_output(None, None) print(f'Transaction sent: {transaction.transactionId}') # Wait for transaction to get included diff --git a/bindings/python/examples/how_tos/native_tokens/melt.py b/bindings/python/examples/how_tos/native_tokens/melt.py index 79d7365641..d7341e8fbb 100644 --- a/bindings/python/examples/how_tos/native_tokens/melt.py +++ b/bindings/python/examples/how_tos/native_tokens/melt.py @@ -30,7 +30,7 @@ melt_amount = 10 # Send transaction. -transaction = account.prepare_melt_native_token(token_id, melt_amount).send() +transaction = account.melt_native_token(token_id, melt_amount) print(f'Transaction sent: {transaction.transactionId}') # Wait for transaction to get included diff --git a/bindings/python/examples/how_tos/native_tokens/mint.py b/bindings/python/examples/how_tos/native_tokens/mint.py index 38624d241e..9e07d8889a 100644 --- a/bindings/python/examples/how_tos/native_tokens/mint.py +++ b/bindings/python/examples/how_tos/native_tokens/mint.py @@ -29,8 +29,8 @@ mint_amount = 10 -# Prepare and send transaction. -transaction = account.prepare_mint_native_token(token_id, mint_amount).send() +# Send transaction. +transaction = account.mint_native_token(token_id, mint_amount) print(f'Transaction sent: {transaction.transactionId}') # Wait for transaction to get included diff --git a/bindings/python/examples/how_tos/native_tokens/send.py b/bindings/python/examples/how_tos/native_tokens/send.py index 00d195cc07..ae0db2d8d9 100644 --- a/bindings/python/examples/how_tos/native_tokens/send.py +++ b/bindings/python/examples/how_tos/native_tokens/send.py @@ -32,7 +32,7 @@ )], )] -transaction = account.prepare_send_native_tokens(outputs, None).send() +transaction = account.send_native_tokens(outputs, None) print(f'Transaction sent: {transaction.transactionId}') # Wait for transaction to get included diff --git a/bindings/python/examples/how_tos/nft_collection/00_mint_issuer_nft.py b/bindings/python/examples/how_tos/nft_collection/00_mint_issuer_nft.py index 64d7bc1570..c89b105dab 100644 --- a/bindings/python/examples/how_tos/nft_collection/00_mint_issuer_nft.py +++ b/bindings/python/examples/how_tos/nft_collection/00_mint_issuer_nft.py @@ -28,8 +28,7 @@ ) -prepared = account.prepare_mint_nfts([params]) -transaction = prepared.send() +transaction = account.mint_nfts([params]) # Wait for transaction to get included block_id = account.retry_transaction_until_included(transaction.transactionId) diff --git a/bindings/python/examples/how_tos/nft_collection/01_mint_collection_nft.py b/bindings/python/examples/how_tos/nft_collection/01_mint_collection_nft.py index a21154736b..79e22b7008 100644 --- a/bindings/python/examples/how_tos/nft_collection/01_mint_collection_nft.py +++ b/bindings/python/examples/how_tos/nft_collection/01_mint_collection_nft.py @@ -63,8 +63,7 @@ def get_immutable_metadata(index: int, collection_id: str) -> str: chunk, nft_mint_params = nft_mint_params[:NUM_NFTS_MINTED_PER_TRANSACTION], nft_mint_params[NUM_NFTS_MINTED_PER_TRANSACTION:] print( f'Minting {len(chunk)} NFTs... ({NFT_COLLECTION_SIZE-len(nft_mint_params)}/{NFT_COLLECTION_SIZE})') - prepared = account.prepare_mint_nfts(chunk) - transaction = prepared.send() + transaction = account.mint_nfts(chunk) # Wait for transaction to get included block_id = account.retry_transaction_until_included( diff --git a/bindings/python/examples/how_tos/nfts/mint_nft.py b/bindings/python/examples/how_tos/nfts/mint_nft.py index 6b0042f587..a1a97f975c 100644 --- a/bindings/python/examples/how_tos/nfts/mint_nft.py +++ b/bindings/python/examples/how_tos/nfts/mint_nft.py @@ -24,5 +24,5 @@ immutableMetadata=utf8_to_hex("some immutable nft metadata"), )] -transaction = account.prepare_mint_nfts(outputs).send() +transaction = account.mint_nfts(outputs) print(f'Block sent: {os.environ["EXPLORER_URL"]}/block/{transaction.blockId}') diff --git a/bindings/python/examples/how_tos/nfts/send_nft.py b/bindings/python/examples/how_tos/nfts/send_nft.py index 7415082d2a..282682b58e 100644 --- a/bindings/python/examples/how_tos/nfts/send_nft.py +++ b/bindings/python/examples/how_tos/nfts/send_nft.py @@ -25,5 +25,5 @@ nftId=balance.nfts[0], )] -transaction = account.prepare_send_nft(outputs).send() +transaction = account.send_nft(outputs) print(f'Block sent: {os.environ["EXPLORER_URL"]}/block/{transaction.blockId}') diff --git a/bindings/python/examples/wallet/create_alias.py b/bindings/python/examples/wallet/create_alias.py index 4700e3ee62..73e4fb4a5f 100644 --- a/bindings/python/examples/wallet/create_alias.py +++ b/bindings/python/examples/wallet/create_alias.py @@ -21,5 +21,5 @@ wallet.set_stronghold_password(os.environ["STRONGHOLD_PASSWORD"]) # Send transaction. -transaction = account.prepare_create_alias_output(None, None).send() +transaction = account.create_alias_output(None, None) print(f'Block sent: {os.environ["EXPLORER_URL"]}/block/{transaction.blockId}') diff --git a/bindings/python/iota_sdk/wallet/account.py b/bindings/python/iota_sdk/wallet/account.py index e68b5797a5..864ab894fc 100644 --- a/bindings/python/iota_sdk/wallet/account.py +++ b/bindings/python/iota_sdk/wallet/account.py @@ -78,6 +78,12 @@ def get_metadata(self) -> AccountMetadata: return AccountMetadata( self.meta["alias"], self.meta["coinType"], self.meta["index"]) + def burn( + self, burn: Burn, options: Optional[TransactionOptions] = None) -> Transaction: + """A generic function that can be used to burn native tokens, nfts, foundries and aliases. + """ + return self.prepare_burn(burn, options).send() + def prepare_burn( self, burn: Burn, options: Optional[TransactionOptions] = None) -> PreparedTransaction: """A generic `prepare_burn()` function that can be used to prepare the burn of native tokens, nfts, foundries and aliases. @@ -119,6 +125,12 @@ def prepare_burn_nft(self, ) return PreparedTransaction(self, prepared) + def consolidate_outputs( + self, params: ConsolidationParams) -> Transaction: + """Consolidate outputs. + """ + return self.prepare_consolidate_outputs(params).send() + def prepare_consolidate_outputs( self, params: ConsolidationParams) -> PreparedTransaction: """Consolidate outputs. @@ -130,6 +142,13 @@ def prepare_consolidate_outputs( ) return PreparedTransaction(self, prepared) + def create_alias_output(self, + params: Optional[CreateAliasOutputParams] = None, + options: Optional[TransactionOptions] = None) -> Transaction: + """Create an alias output. + """ + return self.prepare_create_alias_output(params, options).send() + def prepare_create_alias_output(self, params: Optional[CreateAliasOutputParams] = None, options: Optional[TransactionOptions] = None) -> PreparedTransaction: @@ -272,6 +291,12 @@ def pending_transactions(self): ) return [Transaction.from_dict(tx) for tx in transactions] + def create_native_token(self, params: CreateNativeTokenParams, + options: Optional[TransactionOptions] = None) -> Transaction: + """Create native token. + """ + return self.prepare_create_native_token(params, options).send() + def prepare_create_native_token(self, params: CreateNativeTokenParams, options: Optional[TransactionOptions] = None) -> PreparedTransaction: """Create native token. @@ -285,6 +310,16 @@ def prepare_create_native_token(self, params: CreateNativeTokenParams, return PreparedCreateTokenTransaction( account=self, prepared_transaction_data=prepared) + def melt_native_token(self, + token_id: HexStr, + melt_amount: int, + options: Optional[TransactionOptions] = None) -> Transaction: + """Melt native tokens. This happens with the foundry output which minted them, by increasing it's + `melted_tokens` field. + """ + return self.prepare_melt_native_token( + token_id, melt_amount, options).send() + def prepare_melt_native_token(self, token_id: HexStr, melt_amount: int, @@ -301,6 +336,13 @@ def prepare_melt_native_token(self, ) return PreparedTransaction(self, prepared) + def mint_native_token(self, token_id: HexStr, mint_amount: int, + options: Optional[TransactionOptions] = None) -> Transaction: + """Mint additional native tokens. + """ + return self.prepare_mint_native_token( + token_id, mint_amount, options).send() + def prepare_mint_native_token(self, token_id: HexStr, mint_amount: int, options: Optional[TransactionOptions] = None) -> PreparedTransaction: """Mint additional native tokens. @@ -314,6 +356,12 @@ def prepare_mint_native_token(self, token_id: HexStr, mint_amount: int, ) return PreparedTransaction(self, prepared) + def mint_nfts(self, params: List[MintNftParams], + options: Optional[TransactionOptions] = None) -> Transaction: + """Mint NFTs. + """ + return self.prepare_mint_nfts(params, options).send() + def prepare_mint_nfts(self, params: List[MintNftParams], options: Optional[TransactionOptions] = None) -> PreparedTransaction: """Mint NFTs. @@ -361,6 +409,12 @@ def prepare_send(self, params: List[SendParams], ) return PreparedTransaction(self, prepared) + def send_transaction( + self, outputs: List[Output], options: Optional[TransactionOptions] = None) -> Transaction: + """Send a transaction. + """ + return self.prepare_transaction(outputs, options).send() + def prepare_transaction( self, outputs: List[Output], options: Optional[TransactionOptions] = None) -> PreparedTransaction: """Prepare transaction. @@ -420,6 +474,12 @@ def send_with_params( } )) + def send_native_tokens( + self, params: List[SendNativeTokensParams], options: Optional[TransactionOptions] = None) -> Transaction: + """Send native tokens. + """ + return self.prepare_send_native_tokens(params, options).send() + def prepare_send_native_tokens( self, params: List[SendNativeTokensParams], options: Optional[TransactionOptions] = None) -> PreparedTransaction: """Send native tokens. @@ -432,6 +492,12 @@ def prepare_send_native_tokens( ) return PreparedTransaction(self, prepared) + def send_nft(self, params: List[SendNftParams], + options: Optional[TransactionOptions] = None) -> Transaction: + """Send nft. + """ + return self.prepare_send_nft(params, options).send() + def prepare_send_nft(self, params: List[SendNftParams], options: Optional[TransactionOptions] = None) -> PreparedTransaction: """Send nft. diff --git a/sdk/src/wallet/account/operations/transaction/high_level/burning_melting/mod.rs b/sdk/src/wallet/account/operations/transaction/high_level/burning_melting/mod.rs index be0fbb61c1..7d16ccd177 100644 --- a/sdk/src/wallet/account/operations/transaction/high_level/burning_melting/mod.rs +++ b/sdk/src/wallet/account/operations/transaction/high_level/burning_melting/mod.rs @@ -12,7 +12,7 @@ use crate::{ pub(crate) mod melt_native_token; impl Account { - /// A generic `burn()` function that can be used to burn native tokens, nfts, foundries and aliases. + /// A generic function that can be used to burn native tokens, nfts, foundries and aliases. /// /// Note that burning **native tokens** doesn't require the foundry output which minted them, but will not increase /// the foundries `melted_tokens` field, which makes it impossible to destroy the foundry output. Therefore it's From 72f19a36f038487b94847f62ccdd2fdf279db229 Mon Sep 17 00:00:00 2001 From: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> Date: Wed, 30 Aug 2023 15:27:41 +0200 Subject: [PATCH 17/22] Add nodejs send wrapper methods (#1103) * Add send wrapper methods * Update bindings/nodejs/lib/wallet/account.ts Co-authored-by: Thibault Martinez * Address review comments * Update bindings/nodejs/CHANGELOG.md * Update bindings/nodejs/lib/wallet/account.ts Co-authored-by: Thibault Martinez * Update bindings/nodejs/lib/wallet/account.ts Co-authored-by: Thibault Martinez * Fix example --------- Co-authored-by: Thibault Martinez --- bindings/nodejs-old/lib/Account.ts | 32 +-- bindings/nodejs/CHANGELOG.md | 6 + .../consolidate-outputs.ts | 3 +- .../nodejs/examples/how_tos/alias/create.ts | 4 +- .../examples/how_tos/native_tokens/melt.ts | 7 +- .../examples/how_tos/native_tokens/send.ts | 4 +- .../nft_collection/00_mint_issuer_nft.ts | 4 +- .../nft_collection/01_mint_collection_nft.ts | 3 +- .../nodejs/examples/how_tos/nfts/mint_nft.ts | 4 +- .../nodejs/examples/how_tos/nfts/send_nft.ts | 4 +- bindings/nodejs/lib/wallet/account.ts | 213 ++++++++++++++++-- 11 files changed, 223 insertions(+), 61 deletions(-) diff --git a/bindings/nodejs-old/lib/Account.ts b/bindings/nodejs-old/lib/Account.ts index e43ae2cd35..832985c7ee 100644 --- a/bindings/nodejs-old/lib/Account.ts +++ b/bindings/nodejs-old/lib/Account.ts @@ -138,7 +138,7 @@ export class Account { * recommended to use melting, if the foundry output is available. * @param tokenId The native token id. * @param burnAmount The to be burned amount. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The transaction. */ @@ -164,7 +164,7 @@ export class Account { /** * Burn an nft output * @param nftId The NftId. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The transaction. */ @@ -229,7 +229,7 @@ export class Account { /** * `createAliasOutput` creates an alias output * @param params The alias output options. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns A transaction object. */ @@ -255,7 +255,7 @@ export class Account { * `melted_tokens` field. * @param tokenId The native token id. * @param meltAmount To be melted amount. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The transaction. */ @@ -296,7 +296,7 @@ export class Account { /** * Destroy an alias output. * @param aliasId The AliasId. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The transaction. */ @@ -321,7 +321,7 @@ export class Account { * Function to destroy a foundry output with a circulating supply of 0. * Native tokens in the foundry (minted by other foundries) will be transactioned to the controlling alias. * @param foundryId The FoundryId. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The transaction. */ @@ -761,7 +761,7 @@ export class Account { * Mint additional native tokens. * @param tokenId The native token id. * @param mintAmount To be minted amount. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The minting transaction. */ @@ -788,7 +788,7 @@ export class Account { /** * Create a native token. * @param params The options for creating the token. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The creating transaction and the token ID. */ @@ -813,7 +813,7 @@ export class Account { /** * Mint nfts. * @param params The options for minting nfts. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The minting transaction. */ @@ -844,7 +844,7 @@ export class Account { * storage deposit will be sent to the recipient. When the assets contain * an nft id, the data from the existing `NftOutput` will be used, just with * the address unlock conditions replaced. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The prepared output. */ @@ -868,7 +868,7 @@ export class Account { /** * Prepare a send transaction, useful for offline signing. * @param params Address with amounts to send. - * @param options The options to define a `RemainderValueStrategy` + * @param options Additional transaction options * or custom inputs. * @returns The prepared transaction data. */ @@ -892,7 +892,7 @@ export class Account { /** * Prepare a transaction, useful for offline signing. * @param outputs Outputs to use in the transaction. - * @param options The options to define a `RemainderValueStrategy` + * @param options Additional transaction options * or custom inputs. * @returns The prepared transaction data. */ @@ -971,7 +971,7 @@ export class Account { /** * Send a transaction with amounts from input addresses. * @param params Addresses with amounts. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The sent transaction. */ @@ -996,7 +996,7 @@ export class Account { /** * Send native tokens. * @param params Addresses amounts and native tokens. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The sent transaction. */ @@ -1021,7 +1021,7 @@ export class Account { /** * Send nft. * @param params Addresses and nft ids. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The sent transaction. */ @@ -1046,7 +1046,7 @@ export class Account { /** * Send outputs in a transaction. * @param outputs The outputs to send. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The sent transaction. */ diff --git a/bindings/nodejs/CHANGELOG.md b/bindings/nodejs/CHANGELOG.md index a14b72f45b..072e4736de 100644 --- a/bindings/nodejs/CHANGELOG.md +++ b/bindings/nodejs/CHANGELOG.md @@ -19,6 +19,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Security --> +## 1.1.0 - 2023-MM-DD + +### Added + +- `Account::{burn(), consolidateOutputs(), createAliasOutput(), meltNativeToken(), mintNativeToken(), createNativeToken(), mintNfts(), sendTransaction(), sendNativeTokens(), sendNft()}` methods; + ## 1.0.7 - 2023-08-29 ### Fixed diff --git a/bindings/nodejs/examples/how_tos/accounts_and_addresses/consolidate-outputs.ts b/bindings/nodejs/examples/how_tos/accounts_and_addresses/consolidate-outputs.ts index 0d033560d3..fcb278c9f5 100644 --- a/bindings/nodejs/examples/how_tos/accounts_and_addresses/consolidate-outputs.ts +++ b/bindings/nodejs/examples/how_tos/accounts_and_addresses/consolidate-outputs.ts @@ -67,10 +67,9 @@ async function run() { // Consolidate unspent outputs and print the consolidation transaction ID // Set `force` to true to force the consolidation even though the `output_threshold` isn't reached - const preparedTransaction = await account.prepareConsolidateOutputs({ + const transaction = await account.consolidateOutputs({ force: true, }); - const transaction = await preparedTransaction.send(); console.log('Transaction sent: %s', transaction.transactionId); // Wait for the consolidation transaction to get confirmed diff --git a/bindings/nodejs/examples/how_tos/alias/create.ts b/bindings/nodejs/examples/how_tos/alias/create.ts index 799027c7b0..7f135e4f40 100644 --- a/bindings/nodejs/examples/how_tos/alias/create.ts +++ b/bindings/nodejs/examples/how_tos/alias/create.ts @@ -50,9 +50,7 @@ async function run() { console.log('Sending the create-alias transaction...'); // Create an alias - const transaction = await account - .prepareCreateAliasOutput() - .then((prepared) => prepared.send()); + const transaction = await account.createAliasOutput(); console.log(`Transaction sent: ${transaction.transactionId}`); diff --git a/bindings/nodejs/examples/how_tos/native_tokens/melt.ts b/bindings/nodejs/examples/how_tos/native_tokens/melt.ts index f4e0ec8e6d..248aa25c3d 100644 --- a/bindings/nodejs/examples/how_tos/native_tokens/melt.ts +++ b/bindings/nodejs/examples/how_tos/native_tokens/melt.ts @@ -43,9 +43,10 @@ async function run() { console.log(`Balance before melting: ${token.available}`); // Melt some of the circulating supply - const transaction = await account - .prepareMeltNativeToken(token.tokenId, MELT_AMOUNT) - .then((prepared) => prepared.send()); + const transaction = await account.meltNativeToken( + token.tokenId, + MELT_AMOUNT, + ); console.log(`Transaction sent: ${transaction.transactionId}`); diff --git a/bindings/nodejs/examples/how_tos/native_tokens/send.ts b/bindings/nodejs/examples/how_tos/native_tokens/send.ts index b8a17e6b8a..5dfbe6cb97 100644 --- a/bindings/nodejs/examples/how_tos/native_tokens/send.ts +++ b/bindings/nodejs/examples/how_tos/native_tokens/send.ts @@ -52,9 +52,7 @@ async function run() { } console.log(`Balance before sending: ${token.available}`); - const transaction = await account - .prepareSendNativeTokens(outputs) - .then((prepared) => prepared.send()); + const transaction = await account.sendNativeTokens(outputs); console.log(`Transaction sent: ${transaction.transactionId}`); diff --git a/bindings/nodejs/examples/how_tos/nft_collection/00_mint_issuer_nft.ts b/bindings/nodejs/examples/how_tos/nft_collection/00_mint_issuer_nft.ts index 38fa699adc..7df8cbfb94 100644 --- a/bindings/nodejs/examples/how_tos/nft_collection/00_mint_issuer_nft.ts +++ b/bindings/nodejs/examples/how_tos/nft_collection/00_mint_issuer_nft.ts @@ -49,9 +49,7 @@ async function run() { 'This NFT will be the issuer from the awesome NFT collection', ), }; - const prepared = await account.prepareMintNfts([params]); - - const transaction = await prepared.send(); + const transaction = await account.mintNfts([params]); // Wait for transaction to get included const blockId = await account.retryTransactionUntilIncluded( diff --git a/bindings/nodejs/examples/how_tos/nft_collection/01_mint_collection_nft.ts b/bindings/nodejs/examples/how_tos/nft_collection/01_mint_collection_nft.ts index 2a294b0f3c..686e62c146 100644 --- a/bindings/nodejs/examples/how_tos/nft_collection/01_mint_collection_nft.ts +++ b/bindings/nodejs/examples/how_tos/nft_collection/01_mint_collection_nft.ts @@ -72,8 +72,7 @@ async function run() { i + chunk.length }/${NFT_COLLECTION_SIZE})`, ); - const prepared = await account.prepareMintNfts(chunk); - const transaction = await prepared.send(); + const transaction = await account.mintNfts(chunk); // Wait for transaction to get included const blockId = await account.retryTransactionUntilIncluded( diff --git a/bindings/nodejs/examples/how_tos/nfts/mint_nft.ts b/bindings/nodejs/examples/how_tos/nfts/mint_nft.ts index 5ce684ca48..889e443e5e 100644 --- a/bindings/nodejs/examples/how_tos/nfts/mint_nft.ts +++ b/bindings/nodejs/examples/how_tos/nfts/mint_nft.ts @@ -60,9 +60,7 @@ async function run() { issuer: senderAddress, immutableMetadata: NFT1_IMMUTABLE_METADATA, }; - const prepared = await account.prepareMintNfts([params]); - - let transaction = await prepared.send(); + let transaction = await account.mintNfts([params]); console.log(`Transaction sent: ${transaction.transactionId}`); // Wait for transaction to get included diff --git a/bindings/nodejs/examples/how_tos/nfts/send_nft.ts b/bindings/nodejs/examples/how_tos/nfts/send_nft.ts index 4bd4b1386b..e0ad2944b8 100644 --- a/bindings/nodejs/examples/how_tos/nfts/send_nft.ts +++ b/bindings/nodejs/examples/how_tos/nfts/send_nft.ts @@ -50,9 +50,7 @@ async function run() { ]; // Send the full NFT output to the specified address - const transaction = await account - .prepareSendNft(outputs) - .then((prepared) => prepared.send()); + const transaction = await account.sendNft(outputs); console.log(`Transaction sent: ${transaction.transactionId}`); diff --git a/bindings/nodejs/lib/wallet/account.ts b/bindings/nodejs/lib/wallet/account.ts index 8366c2f357..6042e55c99 100644 --- a/bindings/nodejs/lib/wallet/account.ts +++ b/bindings/nodejs/lib/wallet/account.ts @@ -117,10 +117,23 @@ export class Account { } /** - * A generic function that can be used to prepare to burn native tokens, nfts, foundries and aliases. - * @param burn The outputs to burn - * @param transactionOptions The options to define a `RemainderValueStrategy` + * A generic function that can be used to burn native tokens, nfts, foundries and aliases. + * @param burn The outputs or native tokens to burn + * @param transactionOptions Additional transaction options * or custom inputs. + * @returns The transaction. + */ + async burn( + burn: Burn, + transactionOptions?: TransactionOptions, + ): Promise { + return (await this.prepareBurn(burn, transactionOptions)).send(); + } + + /** + * A generic function that can be used to prepare to burn native tokens, nfts, foundries and aliases. + * @param burn The outputs or native tokens to burn + * @param transactionOptions Additional transaction options * @returns The prepared transaction. */ async prepareBurn( @@ -152,7 +165,7 @@ export class Account { * recommended to use melting, if the foundry output is available. * @param tokenId The native token id. * @param burnAmount The to be burned amount. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The prepared transaction. */ @@ -181,10 +194,11 @@ export class Account { this, ); } + /** * Burn an nft output. * @param nftId The NftId. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The prepared transaction. */ @@ -237,8 +251,20 @@ export class Account { * Consolidate basic outputs with only an `AddressUnlockCondition` from an account * by sending them to an own address again if the output amount is greater or * equal to the output consolidation threshold. - * @param force Force consolidation on addresses where the threshold isn't met. - * @param outputConsolidationThreshold A default threshold is used if this is omitted. + * @param params Consolidation options. + * @returns The consolidation transaction. + */ + async consolidateOutputs( + params: ConsolidationParams, + ): Promise { + return (await this.prepareConsolidateOutputs(params)).send(); + } + + /** + * Consolidate basic outputs with only an `AddressUnlockCondition` from an account + * by sending them to an own address again if the output amount is greater or + * equal to the output consolidation threshold. + * @param params Consolidation options. * @returns The prepared consolidation transaction. */ async prepareConsolidateOutputs( @@ -262,10 +288,26 @@ export class Account { ); } + /** + * Creates an alias output. + * @param params The alias output options. + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The transaction. + */ + async createAliasOutput( + params?: AliasOutputParams, + transactionOptions?: TransactionOptions, + ): Promise { + return ( + await this.prepareCreateAliasOutput(params, transactionOptions) + ).send(); + } + /** * `createAliasOutput` creates an alias output * @param params The alias output options. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The prepared transaction. */ @@ -297,7 +339,30 @@ export class Account { * `melted_tokens` field. * @param tokenId The native token id. * @param meltAmount To be melted amount. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The transaction. + */ + async meltNativeToken( + tokenId: string, + meltAmount: bigint, + transactionOptions?: TransactionOptions, + ): Promise { + return ( + await this.prepareMeltNativeToken( + tokenId, + meltAmount, + transactionOptions, + ) + ).send(); + } + + /** + * Melt native tokens. This happens with the foundry output which minted them, by increasing its + * `melted_tokens` field. + * @param tokenId The native token id. + * @param meltAmount To be melted amount. + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The prepared transaction. */ @@ -350,7 +415,7 @@ export class Account { * Destroy an alias output. * * @param aliasId The AliasId. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The prepared transaction. */ @@ -384,7 +449,7 @@ export class Account { * Native tokens in the foundry (minted by other foundries) will be transacted to the controlling alias. * * @param foundryId The FoundryId. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The prepared transaction. */ @@ -795,7 +860,30 @@ export class Account { * * @param tokenId The native token id. * @param mintAmount To be minted amount. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The minting transaction. + */ + async mintNativeToken( + tokenId: string, + mintAmount: bigint, + transactionOptions?: TransactionOptions, + ): Promise { + return ( + await this.prepareMintNativeToken( + tokenId, + mintAmount, + transactionOptions, + ) + ).send(); + } + + /** + * Mint additional native tokens. + * + * @param tokenId The native token id. + * @param mintAmount To be minted amount. + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The prepared minting transaction. */ @@ -829,9 +917,26 @@ export class Account { * Create a native token. * * @param params The options for creating a native token. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. - * @returns The creating transaction and the token ID. + * @returns The created transaction. + */ + async createNativeToken( + params: CreateNativeTokenParams, + transactionOptions?: TransactionOptions, + ): Promise { + return ( + await this.prepareCreateNativeToken(params, transactionOptions) + ).send(); + } + + /** + * Create a native token. + * + * @param params The options for creating a native token. + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The created transaction and the token ID. */ async prepareCreateNativeToken( params: CreateNativeTokenParams, @@ -870,7 +975,22 @@ export class Account { * Mint NFTs. * * @param params The options for minting nfts. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The minting transaction. + */ + async mintNfts( + params: MintNftParams[], + transactionOptions?: TransactionOptions, + ): Promise { + return (await this.prepareMintNfts(params, transactionOptions)).send(); + } + + /** + * Mint NFTs. + * + * @param params The options for minting nfts. + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The prepared minting transaction. */ @@ -908,7 +1028,7 @@ export class Account { * storage deposit will be sent to the recipient. When the assets contain * an nft id, the data from the existing `NftOutput` will be used, just with * the address unlock conditions replaced. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The prepared output. */ @@ -938,7 +1058,7 @@ export class Account { * Prepare to send base coins, useful for offline signing. * * @param params Address with amounts to send. - * @param options The options to define a `RemainderValueStrategy` + * @param options Additional transaction options * or custom inputs. * @returns The prepared transaction data. */ @@ -970,11 +1090,26 @@ export class Account { ); } + /** + * Send a transaction. + * + * @param outputs Outputs to use in the transaction. + * @param options Additional transaction options + * or custom inputs. + * @returns The transaction data. + */ + async sendTransaction( + outputs: Output[], + options?: TransactionOptions, + ): Promise { + return (await this.prepareTransaction(outputs, options)).send(); + } + /** * Prepare a transaction, useful for offline signing. * * @param outputs Outputs to use in the transaction. - * @param options The options to define a `RemainderValueStrategy` + * @param options Additional transaction options * or custom inputs. * @returns The prepared transaction data. */ @@ -1050,7 +1185,7 @@ export class Account { * * @param amount Amount of coins. * @param address Receiving address. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The sent transaction. */ @@ -1081,7 +1216,7 @@ export class Account { * Send base coins with amounts from input addresses. * * @param params Addresses with amounts. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The sent transaction. */ @@ -1112,7 +1247,24 @@ export class Account { * Send native tokens. * * @param params Addresses amounts and native tokens. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The transaction. + */ + async sendNativeTokens( + params: SendNativeTokensParams[], + transactionOptions?: TransactionOptions, + ): Promise { + return ( + await this.prepareSendNativeTokens(params, transactionOptions) + ).send(); + } + + /** + * Send native tokens. + * + * @param params Addresses amounts and native tokens. + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The prepared transaction. */ @@ -1143,7 +1295,22 @@ export class Account { * Send NFT. * * @param params Addresses and nft ids. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The transaction. + */ + async sendNft( + params: SendNftParams[], + transactionOptions?: TransactionOptions, + ): Promise { + return (await this.prepareSendNft(params, transactionOptions)).send(); + } + + /** + * Send NFT. + * + * @param params Addresses and nft ids. + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The prepared transaction. */ @@ -1174,7 +1341,7 @@ export class Account { * Send outputs in a transaction. * * @param outputs The outputs to send. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The sent transaction. */ From 8c962dd46334e96147b8683911722c48e9fda75c Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Wed, 6 Sep 2023 15:34:20 +0200 Subject: [PATCH 18/22] CLI: refactor print help (#1154) * CLI: refactor print help * Changelog * Return error --- cli/CHANGELOG.md | 3 +++ cli/src/account.rs | 4 ++-- cli/src/command/account.rs | 9 ++++++++- cli/src/command/wallet.rs | 9 ++++++++- cli/src/helper.rs | 19 +------------------ cli/src/wallet.rs | 4 ++-- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index eedef8b1df..76e6a33feb 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -32,6 +32,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `WalletCommand::Mnemonic` now takes 2 optional arguments to avoid user interaction; - `AccountCommand::Transaction` now accepts either an index or an ID; +- Use `CommandFactory` to print help programmatically; +- `print_wallet_help` changed to `WalletCli::print_help`; +- `print_account_help` changed to `AccountCli::print_help`; ## 1.0.0 - 2023-07-27 diff --git a/cli/src/account.rs b/cli/src/account.rs index dfca2563a7..aec3cc338e 100644 --- a/cli/src/account.rs +++ b/cli/src/account.rs @@ -20,7 +20,7 @@ use crate::{ voting_power_command, AccountCli, AccountCommand, }, error::Error, - helper::{bytes_from_hex_or_file, print_account_help}, + helper::bytes_from_hex_or_file, println_log_error, }; @@ -67,7 +67,7 @@ pub async fn account_prompt_internal( .completion_with(&AccountCompletion) .interact_text()?; match command.as_str() { - "h" | "help" => print_account_help(), + "h" | "help" => AccountCli::print_help()?, "c" | "clear" => { // Clear console let _ = std::process::Command::new("clear").status(); diff --git a/cli/src/command/account.rs b/cli/src/command/account.rs index baa336fe34..d1fae699a4 100644 --- a/cli/src/command/account.rs +++ b/cli/src/command/account.rs @@ -3,7 +3,7 @@ use std::str::FromStr; -use clap::{Parser, Subcommand}; +use clap::{CommandFactory, Parser, Subcommand}; use iota_sdk::{ client::request_funds_from_faucet, types::{ @@ -37,6 +37,13 @@ pub struct AccountCli { pub command: AccountCommand, } +impl AccountCli { + pub fn print_help() -> Result<(), Error> { + Self::command().bin_name("Account:").print_help()?; + Ok(()) + } +} + #[derive(Debug, Subcommand)] #[allow(clippy::large_enum_variant)] pub enum AccountCommand { diff --git a/cli/src/command/wallet.rs b/cli/src/command/wallet.rs index bb71ca596f..06e33286fc 100644 --- a/cli/src/command/wallet.rs +++ b/cli/src/command/wallet.rs @@ -3,7 +3,7 @@ use std::path::Path; -use clap::{builder::BoolishValueParser, Args, Parser, Subcommand}; +use clap::{builder::BoolishValueParser, Args, CommandFactory, Parser, Subcommand}; use iota_sdk::{ client::{ constants::SHIMMER_COIN_TYPE, @@ -44,6 +44,13 @@ pub struct WalletCli { pub command: Option, } +impl WalletCli { + pub fn print_help() -> Result<(), Error> { + Self::command().bin_name("wallet").print_help()?; + Ok(()) + } +} + #[derive(Debug, Clone, Subcommand)] pub enum WalletCommand { /// List all accounts. diff --git a/cli/src/helper.rs b/cli/src/helper.rs index 64f5690079..e0a031c28e 100644 --- a/cli/src/helper.rs +++ b/cli/src/helper.rs @@ -4,7 +4,6 @@ use std::path::Path; use chrono::{DateTime, NaiveDateTime, Utc}; -use clap::Parser; use dialoguer::{console::Term, theme::ColorfulTheme, Input, Select}; use iota_sdk::{ client::{utils::Password, verify_mnemonic}, @@ -17,11 +16,7 @@ use tokio::{ }; use zeroize::Zeroize; -use crate::{ - command::{account::AccountCli, wallet::WalletCli}, - error::Error, - println_log_error, println_log_info, -}; +use crate::{error::Error, println_log_error, println_log_info}; const DEFAULT_MNEMONIC_FILE_PATH: &str = "./mnemonic.txt"; @@ -90,18 +85,6 @@ pub async fn pick_account(wallet: &Wallet) -> Result, Error> { } } -pub fn print_wallet_help() { - if let Err(err) = WalletCli::try_parse_from(["Wallet:", "help"]) { - println!("{err}"); - } -} - -pub fn print_account_help() { - if let Err(err) = AccountCli::try_parse_from(["Account:", "help"]) { - println!("{err}"); - } -} - pub async fn bytes_from_hex_or_file(hex: Option, file: Option) -> Result>, Error> { Ok(if let Some(hex) = hex { Some(prefix_hex::decode(hex).map_err(|e| Error::Miscellaneous(e.to_string()))?) diff --git a/cli/src/wallet.rs b/cli/src/wallet.rs index f7e5ddfb5e..e8ce5bdf4f 100644 --- a/cli/src/wallet.rs +++ b/cli/src/wallet.rs @@ -12,7 +12,7 @@ use crate::{ restore_command, set_node_url_command, sync_command, unlock_wallet, InitParameters, WalletCli, WalletCommand, }, error::Error, - helper::{get_account_alias, get_decision, get_password, pick_account, print_wallet_help}, + helper::{get_account_alias, get_decision, get_password, pick_account}, println_log_error, println_log_info, }; @@ -92,7 +92,7 @@ pub async fn new_wallet(cli: WalletCli) -> Result<(Option, Option Date: Thu, 7 Sep 2023 12:57:40 -0400 Subject: [PATCH 19/22] Add IRC27 and IRC30 metadata utilities (#1160) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add IRC metadata helpers * Use metadata in a test * fix test and optional fields * features * fmt and refactor tests * clippy * allow more types for feature constructor and feature gate the test * 🔪 --- sdk/Cargo.toml | 2 + .../input_selection/core/transition.rs | 4 +- sdk/src/types/block/address/bech32.rs | 2 +- .../types/block/output/feature/metadata.rs | 343 +++++++++++++++++- sdk/src/types/block/output/feature/mod.rs | 4 + .../client/input_selection/nft_outputs.rs | 28 +- 6 files changed, 373 insertions(+), 10 deletions(-) diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index e92473afea..c0f2f41b13 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -140,6 +140,8 @@ tokio = { version = "1.31.0", default-features = false, features = [ default = ["client", "wallet", "tls"] events = [] +irc_27 = ["url", "serde"] +irc_30 = ["url", "serde"] ledger_nano = ["iota-ledger-nano"] mqtt = ["std", "regex", "rumqttc", "dep:once_cell"] participation = ["storage"] diff --git a/sdk/src/client/api/block_builder/input_selection/core/transition.rs b/sdk/src/client/api/block_builder/input_selection/core/transition.rs index 224274a2ed..aaa7e9a3d4 100644 --- a/sdk/src/client/api/block_builder/input_selection/core/transition.rs +++ b/sdk/src/client/api/block_builder/input_selection/core/transition.rs @@ -54,7 +54,7 @@ impl InputSelection { } // Remove potential sender feature because it will not be needed anymore as it only needs to be verified once. - let features = input.features().iter().cloned().filter(|feature| !feature.is_sender()); + let features = input.features().iter().filter(|feature| !feature.is_sender()).cloned(); let mut builder = AliasOutputBuilder::from(input) .with_alias_id(alias_id) @@ -101,7 +101,7 @@ impl InputSelection { } // Remove potential sender feature because it will not be needed anymore as it only needs to be verified once. - let features = input.features().iter().cloned().filter(|feature| !feature.is_sender()); + let features = input.features().iter().filter(|feature| !feature.is_sender()).cloned(); let output = NftOutputBuilder::from(input) .with_nft_id(nft_id) diff --git a/sdk/src/types/block/address/bech32.rs b/sdk/src/types/block/address/bech32.rs index 309e64cd7c..d23109060d 100644 --- a/sdk/src/types/block/address/bech32.rs +++ b/sdk/src/types/block/address/bech32.rs @@ -137,7 +137,7 @@ impl + Send> ConvertTo for T { } /// An address and its network type. -#[derive(Copy, Clone, Eq, PartialEq, Hash, AsRef, Deref)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, AsRef, Deref, Ord, PartialOrd)] pub struct Bech32Address { pub(crate) hrp: Hrp, #[as_ref] diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index 51fba6e8f6..6a48cecc81 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -1,7 +1,7 @@ // Copyright 2021-2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use alloc::{boxed::Box, vec::Vec}; +use alloc::{boxed::Box, string::String, vec::Vec}; use core::{ops::RangeInclusive, str::FromStr}; use packable::{bounded::BoundedU16, prefix::BoxedSlicePrefix}; @@ -19,6 +19,29 @@ pub struct MetadataFeature( BoxedSlicePrefix, ); +macro_rules! impl_from_vec { + ($type:ty) => { + impl TryFrom<$type> for MetadataFeature { + type Error = Error; + + fn try_from(value: $type) -> Result { + Vec::::from(value).try_into() + } + } + }; +} +impl_from_vec!(&str); +impl_from_vec!(String); +impl_from_vec!(&[u8]); + +impl TryFrom<[u8; N]> for MetadataFeature { + type Error = Error; + + fn try_from(value: [u8; N]) -> Result { + value.to_vec().try_into() + } +} + impl TryFrom> for MetadataFeature { type Error = Error; @@ -51,8 +74,8 @@ impl MetadataFeature { /// Creates a new [`MetadataFeature`]. #[inline(always)] - pub fn new(data: impl Into>) -> Result { - Self::try_from(data.into()) + pub fn new>(data: T) -> Result { + data.try_into() } /// Returns the data. @@ -74,6 +97,320 @@ impl core::fmt::Debug for MetadataFeature { } } +#[cfg(feature = "irc_27")] +pub(crate) mod irc_27 { + use alloc::{ + borrow::ToOwned, + collections::{BTreeMap, BTreeSet}, + string::String, + }; + + use getset::Getters; + use serde::{Deserialize, Serialize}; + use url::Url; + + use super::*; + use crate::types::block::address::Bech32Address; + + /// The IRC27 NFT standard schema. + #[derive(Clone, Debug, Serialize, Deserialize, Getters, PartialEq)] + #[serde(rename_all = "camelCase")] + #[serde(tag = "standard", rename = "IRC27")] + #[getset(get = "pub")] + pub struct Irc27Metadata { + version: String, + /// The media type (MIME) of the asset. + /// + /// ## Examples + /// - Image files: `image/jpeg`, `image/png`, `image/gif`, etc. + /// - Video files: `video/x-msvideo` (avi), `video/mp4`, `video/mpeg`, etc. + /// - Audio files: `audio/mpeg`, `audio/wav`, etc. + /// - 3D Assets: `model/obj`, `model/u3d`, etc. + /// - Documents: `application/pdf`, `text/plain`, etc. + #[serde(rename = "type")] + media_type: String, + /// URL pointing to the NFT file location. + uri: Url, + /// The human-readable name of the native token. + name: String, + /// The human-readable collection name of the native token. + #[serde(default, skip_serializing_if = "Option::is_none")] + collection_name: Option, + /// Royalty payment addresses mapped to the payout percentage. + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + royalties: BTreeMap, + /// The human-readable name of the native token creator. + #[serde(default, skip_serializing_if = "Option::is_none")] + issuer_name: Option, + /// The human-readable description of the token. + #[serde(default, skip_serializing_if = "Option::is_none")] + description: Option, + /// Additional attributes which follow [OpenSea Metadata standards](https://docs.opensea.io/docs/metadata-standards). + #[serde(default, skip_serializing_if = "BTreeSet::is_empty")] + attributes: BTreeSet, + } + + impl Irc27Metadata { + pub fn new(media_type: impl Into, uri: Url, name: impl Into) -> Self { + Self { + version: "v1.0".to_owned(), + media_type: media_type.into(), + uri, + name: name.into(), + collection_name: Default::default(), + royalties: Default::default(), + issuer_name: Default::default(), + description: Default::default(), + attributes: Default::default(), + } + } + + pub fn with_collection_name(mut self, collection_name: impl Into) -> Self { + self.collection_name.replace(collection_name.into()); + self + } + + pub fn add_royalty(mut self, address: Bech32Address, percentage: f64) -> Self { + self.royalties.insert(address, percentage); + self + } + + pub fn with_royalties(mut self, royalties: BTreeMap) -> Self { + self.royalties = royalties; + self + } + + pub fn with_issuer_name(mut self, issuer_name: impl Into) -> Self { + self.issuer_name.replace(issuer_name.into()); + self + } + + pub fn with_description(mut self, description: impl Into) -> Self { + self.description.replace(description.into()); + self + } + + pub fn add_attribute(mut self, attribute: Attribute) -> Self { + self.attributes.insert(attribute); + self + } + + pub fn with_attributes(mut self, attributes: BTreeSet) -> Self { + self.attributes = attributes; + self + } + + pub fn to_bytes(&self) -> Vec { + serde_json::to_string(self).unwrap().into_bytes() + } + } + + impl TryFrom for MetadataFeature { + type Error = Error; + fn try_from(value: Irc27Metadata) -> Result { + Self::new(value.to_bytes()) + } + } + + #[derive(Clone, Debug, Serialize, Deserialize, Getters, PartialEq, Eq)] + #[getset(get = "pub")] + pub struct Attribute { + trait_type: String, + value: serde_json::Value, + #[serde(default, skip_serializing_if = "Option::is_none")] + display_type: Option, + } + + impl Attribute { + pub fn new(trait_type: impl Into, value: impl Into) -> Self { + Self { + trait_type: trait_type.into(), + display_type: None, + value: value.into(), + } + } + + pub fn with_display_type(mut self, display_type: impl Into) -> Self { + self.display_type.replace(display_type.into()); + self + } + } + + impl Ord for Attribute { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.trait_type.cmp(&other.trait_type) + } + } + impl PartialOrd for Attribute { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + impl core::hash::Hash for Attribute { + fn hash(&self, state: &mut H) { + self.trait_type.hash(state); + } + } + + #[cfg(test)] + mod test { + use super::*; + use crate::types::block::{address::ToBech32Ext, rand::address::rand_address}; + + #[test] + fn serialization() { + let metadata = Irc27Metadata::new( + "image/jpeg", + "https://mywebsite.com/my-nft-files-1.jpeg".parse().unwrap(), + "My NFT #0001", + ) + .with_collection_name("My Collection of Art") + .add_royalty(rand_address().to_bech32_unchecked("iota1"), 0.025) + .add_royalty(rand_address().to_bech32_unchecked("iota1"), 0.025) + .with_issuer_name("My Artist Name") + .with_description("A little information about my NFT collection") + .add_attribute(Attribute::new("Background", "Purple")) + .add_attribute(Attribute::new("Element", "Water")) + .add_attribute(Attribute::new("Attack", 150)) + .add_attribute(Attribute::new("Health", 500)); + let json = serde_json::json!( + { + "standard": "IRC27", + "version": metadata.version(), + "type": metadata.media_type(), + "uri": metadata.uri(), + "name": metadata.name(), + "collectionName": metadata.collection_name(), + "royalties": metadata.royalties(), + "issuerName": metadata.issuer_name(), + "description": metadata.description(), + "attributes": metadata.attributes() + } + ); + let metadata_deser = serde_json::from_value::(json.clone()).unwrap(); + + assert_eq!(metadata, metadata_deser); + assert_eq!(json, serde_json::to_value(metadata).unwrap()) + } + } +} + +#[cfg(feature = "irc_30")] +pub(crate) mod irc_30 { + use alloc::string::String; + + use getset::Getters; + use serde::{Deserialize, Serialize}; + use url::Url; + + use super::*; + + /// The IRC30 NFT standard schema. + #[derive(Clone, Debug, Serialize, Deserialize, Getters, PartialEq, Eq)] + #[serde(rename_all = "camelCase")] + #[serde(tag = "standard", rename = "IRC30")] + #[getset(get = "pub")] + pub struct Irc30Metadata { + /// The human-readable name of the native token. + name: String, + /// The symbol/ticker of the token. + symbol: String, + /// Number of decimals the token uses (divide the token amount by 10^decimals to get its user representation). + decimals: u32, + /// The human-readable description of the token. + #[serde(default, skip_serializing_if = "Option::is_none")] + description: Option, + /// URL pointing to more resources about the token. + #[serde(default, skip_serializing_if = "Option::is_none")] + url: Option, + /// URL pointing to an image resource of the token logo. + #[serde(default, skip_serializing_if = "Option::is_none")] + logo_url: Option, + /// The svg logo of the token encoded as a byte string. + #[serde(default, skip_serializing_if = "Option::is_none")] + logo: Option, + } + + impl Irc30Metadata { + pub fn new(name: impl Into, symbol: impl Into, decimals: u32) -> Self { + Self { + name: name.into(), + symbol: symbol.into(), + decimals, + description: Default::default(), + url: Default::default(), + logo_url: Default::default(), + logo: Default::default(), + } + } + + pub fn with_description(mut self, description: impl Into) -> Self { + self.description.replace(description.into()); + self + } + + pub fn with_url(mut self, url: Url) -> Self { + self.url.replace(url); + self + } + + pub fn with_logo_url(mut self, logo_url: Url) -> Self { + self.logo_url.replace(logo_url); + self + } + + pub fn with_logo(mut self, logo: impl Into) -> Self { + self.logo.replace(logo.into()); + self + } + + pub fn to_bytes(&self) -> Vec { + serde_json::to_string(self).unwrap().into_bytes() + } + } + + impl TryFrom for MetadataFeature { + type Error = Error; + fn try_from(value: Irc30Metadata) -> Result { + Self::new(value.to_bytes()) + } + } + + #[cfg(test)] + mod test { + use super::*; + + #[test] + fn serialization() { + let description = "FooCoin is the utility and governance token of FooLand, \ + a revolutionary protocol in the play-to-earn crypto gaming field."; + let metadata = Irc30Metadata::new("FooCoin", "FOO", 3) + .with_description(description) + .with_url("https://foocoin.io/".parse().unwrap()) + .with_logo_url( + "https://ipfs.io/ipfs/QmR36VFfo1hH2RAwVs4zVJ5btkopGip5cW7ydY4jUQBrkR" + .parse() + .unwrap(), + ); + let json = serde_json::json!( + { + "standard": "IRC30", + "name": metadata.name(), + "description": metadata.description(), + "decimals": metadata.decimals(), + "symbol": metadata.symbol(), + "url": metadata.url(), + "logoUrl": metadata.logo_url() + } + ); + let metadata_deser = serde_json::from_value::(json.clone()).unwrap(); + + assert_eq!(metadata, metadata_deser); + assert_eq!(json, serde_json::to_value(metadata).unwrap()) + } + } +} + #[cfg(feature = "serde")] pub(crate) mod dto { use alloc::boxed::Box; diff --git a/sdk/src/types/block/output/feature/mod.rs b/sdk/src/types/block/output/feature/mod.rs index 716db19d7f..363fb1befc 100644 --- a/sdk/src/types/block/output/feature/mod.rs +++ b/sdk/src/types/block/output/feature/mod.rs @@ -13,6 +13,10 @@ use derive_more::{Deref, From}; use iterator_sorted::is_unique_sorted; use packable::{bounded::BoundedU8, prefix::BoxedSlicePrefix, Packable}; +#[cfg(feature = "irc_27")] +pub use self::metadata::irc_27::{Attribute, Irc27Metadata}; +#[cfg(feature = "irc_30")] +pub use self::metadata::irc_30::Irc30Metadata; pub use self::{issuer::IssuerFeature, metadata::MetadataFeature, sender::SenderFeature, tag::TagFeature}; pub(crate) use self::{metadata::MetadataFeatureLength, tag::TagFeatureLength}; use crate::types::block::{create_bitflags, Error}; diff --git a/sdk/tests/client/input_selection/nft_outputs.rs b/sdk/tests/client/input_selection/nft_outputs.rs index cfc449d908..59980c680b 100644 --- a/sdk/tests/client/input_selection/nft_outputs.rs +++ b/sdk/tests/client/input_selection/nft_outputs.rs @@ -1188,9 +1188,19 @@ fn changed_immutable_metadata() { let protocol_parameters = protocol_parameters(); let nft_id_1 = NftId::from_str(NFT_ID_1).unwrap(); + #[cfg(feature = "irc_27")] + let metadata = iota_sdk::types::block::output::feature::Irc27Metadata::new( + "image/jpeg", + "https://mywebsite.com/my-nft-files-1.jpeg".parse().unwrap(), + "file 1", + ) + .with_issuer_name("Alice"); + #[cfg(not(feature = "irc_27"))] + let metadata = [1, 2, 3]; + let nft_output = NftOutputBuilder::new_with_minimum_storage_deposit(*protocol_parameters.rent_structure(), nft_id_1) - .with_immutable_features(MetadataFeature::new([1, 2, 3])) + .with_immutable_features(MetadataFeature::try_from(metadata)) .add_unlock_condition(AddressUnlockCondition::new( Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), )) @@ -1203,14 +1213,24 @@ fn changed_immutable_metadata() { chain: None, }]; + #[cfg(feature = "irc_27")] + let metadata = iota_sdk::types::block::output::feature::Irc27Metadata::new( + "image/jpeg", + "https://mywebsite.com/my-nft-files-2.jpeg".parse().unwrap(), + "file 2", + ) + .with_issuer_name("Alice"); + #[cfg(not(feature = "irc_27"))] + let metadata = [4, 5, 6]; + // New nft output with changed immutable metadata feature - let updated_alias_output = NftOutputBuilder::from(nft_output.as_nft()) + let updated_nft_output = NftOutputBuilder::from(nft_output.as_nft()) .with_minimum_storage_deposit(*protocol_parameters.rent_structure()) - .with_immutable_features(MetadataFeature::new([4, 5, 6])) + .with_immutable_features(MetadataFeature::try_from(metadata)) .finish_output(protocol_parameters.token_supply()) .unwrap(); - let outputs = [updated_alias_output]; + let outputs = [updated_nft_output]; let selected = InputSelection::new( inputs, From b56eec6176e14914a8e542acc8caf59025e2e646 Mon Sep 17 00:00:00 2001 From: Alexandcoats Date: Thu, 7 Sep 2023 14:46:54 -0400 Subject: [PATCH 20/22] revert breaking change (#1178) --- sdk/src/types/block/output/feature/metadata.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index 6a48cecc81..d94583ba98 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -74,8 +74,8 @@ impl MetadataFeature { /// Creates a new [`MetadataFeature`]. #[inline(always)] - pub fn new>(data: T) -> Result { - data.try_into() + pub fn new(data: impl Into>) -> Result { + Self::try_from(data.into()) } /// Returns the data. From c3c0cf86010f7ec7160c31a4f266664a8e6ffc53 Mon Sep 17 00:00:00 2001 From: Alexandcoats Date: Fri, 8 Sep 2023 11:53:55 -0400 Subject: [PATCH 21/22] Use IRC metadata in examples (#1180) * Use metadata in examples * Update sdk/examples/how_tos/nft_collection/01_mint_collection_nft.rs Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> * fix and add more * Update sdk/examples/how_tos/native_tokens/create.rs Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> * fmt * changelog * impls --------- Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> --- sdk/CHANGELOG.md | 1 + sdk/Cargo.toml | 6 ++-- sdk/examples/how_tos/native_tokens/create.rs | 6 +++- .../nft_collection/01_mint_collection_nft.rs | 33 +++++++++---------- sdk/examples/how_tos/nfts/mint_nft.rs | 15 ++++++--- .../types/block/output/feature/metadata.rs | 14 ++++++++ 6 files changed, 50 insertions(+), 25 deletions(-) diff --git a/sdk/CHANGELOG.md b/sdk/CHANGELOG.md index ee771bc158..e090cf81ce 100644 --- a/sdk/CHANGELOG.md +++ b/sdk/CHANGELOG.md @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Output::kind_str()` method; - `ConflictReason` display implementation with an explanation of the conflict; - `TokenScheme` methods `is_simple` and `as_simple`; +- `Irc27Metadata` and `Irc30Metadata` helpers; ### Changed diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index b2f726e815..138b056d37 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -305,14 +305,14 @@ required-features = ["wallet", "storage", "stronghold"] [[example]] name = "mint_collection_nft" path = "examples/how_tos/nft_collection/01_mint_collection_nft.rs" -required-features = ["wallet", "stronghold"] +required-features = ["wallet", "stronghold", "irc_27"] # Native Tokens Examples [[example]] name = "create_native_token" path = "examples/how_tos/native_tokens/create.rs" -required-features = ["rocksdb", "stronghold"] +required-features = ["rocksdb", "stronghold", "irc_30"] [[example]] name = "destroy_foundry" @@ -788,7 +788,7 @@ required-features = ["wallet", "storage"] [[example]] name = "mint_nft" path = "examples/how_tos/nfts/mint_nft.rs" -required-features = ["wallet", "stronghold"] +required-features = ["wallet", "stronghold", "irc_27"] [[example]] name = "send_nft" diff --git a/sdk/examples/how_tos/native_tokens/create.rs b/sdk/examples/how_tos/native_tokens/create.rs index 5aa47936fa..8eff34f88b 100644 --- a/sdk/examples/how_tos/native_tokens/create.rs +++ b/sdk/examples/how_tos/native_tokens/create.rs @@ -12,6 +12,7 @@ //! ``` use iota_sdk::{ + types::block::output::feature::Irc30Metadata, wallet::{CreateNativeTokenParams, Result}, Wallet, U256, }; @@ -59,13 +60,16 @@ async fn main() -> Result<()> { println!("Account synced"); } + let metadata = + Irc30Metadata::new("My Native Token", "MNT", 10).with_description("A native token to test the iota-sdk."); + println!("Preparing transaction to create native token..."); let params = CreateNativeTokenParams { alias_id: None, circulating_supply: U256::from(CIRCULATING_SUPPLY), maximum_supply: U256::from(MAXIMUM_SUPPLY), - foundry_metadata: None, + foundry_metadata: Some(metadata.to_bytes()), }; let transaction = account.create_native_token(params, None).await?; diff --git a/sdk/examples/how_tos/nft_collection/01_mint_collection_nft.rs b/sdk/examples/how_tos/nft_collection/01_mint_collection_nft.rs index 42c291a804..becf2b33dc 100644 --- a/sdk/examples/how_tos/nft_collection/01_mint_collection_nft.rs +++ b/sdk/examples/how_tos/nft_collection/01_mint_collection_nft.rs @@ -17,7 +17,7 @@ use iota_sdk::{ types::block::{ address::{Bech32Address, NftAddress}, - output::NftId, + output::{feature::Irc27Metadata, NftId}, payload::transaction::TransactionId, }, wallet::{Account, MintNftParams, Result}, @@ -60,7 +60,7 @@ async fn main() -> Result<()> { let nft_mint_params = (0..NFT_COLLECTION_SIZE) .map(|index| { MintNftParams::new() - .with_immutable_metadata(get_immutable_metadata(index, issuer_nft_id).as_bytes().to_vec()) + .with_immutable_metadata(get_immutable_metadata(index).to_bytes()) // The NFT address from the NFT we minted in mint_issuer_nft example .with_issuer(issuer) }) @@ -89,21 +89,20 @@ async fn main() -> Result<()> { Ok(()) } -fn get_immutable_metadata(index: usize, issuer_nft_id: NftId) -> String { - // Note: we use `serde_json::from_str` to remove all unnecessary whitespace - serde_json::from_str::(&format!( - r#"{{ - "standard":"IRC27", - "version":"v1.0", - "type":"video/mp4", - "uri":"ipfs://wrongcVm9fx47YXNTkhpMEYSxCD3Bqh7PJYr7eo5Ywrong", - "name":"Shimmer OG NFT #{index}", - "description":"The Shimmer OG NFT was handed out 1337 times by the IOTA Foundation to celebrate the official launch of the Shimmer Network.", - "issuerName":"IOTA Foundation", - "collectionId":"{issuer_nft_id}", - "collectionName":"Shimmer OG" - }}"# - )).unwrap().to_string() +fn get_immutable_metadata(index: usize) -> Irc27Metadata { + Irc27Metadata::new( + "video/mp4", + "https://ipfs.io/ipfs/QmPoYcVm9fx47YXNTkhpMEYSxCD3Bqh7PJYr7eo5YjLgiT" + .parse() + .unwrap(), + format!("Shimmer OG NFT #{index}"), + ) + .with_description( + "The Shimmer OG NFT was handed out 1337 times by the IOTA Foundation \ + to celebrate the official launch of the Shimmer Network.", + ) + .with_issuer_name("IOTA Foundation") + .with_collection_name("Shimmer OG") } async fn wait_for_inclusion(transaction_id: &TransactionId, account: &Account) -> Result<()> { diff --git a/sdk/examples/how_tos/nfts/mint_nft.rs b/sdk/examples/how_tos/nfts/mint_nft.rs index bb116da5fe..cde3d42259 100644 --- a/sdk/examples/how_tos/nfts/mint_nft.rs +++ b/sdk/examples/how_tos/nfts/mint_nft.rs @@ -13,7 +13,7 @@ use iota_sdk::{ types::block::output::{ - feature::{IssuerFeature, SenderFeature}, + feature::{Irc27Metadata, IssuerFeature, SenderFeature}, unlock_condition::AddressUnlockCondition, NftId, NftOutputBuilder, }, @@ -25,8 +25,6 @@ use iota_sdk::{ const NFT1_OWNER_ADDRESS: &str = "rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu"; // The metadata of the first minted NFT const NFT1_METADATA: &str = "some NFT metadata"; -// The immutable metadata of the first minted NFT -const NFT1_IMMUTABLE_METADATA: &str = "some NFT immutable metadata"; // The tag of the first minted NFT const NFT1_TAG: &str = "some NFT tag"; // The base coin amount we sent with the second NFT @@ -57,13 +55,22 @@ async fn main() -> Result<()> { .set_stronghold_password(std::env::var("STRONGHOLD_PASSWORD").unwrap()) .await?; + let metadata = Irc27Metadata::new( + "video/mp4", + "https://ipfs.io/ipfs/QmPoYcVm9fx47YXNTkhpMEYSxCD3Bqh7PJYr7eo5YjLgiT" + .parse() + .unwrap(), + "Shimmer OG NFT", + ) + .with_description("The original Shimmer NFT"); + let nft_params = [MintNftParams::new() .try_with_address(NFT1_OWNER_ADDRESS)? .try_with_sender(sender_address)? .with_metadata(NFT1_METADATA.as_bytes().to_vec()) .with_tag(NFT1_TAG.as_bytes().to_vec()) .try_with_issuer(sender_address)? - .with_immutable_metadata(NFT1_IMMUTABLE_METADATA.as_bytes().to_vec())]; + .with_immutable_metadata(metadata.to_bytes())]; let transaction = account.mint_nfts(nft_params, None).await?; println!("Transaction sent: {}", transaction.transaction_id); diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index d94583ba98..eaebb7f72f 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -201,6 +201,7 @@ pub(crate) mod irc_27 { } pub fn to_bytes(&self) -> Vec { + // Unwrap: Safe because this struct is known to be valid serde_json::to_string(self).unwrap().into_bytes() } } @@ -212,6 +213,12 @@ pub(crate) mod irc_27 { } } + impl From for Vec { + fn from(value: Irc27Metadata) -> Self { + value.to_bytes() + } + } + #[derive(Clone, Debug, Serialize, Deserialize, Getters, PartialEq, Eq)] #[getset(get = "pub")] pub struct Attribute { @@ -365,6 +372,7 @@ pub(crate) mod irc_30 { } pub fn to_bytes(&self) -> Vec { + // Unwrap: Safe because this struct is known to be valid serde_json::to_string(self).unwrap().into_bytes() } } @@ -376,6 +384,12 @@ pub(crate) mod irc_30 { } } + impl From for Vec { + fn from(value: Irc30Metadata) -> Self { + value.to_bytes() + } + } + #[cfg(test)] mod test { use super::*; From 7b402458be2ccdbd524d89b407c9d861fc3c508c Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Tue, 12 Sep 2023 11:24:01 +0200 Subject: [PATCH 22/22] Fix security audits --- Cargo.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 60e9214b60..c21724636a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1519,9 +1519,9 @@ dependencies = [ [[package]] name = "iota-ledger-nano" -version = "1.0.0-alpha.4" +version = "1.0.0-alpha.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a3ecb2b7f3881d5ced64dde43ba36864191e90a249853e0dda56a9521a2a3d" +checksum = "07341d2984f6ae956f0ee0bc62b3119ded2d40441957712761a3fc5591c5e950" dependencies = [ "bech32 0.7.3", "enum-iterator", @@ -2090,9 +2090,9 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "3.6.8" +version = "3.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f88eaac72ead1b9bd4ce747d577dbd2ad31fb0a56a9a20c611bf27bd1b97fbed" +checksum = "0dec8a8073036902368c2cdc0387e85ff9a37054d7e7c98e592145e0c92cd4fb" dependencies = [ "arrayvec", "bitvec", @@ -2104,9 +2104,9 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.6.8" +version = "3.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33bdcd446e9400b6ad9fc85b4aea68846c258b07c3efb994679ae82707b133f0" +checksum = "312270ee71e1cd70289dacf597cab7b207aa107d2f28191c2ae45b2ece18a260" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -2667,7 +2667,7 @@ checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" dependencies = [ "log", "ring", - "rustls-webpki 0.101.4", + "rustls-webpki 0.101.5", "sct", ] @@ -2704,9 +2704,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.101.4" +version = "0.101.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d" +checksum = "45a27e3b59326c16e23d30aeb7a36a24cc0d29e71d68ff611cdfb4a01d013bed" dependencies = [ "ring", "untrusted",