diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index c10b88280a..729e96fe27 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -91,10 +91,10 @@ class TapWalletManager( store.dispatch(GlobalAction.SetIfCardVerifiedOnline(!attestationFailed)) store.dispatchWalletAction(action = WalletAction.Warnings.CheckIfNeeded) } - setupWalletConnectV2(userWallet) val walletFeatureToggles = store.state.daggerGraphState.get(DaggerGraphState::walletFeatureToggles) if (!walletFeatureToggles.isRedesignedScreenEnabled) { + setupWalletConnectV2(userWallet) loadData(userWallet = userWallet, refresh = refresh) } } @@ -161,21 +161,31 @@ class TapWalletManager( } } - private fun getAccountsForWc(wcInteractor: WalletConnectInteractor): List { - return store.state.walletState.walletManagers - .mapNotNull { - val wallet = it.wallet - val chainId = wcInteractor.blockchainHelper.networkIdToChainIdOrNull( - wallet.blockchain.toNetworkId(), + private suspend fun getAccountsForWc(wcInteractor: WalletConnectInteractor): List { + val walletManagerToggles = store.state.daggerGraphState + .get(DaggerGraphState::walletFeatureToggles) + val walletManagers = if (walletManagerToggles.isRedesignedScreenEnabled) { + val walletManagerFacade = store.state.daggerGraphState + .get(DaggerGraphState::walletManagersFacade) + val userWallet = userWalletsListManager.selectedUserWalletSync ?: return emptyList() + walletManagerFacade.getStoredWalletManagers(userWallet.walletId) + } else { + store.state.walletState.walletManagers + } + + return walletManagers.mapNotNull { + val wallet = it.wallet + val chainId = wcInteractor.blockchainHelper.networkIdToChainIdOrNull( + wallet.blockchain.toNetworkId(), + ) + chainId?.let { + Account( + chainId, + wallet.address, + wallet.publicKey.derivationPath?.rawPath, ) - chainId?.let { - Account( - chainId, - wallet.address, - wallet.publicKey.derivationPath?.rawPath, - ) - } } + } } fun updateConfigManager(data: ScanResponse) { diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index a436d55d8e..4ed140a246 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -14,6 +14,8 @@ import com.tangem.domain.common.extensions.withMainContext import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.common.util.derivationStyleProvider import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.walletconnect.WalletConnectActions +import com.tangem.domain.wallets.models.UserWallet import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction @@ -32,6 +34,7 @@ import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.scope import com.tangem.tap.store +import com.tangem.tap.userWalletsListManager import kotlinx.coroutines.launch import org.rekotlin.Action import org.rekotlin.Middleware @@ -59,6 +62,28 @@ class WalletConnectMiddleware { if (DemoHelper.tryHandle(state, action)) return when (action) { + is WalletConnectActions.New.Initialize -> { + val userWallet = action.userWallet + val cardId = if (userWallet.scanResponse.card.backupStatus?.isActive != true) { + userWallet.cardId + } else { // if wallet has backup, any card from wallet can be used to sign + null + } + scope.launch { + val wcInteractor = store.state.daggerGraphState.walletConnectInteractor ?: return@launch + wcInteractor.startListening( + userWalletId = userWallet.walletId.stringValue, + cardId = cardId, + ) + } + } + is WalletConnectActions.New.SetupUserChains -> { + scope.launch { + val userWallet = action.userWallet + val wcInteractor = store.state.daggerGraphState.walletConnectInteractor ?: return@launch + wcInteractor.setUserChains(getAccountsForWc(wcInteractor, userWallet)) + } + } is WalletConnectAction.ResetState -> walletConnectManager = WalletConnectManager() is WalletConnectAction.RestoreSessions -> { walletConnectManager.restoreSessions(action.scanResponse) @@ -246,47 +271,50 @@ class WalletConnectMiddleware { store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork())) return } - val walletManager = getWalletManager( - wallet = action.session.wallet, - blockchain = blockchain, - walletState = store.state.walletState, - ).guard { - store.dispatchOnMain( - GlobalAction.ShowDialog( - WalletConnectDialog.AddNetwork(blockchain.fullName), - ), + scope.launch { + val walletManager = getWalletManager( + wallet = action.session.wallet, + blockchain = blockchain, + walletState = store.state.walletState, + ).guard { + store.dispatchOnMain( + GlobalAction.ShowDialog( + WalletConnectDialog.AddNetwork(blockchain.fullName), + ), + ) + return@launch + } + val updatedWallet = action.session.wallet.copy( + walletPublicKey = walletManager.wallet.publicKey.seedKey, + derivedPublicKey = walletManager.wallet.publicKey.derivedKey, + derivationPath = walletManager.wallet.publicKey.derivationPath, + blockchain = action.blockchain, ) - return + val updatedSession = action.session.copy(wallet = updatedWallet) + store.dispatchOnMain(WalletConnectAction.UpdateBlockchain(updatedSession)) } - val updatedWallet = action.session.wallet.copy( - walletPublicKey = walletManager.wallet.publicKey.seedKey, - derivedPublicKey = walletManager.wallet.publicKey.derivedKey, - derivationPath = walletManager.wallet.publicKey.derivationPath, - blockchain = action.blockchain, - ) - val updatedSession = action.session.copy(wallet = updatedWallet) - store.dispatchOnMain(WalletConnectAction.UpdateBlockchain(updatedSession)) } is WalletConnectAction.UpdateBlockchain -> { walletConnectManager.updateBlockchain(action.updatedSession) } is WalletConnectAction.ApproveProposal -> { - val accounts = store.state.walletState.walletManagers - .mapNotNull { - val wallet = it.wallet - val chainId = walletConnectInteractor.blockchainHelper.networkIdToChainIdOrNull( - wallet.blockchain.toNetworkId(), - ) - chainId?.let { - Account( - chainId, - wallet.address, - wallet.publicKey.derivationPath?.rawPath, + scope.launch { + val accounts = getWalletManagers() + .mapNotNull { + val wallet = it.wallet + val chainId = walletConnectInteractor.blockchainHelper.networkIdToChainIdOrNull( + wallet.blockchain.toNetworkId(), ) + chainId?.let { + Account( + chainId, + wallet.address, + wallet.publicKey.derivationPath?.rawPath, + ) + } } - } - - walletConnectInteractor.approveSessionProposal(accounts) + walletConnectInteractor.approveSessionProposal(accounts) + } } is WalletConnectAction.RejectProposal -> { walletConnectInteractor.rejectSessionProposal() @@ -345,6 +373,19 @@ class WalletConnectMiddleware { } } + private suspend fun getWalletManagers(): List { + val walletManagerToggles = store.state.daggerGraphState + .get(DaggerGraphState::walletFeatureToggles) + return if (walletManagerToggles.isRedesignedScreenEnabled) { + val walletManagerFacade = store.state.daggerGraphState + .get(DaggerGraphState::walletManagersFacade) + val userWallet = userWalletsListManager.selectedUserWalletSync ?: return emptyList() + walletManagerFacade.getStoredWalletManagers(userWallet.walletId) + } else { + store.state.walletState.walletManagers + } + } + private fun scanCard(scanResponse: ScanResponse, session: WalletConnectSession, chainId: Int?) { val blockchain = WalletConnectNetworkUtils.parseBlockchain( chainId = chainId, @@ -430,7 +471,7 @@ class WalletConnectMiddleware { ) } - private fun getWalletManager( + private suspend fun getWalletManager( wallet: WalletForSession, blockchain: Blockchain, walletState: WalletState, @@ -440,18 +481,50 @@ class WalletConnectMiddleware { } else { blockchain } + val userWallet = userWalletsListManager.selectedUserWalletSync ?: return null val derivation = blockchainToMake.derivationPath( - style = store.state.globalState.scanResponse?.derivationStyleProvider?.getDerivationStyle(), + style = userWallet.scanResponse.derivationStyleProvider.getDerivationStyle(), )?.rawPath - val blockchainNetwork = BlockchainNetwork( - blockchain = blockchainToMake, - derivationPath = derivation, - tokens = emptyList(), - ) - return walletState.getWalletManager(blockchainNetwork) + val walletFeatureToggles = store.state.daggerGraphState + .get(DaggerGraphState::walletFeatureToggles) + + return if (walletFeatureToggles.isRedesignedScreenEnabled) { + val walletManagerFacade = store.state.daggerGraphState + .get(DaggerGraphState::walletManagersFacade) + walletManagerFacade.getOrCreateWalletManager( + userWalletId = userWallet.walletId, + blockchain = blockchainToMake, + derivationPath = derivation, + ) + } else { + val blockchainNetwork = BlockchainNetwork( + blockchain = blockchainToMake, + derivationPath = derivation, + tokens = emptyList(), + ) + walletState.getWalletManager(blockchainNetwork) + } } private fun isWalletConnectUri(uri: String): Boolean { return WalletConnectManager.isCorrectWcUri(uri) || walletConnectInteractor.isWalletConnectUri(uri) } + + private suspend fun getAccountsForWc(wcInteractor: WalletConnectInteractor, userWallet: UserWallet): List { + val walletManagerFacade = store.state.daggerGraphState + .get(DaggerGraphState::walletManagersFacade) + return walletManagerFacade.getStoredWalletManagers(userWallet.walletId).mapNotNull { + val wallet = it.wallet + val chainId = wcInteractor.blockchainHelper.networkIdToChainIdOrNull( + wallet.blockchain.toNetworkId(), + ) + chainId?.let { + Account( + chainId, + wallet.address, + wallet.publicKey.derivationPath?.rawPath, + ) + } + } + } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/walletmanager/DefaultWalletManagersStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/walletmanager/DefaultWalletManagersStore.kt index 1222edafb4..f99db26624 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/walletmanager/DefaultWalletManagersStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/walletmanager/DefaultWalletManagersStore.kt @@ -33,6 +33,10 @@ internal class DefaultWalletManagersStore( } } + override suspend fun getAllSync(userWalletId: UserWalletId): List { + return getSyncOrNull(userWalletId) ?: emptyList() + } + override suspend fun store(userWalletId: UserWalletId, walletManager: WalletManager) { val walletManagers = getSyncOrNull(userWalletId) diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/walletmanager/WalletManagersStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/walletmanager/WalletManagersStore.kt index 8506eb5803..a820b1d7c9 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/walletmanager/WalletManagersStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/walletmanager/WalletManagersStore.kt @@ -15,6 +15,8 @@ interface WalletManagersStore { derivationPath: String?, ): WalletManager? + suspend fun getAllSync(userWalletId: UserWalletId): List + suspend fun store(userWalletId: UserWalletId, walletManager: WalletManager) suspend fun clear() diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt index 0970e85d77..a37ce82bcb 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt @@ -212,6 +212,4 @@ private const val NODL_AMOUNT_TO_CREATE_ACCOUNT = 1.5 private val excludedBlockchains = listOf( Blockchain.Unknown, Blockchain.Ducatus, - Blockchain.Telos, // disable in 4.9 - Blockchain.TelosTestnet, // disable in 4.9 ) \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletconnect/WalletConnectActions.kt b/domain/legacy/src/main/java/com/tangem/domain/walletconnect/WalletConnectActions.kt new file mode 100644 index 0000000000..afb7d26d9f --- /dev/null +++ b/domain/legacy/src/main/java/com/tangem/domain/walletconnect/WalletConnectActions.kt @@ -0,0 +1,12 @@ +package com.tangem.domain.walletconnect + +import com.tangem.domain.wallets.models.UserWallet +import org.rekotlin.Action + +sealed class WalletConnectActions : Action { + sealed class New { + data class Initialize(val userWallet: UserWallet) : WalletConnectActions() + + data class SetupUserChains(val userWallet: UserWallet) : WalletConnectActions() + } +} \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt index 9851ae5bc8..01a025f741 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt @@ -221,6 +221,10 @@ class DefaultWalletManagersFacade( return walletManager } + override suspend fun getStoredWalletManagers(userWalletId: UserWalletId): List { + return walletManagersStore.getAllSync(userWalletId) + } + override suspend fun getAddress(userWalletId: UserWalletId, network: Network): List
{ val blockchain = Blockchain.fromId(network.id.value) diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt index 94ce68d1d3..6df4bf3f84 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt @@ -77,6 +77,9 @@ interface WalletManagersFacade { derivationPath: String?, ): WalletManager? + @Deprecated("Will be removed in future") + suspend fun getStoredWalletManagers(userWalletId: UserWalletId): List + /** * Returns ordered list of addresses for selected wallet for given currency * diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index e860f3d518..9cb22da162 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels import androidx.lifecycle.* import androidx.paging.cachedIn +import arrow.core.Either import arrow.core.getOrElse import com.tangem.blockchain.blockchains.cardano.CardanoUtils import com.tangem.blockchain.common.Blockchain @@ -39,6 +40,7 @@ import com.tangem.domain.redux.LegacyAction import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.settings.* import com.tangem.domain.tokens.* +import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -48,6 +50,7 @@ import com.tangem.domain.tokens.models.analytics.TokenReceiveAnalyticsEvent import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase import com.tangem.domain.userwallets.UserWalletBuilder +import com.tangem.domain.walletconnect.WalletConnectActions import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId @@ -158,6 +161,7 @@ internal class WalletViewModel @Inject constructor( private var singleWalletCryptoCurrencyStatus: CryptoCurrencyStatus? = null private val tokensJobHolder = JobHolder() + private val updateWcJobHolder = JobHolder() private val marketPriceJobHolder = JobHolder() private val buttonsJobHolder = JobHolder() private val notificationsJobHolder = JobHolder() @@ -455,6 +459,7 @@ internal class WalletViewModel @Inject constructor( * If jobs aren't stopped and wallet is changed then it will update state for the prev wallet. */ tokensJobHolder.update(job = null) + updateWcJobHolder.update(job = null) marketPriceJobHolder.update(job = null) buttonsJobHolder.update(job = null) notificationsJobHolder.update(job = null) @@ -851,6 +856,7 @@ internal class WalletViewModel @Inject constructor( * If jobs aren't stopped and wallet is changed then it will update state for the prev wallet. */ tokensJobHolder.update(job = null) + updateWcJobHolder.update(job = null) marketPriceJobHolder.update(job = null) buttonsJobHolder.update(job = null) notificationsJobHolder.update(job = null) @@ -862,17 +868,22 @@ internal class WalletViewModel @Inject constructor( wallet.isLocked -> { uiState = stateFactory.getLockedState() } - wallet.isMultiCurrency -> getMultiCurrencyContent(index) + wallet.isMultiCurrency -> getMultiCurrencyContent(wallet, index) !wallet.isMultiCurrency -> getSingleCurrencyContent(index) } } - private fun getMultiCurrencyContent(walletIndex: Int) { + private fun getMultiCurrencyContent(wallet: UserWallet, walletIndex: Int) { val state = requireNotNull(uiState as? WalletMultiCurrencyState) { "Impossible to get a token list updates if state isn't WalletMultiCurrencyState" } - getTokenListUseCase(userWalletId = state.walletsListConfig.wallets[walletIndex].id) + val tokenListFlow = getTokenListUseCase(userWalletId = state.walletsListConfig.wallets[walletIndex].id) + .shareIn(viewModelScope, SharingStarted.WhileSubscribed()) + + initAndSetupWc(tokenListFlow, wallet) + + tokenListFlow .distinctUntilChanged() .onEach { maybeTokenList -> uiState = stateFactory.getStateByTokensList(maybeTokenList) @@ -905,6 +916,44 @@ internal class WalletViewModel @Inject constructor( } } + private fun initAndSetupWc(tokenListFlow: SharedFlow>, wallet: UserWallet) { + initWalletConnectForWallet(wallet) + tokenListFlow + .filter(::filterLoadedTokenList) + .take(1) + .onEach { + it.onRight { + setupWalletConnectOnWallet(wallet) + } + } + .flowOn(dispatchers.io) + .launchIn(viewModelScope) + .saveIn(updateWcJobHolder) + } + + private fun List.isAllCurrenciesLoaded(): Boolean { + return !this.any { it.value is CryptoCurrencyStatus.Loading } + } + + private fun filterLoadedTokenList(either: Either): Boolean { + return either.fold( + ifRight = { list -> + when (list) { + is TokenList.Ungrouped -> { + list.currencies.isAllCurrenciesLoaded() + } + is TokenList.GroupedByNetwork -> { + list.groups.flatMap { group -> group.currencies }.isAllCurrenciesLoaded() + } + else -> { + false + } + } + }, + ifLeft = { false }, + ) + } + private fun List.hasNonZeroWallets(): Boolean { return any { val amount = it.value.amount ?: return@any false @@ -1023,6 +1072,18 @@ internal class WalletViewModel @Inject constructor( ) } + private fun initWalletConnectForWallet(userWallet: UserWallet) { + reduxStateHolder.dispatch( + WalletConnectActions.New.Initialize(userWallet = userWallet), + ) + } + + private fun setupWalletConnectOnWallet(userWallet: UserWallet) { + reduxStateHolder.dispatch( + WalletConnectActions.New.SetupUserChains(userWallet = userWallet), + ) + } + private fun getWallet(index: Int): UserWallet { return requireNotNull( value = wallets.getOrNull(index),