diff --git a/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt index dd78da6dfd..0dd7b11804 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt @@ -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) + } } \ No newline at end of file diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultCustomTokensRepository.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultCustomTokensRepository.kt new file mode 100644 index 0000000000..50f20df32c --- /dev/null +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultCustomTokensRepository.kt @@ -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]") + } +} \ No newline at end of file diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt index 5cb10e4936..f61ff6a0b3 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt @@ -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() + } } \ No newline at end of file diff --git a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/CheckIsCurrencyNotAddedUseCase.kt b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/CheckIsCurrencyNotAddedUseCase.kt new file mode 100644 index 0000000000..04aa0761eb --- /dev/null +++ b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/CheckIsCurrencyNotAddedUseCase.kt @@ -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 = Either.catch { + repository.isCurrencyNotAdded(userWalletId, networkId, derivationPath, contractAddress) + } +} \ No newline at end of file diff --git a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/CreateCurrencyUseCase.kt b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/CreateCurrencyUseCase.kt new file mode 100644 index 0000000000..dc935f08af --- /dev/null +++ b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/CreateCurrencyUseCase.kt @@ -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 = Either.catch { + if (formValues == null) { + repository.createCoin(networkId, derivationPath) + } else { + repository.createCustomToken(networkId, derivationPath, formValues) + } + } +} \ No newline at end of file diff --git a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/FindTokenUseCase.kt b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/FindTokenUseCase.kt new file mode 100644 index 0000000000..3542154d4e --- /dev/null +++ b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/FindTokenUseCase.kt @@ -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 = either { + val currency = findCurrency( + userWalletId = userWalletId, + contractAddress = contractAddress, + networkId = networkId, + derivationPath = derivationPath, + ) + + ensureNotNull(currency) { + FindTokenException.NotFound + } + } + + private suspend fun Raise.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)) + }, + ) +} \ No newline at end of file diff --git a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/ValidateTokenFormUseCase.kt b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/ValidateTokenFormUseCase.kt new file mode 100644 index 0000000000..ffbfa795bf --- /dev/null +++ b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/ValidateTokenFormUseCase.kt @@ -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 = 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.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.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 + } +} \ No newline at end of file diff --git a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/model/AddCustomTokenForm.kt b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/model/AddCustomTokenForm.kt new file mode 100644 index 0000000000..b70cd54ab9 --- /dev/null +++ b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/model/AddCustomTokenForm.kt @@ -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() + } +} \ No newline at end of file diff --git a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/model/exceptoin/CustomTokenFormValidationException.kt b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/model/exceptoin/CustomTokenFormValidationException.kt new file mode 100644 index 0000000000..a197980215 --- /dev/null +++ b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/model/exceptoin/CustomTokenFormValidationException.kt @@ -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() +} \ No newline at end of file diff --git a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/model/exceptoin/FindTokenException.kt b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/model/exceptoin/FindTokenException.kt new file mode 100644 index 0000000000..cc8b180305 --- /dev/null +++ b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/model/exceptoin/FindTokenException.kt @@ -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() +} \ No newline at end of file diff --git a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/repository/CustomTokensRepository.kt b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/repository/CustomTokensRepository.kt new file mode 100644 index 0000000000..b578627da0 --- /dev/null +++ b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/repository/CustomTokensRepository.kt @@ -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 +} \ No newline at end of file