Updated on 2026-08-14
This commit is contained in:
commit
f3dfb262c2
527 changed files with 12898 additions and 3301 deletions
|
|
@ -80,7 +80,7 @@ class ArchiveCryptoPortfolioUseCase(
|
|||
val hasNotReferralToken = statuses.none { status ->
|
||||
val currency = status.currency
|
||||
|
||||
currency.network.backendId == referralToken.networkId &&
|
||||
currency.network.rawId == referralToken.networkId &&
|
||||
(currency as? CryptoCurrency.Token)?.contractAddress == referralToken.contractAddress &&
|
||||
status.value.networkAddress?.availableAddresses?.any { it.value == address } == true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ class ManageCryptoCurrenciesUseCase(
|
|||
val foundToken = accountStatus.tokenList.flattenCurrencies()
|
||||
.mapNotNull { it.currency as? CryptoCurrency.Token }
|
||||
.firstOrNull { token ->
|
||||
token.network.backendId == networkId &&
|
||||
token.network.rawId == networkId &&
|
||||
!token.isCustom &&
|
||||
token.contractAddress.equals(contractAddress, true)
|
||||
}
|
||||
|
|
@ -361,7 +361,7 @@ class ManageCryptoCurrenciesUseCase(
|
|||
launch {
|
||||
val assetIds = currencies.mapTo(hashSetOf()) { currency ->
|
||||
ExpressAsset.ID(
|
||||
networkId = currency.network.backendId,
|
||||
networkId = currency.network.rawId,
|
||||
contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress,
|
||||
)
|
||||
}
|
||||
|
|
@ -388,19 +388,19 @@ class ManageCryptoCurrenciesUseCase(
|
|||
) {
|
||||
|
||||
constructor(network: Network) : this(
|
||||
networkId = network.backendId,
|
||||
networkId = network.rawId,
|
||||
derivationPath = network.derivationPath,
|
||||
contractAddress = null,
|
||||
)
|
||||
|
||||
constructor(currency: CryptoCurrency) : this(
|
||||
networkId = currency.network.backendId,
|
||||
networkId = currency.network.rawId,
|
||||
derivationPath = currency.network.derivationPath,
|
||||
contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress,
|
||||
)
|
||||
|
||||
constructor(status: CryptoCurrencyStatus) : this(
|
||||
networkId = status.currency.network.backendId,
|
||||
networkId = status.currency.network.rawId,
|
||||
derivationPath = status.currency.network.derivationPath,
|
||||
contractAddress = (status.currency as? CryptoCurrency.Token)?.contractAddress,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.domain.account.status.utils
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.status.model.AccountCryptoCurrency
|
||||
|
|
@ -180,7 +180,7 @@ internal object AccountCryptoCurrencyStatusFinder {
|
|||
contractAddress: String?,
|
||||
): AccountCryptoCurrency? {
|
||||
return accountList.getExpectedAccounts(
|
||||
rawNetworkId = networkId.rawId.value,
|
||||
rawNetworkId = networkId.rawId,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
.asSequence()
|
||||
|
|
@ -220,7 +220,7 @@ internal object AccountCryptoCurrencyStatusFinder {
|
|||
|
||||
internal fun AccountStatusList.getExpectedAccountStatuses(networkId: Network.ID): List<AccountStatus> {
|
||||
val possibleAccountIndex = getAccountIndexOrNull(
|
||||
rawNetworkId = networkId.rawId.value,
|
||||
rawNetworkId = networkId.rawId,
|
||||
derivationPath = networkId.derivationPath,
|
||||
)
|
||||
|
||||
|
|
@ -239,7 +239,7 @@ internal object AccountCryptoCurrencyStatusFinder {
|
|||
}
|
||||
|
||||
internal fun AccountStatusList.getExpectedAccountStatuses(networks: List<Network>): List<AccountStatus> {
|
||||
val possibleAccountIndexes = networks.mapNotNull { getAccountIndexOrNull(it.rawId, it.derivationPath) }
|
||||
val possibleAccountIndexes = networks.mapNotNull { getAccountIndexOrNull(it.id.rawId, it.derivationPath) }
|
||||
|
||||
if (possibleAccountIndexes.isEmpty()) return accountStatuses
|
||||
|
||||
|
|
@ -256,16 +256,14 @@ internal object AccountCryptoCurrencyStatusFinder {
|
|||
// region AccountList helpers
|
||||
|
||||
internal fun AccountList.getExpectedAccounts(network: Network?): List<Account> {
|
||||
return getExpectedAccounts(rawNetworkId = network?.rawId, derivationPath = network?.derivationPath)
|
||||
return getExpectedAccounts(rawNetworkId = network?.id?.rawId, derivationPath = network?.derivationPath)
|
||||
}
|
||||
|
||||
private fun AccountList.getExpectedAccounts(
|
||||
rawNetworkId: String?,
|
||||
rawNetworkId: Network.RawID?,
|
||||
derivationPath: Network.DerivationPath?,
|
||||
): List<Account> {
|
||||
val possibleAccountIndex = getAccountIndexOrNull(rawNetworkId, derivationPath)
|
||||
|
||||
return when (possibleAccountIndex) {
|
||||
return when (val possibleAccountIndex = getAccountIndexOrNull(rawNetworkId, derivationPath)) {
|
||||
null -> accounts
|
||||
DerivationIndex.Main.value -> listOf(mainAccount)
|
||||
// currency only in the account with specific derivation index or in the main account
|
||||
|
|
@ -283,10 +281,10 @@ internal object AccountCryptoCurrencyStatusFinder {
|
|||
|
||||
// region Common helpers
|
||||
|
||||
private fun getAccountIndexOrNull(rawNetworkId: String?, derivationPath: Network.DerivationPath?): Int? {
|
||||
private fun getAccountIndexOrNull(rawNetworkId: Network.RawID?, derivationPath: Network.DerivationPath?): Int? {
|
||||
if (rawNetworkId == null || derivationPath == null) return null
|
||||
|
||||
val blockchain = Blockchain.fromId(id = rawNetworkId)
|
||||
val blockchain = rawNetworkId.toBlockchain()
|
||||
val recognizer = AccountNodeRecognizer(blockchain)
|
||||
|
||||
return recognizer.recognize(derivationPath)?.toInt()
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ class ArchiveCryptoPortfolioUseCaseTest {
|
|||
val defaultAddress = "0xABC"
|
||||
|
||||
val cryptoCurrency = mockk<CryptoCurrency.Token> {
|
||||
every { this@mockk.network.backendId } returns token.networkId
|
||||
every { this@mockk.network.rawId } returns token.networkId
|
||||
every { this@mockk.contractAddress } returns token.contractAddress!!
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ plugins {
|
|||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.domain.tokensync"
|
||||
namespace = "com.tangem.domain.assetsdiscovery"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
|
@ -14,6 +14,9 @@ dependencies {
|
|||
implementation(projects.domain.account.status)
|
||||
implementation(projects.core.utils)
|
||||
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(tangemDeps.blockchain)
|
||||
|
||||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.arrow.core)
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.domain.assetsdiscovery
|
||||
|
||||
import com.tangem.blockchain.assetsdiscovery.AssetsDiscoveryService
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
interface AssetsDiscoveryFacade {
|
||||
|
||||
suspend fun getAssetsDiscoveryService(userWalletId: UserWalletId, network: Network): AssetsDiscoveryServiceInfo?
|
||||
|
||||
data class AssetsDiscoveryServiceInfo(
|
||||
val address: String,
|
||||
val service: AssetsDiscoveryService,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
package com.tangem.domain.tokensync.model
|
||||
package com.tangem.domain.assetsdiscovery.model
|
||||
|
||||
sealed class TokenSyncProgress {
|
||||
sealed class AssetsDiscoveryProgress {
|
||||
|
||||
data object Idle : TokenSyncProgress()
|
||||
data object Idle : AssetsDiscoveryProgress()
|
||||
|
||||
data class InProgress(
|
||||
val completedNetworks: Int,
|
||||
val totalNetworks: Int,
|
||||
) : TokenSyncProgress() {
|
||||
) : AssetsDiscoveryProgress() {
|
||||
val progressPercent: Int
|
||||
get() = if (totalNetworks > 0) {
|
||||
completedNetworks * 100 / totalNetworks
|
||||
|
|
@ -16,5 +16,5 @@ sealed class TokenSyncProgress {
|
|||
}
|
||||
}
|
||||
|
||||
data object Completed : TokenSyncProgress()
|
||||
data object Completed : AssetsDiscoveryProgress()
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.domain.assetsdiscovery.repository
|
||||
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.assetsdiscovery.model.AssetsDiscoveryProgress
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface AssetsDiscoveryRepository {
|
||||
|
||||
suspend fun runDiscovery(userWalletId: UserWalletId)
|
||||
|
||||
suspend fun completeDiscovery(userWalletId: UserWalletId)
|
||||
|
||||
suspend fun getPendingDiscoveryWalletIds(): List<UserWalletId>
|
||||
|
||||
fun observeDiscoveryProgress(userWalletId: UserWalletId): Flow<AssetsDiscoveryProgress>
|
||||
|
||||
fun acknowledgeCompletion(userWalletId: UserWalletId)
|
||||
|
||||
suspend fun clearPendingFlag(userWalletId: UserWalletId)
|
||||
|
||||
suspend fun getDiscoveredCurrencies(userWalletId: UserWalletId): List<CryptoCurrency>
|
||||
|
||||
suspend fun clearDiscoveredTokens(userWalletId: UserWalletId)
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.domain.assetsdiscovery.usecase
|
||||
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.assetsdiscovery.repository.AssetsDiscoveryRepository
|
||||
|
||||
class AcknowledgeAssetsDiscoveryCompletionUseCase(
|
||||
private val assetsDiscoveryRepository: AssetsDiscoveryRepository,
|
||||
) {
|
||||
|
||||
operator fun invoke(userWalletId: UserWalletId) {
|
||||
assetsDiscoveryRepository.acknowledgeCompletion(userWalletId)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.domain.assetsdiscovery.usecase
|
||||
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.assetsdiscovery.model.AssetsDiscoveryProgress
|
||||
import com.tangem.domain.assetsdiscovery.repository.AssetsDiscoveryRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class ObserveAssetsDiscoveryUseCase(
|
||||
private val assetsDiscoveryRepository: AssetsDiscoveryRepository,
|
||||
) {
|
||||
|
||||
operator fun invoke(userWalletId: UserWalletId): Flow<AssetsDiscoveryProgress> {
|
||||
return assetsDiscoveryRepository.observeDiscoveryProgress(userWalletId)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,18 @@
|
|||
package com.tangem.domain.tokensync.usecase
|
||||
package com.tangem.domain.assetsdiscovery.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.tokensync.repository.TokenSyncRepository
|
||||
import com.tangem.domain.assetsdiscovery.repository.AssetsDiscoveryRepository
|
||||
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class StartTokenSyncUseCase(
|
||||
private val tokenSyncRepository: TokenSyncRepository,
|
||||
class StartAssetsDiscoveryUseCase(
|
||||
private val assetsDiscoveryRepository: AssetsDiscoveryRepository,
|
||||
private val manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase,
|
||||
private val appCoroutineScope: AppCoroutineScope,
|
||||
) {
|
||||
|
|
@ -23,9 +23,9 @@ class StartTokenSyncUseCase(
|
|||
activeSyncJobs[userWalletId]?.cancel()
|
||||
activeSyncJobs[userWalletId] = appCoroutineScope.launch {
|
||||
try {
|
||||
tokenSyncRepository.runSync(userWalletId)
|
||||
assetsDiscoveryRepository.runDiscovery(userWalletId)
|
||||
applyDiscoveredTokens(userWalletId)
|
||||
tokenSyncRepository.completeSync(userWalletId)
|
||||
assetsDiscoveryRepository.completeDiscovery(userWalletId)
|
||||
} catch (e: Exception) {
|
||||
TangemLogger.e("Token sync failed for wallet: $userWalletId", e)
|
||||
} finally {
|
||||
|
|
@ -36,18 +36,18 @@ class StartTokenSyncUseCase(
|
|||
|
||||
suspend fun cancel(userWalletId: UserWalletId): Either<Throwable, Unit> = Either.catch {
|
||||
activeSyncJobs.remove(userWalletId)?.cancel()
|
||||
tokenSyncRepository.clearPendingFlag(userWalletId)
|
||||
tokenSyncRepository.clearDiscoveredTokens(userWalletId)
|
||||
assetsDiscoveryRepository.clearPendingFlag(userWalletId)
|
||||
assetsDiscoveryRepository.clearDiscoveredTokens(userWalletId)
|
||||
}
|
||||
|
||||
fun applyPendingSyncs() {
|
||||
fun applyPendingAssetsDiscovery() {
|
||||
appCoroutineScope.launch {
|
||||
try {
|
||||
val pendingIds = tokenSyncRepository.getPendingSyncWalletIds()
|
||||
val pendingIds = assetsDiscoveryRepository.getPendingDiscoveryWalletIds()
|
||||
for (walletId in pendingIds) {
|
||||
val isApplied = applyDiscoveredTokens(walletId)
|
||||
if (isApplied) {
|
||||
tokenSyncRepository.clearPendingFlag(walletId)
|
||||
assetsDiscoveryRepository.clearPendingFlag(walletId)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
|
@ -57,7 +57,7 @@ class StartTokenSyncUseCase(
|
|||
}
|
||||
|
||||
private suspend fun applyDiscoveredTokens(userWalletId: UserWalletId): Boolean {
|
||||
val currencies = tokenSyncRepository.getDiscoveredCurrencies(userWalletId)
|
||||
val currencies = assetsDiscoveryRepository.getDiscoveredCurrencies(userWalletId)
|
||||
|
||||
if (currencies.isEmpty()) return true
|
||||
|
||||
|
|
@ -67,7 +67,7 @@ class StartTokenSyncUseCase(
|
|||
add = currencies,
|
||||
).fold(
|
||||
ifRight = {
|
||||
tokenSyncRepository.clearDiscoveredTokens(userWalletId)
|
||||
assetsDiscoveryRepository.clearDiscoveredTokens(userWalletId)
|
||||
true
|
||||
},
|
||||
ifLeft = { error ->
|
||||
|
|
@ -13,8 +13,12 @@ dependencies {
|
|||
api(projects.domain.dynamicAddresses.models)
|
||||
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.walletManager)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
|
||||
implementation(tangemDeps.blockchain) {
|
||||
exclude(module = "joda-time")
|
||||
}
|
||||
implementation(tangemDeps.card.core)
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ class DisableDynamicAddressesUseCase(
|
|||
|
||||
/**
|
||||
* Returns true when consolidation is required before disabling (non-base balances exist),
|
||||
* or false when DA was disabled immediately.
|
||||
* or false when dynamic addresses was disabled immediately.
|
||||
*/
|
||||
suspend operator fun invoke(userWalletId: UserWalletId, network: Network): Either<Throwable, Boolean> =
|
||||
Either.catch {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
package com.tangem.domain.dynamicaddresses
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
|
||||
/**
|
||||
* List of blockchains that support Dynamic Addresses (XPUB-based multi-address mode).
|
||||
* Must match [Blockchain.isBip44DerivationStyleXPUB] from blockchain-sdk minus Kaspa (deferred).
|
||||
*
|
||||
* Dynamic addresses is NOT used for Legacy (m/44' for BTC/LTC) or Taproot (m/86') addresses.
|
||||
* Only the default derivation style per blockchain is supported.
|
||||
*/
|
||||
object DynamicAddressesSupportedBlockchains {
|
||||
|
||||
private const val BIP44_PURPOSE = 44L
|
||||
private const val BIP84_PURPOSE = 84L
|
||||
|
||||
private val supported = setOf(
|
||||
Blockchain.Bitcoin,
|
||||
Blockchain.BitcoinTestnet,
|
||||
Blockchain.BitcoinCash,
|
||||
Blockchain.BitcoinCashTestnet,
|
||||
Blockchain.Litecoin,
|
||||
Blockchain.Dogecoin,
|
||||
Blockchain.Dash,
|
||||
Blockchain.Ravencoin,
|
||||
Blockchain.RavencoinTestnet,
|
||||
)
|
||||
|
||||
private val supportedNetworkIds = supported.map { it.toNetworkId() }.toSet()
|
||||
|
||||
/**
|
||||
* Allowed BIP purpose nodes per network ID.
|
||||
* BTC/LTC use BIP-84 (SegWit), others use BIP-44 (Legacy P2PKH).
|
||||
*/
|
||||
private val allowedPurposeByNetworkId: Map<String, Long> = buildMap {
|
||||
put(Blockchain.Bitcoin.toNetworkId(), BIP84_PURPOSE)
|
||||
put(Blockchain.BitcoinTestnet.toNetworkId(), BIP84_PURPOSE)
|
||||
put(Blockchain.Litecoin.toNetworkId(), BIP84_PURPOSE)
|
||||
put(Blockchain.BitcoinCash.toNetworkId(), BIP44_PURPOSE)
|
||||
put(Blockchain.BitcoinCashTestnet.toNetworkId(), BIP44_PURPOSE)
|
||||
put(Blockchain.Dogecoin.toNetworkId(), BIP44_PURPOSE)
|
||||
put(Blockchain.Dash.toNetworkId(), BIP44_PURPOSE)
|
||||
put(Blockchain.Ravencoin.toNetworkId(), BIP44_PURPOSE)
|
||||
put(Blockchain.RavencoinTestnet.toNetworkId(), BIP44_PURPOSE)
|
||||
}
|
||||
|
||||
fun isSupported(blockchain: Blockchain): Boolean = blockchain in supported
|
||||
|
||||
fun isSupportedByNetworkId(networkId: String): Boolean = networkId in supportedNetworkIds
|
||||
|
||||
/** Returns the allowed BIP purpose node for the given network, or null if not supported */
|
||||
fun getAllowedPurpose(networkId: String): Long? = allowedPurposeByNetworkId[networkId]
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.domain.dynamicaddresses
|
||||
|
||||
sealed class EnableDynamicAddressesError {
|
||||
|
||||
data object ConflictingCustomTokens : EnableDynamicAddressesError()
|
||||
|
||||
data class ServiceError(val cause: Throwable) : EnableDynamicAddressesError()
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.domain.dynamicaddresses
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.dynamicaddresses.repository.DynamicAddressesRepository
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
|
@ -9,8 +11,19 @@ class EnableDynamicAddressesUseCase(
|
|||
private val dynamicAddressesRepository: DynamicAddressesRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(userWalletId: UserWalletId, network: Network, xpub: String): Either<Throwable, Unit> =
|
||||
Either.catch {
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
xpub: String,
|
||||
): Either<EnableDynamicAddressesError, Unit> {
|
||||
return try {
|
||||
if (dynamicAddressesRepository.hasConflictingCustomTokens(userWalletId, network)) {
|
||||
return EnableDynamicAddressesError.ConflictingCustomTokens.left()
|
||||
}
|
||||
dynamicAddressesRepository.enable(userWalletId, network, xpub)
|
||||
Unit.right()
|
||||
} catch (e: Throwable) {
|
||||
EnableDynamicAddressesError.ServiceError(e).left()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.tangem.domain.dynamicaddresses
|
||||
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.common.extensions.calculateRipemd160
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.crypto.NetworkType
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.derivations.DerivationsRepository
|
||||
|
||||
/**
|
||||
* Returns the XPUB string if account-level keys are already derived (no card scan needed),
|
||||
* or null if keys are not available.
|
||||
*/
|
||||
class GetDerivedXpubUseCase(
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val derivationsRepository: DerivationsRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(userWalletId: UserWalletId, network: Network): String? {
|
||||
val blockchain = network.toBlockchain()
|
||||
if (!DynamicAddressesSupportedBlockchains.isSupported(blockchain)) return null
|
||||
if (!blockchain.isBip44DerivationStyleXPUB()) return null
|
||||
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(userWalletId, network) ?: return null
|
||||
val hdKey = walletManager.wallet.publicKey.derivationType?.hdKey ?: return null
|
||||
if (hdKey.path.nodes.size <= ACCOUNT_PATH_DROP_COUNT) return null
|
||||
|
||||
val seedKey = ByteArrayKey(walletManager.wallet.publicKey.seedKey)
|
||||
val existingKeys = derivationsRepository.getExistingDerivedKeys(userWalletId, seedKey)
|
||||
|
||||
val accountPath = DerivationPath(hdKey.path.nodes.dropLast(ACCOUNT_PATH_DROP_COUNT))
|
||||
val parentPath = DerivationPath(accountPath.nodes.dropLast(1))
|
||||
|
||||
val childExtKey = existingKeys[accountPath] ?: return null
|
||||
val parentExtKey = existingKeys[parentPath] ?: return null
|
||||
|
||||
val parentFingerprint = parentExtKey.publicKey
|
||||
.calculateSha256().calculateRipemd160()
|
||||
.take(PARENT_FINGERPRINT_SIZE).toByteArray()
|
||||
|
||||
val net = if (blockchain.isTestnet()) NetworkType.Testnet else NetworkType.Mainnet
|
||||
return ExtendedPublicKey(
|
||||
publicKey = childExtKey.publicKey,
|
||||
chainCode = childExtKey.chainCode,
|
||||
depth = accountPath.nodes.size,
|
||||
parentFingerprint = parentFingerprint,
|
||||
childNumber = accountPath.nodes.last().index,
|
||||
).serialize(net)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val ACCOUNT_PATH_DROP_COUNT = 2
|
||||
const val PARENT_FINGERPRINT_SIZE = 4
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.domain.dynamicaddresses
|
||||
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
|
||||
/**
|
||||
* Checks if XPUB generation is supported for the given wallet and network (hardware capability check).
|
||||
*/
|
||||
class IsXpubSupportedUseCase(
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(userWalletId: UserWalletId, network: Network): Boolean {
|
||||
val blockchain = network.toBlockchain()
|
||||
if (!DynamicAddressesSupportedBlockchains.isSupported(blockchain)) return false
|
||||
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(userWalletId, network) ?: return false
|
||||
return walletManager.wallet.publicKey.derivationType?.hdKey != null
|
||||
}
|
||||
}
|
||||
|
|
@ -19,4 +19,7 @@ interface DynamicAddressesRepository {
|
|||
suspend fun getLastUsedReceiveAddress(userWalletId: UserWalletId, network: Network): String?
|
||||
|
||||
suspend fun hasNonBaseBalances(userWalletId: UserWalletId, network: Network): Boolean
|
||||
|
||||
/** Returns true if there are custom tokens with change/index ≠ 0 that conflict with dynamic addresses */
|
||||
suspend fun hasConflictingCustomTokens(userWalletId: UserWalletId, network: Network): Boolean
|
||||
}
|
||||
|
|
@ -56,7 +56,7 @@ class GetEarnNetworksUseCase(
|
|||
accountLists
|
||||
.filter { it.userWalletId in unlockedWalletsId }
|
||||
.flatMap(AccountList::flattenCurrencies)
|
||||
.mapTo(HashSet()) { it.network.backendId }
|
||||
.mapTo(HashSet()) { it.network.rawId }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
package com.tangem.domain.feedback.models
|
||||
|
||||
import com.tangem.domain.models.network.Network
|
||||
|
||||
/**
|
||||
* Information about blockchain's operation error
|
||||
*
|
||||
* @property errorMessage message about error
|
||||
* @property blockchainId blockchain id
|
||||
* @property derivationPath derivation path
|
||||
* @property networkId network ID
|
||||
* @property destinationAddress destination address
|
||||
* @property tokenSymbol token symbol or null, if it isn't operation with token
|
||||
* @property amount amount
|
||||
|
|
@ -13,8 +14,7 @@ package com.tangem.domain.feedback.models
|
|||
*/
|
||||
data class BlockchainErrorInfo(
|
||||
val errorMessage: String,
|
||||
val blockchainId: String,
|
||||
val derivationPath: String?,
|
||||
val networkId: Network.ID?,
|
||||
val destinationAddress: String,
|
||||
val tokenSymbol: String?,
|
||||
val amount: String,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.domain.feedback.repository
|
||||
|
||||
import com.tangem.domain.feedback.models.*
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import java.io.File
|
||||
|
|
@ -17,11 +18,7 @@ interface FeedbackRepository {
|
|||
|
||||
fun getPhoneInfo(): PhoneInfo
|
||||
|
||||
suspend fun getBlockchainInfo(
|
||||
userWalletId: UserWalletId,
|
||||
blockchainId: String,
|
||||
derivationPath: String?,
|
||||
): BlockchainInfo?
|
||||
suspend fun getBlockchainInfo(userWalletId: UserWalletId, networkId: Network.ID): BlockchainInfo?
|
||||
|
||||
fun saveBlockchainErrorInfo(error: BlockchainErrorInfo)
|
||||
|
||||
|
|
|
|||
|
|
@ -94,11 +94,10 @@ class EmailMessageBodyResolver(
|
|||
|
||||
val userWalletId = requireNotNull(type.walletMetaInfo.userWalletId) { "UserWalletId must be not null" }
|
||||
val blockchainError = feedbackRepository.getBlockchainErrorInfo(userWalletId = userWalletId)
|
||||
val blockchainInfo = blockchainError?.let {
|
||||
val blockchainInfo = blockchainError?.networkId?.let { networkId ->
|
||||
feedbackRepository.getBlockchainInfo(
|
||||
userWalletId = userWalletId,
|
||||
blockchainId = blockchainError.blockchainId,
|
||||
derivationPath = blockchainError.derivationPath,
|
||||
networkId = networkId,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -159,11 +158,10 @@ class EmailMessageBodyResolver(
|
|||
|
||||
val userWalletId = requireNotNull(walletMetaInfo.userWalletId) { "UserWalletId must be not null" }
|
||||
val blockchainError = feedbackRepository.getBlockchainErrorInfo(userWalletId = userWalletId)
|
||||
val blockchainInfo = blockchainError?.let {
|
||||
val blockchainInfo = blockchainError?.networkId?.let { networkId ->
|
||||
feedbackRepository.getBlockchainInfo(
|
||||
userWalletId = userWalletId,
|
||||
blockchainId = blockchainError.blockchainId,
|
||||
derivationPath = blockchainError.derivationPath,
|
||||
networkId = networkId,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -181,11 +179,10 @@ class EmailMessageBodyResolver(
|
|||
|
||||
val userWalletId = requireNotNull(type.walletMetaInfo.userWalletId) { "UserWalletId must be not null" }
|
||||
val blockchainError = feedbackRepository.getBlockchainErrorInfo(userWalletId = userWalletId)
|
||||
val blockchainInfo = blockchainError?.let {
|
||||
val blockchainInfo = blockchainError?.networkId?.let { networkId ->
|
||||
feedbackRepository.getBlockchainInfo(
|
||||
userWalletId = userWalletId,
|
||||
blockchainId = blockchainError.blockchainId,
|
||||
derivationPath = blockchainError.derivationPath,
|
||||
networkId = networkId,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -210,11 +207,10 @@ class EmailMessageBodyResolver(
|
|||
|
||||
val userWalletId = requireNotNull(type.walletMetaInfo.userWalletId) { "UserWalletId must be not null" }
|
||||
val blockchainError = feedbackRepository.getBlockchainErrorInfo(userWalletId = userWalletId)
|
||||
val blockchainInfo = blockchainError?.let {
|
||||
val blockchainInfo = blockchainError?.networkId?.let { networkId ->
|
||||
feedbackRepository.getBlockchainInfo(
|
||||
userWalletId = userWalletId,
|
||||
blockchainId = blockchainError.blockchainId,
|
||||
derivationPath = blockchainError.derivationPath,
|
||||
networkId = networkId,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
package com.tangem.domain.redux
|
||||
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed interface LegacyAction : Action {
|
||||
|
||||
data object PrepareDetailsScreen : LegacyAction
|
||||
}
|
||||
|
|
@ -18,11 +18,11 @@ data class TokenReceiveConfig(
|
|||
|
||||
@Serializable
|
||||
data class ReceiveAddressModel(
|
||||
val nameService: NameService,
|
||||
val displayType: DisplayType,
|
||||
val value: String,
|
||||
) {
|
||||
enum class NameService {
|
||||
Default, Legacy, Ens
|
||||
enum class DisplayType {
|
||||
Default, Legacy, Ens, Dynamic,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.domain.models.account
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.raise.ensure
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@ConsistentCopyVisibility
|
||||
data class CardDisplayName private constructor(val value: String) {
|
||||
|
||||
@Serializable
|
||||
sealed interface Error {
|
||||
@Serializable
|
||||
data object Empty : Error
|
||||
|
||||
@Serializable
|
||||
data object ExceedsMaxLength : Error
|
||||
|
||||
@Serializable
|
||||
data object InvalidCharacters : Error
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val MAX_LENGTH = 20
|
||||
private val allowedPattern = Regex("^[\\p{L}\\p{N} ]+$")
|
||||
|
||||
operator fun invoke(name: String): Either<Error, CardDisplayName> = either {
|
||||
val trimmed = name.trim()
|
||||
ensure(trimmed.isNotEmpty()) { Error.Empty }
|
||||
ensure(trimmed.length <= MAX_LENGTH) { Error.ExceedsMaxLength }
|
||||
ensure(allowedPattern.matches(trimmed)) { Error.InvalidCharacters }
|
||||
CardDisplayName(trimmed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,9 +2,13 @@ package com.tangem.domain.models.account
|
|||
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.kyc.KycStatus
|
||||
import com.tangem.domain.models.network.NetworkAddress
|
||||
import com.tangem.domain.models.serialization.SerializedBigDecimal
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Represents the various states a payment account can have, encapsulating different information based on the state.
|
||||
|
|
@ -104,7 +108,30 @@ sealed class PaymentAccountStatusValue {
|
|||
val isPinSet: Boolean,
|
||||
val fiatBalance: FiatBalance,
|
||||
val cryptoBalance: CryptoBalance,
|
||||
) : PaymentAccountStatusValue()
|
||||
val cryptoCurrency: CryptoCurrency.Token,
|
||||
val displayName: CardDisplayName?,
|
||||
) : PaymentAccountStatusValue() {
|
||||
val cryptoCurrencyStatus: CryptoCurrencyStatus = CryptoCurrencyStatus(
|
||||
currency = cryptoCurrency,
|
||||
value = CryptoCurrencyStatus.Loaded(
|
||||
amount = cryptoBalance.balance,
|
||||
fiatAmount = fiatBalance.availableBalance,
|
||||
fiatRate = BigDecimal.ONE,
|
||||
priceChange = BigDecimal.ZERO,
|
||||
networkAddress = NetworkAddress.Single(
|
||||
defaultAddress = NetworkAddress.Address(
|
||||
type = NetworkAddress.Address.Type.Primary,
|
||||
value = cryptoBalance.depositAddress,
|
||||
),
|
||||
),
|
||||
sources = CryptoCurrencyStatus.Sources(),
|
||||
pendingTransactions = emptySet(),
|
||||
stakingBalance = null,
|
||||
yieldSupplyStatus = null,
|
||||
hasCurrentNetworkTransactions = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a state where the payment account is successfully loaded with complete information.
|
||||
|
|
@ -130,7 +157,30 @@ sealed class PaymentAccountStatusValue {
|
|||
val isPinSet: Boolean,
|
||||
val fiatBalance: FiatBalance,
|
||||
val cryptoBalance: CryptoBalance,
|
||||
) : PaymentAccountStatusValue()
|
||||
val cryptoCurrency: CryptoCurrency.Token,
|
||||
val displayName: CardDisplayName?,
|
||||
) : PaymentAccountStatusValue() {
|
||||
val cryptoCurrencyStatus: CryptoCurrencyStatus = CryptoCurrencyStatus(
|
||||
currency = cryptoCurrency,
|
||||
value = CryptoCurrencyStatus.Loaded(
|
||||
amount = cryptoBalance.balance,
|
||||
fiatAmount = fiatBalance.availableBalance,
|
||||
fiatRate = BigDecimal.ONE,
|
||||
priceChange = BigDecimal.ZERO,
|
||||
networkAddress = NetworkAddress.Single(
|
||||
defaultAddress = NetworkAddress.Address(
|
||||
type = NetworkAddress.Address.Type.Primary,
|
||||
value = cryptoBalance.depositAddress,
|
||||
),
|
||||
),
|
||||
sources = CryptoCurrencyStatus.Sources(),
|
||||
pendingTransactions = emptySet(),
|
||||
stakingBalance = null,
|
||||
yieldSupplyStatus = null,
|
||||
hasCurrentNetworkTransactions = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/** Represents an error state for the payment account status. */
|
||||
@Serializable
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.tangem.domain.models.currency
|
|||
import java.math.BigDecimal
|
||||
|
||||
fun CryptoCurrency.Token.yieldSupplyKey(): String {
|
||||
return "${network.backendId}_$contractAddress"
|
||||
return "${network.rawId}_$contractAddress"
|
||||
}
|
||||
|
||||
fun CryptoCurrencyStatus.hasNotSuppliedAmount(): Boolean {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import kotlinx.serialization.Serializable
|
|||
* (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 currencySymbol the symbol of the currency associated with the network
|
||||
* @property derivationPath the path used to derive keys for this network
|
||||
|
|
@ -25,7 +24,6 @@ import kotlinx.serialization.Serializable
|
|||
@Serializable
|
||||
data class Network(
|
||||
val id: ID,
|
||||
val backendId: String,
|
||||
val name: String,
|
||||
val currencySymbol: String,
|
||||
val derivationPath: DerivationPath,
|
||||
|
|
@ -49,7 +47,7 @@ data class Network(
|
|||
/**
|
||||
* Represents a unique identifier for a blockchain network
|
||||
*
|
||||
* @property rawId raw network ID
|
||||
* @property rawId raw network ID (backend id)
|
||||
* @property derivationPath derivation path
|
||||
*/
|
||||
@Serializable
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>UnnecessaryAbstractClass:MultiNetworkStatusSupplier.kt$MultiNetworkStatusSupplier$MultiNetworkStatusSupplier</ID>
|
||||
<ID>UnnecessaryAbstractClass:SingleNetworkStatusSupplier.kt$SingleNetworkStatusSupplier$SingleNetworkStatusSupplier</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -11,7 +11,7 @@ import com.tangem.domain.models.network.NetworkStatus
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
abstract class MultiNetworkStatusSupplier(
|
||||
open class MultiNetworkStatusSupplier(
|
||||
override val factory: MultiNetworkStatusProducer.Factory,
|
||||
override val keyCreator: (MultiNetworkStatusProducer.Params) -> String,
|
||||
) : FlowCachingSupplier<MultiNetworkStatusProducer, MultiNetworkStatusProducer.Params, Set<NetworkStatus>>()
|
||||
|
|
@ -11,7 +11,7 @@ import com.tangem.domain.models.network.NetworkStatus
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
abstract class SingleNetworkStatusSupplier(
|
||||
open class SingleNetworkStatusSupplier(
|
||||
override val factory: SingleNetworkStatusProducer.Factory,
|
||||
override val keyCreator: (SingleNetworkStatusProducer.Params) -> String,
|
||||
) : FlowCachingSupplier<SingleNetworkStatusProducer, SingleNetworkStatusProducer.Params, NetworkStatus>()
|
||||
|
|
@ -3,5 +3,5 @@ package com.tangem.domain.search.model
|
|||
data class SearchResult(
|
||||
val textHints: List<SearchTextHint>,
|
||||
val recentTokens: List<RecentSearchToken>,
|
||||
val userAssets: List<UserAssetSearchEntry>,
|
||||
val userAssets: List<UserAssetSearchItem>,
|
||||
)
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.domain.search.model
|
|||
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.account.AccountName
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
|
|
@ -10,5 +11,6 @@ data class UserAssetSearchEntry(
|
|||
val userWalletName: String,
|
||||
val accountId: AccountId,
|
||||
val accountName: AccountName,
|
||||
val accountIcon: CryptoPortfolioIcon,
|
||||
val currencyStatus: CryptoCurrencyStatus,
|
||||
)
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.domain.search.model
|
||||
|
||||
sealed interface UserAssetSearchItem {
|
||||
|
||||
data class Single(val entry: UserAssetSearchEntry) : UserAssetSearchItem
|
||||
|
||||
data class Grouped(
|
||||
val tokenName: String,
|
||||
val tokenSymbol: String,
|
||||
val tokenIconUrl: String?,
|
||||
val entries: List<UserAssetSearchEntry>,
|
||||
) : UserAssetSearchItem
|
||||
}
|
||||
|
|
@ -9,10 +9,12 @@ import com.tangem.domain.models.wallet.UserWalletId
|
|||
import com.tangem.domain.models.wallet.isLocked
|
||||
import com.tangem.domain.search.model.SearchResult
|
||||
import com.tangem.domain.search.model.UserAssetSearchEntry
|
||||
import com.tangem.domain.search.model.UserAssetSearchItem
|
||||
import com.tangem.domain.search.repository.SearchRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Primary search use case that produces [SearchResult] based on the current query.
|
||||
|
|
@ -70,9 +72,12 @@ class GetSearchResultsUseCase(
|
|||
|
||||
if (unlockedWallets.isEmpty()) return@combine emptyList()
|
||||
|
||||
statusLists
|
||||
val entries = statusLists
|
||||
.filter { it.userWalletId in unlockedWallets }
|
||||
.flatMap { statusList -> extractMatchingAssets(statusList, unlockedWallets, lowerQuery) }
|
||||
|
||||
val shouldGroup = needsGrouping(unlockedWallets.values, statusLists)
|
||||
groupAndSort(entries, shouldGroup)
|
||||
}.map { userAssets ->
|
||||
SearchResult(
|
||||
textHints = emptyList(),
|
||||
|
|
@ -82,6 +87,44 @@ class GetSearchResultsUseCase(
|
|||
}
|
||||
}
|
||||
|
||||
private fun needsGrouping(unlockedWallets: Collection<UserWallet>, statusLists: List<AccountStatusList>): Boolean {
|
||||
if (unlockedWallets.size > 1) return true
|
||||
|
||||
val totalAccounts = statusLists
|
||||
.filter { sl -> unlockedWallets.any { it.walletId == sl.userWalletId } }
|
||||
.sumOf { it.accountStatuses.filterCryptoPortfolio().size }
|
||||
|
||||
return totalAccounts > 1
|
||||
}
|
||||
|
||||
private fun groupAndSort(entries: List<UserAssetSearchEntry>, shouldGroup: Boolean): List<UserAssetSearchItem> {
|
||||
if (!shouldGroup) {
|
||||
return entries
|
||||
.map { UserAssetSearchItem.Single(it) }
|
||||
.sortedByDescending { it.entry.currencyStatus.value.fiatAmount ?: BigDecimal.ZERO }
|
||||
}
|
||||
|
||||
val grouped = entries.groupBy { entry ->
|
||||
val rawId = entry.currencyStatus.currency.id.rawCurrencyId
|
||||
rawId?.value ?: "${entry.currencyStatus.currency.name}|${entry.currencyStatus.currency.symbol}"
|
||||
}
|
||||
|
||||
return grouped.map { (_, groupEntries) ->
|
||||
val assetInfo = groupEntries.first()
|
||||
UserAssetSearchItem.Grouped(
|
||||
tokenName = assetInfo.currencyStatus.currency.name,
|
||||
tokenSymbol = assetInfo.currencyStatus.currency.symbol,
|
||||
tokenIconUrl = assetInfo.currencyStatus.currency.iconUrl,
|
||||
entries = groupEntries,
|
||||
)
|
||||
}.sortedByDescending { item ->
|
||||
when (item) {
|
||||
is UserAssetSearchItem.Grouped ->
|
||||
item.entries.sumOf { it.currencyStatus.value.fiatAmount ?: BigDecimal.ZERO }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractMatchingAssets(
|
||||
statusList: AccountStatusList,
|
||||
wallets: Map<UserWalletId, UserWallet>,
|
||||
|
|
@ -103,6 +146,7 @@ class GetSearchResultsUseCase(
|
|||
userWalletName = wallet.name,
|
||||
accountId = accountStatus.accountId,
|
||||
accountName = accountStatus.account.accountName,
|
||||
accountIcon = accountStatus.account.icon,
|
||||
currencyStatus = currencyStatus,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>MultilineLambdaItParameter:FetchStakingYieldBalanceUseCase.kt$FetchStakingYieldBalanceUseCase${ when (it) { is StakingIdFactory.Error.UnableToGetAddress -> raise(StakingError.DomainError("$it")) StakingIdFactory.Error.UnsupportedCurrency -> Unit.right() } return@either }</ID>
|
||||
<ID>MultilineLambdaItParameter:InvalidatePendingTransactionsUseCase.kt$InvalidatePendingTransactionsUseCase${ !it.isPending && action.amount < it.amount && it.type == BalanceType.STAKED && it.validatorAddress == action.validatorAddress }</ID>
|
||||
<ID>NamedArguments:GetConstructedStakingTransactionUseCase.kt$GetConstructedStakingTransactionUseCase$constructTransaction(networkId, fee, amount, transactionId)</ID>
|
||||
<ID>UnnecessaryAbstractClass:MultiStakingBalanceSupplier.kt$MultiStakingBalanceSupplier$MultiStakingBalanceSupplier</ID>
|
||||
<ID>UnnecessaryAbstractClass:SingleStakingBalanceSupplier.kt$SingleStakingBalanceSupplier$SingleStakingBalanceSupplier</ID>
|
||||
<ID>UseEmptyCounterpart:StakingAnalyticsEvent.kt$StakingAnalyticsEvent$mapOf()</ID>
|
||||
<ID>UseOrEmpty:InvalidatePendingTransactionsUseCase.kt$InvalidatePendingTransactionsUseCase$action.validatorAddress ?: action.validatorAddresses?.getOrNull(0) ?: ""</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -23,9 +23,9 @@ class FetchStakingYieldBalanceUseCase(
|
|||
currencyId = cryptoCurrency.id,
|
||||
network = cryptoCurrency.network,
|
||||
)
|
||||
.getOrElse {
|
||||
when (it) {
|
||||
is StakingIdFactory.Error.UnableToGetAddress -> raise(StakingError.DomainError("$it"))
|
||||
.getOrElse { error ->
|
||||
when (error) {
|
||||
is StakingIdFactory.Error.UnableToGetAddress -> raise(StakingError.DomainError("$error"))
|
||||
StakingIdFactory.Error.UnsupportedCurrency -> Unit.right()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,10 +4,11 @@ import arrow.core.Either
|
|||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.staking.model.stakekit.StakingError
|
||||
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction
|
||||
import com.tangem.domain.staking.repositories.StakingErrorResolver
|
||||
import com.tangem.domain.staking.repositories.StakeKitRepository
|
||||
import com.tangem.domain.staking.repositories.StakingErrorResolver
|
||||
|
||||
class GetConstructedStakingTransactionUseCase(
|
||||
private val stakeKitRepository: StakeKitRepository,
|
||||
|
|
@ -15,12 +16,17 @@ class GetConstructedStakingTransactionUseCase(
|
|||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
networkId: String,
|
||||
networkId: Network.RawID,
|
||||
fee: Fee,
|
||||
amount: Amount,
|
||||
transactionId: String,
|
||||
): Either<StakingError, Pair<StakingTransaction, TransactionData.Compiled>> = Either.catch {
|
||||
stakeKitRepository.constructTransaction(networkId, fee, amount, transactionId)
|
||||
stakeKitRepository.constructTransaction(
|
||||
networkId = networkId,
|
||||
fee = fee,
|
||||
amount = amount,
|
||||
transactionId = transactionId,
|
||||
)
|
||||
}.mapLeft {
|
||||
stakingErrorResolver.resolve(it)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ class InvalidatePendingTransactionsUseCase(
|
|||
type = BalanceType.STAKED,
|
||||
amount = action.amount,
|
||||
rawCurrencyId = null,
|
||||
validatorAddress = action.validatorAddress ?: action.validatorAddresses?.getOrNull(0) ?: "",
|
||||
validatorAddress = action.validatorAddress ?: action.validatorAddresses?.getOrNull(0).orEmpty(),
|
||||
date = null,
|
||||
pendingActions = emptyList(),
|
||||
pendingActionsConstraints = emptyList(),
|
||||
|
|
@ -149,10 +149,10 @@ class InvalidatePendingTransactionsUseCase(
|
|||
}
|
||||
|
||||
private fun findPartialUnstake(balances: MutableList<BalanceItem>, action: StakingAction): Pair<Int, BigDecimal> {
|
||||
val index = balances.indexOfFirst {
|
||||
!it.isPending && action.amount < it.amount &&
|
||||
it.type == BalanceType.STAKED &&
|
||||
it.validatorAddress == action.validatorAddress
|
||||
val index = balances.indexOfFirst { balance ->
|
||||
!balance.isPending && action.amount < balance.amount &&
|
||||
balance.type == BalanceType.STAKED &&
|
||||
balance.validatorAddress == action.validatorAddress
|
||||
}
|
||||
return index to action.amount
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,23 +2,27 @@ package com.tangem.domain.staking
|
|||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.raise.ensure
|
||||
import arrow.core.raise.ensureNotNull
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.staking.StakingID
|
||||
import com.tangem.domain.staking.model.StakingIntegrationID
|
||||
import com.tangem.domain.staking.toggles.StakingFeatureToggles
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
|
||||
/**
|
||||
* Factory class for creating instances of [StakingID]
|
||||
*
|
||||
* @property walletManagersFacade wallet manager facade
|
||||
* @property walletManagersFacade wallet manager facade
|
||||
* @property stakingFeatureToggles staking feature toggles
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class StakingIdFactory(
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val stakingFeatureToggles: StakingFeatureToggles,
|
||||
) {
|
||||
|
||||
/**
|
||||
|
|
@ -72,6 +76,8 @@ class StakingIdFactory(
|
|||
|
||||
ensureNotNull(integrationId) { Error.UnsupportedCurrency }
|
||||
|
||||
ensure(stakingFeatureToggles.isIntegrationEnabled(integrationId)) { Error.UnsupportedCurrency }
|
||||
|
||||
val address = defaultAddressProvider().takeUnless { it.isNullOrEmpty() }
|
||||
|
||||
ensureNotNull(address) { Error.UnableToGetAddress(integrationId = integrationId) }
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import com.tangem.domain.models.staking.action.StakingActionType
|
|||
|
||||
sealed class StakingAnalyticsEvent(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
params: Map<String, String> = emptyMap(),
|
||||
) : AnalyticsEvent(
|
||||
category = "Staking",
|
||||
event = event,
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ sealed interface StakingIntegrationID {
|
|||
* @return a [StakingIntegrationID] if supported, or `null` if not supported.
|
||||
*/
|
||||
fun create(currencyId: CryptoCurrency.ID): StakingIntegrationID? {
|
||||
val blockchain = Blockchain.fromId(id = currencyId.rawNetworkId)
|
||||
val blockchain = currencyId.toBlockchain()
|
||||
|
||||
return if (currencyId.contractAddress.isNullOrBlank()) {
|
||||
// Order is not important — either P2PEthPool or Stakekit.Coin can be in any order
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import com.tangem.domain.models.staking.StakingBalance
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
abstract class MultiStakingBalanceSupplier(
|
||||
open class MultiStakingBalanceSupplier(
|
||||
override val factory: FlowProducer.Factory<MultiStakingBalanceProducer.Params, MultiStakingBalanceProducer>,
|
||||
override val keyCreator: (MultiStakingBalanceProducer.Params) -> String,
|
||||
) : FlowCachingSupplier<MultiStakingBalanceProducer, MultiStakingBalanceProducer.Params, Set<StakingBalance>>()
|
||||
|
|
@ -5,11 +5,11 @@ import com.tangem.blockchain.common.TransactionData
|
|||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.staking.NetworkType
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.domain.staking.model.StakingEntryInfo
|
||||
import com.tangem.domain.staking.model.StakingIntegrationID
|
||||
import com.tangem.domain.models.staking.NetworkType
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingAction
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus
|
||||
|
|
@ -55,7 +55,7 @@ interface StakeKitRepository {
|
|||
suspend fun estimateGas(userWalletId: UserWalletId, network: Network, params: ActionParams): StakingGasEstimate
|
||||
|
||||
suspend fun constructTransaction(
|
||||
networkId: String,
|
||||
networkId: Network.RawID,
|
||||
fee: Fee,
|
||||
amount: Amount,
|
||||
transactionId: String,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import com.tangem.domain.models.staking.StakingBalance
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
abstract class SingleStakingBalanceSupplier(
|
||||
open class SingleStakingBalanceSupplier(
|
||||
override val factory: FlowProducer.Factory<SingleStakingBalanceProducer.Params, SingleStakingBalanceProducer>,
|
||||
override val keyCreator: (SingleStakingBalanceProducer.Params) -> String,
|
||||
) : FlowCachingSupplier<SingleStakingBalanceProducer, SingleStakingBalanceProducer.Params, StakingBalance>()
|
||||
|
|
@ -1,5 +1,8 @@
|
|||
package com.tangem.domain.staking.toggles
|
||||
|
||||
import com.tangem.domain.staking.model.StakingIntegrationID
|
||||
|
||||
interface StakingFeatureToggles {
|
||||
val isEthStakingEnabled: Boolean
|
||||
|
||||
fun isIntegrationEnabled(integrationId: StakingIntegrationID): Boolean
|
||||
}
|
||||
|
|
@ -5,17 +5,16 @@ import arrow.core.left
|
|||
import arrow.core.right
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.staking.StakingID
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.staking.model.StakingIntegrationID
|
||||
import com.tangem.domain.staking.toggles.StakingFeatureToggles
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.test.core.ProvideTestModels
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.mockk
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Nested
|
||||
|
|
@ -30,11 +29,16 @@ import org.junit.jupiter.params.ParameterizedTest
|
|||
internal class StakingIdFactoryTest {
|
||||
|
||||
private val walletManagersFacade: WalletManagersFacade = mockk()
|
||||
private val factory = StakingIdFactory(walletManagersFacade = walletManagersFacade)
|
||||
private val stakingFeatureToggles: StakingFeatureToggles = mockk()
|
||||
private val factory = StakingIdFactory(
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
stakingFeatureToggles = stakingFeatureToggles,
|
||||
)
|
||||
|
||||
@BeforeEach
|
||||
fun resetMocks() {
|
||||
clearMocks(walletManagersFacade)
|
||||
clearMocks(walletManagersFacade, stakingFeatureToggles)
|
||||
every { stakingFeatureToggles.isIntegrationEnabled(any()) } returns true
|
||||
}
|
||||
|
||||
@Nested
|
||||
|
|
@ -66,6 +70,33 @@ internal class StakingIdFactoryTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `create returns UnsupportedCurrency if integration is disabled by toggle`() = runTest {
|
||||
// Arrange
|
||||
val userWalletId = UserWalletId(stringValue = "011")
|
||||
val currency = MockCryptoCurrencyFactory().createCoin(Blockchain.TON)
|
||||
|
||||
every {
|
||||
stakingFeatureToggles.isIntegrationEnabled(StakingIntegrationID.StakeKit.Coin.Ton)
|
||||
} returns false
|
||||
|
||||
// Act
|
||||
val actual = factory.create(
|
||||
userWalletId = userWalletId,
|
||||
currencyId = currency.id,
|
||||
network = currency.network,
|
||||
)
|
||||
|
||||
// Assert
|
||||
val expected = StakingIdFactory.Error.UnsupportedCurrency
|
||||
|
||||
Truth.assertThat(actual.leftOrNull()).isEqualTo(expected)
|
||||
|
||||
coVerify(inverse = true) {
|
||||
walletManagersFacade.getDefaultAddress(userWalletId = any(), network = any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `create returns UnableToGetAddress if address is null`() = runTest {
|
||||
// Arrange
|
||||
|
|
@ -154,7 +185,7 @@ internal class StakingIdFactoryTest {
|
|||
),
|
||||
CreateModel(
|
||||
currencyId = CryptoCurrency.ID.fromValue(
|
||||
value = "token⟨ETH⟩polygon-ecosystem-token⚓0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0",
|
||||
value = "token⟨ethereum⟩polygon-ecosystem-token⚓0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0",
|
||||
),
|
||||
expected = createStakingId(integrationId = StakingIntegrationID.StakeKit.EthereumToken.Polygon),
|
||||
),
|
||||
|
|
@ -168,6 +199,6 @@ internal class StakingIdFactoryTest {
|
|||
data class CreateModel(val currencyId: CryptoCurrency.ID, val expected: Either<StakingIdFactory.Error, StakingID>)
|
||||
|
||||
private fun createCurrencyId(blockchain: Blockchain): CryptoCurrency.ID {
|
||||
return CryptoCurrency.ID.fromValue(value = "coin⟨${blockchain.id}⟩")
|
||||
return CryptoCurrency.ID.fromValue(value = "coin⟨${blockchain.toNetworkId()}⟩")
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.domain.staking
|
|||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.staking.model.StakingApproval
|
||||
import com.tangem.domain.staking.model.StakingIntegrationID
|
||||
|
|
@ -144,11 +145,11 @@ class StakingIntegrationIDTest {
|
|||
expected = StakingIntegrationID.P2PEthPool,
|
||||
),
|
||||
CreateModel(
|
||||
currencyId = CryptoCurrency.ID.fromValue(value = "token⟨ETH⟩polygon-ecosystem-token⚓1234567890"),
|
||||
currencyId = CryptoCurrency.ID.fromValue(value = "token⟨ethereum⟩polygon-ecosystem-token⚓1234567890"),
|
||||
expected = StakingIntegrationID.StakeKit.EthereumToken.Polygon,
|
||||
),
|
||||
CreateModel(
|
||||
currencyId = CryptoCurrency.ID.fromValue(value = "token⟨SOLANA⟩solana⚓1234567890"),
|
||||
currencyId = CryptoCurrency.ID.fromValue(value = "token⟨solana⟩solana⚓1234567890"),
|
||||
expected = null,
|
||||
),
|
||||
)
|
||||
|
|
@ -157,6 +158,6 @@ class StakingIntegrationIDTest {
|
|||
data class CreateModel(val currencyId: CryptoCurrency.ID, val expected: StakingIntegrationID?)
|
||||
|
||||
private fun createCurrencyId(blockchain: Blockchain): CryptoCurrency.ID {
|
||||
return CryptoCurrency.ID.fromValue(value = "coin⟨${blockchain.id}⟩")
|
||||
return CryptoCurrency.ID.fromValue(value = "coin⟨${blockchain.toNetworkId()}⟩")
|
||||
}
|
||||
}
|
||||
|
|
@ -190,7 +190,7 @@ class WalletBalanceFetcher internal constructor(
|
|||
private suspend fun fetchExpressAssets(userWallet: UserWallet, currencies: Set<CryptoCurrency>) {
|
||||
val assetIds = currencies.mapTo(hashSetOf()) { currency ->
|
||||
ExpressAsset.ID(
|
||||
networkId = currency.network.backendId,
|
||||
networkId = currency.network.rawId,
|
||||
contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ internal object MockNetworks {
|
|||
name = "Network One",
|
||||
isTestnet = false,
|
||||
standardType = Network.StandardType.ERC20,
|
||||
backendId = "network1",
|
||||
currencySymbol = "ETH",
|
||||
derivationPath = Network.DerivationPath.None,
|
||||
hasFiatFeeRate = true,
|
||||
|
|
@ -32,7 +31,6 @@ internal object MockNetworks {
|
|||
name = "Network Two",
|
||||
isTestnet = false,
|
||||
standardType = Network.StandardType.ERC20,
|
||||
backendId = "network1",
|
||||
currencySymbol = "ETH",
|
||||
derivationPath = Network.DerivationPath.None,
|
||||
hasFiatFeeRate = true,
|
||||
|
|
@ -46,7 +44,6 @@ internal object MockNetworks {
|
|||
name = "Network Three",
|
||||
isTestnet = false,
|
||||
standardType = Network.StandardType.ERC20,
|
||||
backendId = "network1",
|
||||
currencySymbol = "ETH",
|
||||
derivationPath = Network.DerivationPath.None,
|
||||
hasFiatFeeRate = true,
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
package com.tangem.domain.tokensync.repository
|
||||
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.tokensync.model.TokenSyncProgress
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface TokenSyncRepository {
|
||||
|
||||
suspend fun runSync(userWalletId: UserWalletId)
|
||||
|
||||
suspend fun completeSync(userWalletId: UserWalletId)
|
||||
|
||||
suspend fun getPendingSyncWalletIds(): List<UserWalletId>
|
||||
|
||||
fun observeSyncProgress(userWalletId: UserWalletId): Flow<TokenSyncProgress>
|
||||
|
||||
fun acknowledgeCompletion(userWalletId: UserWalletId)
|
||||
|
||||
suspend fun clearPendingFlag(userWalletId: UserWalletId)
|
||||
|
||||
suspend fun getDiscoveredCurrencies(userWalletId: UserWalletId): List<CryptoCurrency>
|
||||
|
||||
suspend fun clearDiscoveredTokens(userWalletId: UserWalletId)
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
package com.tangem.domain.tokensync.usecase
|
||||
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.tokensync.repository.TokenSyncRepository
|
||||
|
||||
class AcknowledgeTokenSyncCompletionUseCase(
|
||||
private val tokenSyncRepository: TokenSyncRepository,
|
||||
) {
|
||||
|
||||
operator fun invoke(userWalletId: UserWalletId) {
|
||||
tokenSyncRepository.acknowledgeCompletion(userWalletId)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
package com.tangem.domain.tokensync.usecase
|
||||
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.tokensync.model.TokenSyncProgress
|
||||
import com.tangem.domain.tokensync.repository.TokenSyncRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class ObserveTokenSyncUseCase(
|
||||
private val tokenSyncRepository: TokenSyncRepository,
|
||||
) {
|
||||
|
||||
operator fun invoke(userWalletId: UserWalletId): Flow<TokenSyncProgress> {
|
||||
return tokenSyncRepository.observeSyncProgress(userWalletId)
|
||||
}
|
||||
}
|
||||
|
|
@ -27,6 +27,8 @@ dependencies {
|
|||
implementation(projects.libs.crypto)
|
||||
|
||||
implementation(projects.domain.account.status)
|
||||
implementation(projects.domain.dynamicAddresses)
|
||||
implementation(projects.domain.dynamicAddresses.models)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.domain.walletManager)
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ class GetEthSpecificFeeUseCase(
|
|||
?: (walletManager as? EthereumWalletManager)?.getGasPriceValue()
|
||||
?: error("not supported for ${cryptoCurrency.network}")
|
||||
|
||||
val blockchain = Blockchain.fromNetworkId(networkId = cryptoCurrency.network.backendId)
|
||||
?: error("unknown networkId ${cryptoCurrency.network.backendId}")
|
||||
val blockchain = Blockchain.fromNetworkId(networkId = cryptoCurrency.network.rawId)
|
||||
?: error("unknown networkId ${cryptoCurrency.network.rawId}")
|
||||
|
||||
val minimalFee = getEthLegacyFee(
|
||||
gasPrice = gasPriceResult,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
package com.tangem.domain.transaction.usecase
|
||||
|
||||
import com.tangem.domain.dynamicaddresses.DynamicAddressesFeatureToggles
|
||||
import com.tangem.domain.dynamicaddresses.GetDynamicReceiveAddressUseCase
|
||||
import com.tangem.domain.dynamicaddresses.model.DynamicAddressesStatus
|
||||
import com.tangem.domain.dynamicaddresses.repository.DynamicAddressesRepository
|
||||
import com.tangem.domain.models.Asset
|
||||
import com.tangem.domain.models.ReceiveAddressModel
|
||||
import com.tangem.domain.models.TokenReceiveConfig
|
||||
|
|
@ -12,10 +16,15 @@ import com.tangem.domain.models.wallet.UserWalletId
|
|||
import com.tangem.domain.tokens.GetViewedTokenReceiveWarningUseCase
|
||||
import com.tangem.domain.transaction.R
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
|
||||
class ReceiveAddressesFactory(
|
||||
private val getEnsNameUseCase: GetEnsNameUseCase,
|
||||
private val getViewedTokenReceiveWarningUseCase: GetViewedTokenReceiveWarningUseCase,
|
||||
private val getDynamicReceiveAddressUseCase: GetDynamicReceiveAddressUseCase,
|
||||
private val dynamicAddressesRepository: DynamicAddressesRepository,
|
||||
private val dynamicAddressesFeatureToggles: DynamicAddressesFeatureToggles,
|
||||
) {
|
||||
|
||||
suspend fun create(
|
||||
|
|
@ -32,27 +41,14 @@ class ReceiveAddressesFactory(
|
|||
address = addresses.defaultAddress.value,
|
||||
)
|
||||
|
||||
val receiveAddresses = buildList {
|
||||
ensName?.let { ens ->
|
||||
add(
|
||||
ReceiveAddressModel(
|
||||
nameService = ReceiveAddressModel.NameService.Ens,
|
||||
value = ens,
|
||||
),
|
||||
)
|
||||
}
|
||||
addresses.availableAddresses.map { address ->
|
||||
add(
|
||||
ReceiveAddressModel(
|
||||
nameService = when (address.type) {
|
||||
NetworkAddress.Address.Type.Primary -> ReceiveAddressModel.NameService.Default
|
||||
NetworkAddress.Address.Type.Secondary -> ReceiveAddressModel.NameService.Legacy
|
||||
},
|
||||
value = address.value,
|
||||
),
|
||||
)
|
||||
}
|
||||
val dynamicAddress = getDynamicAddressIfEnabled(userWalletId, cryptoCurrency)
|
||||
|
||||
val receiveAddresses = if (dynamicAddress != null) {
|
||||
buildDynamicAddressList(ensName, dynamicAddress)
|
||||
} else {
|
||||
buildStandardAddressList(ensName, addresses)
|
||||
}
|
||||
|
||||
return TokenReceiveConfig(
|
||||
shouldShowWarning = cryptoCurrency.name !in getViewedTokenReceiveWarningUseCase(),
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
|
|
@ -64,6 +60,52 @@ class ReceiveAddressesFactory(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun getDynamicAddressIfEnabled(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): String? {
|
||||
if (!dynamicAddressesFeatureToggles.isDynamicAddressesEnabled) return null
|
||||
if (cryptoCurrency !is CryptoCurrency.Coin) return null
|
||||
|
||||
val status = dynamicAddressesRepository.getStatus(userWalletId, cryptoCurrency.network).firstOrNull()
|
||||
if (status != DynamicAddressesStatus.ENABLED) return null
|
||||
|
||||
return getDynamicReceiveAddressUseCase(userWalletId, cryptoCurrency.network)
|
||||
.onLeft { TangemLogger.e("Failed to get dynamic receive address: ${it.message}") }
|
||||
.getOrNull()
|
||||
}
|
||||
|
||||
private fun buildDynamicAddressList(ensName: String?, dynamicAddress: String): List<ReceiveAddressModel> =
|
||||
buildList {
|
||||
ensName?.let { ens ->
|
||||
add(ReceiveAddressModel(displayType = ReceiveAddressModel.DisplayType.Ens, value = ens))
|
||||
}
|
||||
add(
|
||||
ReceiveAddressModel(
|
||||
displayType = ReceiveAddressModel.DisplayType.Dynamic,
|
||||
value = dynamicAddress,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun buildStandardAddressList(ensName: String?, addresses: NetworkAddress): List<ReceiveAddressModel> =
|
||||
buildList {
|
||||
ensName?.let { ens ->
|
||||
add(ReceiveAddressModel(displayType = ReceiveAddressModel.DisplayType.Ens, value = ens))
|
||||
}
|
||||
addresses.availableAddresses.map { address ->
|
||||
add(
|
||||
ReceiveAddressModel(
|
||||
displayType = when (address.type) {
|
||||
NetworkAddress.Address.Type.Primary -> ReceiveAddressModel.DisplayType.Default
|
||||
NetworkAddress.Address.Type.Secondary -> ReceiveAddressModel.DisplayType.Legacy
|
||||
},
|
||||
value = address.value,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun createForNft(
|
||||
userWalletId: UserWalletId,
|
||||
addresses: NetworkAddress,
|
||||
|
|
@ -82,7 +124,7 @@ class ReceiveAddressesFactory(
|
|||
ensName?.let { ens ->
|
||||
add(
|
||||
ReceiveAddressModel(
|
||||
nameService = ReceiveAddressModel.NameService.Ens,
|
||||
displayType = ReceiveAddressModel.DisplayType.Ens,
|
||||
value = ens,
|
||||
),
|
||||
)
|
||||
|
|
@ -90,9 +132,9 @@ class ReceiveAddressesFactory(
|
|||
addresses.availableAddresses.map { address ->
|
||||
add(
|
||||
ReceiveAddressModel(
|
||||
nameService = when (address.type) {
|
||||
NetworkAddress.Address.Type.Primary -> ReceiveAddressModel.NameService.Default
|
||||
NetworkAddress.Address.Type.Secondary -> ReceiveAddressModel.NameService.Legacy
|
||||
displayType = when (address.type) {
|
||||
NetworkAddress.Address.Type.Primary -> ReceiveAddressModel.DisplayType.Default
|
||||
NetworkAddress.Address.Type.Secondary -> ReceiveAddressModel.DisplayType.Legacy
|
||||
},
|
||||
value = address.value,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.domain.pay
|
||||
|
||||
import com.tangem.domain.models.account.CardDisplayName
|
||||
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
|
@ -11,4 +12,5 @@ data class TangemPayDetailsConfig(
|
|||
val cardFrozenState: TangemPayCardFrozenState,
|
||||
val cardNumberEnd: String,
|
||||
val chainId: Int,
|
||||
val displayName: CardDisplayName?,
|
||||
)
|
||||
|
|
@ -5,8 +5,8 @@ import com.tangem.core.error.UniversalError
|
|||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
|
||||
@Deprecated("TangemPayCurrencyFactory")
|
||||
interface TangemPayCryptoCurrencyFactory {
|
||||
|
||||
fun create(userWallet: UserWallet, chainId: Int): Either<UniversalError, CryptoCurrency>
|
||||
fun create(userWallet: UserWallet): Either<UniversalError, CryptoCurrency.Token>
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.domain.pay.model
|
||||
|
||||
import com.tangem.domain.models.account.CardDisplayName
|
||||
import com.tangem.domain.models.account.PaymentAccountStatusValue
|
||||
import com.tangem.domain.models.kyc.KycStatus
|
||||
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
||||
|
|
@ -49,6 +50,7 @@ data class CustomerInfo(
|
|||
val id: String,
|
||||
val cardId: String,
|
||||
val frozenState: TangemPayCardFrozenState,
|
||||
val displayName: CardDisplayName?,
|
||||
)
|
||||
|
||||
data class CardInfo(
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.domain.pay.repository
|
|||
|
||||
import arrow.core.Either
|
||||
import com.tangem.core.error.UniversalError
|
||||
import com.tangem.domain.models.account.CardDisplayName
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.model.SetPinResult
|
||||
import com.tangem.domain.pay.model.TangemPayCardBalance
|
||||
|
|
@ -31,4 +32,9 @@ interface TangemPayCardDetailsRepository {
|
|||
|
||||
fun cardFrozenState(cardId: String): Flow<TangemPayCardFrozenState>
|
||||
suspend fun cardFrozenStateSync(cardId: String): TangemPayCardFrozenState?
|
||||
|
||||
suspend fun updateCardDisplayName(
|
||||
userWalletId: UserWalletId,
|
||||
displayName: CardDisplayName,
|
||||
): Either<UniversalError, Unit>
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.domain.pay.usecase
|
||||
|
||||
import arrow.core.Option
|
||||
import arrow.core.none
|
||||
import arrow.core.some
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.PaymentAccountStatusValue
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
|
||||
class GetPaymentAccountCryptoCurrencyStatusUseCase(
|
||||
private val paymentAccountStatusSupplier: PaymentAccountStatusSupplier,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): Option<Pair<Account.Payment, CryptoCurrencyStatus>> {
|
||||
val accountStatus = paymentAccountStatusSupplier.invoke(userWalletId).firstOrNull() ?: return none()
|
||||
val cryptoCurrencyStatus = when (val statusValue = accountStatus.value) {
|
||||
is PaymentAccountStatusValue.Loaded -> statusValue.cryptoCurrencyStatus
|
||||
is PaymentAccountStatusValue.Locked -> statusValue.cryptoCurrencyStatus
|
||||
else -> return none()
|
||||
}
|
||||
return if (cryptoCurrencyStatus.currency == cryptoCurrency) {
|
||||
(accountStatus.account to cryptoCurrencyStatus).some()
|
||||
} else {
|
||||
none()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,6 @@ 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.blockchain.tokenbalance.models.TokenBalance
|
||||
import com.tangem.blockchainsdk.models.UpdateWalletManagerResult
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
|
|
@ -39,12 +38,14 @@ interface WalletManagersFacade {
|
|||
* @param userWalletId The ID of the user's wallet.
|
||||
* @param network The network.
|
||||
* @param extraTokens Additional tokens.
|
||||
* @param xpub XPUB string to restore dynamic addresses mode if not yet active.
|
||||
* @return The result of updating the wallet manager.
|
||||
*/
|
||||
suspend fun update(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
extraTokens: Set<CryptoCurrency.Token>,
|
||||
xpub: String? = null,
|
||||
): UpdateWalletManagerResult
|
||||
|
||||
/**
|
||||
|
|
@ -282,8 +283,6 @@ interface WalletManagersFacade {
|
|||
|
||||
suspend fun getNFTExploreUrl(network: Network, assetIdentifier: NFTAsset.Identifier): String?
|
||||
|
||||
suspend fun getTokenBalances(userWalletId: UserWalletId, network: Network): List<TokenBalance>
|
||||
|
||||
/**
|
||||
* If wallet manager implements [InitializableAccount] then returns [InitializableAccount.isAccountInitialized]
|
||||
* value. Otherwise always return true
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.domain.wallets.models
|
||||
|
||||
enum class WalletSyncResult {
|
||||
AlreadyExists,
|
||||
Created,
|
||||
}
|
||||
|
|
@ -29,6 +29,9 @@ interface DerivationsRepository {
|
|||
derivations: Map<ByteArrayKey, List<DerivationPath>>,
|
||||
): Map<ByteArrayKey, ExtendedPublicKeysMap>
|
||||
|
||||
/** Returns already derived extended public keys for the given [seedKey] */
|
||||
suspend fun getExistingDerivedKeys(userWalletId: UserWalletId, seedKey: ByteArrayKey): ExtendedPublicKeysMap
|
||||
|
||||
/** Check if user [userWalletId] has missed derivations using map of [Network.ID] with extraDerivationPath */
|
||||
suspend fun hasMissedDerivations(
|
||||
userWalletId: UserWalletId,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import arrow.core.Either
|
|||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.wallets.models.UserWalletRemoteInfo
|
||||
import com.tangem.domain.wallets.models.WalletSyncResult
|
||||
import com.tangem.domain.wallets.models.errors.ActivatePromoCodeError
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
|
|
@ -22,7 +23,7 @@ interface WalletsRepository {
|
|||
|
||||
suspend fun setHasWalletsWithRing(userWalletId: UserWalletId)
|
||||
|
||||
suspend fun createWallet(userWalletId: UserWalletId)
|
||||
suspend fun createWallet(userWalletId: UserWalletId): WalletSyncResult
|
||||
|
||||
fun nftEnabledStatus(userWalletId: UserWalletId): Flow<Boolean>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.domain.wallets.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.right
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
|
|
@ -38,23 +37,37 @@ class GetExtendedPublicKeyForCurrencyUseCase(
|
|||
error("No derivation found")
|
||||
}
|
||||
|
||||
val seedKey = walletManager.wallet.publicKey.seedKey
|
||||
val existingKeys = derivationsRepository.getExistingDerivedKeys(
|
||||
userWalletId = userWalletId,
|
||||
seedKey = ByteArrayKey(seedKey),
|
||||
)
|
||||
|
||||
var childKey = makeChildKey(
|
||||
isBip44DerivationStyleXPUB = blockchain.isBip44DerivationStyleXPUB(),
|
||||
extendedPublicKey = hdKey.extendedPublicKey,
|
||||
derivationPath = hdKey.path,
|
||||
)
|
||||
|
||||
// Fill from already derived keys if available
|
||||
if (childKey.extendedPublicKey == null) {
|
||||
existingKeys[childKey.derivationPath]?.let {
|
||||
childKey = childKey.copy(extendedPublicKey = it)
|
||||
}
|
||||
}
|
||||
|
||||
val parentPath = childKey.derivationPath.dropLastNodes(1)
|
||||
var parentKey = Key(
|
||||
derivationPath = childKey.derivationPath.dropLastNodes(1),
|
||||
extendedPublicKey = null,
|
||||
derivationPath = parentPath,
|
||||
extendedPublicKey = existingKeys[parentPath],
|
||||
)
|
||||
|
||||
val pendingDerivations = getPendingDerivations(childKey, parentKey)
|
||||
val derivedKeys = deriveKeys(
|
||||
userWalletId = userWalletId,
|
||||
seedKey = walletManager.wallet.publicKey.seedKey,
|
||||
paths = pendingDerivations,
|
||||
)
|
||||
val derivedKeys = if (pendingDerivations.isNotEmpty()) {
|
||||
deriveKeys(userWalletId = userWalletId, seedKey = seedKey, paths = pendingDerivations)
|
||||
} else {
|
||||
ExtendedPublicKeysMap(emptyMap())
|
||||
}
|
||||
|
||||
if (childKey.extendedPublicKey == null) {
|
||||
childKey = childKey.copy(
|
||||
|
|
@ -72,22 +85,6 @@ class GetExtendedPublicKeyForCurrencyUseCase(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if xpub generation is supported, false otherwise
|
||||
*/
|
||||
suspend fun isSupported(userWalletId: UserWalletId, network: Network): Either<Throwable, Boolean> = Either.catch {
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(userWalletId, network)
|
||||
?: error("Wallet not found for user wallet $userWalletId and network ${network.id}")
|
||||
|
||||
val blockchain = network.toBlockchain()
|
||||
val isSecp256k1Blockchain = Blockchain.secp256k1Blockchains(network.isTestnet).contains(blockchain)
|
||||
val isHdKey = walletManager.wallet.publicKey.derivationType?.hdKey
|
||||
|
||||
val isSupported = isSecp256k1Blockchain && isHdKey != null
|
||||
|
||||
return isSupported.right()
|
||||
}
|
||||
|
||||
private suspend fun deriveKeys(
|
||||
userWalletId: UserWalletId,
|
||||
seedKey: ByteArray,
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ package com.tangem.domain.wallets.usecase
|
|||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.util.LinkedHashMap
|
||||
|
||||
/**
|
||||
* Use case for getting list of user wallets
|
||||
|
|
@ -22,9 +22,13 @@ class GetWalletsUseCase(
|
|||
operator fun invoke(): Flow<List<UserWallet>> = userWalletsListRepository.userWallets.map { requireNotNull(it) }
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun invokeAsMap(): Flow<LinkedHashMap<UserWalletId, UserWallet>> = userWalletsListRepository.userWallets
|
||||
.map { requireNotNull(it) }
|
||||
.map { wallets ->
|
||||
fun invokeAsMap(isOnlyMultiCurrency: Boolean = true): Flow<LinkedHashMap<UserWalletId, UserWallet>> = invoke()
|
||||
.map { list ->
|
||||
val wallets = if (isOnlyMultiCurrency) {
|
||||
list.filter { wallet -> wallet.isMultiCurrency }
|
||||
} else {
|
||||
list
|
||||
}
|
||||
wallets.associateByTo(
|
||||
destination = linkedMapOf(),
|
||||
keySelector = { wallet -> wallet.walletId },
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.domain.wallets.usecase
|
||||
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.wallets.models.WalletSyncResult
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.utils.coroutines.runSuspendCatching
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
|
|
@ -14,8 +15,9 @@ class SyncWalletWithRemoteUseCase(
|
|||
private val walletsRepository: WalletsRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(userWalletId: UserWalletId) {
|
||||
runSuspendCatching { walletsRepository.createWallet(userWalletId) }
|
||||
suspend operator fun invoke(userWalletId: UserWalletId): WalletSyncResult {
|
||||
return runSuspendCatching { walletsRepository.createWallet(userWalletId) }
|
||||
.onFailure { TangemLogger.e("Error", it) }
|
||||
.getOrDefault(WalletSyncResult.AlreadyExists)
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ dependencies {
|
|||
/** Core */
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
|
||||
/** Domain */
|
||||
implementation(projects.domain.account.status)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import arrow.core.Either.Companion.catch
|
|||
import arrow.core.getOrElse
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.domain.account.status.utils.CryptoCurrencyOperations.getCoin
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
|
|
@ -67,8 +68,7 @@ class YieldSupplyGetCurrentFeeUseCase(
|
|||
|
||||
val tokenValue = rateRatio.multiply(nativeGas.amount.value)
|
||||
|
||||
val isEthereum = cryptoCurrencyStatus.currency
|
||||
.network.id.rawId.value == Blockchain.Ethereum.id
|
||||
val isEthereum = cryptoCurrencyStatus.currency.network.rawId == Blockchain.Ethereum.toNetworkId()
|
||||
|
||||
val isHighFee = if (isEthereum) {
|
||||
val maxFeePerGas = (feeWithoutGas as? Fee.Ethereum.EIP1559)?.maxFeePerGas ?: 0.toBigInteger()
|
||||
|
|
|
|||
|
|
@ -165,8 +165,7 @@ class YieldSupplyMinAmountUseCaseTest {
|
|||
private fun createNetwork(): Network {
|
||||
val derivationPath = Network.DerivationPath.None
|
||||
return Network(
|
||||
id = Network.ID(Network.RawID("polygon"), derivationPath),
|
||||
backendId = "polygon",
|
||||
id = Network.ID(value = "polygon", derivationPath = derivationPath),
|
||||
name = "Polygon",
|
||||
currencySymbol = "MATIC",
|
||||
derivationPath = derivationPath,
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
|||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.network.NetworkAddress
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||
import com.tangem.domain.models.yield.supply.YieldSupplyStatus
|
||||
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||
import com.tangem.domain.yield.supply.models.YieldSupplyPendingStatus
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
|
|
@ -324,7 +324,6 @@ class YieldSupplyEnterStatusUseCaseTest {
|
|||
val derivationPath = Network.DerivationPath.None
|
||||
val network = Network(
|
||||
id = Network.ID(value = rawNetworkId, derivationPath = derivationPath),
|
||||
backendId = rawNetworkId,
|
||||
name = rawNetworkId,
|
||||
currencySymbol = rawNetworkId.take(3).uppercase(),
|
||||
derivationPath = derivationPath,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.tangem.domain.yield.supply.usecase
|
|||
|
||||
import arrow.core.Either
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
|
|
@ -48,7 +48,7 @@ class YieldSupplyGetCurrentFeeUseCaseTest {
|
|||
|
||||
@Test
|
||||
fun `GIVEN valid inputs on non-ethereum WHEN invoke THEN returns fee value and not high`() = runTest {
|
||||
val rawNetworkId = Blockchain.BSC.id
|
||||
val rawNetworkId = "binance-smart-chain"
|
||||
val tokenDecimals = 8
|
||||
val nativeDecimals = 18
|
||||
val token = createToken(rawNetworkId = rawNetworkId, decimals = tokenDecimals)
|
||||
|
|
@ -100,7 +100,7 @@ class YieldSupplyGetCurrentFeeUseCaseTest {
|
|||
|
||||
@Test
|
||||
fun `GIVEN ethereum with high gas WHEN invoke THEN returns high fee flag`() = runTest {
|
||||
val rawNetworkId = Blockchain.Ethereum.id
|
||||
val rawNetworkId = "ethereum"
|
||||
val tokenDecimals = 8
|
||||
val nativeDecimals = 18
|
||||
val token = createToken(rawNetworkId = rawNetworkId, decimals = tokenDecimals)
|
||||
|
|
@ -152,7 +152,7 @@ class YieldSupplyGetCurrentFeeUseCaseTest {
|
|||
|
||||
@Test
|
||||
fun `GIVEN token fiat rate missing WHEN invoke THEN returns error`() = runTest {
|
||||
val rawNetworkId = Blockchain.BSC.id
|
||||
val rawNetworkId = "binance-smart-chain"
|
||||
val token = createToken(rawNetworkId = rawNetworkId, decimals = 8)
|
||||
val cryptoStatus = createStatus(token = token, fiatRate = null)
|
||||
|
||||
|
|
@ -175,7 +175,7 @@ class YieldSupplyGetCurrentFeeUseCaseTest {
|
|||
|
||||
@Test
|
||||
fun `GIVEN native quotes unavailable WHEN invoke THEN returns error`() = runTest {
|
||||
val rawNetworkId = Blockchain.BSC.id
|
||||
val rawNetworkId = "binance-smart-chain"
|
||||
val token = createToken(rawNetworkId = rawNetworkId, decimals = 8)
|
||||
val cryptoStatus = createStatus(token = token, fiatRate = BigDecimal("2.00"))
|
||||
val nativeCoin = createCoin(rawNetworkId = rawNetworkId, decimals = 18)
|
||||
|
|
@ -204,7 +204,7 @@ class YieldSupplyGetCurrentFeeUseCaseTest {
|
|||
|
||||
@Test
|
||||
fun `GIVEN empty quotes list WHEN invoke THEN returns error`() = runTest {
|
||||
val rawNetworkId = Blockchain.BSC.id
|
||||
val rawNetworkId = "binance-smart-chain"
|
||||
val token = createToken(rawNetworkId = rawNetworkId, decimals = 8)
|
||||
val cryptoStatus = createStatus(token = token, fiatRate = BigDecimal("2.00"))
|
||||
val nativeCoin = createCoin(rawNetworkId = rawNetworkId, decimals = 18)
|
||||
|
|
@ -233,7 +233,7 @@ class YieldSupplyGetCurrentFeeUseCaseTest {
|
|||
|
||||
@Test
|
||||
fun `GIVEN native fiat rate non-positive WHEN invoke THEN returns error`() = runTest {
|
||||
val rawNetworkId = Blockchain.BSC.id
|
||||
val rawNetworkId = "binance-smart-chain"
|
||||
val token = createToken(rawNetworkId = rawNetworkId, decimals = 8)
|
||||
val cryptoStatus = createStatus(token = token, fiatRate = BigDecimal("2.00"))
|
||||
val nativeCoin = createCoin(rawNetworkId = rawNetworkId, decimals = 18)
|
||||
|
|
@ -274,7 +274,7 @@ class YieldSupplyGetCurrentFeeUseCaseTest {
|
|||
|
||||
@Test
|
||||
fun `GIVEN token fiat rate non-positive WHEN invoke THEN returns error`() = runTest {
|
||||
val rawNetworkId = Blockchain.BSC.id
|
||||
val rawNetworkId = "binance-smart-chain"
|
||||
val token = createToken(rawNetworkId = rawNetworkId, decimals = 8)
|
||||
val cryptoStatus = createStatus(token = token, fiatRate = BigDecimal.ZERO) // non-positive
|
||||
|
||||
|
|
@ -299,7 +299,6 @@ class YieldSupplyGetCurrentFeeUseCaseTest {
|
|||
val derivationPath = Network.DerivationPath.None
|
||||
val network = Network(
|
||||
id = Network.ID(value = rawNetworkId, derivationPath = derivationPath),
|
||||
backendId = rawNetworkId,
|
||||
name = rawNetworkId,
|
||||
currencySymbol = rawNetworkId.take(3).uppercase(),
|
||||
derivationPath = derivationPath,
|
||||
|
|
@ -331,7 +330,6 @@ class YieldSupplyGetCurrentFeeUseCaseTest {
|
|||
val derivationPath = Network.DerivationPath.None
|
||||
val network = Network(
|
||||
id = Network.ID(value = rawNetworkId, derivationPath = derivationPath),
|
||||
backendId = rawNetworkId,
|
||||
name = rawNetworkId,
|
||||
currencySymbol = rawNetworkId.take(3).uppercase(),
|
||||
derivationPath = derivationPath,
|
||||
|
|
|
|||
|
|
@ -61,8 +61,7 @@ class YieldSupplyGetDustMinAmountUseCaseTest {
|
|||
private fun createNetwork(): Network {
|
||||
val derivationPath = Network.DerivationPath.None
|
||||
return Network(
|
||||
id = Network.ID(Network.RawID("polygon"), derivationPath),
|
||||
backendId = "polygon",
|
||||
id = Network.ID(value = "polygon", derivationPath = derivationPath),
|
||||
name = "Polygon",
|
||||
currencySymbol = "MATIC",
|
||||
derivationPath = derivationPath,
|
||||
|
|
|
|||
|
|
@ -238,8 +238,7 @@ class YieldSupplyGetRewardsBalanceUseCaseTest {
|
|||
@Test
|
||||
fun `GIVEN polygon USDT0 Loaded status WHEN invoke THEN emit formatted balances`() = runTest {
|
||||
val network = Network(
|
||||
id = Network.ID(Network.RawID("POLYGON"), Network.DerivationPath.Card("m/44'/60'/0'/0/0")),
|
||||
backendId = "polygon-pos",
|
||||
id = Network.ID(value = "polygon-pos", derivationPath = Network.DerivationPath.Card("m/44'/60'/0'/0/0")),
|
||||
name = "Polygon",
|
||||
currencySymbol = "POL",
|
||||
derivationPath = Network.DerivationPath.Card("m/44'/60'/0'/0/0"),
|
||||
|
|
@ -365,8 +364,7 @@ class YieldSupplyGetRewardsBalanceUseCaseTest {
|
|||
private fun createNetwork(): Network {
|
||||
val derivationPath = Network.DerivationPath.None
|
||||
return Network(
|
||||
id = Network.ID(Network.RawID("polygon"), derivationPath),
|
||||
backendId = "polygon",
|
||||
id = Network.ID(value = "polygon", derivationPath = derivationPath),
|
||||
name = "Polygon",
|
||||
currencySymbol = "MATIC",
|
||||
derivationPath = derivationPath,
|
||||
|
|
|
|||
|
|
@ -279,7 +279,6 @@ class YieldSupplyPendingTrackerTest {
|
|||
val derivationPath = Network.DerivationPath.None
|
||||
val network = Network(
|
||||
id = Network.ID(value = networkId, derivationPath = derivationPath),
|
||||
backendId = networkId,
|
||||
name = networkId,
|
||||
currencySymbol = networkId.take(3).uppercase(),
|
||||
derivationPath = derivationPath,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue