Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-15 17:50:32 +05:00
commit 2002834e57
30 changed files with 461 additions and 31 deletions

View file

@ -81,6 +81,7 @@ class UserTokensResponseAddressesEnricherTest {
},
amounts = emptyMap(),
pendingTransactions = emptyMap(),
yieldSupplyStatuses = emptyMap(),
source = StatusSource.ACTUAL,
),
),
@ -124,6 +125,7 @@ class UserTokensResponseAddressesEnricherTest {
},
amounts = emptyMap(),
pendingTransactions = emptyMap(),
yieldSupplyStatuses = emptyMap(),
source = StatusSource.ACTUAL,
),
),
@ -161,6 +163,7 @@ class UserTokensResponseAddressesEnricherTest {
},
amounts = emptyMap(),
pendingTransactions = emptyMap(),
yieldSupplyStatuses = emptyMap(),
source = StatusSource.ACTUAL,
),
),

View file

@ -22,6 +22,7 @@ internal object NetworkStatusDataModelConverter : Converter<NetworkStatus, Netwo
selectedAddress = address.selectedAddress,
availableAddresses = address.addresses,
amounts = NetworkAmountsConverter.convertBack(value = status.amounts),
yieldSupplyStatuses = NetworkYieldSupplyStatusConverter.convertBack(status.yieldSupplyStatuses),
)
}
is NetworkStatus.NoAccount -> {

View file

@ -0,0 +1,46 @@
package com.tangem.data.networks.converters
import com.tangem.datasource.local.network.entity.NetworkStatusDM
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.yield.supply.YieldSupplyStatus
import com.tangem.utils.converter.TwoWayConverter
import com.tangem.utils.extensions.mapNotNullValues
private typealias YieldSupplyStatusDataModel = Map<String, NetworkStatusDM.YieldSupplyStatus?>
private typealias YieldSupplyStatusDomainModel = Map<CryptoCurrency.ID, YieldSupplyStatus?>
internal object NetworkYieldSupplyStatusConverter :
TwoWayConverter<YieldSupplyStatusDataModel, YieldSupplyStatusDomainModel> {
override fun convert(value: YieldSupplyStatusDataModel): YieldSupplyStatusDomainModel {
return value
.mapKeys { CryptoCurrency.ID.fromValue(value = it.key) }
.mapValues { (_, yieldSupplyStatus) ->
if (yieldSupplyStatus != null) {
YieldSupplyStatus(
isActive = yieldSupplyStatus.isActive,
isInitialized = yieldSupplyStatus.isInitialized,
isAllowedToSpend = yieldSupplyStatus.isAllowedToSpend,
)
} else {
null
}
}
}
override fun convertBack(value: YieldSupplyStatusDomainModel): YieldSupplyStatusDataModel {
return value
.mapKeys { (id, _) -> id.value }
.mapNotNullValues { (_, yieldSupplyStatus) ->
if (yieldSupplyStatus != null) {
NetworkStatusDM.YieldSupplyStatus(
isActive = yieldSupplyStatus.isActive,
isInitialized = yieldSupplyStatus.isInitialized,
isAllowedToSpend = yieldSupplyStatus.isAllowedToSpend,
)
} else {
null
}
}
}
}

View file

@ -29,6 +29,7 @@ internal object SimpleNetworkStatusConverter : Converter<NetworkStatusDM, Simple
amounts = NetworkAmountsConverter.convert(value = value.amounts),
pendingTransactions = emptyMap(),
source = StatusSource.CACHE,
yieldSupplyStatuses = NetworkYieldSupplyStatusConverter.convert(value = value.yieldSupplyStatuses),
)
}
is NetworkStatusDM.NoAccount -> {

View file

@ -8,6 +8,7 @@ import com.tangem.domain.models.network.Network
import com.tangem.domain.models.network.NetworkAddress
import com.tangem.domain.models.network.NetworkStatus
import com.tangem.domain.models.network.TxInfo
import com.tangem.domain.models.yield.supply.YieldSupplyStatus
import timber.log.Timber
/** Factory for creating [NetworkStatus] */
@ -70,6 +71,10 @@ object NetworkStatusFactory {
transactions = result.currentTransactions,
currencies = addedCurrencies,
),
yieldSupplyStatuses = formatYieldSupplyStatuses(
amounts = result.currenciesAmounts,
currencies = addedCurrencies,
),
source = StatusSource.ACTUAL,
)
}
@ -107,6 +112,31 @@ object NetworkStatusFactory {
}
}
private fun formatYieldSupplyStatuses(
amounts: Set<CryptoCurrencyAmount>,
currencies: Set<CryptoCurrency>,
): Map<CryptoCurrency.ID, YieldSupplyStatus?> {
return currencies.associate { currency ->
val amount = when (currency) {
is CryptoCurrency.Coin -> null
is CryptoCurrency.Token -> {
amounts.filterIsInstance<CryptoCurrencyAmount.Token.YieldSupplyToken>()
.firstOrNull { amount ->
currency.id.rawCurrencyId == amount.currencyRawId &&
currency.contractAddress.equals(amount.contractAddress, ignoreCase = true)
}
}
}
if (amount == null) {
Timber.w("Unable to find amount for cryptocurrency: $currency")
currency.id to null
} else {
currency.id to amount.yieldSupplyStatus
}
}
}
private fun formatTransactions(
transactions: Set<CryptoCurrencyTransaction>,
currencies: Set<CryptoCurrency>,

View file

@ -11,6 +11,7 @@ import com.tangem.domain.models.network.Network
import com.tangem.domain.models.network.NetworkAddress
import com.tangem.domain.models.network.NetworkStatus
import com.tangem.domain.models.network.NetworkStatus.Amount
import com.tangem.domain.models.yield.supply.YieldSupplyStatus
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.MethodSource
@ -59,6 +60,22 @@ internal class NetworkStatusDataModelConverterTest {
) to Amount.NotFound,
),
pendingTransactions = mapOf(), // doesn't matter
yieldSupplyStatuses = mapOf(
ID(
prefix = Prefix.COIN_PREFIX,
body = Body.NetworkId(rawId = "BCH"),
suffix = ID.Suffix.RawID(rawId = "bitcoin-cash"),
) to YieldSupplyStatus(
isActive = false,
isInitialized = false,
isAllowedToSpend = false,
),
ID(
prefix = Prefix.COIN_PREFIX,
body = Body.NetworkId(rawId = "BTC"),
suffix = ID.Suffix.RawID(rawId = "bitcoin"),
) to null,
),
source = StatusSource.ACTUAL, // doesn't matter
),
),
@ -76,6 +93,13 @@ internal class NetworkStatusDataModelConverterTest {
),
),
amounts = mapOf("coin⟨BCH⟩bitcoin-cash" to BigDecimal.ZERO),
yieldSupplyStatuses = mapOf(
"coin⟨BCH⟩bitcoin-cash" to NetworkStatusDM.YieldSupplyStatus(
isActive = false,
isInitialized = false,
isAllowedToSpend = false,
),
),
),
),
// endregion

View file

@ -0,0 +1,83 @@
package com.tangem.data.networks.converters
import com.google.common.truth.Truth
import com.tangem.datasource.local.network.entity.NetworkStatusDM
import com.tangem.domain.models.currency.CryptoCurrency.ID
import com.tangem.domain.models.currency.CryptoCurrency.ID.Body
import com.tangem.domain.models.currency.CryptoCurrency.ID.Prefix
import com.tangem.domain.models.yield.supply.YieldSupplyStatus
import org.junit.jupiter.api.Test
internal class NetworkYieldSupplyStatusConverterTest {
@Test
fun convert() {
// Arrange
val value = mapOf(
"coin⟨ETH⟩ethereum" to NetworkStatusDM.YieldSupplyStatus(
isActive = false,
isInitialized = false,
isAllowedToSpend = false,
),
"coin⟨ETH→12367123⟩ethereum" to null,
)
// Act
val actual = NetworkYieldSupplyStatusConverter.convert(value)
// Assert
val expected = mapOf(
ID(
prefix = Prefix.COIN_PREFIX,
body = Body.NetworkId(rawId = "ETH"),
suffix = ID.Suffix.RawID(rawId = "ethereum"),
) to YieldSupplyStatus(
isActive = false,
isInitialized = false,
isAllowedToSpend = false,
),
ID(
prefix = Prefix.COIN_PREFIX,
body = Body.NetworkIdWithDerivationPath(rawId = "ETH", derivationPathHashCode = 12367123),
suffix = ID.Suffix.RawID(rawId = "ethereum"),
) to null,
)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun convertBack() {
// Arrange
val value = mapOf(
ID(
prefix = Prefix.COIN_PREFIX,
body = Body.NetworkId(rawId = "ETH"),
suffix = ID.Suffix.RawID(rawId = "ethereum"),
) to YieldSupplyStatus(
isActive = false,
isInitialized = false,
isAllowedToSpend = false,
),
ID(
prefix = Prefix.COIN_PREFIX,
body = Body.NetworkIdWithDerivationPath(rawId = "ETH", derivationPathHashCode = 12367123),
suffix = ID.Suffix.RawID(rawId = "ethereum"),
) to null,
)
// Act
val actual = NetworkYieldSupplyStatusConverter.convertBack(value)
// Assert
val expected = mapOf(
"coin⟨ETH⟩ethereum" to NetworkStatusDM.YieldSupplyStatus(
isActive = false,
isInitialized = false,
isAllowedToSpend = false,
),
)
Truth.assertThat(actual).isEqualTo(expected)
}
}

View file

@ -12,6 +12,7 @@ import com.tangem.domain.models.network.Network
import com.tangem.domain.models.network.NetworkAddress
import com.tangem.domain.models.network.NetworkStatus
import com.tangem.domain.models.network.NetworkStatus.Amount
import com.tangem.domain.models.yield.supply.YieldSupplyStatus
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.MethodSource
@ -68,6 +69,14 @@ internal class SimpleNetworkStatusConverterTest {
"coin⟨BCH⟩bitcoin-cash" to BigDecimal.ZERO,
"coin⟨ETH→12367123⟩ethereum" to BigDecimal.ONE,
),
yieldSupplyStatuses = mapOf(
"coin⟨ETH⟩ethereum" to NetworkStatusDM.YieldSupplyStatus(
isActive = false,
isInitialized = false,
isAllowedToSpend = false,
),
"coin⟨ETH⟩ethereum" to null,
),
),
expected = SimpleNetworkStatus(
id = Network.ID(
@ -104,6 +113,22 @@ internal class SimpleNetworkStatusConverterTest {
) to Amount.Loaded(value = BigDecimal.ONE),
),
pendingTransactions = emptyMap(),
yieldSupplyStatuses = mapOf(
ID(
prefix = Prefix.COIN_PREFIX,
body = Body.NetworkId(rawId = "ETH"),
suffix = ID.Suffix.RawID(rawId = "ethereum"),
) to YieldSupplyStatus(
isActive = false,
isInitialized = false,
isAllowedToSpend = false,
),
ID(
prefix = Prefix.COIN_PREFIX,
body = Body.NetworkId(rawId = "ETH"),
suffix = ID.Suffix.RawID(rawId = "ethereum"),
) to null,
),
source = StatusSource.CACHE,
),
).let(Result.Companion::success),
@ -178,6 +203,7 @@ internal class SimpleNetworkStatusConverterTest {
),
),
amounts = emptyMap(),
yieldSupplyStatuses = emptyMap(),
),
expected = Result.failure(
exception = IllegalArgumentException("Selected address must not be null"),
@ -193,6 +219,7 @@ internal class SimpleNetworkStatusConverterTest {
selectedAddress = "0x1",
availableAddresses = setOf(),
amounts = emptyMap(),
yieldSupplyStatuses = emptyMap(),
),
expected = Result.failure(
exception = IllegalArgumentException("Selected address must not be null"),
@ -217,6 +244,7 @@ internal class SimpleNetworkStatusConverterTest {
),
),
amounts = emptyMap(),
yieldSupplyStatuses = emptyMap(),
),
expected = Result.failure(
exception = IllegalArgumentException("Selected address must not be null"),

View file

@ -149,6 +149,11 @@ internal class CommonNetworkStatusFetcherTest {
pendingTransactions = mapOf(
CryptoCurrency.ID.fromValue(value = "token⟨ETH⟩NEVER-MIND⚓NEVER-MIND") to emptySet(),
),
yieldSupplyStatuses = mapOf(
CryptoCurrency.ID.fromValue(
value = "token⟨ETH⟩NEVER-MIND⚓NEVER-MIND",
) to null,
),
)
},
),

View file

@ -13,6 +13,7 @@ import com.tangem.domain.models.network.NetworkAddress
import com.tangem.domain.models.network.NetworkStatus
import com.tangem.domain.models.network.NetworkStatus.Amount
import com.tangem.domain.models.network.TxInfo
import com.tangem.domain.models.yield.supply.YieldSupplyStatus
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@ -222,6 +223,57 @@ internal class NetworkStatusFactoryTest(private val model: Model) {
currencies.last().id to setOf(),
),
source = StatusSource.ACTUAL,
yieldSupplyStatuses = mapOf(),
),
),
createSuccess(
result = updateWalletManagerResultFactory.createVerifiedWithToken(),
currencies = currencies,
status = NetworkStatus.Verified(
address = NetworkAddress.Single(
defaultAddress = NetworkAddress.Address(
value = "0x1",
type = NetworkAddress.Address.Type.Primary,
),
),
amounts = mapOf(
currencies.first().id to Amount.Loaded(BigDecimal.ONE),
currencies.last().id to Amount.NotFound,
),
pendingTransactions = mapOf(
currencies.first().id to setOf(txInfo),
currencies.last().id to setOf(txInfo),
),
source = StatusSource.ACTUAL,
yieldSupplyStatuses = mapOf(),
),
),
createSuccess(
result = updateWalletManagerResultFactory.createVerifiedWithSuppliedToken(),
currencies = currencies,
status = NetworkStatus.Verified(
address = NetworkAddress.Single(
defaultAddress = NetworkAddress.Address(
value = "0x1",
type = NetworkAddress.Address.Type.Primary,
),
),
amounts = mapOf(
currencies.first().id to Amount.Loaded(BigDecimal.ONE),
currencies.last().id to Amount.NotFound,
),
pendingTransactions = mapOf(
currencies.first().id to setOf(txInfo),
currencies.last().id to setOf(txInfo),
),
source = StatusSource.ACTUAL,
yieldSupplyStatuses = mapOf(
currencies.first().id to YieldSupplyStatus(
isActive = false,
isInitialized = false,
isAllowedToSpend = false,
),
),
),
),
// endregion

View file

@ -1,24 +1,20 @@
package com.tangem.data.walletmanager
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.TransactionStatus
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.address.Address
import com.tangem.blockchainsdk.models.UpdateWalletManagerResult
import com.tangem.blockchainsdk.utils.amountToCreateAccount
import com.tangem.data.walletmanager.utils.SdkAddressToAddressConverter
import com.tangem.data.walletmanager.utils.TransactionDataToTxHistoryItemConverter
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.yield.supply.YieldSupplyStatus
import timber.log.Timber
import java.math.BigDecimal
/** Factory for creating [com.tangem.blockchainsdk.models.UpdateWalletManagerResult] */
/** Factory for creating [UpdateWalletManagerResult] */
internal class UpdateWalletManagerResultFactory {
/** Get [com.tangem.blockchainsdk.models.UpdateWalletManagerResult.Verified] result for [walletManager] */
/** Get [UpdateWalletManagerResult.Verified] result for [walletManager] */
fun getResult(walletManager: WalletManager): UpdateWalletManagerResult.Verified {
val wallet = walletManager.wallet
val addresses = getAvailableAddresses(wallet.addresses)
@ -110,7 +106,7 @@ internal class UpdateWalletManagerResultFactory {
is AmountType.Token -> {
val value = getCurrencyAmountValue(amount) ?: return null
UpdateWalletManagerResult.CryptoCurrencyAmount.Token(
UpdateWalletManagerResult.CryptoCurrencyAmount.Token.BasicToken(
currencyRawId = type.token.id?.let(CryptoCurrency::RawID),
contractAddress = type.token.contractAddress,
value = value,
@ -121,6 +117,20 @@ internal class UpdateWalletManagerResultFactory {
UpdateWalletManagerResult.CryptoCurrencyAmount.Coin(value = value)
}
is AmountType.TokenYieldSupply -> {
val value = getCurrencyAmountValue(amount) ?: return null
UpdateWalletManagerResult.CryptoCurrencyAmount.Token.YieldSupplyToken(
value = value,
currencyRawId = type.token.id?.let(CryptoCurrency::RawID),
contractAddress = type.token.contractAddress,
yieldSupplyStatus = YieldSupplyStatus(
isActive = type.isActive,
isInitialized = type.isInitialized,
isAllowedToSpend = type.isAllowedToSpend,
),
)
}
is AmountType.FeeResource,
is AmountType.Reserve,
-> null
@ -147,7 +157,7 @@ internal class UpdateWalletManagerResultFactory {
)
return tokens.mapTo(demoAmounts) { token ->
UpdateWalletManagerResult.CryptoCurrencyAmount.Token(
UpdateWalletManagerResult.CryptoCurrencyAmount.Token.BasicToken(
currencyRawId = token.id?.let(CryptoCurrency::RawID),
contractAddress = token.contractAddress,
value = amountValue,
@ -188,6 +198,15 @@ internal class UpdateWalletManagerResultFactory {
txInfo = txHistoryItem,
)
}
is AmountType.TokenYieldSupply -> {
val txHistoryItem = txHistoryItemConverter.convert(data) ?: return null
UpdateWalletManagerResult.CryptoCurrencyTransaction.Token(
tokenId = type.token.id,
contractAddress = type.token.contractAddress,
txInfo = txHistoryItem,
)
}
is AmountType.FeeResource,
is AmountType.Reserve,
-> null

View file

@ -188,7 +188,7 @@ internal class UpdateWalletManagerResultFactoryTest {
),
currenciesAmounts = setOf(
UpdateWalletManagerResult.CryptoCurrencyAmount.Coin(value = BigDecimal.ONE),
UpdateWalletManagerResult.CryptoCurrencyAmount.Token(
UpdateWalletManagerResult.CryptoCurrencyAmount.Token.BasicToken(
value = BigDecimal.ZERO,
currencyRawId = usdtToken.id?.let(CryptoCurrency::RawID),
contractAddress = usdtToken.contractAddress,
@ -416,7 +416,7 @@ internal class UpdateWalletManagerResultFactoryTest {
),
currenciesAmounts = setOf(
UpdateWalletManagerResult.CryptoCurrencyAmount.Coin(value = BigDecimal.ZERO),
UpdateWalletManagerResult.CryptoCurrencyAmount.Token(
UpdateWalletManagerResult.CryptoCurrencyAmount.Token.BasicToken(
value = BigDecimal.ZERO,
currencyRawId = usdtToken.id?.let(CryptoCurrency::RawID),
contractAddress = usdtToken.contractAddress,
@ -477,7 +477,7 @@ internal class UpdateWalletManagerResultFactoryTest {
),
currenciesAmounts = setOf(
UpdateWalletManagerResult.CryptoCurrencyAmount.Coin(value = BigDecimal.TEN),
UpdateWalletManagerResult.CryptoCurrencyAmount.Token(
UpdateWalletManagerResult.CryptoCurrencyAmount.Token.BasicToken(
value = BigDecimal.TEN,
currencyRawId = usdtToken.id?.let(CryptoCurrency::RawID),
contractAddress = usdtToken.contractAddress,