From c896df1e3400573e78c8d99c7a00e67132d6a05d Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 26 May 2026 19:04:08 +0300 Subject: [PATCH 1/6] Updated on 2026-08-14 --- .../src/main/assets/configs/feature_toggles_config.json | 8 ++++---- gradle/tangem_dependencies.toml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json index d0f4da1ecc..5a84e183be 100644 --- a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json @@ -9,7 +9,7 @@ }, { "name": "STAKING_ETH_ENABLED", - "version": "undefined" + "version": "5.39" }, { "name": "USEDESK_ENABLED", @@ -25,7 +25,7 @@ }, { "name": "DYNAMIC_ADDRESSES_ENABLED", - "version": "undefined" + "version": "5.39" }, { "name": "VIRTUAL_ACCOUNTS_ENABLED", @@ -37,11 +37,11 @@ }, { "name": "SOLANA_TX_HISTORY_ENABLED", - "version": "undefined" + "version": "5.39" }, { "name": "SOLANA_SCALED_UI_AMOUNT_ENABLED", - "version": "undefined" + "version": "5.39" }, { "name": "HEDERA_ERC20_ENABLED", diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 1b0d2dc074..8f5b28ffc0 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,9 +5,9 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "develop-1527" +tangemBlockchainSdk = "releases-5.39-1530" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "develop-614" +tangemCardSdk = "releases-5.39-622" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "tangem-master-21" #tangemVico = "0.0.1" # Keep it! - used for local builds ^ From 2f9215e3aa9a3216dbabdadf7182d4b0fba8350a Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 26 May 2026 19:11:27 +0300 Subject: [PATCH 2/6] Updated on 2026-08-14 --- .../assets/configs/feature_toggles_config.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json index 5a84e183be..697f7ed6cb 100644 --- a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json @@ -69,7 +69,7 @@ }, { "name": "SWAP_AB_ENABLED", - "version": "undefined" + "version": "5.39" }, { "name": "TWI_1403_PUSH_NOTIFICATION_SETTINGS_ENABLED", @@ -77,30 +77,30 @@ }, { "name": "AND_15310_ADD_FUNDS_STAGE1", - "version": "undefined" + "version": "5.39" }, { "name": "AND_15009_SWAP_PROVIDER_FILTER_ENABLED", - "version": "undefined" + "version": "5.39" }, { "name": "AND_15101_TANGEM_PAY_HOT_WALLET_ONBOARDING", - "version": "undefined" + "version": "5.39" }, { "name": "AND_15402_ADI_MAIN_SCREEN_DEFAULT_ENABLED", - "version": "undefined" + "version": "5.39" }, { "name": "AND_15103_SWAP_RATE_EXPERIENCE_ENABLED", - "version": "undefined" + "version": "5.39" }, { "name": "AND_15122_SWAP_PREDEFINED_BUTTONS_ENABLED", - "version": "undefined" + "version": "5.39" }, { "name": "AND_15154_YIELD_PROMO_ENABLED", - "version": "undefined" + "version": "5.39" } ] From a2b99aa465e131a58ea881d31ab32bb062fc9b85 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 27 May 2026 10:29:12 +0300 Subject: [PATCH 3/6] Updated on 2026-08-14 --- .../src/main/assets/configs/feature_toggles_config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json index 697f7ed6cb..e86e9ffe5a 100644 --- a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json @@ -101,6 +101,6 @@ }, { "name": "AND_15154_YIELD_PROMO_ENABLED", - "version": "5.39" + "version": "undefined" } ] From 9be9f8f3e793a73f73231f45c18afb27d9545418 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 27 May 2026 19:43:31 +0500 Subject: [PATCH 4/6] Updated on 2026-08-14 --- .../feature/swap/domain/SwapInteractorImpl.kt | 68 +-- .../domain/SwapInteractorImplTangemPayTest.kt | 336 +++++++++++++++ .../feature/swap/DefaultSwapComponent.kt | 3 +- .../tangem/feature/swap/model/SwapModel.kt | 2 +- .../tangem/feature/swap/ui/StateBuilder.kt | 12 +- .../swap/StateBuilderSwapButtonTest.kt | 386 ++++++++++++++++++ 6 files changed, 774 insertions(+), 33 deletions(-) create mode 100644 features/swap/domain/src/test/kotlin/com/tangem/feature/swap/domain/SwapInteractorImplTangemPayTest.kt create mode 100644 features/swap/impl/src/test/java/com/tangem/feature/swap/StateBuilderSwapButtonTest.kt diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index c7b7cb9778..f902272fa7 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -27,6 +27,7 @@ import com.tangem.domain.appcurrency.repository.AppCurrencyRepository import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.express.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.network.Network @@ -1372,37 +1373,46 @@ internal class SwapInteractorImpl @Inject constructor( feeValue: BigDecimal, selectedFeeToken: CryptoCurrencyStatus? = null, ): IncludeFeeInAmountInternal { - val isFeeInSameCurrencyToken = selectedFeeToken != null && - fromSwapCurrencyStatus.currency.id == selectedFeeToken.currency.id && - selectedFeeToken.currency is CryptoCurrency.Token - - return if (isFeeInSameCurrencyToken) { - // we have a token selected for fee payment the same as sending token - val fromBalance = fromSwapCurrencyStatus.status.value.amount - val reducedBalance = fromBalance?.minus(reduceBalanceBy).orZero() - when { - amount.value > reducedBalance -> IncludeFeeInAmountInternal.BalanceNotEnough - amount.value + feeValue <= reducedBalance -> IncludeFeeInAmountInternal.Excluded - else -> { - if (feeValue < amount.value) { - IncludeFeeInAmountInternal.Included( - amountSubtractFee = SwapAmount( - value = reducedBalance - feeValue, - decimals = fromSwapCurrencyStatus.currency.decimals, - ), - ) - } else { - IncludeFeeInAmountInternal.Excluded - } - } + return if (fromSwapCurrencyStatus.account is Account.Payment) { + val fromBalance = fromSwapCurrencyStatus.status.value.amount.orZero() + if (amount.value > fromBalance) { + IncludeFeeInAmountInternal.BalanceNotEnough + } else { + IncludeFeeInAmountInternal.Excluded } } else { - getIncludeFeeInAmountForNative( - fromSwapCurrencyStatus = fromSwapCurrencyStatus, - amount = amount, - reduceBalanceBy = reduceBalanceBy, - feeValue = feeValue, - ) + val isFeeInSameCurrencyToken = selectedFeeToken != null && + fromSwapCurrencyStatus.currency.id == selectedFeeToken.currency.id && + selectedFeeToken.currency is CryptoCurrency.Token + + if (isFeeInSameCurrencyToken) { + // we have a token selected for fee payment the same as sending token + val fromBalance = fromSwapCurrencyStatus.status.value.amount + val reducedBalance = fromBalance?.minus(reduceBalanceBy).orZero() + when { + amount.value > reducedBalance -> IncludeFeeInAmountInternal.BalanceNotEnough + amount.value + feeValue <= reducedBalance -> IncludeFeeInAmountInternal.Excluded + else -> { + if (feeValue < amount.value) { + IncludeFeeInAmountInternal.Included( + amountSubtractFee = SwapAmount( + value = reducedBalance - feeValue, + decimals = fromSwapCurrencyStatus.currency.decimals, + ), + ) + } else { + IncludeFeeInAmountInternal.Excluded + } + } + } + } else { + getIncludeFeeInAmountForNative( + fromSwapCurrencyStatus = fromSwapCurrencyStatus, + amount = amount, + reduceBalanceBy = reduceBalanceBy, + feeValue = feeValue, + ) + } } } diff --git a/features/swap/domain/src/test/kotlin/com/tangem/feature/swap/domain/SwapInteractorImplTangemPayTest.kt b/features/swap/domain/src/test/kotlin/com/tangem/feature/swap/domain/SwapInteractorImplTangemPayTest.kt new file mode 100644 index 0000000000..e4aca7d3b4 --- /dev/null +++ b/features/swap/domain/src/test/kotlin/com/tangem/feature/swap/domain/SwapInteractorImplTangemPayTest.kt @@ -0,0 +1,336 @@ +package com.tangem.feature.swap.domain + +import arrow.core.right +import com.google.common.truth.Truth.assertThat +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.transaction.Fee +import com.tangem.blockchain.common.transaction.TransactionFee +import com.tangem.blockchainsdk.utils.toNetworkId +import com.tangem.domain.models.account.Account +import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.tokens.model.FeePaidCurrency +import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck +import com.tangem.feature.swap.domain.fee.TransactionFeeResult +import com.tangem.feature.swap.domain.models.SwapAmount +import com.tangem.feature.swap.domain.models.domain.ExchangeProviderType +import com.tangem.feature.swap.domain.models.domain.PreparedSwapConfigState +import com.tangem.feature.swap.domain.models.domain.SwapBalanceStatus +import com.tangem.feature.swap.domain.models.ui.* +import io.mockk.coEvery +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import java.math.BigDecimal + +/** + * Tests for the Tangem Pay early-exit branch in [SwapInteractorImpl]. + * + * When the from-currency belongs to a [Account.Payment] account the fee must + * never be included in the swap amount ([IncludeFeeInAmountInternal.Excluded]). + * + * The private [SwapInteractorImpl.getIncludeFeeInAmountInternal] function is exercised + * through the public [SwapInteractorImpl.applySwapFee] entry point (CEX provider path), + * which calls [computeBalanceStatus] → [getIncludeFeeInAmountInternal]. + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@DisplayName("SwapInteractorImpl — Tangem Pay (Payment account) fee-inclusion behaviour") +internal class SwapInteractorImplTangemPayTest : SwapInteractorImplTestBase() { + + private val ethNetwork = Blockchain.Ethereum.toNetworkId() + private val userWalletId = UserWalletId(stringValue = "deadbeef") + private val lastReducedBalanceBy = BigDecimal.ZERO + + @BeforeEach + fun setup() { + // Shared stubs required by computeBalanceStatus / manageWarnings / manageTransactionValidationWarnings + coEvery { + getCurrencyCheckUseCase.invoke( + userWalletId = any(), + currencyStatus = any(), + feeCurrencyStatus = any(), + amount = any(), + fee = any(), + feeCurrencyBalanceAfterTransaction = any(), + recipientAddress = any(), + ) + } returns buildCurrencyCheck() + + coEvery { + validateTransactionUseCase.invoke( + amount = any(), + fee = any(), + memo = any(), + destination = any(), + userWalletId = any(), + network = any(), + ) + } returns Unit.right() + + coEvery { walletManagersFacade.getNativeTokenBalance(any(), any(), any()) } returns BigDecimal("10") + coEvery { getFeePaidCryptoCurrencyStatusSyncUseCase.invoke(any(), any()) } returns null.right() + coEvery { currenciesRepository.createCoinCurrency(any()) } returns buildCoinCurrency() + coEvery { currenciesRepository.getFeePaidCurrency(any(), any()) } returns FeePaidCurrency.Coin + } + + // ------------------------------------------------------------------------- + // Payment account — fee always Excluded + // ------------------------------------------------------------------------- + + @Nested + @DisplayName("Payment account (Tangem Pay withdrawal)") + inner class PaymentAccountBranch { + + @Test + @DisplayName("should produce Sufficient and not FeeAdjustedAmount when Payment account token amount within balance") + fun `should produce Sufficient when Payment account token swap and amount within balance`() = runTest { + // Token swap: balance=1, amount=0.95, fee=0.1 (amount + fee > balance). + // On a CryptoPortfolio account with a same-currency token fee this triggers FeeAdjustedAmount. + // On a Payment account the early-exit returns Excluded, so computeBalanceStatus falls through + // to isBalanceEnough (token: checks balance >= amount only → true) → Sufficient. + val state = buildCexQuotesLoadedState( + fromAmount = SwapAmount(BigDecimal("0.95"), 18), + isCoin = false, + fromBalance = BigDecimal("1"), + account = Account.Payment(userWalletId), + ) + val swapFee = buildTestSwapFee(feeValue = BigDecimal("0.001")) + + val patched = sut.applySwapFee(state, swapFee, lastReducedBalanceBy) + + assertThat(patched.preparedSwapConfigState.balanceStatus) + .isInstanceOf(SwapBalanceStatus.Sufficient::class.java) + } + + @Test + @DisplayName("should produce Sufficient even when from-token and fee-token ids match (same-currency token path bypassed)") + fun `should produce Sufficient when Payment account and same-currency token fee selected`() = runTest { + // With a CryptoPortfolio account this scenario (same token for fee and swap) would trigger + // the IncludeFeeInAmountInternal.Included / BalanceNotEnough paths. + // Payment account must short-circuit before reaching that logic. + val state = buildCexQuotesLoadedState( + fromAmount = SwapAmount(BigDecimal("1"), 18), + isCoin = false, + fromBalance = BigDecimal("1"), + account = Account.Payment(userWalletId), + ) + // Build a fee token that shares the same currency id as the from-token — triggers same-currency path + // on non-Payment accounts. + val fromCurrencyStatus = state.fromTokenInfo.swapCurrencyStatus.status + val swapFee = buildTestSwapFeeWithToken( + feeValue = BigDecimal("0.5"), + selectedFeeToken = fromCurrencyStatus, + ) + + val patched = sut.applySwapFee(state, swapFee, lastReducedBalanceBy) + + // Must be Sufficient, not FeeAdjustedAmount or InsufficientFee + assertThat(patched.preparedSwapConfigState.balanceStatus) + .isInstanceOf(SwapBalanceStatus.Sufficient::class.java) + } + + @Test + @DisplayName("should produce InsufficientAmount when Payment account and amount exceeds balance") + fun `should produce InsufficientAmount when Payment account and amount exceeds from-balance`() = runTest { + // Even on a Payment account the basic amount-vs-balance check must still apply. + coEvery { walletManagersFacade.getNativeTokenBalance(any(), any(), any()) } returns BigDecimal("10") + + val state = buildCexQuotesLoadedState( + fromAmount = SwapAmount(BigDecimal("5"), 18), + isCoin = true, + fromBalance = BigDecimal("1"), + account = Account.Payment(userWalletId), + ) + val swapFee = buildTestSwapFee(feeValue = BigDecimal("0.001")) + + val patched = sut.applySwapFee(state, swapFee, lastReducedBalanceBy) + + assertThat(patched.preparedSwapConfigState.balanceStatus) + .isInstanceOf(SwapBalanceStatus.InsufficientAmount::class.java) + } + } + + // ------------------------------------------------------------------------- + // Non-Payment account — existing native-fee logic preserved + // ------------------------------------------------------------------------- + + @Nested + @DisplayName("CryptoPortfolio account (existing behaviour preserved)") + inner class CryptoPortfolioAccountBranch { + + @Test + @DisplayName("should produce Sufficient when CryptoPortfolio account and native balance covers fee") + fun `should produce Sufficient when CryptoPortfolio account and native balance covers fee`() = runTest { + coEvery { walletManagersFacade.getNativeTokenBalance(any(), any(), any()) } returns BigDecimal("10") + + val state = buildCexQuotesLoadedState( + fromAmount = SwapAmount(BigDecimal("1"), 18), + isCoin = true, + fromBalance = BigDecimal("10"), + account = Account.CryptoPortfolio.createMainAccount(userWalletId), + ) + val swapFee = buildTestSwapFee(feeValue = BigDecimal("0.001")) + + val patched = sut.applySwapFee(state, swapFee, lastReducedBalanceBy) + + assertThat(patched.preparedSwapConfigState.balanceStatus) + .isInstanceOf(SwapBalanceStatus.Sufficient::class.java) + } + + @Test + @DisplayName("should produce InsufficientFee when CryptoPortfolio account and native balance below fee") + fun `should produce InsufficientFee when CryptoPortfolio and native balance below fee`() = runTest { + coEvery { walletManagersFacade.getNativeTokenBalance(any(), any(), any()) } returns BigDecimal("0.0001") + + val state = buildCexQuotesLoadedState( + fromAmount = SwapAmount(BigDecimal("1"), 18), + isCoin = false, // token → fee paid from native + fromBalance = BigDecimal("10"), + account = Account.CryptoPortfolio.createMainAccount(userWalletId), + ) + val swapFee = buildTestSwapFee(feeValue = BigDecimal("0.01")) + + val patched = sut.applySwapFee(state, swapFee, lastReducedBalanceBy) + + assertThat(patched.preparedSwapConfigState.balanceStatus) + .isInstanceOf(SwapBalanceStatus.InsufficientFee::class.java) + } + + @Test + @DisplayName("should produce FeeAdjustedAmount when CryptoPortfolio account, same-currency token fee, and amount fills balance") + fun `should produce FeeAdjustedAmount when CryptoPortfolio and same-currency token fee squeezes amount`() = + runTest { + // same-token fee path: amount fills the balance but amount + fee > balance → FeeAdjustedAmount + val fromBalance = BigDecimal("1") + val feeValue = BigDecimal("0.1") + val amount = BigDecimal("0.95") // 0.95 + 0.1 = 1.05 > 1 → triggers Included + + val state = buildCexQuotesLoadedState( + fromAmount = SwapAmount(amount, 18), + isCoin = false, + fromBalance = fromBalance, + account = Account.CryptoPortfolio.createMainAccount(userWalletId), + ) + val fromCurrencyStatus = state.fromTokenInfo.swapCurrencyStatus.status + val swapFee = buildTestSwapFeeWithToken( + feeValue = feeValue, + selectedFeeToken = fromCurrencyStatus, + ) + + val patched = sut.applySwapFee(state, swapFee, lastReducedBalanceBy) + + assertThat(patched.preparedSwapConfigState.balanceStatus) + .isInstanceOf(SwapBalanceStatus.FeeAdjustedAmount::class.java) + } + } + + // ------------------------------------------------------------------------- + // Helpers + // ------------------------------------------------------------------------- + + private fun buildCurrencyCheck(): CryptoCurrencyCheck = CryptoCurrencyCheck( + dustValue = null, + reserveAmount = null, + minimumSendAmount = null, + existentialDeposit = null, + utxoAmountLimit = null, + isAccountFunded = true, + rentWarning = null, + isMemoRequired = false, + ) + + /** + * Builds a [SwapState.QuotesLoadedState] with a CEX provider so that [applySwapFee] routes + * through [computeBalanceStatus] → [getIncludeFeeInAmountInternal]. + * + * The [account] parameter is the real domain [Account] instance to put on [SwapCurrencyStatus]. + */ + private fun buildCexQuotesLoadedState( + fromAmount: SwapAmount, + isCoin: Boolean, + fromBalance: BigDecimal, + account: Account, + ): SwapState.QuotesLoadedState { + val from = buildSwapCurrencyStatus( + networkRawId = ethNetwork, + isCoin = isCoin, + amount = fromBalance, + ).copy(account = account) + val to = buildSwapCurrencyStatus(networkRawId = ethNetwork) + return SwapState.QuotesLoadedState( + fromTokenInfo = TokenSwapInfo( + tokenAmount = fromAmount, + swapCurrencyStatus = from, + amountFiat = BigDecimal.ZERO, + ), + toTokenInfo = TokenSwapInfo( + tokenAmount = SwapAmount(BigDecimal("0.5"), 18), + swapCurrencyStatus = to, + amountFiat = BigDecimal.ZERO, + ), + priceImpact = PriceImpact.Empty, + preparedSwapConfigState = PreparedSwapConfigState( + balanceStatus = SwapBalanceStatus.Pending, + hasOutgoingTransaction = false, + ), + permissionState = PermissionDataState.Empty, + swapDataModel = null, + currencyCheck = null, + validationResult = null, + minAdaValue = null, + swapProvider = buildSwapProvider(ExchangeProviderType.CEX), + ) + } + + private fun buildTestSwapFee( + feeValue: BigDecimal, + otherNativeFee: BigDecimal = BigDecimal.ZERO, + ): SwapFee { + val feeAmount = mockk(relaxed = true) { + every { value } returns feeValue + } + val fee = mockk(relaxed = true) { + every { this@mockk.amount } returns feeAmount + } + val feeTokenStatus = mockk(relaxed = true) { + every { currency } returns buildCoinCurrency() + } + return SwapFee( + fee = fee, + transactionFeeResult = TransactionFeeResult.Loaded(mockk(relaxed = true)), + selectedFeeToken = feeTokenStatus, + otherNativeFee = otherNativeFee, + feeBucket = FeeBucket.MARKET, + ) + } + + /** + * Builds a [SwapFee] whose [SwapFee.selectedFeeToken] is the given [CryptoCurrencyStatus]. + * This triggers the same-currency-token path in [getIncludeFeeInAmountInternal] for + * [Account.CryptoPortfolio] accounts. + */ + private fun buildTestSwapFeeWithToken( + feeValue: BigDecimal, + selectedFeeToken: CryptoCurrencyStatus, + ): SwapFee { + val feeAmount = mockk(relaxed = true) { + every { value } returns feeValue + } + val fee = mockk(relaxed = true) { + every { this@mockk.amount } returns feeAmount + } + return SwapFee( + fee = fee, + transactionFeeResult = TransactionFeeResult.Loaded(mockk(relaxed = true)), + selectedFeeToken = selectedFeeToken, + otherNativeFee = BigDecimal.ZERO, + feeBucket = FeeBucket.MARKET, + ) + } +} \ No newline at end of file diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/DefaultSwapComponent.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/DefaultSwapComponent.kt index 3e10710467..16cb6b2bcb 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/DefaultSwapComponent.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/DefaultSwapComponent.kt @@ -160,8 +160,9 @@ internal class DefaultSwapComponent @AssistedInject constructor( val isPermissionNotReady = loadedState?.permissionState !is PermissionDataState.Empty val isInTransferMode = dataState.currentTransferState != null val isSwapNotReady = !isInTransferMode && (isProviderMissing || isPermissionNotReady) + val isTangemPayWithdrawal = model.isTangemPayWithdrawal() - isAmountEmptyOrZero || isInsufficientFunds || isSwapNotReady + isAmountEmptyOrZero || isInsufficientFunds || isSwapNotReady || isTangemPayWithdrawal } } diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt index 47e0377fff..cd731174ee 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt @@ -1924,7 +1924,7 @@ internal class SwapModel @Inject constructor( ) } - private fun isTangemPayWithdrawal(): Boolean { + fun isTangemPayWithdrawal(): Boolean { return tangemPayInput?.isWithdrawal == true || dataState.fromSwapCurrencyStatus?.account is Account.Payment } diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index 65bd71ca32..7380667baa 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -497,6 +497,7 @@ internal class StateBuilder( } } val priceImpact = quoteModel.priceImpact + val isTangemPayWithdrawal = fromSwapCurrencyStatus.account is Account.Payment return uiStateHolder.copy( sendCardData = SwapCardState.SwapCardData( type = sendInput, @@ -550,7 +551,12 @@ internal class StateBuilder( ), swapButton = SwapButton( walletInteractionIcon = walletInterationIcon(fromSwapCurrencyStatus.userWallet), - isEnabled = getSwapButtonEnabled(notifications, priceImpact, swapFee), + isEnabled = getSwapButtonEnabled( + notifications = notifications, + priceImpact = priceImpact, + swapFee = swapFee, + isTangemPayWithdrawal = isTangemPayWithdrawal, + ), isHoldToConfirm = fromSwapCurrencyStatus.userWallet.isHotWallet, onClick = actions.onSwapClick, ), @@ -631,8 +637,10 @@ internal class StateBuilder( notifications: ImmutableList, priceImpact: PriceImpact, swapFee: SwapFee?, + isTangemPayWithdrawal: Boolean, ): Boolean { - return swapFee != null && notifications.none { notification -> + val isSwapTxReady = isTangemPayWithdrawal || swapFee != null + return isSwapTxReady && notifications.none { notification -> notification is SwapNotificationUM.Error || notification is NotificationUM.Error || notification is SwapNotificationUM.Warning.ExpressErrorWarning || notification is SwapNotificationUM.Warning.ExpressGeneralError || diff --git a/features/swap/impl/src/test/java/com/tangem/feature/swap/StateBuilderSwapButtonTest.kt b/features/swap/impl/src/test/java/com/tangem/feature/swap/StateBuilderSwapButtonTest.kt new file mode 100644 index 0000000000..184a9aa6a3 --- /dev/null +++ b/features/swap/impl/src/test/java/com/tangem/feature/swap/StateBuilderSwapButtonTest.kt @@ -0,0 +1,386 @@ +package com.tangem.feature.swap + +import com.google.common.truth.Truth.assertThat +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.transaction.Fee +import com.tangem.blockchain.common.transaction.TransactionFee +import com.tangem.blockchainsdk.utils.toNetworkId +import com.tangem.common.routing.AppRouter +import com.tangem.core.ui.extensions.stringReference +import com.tangem.domain.appcurrency.model.AppCurrency +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.network.Network +import com.tangem.domain.models.network.NetworkAddress +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.swap.models.SwapCurrencyStatus +import com.tangem.domain.transaction.usecase.gasless.IsGaslessFeeSupportedForNetwork +import com.tangem.feature.swap.domain.fee.TransactionFeeResult +import com.tangem.feature.swap.domain.models.SwapAmount +import com.tangem.feature.swap.domain.models.domain.* +import com.tangem.feature.swap.domain.models.ui.* +import com.tangem.feature.swap.models.* +import com.tangem.feature.swap.ui.StateBuilder +import com.tangem.features.send.v2.api.entity.FeeSelectorUM +import com.tangem.features.swap.SwapFeatureToggles +import com.tangem.utils.Provider +import io.mockk.every +import io.mockk.mockk +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import java.math.BigDecimal + +/** + * Tests for the [StateBuilder.getSwapButtonEnabled] path as exposed via + * [StateBuilder.createQuotesLoadedState]. + * + * The change under test: + * val isSwapTxReady = isTangemPayWithdrawal || swapFee != null + * + * Truth table asserted here: + * | isTangemPay | swapFee | blocking notification | expected isEnabled | + * |-------------|---------|----------------------|--------------------| + * | true | null | none | true | + * | true | null | present | false | + * | false | null | none | false | + * | false | non-null| none | true | + * | false | non-null| present | false | + */ +@DisplayName("StateBuilder — swap button enabled logic (isTangemPayWithdrawal gate)") +internal class StateBuilderSwapButtonTest { + + private val actions: UiActions = mockk(relaxed = true) + private val isBalanceHiddenProvider: Provider = mockk() + private val appCurrencyProvider: Provider = mockk() + private val isAccountsModeProvider: Provider = mockk() + private val isGaslessFeeSupportedForNetwork: IsGaslessFeeSupportedForNetwork = mockk() + private val swapFeatureToggles: SwapFeatureToggles = mockk(relaxed = true) + private val appRouter: AppRouter = mockk() + + private lateinit var sut: StateBuilder + + private val userWalletId = UserWalletId(stringValue = "deadbeef") + private val coldWallet: UserWallet.Cold = mockk(relaxed = true) { + every { walletId } returns userWalletId + } + + @BeforeEach + fun setup() { + every { isBalanceHiddenProvider() } returns false + every { appCurrencyProvider() } returns AppCurrency.Default + every { isAccountsModeProvider() } returns false + + sut = StateBuilder( + actions = actions, + isBalanceHiddenProvider = isBalanceHiddenProvider, + appCurrencyProvider = appCurrencyProvider, + isAccountsModeProvider = isAccountsModeProvider, + isGaslessFeeSupportedForNetwork = isGaslessFeeSupportedForNetwork, + swapFeatureToggles = swapFeatureToggles, + appRouter = appRouter, + ) + } + + @Nested + @DisplayName("Tangem Pay withdrawal (Payment account)") + inner class `Tangem Pay withdrawal` { + + @Test + @DisplayName("should enable swap button when Payment account, swapFee is null, and no blocking notifications") + fun `should enable swap button when Payment account and swapFee null and no blocking notifications`() { + val paymentAccount = Account.Payment(userWalletId) + val state = buildQuotesLoadedStateFor( + account = paymentAccount, + hasOutgoingTransaction = false, + permissionState = PermissionDataState.Empty, + ) + val baseHolder = buildInputtableHolder() + + val result = sut.createQuotesLoadedState( + uiStateHolder = baseHolder, + quoteModel = state, + feeCryptoCurrencyStatus = null, + swapProvider = buildProvider(ExchangeProviderType.CEX), + bestRatedProviderId = "p", + isNeedBestRateBadge = false, + needApplyFCARestrictions = false, + swapFee = null, + feeError = null, + ) + + assertThat(result.swapButton.isEnabled).isTrue() + } + + @Test + @DisplayName("should disable swap button when Payment account, swapFee is null, but a blocking notification is present") + fun `should disable swap button when Payment account and swapFee null and blocking notification`() { + val paymentAccount = Account.Payment(userWalletId) + val state = buildQuotesLoadedStateFor( + account = paymentAccount, + // hasOutgoingTransaction=true produces a SwapNotificationUM.Error which blocks the button + hasOutgoingTransaction = true, + permissionState = PermissionDataState.Empty, + ) + val baseHolder = buildInputtableHolder() + + val result = sut.createQuotesLoadedState( + uiStateHolder = baseHolder, + quoteModel = state, + feeCryptoCurrencyStatus = null, + swapProvider = buildProvider(ExchangeProviderType.CEX), + bestRatedProviderId = "p", + isNeedBestRateBadge = false, + needApplyFCARestrictions = false, + swapFee = null, + feeError = null, + ) + + assertThat(result.swapButton.isEnabled).isFalse() + } + } + + @Nested + @DisplayName("Non-Pay account (CryptoPortfolio)") + inner class `Non-Pay account` { + + @Test + @DisplayName("should disable swap button when CryptoPortfolio account and swapFee is null") + fun `should disable swap button when CryptoPortfolio account and swapFee null`() { + val cryptoAccount = Account.CryptoPortfolio.createMainAccount(userWalletId) + val state = buildQuotesLoadedStateFor( + account = cryptoAccount, + hasOutgoingTransaction = false, + permissionState = PermissionDataState.Empty, + ) + val baseHolder = buildInputtableHolder() + + val result = sut.createQuotesLoadedState( + uiStateHolder = baseHolder, + quoteModel = state, + feeCryptoCurrencyStatus = null, + swapProvider = buildProvider(ExchangeProviderType.CEX), + bestRatedProviderId = "p", + isNeedBestRateBadge = false, + needApplyFCARestrictions = false, + swapFee = null, + feeError = null, + ) + + assertThat(result.swapButton.isEnabled).isFalse() + } + + @Test + @DisplayName("should enable swap button when CryptoPortfolio account, swapFee is non-null, and no blocking notifications") + fun `should enable swap button when CryptoPortfolio account and swapFee non-null and no blocking notifications`() { + val cryptoAccount = Account.CryptoPortfolio.createMainAccount(userWalletId) + val state = buildQuotesLoadedStateFor( + account = cryptoAccount, + hasOutgoingTransaction = false, + permissionState = PermissionDataState.Empty, + ) + val baseHolder = buildInputtableHolder() + + val result = sut.createQuotesLoadedState( + uiStateHolder = baseHolder, + quoteModel = state, + feeCryptoCurrencyStatus = null, + swapProvider = buildProvider(ExchangeProviderType.CEX), + bestRatedProviderId = "p", + isNeedBestRateBadge = false, + needApplyFCARestrictions = false, + swapFee = buildSwapFee(), + feeError = null, + ) + + assertThat(result.swapButton.isEnabled).isTrue() + } + + @Test + @DisplayName("should disable swap button when CryptoPortfolio account, swapFee is non-null, but a blocking notification is present") + fun `should disable swap button when CryptoPortfolio account and swapFee non-null and blocking notification`() { + val cryptoAccount = Account.CryptoPortfolio.createMainAccount(userWalletId) + val state = buildQuotesLoadedStateFor( + account = cryptoAccount, + // PermissionRequired triggers SwapNotificationUM.Info.PermissionNeeded — in the blocking list + hasOutgoingTransaction = false, + permissionState = PermissionDataState.PermissionRequired( + isResetApproval = false, + spenderAddress = "0xspender", + ), + ) + val baseHolder = buildInputtableHolder() + + val result = sut.createQuotesLoadedState( + uiStateHolder = baseHolder, + quoteModel = state, + feeCryptoCurrencyStatus = null, + swapProvider = buildProvider(ExchangeProviderType.CEX), + bestRatedProviderId = "p", + isNeedBestRateBadge = false, + needApplyFCARestrictions = false, + swapFee = buildSwapFee(), + feeError = null, + ) + + assertThat(result.swapButton.isEnabled).isFalse() + } + } + + // ------------------------------------------------------------------------- + // Helpers + // ------------------------------------------------------------------------- + + /** + * Builds a [SwapStateHolder] whose send/receive cards are [SwapCardState.SwapCardData] with + * [TransactionCardType.Inputtable] type — required by [StateBuilder.createQuotesLoadedState]. + */ + private fun buildInputtableHolder(): SwapStateHolder { + val fromStatus = buildSwapCurrencyStatus(coldWallet) + val toStatus = buildSwapCurrencyStatus(coldWallet) + val emptyAmountState = SwapState.EmptyAmountState(stringReference("$0.00")) + val loading = sut.createInitialLoadingState() + return sut.createInitialReadyState( + uiStateHolder = loading, + emptyAmountState = emptyAmountState, + fromSwapCurrencyStatus = fromStatus, + toSwapCurrencyStatus = toStatus, + ) + } + + /** + * Builds a minimal [SwapState.QuotesLoadedState] with the given [account] on the from-currency + * and configurable notification triggers. + * + * @param hasOutgoingTransaction when true, [SwapNotificationsFactory] adds a + * [SwapNotificationUM.Error.TransactionInProgressWarning] — a blocking Error notification. + * @param permissionState when [PermissionDataState.PermissionRequired], adds a + * [SwapNotificationUM.Info.PermissionNeeded] — also in the blocking list. + */ + private fun buildQuotesLoadedStateFor( + account: Account, + hasOutgoingTransaction: Boolean, + permissionState: PermissionDataState, + ): SwapState.QuotesLoadedState { + val networkRawId = Blockchain.Ethereum.toNetworkId() + + val networkId = mockk(relaxed = true) { + every { rawId } returns Network.RawID(networkRawId) + } + val network = mockk(relaxed = true) { + every { rawId } returns networkRawId + every { id } returns networkId + every { currencySymbol } returns "ETH" + every { name } returns "Ethereum" + } + val currency = mockk(relaxed = true) { + every { this@mockk.network } returns network + every { this@mockk.symbol } returns "ETH" + every { this@mockk.decimals } returns 18 + } + val networkAddress = mockk(relaxed = true) { + every { defaultAddress } returns NetworkAddress.Address( + value = "0xTest", + type = NetworkAddress.Address.Type.Primary, + ) + } + val statusValue = mockk(relaxed = true) { + every { amount } returns BigDecimal("1") + every { this@mockk.networkAddress } returns networkAddress + every { pendingTransactions } returns emptySet() + } + val cryptoCurrencyStatus = CryptoCurrencyStatus(currency = currency, value = statusValue) + + val userWallet = mockk(relaxed = true) { + every { walletId } returns userWalletId + } + + val fromSwapCurrencyStatus = SwapCurrencyStatus( + userWallet = userWallet, + status = cryptoCurrencyStatus, + account = account, + ) + val toSwapCurrencyStatus = buildSwapCurrencyStatusWithCryptoPortfolio(coldWallet) + + return SwapState.QuotesLoadedState( + fromTokenInfo = TokenSwapInfo( + tokenAmount = SwapAmount(BigDecimal("0.5"), 18), + swapCurrencyStatus = fromSwapCurrencyStatus, + amountFiat = BigDecimal.ZERO, + ), + toTokenInfo = TokenSwapInfo( + tokenAmount = SwapAmount(BigDecimal("0.5"), 18), + swapCurrencyStatus = toSwapCurrencyStatus, + amountFiat = BigDecimal.ZERO, + ), + priceImpact = PriceImpact.Empty, + preparedSwapConfigState = PreparedSwapConfigState( + balanceStatus = SwapBalanceStatus.Sufficient, + hasOutgoingTransaction = hasOutgoingTransaction, + ), + permissionState = permissionState, + swapDataModel = null, + currencyCheck = null, + validationResult = null, + minAdaValue = null, + swapProvider = buildProvider(ExchangeProviderType.CEX), + ) + } + + private fun buildSwapCurrencyStatusWithCryptoPortfolio(userWallet: UserWallet): SwapCurrencyStatus { + val walletId = userWallet.walletId + val account = Account.CryptoPortfolio.createMainAccount(walletId) + val currency: CryptoCurrency = mockk(relaxed = true) { + every { symbol } returns "BTC" + every { decimals } returns 8 + every { network } returns mockk(relaxed = true) { + every { id } returns mockk(relaxed = true) + every { name } returns "Bitcoin" + every { currencySymbol } returns "BTC" + } + } + val statusValue: CryptoCurrencyStatus.Value = mockk(relaxed = true) { + every { amount } returns BigDecimal("1.0") + } + val cryptoCurrencyStatus = CryptoCurrencyStatus(currency = currency, value = statusValue) + return SwapCurrencyStatus( + userWallet = userWallet, + status = cryptoCurrencyStatus, + account = account, + ) + } + + private fun buildProvider(type: ExchangeProviderType): SwapProvider = SwapProvider( + providerId = "p", + rateTypes = listOf(RateType.FLOAT), + name = "Provider", + type = type, + imageLarge = "", + termsOfUse = null, + privacyPolicy = null, + isRecommended = false, + slippage = null, + isExtraIdSupported = false, + ) + + private fun buildSwapFee(): SwapFee { + val amount = mockk(relaxed = true) { + every { value } returns BigDecimal("0.001") + } + val fee = mockk(relaxed = true) { + every { this@mockk.amount } returns amount + } + val feeTokenStatus = mockk(relaxed = true) + return SwapFee( + fee = fee, + transactionFeeResult = TransactionFeeResult.Loaded(mockk(relaxed = true)), + selectedFeeToken = feeTokenStatus, + otherNativeFee = BigDecimal.ZERO, + feeBucket = FeeBucket.MARKET, + ) + } +} \ No newline at end of file From 865d4a1ed5efd4e2cb8d7d760ecf9fdcd85a67cf Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 27 May 2026 19:44:31 +0500 Subject: [PATCH 5/6] Updated on 2026-08-14 --- .../component/impl/DefaultRoutingComponent.kt | 24 +++++++++++++++---- .../tangem/tap/routing/utils/ChildFactory.kt | 2 +- .../com/tangem/common/routing/AppRoute.kt | 1 + .../configs/feature_toggles_config.json | 4 ++++ 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt b/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt index 58d3c9e7fb..6a7f07951e 100644 --- a/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt +++ b/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt @@ -44,6 +44,7 @@ import com.tangem.domain.onboarding.repository.OnboardingRepository import com.tangem.domain.settings.NeverRequestPermissionUseCase import com.tangem.domain.settings.NeverToInitiallyAskPermissionUseCase import com.tangem.domain.settings.ShouldInitiallyAskPermissionUseCase +import com.tangem.feature.referral.domain.ShouldShowMobileWalletPromoUseCase import com.tangem.features.hotwallet.HotAccessCodeRequestComponent import com.tangem.features.hotwallet.accesscoderequest.proxy.HotWalletPasswordRequesterProxy import com.tangem.features.onboarding.v2.common.analytics.OnboardingEvent @@ -70,7 +71,7 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.launch -@Suppress("LongParameterList") +@Suppress("LongParameterList", "LargeClass") internal class DefaultRoutingComponent @AssistedInject constructor( @Assisted context: AppComponentContext, @Assisted val initialStack: List?, @@ -99,6 +100,7 @@ internal class DefaultRoutingComponent @AssistedInject constructor( private val shouldInitiallyAskPermissionUseCase: ShouldInitiallyAskPermissionUseCase, private val neverRequestPermissionUseCase: NeverRequestPermissionUseCase, private val featureTogglesManager: FeatureTogglesManager, + private val shouldShowMobileWalletPromoUseCase: ShouldShowMobileWalletPromoUseCase, ) : RoutingComponent, AppComponentContext by context, SnackbarHandler { @@ -229,18 +231,32 @@ internal class DefaultRoutingComponent @AssistedInject constructor( } } + val isHideStoriesForReferralEnabled = featureTogglesManager.isFeatureEnabled( + FeatureToggles.TWI_1512_HIDE_STORIES_FOR_REFERRAL_ENABLED, + ) + // Referral users skip the Home stories screen and land directly on the + // mobile wallet creation flow. + val afterEmptyRoute: AppRoute = if (isHideStoriesForReferralEnabled && shouldShowMobileWalletPromoUseCase()) { + AppRoute.CreateWalletStart(mode = AppRoute.CreateWalletStart.Mode.HotWallet) + } else { + AppRoute.Home(launchMode = launchMode) + } + val shouldAskPushPermission = shouldInitiallyAskPermissionUseCase(PUSH_PERMISSION).getOrNull() - ?: return AppRoute.Home(launchMode = launchMode) + ?: return afterEmptyRoute return if (shouldAskPushPermission) { notificationsRepository.setShouldShowNotifications( key = NotificationId.EnablePushesReminderNotification.key, value = false, ) - AppRoute.PushNotification(AppRoute.PushNotification.Source.Stories) + AppRoute.PushNotification( + source = AppRoute.PushNotification.Source.Stories, + nextRoute = afterEmptyRoute, + ) } else { neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION) neverRequestPermissionUseCase(PUSH_PERMISSION) - AppRoute.Home(launchMode = launchMode) + afterEmptyRoute } } diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index da1aaddef4..3be7df1c1f 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -448,7 +448,7 @@ internal class ChildFactory @Inject constructor( params = PushNotificationsParams( modelCallbacks = PushNotificationsModelCallbacksStub(), source = route.source, - nextRoute = AppRoute.Home(), + nextRoute = route.nextRoute ?: AppRoute.Home(), ), componentFactory = pushNotificationsComponentFactory, ) diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index fd1fd59c15..4854b69392 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -243,6 +243,7 @@ sealed class AppRoute(val path: String) : Route { @Serializable data class PushNotification( val source: Source, + val nextRoute: AppRoute? = null, ) : AppRoute(path = "/push_notification") { enum class Source { Stories, diff --git a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json index e86e9ffe5a..45a73be769 100644 --- a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json @@ -102,5 +102,9 @@ { "name": "AND_15154_YIELD_PROMO_ENABLED", "version": "undefined" + }, + { + "name": "TWI_1512_HIDE_STORIES_FOR_REFERRAL_ENABLED", + "version": "5.39" } ] From c69f44cb8c3c1e944bbced45d32c2536dee50d2d Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 27 May 2026 14:44:54 +0000 Subject: [PATCH 6/6] Updated on 2026-08-14 --- gradle/tangem_dependencies.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 8f5b28ffc0..b745566b76 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,9 +5,9 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "releases-5.39-1530" +tangemBlockchainSdk = "develop-1527" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "releases-5.39-622" +tangemCardSdk = "develop-620" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "tangem-master-21" #tangemVico = "0.0.1" # Keep it! - used for local builds ^