From b004f8df6be651b1fcb129964ccdf500efcaadca Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 27 Apr 2026 19:04:07 +0400 Subject: [PATCH] Updated on 2026-08-14 --- .../converters/NetworkCurrencyIdConverter.kt | 14 ++++- .../converters/NetworkAmountsConverterTest.kt | 8 ++- .../NetworkCurrencyIdConverterTest.kt | 61 +++++++++++++++++-- .../NetworkYieldSupplyStatusConverterTest.kt | 8 ++- .../SimpleNetworkStatusConverterTest.kt | 6 +- 5 files changed, 83 insertions(+), 14 deletions(-) diff --git a/data/networks/src/main/java/com/tangem/data/networks/converters/NetworkCurrencyIdConverter.kt b/data/networks/src/main/java/com/tangem/data/networks/converters/NetworkCurrencyIdConverter.kt index 8bef0658ac..c8d7b12399 100644 --- a/data/networks/src/main/java/com/tangem/data/networks/converters/NetworkCurrencyIdConverter.kt +++ b/data/networks/src/main/java/com/tangem/data/networks/converters/NetworkCurrencyIdConverter.kt @@ -1,7 +1,9 @@ package com.tangem.data.networks.converters +import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.toBlockchain import com.tangem.blockchainsdk.utils.toCoinId +import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.datasource.local.network.entity.NetworkStatusDM.CurrencyId import com.tangem.datasource.local.network.entity.NetworkStatusDM.CurrencyId.Companion.CONTRACT_ADDRESS_DELIMITER import com.tangem.domain.models.currency.CryptoCurrency @@ -22,6 +24,12 @@ internal class NetworkCurrencyIdConverter( private val derivationPath: Network.DerivationPath, ) : TwoWayConverter { + // Cache stores blockchainId in legacy format (e.g. "BTC"), but runtime + // CryptoCurrency.ID expects the new network rawId (e.g. "bitcoin") matching + // Network.rawId built from Blockchain.toNetworkId(). Convert once on construction + // so that IDs reconstructed from cache match those built at runtime. + private val networkRawId: String = Blockchain.fromId(blockchainId).toNetworkId() + override fun convert(value: CurrencyId): CryptoCurrency.ID { val suffixParts = value.value.split(CONTRACT_ADDRESS_DELIMITER) @@ -80,17 +88,17 @@ internal class NetworkCurrencyIdConverter( return when (derivationPath) { is Network.DerivationPath.Card -> { CryptoCurrency.ID.Body.NetworkIdWithDerivationPath( - rawId = blockchainId, + rawId = networkRawId, derivationPath = derivationPath.value, ) } is Network.DerivationPath.Custom -> { CryptoCurrency.ID.Body.NetworkIdWithDerivationPath( - rawId = blockchainId, + rawId = networkRawId, derivationPath = derivationPath.value, ) } - is Network.DerivationPath.None -> CryptoCurrency.ID.Body.NetworkId(blockchainId) + is Network.DerivationPath.None -> CryptoCurrency.ID.Body.NetworkId(networkRawId) } } } \ No newline at end of file diff --git a/data/networks/src/test/java/com/tangem/data/networks/converters/NetworkAmountsConverterTest.kt b/data/networks/src/test/java/com/tangem/data/networks/converters/NetworkAmountsConverterTest.kt index 4ca75519b9..5175a980de 100644 --- a/data/networks/src/test/java/com/tangem/data/networks/converters/NetworkAmountsConverterTest.kt +++ b/data/networks/src/test/java/com/tangem/data/networks/converters/NetworkAmountsConverterTest.kt @@ -16,10 +16,14 @@ import java.math.BigDecimal @TestInstance(TestInstance.Lifecycle.PER_CLASS) internal class NetworkAmountsConverterTest { - private val rawNetworkId = "ethereum" + // Cache stores the SDK-level Blockchain.id (legacy format, e.g. "ETH"). + // The converter normalizes it to the canonical network rawId ("ethereum") via + // Blockchain.fromId(...).toNetworkId() so that resulting CryptoCurrency.IDs match those + // built at runtime from Network.rawId. + private val blockchainId = "ETH" private val derivationPath = Network.DerivationPath.Card(value = "m/44'/60'/0'/0/0") private val derivationPathHashCode = "-1843072795" - private val converter = NetworkAmountsConverter(blockchainId = rawNetworkId, derivationPath = derivationPath) + private val converter = NetworkAmountsConverter(blockchainId = blockchainId, derivationPath = derivationPath) @Test fun convert() { diff --git a/data/networks/src/test/java/com/tangem/data/networks/converters/NetworkCurrencyIdConverterTest.kt b/data/networks/src/test/java/com/tangem/data/networks/converters/NetworkCurrencyIdConverterTest.kt index 9e129e6cd4..f1c72465ca 100644 --- a/data/networks/src/test/java/com/tangem/data/networks/converters/NetworkCurrencyIdConverterTest.kt +++ b/data/networks/src/test/java/com/tangem/data/networks/converters/NetworkCurrencyIdConverterTest.kt @@ -6,6 +6,7 @@ import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.Network import com.tangem.test.core.ProvideTestModels import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance import org.junit.jupiter.params.ParameterizedTest @@ -15,10 +16,14 @@ import org.junit.jupiter.params.ParameterizedTest @TestInstance(TestInstance.Lifecycle.PER_CLASS) class NetworkCurrencyIdConverterTest { - private val rawNetworkId = "ethereum" + // Legacy SDK format stored in cache (see NetworkStatusDataModelConverter: + // `value.network.toBlockchain().id`). Runtime CryptoCurrency.ID expects the canonical + // network rawId ("ethereum"), so the converter normalizes via Blockchain.fromId(...).toNetworkId(). + private val blockchainId = "ETH" + private val canonicalNetworkRawId = "ethereum" private val derivationPath = Network.DerivationPath.Card(value = "m/44'/60'/0'/0/0") private val derivationPathHashCode = "-1843072795" - private val converter = NetworkCurrencyIdConverter(blockchainId = rawNetworkId, derivationPath = derivationPath) + private val converter = NetworkCurrencyIdConverter(blockchainId = blockchainId, derivationPath = derivationPath) @Nested @TestInstance(TestInstance.Lifecycle.PER_CLASS) @@ -54,13 +59,13 @@ class NetworkCurrencyIdConverterTest { ConvertModel( value = CurrencyId.createCoinId(""), expected = Result.failure( - IllegalStateException("Coin id is null for $rawNetworkId with $derivationPath"), + IllegalStateException("Coin id is null for $blockchainId with $derivationPath"), ), ), ConvertModel( value = CurrencyId.createCoinId(" "), expected = Result.failure( - IllegalStateException("Coin id is null for $rawNetworkId with $derivationPath"), + IllegalStateException("Coin id is null for $blockchainId with $derivationPath"), ), ), // create token id @@ -184,6 +189,54 @@ class NetworkCurrencyIdConverterTest { ) } + /** + * Regression coverage for [REDACTED_TASK_KEY]. Cache stores `blockchainId` in the legacy SDK format + * (`Blockchain.id`, e.g. "ETH"), but runtime [CryptoCurrency.ID] is built using the canonical + * network rawId (`Blockchain.toNetworkId()`, e.g. "ethereum"). The converter must bridge the + * two formats so that IDs reconstructed from cache equal those built at runtime — otherwise + * `NetworkStatus.Verified.amounts[currency.id]` returns null and the wallet shimmer never clears. + */ + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class LegacyBlockchainIdNormalization { + + @Test + fun `convert with legacy ETH blockchainId produces id with canonical ethereum rawId`() { + val cached = CurrencyId.createCoinId("ethereum") + + val result = converter.convert(cached) + + Truth.assertThat(result) + .isEqualTo(CryptoCurrency.ID.fromValue("coin⟨$canonicalNetworkRawId→$derivationPathHashCode⟩ethereum")) + } + + @Test + fun `convert with legacy BTC blockchainId produces id with canonical bitcoin rawId`() { + val btcDerivationPath = Network.DerivationPath.Card(value = "m/44'/0'/0'/0/0") + val btcDerivationHash = btcDerivationPath.value.hashCode() + val btcConverter = NetworkCurrencyIdConverter( + blockchainId = "BTC", + derivationPath = btcDerivationPath, + ) + val cached = CurrencyId.createCoinId("bitcoin") + + val result = btcConverter.convert(cached) + + Truth.assertThat(result) + .isEqualTo(CryptoCurrency.ID.fromValue("coin⟨bitcoin→$btcDerivationHash⟩bitcoin")) + } + + @Test + fun `convert and convertBack roundtrip preserves CurrencyId`() { + val cached = CurrencyId.createCoinId("ethereum") + + val runtimeId = converter.convert(cached) + val roundTrip = converter.convertBack(runtimeId) + + Truth.assertThat(roundTrip).isEqualTo(cached) + } + } + data class ConvertModel(val value: CurrencyId, val expected: Result) data class ConvertBackModel(val value: CryptoCurrency.ID, val expected: Result) diff --git a/data/networks/src/test/java/com/tangem/data/networks/converters/NetworkYieldSupplyStatusConverterTest.kt b/data/networks/src/test/java/com/tangem/data/networks/converters/NetworkYieldSupplyStatusConverterTest.kt index b18f2e14d2..53e5bc65a6 100644 --- a/data/networks/src/test/java/com/tangem/data/networks/converters/NetworkYieldSupplyStatusConverterTest.kt +++ b/data/networks/src/test/java/com/tangem/data/networks/converters/NetworkYieldSupplyStatusConverterTest.kt @@ -13,10 +13,14 @@ import java.math.BigDecimal @TestInstance(TestInstance.Lifecycle.PER_CLASS) internal class NetworkYieldSupplyStatusConverterTest { - private val rawNetworkId = "ethereum" + // Cache stores the SDK-level Blockchain.id (legacy format, e.g. "ETH"). + // The converter normalizes it to the canonical network rawId ("ethereum") via + // Blockchain.fromId(...).toNetworkId() so that resulting CryptoCurrency.IDs match those + // built at runtime from Network.rawId. + private val blockchainId = "ETH" private val derivationPath = Network.DerivationPath.Card(value = "m/44'/60'/0'/0/0") private val derivationPathHashCode = "-1843072795" - private val converter = NetworkYieldSupplyStatusConverter(rawNetworkId, derivationPath) + private val converter = NetworkYieldSupplyStatusConverter(blockchainId, derivationPath) private val domainStatus = YieldSupplyStatus( isActive = true, diff --git a/data/networks/src/test/java/com/tangem/data/networks/converters/SimpleNetworkStatusConverterTest.kt b/data/networks/src/test/java/com/tangem/data/networks/converters/SimpleNetworkStatusConverterTest.kt index 0af092a1ca..d28e1c3f28 100644 --- a/data/networks/src/test/java/com/tangem/data/networks/converters/SimpleNetworkStatusConverterTest.kt +++ b/data/networks/src/test/java/com/tangem/data/networks/converters/SimpleNetworkStatusConverterTest.kt @@ -96,12 +96,12 @@ internal class SimpleNetworkStatusConverterTest { ), ), amounts = mapOf( - ID.fromValue("coin⟨ETH→3046160⟩ethereum") to Amount.Loaded(value = BigDecimal.ZERO), - ID.fromValue("token⟨ETH→3046160⟩usdt⚓0x1") to Amount.Loaded(value = BigDecimal.ZERO), + ID.fromValue("coin⟨ethereum→3046160⟩ethereum") to Amount.Loaded(value = BigDecimal.ZERO), + ID.fromValue("token⟨ethereum→3046160⟩usdt⚓0x1") to Amount.Loaded(value = BigDecimal.ZERO), ), pendingTransactions = emptyMap(), yieldSupplyStatuses = mapOf( - ID.fromValue("coin⟨ETH→3046160⟩ethereum") to YieldSupplyStatus( + ID.fromValue("coin⟨ethereum→3046160⟩ethereum") to YieldSupplyStatus( isActive = false, isInitialized = false, isAllowedToSpend = false,