diff --git a/data/dynamic-addresses/src/main/java/com/tangem/data/dynamicaddresses/DefaultDynamicAddressesRepository.kt b/data/dynamic-addresses/src/main/java/com/tangem/data/dynamicaddresses/DefaultDynamicAddressesRepository.kt index ce82539915..56ff5bfdb2 100644 --- a/data/dynamic-addresses/src/main/java/com/tangem/data/dynamicaddresses/DefaultDynamicAddressesRepository.kt +++ b/data/dynamic-addresses/src/main/java/com/tangem/data/dynamicaddresses/DefaultDynamicAddressesRepository.kt @@ -150,7 +150,7 @@ internal class DefaultDynamicAddressesRepository( } private suspend fun isXpubAvailable(userWalletId: UserWalletId, network: Network): Boolean { - // Check if WalletManager is already in XPUB mode (dynamic addresses was previously enabled on this device) + // Check if WalletManager is already in XPUB mode (dynamic addresses were previously enabled on this device) return walletManagersFacade.getDynamicAddressesReceiveAddress(userWalletId, network) != null } diff --git a/data/networks/src/test/java/com/tangem/data/networks/multi/DefaultMultiNetworkStatusFetcherTest.kt b/data/networks/src/test/java/com/tangem/data/networks/multi/DefaultMultiNetworkStatusFetcherTest.kt index 8a1a396ed2..f355cc27c1 100644 --- a/data/networks/src/test/java/com/tangem/data/networks/multi/DefaultMultiNetworkStatusFetcherTest.kt +++ b/data/networks/src/test/java/com/tangem/data/networks/multi/DefaultMultiNetworkStatusFetcherTest.kt @@ -265,6 +265,219 @@ internal class DefaultMultiNetworkStatusFetcherTest { coVerify(inverse = true) { commonNetworkStatusFetcher.fetch(any(), any(), any(), any()) } } + @Test + fun `fetch passes xpub to correct network and null to others`() = runTest { + // Arrange + val xpub = "xpub_test_eth" + val params = setupTwoNetworkParams() + coEvery { dynamicAddressesInitializer.getXpubs(params.userWalletId, params.networks) } returns mapOf(ethereum.network to xpub) + + coEvery { + commonNetworkStatusFetcher.fetch( + userWalletId = params.userWalletId, + network = ethereum.network, + networkCurrencies = setOf(ethereum), + xpub = xpub, + ) + } returns Either.Right(Unit) + coEvery { + commonNetworkStatusFetcher.fetch( + userWalletId = params.userWalletId, + network = cardano.network, + networkCurrencies = setOf(cardano), + xpub = null, + ) + } returns Either.Right(Unit) + + // Act + val actual = fetcher(params) + + // Assert + val expected = Either.Right(Unit) + assertEither(actual, expected) + coVerify(exactly = 1) { + commonNetworkStatusFetcher.fetch( + userWalletId = params.userWalletId, + network = ethereum.network, + networkCurrencies = setOf(ethereum), + xpub = xpub, + ) + } + coVerify(exactly = 1) { + commonNetworkStatusFetcher.fetch( + userWalletId = params.userWalletId, + network = cardano.network, + networkCurrencies = setOf(cardano), + xpub = null, + ) + } + } + + @Test + fun `fetch continues with null xpub for all networks if getXpubs throws`() = runTest { + // Arrange + val params = setupTwoNetworkParams() + coEvery { + dynamicAddressesInitializer.getXpubs(params.userWalletId, params.networks) + } throws RuntimeException("XPUB derivation failed") + + val fetchResult = Either.Right(Unit) + coEvery { + commonNetworkStatusFetcher.fetch( + userWalletId = params.userWalletId, + network = ethereum.network, + networkCurrencies = setOf(ethereum), + xpub = null, + ) + } returns fetchResult + coEvery { + commonNetworkStatusFetcher.fetch( + userWalletId = params.userWalletId, + network = cardano.network, + networkCurrencies = setOf(cardano), + xpub = null, + ) + } returns fetchResult + + // Act + val actual = fetcher(params) + + // Assert + val expected = Either.Right(Unit) + assertEither(actual, expected) + coVerify(exactly = 1) { + commonNetworkStatusFetcher.fetch( + userWalletId = params.userWalletId, + network = ethereum.network, + networkCurrencies = setOf(ethereum), + xpub = null, + ) + } + coVerify(exactly = 1) { + commonNetworkStatusFetcher.fetch( + userWalletId = params.userWalletId, + network = cardano.network, + networkCurrencies = setOf(cardano), + xpub = null, + ) + } + // getXpubs failure must not degrade network status to OnlyCache + coVerify(inverse = true) { networksStatusesStore.setSourceAsOnlyCache(userWalletId = any(), networks = any()) } + } + + @Test + fun `fetch passes xpubs to all networks returned by getXpubs`() = runTest { + // Arrange + val ethXpub = "xpub_eth" + val adaXpub = "xpub_ada" + val params = setupTwoNetworkParams() + coEvery { + dynamicAddressesInitializer.getXpubs(params.userWalletId, params.networks) + } returns mapOf(ethereum.network to ethXpub, cardano.network to adaXpub) + + coEvery { + commonNetworkStatusFetcher.fetch( + userWalletId = params.userWalletId, + network = ethereum.network, + networkCurrencies = setOf(ethereum), + xpub = ethXpub, + ) + } returns Either.Right(Unit) + coEvery { + commonNetworkStatusFetcher.fetch( + userWalletId = params.userWalletId, + network = cardano.network, + networkCurrencies = setOf(cardano), + xpub = adaXpub, + ) + } returns Either.Right(Unit) + + // Act + val actual = fetcher(params) + + // Assert + val expected = Either.Right(Unit) + assertEither(actual, expected) + coVerify(exactly = 1) { + commonNetworkStatusFetcher.fetch( + userWalletId = params.userWalletId, + network = ethereum.network, + networkCurrencies = setOf(ethereum), + xpub = ethXpub, + ) + } + coVerify(exactly = 1) { + commonNetworkStatusFetcher.fetch( + userWalletId = params.userWalletId, + network = cardano.network, + networkCurrencies = setOf(cardano), + xpub = adaXpub, + ) + } + } + + @Test + fun `fetch calls getXpubs with exactly the networks from params`() = runTest { + // Arrange + val params = setupTwoNetworkParams() + coEvery { + commonNetworkStatusFetcher.fetch(userWalletId = any(), network = any(), networkCurrencies = any(), xpub = any()) + } returns Either.Right(Unit) + + // Act + fetcher(params) + + // Assert + coVerify(exactly = 1) { dynamicAddressesInitializer.getXpubs(params.userWalletId, params.networks) } + } + + @Test + fun `fetch failure if network fetch fails when xpub is provided`() = runTest { + // Arrange + val xpub = "xpub_eth" + val params = setupTwoNetworkParams() + coEvery { + dynamicAddressesInitializer.getXpubs(params.userWalletId, params.networks) + } returns mapOf(ethereum.network to xpub) + + val fetchFailure = Either.Left(IllegalStateException()) + coEvery { + commonNetworkStatusFetcher.fetch( + userWalletId = params.userWalletId, + network = ethereum.network, + networkCurrencies = setOf(ethereum), + xpub = xpub, + ) + } returns fetchFailure + coEvery { + commonNetworkStatusFetcher.fetch( + userWalletId = params.userWalletId, + network = cardano.network, + networkCurrencies = setOf(cardano), + xpub = null, + ) + } returns Either.Right(Unit) + + // Act + val actual = fetcher(params) + + // Assert + val expected = Either.Left(IllegalStateException("Failed to fetch network statuses")) + assertEither(actual, expected) + } + + private fun setupTwoNetworkParams(): MultiNetworkStatusFetcher.Params { + val params = MultiNetworkStatusFetcher.Params( + userWalletId = userWalletId, + networks = setOf(ethereum.network, cardano.network), + ) + coEvery { cardCryptoCurrencyFactory.create(params.userWalletId, params.networks) } returns mapOf( + ethereum.network to listOf(ethereum), + cardano.network to listOf(cardano), + ) + return params + } + private companion object { val userWalletId = UserWalletId("011") val cryptoCurrencyFactory = MockCryptoCurrencyFactory() diff --git a/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/DisableDynamicAddressesUseCase.kt b/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/DisableDynamicAddressesUseCase.kt index d0bcbb4660..17d8139c00 100644 --- a/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/DisableDynamicAddressesUseCase.kt +++ b/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/DisableDynamicAddressesUseCase.kt @@ -11,7 +11,7 @@ class DisableDynamicAddressesUseCase( /** * Returns true when consolidation is required before disabling (non-base balances exist), - * or false when dynamic addresses was disabled immediately. + * or false when dynamic addresses were disabled immediately. */ suspend operator fun invoke(userWalletId: UserWalletId, network: Network): Either = Either.catch { diff --git a/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/DynamicAddressesSupportedBlockchains.kt b/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/DynamicAddressesSupportedBlockchains.kt index c3df141dd3..9ba156ddaa 100644 --- a/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/DynamicAddressesSupportedBlockchains.kt +++ b/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/DynamicAddressesSupportedBlockchains.kt @@ -7,7 +7,7 @@ import com.tangem.blockchainsdk.utils.toNetworkId * List of blockchains that support Dynamic Addresses (XPUB-based multi-address mode). * Must match [Blockchain.isBip44DerivationStyleXPUB] from blockchain-sdk minus Kaspa (deferred). * - * Dynamic addresses is NOT used for Legacy (m/44' for BTC/LTC) or Taproot (m/86') addresses. + * Dynamic addresses are NOT used for Legacy (m/44' for BTC/LTC) or Taproot (m/86') addresses. * Only the default derivation style per blockchain is supported. */ object DynamicAddressesSupportedBlockchains {