Updated on 2026-08-14
This commit is contained in:
parent
ab2f0f58bb
commit
22ad2ddc67
22 changed files with 190 additions and 15 deletions
|
|
@ -79,6 +79,18 @@ internal object TransactionDomainModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOpenTrustlineUseCase(
|
||||
cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
): OpenTrustlineUseCase {
|
||||
return OpenTrustlineUseCase(
|
||||
cardSdkConfigRepository = cardSdkConfigRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideDismissIncompleteTransactionUseCase(
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.core.ui.extensions.resourceReference
|
|||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
fun ScenarioUnavailabilityReason.getUnavailabilityReasonText(): TextReference {
|
||||
return when (val unavailabilityReason = this) {
|
||||
is ScenarioUnavailabilityReason.StakingUnavailable -> {
|
||||
|
|
@ -46,6 +47,9 @@ fun ScenarioUnavailabilityReason.getUnavailabilityReasonText(): TextReference {
|
|||
ScenarioUnavailabilityReason.UnassociatedAsset -> resourceReference(
|
||||
id = R.string.warning_receive_blocked_hedera_token_association_required_message,
|
||||
)
|
||||
ScenarioUnavailabilityReason.TrustlineRequired -> resourceReference(
|
||||
id = R.string.warning_receive_blocked_token_trustline_required_message,
|
||||
)
|
||||
ScenarioUnavailabilityReason.UsedOutdatedData -> {
|
||||
resourceReference(id = R.string.token_button_unavailability_reason_out_of_date_balance)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1299,8 +1299,8 @@
|
|||
<string name="warning_token_balance_not_updated">Der Kontostand ist möglicherweise veraltet. Aktualisiere bittre die Seite.</string>
|
||||
<string name="warning_token_required_min_coin_reserve">Nicht genug %1$s. Lade Dein XLM-Konto auf, um dieses Token zu verknüpfen</string>
|
||||
<string name="warning_token_trustline_button_title">Trustline aktivieren</string>
|
||||
<string name="warning_token_trustline_subtiile">Um dieses Token zu erhalten, muss eine Trustline aktiviert sein. Das Netzwerk benötigt eine Reserve von %1$s %2$s.</string>
|
||||
<string name="warning_token_trustline_tiile">Trustline erforderlich</string>
|
||||
<string name="warning_token_trustline_subtitle">Um dieses Token zu erhalten, muss eine Trustline aktiviert sein. Das Netzwerk benötigt eine Reserve von %1$s %2$s.</string>
|
||||
<string name="warning_token_trustline_title">Trustline erforderlich</string>
|
||||
<string name="wc_alert_audit_unknown_domain">Unbekannte Domäne</string>
|
||||
<string name="wc_alert_connect_anyway">Trotzdem verbinden</string>
|
||||
<string name="wc_alert_connection_timeout_description">Zeitüberschreitungsfehler. Bitte versuche es später erneut.</string>
|
||||
|
|
|
|||
|
|
@ -1279,8 +1279,8 @@
|
|||
<string name="warning_token_balance_not_updated">残高が古い可能性があります。ページを更新してください。</string>
|
||||
<string name="warning_token_required_min_coin_reserve">%1$sが足りません。このトークンを関連付けるには、XLMアカウントにチャージしてください。</string>
|
||||
<string name="warning_token_trustline_button_title">トラストラインを有効にする</string>
|
||||
<string name="warning_token_trustline_subtiile">このトークンを受け取るには、トラストラインを有効にする必要があります。ネットワークには%1$s %2$s予備金が必要です。</string>
|
||||
<string name="warning_token_trustline_tiile">トラストラインが必要</string>
|
||||
<string name="warning_token_trustline_subtitle">このトークンを受け取るには、トラストラインを有効にする必要があります。ネットワークには%1$s %2$s予備金が必要です。</string>
|
||||
<string name="warning_token_trustline_title">トラストラインが必要</string>
|
||||
<string name="wc_alert_audit_unknown_domain">不明なドメイン</string>
|
||||
<string name="wc_alert_connect_anyway">とにかく接続する</string>
|
||||
<string name="wc_alert_connection_timeout_description">タイムアウトエラーが発生しました。しばらくしてからもう一度お試しください。</string>
|
||||
|
|
|
|||
|
|
@ -1263,8 +1263,8 @@
|
|||
<string name="warning_testnet_card_title">Только для целей тестирования</string>
|
||||
<string name="warning_token_balance_not_updated">Возможно, баланс устарел. Обновите страницу.</string>
|
||||
<string name="warning_token_trustline_button_title">Открыть Trustline</string>
|
||||
<string name="warning_token_trustline_subtiile">Согласно правилам сети, чтобы пополнить этот токен, необходимо сначала открыть Trustline — это позволит вашему кошельку принимать и хранить этот актив.</string>
|
||||
<string name="warning_token_trustline_tiile">Откройте Trustline</string>
|
||||
<string name="warning_token_trustline_subtitle">Согласно правилам сети, чтобы пополнить этот токен, необходимо сначала открыть Trustline — это позволит вашему кошельку принимать и хранить этот актив.</string>
|
||||
<string name="warning_token_trustline_title">Откройте Trustline</string>
|
||||
<string name="wc_alert_session_disconnected_description">Пожалуйста, вернитесь в браузер и выполните повторное подключение через WalletConnect.</string>
|
||||
<string name="wc_alert_wrong_card_description">Выбрана не верная карта или кольцо</string>
|
||||
<string name="wc_common_address">Адрес</string>
|
||||
|
|
|
|||
|
|
@ -1344,8 +1344,8 @@
|
|||
<string name="warning_token_balance_not_updated">Balance may be outdated. Refresh the page.</string>
|
||||
<string name="warning_token_required_min_coin_reserve">Not enough %1$s. Top up your XLM account to associate this token</string>
|
||||
<string name="warning_token_trustline_button_title">Enable Trustline</string>
|
||||
<string name="warning_token_trustline_subtiile">A Trustline must be enabled to receive this token. The network requires a %1$s %2$s reserve.</string>
|
||||
<string name="warning_token_trustline_tiile">Trustline Required</string>
|
||||
<string name="warning_token_trustline_subtitle">A Trustline must be enabled to receive this token. The network requires a %1$s %2$s reserve.</string>
|
||||
<string name="warning_token_trustline_title">Trustline Required</string>
|
||||
<string name="wc_alert_audit_unknown_domain">Unknown domain</string>
|
||||
<string name="wc_alert_connect_anyway">Connect anyway</string>
|
||||
<string name="wc_alert_connection_timeout_description">Timeout error. Please, try again later.</string>
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ internal class DefaultCustomTokensRepository(
|
|||
-> true
|
||||
Blockchain.Cardano,
|
||||
Blockchain.Sui,
|
||||
Blockchain.Stellar,
|
||||
-> blockchain.validateContractAddress(contractAddress)
|
||||
else -> blockchain.validateAddress(contractAddress)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,11 @@ internal class SdkRequirementsConditionConverter : Converter<SdkRequirementsCond
|
|||
override fun convert(value: SdkRequirementsCondition): AssetRequirementsCondition {
|
||||
return when (value) {
|
||||
is SdkRequirementsCondition.PaidTransaction -> AssetRequirementsCondition.PaidTransaction
|
||||
is SdkRequirementsCondition.RequiredTrustline -> TODO("added in [REDACTED_TASK_KEY]")
|
||||
is SdkRequirementsCondition.RequiredTrustline -> AssetRequirementsCondition.RequiredTrustline(
|
||||
requiredAmount = requireNotNull(value.amount.value),
|
||||
currencySymbol = value.amount.currencySymbol,
|
||||
decimals = value.amount.decimals,
|
||||
)
|
||||
is SdkRequirementsCondition.PaidTransactionWithFee -> AssetRequirementsCondition.PaidTransactionWithFee(
|
||||
feeAmount = requireNotNull(value.feeAmount.value),
|
||||
feeCurrencySymbol = value.feeAmount.currencySymbol,
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@ sealed class ScenarioUnavailabilityReason {
|
|||
|
||||
data object UnassociatedAsset : ScenarioUnavailabilityReason()
|
||||
|
||||
data object TrustlineRequired : ScenarioUnavailabilityReason()
|
||||
|
||||
enum class WithdrawalScenario {
|
||||
SELL, SEND // TODO staking create&process STAKING
|
||||
}
|
||||
|
|
|
|||
|
|
@ -167,6 +167,7 @@ sealed class TokenScreenAnalyticsEvent(
|
|||
private const val SINGLE_WALLET = "Single Wallet"
|
||||
private const val LOADING = "Loading"
|
||||
private const val ASSET_REQUIREMENT = "AssetRequirement"
|
||||
private const val TRUSTLINE_REQUIREMENT = "TrustlineRequirement"
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
fun ScenarioUnavailabilityReason.toReasonAnalyticsText(): String {
|
||||
|
|
@ -177,6 +178,7 @@ sealed class TokenScreenAnalyticsEvent(
|
|||
is ScenarioUnavailabilityReason.StakingUnavailable,
|
||||
-> UNAVAILABLE
|
||||
ScenarioUnavailabilityReason.UnassociatedAsset -> ASSET_REQUIREMENT
|
||||
ScenarioUnavailabilityReason.TrustlineRequired -> TRUSTLINE_REQUIREMENT
|
||||
is ScenarioUnavailabilityReason.EmptyBalance -> EMPTY
|
||||
is ScenarioUnavailabilityReason.PendingTransaction -> PENDING
|
||||
ScenarioUnavailabilityReason.UsedOutdatedData -> CACHING
|
||||
|
|
|
|||
|
|
@ -61,4 +61,11 @@ sealed class CryptoCurrencyWarning {
|
|||
) : CryptoCurrencyWarning()
|
||||
|
||||
data object UsedOutdatedDataWarning : CryptoCurrencyWarning()
|
||||
|
||||
data class RequiredTrustline(
|
||||
val currency: CryptoCurrency,
|
||||
val currencySymbol: String,
|
||||
val requiredAmount: BigDecimal,
|
||||
val currencyDecimals: Int,
|
||||
) : CryptoCurrencyWarning()
|
||||
}
|
||||
|
|
@ -375,12 +375,14 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
}
|
||||
|
||||
private fun getReceiveScenario(requirements: AssetRequirementsCondition?): ScenarioUnavailabilityReason {
|
||||
return if (requirements is AssetRequirementsCondition.PaidTransaction ||
|
||||
requirements is AssetRequirementsCondition.PaidTransactionWithFee
|
||||
) {
|
||||
ScenarioUnavailabilityReason.UnassociatedAsset
|
||||
} else {
|
||||
ScenarioUnavailabilityReason.None
|
||||
return when (requirements) {
|
||||
AssetRequirementsCondition.PaidTransaction,
|
||||
is AssetRequirementsCondition.PaidTransactionWithFee,
|
||||
-> ScenarioUnavailabilityReason.UnassociatedAsset
|
||||
is AssetRequirementsCondition.IncompleteTransaction,
|
||||
null,
|
||||
-> ScenarioUnavailabilityReason.None
|
||||
is AssetRequirementsCondition.RequiredTrustline -> ScenarioUnavailabilityReason.TrustlineRequired
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -222,6 +222,12 @@ class GetCurrencyWarningsUseCase(
|
|||
): CryptoCurrencyWarning? {
|
||||
return when (val requirements = walletManagersFacade.getAssetRequirements(userWalletId, currency)) {
|
||||
is AssetRequirementsCondition.PaidTransaction -> HederaWarnings.AssociateWarning(currency = currency)
|
||||
is AssetRequirementsCondition.RequiredTrustline -> CryptoCurrencyWarning.RequiredTrustline(
|
||||
currency = currency,
|
||||
requiredAmount = requirements.requiredAmount,
|
||||
currencyDecimals = requirements.decimals,
|
||||
currencySymbol = requirements.currencySymbol,
|
||||
)
|
||||
is AssetRequirementsCondition.PaidTransactionWithFee -> {
|
||||
HederaWarnings.AssociateWarningWithFee(
|
||||
currency = currency,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,15 @@ sealed class AssetRequirementsCondition {
|
|||
*/
|
||||
data object PaidTransaction : AssetRequirementsCondition()
|
||||
|
||||
/**
|
||||
* Trustlines are an explicit opt-in for an account to hold a particular asset.
|
||||
*/
|
||||
data class RequiredTrustline(
|
||||
val requiredAmount: BigDecimal,
|
||||
val currencySymbol: String,
|
||||
val decimals: Int,
|
||||
) : AssetRequirementsCondition()
|
||||
|
||||
/**
|
||||
* The exact value of the fee for this type of condition is stored in `feeAmount`.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.domain.transaction.error
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
sealed interface OpenTrustlineError {
|
||||
val message: String?
|
||||
|
||||
data class SomeError(override val message: String?) : OpenTrustlineError
|
||||
data class NotEnoughCoin(val amount: BigDecimal, override val message: String?) : OpenTrustlineError
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.tangem.domain.transaction.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.transaction.error.OpenTrustlineError
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
class OpenTrustlineUseCase(
|
||||
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
): Either<OpenTrustlineError, Unit> {
|
||||
return either {
|
||||
val signer = cardSdkConfigRepository.getCommonSigner(
|
||||
cardId = null,
|
||||
twinKey = null, // use null here because no assets support for Twin cards
|
||||
)
|
||||
|
||||
catch(
|
||||
block = {
|
||||
when (val result = walletManagersFacade.fulfillRequirements(userWalletId, currency, signer)) {
|
||||
is SimpleResult.Failure -> when (val error = result.error) {
|
||||
is BlockchainSdkError.Stellar.MinReserveRequired ->
|
||||
raise(OpenTrustlineError.NotEnoughCoin(error.amount, result.error.customMessage))
|
||||
else -> raise(OpenTrustlineError.SomeError(result.error.customMessage))
|
||||
}
|
||||
SimpleResult.Success -> Unit
|
||||
}
|
||||
},
|
||||
catch = { error -> raise(OpenTrustlineError.SomeError(error.message)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -51,6 +51,7 @@ internal class TokenDetailsNotificationsAnalyticsSender(
|
|||
is TokenDetailsNotification.RentInfo,
|
||||
is TokenDetailsNotification.NetworkShutdown,
|
||||
is TokenDetailsNotification.HederaAssociateWarning,
|
||||
is TokenDetailsNotification.RequiredTrustlineWarning,
|
||||
is TokenDetailsNotification.KoinosMana,
|
||||
is TokenDetailsNotification.MigrationMaticToPol,
|
||||
is TokenDetailsNotification.UsedOutdatedData,
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ interface TokenDetailsClickIntents {
|
|||
|
||||
fun onRetryIncompleteTransactionClick()
|
||||
|
||||
fun onOpenTrustlineClick()
|
||||
|
||||
fun onDismissIncompleteTransactionClick()
|
||||
|
||||
fun onConfirmDismissIncompleteTransactionClick()
|
||||
|
|
|
|||
|
|
@ -56,9 +56,11 @@ import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent.Compan
|
|||
import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent
|
||||
import com.tangem.domain.transaction.error.AssociateAssetError
|
||||
import com.tangem.domain.transaction.error.IncompleteTransactionError
|
||||
import com.tangem.domain.transaction.error.OpenTrustlineError
|
||||
import com.tangem.domain.transaction.error.SendTransactionError
|
||||
import com.tangem.domain.transaction.usecase.AssociateAssetUseCase
|
||||
import com.tangem.domain.transaction.usecase.DismissIncompleteTransactionUseCase
|
||||
import com.tangem.domain.transaction.usecase.OpenTrustlineUseCase
|
||||
import com.tangem.domain.transaction.usecase.RetryIncompleteTransactionUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
|
||||
|
|
@ -116,6 +118,7 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
private val isDemoCardUseCase: IsDemoCardUseCase,
|
||||
private val associateAssetUseCase: AssociateAssetUseCase,
|
||||
private val retryIncompleteTransactionUseCase: RetryIncompleteTransactionUseCase,
|
||||
private val openTrustlineUseCase: OpenTrustlineUseCase,
|
||||
private val dismissIncompleteTransactionUseCase: DismissIncompleteTransactionUseCase,
|
||||
private val reduxStateHolder: ReduxStateHolder,
|
||||
private val analyticsEventsHandler: AnalyticsEventHandler,
|
||||
|
|
@ -893,6 +896,37 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onOpenTrustlineClick() {
|
||||
analyticsEventsHandler.send(
|
||||
TokenScreenAnalyticsEvent.Associate(
|
||||
tokenSymbol = cryptoCurrency.symbol,
|
||||
blockchain = cryptoCurrency.network.name,
|
||||
),
|
||||
)
|
||||
modelScope.launch(dispatchers.mainImmediate) {
|
||||
openTrustlineUseCase(
|
||||
userWalletId = userWalletId,
|
||||
currency = cryptoCurrency,
|
||||
).fold(
|
||||
ifLeft = { e ->
|
||||
Timber.e(e.message)
|
||||
internalUiState.value = when (e) {
|
||||
is OpenTrustlineError.SomeError ->
|
||||
stateFactory.getStateWithErrorDialog(stringReference(e.message.orEmpty()))
|
||||
is OpenTrustlineError.NotEnoughCoin -> {
|
||||
val text = resourceReference(
|
||||
id = R.string.warning_token_required_min_coin_reserve,
|
||||
formatArgs = wrappedList(e.amount),
|
||||
)
|
||||
stateFactory.getStateWithErrorDialog(text)
|
||||
}
|
||||
}
|
||||
},
|
||||
ifRight = { internalUiState.value = stateFactory.getStateWithRemovedRequiredTrustlineNotification() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDismissIncompleteTransactionClick() {
|
||||
analyticsEventsHandler.send(
|
||||
TokenScreenAnalyticsEvent.RevealCancel(
|
||||
|
|
|
|||
|
|
@ -193,6 +193,24 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) {
|
|||
),
|
||||
)
|
||||
|
||||
data class RequiredTrustlineWarning(
|
||||
private val currency: CryptoCurrency,
|
||||
private val amount: String,
|
||||
private val currencySymbol: String,
|
||||
private val onOpenClick: () -> Unit,
|
||||
) : Warning(
|
||||
title = resourceReference(id = R.string.warning_token_trustline_title),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.warning_token_trustline_subtitle,
|
||||
formatArgs = wrappedList(amount, currencySymbol),
|
||||
),
|
||||
iconResId = currency.networkIconResId,
|
||||
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.warning_token_trustline_button_title),
|
||||
onClick = onOpenClick,
|
||||
),
|
||||
)
|
||||
|
||||
data class KaspaIncompleteTransactionWarning(
|
||||
private val currency: CryptoCurrency,
|
||||
private val amount: String,
|
||||
|
|
|
|||
|
|
@ -42,6 +42,12 @@ internal class TokenDetailsNotificationConverter(
|
|||
return newNotifications.toImmutableList()
|
||||
}
|
||||
|
||||
fun removeRequiredTrustlineWarning(currentState: TokenDetailsState): ImmutableList<TokenDetailsNotification> {
|
||||
val newNotifications = currentState.notifications.toMutableList()
|
||||
newNotifications.removeBy { it is RequiredTrustlineWarning }
|
||||
return newNotifications.toImmutableList()
|
||||
}
|
||||
|
||||
fun removeKaspaIncompleteTransactionWarning(
|
||||
currentState: TokenDetailsState,
|
||||
): ImmutableList<TokenDetailsNotification> {
|
||||
|
|
@ -118,6 +124,12 @@ internal class TokenDetailsNotificationConverter(
|
|||
feeCurrencySymbol = warning.feeCurrencySymbol,
|
||||
onAssociateClick = clickIntents::onAssociateClick,
|
||||
)
|
||||
is CryptoCurrencyWarning.RequiredTrustline -> RequiredTrustlineWarning(
|
||||
currency = warning.currency,
|
||||
amount = warning.requiredAmount.format { crypto(symbol = "", warning.currencyDecimals) },
|
||||
currencySymbol = warning.currencySymbol,
|
||||
onOpenClick = clickIntents::onOpenTrustlineClick,
|
||||
)
|
||||
is KaspaWarnings.IncompleteTransaction -> KaspaIncompleteTransactionWarning(
|
||||
currency = warning.currency,
|
||||
amount = warning.amount.format { crypto(symbol = "", decimals = warning.currencyDecimals) },
|
||||
|
|
|
|||
|
|
@ -334,6 +334,11 @@ internal class TokenDetailsStateFactory(
|
|||
return state.copy(notifications = notificationConverter.removeHederaAssociateWarning(state))
|
||||
}
|
||||
|
||||
fun getStateWithRemovedRequiredTrustlineNotification(): TokenDetailsState {
|
||||
val state = currentStateProvider()
|
||||
return state.copy(notifications = notificationConverter.removeRequiredTrustlineWarning(state))
|
||||
}
|
||||
|
||||
fun getStateWithRemovedKaspaIncompleteTransactionNotification(): TokenDetailsState {
|
||||
val state = currentStateProvider()
|
||||
return state.copy(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue