Updated on 2026-08-14
This commit is contained in:
parent
53b02cfb10
commit
2be7445212
81 changed files with 4444 additions and 3434 deletions
|
|
@ -2,6 +2,7 @@ package com.tangem.domain.express.models
|
|||
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
sealed class ExpressError : Throwable() {
|
||||
|
||||
abstract val code: Int
|
||||
|
|
@ -61,4 +62,12 @@ sealed class ExpressError : Throwable() {
|
|||
data object UnknownError : ExpressError() {
|
||||
override val code: Int = -1
|
||||
}
|
||||
|
||||
data class TooLargeSolanaTransactionError(override val code: Int = -2) : ExpressError() {
|
||||
override val message: String = "tooLargeSolanaTransaction"
|
||||
}
|
||||
|
||||
data class DexActiveSupplyError(override val code: Int = -3) : ExpressError() {
|
||||
override val message: String = "dexActiveSupplyError"
|
||||
}
|
||||
}
|
||||
|
|
@ -23,14 +23,18 @@ enum class ExpressProviderType(val typeName: String) {
|
|||
ONRAMP(typeName = "ONRAMP"),
|
||||
;
|
||||
|
||||
fun shouldStoreSwapTransaction() = when (this) {
|
||||
CEX,
|
||||
DEX_BRIDGE,
|
||||
DEX,
|
||||
-> true
|
||||
ONRAMP,
|
||||
-> false
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun ExpressProviderType.shouldStoreSwapTransaction() = when (this) {
|
||||
CEX,
|
||||
DEX_BRIDGE,
|
||||
DEX,
|
||||
-> true
|
||||
ONRAMP,
|
||||
-> false
|
||||
fun getSwapProviderTypes(): List<ExpressProviderType> {
|
||||
return listOf(CEX, DEX, DEX_BRIDGE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -31,11 +31,22 @@ interface RampStateManager {
|
|||
sendUnavailabilityReason: ScenarioUnavailabilityReason?,
|
||||
): Either<ScenarioUnavailabilityReason, Unit>
|
||||
|
||||
/**
|
||||
* Check if [CryptoCurrency] is available for swap (express/assets request)
|
||||
*
|
||||
* @param userWalletId the ID of the user's wallet
|
||||
* @param cryptoCurrency cryptocurrency
|
||||
*/
|
||||
suspend fun availableForSwap(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): ScenarioUnavailabilityReason
|
||||
|
||||
suspend fun availableForSwap(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
): Map<CryptoCurrency, ScenarioUnavailabilityReason>
|
||||
|
||||
suspend fun fetchSellServiceData()
|
||||
|
||||
fun getSellInitializationStatus(): Flow<Lce<Throwable, Any>>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.domain.swap.models
|
||||
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
/**
|
||||
* Represents the status of a cryptocurrency in the context of a swap operation.
|
||||
*
|
||||
* Combines the [UserWallet], [CryptoCurrencyStatus], and [Account] to provide
|
||||
* all necessary information about a currency participating in a swap.
|
||||
*
|
||||
* @property userWallet the user wallet that owns the currency
|
||||
* @property status the current status of the cryptocurrency, including balance and value state
|
||||
* @property account the account within the wallet that holds the currency
|
||||
* @property currency shortcut to the [CryptoCurrency] from [status]
|
||||
* @property userWalletId shortcut to the wallet ID from [userWallet]
|
||||
* @property isAvailableForSwap whether this currency can participate in a swap operation,
|
||||
* determined by [RampStateManager][com.tangem.domain.exchange.RampStateManager]
|
||||
*/
|
||||
data class SwapCurrencyStatus(
|
||||
val userWallet: UserWallet,
|
||||
val status: CryptoCurrencyStatus,
|
||||
val account: Account,
|
||||
val isAvailableForSwap: Boolean = true,
|
||||
) {
|
||||
val currency: CryptoCurrency
|
||||
get() = status.currency
|
||||
val userWalletId: UserWalletId
|
||||
get() = userWallet.walletId
|
||||
}
|
||||
|
|
@ -9,7 +9,8 @@ import java.math.BigDecimal
|
|||
* List of saved swap transactions
|
||||
*/
|
||||
data class SwapTransactionListModel(
|
||||
val userWalletId: String,
|
||||
val fromUserWalletId: String,
|
||||
val toUserWalletId: String,
|
||||
val fromCryptoCurrencyId: String,
|
||||
val toCryptoCurrencyId: String,
|
||||
val fromCryptoCurrency: CryptoCurrency,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,21 @@ import java.math.BigDecimal
|
|||
@Suppress("LongParameterList")
|
||||
interface SwapRepositoryV2 {
|
||||
|
||||
/**
|
||||
* Returns express swap pairs for a specific primary and secondary currency.
|
||||
*
|
||||
* @param primarySwapCurrencyStatus primary currency status participating in the swap
|
||||
* @param secondarySwapCurrencyStatus secondary currency status participating in the swap
|
||||
* @param filterProviderTypes filters only specified provider types, if empty returns providers as is
|
||||
* @param swapTxType swap tx type
|
||||
*/
|
||||
suspend fun getPairs(
|
||||
primarySwapCurrencyStatus: SwapCurrencyStatus,
|
||||
secondarySwapCurrencyStatus: SwapCurrencyStatus,
|
||||
filterProviderTypes: List<ExpressProviderType>,
|
||||
swapTxType: SwapTxType,
|
||||
): List<SwapPairModel>
|
||||
|
||||
/**
|
||||
* Express swap pairs, both direct and reversed
|
||||
*
|
||||
|
|
@ -84,7 +99,7 @@ interface SwapRepositoryV2 {
|
|||
userWallet: UserWallet,
|
||||
fromCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
toCryptoCurrency: CryptoCurrency,
|
||||
amount: String,
|
||||
amount: BigDecimal,
|
||||
amountType: SwapAmountType,
|
||||
toAddress: String,
|
||||
toExtraId: String?,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@ interface SwapTransactionRepository {
|
|||
/**
|
||||
* Store new swap transaction
|
||||
*
|
||||
* @param userWalletId selected user wallet id
|
||||
* @param fromUserWalletId wallet id swap from
|
||||
* @param toUserWalletId wallet id swap to
|
||||
* @param fromCryptoCurrency currency swap from
|
||||
* @param toCryptoCurrency currency swap to
|
||||
* @param fromAccount account swap from
|
||||
|
|
@ -27,7 +28,8 @@ interface SwapTransactionRepository {
|
|||
*/
|
||||
@Suppress("LongParameterList")
|
||||
suspend fun storeTransaction(
|
||||
userWalletId: UserWalletId,
|
||||
fromUserWalletId: UserWalletId,
|
||||
toUserWalletId: UserWalletId,
|
||||
fromCryptoCurrency: CryptoCurrency,
|
||||
toCryptoCurrency: CryptoCurrency,
|
||||
fromAccount: Account?,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.tangem.domain.swap.SwapErrorResolver
|
|||
import com.tangem.domain.swap.SwapRepositoryV2
|
||||
import com.tangem.domain.swap.models.SwapAmountType
|
||||
import com.tangem.domain.swap.models.SwapDataModel
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
class GetSwapDataUseCase(
|
||||
|
|
@ -22,7 +23,7 @@ class GetSwapDataUseCase(
|
|||
suspend operator fun invoke(
|
||||
userWallet: UserWallet,
|
||||
fromCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
amount: String,
|
||||
amount: BigDecimal,
|
||||
amountType: SwapAmountType,
|
||||
toCryptoCurrency: CryptoCurrency,
|
||||
toAddress: String,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
package com.tangem.domain.swap.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.express.models.ExpressProviderType
|
||||
import com.tangem.domain.swap.SwapErrorResolver
|
||||
import com.tangem.domain.swap.SwapRepositoryV2
|
||||
import com.tangem.domain.swap.models.SwapCurrencyStatus
|
||||
import com.tangem.domain.swap.models.SwapTxType
|
||||
|
||||
/**
|
||||
* Use case for retrieving swap pairs between a specific primary and secondary currency.
|
||||
*
|
||||
* Returns either a list of available swap pairs or a resolved swap error.
|
||||
*
|
||||
* @property swapRepositoryV2 repository providing swap pair data
|
||||
* @property swapErrorResolver resolver that maps exceptions to domain swap errors
|
||||
*/
|
||||
class GetSwapPairUseCase(
|
||||
private val swapRepositoryV2: SwapRepositoryV2,
|
||||
private val swapErrorResolver: SwapErrorResolver,
|
||||
) {
|
||||
suspend operator fun invoke(
|
||||
primarySwapCurrencyStatus: SwapCurrencyStatus,
|
||||
secondarySwapCurrencyStatus: SwapCurrencyStatus,
|
||||
filterProviderTypes: List<ExpressProviderType>,
|
||||
swapTxType: SwapTxType,
|
||||
) = Either.catch {
|
||||
swapRepositoryV2.getPairs(
|
||||
primarySwapCurrencyStatus = primarySwapCurrencyStatus,
|
||||
secondarySwapCurrencyStatus = secondarySwapCurrencyStatus,
|
||||
filterProviderTypes = filterProviderTypes,
|
||||
swapTxType = swapTxType,
|
||||
)
|
||||
}.mapLeft(swapErrorResolver::resolve)
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ package com.tangem.domain.swap.usecase
|
|||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.express.models.ExpressProvider
|
||||
import com.tangem.domain.express.models.ExpressProviderType.Companion.shouldStoreSwapTransaction
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
|
|
@ -19,7 +18,8 @@ class SwapTransactionSentUseCase(
|
|||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
userWallet: UserWallet,
|
||||
fromUserWallet: UserWallet,
|
||||
toUserWallet: UserWallet,
|
||||
fromCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
toCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
fromAccount: Account?,
|
||||
|
|
@ -33,7 +33,8 @@ class SwapTransactionSentUseCase(
|
|||
) = Either.catch {
|
||||
if (provider.type.shouldStoreSwapTransaction()) {
|
||||
swapTransactionRepository.storeTransaction(
|
||||
userWalletId = userWallet.walletId,
|
||||
fromUserWalletId = fromUserWallet.walletId,
|
||||
toUserWalletId = toUserWallet.walletId,
|
||||
fromCryptoCurrency = fromCryptoCurrencyStatus.currency,
|
||||
toCryptoCurrency = toCryptoCurrencyStatus.currency,
|
||||
fromAccount = fromAccount,
|
||||
|
|
@ -58,11 +59,11 @@ class SwapTransactionSentUseCase(
|
|||
}
|
||||
|
||||
swapTransactionRepository.storeLastSwappedCryptoCurrencyId(
|
||||
userWalletId = userWallet.walletId,
|
||||
userWalletId = fromUserWallet.walletId,
|
||||
cryptoCurrencyId = toCryptoCurrencyStatus.currency.id,
|
||||
)
|
||||
swapRepositoryV2.swapTransactionSent(
|
||||
userWallet = userWallet,
|
||||
userWallet = fromUserWallet,
|
||||
fromCryptoCurrencyStatus = fromCryptoCurrencyStatus,
|
||||
payInAddress = payInAddress,
|
||||
txId = swapDataTransactionModel.txId,
|
||||
|
|
|
|||
|
|
@ -3,14 +3,11 @@ package com.tangem.domain.tokens.actions
|
|||
import com.tangem.domain.exchange.RampStateManager
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||
import com.tangem.domain.tokens.model.TokenActionsState.ActionState
|
||||
import com.tangem.domain.transaction.models.AssetRequirementsCondition
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.yield.supply.models.YieldSupplyAvailability
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
|
||||
|
|
@ -65,20 +62,6 @@ internal class CommonActionsFactory(
|
|||
getSendUnavailabilityReason(userWalletId = userWallet.walletId, cryptoCurrencyStatus = cryptoCurrencyStatus)
|
||||
}
|
||||
|
||||
val swapUnavailabilityReason = if (!cryptoCurrencyStatus.currency.isCustom &&
|
||||
cryptoCurrencyStatus.value !is CryptoCurrencyStatus.NoQuote
|
||||
) {
|
||||
async {
|
||||
getSwapUnavailabilityReason(
|
||||
userWalletId = userWallet.walletId,
|
||||
currencyStatus = cryptoCurrencyStatus,
|
||||
requirementsDeferred = requirementsDeferred,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val hideTokenUnavailabilityReason = getTokenHideUnavailabilityReason(userWallet)
|
||||
|
||||
actionAvailabilityBuilder {
|
||||
|
|
@ -111,7 +94,6 @@ internal class CommonActionsFactory(
|
|||
createSwapAction(
|
||||
userWallet = userWallet,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
swapUnavailableReasonDeferred = swapUnavailabilityReason,
|
||||
shouldShowSwapStories = shouldShowSwapStories,
|
||||
).addByReason()
|
||||
// endregion
|
||||
|
|
@ -140,11 +122,9 @@ internal class CommonActionsFactory(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("CanBeNonNullable")
|
||||
private suspend fun createSwapAction(
|
||||
private fun createSwapAction(
|
||||
userWallet: UserWallet,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
swapUnavailableReasonDeferred: Deferred<ScenarioUnavailabilityReason>?,
|
||||
shouldShowSwapStories: Boolean,
|
||||
): ActionState {
|
||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||
|
|
@ -172,35 +152,11 @@ internal class CommonActionsFactory(
|
|||
)
|
||||
}
|
||||
else -> {
|
||||
val reason = requireNotNull(swapUnavailableReasonDeferred) {
|
||||
"swapUnavailableReasonDeferred must not be null for available swap action"
|
||||
}.await()
|
||||
|
||||
return ActionState.Swap(
|
||||
unavailabilityReason = reason,
|
||||
shouldShowBadge = reason == ScenarioUnavailabilityReason.None && shouldShowSwapStories,
|
||||
ActionState.Swap(
|
||||
unavailabilityReason = ScenarioUnavailabilityReason.None,
|
||||
shouldShowBadge = shouldShowSwapStories,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getSwapUnavailabilityReason(
|
||||
userWalletId: UserWalletId,
|
||||
currencyStatus: CryptoCurrencyStatus,
|
||||
requirementsDeferred: Deferred<AssetRequirementsCondition?>?,
|
||||
): ScenarioUnavailabilityReason {
|
||||
val swapUnavailabilityReason = rampStateManager
|
||||
.availableForSwap(userWalletId = userWalletId, cryptoCurrency = currencyStatus.currency)
|
||||
val shouldCheckAssetRequirements =
|
||||
swapUnavailabilityReason == ScenarioUnavailabilityReason.None && requirementsDeferred != null
|
||||
|
||||
val yieldSupplyStatus = currencyStatus.value.yieldSupplyStatus
|
||||
val isUnavailableByYieldSupply = yieldSupplyStatus?.isAllowedToSpend == false && yieldSupplyStatus.isActive
|
||||
|
||||
return when {
|
||||
isUnavailableByYieldSupply -> ScenarioUnavailabilityReason.YieldSupplyApprovalRequired
|
||||
shouldCheckAssetRequirements -> getReceiveScenario(requirementsDeferred.await())
|
||||
else -> swapUnavailabilityReason
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,13 +9,32 @@ import com.tangem.domain.models.currency.CryptoCurrency
|
|||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
|
||||
class GetPaymentAccountCryptoCurrencyStatusUseCase(
|
||||
private val paymentAccountStatusSupplier: PaymentAccountStatusSupplier,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): Flow<Pair<Account.Payment, CryptoCurrencyStatus>> {
|
||||
return paymentAccountStatusSupplier(userWalletId).mapNotNull { accountStatus ->
|
||||
val cryptoCurrencyStatus = when (val statusValue = accountStatus.value) {
|
||||
is PaymentAccountStatusValue.Loaded -> statusValue.cryptoCurrencyStatus
|
||||
else -> return@mapNotNull null
|
||||
}
|
||||
if (cryptoCurrencyStatus.currency == cryptoCurrency) {
|
||||
accountStatus.account to cryptoCurrencyStatus
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun invokeSync(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): Option<Pair<Account.Payment, CryptoCurrencyStatus>> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue