Updated on 2026-08-14

This commit is contained in:
Tangem 2026-05-29 14:20:53 +05:00
parent f747c75708
commit f000c0c687
14 changed files with 143 additions and 146 deletions

View file

@ -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<TangemPayCard>,
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 }

View file

@ -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<CryptoCurrency>): Either<Throwable, Unit> {
suspend fun fetchQuotes(rawCurrencyIds: Set<CryptoCurrency.RawID>): Either<Throwable, Unit> {
return multiQuoteStatusFetcher(
params = MultiQuoteStatusFetcher.Params(
currenciesIds = currencies.mapNotNullTo(hashSetOf()) { it.id.rawCurrencyId },
appCurrencyId = null,
),
params = MultiQuoteStatusFetcher.Params(currenciesIds = rawCurrencyIds, appCurrencyId = null),
)
}

View file

@ -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))
}
}

View file

@ -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<UniversalError, CryptoCurrency>
}

View file

@ -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
}
}