diff --git a/bindings/core/src/method/wallet.rs b/bindings/core/src/method/wallet.rs index 36f280a558..59be946bda 100644 --- a/bindings/core/src/method/wallet.rs +++ b/bindings/core/src/method/wallet.rs @@ -191,6 +191,10 @@ pub enum WalletMethod { /// Returns the implicit account creation address of the wallet if it is Ed25519 based. /// Expected response: [`Bech32Address`](crate::Response::Bech32Address) ImplicitAccountCreationAddress, + /// Prepares to transition an implicit account to an account. + /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) + #[serde(rename_all = "camelCase")] + PrepareImplicitAccountTransition { output_id: OutputId }, /// Returns the implicit accounts of the wallet. /// Expected response: [`OutputsData`](crate::Response::OutputsData) ImplicitAccounts, diff --git a/bindings/core/src/method_handler/wallet.rs b/bindings/core/src/method_handler/wallet.rs index d7dc3d1121..b70d2e6862 100644 --- a/bindings/core/src/method_handler/wallet.rs +++ b/bindings/core/src/method_handler/wallet.rs @@ -210,6 +210,10 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM let implicit_account_creation_address = wallet.implicit_account_creation_address().await?; Response::Bech32Address(implicit_account_creation_address) } + WalletMethod::PrepareImplicitAccountTransition { output_id } => { + let data = wallet.prepare_implicit_account_transition(&output_id).await?; + Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) + } WalletMethod::ImplicitAccounts => { let implicit_accounts = wallet.implicit_accounts().await; Response::OutputsData(implicit_accounts.iter().map(OutputDataDto::from).collect()) diff --git a/bindings/core/src/response.rs b/bindings/core/src/response.rs index 365b629b09..da71cecd3d 100644 --- a/bindings/core/src/response.rs +++ b/bindings/core/src/response.rs @@ -284,6 +284,7 @@ pub enum Response { /// - [`PrepareStopParticipating`](crate::method::WalletMethod::PrepareStopParticipating) /// - [`PrepareTransaction`](crate::method::WalletMethod::PrepareTransaction) /// - [`PrepareVote`](crate::method::WalletMethod::PrepareVote) + /// - [`PrepareImplicitAccountTransition`](crate::method::WalletMethod::PrepareImplicitAccountTransition) PreparedTransaction(PreparedTransactionDataDto), /// Response for: /// - [`PrepareCreateNativeToken`](crate::method::WalletMethod::PrepareCreateNativeToken), diff --git a/bindings/nodejs/lib/types/wallet/bridge/account.ts b/bindings/nodejs/lib/types/wallet/bridge/account.ts deleted file mode 100644 index 26c33803ac..0000000000 --- a/bindings/nodejs/lib/types/wallet/bridge/account.ts +++ /dev/null @@ -1,384 +0,0 @@ -// Copyright 2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import type { - SendParams, - SendNativeTokenParams, - SendNftParams, - GenerateAddressOptions, -} from '../address'; -import type { Burn, INode, PreparedTransactionData } from '../../client'; -import type { OutputParams } from '../output-params'; -import type { OutputsToClaim } from '../output'; -import type { SignedTransactionData } from '../signed-transaction-data'; -import type { - AccountOutputParams, - CreateNativeTokenParams, - TransactionOptions, - MintNftParams, -} from '../transaction-options'; -import type { - ParticipationEventId, - ParticipationEventRegistrationOptions, - ParticipationEventType, -} from '../participation'; -import type { ConsolidationParams } from '../consolidation-params'; -import { - FilterOptions, - HexEncodedAmount, - NumericString, - Output, - OutputId, - SyncOptions, - TokenId, - TransactionId, -} from '../../'; - -export type __PrepareBurnMethod__ = { - name: 'prepareBurn'; - data: { - burn: Burn; - options?: TransactionOptions; - }; -}; - -export type __ClaimOutputsMethod__ = { - name: 'claimOutputs'; - data: { - outputIdsToClaim: OutputId[]; - }; -}; - -export type __PrepareConsolidateOutputsMethod__ = { - name: 'prepareConsolidateOutputs'; - data: { - params: ConsolidationParams; - }; -}; - -export type __PrepareCreateAccountOutputMethod__ = { - name: 'prepareCreateAccountOutput'; - data: { - params?: AccountOutputParams; - options?: TransactionOptions; - }; -}; - -export type __PrepareMeltNativeTokenMethod__ = { - name: 'prepareMeltNativeToken'; - data: { - tokenId: TokenId; - meltAmount: HexEncodedAmount; - options?: TransactionOptions; - }; -}; - -export type __DeregisterParticipationEventMethod__ = { - name: 'deregisterParticipationEvent'; - data: { - eventId: ParticipationEventId; - }; -}; - -export type __GenerateEd25519AddressesMethod__ = { - name: 'generateEd25519Addresses'; - data: { - amount: number; - options?: GenerateAddressOptions; - }; -}; - -export type __GetBalanceMethod__ = { - name: 'getBalance'; -}; - -export type __GetIncomingTransactionMethod__ = { - name: 'getIncomingTransaction'; - data: { - transactionId: TransactionId; - }; -}; - -export type __GetOutputMethod__ = { - name: 'getOutput'; - data: { - outputId: OutputId; - }; -}; - -export type __GetFoundryOutputMethod__ = { - name: 'getFoundryOutput'; - data: { - tokenId: TokenId; - }; -}; - -export type __ClaimableOutputsMethod__ = { - name: 'claimableOutputs'; - data: { - outputsToClaim: OutputsToClaim; - }; -}; - -export type __GetTransactionMethod__ = { - name: 'getTransaction'; - data: { - transactionId: TransactionId; - }; -}; - -export type __AddressesMethod__ = { - name: 'addresses'; -}; - -export type __AddressesWithUnspentOutputsMethod__ = { - name: 'addressesWithUnspentOutputs'; -}; - -export type __OutputsMethod__ = { - name: 'outputs'; - data: { - filterOptions?: FilterOptions; - }; -}; - -export type __PendingTransactionsMethod__ = { - name: 'pendingTransactions'; -}; - -export type __ImplicitAccountCreationAddressMethod__ = { - name: 'implicitAccountCreationAddress'; -}; - -export type __AccountsMethod__ = { - name: 'accounts'; -}; - -export type __ImplicitAccountsMethod__ = { - name: 'implicitAccounts'; -}; - -export type __IncomingTransactionsMethod__ = { - name: 'incomingTransactions'; -}; - -export type __TransactionsMethod__ = { - name: 'transactions'; -}; - -export type __UnspentOutputsMethod__ = { - name: 'unspentOutputs'; - data: { - filterOptions?: FilterOptions; - }; -}; - -export type __PrepareMintNativeTokenMethod__ = { - name: 'prepareMintNativeToken'; - data: { - tokenId: TokenId; - mintAmount: HexEncodedAmount; - options?: TransactionOptions; - }; -}; - -export type __PrepareCreateNativeTokenMethod__ = { - name: 'prepareCreateNativeToken'; - data: { - params: CreateNativeTokenParams; - options?: TransactionOptions; - }; -}; - -export type __PrepareMintNftsMethod__ = { - name: 'prepareMintNfts'; - data: { - params: MintNftParams[]; - options?: TransactionOptions; - }; -}; - -export type __PrepareOutputMethod__ = { - name: 'prepareOutput'; - data: { - params: OutputParams; - transactionOptions?: TransactionOptions; - }; -}; - -export type __PrepareSendMethod__ = { - name: 'prepareSend'; - data: { - params: SendParams[]; - options?: TransactionOptions; - }; -}; - -export type __PrepareTransactionMethod__ = { - name: 'prepareTransaction'; - data: { - outputs: Output[]; - options?: TransactionOptions; - }; -}; - -export type __RegisterParticipationEventsMethod__ = { - name: 'registerParticipationEvents'; - data: { - options: ParticipationEventRegistrationOptions; - }; -}; - -export type __ReissueTransactionUntilIncludedMethod__ = { - name: 'reissueTransactionUntilIncluded'; - data: { - transactionId: TransactionId; - interval?: number; - maxAttempts?: number; - }; -}; - -export type __SendMethod__ = { - name: 'send'; - data: { - amount: NumericString; - address: string; - options?: TransactionOptions; - }; -}; - -export type __SendWithParamsMethod__ = { - name: 'sendWithParams'; - data: { - params: SendParams[]; - options?: TransactionOptions; - }; -}; - -export type __PrepareSendNativeTokensMethod__ = { - name: 'prepareSendNativeTokens'; - data: { - params: SendNativeTokenParams[]; - options?: TransactionOptions; - }; -}; - -export type __PrepareSendNftMethod__ = { - name: 'prepareSendNft'; - data: { - params: SendNftParams[]; - options?: TransactionOptions; - }; -}; - -export type __SendOutputsMethod__ = { - name: 'sendOutputs'; - data: { - outputs: Output[]; - options?: TransactionOptions; - }; -}; - -export type __SetAliasMethod__ = { - name: 'setAlias'; - data: { - alias: string; - }; -}; - -export type __SetDefaultSyncOptionsMethod__ = { - name: 'setDefaultSyncOptions'; - data: { - options: SyncOptions; - }; -}; - -export type __SignTransactionMethod__ = { - name: 'signTransaction'; - data: { - preparedTransactionData: PreparedTransactionData; - }; -}; - -export type __SignAndSubmitTransactionMethod__ = { - name: 'signAndSubmitTransaction'; - data: { - preparedTransactionData: PreparedTransactionData; - }; -}; - -export type __SubmitAndStoreTransactionMethod__ = { - name: 'submitAndStoreTransaction'; - data: { - signedTransactionData: SignedTransactionData; - }; -}; - -export type __SyncAccountMethod__ = { - name: 'sync'; - data: { - options?: SyncOptions; - }; -}; - -export type __PrepareVoteMethod__ = { - name: 'prepareVote'; - data: { - eventId?: ParticipationEventId; - answers?: number[]; - }; -}; - -export type __PrepareStopParticipatingMethod__ = { - name: 'prepareStopParticipating'; - data: { - eventId: ParticipationEventId; - }; -}; - -export type __GetParticipationOverviewMethod__ = { - name: 'getParticipationOverview'; - data: { - eventIds?: ParticipationEventId[]; - }; -}; - -export type __PrepareIncreaseVotingPowerMethod__ = { - name: 'prepareIncreaseVotingPower'; - data: { - amount: NumericString; - }; -}; - -export type __GetParticipationEventMethod__ = { - name: 'getParticipationEvent'; - data: { - eventId: ParticipationEventId; - }; -}; - -export type __GetParticipationEventIdsMethod__ = { - name: 'getParticipationEventIds'; - data: { - node: INode; - eventType?: ParticipationEventType; - }; -}; - -export type __GetParticipationEventsMethod__ = { - name: 'getParticipationEvents'; -}; - -export type __GetParticipationEventStatusMethod__ = { - name: 'getParticipationEventStatus'; - data: { - eventId: ParticipationEventId; - }; -}; - -export type __PrepareDecreaseVotingPowerMethod__ = { - name: 'prepareDecreaseVotingPower'; - data: { - amount: NumericString; - }; -}; diff --git a/bindings/nodejs/lib/types/wallet/bridge/index.ts b/bindings/nodejs/lib/types/wallet/bridge/index.ts index 2869168102..e5d818dbb6 100644 --- a/bindings/nodejs/lib/types/wallet/bridge/index.ts +++ b/bindings/nodejs/lib/types/wallet/bridge/index.ts @@ -12,6 +12,7 @@ import type { __OutputsMethod__, __PendingTransactionsMethod__, __ImplicitAccountCreationAddressMethod__, + __PrepareImplicitAccountTransitionMethod__, __AccountsMethod__, __ImplicitAccountsMethod__, __IncomingTransactionsMethod__, @@ -85,6 +86,7 @@ export type __WalletMethod__ = | __OutputsMethod__ | __PendingTransactionsMethod__ | __ImplicitAccountCreationAddressMethod__ + | __PrepareImplicitAccountTransitionMethod__ | __AccountsMethod__ | __ImplicitAccountsMethod__ | __IncomingTransactionsMethod__ diff --git a/bindings/nodejs/lib/types/wallet/bridge/wallet.ts b/bindings/nodejs/lib/types/wallet/bridge/wallet.ts index 895a9aeeaf..945086d9fd 100644 --- a/bindings/nodejs/lib/types/wallet/bridge/wallet.ts +++ b/bindings/nodejs/lib/types/wallet/bridge/wallet.ts @@ -233,6 +233,13 @@ export type __ImplicitAccountCreationAddressMethod__ = { name: 'implicitAccountCreationAddress'; }; +export type __PrepareImplicitAccountTransitionMethod__ = { + name: 'prepareImplicitAccountTransition'; + data: { + outputId: OutputId; + }; +}; + export type __ImplicitAccountsMethod__ = { name: 'implicitAccounts'; }; diff --git a/bindings/nodejs/lib/wallet/wallet.ts b/bindings/nodejs/lib/wallet/wallet.ts index 071d0b3667..1b8e547a56 100644 --- a/bindings/nodejs/lib/wallet/wallet.ts +++ b/bindings/nodejs/lib/wallet/wallet.ts @@ -895,6 +895,41 @@ export class Wallet { return JSON.parse(response).payload; } + /** + * Transitions an implicit account to an account. + * + * @param outputId Identifier of the implicit account output. + * @returns The created transaction. + */ + async implicitAccountTransition( + outputId: OutputId, + ): Promise { + return (await this.prepareImplicitAccountTransition(outputId)).send(); + } + + /** + * Prepares to transition an implicit account to an account. + * + * @param outputId Identifier of the implicit account output. + * @returns The prepared transaction. + */ + async prepareImplicitAccountTransition( + outputId: OutputId, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'prepareImplicitAccountTransition', + data: { outputId }, + }); + + const parsed = JSON.parse( + response, + ) as Response; + return new PreparedTransaction( + plainToInstance(PreparedTransactionData, parsed.payload), + this, + ); + } + /** * Returns the implicit accounts of the wallet. * diff --git a/bindings/python/iota_sdk/wallet/account.py b/bindings/python/iota_sdk/wallet/account.py index 3bc91c0181..02ce968a41 100644 --- a/bindings/python/iota_sdk/wallet/account.py +++ b/bindings/python/iota_sdk/wallet/account.py @@ -278,6 +278,24 @@ def implicit_account_creation_address(self) -> str: 'implicitAccountCreationAddress' ) + def implicit_account_transition( + self, output_id: OutputId) -> TransactionWithMetadata: + """Transitions an implicit account to an account. + """ + return self.prepare_implicit_account_transition(output_id).send() + + def prepare_implicit_account_transition( + self, output_id: OutputId) -> PreparedTransaction: + """Prepares to transition an implicit account to an account. + """ + prepared = self._call_account_method( + 'implicitAccountTransition', { + 'outputId': output_id + } + ) + return PreparedTransaction( + account=self, prepared_transaction_data=prepared) + def accounts(self) -> List[OutputData]: """Returns the accounts of the wallet. """ diff --git a/cli/src/wallet_cli/completer.rs b/cli/src/wallet_cli/completer.rs index 140183b0b6..af3bc3a843 100644 --- a/cli/src/wallet_cli/completer.rs +++ b/cli/src/wallet_cli/completer.rs @@ -25,6 +25,7 @@ const WALLET_COMMANDS: &[&str] = &[ "exit", "faucet", "implicit-account-creation-address", + "implicit-account-transition", "implicit-accounts", "melt-native-token", "mint-native-token", diff --git a/cli/src/wallet_cli/mod.rs b/cli/src/wallet_cli/mod.rs index 5715f0dabe..9600f81abf 100644 --- a/cli/src/wallet_cli/mod.rs +++ b/cli/src/wallet_cli/mod.rs @@ -120,6 +120,11 @@ pub enum WalletCommand { }, /// Returns the implicit account creation address of the wallet if it is Ed25519 based. ImplicitAccountCreationAddress, + /// Transitions an implicit account to an account. + ImplicitAccountTransition { + /// Identifier of the implicit account output. + output_id: OutputId, + }, /// Lists the implicit accounts of the wallet. ImplicitAccounts, /// Mint additional native tokens. @@ -578,6 +583,19 @@ pub async fn implicit_account_creation_address_command(wallet: &Wallet) -> Resul Ok(()) } +// `implicit-account-transition` command +pub async fn implicit_account_transition_command(wallet: &Wallet, output_id: OutputId) -> Result<(), Error> { + let transaction = wallet.implicit_account_transition(&output_id).await?; + + println_log_info!( + "Implicit account transition transaction sent:\n{:?}\n{:?}", + transaction.transaction_id, + transaction.block_id + ); + + Ok(()) +} + // `implicit-accounts` command pub async fn implicit_accounts_command(wallet: &Wallet) -> Result<(), Error> { print_outputs(wallet.implicit_accounts().await, "Implicit accounts:").await @@ -1103,6 +1121,9 @@ pub async fn prompt_internal( WalletCommand::ImplicitAccountCreationAddress => { implicit_account_creation_address_command(wallet).await } + WalletCommand::ImplicitAccountTransition { output_id } => { + implicit_account_transition_command(wallet, output_id).await + } WalletCommand::ImplicitAccounts => implicit_accounts_command(wallet).await, WalletCommand::MeltNativeToken { token_id, amount } => { melt_native_token_command(wallet, token_id, amount).await diff --git a/sdk/src/client/api/block_builder/input_selection/mod.rs b/sdk/src/client/api/block_builder/input_selection/mod.rs index e9efec2740..da4a4014f3 100644 --- a/sdk/src/client/api/block_builder/input_selection/mod.rs +++ b/sdk/src/client/api/block_builder/input_selection/mod.rs @@ -14,6 +14,7 @@ use std::collections::HashSet; use packable::PackableExt; +use self::requirement::account::is_account_with_id; pub use self::{burn::Burn, error::Error, requirement::Requirement}; use crate::{ client::{api::types::RemainderData, secret::types::InputSigningData}, @@ -68,6 +69,7 @@ impl InputSelection { Address::Account(account_address) => Ok(Some(Requirement::Account(*account_address.account_id()))), Address::Nft(nft_address) => Ok(Some(Requirement::Nft(*nft_address.nft_id()))), Address::Anchor(_) => Err(Error::UnsupportedAddressType(AnchorAddress::KIND)), + Address::ImplicitAccountCreation(_) => Ok(None), Address::Restricted(_) => Ok(None), _ => todo!("What do we do here?"), } @@ -235,10 +237,12 @@ impl InputSelection { .unwrap() .0; - if let Address::Restricted(restricted_address) = required_address { - self.addresses.contains(restricted_address.address()) - } else { - self.addresses.contains(&required_address) + match required_address { + Address::ImplicitAccountCreation(implicit_account_creation) => self + .addresses + .contains(&Address::from(*implicit_account_creation.ed25519_address())), + Address::Restricted(restricted) => self.addresses.contains(restricted.address()), + _ => self.addresses.contains(&required_address), } }) } @@ -401,6 +405,11 @@ impl InputSelection { input_native_tokens_builder.add_native_token(*native_token)?; } match &input.output { + Output::Basic(basic) => { + if basic.is_implicit_account() { + input_accounts.push(input); + } + } Output::Account(_) => { input_accounts.push(input); } @@ -432,25 +441,27 @@ impl InputSelection { let account_input = input_accounts .iter() - .find(|i| { - if let Output::Account(account_input) = &i.output { - *account_output.account_id() == account_input.account_id_non_null(i.output_id()) - } else { - false - } - }) + .find(|i| is_account_with_id(&i.output, account_output.account_id(), i.output_id())) .expect("ISA is broken because there is no account input"); - if let Err(err) = AccountOutput::transition_inner( - account_input.output.as_account(), - account_output, - &input_chains_foundries, - &self.outputs, - ) { - log::debug!("validate_transitions error {err:?}"); - return Err(Error::UnfulfillableRequirement(Requirement::Account( - *account_output.account_id(), - ))); + match &account_input.output { + Output::Account(account) => { + if let Err(err) = AccountOutput::transition_inner( + account, + account_output, + &input_chains_foundries, + &self.outputs, + ) { + log::debug!("validate_transitions error {err:?}"); + return Err(Error::UnfulfillableRequirement(Requirement::Account( + *account_output.account_id(), + ))); + } + } + Output::Basic(_) => { + // TODO https://github.com/iotaledger/iota-sdk/issues/1664 + } + _ => panic!("unreachable: \"input_accounts\" only contains account outputs and implicit account (basic) outputs") } } Output::Foundry(foundry_output) => { diff --git a/sdk/src/client/api/block_builder/input_selection/requirement/account.rs b/sdk/src/client/api/block_builder/input_selection/requirement/account.rs index cc1cf01fe9..87f8cd20cd 100644 --- a/sdk/src/client/api/block_builder/input_selection/requirement/account.rs +++ b/sdk/src/client/api/block_builder/input_selection/requirement/account.rs @@ -9,10 +9,10 @@ use crate::{ /// Checks if an output is an account with output ID that matches the given account ID. pub(crate) fn is_account_with_id(output: &Output, account_id: &AccountId, output_id: &OutputId) -> bool { - if let Output::Account(account) = output { - &account.account_id_non_null(output_id) == account_id - } else { - false + match output { + Output::Basic(basic) => basic.is_implicit_account() && &AccountId::from(output_id) == account_id, + Output::Account(account) => &account.account_id_non_null(output_id) == account_id, + _ => false, } } diff --git a/sdk/src/client/secret/mod.rs b/sdk/src/client/secret/mod.rs index 7293ccfd24..c9af7bc968 100644 --- a/sdk/src/client/secret/mod.rs +++ b/sdk/src/client/secret/mod.rs @@ -553,8 +553,10 @@ where // We can only sign ed25519 addresses and block_indexes needs to contain the account or nft // address already at this point, because the reference index needs to be lower // than the current block index - if !input_address.is_ed25519() { - Err(InputSelectionError::MissingInputWithEd25519Address)?; + match &input_address { + Address::Ed25519(_) | Address::ImplicitAccountCreation(_) => {} + Address::Restricted(restricted) if restricted.address().is_ed25519() => {} + _ => Err(InputSelectionError::MissingInputWithEd25519Address)?, } let chain = input.chain.ok_or(Error::MissingBip32Chain)?; diff --git a/sdk/src/types/block/output/account.rs b/sdk/src/types/block/output/account.rs index 28bb146368..b9b1555725 100644 --- a/sdk/src/types/block/output/account.rs +++ b/sdk/src/types/block/output/account.rs @@ -42,7 +42,11 @@ impl From<&OutputId> for AccountId { impl AccountId { /// pub fn or_from_output_id(self, output_id: &OutputId) -> Self { - if self.is_null() { Self::from(output_id) } else { self } + if self.is_null() { + Self::from(output_id) + } else { + self + } } } diff --git a/sdk/src/types/block/output/mod.rs b/sdk/src/types/block/output/mod.rs index 0f13e5bed2..1a43ccc664 100644 --- a/sdk/src/types/block/output/mod.rs +++ b/sdk/src/types/block/output/mod.rs @@ -319,6 +319,14 @@ impl Output { (None, Some(Self::Delegation(next_state))) => DelegationOutput::creation(next_state, context), // Transitions. + (Some(Self::Basic(current_state)), Some(Self::Account(_next_state))) => { + if !current_state.is_implicit_account() { + Err(StateTransitionError::UnsupportedStateTransition) + } else { + // TODO https://github.com/iotaledger/iota-sdk/issues/1664 + Ok(()) + } + } (Some(Self::Account(current_state)), Some(Self::Account(next_state))) => { AccountOutput::transition(current_state, next_state, context) } diff --git a/sdk/src/types/block/semantic.rs b/sdk/src/types/block/semantic.rs index 6f2b7f726b..c22695ac45 100644 --- a/sdk/src/types/block/semantic.rs +++ b/sdk/src/types/block/semantic.rs @@ -10,7 +10,7 @@ use primitive_types::U256; use crate::types::block::{ address::{Address, AddressCapabilityFlag}, output::{ - AnchorOutput, ChainId, FoundryId, NativeTokens, Output, OutputId, StateTransitionError, TokenId, + AccountId, AnchorOutput, ChainId, FoundryId, NativeTokens, Output, OutputId, StateTransitionError, TokenId, UnlockCondition, }, payload::signed_transaction::{Transaction, TransactionCapabilityFlag, TransactionId, TransactionSigningHash}, @@ -216,6 +216,32 @@ impl<'a> SemanticValidationContext<'a> { inputs: &'a [(&'a OutputId, &'a Output)], unlocks: &'a Unlocks, ) -> Self { + let input_chains = inputs + .iter() + .filter_map(|(output_id, input)| { + if input.is_implicit_account() { + Some((ChainId::from(AccountId::from(*output_id)), *input)) + } else { + input + .chain_id() + .map(|chain_id| (chain_id.or_from_output_id(output_id), *input)) + } + }) + .collect(); + let output_chains = transaction + .outputs() + .iter() + .enumerate() + .filter_map(|(index, output)| { + output.chain_id().map(|chain_id| { + ( + chain_id.or_from_output_id(&OutputId::new(*transaction_id, index as u16).unwrap()), + output, + ) + }) + }) + .collect(); + Self { transaction, transaction_signing_hash: transaction.signing_hash(), @@ -224,30 +250,11 @@ impl<'a> SemanticValidationContext<'a> { input_amount: 0, input_mana: 0, input_native_tokens: BTreeMap::::new(), - input_chains: inputs - .iter() - .filter_map(|(output_id, input)| { - input - .chain_id() - .map(|chain_id| (chain_id.or_from_output_id(output_id), *input)) - }) - .collect(), + input_chains, output_amount: 0, output_mana: 0, output_native_tokens: BTreeMap::::new(), - output_chains: transaction - .outputs() - .iter() - .enumerate() - .filter_map(|(index, output)| { - output.chain_id().map(|chain_id| { - ( - chain_id.or_from_output_id(&OutputId::new(*transaction_id, index as u16).unwrap()), - output, - ) - }) - }) - .collect(), + output_chains, unlocked_addresses: HashSet::new(), storage_deposit_returns: HashMap::new(), simple_deposits: HashMap::new(), diff --git a/sdk/src/wallet/error.rs b/sdk/src/wallet/error.rs index e16408b6e7..a23cb78d59 100644 --- a/sdk/src/wallet/error.rs +++ b/sdk/src/wallet/error.rs @@ -124,6 +124,9 @@ pub enum Error { /// 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, + /// Implicit account not found. + #[error("implicit account not found")] + ImplicitAccountNotFound, } // Serialize type with Display error diff --git a/sdk/src/wallet/operations/transaction/account.rs b/sdk/src/wallet/operations/transaction/account.rs new file mode 100644 index 0000000000..099f9f1fcf --- /dev/null +++ b/sdk/src/wallet/operations/transaction/account.rs @@ -0,0 +1,83 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use crate::{ + client::{api::PreparedTransactionData, secret::SecretManage}, + types::block::{ + address::Address, + output::{ + feature::{BlockIssuerFeature, BlockIssuerKey, BlockIssuerKeys, Ed25519BlockIssuerKey}, + unlock_condition::AddressUnlockCondition, + AccountId, AccountOutput, OutputId, + }, + }, + wallet::{ + operations::transaction::{TransactionOptions, TransactionWithMetadata}, + Error, Result, Wallet, + }, +}; + +impl Wallet +where + crate::wallet::Error: From, + crate::client::Error: From, +{ + /// Transitions an implicit account to an account. + pub async fn implicit_account_transition(&self, output_id: &OutputId) -> Result { + self.sign_and_submit_transaction(self.prepare_implicit_account_transition(output_id).await?, None) + .await + } + + /// Prepares to transition an implicit account to an account. + pub async fn prepare_implicit_account_transition(&self, output_id: &OutputId) -> Result { + let implicit_account_data = self.data().await.unspent_outputs.get(output_id).cloned(); + + let implicit_account = if let Some(implicit_account_data) = &implicit_account_data { + if implicit_account_data.output.is_implicit_account() { + implicit_account_data.output.as_basic() + } else { + return Err(Error::ImplicitAccountNotFound); + } + } else { + return Err(Error::ImplicitAccountNotFound); + }; + + let public_key = if let Some(bip_path) = self.bip_path().await { + self.secret_manager + .read() + .await + .generate_ed25519_public_keys( + bip_path.coin_type, + bip_path.account, + bip_path.address_index..bip_path.address_index + 1, + None, + ) + .await?[0] + } else { + // TODO https://github.com/iotaledger/iota-sdk/issues/1666 + todo!() + }; + + let account = AccountOutput::build_with_amount(implicit_account.amount(), AccountId::from(output_id)) + .with_mana(implicit_account.mana()) + .with_unlock_conditions([AddressUnlockCondition::from(Address::from( + *implicit_account + .address() + .as_implicit_account_creation() + .ed25519_address(), + ))]) + .with_features([BlockIssuerFeature::new( + u32::MAX, + BlockIssuerKeys::from_vec(vec![BlockIssuerKey::from(Ed25519BlockIssuerKey::from(public_key))])?, + )?]) + .finish_output()?; + + let transaction_options = TransactionOptions { + custom_inputs: Some(vec![*output_id]), + ..Default::default() + }; + + self.prepare_transaction(vec![account], transaction_options.clone()) + .await + } +} diff --git a/sdk/src/wallet/operations/transaction/mod.rs b/sdk/src/wallet/operations/transaction/mod.rs index 78cfcdfbf7..d3e2d5498c 100644 --- a/sdk/src/wallet/operations/transaction/mod.rs +++ b/sdk/src/wallet/operations/transaction/mod.rs @@ -1,6 +1,7 @@ // Copyright 2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +pub(crate) mod account; mod build_transaction; pub(crate) mod high_level; mod input_selection; @@ -185,7 +186,7 @@ where wallet_data.pending_transactions.insert(transaction_id); #[cfg(feature = "storage")] { - // TODO: maybe better to use the wallt address as identifier now? + // TODO: maybe better to use the wallet address as identifier now? log::debug!("[TRANSACTION] storing wallet"); self.save(Some(&wallet_data)).await?; } diff --git a/sdk/src/wallet/operations/transaction/submit_transaction.rs b/sdk/src/wallet/operations/transaction/submit_transaction.rs index 4ce30c6a8f..5c36dcfb1b 100644 --- a/sdk/src/wallet/operations/transaction/submit_transaction.rs +++ b/sdk/src/wallet/operations/transaction/submit_transaction.rs @@ -5,7 +5,7 @@ use crate::wallet::events::types::{TransactionProgressEvent, WalletEvent}; use crate::{ client::secret::{SecretManage, SignBlock}, - types::block::{payload::Payload, BlockId}, + types::block::{payload::Payload, BlockId, IssuerId}, wallet::{operations::transaction::SignedTransactionPayload, Error, Wallet}, }; @@ -23,7 +23,8 @@ where let block = self .client() - .build_basic_block(todo!("issuer id"), Some(Payload::from(transaction_payload))) + // TODO https://github.com/iotaledger/iota-sdk/issues/1665 to set IssuerId + .build_basic_block(IssuerId::null(), Some(Payload::from(transaction_payload))) .await? .sign_ed25519( &*self.get_secret_manager().read().await, @@ -35,7 +36,9 @@ where self.emit(WalletEvent::TransactionProgress(TransactionProgressEvent::Broadcasting)) .await; let block_id = self.client().post_block(&block).await?; + log::debug!("[TRANSACTION] submitted block {}", block_id); + Ok(block_id) } }