Updated on 2026-08-14

This commit is contained in:
Tangem 2023-10-09 18:07:05 +03:00
commit 7d9da40464
9 changed files with 226 additions and 59 deletions

View file

@ -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<Account> {
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<Account> {
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) {

View file

@ -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<WalletManager> {
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<Account> {
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,
)
}
}
}
}

View file

@ -33,6 +33,10 @@ internal class DefaultWalletManagersStore(
}
}
override suspend fun getAllSync(userWalletId: UserWalletId): List<WalletManager> {
return getSyncOrNull(userWalletId) ?: emptyList()
}
override suspend fun store(userWalletId: UserWalletId, walletManager: WalletManager) {
val walletManagers = getSyncOrNull(userWalletId)

View file

@ -15,6 +15,8 @@ interface WalletManagersStore {
derivationPath: String?,
): WalletManager?
suspend fun getAllSync(userWalletId: UserWalletId): List<WalletManager>
suspend fun store(userWalletId: UserWalletId, walletManager: WalletManager)
suspend fun clear()

View file

@ -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
)

View file

@ -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()
}
}

View file

@ -221,6 +221,10 @@ class DefaultWalletManagersFacade(
return walletManager
}
override suspend fun getStoredWalletManagers(userWalletId: UserWalletId): List<WalletManager> {
return walletManagersStore.getAllSync(userWalletId)
}
override suspend fun getAddress(userWalletId: UserWalletId, network: Network): List<Address> {
val blockchain = Blockchain.fromId(network.id.value)

View file

@ -77,6 +77,9 @@ interface WalletManagersFacade {
derivationPath: String?,
): WalletManager?
@Deprecated("Will be removed in future")
suspend fun getStoredWalletManagers(userWalletId: UserWalletId): List<WalletManager>
/**
* Returns ordered list of addresses for selected wallet for given currency
*

View file

@ -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<Either<TokenListError, TokenList>>, wallet: UserWallet) {
initWalletConnectForWallet(wallet)
tokenListFlow
.filter(::filterLoadedTokenList)
.take(1)
.onEach {
it.onRight {
setupWalletConnectOnWallet(wallet)
}
}
.flowOn(dispatchers.io)
.launchIn(viewModelScope)
.saveIn(updateWcJobHolder)
}
private fun List<CryptoCurrencyStatus>.isAllCurrenciesLoaded(): Boolean {
return !this.any { it.value is CryptoCurrencyStatus.Loading }
}
private fun filterLoadedTokenList(either: Either<TokenListError, TokenList>): 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<CryptoCurrencyStatus>.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),