Updated on 2026-08-14

This commit is contained in:
Tangem 2025-05-29 17:08:18 +03:00
commit 1bac8466b1
990 changed files with 22183 additions and 7489 deletions

View file

@ -2,7 +2,15 @@ package com.domain.blockaid.models.transaction.simultation
import java.math.BigDecimal
data class AmountInfo(
val amount: BigDecimal,
val token: TokenInfo,
)
sealed class AmountInfo {
data class FungibleTokens(
val amount: BigDecimal,
val token: TokenInfo,
) : AmountInfo()
data class NonFungibleTokens(
val name: String,
val logoUrl: String?,
) : AmountInfo()
}

View file

@ -4,4 +4,5 @@ data class TokenInfo(
val chainId: Int?,
val logoUrl: String?,
val symbol: String,
val decimals: Int,
)

View file

@ -2,7 +2,7 @@ package com.tangem.domain.card
import arrow.core.Either
import com.tangem.domain.card.repository.DerivationsRepository
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
class DerivePublicKeysUseCase(

View file

@ -2,6 +2,7 @@ package com.tangem.domain.card
import arrow.core.Either
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.extensions.calculateRipemd160
import com.tangem.common.extensions.calculateSha256
@ -9,7 +10,7 @@ import com.tangem.crypto.NetworkType
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.domain.card.repository.DerivationsRepository
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.operations.derivation.ExtendedPublicKeysMap
@ -26,7 +27,7 @@ class GetExtendedPublicKeyForCurrencyUseCase(
val userWallet = walletManagersFacade.getOrCreateWalletManager(userWalletId, network)
?: error("Wallet not found")
val blockchain = Blockchain.fromId(network.id.value)
val blockchain = network.toBlockchain()
val isSecp256k1Blockchain = Blockchain.secp256k1Blockchains(network.isTestnet).contains(blockchain)
val hdKey = if (isSecp256k1Blockchain) {
@ -76,7 +77,7 @@ class GetExtendedPublicKeyForCurrencyUseCase(
val userWallet = walletManagersFacade.getOrCreateWalletManager(userWalletId, network)
?: error("Wallet not found")
val blockchain = Blockchain.fromId(network.id.value)
val blockchain = network.toBlockchain()
val isSecp256k1Blockchain = Blockchain.secp256k1Blockchains(network.isTestnet).contains(blockchain)
val isHdKey = userWallet.wallet.publicKey.derivationType?.hdKey

View file

@ -1,7 +1,7 @@
package com.tangem.domain.card
import com.tangem.domain.card.repository.DerivationsRepository
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
import com.tangem.domain.wallets.models.UserWalletId
/**

View file

@ -1,15 +1,15 @@
package com.tangem.domain.card
import arrow.core.Either
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.domain.common.util.hasDerivation
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.model.Network
class NetworkHasDerivationUseCase {
operator fun invoke(scanResponse: ScanResponse, network: Network): Either<Throwable, Boolean> {
val blockchain = Blockchain.fromId(network.id.value)
val blockchain = network.toBlockchain()
val derivationPath = network.derivationPath.value
return Either.catch { derivationPath != null && scanResponse.hasDerivation(blockchain, derivationPath) }
}

View file

@ -3,8 +3,8 @@ package com.tangem.domain.card.repository
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.card.BackendId
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.operations.derivation.ExtendedPublicKeysMap
@ -13,7 +13,7 @@ interface DerivationsRepository {
@Throws
suspend fun derivePublicKeys(userWalletId: UserWalletId, currencies: List<CryptoCurrency>)
suspend fun derivePublicKeysByNetworkIds(userWalletId: UserWalletId, networkIds: List<Network.ID>)
suspend fun derivePublicKeysByNetworkIds(userWalletId: UserWalletId, networkIds: List<Network.RawID>)
@Throws
suspend fun derivePublicKeysByNetworks(userWalletId: UserWalletId, networks: List<Network>)

View file

@ -0,0 +1,5 @@
package com.tangem.domain.feedback.repository
interface FeedbackFeatureToggles {
val isUsedeskEnabled: Boolean
}

View file

@ -9,6 +9,10 @@ android {
namespace = "com.tangem.domain.features"
}
tasks.withType<Test>().configureEach {
useJUnitPlatform()
}
dependencies {
implementation(projects.core.datasource)
implementation(projects.core.utils)
@ -43,8 +47,11 @@ dependencies {
ksp(deps.moshi.kotlin.codegen)
/** Testing libraries */
testImplementation(deps.test.junit)
testImplementation(deps.test.junit5)
testRuntimeOnly(deps.test.junit5.engine)
testImplementation(deps.test.mockk)
testImplementation(deps.test.truth)
testImplementation(projects.common.test)
androidTestImplementation(deps.test.junit.android)
androidTestImplementation(deps.test.espresso)
}

View file

@ -2,8 +2,8 @@ package com.tangem.domain.exchange
import arrow.core.Either
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
import com.tangem.domain.wallets.models.UserWalletId

View file

@ -2,7 +2,7 @@ package com.tangem.domain.utils
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Token
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import java.math.BigDecimal
import com.tangem.blockchain.common.Amount as SdkAmount

View file

@ -20,22 +20,23 @@ import com.tangem.blockchain.nft.models.NFTAsset
import com.tangem.blockchain.nft.models.NFTCollection
import com.tangem.blockchain.transactionhistory.models.TransactionHistoryRequest
import com.tangem.blockchainsdk.BlockchainSDKFactory
import com.tangem.blockchainsdk.models.UpdateWalletManagerResult
import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.datasource.local.walletmanager.WalletManagersStore
import com.tangem.domain.common.util.hasDerivation
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.network.TxInfo
import com.tangem.domain.transaction.models.AssetRequirementsCondition
import com.tangem.domain.txhistory.models.PaginationWrapper
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.domain.txhistory.models.TxHistoryState
import com.tangem.domain.walletmanager.model.RentData
import com.tangem.domain.walletmanager.model.SmartContractMethod
import com.tangem.domain.walletmanager.model.TokenInfo
import com.tangem.domain.walletmanager.model.UpdateWalletManagerResult
import com.tangem.domain.walletmanager.utils.*
import com.tangem.domain.walletmanager.utils.WalletManagerFactory
import com.tangem.domain.wallets.models.UserWallet
@ -78,7 +79,7 @@ class DefaultWalletManagersFacade(
extraTokens: Set<CryptoCurrency.Token>,
): UpdateWalletManagerResult {
val userWallet = getUserWallet(userWalletId)
val blockchain = Blockchain.fromId(network.id.value)
val blockchain = network.toBlockchain()
val derivationPath = network.derivationPath.value
return getAndUpdateWalletManager(userWallet, blockchain, derivationPath, extraTokens)
@ -88,7 +89,7 @@ class DefaultWalletManagersFacade(
if (networks.isEmpty()) return
val blockchainsToDerivationPaths = networks.map {
Blockchain.fromId(it.id.value) to it.derivationPath.value
it.toBlockchain() to it.derivationPath.value
}
withContext(dispatchers.io) {
@ -141,7 +142,7 @@ class DefaultWalletManagersFacade(
withContext(dispatchers.io) {
val walletManager = walletManagersStore.getSyncOrNull(
userWalletId = userWalletId,
blockchain = Blockchain.fromId(network.id.value),
blockchain = network.toBlockchain(),
derivationPath = network.derivationPath.value,
) ?: return@withContext
@ -158,7 +159,7 @@ class DefaultWalletManagersFacade(
network: Network,
): UpdateWalletManagerResult {
val userWallet = getUserWallet(userWalletId)
val blockchain = Blockchain.fromId(network.id.value)
val blockchain = network.toBlockchain()
val derivationPath = network.derivationPath.value
if (derivationPath != null && !userWallet.scanResponse.hasDerivation(blockchain, derivationPath)) {
@ -181,7 +182,7 @@ class DefaultWalletManagersFacade(
addressType: AddressType,
contractAddress: String?,
): String {
val blockchain = Blockchain.fromId(network.id.value)
val blockchain = network.toBlockchain()
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
network = network,
@ -234,7 +235,7 @@ class DefaultWalletManagersFacade(
currency: CryptoCurrency,
page: Page,
pageSize: Int,
): PaginationWrapper<TxHistoryItem> {
): PaginationWrapper<TxInfo> {
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
network = currency.network,
@ -385,7 +386,7 @@ class DefaultWalletManagersFacade(
@Deprecated("Will be removed in future")
override suspend fun getOrCreateWalletManager(userWalletId: UserWalletId, network: Network): WalletManager? {
val blockchain = Blockchain.fromId(network.id.value)
val blockchain = network.toBlockchain()
return getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
@ -477,7 +478,7 @@ class DefaultWalletManagersFacade(
userWalletId: UserWalletId,
network: Network,
): Result<TransactionFee>? = withContext(dispatchers.io) {
val blockchain = Blockchain.fromId(network.id.value)
val blockchain = network.toBlockchain()
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
@ -534,10 +535,7 @@ class DefaultWalletManagersFacade(
return walletManager?.createTransaction(amount, fee, destination)
}
override suspend fun getRecentTransactions(
userWalletId: UserWalletId,
currency: CryptoCurrency,
): List<TxHistoryItem> {
override suspend fun getRecentTransactions(userWalletId: UserWalletId, currency: CryptoCurrency): List<TxInfo> {
val walletManager = getOrCreateWalletManager(userWalletId = userWalletId, network = currency.network)
if (walletManager == null) {
@ -569,7 +567,7 @@ class DefaultWalletManagersFacade(
decimals: Int,
id: String?,
): BigDecimal {
val blockchain = Blockchain.fromId(network.id.value)
val blockchain = network.toBlockchain()
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
@ -640,7 +638,7 @@ class DefaultWalletManagersFacade(
}
override suspend fun checkUtxoConsolidationAvailability(userWalletId: UserWalletId, network: Network): Boolean {
val blockchain = Blockchain.fromId(network.id.value)
val blockchain = network.toBlockchain()
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
@ -651,7 +649,7 @@ class DefaultWalletManagersFacade(
}
override suspend fun getNFTCollections(userWalletId: UserWalletId, network: Network): List<NFTCollection> {
val blockchain = Blockchain.fromId(network.id.value)
val blockchain = network.toBlockchain()
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
@ -666,7 +664,7 @@ class DefaultWalletManagersFacade(
network: Network,
collectionIdentifier: NFTCollection.Identifier,
): List<NFTAsset> {
val blockchain = Blockchain.fromId(network.id.value)
val blockchain = network.toBlockchain()
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
@ -682,7 +680,7 @@ class DefaultWalletManagersFacade(
collectionIdentifier: NFTCollection.Identifier,
assetIdentifier: NFTAsset.Identifier,
): NFTAsset? {
val blockchain = Blockchain.fromId(network.id.value)
val blockchain = network.toBlockchain()
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
@ -697,7 +695,7 @@ class DefaultWalletManagersFacade(
collectionIdentifier: NFTCollection.Identifier,
assetIdentifier: NFTAsset.Identifier,
): NFTAsset.SalePrice? {
val blockchain = Blockchain.fromId(network.id.value)
val blockchain = network.toBlockchain()
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
@ -707,7 +705,7 @@ class DefaultWalletManagersFacade(
}
override suspend fun getNFTExploreUrl(network: Network, assetIdentifier: NFTAsset.Identifier): String? {
val blockchain = Blockchain.fromId(network.id.value)
val blockchain = network.toBlockchain()
return blockchain.getNFTExploreUrl(assetIdentifier)
}

View file

@ -12,15 +12,15 @@ import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.blockchain.nft.models.NFTAsset
import com.tangem.blockchain.nft.models.NFTCollection
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.blockchainsdk.models.UpdateWalletManagerResult
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.network.TxInfo
import com.tangem.domain.transaction.models.AssetRequirementsCondition
import com.tangem.domain.txhistory.models.PaginationWrapper
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.domain.txhistory.models.TxHistoryState
import com.tangem.domain.walletmanager.model.RentData
import com.tangem.domain.walletmanager.model.TokenInfo
import com.tangem.domain.walletmanager.model.UpdateWalletManagerResult
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
import java.math.BigDecimal
@ -106,7 +106,7 @@ interface WalletManagersFacade {
currency: CryptoCurrency,
page: Page,
pageSize: Int,
): PaginationWrapper<TxHistoryItem>
): PaginationWrapper<TxInfo>
@Deprecated("Will be removed in future")
suspend fun getOrCreateWalletManager(
@ -216,7 +216,7 @@ interface WalletManagersFacade {
): TransactionData?
/** Get recent transactions of [userWalletId] for [currency] */
suspend fun getRecentTransactions(userWalletId: UserWalletId, currency: CryptoCurrency): List<TxHistoryItem>
suspend fun getRecentTransactions(userWalletId: UserWalletId, currency: CryptoCurrency): List<TxInfo>
@Suppress("LongParameterList")
suspend fun tokenBalance(

View file

@ -1,11 +0,0 @@
package com.tangem.domain.walletmanager.model
data class Address(
val value: String,
val type: Type,
) {
enum class Type {
Primary, Secondary,
}
}

View file

@ -1,17 +0,0 @@
package com.tangem.domain.walletmanager.model
import com.tangem.domain.tokens.model.CryptoCurrency
import java.math.BigDecimal
sealed class CryptoCurrencyAmount {
abstract val value: BigDecimal
data class Coin(override val value: BigDecimal) : CryptoCurrencyAmount()
data class Token(
val tokenId: CryptoCurrency.RawID?,
val tokenContractAddress: String,
override val value: BigDecimal,
) : CryptoCurrencyAmount()
}

View file

@ -1,17 +0,0 @@
package com.tangem.domain.walletmanager.model
import com.tangem.domain.txhistory.models.TxHistoryItem
// TODO: [REDACTED_JIRA] move to txhistory module
sealed class CryptoCurrencyTransaction {
abstract val txHistoryItem: TxHistoryItem
data class Coin(override val txHistoryItem: TxHistoryItem) : CryptoCurrencyTransaction()
data class Token(
val tokenId: String?,
val tokenContractAddress: String,
override val txHistoryItem: TxHistoryItem,
) : CryptoCurrencyTransaction()
}

View file

@ -1,6 +1,6 @@
package com.tangem.domain.walletmanager.model
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
data class TokenInfo(
val network: Network,

View file

@ -1,27 +0,0 @@
package com.tangem.domain.walletmanager.model
import java.math.BigDecimal
sealed class UpdateWalletManagerResult {
data object MissedDerivation : UpdateWalletManagerResult()
data class Unreachable(
val selectedAddress: String? = null,
val addresses: Set<Address>? = null,
) : UpdateWalletManagerResult()
data class Verified(
val selectedAddress: String,
val addresses: Set<Address>,
val currenciesAmounts: Set<CryptoCurrencyAmount>,
val currentTransactions: Set<CryptoCurrencyTransaction>,
) : UpdateWalletManagerResult()
data class NoAccount(
val selectedAddress: String,
val addresses: Set<Address>,
val amountToCreateAccount: BigDecimal,
val errorMessage: String,
) : UpdateWalletManagerResult()
}

View file

@ -2,7 +2,7 @@ package com.tangem.domain.walletmanager.utils
import com.tangem.blockchain.common.CryptoCurrencyType
import com.tangem.blockchain.common.Token
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.utils.converter.Converter
internal class CryptoCurrencyTypeConverter : Converter<CryptoCurrency, CryptoCurrencyType> {

View file

@ -1,7 +1,7 @@
package com.tangem.domain.walletmanager.utils
import com.tangem.blockchain.common.address.AddressType
import com.tangem.domain.walletmanager.model.Address
import com.tangem.blockchainsdk.models.UpdateWalletManagerResult.Address
import com.tangem.utils.converter.Converter
import com.tangem.blockchain.common.address.Address as SdkAddress

View file

@ -1,6 +1,6 @@
package com.tangem.domain.walletmanager.utils
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.utils.converter.Converter
import com.tangem.blockchain.common.Token as SdkToken

View file

@ -1,18 +1,18 @@
package com.tangem.domain.walletmanager.utils
import com.tangem.blockchain.transactionhistory.models.TransactionHistoryItem
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.domain.models.network.TxInfo
import com.tangem.domain.walletmanager.model.SmartContractMethod
import com.tangem.utils.converter.Converter
import com.tangem.blockchain.transactionhistory.models.TransactionHistoryItem as SdkTransactionHistoryItem
internal class SdkTransactionHistoryItemConverter(
smartContractMethods: Map<String, SmartContractMethod>,
) : Converter<SdkTransactionHistoryItem, TxHistoryItem> {
) : Converter<SdkTransactionHistoryItem, TxInfo> {
private val typeConverter by lazy { SdkTransactionTypeConverter(smartContractMethods) }
override fun convert(value: SdkTransactionHistoryItem): TxHistoryItem = TxHistoryItem(
override fun convert(value: SdkTransactionHistoryItem): TxInfo = TxInfo(
txHash = value.txHash,
timestampInMillis = value.timestamp,
isOutgoing = value.isOutgoing,
@ -20,35 +20,35 @@ internal class SdkTransactionHistoryItemConverter(
sourceType = value.sourceType.toDomain(),
interactionAddressType = value.extractInteractionAddressType(),
status = when (value.status) {
SdkTransactionHistoryItem.TransactionStatus.Confirmed -> TxHistoryItem.TransactionStatus.Confirmed
SdkTransactionHistoryItem.TransactionStatus.Failed -> TxHistoryItem.TransactionStatus.Failed
SdkTransactionHistoryItem.TransactionStatus.Unconfirmed -> TxHistoryItem.TransactionStatus.Unconfirmed
SdkTransactionHistoryItem.TransactionStatus.Confirmed -> TxInfo.TransactionStatus.Confirmed
SdkTransactionHistoryItem.TransactionStatus.Failed -> TxInfo.TransactionStatus.Failed
SdkTransactionHistoryItem.TransactionStatus.Unconfirmed -> TxInfo.TransactionStatus.Unconfirmed
},
type = typeConverter.convert(value.type),
amount = requireNotNull(value.amount.value) { "Transaction amount value must not be null" },
)
private fun SdkTransactionHistoryItem.SourceType.toDomain(): TxHistoryItem.SourceType = when (this) {
is TransactionHistoryItem.SourceType.Single -> TxHistoryItem.SourceType.Single(address)
is TransactionHistoryItem.SourceType.Multiple -> TxHistoryItem.SourceType.Multiple(addresses)
private fun SdkTransactionHistoryItem.SourceType.toDomain(): TxInfo.SourceType = when (this) {
is TransactionHistoryItem.SourceType.Single -> TxInfo.SourceType.Single(address)
is TransactionHistoryItem.SourceType.Multiple -> TxInfo.SourceType.Multiple(addresses)
}
private fun SdkTransactionHistoryItem.DestinationType.toDomain(): TxHistoryItem.DestinationType = when (this) {
is SdkTransactionHistoryItem.DestinationType.Single -> TxHistoryItem.DestinationType.Single(
private fun SdkTransactionHistoryItem.DestinationType.toDomain(): TxInfo.DestinationType = when (this) {
is SdkTransactionHistoryItem.DestinationType.Single -> TxInfo.DestinationType.Single(
addressType.toDomain(),
)
is SdkTransactionHistoryItem.DestinationType.Multiple -> TxHistoryItem.DestinationType.Multiple(
is SdkTransactionHistoryItem.DestinationType.Multiple -> TxInfo.DestinationType.Multiple(
addressTypes.map { it.toDomain() },
)
}
private fun SdkTransactionHistoryItem.AddressType.toDomain(): TxHistoryItem.AddressType = when (this) {
is SdkTransactionHistoryItem.AddressType.Contract -> TxHistoryItem.AddressType.Contract(address)
is SdkTransactionHistoryItem.AddressType.User -> TxHistoryItem.AddressType.User(address)
is SdkTransactionHistoryItem.AddressType.Validator -> TxHistoryItem.AddressType.Validator(address)
private fun SdkTransactionHistoryItem.AddressType.toDomain(): TxInfo.AddressType = when (this) {
is SdkTransactionHistoryItem.AddressType.Contract -> TxInfo.AddressType.Contract(address)
is SdkTransactionHistoryItem.AddressType.User -> TxInfo.AddressType.User(address)
is SdkTransactionHistoryItem.AddressType.Validator -> TxInfo.AddressType.Validator(address)
}
private fun SdkTransactionHistoryItem.extractInteractionAddressType(): TxHistoryItem.InteractionAddressType? {
private fun SdkTransactionHistoryItem.extractInteractionAddressType(): TxInfo.InteractionAddressType? {
return when (val transactionType = type) {
SdkTransactionHistoryItem.TransactionType.Transfer -> if (isOutgoing) {
mapToInteractionAddressType(destinationType = destinationType)
@ -61,7 +61,7 @@ internal class SdkTransactionHistoryItemConverter(
-> mapToInteractionAddressType(destinationType = destinationType)
is SdkTransactionHistoryItem.TransactionType.TronStakingTransactionType.VoteWitnessContract -> {
TxHistoryItem.InteractionAddressType.Validator(address = transactionType.validatorAddress)
TxInfo.InteractionAddressType.Validator(address = transactionType.validatorAddress)
}
else -> null
}
@ -69,19 +69,19 @@ internal class SdkTransactionHistoryItemConverter(
private fun mapToInteractionAddressType(
destinationType: SdkTransactionHistoryItem.DestinationType,
): TxHistoryItem.InteractionAddressType {
): TxInfo.InteractionAddressType {
return when (destinationType) {
is TransactionHistoryItem.DestinationType.Multiple -> TxHistoryItem.InteractionAddressType.Multiple(
is TransactionHistoryItem.DestinationType.Multiple -> TxInfo.InteractionAddressType.Multiple(
destinationType.addressTypes.map { it.address },
)
is TransactionHistoryItem.DestinationType.Single -> when (destinationType.addressType) {
is TransactionHistoryItem.AddressType.Contract -> TxHistoryItem.InteractionAddressType.Contract(
is TransactionHistoryItem.AddressType.Contract -> TxInfo.InteractionAddressType.Contract(
destinationType.addressType.address,
)
is TransactionHistoryItem.AddressType.User -> TxHistoryItem.InteractionAddressType.User(
is TransactionHistoryItem.AddressType.User -> TxInfo.InteractionAddressType.User(
destinationType.addressType.address,
)
is TransactionHistoryItem.AddressType.Validator -> TxHistoryItem.InteractionAddressType.Validator(
is TransactionHistoryItem.AddressType.Validator -> TxInfo.InteractionAddressType.Validator(
destinationType.addressType.address,
)
}
@ -90,13 +90,13 @@ internal class SdkTransactionHistoryItemConverter(
private fun mapToInteractionAddressType(
sourceType: SdkTransactionHistoryItem.SourceType,
): TxHistoryItem.InteractionAddressType {
): TxInfo.InteractionAddressType {
return when (sourceType) {
is TransactionHistoryItem.SourceType.Multiple -> TxHistoryItem.InteractionAddressType.Multiple(
is TransactionHistoryItem.SourceType.Multiple -> TxInfo.InteractionAddressType.Multiple(
sourceType.addresses,
)
is TransactionHistoryItem.SourceType.Single -> {
TxHistoryItem.InteractionAddressType.User(sourceType.address)
TxInfo.InteractionAddressType.User(sourceType.address)
}
}
}

View file

@ -1,15 +1,15 @@
package com.tangem.domain.walletmanager.utils
import com.tangem.blockchain.transactionhistory.models.TransactionHistoryItem.TransactionType
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.domain.models.network.TxInfo
import com.tangem.domain.walletmanager.model.SmartContractMethod
import com.tangem.utils.converter.Converter
internal class SdkTransactionTypeConverter(
private val smartContractMethods: Map<String, SmartContractMethod>,
) : Converter<TransactionType, TxHistoryItem.TransactionType> {
) : Converter<TransactionType, TxInfo.TransactionType> {
override fun convert(value: TransactionType): TxHistoryItem.TransactionType {
override fun convert(value: TransactionType): TxInfo.TransactionType {
return when (value) {
is TransactionType.ContractMethod -> {
getTransactionType(methodName = smartContractMethods[value.id]?.name)
@ -18,49 +18,49 @@ internal class SdkTransactionTypeConverter(
getTransactionType(methodName = value.name)
}
is TransactionType.Transfer -> {
TxHistoryItem.TransactionType.Transfer
TxInfo.TransactionType.Transfer
}
is TransactionType.TronStakingTransactionType.FreezeBalanceV2Contract -> {
TxHistoryItem.TransactionType.Staking.Stake
TxInfo.TransactionType.Staking.Stake
}
is TransactionType.TronStakingTransactionType.UnfreezeBalanceV2Contract -> {
TxHistoryItem.TransactionType.Staking.Unstake
TxInfo.TransactionType.Staking.Unstake
}
is TransactionType.TronStakingTransactionType.VoteWitnessContract -> {
TxHistoryItem.TransactionType.Staking.Vote(value.validatorAddress)
TxInfo.TransactionType.Staking.Vote(value.validatorAddress)
}
is TransactionType.TronStakingTransactionType.WithdrawBalanceContract -> {
TxHistoryItem.TransactionType.Staking.ClaimRewards
TxInfo.TransactionType.Staking.ClaimRewards
}
is TransactionType.TronStakingTransactionType.WithdrawExpireUnfreezeContract -> {
TxHistoryItem.TransactionType.Staking.Withdraw
TxInfo.TransactionType.Staking.Withdraw
}
}
}
private fun getTransactionType(methodName: String?): TxHistoryItem.TransactionType {
private fun getTransactionType(methodName: String?): TxInfo.TransactionType {
return when (methodName) {
"transfer" -> TxHistoryItem.TransactionType.Transfer
"approve" -> TxHistoryItem.TransactionType.Approve
"swap" -> TxHistoryItem.TransactionType.Swap
"transfer" -> TxInfo.TransactionType.Transfer
"approve" -> TxInfo.TransactionType.Approve
"swap" -> TxInfo.TransactionType.Swap
"buyVoucher",
"buyVoucherPOL",
"delegate",
-> TxHistoryItem.TransactionType.Staking.Stake
-> TxInfo.TransactionType.Staking.Stake
"sellVoucher_new",
"sellVoucher_newPOL",
"undelegate",
-> TxHistoryItem.TransactionType.Staking.Unstake
-> TxInfo.TransactionType.Staking.Unstake
"unstakeClaimTokens_new",
"unstakeClaimTokens_newPOL",
"claim",
-> TxHistoryItem.TransactionType.Staking.Withdraw
-> TxInfo.TransactionType.Staking.Withdraw
"withdrawRewards",
"withdrawRewardsPOL",
-> TxHistoryItem.TransactionType.Staking.ClaimRewards
"redelegate" -> TxHistoryItem.TransactionType.Staking.Restake
null -> TxHistoryItem.TransactionType.UnknownOperation
else -> TxHistoryItem.TransactionType.Operation(name = methodName.replaceFirstChar { it.titlecase() })
-> TxInfo.TransactionType.Staking.ClaimRewards
"redelegate" -> TxInfo.TransactionType.Staking.Restake
null -> TxInfo.TransactionType.UnknownOperation
else -> TxInfo.TransactionType.Operation(name = methodName.replaceFirstChar { it.titlecase() })
}
}
}

View file

@ -1,14 +1,14 @@
package com.tangem.domain.walletmanager.utils
import com.tangem.blockchain.common.*
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.domain.walletmanager.model.Address
import com.tangem.blockchainsdk.models.UpdateWalletManagerResult.Address
import com.tangem.domain.models.network.TxInfo
import com.tangem.utils.converter.Converter
import timber.log.Timber
import java.math.BigDecimal
/**
* Convert [TransactionData] to [TxHistoryItem]
* Convert [TransactionData] to [TxInfo]
*
* @property walletAddresses wallet addresses
*
@ -17,30 +17,30 @@ import java.math.BigDecimal
internal class TransactionDataToTxHistoryItemConverter(
private val walletAddresses: Set<Address>,
private val feePaidCurrency: FeePaidCurrency,
) : Converter<TransactionData.Uncompiled, TxHistoryItem?> {
) : Converter<TransactionData.Uncompiled, TxInfo?> {
override fun convert(value: TransactionData.Uncompiled): TxHistoryItem? {
override fun convert(value: TransactionData.Uncompiled): TxInfo? {
val hash = value.hash ?: return null
val millis = value.date?.timeInMillis ?: return null
val amount = getTransactionAmountValue(value.amount, value.fee?.amount) ?: return null
val isOutgoing = value.sourceAddress in walletAddresses.map(Address::value)
return TxHistoryItem(
return TxInfo(
txHash = hash,
timestampInMillis = millis,
isOutgoing = isOutgoing,
destinationType = TxHistoryItem.DestinationType.Single(
addressType = TxHistoryItem.AddressType.User(value.destinationAddress),
destinationType = TxInfo.DestinationType.Single(
addressType = TxInfo.AddressType.User(value.destinationAddress),
),
sourceType = TxHistoryItem.SourceType.Single(value.sourceAddress),
interactionAddressType = TxHistoryItem.InteractionAddressType.User(
sourceType = TxInfo.SourceType.Single(value.sourceAddress),
interactionAddressType = TxInfo.InteractionAddressType.User(
address = if (isOutgoing) value.destinationAddress else value.sourceAddress,
),
status = when (value.status) {
TransactionStatus.Confirmed -> TxHistoryItem.TransactionStatus.Confirmed
TransactionStatus.Unconfirmed -> TxHistoryItem.TransactionStatus.Unconfirmed
TransactionStatus.Confirmed -> TxInfo.TransactionStatus.Confirmed
TransactionStatus.Unconfirmed -> TxInfo.TransactionStatus.Unconfirmed
},
type = TxHistoryItem.TransactionType.Transfer,
type = TxInfo.TransactionType.Transfer,
amount = amount,
)
}

View file

@ -1,25 +1,25 @@
package com.tangem.domain.walletmanager.utils
import com.tangem.blockchain.common.*
import com.tangem.blockchainsdk.models.UpdateWalletManagerResult
import com.tangem.blockchainsdk.models.UpdateWalletManagerResult.*
import com.tangem.blockchainsdk.utils.amountToCreateAccount
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.walletmanager.model.Address
import com.tangem.domain.walletmanager.model.CryptoCurrencyAmount
import com.tangem.domain.walletmanager.model.CryptoCurrencyTransaction
import com.tangem.domain.walletmanager.model.UpdateWalletManagerResult
import com.tangem.domain.models.currency.CryptoCurrency
import timber.log.Timber
import java.math.BigDecimal
import com.tangem.blockchain.common.address.Address as SdkAddress
/** Factory for creating [UpdateWalletManagerResult] */
internal class UpdateWalletManagerResultFactory {
fun getResult(walletManager: WalletManager): UpdateWalletManagerResult.Verified {
/** Get [Verified] result for [walletManager] */
fun getResult(walletManager: WalletManager): Verified {
val wallet = walletManager.wallet
val addresses = getAvailableAddresses(wallet.addresses)
val feePaidCurrency = wallet.blockchain.feePaidCurrency()
val txHistoryItemConverter = TransactionDataToTxHistoryItemConverter(addresses, feePaidCurrency)
return UpdateWalletManagerResult.Verified(
return Verified(
selectedAddress = wallet.address,
addresses = addresses,
currenciesAmounts = getTokensAmounts(wallet.amounts.values.toSet()),
@ -27,13 +27,19 @@ internal class UpdateWalletManagerResultFactory {
)
}
fun getDemoResult(walletManager: WalletManager, demoAmount: Amount): UpdateWalletManagerResult.Verified {
/**
* Get demo [Verified] result
*
* @param walletManager wallet manager
* @param demoAmount amount that will be used for demo result
*/
fun getDemoResult(walletManager: WalletManager, demoAmount: Amount): Verified {
val wallet = walletManager.wallet
val addresses = getAvailableAddresses(wallet.addresses)
val feePaidCurrency = wallet.blockchain.feePaidCurrency()
val txHistoryItemConverter = TransactionDataToTxHistoryItemConverter(addresses, feePaidCurrency)
return UpdateWalletManagerResult.Verified(
return Verified(
selectedAddress = wallet.address,
addresses = addresses,
currenciesAmounts = getDemoTokensAmounts(demoAmount, walletManager.cardTokens),
@ -41,6 +47,14 @@ internal class UpdateWalletManagerResultFactory {
)
}
/**
* Get [NoAccount] result.
* If unable to get required amount for creating account, [Unreachable] result will be returned.
*
* @param walletManager wallet manager
* @param customMessage custom error message
* @param amountToCreateAccount amount to create account
*/
fun getNoAccountResult(
walletManager: WalletManager,
customMessage: String,
@ -53,12 +67,12 @@ internal class UpdateWalletManagerResultFactory {
return if (amount == null) {
Timber.w("Unable to get required amount to create account for: $blockchain")
UpdateWalletManagerResult.Unreachable(
Unreachable(
selectedAddress = wallet.address,
addresses = getAvailableAddresses(wallet.addresses),
)
} else {
UpdateWalletManagerResult.NoAccount(
NoAccount(
selectedAddress = wallet.address,
addresses = getAvailableAddresses(wallet.addresses),
amountToCreateAccount = amount,
@ -67,91 +81,46 @@ internal class UpdateWalletManagerResultFactory {
}
}
fun getUnreachableResult(walletManager: WalletManager): UpdateWalletManagerResult {
/** Get [Unreachable] result for [walletManager] */
fun getUnreachableResult(walletManager: WalletManager): Unreachable {
val wallet = walletManager.wallet
return UpdateWalletManagerResult.Unreachable(
return Unreachable(
selectedAddress = wallet.address,
addresses = getAvailableAddresses(wallet.addresses),
)
}
private fun getAvailableAddresses(addresses: Set<SdkAddress>): Set<Address> {
return SdkAddressToAddressConverter.convertList(addresses).toSet()
}
private fun getTokensAmounts(amounts: Set<Amount>): Set<CryptoCurrencyAmount> {
return amounts.mapNotNullTo(hashSetOf(), ::createCurrencyAmount)
}
private fun getDemoTokensAmounts(demoAmount: Amount, tokens: Set<Token>): Set<CryptoCurrencyAmount> {
val amountValue = demoAmount.value ?: BigDecimal.ZERO
val demoAmounts = hashSetOf<CryptoCurrencyAmount>(CryptoCurrencyAmount.Coin(amountValue))
return tokens.mapTo(demoAmounts) { token ->
CryptoCurrencyAmount.Token(
tokenId = token.id?.let { CryptoCurrency.RawID(it) },
tokenContractAddress = token.contractAddress,
value = amountValue,
)
}
}
private fun getCurrentTransactions(
txHistoryItemConverter: TransactionDataToTxHistoryItemConverter,
recentTransactions: Set<TransactionData.Uncompiled>,
): Set<CryptoCurrencyTransaction> {
val unconfirmedTransactions = recentTransactions.filter {
it.status == TransactionStatus.Unconfirmed
}
return unconfirmedTransactions.mapNotNullTo(hashSetOf()) {
createCurrencyTransaction(
txHistoryItemConverter = txHistoryItemConverter,
data = it,
)
}
}
private fun createCurrencyAmount(amount: Amount): CryptoCurrencyAmount? {
return when (val type = amount.type) {
is AmountType.Token -> CryptoCurrencyAmount.Token(
tokenId = type.token.id?.let { CryptoCurrency.RawID(it) },
tokenContractAddress = type.token.contractAddress,
value = getCurrencyAmountValue(amount) ?: return null,
)
is AmountType.Coin -> CryptoCurrencyAmount.Coin(
value = getCurrencyAmountValue(amount) ?: return null,
)
is AmountType.FeeResource,
AmountType.Reserve,
-> null
}
}
private fun createCurrencyTransaction(
txHistoryItemConverter: TransactionDataToTxHistoryItemConverter,
data: TransactionData.Uncompiled,
): CryptoCurrencyTransaction? {
return when (val type = data.amount.type) {
is AmountType.Coin -> {
val txHistoryItem = txHistoryItemConverter.convert(data) ?: return null
CryptoCurrencyTransaction.Coin(txHistoryItem)
}
is AmountType.Token -> {
val txHistoryItem = txHistoryItemConverter.convert(data) ?: return null
CryptoCurrencyTransaction.Token(
tokenId = type.token.id,
tokenContractAddress = type.token.contractAddress,
txHistoryItem = txHistoryItem,
val value = getCurrencyAmountValue(amount) ?: return null
CryptoCurrencyAmount.Token(
currencyRawId = type.token.id?.let(CryptoCurrency::RawID),
contractAddress = type.token.contractAddress,
value = value,
)
}
is AmountType.Coin -> {
val value = getCurrencyAmountValue(amount) ?: return null
CryptoCurrencyAmount.Coin(value = value)
}
is AmountType.FeeResource,
AmountType.Reserve,
is AmountType.Reserve,
-> null
}
}
private fun getAvailableAddresses(addresses: Set<SdkAddress>): Set<Address> {
return SdkAddressToAddressConverter.convertList(addresses).toSet()
}
private fun getCurrencyAmountValue(amount: Amount): BigDecimal? {
val value = amount.value
@ -161,4 +130,56 @@ internal class UpdateWalletManagerResultFactory {
return value
}
private fun getDemoTokensAmounts(demoAmount: Amount, tokens: Set<Token>): Set<CryptoCurrencyAmount> {
val amountValue = demoAmount.value ?: BigDecimal.ZERO
val demoAmounts = hashSetOf<CryptoCurrencyAmount>(CryptoCurrencyAmount.Coin(amountValue))
return tokens.mapTo(demoAmounts) { token ->
CryptoCurrencyAmount.Token(
currencyRawId = token.id?.let(CryptoCurrency::RawID),
contractAddress = token.contractAddress,
value = amountValue,
)
}
}
private fun getCurrentTransactions(
txHistoryItemConverter: TransactionDataToTxHistoryItemConverter,
recentTransactions: Set<TransactionData.Uncompiled>,
): Set<CryptoCurrencyTransaction> {
val unconfirmedTransactions = recentTransactions.filter { it.status == TransactionStatus.Unconfirmed }
return unconfirmedTransactions.mapNotNullTo(hashSetOf()) {
createCurrencyTransaction(
txHistoryItemConverter = txHistoryItemConverter,
data = it,
)
}
}
private fun createCurrencyTransaction(
txHistoryItemConverter: TransactionDataToTxHistoryItemConverter,
data: TransactionData.Uncompiled,
): CryptoCurrencyTransaction? {
return when (val type = data.amount.type) {
is AmountType.Coin -> {
val txHistoryItem = txHistoryItemConverter.convert(data) ?: return null
CryptoCurrencyTransaction.Coin(txInfo = txHistoryItem)
}
is AmountType.Token -> {
val txHistoryItem = txHistoryItemConverter.convert(data) ?: return null
CryptoCurrencyTransaction.Token(
tokenId = type.token.id,
contractAddress = type.token.contractAddress,
txInfo = txHistoryItem,
)
}
is AmountType.FeeResource,
is AmountType.Reserve,
-> null
}
}
}

View file

@ -0,0 +1,786 @@
package com.tangem.domain.walletmanager.utils
import com.google.common.truth.Truth
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.address.Address
import com.tangem.blockchain.common.address.AddressType
import com.tangem.blockchainsdk.models.UpdateWalletManagerResult
import com.tangem.blockchainsdk.models.UpdateWalletManagerResult.*
import com.tangem.blockchainsdk.models.UpdateWalletManagerResult.Address.Type
import com.tangem.common.test.domain.walletmanager.MockUpdateWalletManagerResultFactory
import com.tangem.common.test.utils.ProvideTestModels
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.TxInfo
import io.mockk.clearMocks
import io.mockk.every
import io.mockk.mockk
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.params.ParameterizedTest
import java.math.BigDecimal
import java.util.Calendar
/**
[REDACTED_AUTHOR]
*/
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class UpdateWalletManagerResultFactoryTest {
private val factory = UpdateWalletManagerResultFactory()
private val mockFactory = MockUpdateWalletManagerResultFactory()
private val walletManager = mockk<WalletManager>()
@BeforeEach
fun resetMocks() {
clearMocks(walletManager)
}
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
inner class GetResult {
@ParameterizedTest
@ProvideTestModels
fun getResult(model: GetResultTestModel) {
// Arrange
every { walletManager.wallet } returns model.wallet
// Act
val actual = factory.getResult(walletManager = walletManager)
// Assert
Truth.assertThat(actual).isEqualTo(model.expected)
}
private fun provideTestModels(): List<GetResultTestModel> = listOf(
// region Wallet without amount and transactions
GetResultTestModel(
wallet = createWallet(
coinValue = null,
addresses = setOf(Address(value = "0x1", type = AddressType.Default)),
transactions = emptyList(),
),
expected = Verified(
selectedAddress = "0x1",
addresses = setOf(Address(value = "0x1", type = Type.Primary)),
currenciesAmounts = emptySet(),
currentTransactions = emptySet(),
),
),
// endregion
// region Wallet with coin amount and without transactions
GetResultTestModel(
wallet = createWallet(
coinValue = BigDecimal.ONE,
addresses = setOf(
Address(value = "0x1", type = AddressType.Default),
),
transactions = emptyList(),
),
expected = Verified(
selectedAddress = "0x1",
addresses = setOf(Address(value = "0x1", type = Type.Primary)),
currenciesAmounts = setOf(CryptoCurrencyAmount.Coin(value = BigDecimal.ONE)),
currentTransactions = emptySet(),
),
),
// endregion
// region Wallet with coin amount and coin transaction
GetResultTestModel(
wallet = createWallet(
coinValue = BigDecimal.ONE,
addresses = setOf(
Address(value = "0x1", type = AddressType.Default),
),
transactions = listOf(
createRecentTransaction(
amount = Amount(
blockchain = Blockchain.Ethereum,
value = BigDecimal.ONE,
),
status = TransactionStatus.Unconfirmed,
),
),
),
expected = Verified(
selectedAddress = "0x1",
addresses = setOf(Address(value = "0x1", type = Type.Primary)),
currenciesAmounts = setOf(CryptoCurrencyAmount.Coin(value = BigDecimal.ONE)),
currentTransactions = setOf(
CryptoCurrencyTransaction.Coin(
txInfo = createTxInfo(
status = TxInfo.TransactionStatus.Unconfirmed,
amount = BigDecimal.ONE,
),
),
),
),
),
// endregion
// region Wallet with amounts and transactions (coin and token)
GetResultTestModel(
wallet = createWallet(
coinValue = BigDecimal.ONE,
tokensAmount = mapOf(usdtToken to BigDecimal.ZERO),
addresses = setOf(
Address(value = "0x1", type = AddressType.Default),
Address(value = "0x11", type = AddressType.Legacy),
),
transactions = listOf(
createRecentTransaction(
amount = Amount(
blockchain = Blockchain.Ethereum,
value = BigDecimal.ONE,
),
status = TransactionStatus.Confirmed,
),
createRecentTransaction(
amount = Amount(
value = BigDecimal.TEN,
blockchain = Blockchain.Ethereum,
type = AmountType.Token(token = usdtToken),
currencySymbol = "USDT",
),
status = TransactionStatus.Unconfirmed,
),
),
),
expected = Verified(
selectedAddress = "0x1",
addresses = setOf(
Address(value = "0x1", type = Type.Primary),
Address(value = "0x11", type = Type.Secondary),
),
currenciesAmounts = setOf(
CryptoCurrencyAmount.Coin(value = BigDecimal.ONE),
CryptoCurrencyAmount.Token(
value = BigDecimal.ZERO,
currencyRawId = usdtToken.id?.let(CryptoCurrency::RawID),
contractAddress = usdtToken.contractAddress,
),
),
currentTransactions = setOf(
CryptoCurrencyTransaction.Token(
txInfo = createTxInfo(
status = TxInfo.TransactionStatus.Unconfirmed,
amount = BigDecimal.TEN,
),
tokenId = "0x3",
contractAddress = "0x4",
),
),
),
),
// endregion
)
}
data class GetResultTestModel(val wallet: Wallet, val expected: Verified)
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
inner class GetDemoResult {
@ParameterizedTest
@ProvideTestModels
fun getDemoResult(model: GetDemoResultTestModel) {
// Arrange
every { walletManager.wallet } returns model.wallet
every { walletManager.cardTokens } returns model.cardTokens.toMutableSet()
// Act
val actual = factory.getDemoResult(walletManager = walletManager, demoAmount = model.demoAmount)
// Assert
Truth.assertThat(actual).isEqualTo(model.expected)
}
private fun provideTestModels(): List<GetDemoResultTestModel> {
return listOf(
// region Wallet without amount and transactions
GetDemoResultTestModel(
wallet = createWallet(
coinValue = null,
addresses = setOf(Address(value = "0x1", type = AddressType.Default)),
transactions = emptyList(),
),
demoAmount = Amount(value = null, blockchain = Blockchain.Ethereum),
cardTokens = emptySet(),
expected = Verified(
selectedAddress = "0x1",
addresses = setOf(
Address(value = "0x1", type = Type.Primary),
),
currenciesAmounts = setOf(
CryptoCurrencyAmount.Coin(value = BigDecimal.ZERO), // default for demo
),
currentTransactions = emptySet(),
),
),
GetDemoResultTestModel(
wallet = createWallet(
coinValue = null,
addresses = setOf(Address(value = "0x1", type = AddressType.Default)),
transactions = emptyList(),
),
demoAmount = Amount(value = BigDecimal.ONE, blockchain = Blockchain.Ethereum),
cardTokens = emptySet(),
expected = Verified(
selectedAddress = "0x1",
addresses = setOf(
Address(value = "0x1", type = Type.Primary),
),
currenciesAmounts = setOf(
CryptoCurrencyAmount.Coin(value = BigDecimal.ONE), // used demo amount
),
currentTransactions = emptySet(),
),
),
// endregion
// region Wallet with coin amount and without transactions
GetDemoResultTestModel(
wallet = createWallet(
coinValue = BigDecimal.ONE,
addresses = setOf(
Address(value = "0x1", type = AddressType.Default),
),
transactions = emptyList(),
),
demoAmount = Amount(value = null, blockchain = Blockchain.Ethereum),
cardTokens = emptySet(),
expected = Verified(
selectedAddress = "0x1",
addresses = setOf(
Address(
value = "0x1",
type = Type.Primary,
),
),
currenciesAmounts = setOf(CryptoCurrencyAmount.Coin(value = BigDecimal.ZERO)),
currentTransactions = emptySet(),
),
),
GetDemoResultTestModel(
wallet = createWallet(
coinValue = BigDecimal.ONE,
addresses = setOf(
Address(value = "0x1", type = AddressType.Default),
),
transactions = emptyList(),
),
demoAmount = Amount(value = BigDecimal.TEN, blockchain = Blockchain.Ethereum),
cardTokens = emptySet(),
expected = Verified(
selectedAddress = "0x1",
addresses = setOf(
Address(
value = "0x1",
type = Type.Primary,
),
),
currenciesAmounts = setOf(CryptoCurrencyAmount.Coin(value = BigDecimal.TEN)),
currentTransactions = emptySet(),
),
),
// endregion
// region Wallet with coin amount and coin transaction
GetDemoResultTestModel(
wallet = createWallet(
coinValue = BigDecimal.ONE,
addresses = setOf(
Address(value = "0x1", type = AddressType.Default),
),
transactions = listOf(
createRecentTransaction(
amount = Amount(
blockchain = Blockchain.Ethereum,
value = BigDecimal.ONE,
),
status = TransactionStatus.Unconfirmed,
),
),
),
demoAmount = Amount(value = BigDecimal.TEN, blockchain = Blockchain.Ethereum),
cardTokens = emptySet(),
expected = Verified(
selectedAddress = "0x1",
addresses = setOf(
Address(value = "0x1", type = Type.Primary),
),
currenciesAmounts = setOf(CryptoCurrencyAmount.Coin(value = BigDecimal.TEN)),
currentTransactions = setOf(
CryptoCurrencyTransaction.Coin(
txInfo = createTxInfo(
status = TxInfo.TransactionStatus.Unconfirmed,
amount = BigDecimal.ONE,
),
),
),
),
),
// endregion
// region Wallet with amounts and transactions (coin and token)
GetDemoResultTestModel(
wallet = createWallet(
coinValue = BigDecimal.TEN,
tokensAmount = mapOf(usdtToken to BigDecimal.TEN),
addresses = setOf(
Address(value = "0x1", type = AddressType.Default),
Address(value = "0x11", type = AddressType.Legacy),
),
transactions = listOf(
createRecentTransaction(
amount = Amount(
blockchain = Blockchain.Ethereum,
value = BigDecimal.ONE,
),
status = TransactionStatus.Confirmed,
),
createRecentTransaction(
amount = Amount(
value = BigDecimal.TEN,
blockchain = Blockchain.Ethereum,
type = AmountType.Token(token = usdtToken),
currencySymbol = "USDT",
),
status = TransactionStatus.Unconfirmed,
),
),
),
demoAmount = Amount(value = null, blockchain = Blockchain.Ethereum),
cardTokens = setOf(usdtToken),
expected = Verified(
selectedAddress = "0x1",
addresses = setOf(
Address(value = "0x1", type = Type.Primary),
Address(value = "0x11", type = Type.Secondary),
),
currenciesAmounts = setOf(
CryptoCurrencyAmount.Coin(value = BigDecimal.ZERO),
CryptoCurrencyAmount.Token(
value = BigDecimal.ZERO,
currencyRawId = usdtToken.id?.let(CryptoCurrency::RawID),
contractAddress = usdtToken.contractAddress,
),
),
currentTransactions = setOf(
CryptoCurrencyTransaction.Token(
txInfo = createTxInfo(
status = TxInfo.TransactionStatus.Unconfirmed,
amount = BigDecimal.TEN,
),
tokenId = "0x3",
contractAddress = "0x4",
),
),
),
),
GetDemoResultTestModel(
wallet = createWallet(
coinValue = BigDecimal.ONE,
tokensAmount = mapOf(usdtToken to BigDecimal.ZERO),
addresses = setOf(
Address(value = "0x1", type = AddressType.Default),
Address(value = "0x11", type = AddressType.Legacy),
),
transactions = listOf(
createRecentTransaction(
amount = Amount(
blockchain = Blockchain.Ethereum,
value = BigDecimal.ONE,
),
status = TransactionStatus.Unconfirmed,
),
createRecentTransaction(
amount = Amount(
value = BigDecimal.TEN,
blockchain = Blockchain.Ethereum,
type = AmountType.Token(token = usdtToken),
currencySymbol = "USDT",
),
status = TransactionStatus.Unconfirmed,
),
),
),
demoAmount = Amount(value = BigDecimal.TEN, blockchain = Blockchain.Ethereum),
cardTokens = setOf(usdtToken),
expected = Verified(
selectedAddress = "0x1",
addresses = setOf(
Address(value = "0x1", type = Type.Primary),
Address(value = "0x11", type = Type.Secondary),
),
currenciesAmounts = setOf(
CryptoCurrencyAmount.Coin(value = BigDecimal.TEN),
CryptoCurrencyAmount.Token(
value = BigDecimal.TEN,
currencyRawId = usdtToken.id?.let(CryptoCurrency::RawID),
contractAddress = usdtToken.contractAddress,
),
),
currentTransactions = setOf(
CryptoCurrencyTransaction.Token(
txInfo = createTxInfo(
status = TxInfo.TransactionStatus.Unconfirmed,
amount = BigDecimal.TEN,
),
tokenId = "0x3",
contractAddress = "0x4",
),
CryptoCurrencyTransaction.Coin(
txInfo = createTxInfo(
status = TxInfo.TransactionStatus.Unconfirmed,
amount = BigDecimal.ONE,
),
),
),
),
),
// endregion
)
}
}
data class GetDemoResultTestModel(
val wallet: Wallet,
val demoAmount: Amount,
val cardTokens: Set<Token>,
val expected: Verified,
)
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
inner class GetNoAccountResult {
@ParameterizedTest
@ProvideTestModels
fun getNoAccountResult(model: GetNoAccountResultModel) {
// Arrange
every { walletManager.wallet } returns model.wallet
// Act
val actual = factory.getNoAccountResult(
walletManager = walletManager,
customMessage = model.customMessage,
amountToCreateAccount = model.amountToCreateAccount,
)
// Assert
Truth.assertThat(actual).isEqualTo(model.expected)
}
private fun provideTestModels() = listOf(
// region amountToCreateAccount is null
GetNoAccountResultModel(
wallet = createWallet(
coinValue = null,
addresses = setOf(Address(value = "0x1", type = AddressType.Default)),
transactions = emptyList(),
),
customMessage = "",
amountToCreateAccount = null,
expected = Unreachable(
selectedAddress = "0x1",
addresses = setOf(Address(value = "0x1", type = Type.Primary)),
),
),
// endregion
// region Wallet without amount and transactions
GetNoAccountResultModel(
wallet = createWallet(
coinValue = null,
addresses = setOf(Address(value = "0x1", type = AddressType.Default)),
transactions = emptyList(),
),
customMessage = "",
amountToCreateAccount = BigDecimal.ONE,
expected = mockFactory.createNoAccount(),
),
// endregion
// region Wallet with coin amount and without transactions
GetNoAccountResultModel(
wallet = createWallet(
coinValue = BigDecimal.ONE,
addresses = setOf(
Address(value = "0x1", type = AddressType.Default),
),
transactions = emptyList(),
),
customMessage = "custom message",
amountToCreateAccount = BigDecimal.ONE,
expected = NoAccount(
selectedAddress = "0x1",
addresses = setOf(Address(value = "0x1", type = Type.Primary)),
amountToCreateAccount = BigDecimal.ONE,
errorMessage = "custom message",
),
),
// endregion
// region Wallet with coin amount and coin transaction
GetNoAccountResultModel(
wallet = createWallet(
coinValue = BigDecimal.ONE,
addresses = setOf(
Address(value = "0x1", type = AddressType.Default),
),
transactions = listOf(
createRecentTransaction(
amount = Amount(
blockchain = Blockchain.Ethereum,
value = BigDecimal.ONE,
),
status = TransactionStatus.Unconfirmed,
),
),
),
customMessage = "",
amountToCreateAccount = BigDecimal.ZERO,
expected = NoAccount(
selectedAddress = "0x1",
addresses = setOf(Address(value = "0x1", type = Type.Primary)),
amountToCreateAccount = BigDecimal.ZERO,
errorMessage = "",
),
),
// endregion
// region Wallet with amounts and transactions (coin and token)
GetNoAccountResultModel(
wallet = createWallet(
coinValue = BigDecimal.ONE,
tokensAmount = mapOf(usdtToken to BigDecimal.ZERO),
addresses = setOf(
Address(value = "0x1", type = AddressType.Default),
Address(value = "0x11", type = AddressType.Legacy),
),
transactions = listOf(
createRecentTransaction(
amount = Amount(
blockchain = Blockchain.Ethereum,
value = BigDecimal.ONE,
),
status = TransactionStatus.Unconfirmed,
),
createRecentTransaction(
amount = Amount(
value = BigDecimal.TEN,
blockchain = Blockchain.Ethereum,
type = AmountType.Token(token = usdtToken),
currencySymbol = "USDT",
),
status = TransactionStatus.Unconfirmed,
),
),
),
customMessage = "",
amountToCreateAccount = BigDecimal.ZERO,
expected = NoAccount(
selectedAddress = "0x1",
addresses = setOf(
Address(value = "0x1", type = Type.Primary),
Address(value = "0x11", type = Type.Secondary),
),
amountToCreateAccount = BigDecimal.ZERO,
errorMessage = "",
),
),
// endregion
)
}
data class GetNoAccountResultModel(
val wallet: Wallet,
val customMessage: String,
val amountToCreateAccount: BigDecimal?,
val expected: UpdateWalletManagerResult,
)
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
inner class GetUnreachableResult {
@ParameterizedTest
@ProvideTestModels
fun getUnreachableResult(model: GetUnreachableResultModel) {
// Arrange
every { walletManager.wallet } returns model.wallet
// Act
val actual = factory.getUnreachableResult(walletManager = walletManager)
// Assert
Truth.assertThat(actual).isEqualTo(model.expected)
}
private fun provideTestModels() = listOf(
// region Wallet without amount and transactions
GetUnreachableResultModel(
wallet = createWallet(
coinValue = null,
addresses = setOf(Address(value = "0x1", type = AddressType.Default)),
transactions = emptyList(),
),
expected = Unreachable(
selectedAddress = "0x1",
addresses = setOf(Address(value = "0x1", type = Type.Primary)),
),
),
// endregion
// region Wallet with coin amount and without transactions
GetUnreachableResultModel(
wallet = createWallet(
coinValue = BigDecimal.ONE,
addresses = setOf(
Address(value = "0x1", type = AddressType.Default),
),
transactions = emptyList(),
),
expected = Unreachable(
selectedAddress = "0x1",
addresses = setOf(Address(value = "0x1", type = Type.Primary)),
),
),
// endregion
// region Wallet with coin amount and coin transaction
GetUnreachableResultModel(
wallet = createWallet(
coinValue = BigDecimal.ONE,
addresses = setOf(
Address(value = "0x1", type = AddressType.Default),
),
transactions = listOf(
createRecentTransaction(
amount = Amount(
blockchain = Blockchain.Ethereum,
value = BigDecimal.ONE,
),
status = TransactionStatus.Unconfirmed,
),
),
),
expected = Unreachable(
selectedAddress = "0x1",
addresses = setOf(Address(value = "0x1", type = Type.Primary)),
),
),
// endregion
// region Wallet with amounts and transactions (coin and token)
GetUnreachableResultModel(
wallet = createWallet(
coinValue = BigDecimal.ONE,
tokensAmount = mapOf(usdtToken to BigDecimal.ZERO),
addresses = setOf(
Address(value = "0x1", type = AddressType.Default),
Address(value = "0x11", type = AddressType.Legacy),
),
transactions = listOf(
createRecentTransaction(
amount = Amount(
blockchain = Blockchain.Ethereum,
value = BigDecimal.ONE,
),
status = TransactionStatus.Unconfirmed,
),
createRecentTransaction(
amount = Amount(
value = BigDecimal.TEN,
blockchain = Blockchain.Ethereum,
type = AmountType.Token(token = usdtToken),
currencySymbol = "USDT",
),
status = TransactionStatus.Unconfirmed,
),
),
),
expected = Unreachable(
selectedAddress = "0x1",
addresses = setOf(
Address(value = "0x1", type = Type.Primary),
Address(value = "0x11", type = Type.Secondary),
),
),
),
// endregion
)
}
data class GetUnreachableResultModel(
val wallet: Wallet,
val expected: UpdateWalletManagerResult,
)
private fun createWallet(
coinValue: BigDecimal?,
tokensAmount: Map<Token, BigDecimal> = emptyMap(),
addresses: Set<Address>,
transactions: List<TransactionData.Uncompiled>,
): Wallet {
return Wallet(
blockchain = Blockchain.Ethereum,
addresses = addresses,
publicKey = mockk(),
tokens = setOf(),
).apply {
coinValue?.let(::setCoinValue)
tokensAmount.forEach { (token, amount) -> addTokenValue(value = amount, token = token) }
recentTransactions += transactions
}
}
private fun createRecentTransaction(amount: Amount, status: TransactionStatus): TransactionData.Uncompiled {
return TransactionData.Uncompiled(
amount = amount,
fee = null,
sourceAddress = "0x1",
destinationAddress = "0x2",
status = status,
hash = "hash",
date = Calendar.getInstance().apply {
timeInMillis = 1748251839317
},
extras = null,
contractAddress = null,
)
}
private fun createTxInfo(status: TxInfo.TransactionStatus, amount: BigDecimal): TxInfo {
return TxInfo(
txHash = "hash",
timestampInMillis = 1748251839317,
isOutgoing = true,
destinationType = TxInfo.DestinationType.Single(
addressType = TxInfo.AddressType.User(address = "0x2"),
),
sourceType = TxInfo.SourceType.Single(address = "0x1"),
interactionAddressType = TxInfo.InteractionAddressType.User(address = "0x2"),
status = status,
type = TxInfo.TransactionType.Transfer,
amount = amount,
)
}
private companion object {
val usdtToken = Token(
id = "0x3",
contractAddress = "0x4",
symbol = "USDT",
decimals = 6,
name = "Tether",
)
}
}

View file

@ -6,5 +6,6 @@ plugins {
dependencies {
/* Domain */
implementation(projects.domain.models)
implementation(projects.domain.tokens.models)
}

View file

@ -1,7 +1,7 @@
package com.tangem.domain.managetokens.model
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
sealed class ManagedCryptoCurrency {

View file

@ -3,7 +3,7 @@ package com.tangem.domain.managetokens
import arrow.core.Either
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
import com.tangem.domain.managetokens.repository.ManageTokensRepository
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
import com.tangem.domain.wallets.models.UserWalletId
class CheckHasLinkedTokensUseCase(

View file

@ -2,7 +2,7 @@ package com.tangem.domain.managetokens
import arrow.core.Either
import com.tangem.domain.managetokens.repository.CustomTokensRepository
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
import com.tangem.domain.wallets.models.UserWalletId
class CheckIsCurrencyNotAddedUseCase(

View file

@ -3,8 +3,8 @@ package com.tangem.domain.managetokens
import arrow.core.Either
import com.tangem.domain.managetokens.model.AddCustomTokenForm
import com.tangem.domain.managetokens.repository.CustomTokensRepository
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.wallets.models.UserWalletId
class CreateCurrencyUseCase(

View file

@ -7,8 +7,8 @@ import arrow.core.raise.either
import arrow.core.raise.ensureNotNull
import com.tangem.domain.managetokens.model.exceptoin.FindTokenException
import com.tangem.domain.managetokens.repository.CustomTokensRepository
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.wallets.models.UserWalletId
class FindTokenUseCase(

View file

@ -6,7 +6,7 @@ import arrow.core.raise.either
import arrow.core.raise.ensureNotNull
import com.tangem.domain.managetokens.model.exceptoin.SupportedBlockchainException
import com.tangem.domain.managetokens.repository.CustomTokensRepository
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
import com.tangem.domain.wallets.models.UserWalletId
class GetSupportedNetworksUseCase(

View file

@ -5,16 +5,15 @@ import arrow.core.flatten
import com.tangem.domain.card.repository.DerivationsRepository
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
import com.tangem.domain.managetokens.repository.CustomTokensRepository
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
import com.tangem.domain.staking.fetcher.YieldBalanceFetcherParams
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.TokensFeatureToggles
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWalletId
@ -24,7 +23,6 @@ class SaveManagedTokensUseCase(
private val customTokensRepository: CustomTokensRepository,
private val walletManagersFacade: WalletManagersFacade,
private val currenciesRepository: CurrenciesRepository,
private val networksRepository: NetworksRepository,
private val derivationsRepository: DerivationsRepository,
private val stakingRepository: StakingRepository,
private val quotesRepository: QuotesRepository,
@ -102,20 +100,12 @@ class SaveManagedTokensUseCase(
val networkToUpdate = currenciesToAdd.map { it.network }
.subtract(existingCurrencies.map { it.network }.toSet())
if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) {
multiNetworkStatusFetcher(
MultiNetworkStatusFetcher.Params(
userWalletId = userWalletId,
networks = networksToUpdate + networkToUpdate,
),
)
} else {
networksRepository.getNetworkStatusesSync(
multiNetworkStatusFetcher(
MultiNetworkStatusFetcher.Params(
userWalletId = userWalletId,
networks = networksToUpdate + networkToUpdate,
refresh = true,
)
}
),
)
}
private suspend fun refreshUpdatedYieldBalances(

View file

@ -6,7 +6,7 @@ import arrow.core.raise.either
import arrow.core.raise.ensure
import com.tangem.domain.managetokens.model.exceptoin.DerivationPathValidationException
import com.tangem.domain.managetokens.repository.CustomTokensRepository
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
class ValidateDerivationPathUseCase(
private val repository: CustomTokensRepository,

View file

@ -6,7 +6,7 @@ import arrow.core.raise.*
import com.tangem.domain.managetokens.model.AddCustomTokenForm
import com.tangem.domain.managetokens.model.exceptoin.CustomTokenFormValidationException
import com.tangem.domain.managetokens.repository.CustomTokensRepository
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
class ValidateTokenFormUseCase(
private val repository: CustomTokensRepository,

View file

@ -1,6 +1,6 @@
package com.tangem.domain.managetokens.model
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
sealed class ManageTokensUpdateAction {

View file

@ -2,8 +2,8 @@ package com.tangem.domain.managetokens.repository
import com.tangem.domain.managetokens.model.AddCustomTokenForm
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.wallets.models.UserWalletId
interface CustomTokensRepository {

View file

@ -4,7 +4,7 @@ import com.tangem.domain.managetokens.model.CurrencyUnsupportedState
import com.tangem.domain.managetokens.model.ManageTokensListBatchFlow
import com.tangem.domain.managetokens.model.ManageTokensListBatchingContext
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
import com.tangem.domain.wallets.models.UserWalletId
interface ManageTokensRepository {

View file

@ -5,6 +5,7 @@ plugins {
}
dependencies {
api(projects.domain.models)
api(projects.domain.tokens.models)
implementation(projects.domain.core)

View file

@ -1,6 +1,6 @@
package com.tangem.domain.markets
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import java.math.BigDecimal
data class TokenMarket(

View file

@ -1,7 +1,7 @@
package com.tangem.domain.markets
import com.tangem.domain.core.serialization.SerializedBigDecimal
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import kotlinx.serialization.Serializable
@Serializable

View file

@ -3,10 +3,10 @@ package com.tangem.domain.markets
import arrow.core.None
import arrow.core.Option
import arrow.core.toOption
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.quotes.single.SingleQuoteProducer
import com.tangem.domain.quotes.single.SingleQuoteSupplier
import com.tangem.domain.tokens.TokensFeatureToggles
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Quote
import com.tangem.domain.tokens.repository.QuotesRepository
import kotlinx.coroutines.flow.Flow

View file

@ -2,7 +2,7 @@ package com.tangem.domain.markets
import arrow.core.Either
import com.tangem.domain.markets.repositories.MarketsTokenRepository
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
/**
* Get token exchanges use case

View file

@ -3,7 +3,7 @@ package com.tangem.domain.markets
import arrow.core.Either
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.repositories.MarketsTokenRepository
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
class GetTokenFullQuotesUseCase(
private val marketsTokenRepository: MarketsTokenRepository,

View file

@ -3,7 +3,7 @@ package com.tangem.domain.markets
import arrow.core.Either
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.repositories.MarketsTokenRepository
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.utils.SupportedLanguages
class GetTokenMarketInfoUseCase(

View file

@ -3,7 +3,7 @@ package com.tangem.domain.markets
import arrow.core.Either
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.repositories.MarketsTokenRepository
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
class GetTokenPriceChartUseCase(
private val marketsTokenRepository: MarketsTokenRepository,

View file

@ -3,16 +3,15 @@ package com.tangem.domain.markets
import arrow.core.Either
import com.tangem.domain.card.repository.DerivationsRepository
import com.tangem.domain.markets.repositories.MarketsTokenRepository
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
import com.tangem.domain.staking.fetcher.YieldBalanceFetcherParams
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.TokensFeatureToggles
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.wallets.models.UserWalletId
@ -30,7 +29,6 @@ class SaveMarketTokensUseCase(
private val derivationsRepository: DerivationsRepository,
private val marketsTokenRepository: MarketsTokenRepository,
private val currenciesRepository: CurrenciesRepository,
private val networksRepository: NetworksRepository,
private val stakingRepository: StakingRepository,
private val quotesRepository: QuotesRepository,
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
@ -61,7 +59,7 @@ class SaveMarketTokensUseCase(
if (addedNetworks.isNotEmpty()) {
derivationsRepository.derivePublicKeysByNetworkIds(
userWalletId = userWalletId,
networkIds = addedNetworks.map { Network.ID(it.networkId) },
networkIds = addedNetworks.map { Network.RawID(it.networkId) },
)
val addedCurrencies = addedNetworks.mapNotNull {
@ -83,20 +81,12 @@ class SaveMarketTokensUseCase(
}
private suspend fun refreshUpdatedNetworks(userWalletId: UserWalletId, addedCurrencies: List<CryptoCurrency>) {
if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) {
multiNetworkStatusFetcher(
MultiNetworkStatusFetcher.Params(
userWalletId = userWalletId,
networks = addedCurrencies.map(CryptoCurrency::network).toSet(),
),
)
} else {
networksRepository.getNetworkStatusesSync(
multiNetworkStatusFetcher(
MultiNetworkStatusFetcher.Params(
userWalletId = userWalletId,
networks = addedCurrencies.map(CryptoCurrency::network).toSet(),
refresh = true,
)
}
),
)
}
private suspend fun refreshUpdatedYieldBalances(

View file

@ -1,7 +1,7 @@
package com.tangem.domain.markets.repositories
import com.tangem.domain.markets.*
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
import java.math.BigDecimal

View file

@ -1,32 +1,34 @@
package com.tangem.domain.tokens.model
package com.tangem.domain.models.currency
import com.tangem.domain.models.network.Network
import kotlinx.serialization.Serializable
/**
* Represents a generic cryptocurrency.
*
* @property id Unique identifier for the cryptocurrency.
* @property network The network to which the cryptocurrency belongs.
* @property name Human-readable name of the cryptocurrency.
* @property symbol Symbol of the cryptocurrency.
* @property decimals Number of decimal places used by the cryptocurrency.
* @property iconUrl Optional URL of the cryptocurrency icon. `null` if not found.
* @property isCustom Indicates whether the currency is a custom user-added currency or not.
*/
/** Represents a generic cryptocurrency */
@Serializable
sealed class CryptoCurrency {
/** Unique identifier for the cryptocurrency */
abstract val id: ID
/** The network to which the cryptocurrency belongs */
abstract val network: Network
/** Human-readable name of the cryptocurrency */
abstract val name: String
/** Symbol of the cryptocurrency */
abstract val symbol: String
/** Number of decimal places used by the cryptocurrency */
abstract val decimals: Int
/** Optional URL of the cryptocurrency icon. `null` if not found. */
abstract val iconUrl: String?
/** Indicates whether the currency is a custom user-added currency or not */
abstract val isCustom: Boolean
/**
* Represents a native coin in the blockchain network.
*/
/** Represents a native coin in the blockchain network */
@Serializable
data class Coin(
override val id: ID,
@ -44,9 +46,9 @@ sealed class CryptoCurrency {
}
/**
* Represents a token in the blockchain network, typically a non-native asset.
* Represents a token in the blockchain network, typically a non-native asset
*
* @property contractAddress Address of the contract managing the token.
* @property contractAddress address of the contract managing the token
*/
@Serializable
data class Token(
@ -72,9 +74,9 @@ sealed class CryptoCurrency {
* The ID is designed to ensure that different cryptocurrencies, whether they are standard tokens, custom tokens or
* standard coins, can be distinctly identified within a system.
*
* @property value Constructed unique identifier value, made up of prefix, network ID, and suffix.
* @property rawCurrencyId Represents not unique currency ID from the blockchain network. `null` if
* its ID of the custom token.
* @property prefix prefix
* @property body body
* @property suffix suffix
*/
@Serializable
data class ID(
@ -83,6 +85,7 @@ sealed class CryptoCurrency {
private val suffix: Suffix,
) {
/** Constructed unique identifier value, made up of prefix, network ID, and suffix */
val value: String
get() = buildString {
append(prefix.value)
@ -92,12 +95,13 @@ sealed class CryptoCurrency {
append(suffix.value)
}
/** Represents a raw cryptocurrency ID. If it is a custom token, the value will be `null`. */
/** Represents a raw cryptocurrency ID. If it is a custom token, the value will be `null` */
val rawCurrencyId: RawID? get() = (suffix as? Suffix.RawID)?.rawId?.let { RawID(it) }
/** Represents a contract address */
val contractAddress: String? get() = (suffix as? Suffix.RawID)?.contractAddress
/** Represents a raw cryptocurrency's network ID. */
/** Represents a raw cryptocurrency's network ID */
val rawNetworkId: String
get() = when (body) {
is Body.NetworkId -> body.rawId
@ -106,10 +110,10 @@ sealed class CryptoCurrency {
/**
* Represents the different types of prefixes that can be associated with a cryptocurrency ID.
*
* These prefixes can help in quickly categorizing the type of cryptocurrency.
*/
enum class Prefix(val value: String) {
/** Prefix for standard coins. */
COIN_PREFIX(value = "coin"),
@ -119,7 +123,6 @@ sealed class CryptoCurrency {
/**
* Represents the body part of the cryptocurrency ID.
*
* The body can be either a raw network ID or a raw network ID with a network derivation path.
*/
@Serializable
@ -136,9 +139,8 @@ sealed class CryptoCurrency {
/**
* Represents a raw network ID with a network derivation path.
*
* Should be used for a cryptocurrencies with custom derivation path.
* */
*/
@Serializable
data class NetworkIdWithDerivationPath(
val rawId: String,
@ -161,7 +163,6 @@ sealed class CryptoCurrency {
/**
* Represents the suffix part of the cryptocurrency ID.
*
* The suffix can either be a raw ID or a contract address.
*/
@Serializable

View file

@ -0,0 +1,11 @@
package com.tangem.domain.models.network
import com.tangem.domain.models.currency.CryptoCurrency
/**
* Crypto currency address
*
* @property cryptoCurrency crypto currency
* @property address default address
*/
data class CryptoCurrencyAddress(val cryptoCurrency: CryptoCurrency, val address: String)

View file

@ -1,4 +1,4 @@
package com.tangem.domain.tokens.model
package com.tangem.domain.models.network
import kotlinx.serialization.Serializable
@ -9,17 +9,17 @@ import kotlinx.serialization.Serializable
* whether it operates as a test network, and the type of blockchain standard it conforms to
* (e.g., ERC20, BEP20).
*
* @property id The unique identifier of the network.
* @property backendId The name of this network in the Tangem backend.
* @property name The human-readable name of the network, such as "Ethereum" or "Bitcoin".
* @property derivationPath The path used to derive keys for this network.
* @property isTestnet Indicates whether the network is a test network or a main network.
* @property standardType The type of blockchain standard the network adheres to.
* @property hasFiatFeeRate Indicates whether there is a fee in the network
* that cannot be represented in a fiat currency.
* (For those blockchains that have FeeResource instead of a standard type of fee)
* @property canHandleTokens Indicates whether the network can handle tokens.
* @property transactionExtrasType The type of extras supported for sending a transaction.
* @property id the unique identifier of the network
* @property backendId the name of this network in the Tangem backend
* @property name the human-readable name of the network, such as "Ethereum" or "Bitcoin"
* @property currencySymbol the symbol of the currency associated with the network
* @property derivationPath the path used to derive keys for this network
* @property isTestnet indicates whether the network is a test network or a main network
* @property standardType the type of blockchain standard the network adheres to
* @property hasFiatFeeRate indicates whether there is a fee in the network that cannot be represented in a fiat
* currency (for those blockchains that have FeeResource instead of a standard type of fee)
* @property canHandleTokens indicates whether the network can handle tokens
* @property transactionExtrasType the type of extras supported for sending a transaction
*/
@Serializable
data class Network(
@ -35,55 +35,60 @@ data class Network(
val transactionExtrasType: TransactionExtrasType,
) {
/** Raw ID */
val rawId: String
get() = id.rawId.value
init {
require(name.isNotBlank()) { "Network name must not be blank" }
require(id.derivationPath == derivationPath) { "Derivation path must be the same as in the ID" }
}
/**
* Represents a unique identifier for a blockchain network.
* Represents a unique identifier for a blockchain network
*
* @property value The string representation of the network ID.
* @property rawId raw network ID
* @property derivationPath derivation path
*/
@JvmInline
@Serializable
value class ID(val value: String) {
data class ID(val rawId: RawID, val derivationPath: DerivationPath) {
init {
require(value.isNotBlank()) { "Network ID must not be blank" }
require(rawId.value.isNotBlank()) { "Network ID must not be blank" }
}
constructor(value: String, derivationPath: DerivationPath) : this(
rawId = RawID(value),
derivationPath = derivationPath,
)
}
@Serializable
data class RawID(val value: String) {
override fun toString(): String = value
}
/**
* Represents a path used to derive cryptographic keys for a blockchain network.
*
* This class represents such paths in a generic manner, allowing for predefined card-based paths,
* custom paths, or even no derivation path at all.
* This class represents such paths in a generic manner, allowing for predefined card-based paths, custom paths,
* or even no derivation path at all.
*/
@Serializable
sealed class DerivationPath {
/** The actual derivation path value, if any. */
/** The actual derivation path value, if any */
abstract val value: String?
/**
* Represents a predefined card-based derivation path.
*
* @property value The derivation path string.
*/
/** Represents a predefined card-based derivation path [value] */
@Serializable
data class Card(override val value: String) : DerivationPath()
/**
* Represents a custom derivation path specified by the user.
*
* @property value The derivation path string.
*/
/** Represents a custom derivation path [value] specified by the user */
@Serializable
data class Custom(override val value: String) : DerivationPath()
/**
* Represents a lack of derivation path, which means the wallet does not support the HD wallet feature.
*/
/** Represents a lack of derivation path, which means the wallet does not support the HD wallet feature */
@Serializable
data object None : DerivationPath() {
override val value: String? get() = null
@ -96,45 +101,43 @@ data class Network(
* Blockchain networks often follow certain standards that dictate how tokens operate on them.
* These standards can define functionalities such as how transactions are processed,
* how tokens are minted or burned, and more.
*
* @property name The human-readable name of the standard type.
*/
@Serializable
sealed class StandardType {
/** The human-readable name of the standard type */
abstract val name: String
/** Represents the ERC20 token standard, common on the Ethereum network. */
/** Represents the ERC20 token standard, common on the Ethereum network */
@Serializable
data object ERC20 : StandardType() {
override val name: String get() = "ERC20"
}
/** Represents the TRC20 token standard, common on the TRON network. */
/** Represents the TRC20 token standard, common on the TRON network */
@Serializable
data object TRC20 : StandardType() {
override val name: String get() = "TRC20"
}
/** Represents the BEP20 token standard, common on the Binance Smart Chain network. */
/** Represents the BEP20 token standard, common on the Binance Smart Chain network */
@Serializable
data object BEP20 : StandardType() {
override val name: String get() = "BEP20"
}
/** Represents the BEP2 token standard, common on the Binance Chain network. */
/** Represents the BEP2 token standard, common on the Binance Chain network */
@Serializable
data object BEP2 : StandardType() {
override val name: String get() = "BEP2"
}
/** Represents a network that does not adhere to a predefined standard type. */
/** Represents a network that does not adhere to a predefined standard type */
@Serializable
data class Unspecified(override val name: String) : StandardType()
}
/**
* Represents the supported type of extras for sending a transaction.
* */
/** Represents the supported type of extras for sending a transaction */
enum class TransactionExtrasType {
/** No transaction extras supported */

View file

@ -1,20 +1,18 @@
package com.tangem.domain.tokens.model
package com.tangem.domain.models.network
/**
* Represents a network address configuration.
*/
/** Represents a network address */
sealed class NetworkAddress {
/** The default or currently selected network address. */
/** The default or currently selected network address */
abstract val defaultAddress: Address
/** The set of available network addresses to choose from. */
/** The set of available network addresses to choose from */
abstract val availableAddresses: Set<Address>
/**
* Represents a single static network address.
* Represents a single static network address
*
* @property defaultAddress The static network address.
* @property defaultAddress the static network address
*/
data class Single(override val defaultAddress: Address) : NetworkAddress() {
@ -22,10 +20,10 @@ sealed class NetworkAddress {
}
/**
* Represents a network configuration where an address can be chosen from a set of available addresses.
* Represents a network configuration where an address can be chosen from a set of available addresses
*
* @property defaultAddress The currently selected or default network address.
* @property availableAddresses The set of available network addresses to choose from.
* @property defaultAddress the currently selected or default network address
* @property availableAddresses the set of available network addresses to choose from
*/
data class Selectable(
override val defaultAddress: Address,
@ -37,6 +35,12 @@ sealed class NetworkAddress {
}
}
/**
* Address
*
* @property value string representation of the address
* @property type address type
*/
data class Address(
val value: String,
val type: Type,

View file

@ -0,0 +1,92 @@
package com.tangem.domain.models.network
import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.currency.CryptoCurrency
import java.math.BigDecimal
/**
* Represents the status of a specific blockchain network
*
* @property network the network for which the status is provided
* @property value the specific status value, represented as a sealed class to encapsulate the various possible
* states of the network
*/
data class NetworkStatus(val network: Network, val value: Value) {
/** Represents the various possible statuses of a network */
sealed class Value {
/** Status source */
abstract val source: StatusSource
fun copySealed(source: StatusSource): Value {
return when (this) {
is NoAccount -> copy(source = source)
is Verified -> copy(source = source)
is Unreachable,
is MissedDerivation,
-> this
}
}
}
/**
* Represents the state where the network is unreachable
*
* @property address network address
*/
data class Unreachable(val address: NetworkAddress?) : Value() {
override val source: StatusSource = StatusSource.ACTUAL
}
/** Represents the state where a derivation has been missed */
data object MissedDerivation : Value() {
override val source: StatusSource = StatusSource.ACTUAL
}
/**
* Represents the verified state of the network, including the amounts associated with different cryptocurrencies
* and whether there are transactions in progress
*
* @property address network address
* @property amounts a map containing the amounts associated with different cryptocurrencies within the
* network
* @property pendingTransactions a map containing pending transactions associated with different cryptocurrencies
* @property source source of data
*/
data class Verified(
val address: NetworkAddress,
val amounts: Map<CryptoCurrency.ID, Amount>,
val pendingTransactions: Map<CryptoCurrency.ID, Set<TxInfo>>,
override val source: StatusSource,
) : Value()
/**
* Represents the state where there is no account, and an amount is required to create one
*
* @property address network address
* @property amountToCreateAccount the amount required to create an account within the network
* @property errorMessage error message
* @property source source of data
*/
data class NoAccount(
val address: NetworkAddress,
val amountToCreateAccount: BigDecimal,
val errorMessage: String,
override val source: StatusSource,
) : Value()
/** Represents possible statuses of cryptocurrency amount */
sealed interface Amount {
/**
* Loaded amount
*
* @property value amount value
*/
data class Loaded(val value: BigDecimal) : Amount
/** Amount which failed to load */
data object NotFound : Amount
}
}

View file

@ -1,8 +1,21 @@
package com.tangem.domain.txhistory.models
package com.tangem.domain.models.network
import java.math.BigDecimal
data class TxHistoryItem(
/**
* Represents information about a transaction. Do not use it for sending transactions.
*
* @property txHash transaction hash
* @property timestampInMillis transaction timestamp in milliseconds
* @property isOutgoing flag that determines the direction of the transaction (incoming or outgoing)
* @property destinationType type of destination (single or multiple)
* @property sourceType type of source (single or multiple)
* @property interactionAddressType interaction address type
* @property status transaction status
* @property type transaction type
* @property amount transaction amount
*/
data class TxInfo(
val txHash: String,
val timestampInMillis: Long,
val isOutgoing: Boolean,
@ -14,18 +27,28 @@ data class TxHistoryItem(
val amount: BigDecimal,
) {
/** Destination type*/
sealed class DestinationType {
/**
* Single
*
* @property addressType address type
*/
data class Single(val addressType: AddressType) : DestinationType()
/**
* Multiple
*
* @property addressTypes addresses types
*/
data class Multiple(val addressTypes: List<AddressType>) : DestinationType()
}
sealed class SourceType {
data class Single(val address: String) : SourceType()
data class Multiple(val addresses: List<String>) : SourceType()
}
/** Address type */
sealed class AddressType {
/** Address value */
abstract val address: String
data class User(override val address: String) : AddressType()
@ -33,6 +56,25 @@ data class TxHistoryItem(
data class Validator(override val address: String) : AddressType()
}
/** Source type */
sealed class SourceType {
/**
* Single
*
* @property address address
*/
data class Single(val address: String) : SourceType()
/**
* Multiple
*
* @property addresses addresses
*/
data class Multiple(val addresses: List<String>) : SourceType()
}
/** Transaction type */
sealed interface TransactionType {
data object Transfer : TransactionType
data object Approve : TransactionType
@ -50,6 +92,7 @@ data class TxHistoryItem(
}
}
/** Transaction status */
sealed class TransactionStatus {
data object Failed : TransactionStatus()
data object Unconfirmed : TransactionStatus()

View file

@ -5,6 +5,6 @@ plugins {
dependencies {
api(projects.domain.core)
api(projects.domain.tokens.models)
api(projects.domain.models)
api(projects.domain.wallets.models)
}

View file

@ -1,11 +1,11 @@
package com.tangem.domain.networks.multi
import com.tangem.domain.core.flow.FlowFetcher
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
import com.tangem.domain.wallets.models.UserWalletId
/**
* Fetcher of network status [Network] for wallet with [UserWalletId]
* Fetcher of network status [Network] for multi-currency wallet with [UserWalletId]
*
[REDACTED_AUTHOR]
*/

View file

@ -1,7 +1,7 @@
package com.tangem.domain.networks.multi
import com.tangem.domain.core.flow.FlowProducer
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.models.network.NetworkStatus
import com.tangem.domain.wallets.models.UserWalletId
/**

View file

@ -1,7 +1,7 @@
package com.tangem.domain.networks.multi
import com.tangem.domain.core.flow.FlowCachingSupplier
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.models.network.NetworkStatus
/**
* Supplier of all networks statuses for selected wallet [MultiNetworkStatusProducer.Params]

View file

@ -0,0 +1,24 @@
package com.tangem.domain.networks.repository
import com.tangem.domain.models.network.CryptoCurrencyAddress
import com.tangem.domain.models.network.Network
import com.tangem.domain.wallets.models.UserWalletId
/**
* Repository for working with pending transactions
*
[REDACTED_AUTHOR]
*/
interface NetworksRepository {
/** Fetches pending transactions for given [network] in selected [userWalletId] */
suspend fun fetchPendingTransactions(userWalletId: UserWalletId, network: Network)
/**
* Returns addresses and crypto currency
*
* @param userWalletId the unique identifier of the user wallet
* @param network network
*/
suspend fun getNetworkAddresses(userWalletId: UserWalletId, network: Network): List<CryptoCurrencyAddress>
}

View file

@ -1,8 +1,7 @@
package com.tangem.domain.networks.single
import com.tangem.domain.core.flow.FlowFetcher
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
import com.tangem.domain.wallets.models.UserWalletId
/**
@ -12,18 +11,11 @@ import com.tangem.domain.wallets.models.UserWalletId
*/
interface SingleNetworkStatusFetcher : FlowFetcher<SingleNetworkStatusFetcher.Params> {
/** Params */
sealed interface Params {
val userWalletId: UserWalletId
val network: Network
data class Simple(override val userWalletId: UserWalletId, override val network: Network) : Params
data class Prepared(
override val userWalletId: UserWalletId,
override val network: Network,
val addedNetworkCurrencies: Set<CryptoCurrency>,
) : Params
}
/**
* Params
*
* @property userWalletId user wallet id
* @property network network
*/
data class Params(val userWalletId: UserWalletId, val network: Network)
}

View file

@ -1,8 +1,8 @@
package com.tangem.domain.networks.single
import com.tangem.domain.core.flow.FlowProducer
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.network.NetworkStatus
import com.tangem.domain.wallets.models.UserWalletId
/**

View file

@ -1,7 +1,7 @@
package com.tangem.domain.networks.single
import com.tangem.domain.core.flow.FlowCachingSupplier
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.models.network.NetworkStatus
/**
* Supplier of network status for selected wallet [SingleNetworkStatusProducer.Params]

View file

@ -10,16 +10,25 @@ android {
}
dependencies {
implementation(deps.arrow.core)
implementation(deps.kotlin.coroutines)
// region Project Core
implementation(projects.core.analytics.models)
implementation(projects.core.utils)
// endregion
// region Project Domain
implementation(projects.domain.core)
implementation(projects.domain.models)
implementation(projects.domain.networks)
implementation(projects.domain.nft.models)
implementation(projects.domain.quotes)
implementation(projects.domain.tokens)
implementation(projects.domain.tokens.models)
implementation(projects.domain.wallets)
implementation(projects.domain.wallets.models)
// endregion
// region Others
implementation(deps.arrow.core)
implementation(deps.kotlin.coroutines)
// endregion
}

View file

@ -2,7 +2,7 @@ package com.tangem.domain.nft.models
import com.tangem.domain.core.serialization.SerializedBigInteger
import com.tangem.domain.models.StatusSource
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
import kotlinx.serialization.Serializable
@Serializable

View file

@ -1,7 +1,7 @@
package com.tangem.domain.nft.models
import com.tangem.domain.models.StatusSource
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
import kotlinx.serialization.Serializable
@Serializable
@ -41,7 +41,7 @@ data class NFTCollection(
data class TON(val contractAddress: String?) : Identifier()
@Serializable
data class Solana(val collection: String?) : Identifier()
data class Solana(val collectionAddress: String?) : Identifier()
@Serializable
data object Unknown : Identifier()

View file

@ -1,7 +1,7 @@
package com.tangem.domain.nft.models
import com.tangem.domain.models.StatusSource
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
data class NFTCollections(
val network: Network,

View file

@ -1,6 +1,6 @@
package com.tangem.domain.nft.models
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
data class NFTNetworks(
val availableNetworks: List<Network>,

View file

@ -26,6 +26,8 @@ sealed class NFTSalePrice {
data class Value(
override val assetId: NFTAsset.Identifier,
val value: SerializedBigDecimal,
val fiatValue: SerializedBigDecimal?,
val symbol: String,
val decimals: Int,
) : NFTSalePrice()
}

View file

@ -0,0 +1,21 @@
package com.tangem.domain.nft
import com.tangem.domain.nft.repository.NFTRepository
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.repository.WalletsRepository
class DisableWalletNFTUseCase(
private val walletsRepository: WalletsRepository,
private val nftRepository: NFTRepository,
private val currenciesRepository: CurrenciesRepository,
) {
suspend operator fun invoke(userWalletId: UserWalletId) {
walletsRepository.disableNFT(userWalletId)
val currencies = currenciesRepository.getMultiCurrencyWalletCachedCurrenciesSync(userWalletId)
val networks = currencies.map { it.network }
nftRepository.clearCache(userWalletId, networks)
}
}

View file

@ -0,0 +1,13 @@
package com.tangem.domain.nft
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.repository.WalletsRepository
class EnableWalletNFTUseCase(
private val walletsRepository: WalletsRepository,
) {
suspend operator fun invoke(userWalletId: UserWalletId) {
walletsRepository.enableNFT(userWalletId)
}
}

View file

@ -1,8 +1,8 @@
package com.tangem.domain.nft
import com.tangem.domain.models.network.Network
import com.tangem.domain.nft.models.NFTCollection
import com.tangem.domain.nft.repository.NFTRepository
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.wallets.models.UserWalletId
class FetchNFTCollectionAssetsUseCase(

View file

@ -0,0 +1,26 @@
package com.tangem.domain.nft
import arrow.core.Either
import com.tangem.domain.models.network.Network
import com.tangem.domain.nft.repository.NFTRepository
import com.tangem.domain.quotes.single.SingleQuoteFetcher
class FetchNFTPriceUseCase(
private val nftRepository: NFTRepository,
private val singleQuoteFetcher: SingleQuoteFetcher,
) {
suspend operator fun invoke(network: Network, appCurrencyId: String?): Either<Throwable, Unit> {
return Either.catch {
val nftCurrency = nftRepository.getNFTCurrency(network)
val rawId = nftCurrency.id.rawCurrencyId ?: error("Invalid nft currency id")
singleQuoteFetcher(
params = SingleQuoteFetcher.Params(
rawCurrencyId = rawId,
appCurrencyId = appCurrencyId,
),
)
}
}
}

View file

@ -1,7 +1,7 @@
package com.tangem.domain.nft
import com.tangem.domain.models.network.Network
import com.tangem.domain.nft.models.NFTNetworks
import com.tangem.domain.tokens.model.Network
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext

View file

@ -1,9 +1,9 @@
package com.tangem.domain.nft
import arrow.core.raise.catch
import com.tangem.domain.models.network.Network
import com.tangem.domain.nft.models.NFTAsset
import com.tangem.domain.nft.repository.NFTRepository
import com.tangem.domain.tokens.model.Network
class GetNFTExploreUrlUseCase(
private val nftRepository: NFTRepository,

View file

@ -1,15 +1,20 @@
package com.tangem.domain.nft
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.network.NetworkStatus
import com.tangem.domain.networks.single.SingleNetworkStatusProducer
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.firstOrNull
class GetNFTNetworkStatusUseCase(
private val networksRepository: NetworksRepository,
private val singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
) {
suspend operator fun invoke(userWalletId: UserWalletId, network: Network): NetworkStatus? = networksRepository
.getNetworkStatusesSync(userWalletId, setOf(network), false)
.firstOrNull { it.network == network }
suspend operator fun invoke(userWalletId: UserWalletId, network: Network): NetworkStatus? {
return singleNetworkStatusSupplier(
params = SingleNetworkStatusProducer.Params(userWalletId = userWalletId, network = network),
)
.firstOrNull()
}
}

View file

@ -0,0 +1,44 @@
package com.tangem.domain.nft
import arrow.core.Either
import com.tangem.domain.nft.models.NFTAsset
import com.tangem.domain.nft.models.NFTSalePrice
import com.tangem.domain.nft.repository.NFTRepository
import com.tangem.domain.quotes.single.SingleQuoteProducer
import com.tangem.domain.quotes.single.SingleQuoteSupplier
import com.tangem.domain.tokens.model.Quote
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
class GetNFTPriceUseCase(
private val nftRepository: NFTRepository,
private val singleQuoteSupplier: SingleQuoteSupplier,
) {
suspend operator fun invoke(userWalletId: UserWalletId, nftAsset: NFTAsset): Either<Throwable, Flow<NFTSalePrice>> {
return Either.catch {
val nftCurrency = nftRepository.getNFTCurrency(nftAsset.network)
val rawId = nftCurrency.id.rawCurrencyId ?: error("Invalid nft currency id")
singleQuoteSupplier(
params = SingleQuoteProducer.Params(rawCurrencyId = rawId),
).map { quote ->
val nftPrice = nftRepository.getNFTSalePrice(
userWalletId = userWalletId,
network = nftAsset.network,
collectionId = nftAsset.collectionId,
assetId = nftAsset.id,
)
val quoteValue = quote as? Quote.Value
if (nftPrice !is NFTSalePrice.Value) {
nftPrice
} else {
nftPrice.copy(fiatValue = quoteValue?.fiatRate?.multiply(nftPrice.value))
}
}
}
}
}

View file

@ -0,0 +1,13 @@
package com.tangem.domain.nft
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.repository.WalletsRepository
import kotlinx.coroutines.flow.Flow
class GetWalletNFTEnabledUseCase(
private val walletsRepository: WalletsRepository,
) {
operator fun invoke(userWalletId: UserWalletId): Flow<Boolean> = walletsRepository
.nftEnabledStatus(userWalletId)
}

View file

@ -0,0 +1,38 @@
package com.tangem.domain.nft
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.nft.repository.NFTRepository
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.*
class ObserveAndClearNFTCacheIfNeedUseCase(
private val nftRepository: NFTRepository,
private val currenciesRepository: CurrenciesRepository,
) {
operator fun invoke(userWalletId: UserWalletId): Flow<Set<Network>> = currenciesRepository
.getWalletCurrenciesUpdates(userWalletId)
.map { it.map(CryptoCurrency::network) }
.mapDiff { old, new ->
// calculate networks sets difference to determine which networks were removed
old.toSet() - new.toSet()
}
.distinctUntilChanged()
.onEach { removedNetworks ->
if (removedNetworks.isNotEmpty()) {
nftRepository.clearCache(userWalletId, removedNetworks.toList())
}
}
private fun <T, R> Flow<T>.mapDiff(diff: (old: T, new: T) -> R): Flow<R> = flow {
var previous: T? = null
collect { current ->
val prev = previous
if (prev != null) {
emit(diff(prev, current))
}
previous = current
}
}
}

View file

@ -1,15 +1,26 @@
package com.tangem.domain.nft.repository
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.nft.models.NFTAsset
import com.tangem.domain.nft.models.NFTCollection
import com.tangem.domain.nft.models.NFTCollections
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.nft.models.NFTSalePrice
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
interface NFTRepository {
fun observeCollections(userWalletId: UserWalletId, networks: List<Network>): Flow<List<NFTCollections>>
fun getNFTCurrency(network: Network): CryptoCurrency
suspend fun getNFTSalePrice(
userWalletId: UserWalletId,
network: Network,
collectionId: NFTCollection.Identifier,
assetId: NFTAsset.Identifier,
): NFTSalePrice
suspend fun refreshCollections(userWalletId: UserWalletId, networks: List<Network>)
suspend fun refreshAssets(userWalletId: UserWalletId, network: Network, collectionId: NFTCollection.Identifier)
@ -21,4 +32,6 @@ interface NFTRepository {
suspend fun getNFTSupportedNetworks(userWalletId: UserWalletId): List<Network>
suspend fun getNFTExploreUrl(network: Network, assetIdentifier: NFTAsset.Identifier): String?
suspend fun clearCache(userWalletId: UserWalletId, networks: List<Network>)
}

View file

@ -13,10 +13,10 @@ android {
dependencies {
implementation(projects.core.utils)
implementation(projects.domain.core)
implementation(projects.domain.models)
implementation(projects.domain.notifications.models)
implementation(projects.domain.wallets.models)
implementation(projects.domain.tokens.models)
implementation(projects.domain.notifications.models)
implementation(projects.domain.wallets)
implementation(projects.libs.crypto)
// region DI

View file

@ -0,0 +1,4 @@
package com.tangem.domain.notifications.models
@JvmInline
value class ApplicationId(val value: String)

View file

@ -0,0 +1,13 @@
package com.tangem.domain.notifications.models
enum class NotificationType(val type: String) {
Promo("promo"),
Unknown("unknown"),
;
companion object {
fun getType(type: String?): NotificationType {
return entries.firstOrNull { it.type == type } ?: Unknown
}
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.domain.notifications
import arrow.core.Either
import com.tangem.domain.notifications.models.ApplicationId
import com.tangem.domain.notifications.repository.NotificationsRepository
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
@ -10,7 +11,7 @@ class GetApplicationIdUseCase(
) {
private val mutex = Mutex()
suspend operator fun invoke(): Either<Throwable, String> = Either.catch {
suspend operator fun invoke(): Either<Throwable, ApplicationId> = Either.catch {
val localApplicationId = notificationsRepository.getApplicationId()
if (localApplicationId != null) return@catch localApplicationId

View file

@ -1,7 +1,7 @@
package com.tangem.domain.notifications
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.notifications.repository.NotificationsRepository
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.lib.crypto.BlockchainUtils.isTron
class IncrementNotificationsShowCountUseCase(
@ -10,7 +10,7 @@ class IncrementNotificationsShowCountUseCase(
suspend operator fun invoke(cryptoCurrency: CryptoCurrency) {
val isTronToken = cryptoCurrency is CryptoCurrency.Token &&
isTron(cryptoCurrency.network.id.value)
isTron(cryptoCurrency.network.rawId)
if (isTronToken) {
notificationsRepository.incrementTronTokenFeeNotificationShowCounter()

View file

@ -1,18 +1,16 @@
package com.tangem.domain.notifications
import arrow.core.Either
import com.tangem.domain.notifications.models.ApplicationId
import com.tangem.domain.notifications.repository.NotificationsRepository
import com.tangem.utils.notifications.PushNotificationsTokenProvider
class SendPushTokenUseCase(
private val notificationsRepository: NotificationsRepository,
private val getApplicationIdUseCase: GetApplicationIdUseCase,
private val pushNotificationsTokenProvider: PushNotificationsTokenProvider,
) {
suspend operator fun invoke(): Either<Throwable, Unit> = Either.catch {
val applicationId = getApplicationIdUseCase().getOrNull()
?: error("Application ID not found")
suspend operator fun invoke(applicationId: ApplicationId): Either<Throwable, Unit> = Either.catch {
val token = pushNotificationsTokenProvider.getToken()
notificationsRepository.sendPushToken(applicationId, token)
}

View file

@ -1,31 +1,23 @@
package com.tangem.domain.notifications.repository
import com.tangem.domain.notifications.models.ApplicationId
import com.tangem.domain.notifications.models.NotificationsEligibleNetwork
interface NotificationsRepository {
@Throws
suspend fun createApplicationId(pushToken: String? = null): String
suspend fun createApplicationId(pushToken: String? = null): ApplicationId
suspend fun saveApplicationId(appId: String)
suspend fun saveApplicationId(appId: ApplicationId)
suspend fun getApplicationId(): String?
suspend fun getApplicationId(): ApplicationId?
suspend fun getTronTokenFeeNotificationShowCounter(): Int
suspend fun incrementTronTokenFeeNotificationShowCounter()
@Throws
suspend fun associateApplicationIdWithWallets(appId: String, wallets: List<String>)
@Throws
suspend fun setWalletName(walletId: String, walletName: String)
@Throws
suspend fun getWalletName(walletId: String): String?
@Throws
suspend fun sendPushToken(appId: String, pushToken: String)
suspend fun sendPushToken(appId: ApplicationId, pushToken: String)
@Throws
suspend fun getEligibleNetworks(): List<NotificationsEligibleNetwork>

View file

@ -2,6 +2,7 @@ package com.tangem.domain.notifications
import arrow.core.Either
import com.google.common.truth.Truth.assertThat
import com.tangem.domain.notifications.models.ApplicationId
import com.tangem.domain.notifications.repository.NotificationsRepository
import io.mockk.coEvery
import io.mockk.coVerify
@ -20,7 +21,7 @@ class GetApplicationIdUseCaseTest {
@Test
fun `GIVEN local application ID exists WHEN invoke THEN return local application ID`() = runTest {
// GIVEN
val expectedApplicationId = "test-app-id"
val expectedApplicationId = ApplicationId("test-app-id")
coEvery { notificationsRepository.getApplicationId() } returns expectedApplicationId
// WHEN
@ -39,7 +40,7 @@ class GetApplicationIdUseCaseTest {
@Test
fun `GIVEN local application ID does not exist WHEN invoke THEN create and save new application ID`() = runTest {
// GIVEN
val newApplicationId = "new-app-id"
val newApplicationId = ApplicationId("new-app-id")
coEvery { notificationsRepository.getApplicationId() } returns null
coEvery { notificationsRepository.createApplicationId() } returns newApplicationId
coEvery { notificationsRepository.saveApplicationId(newApplicationId) } returns Unit
@ -81,7 +82,7 @@ class GetApplicationIdUseCaseTest {
fun `GIVEN no local application ID WHEN multiple concurrent invokes THEN create only one application ID`() =
runTest {
// GIVEN
val newApplicationId = "new-app-id"
val newApplicationId = ApplicationId("new-app-id")
var isIdCreated = false
coEvery { notificationsRepository.getApplicationId() } answers {
@ -115,7 +116,7 @@ class GetApplicationIdUseCaseTest {
@Test
fun `GIVEN no local application ID WHEN multiple concurrent invokes with delay THEN create only one application ID`() = runTest {
// GIVEN
val newApplicationId = "new-app-id"
val newApplicationId = ApplicationId("new-app-id")
var isIdCreated = false
coEvery { notificationsRepository.getApplicationId() } answers {

View file

@ -2,6 +2,7 @@ package com.tangem.domain.notifications
import arrow.core.Either
import com.google.common.truth.Truth.assertThat
import com.tangem.domain.notifications.models.ApplicationId
import com.tangem.domain.notifications.repository.NotificationsRepository
import com.tangem.utils.notifications.PushNotificationsTokenProvider
import io.mockk.coEvery
@ -14,18 +15,15 @@ import org.junit.Test
class SendPushTokenUseCaseTest {
private lateinit var notificationsRepository: NotificationsRepository
private lateinit var getApplicationIdUseCase: GetApplicationIdUseCase
private lateinit var pushNotificationsTokenProvider: PushNotificationsTokenProvider
private lateinit var sendPushTokenUseCase: SendPushTokenUseCase
@Before
fun setup() {
notificationsRepository = mockk()
getApplicationIdUseCase = mockk()
pushNotificationsTokenProvider = mockk()
sendPushTokenUseCase = SendPushTokenUseCase(
notificationsRepository = notificationsRepository,
getApplicationIdUseCase = getApplicationIdUseCase,
pushNotificationsTokenProvider = pushNotificationsTokenProvider,
)
}
@ -33,44 +31,30 @@ class SendPushTokenUseCaseTest {
@Test
fun `GIVEN valid application ID and token WHEN invoke THEN token is sent successfully`() = runTest {
// GIVEN
val applicationId = "test-app-id"
val applicationId = ApplicationId("test-app-id")
val token = "test-token"
coEvery { getApplicationIdUseCase() } returns Either.Right(applicationId)
coEvery { pushNotificationsTokenProvider.getToken() } returns token
coEvery { notificationsRepository.sendPushToken(applicationId, token) } returns Unit
// WHEN
val result = sendPushTokenUseCase()
val result = sendPushTokenUseCase(applicationId)
// THEN
assertThat(result).isEqualTo(Either.Right(Unit))
coVerify(exactly = 1) { notificationsRepository.sendPushToken(applicationId, token) }
}
@Test
fun `GIVEN application ID is not found WHEN invoke THEN throws error`() = runTest {
// GIVEN
coEvery { getApplicationIdUseCase() } returns Either.Left(Throwable("Application ID not found"))
// WHEN & THEN
val result = sendPushTokenUseCase()
assertThat(result.isLeft()).isTrue()
assertThat(result.fold({ it.message }, { null })).isEqualTo("Application ID not found")
coVerify(exactly = 0) { notificationsRepository.sendPushToken(any(), any()) }
}
@Test
fun `GIVEN repository throws error WHEN invoke THEN returns error`() = runTest {
// GIVEN
val applicationId = "test-app-id"
val applicationId = ApplicationId("test-app-id")
val token = "test-token"
val expectedError = RuntimeException("Network error")
coEvery { getApplicationIdUseCase() } returns Either.Right(applicationId)
coEvery { pushNotificationsTokenProvider.getToken() } returns token
coEvery { notificationsRepository.sendPushToken(applicationId, token) } throws expectedError
// WHEN
val result = sendPushTokenUseCase()
val result = sendPushTokenUseCase(applicationId)
// THEN
assertThat(result).isEqualTo(Either.Left(expectedError))

View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,4 @@
plugins {
alias(deps.plugins.kotlin.jvm)
id("configuration")
}

View file

@ -0,0 +1,5 @@
package com.tangem.domain.notifications.toggles
interface NotificationsFeatureToggles {
val isNotificationsEnabled: Boolean
}

View file

@ -6,6 +6,7 @@ plugins {
}
dependencies {
api(projects.domain.models)
implementation(projects.domain.core)
implementation(projects.domain.tokens.models)
implementation(projects.domain.wallets.models)

View file

@ -1,6 +1,6 @@
package com.tangem.domain.onramp.model
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.tokens.model.Quote
/**

View file

@ -1,13 +1,13 @@
package com.tangem.domain.onramp
import arrow.core.Either
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
import com.tangem.domain.onramp.model.cache.OnrampTransaction
import com.tangem.domain.onramp.model.error.OnrampError
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
import com.tangem.domain.onramp.repositories.OnrampRepository
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWallet
class GetOnrampRedirectUrlUseCase(

View file

@ -3,11 +3,11 @@ package com.tangem.domain.onramp
import arrow.core.Either
import arrow.core.left
import arrow.core.right
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.onramp.model.cache.OnrampTransaction
import com.tangem.domain.onramp.model.error.OnrampError
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch

View file

@ -1,10 +1,10 @@
package com.tangem.domain.onramp
import arrow.core.Either
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.onramp.model.error.OnrampError
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
import com.tangem.domain.onramp.repositories.OnrampRepository
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWallet
class OnrampFetchPairsUseCase(

View file

@ -1,10 +1,10 @@
package com.tangem.domain.onramp
import arrow.core.Either
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
import com.tangem.domain.onramp.repositories.OnrampRepository
import com.tangem.domain.tokens.model.Amount
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWallet
class OnrampFetchQuotesUseCase(

View file

@ -1,6 +1,6 @@
package com.tangem.domain.onramp.repositories
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
/**
* Support legacy onboarding note/twins to get top up URL from mercuryo

View file

@ -1,9 +1,9 @@
package com.tangem.domain.onramp.repositories
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.onramp.model.*
import com.tangem.domain.onramp.model.cache.OnrampTransaction
import com.tangem.domain.tokens.model.Amount
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWallet
import kotlinx.coroutines.flow.Flow

Some files were not shown because too many files have changed in this diff Show more