Updated on 2026-08-14
This commit is contained in:
commit
c3522320ae
660 changed files with 10672 additions and 6904 deletions
|
|
@ -14,6 +14,7 @@ dependencies {
|
|||
implementation(projects.domain.demo)
|
||||
implementation(projects.domain.core)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
// TODO: Remove after new card scan result was implemented
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
package com.tangem.domain.card
|
||||
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
|
||||
/**
|
||||
* Use case for getting access code saving status
|
||||
*
|
||||
* @property cardSdkConfigRepository repository for managing of CardSDK config
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class GetAccessCodeSavingStatusUseCase(private val cardSdkConfigRepository: CardSdkConfigRepository) {
|
||||
|
||||
operator fun invoke(): Boolean = cardSdkConfigRepository.isAccessCodeSavingEnabled()
|
||||
}
|
||||
|
|
@ -27,9 +27,6 @@ interface CardSdkConfigRepository {
|
|||
/** Update the card ID display format according to the [productType] of the scanned card */
|
||||
fun updateCardIdDisplayFormat(productType: ProductType)
|
||||
|
||||
/** Check if access code saving is enabled */
|
||||
fun isAccessCodeSavingEnabled(): Boolean
|
||||
|
||||
/** Get common signer by [cardId] */
|
||||
fun getCommonSigner(cardId: String?): CommonSigner
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
package com.tangem.domain.core.lce
|
||||
|
||||
import arrow.core.identity
|
||||
import com.tangem.domain.core.utils.flatMap
|
||||
import com.tangem.domain.core.utils.lceContent
|
||||
import com.tangem.domain.core.utils.lceError
|
||||
import com.tangem.domain.core.utils.lceLoading
|
||||
|
||||
/**
|
||||
* A sealed class representing the three states of a data load operation: Loading, Content, and Error.
|
||||
*
|
||||
* @param E The type of the error object.
|
||||
* @param C The type of the content object.
|
||||
*/
|
||||
sealed class Lce<out E : Any, out C : Any> {
|
||||
|
||||
/**
|
||||
* Represents the loading state, which may contain partial content.
|
||||
*
|
||||
* @param partialContent The partial content that has been loaded so far, if any.
|
||||
*/
|
||||
data class Loading<C : Any>(val partialContent: C?) : Lce<Nothing, C>()
|
||||
|
||||
/**
|
||||
* Represents the content state, which contains the loaded content.
|
||||
*
|
||||
* @param content The loaded content.
|
||||
*/
|
||||
data class Content<C : Any>(val content: C) : Lce<Nothing, C>()
|
||||
|
||||
/**
|
||||
* Represents the error state, which contains an error object.
|
||||
*
|
||||
* @param error The error that occurred during loading.
|
||||
*/
|
||||
data class Error<E : Any>(val error: E) : Lce<E, Nothing>()
|
||||
|
||||
/**
|
||||
* Applies the given functions to the content, error, or partial content of this Lce, depending on its state.
|
||||
*
|
||||
* @param ifLoading The function to apply if this is a [Loading] state.
|
||||
* @param ifContent The function to apply if this is a [Content] state.
|
||||
* @param ifError The function to apply if this is an [Error] state.
|
||||
* @return The result of applying the corresponding function.
|
||||
*/
|
||||
inline fun <T> fold(
|
||||
ifLoading: (partialContent: C?) -> T,
|
||||
ifContent: (content: C) -> T,
|
||||
ifError: (error: E) -> T,
|
||||
): T = when (this) {
|
||||
is Loading -> ifLoading(partialContent)
|
||||
is Error -> ifError(error)
|
||||
is Content -> ifContent(content)
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the content of this [Lce] by applying the given function.
|
||||
* If this is a [Content] state, the function is applied to the [Content.content].
|
||||
* If this is a [Loading] state and partialContent is present,
|
||||
* the function is applied to the [Loading.partialContent].
|
||||
*
|
||||
* @param ifContent The function to apply to the content or partial content.
|
||||
* @return A new [Lce] instance containing the result of applying the function.
|
||||
*/
|
||||
inline fun <T : Any> map(ifContent: (C) -> T): Lce<E, T> = flatMap { content, isLoading ->
|
||||
if (isLoading) {
|
||||
lceLoading(ifContent(content))
|
||||
} else {
|
||||
ifContent(content).lceContent()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the error of this [Lce] by applying the given function, if this is an [Error] state.
|
||||
*
|
||||
* @param ifError The function to apply to the error.
|
||||
* @return A new [Lce] with the transformed error, or this [Lce] unchanged if it is not an [Error] state.
|
||||
*/
|
||||
inline fun <T : Any> mapError(ifError: (E) -> T): Lce<T, C> = when (this) {
|
||||
is Loading -> this
|
||||
is Content -> this
|
||||
is Error -> ifError(error).lceError()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content of this [Lce] if it's a [Lce.Content] or partial content if it's a [Lce.Loading],
|
||||
* `null` if it's a [Lce.Error].
|
||||
*
|
||||
* @return The content of this [Lce] or `null`.
|
||||
*/
|
||||
fun getOrNull(): C? = fold(
|
||||
ifLoading = ::identity,
|
||||
ifContent = ::identity,
|
||||
ifError = { null },
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
package com.tangem.domain.core.lce
|
||||
|
||||
import arrow.core.raise.Raise
|
||||
import com.tangem.domain.core.utils.lceContent
|
||||
import com.tangem.domain.core.utils.lceLoading
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.channels.ProducerScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.experimental.ExperimentalTypeInference
|
||||
|
||||
/**
|
||||
* A [Flow] of [Lce]
|
||||
*
|
||||
* @param E The type of the error object.
|
||||
* @param C The type of the content object.
|
||||
* */
|
||||
typealias LceFlow<E, C> = Flow<Lce<E, C>>
|
||||
|
||||
/**
|
||||
* A class that wraps a [LceRaise] instance for [Lce] type within a [ProducerScope].
|
||||
* It provides methods to handle [Lce] instances and raise errors within a [Flow].
|
||||
*
|
||||
* @property raise The [LceRaise] instance that this class wraps.
|
||||
* @property scope The [ProducerScope] that this class operates within.
|
||||
* @property ifLoading The function to call if a loading state is raised.
|
||||
*/
|
||||
class LceFlowScope<E : Any, C : Any> @PublishedApi internal constructor(
|
||||
private val raise: LceRaise<E>,
|
||||
private val scope: ProducerScope<Lce<E, C>>,
|
||||
private val ifLoading: LceRaise<E>.(C) -> Lce<E, C>,
|
||||
) : Raise<Lce<E, Nothing>> by raise,
|
||||
CoroutineScope by scope {
|
||||
|
||||
/**
|
||||
* Raises an [Lce] instance within the [ProducerScope].
|
||||
* It closes the [ProducerScope] after raise.
|
||||
*
|
||||
* @param r The [Lce] instance to raise.
|
||||
*/
|
||||
override fun raise(r: Lce<E, Nothing>): Nothing {
|
||||
scope.launch(NonCancellable) {
|
||||
scope.send(r)
|
||||
scope.close()
|
||||
}
|
||||
|
||||
raise.raise(r)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a content value within the [ProducerScope].
|
||||
* If the content is still loading, it calls [ifLoading] lambda to retrieve a state.
|
||||
* Otherwise, it wraps the content in a [Lce.Content] state.
|
||||
*
|
||||
* @param content The content value to send.
|
||||
* @param isStillLoading A flag indicating whether the content is still loading.
|
||||
*/
|
||||
suspend fun send(content: C, isStillLoading: Boolean = false) {
|
||||
val value = if (isStillLoading) {
|
||||
ifLoading(raise, content)
|
||||
} else {
|
||||
content.lceContent()
|
||||
}
|
||||
|
||||
scope.send(value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [LceFlow] by executing the given [block] within a [LceFlowScope] context.
|
||||
*
|
||||
* Flow starts with a [Lce.Loading] state.
|
||||
*
|
||||
* @param ifLoading The function to call if the [block] raises a [Lce.Loading] state.
|
||||
* By default, it creates a new [Lce.Loading] state with the value returned by the [block].
|
||||
* @param block The block to execute within a [LceFlowScope] context.
|
||||
* @return A [LceFlow] representing the result of the [block].
|
||||
*/
|
||||
@OptIn(ExperimentalTypeInference::class)
|
||||
fun <E : Any, C : Any> lceFlow(
|
||||
ifLoading: LceRaise<E>.(C) -> Lce<E, C> = { lceLoading(partialContent = it) },
|
||||
@BuilderInference block: suspend LceFlowScope<E, C>.() -> Unit,
|
||||
): LceFlow<E, C> {
|
||||
return channelFlow {
|
||||
trySend(lceLoading())
|
||||
|
||||
lce {
|
||||
val scope = LceFlowScope(
|
||||
raise = this@lce,
|
||||
scope = this@channelFlow,
|
||||
ifLoading = ifLoading,
|
||||
)
|
||||
|
||||
block(scope)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
package com.tangem.domain.core.lce
|
||||
|
||||
import arrow.atomic.Atomic
|
||||
import arrow.core.raise.Raise
|
||||
import arrow.core.raise.recover
|
||||
import com.tangem.domain.core.utils.lceContent
|
||||
import com.tangem.domain.core.utils.lceLoading
|
||||
import kotlin.experimental.ExperimentalTypeInference
|
||||
|
||||
/**
|
||||
* A class that wraps a [Raise] instance for [Lce] type.
|
||||
* It provides methods to handle [Lce] instances and raise errors.
|
||||
*
|
||||
* @property raise The [Raise] instance that this class wraps.
|
||||
* @property isLoading An [Atomic] boolean flag indicating whether a loading operation is in progress.
|
||||
*/
|
||||
class LceRaise<E : Any> @PublishedApi internal constructor(
|
||||
private val raise: Raise<Lce<E, Nothing>>,
|
||||
) : Raise<Lce<E, Nothing>> by raise {
|
||||
|
||||
val isLoading: Atomic<Boolean> = Atomic(false)
|
||||
|
||||
/**
|
||||
* Binds the content of this [Lce] instance and handles its state.
|
||||
* If this is a [Lce.Loading] state, sets the [isLoading] flag to true and calls the [ifLoading] function.
|
||||
* If this is a [Lce.Content] state, returns the content.
|
||||
* If this is a [Lce.Error] state, raises the error.
|
||||
*
|
||||
* @param ifLoading The function to call if this is a [Lce.Loading] state.
|
||||
* By default, it raises a new [Lce.Loading] state.
|
||||
* @return The content of this [Lce] instance.
|
||||
*/
|
||||
fun <C : Any> Lce<E, C>.bind(ifLoading: (partialContent: C?) -> C = { raise(lceLoading()) }): C = when (this) {
|
||||
is Lce.Loading -> {
|
||||
isLoading.set(true)
|
||||
|
||||
ifLoading(partialContent)
|
||||
}
|
||||
is Lce.Content -> content
|
||||
is Lce.Error -> raise(r = this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds the content of this [Lce] instance and handles its state.
|
||||
* If this is a [Lce.Loading] state, sets the [isLoading] flag to true and returns the partial content.
|
||||
* If this is a [Lce.Content] state, returns the content.
|
||||
* If this is a [Lce.Error] state, raises the error.
|
||||
*
|
||||
* @return The content of this [Lce] instance.
|
||||
*/
|
||||
fun <C : Any> Lce<E, C>.bindOrNull(): C? = when (this) {
|
||||
is Lce.Loading -> {
|
||||
isLoading.set(true)
|
||||
|
||||
partialContent
|
||||
}
|
||||
is Lce.Content -> content
|
||||
is Lce.Error -> raise(r = this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [Lce] instance by executing the given [block] within a [LceRaise] context.
|
||||
*
|
||||
* @param ifLoading The function to call if the [block] raises a [Lce.Loading] state.
|
||||
* By default, it creates a new [Lce.Loading] state with the value returned by the [block].
|
||||
* @param block The block to execute within a [LceRaise] context.
|
||||
* @return A [Lce] instance representing the result of the [block].
|
||||
*/
|
||||
@OptIn(ExperimentalTypeInference::class)
|
||||
inline fun <E : Any, C : Any> lce(
|
||||
ifLoading: LceRaise<E>.(C) -> Lce<E, C> = { lceLoading(partialContent = it) },
|
||||
@BuilderInference block: LceRaise<E>.() -> C,
|
||||
): Lce<E, C> = recover(
|
||||
block = {
|
||||
val raise = LceRaise(raise = this)
|
||||
val value = block(raise)
|
||||
|
||||
if (raise.isLoading.get()) {
|
||||
ifLoading(raise, value)
|
||||
} else {
|
||||
value.lceContent()
|
||||
}
|
||||
},
|
||||
recover = { e: Lce<E, Nothing> -> e },
|
||||
)
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.domain.core.utils
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* [Flow] of [Either]
|
||||
*
|
||||
* @param E type of left value
|
||||
* @param A type of right value
|
||||
* */
|
||||
typealias EitherFlow<E, A> = Flow<Either<E, A>>
|
||||
|
||||
/**
|
||||
* Converts an [Either] instance to a [Lce] instance.
|
||||
* If this is a [Either.Left], it is converted to a [Lce.Error] with the same error.
|
||||
* If this is a [Either.Right], it is converted to a [Lce.Content] or [Lce.Loading] with the same content,
|
||||
* depending on the [isStillLoading] parameter.
|
||||
*
|
||||
* @param isStillLoading A flag indicating whether the content is still loading.
|
||||
* If true, the [Either.Right] is converted to a [Lce.Loading].
|
||||
* @return A [Lce] instance containing the same content or error as this [Either],
|
||||
* and possibly indicating a loading state.
|
||||
*/
|
||||
inline fun <reified E : Any, reified T : Any> Either<E, T>.toLce(isStillLoading: Boolean = false): Lce<E, T> {
|
||||
return when (this) {
|
||||
is Either.Left -> Lce.Error(value)
|
||||
is Either.Right -> {
|
||||
if (isStillLoading) {
|
||||
Lce.Loading(value)
|
||||
} else {
|
||||
Lce.Content(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package com.tangem.domain.core.utils
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.identity
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
|
||||
/**
|
||||
* Creates a [Lce.Loading] instance with optional partial content.
|
||||
*
|
||||
* @param partialContent The partial content that has been loaded so far, if any.
|
||||
* @return A [Lce.Loading] instance.
|
||||
*/
|
||||
fun <C : Any> lceLoading(partialContent: C? = null): Lce<Nothing, C> = Lce.Loading(partialContent)
|
||||
|
||||
/**
|
||||
* Wraps the receiver object in a [Lce.Content] instance.
|
||||
*
|
||||
* @return A [Lce.Content] instance containing the receiver object.
|
||||
*/
|
||||
fun <C : Any> C.lceContent(): Lce<Nothing, C> = Lce.Content(content = this)
|
||||
|
||||
/**
|
||||
* Wraps the receiver object in a [Lce.Error] instance.
|
||||
*
|
||||
* @return A [Lce.Error] instance containing the receiver object.
|
||||
*/
|
||||
fun <E : Any> E.lceError(): Lce<E, Nothing> = Lce.Error(error = this)
|
||||
|
||||
/**
|
||||
* Transforms this [Lce] instance by applying the given function into a new [Lce] instance.
|
||||
*
|
||||
* @param block The function to apply to the content of this [Lce].
|
||||
* @return A new [Lce] instance containing the result of applying the function.
|
||||
* */
|
||||
inline fun <E : Any, C : Any, T : Any> Lce<E, C>.flatMap(
|
||||
block: (content: C, isLoading: Boolean) -> Lce<E, T>,
|
||||
): Lce<E, T> = when (this) {
|
||||
is Lce.Loading -> partialContent?.let { block(it, true) } ?: lceLoading()
|
||||
is Lce.Content -> block(content, false)
|
||||
is Lce.Error -> this
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content of this [Lce] if it's a [Lce.Content] or applies the given functions if it's a [Lce.Loading] or [Lce.Error].
|
||||
*
|
||||
* @param ifLoading The function to apply if this is a [Lce.Loading] state.
|
||||
* @param ifError The function to apply if this is a [Lce.Error] state.
|
||||
* @return The content of this [Lce] or the result of applying the corresponding function.
|
||||
*/
|
||||
inline fun <E : Any, C : Any> Lce<E, C>.getOrElse(ifLoading: (maybeContent: C?) -> C, ifError: (error: E) -> C): C {
|
||||
return fold(
|
||||
ifLoading = ifLoading,
|
||||
ifContent = ::identity,
|
||||
ifError = ifError,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms this [Lce] into an [Either] instance.
|
||||
* If this is a [Lce.Content], the content is wrapped in a [Either.Right].
|
||||
* If this is a [Lce.Error], the error is wrapped in a [Either.Left].
|
||||
* If this is a [Lce.Loading], the [ifLoading] function is applied to the partial content and the result is wrapped in a
|
||||
* [Either.Right].
|
||||
*
|
||||
* @param ifLoading The function to apply if this is a [Lce.Loading] state.
|
||||
* @return An [Either] instance containing the content or error of this [Lce],
|
||||
* or the result of applying the [ifLoading] function to the partial content.
|
||||
*/
|
||||
inline fun <E : Any, C : Any> Lce<E, C>.toEither(ifLoading: (maybeContent: C?) -> C): Either<E, C> = fold(
|
||||
ifLoading = { ifLoading(it).right() },
|
||||
ifContent = { it.right() },
|
||||
ifError = { it.left() },
|
||||
)
|
||||
|
|
@ -13,4 +13,5 @@ dependencies {
|
|||
implementation(deps.jodatime)
|
||||
|
||||
implementation(projects.core.res)
|
||||
implementation(projects.domain.wallets.models)
|
||||
}
|
||||
|
|
@ -1,9 +1,6 @@
|
|||
package com.tangem.domain.feedback
|
||||
|
||||
import com.tangem.domain.feedback.models.BlockchainInfo
|
||||
import com.tangem.domain.feedback.models.CardInfo
|
||||
import com.tangem.domain.feedback.models.PhoneInfo
|
||||
import com.tangem.domain.feedback.models.UserWalletsInfo
|
||||
import com.tangem.domain.feedback.models.*
|
||||
import com.tangem.domain.feedback.utils.breakLine
|
||||
import com.tangem.domain.feedback.models.BlockchainInfo.Addresses as BlockchainAddresses
|
||||
|
||||
|
|
@ -68,6 +65,24 @@ internal class FeedbackDataBuilder {
|
|||
builder.appendKeyValue("App version", phoneInfo.appVersion)
|
||||
}
|
||||
|
||||
fun addBlockchainError(info: BlockchainInfo, error: BlockchainErrorInfo) {
|
||||
builder.appendKeyValue("Blockchain", info.blockchain)
|
||||
builder.appendKeyValue("Derivation path", info.derivationPath)
|
||||
builder.appendKeyValue("Host", info.host)
|
||||
builder.appendKeyValue("Token", error.tokenSymbol)
|
||||
builder.appendKeyValue("Error", error.errorMessage)
|
||||
|
||||
builder.appendDelimiter()
|
||||
|
||||
builder.appendAddresses(
|
||||
key = "Source address${info.addresses.isMultiple(suffix = "es")}",
|
||||
addresses = info.addresses,
|
||||
)
|
||||
builder.appendKeyValue("Destination address", error.destinationAddress)
|
||||
builder.appendKeyValue("Amount", error.amount)
|
||||
builder.appendKeyValue("Fee", error.fee ?: "Unable to receive")
|
||||
}
|
||||
|
||||
fun addDelimiter(): StringBuilder = builder.appendDelimiter()
|
||||
|
||||
fun build(): String = builder.trimEnd().toString()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
package com.tangem.domain.feedback
|
||||
|
||||
import android.content.res.Resources
|
||||
import com.tangem.domain.feedback.models.CardInfo
|
||||
import com.tangem.domain.feedback.models.FeedbackEmail
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.feedback.repository.FeedbackRepository
|
||||
import com.tangem.domain.feedback.utils.*
|
||||
|
||||
/**
|
||||
* Get email with feedback for support
|
||||
*
|
||||
* @property feedbackRepository feedback repository
|
||||
* @property resources resources for getting strings
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class GetFeedbackEmailUseCase(
|
||||
private val feedbackRepository: FeedbackRepository,
|
||||
private val resources: Resources,
|
||||
) {
|
||||
|
||||
private val emailSubjectResolver = EmailSubjectResolver(resources)
|
||||
private val emailMessageTitleResolver = EmailMessageTitleResolver(resources)
|
||||
private val emailMessageBodyResolver = EmailMessageBodyResolver(feedbackRepository)
|
||||
|
||||
suspend operator fun invoke(type: FeedbackEmailType): FeedbackEmail {
|
||||
val cardInfo = feedbackRepository.getCardInfo()
|
||||
|
||||
val formattedLogs = AppLogsFormatter().format(appLogs = feedbackRepository.getAppLogs())
|
||||
|
||||
return FeedbackEmail(
|
||||
address = getAddress(cardInfo),
|
||||
subject = emailSubjectResolver.resolve(type, cardInfo),
|
||||
message = createMessage(type, cardInfo),
|
||||
file = feedbackRepository.createLogFile(logs = formattedLogs),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getAddress(cardInfo: CardInfo): String {
|
||||
return if (cardInfo.isStart2Coin) START2COIN_SUPPORT_EMAIL else TANGEM_SUPPORT_EMAIL
|
||||
}
|
||||
|
||||
private suspend fun createMessage(type: FeedbackEmailType, cardInfo: CardInfo): String {
|
||||
return StringBuilder().apply {
|
||||
val title = emailMessageTitleResolver.resolve(type)
|
||||
append(title)
|
||||
|
||||
skipLine()
|
||||
|
||||
appendDisclaimerIfNeeded(type)
|
||||
|
||||
skipLine()
|
||||
|
||||
val body = emailMessageBodyResolver.resolve(type, cardInfo)
|
||||
append(body)
|
||||
}.toString()
|
||||
}
|
||||
|
||||
private fun StringBuilder.appendDisclaimerIfNeeded(type: FeedbackEmailType): StringBuilder {
|
||||
return if (type is FeedbackEmailType.ScanningProblem) {
|
||||
this
|
||||
} else {
|
||||
append(resources.getString(R.string.feedback_data_collection_message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
package com.tangem.domain.feedback
|
||||
|
||||
import android.content.res.Resources
|
||||
import com.tangem.domain.feedback.models.CardInfo
|
||||
import com.tangem.domain.feedback.models.SupportFeedbackEmail
|
||||
import com.tangem.domain.feedback.repository.FeedbackRepository
|
||||
import com.tangem.domain.feedback.utils.skipLine
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.format.DateTimeFormatterBuilder
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Get email with feedback for support
|
||||
*
|
||||
* @property feedbackRepository feedback repository
|
||||
* @property resources resources for getting strings
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class GetSupportFeedbackEmailUseCase(
|
||||
private val feedbackRepository: FeedbackRepository,
|
||||
private val resources: Resources,
|
||||
) {
|
||||
|
||||
// 00.00 00:00:00.000
|
||||
private val dateFormatter = DateTimeFormatterBuilder()
|
||||
.appendDayOfMonth(2)
|
||||
.appendLiteral('.')
|
||||
.appendMonthOfYear(2)
|
||||
.appendLiteral(' ')
|
||||
.appendHourOfDay(2)
|
||||
.appendLiteral(':')
|
||||
.appendMinuteOfHour(2)
|
||||
.appendLiteral(':')
|
||||
.appendSecondOfMinute(2)
|
||||
.appendLiteral('.')
|
||||
.appendMillisOfSecond(3)
|
||||
.toFormatter()
|
||||
.withLocale(Locale.getDefault())
|
||||
|
||||
suspend operator fun invoke(): SupportFeedbackEmail {
|
||||
val cardInfo = feedbackRepository.getCardInfo()
|
||||
|
||||
return SupportFeedbackEmail(
|
||||
address = getEmail(isStart2Coin = cardInfo.isStart2Coin),
|
||||
subject = getSubject(isStart2Coin = cardInfo.isStart2Coin),
|
||||
message = createMessage(cardInfo),
|
||||
file = feedbackRepository.createLogFile(logs = getLogs()),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getEmail(isStart2Coin: Boolean): String {
|
||||
return if (isStart2Coin) START2COIN_SUPPORT_EMAIL else TANGEM_SUPPORT_EMAIL
|
||||
}
|
||||
|
||||
private fun getSubject(isStart2Coin: Boolean): String {
|
||||
return resources.getString(
|
||||
if (isStart2Coin) {
|
||||
R.string.feedback_subject_support
|
||||
} else {
|
||||
R.string.feedback_subject_support_tangem
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun createMessage(cardInfo: CardInfo): String {
|
||||
return StringBuilder().apply {
|
||||
append(resources.getString(R.string.feedback_preface_support))
|
||||
skipLine()
|
||||
append(resources.getString(R.string.feedback_data_collection_message))
|
||||
skipLine()
|
||||
append(
|
||||
FeedbackDataBuilder().apply {
|
||||
addUserWalletsInfo(userWalletsInfo = feedbackRepository.getUserWalletsInfo())
|
||||
addDelimiter()
|
||||
addCardInfo(cardInfo)
|
||||
addDelimiter()
|
||||
addBlockchainInfoList(blockchainInfoList = feedbackRepository.getBlockchainInfoList())
|
||||
addDelimiter()
|
||||
addPhoneInfo(phoneInfo = feedbackRepository.getPhoneInfo())
|
||||
}.build(),
|
||||
)
|
||||
}.toString()
|
||||
}
|
||||
|
||||
private suspend fun getLogs(): String {
|
||||
val builder = StringBuilder()
|
||||
|
||||
var sum = 0
|
||||
val appLogs = feedbackRepository.getAppLogs()
|
||||
for (i in appLogs.lastIndex downTo 0) {
|
||||
val log = appLogs[i]
|
||||
val date = dateFormatter.print(DateTime(log.timestamp))
|
||||
|
||||
val formattedLog = "$date: ${log.message}\n"
|
||||
|
||||
sum += formattedLog.length
|
||||
if (sum < GMAIL_MAX_FILE_SIZE) {
|
||||
builder.insert(0, formattedLog)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val START2COIN_SUPPORT_EMAIL = "cardsupport@start2coin.com"
|
||||
const val TANGEM_SUPPORT_EMAIL = "support@tangem.com"
|
||||
const val GMAIL_MAX_FILE_SIZE = 24_900_000 // ≈ 25 MB
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.domain.feedback
|
||||
|
||||
import com.tangem.domain.feedback.models.BlockchainErrorInfo
|
||||
import com.tangem.domain.feedback.repository.FeedbackRepository
|
||||
|
||||
/**
|
||||
* Save last blockchain error
|
||||
*
|
||||
* @property feedbackRepository feedback repository
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class SaveBlockchainErrorUseCase(
|
||||
private val feedbackRepository: FeedbackRepository,
|
||||
) {
|
||||
|
||||
fun invoke(error: BlockchainErrorInfo) {
|
||||
feedbackRepository.saveBlockchainErrorInfo(error = error)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.domain.feedback.models
|
||||
|
||||
/**
|
||||
* Information about blockchain's operation error
|
||||
*
|
||||
* @property errorMessage message about error
|
||||
* @property blockchainId blockchain id
|
||||
* @property derivationPath derivation path
|
||||
* @property destinationAddress destination address
|
||||
* @property tokenSymbol token symbol or null, if it isn't operation with token
|
||||
* @property amount amount
|
||||
* @property fee fee or null, if unable to get
|
||||
*/
|
||||
data class BlockchainErrorInfo(
|
||||
val errorMessage: String,
|
||||
val blockchainId: String,
|
||||
val derivationPath: String?,
|
||||
val destinationAddress: String,
|
||||
val tokenSymbol: String?,
|
||||
val amount: String,
|
||||
val fee: String?,
|
||||
)
|
||||
|
|
@ -2,7 +2,7 @@ package com.tangem.domain.feedback.models
|
|||
|
||||
import java.io.File
|
||||
|
||||
data class SupportFeedbackEmail(
|
||||
data class FeedbackEmail(
|
||||
val address: String,
|
||||
val subject: String,
|
||||
val message: String,
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.domain.feedback.models
|
||||
|
||||
/**
|
||||
* Email feedback type
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed interface FeedbackEmailType {
|
||||
|
||||
/** User initiate request yourself. Example, button on DetailsScreen or OnboardingScreen */
|
||||
data object DirectUserRequest : FeedbackEmailType
|
||||
|
||||
/** User rate the app as "can be better" */
|
||||
data object RateCanBeBetter : FeedbackEmailType
|
||||
|
||||
/** User has problem with scanning */
|
||||
data object ScanningProblem : FeedbackEmailType
|
||||
|
||||
/** User has problem with sending transaction */
|
||||
data object TransactionSendingProblem : FeedbackEmailType
|
||||
}
|
||||
|
|
@ -11,8 +11,14 @@ interface FeedbackRepository {
|
|||
|
||||
suspend fun getBlockchainInfoList(): List<BlockchainInfo>
|
||||
|
||||
suspend fun getBlockchainInfo(blockchainId: String, derivationPath: String?): BlockchainInfo?
|
||||
|
||||
fun getPhoneInfo(): PhoneInfo
|
||||
|
||||
fun saveBlockchainErrorInfo(error: BlockchainErrorInfo)
|
||||
|
||||
suspend fun getBlockchainErrorInfo(): BlockchainErrorInfo?
|
||||
|
||||
suspend fun getAppLogs(): List<AppLogModel>
|
||||
|
||||
suspend fun createLogFile(logs: String): File?
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
package com.tangem.domain.feedback.utils
|
||||
|
||||
import com.tangem.domain.feedback.models.AppLogModel
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.format.DateTimeFormatter
|
||||
import org.joda.time.format.DateTimeFormatterBuilder
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* App logs formatter
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class AppLogsFormatter {
|
||||
|
||||
private val dateFormatter = createDateFormatter()
|
||||
|
||||
/** Format [appLogs] to [String] */
|
||||
fun format(appLogs: List<AppLogModel>): String {
|
||||
val builder = StringBuilder()
|
||||
|
||||
var sum = 0
|
||||
for (i in appLogs.lastIndex downTo 0) {
|
||||
val log = appLogs[i]
|
||||
val date = dateFormatter.print(DateTime(log.timestamp))
|
||||
|
||||
val formattedLog = "$date: ${log.message}\n"
|
||||
|
||||
sum += formattedLog.length
|
||||
if (sum < GMAIL_MAX_FILE_SIZE) {
|
||||
builder.insert(0, formattedLog)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString()
|
||||
}
|
||||
|
||||
// Example, 00.00 00:00:00.000
|
||||
private fun createDateFormatter(): DateTimeFormatter {
|
||||
return DateTimeFormatterBuilder()
|
||||
.appendDayOfMonth(2)
|
||||
.appendLiteral('.')
|
||||
.appendMonthOfYear(2)
|
||||
.appendLiteral(' ')
|
||||
.appendHourOfDay(2)
|
||||
.appendLiteral(':')
|
||||
.appendMinuteOfHour(2)
|
||||
.appendLiteral(':')
|
||||
.appendSecondOfMinute(2)
|
||||
.appendLiteral('.')
|
||||
.appendMillisOfSecond(MIN_MILLIS_DIGITS)
|
||||
.toFormatter()
|
||||
.withLocale(Locale.getDefault())
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val MIN_MILLIS_DIGITS = 3
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package com.tangem.domain.feedback.utils
|
||||
|
||||
import com.tangem.domain.feedback.FeedbackDataBuilder
|
||||
import com.tangem.domain.feedback.models.CardInfo
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.feedback.repository.FeedbackRepository
|
||||
|
||||
/**
|
||||
* Email message body resolver
|
||||
*
|
||||
* @property feedbackRepository feedback repository
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class EmailMessageBodyResolver(
|
||||
private val feedbackRepository: FeedbackRepository,
|
||||
) {
|
||||
|
||||
/** Resolve email message body by [type] using [cardInfo] */
|
||||
suspend fun resolve(type: FeedbackEmailType, cardInfo: CardInfo): String = with(FeedbackDataBuilder()) {
|
||||
when (type) {
|
||||
FeedbackEmailType.DirectUserRequest -> addUserRequestBody(cardInfo)
|
||||
FeedbackEmailType.RateCanBeBetter -> addCardAndPhoneInfo(cardInfo)
|
||||
FeedbackEmailType.ScanningProblem -> addScanningProblemBody()
|
||||
FeedbackEmailType.TransactionSendingProblem -> addTransactionSendingProblemBody(cardInfo)
|
||||
}
|
||||
|
||||
return build()
|
||||
}
|
||||
|
||||
private suspend fun FeedbackDataBuilder.addUserRequestBody(cardInfo: CardInfo) {
|
||||
addUserWalletsInfo(userWalletsInfo = feedbackRepository.getUserWalletsInfo())
|
||||
addDelimiter()
|
||||
addCardInfo(cardInfo)
|
||||
addDelimiter()
|
||||
addBlockchainInfoList(blockchainInfoList = feedbackRepository.getBlockchainInfoList())
|
||||
addDelimiter()
|
||||
addPhoneInfo(phoneInfo = feedbackRepository.getPhoneInfo())
|
||||
}
|
||||
|
||||
private fun FeedbackDataBuilder.addScanningProblemBody() {
|
||||
addPhoneInfo(phoneInfo = feedbackRepository.getPhoneInfo())
|
||||
}
|
||||
|
||||
private suspend fun FeedbackDataBuilder.addTransactionSendingProblemBody(cardInfo: CardInfo) {
|
||||
addCardInfo(cardInfo)
|
||||
addDelimiter()
|
||||
|
||||
val blockchainError = feedbackRepository.getBlockchainErrorInfo()
|
||||
val blockchainInfo = blockchainError?.let {
|
||||
feedbackRepository.getBlockchainInfo(
|
||||
blockchainId = blockchainError.blockchainId,
|
||||
derivationPath = blockchainError.derivationPath,
|
||||
)
|
||||
}
|
||||
|
||||
if (blockchainInfo != null) {
|
||||
addBlockchainError(blockchainInfo, blockchainError)
|
||||
addDelimiter()
|
||||
}
|
||||
|
||||
addPhoneInfo(phoneInfo = feedbackRepository.getPhoneInfo())
|
||||
}
|
||||
|
||||
private fun FeedbackDataBuilder.addCardAndPhoneInfo(cardInfo: CardInfo) {
|
||||
addCardInfo(cardInfo)
|
||||
addDelimiter()
|
||||
addPhoneInfo(phoneInfo = feedbackRepository.getPhoneInfo())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.domain.feedback.utils
|
||||
|
||||
import android.content.res.Resources
|
||||
import com.tangem.domain.feedback.R
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
|
||||
/**
|
||||
* Email message title resolver
|
||||
*
|
||||
* @property resources resources
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class EmailMessageTitleResolver(private val resources: Resources) {
|
||||
|
||||
/** Resolve email message title by [type] */
|
||||
fun resolve(type: FeedbackEmailType): String {
|
||||
return when (type) {
|
||||
FeedbackEmailType.DirectUserRequest -> R.string.feedback_preface_support
|
||||
FeedbackEmailType.RateCanBeBetter -> R.string.feedback_preface_rate_negative
|
||||
FeedbackEmailType.ScanningProblem -> R.string.feedback_preface_scan_failed
|
||||
FeedbackEmailType.TransactionSendingProblem -> R.string.feedback_preface_tx_failed
|
||||
}
|
||||
.let(resources::getString)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.domain.feedback.utils
|
||||
|
||||
import android.content.res.Resources
|
||||
import com.tangem.domain.feedback.R
|
||||
import com.tangem.domain.feedback.models.CardInfo
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
|
||||
/**
|
||||
* Email subject resolver
|
||||
*
|
||||
* @property resources resources
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class EmailSubjectResolver(private val resources: Resources) {
|
||||
|
||||
/** Resolve email message body by [type] using [cardInfo] */
|
||||
fun resolve(type: FeedbackEmailType, cardInfo: CardInfo): String {
|
||||
return when (type) {
|
||||
FeedbackEmailType.DirectUserRequest -> {
|
||||
if (cardInfo.isStart2Coin) {
|
||||
R.string.feedback_subject_support
|
||||
} else {
|
||||
R.string.feedback_subject_support_tangem
|
||||
}
|
||||
}
|
||||
FeedbackEmailType.RateCanBeBetter -> R.string.feedback_subject_rate_negative
|
||||
FeedbackEmailType.ScanningProblem -> R.string.feedback_subject_scan_failed
|
||||
FeedbackEmailType.TransactionSendingProblem -> R.string.feedback_subject_tx_failed
|
||||
}
|
||||
.let(resources::getString)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.domain.feedback.utils
|
||||
|
||||
internal const val GMAIL_MAX_FILE_SIZE = 24_900_000 // ≈ 25 MB
|
||||
|
||||
internal const val START2COIN_SUPPORT_EMAIL = "cardsupport@start2coin.com"
|
||||
internal const val TANGEM_SUPPORT_EMAIL = "support@tangem.com"
|
||||
|
|
@ -9,16 +9,17 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":core:datasource"))
|
||||
implementation(project(":core:utils"))
|
||||
implementation(project(":common"))
|
||||
implementation(project(":libs:auth"))
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.common)
|
||||
implementation(projects.libs.auth)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.domain.demo)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.transaction.models)
|
||||
implementation(projects.domain.txhistory.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
||||
/** Tangem libraries */
|
||||
implementation(deps.tangem.blockchain) {
|
||||
exclude(module = "joda-time")
|
||||
|
|
|
|||
|
|
@ -20,6 +20,5 @@ object NetworkLogConfig {
|
|||
|
||||
object AnalyticsHandlersLogConfig {
|
||||
const val firebase: Boolean = false
|
||||
const val appsFlyer: Boolean = false
|
||||
val amplitude: Boolean = BuildConfig.LOG_ENABLED
|
||||
}
|
||||
|
|
@ -1,338 +0,0 @@
|
|||
package com.tangem.domain.common.extensions
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("ComplexMethod", "LongMethod")
|
||||
fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? {
|
||||
return when (networkId) {
|
||||
"arbitrum-one" -> Blockchain.Arbitrum
|
||||
"arbitrum-one/test" -> Blockchain.ArbitrumTestnet
|
||||
"avalanche", "avalanche-2" -> Blockchain.Avalanche
|
||||
"avalanche/test", "avalanche-2/test" -> Blockchain.AvalancheTestnet
|
||||
"binancecoin" -> Blockchain.Binance
|
||||
"binancecoin/test" -> Blockchain.BinanceTestnet
|
||||
"binance-smart-chain" -> Blockchain.BSC
|
||||
"binance-smart-chain/test" -> Blockchain.BSCTestnet
|
||||
"ethereum" -> Blockchain.Ethereum
|
||||
"ethereum/test" -> Blockchain.EthereumTestnet
|
||||
"ethereum-classic" -> Blockchain.EthereumClassic
|
||||
"ethereum-classic/test" -> Blockchain.EthereumClassicTestnet
|
||||
"polygon-pos", "matic-network" -> Blockchain.Polygon
|
||||
"polygon-pos/test", "matic-network/test" -> Blockchain.PolygonTestnet
|
||||
"solana" -> Blockchain.Solana
|
||||
"solana/test" -> Blockchain.SolanaTestnet
|
||||
"fantom" -> Blockchain.Fantom
|
||||
"fantom/test" -> Blockchain.FantomTestnet
|
||||
"bitcoin" -> Blockchain.Bitcoin
|
||||
"bitcoin/test" -> Blockchain.BitcoinTestnet
|
||||
"bitcoin-cash" -> Blockchain.BitcoinCash
|
||||
"bitcoin-cash/test" -> Blockchain.BitcoinCashTestnet
|
||||
"cardano" -> Blockchain.Cardano
|
||||
"dogecoin" -> Blockchain.Dogecoin
|
||||
"ducatus" -> Blockchain.Ducatus
|
||||
"litecoin" -> Blockchain.Litecoin
|
||||
"rootstock" -> Blockchain.RSK
|
||||
"stellar" -> Blockchain.Stellar
|
||||
"stellar/test" -> Blockchain.StellarTestnet
|
||||
"tezos" -> Blockchain.Tezos
|
||||
"tron" -> Blockchain.Tron
|
||||
"tron/test" -> Blockchain.TronTestnet
|
||||
"xrp", "ripple" -> Blockchain.XRP
|
||||
"xdai" -> Blockchain.Gnosis
|
||||
"ethereum-pow-iou" -> Blockchain.EthereumPow
|
||||
"ethereum-pow-iou/test" -> Blockchain.EthereumPowTestnet
|
||||
"ethereumfair", "dischain" -> Blockchain.Dischain // for old client compatibility
|
||||
"polkadot" -> Blockchain.Polkadot
|
||||
"polkadot/test" -> Blockchain.PolkadotTestnet
|
||||
"kusama" -> Blockchain.Kusama
|
||||
"optimistic-ethereum" -> Blockchain.Optimism
|
||||
"optimistic-ethereum/test" -> Blockchain.OptimismTestnet
|
||||
"dash" -> Blockchain.Dash
|
||||
"kaspa" -> Blockchain.Kaspa
|
||||
"the-open-network" -> Blockchain.TON
|
||||
"the-open-network/test" -> Blockchain.TONTestnet
|
||||
"kava" -> Blockchain.Kava
|
||||
"kava/test" -> Blockchain.KavaTestnet
|
||||
"ravencoin" -> Blockchain.Ravencoin
|
||||
"ravencoin/test" -> Blockchain.RavencoinTestnet
|
||||
"cosmos" -> Blockchain.Cosmos
|
||||
"cosmos/test" -> Blockchain.CosmosTestnet
|
||||
"terra" -> Blockchain.TerraV1
|
||||
"terra-2" -> Blockchain.TerraV2
|
||||
"cronos" -> Blockchain.Cronos
|
||||
"telos" -> Blockchain.Telos
|
||||
"telos/test" -> Blockchain.TelosTestnet
|
||||
"aleph-zero" -> Blockchain.AlephZero
|
||||
"aleph-zero/test" -> Blockchain.AlephZeroTestnet
|
||||
"octaspace" -> Blockchain.OctaSpace
|
||||
"octaspace/test" -> Blockchain.OctaSpaceTestnet
|
||||
"chia" -> Blockchain.Chia
|
||||
"chia/test" -> Blockchain.ChiaTestnet
|
||||
"near-protocol" -> Blockchain.Near
|
||||
"near-protocol/test" -> Blockchain.NearTestnet
|
||||
"decimal" -> Blockchain.Decimal
|
||||
"decimal/test" -> Blockchain.DecimalTestnet
|
||||
"xdc-network" -> Blockchain.XDC
|
||||
"xdc-network/test" -> Blockchain.XDCTestnet
|
||||
"vechain" -> Blockchain.VeChain
|
||||
"vechain/test" -> Blockchain.VeChainTestnet
|
||||
"aptos" -> Blockchain.Aptos
|
||||
"aptos/test" -> Blockchain.AptosTestnet
|
||||
"playa3ull-games" -> Blockchain.Playa3ull
|
||||
"shibarium" -> Blockchain.Shibarium
|
||||
"shibarium/test" -> Blockchain.ShibariumTestnet
|
||||
"algorand" -> Blockchain.Algorand
|
||||
"algorand/test" -> Blockchain.AlgorandTestnet
|
||||
"hedera-hashgraph" -> Blockchain.Hedera
|
||||
"hedera-hashgraph/test" -> Blockchain.HederaTestnet
|
||||
"aurora" -> Blockchain.Aurora
|
||||
"aurora/test" -> Blockchain.AuroraTestnet
|
||||
"areon-network" -> Blockchain.Areon
|
||||
"areon-network/test" -> Blockchain.AreonTestnet
|
||||
"pulsechain" -> Blockchain.PulseChain
|
||||
"pulsechain/test" -> Blockchain.PulseChainTestnet
|
||||
"zksync" -> Blockchain.ZkSyncEra
|
||||
"zksync/test" -> Blockchain.ZkSyncEraTestnet
|
||||
"moonbeam" -> Blockchain.Moonbeam
|
||||
"moonbeam/test" -> Blockchain.MoonbeamTestnet
|
||||
"manta-network" -> Blockchain.Manta
|
||||
"manta-network/test" -> Blockchain.MantaTestnet
|
||||
"polygon-zkevm" -> Blockchain.PolygonZkEVM
|
||||
"polygon-zkevm/test" -> Blockchain.PolygonZkEVMTestnet
|
||||
"nexa" -> Blockchain.Nexa // FIXME
|
||||
"nexa/test" -> Blockchain.NexaTestnet // FIXME
|
||||
"radiant" -> Blockchain.Radiant
|
||||
"moonriver" -> Blockchain.Moonriver
|
||||
"moonriver/test" -> Blockchain.MoonriverTestnet
|
||||
"mantle" -> Blockchain.Mantle
|
||||
"mantle/test" -> Blockchain.MantleTestnet
|
||||
"flare-network" -> Blockchain.Flare
|
||||
"flare-network/test" -> Blockchain.FlareTestnet
|
||||
"taraxa" -> Blockchain.Taraxa
|
||||
"taraxa/test" -> Blockchain.TaraxaTestnet
|
||||
"base" -> Blockchain.Base
|
||||
"base/test" -> Blockchain.BaseTestnet
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("ComplexMethod", "LongMethod")
|
||||
fun Blockchain.toNetworkId(): String {
|
||||
return when (this) {
|
||||
Blockchain.Unknown -> "unknown"
|
||||
Blockchain.Arbitrum -> "arbitrum-one"
|
||||
Blockchain.ArbitrumTestnet -> "arbitrum-one/test"
|
||||
Blockchain.Avalanche -> "avalanche"
|
||||
Blockchain.AvalancheTestnet -> "avalanche/test"
|
||||
Blockchain.Binance -> "binancecoin"
|
||||
Blockchain.BinanceTestnet -> "binancecoin/test"
|
||||
Blockchain.BSC -> "binance-smart-chain"
|
||||
Blockchain.BSCTestnet -> "binance-smart-chain/test"
|
||||
Blockchain.Bitcoin -> "bitcoin"
|
||||
Blockchain.BitcoinTestnet -> "bitcoin/test"
|
||||
Blockchain.BitcoinCash -> "bitcoin-cash"
|
||||
Blockchain.BitcoinCashTestnet -> "bitcoin-cash/test"
|
||||
Blockchain.Cardano -> "cardano"
|
||||
Blockchain.Dogecoin -> "dogecoin"
|
||||
Blockchain.Ducatus -> "ducatus"
|
||||
Blockchain.Ethereum -> "ethereum"
|
||||
Blockchain.EthereumTestnet -> "ethereum/test"
|
||||
Blockchain.EthereumClassic -> "ethereum-classic"
|
||||
Blockchain.EthereumClassicTestnet -> "ethereum-classic/test"
|
||||
Blockchain.Fantom -> "fantom"
|
||||
Blockchain.FantomTestnet -> "fantom/test"
|
||||
Blockchain.Litecoin -> "litecoin"
|
||||
Blockchain.Polygon -> "polygon-pos"
|
||||
Blockchain.PolygonTestnet -> "polygon-pos/test"
|
||||
Blockchain.RSK -> "rootstock"
|
||||
Blockchain.Stellar -> "stellar"
|
||||
Blockchain.StellarTestnet -> "stellar/test"
|
||||
Blockchain.Solana -> "solana"
|
||||
Blockchain.SolanaTestnet -> "solana/test"
|
||||
Blockchain.Tezos -> "tezos"
|
||||
Blockchain.XRP -> "xrp"
|
||||
Blockchain.Tron -> "tron"
|
||||
Blockchain.TronTestnet -> "tron/test"
|
||||
Blockchain.Gnosis -> "xdai"
|
||||
Blockchain.EthereumPow -> "ethereum-pow-iou"
|
||||
Blockchain.EthereumPowTestnet -> "ethereum-pow-iou/test"
|
||||
Blockchain.Dischain -> "ethereumfair" // for backend compatibility
|
||||
Blockchain.Polkadot -> "polkadot"
|
||||
Blockchain.PolkadotTestnet -> "polkadot/test"
|
||||
Blockchain.Kusama -> "kusama"
|
||||
Blockchain.Optimism -> "optimistic-ethereum"
|
||||
Blockchain.OptimismTestnet -> "optimistic-ethereum/test"
|
||||
Blockchain.Dash -> "dash"
|
||||
Blockchain.Kaspa -> "kaspa"
|
||||
Blockchain.TON -> "the-open-network"
|
||||
Blockchain.TONTestnet -> "the-open-network/test"
|
||||
Blockchain.Kava -> "kava"
|
||||
Blockchain.KavaTestnet -> "kava/test"
|
||||
Blockchain.Ravencoin -> "ravencoin"
|
||||
Blockchain.RavencoinTestnet -> "ravencoin/test"
|
||||
Blockchain.Cosmos -> "cosmos"
|
||||
Blockchain.CosmosTestnet -> "cosmos/test"
|
||||
Blockchain.TerraV1 -> "terra"
|
||||
Blockchain.TerraV2 -> "terra-2"
|
||||
Blockchain.Cronos -> "cronos"
|
||||
Blockchain.Telos -> "telos"
|
||||
Blockchain.TelosTestnet -> "telos/test"
|
||||
Blockchain.AlephZero -> "aleph-zero"
|
||||
Blockchain.AlephZeroTestnet -> "aleph-zero/test"
|
||||
Blockchain.OctaSpace -> "octaspace"
|
||||
Blockchain.OctaSpaceTestnet -> "octaspace/test"
|
||||
Blockchain.Chia -> "chia"
|
||||
Blockchain.ChiaTestnet -> "chia/test"
|
||||
Blockchain.Near -> "near-protocol"
|
||||
Blockchain.NearTestnet -> "near-protocol/test"
|
||||
Blockchain.Decimal -> "decimal"
|
||||
Blockchain.DecimalTestnet -> "decimal/test"
|
||||
Blockchain.XDC -> "xdc-network"
|
||||
Blockchain.XDCTestnet -> "xdc-network/test"
|
||||
Blockchain.VeChain -> "vechain"
|
||||
Blockchain.VeChainTestnet -> "vechain/test"
|
||||
Blockchain.Aptos -> "aptos"
|
||||
Blockchain.AptosTestnet -> "aptos/test"
|
||||
Blockchain.Playa3ull -> "playa3ull-games"
|
||||
Blockchain.Shibarium -> "shibarium"
|
||||
Blockchain.ShibariumTestnet -> "shibarium/test"
|
||||
Blockchain.Algorand -> "algorand"
|
||||
Blockchain.AlgorandTestnet -> "algorand/test"
|
||||
Blockchain.Hedera -> "hedera-hashgraph"
|
||||
Blockchain.HederaTestnet -> "hedera-hashgraph/test"
|
||||
Blockchain.Aurora -> "aurora"
|
||||
Blockchain.AuroraTestnet -> "aurora/test"
|
||||
Blockchain.Areon -> "areon-network"
|
||||
Blockchain.AreonTestnet -> "areon-network/test"
|
||||
Blockchain.PulseChain -> "pulsechain"
|
||||
Blockchain.PulseChainTestnet -> "pulsechain/test"
|
||||
Blockchain.ZkSyncEra -> "zksync"
|
||||
Blockchain.ZkSyncEraTestnet -> "zksync/test"
|
||||
Blockchain.Moonbeam -> "moonbeam"
|
||||
Blockchain.MoonbeamTestnet -> "moonbeam/test"
|
||||
Blockchain.Manta -> "manta-network"
|
||||
Blockchain.MantaTestnet -> "manta-network/test"
|
||||
Blockchain.PolygonZkEVM -> "polygon-zkevm"
|
||||
Blockchain.PolygonZkEVMTestnet -> "polygon-zkevm/test"
|
||||
Blockchain.Nexa -> "nexa" // FIXME
|
||||
Blockchain.NexaTestnet -> "nexa/test" // FIXME
|
||||
Blockchain.Radiant -> "radiant"
|
||||
Blockchain.Moonriver -> "moonriver"
|
||||
Blockchain.MoonriverTestnet -> "moonriver/test"
|
||||
Blockchain.Mantle -> "mantle"
|
||||
Blockchain.MantleTestnet -> "mantle/test"
|
||||
Blockchain.Flare -> "flare-network"
|
||||
Blockchain.FlareTestnet -> "flare-network/test"
|
||||
Blockchain.Taraxa -> "taraxa"
|
||||
Blockchain.TaraxaTestnet -> "taraxa/test"
|
||||
Blockchain.Base -> "base"
|
||||
Blockchain.BaseTestnet -> "base/test"
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("ComplexMethod", "LongMethod")
|
||||
fun Blockchain.toCoinId(): String {
|
||||
return when (this) {
|
||||
Blockchain.Binance, Blockchain.BinanceTestnet, Blockchain.BSC, Blockchain.BSCTestnet -> "binancecoin"
|
||||
Blockchain.Bitcoin, Blockchain.BitcoinTestnet -> "bitcoin"
|
||||
Blockchain.BitcoinCash, Blockchain.BitcoinCashTestnet -> "bitcoin-cash"
|
||||
Blockchain.Ethereum, Blockchain.EthereumTestnet -> "ethereum"
|
||||
Blockchain.EthereumClassic, Blockchain.EthereumClassicTestnet -> "ethereum-classic"
|
||||
Blockchain.Stellar, Blockchain.StellarTestnet -> "stellar"
|
||||
Blockchain.Cardano -> "cardano"
|
||||
Blockchain.Polygon, Blockchain.PolygonTestnet -> "matic-network"
|
||||
Blockchain.Arbitrum, Blockchain.ArbitrumTestnet -> "arbitrum-one"
|
||||
Blockchain.Avalanche, Blockchain.AvalancheTestnet -> "avalanche-2"
|
||||
Blockchain.Solana, Blockchain.SolanaTestnet -> "solana"
|
||||
Blockchain.Fantom, Blockchain.FantomTestnet -> "fantom"
|
||||
Blockchain.Tron, Blockchain.TronTestnet -> "tron"
|
||||
Blockchain.Polkadot, Blockchain.PolkadotTestnet -> "polkadot"
|
||||
Blockchain.Ducatus -> "ducatus"
|
||||
Blockchain.Litecoin -> "litecoin"
|
||||
Blockchain.RSK -> "rootstock"
|
||||
Blockchain.Tezos -> "tezos"
|
||||
Blockchain.XRP -> "ripple"
|
||||
Blockchain.Dogecoin -> "dogecoin"
|
||||
Blockchain.Gnosis -> "xdai"
|
||||
Blockchain.EthereumPow, Blockchain.EthereumPowTestnet -> "ethereum-pow-iou"
|
||||
Blockchain.Dischain -> "ethereumfair" // for backend compatibility
|
||||
Blockchain.Kusama -> "kusama"
|
||||
Blockchain.Optimism, Blockchain.OptimismTestnet -> "optimistic-ethereum"
|
||||
Blockchain.Dash -> "dash"
|
||||
Blockchain.Kaspa -> "kaspa"
|
||||
Blockchain.TON, Blockchain.TONTestnet -> "the-open-network"
|
||||
Blockchain.Kava, Blockchain.KavaTestnet -> "kava"
|
||||
Blockchain.Ravencoin, Blockchain.RavencoinTestnet -> "ravencoin"
|
||||
Blockchain.Cosmos, Blockchain.CosmosTestnet -> "cosmos"
|
||||
Blockchain.TerraV1 -> "terra-luna"
|
||||
Blockchain.TerraV2 -> "terra-luna-2"
|
||||
Blockchain.Cronos -> "crypto-com-chain"
|
||||
Blockchain.Telos, Blockchain.TelosTestnet -> "telos"
|
||||
Blockchain.AlephZero, Blockchain.AlephZeroTestnet -> "aleph-zero"
|
||||
Blockchain.OctaSpace, Blockchain.OctaSpaceTestnet -> "octaspace"
|
||||
Blockchain.Chia, Blockchain.ChiaTestnet -> "chia"
|
||||
Blockchain.Near -> "near"
|
||||
Blockchain.NearTestnet -> "near/test"
|
||||
Blockchain.Decimal, Blockchain.DecimalTestnet -> "decimal"
|
||||
Blockchain.XDC, Blockchain.XDCTestnet -> "xdce-crowd-sale"
|
||||
Blockchain.VeChain, Blockchain.VeChainTestnet -> "vechain"
|
||||
Blockchain.Aptos -> "aptos"
|
||||
Blockchain.AptosTestnet -> "aptos/test"
|
||||
Blockchain.Playa3ull -> "playa3ull-games-2"
|
||||
Blockchain.Shibarium -> "bone-shibaswap"
|
||||
Blockchain.ShibariumTestnet -> "bone-shibaswap/test"
|
||||
Blockchain.Algorand -> "algorand"
|
||||
Blockchain.AlgorandTestnet -> "algorand/test"
|
||||
Blockchain.Unknown -> "unknown"
|
||||
Blockchain.Hedera -> "hedera-hashgraph"
|
||||
Blockchain.HederaTestnet -> "hedera-hashgraph/test"
|
||||
Blockchain.Aurora, Blockchain.AuroraTestnet -> "aurora-ethereum"
|
||||
Blockchain.Areon, Blockchain.AreonTestnet -> "areon-network"
|
||||
Blockchain.PulseChain, Blockchain.PulseChainTestnet -> "pulsechain"
|
||||
Blockchain.ZkSyncEra, Blockchain.ZkSyncEraTestnet -> "zksync-ethereum"
|
||||
Blockchain.Moonbeam, Blockchain.MoonbeamTestnet -> "moonbeam"
|
||||
Blockchain.Manta, Blockchain.MantaTestnet -> "manta-network-ethereum"
|
||||
Blockchain.PolygonZkEVM, Blockchain.PolygonZkEVMTestnet -> "polygon-zkevm-ethereum"
|
||||
Blockchain.Nexa, Blockchain.NexaTestnet -> "nexa" // FIXME
|
||||
Blockchain.Radiant -> "radiant"
|
||||
Blockchain.Moonriver, Blockchain.MoonriverTestnet -> "moonriver"
|
||||
Blockchain.Mantle, Blockchain.MantleTestnet -> "mantle"
|
||||
Blockchain.Flare, Blockchain.FlareTestnet -> "flare-networks"
|
||||
Blockchain.Taraxa, Blockchain.TaraxaTestnet -> "taraxa"
|
||||
Blockchain.Base, Blockchain.BaseTestnet -> "base-ethereum"
|
||||
}
|
||||
}
|
||||
|
||||
fun Blockchain.isSupportedInApp(): Boolean {
|
||||
return !excludedBlockchains.contains(this)
|
||||
}
|
||||
|
||||
fun Blockchain.amountToCreateAccount(token: Token? = null): BigDecimal? {
|
||||
return when (this) {
|
||||
Blockchain.Stellar -> if (token?.symbol == NODL) BigDecimal(NODL_AMOUNT_TO_CREATE_ACCOUNT) else BigDecimal.ONE
|
||||
Blockchain.XRP -> BigDecimal.TEN
|
||||
Blockchain.Near, Blockchain.NearTestnet -> 0.00182.toBigDecimal()
|
||||
Blockchain.Aptos, Blockchain.AptosTestnet -> BigDecimal.ZERO
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun Blockchain.minimalAmount(): BigDecimal {
|
||||
return BigDecimal.ONE.movePointLeft(decimals())
|
||||
}
|
||||
|
||||
private const val NODL = "NODL"
|
||||
private const val NODL_AMOUNT_TO_CREATE_ACCOUNT = 1.5
|
||||
|
||||
private val excludedBlockchains = listOf(
|
||||
Blockchain.Unknown,
|
||||
Blockchain.Nexa,
|
||||
Blockchain.NexaTestnet,
|
||||
Blockchain.Radiant,
|
||||
Blockchain.Manta,
|
||||
Blockchain.MantaTestnet,
|
||||
Blockchain.Mantle,
|
||||
Blockchain.MantleTestnet,
|
||||
)
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.domain.common.extensions
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.isSupportedInApp
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
|
|
@ -22,7 +23,7 @@ fun CardDTO.supportedBlockchains(cardTypesResolver: CardTypesResolver): List<Blo
|
|||
wallets.flatMap { Blockchain.fromCurve(it.curve) }.distinct().toMutableList()
|
||||
} else {
|
||||
// multiwallet supports all blockchains, move this logic to config
|
||||
Blockchain.values().toMutableList()
|
||||
Blockchain.entries.toMutableList()
|
||||
}
|
||||
return supportedBlockchains
|
||||
.filter { isTestCard == it.isTestnet() }
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import com.tangem.domain.tokens.model.CryptoCurrency
|
|||
* Manager that holds info about available actions as Sell and Buy
|
||||
*/
|
||||
interface RampStateManager {
|
||||
|
||||
fun availableForBuy(cryptoCurrency: CryptoCurrency): Boolean
|
||||
|
||||
fun availableForSell(cryptoCurrency: CryptoCurrency): Boolean
|
||||
}
|
||||
|
|
@ -2,11 +2,11 @@ package com.tangem.domain.redux.global
|
|||
|
||||
import android.webkit.ValueCallback
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.datasource.api.common.AuthProvider
|
||||
import com.tangem.datasource.utils.RequestHeader
|
||||
import com.tangem.domain.redux.BaseStoreHub
|
||||
import com.tangem.domain.redux.DomainState
|
||||
import com.tangem.domain.redux.ReStoreReducer
|
||||
import com.tangem.lib.auth.AuthProvider
|
||||
import org.rekotlin.Action
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
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()
|
||||
}
|
||||
}
|
||||
|
|
@ -10,26 +10,24 @@ import com.tangem.blockchain.common.*
|
|||
import com.tangem.blockchain.common.address.Address
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.blockchain.common.address.EstimationFeeAddressFactory
|
||||
import com.tangem.blockchain.common.datastorage.BlockchainDataStorage
|
||||
import com.tangem.blockchain.common.logging.BlockchainSDKLogger
|
||||
import com.tangem.blockchain.common.pagination.Page
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.blockchain.common.trustlines.AssetRequirementsManager
|
||||
import com.tangem.blockchain.common.txhistory.TransactionHistoryRequest
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
import com.tangem.crypto.bip39.Mnemonic
|
||||
import com.tangem.blockchainsdk.BlockchainSDKFactory
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.datasource.asset.AssetReader
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.datasource.asset.reader.AssetReader
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.datasource.local.walletmanager.WalletManagersStore
|
||||
import com.tangem.domain.common.util.hasDerivation
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
|
||||
import com.tangem.domain.transaction.models.AssetRequirementsCondition
|
||||
import com.tangem.domain.txhistory.models.PaginationWrapper
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
import com.tangem.domain.txhistory.models.TxHistoryState
|
||||
|
|
@ -49,31 +47,21 @@ import java.util.EnumSet
|
|||
class DefaultWalletManagersFacade(
|
||||
private val walletManagersStore: WalletManagersStore,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
mnemonic: Mnemonic,
|
||||
assetReader: AssetReader,
|
||||
moshi: Moshi,
|
||||
configManager: ConfigManager,
|
||||
blockchainDataStorage: BlockchainDataStorage,
|
||||
accountCreator: AccountCreator,
|
||||
blockchainSDKLogger: BlockchainSDKLogger,
|
||||
feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles,
|
||||
blockchainSDKFactory: BlockchainSDKFactory,
|
||||
) : WalletManagersFacade {
|
||||
|
||||
private val demoConfig by lazy { DemoConfig() }
|
||||
private val resultFactory by lazy { UpdateWalletManagerResultFactory() }
|
||||
private val walletManagerFactory by lazy {
|
||||
WalletManagerFactory(
|
||||
configManager = configManager,
|
||||
accountCreator = accountCreator,
|
||||
blockchainDataStorage = blockchainDataStorage,
|
||||
blockchainSDKLogger = if (feedbackManagerFeatureToggles.isLocalLogsEnabled) blockchainSDKLogger else null,
|
||||
)
|
||||
}
|
||||
private val walletManagerFactory by lazy { WalletManagerFactory(blockchainSDKFactory) }
|
||||
private val sdkTokenConverter by lazy { SdkTokenConverter() }
|
||||
private val txHistoryStateConverter by lazy { SdkTransactionHistoryStateConverter() }
|
||||
private val txHistoryItemConverter by lazy { SdkTransactionHistoryItemConverter(assetReader, moshi) }
|
||||
private val sdkPageConverter by lazy { SdkPageConverter() }
|
||||
private val estimationFeeAddressFactory by lazy { EstimationFeeAddressFactory(mnemonic) }
|
||||
private val cryptoCurrencyTypeConverter by lazy { CryptoCurrencyTypeConverter() }
|
||||
private val requirementsConditionConverter by lazy { SdkRequirementsConditionConverter() }
|
||||
private val estimationFeeAddressFactory by lazy { EstimationFeeAddressFactory() }
|
||||
|
||||
override suspend fun update(
|
||||
userWalletId: UserWalletId,
|
||||
|
|
@ -192,7 +180,16 @@ class DefaultWalletManagersFacade(
|
|||
address = walletManager.wallet.address,
|
||||
filterType = when (currency) {
|
||||
is CryptoCurrency.Coin -> TransactionHistoryRequest.FilterType.Coin
|
||||
is CryptoCurrency.Token -> TransactionHistoryRequest.FilterType.Contract(currency.contractAddress)
|
||||
is CryptoCurrency.Token -> {
|
||||
val blockchainToken = Token(
|
||||
name = currency.name,
|
||||
symbol = currency.symbol,
|
||||
contractAddress = currency.contractAddress,
|
||||
decimals = currency.decimals,
|
||||
id = currency.id.rawCurrencyId,
|
||||
)
|
||||
TransactionHistoryRequest.FilterType.Contract(blockchainToken)
|
||||
}
|
||||
},
|
||||
)
|
||||
.let(txHistoryStateConverter::convert)
|
||||
|
|
@ -221,7 +218,16 @@ class DefaultWalletManagersFacade(
|
|||
pageSize = pageSize,
|
||||
filterType = when (currency) {
|
||||
is CryptoCurrency.Coin -> TransactionHistoryRequest.FilterType.Coin
|
||||
is CryptoCurrency.Token -> TransactionHistoryRequest.FilterType.Contract(currency.contractAddress)
|
||||
is CryptoCurrency.Token -> {
|
||||
val blockchainToken = Token(
|
||||
name = currency.name,
|
||||
symbol = currency.symbol,
|
||||
contractAddress = currency.contractAddress,
|
||||
decimals = currency.decimals,
|
||||
id = currency.id.rawCurrencyId,
|
||||
)
|
||||
TransactionHistoryRequest.FilterType.Contract(blockchainToken)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
|
|
@ -573,6 +579,34 @@ class DefaultWalletManagersFacade(
|
|||
)
|
||||
}
|
||||
|
||||
override suspend fun getAssetRequirements(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
): AssetRequirementsCondition? {
|
||||
val walletManager = getOrCreateWalletManager(userWalletId = userWalletId, network = currency.network)
|
||||
val currencyType = cryptoCurrencyTypeConverter.convert(currency)
|
||||
if (walletManager !is AssetRequirementsManager || !walletManager.hasRequirements(currencyType)) return null
|
||||
|
||||
val condition = walletManager.requirementsCondition(currencyType) ?: return null
|
||||
return requirementsConditionConverter.convert(condition)
|
||||
}
|
||||
|
||||
override suspend fun associateAsset(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
signer: CommonSigner,
|
||||
): SimpleResult {
|
||||
val walletManager = getOrCreateWalletManager(userWalletId = userWalletId, network = currency.network)
|
||||
val currencyType = cryptoCurrencyTypeConverter.convert(currency)
|
||||
|
||||
if (walletManager !is AssetRequirementsManager) {
|
||||
return SimpleResult.Failure(
|
||||
BlockchainSdkError.CustomError("WalletManager is not implemented AssetRequirementsManager"),
|
||||
)
|
||||
}
|
||||
return walletManager.fulfillRequirements(currencyType, signer)
|
||||
}
|
||||
|
||||
private fun updateWalletManagerTokensIfNeeded(walletManager: WalletManager, tokens: Set<CryptoCurrency.Token>) {
|
||||
if (tokens.isEmpty()) return
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import com.tangem.blockchain.extensions.SimpleResult
|
|||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
|
||||
import com.tangem.domain.transaction.models.AssetRequirementsCondition
|
||||
import com.tangem.domain.txhistory.models.PaginationWrapper
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
import com.tangem.domain.txhistory.models.TxHistoryState
|
||||
|
|
@ -240,4 +241,16 @@ interface WalletManagersFacade {
|
|||
decimals: Int,
|
||||
id: String? = null,
|
||||
): BigDecimal
|
||||
|
||||
/**
|
||||
* Get requirements for asset(currency)
|
||||
* @return null if there's no requirement, otherwise [AssetRequirementsCondition].
|
||||
*/
|
||||
suspend fun getAssetRequirements(userWalletId: UserWalletId, currency: CryptoCurrency): AssetRequirementsCondition?
|
||||
|
||||
suspend fun associateAsset(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
signer: CommonSigner,
|
||||
): SimpleResult
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.domain.walletmanager.utils
|
||||
|
||||
import com.tangem.blockchain.common.CryptoCurrencyType
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class CryptoCurrencyTypeConverter : Converter<CryptoCurrency, CryptoCurrencyType> {
|
||||
override fun convert(value: CryptoCurrency): CryptoCurrencyType {
|
||||
return when (value) {
|
||||
is CryptoCurrency.Coin -> CryptoCurrencyType.Coin
|
||||
is CryptoCurrency.Token -> CryptoCurrencyType.Token(
|
||||
info = Token(
|
||||
name = value.name,
|
||||
symbol = value.symbol,
|
||||
contractAddress = value.contractAddress,
|
||||
decimals = value.decimals,
|
||||
id = value.id.rawCurrencyId,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.domain.walletmanager.utils
|
||||
|
||||
import com.tangem.domain.transaction.models.AssetRequirementsCondition
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.blockchain.common.trustlines.AssetRequirementsCondition as SdkRequirementsCondition
|
||||
|
||||
internal class SdkRequirementsConditionConverter : Converter<SdkRequirementsCondition, AssetRequirementsCondition> {
|
||||
override fun convert(value: SdkRequirementsCondition): AssetRequirementsCondition {
|
||||
return when (value) {
|
||||
SdkRequirementsCondition.PaidTransaction -> AssetRequirementsCondition.PaidTransaction
|
||||
is SdkRequirementsCondition.PaidTransactionWithFee -> AssetRequirementsCondition.PaidTransactionWithFee(
|
||||
feeAmount = requireNotNull(value.feeAmount.value),
|
||||
feeCurrencySymbol = value.feeAmount.currencySymbol,
|
||||
decimals = value.feeAmount.decimals,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ package com.tangem.domain.walletmanager.utils
|
|||
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.blockchain.common.txhistory.TransactionHistoryItem
|
||||
import com.tangem.datasource.asset.AssetReader
|
||||
import com.tangem.datasource.asset.reader.AssetReader
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.blockchain.common.txhistory.TransactionHistoryItem as SdkTransactionHistoryItem
|
||||
|
|
@ -58,7 +58,9 @@ internal class SdkTransactionHistoryItemConverter(
|
|||
} else {
|
||||
mapToInteractionAddressType(sourceType = sourceType)
|
||||
}
|
||||
is SdkTransactionHistoryItem.TransactionType.ContractMethod -> mapToInteractionAddressType(destinationType)
|
||||
is SdkTransactionHistoryItem.TransactionType.ContractMethod,
|
||||
is SdkTransactionHistoryItem.TransactionType.ContractMethodName,
|
||||
-> mapToInteractionAddressType(destinationType)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ import com.squareup.moshi.JsonAdapter
|
|||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import com.tangem.blockchain.common.txhistory.TransactionHistoryItem
|
||||
import com.tangem.datasource.asset.AssetReader
|
||||
import com.tangem.datasource.asset.reader.AssetReader
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
import com.tangem.domain.walletmanager.model.SmartContractMethod
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
class SdkTransactionTypeConverter(
|
||||
internal class SdkTransactionTypeConverter(
|
||||
private val assetReader: AssetReader,
|
||||
private val moshi: Moshi,
|
||||
) : Converter<TransactionHistoryItem.TransactionType, TxHistoryItem.TransactionType> {
|
||||
|
|
@ -27,16 +27,20 @@ class SdkTransactionTypeConverter(
|
|||
|
||||
override fun convert(value: TransactionHistoryItem.TransactionType): TxHistoryItem.TransactionType {
|
||||
return when (value) {
|
||||
TransactionHistoryItem.TransactionType.Transfer -> TxHistoryItem.TransactionType.Transfer
|
||||
is TransactionHistoryItem.TransactionType.ContractMethod -> {
|
||||
return when (val name = smartContractMethods[value.id]?.name) {
|
||||
"transfer" -> TxHistoryItem.TransactionType.Transfer
|
||||
"approve" -> TxHistoryItem.TransactionType.Approve
|
||||
"swap" -> TxHistoryItem.TransactionType.Swap
|
||||
null -> TxHistoryItem.TransactionType.UnknownOperation
|
||||
else -> TxHistoryItem.TransactionType.Operation(name = name.replaceFirstChar { it.titlecase() })
|
||||
}
|
||||
}
|
||||
is TransactionHistoryItem.TransactionType.ContractMethod ->
|
||||
getTransactionType(methodName = smartContractMethods[value.id]?.name)
|
||||
is TransactionHistoryItem.TransactionType.ContractMethodName -> getTransactionType(methodName = value.name)
|
||||
is TransactionHistoryItem.TransactionType.Transfer -> TxHistoryItem.TransactionType.Transfer
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTransactionType(methodName: String?): TxHistoryItem.TransactionType {
|
||||
return when (methodName) {
|
||||
"transfer" -> TxHistoryItem.TransactionType.Transfer
|
||||
"approve" -> TxHistoryItem.TransactionType.Approve
|
||||
"swap" -> TxHistoryItem.TransactionType.Swap
|
||||
null -> TxHistoryItem.TransactionType.UnknownOperation
|
||||
else -> TxHistoryItem.TransactionType.Operation(name = methodName.replaceFirstChar { it.titlecase() })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.domain.walletmanager.utils
|
||||
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.domain.common.extensions.amountToCreateAccount
|
||||
import com.tangem.blockchainsdk.utils.amountToCreateAccount
|
||||
import com.tangem.domain.walletmanager.model.Address
|
||||
import com.tangem.domain.walletmanager.model.CryptoCurrencyAmount
|
||||
import com.tangem.domain.walletmanager.model.CryptoCurrencyTransaction
|
||||
|
|
|
|||
|
|
@ -1,37 +1,21 @@
|
|||
package com.tangem.domain.walletmanager.utils
|
||||
|
||||
import com.tangem.blockchain.common.AccountCreator
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.DerivationParams
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.datastorage.BlockchainDataStorage
|
||||
import com.tangem.blockchain.common.logging.BlockchainSDKLogger
|
||||
import com.tangem.blockchainsdk.BlockchainSDKFactory
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.domain.common.DerivationStyleProvider
|
||||
import com.tangem.domain.common.extensions.makeWalletManagerForApp
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import timber.log.Timber
|
||||
import com.tangem.blockchain.common.WalletManagerFactory as BlockchainWalletManagerFactory
|
||||
|
||||
internal class WalletManagerFactory(
|
||||
configManager: ConfigManager,
|
||||
accountCreator: AccountCreator,
|
||||
blockchainDataStorage: BlockchainDataStorage,
|
||||
blockchainSDKLogger: BlockchainSDKLogger? = null,
|
||||
private val blockchainSDKFactory: BlockchainSDKFactory,
|
||||
) {
|
||||
|
||||
private val sdkWalletManagerFactory by lazy {
|
||||
BlockchainWalletManagerFactory(
|
||||
config = configManager.config.blockchainSdkConfig,
|
||||
accountCreator = accountCreator,
|
||||
blockchainDataStorage = blockchainDataStorage,
|
||||
loggers = listOfNotNull(blockchainSDKLogger),
|
||||
)
|
||||
}
|
||||
|
||||
fun createWalletManager(
|
||||
suspend fun createWalletManager(
|
||||
scanResponse: ScanResponse,
|
||||
blockchain: Blockchain,
|
||||
derivationPath: DerivationPath?,
|
||||
|
|
@ -39,7 +23,7 @@ internal class WalletManagerFactory(
|
|||
val derivationParams = getDerivationParams(derivationPath, scanResponse.derivationStyleProvider)
|
||||
|
||||
return try {
|
||||
sdkWalletManagerFactory.makeWalletManagerForApp(
|
||||
blockchainSDKFactory.getWalletManagerFactorySync()?.makeWalletManagerForApp(
|
||||
scanResponse = scanResponse,
|
||||
blockchain = blockchain,
|
||||
derivationParams = derivationParams,
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@ package com.tangem.domain.features
|
|||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import org.junit.Test
|
||||
|
||||
class BlockchainTests {
|
||||
@Test
|
||||
fun allNetworkIdsAreImplemented() {
|
||||
val unimplementedIds = Blockchain.values()
|
||||
val unimplementedIds = Blockchain.entries
|
||||
.toMutableList()
|
||||
.apply {
|
||||
remove(Blockchain.Unknown)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.domain.settings
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
|
||||
class IncrementAppLaunchCounterUseCase(
|
||||
private val settingsRepository: SettingsRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(): Either<Throwable, Unit> {
|
||||
return Either.catch { settingsRepository.incrementAppLaunchCounter() }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.domain.settings
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
|
||||
class SetSaveWalletScreenShownUseCase(
|
||||
private val settingsRepository: SettingsRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(): Either<Throwable, Unit> {
|
||||
return Either.catch {
|
||||
settingsRepository.setShouldShowSaveUserWalletScreen(value = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,8 @@ interface SettingsRepository {
|
|||
|
||||
suspend fun shouldShowSaveUserWalletScreen(): Boolean
|
||||
|
||||
suspend fun setShouldShowSaveUserWalletScreen(value: Boolean)
|
||||
|
||||
suspend fun isWalletScrollPreviewEnabled(): Boolean
|
||||
|
||||
suspend fun setWalletScrollPreviewAvailability(isEnabled: Boolean)
|
||||
|
|
@ -17,4 +19,18 @@ interface SettingsRepository {
|
|||
suspend fun isSendTapHelpPreviewEnabled(): Boolean
|
||||
|
||||
suspend fun setSendTapHelpPreviewAvailability(isEnabled: Boolean)
|
||||
|
||||
suspend fun wasApplicationStopped(): Boolean
|
||||
|
||||
suspend fun setWasApplicationStopped(value: Boolean)
|
||||
|
||||
suspend fun shouldOpenWelcomeScreenOnResume(): Boolean
|
||||
|
||||
suspend fun setShouldOpenWelcomeScreenOnResume(value: Boolean)
|
||||
|
||||
suspend fun shouldSaveAccessCodes(): Boolean
|
||||
|
||||
suspend fun setShouldSaveAccessCodes(value: Boolean)
|
||||
|
||||
suspend fun incrementAppLaunchCounter()
|
||||
}
|
||||
|
|
@ -11,11 +11,13 @@ android {
|
|||
dependencies {
|
||||
|
||||
/** Project - Domain */
|
||||
implementation(projects.domain.core)
|
||||
api(projects.domain.core)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.txhistory.models)
|
||||
implementation(projects.domain.transaction.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.appCurrency.models)
|
||||
implementation(projects.domain.settings)
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import java.math.BigDecimal
|
|||
*/
|
||||
data class CryptoCurrencyStatus(
|
||||
val currency: CryptoCurrency,
|
||||
val value: Status,
|
||||
val value: Value,
|
||||
) {
|
||||
|
||||
/**
|
||||
|
|
@ -23,7 +23,7 @@ data class CryptoCurrencyStatus(
|
|||
*
|
||||
* @property isError Indicates whether this status represents an error status.
|
||||
*/
|
||||
sealed class Status(val isError: Boolean) {
|
||||
sealed class Value(val isError: Boolean) {
|
||||
|
||||
/** The amount of the cryptocurrency. */
|
||||
open val amount: BigDecimal? = null
|
||||
|
|
@ -48,7 +48,7 @@ data class CryptoCurrencyStatus(
|
|||
}
|
||||
|
||||
/** Represents the Loading state of a cryptocurrency, typically while fetching its details. */
|
||||
object Loading : Status(isError = false)
|
||||
data object Loading : Value(isError = false)
|
||||
|
||||
/**
|
||||
* Represents a state where the cryptocurrency is not reachable.
|
||||
|
|
@ -61,19 +61,19 @@ data class CryptoCurrencyStatus(
|
|||
override val priceChange: BigDecimal?,
|
||||
override val fiatRate: BigDecimal?,
|
||||
override val networkAddress: NetworkAddress?,
|
||||
) : Status(isError = true)
|
||||
) : Value(isError = true)
|
||||
|
||||
/** Represents a state where the cryptocurrency's network amount not found. */
|
||||
data class NoAmount(
|
||||
override val priceChange: BigDecimal?,
|
||||
override val fiatRate: BigDecimal?,
|
||||
) : Status(isError = true)
|
||||
) : Value(isError = true)
|
||||
|
||||
/** Represents a state where the cryptocurrency's derivation is missed. */
|
||||
data class MissedDerivation(
|
||||
override val priceChange: BigDecimal?,
|
||||
override val fiatRate: BigDecimal?,
|
||||
) : Status(isError = true)
|
||||
) : Value(isError = true)
|
||||
|
||||
/**
|
||||
* Represents a state where there is no account associated with the cryptocurrency
|
||||
|
|
@ -86,7 +86,7 @@ data class CryptoCurrencyStatus(
|
|||
override val priceChange: BigDecimal?,
|
||||
override val fiatRate: BigDecimal?,
|
||||
override val networkAddress: NetworkAddress,
|
||||
) : Status(isError = false) {
|
||||
) : Value(isError = false) {
|
||||
|
||||
override val amount: BigDecimal = BigDecimal.ZERO
|
||||
}
|
||||
|
|
@ -110,7 +110,7 @@ data class CryptoCurrencyStatus(
|
|||
override val hasCurrentNetworkTransactions: Boolean,
|
||||
override val pendingTransactions: Set<TxHistoryItem>,
|
||||
override val networkAddress: NetworkAddress,
|
||||
) : Status(isError = false)
|
||||
) : Value(isError = false)
|
||||
|
||||
/**
|
||||
* Represents a Custom state of a cryptocurrency, typically used for user-defined tokens.
|
||||
|
|
@ -131,7 +131,7 @@ data class CryptoCurrencyStatus(
|
|||
override val hasCurrentNetworkTransactions: Boolean,
|
||||
override val pendingTransactions: Set<TxHistoryItem>,
|
||||
override val networkAddress: NetworkAddress,
|
||||
) : Status(isError = false)
|
||||
) : Value(isError = false)
|
||||
|
||||
/**
|
||||
* Represents a state where the cryptocurrency is available, but there is no current quote available for it.
|
||||
|
|
@ -146,5 +146,5 @@ data class CryptoCurrencyStatus(
|
|||
override val hasCurrentNetworkTransactions: Boolean,
|
||||
override val pendingTransactions: Set<TxHistoryItem>,
|
||||
override val networkAddress: NetworkAddress,
|
||||
) : Status(isError = false)
|
||||
) : Value(isError = false)
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ import java.math.BigDecimal
|
|||
*/
|
||||
data class NetworkStatus(
|
||||
val network: Network,
|
||||
val value: Status,
|
||||
val value: Value,
|
||||
) {
|
||||
|
||||
/**
|
||||
|
|
@ -19,19 +19,19 @@ data class NetworkStatus(
|
|||
*
|
||||
* This sealed class includes different states like unreachable, missed derivation, verified, and no account.
|
||||
*/
|
||||
sealed class Status
|
||||
sealed class Value
|
||||
|
||||
/**
|
||||
* Represents the state where the network is unreachable.
|
||||
*
|
||||
* @property address Network addresses.
|
||||
*/
|
||||
data class Unreachable(val address: NetworkAddress?) : Status()
|
||||
data class Unreachable(val address: NetworkAddress?) : Value()
|
||||
|
||||
/**
|
||||
* Represents the state where a derivation has been missed.
|
||||
*/
|
||||
object MissedDerivation : Status()
|
||||
data object MissedDerivation : Value()
|
||||
|
||||
/**
|
||||
* Represents the verified state of the network, including the amounts associated with different cryptocurrencies
|
||||
|
|
@ -46,7 +46,7 @@ data class NetworkStatus(
|
|||
val address: NetworkAddress,
|
||||
val amounts: Map<CryptoCurrency.ID, CryptoCurrencyAmountStatus>,
|
||||
val pendingTransactions: Map<CryptoCurrency.ID, Set<TxHistoryItem>>,
|
||||
) : Status()
|
||||
) : Value()
|
||||
|
||||
/**
|
||||
* Represents the state where there is no account, and an amount is required to create one.
|
||||
|
|
@ -59,5 +59,5 @@ data class NetworkStatus(
|
|||
val address: NetworkAddress,
|
||||
val amountToCreateAccount: BigDecimal,
|
||||
val errorMessage: String,
|
||||
) : Status()
|
||||
) : Value()
|
||||
}
|
||||
|
|
@ -31,7 +31,7 @@ sealed class CryptoCurrencyWarning {
|
|||
val amountCurrency: CryptoCurrency,
|
||||
) : CryptoCurrencyWarning()
|
||||
|
||||
object TopUpWithoutReserve : CryptoCurrencyWarning()
|
||||
data object TopUpWithoutReserve : CryptoCurrencyWarning()
|
||||
|
||||
/**
|
||||
* Represents wallet blockchain rent
|
||||
|
|
@ -41,8 +41,6 @@ sealed class CryptoCurrencyWarning {
|
|||
*/
|
||||
data class Rent(val rent: BigDecimal, val exemptionAmount: BigDecimal) : CryptoCurrencyWarning()
|
||||
|
||||
data class HasPendingTransactions(val blockchainSymbol: String) : CryptoCurrencyWarning()
|
||||
|
||||
data class SwapPromo(
|
||||
val startDateTime: DateTime,
|
||||
val endDateTime: DateTime,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.domain.tokens.model.warnings
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import java.math.BigDecimal
|
||||
|
||||
sealed class HederaWarnings : CryptoCurrencyWarning() {
|
||||
|
||||
abstract val currency: CryptoCurrency
|
||||
|
||||
data class AssociateWarning(override val currency: CryptoCurrency) : HederaWarnings()
|
||||
|
||||
data class AssociateWarningWithFee(
|
||||
override val currency: CryptoCurrency,
|
||||
val fee: BigDecimal,
|
||||
val feeCurrencySymbol: String,
|
||||
val feeCurrencyDecimals: Int,
|
||||
) : HederaWarnings()
|
||||
}
|
||||
|
|
@ -72,4 +72,9 @@ sealed class TokenScreenAnalyticsEvent(
|
|||
event = "Token Bought",
|
||||
params = mapOf("Token" to token),
|
||||
)
|
||||
|
||||
class Associate(tokenSymbol: String, blockchain: String) : TokenScreenAnalyticsEvent(
|
||||
event = "Button - Token Trustline",
|
||||
params = mapOf("Token" to tokenSymbol, "Blockchain" to blockchain),
|
||||
)
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.domain.tokens
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import com.tangem.domain.core.utils.EitherFlow
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.tokens.error.mapper.mapToTokenListError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
|
|
@ -12,19 +12,19 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository
|
|||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
|
||||
class GetCardTokensListUseCase(
|
||||
internal val currenciesRepository: CurrenciesRepository,
|
||||
internal val quotesRepository: QuotesRepository,
|
||||
internal val networksRepository: NetworksRepository,
|
||||
internal val dispatchers: CoroutineDispatcherProvider,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
) {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
operator fun invoke(userWalletId: UserWalletId): Flow<Either<TokenListError, TokenList>> {
|
||||
operator fun invoke(userWalletId: UserWalletId): EitherFlow<TokenListError, TokenList> {
|
||||
return getTokensStatuses(userWalletId).transformLatest { maybeTokens ->
|
||||
maybeTokens.fold(
|
||||
ifLeft = { error ->
|
||||
|
|
@ -37,9 +37,7 @@ class GetCardTokensListUseCase(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getTokensStatuses(
|
||||
userWalletId: UserWalletId,
|
||||
): Flow<Either<TokenListError, List<CryptoCurrencyStatus>>> {
|
||||
private fun getTokensStatuses(userWalletId: UserWalletId): EitherFlow<TokenListError, List<CryptoCurrencyStatus>> {
|
||||
val operations = CurrenciesStatusesOperations(
|
||||
userWalletId = userWalletId,
|
||||
currenciesRepository = currenciesRepository,
|
||||
|
|
@ -56,7 +54,7 @@ class GetCardTokensListUseCase(
|
|||
private fun createTokenList(
|
||||
userWalletId: UserWalletId,
|
||||
tokens: List<CryptoCurrencyStatus>,
|
||||
): Flow<Either<TokenListError, TokenList>> {
|
||||
): EitherFlow<TokenListError, TokenList> {
|
||||
val operations = TokenListOperations(
|
||||
userWalletId = userWalletId,
|
||||
tokens = tokens,
|
||||
|
|
|
|||
|
|
@ -8,14 +8,12 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository
|
|||
import com.tangem.domain.tokens.repository.MarketCryptoCurrencyRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.isNullOrZero
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Use case to determine which TokenActions are available for a [CryptoCurrency]
|
||||
|
|
@ -25,16 +23,19 @@ import java.math.BigDecimal
|
|||
@Suppress("LongParameterList")
|
||||
class GetCryptoCurrencyActionsUseCase(
|
||||
private val rampManager: RampStateManager,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
private val sendFeatureToggles: SendFeatureToggles,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
operator fun invoke(userWallet: UserWallet, cryptoCurrencyStatus: CryptoCurrencyStatus): Flow<TokenActionsState> {
|
||||
suspend operator fun invoke(
|
||||
userWallet: UserWallet,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
): Flow<TokenActionsState> {
|
||||
val operations = CurrenciesStatusesOperations(
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
|
|
@ -42,7 +43,7 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
userWalletId = userWallet.walletId,
|
||||
)
|
||||
val networkId = cryptoCurrencyStatus.currency.network.id
|
||||
|
||||
val requirements = walletManagersFacade.getAssetRequirements(userWallet.walletId, cryptoCurrencyStatus.currency)
|
||||
return flow {
|
||||
val networkFlow = if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
|
||||
operations.getNetworkCoinForSingleWalletWithTokenFlow(networkId)
|
||||
|
|
@ -57,6 +58,7 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
userWallet = userWallet,
|
||||
coinStatus = maybeCoinStatus.getOrNull(),
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
needAssociateAsset = requirements != null,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -68,33 +70,37 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
userWallet: UserWallet,
|
||||
coinStatus: CryptoCurrencyStatus?,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
needAssociateAsset: Boolean,
|
||||
): TokenActionsState {
|
||||
return TokenActionsState(
|
||||
walletId = userWallet.walletId,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
states = createListOfActions(
|
||||
userWallet,
|
||||
coinStatus,
|
||||
cryptoCurrencyStatus,
|
||||
userWallet = userWallet,
|
||||
coinStatus = coinStatus,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
needAssociateAsset = needAssociateAsset,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates list of action for expected order
|
||||
* Actions priority: [Buy Send Receive Sell Swap]
|
||||
* Actions priority: [Receive Send Swap Buy Sell]
|
||||
*/
|
||||
@Suppress("CyclomaticComplexMethod", "LongMethod")
|
||||
private suspend fun createListOfActions(
|
||||
userWallet: UserWallet,
|
||||
coinStatus: CryptoCurrencyStatus?,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
needAssociateAsset: Boolean,
|
||||
): List<TokenActionsState.ActionState> {
|
||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||
if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.MissedDerivation) {
|
||||
return listOf(TokenActionsState.ActionState.HideToken(true))
|
||||
return listOf(TokenActionsState.ActionState.HideToken(ScenarioUnavailabilityReason.None))
|
||||
}
|
||||
if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.Unreachable) {
|
||||
return getActionsForUnreachableCurrency(cryptoCurrencyStatus)
|
||||
return getActionsForUnreachableCurrency(cryptoCurrencyStatus, needAssociateAsset)
|
||||
}
|
||||
|
||||
val activeList = mutableListOf<TokenActionsState.ActionState>()
|
||||
|
|
@ -102,115 +108,155 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
|
||||
// copy address
|
||||
if (isAddressAvailable(cryptoCurrencyStatus.value.networkAddress)) {
|
||||
activeList.add(TokenActionsState.ActionState.CopyAddress(true))
|
||||
activeList.add(TokenActionsState.ActionState.CopyAddress(ScenarioUnavailabilityReason.None))
|
||||
}
|
||||
|
||||
// receive
|
||||
if (isAddressAvailable(cryptoCurrencyStatus.value.networkAddress)) {
|
||||
activeList.add(TokenActionsState.ActionState.Receive(true))
|
||||
val scenario = if (needAssociateAsset) {
|
||||
ScenarioUnavailabilityReason.UnassociatedAsset
|
||||
} else {
|
||||
ScenarioUnavailabilityReason.None
|
||||
}
|
||||
activeList.add(TokenActionsState.ActionState.Receive(scenario))
|
||||
}
|
||||
|
||||
// send
|
||||
if (
|
||||
isSendDisabled(
|
||||
userWalletId = userWallet.walletId,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
coinStatus = coinStatus,
|
||||
)
|
||||
) {
|
||||
disabledList.add(TokenActionsState.ActionState.Send(false))
|
||||
val sendUnavailabilityReason = getSendUnavailabilityReason(
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
coinStatus = coinStatus,
|
||||
)
|
||||
if (sendUnavailabilityReason == ScenarioUnavailabilityReason.None) {
|
||||
activeList.add(TokenActionsState.ActionState.Send(sendUnavailabilityReason))
|
||||
} else {
|
||||
activeList.add(TokenActionsState.ActionState.Send(true))
|
||||
disabledList.add(TokenActionsState.ActionState.Send(sendUnavailabilityReason))
|
||||
}
|
||||
|
||||
// swap
|
||||
if (userWallet.isMultiCurrency) {
|
||||
if (marketCryptoCurrencyRepository.isExchangeable(userWallet.walletId, cryptoCurrency)) {
|
||||
activeList.add(TokenActionsState.ActionState.Swap(true))
|
||||
if (
|
||||
marketCryptoCurrencyRepository.isExchangeable(userWallet.walletId, cryptoCurrency) &&
|
||||
cryptoCurrencyStatus.value !is CryptoCurrencyStatus.NoQuote
|
||||
) {
|
||||
activeList.add(TokenActionsState.ActionState.Swap(ScenarioUnavailabilityReason.None))
|
||||
} else {
|
||||
disabledList.add(TokenActionsState.ActionState.Swap(false))
|
||||
disabledList.add(
|
||||
TokenActionsState.ActionState.Swap(
|
||||
unavailabilityReason = ScenarioUnavailabilityReason.NotExchangeable(cryptoCurrency.name),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// buy
|
||||
if (rampManager.availableForBuy(cryptoCurrency)) {
|
||||
activeList.add(TokenActionsState.ActionState.Buy(true))
|
||||
activeList.add(TokenActionsState.ActionState.Buy(ScenarioUnavailabilityReason.None))
|
||||
} else {
|
||||
disabledList.add(TokenActionsState.ActionState.Buy(false))
|
||||
disabledList.add(
|
||||
TokenActionsState.ActionState.Buy(ScenarioUnavailabilityReason.BuyUnavailable(cryptoCurrency.symbol)),
|
||||
)
|
||||
}
|
||||
|
||||
// sell
|
||||
if (rampManager.availableForSell(cryptoCurrency)) {
|
||||
activeList.add(TokenActionsState.ActionState.Sell(true))
|
||||
} else {
|
||||
disabledList.add(TokenActionsState.ActionState.Sell(false))
|
||||
val sellSupportedByService = rampManager.availableForSell(cryptoCurrency)
|
||||
val sendAvailable = sendUnavailabilityReason is ScenarioUnavailabilityReason.None
|
||||
|
||||
when {
|
||||
sellSupportedByService && sendAvailable -> {
|
||||
activeList.add(TokenActionsState.ActionState.Sell(ScenarioUnavailabilityReason.None))
|
||||
}
|
||||
sellSupportedByService && !sendAvailable -> {
|
||||
(sendUnavailabilityReason as? ScenarioUnavailabilityReason.EmptyBalance)?.let {
|
||||
disabledList.add(
|
||||
TokenActionsState.ActionState.Sell(
|
||||
unavailabilityReason = it.copy(
|
||||
withdrawalScenario = ScenarioUnavailabilityReason.WithdrawalScenario.SELL,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
(sendUnavailabilityReason as? ScenarioUnavailabilityReason.PendingTransaction)?.let {
|
||||
disabledList.add(
|
||||
TokenActionsState.ActionState.Sell(
|
||||
unavailabilityReason = it.copy(
|
||||
withdrawalScenario = ScenarioUnavailabilityReason.WithdrawalScenario.SELL,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
disabledList.add(
|
||||
TokenActionsState.ActionState.Sell(
|
||||
unavailabilityReason = ScenarioUnavailabilityReason.NotSupportedBySellService(
|
||||
cryptoCurrency.name,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// hide
|
||||
activeList.add(TokenActionsState.ActionState.HideToken(true))
|
||||
activeList.add(TokenActionsState.ActionState.HideToken(ScenarioUnavailabilityReason.None))
|
||||
|
||||
return activeList + disabledList
|
||||
}
|
||||
|
||||
private fun getActionsForUnreachableCurrency(
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
needAssociateAsset: Boolean,
|
||||
): List<TokenActionsState.ActionState> {
|
||||
val activeList = mutableListOf<TokenActionsState.ActionState>()
|
||||
val disabledList = mutableListOf<TokenActionsState.ActionState>()
|
||||
val actionsList = mutableListOf<TokenActionsState.ActionState>()
|
||||
|
||||
if (isAddressAvailable(cryptoCurrencyStatus.value.networkAddress)) {
|
||||
activeList.add(TokenActionsState.ActionState.CopyAddress(true))
|
||||
actionsList.add(TokenActionsState.ActionState.CopyAddress(ScenarioUnavailabilityReason.None))
|
||||
}
|
||||
if (rampManager.availableForBuy(cryptoCurrencyStatus.currency)) {
|
||||
activeList.add(TokenActionsState.ActionState.Buy(true))
|
||||
actionsList.add(TokenActionsState.ActionState.Buy(ScenarioUnavailabilityReason.None))
|
||||
} else {
|
||||
disabledList.add(TokenActionsState.ActionState.Buy(false))
|
||||
actionsList.add(
|
||||
TokenActionsState.ActionState.Buy(
|
||||
ScenarioUnavailabilityReason.BuyUnavailable(
|
||||
cryptoCurrencyName = cryptoCurrencyStatus.currency.name,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
disabledList.add(TokenActionsState.ActionState.Send(false))
|
||||
disabledList.add(TokenActionsState.ActionState.Swap(false))
|
||||
disabledList.add(TokenActionsState.ActionState.Sell(false))
|
||||
actionsList.add(TokenActionsState.ActionState.Send(ScenarioUnavailabilityReason.Unreachable))
|
||||
actionsList.add(TokenActionsState.ActionState.Swap(ScenarioUnavailabilityReason.Unreachable))
|
||||
actionsList.add(TokenActionsState.ActionState.Sell(ScenarioUnavailabilityReason.Unreachable))
|
||||
|
||||
if (isAddressAvailable(cryptoCurrencyStatus.value.networkAddress)) {
|
||||
activeList.add(TokenActionsState.ActionState.Receive(true))
|
||||
val scenario = if (needAssociateAsset) {
|
||||
ScenarioUnavailabilityReason.UnassociatedAsset
|
||||
} else {
|
||||
ScenarioUnavailabilityReason.None
|
||||
}
|
||||
actionsList.add(TokenActionsState.ActionState.Receive(scenario))
|
||||
}
|
||||
activeList.add(TokenActionsState.ActionState.HideToken(true))
|
||||
return activeList + disabledList
|
||||
actionsList.add(TokenActionsState.ActionState.HideToken(ScenarioUnavailabilityReason.None))
|
||||
return actionsList
|
||||
}
|
||||
|
||||
private suspend fun isSendDisabled(
|
||||
userWalletId: UserWalletId,
|
||||
private fun getSendUnavailabilityReason(
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
coinStatus: CryptoCurrencyStatus?,
|
||||
): Boolean {
|
||||
val feePaidCurrency = currenciesRepository.getFeePaidCurrency(userWalletId, cryptoCurrencyStatus.currency)
|
||||
val notEnoughBalanceForFee = isNotEnoughBalanceForFee(
|
||||
feePaidCurrency = feePaidCurrency,
|
||||
tokenStatus = cryptoCurrencyStatus,
|
||||
coinStatus = coinStatus,
|
||||
)
|
||||
return cryptoCurrencyStatus.value.amount.isNullOrZero() ||
|
||||
notEnoughBalanceForFee ||
|
||||
): ScenarioUnavailabilityReason {
|
||||
return when {
|
||||
cryptoCurrencyStatus.value.amount.isNullOrZero() -> {
|
||||
ScenarioUnavailabilityReason.EmptyBalance(ScenarioUnavailabilityReason.WithdrawalScenario.SEND)
|
||||
}
|
||||
currenciesRepository.hasPendingTransactions(
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
coinStatus = coinStatus,
|
||||
)
|
||||
}
|
||||
|
||||
private fun isNotEnoughBalanceForFee(
|
||||
feePaidCurrency: FeePaidCurrency,
|
||||
tokenStatus: CryptoCurrencyStatus,
|
||||
coinStatus: CryptoCurrencyStatus?,
|
||||
): Boolean {
|
||||
return if (sendFeatureToggles.isRedesignedSendEnabled) {
|
||||
tokenStatus.value.amount.isZero()
|
||||
} else {
|
||||
when (feePaidCurrency) {
|
||||
FeePaidCurrency.Coin -> !tokenStatus.value.amount.isZero() && coinStatus?.value?.amount.isZero()
|
||||
FeePaidCurrency.SameCurrency -> tokenStatus.value.amount.isZero()
|
||||
is FeePaidCurrency.Token -> {
|
||||
val feePaidTokenBalance = feePaidCurrency.balance
|
||||
!tokenStatus.value.amount.isZero() && feePaidTokenBalance.isZero()
|
||||
}
|
||||
) -> {
|
||||
ScenarioUnavailabilityReason.PendingTransaction(
|
||||
withdrawalScenario = ScenarioUnavailabilityReason.WithdrawalScenario.SEND,
|
||||
networkName = coinStatus?.currency?.network?.name.orEmpty(),
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
ScenarioUnavailabilityReason.None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -218,8 +264,4 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
private fun isAddressAvailable(networkAddress: NetworkAddress?): Boolean {
|
||||
return networkAddress != null && networkAddress.defaultAddress.value.isNotEmpty()
|
||||
}
|
||||
|
||||
private fun BigDecimal?.isZero(): Boolean {
|
||||
return this?.signum() == 0
|
||||
}
|
||||
}
|
||||
|
|
@ -6,8 +6,10 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
|||
import com.tangem.domain.tokens.model.FeePaidCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
|
||||
import com.tangem.domain.tokens.model.warnings.HederaWarnings
|
||||
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
|
||||
import com.tangem.domain.tokens.repository.*
|
||||
import com.tangem.domain.transaction.models.AssetRequirementsCondition
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.swap.domain.api.SwapRepository
|
||||
|
|
@ -76,6 +78,7 @@ class GetCurrencyWarningsUseCase(
|
|||
getNetworkUnavailableWarning(currencyStatus),
|
||||
getNetworkNoAccountWarning(currencyStatus),
|
||||
getBeaconChainShutdownWarning(currency.network.id),
|
||||
getAssetRequirementsWarning(userWalletId = userWalletId, currency = currency),
|
||||
)
|
||||
}.flowOn(dispatchers.io)
|
||||
}
|
||||
|
|
@ -169,9 +172,6 @@ class GetCurrencyWarningsUseCase(
|
|||
when {
|
||||
tokenStatus != null && coinStatus != null -> {
|
||||
buildList {
|
||||
if (currenciesRepository.hasPendingTransactions(tokenStatus, coinStatus)) {
|
||||
add(CryptoCurrencyWarning.HasPendingTransactions(coinStatus.currency.symbol))
|
||||
}
|
||||
getFeeWarning(
|
||||
userWalletId = userWalletId,
|
||||
coinStatus = coinStatus,
|
||||
|
|
@ -270,6 +270,22 @@ class GetCurrencyWarningsUseCase(
|
|||
return if (BlockchainUtils.isBeaconChain(networkId.value)) CryptoCurrencyWarning.BeaconChainShutdown else null
|
||||
}
|
||||
|
||||
private suspend fun getAssetRequirementsWarning(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
): CryptoCurrencyWarning? {
|
||||
return when (val requirements = walletManagersFacade.getAssetRequirements(userWalletId, currency)) {
|
||||
is AssetRequirementsCondition.PaidTransaction -> HederaWarnings.AssociateWarning(currency = currency)
|
||||
is AssetRequirementsCondition.PaidTransactionWithFee -> HederaWarnings.AssociateWarningWithFee(
|
||||
currency = currency,
|
||||
fee = requirements.feeAmount,
|
||||
feeCurrencySymbol = requirements.feeCurrencySymbol,
|
||||
feeCurrencyDecimals = requirements.decimals,
|
||||
)
|
||||
null -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun BigDecimal?.isZero(): Boolean {
|
||||
return this?.signum() == 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,37 +1,46 @@
|
|||
package com.tangem.domain.tokens
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import com.tangem.domain.core.lce.LceFlow
|
||||
import com.tangem.domain.core.utils.EitherFlow
|
||||
import com.tangem.domain.core.utils.lceError
|
||||
import com.tangem.domain.core.utils.lceLoading
|
||||
import com.tangem.domain.core.utils.toLce
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.tokens.error.mapper.mapToTokenListError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.TokenList
|
||||
import com.tangem.domain.tokens.operations.CurrenciesStatusesLceOperations
|
||||
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
|
||||
import com.tangem.domain.tokens.operations.TokenListOperations
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
|
||||
class GetTokenListUseCase(
|
||||
internal val currenciesRepository: CurrenciesRepository,
|
||||
internal val quotesRepository: QuotesRepository,
|
||||
internal val networksRepository: NetworksRepository,
|
||||
internal val dispatchers: CoroutineDispatcherProvider,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
) {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
operator fun invoke(userWalletId: UserWalletId): Flow<Either<TokenListError, TokenList>> {
|
||||
return getTokensStatuses(userWalletId).transformLatest { maybeTokens ->
|
||||
fun launch(userWalletId: UserWalletId): EitherFlow<TokenListError, TokenList> {
|
||||
val operations = CurrenciesStatusesOperations(
|
||||
userWalletId = userWalletId,
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
networksRepository = networksRepository,
|
||||
)
|
||||
|
||||
return operations.getCurrenciesStatusesFlow().transformLatest { maybeTokens ->
|
||||
maybeTokens.fold(
|
||||
ifLeft = { error ->
|
||||
emit(error.left())
|
||||
emit(error.mapToTokenListError().left())
|
||||
},
|
||||
ifRight = { tokens ->
|
||||
emitAll(createTokenList(userWalletId, tokens))
|
||||
|
|
@ -40,32 +49,61 @@ class GetTokenListUseCase(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getTokensStatuses(
|
||||
userWalletId: UserWalletId,
|
||||
): Flow<Either<TokenListError, List<CryptoCurrencyStatus>>> {
|
||||
val operations = CurrenciesStatusesOperations(
|
||||
userWalletId = userWalletId,
|
||||
useCase = this@GetTokenListUseCase,
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun launchLce(userWalletId: UserWalletId): LceFlow<TokenListError, TokenList> {
|
||||
val operations = CurrenciesStatusesLceOperations(
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
networksRepository = networksRepository,
|
||||
)
|
||||
|
||||
return operations.getCurrenciesStatusesFlow()
|
||||
.map { maybeCurrenciesStatuses ->
|
||||
maybeCurrenciesStatuses.mapLeft(CurrenciesStatusesOperations.Error::mapToTokenListError)
|
||||
}
|
||||
return operations.getCurrenciesStatuses(userWalletId).transformLatest { maybeCurrencies ->
|
||||
maybeCurrencies.fold(
|
||||
ifLoading = { maybeContent ->
|
||||
if (maybeContent != null) {
|
||||
emitAll(createTokenListLce(userWalletId, maybeContent, isCurrenciesLoading = true))
|
||||
} else {
|
||||
emit(lceLoading())
|
||||
}
|
||||
},
|
||||
ifContent = { content ->
|
||||
emitAll(createTokenListLce(userWalletId, content, isCurrenciesLoading = false))
|
||||
},
|
||||
ifError = { error -> emit(error.lceError()) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTokenList(
|
||||
userWalletId: UserWalletId,
|
||||
tokens: List<CryptoCurrencyStatus>,
|
||||
): Flow<Either<TokenListError, TokenList>> {
|
||||
): EitherFlow<TokenListError, TokenList> {
|
||||
val operations = TokenListOperations(
|
||||
userWalletId = userWalletId,
|
||||
tokens = tokens,
|
||||
useCase = this@GetTokenListUseCase,
|
||||
currenciesRepository = currenciesRepository,
|
||||
)
|
||||
|
||||
return operations.getTokenListFlow().map { maybeTokenList ->
|
||||
maybeTokenList.mapLeft(TokenListOperations.Error::mapToTokenListError)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTokenListLce(
|
||||
userWalletId: UserWalletId,
|
||||
currencies: List<CryptoCurrencyStatus>,
|
||||
isCurrenciesLoading: Boolean,
|
||||
): LceFlow<TokenListError, TokenList> {
|
||||
val operations = TokenListOperations(
|
||||
userWalletId = userWalletId,
|
||||
tokens = currencies,
|
||||
currenciesRepository = currenciesRepository,
|
||||
)
|
||||
|
||||
return operations.getTokenListFlow().map { maybeTokenList ->
|
||||
maybeTokenList
|
||||
.mapLeft(TokenListOperations.Error::mapToTokenListError)
|
||||
.toLce(isCurrenciesLoading)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.domain.tokens.model
|
||||
|
||||
sealed class ScenarioUnavailabilityReason {
|
||||
data object None : ScenarioUnavailabilityReason()
|
||||
|
||||
// send&sell-specific
|
||||
data class PendingTransaction(
|
||||
val withdrawalScenario: WithdrawalScenario,
|
||||
val networkName: String,
|
||||
) : ScenarioUnavailabilityReason()
|
||||
data class EmptyBalance(val withdrawalScenario: WithdrawalScenario) : ScenarioUnavailabilityReason()
|
||||
|
||||
// buy-specific
|
||||
data class BuyUnavailable(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason()
|
||||
|
||||
// swap-specific
|
||||
data class NotExchangeable(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason()
|
||||
|
||||
// sell-specific
|
||||
data class NotSupportedBySellService(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason()
|
||||
|
||||
data object Unreachable : ScenarioUnavailabilityReason()
|
||||
|
||||
data object UnassociatedAsset : ScenarioUnavailabilityReason()
|
||||
|
||||
enum class WithdrawalScenario {
|
||||
SELL, SEND
|
||||
}
|
||||
}
|
||||
|
|
@ -10,20 +10,20 @@ data class TokenActionsState(
|
|||
|
||||
sealed class ActionState {
|
||||
|
||||
abstract val enabled: Boolean
|
||||
abstract val unavailabilityReason: ScenarioUnavailabilityReason
|
||||
|
||||
data class Buy(override val enabled: Boolean) : ActionState()
|
||||
data class Buy(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState()
|
||||
|
||||
data class CopyAddress(override val enabled: Boolean) : ActionState()
|
||||
data class CopyAddress(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState()
|
||||
|
||||
data class Sell(override val enabled: Boolean) : ActionState()
|
||||
data class Sell(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState()
|
||||
|
||||
data class Receive(override val enabled: Boolean) : ActionState()
|
||||
data class Receive(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState()
|
||||
|
||||
data class Swap(override val enabled: Boolean) : ActionState()
|
||||
data class Swap(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState()
|
||||
|
||||
data class Send(override val enabled: Boolean) : ActionState()
|
||||
data class Send(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState()
|
||||
|
||||
data class HideToken(override val enabled: Boolean) : ActionState()
|
||||
data class HideToken(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
package com.tangem.domain.tokens.operations
|
||||
|
||||
import arrow.core.*
|
||||
import arrow.core.raise.recover
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.core.lce.LceFlow
|
||||
import com.tangem.domain.core.lce.lce
|
||||
import com.tangem.domain.core.utils.lceError
|
||||
import com.tangem.domain.core.utils.lceLoading
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.tokens.model.*
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
internal class CurrenciesStatusesLceOperations(
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
) {
|
||||
|
||||
fun getCurrenciesStatuses(userWalletId: UserWalletId): LceFlow<TokenListError, List<CryptoCurrencyStatus>> {
|
||||
return getMultiCurrencyWalletCurrencies(userWalletId).transform transform@{ maybeCurrencies ->
|
||||
val nonEmptyCurrencies = maybeCurrencies.fold(
|
||||
ifLoading = { maybeContent ->
|
||||
emit(createLoadingCurrenciesStatuses(maybeContent))
|
||||
return@transform
|
||||
},
|
||||
ifContent = { content ->
|
||||
val nonEmptyCurrencies = content.toNonEmptyListOrNull()
|
||||
|
||||
if (nonEmptyCurrencies == null) {
|
||||
emit(TokenListError.EmptyTokens.lceError())
|
||||
return@transform
|
||||
} else {
|
||||
nonEmptyCurrencies
|
||||
}
|
||||
},
|
||||
ifError = { error ->
|
||||
emit(error.lceError())
|
||||
return@transform
|
||||
},
|
||||
)
|
||||
|
||||
val (networks, currenciesIds) = getIds(nonEmptyCurrencies)
|
||||
|
||||
combine(
|
||||
getQuotes(currenciesIds),
|
||||
getNetworksStatuses(userWalletId, networks),
|
||||
) { maybeQuotes, maybeNetworksStatuses ->
|
||||
val statuses = createCurrenciesStatuses(nonEmptyCurrencies, maybeQuotes, maybeNetworksStatuses)
|
||||
emit(statuses)
|
||||
}.collect()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createLoadingCurrenciesStatuses(
|
||||
maybeCurrencies: List<CryptoCurrency>?,
|
||||
): Lce<TokenListError, List<CryptoCurrencyStatus>> {
|
||||
val nonEmptyCurrencies = maybeCurrencies?.toNonEmptyListOrNull()
|
||||
|
||||
val statuses = if (nonEmptyCurrencies == null) {
|
||||
lceLoading()
|
||||
} else {
|
||||
createCurrenciesStatuses(
|
||||
nonEmptyCurrencies,
|
||||
maybeNetworkStatuses = null,
|
||||
maybeQuotes = null,
|
||||
)
|
||||
}
|
||||
|
||||
return statuses
|
||||
}
|
||||
|
||||
private fun getMultiCurrencyWalletCurrencies(
|
||||
userWalletId: UserWalletId,
|
||||
): LceFlow<TokenListError, List<CryptoCurrency>> {
|
||||
return currenciesRepository.getMultiCurrencyWalletCurrenciesUpdatesLce(userWalletId)
|
||||
.map { maybeCurrencies ->
|
||||
maybeCurrencies.mapError { TokenListError.DataError(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun createCurrenciesStatuses(
|
||||
currencies: NonEmptyList<CryptoCurrency>,
|
||||
maybeQuotes: Either<TokenListError, Set<Quote>>?,
|
||||
maybeNetworkStatuses: Lce<TokenListError, Set<NetworkStatus>>?,
|
||||
): Lce<TokenListError, List<CryptoCurrencyStatus>> = lce {
|
||||
isLoading.set(maybeNetworkStatuses == null)
|
||||
|
||||
var quotesRetrievingFailed = false
|
||||
|
||||
val networksStatuses = maybeNetworkStatuses?.bindOrNull()?.toNonEmptySetOrNull()
|
||||
val quotes = recover({ maybeQuotes?.bind()?.toNonEmptySetOrNull() }) {
|
||||
quotesRetrievingFailed = true
|
||||
null
|
||||
}
|
||||
|
||||
currencies.map { currency ->
|
||||
val quote = quotes?.firstOrNull { it.rawCurrencyId == currency.id.rawCurrencyId }
|
||||
val networkStatus = networksStatuses?.firstOrNull { it.network == currency.network }
|
||||
|
||||
createCurrencyStatus(currency, quote, networkStatus, ignoreQuote = quotesRetrievingFailed)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createCurrencyStatus(
|
||||
currency: CryptoCurrency,
|
||||
quote: Quote?,
|
||||
networkStatus: NetworkStatus?,
|
||||
ignoreQuote: Boolean,
|
||||
): CryptoCurrencyStatus {
|
||||
val currencyStatusOperations = CurrencyStatusOperations(
|
||||
currency = currency,
|
||||
quote = quote,
|
||||
networkStatus = networkStatus,
|
||||
ignoreQuote = ignoreQuote,
|
||||
)
|
||||
|
||||
return currencyStatusOperations.createTokenStatus()
|
||||
}
|
||||
|
||||
private fun getQuotes(tokensIds: NonEmptySet<CryptoCurrency.ID>): Flow<Either<TokenListError, Set<Quote>>> {
|
||||
return quotesRepository.getQuotesUpdates(tokensIds)
|
||||
.map<Set<Quote>, Either<TokenListError, Set<Quote>>> { it.right() }
|
||||
.catch { emit(TokenListError.DataError(it).left()) }
|
||||
}
|
||||
|
||||
private fun getNetworksStatuses(
|
||||
userWalletId: UserWalletId,
|
||||
networks: NonEmptySet<Network>,
|
||||
): LceFlow<TokenListError, Set<NetworkStatus>> {
|
||||
return networksRepository.getNetworkStatusesUpdatesLce(userWalletId, networks)
|
||||
.map { maybeStatuses ->
|
||||
maybeStatuses.mapError { TokenListError.DataError(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun getIds(currencies: List<CryptoCurrency>): Pair<NonEmptySet<Network>, NonEmptySet<CryptoCurrency.ID>> {
|
||||
val currencyIdToNetworkId = currencies.associate { currency ->
|
||||
currency.id to currency.network
|
||||
}
|
||||
val currenciesIds = currencyIdToNetworkId.keys.toNonEmptySetOrNull()
|
||||
val networks = currencyIdToNetworkId.values.toNonEmptySetOrNull()
|
||||
|
||||
requireNotNull(currenciesIds) { "Currencies IDs cannot be empty" }
|
||||
requireNotNull(networks) { "Networks IDs cannot be empty" }
|
||||
|
||||
return networks to currenciesIds
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ package com.tangem.domain.tokens.operations
|
|||
|
||||
import arrow.core.*
|
||||
import arrow.core.raise.*
|
||||
import com.tangem.domain.tokens.GetTokenListUseCase
|
||||
import com.tangem.domain.core.utils.EitherFlow
|
||||
import com.tangem.domain.tokens.model.*
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
|
|
@ -20,18 +20,8 @@ internal class CurrenciesStatusesOperations(
|
|||
private val userWalletId: UserWalletId,
|
||||
) {
|
||||
|
||||
constructor(
|
||||
userWalletId: UserWalletId,
|
||||
useCase: GetTokenListUseCase,
|
||||
) : this(
|
||||
currenciesRepository = useCase.currenciesRepository,
|
||||
quotesRepository = useCase.quotesRepository,
|
||||
networksRepository = useCase.networksRepository,
|
||||
userWalletId = userWalletId,
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun getCurrenciesStatusesFlow(): Flow<Either<Error, List<CryptoCurrencyStatus>>> {
|
||||
fun getCurrenciesStatusesFlow(): EitherFlow<Error, List<CryptoCurrencyStatus>> {
|
||||
return getMultiCurrencyWalletCurrencies().transformLatest { maybeCurrencies ->
|
||||
val nonEmptyCurrencies = maybeCurrencies.fold(
|
||||
ifLeft = { error ->
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ internal class CurrencyStatusOperations(
|
|||
|
||||
fun createTokenStatus(): CryptoCurrencyStatus = CryptoCurrencyStatus(currency, createStatus())
|
||||
|
||||
private fun createStatus(): CryptoCurrencyStatus.Status {
|
||||
private fun createStatus(): CryptoCurrencyStatus.Value {
|
||||
return when (val status = networkStatus?.value) {
|
||||
null -> CryptoCurrencyStatus.Loading
|
||||
is NetworkStatus.MissedDerivation -> createMissedDerivationStatus()
|
||||
|
|
@ -42,7 +42,7 @@ internal class CurrencyStatusOperations(
|
|||
networkAddress = status.address,
|
||||
)
|
||||
|
||||
private fun createStatus(status: NetworkStatus.Verified): CryptoCurrencyStatus.Status {
|
||||
private fun createStatus(status: NetworkStatus.Verified): CryptoCurrencyStatus.Value {
|
||||
val amount = when (val amount = status.amounts[currency.id]) {
|
||||
null -> {
|
||||
return CryptoCurrencyStatus.Loading
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import arrow.core.*
|
|||
import arrow.core.raise.Raise
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.raise.withError
|
||||
import com.tangem.domain.tokens.GetTokenListUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.TokenList
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
|
|
@ -18,16 +17,6 @@ internal class TokenListOperations(
|
|||
private val tokens: List<CryptoCurrencyStatus>,
|
||||
) {
|
||||
|
||||
constructor(
|
||||
userWalletId: UserWalletId,
|
||||
tokens: List<CryptoCurrencyStatus>,
|
||||
useCase: GetTokenListUseCase,
|
||||
) : this(
|
||||
currenciesRepository = useCase.currenciesRepository,
|
||||
userWalletId = userWalletId,
|
||||
tokens = tokens,
|
||||
)
|
||||
|
||||
fun getTokenListFlow(): Flow<Either<Error, TokenList>> {
|
||||
return combine(
|
||||
getIsGrouped(),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.domain.tokens.repository
|
||||
|
||||
import com.tangem.domain.core.error.DataError
|
||||
import com.tangem.domain.core.lce.LceFlow
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.FeePaidCurrency
|
||||
|
|
@ -20,7 +22,7 @@ interface CurrenciesRepository {
|
|||
* @param currencies The list of cryptocurrencies to be saved.
|
||||
* @param isGroupedByNetwork A boolean flag indicating whether the tokens should be grouped by network.
|
||||
* @param isSortedByBalance A boolean flag indicating whether the tokens should be sorted by balance.
|
||||
* @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If single-currency user wallet
|
||||
* @throws DataError.UserWalletError.WrongUserWallet If single-currency user wallet
|
||||
* ID provided.
|
||||
*/
|
||||
suspend fun saveTokens(
|
||||
|
|
@ -35,7 +37,7 @@ interface CurrenciesRepository {
|
|||
*
|
||||
* @param userWalletId The unique identifier of the user wallet.
|
||||
* @param currencies The currencies which must be added.
|
||||
* @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If single-currency user wallet
|
||||
* @throws DataError.UserWalletError.WrongUserWallet If single-currency user wallet
|
||||
* ID provided.
|
||||
*/
|
||||
suspend fun addCurrencies(userWalletId: UserWalletId, currencies: List<CryptoCurrency>)
|
||||
|
|
@ -45,7 +47,7 @@ interface CurrenciesRepository {
|
|||
*
|
||||
* @param userWalletId The unique identifier of the user wallet.
|
||||
* @param currency The currency which must be removed.
|
||||
* @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If multi-currency user wallet
|
||||
* @throws DataError.UserWalletError.WrongUserWallet If multi-currency user wallet
|
||||
* ID provided.
|
||||
*/
|
||||
suspend fun removeCurrency(userWalletId: UserWalletId, currency: CryptoCurrency)
|
||||
|
|
@ -55,7 +57,7 @@ interface CurrenciesRepository {
|
|||
*
|
||||
* @param userWalletId The unique identifier of the user wallet.
|
||||
* @param currencies The currencies which must be removed.
|
||||
* @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If single-currency user wallet
|
||||
* @throws DataError.UserWalletError.WrongUserWallet If single-currency user wallet
|
||||
* ID provided.
|
||||
*/
|
||||
suspend fun removeCurrencies(userWalletId: UserWalletId, currencies: List<CryptoCurrency>)
|
||||
|
|
@ -65,7 +67,7 @@ interface CurrenciesRepository {
|
|||
*
|
||||
* @param userWalletId The unique identifier of the user wallet.
|
||||
* @return The primary cryptocurrency associated with the user wallet.
|
||||
* @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If multi-currency user wallet
|
||||
* @throws DataError.UserWalletError.WrongUserWallet If multi-currency user wallet
|
||||
* ID provided.
|
||||
*/
|
||||
suspend fun getSingleCurrencyWalletPrimaryCurrency(userWalletId: UserWalletId): CryptoCurrency
|
||||
|
|
@ -75,7 +77,7 @@ interface CurrenciesRepository {
|
|||
*
|
||||
* @param userWalletId The unique identifier of the user wallet.
|
||||
* @return The primary cryptocurrency associated with the user wallet.
|
||||
* @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If multi-currency user wallet
|
||||
* @throws DataError.UserWalletError.WrongUserWallet If multi-currency user wallet
|
||||
* ID provided.
|
||||
*/
|
||||
suspend fun getSingleCurrencyWalletWithCardCurrencies(userWalletId: UserWalletId): List<CryptoCurrency>
|
||||
|
|
@ -87,7 +89,7 @@ interface CurrenciesRepository {
|
|||
* @param userWalletId The unique identifier of the user wallet.
|
||||
* @param id The unique identifier of the cryptocurrency to be retrieved.
|
||||
* @return The cryptocurrency associated with the user wallet and ID.
|
||||
* @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If single-currency user wallet
|
||||
* @throws DataError.UserWalletError.WrongUserWallet If single-currency user wallet
|
||||
* ID provided.
|
||||
*/
|
||||
suspend fun getSingleCurrencyWalletWithCardCurrency(
|
||||
|
|
@ -102,11 +104,22 @@ interface CurrenciesRepository {
|
|||
*
|
||||
* @param userWalletId The unique identifier of the user wallet.
|
||||
* @return A [Flow] emitting the set of cryptocurrencies associated with the user wallet.
|
||||
* @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If single-currency user wallet
|
||||
* @throws DataError.UserWalletError.WrongUserWallet If single-currency user wallet
|
||||
* ID provided.
|
||||
*/
|
||||
fun getMultiCurrencyWalletCurrenciesUpdates(userWalletId: UserWalletId): Flow<List<CryptoCurrency>>
|
||||
|
||||
/**
|
||||
* Retrieves updates of the list of cryptocurrencies within a multi-currency wallet.
|
||||
*
|
||||
* Loads remote cryptocurrencies if they have expired.
|
||||
*
|
||||
* @param userWalletId The unique identifier of the user wallet.
|
||||
* @return A [LceFlow] emitting the set of cryptocurrencies associated with the user wallet. May emit an
|
||||
* [DataError.UserWalletError.WrongUserWallet] if single-currency user wallet ID provided.
|
||||
*/
|
||||
fun getMultiCurrencyWalletCurrenciesUpdatesLce(userWalletId: UserWalletId): LceFlow<Throwable, List<CryptoCurrency>>
|
||||
|
||||
/**
|
||||
* Retrieves the list of cryptocurrencies within a multi-currency wallet.
|
||||
*
|
||||
|
|
@ -115,7 +128,7 @@ interface CurrenciesRepository {
|
|||
* @param userWalletId The unique identifier of the user wallet.
|
||||
* @param refresh A boolean flag indicating whether the data should be refreshed.
|
||||
* @return A list of [CryptoCurrency].
|
||||
* @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If single-currency user wallet
|
||||
* @throws DataError.UserWalletError.WrongUserWallet If single-currency user wallet
|
||||
* ID provided.
|
||||
*/
|
||||
suspend fun getMultiCurrencyWalletCurrenciesSync(
|
||||
|
|
@ -129,7 +142,7 @@ interface CurrenciesRepository {
|
|||
* @param userWalletId The unique identifier of the user wallet.
|
||||
* @param id The unique identifier of the cryptocurrency to be retrieved.
|
||||
* @return The cryptocurrency associated with the user wallet and ID.
|
||||
* @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If single-currency user wallet
|
||||
* @throws DataError.UserWalletError.WrongUserWallet If single-currency user wallet
|
||||
* ID provided.
|
||||
*/
|
||||
suspend fun getMultiCurrencyWalletCurrency(userWalletId: UserWalletId, id: CryptoCurrency.ID): CryptoCurrency
|
||||
|
|
@ -152,7 +165,7 @@ interface CurrenciesRepository {
|
|||
*
|
||||
* @param userWalletId The unique identifier of the user wallet.
|
||||
* @return A [Flow] emitting a boolean value indicating whether the tokens are grouped.
|
||||
* @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If single-currency user wallet
|
||||
* @throws DataError.UserWalletError.WrongUserWallet If single-currency user wallet
|
||||
* ID provided.
|
||||
*/
|
||||
fun isTokensGrouped(userWalletId: UserWalletId): Flow<Boolean>
|
||||
|
|
@ -162,7 +175,7 @@ interface CurrenciesRepository {
|
|||
*
|
||||
* @param userWalletId The unique identifier of the user wallet.
|
||||
* @return A [Flow] emitting a boolean value indicating whether the tokens are sorted by balance.
|
||||
* @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If single-currency user wallet
|
||||
* @throws DataError.UserWalletError.WrongUserWallet If single-currency user wallet
|
||||
* ID provided.
|
||||
*/
|
||||
fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow<Boolean>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.domain.tokens.repository
|
||||
|
||||
import com.tangem.domain.core.lce.LceFlow
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyAddress
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
|
|
@ -21,6 +22,20 @@ interface NetworksRepository {
|
|||
*/
|
||||
fun getNetworkStatusesUpdates(userWalletId: UserWalletId, networks: Set<Network>): Flow<Set<NetworkStatus>>
|
||||
|
||||
/**
|
||||
* Retrieves updates of network statuses of specified blockchain networks for a specific user wallet.
|
||||
*
|
||||
* Loads remote network statuses if they have expired.
|
||||
*
|
||||
* @param userWalletId The unique identifier of the user wallet.
|
||||
* @param networks A set of network which statuses are to be retrieved.
|
||||
* @return A [LceFlow] emitting a set of [NetworkStatus] objects corresponding to the specified networks.
|
||||
*/
|
||||
fun getNetworkStatusesUpdatesLce(
|
||||
userWalletId: UserWalletId,
|
||||
networks: Set<Network>,
|
||||
): LceFlow<Throwable, Set<NetworkStatus>>
|
||||
|
||||
/**
|
||||
* Fetches pending transactions for given network
|
||||
*
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import com.tangem.domain.tokens.repository.MockCurrenciesRepository
|
|||
import com.tangem.domain.tokens.repository.MockNetworksRepository
|
||||
import com.tangem.domain.tokens.repository.MockQuotesRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
|
@ -27,7 +26,6 @@ import org.junit.Test
|
|||
|
||||
internal class GetTokenListUseCaseTest {
|
||||
|
||||
private val dispatchers = TestingCoroutineDispatcherProvider()
|
||||
private val userWalletId = UserWalletId(value = null)
|
||||
|
||||
@Ignore
|
||||
|
|
@ -45,7 +43,7 @@ internal class GetTokenListUseCaseTest {
|
|||
)
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId)
|
||||
val result = useCase.launch(userWalletId)
|
||||
.take(count = 2)
|
||||
.toList()
|
||||
|
||||
|
|
@ -61,7 +59,7 @@ internal class GetTokenListUseCaseTest {
|
|||
val useCase = getUseCase(tokens = flowOf(DataError.NetworkError.NoInternetConnection.left()))
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
val result = useCase.launch(userWalletId).first()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
|
|
@ -81,7 +79,7 @@ internal class GetTokenListUseCaseTest {
|
|||
)
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId)
|
||||
val result = useCase.launch(userWalletId)
|
||||
.take(count = 2)
|
||||
.toList()
|
||||
|
||||
|
|
@ -97,7 +95,7 @@ internal class GetTokenListUseCaseTest {
|
|||
val useCase = getUseCase(isGrouped = flowOf(DataError.NetworkError.NoInternetConnection.left()))
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
val result = useCase.launch(userWalletId).first()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
|
|
@ -111,7 +109,7 @@ internal class GetTokenListUseCaseTest {
|
|||
val useCase = getUseCase(isSortedByBalance = flowOf(DataError.NetworkError.NoInternetConnection.left()))
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
val result = useCase.launch(userWalletId).first()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
|
|
@ -136,7 +134,7 @@ internal class GetTokenListUseCaseTest {
|
|||
)
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId)
|
||||
val result = useCase.launch(userWalletId)
|
||||
.take(count = 3)
|
||||
.toList()
|
||||
|
||||
|
|
@ -155,7 +153,7 @@ internal class GetTokenListUseCaseTest {
|
|||
val useCase = getUseCase(isGrouped = flowOf(true.right()))
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId)
|
||||
val result = useCase.launch(userWalletId)
|
||||
.take(count = 2)
|
||||
.toList()
|
||||
|
||||
|
|
@ -177,7 +175,7 @@ internal class GetTokenListUseCaseTest {
|
|||
)
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId)
|
||||
val result = useCase.launch(userWalletId)
|
||||
.take(count = 2)
|
||||
.toList()
|
||||
|
||||
|
|
@ -199,7 +197,7 @@ internal class GetTokenListUseCaseTest {
|
|||
)
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId)
|
||||
val result = useCase.launch(userWalletId)
|
||||
.take(count = 2)
|
||||
.toList()
|
||||
|
||||
|
|
@ -214,7 +212,7 @@ internal class GetTokenListUseCaseTest {
|
|||
val useCase = getUseCase(tokens = flowOf(emptyList<CryptoCurrency>().right()))
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
val result = useCase.launch(userWalletId).first()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
|
|
@ -227,7 +225,7 @@ internal class GetTokenListUseCaseTest {
|
|||
val useCase = getUseCase(tokens = flowOf())
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
val result = useCase.launch(userWalletId).first()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
|
|
@ -243,7 +241,7 @@ internal class GetTokenListUseCaseTest {
|
|||
val useCase = getUseCase(statuses = flowOf())
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId)
|
||||
val result = useCase.launch(userWalletId)
|
||||
.take(count = 2)
|
||||
.toList()
|
||||
|
||||
|
|
@ -258,7 +256,7 @@ internal class GetTokenListUseCaseTest {
|
|||
val useCase = getUseCase(statuses = flowOf(emptySet<NetworkStatus>().right()))
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
val result = useCase.launch(userWalletId).first()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
|
|
@ -277,7 +275,7 @@ internal class GetTokenListUseCaseTest {
|
|||
)
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId)
|
||||
val result = useCase.launch(userWalletId)
|
||||
.take(count = 2)
|
||||
.toList()
|
||||
|
||||
|
|
@ -295,7 +293,7 @@ internal class GetTokenListUseCaseTest {
|
|||
)
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
val result = useCase.launch(userWalletId).first()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
|
|
@ -308,7 +306,6 @@ internal class GetTokenListUseCaseTest {
|
|||
isGrouped: Flow<Either<DataError, Boolean>> = flowOf(MockTokenLists.isGrouped.right()),
|
||||
isSortedByBalance: Flow<Either<DataError, Boolean>> = flowOf(MockTokenLists.isSortedByBalance.right()),
|
||||
) = GetTokenListUseCase(
|
||||
dispatchers = dispatchers,
|
||||
currenciesRepository = MockCurrenciesRepository(
|
||||
sortTokensResult = Unit.right(),
|
||||
removeCurrencyResult = Unit.right(),
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package com.tangem.domain.tokens.repository
|
|||
import arrow.core.Either
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.domain.core.error.DataError
|
||||
import com.tangem.domain.core.lce.LceFlow
|
||||
import com.tangem.domain.core.utils.toLce
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.FeePaidCurrency
|
||||
|
|
@ -78,6 +80,12 @@ internal class MockCurrenciesRepository(
|
|||
return tokens.map { it.getOrElse { e -> throw e } }
|
||||
}
|
||||
|
||||
override fun getMultiCurrencyWalletCurrenciesUpdatesLce(
|
||||
userWalletId: UserWalletId,
|
||||
): LceFlow<Throwable, List<CryptoCurrency>> {
|
||||
return tokens.map { it.toLce() }
|
||||
}
|
||||
|
||||
override suspend fun getMultiCurrencyWalletCurrency(
|
||||
userWalletId: UserWalletId,
|
||||
id: CryptoCurrency.ID,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import arrow.core.Either
|
|||
import arrow.core.getOrElse
|
||||
import com.tangem.domain.core.error.DataError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyAddress
|
||||
import com.tangem.domain.core.lce.LceFlow
|
||||
import com.tangem.domain.core.utils.toLce
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
|
@ -22,6 +24,13 @@ internal class MockNetworksRepository(
|
|||
return statuses.map { it.getOrElse { e -> throw e } }
|
||||
}
|
||||
|
||||
override fun getNetworkStatusesUpdatesLce(
|
||||
userWalletId: UserWalletId,
|
||||
networks: Set<Network>,
|
||||
): LceFlow<Throwable, Set<NetworkStatus>> {
|
||||
return statuses.map { it.toLce() }
|
||||
}
|
||||
|
||||
override suspend fun fetchNetworkPendingTransactions(userWalletId: UserWalletId, networks: Set<Network>) {
|
||||
// no-op
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ dependencies {
|
|||
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.tokens)
|
||||
implementation(projects.domain.tokens.models)
|
||||
|
|
|
|||
1
domain/transaction/models/.gitignore
vendored
Normal file
1
domain/transaction/models/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
4
domain/transaction/models/build.gradle.kts
Normal file
4
domain/transaction/models/build.gradle.kts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
plugins {
|
||||
alias(deps.plugins.kotlin.jvm)
|
||||
id("configuration")
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.domain.transaction.models
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
sealed class AssetRequirementsCondition {
|
||||
|
||||
/**
|
||||
* The exact value of the fee for this type of condition is unknown.
|
||||
*/
|
||||
data object PaidTransaction : AssetRequirementsCondition()
|
||||
|
||||
/**
|
||||
* The exact value of the fee for this type of condition is stored in `feeAmount`.
|
||||
*/
|
||||
data class PaidTransactionWithFee(
|
||||
val feeAmount: BigDecimal,
|
||||
val feeCurrencySymbol: String,
|
||||
val decimals: Int,
|
||||
) : AssetRequirementsCondition()
|
||||
}
|
||||
|
|
@ -18,8 +18,22 @@ interface TransactionRepository {
|
|||
destination: String,
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
isSwap: Boolean,
|
||||
hash: String?,
|
||||
): TransactionData?
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
suspend fun validateTransaction(
|
||||
amount: Amount,
|
||||
fee: Fee?,
|
||||
memo: String?,
|
||||
destination: String,
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
isSwap: Boolean = false,
|
||||
hash: String? = null,
|
||||
): Result<Unit>
|
||||
|
||||
suspend fun sendTransaction(
|
||||
txData: TransactionData,
|
||||
signer: CommonSigner,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.domain.transaction.error
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
||||
sealed class AssociateAssetError {
|
||||
data class NotEnoughBalance(val feeCurrency: CryptoCurrency) : AssociateAssetError()
|
||||
|
||||
data class DataError(val message: String?) : AssociateAssetError()
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package com.tangem.domain.transaction.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyAmountStatus
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.transaction.error.AssociateAssetError
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.isNullOrZero
|
||||
|
||||
class AssociateAssetUseCase(
|
||||
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
): Either<AssociateAssetError, Unit> {
|
||||
return either {
|
||||
val networkCoin = currenciesRepository.getNetworkCoin(
|
||||
userWalletId = userWalletId,
|
||||
networkId = currency.network.id,
|
||||
derivationPath = currency.network.derivationPath,
|
||||
)
|
||||
if (isBalanceZero(userWalletId, networkCoin)) {
|
||||
raise(AssociateAssetError.NotEnoughBalance(networkCoin))
|
||||
}
|
||||
val signer = cardSdkConfigRepository.getCommonSigner(cardId = null)
|
||||
|
||||
catch(
|
||||
block = {
|
||||
when (val result = walletManagersFacade.associateAsset(userWalletId, currency, signer)) {
|
||||
is SimpleResult.Failure -> raise(AssociateAssetError.DataError(result.error.message))
|
||||
SimpleResult.Success -> Unit
|
||||
}
|
||||
},
|
||||
catch = { error -> AssociateAssetError.DataError(error.message) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun isBalanceZero(userWalletId: UserWalletId, currency: CryptoCurrency): Boolean {
|
||||
val networkStatus = networksRepository.getNetworkStatusesSync(
|
||||
userWalletId = userWalletId,
|
||||
networks = setOf(currency.network),
|
||||
).find { it.network == currency.network }
|
||||
val networkCoinAmountStatus = (networkStatus?.value as? NetworkStatus.Verified)
|
||||
?.amounts
|
||||
?.get(currency.id)
|
||||
return networkCoinAmountStatus is CryptoCurrencyAmountStatus.Loaded &&
|
||||
networkCoinAmountStatus.value.isNullOrZero()
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,8 @@ class CreateTransactionUseCase(
|
|||
destination: String,
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
isSwap: Boolean = false,
|
||||
hash: String? = null,
|
||||
) = Either.catch {
|
||||
requireNotNull(
|
||||
transactionRepository.createTransaction(
|
||||
|
|
@ -31,6 +33,8 @@ class CreateTransactionUseCase(
|
|||
destination = destination,
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
isSwap = isSwap,
|
||||
hash = hash,
|
||||
),
|
||||
) { "Failed to create transaction" }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.domain.transaction.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.transaction.TransactionRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
class ValidateTransactionUseCase(
|
||||
private val transactionRepository: TransactionRepository,
|
||||
) {
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
suspend operator fun invoke(
|
||||
amount: Amount,
|
||||
fee: Fee,
|
||||
memo: String?,
|
||||
destination: String,
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
isSwap: Boolean = false,
|
||||
hash: String? = null,
|
||||
): Either<Throwable, Unit> {
|
||||
return transactionRepository.validateTransaction(
|
||||
amount = amount,
|
||||
fee = fee,
|
||||
memo = memo,
|
||||
destination = destination,
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
isSwap = isSwap,
|
||||
hash = hash,
|
||||
)
|
||||
.fold(onSuccess = { Unit.right() }, onFailure = { it.left() })
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ dependencies {
|
|||
|
||||
// region Domain modules
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.tokens)
|
||||
implementation(projects.domain.tokens.models)
|
||||
|
|
|
|||
|
|
@ -1,36 +1,22 @@
|
|||
package com.tangem.domain.userwallets
|
||||
package com.tangem.domain.wallets.builder
|
||||
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.usecase.GetCardImageUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
|
||||
class UserWalletBuilder(
|
||||
private val scanResponse: ScanResponse,
|
||||
private val generateWalletNameUseCase: GenerateWalletNameUseCase,
|
||||
private val getCardImageUseCase: GetCardImageUseCase = GetCardImageUseCase(),
|
||||
) {
|
||||
private var backupCardsIds: Set<String> = emptySet()
|
||||
private var hasBackupError: Boolean = false
|
||||
|
||||
private val CardDTO.isBackupNotAllowed: Boolean
|
||||
get() = !this.settings.isBackupAllowed
|
||||
|
||||
private val ScanResponse.userWalletName: String
|
||||
get() = when (productType) {
|
||||
ProductType.Note -> "Note"
|
||||
ProductType.Twins -> "Twin"
|
||||
ProductType.Start2Coin -> "Start2Coin"
|
||||
ProductType.Visa -> "Tangem Visa"
|
||||
ProductType.Wallet,
|
||||
ProductType.Wallet2,
|
||||
ProductType.Ring,
|
||||
-> when {
|
||||
card.isBackupNotAllowed -> "Tangem card"
|
||||
cardTypesResolver.isStart2Coin() -> "Start2Coin"
|
||||
else -> "Wallet"
|
||||
}
|
||||
}
|
||||
get() = !settings.isBackupAllowed
|
||||
|
||||
/**
|
||||
* DANGEROUS!!!
|
||||
|
|
@ -56,7 +42,11 @@ class UserWalletBuilder(
|
|||
?.let {
|
||||
UserWallet(
|
||||
walletId = it,
|
||||
name = userWalletName,
|
||||
name = generateWalletNameUseCase(
|
||||
productType = productType,
|
||||
isBackupNotAllowed = card.isBackupNotAllowed,
|
||||
isStartToCoin = cardTypesResolver.isStart2Coin(),
|
||||
),
|
||||
artworkUrl = getCardImageUseCase.invoke(card.cardId, card.cardPublicKey),
|
||||
cardsInWallet = backupCardsIds.plus(card.cardId),
|
||||
scanResponse = this,
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.domain.userwallets
|
||||
package com.tangem.domain.wallets.builder
|
||||
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
package com.tangem.domain.wallets.legacy
|
||||
|
||||
import arrow.core.raise.Raise
|
||||
import arrow.core.raise.ensureNotNull
|
||||
|
||||
internal inline fun <Error> Raise<Error>.ensureUserWalletListManagerNotNull(
|
||||
walletsStateHolder: WalletsStateHolder,
|
||||
raise: (Throwable) -> Error,
|
||||
): UserWalletsListManager {
|
||||
return ensureNotNull(
|
||||
value = walletsStateHolder.userWalletsListManager,
|
||||
raise = {
|
||||
raise(IllegalStateException("User wallets list manager not initialized"))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -7,12 +7,20 @@ import kotlinx.coroutines.flow.Flow
|
|||
|
||||
interface UserWalletsListManager {
|
||||
|
||||
/**
|
||||
* Indicates that the [UserWalletsListManager] is [UserWalletsListManager.Lockable]
|
||||
* */
|
||||
val isLockable: Boolean
|
||||
|
||||
/** [Flow] with all saved [UserWallet]s updates */
|
||||
val userWallets: Flow<List<UserWallet>>
|
||||
|
||||
/** [Flow] with selected [UserWallet] updates */
|
||||
val selectedUserWallet: Flow<UserWallet>
|
||||
|
||||
/** [List] with all saved [UserWallet]s updates */
|
||||
val userWalletsSync: List<UserWallet>
|
||||
|
||||
/** Selected [UserWallet] */
|
||||
val selectedUserWalletSync: UserWallet?
|
||||
|
||||
|
|
@ -84,11 +92,6 @@ interface UserWalletsListManager {
|
|||
*/
|
||||
suspend fun get(userWalletId: UserWalletId): CompletionResult<UserWallet>
|
||||
|
||||
/**
|
||||
* Indicates that the [UserWalletsListManager] supports [UserWalletsListManager.Lockable]
|
||||
* */
|
||||
fun isLockable(): Boolean
|
||||
|
||||
interface Lockable : UserWalletsListManager {
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ suspend fun UserWalletsListManager.unlockIfLockable(type: UnlockType = UnlockTyp
|
|||
* [UserWalletsListManager.Lockable] otherwise
|
||||
* */
|
||||
fun UserWalletsListManager.asLockable(): UserWalletsListManager.Lockable? {
|
||||
if (this.isLockable()) {
|
||||
if (this.isLockable) {
|
||||
return this as? UserWalletsListManager.Lockable
|
||||
}
|
||||
return null
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
package com.tangem.domain.wallets.legacy
|
||||
|
||||
interface UserWalletsListManagerFeatureToggles {
|
||||
|
||||
val isGeneralManagerEnabled: Boolean
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
package com.tangem.domain.wallets.legacy
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface WalletsStateHolder {
|
||||
|
||||
val userWalletsListManager: UserWalletsListManager?
|
||||
|
||||
val userWalletListManagerFlow: Flow<UserWalletsListManager?>
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.domain.userwallets
|
||||
package com.tangem.domain.wallets.models
|
||||
|
||||
data class Artwork(val artworkId: String) {
|
||||
|
||||
|
|
@ -6,9 +6,9 @@ data class Artwork(val artworkId: String) {
|
|||
const val DEFAULT_IMG_URL = "https://app.tangem.com/cards/card_default.png"
|
||||
const val SERGIO_CARD_URL = "https://app.tangem.com/cards/card_tg059.png"
|
||||
const val MARTA_CARD_URL = "https://app.tangem.com/cards/card_tg083.png"
|
||||
const val TWIN_CARD_1_URL = "https://app.tangem.com/cards/card_tg085.png"
|
||||
const val TWIN_CARD_2_URL = "https://app.tangem.com/cards/card_tg086.png"
|
||||
const val SERGIO_CARD_ID = "BC01"
|
||||
const val MARTA_CARD_ID = "BC02"
|
||||
const val TWIN_CARD_1 = "https://app.tangem.com/cards/card_tg085.png"
|
||||
const val TWIN_CARD_2 = "https://app.tangem.com/cards/card_tg086.png"
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,5 @@ package com.tangem.domain.wallets.models
|
|||
|
||||
sealed interface DeleteWalletError {
|
||||
|
||||
object DataError : DeleteWalletError
|
||||
|
||||
object UnableToDelete : DeleteWalletError
|
||||
data object UnableToDelete : DeleteWalletError
|
||||
}
|
||||
|
|
@ -2,7 +2,5 @@ package com.tangem.domain.wallets.models
|
|||
|
||||
sealed class GetUserWalletError {
|
||||
|
||||
data class DataError(val cause: Throwable) : GetUserWalletError()
|
||||
|
||||
object UserWalletNotFound : GetUserWalletError()
|
||||
data object UserWalletNotFound : GetUserWalletError()
|
||||
}
|
||||
|
|
@ -2,7 +2,5 @@ package com.tangem.domain.wallets.models
|
|||
|
||||
sealed interface SelectWalletError {
|
||||
|
||||
object DataError : SelectWalletError
|
||||
|
||||
object UnableToSelectUserWallet : SelectWalletError
|
||||
}
|
||||
|
|
@ -2,5 +2,7 @@ package com.tangem.domain.wallets.models
|
|||
|
||||
sealed interface UpdateWalletError {
|
||||
|
||||
object DataError : UpdateWalletError
|
||||
data object DataError : UpdateWalletError
|
||||
|
||||
data object NameAlreadyExists : UpdateWalletError
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.domain.wallets.repository
|
||||
|
||||
/**
|
||||
* Access to migrate names flag
|
||||
*/
|
||||
interface WalletNamesMigrationRepository {
|
||||
|
||||
suspend fun isMigrationDone(): Boolean
|
||||
|
||||
suspend fun setMigrationDone()
|
||||
}
|
||||
|
|
@ -1,37 +1,36 @@
|
|||
package com.tangem.domain.wallets.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.right
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.domain.wallets.legacy.WalletsStateHolder
|
||||
import com.tangem.domain.wallets.legacy.ensureUserWalletListManagerNotNull
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.models.DeleteWalletError
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
/**
|
||||
* Use case for updating user wallet
|
||||
* Use case for deleting user wallet
|
||||
*
|
||||
* @property walletsStateHolder state holder for getting static initialized 'userWalletsListManager'
|
||||
* @property userWalletsListManager user wallets list manager
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class DeleteWalletUseCase(private val walletsStateHolder: WalletsStateHolder) {
|
||||
class DeleteWalletUseCase(private val userWalletsListManager: UserWalletsListManager) {
|
||||
|
||||
suspend operator fun invoke(userWalletId: UserWalletId): Either<DeleteWalletError, Unit> {
|
||||
/**
|
||||
* Deletes user wallet with provided ID.
|
||||
*
|
||||
* @param userWalletId ID of user wallet to be deleted.
|
||||
*
|
||||
* @return [Either] with [DeleteWalletError] or [Boolean] which indicates that there are still saved wallets.
|
||||
* */
|
||||
suspend operator fun invoke(userWalletId: UserWalletId): Either<DeleteWalletError, Boolean> {
|
||||
return either {
|
||||
val userWalletsListManager = ensureUserWalletListManagerNotNull(
|
||||
walletsStateHolder = walletsStateHolder,
|
||||
raise = { DeleteWalletError.DataError },
|
||||
)
|
||||
|
||||
userWalletsListManager.delete(userWalletIds = listOf(userWalletId))
|
||||
.doOnSuccess { return Unit.right() }
|
||||
.doOnFailure { return DeleteWalletError.UnableToDelete.left() }
|
||||
.doOnFailure {
|
||||
raise(DeleteWalletError.UnableToDelete)
|
||||
}
|
||||
|
||||
return Unit.right()
|
||||
userWalletsListManager.hasUserWallets
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.tangem.domain.wallets.usecase
|
||||
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
|
||||
/**
|
||||
* Use case for user wallet name generation
|
||||
*/
|
||||
class GenerateWalletNameUseCase(
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
) {
|
||||
|
||||
operator fun invoke(productType: ProductType, isBackupNotAllowed: Boolean, isStartToCoin: Boolean): String {
|
||||
val defaultName = getDefaultName(
|
||||
productType = productType,
|
||||
isBackupNotAllowed = isBackupNotAllowed,
|
||||
isStartToCoin = isStartToCoin,
|
||||
)
|
||||
|
||||
val existingNames = userWalletsListManager.userWalletsSync.map { it.name }.toSet()
|
||||
return suggestedWalletName(defaultName, existingNames)
|
||||
}
|
||||
|
||||
private fun suggestedWalletName(defaultName: String, existingNames: Set<String>): String {
|
||||
val startIndex = 2
|
||||
if (!existingNames.contains(defaultName)) {
|
||||
return defaultName
|
||||
}
|
||||
|
||||
for (index in startIndex..MAX_WALLETS_LIMIT) {
|
||||
val potentialName = "$defaultName $index"
|
||||
if (!existingNames.contains(potentialName)) {
|
||||
return potentialName
|
||||
}
|
||||
}
|
||||
|
||||
return defaultName
|
||||
}
|
||||
|
||||
private fun getDefaultName(productType: ProductType, isBackupNotAllowed: Boolean, isStartToCoin: Boolean): String {
|
||||
return when (productType) {
|
||||
ProductType.Note -> "Note"
|
||||
ProductType.Twins -> "Twin"
|
||||
ProductType.Start2Coin -> "Start2Coin"
|
||||
ProductType.Visa -> "Tangem Visa"
|
||||
ProductType.Wallet,
|
||||
ProductType.Wallet2,
|
||||
ProductType.Ring,
|
||||
-> when {
|
||||
isBackupNotAllowed -> "Tangem card"
|
||||
isStartToCoin -> "Start2Coin"
|
||||
else -> "Wallet"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val MAX_WALLETS_LIMIT = 10000
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
package com.tangem.domain.userwallets
|
||||
package com.tangem.domain.wallets.usecase
|
||||
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.common.TwinCardNumber
|
||||
import com.tangem.domain.common.TwinsHelper
|
||||
import com.tangem.domain.wallets.models.Artwork
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.operations.attestation.TangemApi
|
||||
|
||||
|
|
@ -42,8 +43,8 @@ class GetCardImageUseCase(private val verifier: OnlineCardVerifier = OnlineCardV
|
|||
cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL
|
||||
cardId.startsWith(Artwork.MARTA_CARD_ID) -> Artwork.MARTA_CARD_URL
|
||||
else -> when (TwinsHelper.getTwinCardNumber(cardId)) {
|
||||
TwinCardNumber.First -> Artwork.TWIN_CARD_1
|
||||
TwinCardNumber.Second -> Artwork.TWIN_CARD_2
|
||||
TwinCardNumber.First -> Artwork.TWIN_CARD_1_URL
|
||||
TwinCardNumber.Second -> Artwork.TWIN_CARD_2_URL
|
||||
else -> Artwork.DEFAULT_IMG_URL
|
||||
}
|
||||
}
|
||||
|
|
@ -3,8 +3,7 @@ package com.tangem.domain.wallets.usecase
|
|||
import arrow.core.Either
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.raise.ensureNotNull
|
||||
import com.tangem.domain.wallets.legacy.WalletsStateHolder
|
||||
import com.tangem.domain.wallets.legacy.ensureUserWalletListManagerNotNull
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.models.GetUserWalletError
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
||||
|
|
@ -12,19 +11,14 @@ import com.tangem.domain.wallets.models.UserWallet
|
|||
* Use case for getting selected wallet.
|
||||
* Important! If all wallets is locked, use case returns a error.
|
||||
*
|
||||
* @property walletsStateHolder state holder for getting static initialized 'userWalletsListManager'
|
||||
* @property userWalletsListManager user wallets list manager
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class GetSelectedWalletSyncUseCase(private val walletsStateHolder: WalletsStateHolder) {
|
||||
class GetSelectedWalletSyncUseCase(private val userWalletsListManager: UserWalletsListManager) {
|
||||
|
||||
operator fun invoke(): Either<GetUserWalletError, UserWallet> {
|
||||
return either {
|
||||
val userWalletsListManager = ensureUserWalletListManagerNotNull(
|
||||
walletsStateHolder = walletsStateHolder,
|
||||
raise = GetUserWalletError::DataError,
|
||||
)
|
||||
|
||||
ensureNotNull(
|
||||
value = userWalletsListManager.selectedUserWalletSync,
|
||||
raise = { GetUserWalletError.UserWalletNotFound },
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@ package com.tangem.domain.wallets.usecase
|
|||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.domain.wallets.legacy.WalletsStateHolder
|
||||
import com.tangem.domain.wallets.legacy.ensureUserWalletListManagerNotNull
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.models.GetUserWalletError
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
@ -11,19 +10,14 @@ import kotlinx.coroutines.flow.Flow
|
|||
/**
|
||||
* Use case for getting flow of selected wallet.
|
||||
*
|
||||
* @property walletsStateHolder state holder for getting static initialized 'userWalletsListManager'
|
||||
* @property userWalletsListManager user wallets list manager
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class GetSelectedWalletUseCase(private val walletsStateHolder: WalletsStateHolder) {
|
||||
class GetSelectedWalletUseCase(private val userWalletsListManager: UserWalletsListManager) {
|
||||
|
||||
operator fun invoke(): Either<GetUserWalletError, Flow<UserWallet>> {
|
||||
return either {
|
||||
val userWalletsListManager = ensureUserWalletListManagerNotNull(
|
||||
walletsStateHolder = walletsStateHolder,
|
||||
raise = GetUserWalletError::DataError,
|
||||
)
|
||||
|
||||
userWalletsListManager.selectedUserWallet
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,22 +3,15 @@ package com.tangem.domain.wallets.usecase
|
|||
import arrow.core.Either
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.raise.ensureNotNull
|
||||
import com.tangem.domain.wallets.legacy.WalletsStateHolder
|
||||
import com.tangem.domain.wallets.legacy.ensureUserWalletListManagerNotNull
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.models.GetUserWalletError
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
|
||||
class GetUserWalletUseCase(private val walletsStateHolder: WalletsStateHolder) {
|
||||
class GetUserWalletUseCase(private val userWalletsListManager: UserWalletsListManager) {
|
||||
|
||||
suspend operator fun invoke(userWalletId: UserWalletId): Either<GetUserWalletError, UserWallet> = either {
|
||||
val userWalletsListManager = ensureUserWalletListManagerNotNull(
|
||||
walletsStateHolder = walletsStateHolder,
|
||||
raise = GetUserWalletError::DataError,
|
||||
)
|
||||
|
||||
val userWallets = userWalletsListManager.userWallets.firstOrNull().orEmpty()
|
||||
operator fun invoke(userWalletId: UserWalletId): Either<GetUserWalletError, UserWallet> = either {
|
||||
val userWallets = userWalletsListManager.userWalletsSync
|
||||
|
||||
ensureNotNull(userWallets.firstOrNull { it.walletId == userWalletId }) {
|
||||
raise(GetUserWalletError.UserWalletNotFound)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.domain.wallets.usecase
|
||||
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
|
||||
/**
|
||||
* Use case for getting list of user wallets names.
|
||||
*
|
||||
* @property userWalletsListManager user wallets list manager
|
||||
*/
|
||||
class GetWalletNamesUseCase(private val userWalletsListManager: UserWalletsListManager) {
|
||||
|
||||
operator fun invoke(): List<String> = userWalletsListManager.userWalletsSync.map { it.name }
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.domain.wallets.usecase
|
||||
|
||||
import com.tangem.domain.wallets.legacy.WalletsStateHolder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
|
|
@ -8,16 +8,15 @@ import kotlinx.coroutines.flow.firstOrNull
|
|||
/**
|
||||
* Use case for getting list of user wallets
|
||||
*
|
||||
* @property walletsStateHolder state holder for getting static initialized 'userWalletsListManager'
|
||||
* @property userWalletsListManager user wallets list manager
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class GetWalletsUseCase(private val walletsStateHolder: WalletsStateHolder) {
|
||||
class GetWalletsUseCase(private val userWalletsListManager: UserWalletsListManager) {
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
operator fun invoke(): Flow<List<UserWallet>> =
|
||||
requireNotNull(walletsStateHolder.userWalletsListManager).userWallets
|
||||
operator fun invoke(): Flow<List<UserWallet>> = userWalletsListManager.userWallets
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
suspend fun invokeSync(): List<UserWallet>? = walletsStateHolder.userWalletsListManager?.userWallets?.firstOrNull()
|
||||
suspend fun invokeSync(): List<UserWallet>? = userWalletsListManager.userWallets.firstOrNull()
|
||||
}
|
||||
|
|
@ -1,19 +1,19 @@
|
|||
package com.tangem.domain.wallets.usecase
|
||||
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.wallets.legacy.WalletsStateHolder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
/**
|
||||
* Use case that checks if wallet need backup cards
|
||||
*
|
||||
* @property userWalletsListManager user wallets list manager
|
||||
*/
|
||||
class IsNeedToBackupUseCase(private val walletsStateHolder: WalletsStateHolder) {
|
||||
class IsNeedToBackupUseCase(private val userWalletsListManager: UserWalletsListManager) {
|
||||
|
||||
operator fun invoke(id: UserWalletId): Flow<Boolean> {
|
||||
val userWalletsListManager = requireNotNull(walletsStateHolder.userWalletsListManager)
|
||||
|
||||
return userWalletsListManager.userWallets
|
||||
.map { wallets ->
|
||||
val wallet = wallets.firstOrNull { it.walletId == id }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.domain.wallets.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.right
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.models.UpdateWalletError
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
/**
|
||||
* Use case for rename user wallet
|
||||
*
|
||||
* @property userWalletsListManager user wallets list manager
|
||||
*/
|
||||
class RenameWalletUseCase(private val userWalletsListManager: UserWalletsListManager) {
|
||||
|
||||
suspend operator fun invoke(userWalletId: UserWalletId, name: String): Either<UpdateWalletError, UserWallet> {
|
||||
val existingNames = userWalletsListManager.userWalletsSync
|
||||
|
||||
if (existingNames.any { it.name == name && it.walletId != userWalletId }) {
|
||||
return UpdateWalletError.NameAlreadyExists.left()
|
||||
}
|
||||
|
||||
return either {
|
||||
userWalletsListManager.update(userWalletId) { it.copy(name = name) }
|
||||
.doOnSuccess { return it.right() }
|
||||
.doOnFailure { return UpdateWalletError.DataError.left() }
|
||||
|
||||
return UpdateWalletError.DataError.left()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,27 +7,21 @@ import arrow.core.right
|
|||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListError
|
||||
import com.tangem.domain.wallets.legacy.WalletsStateHolder
|
||||
import com.tangem.domain.wallets.legacy.ensureUserWalletListManagerNotNull
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.models.SaveWalletError
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
||||
/**
|
||||
* Use case for saving user wallet
|
||||
*
|
||||
* @property walletsStateHolder state holder for getting static initialized 'userWalletsListManager'
|
||||
* @property userWalletsListManager user wallets list manager
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class SaveWalletUseCase(private val walletsStateHolder: WalletsStateHolder) {
|
||||
class SaveWalletUseCase(private val userWalletsListManager: UserWalletsListManager) {
|
||||
|
||||
suspend operator fun invoke(userWallet: UserWallet, canOverride: Boolean = false): Either<SaveWalletError, Unit> {
|
||||
return either {
|
||||
val userWalletsListManager = ensureUserWalletListManagerNotNull(
|
||||
walletsStateHolder = walletsStateHolder,
|
||||
raise = { SaveWalletError.DataError },
|
||||
)
|
||||
|
||||
userWalletsListManager.save(userWallet, canOverride)
|
||||
.doOnSuccess { return Unit.right() }
|
||||
.doOnFailure {
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@ import arrow.core.raise.either
|
|||
import arrow.core.right
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.wallets.legacy.WalletsStateHolder
|
||||
import com.tangem.domain.wallets.legacy.ensureUserWalletListManagerNotNull
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.models.SelectWalletError
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
|
@ -14,22 +13,18 @@ import com.tangem.domain.wallets.models.UserWalletId
|
|||
/**
|
||||
* Use case for selecting wallet
|
||||
*
|
||||
* @property walletsStateHolder state holder for getting static initialized 'userWalletsListManager'
|
||||
* @property userWalletsListManager user wallets list manager
|
||||
* @property reduxStateHolder redux state holder
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class SelectWalletUseCase(
|
||||
private val walletsStateHolder: WalletsStateHolder,
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
private val reduxStateHolder: ReduxStateHolder,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(userWalletId: UserWalletId): Either<SelectWalletError, UserWallet> {
|
||||
return either {
|
||||
val userWalletsListManager = ensureUserWalletListManagerNotNull(
|
||||
walletsStateHolder = walletsStateHolder,
|
||||
raise = { SelectWalletError.DataError },
|
||||
)
|
||||
|
||||
return when (val result = userWalletsListManager.select(userWalletId)) {
|
||||
is CompletionResult.Failure -> raise(SelectWalletError.UnableToSelectUserWallet)
|
||||
is CompletionResult.Success -> {
|
||||
|
|
|
|||
|
|
@ -5,23 +5,23 @@ import arrow.core.raise.either
|
|||
import arrow.core.raise.ensureNotNull
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListError
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType
|
||||
import com.tangem.domain.wallets.legacy.WalletsStateHolder
|
||||
import com.tangem.domain.wallets.legacy.asLockable
|
||||
import com.tangem.domain.wallets.models.UnlockWalletsError
|
||||
|
||||
/**
|
||||
* Unlock wallets use case
|
||||
*
|
||||
* @property walletsStateHolder wallets state holder
|
||||
* @property userWalletsListManager user wallets list manager
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class UnlockWalletsUseCase(private val walletsStateHolder: WalletsStateHolder) {
|
||||
class UnlockWalletsUseCase(private val userWalletsListManager: UserWalletsListManager) {
|
||||
|
||||
suspend operator fun invoke(type: UnlockType = UnlockType.ANY): Either<UnlockWalletsError, Unit> = either {
|
||||
val userWalletsListManager = ensureNotNull(
|
||||
value = walletsStateHolder.userWalletsListManager?.asLockable(),
|
||||
value = userWalletsListManager.asLockable(),
|
||||
raise = {
|
||||
UnlockWalletsError.DataError(
|
||||
cause = IllegalStateException("The lockable user wallets list manager could not be found"),
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@ import arrow.core.raise.either
|
|||
import arrow.core.right
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.domain.wallets.legacy.WalletsStateHolder
|
||||
import com.tangem.domain.wallets.legacy.ensureUserWalletListManagerNotNull
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.models.UpdateWalletError
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
|
@ -15,22 +14,17 @@ import com.tangem.domain.wallets.models.UserWalletId
|
|||
/**
|
||||
* Use case for updating user wallet
|
||||
*
|
||||
* @property walletsStateHolder state holder for getting static initialized 'userWalletsListManager'
|
||||
* @property userWalletsListManager user wallets list manager
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class UpdateWalletUseCase(private val walletsStateHolder: WalletsStateHolder) {
|
||||
class UpdateWalletUseCase(private val userWalletsListManager: UserWalletsListManager) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
update: suspend (UserWallet) -> UserWallet,
|
||||
): Either<UpdateWalletError, UserWallet> {
|
||||
return either {
|
||||
val userWalletsListManager = ensureUserWalletListManagerNotNull(
|
||||
walletsStateHolder = walletsStateHolder,
|
||||
raise = { UpdateWalletError.DataError },
|
||||
)
|
||||
|
||||
userWalletsListManager.update(userWalletId, update)
|
||||
.doOnSuccess { return it.right() }
|
||||
.doOnFailure { return UpdateWalletError.DataError.left() }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue