Updated on 2026-08-14
This commit is contained in:
commit
3c2a006fb9
331 changed files with 8261 additions and 1670 deletions
|
|
@ -15,4 +15,5 @@ dependencies {
|
|||
implementation(projects.core.res)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.visa.models)
|
||||
}
|
||||
|
|
@ -2,12 +2,43 @@ package com.tangem.domain.feedback
|
|||
|
||||
import com.tangem.domain.feedback.models.*
|
||||
import com.tangem.domain.feedback.utils.breakLine
|
||||
import com.tangem.domain.visa.model.VisaTxDetails
|
||||
import com.tangem.domain.feedback.models.BlockchainInfo.Addresses as BlockchainAddresses
|
||||
|
||||
internal class FeedbackDataBuilder {
|
||||
|
||||
private val builder = StringBuilder()
|
||||
|
||||
fun addVisaTxInfo(txDetails: VisaTxDetails) {
|
||||
builder.appendKeyValue("Type", txDetails.type)
|
||||
builder.appendKeyValue("Status", txDetails.status)
|
||||
builder.appendKeyValue("Blockchain amount", txDetails.blockchainAmount.toString())
|
||||
builder.appendKeyValue("Transaction amount", txDetails.transactionAmount.toString())
|
||||
builder.appendKeyValue("Currency code", txDetails.transactionCurrencyCode.toString())
|
||||
builder.appendKeyValue("Merchant name", txDetails.merchantName)
|
||||
builder.appendKeyValue("Merchant city", txDetails.merchantCity)
|
||||
builder.appendKeyValue("Merchant country code", txDetails.merchantCountryCode)
|
||||
builder.appendKeyValue("Merchant category code", txDetails.merchantCategoryCode)
|
||||
|
||||
builder.appendDelimiter()
|
||||
builder.breakLine()
|
||||
builder.append("Requests:")
|
||||
|
||||
txDetails.requests.forEach { request ->
|
||||
builder.appendKeyValue("Type", request.requestType)
|
||||
builder.appendKeyValue("Status", request.requestStatus)
|
||||
builder.appendKeyValue("Blockchain amount", request.blockchainAmount.toString())
|
||||
builder.appendKeyValue("Transaction amount", request.transactionAmount.toString())
|
||||
builder.appendKeyValue("Currency code", request.txCurrencyCode.toString())
|
||||
builder.appendKeyValue("Error code", request.errorCode.toString())
|
||||
builder.appendKeyValue("Date", request.requestDate.toString())
|
||||
builder.appendKeyValue("Transaction hash", request.txHash)
|
||||
builder.appendKeyValue("Transaction status", request.txStatus)
|
||||
builder.appendDelimiter()
|
||||
builder.breakLine()
|
||||
}
|
||||
}
|
||||
|
||||
fun addUserWalletsInfo(userWalletsInfo: UserWalletsInfo) {
|
||||
builder.appendKeyValue("User Wallet ID", userWalletsInfo.selectedUserWalletId)
|
||||
builder.appendKeyValue("Total saved wallets", userWalletsInfo.totalUserWallets.toString())
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.domain.feedback
|
|||
|
||||
import android.content.res.Resources
|
||||
import com.tangem.core.res.getStringSafe
|
||||
import com.tangem.domain.feedback.models.CardInfo
|
||||
import com.tangem.domain.feedback.models.FeedbackEmail
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.feedback.repository.FeedbackRepository
|
||||
|
|
@ -27,7 +26,7 @@ class SendFeedbackEmailUseCase(
|
|||
|
||||
suspend operator fun invoke(type: FeedbackEmailType) {
|
||||
val email = FeedbackEmail(
|
||||
address = getAddress(type.cardInfo),
|
||||
address = getAddress(type),
|
||||
subject = emailSubjectResolver.resolve(type),
|
||||
message = createMessage(type),
|
||||
// Temporally user data is not sent
|
||||
|
|
@ -37,8 +36,12 @@ class SendFeedbackEmailUseCase(
|
|||
feedbackRepository.sendEmail(email)
|
||||
}
|
||||
|
||||
private fun getAddress(cardInfo: CardInfo?): String {
|
||||
return if (cardInfo?.isStart2Coin == true) START2COIN_SUPPORT_EMAIL else TANGEM_SUPPORT_EMAIL
|
||||
private fun getAddress(type: FeedbackEmailType): String {
|
||||
return when {
|
||||
type is FeedbackEmailType.Visa || type.cardInfo?.isVisa == true -> TANGEM_VISA_SUPPORT_EMAIL
|
||||
type.cardInfo?.isStart2Coin == true -> START2COIN_SUPPORT_EMAIL
|
||||
else -> TANGEM_SUPPORT_EMAIL
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun createMessage(type: FeedbackEmailType): String {
|
||||
|
|
@ -61,12 +64,15 @@ class SendFeedbackEmailUseCase(
|
|||
is FeedbackEmailType.CurrencyDescriptionError,
|
||||
is FeedbackEmailType.PreActivatedWallet,
|
||||
is FeedbackEmailType.CardAttestationFailed,
|
||||
is FeedbackEmailType.Visa.Dispute,
|
||||
-> this
|
||||
is FeedbackEmailType.DirectUserRequest,
|
||||
is FeedbackEmailType.RateCanBeBetter,
|
||||
is FeedbackEmailType.StakingProblem,
|
||||
is FeedbackEmailType.SwapProblem,
|
||||
is FeedbackEmailType.TransactionSendingProblem,
|
||||
is FeedbackEmailType.Visa.Activation,
|
||||
is FeedbackEmailType.Visa.DirectUserRequest,
|
||||
-> {
|
||||
append(resources.getStringSafe(R.string.feedback_data_collection_message))
|
||||
skipLine()
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ data class CardInfo(
|
|||
val signedHashesList: List<SignedHashes>,
|
||||
val isImported: Boolean,
|
||||
val isStart2Coin: Boolean,
|
||||
val isVisa: Boolean,
|
||||
) {
|
||||
|
||||
data class SignedHashes(val curve: String, val total: String?)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.domain.feedback.models
|
||||
|
||||
import com.tangem.domain.visa.model.VisaTxDetails
|
||||
|
||||
/**
|
||||
* Email feedback type
|
||||
*
|
||||
|
|
@ -52,4 +54,15 @@ sealed interface FeedbackEmailType {
|
|||
data object CardAttestationFailed : FeedbackEmailType {
|
||||
override val cardInfo: CardInfo? = null
|
||||
}
|
||||
|
||||
sealed class Visa : FeedbackEmailType {
|
||||
data class DirectUserRequest(override val cardInfo: CardInfo) : Visa()
|
||||
|
||||
data class Activation(override val cardInfo: CardInfo) : Visa()
|
||||
|
||||
data class Dispute(
|
||||
val visaTxDetails: VisaTxDetails,
|
||||
override val cardInfo: CardInfo,
|
||||
) : Visa()
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import com.tangem.domain.feedback.FeedbackDataBuilder
|
|||
import com.tangem.domain.feedback.models.CardInfo
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.feedback.repository.FeedbackRepository
|
||||
import com.tangem.domain.visa.model.VisaTxDetails
|
||||
|
||||
/**
|
||||
* Email message body resolver
|
||||
|
|
@ -29,11 +30,20 @@ internal class EmailMessageBodyResolver(
|
|||
is FeedbackEmailType.ScanningProblem,
|
||||
is FeedbackEmailType.CardAttestationFailed,
|
||||
-> addPhoneInfoBody()
|
||||
is FeedbackEmailType.Visa.Activation -> addUserRequestBody(type.cardInfo)
|
||||
is FeedbackEmailType.Visa.DirectUserRequest -> addUserRequestBody(type.cardInfo)
|
||||
is FeedbackEmailType.Visa.Dispute -> addVisaRequestBody(type.cardInfo, type.visaTxDetails)
|
||||
}
|
||||
|
||||
return build()
|
||||
}
|
||||
|
||||
private suspend fun FeedbackDataBuilder.addVisaRequestBody(cardInfo: CardInfo, visaTxDetails: VisaTxDetails) {
|
||||
addUserRequestBody(cardInfo)
|
||||
addDelimiter()
|
||||
addVisaTxInfo(visaTxDetails)
|
||||
}
|
||||
|
||||
private suspend fun FeedbackDataBuilder.addUserRequestBody(cardInfo: CardInfo) {
|
||||
addUserWalletsInfo(userWalletsInfo = feedbackRepository.getUserWalletsInfo(cardInfo.userWalletId))
|
||||
addDelimiter()
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ internal class EmailMessageTitleResolver(private val resources: Resources) {
|
|||
is FeedbackEmailType.DirectUserRequest,
|
||||
is FeedbackEmailType.CurrencyDescriptionError,
|
||||
is FeedbackEmailType.CardAttestationFailed,
|
||||
is FeedbackEmailType.Visa.Activation,
|
||||
is FeedbackEmailType.Visa.DirectUserRequest,
|
||||
is FeedbackEmailType.Visa.Dispute,
|
||||
-> R.string.feedback_preface_support
|
||||
is FeedbackEmailType.RateCanBeBetter -> R.string.feedback_preface_rate_negative
|
||||
is FeedbackEmailType.ScanningProblem -> R.string.feedback_preface_scan_failed
|
||||
|
|
|
|||
|
|
@ -37,6 +37,9 @@ internal class EmailSubjectResolver(private val resources: Resources) {
|
|||
resources.getStringSafe(R.string.feedback_token_description_error)
|
||||
}
|
||||
FeedbackEmailType.CardAttestationFailed -> "Card attestation failed"
|
||||
is FeedbackEmailType.Visa.Activation -> "[Visa] [Activation] {auto-filled subject}"
|
||||
is FeedbackEmailType.Visa.DirectUserRequest -> "[Visa] {auto-filled subject}"
|
||||
is FeedbackEmailType.Visa.Dispute -> "[Visa] [DISPUTE] {auto-filled subject}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,4 +3,5 @@ package com.tangem.domain.feedback.utils
|
|||
internal const val GMAIL_MAX_FILE_SIZE = 24_900_000 // ≈ 25 MB
|
||||
|
||||
internal const val START2COIN_SUPPORT_EMAIL = "cardsupport@start2coin.com"
|
||||
internal const val TANGEM_SUPPORT_EMAIL = "support@tangem.com"
|
||||
internal const val TANGEM_SUPPORT_EMAIL = "support@tangem.com"
|
||||
internal const val TANGEM_VISA_SUPPORT_EMAIL = "pay@tangem.com"
|
||||
|
|
@ -14,6 +14,7 @@ dependencies {
|
|||
api(projects.domain.core)
|
||||
api(projects.domain.manageTokens.models)
|
||||
api(projects.domain.networks)
|
||||
api(projects.domain.quotes)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.staking)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ 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.networks.multi.MultiNetworkStatusFetcher
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
|
@ -26,6 +27,7 @@ class SaveManagedTokensUseCase(
|
|||
private val stakingRepository: StakingRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
private val multiQuoteFetcher: MultiQuoteFetcher,
|
||||
private val tokensFeatureToggles: TokensFeatureToggles,
|
||||
) {
|
||||
|
||||
|
|
@ -124,10 +126,19 @@ class SaveManagedTokensUseCase(
|
|||
}
|
||||
|
||||
private suspend fun refreshUpdatedQuotes(addedCurrencies: List<CryptoCurrency>) {
|
||||
quotesRepository.fetchQuotes(
|
||||
currenciesIds = addedCurrencies.mapNotNullTo(hashSetOf()) { it.id.rawCurrencyId },
|
||||
refresh = true,
|
||||
)
|
||||
if (tokensFeatureToggles.isQuotesLoadingRefactoringEnabled) {
|
||||
multiQuoteFetcher(
|
||||
params = MultiQuoteFetcher.Params(
|
||||
currenciesIds = addedCurrencies.mapNotNullTo(hashSetOf()) { it.id.rawCurrencyId },
|
||||
appCurrencyId = null,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
quotesRepository.fetchQuotes(
|
||||
currenciesIds = addedCurrencies.mapNotNullTo(hashSetOf()) { it.id.rawCurrencyId },
|
||||
refresh = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ dependencies {
|
|||
api(projects.domain.models)
|
||||
api(projects.domain.networks)
|
||||
api(projects.domain.staking)
|
||||
api(projects.domain.quotes)
|
||||
api(projects.domain.wallets)
|
||||
api(projects.domain.wallets.models)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ package com.tangem.domain.markets
|
|||
import arrow.core.None
|
||||
import arrow.core.Option
|
||||
import arrow.core.toOption
|
||||
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
|
||||
|
|
@ -11,9 +14,10 @@ import kotlinx.coroutines.flow.catch
|
|||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
class GetCurrencyQuotesUseCase(
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val singleQuoteSupplier: SingleQuoteSupplier,
|
||||
private val tokensFeatureToggles: TokensFeatureToggles,
|
||||
) {
|
||||
// TODO apply interval parameter [REDACTED_TASK_KEY]
|
||||
operator fun invoke(
|
||||
|
|
@ -23,9 +27,18 @@ class GetCurrencyQuotesUseCase(
|
|||
): Flow<Option<Quote.Value>> {
|
||||
val rawId = currencyID.rawCurrencyId ?: return flowOf(None)
|
||||
|
||||
return quotesRepository.getQuotesUpdatesLegacy(
|
||||
currenciesIds = setOf(rawId),
|
||||
refresh = refresh,
|
||||
).map { it.filterIsInstance<Quote.Value>().firstOrNull().toOption() }.catch { emit(None) }
|
||||
return if (tokensFeatureToggles.isQuotesLoadingRefactoringEnabled) {
|
||||
singleQuoteSupplier(
|
||||
params = SingleQuoteProducer.Params(rawCurrencyId = rawId),
|
||||
)
|
||||
.map { (it as? Quote.Value).toOption() }
|
||||
} else {
|
||||
quotesRepository.getQuotesUpdatesLegacy(
|
||||
currenciesIds = setOf(rawId),
|
||||
refresh = refresh,
|
||||
)
|
||||
.map { it.filterIsInstance<Quote.Value>().firstOrNull().toOption() }
|
||||
}
|
||||
.catch { emit(None) }
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import arrow.core.Either
|
|||
import com.tangem.domain.card.repository.DerivationsRepository
|
||||
import com.tangem.domain.markets.repositories.MarketsTokenRepository
|
||||
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
|
@ -31,6 +32,7 @@ class SaveMarketTokensUseCase(
|
|||
private val stakingRepository: StakingRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
private val multiQuoteFetcher: MultiQuoteFetcher,
|
||||
private val tokensFeatureToggles: TokensFeatureToggles,
|
||||
) {
|
||||
|
||||
|
|
@ -106,9 +108,18 @@ class SaveMarketTokensUseCase(
|
|||
}
|
||||
|
||||
private suspend fun refreshUpdatedQuotes(addedCurrencies: List<CryptoCurrency>) {
|
||||
quotesRepository.fetchQuotes(
|
||||
currenciesIds = addedCurrencies.mapNotNullTo(hashSetOf()) { it.id.rawCurrencyId },
|
||||
refresh = true,
|
||||
)
|
||||
if (tokensFeatureToggles.isQuotesLoadingRefactoringEnabled) {
|
||||
multiQuoteFetcher(
|
||||
params = MultiQuoteFetcher.Params(
|
||||
currenciesIds = addedCurrencies.mapNotNullTo(hashSetOf()) { it.id.rawCurrencyId },
|
||||
appCurrencyId = null,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
quotesRepository.fetchQuotes(
|
||||
currenciesIds = addedCurrencies.mapNotNullTo(hashSetOf()) { it.id.rawCurrencyId },
|
||||
refresh = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.domain.models
|
||||
|
||||
data class ArtworkModel(
|
||||
val verifiedArtwork: ByteArray? = null,
|
||||
val defaultUrl: String,
|
||||
)
|
||||
|
|
@ -55,6 +55,14 @@ data class NFTAsset(
|
|||
override val stringValue: String = tokenAddress
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class Solana(
|
||||
val tokenAddress: String,
|
||||
val cnft: Boolean,
|
||||
) : Identifier() {
|
||||
override val stringValue: String = tokenAddress
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data object Unknown : Identifier() {
|
||||
override val stringValue: String = ""
|
||||
|
|
|
|||
|
|
@ -40,6 +40,9 @@ data class NFTCollection(
|
|||
@Serializable
|
||||
data class TON(val contractAddress: String?) : Identifier()
|
||||
|
||||
@Serializable
|
||||
data class Solana(val collection: String?) : Identifier()
|
||||
|
||||
@Serializable
|
||||
data object Unknown : Identifier()
|
||||
}
|
||||
|
|
|
|||
1
domain/quotes/.gitignore
vendored
Normal file
1
domain/quotes/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
9
domain/quotes/build.gradle.kts
Normal file
9
domain/quotes/build.gradle.kts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
plugins {
|
||||
alias(deps.plugins.kotlin.jvm)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(projects.domain.core)
|
||||
api(projects.domain.tokens.models)
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.domain.quotes
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Quote
|
||||
|
||||
/**
|
||||
* Quotes repository
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface QuotesRepositoryV2 {
|
||||
|
||||
/** Get quotes by [currenciesIds] synchronously or null */
|
||||
suspend fun getMultiQuoteSyncOrNull(currenciesIds: Set<CryptoCurrency.RawID>): Set<Quote>?
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.domain.quotes.multi
|
||||
|
||||
import com.tangem.domain.core.flow.FlowFetcher
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
||||
/**
|
||||
* Fetcher of quotes
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface MultiQuoteFetcher : FlowFetcher<MultiQuoteFetcher.Params> {
|
||||
|
||||
/**
|
||||
* Params
|
||||
*
|
||||
* @property currenciesIds identifiers of currencies
|
||||
* @property appCurrencyId app currency id, if null then selected app currency will be used
|
||||
*/
|
||||
data class Params(
|
||||
val currenciesIds: Set<CryptoCurrency.RawID>,
|
||||
val appCurrencyId: String?,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.domain.quotes.multi
|
||||
|
||||
/**
|
||||
* Updater of quotes
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface MultiQuoteUpdater {
|
||||
|
||||
/** Subscribe */
|
||||
fun subscribe()
|
||||
|
||||
/** Unsubscribe */
|
||||
fun unsubscribe()
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.domain.quotes.single
|
||||
|
||||
import com.tangem.domain.core.flow.FlowProducer
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Quote
|
||||
|
||||
/**
|
||||
* Producer of quote [CryptoCurrency.RawID]
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface SingleQuoteProducer : FlowProducer<Quote> {
|
||||
|
||||
data class Params(val rawCurrencyId: CryptoCurrency.RawID)
|
||||
|
||||
interface Factory : FlowProducer.Factory<Params, SingleQuoteProducer>
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.domain.quotes.single
|
||||
|
||||
import com.tangem.domain.core.flow.FlowCachingSupplier
|
||||
import com.tangem.domain.tokens.model.Quote
|
||||
|
||||
/**
|
||||
* Supplier of quote [SingleQuoteProducer.Params]
|
||||
*
|
||||
* @property factory factory for creating [SingleQuoteProducer]
|
||||
* @property keyCreator key creator
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
abstract class SingleQuoteSupplier(
|
||||
override val factory: SingleQuoteProducer.Factory,
|
||||
override val keyCreator: (SingleQuoteProducer.Params) -> String,
|
||||
) : FlowCachingSupplier<SingleQuoteProducer, SingleQuoteProducer.Params, Quote>()
|
||||
|
|
@ -1,10 +1,7 @@
|
|||
package com.tangem.domain.staking
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.staking.model.stakekit.BalanceItem
|
||||
import com.tangem.domain.staking.model.stakekit.BalanceType
|
||||
import com.tangem.domain.staking.model.stakekit.StakingError
|
||||
import com.tangem.domain.staking.model.stakekit.Token
|
||||
import com.tangem.domain.staking.model.stakekit.*
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingAction
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
|
||||
import com.tangem.domain.staking.repositories.StakingErrorResolver
|
||||
|
|
@ -51,6 +48,9 @@ class InvalidatePendingTransactionsUseCase(
|
|||
}
|
||||
StakingActionType.WITHDRAW -> {
|
||||
modifyBalancesByStatus(balances, action, BalanceType.UNSTAKED)
|
||||
if (token.network == NetworkType.TON) {
|
||||
modifyBalancesByStatus(balances, action, BalanceType.PREPARING)
|
||||
}
|
||||
}
|
||||
StakingActionType.UNLOCK_LOCKED -> {
|
||||
modifyBalancesByStatus(balances, action, BalanceType.LOCKED)
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ dependencies {
|
|||
implementation(projects.domain.promo.models)
|
||||
implementation(projects.domain.promo)
|
||||
implementation(projects.domain.networks)
|
||||
implementation(projects.domain.quotes)
|
||||
|
||||
/** Project - Api */
|
||||
implementation(projects.features.send.api)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,13 @@ sealed interface Quote {
|
|||
|
||||
val rawCurrencyId: CryptoCurrency.RawID
|
||||
|
||||
fun copySealed(source: StatusSource): Quote {
|
||||
return when (this) {
|
||||
is Empty -> this
|
||||
is Value -> copy(source = source)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents unknown financial information for a specific cryptocurrency.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import arrow.core.raise.Raise
|
|||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
|
|
@ -12,6 +13,9 @@ 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
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
|
||||
/**
|
||||
* A use case for adding multiple cryptocurrencies to a user's wallet.
|
||||
|
|
@ -20,12 +24,14 @@ import com.tangem.domain.wallets.models.UserWalletId
|
|||
* network statuses, particularly after the addition of new tokens.
|
||||
*/
|
||||
// TODO: Add tests
|
||||
@Suppress("LongParameterList")
|
||||
class AddCryptoCurrenciesUseCase(
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
private val stakingRepository: StakingRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
private val multiQuoteFetcher: MultiQuoteFetcher,
|
||||
private val tokensFeatureToggles: TokensFeatureToggles,
|
||||
) {
|
||||
|
||||
|
|
@ -68,9 +74,14 @@ class AddCryptoCurrenciesUseCase(
|
|||
val currencyToAdd = currency.takeUnless(existingCurrencies::contains) ?: return@either
|
||||
|
||||
addCurrencies(userWalletId, currencyToAdd)
|
||||
refreshUpdatedNetworks(userWalletId, currencyToAdd, existingCurrencies)
|
||||
refreshUpdatedYieldBalances(userWalletId, currencyToAdd)
|
||||
refreshUpdatedQuotes(currencyToAdd)
|
||||
|
||||
coroutineScope {
|
||||
awaitAll(
|
||||
async { refreshUpdatedNetworks(userWalletId, currencyToAdd, existingCurrencies) },
|
||||
async { refreshUpdatedYieldBalances(userWalletId, currencyToAdd) },
|
||||
async { refreshUpdatedQuotes(currencyToAdd) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend operator fun invoke(
|
||||
|
|
@ -94,9 +105,15 @@ class AddCryptoCurrenciesUseCase(
|
|||
}
|
||||
val tokenToAdd = createTokenCurrency(userWalletId, contractAddress, networkId)
|
||||
addCurrencies(userWalletId, tokenToAdd)
|
||||
refreshUpdatedNetworks(userWalletId, tokenToAdd, existingCurrencies)
|
||||
refreshUpdatedYieldBalances(userWalletId, tokenToAdd)
|
||||
refreshUpdatedQuotes(tokenToAdd)
|
||||
|
||||
coroutineScope {
|
||||
awaitAll(
|
||||
async { refreshUpdatedNetworks(userWalletId, tokenToAdd, existingCurrencies) },
|
||||
async { refreshUpdatedYieldBalances(userWalletId, tokenToAdd) },
|
||||
async { refreshUpdatedQuotes(tokenToAdd) },
|
||||
)
|
||||
}
|
||||
|
||||
tokenToAdd
|
||||
}
|
||||
|
||||
|
|
@ -150,10 +167,19 @@ class AddCryptoCurrenciesUseCase(
|
|||
}
|
||||
|
||||
private suspend fun refreshUpdatedQuotes(currencyToAdd: CryptoCurrency) {
|
||||
quotesRepository.fetchQuotes(
|
||||
currenciesIds = setOfNotNull(currencyToAdd.id.rawCurrencyId),
|
||||
refresh = true,
|
||||
)
|
||||
if (tokensFeatureToggles.isQuotesLoadingRefactoringEnabled) {
|
||||
multiQuoteFetcher(
|
||||
params = MultiQuoteFetcher.Params(
|
||||
currenciesIds = setOfNotNull(currencyToAdd.id.rawCurrencyId),
|
||||
appCurrencyId = null,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
quotesRepository.fetchQuotes(
|
||||
currenciesIds = setOfNotNull(currencyToAdd.id.rawCurrencyId),
|
||||
refresh = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun Raise<Throwable>.createTokenCurrency(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import arrow.core.raise.Raise
|
|||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
|
@ -17,12 +18,14 @@ import kotlinx.coroutines.async
|
|||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
class FetchCardTokenListUseCase(
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val stakingRepository: StakingRepository,
|
||||
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
private val multiQuoteFetcher: MultiQuoteFetcher,
|
||||
private val tokensFeatureToggles: TokensFeatureToggles,
|
||||
) {
|
||||
|
||||
|
|
@ -90,10 +93,16 @@ class FetchCardTokenListUseCase(
|
|||
}
|
||||
|
||||
private suspend fun fetchQuotes(currenciesIds: Set<CryptoCurrency.RawID>, refresh: Boolean) {
|
||||
catch(
|
||||
block = { quotesRepository.getQuotesSync(currenciesIds, refresh) },
|
||||
catch = { /* Ignore error */ },
|
||||
)
|
||||
if (tokensFeatureToggles.isQuotesLoadingRefactoringEnabled) {
|
||||
multiQuoteFetcher(
|
||||
params = MultiQuoteFetcher.Params(currenciesIds = currenciesIds, appCurrencyId = null),
|
||||
)
|
||||
} else {
|
||||
catch(
|
||||
block = { quotesRepository.getQuotesSync(currenciesIds, refresh) },
|
||||
catch = { /* Ignore error */ },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchYieldBalances(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import arrow.core.raise.Raise
|
|||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.error.CurrencyStatusError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
|
@ -27,12 +28,14 @@ import kotlinx.coroutines.coroutineScope
|
|||
* @param quotesRepository The repository for retrieving cryptocurrency quotes.
|
||||
*/
|
||||
// TODO: Add tests
|
||||
@Suppress("LongParameterList")
|
||||
class FetchCurrencyStatusUseCase(
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val stakingRepository: StakingRepository,
|
||||
private val singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
||||
private val multiQuoteFetcher: MultiQuoteFetcher,
|
||||
private val tokensFeatureToggles: TokensFeatureToggles,
|
||||
) {
|
||||
|
||||
|
|
@ -134,10 +137,19 @@ class FetchCurrencyStatusUseCase(
|
|||
}
|
||||
|
||||
private suspend fun Raise<CurrencyStatusError>.fetchQuote(currencyId: CryptoCurrency.ID, refresh: Boolean) {
|
||||
catch(
|
||||
block = { quotesRepository.getQuotesSync(setOfNotNull(currencyId.rawCurrencyId), refresh) },
|
||||
) {
|
||||
raise(CurrencyStatusError.DataError(it))
|
||||
if (tokensFeatureToggles.isQuotesLoadingRefactoringEnabled) {
|
||||
multiQuoteFetcher(
|
||||
params = MultiQuoteFetcher.Params(
|
||||
currenciesIds = setOfNotNull(currencyId.rawCurrencyId),
|
||||
appCurrencyId = null,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
catch(
|
||||
block = { quotesRepository.getQuotesSync(setOfNotNull(currencyId.rawCurrencyId), refresh) },
|
||||
) {
|
||||
raise(CurrencyStatusError.DataError(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import arrow.core.raise.either
|
|||
import arrow.core.raise.ensureNotNull
|
||||
import arrow.core.toNonEmptyListOrNull
|
||||
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
|
@ -29,12 +30,14 @@ import kotlinx.coroutines.coroutineScope
|
|||
* @param stakingRepository The repository for retrieving staking-related data.
|
||||
*/
|
||||
// TODO: Add tests
|
||||
@Suppress("LongParameterList")
|
||||
class FetchTokenListUseCase(
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val stakingRepository: StakingRepository,
|
||||
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
private val multiQuoteFetcher: MultiQuoteFetcher,
|
||||
private val tokensFeatureToggles: TokensFeatureToggles,
|
||||
) {
|
||||
|
||||
|
|
@ -125,13 +128,22 @@ class FetchTokenListUseCase(
|
|||
}
|
||||
|
||||
private suspend fun fetchQuotes(currenciesIds: Set<CryptoCurrency.ID>, refresh: Boolean) {
|
||||
catch(
|
||||
block = {
|
||||
val rawIds = currenciesIds.mapNotNull { it.rawCurrencyId }.toSet()
|
||||
quotesRepository.getQuotesSync(rawIds, refresh)
|
||||
},
|
||||
) {
|
||||
/* Ignore error */
|
||||
if (tokensFeatureToggles.isQuotesLoadingRefactoringEnabled) {
|
||||
multiQuoteFetcher(
|
||||
params = MultiQuoteFetcher.Params(
|
||||
currenciesIds = currenciesIds.mapNotNull { it.rawCurrencyId }.toSet(),
|
||||
appCurrencyId = null,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
catch(
|
||||
block = {
|
||||
val rawIds = currenciesIds.mapNotNull { it.rawCurrencyId }.toSet()
|
||||
quotesRepository.getQuotesSync(rawIds, refresh)
|
||||
},
|
||||
) {
|
||||
/* Ignore error */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.domain.tokens
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.tokens.error.CurrencyStatusError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
class GetCryptoCurrenciesUseCase(
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Retrieves the list of cryptocurrencies within a multi-currency wallet.
|
||||
*
|
||||
* @param userWalletId The unique identifier of the user wallet.
|
||||
*
|
||||
* @return An [Either] representing success (Right) or an error (Left) in fetching the status.
|
||||
*/
|
||||
suspend operator fun invoke(userWalletId: UserWalletId): Either<CurrencyStatusError, List<CryptoCurrency>> {
|
||||
return Either.catch {
|
||||
currenciesRepository.getMultiCurrencyWalletCurrenciesSync(userWalletId)
|
||||
}.mapLeft(CurrencyStatusError::DataError)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import arrow.core.Either
|
|||
import arrow.core.getOrElse
|
||||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
|
||||
import com.tangem.domain.tokens.error.QuotesError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
|
|
@ -16,6 +17,8 @@ import kotlinx.coroutines.coroutineScope
|
|||
class RefreshMultiCurrencyWalletQuotesUseCase(
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val multiQuoteFetcher: MultiQuoteFetcher,
|
||||
private val tokensFeatureToggles: TokensFeatureToggles,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(userWalletId: UserWalletId): Either<QuotesError, Unit> {
|
||||
|
|
@ -45,14 +48,23 @@ class RefreshMultiCurrencyWalletQuotesUseCase(
|
|||
}
|
||||
|
||||
private suspend fun fetchQuotes(currenciesIds: Set<CryptoCurrency.ID>) {
|
||||
catch(
|
||||
block = {
|
||||
quotesRepository.fetchQuotes(
|
||||
if (tokensFeatureToggles.isQuotesLoadingRefactoringEnabled) {
|
||||
multiQuoteFetcher(
|
||||
params = MultiQuoteFetcher.Params(
|
||||
currenciesIds = currenciesIds.mapNotNullTo(hashSetOf(), CryptoCurrency.ID::rawCurrencyId),
|
||||
refresh = true,
|
||||
)
|
||||
},
|
||||
catch = { /* Ignore error */ },
|
||||
)
|
||||
appCurrencyId = null,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
catch(
|
||||
block = {
|
||||
quotesRepository.fetchQuotes(
|
||||
currenciesIds = currenciesIds.mapNotNullTo(hashSetOf(), CryptoCurrency.ID::rawCurrencyId),
|
||||
refresh = true,
|
||||
)
|
||||
},
|
||||
catch = { /* Ignore error */ },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,4 +8,8 @@ package com.tangem.domain.tokens
|
|||
interface TokensFeatureToggles {
|
||||
|
||||
val isNetworksLoadingRefactoringEnabled: Boolean
|
||||
|
||||
val isQuotesLoadingRefactoringEnabled: Boolean
|
||||
|
||||
val isStakingLoadingRefactoringEnabled: Boolean
|
||||
}
|
||||
|
|
@ -10,6 +10,9 @@ import com.tangem.domain.networks.multi.MultiNetworkStatusProducer
|
|||
import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusProducer
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
||||
import com.tangem.domain.quotes.QuotesRepositoryV2
|
||||
import com.tangem.domain.quotes.single.SingleQuoteProducer
|
||||
import com.tangem.domain.quotes.single.SingleQuoteSupplier
|
||||
import com.tangem.domain.staking.model.stakekit.YieldBalance
|
||||
import com.tangem.domain.staking.model.stakekit.YieldBalanceList
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
|
|
@ -37,10 +40,12 @@ import kotlinx.coroutines.flow.*
|
|||
abstract class BaseCurrencyStatusOperations(
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val quotesRepositoryV2: QuotesRepositoryV2,
|
||||
private val networksRepository: NetworksRepository,
|
||||
private val stakingRepository: StakingRepository,
|
||||
private val multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
|
||||
private val singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
||||
private val singleQuoteSupplier: SingleQuoteSupplier,
|
||||
private val tokensFeatureToggles: TokensFeatureToggles,
|
||||
) {
|
||||
|
||||
|
|
@ -171,7 +176,16 @@ abstract class BaseCurrencyStatusOperations(
|
|||
} else {
|
||||
currenciesRepository.getMultiCurrencyWalletCurrency(userWalletId, cryptoCurrencyId)
|
||||
}
|
||||
val quote = cryptoCurrencyId.rawCurrencyId?.let { quotesRepository.getQuoteSync(it) }?.right()
|
||||
|
||||
val quote = cryptoCurrencyId.rawCurrencyId?.let { rawId ->
|
||||
if (tokensFeatureToggles.isQuotesLoadingRefactoringEnabled) {
|
||||
singleQuoteSupplier(params = SingleQuoteProducer.Params(rawCurrencyId = rawId))
|
||||
.firstOrNull()
|
||||
} else {
|
||||
quotesRepository.getQuoteSync(rawId)
|
||||
}
|
||||
}
|
||||
?.right()
|
||||
?: Error.EmptyQuotes.left()
|
||||
|
||||
val networkStatuses = if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) {
|
||||
|
|
@ -237,7 +251,12 @@ abstract class BaseCurrencyStatusOperations(
|
|||
?: return emptyList<CryptoCurrencyStatus>().right()
|
||||
val (networks, currenciesIds) = getIds(nonEmptyCurrencies)
|
||||
val rawIds = currenciesIds.mapNotNull { it.rawCurrencyId }.toSet()
|
||||
val quotes = quotesRepository.getQuotesSync(rawIds, false).right()
|
||||
|
||||
val quotes = if (tokensFeatureToggles.isQuotesLoadingRefactoringEnabled) {
|
||||
quotesRepositoryV2.getMultiQuoteSyncOrNull(currenciesIds = rawIds)?.right()
|
||||
} else {
|
||||
quotesRepository.getQuotesSync(rawIds, false).right()
|
||||
}
|
||||
|
||||
val networkStatuses = if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) {
|
||||
multiNetworkStatusSupplier(
|
||||
|
|
@ -268,13 +287,23 @@ abstract class BaseCurrencyStatusOperations(
|
|||
block = { currenciesRepository.getSingleCurrencyWalletPrimaryCurrency(userWalletId) },
|
||||
catch = { raise(Error.DataError(it)) },
|
||||
)
|
||||
val quotes = catch(
|
||||
block = {
|
||||
currency.id.rawCurrencyId?.let { quotesRepository.getQuoteSync(it) }
|
||||
?.right() ?: Error.EmptyQuotes.left()
|
||||
},
|
||||
catch = { Error.DataError(it).left() },
|
||||
)
|
||||
|
||||
val quotes = if (tokensFeatureToggles.isQuotesLoadingRefactoringEnabled) {
|
||||
currency.id.rawCurrencyId?.let {
|
||||
singleQuoteSupplier(params = SingleQuoteProducer.Params(rawCurrencyId = it))
|
||||
.firstOrNull()
|
||||
}
|
||||
?.right()
|
||||
?: Error.EmptyQuotes.left()
|
||||
} else {
|
||||
catch(
|
||||
block = {
|
||||
currency.id.rawCurrencyId?.let { quotesRepository.getQuoteSync(it) }
|
||||
?.right() ?: Error.EmptyQuotes.left()
|
||||
},
|
||||
catch = { Error.DataError(it).left() },
|
||||
)
|
||||
}
|
||||
|
||||
val networkStatus = if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) {
|
||||
singleNetworkStatusSupplier(
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@ import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
|||
import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusProducer
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
||||
import com.tangem.domain.quotes.QuotesRepositoryV2
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
|
||||
import com.tangem.domain.quotes.single.SingleQuoteProducer
|
||||
import com.tangem.domain.quotes.single.SingleQuoteSupplier
|
||||
import com.tangem.domain.staking.model.stakekit.YieldBalance
|
||||
import com.tangem.domain.staking.model.stakekit.YieldBalanceList
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
|
|
@ -34,20 +38,25 @@ import kotlinx.coroutines.flow.*
|
|||
class CachedCurrenciesStatusesOperations(
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
quotesRepositoryV2: QuotesRepositoryV2,
|
||||
private val networksRepository: NetworksRepository,
|
||||
private val stakingRepository: StakingRepository,
|
||||
private val singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
||||
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
|
||||
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
private val multiQuoteFetcher: MultiQuoteFetcher,
|
||||
private val singleQuoteSupplier: SingleQuoteSupplier,
|
||||
private val tokensFeatureToggles: TokensFeatureToggles,
|
||||
) : BaseCurrenciesStatusesOperations,
|
||||
BaseCurrencyStatusOperations(
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
quotesRepositoryV2 = quotesRepositoryV2,
|
||||
networksRepository = networksRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
||||
singleQuoteSupplier = singleQuoteSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
) {
|
||||
|
||||
|
|
@ -178,7 +187,14 @@ class CachedCurrenciesStatusesOperations(
|
|||
},
|
||||
async {
|
||||
val rawCurrenciesIds = currenciesIds.mapNotNullTo(mutableSetOf()) { it.rawCurrencyId }
|
||||
quotesRepository.fetchQuotes(rawCurrenciesIds)
|
||||
|
||||
if (tokensFeatureToggles.isQuotesLoadingRefactoringEnabled) {
|
||||
multiQuoteFetcher(
|
||||
params = MultiQuoteFetcher.Params(currenciesIds = rawCurrenciesIds, appCurrencyId = null),
|
||||
)
|
||||
} else {
|
||||
quotesRepository.fetchQuotes(rawCurrenciesIds)
|
||||
}
|
||||
},
|
||||
async { stakingRepository.fetchMultiYieldBalance(userWalletId, currencies) },
|
||||
)
|
||||
|
|
@ -249,27 +265,44 @@ class CachedCurrenciesStatusesOperations(
|
|||
}
|
||||
|
||||
private fun getQuotes(tokensIds: NonEmptySet<CryptoCurrency.ID>): Flow<Either<TokenListError, Set<Quote>>> {
|
||||
return quotesRepository.getQuotesUpdates(tokensIds.mapNotNull { it.rawCurrencyId }.toSet())
|
||||
.map<Set<Quote>, Either<TokenListError, Set<Quote>>> { it.right() }
|
||||
.retryWhen { cause, _ ->
|
||||
emit(TokenListError.DataError(cause).left())
|
||||
// adding delay before retry to avoid spam when flow restarted
|
||||
delay(RETRY_DELAY)
|
||||
true
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
return if (tokensFeatureToggles.isQuotesLoadingRefactoringEnabled) {
|
||||
getQuotesUpdates(
|
||||
rawCurrencyIds = tokensIds.mapNotNullTo(
|
||||
destination = hashSetOf(),
|
||||
transform = CryptoCurrency.ID::rawCurrencyId,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
quotesRepository.getQuotesUpdates(tokensIds.mapNotNull { it.rawCurrencyId }.toSet())
|
||||
.map<Set<Quote>, Either<TokenListError, Set<Quote>>> { it.right() }
|
||||
.retryWhen { cause, _ ->
|
||||
emit(TokenListError.DataError(cause).left())
|
||||
// adding delay before retry to avoid spam when flow restarted
|
||||
delay(RETRY_DELAY)
|
||||
true
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getQuotes(id: CryptoCurrency.RawID): Flow<Either<Error, Set<Quote>>> {
|
||||
return quotesRepository.getQuotesUpdates(setOf(id))
|
||||
.map<Set<Quote>, Either<Error, Set<Quote>>> { it.right() }
|
||||
.retryWhen { cause, _ ->
|
||||
emit(Error.DataError(cause).left())
|
||||
// adding delay before retry to avoid spam when flow restarted
|
||||
delay(RETRY_DELAY)
|
||||
true
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
return if (tokensFeatureToggles.isQuotesLoadingRefactoringEnabled) {
|
||||
singleQuoteSupplier(
|
||||
params = SingleQuoteProducer.Params(rawCurrencyId = id),
|
||||
)
|
||||
.map<Quote, Either<Error, Set<Quote>>> { setOf(it).right() }
|
||||
.distinctUntilChanged()
|
||||
} else {
|
||||
quotesRepository.getQuotesUpdates(setOf(id))
|
||||
.map<Set<Quote>, Either<Error, Set<Quote>>> { it.right() }
|
||||
.retryWhen { cause, _ ->
|
||||
emit(Error.DataError(cause).left())
|
||||
// adding delay before retry to avoid spam when flow restarted
|
||||
delay(RETRY_DELAY)
|
||||
true
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getNetworksStatuses(
|
||||
|
|
@ -363,6 +396,33 @@ class CachedCurrenciesStatusesOperations(
|
|||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
// temporary code because token list is built using networks list
|
||||
private fun getQuotesUpdates(rawCurrencyIds: Set<CryptoCurrency.RawID>): EitherFlow<TokenListError, Set<Quote>> {
|
||||
return channelFlow {
|
||||
val state = MutableStateFlow(emptySet<Quote>())
|
||||
|
||||
rawCurrencyIds.onEach {
|
||||
launch {
|
||||
singleQuoteSupplier(
|
||||
params = SingleQuoteProducer.Params(rawCurrencyId = it),
|
||||
)
|
||||
.onEach { quote ->
|
||||
state.update { loadedStatuses ->
|
||||
loadedStatuses.addOrReplace(quote) { it.rawCurrencyId == quote.rawCurrencyId }
|
||||
}
|
||||
}
|
||||
.launchIn(scope = this)
|
||||
}
|
||||
}
|
||||
|
||||
state
|
||||
.onEach(::send)
|
||||
.launchIn(scope = this)
|
||||
}
|
||||
.map<Set<Quote>, Either<TokenListError, Set<Quote>>> { it.right() }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
private fun isFetchingStarted(userWalletId: UserWalletId): Boolean {
|
||||
return fetchingState.value[userWalletId]?.let { it.isStarted() || it.isFinished() } ?: false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -180,10 +180,15 @@ internal class GetPrimaryCurrencyStatusUpdatesUseCaseTest {
|
|||
|
||||
tokensFeatureToggles = object : TokensFeatureToggles {
|
||||
override val isNetworksLoadingRefactoringEnabled: Boolean = false
|
||||
override val isQuotesLoadingRefactoringEnabled: Boolean = false
|
||||
override val isStakingLoadingRefactoringEnabled: Boolean = false
|
||||
},
|
||||
singleNetworkStatusSupplier = mockk(),
|
||||
multiNetworkStatusFetcher = mockk(),
|
||||
multiNetworkStatusSupplier = mockk(),
|
||||
multiQuoteFetcher = mockk(),
|
||||
singleQuoteSupplier = mockk(),
|
||||
quotesRepositoryV2 = mockk(),
|
||||
),
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -78,4 +78,11 @@ interface TransactionRepository {
|
|||
cryptoCurrency: CryptoCurrency.Token,
|
||||
spenderAddress: String,
|
||||
): BigDecimal
|
||||
|
||||
suspend fun prepareForSend(
|
||||
transactionData: TransactionData,
|
||||
signer: TransactionSigner,
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
): Result<ByteArray>
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.tangem.domain.transaction.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.domain.card.models.TwinKey
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.transaction.TransactionRepository
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
||||
class PrepareForSendUseCase(
|
||||
private val transactionRepository: TransactionRepository,
|
||||
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
) {
|
||||
suspend operator fun invoke(
|
||||
transactionData: TransactionData,
|
||||
userWallet: UserWallet,
|
||||
network: Network,
|
||||
): Either<Throwable, ByteArray> {
|
||||
val card = userWallet.scanResponse.card
|
||||
val isCardNotBackedUp = card.backupStatus?.isActive != true && !card.isTangemTwins
|
||||
|
||||
val signer = cardSdkConfigRepository.getCommonSigner(
|
||||
cardId = card.cardId.takeIf { isCardNotBackedUp },
|
||||
twinKey = TwinKey.getOrNull(scanResponse = userWallet.scanResponse),
|
||||
)
|
||||
return transactionRepository.prepareForSend(
|
||||
transactionData = transactionData,
|
||||
userWalletId = userWallet.walletId,
|
||||
network = network,
|
||||
signer = signer,
|
||||
)
|
||||
.fold(onSuccess = { it.right() }, onFailure = { it.left() })
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.tangem.domain.transaction.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.domain.card.models.TwinKey
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
||||
class SignUseCase(
|
||||
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
) {
|
||||
suspend operator fun invoke(
|
||||
hash: ByteArray,
|
||||
userWallet: UserWallet,
|
||||
network: Network,
|
||||
): Either<TangemError, ByteArray> {
|
||||
val card = userWallet.scanResponse.card
|
||||
val isCardNotBackedUp = card.backupStatus?.isActive != true && !card.isTangemTwins
|
||||
|
||||
val signer = cardSdkConfigRepository.getCommonSigner(
|
||||
cardId = card.cardId.takeIf { isCardNotBackedUp },
|
||||
twinKey = TwinKey.getOrNull(scanResponse = userWallet.scanResponse),
|
||||
)
|
||||
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(userWallet.walletId, network)
|
||||
?: error("WalletManager not found")
|
||||
|
||||
return when (val signResult = signer.sign(hash, walletManager.wallet.publicKey)) {
|
||||
is CompletionResult.Failure -> signResult.error.left()
|
||||
is CompletionResult.Success -> signResult.data.right()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,5 +10,6 @@ dependencies {
|
|||
ksp(deps.moshi.kotlin.codegen)
|
||||
implementation(deps.moshi.adapters)
|
||||
implementation(deps.kotlin.serialization)
|
||||
implementation(deps.jodatime)
|
||||
implementation(projects.core.error)
|
||||
}
|
||||
|
|
@ -8,6 +8,6 @@ import kotlinx.serialization.Serializable
|
|||
@JsonClass(generateAdapter = true)
|
||||
data class VisaActivationOrderInfo(
|
||||
@Json(name = "orderId") val orderId: String,
|
||||
@Json(name = "customer_id") val customerId: String,
|
||||
@Json(name = "customer_wallet_address") val customerWalletAddress: String,
|
||||
@Json(name = "customerId") val customerId: String,
|
||||
@Json(name = "customerWalletAddress") val customerWalletAddress: String,
|
||||
)
|
||||
|
|
@ -20,9 +20,14 @@ sealed class VisaActivationRemoteState {
|
|||
data object PaymentAccountDeploying : VisaActivationRemoteState()
|
||||
|
||||
@Serializable
|
||||
data class WaitingPinCode(
|
||||
data class AwaitingPinCode(
|
||||
val activationOrderInfo: VisaActivationOrderInfo,
|
||||
) : VisaActivationRemoteState()
|
||||
val status: Status,
|
||||
) : VisaActivationRemoteState() {
|
||||
enum class Status {
|
||||
WaitingForPinCode, InProgress, WasError
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data object WaitingForActivationFinishing : VisaActivationRemoteState()
|
||||
|
|
@ -43,6 +48,7 @@ class VisaActivationRemoteState_Json(
|
|||
@Json(name = "type") val type: VisaActivationRemoteState_Type,
|
||||
@Json(name = "requestCardWallet") val requestCardWallet: VisaCardWalletDataToSignRequest? = null,
|
||||
@Json(name = "activationOrderInfo") val activationOrderInfo: VisaActivationOrderInfo? = null,
|
||||
@Json(name = "awaiting_pin_code_status") val awaitingPinCodeStatus: AwaitingPinCodeStatus_Type? = null,
|
||||
)
|
||||
|
||||
@Suppress("ClassNaming")
|
||||
|
|
@ -58,6 +64,12 @@ enum class VisaActivationRemoteState_Type {
|
|||
BlockedForActivation,
|
||||
}
|
||||
|
||||
@Suppress("ClassNaming")
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class AwaitingPinCodeStatus_Type {
|
||||
WaitingForPinCode, InProgress, WasError
|
||||
}
|
||||
|
||||
@Suppress("ClassNaming")
|
||||
class VisaActivationRemoteState_JsonAdapter {
|
||||
|
||||
|
|
@ -70,7 +82,17 @@ class VisaActivationRemoteState_JsonAdapter {
|
|||
VisaActivationRemoteState.CustomerWalletSignatureRequired(value.activationOrderInfo!!)
|
||||
VisaActivationRemoteState_Type.PaymentAccountDeploying -> VisaActivationRemoteState.PaymentAccountDeploying
|
||||
VisaActivationRemoteState_Type.WaitingPinCode ->
|
||||
VisaActivationRemoteState.WaitingPinCode(value.activationOrderInfo!!)
|
||||
VisaActivationRemoteState.AwaitingPinCode(
|
||||
activationOrderInfo = value.activationOrderInfo!!,
|
||||
status = when (value.awaitingPinCodeStatus!!) {
|
||||
AwaitingPinCodeStatus_Type.WaitingForPinCode ->
|
||||
VisaActivationRemoteState.AwaitingPinCode.Status.WaitingForPinCode
|
||||
AwaitingPinCodeStatus_Type.InProgress ->
|
||||
VisaActivationRemoteState.AwaitingPinCode.Status.InProgress
|
||||
AwaitingPinCodeStatus_Type.WasError ->
|
||||
VisaActivationRemoteState.AwaitingPinCode.Status.WasError
|
||||
},
|
||||
)
|
||||
VisaActivationRemoteState_Type.WaitingForActivationFinishing ->
|
||||
VisaActivationRemoteState.WaitingForActivationFinishing
|
||||
VisaActivationRemoteState_Type.Activated -> VisaActivationRemoteState.Activated
|
||||
|
|
@ -93,10 +115,18 @@ class VisaActivationRemoteState_JsonAdapter {
|
|||
)
|
||||
is VisaActivationRemoteState.PaymentAccountDeploying ->
|
||||
VisaActivationRemoteState_Json(VisaActivationRemoteState_Type.PaymentAccountDeploying)
|
||||
is VisaActivationRemoteState.WaitingPinCode ->
|
||||
is VisaActivationRemoteState.AwaitingPinCode ->
|
||||
VisaActivationRemoteState_Json(
|
||||
VisaActivationRemoteState_Type.WaitingPinCode,
|
||||
activationOrderInfo = value.activationOrderInfo,
|
||||
awaitingPinCodeStatus = when (value.status) {
|
||||
VisaActivationRemoteState.AwaitingPinCode.Status.WaitingForPinCode ->
|
||||
AwaitingPinCodeStatus_Type.WaitingForPinCode
|
||||
VisaActivationRemoteState.AwaitingPinCode.Status.InProgress ->
|
||||
AwaitingPinCodeStatus_Type.InProgress
|
||||
VisaActivationRemoteState.AwaitingPinCode.Status.WasError ->
|
||||
AwaitingPinCodeStatus_Type.WasError
|
||||
},
|
||||
)
|
||||
is VisaActivationRemoteState.WaitingForActivationFinishing ->
|
||||
VisaActivationRemoteState_Json(VisaActivationRemoteState_Type.WaitingForActivationFinishing)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ plugins {
|
|||
dependencies {
|
||||
/* Project - Domain */
|
||||
implementation(projects.domain.core)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.walletConnect.models)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.domain.walletconnect.model
|
||||
|
||||
sealed interface WcEthMethod : WcMethod {
|
||||
|
||||
data class PersonalEthSign(
|
||||
val message: String,
|
||||
val account: String,
|
||||
) : WcEthMethod
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
package com.tangem.domain.walletconnect.model
|
||||
|
||||
interface WcMethod {
|
||||
sealed interface WcMethod {
|
||||
data object Unsupported : WcMethod
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
package com.tangem.domain.walletconnect.model
|
||||
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
|
||||
sealed interface WcNetwork {
|
||||
|
||||
val name: String
|
||||
|
||||
data class Supported(val network: Network) : WcNetwork {
|
||||
override val name: String get() = network.name
|
||||
}
|
||||
|
||||
data class Unknown(override val name: String) : WcNetwork
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ sealed class WcPairError(override val message: String) : Exception(message) {
|
|||
|
||||
data object UnsupportedDApp : WcPairError("UnsupportedDApp")
|
||||
data class UnsupportedNetworks(
|
||||
val chains: Set<WcNetwork.Unknown>,
|
||||
val chains: Set<String>,
|
||||
) : WcPairError("ApprovalErrorMissingNetworks")
|
||||
|
||||
data class ExternalApprovalError(
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
package com.tangem.domain.walletconnect.model
|
||||
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSession
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
||||
data class WcSession(
|
||||
val userWalletId: UserWalletId,
|
||||
val wallet: UserWallet,
|
||||
val sdkModel: WcSdkSession,
|
||||
)
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
package com.tangem.domain.walletconnect.model
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
||||
data class WcSessionApprove(
|
||||
val walletId: UserWalletId,
|
||||
val network: List<WcNetwork.Supported>,
|
||||
val wallet: UserWallet,
|
||||
val network: List<Network>,
|
||||
)
|
||||
|
|
@ -1,19 +1,20 @@
|
|||
package com.tangem.domain.walletconnect.model
|
||||
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcAppMetaData
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
||||
data class WcSessionProposal(
|
||||
val dAppMetaData: WcAppMetaData,
|
||||
val proposalNetwork: Map<UserWalletId, ProposalNetwork>,
|
||||
val proposalNetwork: Map<UserWallet, ProposalNetwork>,
|
||||
val securityStatus: Any,
|
||||
) {
|
||||
|
||||
data class ProposalNetwork(
|
||||
val walletId: UserWalletId,
|
||||
val missingRequired: Set<WcNetwork.Supported>,
|
||||
val required: Set<WcNetwork.Supported>,
|
||||
val available: Set<WcNetwork.Supported>,
|
||||
val notAdded: Set<WcNetwork.Supported>,
|
||||
val wallet: UserWallet,
|
||||
val missingRequired: Set<Network>,
|
||||
val required: Set<Network>,
|
||||
val available: Set<Network>,
|
||||
val notAdded: Set<Network>,
|
||||
)
|
||||
}
|
||||
|
|
@ -2,12 +2,12 @@ package com.tangem.domain.walletconnect.repository
|
|||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.walletconnect.model.WcSession
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface WcSessionsManager {
|
||||
val sessions: Flow<Map<UserWalletId, List<WcSession>>>
|
||||
suspend fun saveSession(userWalletId: UserWalletId, session: WcSession)
|
||||
suspend fun removeSession(userWalletId: UserWalletId, session: WcSession): Either<Throwable, Unit>
|
||||
val sessions: Flow<Map<UserWallet, List<WcSession>>>
|
||||
suspend fun saveSession(session: WcSession)
|
||||
suspend fun removeSession(session: WcSession): Either<Throwable, Unit>
|
||||
suspend fun findSessionByTopic(topic: String): WcSession?
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
package com.tangem.domain.walletconnect.request
|
||||
|
||||
import com.tangem.domain.walletconnect.model.WcRequest
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface WcRequestService {
|
||||
val requests: Flow<WcRequest<*>>
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package com.tangem.domain.walletconnect.respond
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
|
||||
|
||||
interface WcRespondService {
|
||||
suspend fun respond(request: WcSdkSessionRequest, response: String): Either<Throwable, Unit>
|
||||
suspend fun rejectRequest(request: WcSdkSessionRequest, message: String = ""): Either<Throwable, Unit>
|
||||
}
|
||||
|
|
@ -2,16 +2,16 @@ package com.tangem.domain.walletconnect.usecase
|
|||
|
||||
import com.tangem.domain.walletconnect.model.WcSession
|
||||
import com.tangem.domain.walletconnect.repository.WcSessionsManager
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
class WcSessionsUseCase(private val sessionsManager: WcSessionsManager) {
|
||||
operator fun invoke(): Flow<Map<UserWalletId, List<WcSession>>> {
|
||||
operator fun invoke(): Flow<Map<UserWallet, List<WcSession>>> {
|
||||
return sessionsManager.sessions
|
||||
}
|
||||
|
||||
suspend fun invokeSync(): Map<UserWalletId, List<WcSession>> {
|
||||
suspend fun invokeSync(): Map<UserWallet, List<WcSession>> {
|
||||
return sessionsManager.sessions.first()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
package com.tangem.domain.walletconnect.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface WcSimpleSignUseCase<SignModel, MiddleAction> : WcUseCase {
|
||||
|
||||
fun signFlow(initModel: SignModel): Flow<State<SignModel>>
|
||||
|
||||
fun action(action: MiddleAction)
|
||||
|
||||
fun cancel()
|
||||
fun sign(toSign: SignModel)
|
||||
|
||||
sealed interface State<SignModel> {
|
||||
val model: SignModel
|
||||
|
||||
data class PreSign<SignModel>(override val model: SignModel) : State<SignModel>
|
||||
data class Signing<SignModel>(override val model: SignModel) : State<SignModel>
|
||||
data class Result<SignModel>(
|
||||
val result: Either<Throwable, Unit>,
|
||||
override val model: SignModel,
|
||||
) : State<SignModel>
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,13 @@
|
|||
package com.tangem.domain.walletconnect.usecase
|
||||
|
||||
interface WcUseCase
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.walletconnect.model.WcSession
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
|
||||
|
||||
interface WcUseCase
|
||||
|
||||
interface WcMethodUseCase : WcUseCase {
|
||||
val session: WcSession
|
||||
val rawSdkRequest: WcSdkSessionRequest
|
||||
val network: Network
|
||||
}
|
||||
|
|
@ -25,6 +25,6 @@ class WcDisconnectUseCase(
|
|||
}
|
||||
|
||||
suspend fun disconnect(session: WcSession) {
|
||||
sessionsManager.removeSession(session.userWalletId, session)
|
||||
sessionsManager.removeSession(session)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
package com.tangem.domain.walletconnect.usecase.ethereum
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.walletconnect.model.WcRequest
|
||||
import com.tangem.domain.walletconnect.respond.WcRespondService
|
||||
import com.tangem.domain.walletconnect.usecase.WcUseCase
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
class EthPersonalSignUseCase(
|
||||
private val wcRequest: WcRequest<WcEthMethod.SignMessage>,
|
||||
private val respondService: WcRespondService,
|
||||
) : WcUseCase {
|
||||
|
||||
private val onCallTerminalAction = Channel<TerminalAction>()
|
||||
|
||||
fun signFlow(): Flow<State> = flow {
|
||||
val model = SignModel(wcRequest.method.raw)
|
||||
emit(State.PreSign(model))
|
||||
|
||||
when (val action = onCallTerminalAction.receiveAsFlow().first()) {
|
||||
TerminalAction.Cancel -> {
|
||||
val result = respondService.rejectRequest(wcRequest.rawSdkRequest)
|
||||
emit(State.Result(result, model))
|
||||
}
|
||||
is TerminalAction.Sign -> {
|
||||
emit(State.Signing(action.toSign))
|
||||
val signed = signAndPrepareForSend()
|
||||
val result =
|
||||
if (signed != null) {
|
||||
respondService.respond(wcRequest.rawSdkRequest, signed)
|
||||
} else {
|
||||
respondService.rejectRequest(wcRequest.rawSdkRequest)
|
||||
}
|
||||
emit(State.Result(result, model))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("FunctionOnlyReturningConstant") // todo(wc) remove later
|
||||
private suspend fun signAndPrepareForSend(): String? {
|
||||
return null // todo(wc)
|
||||
}
|
||||
|
||||
fun sign(toSign: SignModel) {
|
||||
onCallTerminalAction.trySend(TerminalAction.Sign(toSign))
|
||||
}
|
||||
|
||||
fun cancel() {
|
||||
onCallTerminalAction.trySend(TerminalAction.Cancel)
|
||||
}
|
||||
|
||||
sealed interface TerminalAction {
|
||||
data class Sign(val toSign: SignModel) : TerminalAction
|
||||
data object Cancel : TerminalAction
|
||||
}
|
||||
|
||||
sealed interface State {
|
||||
val model: SignModel
|
||||
|
||||
data class PreSign(override val model: SignModel) : State
|
||||
data class Signing(override val model: SignModel) : State
|
||||
data class Result(
|
||||
val result: Either<Throwable, Unit>,
|
||||
override val model: SignModel,
|
||||
) : State
|
||||
}
|
||||
|
||||
data class SignModel(
|
||||
val raw: List<String>,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
package com.tangem.domain.walletconnect.usecase.ethereum
|
||||
|
||||
import com.tangem.domain.walletconnect.model.WcMethod
|
||||
|
||||
sealed interface WcEthMethod : WcMethod {
|
||||
|
||||
data class SignMessage(
|
||||
val raw: List<String>,
|
||||
) : WcEthMethod
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.domain.walletconnect.usecase.ethereum
|
||||
|
||||
import com.tangem.domain.walletconnect.usecase.sign.WcSignUseCase
|
||||
|
||||
interface WcPersonalEthSignUseCase :
|
||||
WcSignUseCase,
|
||||
WcSignUseCase.FinalAction,
|
||||
WcSignUseCase.SimpleRun<WcPersonalEthSignUseCase.SignModel> {
|
||||
|
||||
data class SignModel(
|
||||
val humanMsg: String,
|
||||
val account: String,
|
||||
val rawMsg: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.domain.walletconnect.usecase.sign
|
||||
|
||||
import arrow.core.Either
|
||||
|
||||
data class WcSignState<SignModel>(
|
||||
val signModel: SignModel,
|
||||
val domainStep: WcSignStep,
|
||||
)
|
||||
|
||||
sealed interface WcSignStep {
|
||||
data object PreSign : WcSignStep
|
||||
data object Signing : WcSignStep
|
||||
data class Result(val result: Either<Throwable, Unit>) : WcSignStep
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.domain.walletconnect.usecase.sign
|
||||
|
||||
import com.tangem.domain.walletconnect.usecase.WcMethodUseCase
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface WcSignUseCase : WcMethodUseCase {
|
||||
|
||||
interface FinalAction {
|
||||
fun cancel()
|
||||
fun sign()
|
||||
}
|
||||
|
||||
interface SimpleRun<SignModel> {
|
||||
operator fun invoke(): Flow<WcSignState<SignModel>>
|
||||
}
|
||||
|
||||
interface ArgsRun<SignModel, Args> {
|
||||
operator fun invoke(args: Args): Flow<WcSignState<SignModel>>
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,6 @@ import com.tangem.domain.models.scan.ScanResponse
|
|||
*
|
||||
* @property name User wallet name
|
||||
* @property walletId User wallet [UserWalletId]
|
||||
* @property artworkUrl User wallet card artwork URL
|
||||
* @property cardsInWallet List of cards IDs assigned with this user's wallet. The list will be empty if the wallet
|
||||
* has been backed up on another device.
|
||||
* @property isMultiCurrency Indicates whether this user wallet can work with more than one currency
|
||||
|
|
@ -22,8 +21,6 @@ data class UserWallet(
|
|||
val name: String,
|
||||
@Json(name = "walletId")
|
||||
val walletId: UserWalletId,
|
||||
@Json(name = "artworkUrl")
|
||||
val artworkUrl: String,
|
||||
@Json(name = "cardsInWallet")
|
||||
val cardsInWallet: Set<String>,
|
||||
@Json(name = "isMultiCurrency")
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import com.tangem.domain.models.scan.CardDTO
|
|||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetCardImageUseCase
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
|
@ -13,7 +12,6 @@ import dagger.assisted.AssistedInject
|
|||
class UserWalletBuilder @AssistedInject constructor(
|
||||
@Assisted private val scanResponse: ScanResponse,
|
||||
private val generateWalletNameUseCase: GenerateWalletNameUseCase,
|
||||
private val getCardImageUseCase: GetCardImageUseCase,
|
||||
) {
|
||||
private var backupCardsIds: Set<String> = emptySet()
|
||||
private var hasBackupError: Boolean = false
|
||||
|
|
@ -38,7 +36,7 @@ class UserWalletBuilder @AssistedInject constructor(
|
|||
this.hasBackupError = hasBackupError
|
||||
}
|
||||
|
||||
suspend fun build(): UserWallet? {
|
||||
fun build(): UserWallet? {
|
||||
return with(scanResponse) {
|
||||
UserWalletIdBuilder.scanResponse(scanResponse)
|
||||
.build()
|
||||
|
|
@ -50,7 +48,6 @@ class UserWalletBuilder @AssistedInject constructor(
|
|||
isBackupNotAllowed = card.isBackupNotAllowed,
|
||||
isStartToCoin = cardTypesResolver.isStart2Coin(),
|
||||
),
|
||||
artworkUrl = getCardImageUseCase.invoke(card.cardId, card.cardPublicKey),
|
||||
cardsInWallet = backupCardsIds.plus(card.cardId),
|
||||
scanResponse = this,
|
||||
isMultiCurrency = cardTypesResolver.isMultiwalletAllowed(),
|
||||
|
|
|
|||
|
|
@ -4,8 +4,12 @@ import com.tangem.common.extensions.toHexString
|
|||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.common.TwinCardNumber
|
||||
import com.tangem.domain.common.TwinsHelper
|
||||
import com.tangem.domain.models.ArtworkModel
|
||||
import com.tangem.domain.wallets.models.Artwork
|
||||
import com.tangem.operations.attestation.ArtworkSize
|
||||
import com.tangem.operations.attestation.CardArtworksProvider
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.sdk.api.featuretoggles.CardSdkFeatureToggles
|
||||
|
||||
/**
|
||||
* Use case for getting card image url
|
||||
|
|
@ -14,7 +18,11 @@ import com.tangem.operations.attestation.OnlineCardVerifier
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class GetCardImageUseCase(private val verifier: OnlineCardVerifier) {
|
||||
class GetCardImageUseCase(
|
||||
private val verifier: OnlineCardVerifier,
|
||||
private val cardArtworksProvider: CardArtworksProvider,
|
||||
private val cardSdkFeatureToggles: CardSdkFeatureToggles,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Get card image url
|
||||
|
|
@ -22,14 +30,30 @@ class GetCardImageUseCase(private val verifier: OnlineCardVerifier) {
|
|||
* @param cardId card id
|
||||
* @param cardPublicKey card public key
|
||||
*/
|
||||
suspend operator fun invoke(cardId: String, cardPublicKey: ByteArray): String {
|
||||
suspend operator fun invoke(cardId: String, cardPublicKey: ByteArray, size: ArtworkSize): ArtworkModel {
|
||||
return if (cardSdkFeatureToggles.isNewArtworkLoadingEnabled) {
|
||||
val result = cardArtworksProvider.getArtwork(
|
||||
cardId = cardId,
|
||||
cardPublicKey = cardPublicKey,
|
||||
size = size,
|
||||
)
|
||||
when (result) {
|
||||
is Result.Failure -> ArtworkModel(null, getFallbackArtworkUrl(cardId))
|
||||
is Result.Success -> ArtworkModel(result.data, getFallbackArtworkUrl(cardId))
|
||||
}
|
||||
} else {
|
||||
ArtworkModel(null, getLegacyArtwork(cardId, cardPublicKey))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getLegacyArtwork(cardId: String, cardPublicKey: ByteArray): String {
|
||||
return when (val result = verifier.getCardInfo(cardId, cardPublicKey)) {
|
||||
is Result.Success -> {
|
||||
val artworkId = result.data.artwork?.id
|
||||
if (artworkId.isNullOrEmpty()) {
|
||||
getFallbackArtworkUrl(cardId)
|
||||
} else {
|
||||
OnlineCardVerifier.getUrlForArtwork(
|
||||
CardArtworksProvider.getUrlForArtwork(
|
||||
cardId = cardId,
|
||||
cardPublicKey = cardPublicKey.toHexString(),
|
||||
artworkId = artworkId,
|
||||
|
|
@ -41,8 +65,6 @@ class GetCardImageUseCase(private val verifier: OnlineCardVerifier) {
|
|||
}
|
||||
}
|
||||
|
||||
fun getDefaultFallbackUrl(): String = Artwork.DEFAULT_IMG_URL
|
||||
|
||||
private fun getFallbackArtworkUrl(cardId: String): String {
|
||||
return when {
|
||||
cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL
|
||||
|
|
@ -50,7 +72,7 @@ class GetCardImageUseCase(private val verifier: OnlineCardVerifier) {
|
|||
else -> when (TwinsHelper.getTwinCardNumber(cardId)) {
|
||||
TwinCardNumber.First -> Artwork.TWIN_CARD_1_URL
|
||||
TwinCardNumber.Second -> Artwork.TWIN_CARD_2_URL
|
||||
else -> getDefaultFallbackUrl()
|
||||
else -> Artwork.DEFAULT_IMG_URL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue