Updated on 2026-08-14
This commit is contained in:
parent
a30e44b1cf
commit
0907ba002c
23 changed files with 217 additions and 65 deletions
|
|
@ -26,6 +26,7 @@ dependencies {
|
|||
implementation(project(":features:referral:presentation"))
|
||||
implementation(project(":features:referral:domain"))
|
||||
implementation(project(":features:referral:data"))
|
||||
implementation(project(":features:swap:api"))
|
||||
implementation(project(":features:swap:presentation"))
|
||||
implementation(project(":features:swap:domain"))
|
||||
implementation(project(":features:swap:data"))
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.feature.swap.api.SwapFeatureToggleManager
|
||||
import com.tangem.feature.swap.domain.SwapInteractor
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.models.PendingTransaction
|
||||
|
|
@ -36,22 +37,26 @@ data class WalletData(
|
|||
return exchangeManager.availableForSell(currency)
|
||||
}
|
||||
|
||||
fun isAvailableToSwap(swapInteractor: SwapInteractor, isExchangeFeatureOn: Boolean): Boolean {
|
||||
fun isAvailableToSwap(
|
||||
swapFeatureToggleManager: SwapFeatureToggleManager,
|
||||
swapInteractor: SwapInteractor,
|
||||
): Boolean {
|
||||
val isOptimismEnabled =
|
||||
currency.blockchain.id == Blockchain.Optimism.id && swapFeatureToggleManager.isOptimismSwapEnabled
|
||||
return swapInteractor.isAvailableToSwap(currency.blockchain.toNetworkId()) &&
|
||||
currency.blockchain.id != Blockchain.Optimism.id && // disable optimism blockchain for now
|
||||
isExchangeFeatureOn &&
|
||||
isOptimismEnabled &&
|
||||
!currency.isCustomCurrency(null)
|
||||
}
|
||||
|
||||
fun getAvailableActions(
|
||||
swapInteractor: SwapInteractor,
|
||||
exchangeManager: CurrencyExchangeManager,
|
||||
isExchangeFeatureOn: Boolean,
|
||||
swapFeatureToggleManager: SwapFeatureToggleManager,
|
||||
): Set<CurrencyAction> {
|
||||
return setOfNotNull(
|
||||
if (isAvailableToBuy(exchangeManager)) CurrencyAction.Buy else null,
|
||||
if (isAvailableToSell(exchangeManager)) CurrencyAction.Sell else null,
|
||||
if (isAvailableToSwap(swapInteractor, isExchangeFeatureOn)) CurrencyAction.Swap else null,
|
||||
if (isAvailableToSwap(swapFeatureToggleManager, swapInteractor)) CurrencyAction.Swap else null,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import com.tangem.common.extensions.guard
|
|||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.feature.swap.api.SwapFeatureToggleManager
|
||||
import com.tangem.feature.swap.domain.SwapInteractor
|
||||
import com.tangem.sdk.extensions.dpToPx
|
||||
import com.tangem.tap.common.SnackbarHandler
|
||||
|
|
@ -75,6 +76,9 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
|||
@Inject
|
||||
lateinit var swapInteractor: SwapInteractor
|
||||
|
||||
@Inject
|
||||
lateinit var swapFeatureToggleManager: SwapFeatureToggleManager
|
||||
|
||||
private lateinit var pendingTransactionAdapter: PendingTransactionsAdapter
|
||||
private lateinit var warningMessagesAdapter: WalletDetailWarningMessagesAdapter
|
||||
|
||||
|
|
@ -290,7 +294,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
|||
WalletAction.DialogAction.ChooseTradeActionDialog(
|
||||
buyAllowed = selectedWallet.isAvailableToBuy(exchangeManager),
|
||||
sellAllowed = selectedWallet.isAvailableToSell(exchangeManager),
|
||||
swapAllowed = selectedWallet.isAvailableToSwap(swapInteractor, isExchangeServiceFeatureOn),
|
||||
swapAllowed = selectedWallet.isAvailableToSwap(swapFeatureToggleManager, swapInteractor),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -298,7 +302,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
|||
val actions = selectedWallet.getAvailableActions(
|
||||
swapInteractor = swapInteractor,
|
||||
exchangeManager = exchangeManager,
|
||||
isExchangeFeatureOn = isExchangeServiceFeatureOn,
|
||||
swapFeatureToggleManager = swapFeatureToggleManager,
|
||||
)
|
||||
binding.rowButtons.updateButtonsVisibility(
|
||||
actions = actions,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import coil.size.Scale
|
|||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.ui.fragments.setStatusBarColor
|
||||
import com.tangem.core.ui.utils.OneTouchClickListener
|
||||
import com.tangem.feature.swap.api.SwapFeatureToggleManager
|
||||
import com.tangem.feature.swap.domain.SwapInteractor
|
||||
import com.tangem.tap.MainActivity
|
||||
import com.tangem.tap.common.analytics.events.MainScreen
|
||||
|
|
@ -56,6 +57,9 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
@Inject
|
||||
lateinit var swapInteractor: SwapInteractor
|
||||
|
||||
@Inject
|
||||
lateinit var swapFeatureToggleManager: SwapFeatureToggleManager
|
||||
|
||||
private lateinit var warningsAdapter: WarningMessagesAdapter
|
||||
|
||||
private val binding: FragmentWalletBinding by viewBinding(FragmentWalletBinding::bind)
|
||||
|
|
@ -170,6 +174,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
}
|
||||
|
||||
walletView.swapInteractor = swapInteractor
|
||||
walletView.swapFeatureToggleManager = swapFeatureToggleManager
|
||||
|
||||
walletView.onNewState(state)
|
||||
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ class SingleWalletView : WalletView() {
|
|||
isExchangeServiceFeatureEnabled: Boolean,
|
||||
) {
|
||||
val swapInteractor = this.swapInteractor ?: return
|
||||
val swapFeatureToggleManager = this.swapFeatureToggleManager ?: return
|
||||
|
||||
val exchangeManager = store.state.globalState.exchangeManager
|
||||
binding?.rowButtons?.apply {
|
||||
|
|
@ -154,7 +155,7 @@ class SingleWalletView : WalletView() {
|
|||
val actions = walletData.getAvailableActions(
|
||||
swapInteractor = swapInteractor,
|
||||
exchangeManager = exchangeManager,
|
||||
isExchangeFeatureOn = isExchangeServiceFeatureEnabled,
|
||||
swapFeatureToggleManager = swapFeatureToggleManager,
|
||||
)
|
||||
binding?.rowButtons?.updateButtonsVisibility(
|
||||
actions = actions,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.features.wallet.ui.wallet
|
||||
|
||||
import com.tangem.feature.swap.api.SwapFeatureToggleManager
|
||||
import com.tangem.feature.swap.domain.SwapInteractor
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.ui.WalletFragment
|
||||
|
|
@ -8,6 +9,7 @@ import com.tangem.wallet.databinding.FragmentWalletBinding
|
|||
abstract class WalletView {
|
||||
|
||||
var swapInteractor: SwapInteractor? = null
|
||||
var swapFeatureToggleManager: SwapFeatureToggleManager? = null
|
||||
|
||||
protected var fragment: WalletFragment? = null
|
||||
protected var binding: FragmentWalletBinding? = null
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics
|
|||
import com.tangem.Message
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
|
||||
import com.tangem.blockchain.blockchains.optimism.OptimismWalletManager
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
|
|
@ -155,11 +156,83 @@ class TransactionManagerImpl(
|
|||
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
|
||||
val walletManager = getActualWalletManager(blockchain, derivationPath)
|
||||
if (walletManager is EthereumWalletManager) {
|
||||
if (walletManager is OptimismWalletManager) {
|
||||
return getFeeForOptimismBlockchain(
|
||||
walletManager = walletManager,
|
||||
amount = createAmount(amountToSend, currencyToSend, blockchain),
|
||||
destinationAddress = destinationAddress,
|
||||
data = data,
|
||||
)
|
||||
}
|
||||
return getFeeForEthereumBlockchain(
|
||||
walletManager = walletManager,
|
||||
blockchain = blockchain,
|
||||
amountToSend = amountToSend,
|
||||
currency = currencyToSend,
|
||||
destinationAddress = destinationAddress,
|
||||
data = data,
|
||||
increaseBy = increaseBy,
|
||||
)
|
||||
} else {
|
||||
return getFeeForBlockchain(
|
||||
walletManager = walletManager,
|
||||
amountToSend = amountToSend,
|
||||
currency = currencyToSend,
|
||||
blockchain = blockchain,
|
||||
destinationAddress = destinationAddress,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getBlockchainInfo(networkId: String): ProxyNetworkInfo {
|
||||
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
|
||||
return ProxyNetworkInfo(
|
||||
name = blockchain.fullName,
|
||||
blockchainId = blockchain.id,
|
||||
blockchainCurrency = blockchain.currency,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun getFeeForBlockchain(
|
||||
walletManager: WalletManager,
|
||||
amountToSend: BigDecimal,
|
||||
currency: Currency,
|
||||
blockchain: Blockchain,
|
||||
destinationAddress: String,
|
||||
): ProxyFee {
|
||||
val fee = (walletManager as? TransactionSender)?.getFee(
|
||||
amount = createAmount(amountToSend, currency, blockchain),
|
||||
destination = destinationAddress,
|
||||
) ?: error("Cannot cast to TransactionSender")
|
||||
return when (fee) {
|
||||
is Result.Success -> {
|
||||
// for not EVM blockchains set gasLimit ZERO for now
|
||||
ProxyFee(
|
||||
gasLimit = BigInteger.ZERO,
|
||||
fee = convertToProxyAmount(fee.data.firstOrNull() ?: error("no fee found")),
|
||||
)
|
||||
}
|
||||
is Result.Failure -> {
|
||||
error(fee.error.message ?: fee.error.customMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
private suspend fun getFeeForEthereumBlockchain(
|
||||
walletManager: EthereumWalletManager,
|
||||
blockchain: Blockchain,
|
||||
amountToSend: BigDecimal,
|
||||
currency: Currency,
|
||||
destinationAddress: String,
|
||||
data: String?,
|
||||
increaseBy: Int?,
|
||||
): ProxyFee {
|
||||
val gasLimit = getGasLimit(
|
||||
evmWalletManager = walletManager,
|
||||
blockchain = blockchain,
|
||||
amount = amountToSend,
|
||||
currency = currencyToSend,
|
||||
currency = currency,
|
||||
destinationAddress = destinationAddress,
|
||||
data = data,
|
||||
).let {
|
||||
|
|
@ -188,17 +261,24 @@ class TransactionManagerImpl(
|
|||
error(gasPrice.error.message ?: gasPrice.error.customMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getFeeForOptimismBlockchain(
|
||||
walletManager: OptimismWalletManager,
|
||||
amount: Amount,
|
||||
destinationAddress: String,
|
||||
data: String?,
|
||||
): ProxyFee {
|
||||
val fee = if (data.isNullOrEmpty()) {
|
||||
walletManager.getFee(amount, destinationAddress)
|
||||
} else {
|
||||
val fee = (walletManager as? TransactionSender)?.getFee(
|
||||
amount = createAmount(amountToSend, currencyToSend, blockchain),
|
||||
destination = destinationAddress,
|
||||
) ?: error("Cannot cast to TransactionSender")
|
||||
walletManager.getFee(amount, destinationAddress, data)
|
||||
}
|
||||
when (fee) {
|
||||
is Result.Success -> {
|
||||
// for not EVM blockchains set gasLimit ZERO for now
|
||||
return ProxyFee(
|
||||
gasLimit = BigInteger.ZERO,
|
||||
fee = convertToProxyAmount(fee.data.firstOrNull() ?: error("no fee found")),
|
||||
gasLimit = walletManager.gasLimit ?: BigInteger.ZERO,
|
||||
fee = convertToProxyAmount(fee.data.lastOrNull() ?: error("no fee found")),
|
||||
)
|
||||
}
|
||||
is Result.Failure -> {
|
||||
|
|
@ -206,16 +286,6 @@ class TransactionManagerImpl(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getBlockchainInfo(networkId: String): ProxyNetworkInfo {
|
||||
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
|
||||
return ProxyNetworkInfo(
|
||||
name = blockchain.fullName,
|
||||
blockchainId = blockchain.id,
|
||||
blockchainCurrency = blockchain.currency,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
private suspend fun getGasLimit(
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
[
|
||||
{
|
||||
"name": "INACTIVE_TEST_FEATURE_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "ACTIVE2_TEST_FEATURE_ENABLED",
|
||||
"version": "1.0.0"
|
||||
"name": "OPTIMISM_SWAP_FEATURE_ENABLED",
|
||||
"version": "4.3.1"
|
||||
}
|
||||
]
|
||||
|
|
@ -42,7 +42,7 @@ internal class DevFeatureTogglesManager(
|
|||
.toMutableMap()
|
||||
}
|
||||
|
||||
override fun isFeatureEnabled(name: String): Boolean = featureTogglesMap.any { it.key == name }
|
||||
override fun isFeatureEnabled(name: String): Boolean = featureTogglesMap[name] ?: false
|
||||
|
||||
override fun getFeatureToggles(): Map<String, Boolean> = featureTogglesMap
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ internal class ProdFeatureTogglesManager(
|
|||
.associateToggles(currentVersion = versionProvider.get() ?: "")
|
||||
}
|
||||
|
||||
override fun isFeatureEnabled(name: String): Boolean = featureToggles.any { it.key == name }
|
||||
override fun isFeatureEnabled(name: String): Boolean = featureToggles[name] ?: false
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
fun getProdFeatureToggles() = featureToggles
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ internal class DevFeatureTogglesManagerTest {
|
|||
@Test
|
||||
fun `get feature availability if feature toggle exists`() {
|
||||
val featureToggles = mutableMapOf(
|
||||
"INACTIVE_TEST_FEATURE_ENABLED" to false,
|
||||
"INACTIVE_TEST_FEATURE_ENABLED" to true,
|
||||
"ACTIVE2_TEST_FEATURE_ENABLED" to true,
|
||||
)
|
||||
manager.setFeatureToggles(featureToggles)
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ internal class ProdFeatureTogglesManagerTest {
|
|||
@Test
|
||||
fun `get feature availability if feature toggle exists`() {
|
||||
val featureToggles = mapOf(
|
||||
"INACTIVE_TEST_FEATURE_ENABLED" to false,
|
||||
"INACTIVE_TEST_FEATURE_ENABLED" to true,
|
||||
"ACTIVE2_TEST_FEATURE_ENABLED" to true,
|
||||
)
|
||||
manager.setProdFeatureToggles(featureToggles)
|
||||
|
|
|
|||
1
features/swap/api/.gitignore
vendored
Normal file
1
features/swap/api/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
14
features/swap/api/build.gradle.kts
Normal file
14
features/swap/api/build.gradle.kts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.kotlin.serialization)
|
||||
alias(deps.plugins.hilt.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
}
|
||||
2
features/swap/api/src/main/AndroidManifest.xml
Normal file
2
features/swap/api/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.tangem.feature.swap.api" />
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.tangem.feature.swap.api
|
||||
|
||||
/** Feature toggles manager of "swap" feature */
|
||||
interface SwapFeatureToggleManager {
|
||||
|
||||
val isOptimismSwapEnabled: Boolean
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ plugins {
|
|||
dependencies {
|
||||
/** Core modules */
|
||||
implementation(project(":core:analytics"))
|
||||
implementation(project(":core:featuretoggles"))
|
||||
implementation(project(":core:utils"))
|
||||
implementation(project(":core:ui"))
|
||||
|
||||
|
|
@ -27,6 +28,9 @@ dependencies {
|
|||
implementation(deps.compose.coil)
|
||||
implementation(deps.compose.constraintLayout)
|
||||
|
||||
/** Api */
|
||||
implementation(project(":features:swap:api"))
|
||||
|
||||
/** Domain */
|
||||
implementation(project(":features:swap:domain"))
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.feature.swap.di
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.feature.swap.api.SwapFeatureToggleManager
|
||||
import com.tangem.feature.swap.toggles.DefaultFeatureTogglesManager
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.components.ActivityRetainedComponent
|
||||
import dagger.hilt.android.scopes.ActivityRetainedScoped
|
||||
|
||||
@Module
|
||||
@InstallIn(ActivityRetainedComponent::class)
|
||||
internal object SwapFeatureTogglesManagerModule {
|
||||
|
||||
@Provides
|
||||
@ActivityRetainedScoped
|
||||
fun provideSwapFeatureTogglesManager(featureTogglesManager: FeatureTogglesManager): SwapFeatureToggleManager {
|
||||
return DefaultFeatureTogglesManager(featureTogglesManager)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.feature.swap.toggles
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
|
||||
/** Feature toggles manager implementation of "swap" feature */
|
||||
class DefaultFeatureTogglesManager(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : InnerSwapFeatureTogglesManager {
|
||||
|
||||
override val isOptimismSwapEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "OPTIMISM_SWAP_FEATURE_ENABLED")
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.feature.swap.toggles
|
||||
|
||||
import com.tangem.feature.swap.api.SwapFeatureToggleManager
|
||||
|
||||
/** Feature toggles manager of "swap" feature for internal logic */
|
||||
internal interface InnerSwapFeatureTogglesManager : SwapFeatureToggleManager
|
||||
|
|
@ -438,7 +438,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
|
||||
companion object {
|
||||
private const val INITIAL_AMOUNT = ""
|
||||
private const val UPDATE_DELAY = 10000L
|
||||
private const val UPDATE_DELAY = 1000000L
|
||||
private const val DEBOUNCE_AMOUNT_DELAY = 1000L
|
||||
}
|
||||
}
|
||||
|
|
@ -68,7 +68,7 @@ kotlinSerialization = "1.4.1"
|
|||
# endregion Other libraries
|
||||
|
||||
# region Tangem
|
||||
tangemBlockchainSdk = "develop-176"
|
||||
tangemBlockchainSdk = "develop-177"
|
||||
# tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
|
||||
tangemCardSdk = "develop-204"
|
||||
# tangemCardSdk = "0.0.1" # Keep it! - used for local builds
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ include(":libs:auth")
|
|||
include(":features:referral:data")
|
||||
include(":features:referral:domain")
|
||||
include(":features:referral:presentation")
|
||||
include(":features:swap:api")
|
||||
include(":features:swap:data")
|
||||
include(":features:swap:domain")
|
||||
include(":features:swap:presentation")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue