Updated on 2026-08-14

This commit is contained in:
Tangem 2023-12-15 15:08:07 +03:00
parent 24e0fb015b
commit 0662e31ed0
14 changed files with 252 additions and 6 deletions

View file

@ -21,4 +21,8 @@ internal class RuntimeUserWalletsStore(
?.firstOrNull()
?.singleOrNull { it.walletId == key }
}
override suspend fun update(userWalletId: UserWalletId, update: suspend (UserWallet) -> UserWallet) {
walletsStateHolder.userWalletsListManager?.update(userWalletId, update)
}
}

View file

@ -1,5 +1,6 @@
package com.tangem.tap.di.domain
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.card.*
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.card.repository.CardSdkConfigRepository
@ -8,7 +9,9 @@ import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.wallets.legacy.WalletsStateHolder
import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase
import com.tangem.tap.domain.TangemSdkManager
import com.tangem.tap.domain.card.DefaultDerivationsRepository
import com.tangem.tap.domain.card.DefaultDerivePublicKeysUseCase
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -61,8 +64,15 @@ internal object CardDomainModule {
@Provides
@ViewModelScoped
fun provideDerivePublicKeysUseCase(tangemSdkManager: TangemSdkManager): DerivePublicKeysUseCase {
return DefaultDerivePublicKeysUseCase(tangemSdkManager = tangemSdkManager)
fun provideDerivePublicKeysUseCase(
tangemSdkManager: TangemSdkManager,
userWalletsStore: UserWalletsStore,
dispatchers: CoroutineDispatcherProvider,
): DerivePublicKeysUseCase {
return DefaultDerivePublicKeysUseCase(
tangemSdkManager = tangemSdkManager,
derivationsRepository = DefaultDerivationsRepository(tangemSdkManager, userWalletsStore, dispatchers),
)
}
@Provides

View file

@ -0,0 +1,75 @@
package com.tangem.tap.domain.card
import com.tangem.common.doOnFailure
import com.tangem.common.doOnSuccess
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.card.repository.DerivationsRepository
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.tap.domain.TangemSdkManager
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runCatching
import timber.log.Timber
internal typealias Derivations = Map<ByteArrayKey, List<DerivationPath>>
private typealias DerivedKeys = Map<ByteArrayKey, ExtendedPublicKeysMap>
internal class DefaultDerivationsRepository(
private val tangemSdkManager: TangemSdkManager,
private val userWalletsStore: UserWalletsStore,
private val dispatchers: CoroutineDispatcherProvider,
) : DerivationsRepository {
override suspend fun derivePublicKeys(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
val userWallet = userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found")
val derivations = MissedDerivationsFinder(scanResponse = userWallet.scanResponse)
.find(currencies)
.ifEmpty {
Timber.d("Nothing to derive")
return
}
tangemSdkManager.derivePublicKeys(cardId = null, derivations = derivations)
.doOnSuccess { response ->
updatePublicKeys(userWalletId = userWalletId, keys = response.entries).fold(
onSuccess = { return },
onFailure = { throw it },
)
}
.doOnFailure { throw it }
error("This code should never be reached")
}
private suspend fun updatePublicKeys(userWalletId: UserWalletId, keys: DerivedKeys): Result<Unit> {
return runCatching(dispatchers.io) {
userWalletsStore.update(
userWalletId = userWalletId,
update = { userWallet -> userWallet.updateDerivedKeys(keys) },
)
}
}
private fun UserWallet.updateDerivedKeys(keys: DerivedKeys): UserWallet {
return copy(
scanResponse = scanResponse.copy(
derivedKeys = getUpdatedDerivedKeys(oldKeys = scanResponse.derivedKeys, newKeys = keys),
),
)
}
private fun getUpdatedDerivedKeys(oldKeys: DerivedKeys, newKeys: DerivedKeys): DerivedKeys {
return (oldKeys.keys + newKeys.keys).toSet()
.associateWith { walletKey ->
val oldDerivations = ExtendedPublicKeysMap(oldKeys[walletKey] ?: emptyMap())
val newDerivations = newKeys[walletKey] ?: ExtendedPublicKeysMap(emptyMap())
ExtendedPublicKeysMap(oldDerivations + newDerivations)
}
}
}

View file

@ -8,12 +8,15 @@ import com.tangem.common.doOnSuccess
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.card.DerivePublicKeysUseCase
import com.tangem.domain.card.repository.DerivationsRepository
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.operations.derivation.DerivationTaskResponse
import com.tangem.tap.domain.TangemSdkManager
// TODO: [REDACTED_JIRA]
internal class DefaultDerivePublicKeysUseCase(
private val tangemSdkManager: TangemSdkManager,
private val derivationsRepository: DerivationsRepository,
) : DerivePublicKeysUseCase {
override suspend fun invoke(
@ -26,4 +29,13 @@ internal class DefaultDerivePublicKeysUseCase(
return Unit.left()
}
override suspend fun invoke(
userWalletId: UserWalletId,
currencies: List<CryptoCurrency>,
): Either<Throwable, Unit> {
return Either.catch {
derivationsRepository.derivePublicKeys(userWalletId, currencies)
}
}
}

View file

@ -0,0 +1,118 @@
package com.tangem.tap.domain.card
import com.tangem.blockchain.blockchains.cardano.CardanoUtils
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.card.EllipticCurve
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.extensions.toMapKey
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.common.configs.CardConfig
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.models.scan.KeyWalletPublicKey
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.operations.derivation.ExtendedPublicKeysMap
private typealias DerivationData = Pair<ByteArrayKey, List<DerivationPath>>
/**
* Finder of missed derivations
*
* @property scanResponse scanning response
*
[REDACTED_AUTHOR]
*/
internal class MissedDerivationsFinder(private val scanResponse: ScanResponse) {
/** Find missed derivations for given currencies [currencies] */
fun find(currencies: List<CryptoCurrency>): Derivations {
return buildMap<ByteArrayKey, MutableList<DerivationPath>> {
currencies
.mapToNewDerivations()
.forEach { data ->
val current = this[data.first]
if (current != null) {
current.addAll(data.second)
current.distinct()
} else {
this[data.first] = data.second.toMutableList()
}
}
}
}
private fun List<CryptoCurrency>.mapToNewDerivations(): List<DerivationData> {
val config = CardConfig.createConfig(scanResponse.card)
return mapNotNull { currency ->
val blockchain = Blockchain.fromId(id = currency.network.id.value)
val curve = config.primaryCurve(blockchain) ?: return@mapNotNull null
findNewDerivations(curve = curve, scanResponse = scanResponse, currency = currency)
}
}
private fun findNewDerivations(
curve: EllipticCurve,
scanResponse: ScanResponse,
currency: CryptoCurrency,
): DerivationData? {
val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null
val publicKey = wallet.publicKey.toMapKey()
val derivationCandidates = currency
.getDerivationCandidates(curve)
.ifEmpty { return null }
.filterAlreadyDerivedKeys(publicKey)
.ifEmpty { return null }
return publicKey to derivationCandidates
}
private fun CryptoCurrency.getDerivationCandidates(curve: EllipticCurve): List<DerivationPath> {
val blockchain = Blockchain.fromId(id = network.id.value)
return buildList {
add(blockchain.getDerivationPath(curve = curve))
add(blockchain.getCustomDerivationPath(curve = curve, currency = this@getDerivationCandidates))
add(blockchain.getCardanoDerivationPathIfNeeded(currency = this@getDerivationCandidates))
}
.filterNotNull()
.distinct()
}
private fun Blockchain.getDerivationPath(curve: EllipticCurve): DerivationPath? {
return if (getSupportedCurves().contains(curve)) {
derivationPath(style = scanResponse.derivationStyleProvider.getDerivationStyle())
} else {
null
}
}
private fun Blockchain.getCustomDerivationPath(curve: EllipticCurve, currency: CryptoCurrency): DerivationPath? {
return if (getSupportedCurves().contains(curve)) {
currency.network.derivationPath.value?.let(::DerivationPath)
} else {
null
}
}
private fun Blockchain.getCardanoDerivationPathIfNeeded(currency: CryptoCurrency): DerivationPath? {
return if (currency is CryptoCurrency.Coin && this == Blockchain.Cardano) {
currency.network.derivationPath.value?.let {
CardanoUtils.extendedDerivationPath(derivationPath = DerivationPath(it))
}
} else {
null
}
}
private fun List<DerivationPath>.filterAlreadyDerivedKeys(publicKey: KeyWalletPublicKey): List<DerivationPath> {
val alreadyDerivedPaths = getAlreadyDerivedKeys(publicKey)
return filterNot(alreadyDerivedPaths::contains)
}
private fun getAlreadyDerivedKeys(publicKey: KeyWalletPublicKey): List<DerivationPath> {
val extendedPublicKeysMap = scanResponse.derivedKeys[publicKey] ?: ExtendedPublicKeysMap(emptyMap())
return extendedPublicKeysMap.keys.toList()
}
}

View file

@ -83,6 +83,8 @@ class DefaultCustomTokenInteractor(
return currency.derivationPath?.let { !scanResponse.hasDerivation(currency.blockchain, it) } ?: false
}
@Deprecated("Use DerivePublicKeysUseCase instead")
// FIXME: Migration [REDACTED_JIRA]
private suspend fun deriveMissingBlockchains(
userWallet: UserWallet,
currencyList: List<Currency>,

View file

@ -96,6 +96,8 @@ object TokensMiddleware {
}
}
@Deprecated(message = "Use DerivePublicKeysUseCase instead")
// FIXME: Migration [REDACTED_JIRA]
private fun deriveMissingCoins(
scanResponse: ScanResponse,
currencyList: List<CryptoCurrency>,

View file

@ -8,4 +8,7 @@ interface UserWalletsStore {
val selectedUserWalletOrNull: UserWallet?
suspend fun getSyncOrNull(key: UserWalletId): UserWallet?
@Throws
suspend fun update(userWalletId: UserWalletId, update: suspend (UserWallet) -> UserWallet)
}

View file

@ -16,7 +16,8 @@ dependencies {
implementation(projects.domain.legacy)
// TODO: Remove after new card scan result was implemented
implementation(projects.domain.models)
implementation(projects.domain.tokens.models)
implementation(projects.domain.wallets.models)
implementation(deps.tangem.card.core)
implementation(deps.tangem.blockchain) {

View file

@ -3,12 +3,18 @@ package com.tangem.domain.card
import arrow.core.Either
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.operations.derivation.DerivationTaskResponse
// FIXME: Cover with tests [REDACTED_JIRA]
interface DerivePublicKeysUseCase {
@Deprecated(message = "Use invoke(cardId: String?, derivations: Map<ByteArrayKey, List<DerivationPath>>) instead")
suspend operator fun invoke(
cardId: String? = null,
derivations: Map<ByteArrayKey, List<DerivationPath>>,
): Either<Unit, DerivationTaskResponse>
suspend operator fun invoke(userWalletId: UserWalletId, currencies: List<CryptoCurrency>): Either<Throwable, Unit>
}

View file

@ -0,0 +1,9 @@
package com.tangem.domain.card.repository
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
interface DerivationsRepository {
suspend fun derivePublicKeys(userWalletId: UserWalletId, currencies: List<CryptoCurrency>)
}

View file

@ -315,7 +315,8 @@ internal class WalletViewModel @Inject constructor(
}
}
// TODO: [REDACTED_JIRA]
@Deprecated("Use DerivePublicKeysUseCase instead")
// FIXME: Migration: [REDACTED_JIRA]
private fun deriveMissingCurrencies(
scanResponse: ScanResponse,
currencyList: List<CryptoCurrency>,

View file

@ -107,6 +107,8 @@ internal class WalletWarningsClickIntentsImplementer @Inject constructor(
}
}
@Deprecated("Use DerivePublicKeysUseCase instead")
// FIXME: Migration: [REDACTED_JIRA]
override fun onGenerateMissedAddressesClick(missedAddressCurrencies: List<CryptoCurrency>) {
val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return
@ -127,7 +129,6 @@ internal class WalletWarningsClickIntentsImplementer @Inject constructor(
}
}
// TODO: [REDACTED_JIRA]
private fun deriveMissingCurrencies(
scanResponse: ScanResponse,
currencyList: List<CryptoCurrency>,

View file

@ -2,6 +2,8 @@ package com.tangem.lib.crypto
import com.tangem.lib.crypto.models.Currency
// FIXME: Migration [REDACTED_JIRA]
@Deprecated(message = "Use DerivePublicKeysUseCase instead")
interface DerivationManager {
/**