Updated on 2026-08-14

This commit is contained in:
Tangem 2024-08-23 15:25:08 +04:00
parent 3e79411138
commit 297a006e3d
11 changed files with 351 additions and 1 deletions

View file

@ -1,6 +1,7 @@
package com.tangem.tap.di.domain
import com.tangem.domain.managetokens.GetManagedTokensUseCase
import com.tangem.domain.managetokens.*
import com.tangem.domain.managetokens.repository.CustomTokensRepository
import com.tangem.domain.managetokens.repository.ManageTokensRepository
import dagger.Module
import dagger.Provides
@ -17,4 +18,30 @@ internal object ManageTokensDomainModule {
fun provideGetManageTokensUseCase(manageTokensRepository: ManageTokensRepository): GetManagedTokensUseCase {
return GetManagedTokensUseCase(manageTokensRepository)
}
@Provides
@Singleton
fun provideValidateTokenFormatUseCase(customTokensRepository: CustomTokensRepository): ValidateTokenFormUseCase {
return ValidateTokenFormUseCase(customTokensRepository)
}
@Provides
@Singleton
fun provideCreateCurrencyUseCase(customTokensRepository: CustomTokensRepository): CreateCurrencyUseCase {
return CreateCurrencyUseCase(customTokensRepository)
}
@Provides
@Singleton
fun provideFindTokenUseCase(customTokensRepository: CustomTokensRepository): FindTokenUseCase {
return FindTokenUseCase(customTokensRepository)
}
@Provides
@Singleton
fun provideCheckIsCurrencyNotAddedUseCase(
customTokensRepository: CustomTokensRepository,
): CheckIsCurrencyNotAddedUseCase {
return CheckIsCurrencyNotAddedUseCase(customTokensRepository)
}
}

View file

@ -0,0 +1,47 @@
package com.tangem.data.managetokens
import com.tangem.domain.managetokens.model.AddCustomTokenForm
import com.tangem.domain.managetokens.repository.CustomTokensRepository
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.wallets.models.UserWalletId
internal class DefaultCustomTokensRepository : CustomTokensRepository {
override suspend fun validateContractAddress(contractAddress: String, networkId: Network.ID): Boolean {
TODO("Should be implemented in [REDACTED_JIRA]")
}
override suspend fun isCurrencyNotAdded(
userWalletId: UserWalletId,
networkId: Network.ID,
derivationPath: Network.DerivationPath,
contractAddress: String?,
): Boolean {
TODO("Should be implemented in [REDACTED_JIRA]")
}
override suspend fun findToken(
userWalletId: UserWalletId,
contractAddress: String,
networkId: Network.ID,
derivationPath: Network.DerivationPath,
): CryptoCurrency.Token? {
TODO("Should be implemented in [REDACTED_JIRA]")
}
override suspend fun createCoin(
networkId: Network.ID,
derivationPath: Network.DerivationPath,
): CryptoCurrency.Coin {
TODO("Should be implemented in [REDACTED_JIRA]")
}
override suspend fun createCustomToken(
networkId: Network.ID,
derivationPath: Network.DerivationPath,
formValues: AddCustomTokenForm.Validated.All,
): CryptoCurrency.Token {
TODO("Should be implemented in [REDACTED_JIRA]")
}
}

View file

@ -1,11 +1,13 @@
package com.tangem.data.managetokens.di
import com.tangem.data.managetokens.DefaultCustomTokensRepository
import com.tangem.data.managetokens.DefaultManageTokensRepository
import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher
import com.tangem.data.managetokens.utils.ManagedCryptoCurrencyFactory
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.managetokens.repository.CustomTokensRepository
import com.tangem.domain.managetokens.repository.ManageTokensRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
@ -37,4 +39,10 @@ internal object ManageTokensDataModule {
dispatchers,
)
}
@Provides
@Singleton
fun provideCustomTokensRepository(): CustomTokensRepository {
return DefaultCustomTokensRepository()
}
}

View file

@ -0,0 +1,20 @@
package com.tangem.domain.managetokens
import arrow.core.Either
import com.tangem.domain.managetokens.repository.CustomTokensRepository
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.wallets.models.UserWalletId
class CheckIsCurrencyNotAddedUseCase(
private val repository: CustomTokensRepository,
) {
suspend operator fun invoke(
userWalletId: UserWalletId,
networkId: Network.ID,
derivationPath: Network.DerivationPath,
contractAddress: String?,
): Either<Throwable, Boolean> = Either.catch {
repository.isCurrencyNotAdded(userWalletId, networkId, derivationPath, contractAddress)
}
}

View file

@ -0,0 +1,24 @@
package com.tangem.domain.managetokens
import arrow.core.Either
import com.tangem.domain.managetokens.model.AddCustomTokenForm
import com.tangem.domain.managetokens.repository.CustomTokensRepository
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
class CreateCurrencyUseCase(
private val repository: CustomTokensRepository,
) {
suspend operator fun invoke(
networkId: Network.ID,
derivationPath: Network.DerivationPath,
formValues: AddCustomTokenForm.Validated.All?,
): Either<Throwable, CryptoCurrency> = Either.catch {
if (formValues == null) {
repository.createCoin(networkId, derivationPath)
} else {
repository.createCustomToken(networkId, derivationPath, formValues)
}
}
}

View file

@ -0,0 +1,54 @@
package com.tangem.domain.managetokens
import arrow.core.Either
import arrow.core.raise.Raise
import arrow.core.raise.catch
import arrow.core.raise.either
import arrow.core.raise.ensureNotNull
import com.tangem.domain.managetokens.model.exceptoin.FindTokenException
import com.tangem.domain.managetokens.repository.CustomTokensRepository
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.wallets.models.UserWalletId
class FindTokenUseCase(
private val repository: CustomTokensRepository,
) {
suspend operator fun invoke(
userWalletId: UserWalletId,
contractAddress: String,
networkId: Network.ID,
derivationPath: Network.DerivationPath,
): Either<FindTokenException, CryptoCurrency.Token> = either {
val currency = findCurrency(
userWalletId = userWalletId,
contractAddress = contractAddress,
networkId = networkId,
derivationPath = derivationPath,
)
ensureNotNull(currency) {
FindTokenException.NotFound
}
}
private suspend fun Raise<FindTokenException>.findCurrency(
userWalletId: UserWalletId,
contractAddress: String,
networkId: Network.ID,
derivationPath: Network.DerivationPath,
): CryptoCurrency.Token? = catch(
block = {
repository.findToken(
userWalletId = userWalletId,
contractAddress = contractAddress,
networkId = networkId,
derivationPath = derivationPath,
)
},
catch = {
raise(FindTokenException.DataError(it))
},
)
}

View file

@ -0,0 +1,80 @@
package com.tangem.domain.managetokens
import arrow.core.EitherNel
import arrow.core.nonEmptyListOf
import arrow.core.raise.*
import com.tangem.domain.managetokens.model.AddCustomTokenForm
import com.tangem.domain.managetokens.model.exceptoin.CustomTokenFormValidationException
import com.tangem.domain.managetokens.repository.CustomTokensRepository
import com.tangem.domain.tokens.model.Network
class ValidateTokenFormUseCase(
private val repository: CustomTokensRepository,
) {
suspend operator fun invoke(
networkId: Network.ID,
formValues: AddCustomTokenForm.Raw,
): EitherNel<CustomTokenFormValidationException, AddCustomTokenForm.Validated> = either {
if (formValues.name.isBlank() && formValues.symbol.isBlank() && formValues.decimals.isBlank()) {
val contractAddress = withError({ nonEmptyListOf(it) }) {
ensureIsContractAddressValid(formValues.contractAddress, networkId)
}
AddCustomTokenForm.Validated.ContractAddress(contractAddress)
} else {
zipOrAccumulate(
{ ensureIsContractAddressValid(formValues.contractAddress, networkId) },
{ ensureIsDecimalsValid(formValues.decimals) },
{ ensure(formValues.name.isNotBlank()) { CustomTokenFormValidationException.EmptyName } },
{ ensure(formValues.symbol.isNotBlank()) { CustomTokenFormValidationException.EmptySymbol } },
) { contractAddress, decimals, _, _ ->
AddCustomTokenForm.Validated.All(
contractAddress = contractAddress,
symbol = formValues.symbol,
name = formValues.name,
decimals = decimals,
)
}
}
}
private suspend fun Raise<CustomTokenFormValidationException>.ensureIsContractAddressValid(
contractAddress: String,
networkId: Network.ID,
): String {
ensure(contractAddress.isNotBlank()) {
CustomTokenFormValidationException.ContractAddress.Empty
}
val isValid = catch({ repository.validateContractAddress(contractAddress, networkId) }) {
raise(CustomTokenFormValidationException.DataError(it))
}
ensure(isValid) {
CustomTokenFormValidationException.ContractAddress.Invalid
}
return contractAddress
}
private fun Raise<CustomTokenFormValidationException>.ensureIsDecimalsValid(decimals: String): Int {
ensure(condition = decimals.isNotBlank()) {
CustomTokenFormValidationException.Decimals.Empty
}
val decimalsInt = ensureNotNull(decimals.toIntOrNull()) {
CustomTokenFormValidationException.Decimals.Invalid
}
ensure(condition = decimalsInt in MIN_DECIMALS..MAX_DECIMALS) {
CustomTokenFormValidationException.Decimals.Invalid
}
return decimalsInt
}
companion object {
const val MIN_DECIMALS = 1
const val MAX_DECIMALS = 30
}
}

View file

@ -0,0 +1,25 @@
package com.tangem.domain.managetokens.model
sealed class AddCustomTokenForm {
data class Raw(
val contractAddress: String,
val name: String,
val symbol: String,
val decimals: String,
) : AddCustomTokenForm()
sealed class Validated {
data class ContractAddress(
val contractAddress: String,
) : Validated()
data class All(
val contractAddress: String,
val name: String,
val symbol: String,
val decimals: Int,
) : Validated()
}
}

View file

@ -0,0 +1,24 @@
package com.tangem.domain.managetokens.model.exceptoin
sealed class CustomTokenFormValidationException {
sealed class ContractAddress : CustomTokenFormValidationException() {
data object Empty : ContractAddress()
data object Invalid : ContractAddress()
}
sealed class Decimals : CustomTokenFormValidationException() {
data object Empty : Decimals()
data object Invalid : Decimals()
}
data object EmptyName : CustomTokenFormValidationException()
data object EmptySymbol : CustomTokenFormValidationException()
data class DataError(val cause: Throwable) : CustomTokenFormValidationException()
}

View file

@ -0,0 +1,8 @@
package com.tangem.domain.managetokens.model.exceptoin
sealed class FindTokenException {
data object NotFound : FindTokenException()
data class DataError(val cause: Throwable) : FindTokenException()
}

View file

@ -0,0 +1,33 @@
package com.tangem.domain.managetokens.repository
import com.tangem.domain.managetokens.model.AddCustomTokenForm
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.wallets.models.UserWalletId
interface CustomTokensRepository {
suspend fun validateContractAddress(contractAddress: String, networkId: Network.ID): Boolean
suspend fun isCurrencyNotAdded(
userWalletId: UserWalletId,
networkId: Network.ID,
derivationPath: Network.DerivationPath,
contractAddress: String?,
): Boolean
suspend fun findToken(
userWalletId: UserWalletId,
contractAddress: String,
networkId: Network.ID,
derivationPath: Network.DerivationPath,
): CryptoCurrency.Token?
suspend fun createCoin(networkId: Network.ID, derivationPath: Network.DerivationPath): CryptoCurrency.Coin
suspend fun createCustomToken(
networkId: Network.ID,
derivationPath: Network.DerivationPath,
formValues: AddCustomTokenForm.Validated.All,
): CryptoCurrency.Token
}