Updated on 2026-08-14
This commit is contained in:
commit
c1cc66fea8
162 changed files with 3408 additions and 1585 deletions
|
|
@ -14,9 +14,11 @@ dependencies {
|
|||
implementation(projects.core.datasource)
|
||||
|
||||
/* Domain */
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.demo)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
||||
/* Libs - SDK */
|
||||
implementation(tangemDeps.blockchain)
|
||||
|
|
@ -28,8 +30,16 @@ dependencies {
|
|||
kapt(deps.hilt.kapt)
|
||||
|
||||
/* Libs - Other */
|
||||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.jodatime)
|
||||
implementation(deps.timber)
|
||||
implementation(deps.androidx.datastore)
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.jodatime)
|
||||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.timber)
|
||||
|
||||
/* Test */
|
||||
testImplementation(projects.common.test)
|
||||
testImplementation(deps.test.coroutine)
|
||||
testImplementation(deps.test.junit)
|
||||
testImplementation(deps.test.mockk)
|
||||
testImplementation(deps.test.truth)
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.tangem.data.common.currency
|
||||
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
/**
|
||||
* Factory for creating list of [CryptoCurrency] for selected card
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface CardCryptoCurrencyFactory {
|
||||
|
||||
/**
|
||||
* Universal method for creating list of [CryptoCurrency] in [network] for any card
|
||||
*
|
||||
* @param userWalletId user wallet id that determines type of card
|
||||
* @param network network
|
||||
*/
|
||||
@Throws
|
||||
suspend fun create(userWalletId: UserWalletId, network: Network): List<CryptoCurrency>
|
||||
|
||||
/**
|
||||
* Create default coins for multi currency card
|
||||
*
|
||||
* @param scanResponse scan response
|
||||
*/
|
||||
fun createDefaultCoinsForMultiCurrencyCard(scanResponse: ScanResponse): List<CryptoCurrency.Coin>
|
||||
|
||||
/**
|
||||
* Create primary currency for single currency card
|
||||
*
|
||||
* @param scanResponse scan response
|
||||
*/
|
||||
@Throws
|
||||
fun createPrimaryCurrencyForSingleCurrencyCard(scanResponse: ScanResponse): CryptoCurrency
|
||||
|
||||
/**
|
||||
* Create currencies for single currency card with token (like, NODL)
|
||||
*
|
||||
* @param scanResponse scan response
|
||||
*/
|
||||
@Throws
|
||||
fun createCurrenciesForSingleCurrencyCardWithToken(scanResponse: ScanResponse): List<CryptoCurrency>
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
package com.tangem.data.common.currency
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
/**
|
||||
* Default implementation of factory for creating list of [CryptoCurrency] for selected card
|
||||
*
|
||||
* @property demoConfig demo config
|
||||
* @property excludedBlockchains excluded blockchains
|
||||
* @property userWalletsStore user wallets store
|
||||
* @property userTokensResponseStore user tokens response store
|
||||
*/
|
||||
internal class DefaultCardCryptoCurrencyFactory(
|
||||
private val demoConfig: DemoConfig,
|
||||
private val excludedBlockchains: ExcludedBlockchains,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val userTokensResponseStore: UserTokensResponseStore,
|
||||
) : CardCryptoCurrencyFactory {
|
||||
|
||||
private val cryptoCurrencyFactory by lazy { CryptoCurrencyFactory(excludedBlockchains) }
|
||||
|
||||
override suspend fun create(userWalletId: UserWalletId, network: Network): List<CryptoCurrency> {
|
||||
val userWallet = userWalletsStore.getSyncStrict(key = userWalletId)
|
||||
|
||||
val blockchain = Blockchain.fromNetworkId(networkId = network.backendId)
|
||||
|
||||
// multi-currency wallet
|
||||
if (userWallet.isMultiCurrency) return getMultiWalletCurrencies(userWallet = userWallet, network = network)
|
||||
|
||||
// check if the blockchain of single-currency wallet is the same as network
|
||||
val cardBlockchain = userWallet.scanResponse.cardTypesResolver.getBlockchain()
|
||||
if (cardBlockchain != blockchain) return emptyList()
|
||||
|
||||
// single-currency wallet with token (NODL)
|
||||
if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
|
||||
return createCurrenciesForSingleCurrencyCardWithToken(userWallet.scanResponse)
|
||||
}
|
||||
|
||||
// single-currency wallet
|
||||
return createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse).let(::listOf)
|
||||
}
|
||||
|
||||
override fun createDefaultCoinsForMultiCurrencyCard(scanResponse: ScanResponse): List<CryptoCurrency.Coin> {
|
||||
val card = scanResponse.card
|
||||
|
||||
var blockchains = if (demoConfig.isDemoCardId(card.cardId)) {
|
||||
demoConfig.demoBlockchains
|
||||
} else {
|
||||
listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
|
||||
}
|
||||
|
||||
if (card.isTestCard) {
|
||||
blockchains = blockchains.mapNotNull { it.getTestnetVersion() }
|
||||
}
|
||||
|
||||
return blockchains.mapNotNull {
|
||||
cryptoCurrencyFactory.createCoin(
|
||||
blockchain = it,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun createPrimaryCurrencyForSingleCurrencyCard(scanResponse: ScanResponse): CryptoCurrency {
|
||||
return with(getSingleWalletCurrencies(scanResponse)) {
|
||||
primaryToken ?: coin
|
||||
}
|
||||
}
|
||||
|
||||
override fun createCurrenciesForSingleCurrencyCardWithToken(scanResponse: ScanResponse): List<CryptoCurrency> {
|
||||
return with(getSingleWalletCurrencies(scanResponse)) {
|
||||
listOfNotNull(coin, primaryToken)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getMultiWalletCurrencies(userWallet: UserWallet, network: Network): List<CryptoCurrency> {
|
||||
val response = userTokensResponseStore.getSyncOrNull(userWalletId = userWallet.walletId)
|
||||
?: return emptyList()
|
||||
|
||||
val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory(excludedBlockchains)
|
||||
|
||||
return responseCurrenciesFactory.createCurrencies(
|
||||
tokens = response.tokens.filter {
|
||||
it.networkId == network.backendId && it.derivationPath == network.derivationPath.value
|
||||
},
|
||||
scanResponse = userWallet.scanResponse,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getSingleWalletCurrencies(scanResponse: ScanResponse): SingleWalletCurrencies {
|
||||
val resolver = scanResponse.cardTypesResolver
|
||||
val blockchain = resolver.getBlockchain()
|
||||
|
||||
val coin = cryptoCurrencyFactory.createCoin(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
|
||||
requireNotNull(coin) { "Coin for the single currency card cannot be null" }
|
||||
|
||||
val primaryToken = resolver.getPrimaryToken()?.let { token ->
|
||||
cryptoCurrencyFactory.createToken(
|
||||
sdkToken = token,
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
}
|
||||
|
||||
return SingleWalletCurrencies(coin = coin, primaryToken = primaryToken)
|
||||
}
|
||||
|
||||
private data class SingleWalletCurrencies(val coin: CryptoCurrency, val primaryToken: CryptoCurrency?)
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.data.common.di
|
||||
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.DefaultCardCryptoCurrencyFactory
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object DataCommonModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCardCryptoCurrencyFactory(
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
userWalletsStore: UserWalletsStore,
|
||||
userTokensResponseStore: UserTokensResponseStore,
|
||||
): CardCryptoCurrencyFactory {
|
||||
return DefaultCardCryptoCurrencyFactory(
|
||||
demoConfig = DemoConfig(),
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
userWalletsStore = userWalletsStore,
|
||||
userTokensResponseStore = userTokensResponseStore,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,430 @@
|
|||
package com.tangem.data.common.currency
|
||||
|
||||
import android.net.Uri
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.common.card.WalletData
|
||||
import com.tangem.common.test.domain.card.MockScanResponseFactory
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.common.configs.GenericCardConfig
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultCardCryptoCurrencyFactoryTest {
|
||||
|
||||
private val userWalletsStore: UserWalletsStore = mockk()
|
||||
private val userTokensResponseStore: UserTokensResponseStore = mockk()
|
||||
|
||||
private val factory = DefaultCardCryptoCurrencyFactory(
|
||||
demoConfig = DemoConfig(),
|
||||
excludedBlockchains = ExcludedBlockchains(),
|
||||
userWalletsStore = userWalletsStore,
|
||||
userTokensResponseStore = userTokensResponseStore,
|
||||
)
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
mockkStatic(Uri::class)
|
||||
every { Uri.parse(any()) } returns mockk()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test create if userTokensResponse is not empty`() = runTest {
|
||||
val multiWallet = createMultiWallet()
|
||||
|
||||
val userTokensResponse = UserTokensResponseFactory().createUserTokensResponse(
|
||||
currencies = listOf(ethereum),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
|
||||
coEvery { userWalletsStore.getSyncStrict(key = multiWallet.walletId) } returns multiWallet
|
||||
coEvery { userTokensResponseStore.getSyncOrNull(multiWallet.walletId) } returns userTokensResponse
|
||||
|
||||
val actual = factory.create(userWalletId = multiWallet.walletId, network = ethereum.network)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsStore.getSyncStrict(key = multiWallet.walletId)
|
||||
userTokensResponseStore.getSyncOrNull(multiWallet.walletId)
|
||||
}
|
||||
|
||||
val expected = listOf(ethereum)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test create if userTokensResponse is empty`() = runTest {
|
||||
val multiWallet = createMultiWallet()
|
||||
|
||||
val userTokensResponse = UserTokensResponseFactory().createUserTokensResponse(
|
||||
currencies = listOf(),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
|
||||
coEvery { userWalletsStore.getSyncStrict(key = multiWallet.walletId) } returns multiWallet
|
||||
coEvery { userTokensResponseStore.getSyncOrNull(multiWallet.walletId) } returns userTokensResponse
|
||||
|
||||
val actual = factory.create(userWalletId = multiWallet.walletId, network = ethereum.network)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsStore.getSyncStrict(key = multiWallet.walletId)
|
||||
userTokensResponseStore.getSyncOrNull(multiWallet.walletId)
|
||||
}
|
||||
|
||||
val expected = emptyList<CryptoCurrency>()
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test create if userTokensResponse is null`() = runTest {
|
||||
val multiWallet = createMultiWallet()
|
||||
|
||||
coEvery { userWalletsStore.getSyncStrict(key = multiWallet.walletId) } returns multiWallet
|
||||
coEvery { userTokensResponseStore.getSyncOrNull(multiWallet.walletId) } returns null
|
||||
|
||||
val actual = factory.create(userWalletId = multiWallet.walletId, network = ethereum.network)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsStore.getSyncStrict(key = multiWallet.walletId)
|
||||
userTokensResponseStore.getSyncOrNull(multiWallet.walletId)
|
||||
}
|
||||
|
||||
val expected = emptyList<CryptoCurrency>()
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test create if userTokensResponse does not contain currency of selected network`() = runTest {
|
||||
val multiWallet = createMultiWallet()
|
||||
|
||||
val userTokensResponse = UserTokensResponseFactory().createUserTokensResponse(
|
||||
currencies = listOf(bitcoin),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
|
||||
coEvery { userWalletsStore.getSyncStrict(key = multiWallet.walletId) } returns multiWallet
|
||||
coEvery { userTokensResponseStore.getSyncOrNull(multiWallet.walletId) } returns userTokensResponse
|
||||
|
||||
val actual = factory.create(userWalletId = multiWallet.walletId, network = ethereum.network)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsStore.getSyncStrict(key = multiWallet.walletId)
|
||||
userTokensResponseStore.getSyncOrNull(multiWallet.walletId)
|
||||
}
|
||||
|
||||
val expected = emptyList<CryptoCurrency>()
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test create if single wallet has another primary network`() = runTest {
|
||||
val singleWallet = createSingleWallet()
|
||||
|
||||
coEvery { userWalletsStore.getSyncStrict(key = singleWallet.walletId) } returns singleWallet
|
||||
|
||||
val actual = factory.create(userWalletId = singleWallet.walletId, network = bitcoin.network)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsStore.getSyncStrict(key = singleWallet.walletId)
|
||||
singleWallet.scanResponse.cardTypesResolver.getBlockchain()
|
||||
}
|
||||
|
||||
val expected = emptyList<CryptoCurrency>()
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test create if card is single wallet`() = runTest {
|
||||
val singleWallet = createSingleWallet()
|
||||
|
||||
coEvery { userWalletsStore.getSyncStrict(key = singleWallet.walletId) } returns singleWallet
|
||||
|
||||
val actual = factory.create(userWalletId = singleWallet.walletId, network = ethereum.network)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsStore.getSyncStrict(key = singleWallet.walletId)
|
||||
singleWallet.scanResponse.cardTypesResolver.getBlockchain()
|
||||
}
|
||||
|
||||
val expected = listOf(ethereum)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test create if card is single wallet with token`() = runTest {
|
||||
val singleWallet = createSingleWalletWithToken()
|
||||
|
||||
coEvery { userWalletsStore.getSyncStrict(key = singleWallet.walletId) } returns singleWallet
|
||||
|
||||
val actual = factory.create(userWalletId = singleWallet.walletId, network = ethereum.network)
|
||||
|
||||
val token = CryptoCurrencyFactory(excludedBlockchains = ExcludedBlockchains()).createToken(
|
||||
sdkToken = singleWallet.scanResponse.cardTypesResolver.getPrimaryToken()!!,
|
||||
blockchain = Blockchain.Ethereum,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = singleWallet.scanResponse,
|
||||
)
|
||||
val expected = listOf(ethereum, token)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createDefaultCoinsForMultiCurrencyCard if card is prod`() = runTest {
|
||||
val multiWallet = createMultiWallet()
|
||||
|
||||
val actual = factory.createDefaultCoinsForMultiCurrencyCard(scanResponse = multiWallet.scanResponse)
|
||||
|
||||
val expected = listOf(bitcoin, ethereum)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createDefaultCoinsForMultiCurrencyCard if card is test`() = runTest {
|
||||
val multiWallet = createMultiWallet().let {
|
||||
it.copy(
|
||||
scanResponse = it.scanResponse.copy(
|
||||
card = it.scanResponse.card.copy(cardId = "FF99", batchId = "99FF"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
val actual = factory.createDefaultCoinsForMultiCurrencyCard(scanResponse = multiWallet.scanResponse)
|
||||
|
||||
val expected = listOf(
|
||||
cryptoCurrencyFactory.createCoin(blockchain = Blockchain.BitcoinTestnet),
|
||||
cryptoCurrencyFactory.createCoin(blockchain = Blockchain.EthereumTestnet).setCanHandleTokens(true),
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createDefaultCoinsForMultiCurrencyCard if card is demo`() = runTest {
|
||||
val multiWallet = createMultiWallet().let {
|
||||
it.copy(
|
||||
scanResponse = it.scanResponse.copy(
|
||||
card = it.scanResponse.card.copy(cardId = "AC01000000041225"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
val actual = factory.createDefaultCoinsForMultiCurrencyCard(scanResponse = multiWallet.scanResponse)
|
||||
|
||||
val expected = listOf(
|
||||
bitcoin,
|
||||
ethereum,
|
||||
cryptoCurrencyFactory.createCoin(blockchain = Blockchain.Dogecoin),
|
||||
cryptoCurrencyFactory.createCoin(blockchain = Blockchain.Solana),
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createPrimaryCurrencyForSingleCurrencyCard if unable to create token`() = runTest {
|
||||
val singleWallet = UserWallet(
|
||||
name = "Note",
|
||||
walletId = UserWalletId("011"),
|
||||
cardsInWallet = setOf(),
|
||||
isMultiCurrency = false,
|
||||
scanResponse = MockScanResponseFactory.create(
|
||||
cardConfig = GenericCardConfig(maxWalletCount = 2),
|
||||
derivedKeys = emptyMap(),
|
||||
),
|
||||
hasBackupError = false,
|
||||
)
|
||||
|
||||
val actual = runCatching {
|
||||
factory.createPrimaryCurrencyForSingleCurrencyCard(scanResponse = singleWallet.scanResponse)
|
||||
}
|
||||
|
||||
val exception = IllegalArgumentException("Coin for the single currency card cannot be null")
|
||||
|
||||
Truth.assertThat(actual.isFailure).isTrue()
|
||||
Truth.assertThat(actual.exceptionOrNull()).isInstanceOf(exception::class.java)
|
||||
Truth.assertThat(actual.exceptionOrNull()).hasMessageThat().isEqualTo(exception.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createPrimaryCurrencyForSingleCurrencyCard if primaryToken is null`() = runTest {
|
||||
val singleWallet = createSingleWallet()
|
||||
|
||||
val actual = factory.createPrimaryCurrencyForSingleCurrencyCard(scanResponse = singleWallet.scanResponse)
|
||||
|
||||
val expected = ethereum
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createPrimaryCurrencyForSingleCurrencyCard if primaryToken is not null`() = runTest {
|
||||
val singleWallet = createSingleWalletWithToken()
|
||||
|
||||
val actual = factory.createPrimaryCurrencyForSingleCurrencyCard(scanResponse = singleWallet.scanResponse)
|
||||
|
||||
val expected = CryptoCurrencyFactory(excludedBlockchains = ExcludedBlockchains()).createToken(
|
||||
sdkToken = singleWallet.scanResponse.cardTypesResolver.getPrimaryToken()!!,
|
||||
blockchain = Blockchain.Ethereum,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = singleWallet.scanResponse,
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createCurrenciesForSingleCurrencyCardWithToken if unable to create token`() = runTest {
|
||||
val singleWalletWithToken = UserWallet(
|
||||
name = "Note",
|
||||
walletId = UserWalletId("011"),
|
||||
cardsInWallet = setOf(),
|
||||
isMultiCurrency = false,
|
||||
scanResponse = MockScanResponseFactory.create(
|
||||
cardConfig = GenericCardConfig(maxWalletCount = 2),
|
||||
derivedKeys = emptyMap(),
|
||||
),
|
||||
hasBackupError = false,
|
||||
)
|
||||
|
||||
val actual = runCatching {
|
||||
factory.createCurrenciesForSingleCurrencyCardWithToken(scanResponse = singleWalletWithToken.scanResponse)
|
||||
}
|
||||
|
||||
val exception = IllegalArgumentException("Coin for the single currency card cannot be null")
|
||||
|
||||
Truth.assertThat(actual.isFailure).isTrue()
|
||||
Truth.assertThat(actual.exceptionOrNull()).isInstanceOf(exception::class.java)
|
||||
Truth.assertThat(actual.exceptionOrNull()).hasMessageThat().isEqualTo(exception.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createCurrenciesForSingleCurrencyCardWithToken if primaryToken is null`() = runTest {
|
||||
val singleWalletWithToken = createSingleWallet()
|
||||
|
||||
val actual = factory.createCurrenciesForSingleCurrencyCardWithToken(
|
||||
scanResponse = singleWalletWithToken.scanResponse,
|
||||
)
|
||||
|
||||
val expected = listOf(ethereum)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createCurrenciesForSingleCurrencyCardWithToken if primaryToken is not null`() = runTest {
|
||||
val singleWalletWithToken = createSingleWalletWithToken()
|
||||
|
||||
val actual = factory.createCurrenciesForSingleCurrencyCardWithToken(
|
||||
scanResponse = singleWalletWithToken.scanResponse,
|
||||
)
|
||||
|
||||
val token = CryptoCurrencyFactory(excludedBlockchains = ExcludedBlockchains()).createToken(
|
||||
sdkToken = singleWalletWithToken.scanResponse.cardTypesResolver.getPrimaryToken()!!,
|
||||
blockchain = Blockchain.Ethereum,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = singleWalletWithToken.scanResponse,
|
||||
)
|
||||
|
||||
val expected = listOf(ethereum, token)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
private fun createMultiWallet(): UserWallet {
|
||||
return UserWallet(
|
||||
name = "Wallet 1",
|
||||
walletId = UserWalletId("011"),
|
||||
cardsInWallet = setOf(),
|
||||
isMultiCurrency = true,
|
||||
scanResponse = MockScanResponseFactory.create(
|
||||
cardConfig = GenericCardConfig(maxWalletCount = 2),
|
||||
derivedKeys = emptyMap(),
|
||||
),
|
||||
hasBackupError = false,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createSingleWallet(): UserWallet {
|
||||
return UserWallet(
|
||||
name = "Note",
|
||||
walletId = UserWalletId("011"),
|
||||
cardsInWallet = setOf(),
|
||||
isMultiCurrency = false,
|
||||
scanResponse = MockScanResponseFactory.create(
|
||||
cardConfig = GenericCardConfig(maxWalletCount = 2),
|
||||
derivedKeys = emptyMap(),
|
||||
).let {
|
||||
it.copy(
|
||||
card = it.card.copy(batchId = "AB10"),
|
||||
productType = ProductType.Note,
|
||||
)
|
||||
},
|
||||
hasBackupError = false,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createSingleWalletWithToken(): UserWallet {
|
||||
return UserWallet(
|
||||
name = "NODL",
|
||||
walletId = UserWalletId("011"),
|
||||
cardsInWallet = setOf(),
|
||||
isMultiCurrency = false,
|
||||
scanResponse = MockScanResponseFactory.create(
|
||||
cardConfig = GenericCardConfig(maxWalletCount = 2),
|
||||
derivedKeys = emptyMap(),
|
||||
).copy(
|
||||
productType = ProductType.Note,
|
||||
walletData = WalletData(
|
||||
blockchain = "ETH",
|
||||
token = WalletData.Token(
|
||||
name = "Ethereum",
|
||||
symbol = "ETH",
|
||||
contractAddress = "0x",
|
||||
decimals = 8,
|
||||
),
|
||||
),
|
||||
),
|
||||
hasBackupError = false,
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
val cryptoCurrencyFactory = MockCryptoCurrencyFactory()
|
||||
|
||||
val ethereum = cryptoCurrencyFactory.ethereum.setCanHandleTokens(value = true)
|
||||
|
||||
val bitcoin = cryptoCurrencyFactory.createCoin(blockchain = Blockchain.Bitcoin)
|
||||
|
||||
fun CryptoCurrency.setCanHandleTokens(value: Boolean): CryptoCurrency {
|
||||
return when (this) {
|
||||
is CryptoCurrency.Coin -> copy(network = network.copy(canHandleTokens = value))
|
||||
is CryptoCurrency.Token -> copy(network = network.copy(canHandleTokens = value))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,12 +6,12 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
|||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.data.common.api.safeApiCall
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.UserTokensResponseFactory
|
||||
import com.tangem.data.common.currency.getBlockchain
|
||||
import com.tangem.data.common.utils.retryOnError
|
||||
import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher
|
||||
import com.tangem.data.managetokens.utils.ManagedCryptoCurrencyFactory
|
||||
import com.tangem.data.tokens.utils.CardCryptoCurrenciesFactory
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
|
|
@ -26,7 +26,6 @@ import com.tangem.domain.common.extensions.canHandleToken
|
|||
import com.tangem.domain.common.extensions.supportedBlockchains
|
||||
import com.tangem.domain.common.extensions.supportedTokens
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.managetokens.model.*
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork
|
||||
import com.tangem.domain.managetokens.repository.ManageTokensRepository
|
||||
|
|
@ -48,12 +47,12 @@ internal class DefaultManageTokensRepository(
|
|||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val testnetTokensStorage: TestnetTokensStorage,
|
||||
private val excludedBlockchains: ExcludedBlockchains,
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : ManageTokensRepository {
|
||||
|
||||
private val managedCryptoCurrencyFactory = ManagedCryptoCurrencyFactory(excludedBlockchains)
|
||||
private val userTokensResponseFactory = UserTokensResponseFactory()
|
||||
private val cardCurrenciesFactory = CardCryptoCurrenciesFactory(DemoConfig(), excludedBlockchains)
|
||||
|
||||
// region getTokenListBatchFlow
|
||||
override fun getTokenListBatchFlow(
|
||||
|
|
@ -77,7 +76,7 @@ internal class DefaultManageTokensRepository(
|
|||
prefetchDistance = batchSize,
|
||||
batchSize = batchSize,
|
||||
subFetcher = { request, _, isFirstBatchFetching ->
|
||||
val userWallet = request.params.userWalletId?.let { getUserWallet(it) }
|
||||
val userWallet = request.params.userWalletId?.let(userWalletsStore::getSyncStrict)
|
||||
|
||||
if (userWallet?.scanResponse?.card?.isTestCard == true) {
|
||||
fetchTestnetCurrencies(userWallet, request)
|
||||
|
|
@ -190,17 +189,11 @@ internal class DefaultManageTokensRepository(
|
|||
|
||||
private fun createDefaultUserTokensResponse(userWallet: UserWallet) =
|
||||
userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = cardCurrenciesFactory.createDefaultCoinsForMultiCurrencyCard(userWallet.scanResponse),
|
||||
currencies = cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyCard(userWallet.scanResponse),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
|
||||
private suspend fun getUserWallet(userWalletId: UserWalletId): UserWallet {
|
||||
return requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
|
||||
"Unable to find a user wallet with provided ID: $userWalletId"
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSupportedBlockchains(userWallet: UserWallet?): List<Blockchain> {
|
||||
return userWallet?.scanResponse?.let {
|
||||
it.card.supportedBlockchains(it.cardTypesResolver, excludedBlockchains)
|
||||
|
|
@ -261,7 +254,7 @@ internal class DefaultManageTokensRepository(
|
|||
userWalletId: UserWalletId,
|
||||
sourceNetwork: SourceNetwork,
|
||||
): CurrencyUnsupportedState? {
|
||||
val userWallet = getUserWallet(userWalletId = userWalletId)
|
||||
val userWallet = userWalletsStore.getSyncStrict(key = userWalletId)
|
||||
val blockchain = getBlockchain(sourceNetwork.id)
|
||||
return when (sourceNetwork) {
|
||||
is SourceNetwork.Default -> checkTokenUnsupportedState(userWallet = userWallet, blockchain = blockchain)
|
||||
|
|
@ -274,7 +267,7 @@ internal class DefaultManageTokensRepository(
|
|||
rawNetworkId: String,
|
||||
isMainNetwork: Boolean,
|
||||
): CurrencyUnsupportedState? {
|
||||
val userWallet = getUserWallet(userWalletId = userWalletId)
|
||||
val userWallet = userWalletsStore.getSyncStrict(key = userWalletId)
|
||||
val blockchain = Blockchain.fromNetworkId(networkId = rawNetworkId)
|
||||
?: error("Can not create blockchain with given networkId -> $rawNetworkId")
|
||||
return if (isMainNetwork) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.data.managetokens.di
|
||||
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.managetokens.DefaultCustomTokensRepository
|
||||
import com.tangem.data.managetokens.DefaultManageTokensRepository
|
||||
import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher
|
||||
|
|
@ -32,15 +33,17 @@ internal object ManageTokensDataModule {
|
|||
testnetTokensStorage: TestnetTokensStorage,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
): ManageTokensRepository {
|
||||
return DefaultManageTokensRepository(
|
||||
tangemTechApi,
|
||||
userWalletsStore,
|
||||
manageTokensUpdateFetcher,
|
||||
appPreferencesStore,
|
||||
testnetTokensStorage,
|
||||
excludedBlockchains,
|
||||
dispatchers,
|
||||
tangemTechApi = tangemTechApi,
|
||||
userWalletsStore = userWalletsStore,
|
||||
manageTokensUpdateFetcher = manageTokensUpdateFetcher,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
testnetTokensStorage = testnetTokensStorage,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,27 +1,14 @@
|
|||
package com.tangem.data.networks.single
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.networks.store.NetworksStatusesStoreV2
|
||||
import com.tangem.data.tokens.utils.CardCryptoCurrenciesFactory
|
||||
import com.tangem.data.tokens.utils.NetworkStatusFactory
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.core.utils.catchOn
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
|
@ -30,27 +17,20 @@ import javax.inject.Inject
|
|||
/**
|
||||
* Default implementation of [SingleNetworkStatusFetcher]
|
||||
*
|
||||
* @param excludedBlockchains excluded blockchains
|
||||
* @property walletManagersFacade wallet managers facade
|
||||
* @property networksStatusesStore networks statuses store
|
||||
* @property userWalletsStore user wallets store
|
||||
* @property appPreferencesStore app preferences store
|
||||
* @property dispatchers dispatchers
|
||||
* @property walletManagersFacade wallet managers facade
|
||||
* @property networksStatusesStore networks statuses store
|
||||
* @property cardCryptoCurrencyFactory card crypto currency factory
|
||||
* @property dispatchers dispatchers
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultSingleNetworkStatusFetcher @Inject constructor(
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val networksStatusesStore: NetworksStatusesStoreV2,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : SingleNetworkStatusFetcher {
|
||||
|
||||
private val demoConfig = DemoConfig()
|
||||
private val cardCurrenciesFactory = CardCryptoCurrenciesFactory(demoConfig, excludedBlockchains)
|
||||
private val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory(excludedBlockchains)
|
||||
private val networkStatusFactory = NetworkStatusFactory()
|
||||
|
||||
override suspend fun invoke(params: SingleNetworkStatusFetcher.Params) = Either.catchOn(dispatchers.default) {
|
||||
|
|
@ -59,9 +39,10 @@ internal class DefaultSingleNetworkStatusFetcher @Inject constructor(
|
|||
is SingleNetworkStatusFetcher.Params.Simple -> {
|
||||
networksStatusesStore.refresh(userWalletId = params.userWalletId, network = params.network)
|
||||
|
||||
val userWallet = userWalletsStore.getSyncStrict(key = params.userWalletId)
|
||||
|
||||
createCurrencies(userWallet = userWallet, network = params.network)
|
||||
cardCryptoCurrencyFactory.create(
|
||||
userWalletId = params.userWalletId,
|
||||
network = params.network,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -105,36 +86,4 @@ internal class DefaultSingleNetworkStatusFetcher @Inject constructor(
|
|||
Timber.e("Failed to fetch network status for $params: $it")
|
||||
networksStatusesStore.storeError(userWalletId = params.userWalletId, network = params.network)
|
||||
}
|
||||
|
||||
private suspend fun createCurrencies(userWallet: UserWallet, network: Network): List<CryptoCurrency> {
|
||||
val blockchain = Blockchain.fromNetworkId(networkId = network.backendId)
|
||||
|
||||
// multi-currency wallet
|
||||
if (userWallet.isMultiCurrency) return getMultiWalletCurrencies(userWallet = userWallet, network = network)
|
||||
|
||||
// check if the blockchain of single-currency wallet is the same as network
|
||||
val cardBlockchain = userWallet.scanResponse.cardTypesResolver.getBlockchain()
|
||||
if (cardBlockchain != blockchain) return emptyList()
|
||||
|
||||
// single-currency wallet with token (NODL)
|
||||
if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
|
||||
return cardCurrenciesFactory.createCurrenciesForSingleCurrencyCardWithToken(userWallet.scanResponse)
|
||||
}
|
||||
|
||||
// single-currency wallet
|
||||
return cardCurrenciesFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse).let(::listOf)
|
||||
}
|
||||
|
||||
private suspend fun getMultiWalletCurrencies(userWallet: UserWallet, network: Network): List<CryptoCurrency> {
|
||||
val response = appPreferencesStore.getObjectSyncOrNull<UserTokensResponse>(
|
||||
key = PreferencesKeys.getUserTokensKey(userWallet.walletId.stringValue),
|
||||
) ?: return emptyList()
|
||||
|
||||
return responseCurrenciesFactory.createCurrencies(
|
||||
tokens = response.tokens.filter {
|
||||
it.networkId == network.backendId && it.derivationPath == network.derivationPath.value
|
||||
},
|
||||
scanResponse = userWallet.scanResponse,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,8 +2,8 @@ package com.tangem.data.networks.single
|
|||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.networks.store.NetworksStatusesStoreV2
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
|
|
@ -22,35 +22,35 @@ import org.junit.Test
|
|||
*/
|
||||
internal class DefaultSingleNetworkStatusFetcherTest {
|
||||
|
||||
private val walletManagersFacade: WalletManagersFacade = mockk(relaxed = true)
|
||||
private val networksStatusesStore: NetworksStatusesStoreV2 = mockk(relaxed = true)
|
||||
private val userWalletsStore: UserWalletsStore = mockk(relaxed = true)
|
||||
private val walletManagersFacade: WalletManagersFacade = mockk(relaxUnitFun = true)
|
||||
private val networksStatusesStore: NetworksStatusesStoreV2 = mockk(relaxUnitFun = true)
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory = mockk()
|
||||
|
||||
private val fetcher = DefaultSingleNetworkStatusFetcher(
|
||||
excludedBlockchains = mockk(relaxed = true),
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
networksStatusesStore = networksStatusesStore,
|
||||
userWalletsStore = userWalletsStore,
|
||||
appPreferencesStore = mockk(relaxed = true),
|
||||
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `fetch network status successfully`() = runTest {
|
||||
val params = SingleNetworkStatusFetcher.Params.Simple(userWalletId = userWalletId, network = network)
|
||||
val params = createParams()
|
||||
|
||||
coEvery { cardCryptoCurrencyFactory.create(params.userWalletId, params.network) } returns listOf(ethereum)
|
||||
|
||||
val result = UpdateWalletManagerResult.MissedDerivation
|
||||
coEvery { walletManagersFacade.update(userWalletId, network, emptySet()) } returns result
|
||||
coEvery { walletManagersFacade.update(params.userWalletId, params.network, emptySet()) } returns result
|
||||
|
||||
val actual = fetcher(params)
|
||||
|
||||
coVerifyOrder {
|
||||
networksStatusesStore.refresh(userWalletId = userWalletId, network = network)
|
||||
userWalletsStore.getSyncStrict(key = userWalletId)
|
||||
walletManagersFacade.update(userWalletId, network, emptySet())
|
||||
networksStatusesStore.refresh(params.userWalletId, params.network)
|
||||
cardCryptoCurrencyFactory.create(params.userWalletId, params.network)
|
||||
walletManagersFacade.update(params.userWalletId, params.network, emptySet())
|
||||
networksStatusesStore.storeSuccess(
|
||||
userWalletId = userWalletId,
|
||||
value = NetworkStatus(network, NetworkStatus.MissedDerivation),
|
||||
userWalletId = params.userWalletId,
|
||||
value = NetworkStatus(params.network, NetworkStatus.MissedDerivation),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -59,17 +59,17 @@ internal class DefaultSingleNetworkStatusFetcherTest {
|
|||
|
||||
@Test
|
||||
fun `fetch network status failure`() = runTest {
|
||||
val params = SingleNetworkStatusFetcher.Params.Simple(userWalletId = userWalletId, network = network)
|
||||
val params = createParams()
|
||||
|
||||
val exception = IllegalStateException()
|
||||
coEvery { userWalletsStore.getSyncStrict(key = userWalletId) } throws exception
|
||||
coEvery { cardCryptoCurrencyFactory.create(params.userWalletId, params.network) } throws exception
|
||||
|
||||
val actual = fetcher(params)
|
||||
|
||||
coVerifyOrder {
|
||||
networksStatusesStore.refresh(userWalletId = userWalletId, network = network)
|
||||
userWalletsStore.getSyncStrict(key = userWalletId)
|
||||
networksStatusesStore.storeError(userWalletId = userWalletId, network = network)
|
||||
networksStatusesStore.refresh(userWalletId = params.userWalletId, network = params.network)
|
||||
cardCryptoCurrencyFactory.create(userWalletId = params.userWalletId, network = params.network)
|
||||
networksStatusesStore.storeError(userWalletId = params.userWalletId, network = params.network)
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
|
|
@ -81,8 +81,15 @@ internal class DefaultSingleNetworkStatusFetcherTest {
|
|||
Truth.assertThat(actual.leftOrNull()).isEqualTo(exception)
|
||||
}
|
||||
|
||||
private fun createParams(): SingleNetworkStatusFetcher.Params {
|
||||
return SingleNetworkStatusFetcher.Params.Simple(
|
||||
userWalletId = UserWalletId("011"),
|
||||
network = ethereum.network,
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val userWalletId = UserWalletId("011")
|
||||
val network = MockCryptoCurrencyFactory().ethereum.network
|
||||
|
||||
val ethereum = MockCryptoCurrencyFactory().ethereum
|
||||
}
|
||||
}
|
||||
|
|
@ -4,12 +4,11 @@ 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.WalletBody
|
||||
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
|
||||
import com.tangem.datasource.local.preferences.utils.*
|
||||
import com.tangem.domain.notifications.models.ApplicationId
|
||||
import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||
import com.tangem.domain.notifications.models.NotificationsEligibleNetwork
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -23,7 +22,7 @@ internal class DefaultNotificationsRepository @Inject constructor(
|
|||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : NotificationsRepository {
|
||||
|
||||
override suspend fun createApplicationId(pushToken: String?): String = withContext(dispatchers.io) {
|
||||
override suspend fun createApplicationId(pushToken: String?): ApplicationId = withContext(dispatchers.io) {
|
||||
tangemTechApi.createApplicationId(
|
||||
NotificationApplicationCreateBody(
|
||||
platform = appInfoProvider.platform,
|
||||
|
|
@ -33,15 +32,16 @@ internal class DefaultNotificationsRepository @Inject constructor(
|
|||
timezone = appInfoProvider.timezone,
|
||||
pushToken = pushToken,
|
||||
),
|
||||
).getOrThrow().appId
|
||||
).getOrThrow().appId.let(::ApplicationId)
|
||||
}
|
||||
|
||||
override suspend fun saveApplicationId(appId: String) {
|
||||
appPreferencesStore.store(PreferencesKeys.NOTIFICATIONS_APPLICATION_ID_KEY, appId)
|
||||
override suspend fun saveApplicationId(appId: ApplicationId) {
|
||||
appPreferencesStore.store(PreferencesKeys.NOTIFICATIONS_APPLICATION_ID_KEY, appId.value)
|
||||
}
|
||||
|
||||
override suspend fun getApplicationId(): String? {
|
||||
override suspend fun getApplicationId(): ApplicationId? {
|
||||
return appPreferencesStore.getSyncOrNull(PreferencesKeys.NOTIFICATIONS_APPLICATION_ID_KEY)
|
||||
?.let(::ApplicationId)
|
||||
}
|
||||
|
||||
override suspend fun incrementTronTokenFeeNotificationShowCounter() {
|
||||
|
|
@ -61,31 +61,10 @@ internal class DefaultNotificationsRepository @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
override suspend fun associateApplicationIdWithWallets(appId: String, wallets: List<String>) =
|
||||
withContext(dispatchers.io) {
|
||||
tangemTechApi.associateApplicationIdWithWallets(
|
||||
applicationId = appId,
|
||||
body = wallets.map {
|
||||
WalletIdBody(it)
|
||||
},
|
||||
).getOrThrow()
|
||||
}
|
||||
|
||||
override suspend fun setWalletName(walletId: String, walletName: String) = withContext(dispatchers.io) {
|
||||
tangemTechApi.updateWallet(
|
||||
walletId,
|
||||
WalletBody(name = walletName),
|
||||
).getOrThrow()
|
||||
}
|
||||
|
||||
override suspend fun getWalletName(walletId: String): String? = withContext(dispatchers.io) {
|
||||
tangemTechApi.getWalletById(walletId).getOrThrow().name
|
||||
}
|
||||
|
||||
override suspend fun sendPushToken(appId: String, pushToken: String) {
|
||||
override suspend fun sendPushToken(appId: ApplicationId, pushToken: String) {
|
||||
withContext(dispatchers.io) {
|
||||
tangemTechApi.updatePushTokenForApplicationId(
|
||||
appId,
|
||||
appId.value,
|
||||
NotificationApplicationCreateBody(
|
||||
pushToken = pushToken,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.squareup.moshi.Moshi
|
|||
import com.tangem.data.notifications.converters.NotificationsEligibleNetworkConverter
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.*
|
||||
import com.tangem.domain.notifications.models.ApplicationId
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
|
|
@ -41,9 +42,9 @@ class DefaultNotificationsRepositoryTest {
|
|||
fun `GIVEN valid push token WHEN createApplicationId THEN returns application id`() = runTest {
|
||||
// GIVEN
|
||||
val pushToken = "test-push-token"
|
||||
val expectedAppId = "test-app-id"
|
||||
val expectedAppId = ApplicationId("test-app-id")
|
||||
val expectedAppIdResponse = NotificationApplicationIdResponse(
|
||||
appId = expectedAppId,
|
||||
appId = expectedAppId.value,
|
||||
)
|
||||
coEvery { appInfoProvider.platform } returns "android"
|
||||
coEvery { appInfoProvider.device } returns "test-device"
|
||||
|
|
@ -76,7 +77,7 @@ class DefaultNotificationsRepositoryTest {
|
|||
@Test
|
||||
fun `GIVEN application id WHEN saveApplicationId THEN stores it in preferences`() = runTest {
|
||||
// GIVEN
|
||||
val appId = "test-app-id"
|
||||
val appId = ApplicationId("test-app-id")
|
||||
val preferences = mockk<Preferences>(relaxed = true)
|
||||
coEvery { preferencesDataStore.updateData(any()) } returns preferences
|
||||
|
||||
|
|
@ -90,10 +91,10 @@ class DefaultNotificationsRepositoryTest {
|
|||
@Test
|
||||
fun `GIVEN stored application id WHEN getApplicationId THEN returns it`() = runTest {
|
||||
// GIVEN
|
||||
val expectedAppId = "test-app-id"
|
||||
val expectedAppId = ApplicationId("test-app-id")
|
||||
val preferences = mockk<Preferences>(relaxed = true)
|
||||
val key = stringPreferencesKey(PreferencesKeys.NOTIFICATIONS_APPLICATION_ID_KEY.name)
|
||||
every { preferences[key] } returns expectedAppId
|
||||
every { preferences[key] } returns expectedAppId.value
|
||||
coEvery { preferencesDataStore.data } returns flowOf(preferences)
|
||||
|
||||
// WHEN
|
||||
|
|
@ -103,75 +104,24 @@ 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 wallet id and name WHEN setWalletName THEN updates wallet name`() = runTest {
|
||||
// GIVEN
|
||||
val walletId = "test-wallet-id"
|
||||
val walletName = "Test Wallet"
|
||||
coEvery {
|
||||
tangemTechApi.updateWallet(
|
||||
walletId,
|
||||
WalletBody(name = walletName),
|
||||
)
|
||||
} returns ApiResponse.Success(Unit)
|
||||
|
||||
// WHEN
|
||||
repository.setWalletName(walletId, walletName)
|
||||
|
||||
// THEN
|
||||
coVerify { tangemTechApi.updateWallet(walletId, WalletBody(name = walletName)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN wallet id WHEN getWalletName THEN returns wallet name`() = runTest {
|
||||
// GIVEN
|
||||
val walletId = "test-wallet-id"
|
||||
val expectedName = "Test Wallet"
|
||||
coEvery { tangemTechApi.getWalletById(walletId) } returns ApiResponse.Success(
|
||||
WalletResponse(
|
||||
notifyStatus = false,
|
||||
name = expectedName,
|
||||
id = walletId,
|
||||
),
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result = repository.getWalletName(walletId)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(expectedName)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN application id and push token WHEN sendPushToken THEN updates push token`() = runTest {
|
||||
// GIVEN
|
||||
val appId = "test-app-id"
|
||||
val appId = ApplicationId("test-app-id")
|
||||
val pushToken = "test-push-token"
|
||||
coEvery {
|
||||
tangemTechApi.updatePushTokenForApplicationId(
|
||||
appId,
|
||||
NotificationApplicationCreateBody(pushToken = pushToken),
|
||||
appId.value,
|
||||
NotificationApplicationCreateBody(
|
||||
pushToken = pushToken,
|
||||
platform = null,
|
||||
device = null,
|
||||
systemVersion = null,
|
||||
language = null,
|
||||
timezone = null,
|
||||
),
|
||||
)
|
||||
} returns ApiResponse.Success(appId)
|
||||
} returns ApiResponse.Success(appId.value)
|
||||
|
||||
// WHEN
|
||||
repository.sendPushToken(appId, pushToken)
|
||||
|
|
@ -179,8 +129,15 @@ class DefaultNotificationsRepositoryTest {
|
|||
// THEN
|
||||
coVerify {
|
||||
tangemTechApi.updatePushTokenForApplicationId(
|
||||
appId,
|
||||
NotificationApplicationCreateBody(pushToken = pushToken),
|
||||
appId.value,
|
||||
NotificationApplicationCreateBody(
|
||||
pushToken = pushToken,
|
||||
platform = null,
|
||||
device = null,
|
||||
systemVersion = null,
|
||||
language = null,
|
||||
timezone = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ package com.tangem.data.quotes.di
|
|||
|
||||
import com.tangem.data.quotes.multi.DefaultMultiQuoteFetcher
|
||||
import com.tangem.data.quotes.multi.DefaultMultiQuoteUpdater
|
||||
import com.tangem.data.quotes.single.DefaultSingleQuoteFetcher
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteUpdater
|
||||
import com.tangem.domain.quotes.single.SingleQuoteFetcher
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -21,4 +23,8 @@ internal interface QuoteFetcherModule {
|
|||
@Binds
|
||||
@Singleton
|
||||
fun bindMultiQuoteUpdater(impl: DefaultMultiQuoteUpdater): MultiQuoteUpdater
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindSingleQuoteFetcher(impl: DefaultSingleQuoteFetcher): SingleQuoteFetcher
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.data.quotes.single
|
||||
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
|
||||
import com.tangem.domain.quotes.single.SingleQuoteFetcher
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultSingleQuoteFetcher @Inject constructor(
|
||||
private val multiQuoteFetcher: MultiQuoteFetcher,
|
||||
) : SingleQuoteFetcher {
|
||||
|
||||
override suspend fun invoke(params: SingleQuoteFetcher.Params) = multiQuoteFetcher.invoke(
|
||||
MultiQuoteFetcher.Params(
|
||||
currenciesIds = setOf(params.rawCurrencyId),
|
||||
appCurrencyId = params.appCurrencyId,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
package com.tangem.data.quotes.single
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.common.test.data.quote.MockQuoteResponseFactory
|
||||
import com.tangem.data.quotes.multi.DefaultMultiQuoteFetcher
|
||||
import com.tangem.data.quotes.store.QuotesStoreV2
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
|
||||
import com.tangem.datasource.appcurrency.AppCurrencyResponseStore
|
||||
import com.tangem.domain.quotes.single.SingleQuoteFetcher
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.coVerifyOrder
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class DefaultSingleQuoteFetcherTest {
|
||||
|
||||
private val tangemTechApi = mockk<TangemTechApi>(relaxed = true)
|
||||
private val appCurrencyResponseStore = mockk<AppCurrencyResponseStore>(relaxed = true)
|
||||
private val quotesStore = mockk<QuotesStoreV2>(relaxed = true)
|
||||
|
||||
private val multiFetcher = DefaultMultiQuoteFetcher(
|
||||
tangemTechApi = tangemTechApi,
|
||||
appCurrencyResponseStore = appCurrencyResponseStore,
|
||||
quotesStore = quotesStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
private val singleFetcher = DefaultSingleQuoteFetcher(multiFetcher)
|
||||
|
||||
@Test
|
||||
fun `fetch single quote successfully`() = runTest {
|
||||
val params = SingleQuoteFetcher.Params(rawCurrencyId = currenciesId, appCurrencyId = null)
|
||||
|
||||
coEvery { appCurrencyResponseStore.getSyncOrNull() } returns usdAppCurrency
|
||||
|
||||
val coinIds = "BTC"
|
||||
coEvery {
|
||||
tangemTechApi.getQuotes(currencyId = "usd", coinIds = coinIds)
|
||||
} returns ApiResponse.Success(successResponse)
|
||||
|
||||
val actual = singleFetcher(params)
|
||||
|
||||
coVerifyOrder {
|
||||
quotesStore.refresh(currenciesIds = setOf(params.rawCurrencyId))
|
||||
appCurrencyResponseStore.getSyncOrNull()
|
||||
tangemTechApi.getQuotes(currencyId = "usd", coinIds = coinIds)
|
||||
quotesStore.storeActual(values = successResponse.quotes)
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
quotesStore.storeError(currenciesIds = any())
|
||||
}
|
||||
|
||||
Truth.assertThat(actual.isRight()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch single quote successfully if appCurrencyId from params is not null`() = runTest {
|
||||
val appCurrencyId = "usd"
|
||||
val params = SingleQuoteFetcher.Params(rawCurrencyId = currenciesId, appCurrencyId = appCurrencyId)
|
||||
|
||||
val coinIds = "BTC"
|
||||
coEvery {
|
||||
tangemTechApi.getQuotes(currencyId = appCurrencyId, coinIds = coinIds)
|
||||
} returns ApiResponse.Success(successResponse)
|
||||
|
||||
val actual = singleFetcher(params)
|
||||
|
||||
coVerifyOrder {
|
||||
quotesStore.refresh(currenciesIds = setOf(currenciesId))
|
||||
tangemTechApi.getQuotes(currencyId = "usd", coinIds = coinIds)
|
||||
quotesStore.storeActual(values = successResponse.quotes)
|
||||
}
|
||||
|
||||
Truth.assertThat(actual.isRight()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch single quote failure because appCurrencyId from params is blank`() = runTest {
|
||||
val appCurrencyId = ""
|
||||
val params = SingleQuoteFetcher.Params(rawCurrencyId = currenciesId, appCurrencyId = appCurrencyId)
|
||||
|
||||
val actual = singleFetcher(params)
|
||||
|
||||
coVerifyOrder {
|
||||
quotesStore.refresh(currenciesIds = setOf(currenciesId))
|
||||
quotesStore.storeError(currenciesIds = setOf(currenciesId))
|
||||
}
|
||||
|
||||
Truth.assertThat(actual.isLeft()).isTrue()
|
||||
Truth.assertThat(actual.leftOrNull()).isInstanceOf(IllegalStateException::class.java)
|
||||
Truth.assertThat(actual.leftOrNull()).hasMessageThat()
|
||||
.isEqualTo("Unable to get AppCurrency for updating quotes")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch single quote failure because api request failed`() = runTest {
|
||||
val params = SingleQuoteFetcher.Params(rawCurrencyId = currenciesId, appCurrencyId = null)
|
||||
|
||||
coEvery { appCurrencyResponseStore.getSyncOrNull() } returns usdAppCurrency
|
||||
|
||||
val coinIds = "BTC"
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val errorResponse = ApiResponse.Error(ApiResponseError.NetworkException) as ApiResponse<QuotesResponse>
|
||||
coEvery { tangemTechApi.getQuotes(currencyId = "usd", coinIds = coinIds) } returns errorResponse
|
||||
|
||||
val actual = singleFetcher(params)
|
||||
|
||||
coVerifyOrder {
|
||||
quotesStore.refresh(currenciesIds = setOf(currenciesId))
|
||||
appCurrencyResponseStore.getSyncOrNull()
|
||||
tangemTechApi.getQuotes(currencyId = "usd", coinIds = coinIds)
|
||||
quotesStore.storeError(currenciesIds = setOf(currenciesId))
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
quotesStore.storeActual(values = any())
|
||||
}
|
||||
|
||||
Truth.assertThat(actual.isLeft()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch single quote failure because app currency not found`() = runTest {
|
||||
val params = SingleQuoteFetcher.Params(rawCurrencyId = currenciesId, appCurrencyId = null)
|
||||
|
||||
coEvery { appCurrencyResponseStore.getSyncOrNull() } returns null
|
||||
|
||||
val actual = singleFetcher(params)
|
||||
|
||||
coVerifyOrder {
|
||||
quotesStore.refresh(currenciesIds = setOf(currenciesId))
|
||||
appCurrencyResponseStore.getSyncOrNull()
|
||||
quotesStore.storeError(currenciesIds = setOf(currenciesId))
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
tangemTechApi.getQuotes(currencyId = any(), coinIds = any())
|
||||
quotesStore.storeActual(values = any())
|
||||
}
|
||||
|
||||
Truth.assertThat(actual.isLeft()).isTrue()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
val currenciesId = CryptoCurrency.RawID(value = "BTC")
|
||||
|
||||
val usdAppCurrency = CurrenciesResponse.Currency(
|
||||
id = "USD".lowercase(),
|
||||
code = "USD",
|
||||
name = "US Dollar",
|
||||
unit = "$",
|
||||
type = "fiat",
|
||||
rateBTC = "",
|
||||
)
|
||||
|
||||
val successResponse = QuotesResponse(
|
||||
quotes = mapOf(
|
||||
"BTC" to MockQuoteResponseFactory.createSinglePrice(value = BigDecimal.ONE),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.data.tokens.di
|
|||
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.data.common.cache.CacheRegistry
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.tokens.repository.*
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader
|
||||
|
|
@ -33,6 +34,7 @@ internal object TokensDataModule {
|
|||
dispatchers: CoroutineDispatcherProvider,
|
||||
expressServiceLoader: ExpressServiceLoader,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
): CurrenciesRepository {
|
||||
return DefaultCurrenciesRepository(
|
||||
tangemTechApi = tangemTechApi,
|
||||
|
|
@ -43,6 +45,7 @@ internal object TokensDataModule {
|
|||
expressServiceLoader = expressServiceLoader,
|
||||
dispatchers = dispatchers,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -74,6 +77,7 @@ internal object TokensDataModule {
|
|||
cacheRegistry: CacheRegistry,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
): NetworksRepository {
|
||||
return DefaultNetworksRepository(
|
||||
networksStatusesStore = networksStatusesStore,
|
||||
|
|
@ -83,6 +87,7 @@ internal object TokensDataModule {
|
|||
cacheRegistry = cacheRegistry,
|
||||
dispatchers = dispatchers,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import com.tangem.blockchainsdk.utils.*
|
|||
import com.tangem.data.common.api.safeApiCall
|
||||
import com.tangem.data.common.cache.CacheRegistry
|
||||
import com.tangem.data.common.currency.*
|
||||
import com.tangem.data.tokens.utils.CardCryptoCurrenciesFactory
|
||||
import com.tangem.data.tokens.utils.CustomTokensMerger
|
||||
import com.tangem.data.tokens.utils.UserTokensBackwardCompatibility
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
|
|
@ -51,12 +50,12 @@ internal class DefaultCurrenciesRepository(
|
|||
private val expressServiceLoader: ExpressServiceLoader,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val excludedBlockchains: ExcludedBlockchains,
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
) : CurrenciesRepository {
|
||||
|
||||
private val demoConfig = DemoConfig()
|
||||
private val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory(excludedBlockchains)
|
||||
private val cryptoCurrencyFactory = CryptoCurrencyFactory(excludedBlockchains)
|
||||
private val cardCurrenciesFactory = CardCryptoCurrenciesFactory(demoConfig, excludedBlockchains)
|
||||
private val userTokensResponseFactory = UserTokensResponseFactory()
|
||||
private val userTokensBackwardCompatibility = UserTokensBackwardCompatibility()
|
||||
private val customTokensMerger = CustomTokensMerger(tangemTechApi, dispatchers)
|
||||
|
|
@ -223,7 +222,7 @@ internal class DefaultCurrenciesRepository(
|
|||
val userWallet = getUserWallet(userWalletId)
|
||||
ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = false)
|
||||
|
||||
val currency = cardCurrenciesFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse)
|
||||
val currency = cardCryptoCurrencyFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse)
|
||||
fetchExpressAssetsByNetworkIds(userWalletId, listOf(currency), refresh)
|
||||
currency
|
||||
}
|
||||
|
|
@ -237,8 +236,8 @@ internal class DefaultCurrenciesRepository(
|
|||
val userWallet = getUserWallet(userWalletId)
|
||||
ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = false)
|
||||
|
||||
val currencies = cardCurrenciesFactory.createCurrenciesForSingleCurrencyCardWithToken(
|
||||
userWallet.scanResponse,
|
||||
val currencies = cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(
|
||||
scanResponse = userWallet.scanResponse,
|
||||
)
|
||||
fetchExpressAssetsByNetworkIds(userWalletId, currencies, refresh)
|
||||
currencies
|
||||
|
|
@ -253,7 +252,9 @@ internal class DefaultCurrenciesRepository(
|
|||
val userWallet = getUserWallet(userWalletId)
|
||||
ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = false)
|
||||
|
||||
val currency = cardCurrenciesFactory.createCurrenciesForSingleCurrencyCardWithToken(userWallet.scanResponse)
|
||||
val currency = cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(
|
||||
scanResponse = userWallet.scanResponse,
|
||||
)
|
||||
.find { it.id == id }
|
||||
requireNotNull(currency) { "Unable to find currency with provided ID: $id" }
|
||||
fetchExpressAssetsByNetworkIds(userWalletId, listOf(currency))
|
||||
|
|
@ -672,7 +673,7 @@ internal class DefaultCurrenciesRepository(
|
|||
|
||||
private fun createDefaultUserTokensResponse(userWallet: UserWallet) =
|
||||
userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = cardCurrenciesFactory.createDefaultCoinsForMultiCurrencyCard(userWallet.scanResponse),
|
||||
currencies = cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyCard(userWallet.scanResponse),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import com.tangem.blockchain.common.address.AddressType
|
|||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.data.common.cache.CacheRegistry
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory
|
||||
import com.tangem.data.tokens.utils.CardCryptoCurrenciesFactory
|
||||
import com.tangem.data.tokens.utils.NetworkStatusFactory
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.local.network.NetworksStatusesStore
|
||||
|
|
@ -15,7 +15,6 @@ import com.tangem.datasource.local.preferences.PreferencesKeys
|
|||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyAddress
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
|
|
@ -38,12 +37,11 @@ internal class DefaultNetworksRepository(
|
|||
private val userWalletsStore: UserWalletsStore,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val cacheRegistry: CacheRegistry,
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
) : NetworksRepository {
|
||||
|
||||
private val demoConfig = DemoConfig()
|
||||
private val cardCurrenciesFactory = CardCryptoCurrenciesFactory(demoConfig, excludedBlockchains)
|
||||
private val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory(excludedBlockchains)
|
||||
private val networkStatusFactory = NetworkStatusFactory()
|
||||
|
||||
|
|
@ -225,10 +223,14 @@ internal class DefaultNetworksRepository(
|
|||
responseCurrenciesFactory.createCurrencies(response, userWallet.scanResponse).asSequence()
|
||||
} else {
|
||||
if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
|
||||
cardCurrenciesFactory.createCurrenciesForSingleCurrencyCardWithToken(userWallet.scanResponse)
|
||||
cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(
|
||||
scanResponse = userWallet.scanResponse,
|
||||
)
|
||||
.asSequence()
|
||||
} else {
|
||||
val currency = cardCurrenciesFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse)
|
||||
val currency = cardCryptoCurrencyFactory.createPrimaryCurrencyForSingleCurrencyCard(
|
||||
scanResponse = userWallet.scanResponse,
|
||||
)
|
||||
|
||||
sequenceOf(currency)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,86 +0,0 @@
|
|||
package com.tangem.data.tokens.utils
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
||||
class CardCryptoCurrenciesFactory(
|
||||
private val demoConfig: DemoConfig,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
) {
|
||||
|
||||
private val cryptoCurrencyFactory = CryptoCurrencyFactory(excludedBlockchains)
|
||||
|
||||
fun createDefaultCoinsForMultiCurrencyCard(scanResponse: ScanResponse): List<CryptoCurrency.Coin> {
|
||||
val card = scanResponse.card
|
||||
|
||||
var blockchains = if (demoConfig.isDemoCardId(card.cardId)) {
|
||||
demoConfig.demoBlockchains
|
||||
} else {
|
||||
listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
|
||||
}
|
||||
|
||||
if (card.isTestCard) {
|
||||
blockchains = blockchains.mapNotNull { it.getTestnetVersion() }
|
||||
}
|
||||
|
||||
return blockchains.mapNotNull {
|
||||
cryptoCurrencyFactory.createCoin(
|
||||
blockchain = it,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun createPrimaryCurrencyForSingleCurrencyCard(scanResponse: ScanResponse): CryptoCurrency {
|
||||
val resolver = scanResponse.cardTypesResolver
|
||||
val blockchain = resolver.getBlockchain()
|
||||
|
||||
val coin = cryptoCurrencyFactory.createCoin(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
requireNotNull(coin) { "Coin for the single currency card cannot be null" }
|
||||
|
||||
val primaryToken = resolver.getPrimaryToken()?.let { token ->
|
||||
cryptoCurrencyFactory.createToken(
|
||||
sdkToken = token,
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
}
|
||||
|
||||
return primaryToken ?: coin
|
||||
}
|
||||
|
||||
fun createCurrenciesForSingleCurrencyCardWithToken(scanResponse: ScanResponse): List<CryptoCurrency> {
|
||||
val resolver = scanResponse.cardTypesResolver
|
||||
val blockchain = resolver.getBlockchain()
|
||||
|
||||
val coin = cryptoCurrencyFactory.createCoin(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
requireNotNull(coin) { "Coin for the single currency card cannot be null" }
|
||||
|
||||
val primaryToken = resolver.getPrimaryToken()?.let { token ->
|
||||
cryptoCurrencyFactory.createToken(
|
||||
sdkToken = token,
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
}
|
||||
|
||||
return listOfNotNull(coin, primaryToken)
|
||||
}
|
||||
}
|
||||
|
|
@ -190,12 +190,19 @@ internal class DefaultTransactionRepository(
|
|||
null
|
||||
}
|
||||
|
||||
val contractAddress = when (val identifier = nftAsset.identifier) {
|
||||
is NFTAsset.Identifier.EVM -> identifier.tokenAddress
|
||||
is NFTAsset.Identifier.Solana -> identifier.tokenAddress
|
||||
is NFTAsset.Identifier.TON -> identifier.tokenAddress
|
||||
NFTAsset.Identifier.Unknown -> ""
|
||||
}
|
||||
|
||||
return@withContext createTransaction(
|
||||
amount = Amount(
|
||||
value = nftAsset.amount?.toBigDecimal() ?: error("Invalid amount"),
|
||||
token = Token(
|
||||
symbol = blockchain.currency,
|
||||
contractAddress = "",
|
||||
contractAddress = contractAddress,
|
||||
decimals = nftAsset.decimals ?: error("Invalid decimals"),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import javax.inject.Singleton
|
|||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Suppress("TooManyFunctions")
|
||||
internal object WalletConnectDataModule {
|
||||
|
||||
@Provides
|
||||
|
|
@ -86,6 +87,7 @@ internal object WalletConnectDataModule {
|
|||
dispatchers: CoroutineDispatcherProvider,
|
||||
legacyStore: WalletConnectSessionsRepository,
|
||||
getWallets: GetWalletsUseCase,
|
||||
associateNetworks: AssociateNetworksDelegate,
|
||||
): DefaultWcSessionsManager {
|
||||
val scope = CoroutineScope(SupervisorJob() + dispatchers.io)
|
||||
return DefaultWcSessionsManager(
|
||||
|
|
@ -93,6 +95,7 @@ internal object WalletConnectDataModule {
|
|||
dispatchers = dispatchers,
|
||||
legacyStore = legacyStore,
|
||||
getWallets = getWallets,
|
||||
associateNetworks = associateNetworks,
|
||||
scope = scope,
|
||||
)
|
||||
}
|
||||
|
|
@ -121,12 +124,12 @@ internal object WalletConnectDataModule {
|
|||
@Singleton
|
||||
fun wcEthNetwork(
|
||||
@SdkMoshi moshi: Moshi,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
sessionsManager: WcSessionsManager,
|
||||
factories: WcEthNetwork.Factories,
|
||||
namespaceConverter: WcEthNetwork.NamespaceConverter,
|
||||
): WcEthNetwork = WcEthNetwork(
|
||||
moshi = moshi,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
namespaceConverter = namespaceConverter,
|
||||
sessionsManager = sessionsManager,
|
||||
factories = factories,
|
||||
)
|
||||
|
|
@ -135,7 +138,7 @@ internal object WalletConnectDataModule {
|
|||
@Singleton
|
||||
fun wcSolanaNetwork(
|
||||
@SdkMoshi moshi: Moshi,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
namespaceConverter: WcSolanaNetwork.NamespaceConverter,
|
||||
sessionsManager: WcSessionsManager,
|
||||
factories: WcSolanaNetwork.Factories,
|
||||
walletManager: UserWalletManager,
|
||||
|
|
@ -143,28 +146,28 @@ internal object WalletConnectDataModule {
|
|||
moshi = moshi,
|
||||
sessionsManager = sessionsManager,
|
||||
factories = factories,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
namespaceConverter = namespaceConverter,
|
||||
walletManager = walletManager,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun caipNamespaceDelegate(
|
||||
diHelperBox: DiHelperBox,
|
||||
namespaceConverters: Set<@JvmSuppressWildcards WcNamespaceConverter>,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
): CaipNamespaceDelegate = CaipNamespaceDelegate(
|
||||
namespaceConverters = diHelperBox.converters,
|
||||
namespaceConverters = namespaceConverters,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun associateNetworksDelegate(
|
||||
diHelperBox: DiHelperBox,
|
||||
namespaceConverters: Set<@JvmSuppressWildcards WcNamespaceConverter>,
|
||||
getWallets: GetWalletsUseCase,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
): AssociateNetworksDelegate = AssociateNetworksDelegate(
|
||||
namespaceConverters = diHelperBox.converters,
|
||||
namespaceConverters = namespaceConverters,
|
||||
getWallets = getWallets,
|
||||
currenciesRepository = currenciesRepository,
|
||||
)
|
||||
|
|
@ -176,10 +179,16 @@ internal object WalletConnectDataModule {
|
|||
ethNetwork,
|
||||
solanaNetwork,
|
||||
),
|
||||
converters = setOf(
|
||||
ethNetwork,
|
||||
solanaNetwork,
|
||||
),
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun namespaceConverters(
|
||||
ethNamespaceConverter: WcEthNetwork.NamespaceConverter,
|
||||
solanaNamespaceConverter: WcSolanaNetwork.NamespaceConverter,
|
||||
): Set<@JvmSuppressWildcards WcNamespaceConverter> = setOf(
|
||||
ethNamespaceConverter,
|
||||
solanaNamespaceConverter,
|
||||
)
|
||||
|
||||
@Provides
|
||||
|
|
@ -188,6 +197,20 @@ internal object WalletConnectDataModule {
|
|||
return DefaultWcRequestUseCaseFactory(diHelperBox.handlers)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun wcEthNetworkNamespaceConverter(excludedBlockchains: ExcludedBlockchains): WcEthNetwork.NamespaceConverter {
|
||||
return WcEthNetwork.NamespaceConverter(excludedBlockchains)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun wcSolanaNetworkNamespaceConverter(
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
): WcSolanaNetwork.NamespaceConverter {
|
||||
return WcSolanaNetwork.NamespaceConverter(excludedBlockchains)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesWcDisconnectUseCase(sessionsManager: WcSessionsManager): WcDisconnectUseCase {
|
||||
|
|
@ -195,7 +218,6 @@ internal object WalletConnectDataModule {
|
|||
}
|
||||
|
||||
internal class DiHelperBox(
|
||||
val converters: Set<WcNamespaceConverter>,
|
||||
val handlers: Set<WcRequestToUseCaseConverter>,
|
||||
)
|
||||
}
|
||||
|
|
@ -22,12 +22,10 @@ import jakarta.inject.Inject
|
|||
|
||||
internal class WcEthNetwork(
|
||||
private val moshi: Moshi,
|
||||
private val excludedBlockchains: ExcludedBlockchains,
|
||||
private val sessionsManager: WcSessionsManager,
|
||||
private val factories: Factories,
|
||||
) : WcRequestToUseCaseConverter, WcNamespaceConverter {
|
||||
|
||||
override val namespaceKey: NamespaceKey = NamespaceKey("eip155")
|
||||
private val namespaceConverter: NamespaceConverter,
|
||||
) : WcRequestToUseCaseConverter {
|
||||
|
||||
override fun toWcMethodName(request: WcSdkSessionRequest): WcEthMethodName? {
|
||||
val methodKey = request.request.method
|
||||
|
|
@ -39,7 +37,7 @@ internal class WcEthNetwork(
|
|||
val name = toWcMethodName(request) ?: return null
|
||||
val method: WcEthMethod = name.toMethod(request) ?: return null
|
||||
val session = sessionsManager.findSessionByTopic(request.topic) ?: return null
|
||||
val network = toNetwork(request.chainId.orEmpty(), session.wallet) ?: return null
|
||||
val network = namespaceConverter.toNetwork(request.chainId.orEmpty(), session.wallet) ?: return null
|
||||
val accountAddress = when (method) {
|
||||
is WcEthMethod.MessageSign -> method.account
|
||||
is WcEthMethod.SendTransaction -> method.transaction.from
|
||||
|
|
@ -100,24 +98,31 @@ internal class WcEthNetwork(
|
|||
return WcEthMethod.SignTypedData(params = params, account = account, dataForSign = data)
|
||||
}
|
||||
|
||||
override fun toNetwork(chainId: String, wallet: UserWallet): Network? {
|
||||
return toNetwork(chainId, wallet, excludedBlockchains)
|
||||
}
|
||||
internal class NamespaceConverter constructor(
|
||||
private val excludedBlockchains: ExcludedBlockchains,
|
||||
) : WcNamespaceConverter {
|
||||
|
||||
override fun toBlockchain(chainId: CAIP2): Blockchain? {
|
||||
if (chainId.namespace != namespaceKey.key) return null
|
||||
val ethChainId = chainId.reference.toIntOrNull() ?: return null
|
||||
return Blockchain.fromChainId(ethChainId)
|
||||
}
|
||||
override val namespaceKey: NamespaceKey = NamespaceKey("eip155")
|
||||
|
||||
override fun toCAIP2(network: Network): CAIP2? {
|
||||
val blockchain = Blockchain.fromId(network.id.value)
|
||||
if (!blockchain.isEvm()) return null
|
||||
val chainId = blockchain.getChainId() ?: return null
|
||||
return CAIP2(
|
||||
namespace = namespaceKey.key,
|
||||
reference = chainId.toString(),
|
||||
)
|
||||
override fun toNetwork(chainId: String, wallet: UserWallet): Network? {
|
||||
return toNetwork(chainId, wallet, excludedBlockchains)
|
||||
}
|
||||
|
||||
override fun toBlockchain(chainId: CAIP2): Blockchain? {
|
||||
if (chainId.namespace != namespaceKey.key) return null
|
||||
val ethChainId = chainId.reference.toIntOrNull() ?: return null
|
||||
return Blockchain.fromChainId(ethChainId)
|
||||
}
|
||||
|
||||
override fun toCAIP2(network: Network): CAIP2? {
|
||||
val blockchain = Blockchain.fromId(network.id.value)
|
||||
if (!blockchain.isEvm()) return null
|
||||
val chainId = blockchain.getChainId() ?: return null
|
||||
return CAIP2(
|
||||
namespace = namespaceKey.key,
|
||||
reference = chainId.toString(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal class Factories @Inject constructor(
|
||||
|
|
|
|||
|
|
@ -26,11 +26,9 @@ internal class WcSolanaNetwork(
|
|||
private val moshi: Moshi,
|
||||
private val sessionsManager: WcSessionsManager,
|
||||
private val factories: Factories,
|
||||
private val excludedBlockchains: ExcludedBlockchains,
|
||||
private val namespaceConverter: NamespaceConverter,
|
||||
private val walletManager: UserWalletManager,
|
||||
) : WcNamespaceConverter, WcRequestToUseCaseConverter {
|
||||
|
||||
override val namespaceKey: NamespaceKey = NamespaceKey("solana")
|
||||
) : WcRequestToUseCaseConverter {
|
||||
|
||||
override fun toWcMethodName(request: WcSdkSessionRequest): WcSolanaMethodName? {
|
||||
val methodKey = request.request.method
|
||||
|
|
@ -42,7 +40,7 @@ internal class WcSolanaNetwork(
|
|||
val name = toWcMethodName(request) ?: return null
|
||||
val method: WcSolanaMethod = name.toMethod(request) ?: return null
|
||||
val session = sessionsManager.findSessionByTopic(request.topic) ?: return null
|
||||
val network = toNetwork(request.chainId.orEmpty(), session.wallet) ?: return null
|
||||
val network = namespaceConverter.toNetwork(request.chainId.orEmpty(), session.wallet) ?: return null
|
||||
val accountAddress = getAccountAddress(network)
|
||||
val context = WcMethodUseCaseContext(
|
||||
session = session,
|
||||
|
|
@ -66,31 +64,38 @@ internal class WcSolanaNetwork(
|
|||
}
|
||||
}
|
||||
|
||||
override fun toBlockchain(chainId: CAIP2): Blockchain? {
|
||||
if (chainId.namespace != namespaceKey.key) return null
|
||||
return when (chainId.reference) {
|
||||
MAINNET_CHAIN_ID -> Blockchain.Solana
|
||||
TESTNET_CHAIN_ID -> Blockchain.SolanaTestnet
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
internal class NamespaceConverter @Inject constructor(
|
||||
private val excludedBlockchains: ExcludedBlockchains,
|
||||
) : WcNamespaceConverter {
|
||||
|
||||
override fun toNetwork(chainId: String, wallet: UserWallet): Network? {
|
||||
return toNetwork(chainId, wallet, excludedBlockchains)
|
||||
}
|
||||
override val namespaceKey: NamespaceKey = NamespaceKey("solana")
|
||||
|
||||
override fun toCAIP2(network: Network): CAIP2? {
|
||||
val blockchain = Blockchain.fromId(network.id.value)
|
||||
val chainId = when (blockchain) {
|
||||
Blockchain.Solana -> MAINNET_CHAIN_ID
|
||||
Blockchain.SolanaTestnet -> TESTNET_CHAIN_ID
|
||||
else -> null
|
||||
override fun toBlockchain(chainId: CAIP2): Blockchain? {
|
||||
if (chainId.namespace != namespaceKey.key) return null
|
||||
return when (chainId.reference) {
|
||||
MAINNET_CHAIN_ID -> Blockchain.Solana
|
||||
TESTNET_CHAIN_ID -> Blockchain.SolanaTestnet
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override fun toNetwork(chainId: String, wallet: UserWallet): Network? {
|
||||
return toNetwork(chainId, wallet, excludedBlockchains)
|
||||
}
|
||||
|
||||
override fun toCAIP2(network: Network): CAIP2? {
|
||||
val blockchain = Blockchain.fromId(network.id.value)
|
||||
val chainId = when (blockchain) {
|
||||
Blockchain.Solana -> MAINNET_CHAIN_ID
|
||||
Blockchain.SolanaTestnet -> TESTNET_CHAIN_ID
|
||||
else -> null
|
||||
}
|
||||
chainId ?: return null
|
||||
return CAIP2(
|
||||
namespace = namespaceKey.key,
|
||||
reference = chainId,
|
||||
)
|
||||
}
|
||||
chainId ?: return null
|
||||
return CAIP2(
|
||||
namespace = namespaceKey.key,
|
||||
reference = chainId,
|
||||
)
|
||||
}
|
||||
|
||||
private fun WcSolanaMethodName.toMethod(request: WcSdkSessionRequest): WcSolanaMethod? {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.data.walletconnect.pair
|
||||
|
||||
import com.reown.walletkit.client.Wallet
|
||||
import com.reown.walletkit.client.Wallet.Model.Namespace
|
||||
import com.tangem.data.walletconnect.utils.WcNamespaceConverter
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
|
|
@ -16,6 +17,16 @@ internal class AssociateNetworksDelegate constructor(
|
|||
private val currenciesRepository: CurrenciesRepository,
|
||||
) {
|
||||
|
||||
suspend fun associate(wallet: UserWallet, namespaces: Map<String, Namespace.Session>): Set<Network> {
|
||||
val walletNetworks = getWalletNetworks(wallet)
|
||||
val namespacesSet = namespaces.values.flatMap { proposal -> proposal.chains ?: listOf() }.toSet()
|
||||
return namespacesSet.mapNotNullTo(mutableSetOf()) { chainId ->
|
||||
val wcNetwork = namespaceConverters
|
||||
.firstNotNullOfOrNull { it.toNetwork(chainId, wallet) } ?: return@mapNotNullTo null
|
||||
walletNetworks.find { network -> wcNetwork.id == network.id }
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(WcPairError.UnsupportedNetworks::class)
|
||||
suspend fun associate(sessionProposal: Wallet.Model.SessionProposal): Map<UserWallet, ProposalNetwork> {
|
||||
val userWallets = getWallets.invokeSync().filter { it.isMultiCurrency }
|
||||
|
|
@ -31,9 +42,7 @@ internal class AssociateNetworksDelegate constructor(
|
|||
requiredNamespaces: Set<String>,
|
||||
optionalNamespaces: Set<String>,
|
||||
): ProposalNetwork {
|
||||
val walletNetworks = currenciesRepository.getMultiCurrencyWalletCurrenciesSync(wallet.walletId)
|
||||
.filterIsInstance<CryptoCurrency.Coin>()
|
||||
.map { it.network }
|
||||
val walletNetworks = getWalletNetworks(wallet)
|
||||
|
||||
val unknownRequired = mutableSetOf<String>()
|
||||
val missingRequired = mutableSetOf<Network>()
|
||||
|
|
@ -74,6 +83,11 @@ internal class AssociateNetworksDelegate constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun getWalletNetworks(wallet: UserWallet): List<Network> =
|
||||
currenciesRepository.getMultiCurrencyWalletCurrenciesSync(wallet.walletId)
|
||||
.filterIsInstance<CryptoCurrency.Coin>()
|
||||
.map { it.network }
|
||||
|
||||
private fun Map<String, Wallet.Model.Namespace.Proposal>.setOfChainId(): Set<String> =
|
||||
this.values.flatMap { proposal -> proposal.chains ?: listOf() }.toSet()
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import com.tangem.domain.walletconnect.model.sdkcopy.WcAppMetaData
|
|||
import com.tangem.domain.walletconnect.repository.WcSessionsManager
|
||||
import com.tangem.domain.walletconnect.usecase.pair.WcPairState
|
||||
import com.tangem.domain.walletconnect.usecase.pair.WcPairUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
|
@ -78,9 +77,11 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
|
|||
sessionForApprove = sessionForApprove,
|
||||
sdkSessionProposal = sdkSessionProposal,
|
||||
).map { settledSession ->
|
||||
val newSession = settledSession.session.toDomain(
|
||||
val newSession = WcSession(
|
||||
wallet = sessionForApprove.wallet,
|
||||
sdkModel = WcSdkSessionConverter.convert(settledSession.session),
|
||||
securityStatus = proposalState.dAppSession.securityStatus,
|
||||
networks = sessionForApprove.network.toSet(),
|
||||
)
|
||||
sessionsManager.saveSession(newSession)
|
||||
newSession
|
||||
|
|
@ -141,14 +142,6 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
|
|||
}
|
||||
},)
|
||||
|
||||
private fun Wallet.Model.Session.toDomain(wallet: UserWallet, securityStatus: CheckDAppResult): WcSession {
|
||||
return WcSession(
|
||||
wallet = wallet,
|
||||
sdkModel = WcSdkSessionConverter.convert(this),
|
||||
securityStatus = securityStatus,
|
||||
)
|
||||
}
|
||||
|
||||
private sealed interface TerminalAction {
|
||||
data class Approve(val sessionForApprove: WcSessionApprove) : TerminalAction
|
||||
data object Reject : TerminalAction
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import arrow.core.right
|
|||
import com.domain.blockaid.models.dapp.CheckDAppResult
|
||||
import com.reown.walletkit.client.Wallet
|
||||
import com.reown.walletkit.client.WalletKit
|
||||
import com.tangem.data.walletconnect.pair.AssociateNetworksDelegate
|
||||
import com.tangem.data.walletconnect.utils.WcSdkObserver
|
||||
import com.tangem.data.walletconnect.utils.WcSdkSessionConverter
|
||||
import com.tangem.datasource.local.walletconnect.WalletConnectStore
|
||||
|
|
@ -29,6 +30,7 @@ internal class DefaultWcSessionsManager(
|
|||
private val legacyStore: WalletConnectSessionsRepository,
|
||||
private val getWallets: GetWalletsUseCase,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val associateNetworks: AssociateNetworksDelegate,
|
||||
private val scope: CoroutineScope,
|
||||
) : WcSessionsManager, WcSdkObserver {
|
||||
|
||||
|
|
@ -76,10 +78,12 @@ internal class DefaultWcSessionsManager(
|
|||
?: return@withContext null
|
||||
val sdkSession = WalletKit.getActiveSessionByTopic(topic) ?: return@withContext null
|
||||
val wallet = storedSession.wallet
|
||||
val networks = associateNetworks.associate(wallet, sdkSession.namespaces)
|
||||
WcSession(
|
||||
wallet = wallet,
|
||||
sdkModel = WcSdkSessionConverter.convert(sdkSession),
|
||||
securityStatus = storedSession.securityStatus,
|
||||
networks = networks,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -114,7 +118,7 @@ internal class DefaultWcSessionsManager(
|
|||
return mustSaveInNewStore.isNotEmpty()
|
||||
}
|
||||
|
||||
private fun associate(
|
||||
private suspend fun associate(
|
||||
inSdk: List<Wallet.Model.Session>,
|
||||
inStore: Set<WcSessionDTO>,
|
||||
wallets: List<UserWallet>,
|
||||
|
|
@ -122,7 +126,13 @@ internal class DefaultWcSessionsManager(
|
|||
val wcSessions = inStore.mapNotNull { session ->
|
||||
val wallet = wallets.find { it.walletId == session.walletId } ?: return@mapNotNull null
|
||||
val sdkSession = inSdk.find { it.topic == session.topic } ?: return@mapNotNull null
|
||||
WcSession(wallet = wallet, sdkModel = WcSdkSessionConverter.convert(sdkSession), session.securityStatus)
|
||||
val networks = associateNetworks.associate(wallet, sdkSession.namespaces)
|
||||
WcSession(
|
||||
wallet = wallet,
|
||||
sdkModel = WcSdkSessionConverter.convert(sdkSession),
|
||||
securityStatus = session.securityStatus,
|
||||
networks = networks,
|
||||
)
|
||||
}
|
||||
return wcSessions
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import com.tangem.domain.walletconnect.model.WcSession
|
|||
import com.tangem.domain.walletconnect.model.WcSessionApprove
|
||||
import com.tangem.domain.walletconnect.repository.WcSessionsManager
|
||||
import com.tangem.domain.walletconnect.usecase.pair.WcPairState
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerifyOrder
|
||||
import io.mockk.mockk
|
||||
|
|
@ -91,6 +92,7 @@ internal class DefaultWcPairUseCaseTest {
|
|||
wallet = sessionForApprove.wallet,
|
||||
sdkModel = WcSdkSessionConverter.convert(this),
|
||||
securityStatus = CheckDAppResult.SAFE,
|
||||
networks = setOf(),
|
||||
)
|
||||
|
||||
private fun useCaseFactory() = DefaultWcPairUseCase(
|
||||
|
|
@ -99,7 +101,7 @@ internal class DefaultWcPairUseCaseTest {
|
|||
caipNamespaceDelegate = caipNamespaceDelegate,
|
||||
sdkDelegate = sdkDelegate,
|
||||
blockAidVerifier = blockAidVerifier,
|
||||
pairRequest = WcPairRequest(url, source),
|
||||
pairRequest = WcPairRequest(userWalletId = UserWalletId(""), uri = url, source = source),
|
||||
)
|
||||
|
||||
@Before
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1,5 +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
|
||||
|
|
@ -17,7 +20,9 @@ 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
|
||||
import com.tangem.utils.WEEK_MILLIS
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -28,12 +33,14 @@ import kotlinx.coroutines.withContext
|
|||
|
||||
typealias SeedPhraseNotificationsStatuses = Map<UserWalletId, SeedPhraseNotificationsStatus>
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
internal class DefaultWalletsRepository(
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val seedPhraseNotificationVisibilityStore: RuntimeStateStore<SeedPhraseNotificationsStatuses>,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val authProvider: AuthProvider,
|
||||
) : WalletsRepository {
|
||||
|
||||
override suspend fun shouldSaveUserWalletsSync(): Boolean {
|
||||
|
|
@ -254,6 +261,53 @@ internal class DefaultWalletsRepository(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun setWalletName(walletId: String, walletName: String) = withContext(dispatchers.io) {
|
||||
tangemTechApi.updateWallet(
|
||||
walletId = walletId,
|
||||
body = WalletBody(name = walletName),
|
||||
).getOrThrow()
|
||||
}
|
||||
|
||||
override suspend fun getWalletInfo(walletId: String): UserWalletRemoteInfo = withContext(dispatchers.io) {
|
||||
UserWalletRemoteInfoConverter.convert(
|
||||
value = tangemTechApi.getWalletById(walletId).getOrThrow(),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getWalletsInfo(applicationId: String, updateCache: Boolean): List<UserWalletRemoteInfo> =
|
||||
withContext(dispatchers.io) {
|
||||
tangemTechApi.getWallets(applicationId)
|
||||
.getOrThrow()
|
||||
.map { walletInfo ->
|
||||
val userWallet = UserWalletRemoteInfoConverter.convert(
|
||||
value = walletInfo,
|
||||
)
|
||||
if (updateCache) {
|
||||
setNotificationsEnabledLocally(
|
||||
userWalletId = userWallet.walletId,
|
||||
isEnabled = userWallet.isNotificationsEnabled,
|
||||
)
|
||||
}
|
||||
userWallet
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun associateWallets(applicationId: String, wallets: List<UserWallet>) =
|
||||
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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.data.wallets.converters
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.WalletResponse
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.models.UserWalletRemoteInfo
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal object UserWalletRemoteInfoConverter : Converter<WalletResponse, UserWalletRemoteInfo> {
|
||||
override fun convert(value: WalletResponse): UserWalletRemoteInfo {
|
||||
return UserWalletRemoteInfo(
|
||||
walletId = UserWalletId(value.id),
|
||||
name = value.name.orEmpty(),
|
||||
isNotificationsEnabled = value.notifyStatus,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String, String>): WalletIdBody {
|
||||
return WalletIdBody(
|
||||
walletId = userWallet.walletId.stringValue,
|
||||
name = userWallet.name,
|
||||
cards = publicKeys.map {
|
||||
CardInfoBody(
|
||||
cardId = it.key,
|
||||
cardPublicKey = it.value,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -173,4 +176,131 @@ class DefaultWalletsRepositoryTest {
|
|||
}
|
||||
coVerify(exactly = 1) { preferencesDataStore.updateData(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN API returns wallets WHEN getWalletsInfo THEN should return converted wallets and update cache if requested`() = runTest {
|
||||
// GIVEN
|
||||
val applicationId = "test_app_id"
|
||||
val wallet1Id = "1234567890abcdef"
|
||||
val wallet2Id = "fedcba0987654321"
|
||||
val walletResponses = listOf(
|
||||
WalletResponse(
|
||||
id = wallet1Id,
|
||||
notifyStatus = true,
|
||||
),
|
||||
WalletResponse(
|
||||
id = wallet2Id,
|
||||
notifyStatus = false,
|
||||
),
|
||||
)
|
||||
coEvery { tangemTechApi.getWallets(applicationId) } returns ApiResponse.Success(walletResponses)
|
||||
coEvery { preferencesDataStore.updateData(any()) } returns mockk<Preferences>()
|
||||
|
||||
// WHEN
|
||||
val result = repository.getWalletsInfo(applicationId, updateCache = true)
|
||||
|
||||
// THEN
|
||||
assertThat(result).hasSize(2)
|
||||
assertThat(result[0].walletId.stringValue).isEqualTo(wallet1Id)
|
||||
assertThat(result[0].isNotificationsEnabled).isTrue()
|
||||
assertThat(result[1].walletId.stringValue).isEqualTo(wallet2Id)
|
||||
assertThat(result[1].isNotificationsEnabled).isFalse()
|
||||
|
||||
coVerify(exactly = 1) { tangemTechApi.getWallets(applicationId) }
|
||||
coVerify(exactly = 2) { preferencesDataStore.updateData(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN API returns wallets WHEN getWalletsInfo with updateCache false THEN should return converted wallets without updating cache`() = runTest {
|
||||
// GIVEN
|
||||
val applicationId = "test_app_id"
|
||||
val wallet1Id = "1234567890abcdef"
|
||||
val walletResponses = listOf(
|
||||
WalletResponse(
|
||||
id = wallet1Id,
|
||||
notifyStatus = true,
|
||||
),
|
||||
)
|
||||
coEvery { tangemTechApi.getWallets(applicationId) } returns ApiResponse.Success(walletResponses)
|
||||
|
||||
// WHEN
|
||||
val result = repository.getWalletsInfo(applicationId, updateCache = false)
|
||||
|
||||
// THEN
|
||||
assertThat(result).hasSize(1)
|
||||
assertThat(result[0].walletId.stringValue).isEqualTo(wallet1Id)
|
||||
assertThat(result[0].isNotificationsEnabled).isTrue()
|
||||
|
||||
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<UserWallet> {
|
||||
every { cardsInWallet } returns setOf(card1PublicKey)
|
||||
every { walletId } returns UserWalletId(wallet1Id)
|
||||
every { name } returns "Wallet 1"
|
||||
},
|
||||
mockk<UserWallet> {
|
||||
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<AuthProvider> {
|
||||
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"
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String, String>()
|
||||
|
||||
// WHEN
|
||||
val result = WalletIdBodyConverter.convert(userWallet, publicKeys)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(
|
||||
WalletIdBody(
|
||||
walletId = walletId.stringValue,
|
||||
name = walletName,
|
||||
cards = emptyList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue