From 4f3d1f10c7a264d40a2876c328973405d72e822f Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 29 Aug 2024 12:24:26 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../tap/di/domain/TransactionDomainModule.kt | 15 ++ .../DefaultTransactionRepository.kt | 15 ++ .../domain/feedback/FeedbackDataBuilder.kt | 9 +- .../feedback/models/FeedbackEmailType.kt | 4 +- .../utils/EmailMessageBodyResolver.kt | 10 +- .../transaction/TransactionRepository.kt | 8 + .../usecase/SendMultipleTransactionUseCase.kt | 147 ++++++++++++++++++ .../SetConfirmationStateAssentTransformer.kt | 18 +-- ...etConfirmationStateCompletedTransformer.kt | 18 +-- .../viewmodel/StakingViewModel.kt | 102 +++++++----- gradle/dependencies.toml | 2 +- 11 files changed, 268 insertions(+), 80 deletions(-) create mode 100644 domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendMultipleTransactionUseCase.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt index 73c0236567..e50891f228 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt @@ -42,6 +42,21 @@ internal object TransactionDomainModule { ) } + @Provides + @Singleton + fun provideSendMultipleTransactionUseCase( + cardSdkConfigRepository: CardSdkConfigRepository, + transactionRepository: TransactionRepository, + walletManagersFacade: WalletManagersFacade, + ): SendMultipleTransactionUseCase { + return SendMultipleTransactionUseCase( + demoConfig = DemoConfig(), + cardSdkConfigRepository = cardSdkConfigRepository, + transactionRepository = transactionRepository, + walletManagersFacade = walletManagersFacade, + ) + } + @Provides @Singleton fun provideAssociateAssetUseCase( diff --git a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt index 87f16d07be..0452da5a7c 100644 --- a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt +++ b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt @@ -158,6 +158,21 @@ internal class DefaultTransactionRepository( (walletManager as TransactionSender).send(txData, signer) } + override suspend fun sendMultipleTransactions( + txsData: List, + signer: CommonSigner, + userWalletId: UserWalletId, + network: Network, + ) = withContext(coroutineDispatcherProvider.io) { + val blockchain = Blockchain.fromId(network.id.value) + val walletManager = walletManagersFacade.getOrCreateWalletManager( + userWalletId = userWalletId, + blockchain = blockchain, + derivationPath = network.derivationPath.value, + ) + (walletManager as TransactionSender).sendMultiple(txsData, signer) + } + override fun createTransactionDataExtras( data: String, network: Network, diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/FeedbackDataBuilder.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/FeedbackDataBuilder.kt index 30d5dd83a8..91f1f6b990 100644 --- a/domain/feedback/src/main/java/com/tangem/domain/feedback/FeedbackDataBuilder.kt +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/FeedbackDataBuilder.kt @@ -86,10 +86,13 @@ internal class FeedbackDataBuilder { builder.appendKeyValue("Fee", error.fee ?: "Unable to receive") } - fun addStakingInfo(validatorName: String?, transactionType: String?, unsignedTransaction: String?) { + fun addStakingInfo(validatorName: String?, transactionTypes: List, unsignedTransactions: List) { builder.appendKeyValue("Validator", validatorName ?: "unknown") - builder.appendKeyValue("Action", transactionType ?: "unknown") - builder.appendKeyValue("Unsigned transaction", unsignedTransaction ?: "unknown") + builder.appendKeyValue("Action", transactionTypes.joinToString(separator = "\n").ifEmpty { "unknown" }) + builder.appendKeyValue( + key = "Unsigned transaction", + value = unsignedTransactions.joinToString(separator = "\n") { it ?: "unknown" }.ifEmpty { "unknown" }, + ) } fun addDelimiter(): StringBuilder = builder.appendDelimiter() diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/models/FeedbackEmailType.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/models/FeedbackEmailType.kt index 1be0afb1ff..735365aeac 100644 --- a/domain/feedback/src/main/java/com/tangem/domain/feedback/models/FeedbackEmailType.kt +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/models/FeedbackEmailType.kt @@ -27,7 +27,7 @@ sealed interface FeedbackEmailType { data class StakingProblem( override val cardInfo: CardInfo, val validatorName: String?, - val transactionType: String?, - val unsignedTransaction: String?, + val transactionTypes: List, + val unsignedTransactions: List, ) : FeedbackEmailType } \ No newline at end of file diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageBodyResolver.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageBodyResolver.kt index c314b7a4bc..ae17e1da30 100644 --- a/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageBodyResolver.kt +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageBodyResolver.kt @@ -26,8 +26,8 @@ internal class EmailMessageBodyResolver( is FeedbackEmailType.StakingProblem -> addStakingProblemBody( type.cardInfo, type.validatorName, - type.transactionType, - type.unsignedTransaction, + type.transactionTypes, + type.unsignedTransactions, ) } @@ -81,8 +81,8 @@ internal class EmailMessageBodyResolver( private suspend fun FeedbackDataBuilder.addStakingProblemBody( cardInfo: CardInfo, validatorName: String?, - transactionType: String?, - unsignedTransaction: String?, + transactionTypes: List, + unsignedTransactions: List, ) { addCardInfo(cardInfo) addDelimiter() @@ -102,7 +102,7 @@ internal class EmailMessageBodyResolver( addDelimiter() } - addStakingInfo(validatorName, transactionType, unsignedTransaction) + addStakingInfo(validatorName, transactionTypes, unsignedTransactions) addDelimiter() addPhoneInfo(phoneInfo = feedbackRepository.getPhoneInfo()) diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt index 6784c1726f..247f3c7605 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt @@ -6,6 +6,7 @@ import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.TransactionExtras import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionSendResult +import com.tangem.blockchain.common.transaction.TransactionsSendResult import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network import com.tangem.domain.transaction.models.TransactionType @@ -58,6 +59,13 @@ interface TransactionRepository { network: Network, ): com.tangem.blockchain.extensions.Result + suspend fun sendMultipleTransactions( + txsData: List, + signer: CommonSigner, + userWalletId: UserWalletId, + network: Network, + ): com.tangem.blockchain.extensions.Result + fun createTransactionDataExtras( data: String, network: Network, diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendMultipleTransactionUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendMultipleTransactionUseCase.kt new file mode 100644 index 0000000000..c7fce8b278 --- /dev/null +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendMultipleTransactionUseCase.kt @@ -0,0 +1,147 @@ +package com.tangem.domain.transaction.usecase + +import arrow.core.Either +import arrow.core.left +import arrow.core.right +import com.tangem.blockchain.common.BlockchainSdkError +import com.tangem.blockchain.common.TransactionData +import com.tangem.blockchain.common.TransactionSigner +import com.tangem.blockchain.common.transaction.TransactionsSendResult +import com.tangem.blockchain.extensions.Result +import com.tangem.blockchain.network.ResultChecker +import com.tangem.common.core.TangemSdkError +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.domain.card.repository.CardSdkConfigRepository +import com.tangem.domain.common.TapWorkarounds.isStart2Coin +import com.tangem.domain.common.TapWorkarounds.isTangemTwins +import com.tangem.domain.demo.DemoConfig +import com.tangem.domain.demo.DemoTransactionSender +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.transaction.R +import com.tangem.domain.transaction.TransactionRepository +import com.tangem.domain.transaction.error.SendTransactionError +import com.tangem.domain.transaction.error.SendTransactionError.Companion.USER_CANCELLED_ERROR_CODE +import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.sdk.extensions.localizedDescriptionRes +import com.tangem.utils.toFormattedString + +// TODO [REDACTED_TASK_KEY] merge with SendTransactionUseCase +class SendMultipleTransactionUseCase( + private val demoConfig: DemoConfig, + private val cardSdkConfigRepository: CardSdkConfigRepository, + private val transactionRepository: TransactionRepository, + private val walletManagersFacade: WalletManagersFacade, +) { + suspend operator fun invoke( + txsData: List, + userWallet: UserWallet, + network: Network, + ): Either> { + val card = userWallet.scanResponse.card + val isCardNotBackedUp = card.backupStatus?.isActive != true && !card.isTangemTwins + + val signer = cardSdkConfigRepository.getCommonSigner(cardId = card.cardId.takeIf { isCardNotBackedUp }) + + val linkedTerminal = cardSdkConfigRepository.isLinkedTerminal() + if (userWallet.scanResponse.card.isStart2Coin) { + cardSdkConfigRepository.setLinkedTerminal(false) + } + val sendResult = try { + if (demoConfig.isDemoCardId(cardId = userWallet.cardId)) { + sendDemo( + userWallet = userWallet, + network = network, + transactionsData = txsData, + signer = signer, + ) + } else { + val sendResult = transactionRepository.sendMultipleTransactions( + txsData = txsData, + signer = signer, + userWalletId = userWallet.walletId, + network = network, + ) + when (sendResult) { + is Result.Failure -> handleError(sendResult).left() + is Result.Success -> sendResult.data.right() + } + } + } catch (ex: Exception) { + cardSdkConfigRepository.setLinkedTerminal(linkedTerminal) + SendTransactionError.DataError(ex.message).left() + } + + cardSdkConfigRepository.setLinkedTerminal(linkedTerminal) + return sendResult.fold( + ifRight = { result -> result.hashes.right() }, + ifLeft = { it.left() }, + ) + } + + private suspend fun sendDemo( + userWallet: UserWallet, + network: Network, + transactionsData: List, + signer: TransactionSigner, + ): Either { + val demoTransactionSender = DemoTransactionSender( + walletManagersFacade + .getOrCreateWalletManager(userWallet.walletId, network) + ?: error("WalletManager is null"), + ) + + val result = demoTransactionSender.sendMultiple(transactionDataList = transactionsData, signer = signer) + + return if (result is Result.Failure && result.error.customMessage.contains(DemoTransactionSender.ID)) { + SendTransactionError.DemoCardError.left() + } else { + TransactionsSendResult(listOf("hash")).right() + } + } + + private fun handleError(result: Result.Failure): SendTransactionError { + if (ResultChecker.isNetworkError(result)) { + return SendTransactionError.NetworkError( + code = result.error.message, + message = result.error.customMessage, + ) + } + val error = result.error as? BlockchainSdkError ?: return SendTransactionError.UnknownError() + return when (error) { + is BlockchainSdkError.WrappedTangemError -> parseWrappedError(error) + is BlockchainSdkError.CreateAccountUnderfunded -> { + val minAmount = error.minReserve + val minValue = minAmount.value?.toFormattedString(minAmount.decimals).orEmpty() + SendTransactionError.CreateAccountUnderfunded(minValue) + } + else -> { + SendTransactionError.BlockchainSdkError( + code = error.code, + message = error.customMessage, + ) + } + } + } + + private fun parseWrappedError(error: BlockchainSdkError.WrappedTangemError): SendTransactionError { + return if (error.code == USER_CANCELLED_ERROR_CODE) { + SendTransactionError.UserCancelledError + } else { + when (val tangemError = error.tangemError) { + is TangemSdkError -> { + val resource = tangemError.localizedDescriptionRes() + val resId = resource.resId ?: R.string.common_unknown_error + val resArgs = resource.args.map { it.value } + SendTransactionError.TangemSdkError(tangemError.code, resId, wrappedList(resArgs)) + } + is BlockchainSdkError.WrappedTangemError -> { + parseWrappedError(tangemError) // todo remove when sdk errors are revised + } + else -> { + SendTransactionError.BlockchainSdkError(error.code, tangemError.customMessage) + } + } + } + } +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateAssentTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateAssentTransformer.kt index ca470397b6..6a214fde9a 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateAssentTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateAssentTransformer.kt @@ -1,10 +1,8 @@ package com.tangem.features.staking.impl.presentation.state.transformers -import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.transaction.Fee import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.staking.model.stakekit.PendingAction -import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.staking.impl.presentation.state.FeeState import com.tangem.features.staking.impl.presentation.state.InnerConfirmationStakingState @@ -17,31 +15,23 @@ import kotlinx.collections.immutable.ImmutableList internal class SetConfirmationStateAssentTransformer( private val appCurrencyProvider: Provider, private val feeCryptoCurrencyStatus: CryptoCurrencyStatus?, - private val stakingGasEstimate: StakingGasEstimate, + private val fee: Fee, private val pendingActionList: ImmutableList, ) : Transformer { override fun transform(prevState: StakingUiState): StakingUiState { return prevState.copy( - confirmationState = prevState.confirmationState.copyWrapped(stakingGasEstimate), + confirmationState = prevState.confirmationState.copyWrapped(fee), ) } - private fun StakingStates.ConfirmationState.copyWrapped( - gasEstimate: StakingGasEstimate, - ): StakingStates.ConfirmationState { + private fun StakingStates.ConfirmationState.copyWrapped(fee: Fee): StakingStates.ConfirmationState { if (this is StakingStates.ConfirmationState.Data) { val isFeeConvertibleToFiat = feeCryptoCurrencyStatus?.currency?.network?.hasFiatFeeRate == true return copy( innerState = InnerConfirmationStakingState.ASSENT, feeState = FeeState.Content( - fee = Fee.Common( - Amount( - currencySymbol = gasEstimate.token.symbol, - value = gasEstimate.amount, - decimals = gasEstimate.token.decimals, - ), - ), + fee = fee, rate = feeCryptoCurrencyStatus?.value?.fiatRate, isFeeConvertibleToFiat = isFeeConvertibleToFiat, appCurrency = appCurrencyProvider(), diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateCompletedTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateCompletedTransformer.kt index 6b11ad41ab..27e6593bd0 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateCompletedTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateCompletedTransformer.kt @@ -1,9 +1,7 @@ package com.tangem.features.staking.impl.presentation.state.transformers -import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.transaction.Fee import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.staking.impl.presentation.state.* import com.tangem.features.staking.impl.presentation.state.StakingStates @@ -15,32 +13,24 @@ import com.tangem.utils.transformer.Transformer internal class SetConfirmationStateCompletedTransformer( private val appCurrencyProvider: Provider, private val feeCryptoCurrencyStatus: CryptoCurrencyStatus?, - private val stakingGasEstimate: StakingGasEstimate, + private val fee: Fee, private val txUrl: String, ) : Transformer { override fun transform(prevState: StakingUiState): StakingUiState { return prevState.copy( - confirmationState = prevState.confirmationState.copyWrapped(stakingGasEstimate), + confirmationState = prevState.confirmationState.copyWrapped(fee), ) } - private fun StakingStates.ConfirmationState.copyWrapped( - gasEstimate: StakingGasEstimate, - ): StakingStates.ConfirmationState { + private fun StakingStates.ConfirmationState.copyWrapped(fee: Fee): StakingStates.ConfirmationState { if (this is StakingStates.ConfirmationState.Data) { val isFeeConvertibleToFiat = feeCryptoCurrencyStatus?.currency?.network?.hasFiatFeeRate == true return copy( isPrimaryButtonEnabled = true, innerState = InnerConfirmationStakingState.COMPLETED, feeState = FeeState.Content( - fee = Fee.Common( - Amount( - currencySymbol = gasEstimate.token.symbol, - value = gasEstimate.amount, - decimals = gasEstimate.token.decimals, - ), - ), + fee = fee, rate = feeCryptoCurrencyStatus?.value?.fiatRate, isFeeConvertibleToFiat = isFeeConvertibleToFiat, appCurrency = appCurrencyProvider(), diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt index 42a33c0bfd..8dea58c5cd 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt @@ -6,7 +6,9 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import arrow.core.getOrElse +import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.TransactionData +import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.common.routing.AppRoute import com.tangem.common.routing.bundle.unbundle @@ -28,7 +30,6 @@ import com.tangem.domain.staking.model.stakekit.PendingAction import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.domain.staking.model.stakekit.transaction.ActionParams -import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction import com.tangem.domain.staking.model.stakekit.transaction.StakingTransactionType import com.tangem.domain.tokens.FetchPendingTransactionsUseCase @@ -37,10 +38,7 @@ import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.transaction.usecase.CreateApprovalTransactionUseCase -import com.tangem.domain.transaction.usecase.GetAllowanceUseCase -import com.tangem.domain.transaction.usecase.GetFeeUseCase -import com.tangem.domain.transaction.usecase.SendTransactionUseCase +import com.tangem.domain.transaction.usecase.* import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase @@ -70,6 +68,7 @@ import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import timber.log.Timber import java.math.BigDecimal +import java.util.concurrent.CopyOnWriteArrayList import javax.inject.Inject import kotlin.properties.Delegates @@ -87,6 +86,7 @@ internal class StakingViewModel @Inject constructor( private val getConstructedStakingTransactionUseCase: GetConstructedStakingTransactionUseCase, private val estimateGasUseCase: EstimateGasUseCase, private val sendTransactionUseCase: SendTransactionUseCase, + private val sendMultipleTransactionUseCase: SendMultipleTransactionUseCase, private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, private val saveUnsubmittedHashUseCase: SaveUnsubmittedHashUseCase, private val submitHashUseCase: SubmitHashUseCase, @@ -143,7 +143,7 @@ internal class StakingViewModel @Inject constructor( private var stakingApproval: StakingApproval = StakingApproval.Empty private val allowanceTaskScheduler = SingleTaskScheduler() - private var transactionInProgress: StakingTransaction? = null + private val transactionsInProgress: CopyOnWriteArrayList = CopyOnWriteArrayList() private var approvalJobHolder: JobHolder = JobHolder() @@ -187,7 +187,7 @@ internal class StakingViewModel @Inject constructor( val defaultAddress = cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value ?: error("No available address") - val stakingTransaction = getStakingTransactionUseCase( + val stakingTransactions = getStakingTransactionUseCase( userWalletId = userWalletId, network = cryptoCurrencyStatus.currency.network, params = ActionParams( @@ -206,9 +206,9 @@ internal class StakingViewModel @Inject constructor( return@launch } - stakingTransaction + val fullTransactionsData = stakingTransactions .filterNot { it.type == StakingTransactionType.APPROVAL } - .forEach { transaction -> + .map { transaction -> val (constructedTransaction, transactionData) = getConstructedStakingTransactionUseCase( networkId = cryptoCurrencyStatus.currency.network.id.value, fee = fee, @@ -218,17 +218,20 @@ internal class StakingViewModel @Inject constructor( stakingEventFactory.createStakingErrorAlert(it) return@launch } - val gasEstimate = constructedTransaction.gasEstimate - ?: return@launch stakingEventFactory.createGenericErrorAlert("Gas estimate is null") - transactionInProgress = constructedTransaction - sendStakingTransaction( - transactionId = constructedTransaction.id, - gasEstimate = gasEstimate, - txData = transactionData, - pendingActionList = confirmationState.pendingActions, + FullTransactionData( + stakeKitTransaction = constructedTransaction, + tangemTransaction = transactionData, ) } + + transactionsInProgress.addAll(fullTransactionsData.map { it.stakeKitTransaction }) + + sendStakingTransaction( + fullTransactionsData = fullTransactionsData, + fee = fee, + pendingActionList = confirmationState.pendingActions, + ) } } } @@ -291,7 +294,7 @@ internal class StakingViewModel @Inject constructor( validatorAddress: String, ) { val pendingAction = pendingActions.firstOrNull() - val stakingGasEstimate = estimateGasUseCase( + val gasEstimate = estimateGasUseCase( userWalletId = userWalletId, network = cryptoCurrencyStatus.currency.network, params = ActionParams( @@ -313,7 +316,13 @@ internal class StakingViewModel @Inject constructor( SetConfirmationStateAssentTransformer( appCurrencyProvider = Provider { appCurrency }, feeCryptoCurrencyStatus = feeCryptoCurrencyStatus, - stakingGasEstimate = stakingGasEstimate, + fee = Fee.Common( + Amount( + currencySymbol = gasEstimate.token.symbol, + value = gasEstimate.amount, + decimals = gasEstimate.token.decimals, + ), + ), pendingActionList = pendingActions, ), ) @@ -555,8 +564,8 @@ internal class StakingViewModel @Inject constructor( val email = FeedbackEmailType.StakingProblem( cardInfo = cardInfo, validatorName = validator?.name, - transactionType = transactionInProgress?.type?.name, - unsignedTransaction = transactionInProgress?.unsignedTransaction, + transactionTypes = transactionsInProgress.map { it.type.name }, + unsignedTransactions = transactionsInProgress.map { it.unsignedTransaction }, ) feedbackManager.sendEmail(email) @@ -632,13 +641,12 @@ internal class StakingViewModel @Inject constructor( } private suspend fun sendStakingTransaction( - transactionId: String, - gasEstimate: StakingGasEstimate, - txData: TransactionData, + fullTransactionsData: List, + fee: Fee, pendingActionList: ImmutableList, ) { - sendTransactionUseCase( - txData = txData, + sendMultipleTransactionUseCase( + txsData = fullTransactionsData.map { it.tangemTransaction }, userWallet = userWallet, network = cryptoCurrencyStatus.currency.network, ).fold( @@ -648,18 +656,21 @@ internal class StakingViewModel @Inject constructor( SetConfirmationStateAssentTransformer( appCurrencyProvider = Provider { appCurrency }, feeCryptoCurrencyStatus = feeCryptoCurrencyStatus, - stakingGasEstimate = gasEstimate, + fee = fee, pendingActionList = pendingActionList, ), ) stakingEventFactory.createSendTransactionErrorAlert(error) }, - ifRight = { txHash -> - transactionInProgress = null - submitHash(transactionId, txHash) + ifRight = { transactionHashes -> + transactionsInProgress.clear() + submitHash( + transactionIds = fullTransactionsData.map { it.stakeKitTransaction.id }, + transactionHashes = transactionHashes, + ) scheduleUpdates() val txUrl = getExplorerTransactionUrlUseCase( - txHash = txHash, + txHash = transactionHashes.last(), networkId = cryptoCurrencyStatus.currency.network.id, ).getOrElse { "" } @@ -667,7 +678,7 @@ internal class StakingViewModel @Inject constructor( SetConfirmationStateCompletedTransformer( appCurrencyProvider = Provider { appCurrency }, feeCryptoCurrencyStatus = feeCryptoCurrencyStatus, - stakingGasEstimate = gasEstimate, + fee = fee, txUrl = txUrl, ), ) @@ -675,18 +686,22 @@ internal class StakingViewModel @Inject constructor( ) } - private suspend fun submitHash(transactionId: String, transactionHash: String) { - submitHashUseCase.submitHash( - transactionId = transactionId, - transactionHash = transactionHash, - ) - .onLeft { - saveUnsubmittedHashUseCase.invoke( + private suspend fun submitHash(transactionIds: List, transactionHashes: List) { + transactionIds + .zip(transactionHashes) + .forEach { (transactionId, transactionHash) -> + submitHashUseCase.submitHash( transactionId = transactionId, transactionHash = transactionHash, ) - }.onRight { - Timber.d("Successful hash submission") + .onLeft { + saveUnsubmittedHashUseCase.invoke( + transactionId = transactionId, + transactionHash = transactionHash, + ) + }.onRight { + Timber.d("Successful hash submission") + } } } @@ -750,6 +765,11 @@ internal class StakingViewModel @Inject constructor( InnerConfirmationStakingState.ASSENT } + private data class FullTransactionData( + val stakeKitTransaction: StakingTransaction, + val tangemTransaction: TransactionData.Compiled, + ) + private companion object { const val WHAT_IS_STAKING_ARTICLE_URL = "https://tangem.com/en/blog/post/how-to-stake-cryptocurrency/" const val ALLOWANCE_UPDATE_DELAY = 10_000L diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 45137306fa..fc08cd3ad3 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -88,7 +88,7 @@ markdown = "0.7.2" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-748" +tangemBlockchainSdk = "develop-750" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-378" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^