diff --git a/bindings/core/src/method/wallet_command.rs b/bindings/core/src/method/wallet_command.rs index 72aa622eaf..e3ab0dafe4 100644 --- a/bindings/core/src/method/wallet_command.rs +++ b/bindings/core/src/method/wallet_command.rs @@ -102,6 +102,9 @@ pub enum WalletCommandMethod { #[cfg(feature = "participation")] #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] GetVotingPower, + /// Returns the implicit account creation address of the wallet if it is Ed25519 based. + /// Expected response: [`Bech32Address`](crate::Response::Bech32Address) + ImplicitAccountCreationAddress, /// Returns all incoming transactions of the wallet /// Expected response: /// [`Transactions`](crate::Response::Transactions) diff --git a/bindings/core/src/method_handler/wallet_command.rs b/bindings/core/src/method_handler/wallet_command.rs index 562cdd370f..93a5994bdf 100644 --- a/bindings/core/src/method_handler/wallet_command.rs +++ b/bindings/core/src/method_handler/wallet_command.rs @@ -87,6 +87,10 @@ pub(crate) async fn call_wallet_command_method_internal( let voting_power = wallet.get_voting_power().await?; Response::VotingPower(voting_power.to_string()) } + WalletCommandMethod::ImplicitAccountCreationAddress => { + let implicit_account_creation_address = wallet.implicit_account_creation_address().await?; + Response::Bech32Address(implicit_account_creation_address) + } WalletCommandMethod::IncomingTransactions => { let transactions = wallet.incoming_transactions().await; Response::Transactions(transactions.iter().map(TransactionWithMetadataDto::from).collect()) diff --git a/bindings/core/src/response.rs b/bindings/core/src/response.rs index 7a67f1076d..52bacb01cd 100644 --- a/bindings/core/src/response.rs +++ b/bindings/core/src/response.rs @@ -206,6 +206,7 @@ pub enum Response { /// - [`HexPublicKeyToBech32Address`](crate::method::ClientMethod::HexPublicKeyToBech32Address) /// - [`HexToBech32`](crate::method::ClientMethod::HexToBech32) /// - [`NftIdToBech32`](crate::method::ClientMethod::NftIdToBech32) + /// - [`ImplicitAccountCreationAddress`](crate::method::WalletCommandMethod::ImplicitAccountCreationAddress) Bech32Address(Bech32Address), /// - [`Faucet`](crate::method::ClientMethod::RequestFundsFromFaucet) Faucet(String), diff --git a/bindings/nodejs/lib/types/wallet/bridge/account.ts b/bindings/nodejs/lib/types/wallet/bridge/account.ts index 8c4b8cf6eb..363f743486 100644 --- a/bindings/nodejs/lib/types/wallet/bridge/account.ts +++ b/bindings/nodejs/lib/types/wallet/bridge/account.ts @@ -145,6 +145,10 @@ export type __PendingTransactionsMethod__ = { name: 'pendingTransactions'; }; +export type __ImplicitAccountCreationAddressMethod__ = { + name: 'implicitAccountCreationAddress'; +}; + export type __IncomingTransactionsMethod__ = { name: 'incomingTransactions'; }; diff --git a/bindings/nodejs/lib/types/wallet/bridge/index.ts b/bindings/nodejs/lib/types/wallet/bridge/index.ts index fc96daea69..f7cfaee903 100644 --- a/bindings/nodejs/lib/types/wallet/bridge/index.ts +++ b/bindings/nodejs/lib/types/wallet/bridge/index.ts @@ -15,6 +15,7 @@ import type { __AddressesWithUnspentOutputsMethod__, __OutputsMethod__, __PendingTransactionsMethod__, + __ImplicitAccountCreationAddressMethod__, __IncomingTransactionsMethod__, __TransactionsMethod__, __UnspentOutputsMethod__, @@ -96,6 +97,7 @@ export type __AccountMethod__ = | __AddressesWithUnspentOutputsMethod__ | __OutputsMethod__ | __PendingTransactionsMethod__ + | __ImplicitAccountCreationAddressMethod__ | __IncomingTransactionsMethod__ | __TransactionsMethod__ | __UnspentOutputsMethod__ diff --git a/bindings/nodejs/lib/wallet/account.ts b/bindings/nodejs/lib/wallet/account.ts index 7f292745e9..23cbc888d4 100644 --- a/bindings/nodejs/lib/wallet/account.ts +++ b/bindings/nodejs/lib/wallet/account.ts @@ -758,6 +758,22 @@ export class Account { return plainToInstance(TransactionWithMetadata, parsed.payload); } + /** + * Returns the implicit account creation address of the wallet if it is Ed25519 based. + * + * @returns The implicit account creation address. + */ + async implicitAccountCreationAddress(): Promise { + const response = await this.methodHandler.callAccountMethod( + this.meta.index, + { + name: 'implicitAccountCreationAddress', + }, + ); + + return JSON.parse(response).payload; + } + /** * List all incoming transactions of the account. * diff --git a/bindings/python/iota_sdk/wallet/account.py b/bindings/python/iota_sdk/wallet/account.py index 4d434ea0cc..0181f59d02 100644 --- a/bindings/python/iota_sdk/wallet/account.py +++ b/bindings/python/iota_sdk/wallet/account.py @@ -270,6 +270,13 @@ def unspent_outputs( ) return [from_dict(OutputData, o) for o in outputs] + def implicit_account_creation_address(self) -> str: + """Returns the implicit account creation address of the wallet if it is Ed25519 based. + """ + return self._call_account_method( + 'implicitAccountCreationAddress' + ) + def incoming_transactions(self) -> List[Transaction]: """Returns all incoming transactions of the account. """ diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 92aa7b4764..49a40fc799 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -27,7 +27,7 @@ use crate::{ }; const DEFAULT_LOG_LEVEL: &str = "debug"; -const DEFAULT_NODE_URL: &str = "https://api.testnet.shimmer.network"; +const DEFAULT_NODE_URL: &str = "http://localhost:8080"; const DEFAULT_STRONGHOLD_SNAPSHOT_PATH: &str = "./stardust-cli-wallet.stronghold"; const DEFAULT_WALLET_DATABASE_PATH: &str = "./stardust-cli-wallet-db"; diff --git a/cli/src/wallet_cli/completer.rs b/cli/src/wallet_cli/completer.rs index ea2abb4748..c7ee9763b4 100644 --- a/cli/src/wallet_cli/completer.rs +++ b/cli/src/wallet_cli/completer.rs @@ -23,6 +23,7 @@ const WALLET_COMMANDS: &[&str] = &[ "destroy-foundry", "exit", "faucet", + "implicit-account-creation-address", "melt-native-token", "mint-native-token", "mint-nft", diff --git a/cli/src/wallet_cli/mod.rs b/cli/src/wallet_cli/mod.rs index 71b73dcb23..5044ba6311 100644 --- a/cli/src/wallet_cli/mod.rs +++ b/cli/src/wallet_cli/mod.rs @@ -113,9 +113,11 @@ pub enum WalletCommand { Faucet { /// Address the faucet sends the funds to, defaults to the wallet address. address: Option, - /// URL of the faucet, default to . + /// URL of the faucet, default to . url: Option, }, + /// Returns the implicit account creation address of the wallet if it is Ed25519 based. + ImplicitAccountCreationAddress, /// Mint additional native tokens. MintNativeToken { /// Token ID to be minted, e.g. 0x087d205988b733d97fb145ae340e27a8b19554d1ceee64574d7e5ff66c45f69e7a0100000000. @@ -552,15 +554,20 @@ pub async fn faucet_command(wallet: &Wallet, address: Option, url wallet.address().await }; - let faucet_url = url - .as_deref() - .unwrap_or("https://faucet.testnet.shimmer.network/api/enqueue"); + let faucet_url = url.as_deref().unwrap_or("http://localhost:8088/api/enqueue"); println_log_info!("{}", request_funds_from_faucet(faucet_url, &address).await?); Ok(()) } +// `implicit-account-creation-address` command +pub async fn implicit_account_creation_address(wallet: &Wallet) -> Result<(), Error> { + println_log_info!("{}", wallet.implicit_account_creation_address().await?); + + Ok(()) +} + // `melt-native-token` command pub async fn melt_native_token_command(wallet: &Wallet, token_id: String, amount: String) -> Result<(), Error> { let transaction = wallet @@ -1077,6 +1084,9 @@ pub async fn prompt_internal( return Ok(PromptResponse::Done); } WalletCommand::Faucet { address, url } => faucet_command(wallet, address, url).await, + WalletCommand::ImplicitAccountCreationAddress => { + implicit_account_creation_address(wallet).await + } WalletCommand::MeltNativeToken { token_id, amount } => { melt_native_token_command(wallet, token_id, amount).await } diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 0b83ce34c8..0a7e81eb33 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -126,7 +126,9 @@ wasm-bindgen-futures = { version = "0.4.37", default-features = false, optional [dev-dependencies] iota-sdk = { path = ".", default-features = false, features = ["rand"] } -pretty_assertions = { version = "1.4.0", default-features = false, features = [ "alloc" ] } +pretty_assertions = { version = "1.4.0", default-features = false, features = [ + "alloc", +] } dotenvy = { version = "0.15.7", default-features = false } fern-logger = { version = "0.5.0", default-features = false } @@ -340,6 +342,11 @@ name = "destroy_account_output" path = "examples/how_tos/account/destroy.rs" required-features = ["wallet", "stronghold"] +[[example]] +name = "implicit_account_creation" +path = "examples/how_tos/account/implicit_account_creation.rs" +required-features = ["wallet"] + # Outputs [[example]] diff --git a/sdk/examples/how_tos/account/implicit_account_creation.rs b/sdk/examples/how_tos/account/implicit_account_creation.rs new file mode 100644 index 0000000000..9f565da0b8 --- /dev/null +++ b/sdk/examples/how_tos/account/implicit_account_creation.rs @@ -0,0 +1,38 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! In this example, we create an implicit account creation address. +//! +//! Rename `.env.example` to `.env` first, then run the command: +//! ```sh +//! cargo run --release --all-features --example implicit_account_creation +//! ``` + +use iota_sdk::{ + client::{constants::SHIMMER_COIN_TYPE, secret::SecretManager}, + crypto::keys::bip44::Bip44, + wallet::{ClientOptions, Result, Wallet}, +}; + +#[tokio::main] +async fn main() -> Result<()> { + //  This example uses secrets in environment variables for simplicity which should not be done in production. + dotenvy::dotenv().ok(); + + let secret_manager = SecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; + let client_options = ClientOptions::new().with_node("https://api.testnet.shimmer.network")?; + + let wallet = Wallet::builder() + .with_secret_manager(secret_manager) + .with_client_options(client_options) + .with_storage_path("implicit_account_creation") + .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE)) + .finish() + .await?; + + let implicit_account_creation_address = wallet.implicit_account_creation_address().await?; + + println!("{implicit_account_creation_address}"); + + Ok(()) +} diff --git a/sdk/src/wallet/core/mod.rs b/sdk/src/wallet/core/mod.rs index 568e26b4fc..44fb61d0df 100644 --- a/sdk/src/wallet/core/mod.rs +++ b/sdk/src/wallet/core/mod.rs @@ -32,7 +32,7 @@ use crate::{ }, types::{ block::{ - address::{Bech32Address, Hrp}, + address::{Address, Bech32Address, Hrp, ImplicitAccountCreationAddress}, output::{dto::FoundryOutputDto, AccountId, FoundryId, FoundryOutput, NftId, Output, OutputId, TokenId}, payload::signed_transaction::{dto::TransactionDto, Transaction, TransactionId}, }, @@ -41,7 +41,7 @@ use crate::{ wallet::{ operations::syncing::SyncOptions, types::{OutputData, OutputDataDto}, - FilterOptions, Result, + Error, FilterOptions, Result, }, }; @@ -252,6 +252,20 @@ where self.data().await.address.clone() } + /// Returns the implicit account creation address of the wallet if it is Ed25519 based. + pub async fn implicit_account_creation_address(&self) -> Result { + let bech32_address = &self.data().await.address; + + if let Address::Ed25519(address) = bech32_address.inner() { + Ok(Bech32Address::new( + *bech32_address.hrp(), + ImplicitAccountCreationAddress::from(address.clone()), + )) + } else { + return Err(Error::NonEd25519Address); + } + } + /// Get the wallet's configured Bech32 HRP. pub async fn bech32_hrp(&self) -> Hrp { self.data().await.address.hrp diff --git a/sdk/src/wallet/error.rs b/sdk/src/wallet/error.rs index c8d7c3b469..7cd3a7f177 100644 --- a/sdk/src/wallet/error.rs +++ b/sdk/src/wallet/error.rs @@ -116,6 +116,9 @@ pub enum Error { /// Address not the wallet address #[error("address {0} is not the wallet address")] WalletAddressMismatch(Bech32Address), + /// Action requires the wallet to be Ed25519 address based + #[error("tried to perform an action that requires the wallet to be Ed25519 address based")] + NonEd25519Address, } // Serialize type with Display error