Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-26 19:10:22 +05:00
parent 3fd8b110ef
commit 1d06981060
19 changed files with 190 additions and 42 deletions

View file

@ -1,6 +1,10 @@
package com.tangem.data.walletmanager.utils package com.tangem.data.walletmanager.utils
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
import com.tangem.blockchain.common.* import com.tangem.blockchain.common.*
import com.tangem.blockchain.yieldsupply.providers.ethereum.factory.EthereumYieldSupplyDeployCallData
import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyEnterCallData
import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyExitCallData
import com.tangem.blockchainsdk.models.UpdateWalletManagerResult.Address import com.tangem.blockchainsdk.models.UpdateWalletManagerResult.Address
import com.tangem.domain.models.network.TxInfo import com.tangem.domain.models.network.TxInfo
import com.tangem.utils.converter.Converter import com.tangem.utils.converter.Converter
@ -40,7 +44,18 @@ internal class TransactionDataToTxHistoryItemConverter(
TransactionStatus.Confirmed -> TxInfo.TransactionStatus.Confirmed TransactionStatus.Confirmed -> TxInfo.TransactionStatus.Confirmed
TransactionStatus.Unconfirmed -> TxInfo.TransactionStatus.Unconfirmed TransactionStatus.Unconfirmed -> TxInfo.TransactionStatus.Unconfirmed
}, },
type = TxInfo.TransactionType.Transfer, type = when (val extras = value.extras) {
is EthereumTransactionExtras -> {
when (extras.callData) {
is EthereumYieldSupplyDeployCallData,
is EthereumYieldSupplyEnterCallData,
-> TxInfo.TransactionType.YieldSupply.Enter
is EthereumYieldSupplyExitCallData -> TxInfo.TransactionType.YieldSupply.Exit
else -> TxInfo.TransactionType.Transfer
}
}
else -> TxInfo.TransactionType.Transfer
},
amount = amount, amount = amount,
) )
} }

View file

@ -12,6 +12,7 @@ import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.yield.supply.YieldSupplyStatus
import com.tangem.domain.utils.convertToSdkAmount import com.tangem.domain.utils.convertToSdkAmount
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.yield.supply.YieldSupplyTransactionRepository import com.tangem.domain.yield.supply.YieldSupplyTransactionRepository
@ -56,7 +57,7 @@ internal class DefaultYieldSupplyTransactionRepository(
return buildEnterTransactions( return buildEnterTransactions(
walletManager = walletManager, walletManager = walletManager,
cryptoCurrencyStatus = cryptoCurrencyStatus, cryptoCurrencyStatus = cryptoCurrencyStatus,
existingYieldContractAddress = existingYieldContractAddress, existingYieldAddress = existingYieldContractAddress,
calculatedYieldContractAddress = calculatedYieldContractAddress, calculatedYieldContractAddress = calculatedYieldContractAddress,
maxNetworkFee = maxNetworkFee, maxNetworkFee = maxNetworkFee,
) )
@ -109,21 +110,23 @@ internal class DefaultYieldSupplyTransactionRepository(
} }
@Suppress("LongParameterList") @Suppress("LongParameterList")
private fun buildEnterTransactions( private suspend fun buildEnterTransactions(
walletManager: WalletManager, walletManager: WalletManager,
cryptoCurrencyStatus: CryptoCurrencyStatus, cryptoCurrencyStatus: CryptoCurrencyStatus,
existingYieldContractAddress: String?, existingYieldAddress: String?,
calculatedYieldContractAddress: String, calculatedYieldContractAddress: String,
maxNetworkFee: Amount, maxNetworkFee: Amount,
): MutableList<TransactionData.Uncompiled> { ): MutableList<TransactionData.Uncompiled> {
val enterTransactions = mutableListOf<TransactionData.Uncompiled>() val enterTransactions = mutableListOf<TransactionData.Uncompiled>()
val cryptoCurrency = cryptoCurrencyStatus.currency as CryptoCurrency.Token val cryptoCurrency = cryptoCurrencyStatus.currency as CryptoCurrency.Token
val yieldSupplyStatus = cryptoCurrencyStatus.value.yieldSupplyStatus val yieldSupplyStatus = getYieldTokenStatus(walletManager, cryptoCurrency)
val amount = BigDecimal.ZERO.convertToSdkAmount(cryptoCurrencyStatus) val amount = getEnterAmount(cryptoCurrency, yieldSupplyStatus)
val emptyContractAddress = existingYieldAddress == null || existingYieldAddress == EthereumUtils.ZERO_ADDRESS
when { when {
existingYieldContractAddress == null || existingYieldContractAddress == EthereumUtils.ZERO_ADDRESS -> { yieldSupplyStatus == null || emptyContractAddress -> {
enterTransactions.add( enterTransactions.add(
createDeployTransaction( createDeployTransaction(
walletManager = walletManager, walletManager = walletManager,
@ -133,7 +136,6 @@ internal class DefaultYieldSupplyTransactionRepository(
), ),
) )
} }
yieldSupplyStatus == null -> error("Yield token status is null")
!yieldSupplyStatus.isInitialized -> enterTransactions.add( !yieldSupplyStatus.isInitialized -> enterTransactions.add(
createInitTokenTransaction( createInitTokenTransaction(
walletManager = walletManager, walletManager = walletManager,
@ -211,6 +213,28 @@ internal class DefaultYieldSupplyTransactionRepository(
}.onFailure(Timber::e).getOrNull() }.onFailure(Timber::e).getOrNull()
} }
private suspend fun getYieldTokenStatus(
walletManager: WalletManager,
cryptoCurrency: CryptoCurrency.Token,
): YieldSupplyStatus? = withContext(dispatchers.io) {
runCatching {
val sdkSupplyStatus = walletManager.getYieldSupplyStatus(cryptoCurrency.contractAddress)
val isAllowedToSpend = walletManager.isAllowedToSpend(
Token(
symbol = cryptoCurrency.symbol,
contractAddress = cryptoCurrency.contractAddress,
decimals = cryptoCurrency.decimals,
),
)
YieldSupplyStatus(
isActive = sdkSupplyStatus?.isActive == true,
isInitialized = sdkSupplyStatus?.isInitialized == true,
isAllowedToSpend = isAllowedToSpend,
)
}.onFailure(Timber::e).getOrNull()
}
private fun createDeployTransaction( private fun createDeployTransaction(
walletManager: WalletManager, walletManager: WalletManager,
cryptoCurrency: CryptoCurrency.Token, cryptoCurrency: CryptoCurrency.Token,
@ -356,4 +380,20 @@ internal class DefaultYieldSupplyTransactionRepository(
else -> error("Data extras not supported for $blockchain") else -> error("Data extras not supported for $blockchain")
} }
} }
private fun getEnterAmount(cryptoCurrency: CryptoCurrency.Token, yieldSupplyStatus: YieldSupplyStatus?) = Amount(
currencySymbol = cryptoCurrency.symbol,
value = BigDecimal.ZERO,
decimals = cryptoCurrency.decimals,
type = AmountType.TokenYieldSupply(
token = Token(
symbol = cryptoCurrency.symbol,
contractAddress = cryptoCurrency.contractAddress,
decimals = cryptoCurrency.decimals,
),
isActive = yieldSupplyStatus?.isActive ?: false,
isInitialized = yieldSupplyStatus?.isInitialized ?: false,
isAllowedToSpend = yieldSupplyStatus?.isAllowedToSpend ?: false,
),
)
} }

View file

@ -12,7 +12,6 @@ import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.yield.supply.YieldSupplyStatus
import com.tangem.domain.utils.convertToSdkAmount import com.tangem.domain.utils.convertToSdkAmount
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
@ -69,10 +68,10 @@ class DefaultYieldSupplyTransactionRepositoryTest {
@Test @Test
fun `createEnterTransactions returns deploy-approve-enter transactions`() = runTest { fun `createEnterTransactions returns deploy-approve-enter transactions`() = runTest {
coEvery { walletManager.getYieldContract() } returns EthereumUtils.ZERO_ADDRESS coEvery { walletManager.getYieldModuleAddress() } returns EthereumUtils.ZERO_ADDRESS
coEvery { walletManager.getYieldSupplyStatus(any()) } returns null coEvery { walletManager.getYieldSupplyStatus(any()) } returns null
coEvery { walletManager.isAllowedToSpend(any()) } returns false coEvery { walletManager.isAllowedToSpend(any()) } returns false
coEvery { walletManager.calculateYieldContract() } returns yieldContractAddress coEvery { walletManager.calculateYieldModuleAddress() } returns yieldContractAddress
val result = repository.createEnterTransactions( val result = repository.createEnterTransactions(
userWalletId = userWalletId, userWalletId = userWalletId,
@ -119,8 +118,8 @@ class DefaultYieldSupplyTransactionRepositoryTest {
@Test @Test
fun `createEnterTransactions returns init-approve-enter transactions`() = runTest { fun `createEnterTransactions returns init-approve-enter transactions`() = runTest {
coEvery { walletManager.getYieldContract() } returns yieldContractAddress coEvery { walletManager.getYieldModuleAddress() } returns yieldContractAddress
coEvery { walletManager.calculateYieldContract() } returns yieldContractAddress coEvery { walletManager.calculateYieldModuleAddress() } returns yieldContractAddress
coEvery { walletManager.isAllowedToSpend(any()) } returns false coEvery { walletManager.isAllowedToSpend(any()) } returns false
coEvery { walletManager.getYieldSupplyStatus(any()) } returns SDKYieldSupplyStatus( coEvery { walletManager.getYieldSupplyStatus(any()) } returns SDKYieldSupplyStatus(
isActive = false, isActive = false,
@ -172,8 +171,8 @@ class DefaultYieldSupplyTransactionRepositoryTest {
@Test @Test
fun `createEnterTransactions returns reactivate-approve-enter transactions`() = runTest { fun `createEnterTransactions returns reactivate-approve-enter transactions`() = runTest {
coEvery { walletManager.getYieldContract() } returns yieldContractAddress coEvery { walletManager.getYieldModuleAddress() } returns yieldContractAddress
coEvery { walletManager.calculateYieldContract() } returns yieldContractAddress coEvery { walletManager.calculateYieldModuleAddress() } returns yieldContractAddress
coEvery { walletManager.isAllowedToSpend(any()) } returns false coEvery { walletManager.isAllowedToSpend(any()) } returns false
coEvery { walletManager.getYieldSupplyStatus(any()) } returns SDKYieldSupplyStatus( coEvery { walletManager.getYieldSupplyStatus(any()) } returns SDKYieldSupplyStatus(
isActive = false, isActive = false,
@ -225,8 +224,8 @@ class DefaultYieldSupplyTransactionRepositoryTest {
@Test @Test
fun `createEnterTransactions returns reactivate-enter transactions`() = runTest { fun `createEnterTransactions returns reactivate-enter transactions`() = runTest {
coEvery { walletManager.getYieldContract() } returns yieldContractAddress coEvery { walletManager.getYieldModuleAddress() } returns yieldContractAddress
coEvery { walletManager.calculateYieldContract() } returns yieldContractAddress coEvery { walletManager.calculateYieldModuleAddress() } returns yieldContractAddress
coEvery { walletManager.getYieldSupplyStatus(any()) } returns SDKYieldSupplyStatus( coEvery { walletManager.getYieldSupplyStatus(any()) } returns SDKYieldSupplyStatus(
isActive = false, isActive = false,
isInitialized = true, isInitialized = true,
@ -266,8 +265,8 @@ class DefaultYieldSupplyTransactionRepositoryTest {
@Test @Test
fun `createEnterTransactions returns enter transactions`() = runTest { fun `createEnterTransactions returns enter transactions`() = runTest {
coEvery { walletManager.getYieldContract() } returns yieldContractAddress coEvery { walletManager.getYieldModuleAddress() } returns yieldContractAddress
coEvery { walletManager.calculateYieldContract() } returns yieldContractAddress coEvery { walletManager.calculateYieldModuleAddress() } returns yieldContractAddress
coEvery { walletManager.getYieldSupplyStatus(any()) } returns SDKYieldSupplyStatus( coEvery { walletManager.getYieldSupplyStatus(any()) } returns SDKYieldSupplyStatus(
isActive = true, isActive = true,
isInitialized = true, isInitialized = true,
@ -299,9 +298,7 @@ class DefaultYieldSupplyTransactionRepositoryTest {
val expectedCallData = val expectedCallData =
YieldSupplyContractCallDataProviderFactory.getExitCallData(mockedContractAddress) YieldSupplyContractCallDataProviderFactory.getExitCallData(mockedContractAddress)
val yieldSupplyStatus = mockk<YieldSupplyStatus>(relaxed = true) val result = repository.createExitTransaction(userWalletId, cryptoCurrencyStatus, null)
val result = repository.createExitTransaction(userWalletId, cryptoCurrency, yieldSupplyStatus, null)
Truth.assertThat(result).isNotNull() Truth.assertThat(result).isNotNull()
Truth.assertThat(result.extras).isInstanceOf(EthereumTransactionExtras::class.java) Truth.assertThat(result.extras).isInstanceOf(EthereumTransactionExtras::class.java)

View file

@ -107,6 +107,16 @@ data class TxInfo(
@Serializable @Serializable
data class Operation(val name: String) : TransactionType data class Operation(val name: String) : TransactionType
@Serializable
sealed interface YieldSupply : TransactionType {
@Serializable
data object Enter : YieldSupply
@Serializable
data object Exit : YieldSupply
}
@Serializable @Serializable
sealed interface Staking : TransactionType { sealed interface Staking : TransactionType {

View file

@ -1,11 +1,11 @@
package com.tangem.features.kyc package com.tangem.features.kyc
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.AppComponentContext
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import androidx.compose.ui.Modifier
/** /**
* Mocking it for release/external builds to exclude SumSub dependency * Mocking it for release/external builds to exclude SumSub dependency

View file

@ -53,6 +53,7 @@ internal class TxHistoryItemToTransactionStateConverter(
is TxInfo.TransactionType.Operation, is TxInfo.TransactionType.Operation,
is TxInfo.TransactionType.Swap, is TxInfo.TransactionType.Swap,
is TxInfo.TransactionType.Transfer, is TxInfo.TransactionType.Transfer,
is TxInfo.TransactionType.YieldSupply,
is TxInfo.TransactionType.UnknownOperation, is TxInfo.TransactionType.UnknownOperation,
-> if (isOutgoing) R.drawable.ic_arrow_up_24 else R.drawable.ic_arrow_down_24 -> if (isOutgoing) R.drawable.ic_arrow_up_24 else R.drawable.ic_arrow_down_24
} }
@ -62,7 +63,9 @@ internal class TxHistoryItemToTransactionStateConverter(
is TxInfo.TransactionType.Approve -> resourceReference(R.string.common_approval) is TxInfo.TransactionType.Approve -> resourceReference(R.string.common_approval)
is TxInfo.TransactionType.Operation -> stringReference(type.name) is TxInfo.TransactionType.Operation -> stringReference(type.name)
is TxInfo.TransactionType.Swap -> resourceReference(R.string.common_swap) is TxInfo.TransactionType.Swap -> resourceReference(R.string.common_swap)
is TxInfo.TransactionType.Transfer -> resourceReference(R.string.common_transfer) is TxInfo.TransactionType.YieldSupply,
is TxInfo.TransactionType.Transfer,
-> resourceReference(R.string.common_transfer)
is TxInfo.TransactionType.Staking.Stake -> resourceReference(R.string.common_stake) is TxInfo.TransactionType.Staking.Stake -> resourceReference(R.string.common_stake)
is TxInfo.TransactionType.Staking.Unstake -> resourceReference(R.string.common_unstake) is TxInfo.TransactionType.Staking.Unstake -> resourceReference(R.string.common_unstake)
is TxInfo.TransactionType.Staking.Vote -> resourceReference(R.string.staking_vote) is TxInfo.TransactionType.Staking.Vote -> resourceReference(R.string.staking_vote)

View file

@ -61,6 +61,7 @@ internal class TxHistoryItemStateConverter(
is TransactionType.Operation, is TransactionType.Operation,
is TransactionType.Swap, is TransactionType.Swap,
is TransactionType.Transfer, is TransactionType.Transfer,
is TransactionType.YieldSupply,
is TransactionType.UnknownOperation, is TransactionType.UnknownOperation,
-> if (isOutgoing) R.drawable.ic_arrow_up_24 else R.drawable.ic_arrow_down_24 -> if (isOutgoing) R.drawable.ic_arrow_up_24 else R.drawable.ic_arrow_down_24
} }
@ -70,7 +71,9 @@ internal class TxHistoryItemStateConverter(
is TransactionType.Approve -> resourceReference(R.string.common_approval) is TransactionType.Approve -> resourceReference(R.string.common_approval)
is TransactionType.Operation -> stringReference(type.name) is TransactionType.Operation -> stringReference(type.name)
is TransactionType.Swap -> resourceReference(R.string.common_swap) is TransactionType.Swap -> resourceReference(R.string.common_swap)
is TransactionType.Transfer -> resourceReference(R.string.common_transfer) is TransactionType.YieldSupply,
is TransactionType.Transfer,
-> resourceReference(R.string.common_transfer)
is TransactionType.Staking.Stake -> resourceReference(R.string.common_stake) is TransactionType.Staking.Stake -> resourceReference(R.string.common_stake)
is TransactionType.Staking.Unstake -> resourceReference(R.string.common_unstake) is TransactionType.Staking.Unstake -> resourceReference(R.string.common_unstake)
is TransactionType.Staking.Vote -> resourceReference(R.string.staking_vote) is TransactionType.Staking.Vote -> resourceReference(R.string.staking_vote)

View file

@ -26,4 +26,5 @@ internal data class YieldSupplyActionUM(
val currencyIconState: CurrencyIconState, val currencyIconState: CurrencyIconState,
val yieldSupplyFeeUM: YieldSupplyFeeUM, val yieldSupplyFeeUM: YieldSupplyFeeUM,
val isPrimaryButtonEnabled: Boolean, val isPrimaryButtonEnabled: Boolean,
val isTransactionSending: Boolean,
) )

View file

@ -0,0 +1,13 @@
package com.tangem.features.yield.supply.impl.common.entity.transformer
import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyActionUM
import com.tangem.utils.transformer.Transformer
internal object YieldSupplyTransactionInProgressTransformer : Transformer<YieldSupplyActionUM> {
override fun transform(prevState: YieldSupplyActionUM): YieldSupplyActionUM {
return prevState.copy(
isPrimaryButtonEnabled = false,
isTransactionSending = true,
)
}
}

View file

@ -0,0 +1,13 @@
package com.tangem.features.yield.supply.impl.common.entity.transformer
import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyActionUM
import com.tangem.utils.transformer.Transformer
internal object YieldSupplyTransactionReadyTransformer : Transformer<YieldSupplyActionUM> {
override fun transform(prevState: YieldSupplyActionUM): YieldSupplyActionUM {
return prevState.copy(
isPrimaryButtonEnabled = true,
isTransactionSending = false,
)
}
}

View file

@ -143,6 +143,7 @@ private class YieldSupplyActionContentPreviewProvider : PreviewParameterProvider
maxNetworkFeeValue = stringReference("8.50 USDT • \$8.50"), maxNetworkFeeValue = stringReference("8.50 USDT • \$8.50"),
), ),
isPrimaryButtonEnabled = false, isPrimaryButtonEnabled = false,
isTransactionSending = false,
), ),
) )
} }

View file

@ -20,5 +20,9 @@ internal sealed class YieldSupplyUM {
val isAllowedToSpend: Boolean, val isAllowedToSpend: Boolean,
) : YieldSupplyUM() ) : YieldSupplyUM()
data object Processing : YieldSupplyUM() @Immutable
sealed class Processing : YieldSupplyUM() {
data object Enter : Processing()
data object Exit : Processing()
}
} }

View file

@ -11,19 +11,25 @@ import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.TxInfo
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.tokens.FetchCurrencyStatusUseCase
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.features.yield.supply.api.YieldSupplyComponent import com.tangem.features.yield.supply.api.YieldSupplyComponent
import com.tangem.features.yield.supply.impl.R import com.tangem.features.yield.supply.impl.R
import com.tangem.features.yield.supply.impl.main.entity.YieldSupplyUM import com.tangem.features.yield.supply.impl.main.entity.YieldSupplyUM
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.DelayedWork
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import kotlin.properties.Delegates import kotlin.properties.Delegates
@Suppress("LongParameterList")
@ModelScoped @ModelScoped
internal class YieldSupplyModel @Inject constructor( internal class YieldSupplyModel @Inject constructor(
paramsContainer: ParamsContainer, paramsContainer: ParamsContainer,
@ -31,6 +37,8 @@ internal class YieldSupplyModel @Inject constructor(
private val appRouter: AppRouter, private val appRouter: AppRouter,
private val getUserWalletUseCase: GetUserWalletUseCase, private val getUserWalletUseCase: GetUserWalletUseCase,
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase, private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
@DelayedWork private val coroutineScope: CoroutineScope,
) : Model(), YieldSupplyClickIntents { ) : Model(), YieldSupplyClickIntents {
private val params = paramsContainer.require<YieldSupplyComponent.Params>() private val params = paramsContainer.require<YieldSupplyComponent.Params>()
@ -100,9 +108,22 @@ internal class YieldSupplyModel @Inject constructor(
private fun onDataLoaded(cryptoCurrencyStatus: CryptoCurrencyStatus) { private fun onDataLoaded(cryptoCurrencyStatus: CryptoCurrencyStatus) {
val yieldSupplyStatus = cryptoCurrencyStatus.value.yieldSupplyStatus val yieldSupplyStatus = cryptoCurrencyStatus.value.yieldSupplyStatus
val hasActiveTransaction = cryptoCurrencyStatus.value.hasCurrentNetworkTransactions
val yieldTransaction = cryptoCurrencyStatus.value.pendingTransactions.firstOrNull {
it.type is TxInfo.TransactionType.YieldSupply
}?.type as? TxInfo.TransactionType.YieldSupply
// todo yield supply add processing state
val yieldSupplyUM = when { val yieldSupplyUM = when {
hasActiveTransaction && yieldTransaction != null -> {
coroutineScope.launch(dispatchers.io) {
delay(PROCESSING_UPDATE_DELAY)
fetchCurrencyStatusUseCase(userWalletId = userWallet.walletId, cryptoCurrency.id)
}
when (yieldTransaction) {
TxInfo.TransactionType.YieldSupply.Enter -> YieldSupplyUM.Processing.Enter
TxInfo.TransactionType.YieldSupply.Exit -> YieldSupplyUM.Processing.Exit
}
}
yieldSupplyStatus?.isActive == true -> yieldSupplyStatus?.isActive == true ->
YieldSupplyUM.Content( YieldSupplyUM.Content(
rewardsBalance = TextReference.EMPTY, rewardsBalance = TextReference.EMPTY,
@ -121,4 +142,8 @@ internal class YieldSupplyModel @Inject constructor(
uiState.update { yieldSupplyUM } uiState.update { yieldSupplyUM }
} }
private companion object {
const val PROCESSING_UPDATE_DELAY = 10_000L
}
} }

View file

@ -42,7 +42,12 @@ internal fun YieldSupplyBlockContent(yieldSupplyUM: YieldSupplyUM, modifier: Mod
is YieldSupplyUM.Initial -> SupplyInitial(supplyUM) is YieldSupplyUM.Initial -> SupplyInitial(supplyUM)
YieldSupplyUM.Loading -> SupplyLoading() YieldSupplyUM.Loading -> SupplyLoading()
is YieldSupplyUM.Content -> SupplyContent(supplyUM) is YieldSupplyUM.Content -> SupplyContent(supplyUM)
YieldSupplyUM.Processing -> SupplyProcessing() YieldSupplyUM.Processing.Enter -> SupplyProcessing(
resourceReference(R.string.yield_module_token_details_earn_notification_processing),
)
YieldSupplyUM.Processing.Exit -> SupplyProcessing(
resourceReference(R.string.yield_module_stop_earning),
)
} }
} }
} }
@ -153,7 +158,7 @@ private fun SupplyContent(supplyUM: YieldSupplyUM.Content) {
} }
@Composable @Composable
private fun SupplyProcessing() { private fun SupplyProcessing(text: TextReference) {
Column( Column(
verticalArrangement = Arrangement.spacedBy(4.dp), verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier modifier = Modifier
@ -174,9 +179,7 @@ private fun SupplyProcessing() {
horizontalArrangement = Arrangement.spacedBy(4.dp), horizontalArrangement = Arrangement.spacedBy(4.dp),
) { ) {
Text( Text(
text = stringResourceSafe( text = text.resolveReference(),
R.string.yield_module_token_details_earn_notification_processing,
),
style = TangemTheme.typography.body1, style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.tertiary, color = TangemTheme.colors.text.tertiary,
) )
@ -248,7 +251,8 @@ private class PreviewProvider : PreviewParameterProvider<YieldSupplyUM> {
isAllowedToSpend = true, isAllowedToSpend = true,
), ),
YieldSupplyUM.Loading, YieldSupplyUM.Loading,
YieldSupplyUM.Processing, YieldSupplyUM.Processing.Enter,
YieldSupplyUM.Processing.Exit,
) )
} }
// endregion // endregion

View file

@ -22,12 +22,15 @@ import com.tangem.domain.yield.supply.usecase.YieldSupplyGetContractAddressUseCa
import com.tangem.features.yield.supply.impl.R import com.tangem.features.yield.supply.impl.R
import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyActionUM import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyActionUM
import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyFeeUM import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyFeeUM
import com.tangem.features.yield.supply.impl.common.entity.transformer.YieldSupplyTransactionInProgressTransformer
import com.tangem.features.yield.supply.impl.common.entity.transformer.YieldSupplyTransactionReadyTransformer
import com.tangem.features.yield.supply.impl.subcomponents.approve.YieldSupplyApproveComponent import com.tangem.features.yield.supply.impl.subcomponents.approve.YieldSupplyApproveComponent
import com.tangem.features.yield.supply.impl.subcomponents.notifications.YieldSupplyNotificationsComponent import com.tangem.features.yield.supply.impl.subcomponents.notifications.YieldSupplyNotificationsComponent
import com.tangem.features.yield.supply.impl.subcomponents.notifications.YieldSupplyNotificationsUpdateTrigger import com.tangem.features.yield.supply.impl.subcomponents.notifications.YieldSupplyNotificationsUpdateTrigger
import com.tangem.features.yield.supply.impl.subcomponents.notifications.entity.YieldSupplyNotificationData import com.tangem.features.yield.supply.impl.subcomponents.notifications.entity.YieldSupplyNotificationData
import com.tangem.utils.StringsSigns.DOT import com.tangem.utils.StringsSigns.DOT
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.transformer.update
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -82,6 +85,7 @@ internal class YieldSupplyApproveModel @Inject constructor(
currencyIconState = CryptoCurrencyToIconStateConverter().convert(cryptoCurrency), currencyIconState = CryptoCurrencyToIconStateConverter().convert(cryptoCurrency),
yieldSupplyFeeUM = YieldSupplyFeeUM.Loading, yieldSupplyFeeUM = YieldSupplyFeeUM.Loading,
isPrimaryButtonEnabled = false, isPrimaryButtonEnabled = false,
isTransactionSending = false,
), ),
) )
@ -105,7 +109,7 @@ internal class YieldSupplyApproveModel @Inject constructor(
fun onClick() { fun onClick() {
val yieldSupplyFeeUM = uiState.value.yieldSupplyFeeUM as? YieldSupplyFeeUM.Content ?: return val yieldSupplyFeeUM = uiState.value.yieldSupplyFeeUM as? YieldSupplyFeeUM.Content ?: return
uiState.update { it.copy(isPrimaryButtonEnabled = false) } uiState.update(YieldSupplyTransactionInProgressTransformer)
modelScope.launch(dispatchers.default) { modelScope.launch(dispatchers.default) {
sendTransactionUseCase( sendTransactionUseCase(
@ -115,6 +119,7 @@ internal class YieldSupplyApproveModel @Inject constructor(
).fold( ).fold(
ifLeft = { ifLeft = {
Timber.e(it.toString()) Timber.e(it.toString())
uiState.update(YieldSupplyTransactionReadyTransformer)
}, },
ifRight = { ifRight = {
params.callback.onTransactionSent() params.callback.onTransactionSent()
@ -136,7 +141,7 @@ internal class YieldSupplyApproveModel @Inject constructor(
} }
private suspend fun onLoadFee() { private suspend fun onLoadFee() {
if (cryptoCurrency !is CryptoCurrency.Token) return if (cryptoCurrency !is CryptoCurrency.Token || uiState.value.isTransactionSending) return
val contractAddress = yieldSupplyGetContractAddressUseCase.invoke( val contractAddress = yieldSupplyGetContractAddressUseCase.invoke(
userWalletId = userWallet.walletId, userWalletId = userWallet.walletId,

View file

@ -42,6 +42,7 @@ internal class YieldSupplyStartEarningEntryModel @Inject constructor(
currencyIconState = CryptoCurrencyToIconStateConverter().convert(params.cryptoCurrency), currencyIconState = CryptoCurrencyToIconStateConverter().convert(params.cryptoCurrency),
yieldSupplyFeeUM = YieldSupplyFeeUM.Loading, yieldSupplyFeeUM = YieldSupplyFeeUM.Loading,
isPrimaryButtonEnabled = false, isPrimaryButtonEnabled = false,
isTransactionSending = false,
), ),
) )

View file

@ -12,6 +12,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.tokens.FetchCurrencyStatusUseCase
import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
import com.tangem.domain.transaction.usecase.SendTransactionUseCase import com.tangem.domain.transaction.usecase.SendTransactionUseCase
@ -21,6 +22,8 @@ import com.tangem.domain.yield.supply.usecase.YieldSupplyStartEarningUseCase
import com.tangem.features.yield.supply.impl.R import com.tangem.features.yield.supply.impl.R
import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyActionUM import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyActionUM
import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyFeeUM import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyFeeUM
import com.tangem.features.yield.supply.impl.common.entity.transformer.YieldSupplyTransactionInProgressTransformer
import com.tangem.features.yield.supply.impl.common.entity.transformer.YieldSupplyTransactionReadyTransformer
import com.tangem.features.yield.supply.impl.subcomponents.notifications.YieldSupplyNotificationsComponent import com.tangem.features.yield.supply.impl.subcomponents.notifications.YieldSupplyNotificationsComponent
import com.tangem.features.yield.supply.impl.subcomponents.notifications.YieldSupplyNotificationsUpdateTrigger import com.tangem.features.yield.supply.impl.subcomponents.notifications.YieldSupplyNotificationsUpdateTrigger
import com.tangem.features.yield.supply.impl.subcomponents.notifications.entity.YieldSupplyNotificationData import com.tangem.features.yield.supply.impl.subcomponents.notifications.entity.YieldSupplyNotificationData
@ -49,6 +52,7 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
private val yieldSupplyEstimateEnterFeeUseCase: YieldSupplyEstimateEnterFeeUseCase, private val yieldSupplyEstimateEnterFeeUseCase: YieldSupplyEstimateEnterFeeUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val yieldSupplyNotificationsUpdateTrigger: YieldSupplyNotificationsUpdateTrigger, private val yieldSupplyNotificationsUpdateTrigger: YieldSupplyNotificationsUpdateTrigger,
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
) : Model(), YieldSupplyNotificationsComponent.ModelCallback { ) : Model(), YieldSupplyNotificationsComponent.ModelCallback {
private val params: YieldSupplyStartEarningComponent.Params = paramsContainer.require() private val params: YieldSupplyStartEarningComponent.Params = paramsContainer.require()
@ -84,6 +88,7 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
currencyIconState = CryptoCurrencyToIconStateConverter().convert(params.cryptoCurrency), currencyIconState = CryptoCurrencyToIconStateConverter().convert(params.cryptoCurrency),
yieldSupplyFeeUM = YieldSupplyFeeUM.Loading, yieldSupplyFeeUM = YieldSupplyFeeUM.Loading,
isPrimaryButtonEnabled = false, isPrimaryButtonEnabled = false,
isTransactionSending = false,
), ),
) )
@ -100,7 +105,7 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
} }
private suspend fun onLoadFee() { private suspend fun onLoadFee() {
if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.Loading) return if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.Loading || uiState.value.isTransactionSending) return
val transactionListData = yieldSupplyStartEarningUseCase( val transactionListData = yieldSupplyStartEarningUseCase(
userWalletId = userWallet.walletId, userWalletId = userWallet.walletId,
@ -155,7 +160,7 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
fun onClick() { fun onClick() {
val yieldSupplyFeeUM = uiState.value.yieldSupplyFeeUM as? YieldSupplyFeeUM.Content ?: return val yieldSupplyFeeUM = uiState.value.yieldSupplyFeeUM as? YieldSupplyFeeUM.Content ?: return
uiState.update { it.copy(isPrimaryButtonEnabled = false) } uiState.update(YieldSupplyTransactionInProgressTransformer)
modelScope.launch(dispatchers.default) { modelScope.launch(dispatchers.default) {
sendTransactionUseCase.invoke( sendTransactionUseCase.invoke(
txsData = yieldSupplyFeeUM.transactionDataList, txsData = yieldSupplyFeeUM.transactionDataList,
@ -163,11 +168,12 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
network = cryptoCurrency.network, network = cryptoCurrency.network,
sendMode = TransactionSender.MultipleTransactionSendMode.DEFAULT, sendMode = TransactionSender.MultipleTransactionSendMode.DEFAULT,
).fold( ).fold(
ifLeft = { ifLeft = { error ->
Timber.e(it.toString()) Timber.e(error.toString())
uiState.update { it.copy(isPrimaryButtonEnabled = true) } uiState.update(YieldSupplyTransactionReadyTransformer)
}, },
ifRight = { ifRight = {
fetchCurrencyStatusUseCase(userWalletId = userWallet.walletId, cryptoCurrency.id)
modelScope.launch { modelScope.launch {
params.callback.onTransactionSent() params.callback.onTransactionSent()
} }

View file

@ -10,6 +10,7 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.transaction.usecase.GetFeeUseCase import com.tangem.domain.transaction.usecase.GetFeeUseCase
@ -18,6 +19,8 @@ import com.tangem.domain.yield.supply.usecase.YieldSupplyStopEarningUseCase
import com.tangem.features.yield.supply.impl.R import com.tangem.features.yield.supply.impl.R
import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyActionUM import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyActionUM
import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyFeeUM import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyFeeUM
import com.tangem.features.yield.supply.impl.common.entity.transformer.YieldSupplyTransactionInProgressTransformer
import com.tangem.features.yield.supply.impl.common.entity.transformer.YieldSupplyTransactionReadyTransformer
import com.tangem.features.yield.supply.impl.subcomponents.notifications.YieldSupplyNotificationsComponent import com.tangem.features.yield.supply.impl.subcomponents.notifications.YieldSupplyNotificationsComponent
import com.tangem.features.yield.supply.impl.subcomponents.notifications.YieldSupplyNotificationsUpdateTrigger import com.tangem.features.yield.supply.impl.subcomponents.notifications.YieldSupplyNotificationsUpdateTrigger
import com.tangem.features.yield.supply.impl.subcomponents.notifications.entity.YieldSupplyNotificationData import com.tangem.features.yield.supply.impl.subcomponents.notifications.entity.YieldSupplyNotificationData
@ -78,6 +81,7 @@ internal class YieldSupplyStopEarningModel @Inject constructor(
currencyIconState = CryptoCurrencyToIconStateConverter().convert(cryptoCurrency), currencyIconState = CryptoCurrencyToIconStateConverter().convert(cryptoCurrency),
yieldSupplyFeeUM = YieldSupplyFeeUM.Loading, yieldSupplyFeeUM = YieldSupplyFeeUM.Loading,
isPrimaryButtonEnabled = false, isPrimaryButtonEnabled = false,
isTransactionSending = false,
), ),
) )
@ -101,7 +105,7 @@ internal class YieldSupplyStopEarningModel @Inject constructor(
fun onClick() { fun onClick() {
val yieldSupplyFeeUM = uiState.value.yieldSupplyFeeUM as? YieldSupplyFeeUM.Content ?: return val yieldSupplyFeeUM = uiState.value.yieldSupplyFeeUM as? YieldSupplyFeeUM.Content ?: return
uiState.update { it.copy(isPrimaryButtonEnabled = false) } uiState.update(YieldSupplyTransactionInProgressTransformer)
modelScope.launch(dispatchers.default) { modelScope.launch(dispatchers.default) {
sendTransactionUseCase( sendTransactionUseCase(
@ -111,6 +115,7 @@ internal class YieldSupplyStopEarningModel @Inject constructor(
).fold( ).fold(
ifLeft = { ifLeft = {
Timber.e(it.toString()) Timber.e(it.toString())
uiState.update(YieldSupplyTransactionReadyTransformer)
}, },
ifRight = { ifRight = {
params.callback.onTransactionSent() params.callback.onTransactionSent()
@ -132,6 +137,8 @@ internal class YieldSupplyStopEarningModel @Inject constructor(
} }
private suspend fun onLoadFee() { private suspend fun onLoadFee() {
if (cryptoCurrency !is CryptoCurrency.Token || uiState.value.isTransactionSending) return
val exitTransitionData = yieldSupplyStopEarningUseCase( val exitTransitionData = yieldSupplyStopEarningUseCase(
userWalletId = userWallet.walletId, userWalletId = userWallet.walletId,
cryptoCurrencyStatus = cryptoCurrencyStatus, cryptoCurrencyStatus = cryptoCurrencyStatus,

View file

@ -5,7 +5,7 @@
# https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/tangem-sdk-android/
# https://github.com/tangem/vico # https://github.com/tangem/vico
tangemBlockchainSdk = "develop-1245" tangemBlockchainSdk = "develop-1246"
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
tangemCardSdk = "develop-564" tangemCardSdk = "develop-564"
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^