diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/visa/entity/PaymentAccountStatusValueDM.kt b/core/datasource/src/main/java/com/tangem/datasource/local/visa/entity/PaymentAccountStatusValueDM.kt index f9047d1e43..253484bf99 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/visa/entity/PaymentAccountStatusValueDM.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/visa/entity/PaymentAccountStatusValueDM.kt @@ -45,6 +45,7 @@ sealed interface PaymentAccountStatusValueDM { @Json(name = "deposit_address") val depositAddress: String?, @Json(name = "fiat_balance") val fiatBalance: FiatBalanceDM, @Json(name = "crypto_balance") val cryptoBalance: CryptoBalanceDM, + @Json(name = "fiat_rate") val fiatRate: BigDecimal?, @Json(name = "available_for_withdrawal") val availableForWithdrawal: BigDecimal, @Json(name = "cards") val cards: List, ) : PaymentAccountStatusValueDM @@ -58,6 +59,7 @@ sealed interface PaymentAccountStatusValueDM { @NameLabel("deactivated_account") data class DeactivatedAccount( @Json(name = "deactivated_account") val marker: Boolean = true, + @Json(name = "fiat_rate") val fiatRate: BigDecimal?, @Json(name = "fiat_balance") val fiatBalance: FiatBalanceDM, @Json(name = "crypto_balance") val cryptoBalance: CryptoBalanceDM, ) : PaymentAccountStatusValueDM diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayCryptoCurrencyFactory.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayCryptoCurrencyFactory.kt deleted file mode 100644 index ca7b011917..0000000000 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayCryptoCurrencyFactory.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.tangem.data.pay - -import arrow.core.Either -import arrow.core.Either.Companion.catch -import com.tangem.blockchain.blockchains.ethereum.Chain -import com.tangem.blockchainsdk.utils.ExcludedBlockchains -import com.tangem.core.error.UniversalError -import com.tangem.data.common.currency.CryptoCurrencyFactory -import com.tangem.data.common.network.NetworkFactory -import com.tangem.data.pay.entity.TangemPayCurrencyFactory -import com.tangem.data.pay.util.TangemPayErrorConverter -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.pay.TangemPayCryptoCurrencyFactory -import com.tangem.utils.logging.TangemLogger -import javax.inject.Inject - -private const val TAG = "TangemPay: DefaultTangemPayCryptoCurrencyFactory" - -@Deprecated("Use TangemPayCurrencyFactory instead") -internal class DefaultTangemPayCryptoCurrencyFactory @Inject constructor( - excludedBlockchains: ExcludedBlockchains, - private val errorConverter: TangemPayErrorConverter, -) : TangemPayCryptoCurrencyFactory { - - private val cryptoCurrencyFactory by lazy(mode = LazyThreadSafetyMode.NONE) { - CryptoCurrencyFactory(excludedBlockchains) - } - private val networkFactory by lazy(mode = LazyThreadSafetyMode.NONE) { - NetworkFactory(excludedBlockchains) - } - - override fun create(userWallet: UserWallet, chainId: Int): Either { - return catch { - val chain = requireNotNull(Chain.entries.find { it.id == chainId }) { "Can not find chain with $chainId" } - val blockchain = requireNotNull(chain.blockchain) - val network = networkFactory.create( - blockchain = blockchain, - extraDerivationPath = null, - userWallet = userWallet, - ) - cryptoCurrencyFactory.createToken( - network = requireNotNull(network), - rawId = CryptoCurrency.RawID(TangemPayCurrencyFactory.TOKEN_ID), - name = TangemPayCurrencyFactory.TOKEN_NAME, - symbol = TangemPayCurrencyFactory.TOKEN_NAME, - contractAddress = TangemPayCurrencyFactory.TOKEN_CONTRACT_ADDRESS, - decimals = TangemPayCurrencyFactory.TOKEN_DECIMALS, - ) - }.mapLeft { exception -> - TangemLogger.withTag(TAG).e("Error", exception) - errorConverter.convert(exception) - } - } -} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/converter/PaymentAccountStatusValueDMConverter.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/converter/PaymentAccountStatusValueDMConverter.kt index 9eae098385..1b6a9681c8 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/converter/PaymentAccountStatusValueDMConverter.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/converter/PaymentAccountStatusValueDMConverter.kt @@ -1,7 +1,6 @@ package com.tangem.data.pay.converter import arrow.core.getOrElse -import com.tangem.data.pay.entity.TangemPayCurrencyFactory import com.tangem.datasource.local.visa.entity.PaymentAccountStatusValueDM import com.tangem.domain.models.StatusSource import com.tangem.domain.models.account.CardDisplayName @@ -11,6 +10,7 @@ import com.tangem.domain.models.pay.TangemPayCardLimit import com.tangem.domain.models.pay.TangemPayCardLimitData import com.tangem.domain.models.pay.TangemPayCardLimitPeriod import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.pay.TangemPayCurrencyFactory import javax.inject.Inject import javax.inject.Singleton @@ -42,6 +42,7 @@ internal class PaymentAccountStatusValueDMConverter @Inject constructor( fiatBalance = value.fiatBalance.toDM(), cryptoBalance = value.cryptoBalance.toDM(), availableForWithdrawal = value.availableForWithdrawal, + fiatRate = value.fiatRate, cards = value.cards.map { card -> PaymentAccountStatusValueDM.TangemPayCard( id = card.id, @@ -60,6 +61,7 @@ internal class PaymentAccountStatusValueDMConverter @Inject constructor( ) is PaymentAccountStatusValue.Empty -> PaymentAccountStatusValueDM.Empty() is PaymentAccountStatusValue.Deactivated -> PaymentAccountStatusValueDM.DeactivatedAccount( + fiatRate = value.fiatRate, fiatBalance = value.fiatBalance.toDM(), cryptoBalance = value.cryptoBalance.toDM(), ) @@ -92,6 +94,7 @@ internal class PaymentAccountStatusValueDMConverter @Inject constructor( cryptoBalance = value.cryptoBalance.toDomain(), availableForWithdrawal = value.availableForWithdrawal, cryptoCurrency = cryptoCurrency, + fiatRate = value.fiatRate, cards = value.cards.map { card -> TangemPayCard( id = card.id, @@ -121,6 +124,7 @@ internal class PaymentAccountStatusValueDMConverter @Inject constructor( fiatBalance = value.fiatBalance.toDomain(), cryptoBalance = value.cryptoBalance.toDomain(), cryptoCurrency = cryptoCurrency, + fiatRate = value.fiatRate, ) null -> PaymentAccountStatusValue.Error.Unavailable } diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt index 0c6f02473e..ebac88be49 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt @@ -5,9 +5,9 @@ import androidx.datastore.core.DataStoreFactory import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler import androidx.datastore.dataStoreFile import com.squareup.moshi.Moshi -import com.tangem.data.pay.DefaultTangemPayCryptoCurrencyFactory import com.tangem.data.pay.DefaultTangemPayEligibilityManager import com.tangem.data.pay.converter.PaymentAccountStatusValueDMConverter +import com.tangem.data.pay.entity.DefaultTangemPayCurrencyFactory import com.tangem.data.pay.flow.DefaultPaymentAccountStatusFetcher import com.tangem.data.pay.flow.DefaultPaymentAccountStatusProducer import com.tangem.data.pay.repository.* @@ -20,19 +20,12 @@ import com.tangem.datasource.local.datastore.RuntimeSharedStore import com.tangem.datasource.local.visa.entity.PaymentAccountStatusValueDM import com.tangem.datasource.utils.MoshiDataStoreSerializer import com.tangem.datasource.utils.mapWithStringKeyTypes -import com.tangem.domain.pay.TangemPayCryptoCurrencyFactory +import com.tangem.domain.pay.TangemPayCurrencyFactory import com.tangem.domain.pay.TangemPayEligibilityManager import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher import com.tangem.domain.pay.flow.PaymentAccountStatusProducer import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier import com.tangem.domain.pay.repository.* -import com.tangem.domain.pay.usecase.ChangeCardFrozenStateUseCase -import com.tangem.domain.pay.usecase.GetPaymentAccountCryptoCurrencyStatusUseCase -import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase -import com.tangem.domain.pay.usecase.ReissueTangemPayCardUseCase -import com.tangem.domain.pay.usecase.SetTangemPayCardLimitUseCase -import com.tangem.domain.pay.usecase.StartTangemPayOrderPollingUseCase -import com.tangem.domain.pay.usecase.UpdateTangemPayCardNameUseCase import com.tangem.domain.pay.usecase.* import com.tangem.domain.tangempay.GetTangemPayCurrencyStatusUseCase import com.tangem.domain.tangempay.GetTangemPayCustomerIdUseCase @@ -74,9 +67,7 @@ internal interface TangemPayDataModule { @Binds @Singleton - fun bindTangemPayCryptoCurrencyFactory( - factory: DefaultTangemPayCryptoCurrencyFactory, - ): TangemPayCryptoCurrencyFactory + fun bindTangemPayCryptoCurrencyFactory(factory: DefaultTangemPayCurrencyFactory): TangemPayCurrencyFactory @Binds @Singleton diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/entity/TangemPayCurrencyFactory.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/entity/DefaultTangemPayCurrencyFactory.kt similarity index 69% rename from data/visa/src/main/kotlin/com/tangem/data/pay/entity/TangemPayCurrencyFactory.kt rename to data/visa/src/main/kotlin/com/tangem/data/pay/entity/DefaultTangemPayCurrencyFactory.kt index ede6bba797..711c82bc5f 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/entity/TangemPayCurrencyFactory.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/entity/DefaultTangemPayCurrencyFactory.kt @@ -8,20 +8,21 @@ import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.common.wallets.requireUserWalletsSync import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.pay.TangemPayCurrencyFactory import javax.inject.Inject import javax.inject.Singleton @Singleton -internal class TangemPayCurrencyFactory @Inject constructor( +internal class DefaultTangemPayCurrencyFactory @Inject constructor( excludedBlockchains: ExcludedBlockchains, private val userWalletsListRepository: UserWalletsListRepository, private val networkFactory: NetworkFactory, -) { +) : TangemPayCurrencyFactory { private val cryptoCurrencyFactory by lazy(mode = LazyThreadSafetyMode.NONE) { CryptoCurrencyFactory(excludedBlockchains) } - fun create(userWalletId: UserWalletId): CryptoCurrency.Token { + override fun create(userWalletId: UserWalletId): CryptoCurrency.Token { val userWallet = userWalletsListRepository.requireUserWalletsSync() .firstOrNull { it.walletId == userWalletId } ?: error("User wallet with id $userWalletId not found") @@ -32,18 +33,11 @@ internal class TangemPayCurrencyFactory @Inject constructor( ) return cryptoCurrencyFactory.createToken( network = requireNotNull(network), - rawId = CryptoCurrency.RawID(TOKEN_ID), - name = TOKEN_NAME, - symbol = TOKEN_NAME, - contractAddress = TOKEN_CONTRACT_ADDRESS, - decimals = TOKEN_DECIMALS, + rawId = TangemPayCurrencyFactory.TOKEN_ID, + name = TangemPayCurrencyFactory.TOKEN_NAME, + symbol = TangemPayCurrencyFactory.TOKEN_NAME, + contractAddress = TangemPayCurrencyFactory.TOKEN_CONTRACT_ADDRESS, + decimals = TangemPayCurrencyFactory.TOKEN_DECIMALS, ) } - - companion object { - internal const val TOKEN_ID = "usd-coin" - internal const val TOKEN_NAME = "USDC" - internal const val TOKEN_CONTRACT_ADDRESS = "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359" - internal const val TOKEN_DECIMALS = 6 - } } \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcher.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcher.kt index 81159ea6a3..bf330d9db2 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcher.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcher.kt @@ -1,7 +1,6 @@ package com.tangem.data.pay.flow import arrow.core.Either -import com.tangem.data.pay.entity.TangemPayCurrencyFactory import com.tangem.data.pay.store.PaymentAccountStatusesStore import com.tangem.domain.core.utils.catchOn import com.tangem.domain.models.StatusSource @@ -11,7 +10,9 @@ import com.tangem.domain.models.account.PaymentAccountStatusValue import com.tangem.domain.models.kyc.KycStatus import com.tangem.domain.models.pay.TangemPayCard import com.tangem.domain.models.pay.TangemPayCardLimitData +import com.tangem.domain.models.quote.QuoteStatus import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.pay.TangemPayCurrencyFactory import com.tangem.domain.pay.TangemPayEligibilityManager import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher import com.tangem.domain.pay.model.CustomerInfo @@ -21,6 +22,8 @@ import com.tangem.domain.pay.model.TangemPayEntryPoint import com.tangem.domain.pay.repository.CustomerOrderRepository import com.tangem.domain.pay.repository.OnboardingRepository import com.tangem.domain.pay.repository.TangemPayReissueCardRepository +import com.tangem.domain.quotes.single.SingleQuoteStatusProducer +import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier import com.tangem.domain.visa.error.VisaApiError import com.tangem.domain.visa.model.TangemPayCardFrozenState import com.tangem.security.DeviceSecurityInfoProvider @@ -30,6 +33,7 @@ import com.tangem.utils.logging.TangemLogger import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.delay import kotlinx.coroutines.isActive +import java.math.BigDecimal import javax.inject.Inject import kotlin.time.Duration.Companion.minutes @@ -45,6 +49,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor( private val tangemPayCurrencyFactory: TangemPayCurrencyFactory, private val eligibilityManager: TangemPayEligibilityManager, private val reissueCardRepository: TangemPayReissueCardRepository, + private val singleQuoteSupplier: SingleQuoteStatusSupplier, ) : PaymentAccountStatusFetcher { private val logger = TangemLogger.withTag(TAG) @@ -257,6 +262,9 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor( } private suspend fun CustomerInfo.mapToPaymentAccountStatus(userWalletId: UserWalletId): PaymentAccountStatusValue { + val quotesData = singleQuoteSupplier.getSyncOrNull( + params = SingleQuoteStatusProducer.Params(rawCurrencyId = TangemPayCurrencyFactory.TOKEN_ID), + )?.value as? QuoteStatus.Data val cardInfo = this.cardInfo val productInstance = this.productInstance @@ -279,12 +287,14 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor( fiatBalance = fiatBalance, cryptoBalance = cryptoBalance, cryptoCurrency = tangemPayCurrencyFactory.create(userWalletId), + fiatRate = quotesData?.fiatRate, ) } cardInfo != null && productInstance != null && !customerId.isNullOrEmpty() -> convertToContentState( userWalletId = userWalletId, productInstance = productInstance, cardInfo = cardInfo, + fiatRate = quotesData?.fiatRate, customerId = requireNotNull(customerId) { "CustomerId must not be null" }, ) else -> PaymentAccountStatusValue.IssuingCard(source = StatusSource.ACTUAL) @@ -296,6 +306,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor( productInstance: CustomerInfo.ProductInstance, cardInfo: CustomerInfo.CardInfo, customerId: String, + fiatRate: BigDecimal?, ): PaymentAccountStatusValue { val reissueOrder = reissueCardRepository.getReissueOrderInfo( userWalletId = userWalletId, @@ -316,6 +327,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor( cryptoBalance = cardInfo.cryptoBalance, availableForWithdrawal = cardInfo.availableForWithdrawal, cryptoCurrency = cryptoCurrency, + fiatRate = fiatRate, cards = listOf( TangemPayCard( id = productInstance.cardId, diff --git a/data/visa/src/test/kotlin/com/tangem/data/pay/converter/PaymentAccountStatusValueDMConverterTest.kt b/data/visa/src/test/kotlin/com/tangem/data/pay/converter/PaymentAccountStatusValueDMConverterTest.kt index 689f793d48..dfceb506ec 100644 --- a/data/visa/src/test/kotlin/com/tangem/data/pay/converter/PaymentAccountStatusValueDMConverterTest.kt +++ b/data/visa/src/test/kotlin/com/tangem/data/pay/converter/PaymentAccountStatusValueDMConverterTest.kt @@ -1,12 +1,12 @@ package com.tangem.data.pay.converter import com.google.common.truth.Truth.assertThat -import com.tangem.data.pay.entity.TangemPayCurrencyFactory import com.tangem.datasource.local.visa.entity.PaymentAccountStatusValueDM import com.tangem.domain.models.StatusSource import com.tangem.domain.models.account.PaymentAccountStatusValue import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.pay.TangemPayCurrencyFactory import io.mockk.every import io.mockk.mockk import org.junit.jupiter.api.Nested @@ -70,6 +70,7 @@ internal class PaymentAccountStatusValueDMConverterTest { ), cryptoBalance = cryptoBalance(), cryptoCurrency = cryptoCurrency, + fiatRate = BigDecimal("1.05"), ) // WHEN @@ -80,6 +81,7 @@ internal class PaymentAccountStatusValueDMConverterTest { val dm = result as PaymentAccountStatusValueDM.DeactivatedAccount assertThat(dm.fiatBalance.availableBalance).isEqualTo(BigDecimal("100")) assertThat(dm.fiatBalance.currency).isEqualTo("USD") + assertThat(dm.fiatRate).isEqualTo(BigDecimal("1.05")) } @Test @@ -144,6 +146,7 @@ internal class PaymentAccountStatusValueDMConverterTest { currency = "EUR", ), cryptoBalance = cryptoBalanceDM(), + fiatRate = BigDecimal("0.92"), ) // WHEN @@ -155,6 +158,7 @@ internal class PaymentAccountStatusValueDMConverterTest { assertThat(deactivated.source).isEqualTo(StatusSource.CACHE) assertThat(deactivated.fiatBalance.availableBalance).isEqualTo(BigDecimal("200")) assertThat(deactivated.fiatBalance.currency).isEqualTo("EUR") + assertThat(deactivated.fiatRate).isEqualTo(BigDecimal("0.92")) } @Test diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/account/PaymentAccountStatusValue.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/account/PaymentAccountStatusValue.kt index 756bb4b54a..4c10c467e0 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/account/PaymentAccountStatusValue.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/account/PaymentAccountStatusValue.kt @@ -31,8 +31,14 @@ sealed class PaymentAccountStatusValue { is UnderReview, -> TotalFiatBalance.Loaded(amount = SerializedBigDecimal.ZERO, source = source) is Loading -> TotalFiatBalance.Loading - is Loaded -> TotalFiatBalance.Loaded(amount = fiatBalance.availableBalance, source = source) - is Deactivated -> TotalFiatBalance.Loaded(amount = fiatBalance.availableBalance, source = source) + is Loaded -> { + val rate = this.fiatRate ?: return TotalFiatBalance.Failed + TotalFiatBalance.Loaded(amount = fiatBalance.availableBalance.multiply(rate), source = source) + } + is Deactivated -> { + val rate = this.fiatRate ?: return TotalFiatBalance.Failed + TotalFiatBalance.Loaded(amount = fiatBalance.availableBalance.multiply(rate), source = source) + } } /** @@ -99,6 +105,11 @@ sealed class PaymentAccountStatusValue { * * @property source The source of the status information. * @property fiatBalance The fiat balance details. + * @property cryptoBalance The crypto balance details. + * @property cryptoCurrency The crypto currency held by the deactivated account. + * @property fiatRate Exchange rate of [cryptoCurrency] to the account's fiat currency, + * or `null` if the quote is not yet available. When `null`, + * [totalFiatBalance] resolves to [TotalFiatBalance.Failed]. */ @Serializable data class Deactivated( @@ -106,25 +117,15 @@ sealed class PaymentAccountStatusValue { val fiatBalance: FiatBalance, val cryptoBalance: CryptoBalance, val cryptoCurrency: CryptoCurrency.Token, + val fiatRate: SerializedBigDecimal?, ) : PaymentAccountStatusValue() { val cryptoCurrencyStatus: CryptoCurrencyStatus = CryptoCurrencyStatus( currency = cryptoCurrency, - value = CryptoCurrencyStatus.Loaded( + value = buildCryptoCurrencyStatusValue( amount = cryptoBalance.balance, fiatAmount = fiatBalance.availableBalance, - fiatRate = BigDecimal.ONE, - priceChange = BigDecimal.ZERO, - networkAddress = NetworkAddress.Single( - defaultAddress = NetworkAddress.Address( - type = NetworkAddress.Address.Type.Primary, - value = cryptoBalance.depositAddress, - ), - ), - sources = CryptoCurrencyStatus.Sources(), - pendingTransactions = emptySet(), - stakingBalance = null, - yieldSupplyStatus = null, - hasCurrentNetworkTransactions = false, + fiatRate = fiatRate, + depositAddress = cryptoBalance.depositAddress, ), ) } @@ -139,7 +140,11 @@ sealed class PaymentAccountStatusValue { * @property fiatBalance The fiat balance details. * @property cryptoBalance The crypto balance details. * @property availableForWithdrawal The crypto amount currently available for withdrawal/swap (excludes pending/locked funds). + * @property cryptoCurrency The crypto currency held by the account. * @property cards The list of user's cards. + * @property fiatRate Exchange rate of [cryptoCurrency] to the account's fiat currency, + * or `null` if the quote is not yet available. When `null`, + * [totalFiatBalance] resolves to [TotalFiatBalance.Failed]. */ @Serializable data class Loaded( @@ -152,25 +157,15 @@ sealed class PaymentAccountStatusValue { val availableForWithdrawal: SerializedBigDecimal, val cryptoCurrency: CryptoCurrency.Token, val cards: List, + val fiatRate: SerializedBigDecimal?, ) : PaymentAccountStatusValue() { val cryptoCurrencyStatus: CryptoCurrencyStatus = CryptoCurrencyStatus( currency = cryptoCurrency, - value = CryptoCurrencyStatus.Loaded( + value = buildCryptoCurrencyStatusValue( amount = availableForWithdrawal, fiatAmount = fiatBalance.availableBalance, - fiatRate = BigDecimal.ONE, - priceChange = BigDecimal.ZERO, - networkAddress = NetworkAddress.Single( - defaultAddress = NetworkAddress.Address( - type = NetworkAddress.Address.Type.Primary, - value = cryptoBalance.depositAddress, - ), - ), - sources = CryptoCurrencyStatus.Sources(), - pendingTransactions = emptySet(), - stakingBalance = null, - yieldSupplyStatus = null, - hasCurrentNetworkTransactions = false, + fiatRate = fiatRate, + depositAddress = cryptoBalance.depositAddress, ), ) } @@ -235,6 +230,44 @@ sealed class PaymentAccountStatusValue { ) } +private fun buildCryptoCurrencyStatusValue( + amount: SerializedBigDecimal, + fiatAmount: SerializedBigDecimal, + fiatRate: SerializedBigDecimal?, + depositAddress: String, +): CryptoCurrencyStatus.Value { + val networkAddress = NetworkAddress.Single( + defaultAddress = NetworkAddress.Address( + type = NetworkAddress.Address.Type.Primary, + value = depositAddress, + ), + ) + return if (fiatRate != null) { + CryptoCurrencyStatus.Loaded( + amount = amount, + fiatAmount = fiatAmount, + fiatRate = fiatRate, + priceChange = BigDecimal.ZERO, + networkAddress = networkAddress, + sources = CryptoCurrencyStatus.Sources(), + pendingTransactions = emptySet(), + stakingBalance = null, + yieldSupplyStatus = null, + hasCurrentNetworkTransactions = false, + ) + } else { + CryptoCurrencyStatus.NoQuote( + amount = amount, + networkAddress = networkAddress, + stakingBalance = null, + yieldSupplyStatus = null, + hasCurrentNetworkTransactions = false, + pendingTransactions = emptySet(), + sources = CryptoCurrencyStatus.Sources(), + ) + } +} + fun Loaded.hasCardWithId(cardId: String): Boolean = cards.any { it.id == cardId } fun Loaded.findCardWithId(cardId: String): TangemPayCard? = cards.firstOrNull { it.id == cardId } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/BalanceFetchingOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/BalanceFetchingOperations.kt index 2071908140..babba04024 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/BalanceFetchingOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/BalanceFetchingOperations.kt @@ -51,7 +51,9 @@ class BalanceFetchingOperations( async { val result = when (source) { FetchingSource.NETWORK -> fetchNetworks(userWalletId, currencies) - FetchingSource.QUOTE -> fetchQuotes(currencies) + FetchingSource.QUOTE -> fetchQuotes( + currencies.mapNotNullTo(hashSetOf()) { it.id.rawCurrencyId }, + ) FetchingSource.STAKING -> fetchStaking(userWalletId, currencies) } source to result @@ -85,17 +87,14 @@ class BalanceFetchingOperations( } /** - * Fetches quotes for the given currencies. + * Fetches quotes for the given raw currency ids. * - * @param currencies the cryptocurrencies to fetch quotes for + * @param rawCurrencyIds the raw currency ids to fetch quotes for * @return Either with Unit on success or Throwable on failure */ - suspend fun fetchQuotes(currencies: Collection): Either { + suspend fun fetchQuotes(rawCurrencyIds: Set): Either { return multiQuoteStatusFetcher( - params = MultiQuoteStatusFetcher.Params( - currenciesIds = currencies.mapNotNullTo(hashSetOf()) { it.id.rawCurrencyId }, - appCurrencyId = null, - ), + params = MultiQuoteStatusFetcher.Params(currenciesIds = rawCurrencyIds, appCurrencyId = null), ) } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/WalletBalanceFetcher.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/WalletBalanceFetcher.kt index 8f43d68f33..f4345e9c39 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/WalletBalanceFetcher.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/WalletBalanceFetcher.kt @@ -13,6 +13,7 @@ import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher +import com.tangem.domain.pay.TangemPayCurrencyFactory import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher import com.tangem.domain.staking.StakingIdFactory @@ -173,6 +174,7 @@ class WalletBalanceFetcher internal constructor( // Fetch TangemPay separately — may run long-polling, so it must not block balance error checking if (fetchingSources.any { it is WalletFetchingSource.TangemPay }) { + balanceFetchingOperations.fetchQuotes(rawCurrencyIds = setOf(TangemPayCurrencyFactory.TOKEN_ID)) paymentAccountStatusFetcher.invoke(PaymentAccountStatusFetcher.Params(userWalletId)) } } diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/TangemPayCryptoCurrencyFactory.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/TangemPayCryptoCurrencyFactory.kt deleted file mode 100644 index 31a2537914..0000000000 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/TangemPayCryptoCurrencyFactory.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.tangem.domain.pay - -import arrow.core.Either -import com.tangem.core.error.UniversalError -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.models.wallet.UserWallet - -@Deprecated("TangemPayCurrencyFactory") -interface TangemPayCryptoCurrencyFactory { - - fun create(userWallet: UserWallet, chainId: Int): Either -} \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/TangemPayCurrencyFactory.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/TangemPayCurrencyFactory.kt new file mode 100644 index 0000000000..39bdae4191 --- /dev/null +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/TangemPayCurrencyFactory.kt @@ -0,0 +1,29 @@ +package com.tangem.domain.pay + +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.wallet.UserWalletId + +/** + * Factory that builds the [CryptoCurrency.Token] used by Tangem Pay (USDC on Polygon) for a given user wallet. + * + * Replaces the deprecated `TangemPayCryptoCurrencyFactory`: callers no longer pass the chain id explicitly — + * the underlying network is resolved from the wallet. + */ +interface TangemPayCurrencyFactory { + + /** + * Builds the Tangem Pay token bound to the network of the wallet identified by [userWalletId]. + * + * @throws IllegalStateException if no wallet with [userWalletId] is currently loaded. + */ + fun create(userWalletId: UserWalletId): CryptoCurrency.Token + + /** Hardcoded token metadata for the Tangem Pay currency (USDC on Polygon). */ + companion object { + /** CoinGecko-style raw id used to query quotes for the Tangem Pay token. */ + val TOKEN_ID = CryptoCurrency.RawID("usd-coin") + const val TOKEN_NAME = "USDC" + const val TOKEN_CONTRACT_ADDRESS = "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359" + const val TOKEN_DECIMALS = 6 + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt index 7a5d2caf8c..c9f1c94895 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt @@ -10,6 +10,8 @@ import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsSta import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.account.models.AccountStatusList import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer +import com.tangem.domain.assetsdiscovery.model.AssetsDiscoveryProgress +import com.tangem.domain.assetsdiscovery.usecase.ObserveAssetsDiscoveryUseCase import com.tangem.domain.card.CardTypesResolver import com.tangem.domain.card.common.util.cardTypesResolver import com.tangem.domain.demo.IsDemoCardUseCase @@ -26,19 +28,17 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.notifications.repository.NotificationsRepository import com.tangem.domain.settings.IsReadyToShowRateAppUseCase -import com.tangem.domain.assetsdiscovery.model.AssetsDiscoveryProgress -import com.tangem.domain.assetsdiscovery.usecase.ObserveAssetsDiscoveryUseCase import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase import com.tangem.domain.yield.supply.promo.usecase.ShouldShowYieldBoostMainBannerUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase import com.tangem.feature.wallet.child.wallet.model.WalletActivationBannerType -import com.tangem.features.yield.supply.api.YieldSupplyFeatureToggles import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.account.AccountDependencies import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification import com.tangem.features.hotwallet.HotWalletFeatureToggles import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles +import com.tangem.features.yield.supply.api.YieldSupplyFeatureToggles import com.tangem.hot.sdk.model.HotWalletId import com.tangem.lib.crypto.BlockchainUtils import com.tangem.utils.extensions.addIf @@ -222,10 +222,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( ) { val notification = when (status.value) { is PaymentAccountStatusValue.Error.NotSynced -> WalletNotification.Warning.TangemPayRefreshNeeded( - buttonText = when (userWallet) { - is UserWallet.Cold -> resourceReference(id = R.string.home_button_scan) - is UserWallet.Hot -> resourceReference(id = R.string.tangempay_sync_needed_button) - }, + buttonText = resourceReference(id = R.string.tangempay_sync_needed_button), onRefreshClick = { walletClickIntents.onRefreshPayToken(userWallet) }, shouldShowProgress = false, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetWalletNotificationsFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetWalletNotificationsFactory.kt index 2bd65ca2d6..c4238c5988 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetWalletNotificationsFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetWalletNotificationsFactory.kt @@ -251,10 +251,7 @@ internal class GetWalletNotificationsFactory @Inject constructor( ) { val notification = when (status.value) { is PaymentAccountStatusValue.Error.NotSynced -> WalletNotificationUM.TangemPayRefreshNeeded( - buttonText = when (userWallet) { - is UserWallet.Cold -> resourceReference(id = R.string.home_button_scan) - is UserWallet.Hot -> resourceReference(id = R.string.tangempay_sync_needed_button) - }, + buttonText = resourceReference(id = R.string.tangempay_sync_needed_button), onRefreshClick = { walletClickIntents.onRefreshPayToken(userWallet) }, shouldShowProgress = false, )