From a6e40999354d3052c21ebe10ec13ac6eeed34d48 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 12 May 2025 18:42:37 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../tap/network/auth/DefaultAuthProvider.kt | 6 ++ .../datasource/api/common/AuthProvider.kt | 5 ++ .../api/tangemTech/models/CardInfoBody.kt | 10 +++ .../api/tangemTech/models/WalletIdBody.kt | 2 + .../DefaultNotificationsRepository.kt | 11 --- .../DefaultNotificationsRepositoryTest.kt | 19 ----- data/wallets/build.gradle.kts | 1 + .../data/wallets/DefaultWalletsRepository.kt | 20 +++++ .../converters/WalletIdBodyConverter.kt | 21 +++++ .../data/wallets/di/WalletsDataModule.kt | 3 + .../wallets/DefaultWalletsRepositoryTest.kt | 72 +++++++++++++++++ .../converters/WalletIdBodyConverterTest.kt | 80 +++++++++++++++++++ .../repository/NotificationsRepository.kt | 3 - .../DefaultUserWalletsSyncDelegate.kt | 1 + .../wallets/repository/WalletsRepository.kt | 4 + ...ssociateWalletsWithApplicationIdUseCase.kt | 17 ++++ 16 files changed, 242 insertions(+), 33 deletions(-) create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/CardInfoBody.kt create mode 100644 data/wallets/src/main/java/com/tangem/data/wallets/converters/WalletIdBodyConverter.kt create mode 100644 data/wallets/src/test/java/com/tangem/data/wallets/converters/WalletIdBodyConverterTest.kt create mode 100644 domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/AssociateWalletsWithApplicationIdUseCase.kt diff --git a/app/src/main/java/com/tangem/tap/network/auth/DefaultAuthProvider.kt b/app/src/main/java/com/tangem/tap/network/auth/DefaultAuthProvider.kt index c39150c2c2..be40dc6f87 100644 --- a/app/src/main/java/com/tangem/tap/network/auth/DefaultAuthProvider.kt +++ b/app/src/main/java/com/tangem/tap/network/auth/DefaultAuthProvider.kt @@ -13,4 +13,10 @@ internal class DefaultAuthProvider(private val userWalletsListManager: UserWalle override fun getCardId(): String { return userWalletsListManager.selectedUserWalletSync?.scanResponse?.card?.cardId ?: "" } + + override fun getCardsPublicKeys(): Map { + return userWalletsListManager.userWalletsSync.associate { + it.scanResponse.card.cardId to it.scanResponse.card.cardPublicKey.toHexString() + } + } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/AuthProvider.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/AuthProvider.kt index a11b7bf574..7945fc59eb 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/AuthProvider.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/AuthProvider.kt @@ -11,4 +11,9 @@ interface AuthProvider { fun getCardPublicKey(): String fun getCardId(): String + + /** + * Returns map where keys(cardId) associated with cardPublicKey + */ + fun getCardsPublicKeys(): Map } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/CardInfoBody.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/CardInfoBody.kt new file mode 100644 index 0000000000..80454a733b --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/CardInfoBody.kt @@ -0,0 +1,10 @@ +package com.tangem.datasource.api.tangemTech.models + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class CardInfoBody( + @Json(name = "card_id") val cardId: String, + @Json(name = "card_public_key") val cardPublicKey: String, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/WalletIdBody.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/WalletIdBody.kt index dbadddd6fc..3fd5ff6196 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/WalletIdBody.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/WalletIdBody.kt @@ -6,4 +6,6 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class WalletIdBody( @Json(name = "id") val walletId: String, + @Json(name = "name") val name: String, + @Json(name = "cards") val cards: List, ) \ No newline at end of file diff --git a/data/notifications/src/main/java/com/tangem/data/notifications/DefaultNotificationsRepository.kt b/data/notifications/src/main/java/com/tangem/data/notifications/DefaultNotificationsRepository.kt index 62f837994d..d0ea5837e7 100644 --- a/data/notifications/src/main/java/com/tangem/data/notifications/DefaultNotificationsRepository.kt +++ b/data/notifications/src/main/java/com/tangem/data/notifications/DefaultNotificationsRepository.kt @@ -4,7 +4,6 @@ import com.tangem.data.notifications.converters.NotificationsEligibleNetworkConv import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.models.NotificationApplicationCreateBody -import com.tangem.datasource.api.tangemTech.models.WalletIdBody import com.tangem.utils.info.AppInfoProvider import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys @@ -62,16 +61,6 @@ internal class DefaultNotificationsRepository @Inject constructor( ) } - override suspend fun associateApplicationIdWithWallets(appId: String, wallets: List) = - withContext(dispatchers.io) { - tangemTechApi.associateApplicationIdWithWallets( - applicationId = appId, - body = wallets.map { - WalletIdBody(it) - }, - ).getOrThrow() - } - override suspend fun sendPushToken(appId: ApplicationId, pushToken: String) { withContext(dispatchers.io) { tangemTechApi.updatePushTokenForApplicationId( diff --git a/data/notifications/src/test/java/com/tangem/data/notifications/DefaultNotificationsRepositoryTest.kt b/data/notifications/src/test/java/com/tangem/data/notifications/DefaultNotificationsRepositoryTest.kt index 57d8d4c429..691dff0714 100644 --- a/data/notifications/src/test/java/com/tangem/data/notifications/DefaultNotificationsRepositoryTest.kt +++ b/data/notifications/src/test/java/com/tangem/data/notifications/DefaultNotificationsRepositoryTest.kt @@ -104,25 +104,6 @@ class DefaultNotificationsRepositoryTest { assertThat(result).isEqualTo(expectedAppId) } - @Test - fun `GIVEN application id and wallet list WHEN associateApplicationIdWithWallets THEN associates them`() = runTest { - // GIVEN - val appId = "test-app-id" - val wallets = listOf("wallet1", "wallet2") - coEvery { - tangemTechApi.associateApplicationIdWithWallets( - appId, - wallets.map { WalletIdBody(it) }, - ) - } returns ApiResponse.Success(Unit) - - // WHEN - repository.associateApplicationIdWithWallets(appId, wallets) - - // THEN - coVerify { tangemTechApi.associateApplicationIdWithWallets(appId, wallets.map { WalletIdBody(it) }) } - } - @Test fun `GIVEN application id and push token WHEN sendPushToken THEN updates push token`() = runTest { // GIVEN diff --git a/data/wallets/build.gradle.kts b/data/wallets/build.gradle.kts index e84f33b3bc..a9ca9af06f 100644 --- a/data/wallets/build.gradle.kts +++ b/data/wallets/build.gradle.kts @@ -36,6 +36,7 @@ dependencies { implementation(deps.arrow.core) /** tests */ + testImplementation(projects.domain.models) testImplementation(deps.test.junit) testImplementation(deps.test.coroutine) testImplementation(deps.test.truth) diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt b/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt index 25bc5ae0b6..1b8c62c423 100644 --- a/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt +++ b/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt @@ -1,6 +1,8 @@ package com.tangem.data.wallets import com.tangem.data.wallets.converters.UserWalletRemoteInfoConverter +import com.tangem.data.wallets.converters.WalletIdBodyConverter +import com.tangem.datasource.api.common.AuthProvider import com.tangem.datasource.api.common.response.ApiResponseError.HttpException import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.tangemTech.TangemTechApi @@ -18,6 +20,7 @@ import com.tangem.datasource.local.preferences.utils.getSyncOrDefault import com.tangem.datasource.local.preferences.utils.store import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.wallets.models.SeedPhraseNotificationsStatus +import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletRemoteInfo import com.tangem.domain.wallets.repository.WalletsRepository @@ -37,6 +40,7 @@ internal class DefaultWalletsRepository( private val userWalletsStore: UserWalletsStore, private val seedPhraseNotificationVisibilityStore: RuntimeStateStore, private val dispatchers: CoroutineDispatcherProvider, + private val authProvider: AuthProvider, ) : WalletsRepository { override suspend fun shouldSaveUserWalletsSync(): Boolean { @@ -288,6 +292,22 @@ internal class DefaultWalletsRepository( } } + override suspend fun associateWallets(applicationId: String, wallets: List) = + withContext(dispatchers.io) { + val publicKeys = authProvider.getCardsPublicKeys() + val walletsBody = wallets.map { userWallet -> + WalletIdBodyConverter.convert( + userWallet = userWallet, + publicKeys = publicKeys.filterKeys { userWallet.cardsInWallet.contains(it) }, + ) + } + + tangemTechApi.associateApplicationIdWithWallets( + applicationId = applicationId, + body = walletsBody, + ).getOrThrow() + } + private suspend fun loadAndSaveNotificationsEnabled(userWalletId: UserWalletId): Boolean { val walletResponse = tangemTechApi.getWalletById(walletId = userWalletId.stringValue).getOrThrow() val isEnabled = walletResponse.notifyStatus diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/converters/WalletIdBodyConverter.kt b/data/wallets/src/main/java/com/tangem/data/wallets/converters/WalletIdBodyConverter.kt new file mode 100644 index 0000000000..fdf8d28b3b --- /dev/null +++ b/data/wallets/src/main/java/com/tangem/data/wallets/converters/WalletIdBodyConverter.kt @@ -0,0 +1,21 @@ +package com.tangem.data.wallets.converters + +import com.tangem.datasource.api.tangemTech.models.CardInfoBody +import com.tangem.datasource.api.tangemTech.models.WalletIdBody +import com.tangem.domain.wallets.models.UserWallet + +internal object WalletIdBodyConverter { + + fun convert(userWallet: UserWallet, publicKeys: Map): WalletIdBody { + return WalletIdBody( + walletId = userWallet.walletId.stringValue, + name = userWallet.name, + cards = publicKeys.map { + CardInfoBody( + cardId = it.key, + cardPublicKey = it.value, + ) + }, + ) + } +} \ No newline at end of file diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/di/WalletsDataModule.kt b/data/wallets/src/main/java/com/tangem/data/wallets/di/WalletsDataModule.kt index 778257f353..a154dc4f99 100644 --- a/data/wallets/src/main/java/com/tangem/data/wallets/di/WalletsDataModule.kt +++ b/data/wallets/src/main/java/com/tangem/data/wallets/di/WalletsDataModule.kt @@ -2,6 +2,7 @@ package com.tangem.data.wallets.di import com.tangem.data.wallets.DefaultWalletNamesMigrationRepository import com.tangem.data.wallets.DefaultWalletsRepository +import com.tangem.datasource.api.common.AuthProvider import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.local.datastore.RuntimeStateStore import com.tangem.datasource.local.preferences.AppPreferencesStore @@ -26,6 +27,7 @@ internal object WalletsDataModule { tangemTechApi: TangemTechApi, userWalletsStore: UserWalletsStore, dispatchers: CoroutineDispatcherProvider, + authProvider: AuthProvider, ): WalletsRepository { return DefaultWalletsRepository( appPreferencesStore = appPreferencesStore, @@ -33,6 +35,7 @@ internal object WalletsDataModule { userWalletsStore = userWalletsStore, seedPhraseNotificationVisibilityStore = RuntimeStateStore(defaultValue = emptyMap()), dispatchers = dispatchers, + authProvider = authProvider, ) } diff --git a/data/wallets/src/test/java/com/tangem/data/wallets/DefaultWalletsRepositoryTest.kt b/data/wallets/src/test/java/com/tangem/data/wallets/DefaultWalletsRepositoryTest.kt index f430dab62f..af1295b684 100644 --- a/data/wallets/src/test/java/com/tangem/data/wallets/DefaultWalletsRepositoryTest.kt +++ b/data/wallets/src/test/java/com/tangem/data/wallets/DefaultWalletsRepositoryTest.kt @@ -18,6 +18,8 @@ import io.mockk.coVerify import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import com.google.common.truth.Truth.assertThat +import com.tangem.datasource.api.common.AuthProvider +import com.tangem.domain.wallets.models.UserWallet import org.junit.Before import org.junit.Test @@ -48,6 +50,7 @@ class DefaultWalletsRepositoryTest { userWalletsStore = mockk(), seedPhraseNotificationVisibilityStore = mockk(), dispatchers = dispatchers, + authProvider = mockk(), ) } @@ -231,4 +234,73 @@ class DefaultWalletsRepositoryTest { coVerify(exactly = 1) { tangemTechApi.getWallets(applicationId) } coVerify(exactly = 0) { preferencesDataStore.updateData(any()) } } + + @Test + fun `GIVEN user wallets and application ID WHEN associateWallets THEN should convert and send to API`() = runTest { + // GIVEN + val applicationId = "test_app_id" + val wallet1Id = "1234567890abcdef" + val wallet2Id = "fedcba0987654321" + val card1PublicKey = "card1_public_key" + val card2PublicKey = "card2_public_key" + + val userWallets = listOf( + mockk { + every { cardsInWallet } returns setOf(card1PublicKey) + every { walletId } returns UserWalletId(wallet1Id) + every { name } returns "Wallet 1" + }, + mockk { + every { cardsInWallet } returns setOf(card2PublicKey) + every { walletId } returns UserWalletId(wallet2Id) + every { name } returns "Wallet 2" + }, + ) + + val publicKeys = mapOf( + card1PublicKey to "public_key_1", + card2PublicKey to "public_key_2", + ) + + val authProvider = mockk { + every { getCardsPublicKeys() } returns publicKeys + } + + repository = DefaultWalletsRepository( + appPreferencesStore = appPreferenceStore, + tangemTechApi = tangemTechApi, + userWalletsStore = mockk(), + seedPhraseNotificationVisibilityStore = mockk(), + dispatchers = dispatchers, + authProvider = authProvider, + ) + + coEvery { + tangemTechApi.associateApplicationIdWithWallets( + eq(applicationId), + any(), + ) + } returns ApiResponse.Success(Unit) + + // WHEN + repository.associateWallets(applicationId, userWallets) + + // THEN + coVerify(exactly = 1) { + tangemTechApi.associateApplicationIdWithWallets( + eq(applicationId), + match { body -> + body.size == 2 && + body.any { + it.walletId == wallet1Id && it.cards.any { card -> card.cardPublicKey == "public_key_1" } && + it.name == "Wallet 1" + } && + body.any { + it.walletId == wallet2Id && it.cards.any { card -> card.cardPublicKey == "public_key_2" } && + it.name == "Wallet 2" + } + }, + ) + } + } } \ No newline at end of file diff --git a/data/wallets/src/test/java/com/tangem/data/wallets/converters/WalletIdBodyConverterTest.kt b/data/wallets/src/test/java/com/tangem/data/wallets/converters/WalletIdBodyConverterTest.kt new file mode 100644 index 0000000000..2aa327b6d9 --- /dev/null +++ b/data/wallets/src/test/java/com/tangem/data/wallets/converters/WalletIdBodyConverterTest.kt @@ -0,0 +1,80 @@ +package com.tangem.data.wallets.converters + +import com.tangem.datasource.api.tangemTech.models.CardInfoBody +import com.tangem.datasource.api.tangemTech.models.WalletIdBody +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId +import com.google.common.truth.Truth.assertThat +import io.mockk.mockk +import org.junit.Test + +class WalletIdBodyConverterTest { + + @Test + fun `GIVEN user wallet with cards WHEN convert THEN should return correct WalletIdBody`() { + // GIVEN + val walletId = UserWalletId("1234567890abcdef") + val walletName = "Test Wallet" + val userWallet = UserWallet( + walletId = walletId, + name = walletName, + cardsInWallet = setOf("card1", "card2"), + isMultiCurrency = true, + hasBackupError = false, + scanResponse = mockk(), + ) + val publicKeys = mapOf( + "card1" to "public_key_1", + "card2" to "public_key_2", + ) + + // WHEN + val result = WalletIdBodyConverter.convert(userWallet, publicKeys) + + // THEN + assertThat(result).isEqualTo( + WalletIdBody( + walletId = walletId.stringValue, + name = walletName, + cards = listOf( + CardInfoBody( + cardId = "card1", + cardPublicKey = "public_key_1", + ), + CardInfoBody( + cardId = "card2", + cardPublicKey = "public_key_2", + ), + ), + ), + ) + } + + @Test + fun `GIVEN user wallet without cards WHEN convert THEN should return WalletIdBody with empty cards list`() { + // GIVEN + val walletId = UserWalletId("1234567890abcdef") + val walletName = "Test Wallet" + val userWallet = UserWallet( + walletId = walletId, + name = walletName, + cardsInWallet = emptySet(), + isMultiCurrency = true, + hasBackupError = false, + scanResponse = mockk(), + ) + val publicKeys = emptyMap() + + // WHEN + val result = WalletIdBodyConverter.convert(userWallet, publicKeys) + + // THEN + assertThat(result).isEqualTo( + WalletIdBody( + walletId = walletId.stringValue, + name = walletName, + cards = emptyList(), + ), + ) + } +} \ No newline at end of file diff --git a/domain/notifications/src/main/java/com/tangem/domain/notifications/repository/NotificationsRepository.kt b/domain/notifications/src/main/java/com/tangem/domain/notifications/repository/NotificationsRepository.kt index d5e99ab745..a8c46854df 100644 --- a/domain/notifications/src/main/java/com/tangem/domain/notifications/repository/NotificationsRepository.kt +++ b/domain/notifications/src/main/java/com/tangem/domain/notifications/repository/NotificationsRepository.kt @@ -16,9 +16,6 @@ interface NotificationsRepository { suspend fun incrementTronTokenFeeNotificationShowCounter() - @Throws - suspend fun associateApplicationIdWithWallets(appId: String, wallets: List) - @Throws suspend fun sendPushToken(appId: ApplicationId, pushToken: String) diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/delegate/DefaultUserWalletsSyncDelegate.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/delegate/DefaultUserWalletsSyncDelegate.kt index b1c1e11c7e..37ab17d016 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/delegate/DefaultUserWalletsSyncDelegate.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/delegate/DefaultUserWalletsSyncDelegate.kt @@ -27,6 +27,7 @@ class DefaultUserWalletsSyncDelegate( } } + // TODO remove dispatchers whnen UserWalletsListManager will be main safe private suspend fun renameUserWallet( userWalletId: UserWalletId, name: String, diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/repository/WalletsRepository.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/repository/WalletsRepository.kt index 90768f5e78..eaf24f6cfa 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/repository/WalletsRepository.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/repository/WalletsRepository.kt @@ -1,6 +1,7 @@ package com.tangem.domain.wallets.repository import com.tangem.domain.wallets.models.SeedPhraseNotificationsStatus +import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletRemoteInfo import kotlinx.coroutines.flow.Flow @@ -54,4 +55,7 @@ interface WalletsRepository { @Throws suspend fun getWalletsInfo(applicationId: String, updateCache: Boolean = true): List + + @Throws + suspend fun associateWallets(applicationId: String, wallets: List) } \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/AssociateWalletsWithApplicationIdUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/AssociateWalletsWithApplicationIdUseCase.kt new file mode 100644 index 0000000000..e67bf5b744 --- /dev/null +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/AssociateWalletsWithApplicationIdUseCase.kt @@ -0,0 +1,17 @@ +package com.tangem.domain.wallets.usecase + +import arrow.core.Either +import com.tangem.domain.notifications.models.ApplicationId +import com.tangem.domain.wallets.legacy.UserWalletsListManager +import com.tangem.domain.wallets.repository.WalletsRepository + +class AssociateWalletsWithApplicationIdUseCase( + private val userWalletsListManager: UserWalletsListManager, + private val walletsRepository: WalletsRepository, +) { + + suspend operator fun invoke(applicationId: ApplicationId): Either = Either.catch { + val wallets = userWalletsListManager.userWalletsSync + walletsRepository.associateWallets(applicationId.value, wallets) + } +} \ No newline at end of file