Updated on 2026-08-14
This commit is contained in:
commit
89a1a4510a
140 changed files with 3495 additions and 691 deletions
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.di.domain
|
|||
|
||||
import com.tangem.domain.onramp.*
|
||||
import com.tangem.domain.onramp.repositories.*
|
||||
import com.tangem.domain.promo.PromoRepository
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -264,12 +265,14 @@ internal object OnrampDomainModule {
|
|||
onrampErrorResolver: OnrampErrorResolver,
|
||||
onrampTransactionRepository: OnrampTransactionRepository,
|
||||
settingsRepository: SettingsRepository,
|
||||
promoRepository: PromoRepository,
|
||||
): GetOnrampOffersUseCase {
|
||||
return GetOnrampOffersUseCase(
|
||||
onrampRepository = onrampRepository,
|
||||
errorResolver = onrampErrorResolver,
|
||||
onrampTransactionRepository = onrampTransactionRepository,
|
||||
settingsRepository = settingsRepository,
|
||||
promoRepository = promoRepository,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,9 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.staking.*
|
||||
import com.tangem.domain.staking.repositories.P2PEthPoolRepository
|
||||
import com.tangem.domain.staking.repositories.StakeKitActionRepository
|
||||
import com.tangem.domain.staking.repositories.StakingErrorResolver
|
||||
import com.tangem.domain.staking.repositories.StakeKitRepository
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.staking.repositories.StakeKitTransactionHashRepository
|
||||
import com.tangem.domain.staking.repositories.*
|
||||
import com.tangem.domain.staking.single.SingleYieldBalanceFetcher
|
||||
import com.tangem.domain.staking.toggles.StakingFeatureToggles
|
||||
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
||||
import com.tangem.domain.staking.usecase.StakingAvailabilityListUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -238,10 +232,7 @@ internal object StakingDomainModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideStakingApyFlowUseCase(
|
||||
stakeKitRepository: StakeKitRepository,
|
||||
stakingFeatureToggles: StakingFeatureToggles,
|
||||
): StakingApyFlowUseCase {
|
||||
return StakingApyFlowUseCase(stakeKitRepository, stakingFeatureToggles)
|
||||
fun provideStakingApyFlowUseCase(stakingRepository: StakingRepository): StakingAvailabilityListUseCase {
|
||||
return StakingAvailabilityListUseCase(stakingRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -199,4 +199,30 @@ internal object YieldSupplyDomainModule {
|
|||
yieldSupplyRepository = yieldSupplyRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideYieldSupplyGetShouldShowMainPromoUseCase(
|
||||
yieldSupplyRepository: YieldSupplyRepository,
|
||||
): YieldSupplyGetShouldShowMainPromoUseCase {
|
||||
return YieldSupplyGetShouldShowMainPromoUseCase(
|
||||
yieldSupplyRepository = yieldSupplyRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideYieldSupplySetShouldShowMainPromoUseCase(
|
||||
yieldSupplyRepository: YieldSupplyRepository,
|
||||
): YieldSupplySetShouldShowMainPromoUseCase {
|
||||
return YieldSupplySetShouldShowMainPromoUseCase(
|
||||
yieldSupplyRepository = yieldSupplyRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideYieldSupplyGetDustMinAmountUseCase(): YieldSupplyGetDustMinAmountUseCase {
|
||||
return YieldSupplyGetDustMinAmountUseCase()
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ package com.tangem.tap.features.welcome.model
|
|||
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.routing.entity.InitScreenLaunchMode
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
|
|
@ -28,6 +28,7 @@ import javax.inject.Inject
|
|||
internal class WelcomeModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val appFinisher: AppFinisher,
|
||||
private val analyticsEventsHandler: AnalyticsEventHandler,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model(), StoreSubscriber<WelcomeState> {
|
||||
|
||||
|
|
@ -44,6 +45,7 @@ internal class WelcomeModel @Inject constructor(
|
|||
init {
|
||||
subscribeToStoreChanges()
|
||||
initGlobalState()
|
||||
analyticsEventsHandler.send(SignIn.ScreenOpened())
|
||||
|
||||
val welcomeAction = when (params.launchMode) {
|
||||
is InitScreenLaunchMode.WithCardScan -> WelcomeAction.ProceedWithCard
|
||||
|
|
@ -54,12 +56,12 @@ internal class WelcomeModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun unlockWallets() {
|
||||
Analytics.send(SignIn.ButtonBiometricSignIn())
|
||||
analyticsEventsHandler.send(SignIn.ButtonBiometricSignIn())
|
||||
store.dispatch(WelcomeAction.ProceedWithBiometrics)
|
||||
}
|
||||
|
||||
private fun scanCard() {
|
||||
Analytics.send(SignIn.ButtonCardSignIn())
|
||||
analyticsEventsHandler.send(SignIn.ButtonCardSignIn())
|
||||
store.dispatch(WelcomeAction.ProceedWithCard)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -301,6 +301,12 @@ sealed class NotificationUM(val config: NotificationConfig) {
|
|||
),
|
||||
subtitle = TextReference.EMPTY,
|
||||
)
|
||||
|
||||
data object YieldSupplyHighNetworkFee : Info(
|
||||
title = resourceReference(id = R.string.yield_module_high_network_fees_notification_title),
|
||||
subtitle = resourceReference(id = R.string.yield_module_high_network_fees_notification_description),
|
||||
iconTint = NotificationConfig.IconTint.Accent,
|
||||
)
|
||||
}
|
||||
|
||||
sealed interface Cardano {
|
||||
|
|
|
|||
|
|
@ -108,6 +108,13 @@ object NotificationsFactory {
|
|||
}
|
||||
}
|
||||
|
||||
// Must be called last – shown only when no other notifications exist
|
||||
fun MutableList<NotificationUM>.addHighFeeNotificationIfNoOther(shouldShowHighFeeNotification: Boolean) {
|
||||
if (shouldShowHighFeeNotification && this.isEmpty()) {
|
||||
add(NotificationUM.Info.YieldSupplyHighNetworkFee)
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableList<NotificationUM>.addReserveAmountErrorNotification(
|
||||
reserveAmount: BigDecimal?,
|
||||
sendingAmount: BigDecimal,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.common.ui.tokens
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
|
|
@ -22,7 +21,8 @@ import com.tangem.domain.models.currency.CryptoCurrency
|
|||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.currency.yieldSupplyKey
|
||||
import com.tangem.domain.models.staking.YieldBalance
|
||||
import com.tangem.domain.staking.model.isStakingSupported
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.domain.staking.model.StakingOption
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.staking.utils.getTotalWithRewardsStakingBalance
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
|
|
@ -43,12 +43,14 @@ import java.math.BigDecimal
|
|||
*/
|
||||
class TokenItemStateConverter(
|
||||
private val appCurrency: AppCurrency,
|
||||
private val yieldModuleApyMap: Map<String, String> = emptyMap(),
|
||||
private val stakingApyMap: Map<String, List<Yield.Validator>> = emptyMap(),
|
||||
private val yieldModuleApyMap: Map<String, BigDecimal> = emptyMap(),
|
||||
private val stakingApyMap: Map<CryptoCurrency, StakingAvailability> = emptyMap(),
|
||||
private val yieldSupplyPromoBannerKey: String? = null,
|
||||
private val iconStateProvider: (CryptoCurrencyStatus) -> CurrencyIconState = {
|
||||
CryptoCurrencyToIconStateConverter().convert(it)
|
||||
},
|
||||
private val onApyLabelClick: ((CryptoCurrencyStatus, ApySource, String) -> Unit)? = null,
|
||||
private val onYieldPromoCloseClick: (() -> Unit)? = null,
|
||||
private val titleStateProvider: (CryptoCurrencyStatus) -> TokenItemState.TitleState = { currencyStatus ->
|
||||
createTitleState(
|
||||
currencyStatus = currencyStatus,
|
||||
|
|
@ -66,6 +68,15 @@ class TokenItemStateConverter(
|
|||
private val fiatAmountStateProvider: (CryptoCurrencyStatus) -> TokenItemState.FiatAmountState? = {
|
||||
createFiatAmountState(status = it, appCurrency = appCurrency)
|
||||
},
|
||||
private val promoBannerProvider: (CryptoCurrencyStatus) -> TokenItemState.PromoBannerState = { status ->
|
||||
createPromoBannerState(
|
||||
status = status,
|
||||
yieldModuleApyMap = yieldModuleApyMap,
|
||||
yieldSupplyPromoBannerKey = yieldSupplyPromoBannerKey,
|
||||
onApyLabelClick = onApyLabelClick,
|
||||
onYieldPromoCloseClick = onYieldPromoCloseClick,
|
||||
)
|
||||
},
|
||||
private val onItemClick: ((TokenItemState, CryptoCurrencyStatus) -> Unit)? = null,
|
||||
private val onItemLongClick: ((TokenItemState, CryptoCurrencyStatus) -> Unit)? = null,
|
||||
) : Converter<CryptoCurrencyStatus, TokenItemState> {
|
||||
|
|
@ -102,6 +113,7 @@ class TokenItemStateConverter(
|
|||
subtitleState = requireNotNull(subtitleStateProvider(this)),
|
||||
fiatAmountState = requireNotNull(fiatAmountStateProvider(this)),
|
||||
subtitle2State = requireNotNull(subtitle2StateProvider(this)),
|
||||
promoBannerState = promoBannerProvider(this),
|
||||
onItemClick = onItemClick?.let { onItemClick ->
|
||||
{ onItemClick(it, this) }
|
||||
},
|
||||
|
|
@ -164,8 +176,8 @@ class TokenItemStateConverter(
|
|||
|
||||
private fun createTitleState(
|
||||
currencyStatus: CryptoCurrencyStatus,
|
||||
yieldModuleApyMap: Map<String, String>,
|
||||
stakingApyMap: Map<String, List<Yield.Validator>>,
|
||||
yieldModuleApyMap: Map<String, BigDecimal>,
|
||||
stakingApyMap: Map<CryptoCurrency, StakingAvailability>,
|
||||
onApyLabelClick: ((CryptoCurrencyStatus, ApySource, String) -> Unit)?,
|
||||
): TokenItemState.TitleState {
|
||||
return when (val value = currencyStatus.value) {
|
||||
|
|
@ -204,8 +216,8 @@ class TokenItemStateConverter(
|
|||
// polygon-pos_0xc2132d05d31c914a87c6611c10748aeb04b58e8f
|
||||
private fun resolveEarnApy(
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
yieldModuleApyMap: Map<String, String>,
|
||||
stakingApyMap: Map<String, List<Yield.Validator>>,
|
||||
yieldModuleApyMap: Map<String, BigDecimal>,
|
||||
stakingApyMap: Map<CryptoCurrency, StakingAvailability>,
|
||||
): EarnApyInfo? {
|
||||
val token = cryptoCurrencyStatus.currency as? CryptoCurrency.Token
|
||||
if (token != null && yieldModuleApyMap.isNotEmpty()) {
|
||||
|
|
@ -223,7 +235,7 @@ class TokenItemStateConverter(
|
|||
wrappedList(yieldSupplyApy),
|
||||
),
|
||||
isActive = isActive,
|
||||
apy = yieldSupplyApy,
|
||||
apy = yieldSupplyApy.toString(),
|
||||
source = ApySource.YIELD_SUPPLY,
|
||||
)
|
||||
}
|
||||
|
|
@ -260,46 +272,42 @@ class TokenItemStateConverter(
|
|||
|
||||
private fun findStakingRate(
|
||||
currencyStatus: CryptoCurrencyStatus,
|
||||
stakingApyMap: Map<String, List<Yield.Validator>>,
|
||||
stakingApyMap: Map<CryptoCurrency, StakingAvailability>,
|
||||
): StakingLocalInfo {
|
||||
val stakingKey = currencyStatus.currency.stakingKey()
|
||||
val validators = stakingApyMap[stakingKey]
|
||||
val stakingAvailability = stakingApyMap[currencyStatus.currency] as? StakingAvailability.Available
|
||||
?: return StakingLocalInfo(rate = null, isActive = false, rewardType = null)
|
||||
|
||||
val yieldBalance = currencyStatus.value.yieldBalance
|
||||
val hasStakedBalance = yieldBalance is YieldBalance.Data
|
||||
|
||||
val rateInfo: Pair<BigDecimal, Yield.RewardType?>? = if (hasStakedBalance) {
|
||||
val validatorsByAddress = validators.associateBy { it.address }
|
||||
yieldBalance.balance.items
|
||||
.mapNotNull { it.validatorAddress }
|
||||
.firstNotNullOfOrNull { address ->
|
||||
val validator = validatorsByAddress[address]
|
||||
validator?.rewardInfo?.rate?.let { rate ->
|
||||
rate to validator.rewardInfo?.type
|
||||
}
|
||||
}
|
||||
?: validators
|
||||
val rateInfo = when (val stakingOptions = stakingAvailability.option) {
|
||||
is StakingOption.P2P -> null // todo p2p
|
||||
is StakingOption.StakeKit -> if (hasStakedBalance) {
|
||||
val validatorsByAddress = stakingOptions.yield.validators.associateBy { it.address }
|
||||
yieldBalance.balance.items
|
||||
.mapNotNull { it.validatorAddress }
|
||||
.firstNotNullOfOrNull { address ->
|
||||
validatorsByAddress[address]?.rewardInfo
|
||||
} ?: stakingOptions.yield.validators
|
||||
.filter { it.preferred }
|
||||
.mapNotNull { validator ->
|
||||
validator.rewardInfo?.rate?.let { rate -> rate to validator.rewardInfo?.type }
|
||||
validator.rewardInfo
|
||||
}
|
||||
.maxByOrNull { it.first }
|
||||
} else {
|
||||
validators
|
||||
.filter { it.preferred }
|
||||
.mapNotNull { validator ->
|
||||
validator.rewardInfo?.rate?.let { rate ->
|
||||
rate to validator.rewardInfo?.type
|
||||
.maxByOrNull { it.rate }
|
||||
} else {
|
||||
stakingOptions.yield.validators
|
||||
.filter { it.preferred }
|
||||
.mapNotNull { validator ->
|
||||
validator.rewardInfo
|
||||
}
|
||||
}
|
||||
.maxByOrNull { it.first }
|
||||
.maxByOrNull { it.rate }
|
||||
}
|
||||
}
|
||||
|
||||
return StakingLocalInfo(
|
||||
rate = rateInfo?.first,
|
||||
rate = rateInfo?.rate,
|
||||
isActive = hasStakedBalance,
|
||||
rewardType = rateInfo?.second,
|
||||
rewardType = rateInfo?.type,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -381,6 +389,39 @@ class TokenItemStateConverter(
|
|||
}
|
||||
}
|
||||
|
||||
private fun createPromoBannerState(
|
||||
status: CryptoCurrencyStatus,
|
||||
yieldModuleApyMap: Map<String, BigDecimal>,
|
||||
yieldSupplyPromoBannerKey: String?,
|
||||
onApyLabelClick: ((CryptoCurrencyStatus, ApySource, String) -> Unit)?,
|
||||
onYieldPromoCloseClick: (() -> Unit)?,
|
||||
): TokenItemState.PromoBannerState {
|
||||
val token = status.currency as? CryptoCurrency.Token ?: return TokenItemState.PromoBannerState.Empty
|
||||
if (status.value !is CryptoCurrencyStatus.Loaded) {
|
||||
return TokenItemState.PromoBannerState.Empty
|
||||
}
|
||||
if (yieldSupplyPromoBannerKey == null || yieldSupplyPromoBannerKey != token.yieldSupplyKey() ||
|
||||
yieldModuleApyMap[token.yieldSupplyKey()] == null
|
||||
) {
|
||||
return TokenItemState.PromoBannerState.Empty
|
||||
}
|
||||
val yieldSupplyApy =
|
||||
yieldModuleApyMap[token.yieldSupplyKey()] ?: return TokenItemState.PromoBannerState.Empty
|
||||
|
||||
return TokenItemState.PromoBannerState.Content(
|
||||
title = resourceReference(
|
||||
R.string.yield_module_main_screen_promo_banner_message,
|
||||
wrappedList(yieldSupplyApy),
|
||||
),
|
||||
onPromoBannerClick = {
|
||||
onApyLabelClick?.invoke(status, ApySource.YIELD_SUPPLY, yieldSupplyApy.toString())
|
||||
},
|
||||
onCloseClick = {
|
||||
onYieldPromoCloseClick?.invoke()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.getCryptoPriceState(appCurrency: AppCurrency): TokenItemState.SubtitleState {
|
||||
val fiatRate = value.fiatRate
|
||||
val priceChange = value.priceChange
|
||||
|
|
@ -408,18 +449,6 @@ class TokenItemStateConverter(
|
|||
}
|
||||
|
||||
fun CryptoCurrencyStatus.Value.isFlickering(): Boolean = sources.total == StatusSource.CACHE
|
||||
|
||||
private fun CryptoCurrency.stakingKey(): String {
|
||||
if (this is CryptoCurrency.Coin && !network.isStakingSupported) return ""
|
||||
|
||||
if (network.isStakingSupported && this !is CryptoCurrency.Coin) {
|
||||
val isPolygonTokenOnEthereum = this is CryptoCurrency.Token &&
|
||||
this.network.id.rawId.value == Blockchain.Ethereum.id &&
|
||||
this.symbol == Blockchain.Polygon.currency
|
||||
if (!isPolygonTokenOnEthereum) return ""
|
||||
}
|
||||
return "${id.rawCurrencyId}_$symbol"
|
||||
}
|
||||
}
|
||||
|
||||
private data class StakingLocalInfo(
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
},
|
||||
{
|
||||
"name": "STAKING_CARDANO_ENABLED",
|
||||
"version": "undefined"
|
||||
"version": "5.31.1"
|
||||
},
|
||||
{
|
||||
"name": "STAKING_ETH_ENABLED",
|
||||
|
|
|
|||
|
|
@ -18,6 +18,54 @@ sealed class ApiResponseError : Exception() {
|
|||
val errorBody: String?,
|
||||
) : ApiResponseError() {
|
||||
|
||||
fun isServerError(): Boolean = when (code) {
|
||||
Code.OK,
|
||||
Code.CREATED,
|
||||
Code.ACCEPTED,
|
||||
Code.NOT_MODIFIED,
|
||||
Code.BAD_REQUEST,
|
||||
Code.UNAUTHORIZED,
|
||||
Code.PAYMENT_REQUIRED,
|
||||
Code.FORBIDDEN,
|
||||
Code.NOT_FOUND,
|
||||
Code.METHOD_NOT_ALLOWED,
|
||||
Code.NOT_ACCEPTABLE,
|
||||
Code.PROXY_AUTHENTICATION_REQUIRED,
|
||||
Code.REQUEST_TIMEOUT,
|
||||
Code.CONFLICT,
|
||||
Code.GONE,
|
||||
Code.LENGTH_REQUIRED,
|
||||
Code.PRECONDITION_FAILED,
|
||||
Code.PAYLOAD_TOO_LARGE,
|
||||
Code.URI_TOO_LONG,
|
||||
Code.UNSUPPORTED_MEDIA_TYPE,
|
||||
Code.RANGE_NOT_SATISFIABLE,
|
||||
Code.EXPECTATION_FAILED,
|
||||
Code.IM_A_TEAPOT,
|
||||
Code.UNPROCESSABLE_ENTITY,
|
||||
Code.LOCKED,
|
||||
Code.FAILED_DEPENDENCY,
|
||||
Code.TOO_EARLY,
|
||||
Code.UPGRADE_REQUIRED,
|
||||
Code.PRECONDITION_REQUIRED,
|
||||
Code.TOO_MANY_REQUESTS,
|
||||
Code.REQUEST_HEADER_FIELDS_TOO_LARGE,
|
||||
Code.UNAVAILABLE_FOR_LEGAL_REASONS,
|
||||
-> false
|
||||
Code.INTERNAL_SERVER_ERROR,
|
||||
Code.NOT_IMPLEMENTED,
|
||||
Code.BAD_GATEWAY,
|
||||
Code.SERVICE_UNAVAILABLE,
|
||||
Code.GATEWAY_TIMEOUT,
|
||||
Code.HTTP_VERSION_NOT_SUPPORTED,
|
||||
Code.VARIANT_ALSO_NEGOTIATES,
|
||||
Code.INSUFFICIENT_STORAGE,
|
||||
Code.LOOP_DETECTED,
|
||||
Code.NOT_EXTENDED,
|
||||
Code.NETWORK_AUTHENTICATION_REQUIRED,
|
||||
-> true
|
||||
}
|
||||
|
||||
// TODO: extract Code from HttpException
|
||||
// region Error Codes
|
||||
enum class Code(val numericCode: Int) {
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ import java.math.BigDecimal
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class BalanceResponse(
|
||||
@Json(name = "fiat") val fiat: FiatBalance,
|
||||
@Json(name = "crypto") val crypto: CryptoBalance,
|
||||
@Json(name = "available_for_withdrawal") val availableForWithdrawal: AvailableForWithdrawal,
|
||||
@Json(name = "fiat") val fiat: FiatBalance?,
|
||||
@Json(name = "crypto") val crypto: CryptoBalance?,
|
||||
@Json(name = "available_for_withdrawal") val availableForWithdrawal: AvailableForWithdrawal?,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
|
|
|
|||
|
|
@ -77,6 +77,9 @@ enum class NetworkTypeDTO {
|
|||
@Json(name = "canto")
|
||||
CANTO,
|
||||
|
||||
@Json(name = "cardano")
|
||||
CARDANO,
|
||||
|
||||
@Json(name = "chihuahua")
|
||||
CHIHUAHUA,
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.datasource.local.promo.DefaultPromoBannerStore
|
||||
import com.tangem.datasource.local.promo.DefaultPromoStoriesStore
|
||||
import com.tangem.datasource.local.promo.PromoBannerStore
|
||||
import com.tangem.datasource.local.promo.PromoStoriesStore
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -18,4 +21,10 @@ object PromoStoreModule {
|
|||
fun providePromoStoriesStore(): PromoStoriesStore {
|
||||
return DefaultPromoStoriesStore(dataStore = RuntimeDataStore())
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providePromoBannerStore(): PromoBannerStore {
|
||||
return DefaultPromoBannerStore(dataStore = RuntimeSharedStore())
|
||||
}
|
||||
}
|
||||
|
|
@ -122,6 +122,10 @@ object PreferencesKeys {
|
|||
|
||||
val YIELD_SUPPLY_WARNINGS_STATES_KEY by lazy { stringPreferencesKey(name = "yieldSupplyWarningsStates") }
|
||||
|
||||
val YIELD_SUPPLY_SHOULD_SHOW_MAIN_PROMO_KEY by lazy {
|
||||
booleanPreferencesKey(name = "yieldSupplyShouldShowMainPromo")
|
||||
}
|
||||
|
||||
val ACCESS_CODE_SKIPPED_STATES_KEY by lazy { stringPreferencesKey(name = "accessCodeSkippedStates") }
|
||||
|
||||
// region Notifications
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.datasource.local.promo
|
||||
|
||||
import com.tangem.datasource.api.promotion.models.PromoBannerResponse
|
||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
|
||||
internal class DefaultPromoBannerStore(
|
||||
private val dataStore: RuntimeSharedStore<Map<String, PromoBannerResponse>>,
|
||||
) : PromoBannerStore {
|
||||
|
||||
override suspend fun getSyncOrNull(promoId: String): PromoBannerResponse? {
|
||||
return dataStore.getSyncOrNull()?.get(promoId)
|
||||
}
|
||||
|
||||
override suspend fun store(promoId: String, promoBanner: PromoBannerResponse) {
|
||||
dataStore.update(emptyMap()) { current ->
|
||||
current + (promoId to promoBanner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.local.promo
|
||||
|
||||
import com.tangem.datasource.api.promotion.models.PromoBannerResponse
|
||||
|
||||
interface PromoBannerStore {
|
||||
|
||||
suspend fun getSyncOrNull(promoId: String): PromoBannerResponse?
|
||||
|
||||
suspend fun store(promoId: String, promoBanner: PromoBannerResponse)
|
||||
}
|
||||
|
|
@ -33,6 +33,7 @@ object StakingNetworkTypeConverter : TwoWayConverter<NetworkTypeDTO, NetworkType
|
|||
NetworkTypeDTO.BAND_PROTOCOL -> NetworkType.BAND_PROTOCOL
|
||||
NetworkTypeDTO.BITSONG -> NetworkType.BITSONG
|
||||
NetworkTypeDTO.CANTO -> NetworkType.CANTO
|
||||
NetworkTypeDTO.CARDANO -> NetworkType.CARDANO
|
||||
NetworkTypeDTO.CHIHUAHUA -> NetworkType.CHIHUAHUA
|
||||
NetworkTypeDTO.COMDEX -> NetworkType.COMDEX
|
||||
NetworkTypeDTO.COREUM -> NetworkType.COREUM
|
||||
|
|
@ -106,6 +107,7 @@ object StakingNetworkTypeConverter : TwoWayConverter<NetworkTypeDTO, NetworkType
|
|||
NetworkType.BAND_PROTOCOL -> NetworkTypeDTO.BAND_PROTOCOL
|
||||
NetworkType.BITSONG -> NetworkTypeDTO.BITSONG
|
||||
NetworkType.CANTO -> NetworkTypeDTO.CANTO
|
||||
NetworkType.CARDANO -> NetworkTypeDTO.CARDANO
|
||||
NetworkType.CHIHUAHUA -> NetworkTypeDTO.CHIHUAHUA
|
||||
NetworkType.COMDEX -> NetworkTypeDTO.COMDEX
|
||||
NetworkType.COREUM -> NetworkTypeDTO.COREUM
|
||||
|
|
|
|||
|
|
@ -392,6 +392,7 @@
|
|||
<string name="common_week">Woche</string>
|
||||
<string name="common_with">mit</string>
|
||||
<string name="common_yes">Ja</string>
|
||||
<string name="common_yield_mode">Ertragsmodus</string>
|
||||
<string name="contract_address_copied_message">Vertragsadresse kopiert!</string>
|
||||
<string name="currency_subtitle_expanded">Verfügbare Netzwerke</string>
|
||||
<string name="custom_token_another_account_dialog_description">Die Ableitung Deines Tokens entspricht der Ableitung von %1$s. Dein Token wird diesem Konto gutgeschrieben.</string>
|
||||
|
|
@ -849,6 +850,9 @@
|
|||
<string name="no_trustline_xlm_asset">Das Zielkonto verfügt nicht über eine Vertrauensstellung für das gesendete Asset.</string>
|
||||
<string name="notification_black_friday_text">Sicher Dir 10 $ in BTC mit jeder Wallet greif schnell zu!</string>
|
||||
<string name="notification_black_friday_title">Black Friday: bis zu 30% Nachlass</string>
|
||||
<string name="notification_one_plus_one_button">Los geht\'s</string>
|
||||
<string name="notification_one_plus_one_text">Nur für kurze Zeit!</string>
|
||||
<string name="notification_one_plus_one_title">1+1: Beim Kauf einer Wallet erhältst Du 50% Rabatt</string>
|
||||
<string name="notification_referral_promo_button">Jetzt beitreten</string>
|
||||
<string name="notification_referral_promo_text">Teile Deinen Code – verdiene 5 USDT pro Verkauf. Deine Freunde erhalten 10 % Rabatt.</string>
|
||||
<string name="notification_referral_promo_title">Erhalte BELOHNUNGEN für jeden Freund!</string>
|
||||
|
|
@ -1480,11 +1484,11 @@
|
|||
<string name="tangempay_onboarding_security_description">Ein separates Zahlungskonto wird erstellt, ohne Ihre Adressen und Vermögenswerte offenzulegen</string>
|
||||
<string name="tangempay_onboarding_security_title">Unerreichte Privatsphäre</string>
|
||||
<string name="tangempay_onboarding_title">Erhalten Sie Ihre kostenlose Tangem Pay Card in wenigen Minuten</string>
|
||||
<string name="tangempay_payment_account">Payment account</string>
|
||||
<string name="tangempay_payment_account">Zahlungskonto</string>
|
||||
<string name="tangempay_payment_account_sync_needed">Synchronisierung des Zahlungskontos erforderlich</string>
|
||||
<string name="tangempay_service_unavailable_description">Wir beheben ein technisches Problem. Bitte versuchen Sie es später erneut.</string>
|
||||
<string name="tangempay_service_unavailable_title">Service vorübergehend nicht verfügbar</string>
|
||||
<string name="tangempay_service_unreachable_try_later">Der Dienst ist derzeit nicht erreichbar. Bitte versuchen Sie es später erneut.</string>
|
||||
<string name="tangempay_service_unreachable_try_later">Daten können derzeit nicht angezeigt werden, Kartenzahlungen funktionieren jedoch weiterhin.</string>
|
||||
<string name="tangempay_sync_needed">Synchronisation erforderlich</string>
|
||||
<string name="tangempay_tangem_visa_card">Tangem Visa Card</string>
|
||||
<string name="tangempay_temporarily_unavailable">Tangem Pay ist vorübergehend nicht verfügbar</string>
|
||||
|
|
|
|||
|
|
@ -345,6 +345,7 @@
|
|||
<string name="common_week">semana</string>
|
||||
<string name="common_with">con</string>
|
||||
<string name="common_yes">Sí</string>
|
||||
<string name="common_yield_mode">Modo de rendimiento</string>
|
||||
<string name="contract_address_copied_message">Dirección de contrato copiada</string>
|
||||
<string name="currency_subtitle_expanded">Redes disponibles</string>
|
||||
<string name="custom_token_contract_address_input_title">Dirección de contrato</string>
|
||||
|
|
@ -1319,11 +1320,11 @@
|
|||
<string name="tangempay_onboarding_security_description">Se creará una cuenta de pago separada sin divulgar tus direcciones y activos</string>
|
||||
<string name="tangempay_onboarding_security_title">Privacidad inigualable</string>
|
||||
<string name="tangempay_onboarding_title">Obtén tu tarjeta Tangem Pay gratuita en minutos</string>
|
||||
<string name="tangempay_payment_account">Payment account</string>
|
||||
<string name="tangempay_payment_account">Cuenta de pago</string>
|
||||
<string name="tangempay_payment_account_sync_needed">Sincronización de cuenta de pago necesaria</string>
|
||||
<string name="tangempay_service_unavailable_description">Estamos solucionando un problema técnico. Por favor, inténtelo de nuevo más tarde.</string>
|
||||
<string name="tangempay_service_unavailable_title">Servicio temporalmente no disponible</string>
|
||||
<string name="tangempay_service_unreachable_try_later">El servicio no está disponible actualmente. Inténtalo de nuevo más tarde.</string>
|
||||
<string name="tangempay_service_unreachable_try_later">No es posible mostrar los datos en este momento, pero los pagos con tarjeta siguen funcionando.</string>
|
||||
<string name="tangempay_sync_needed">Sincronización necesaria</string>
|
||||
<string name="tangempay_tangem_visa_card">Tangem Visa Card</string>
|
||||
<string name="tangempay_temporarily_unavailable">Tangem Pay temporalmente no disponible</string>
|
||||
|
|
@ -1417,6 +1418,10 @@
|
|||
<string name="user_wallet_list_rename_popup_title">Renombrar la billetera</string>
|
||||
<string name="user_wallet_list_unlock_all">Desbloquear todo</string>
|
||||
<string name="user_wallet_list_unlock_all_with">Desbloquear todo con %s</string>
|
||||
<string name="visa_onboarding_pin_code_description">Configure un código de 4 dígitos.\nSe utilizará para los pagos.</string>
|
||||
<string name="visa_onboarding_pin_code_title">Crear código PIN</string>
|
||||
<string name="visa_onboarding_pin_not_accepted">No se aceptó el PIN. Inténtalo de nuevo o usa un código diferente.</string>
|
||||
<string name="visa_onboarding_pin_validation_error_message">PIN no válido: evitar secuencias o repeticiones</string>
|
||||
<string name="wallet_add_common_title">Elija cómo agregar su billetera</string>
|
||||
<string name="wallet_balance_blockchain_unreachable_try_later">La blockchain está Inaccesible. Inténtelo más tarde</string>
|
||||
<string name="wallet_balance_missing_derivation">Escanee la tarjeta o el anillo</string>
|
||||
|
|
|
|||
|
|
@ -328,6 +328,7 @@
|
|||
<string name="common_week">semaine</string>
|
||||
<string name="common_with">avec</string>
|
||||
<string name="common_yes">Oui</string>
|
||||
<string name="common_yield_mode">Mode Rendement</string>
|
||||
<string name="contract_address_copied_message">Adresse du contrat copiée !</string>
|
||||
<string name="currency_subtitle_expanded">Réseaux disponibles</string>
|
||||
<string name="custom_token_contract_address_input_title">Adresse du contrat</string>
|
||||
|
|
@ -895,6 +896,8 @@
|
|||
<string name="reset_card_without_backup_to_factory_message">La réinitialisation aux paramètres d\'usine supprimera complètement le portefeuille de la carte sélectionnée et le supprimera de l\'application. Vous ne pourrez pas restaurer le portefeuille actuel.</string>
|
||||
<string name="ring_promo_text">Les propriétaires du Tangem Ring ont droit à 3 swap gratis sur Changelly jusqu\'au 15/11 !</string>
|
||||
<string name="ring_promo_title">Échangez avec 0 % de frais !</string>
|
||||
<string name="root_detected_warning_description">Les appareils disposant d\'un accès root sont considérés comme moins sécurisés. Vos données peuvent être exposées à des risques supplémentaires.</string>
|
||||
<string name="root_detected_warning_title">Accès root détecté</string>
|
||||
<string name="save_user_wallet_agreement_access_description">Connectez-vous à l\'application et vérifiez votre solde sans scanner la carte</string>
|
||||
<string name="save_user_wallet_agreement_access_title">Accéder à l\'application</string>
|
||||
<string name="save_user_wallet_agreement_allow_biometrics">Autoriser l\'utilisation de la biométrie</string>
|
||||
|
|
@ -1300,11 +1303,11 @@
|
|||
<string name="tangempay_onboarding_security_description">Un compte de paiement séparé sera créé sans divulguer vos adresses et actifs</string>
|
||||
<string name="tangempay_onboarding_security_title">Confidentialité inégalée</string>
|
||||
<string name="tangempay_onboarding_title">Obtenez votre carte Tangem Pay gratuite en quelques minutes</string>
|
||||
<string name="tangempay_payment_account">Payment account</string>
|
||||
<string name="tangempay_payment_account">Compte de paiement</string>
|
||||
<string name="tangempay_payment_account_sync_needed">Synchronisation du compte de paiement nécessaire</string>
|
||||
<string name="tangempay_service_unavailable_description">Nous réparons un problème technique. Veuillez réessayer plus tard.</string>
|
||||
<string name="tangempay_service_unavailable_title">Service temporairement indisponible</string>
|
||||
<string name="tangempay_service_unreachable_try_later">Le service est actuellement indisponible. Veuillez réessayer plus tard.</string>
|
||||
<string name="tangempay_service_unreachable_try_later">Les données ne peuvent pas être affichées pour le moment, mais les paiements par carte fonctionnent toujours.</string>
|
||||
<string name="tangempay_sync_needed">Synchronisation requise</string>
|
||||
<string name="tangempay_tangem_visa_card">Tangem Visa Card</string>
|
||||
<string name="tangempay_temporarily_unavailable">Tangem Pay est temporairement indisponible</string>
|
||||
|
|
@ -1408,6 +1411,9 @@
|
|||
<string name="visa_onboarding_in_progress_description">Cela ne prendra pas longtemps. Nous configurons votre compte.</string>
|
||||
<string name="visa_onboarding_in_progress_issuer_description">Cela ne prendra pas longtemps. Nous terminons l\'activation.</string>
|
||||
<string name="visa_onboarding_in_progress_title">Tout est en cours de préparation !</string>
|
||||
<string name="visa_onboarding_pin_code_description">Créez un code à 4 chiffres. Il servira pour les paiements.</string>
|
||||
<string name="visa_onboarding_pin_code_title">Créer un code PIN</string>
|
||||
<string name="visa_onboarding_pin_not_accepted">Le code PIN n\'a pas été accepté. Veuillez réessayer ou utiliser un autre code.</string>
|
||||
<string name="visa_onboarding_pin_validation_error_message">Code PIN invalide : évitez les séquences ou les répétitions</string>
|
||||
<string name="visa_onboarding_wallet_connect_title">Accéder le site Web</string>
|
||||
<string name="visa_onboarding_welcome_back_description">Continuons la configuration de votre compte.</string>
|
||||
|
|
|
|||
|
|
@ -162,11 +162,11 @@
|
|||
<string name="tangempay_onboarding_security_description">Verrà creato un conto di pagamento separato senza divulgare i tuoi indirizzi e asset</string>
|
||||
<string name="tangempay_onboarding_security_title">Privacy senza rivali</string>
|
||||
<string name="tangempay_onboarding_title">Ottieni la tua carta Tangem Pay gratuita in pochi minuti</string>
|
||||
<string name="tangempay_payment_account">Payment account</string>
|
||||
<string name="tangempay_payment_account">Conto di pagamento</string>
|
||||
<string name="tangempay_payment_account_sync_needed">Sincronizzazione del conto di pagamento necessaria</string>
|
||||
<string name="tangempay_service_unavailable_description">Stiamo risolvendo un problema tecnico. Riprova più tardi.</string>
|
||||
<string name="tangempay_service_unavailable_title">Servizio temporaneamente non disponibile</string>
|
||||
<string name="tangempay_service_unreachable_try_later">Il servizio è attualmente non raggiungibile. Riprova più tardi.</string>
|
||||
<string name="tangempay_service_unreachable_try_later">Al momento non è possibile visualizzare i dati, ma i pagamenti con carta continuano a funzionare.</string>
|
||||
<string name="tangempay_sync_needed">Sincronizzazione necessaria</string>
|
||||
<string name="tangempay_tangem_visa_card">Tangem Visa Card</string>
|
||||
<string name="tangempay_temporarily_unavailable">Tangem Pay è temporaneamente non disponibile</string>
|
||||
|
|
@ -174,6 +174,10 @@
|
|||
<string name="tangempay_use_tangem_device_to_restore_payment_account">Usa la tua carta o il tuo anello per ripristinare l\'accesso al tuo conto di pagamento</string>
|
||||
<string name="tangempay_your_pin_code">Il tuo codice PIN</string>
|
||||
<string name="twins_recreate_toolbar">Tangem Twin</string>
|
||||
<string name="visa_onboarding_pin_code_description">Imposta un codice di 4 cifre.\nVerrà utilizzato per i pagamenti.</string>
|
||||
<string name="visa_onboarding_pin_code_title">Crea codice PIN</string>
|
||||
<string name="visa_onboarding_pin_not_accepted">Il PIN non è stato accettato. Riprova o utilizza un codice diverso.</string>
|
||||
<string name="visa_onboarding_pin_validation_error_message">PIN non valido: evitare sequenze o ripetizioni</string>
|
||||
<string name="wallet_connect_title">WalletConnect</string>
|
||||
<string name="wallet_notification_address_copied">L\'indirizzo è stato copiato con successo</string>
|
||||
<string name="wallet_notification_no_internet">Nessuna connessione a Internet</string>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<string name="access_code_alert_skip_ok">とにかくスキップ</string>
|
||||
<string name="access_code_alert_skip_title">アクセスコードが設定されていません</string>
|
||||
<string name="access_code_alert_validation_cancel">コードを変更</string>
|
||||
<string name="access_code_alert_validation_description">アクセスコードは、ウォレットのロック解除・資産へのアクセス保護に使用されます</string>
|
||||
<string name="access_code_alert_validation_description">アクセスコードは、ウォレットのロック解除およびウォレットへのアクセス保護に使用されます</string>
|
||||
<string name="access_code_alert_validation_ok">このまま使う</string>
|
||||
<string name="access_code_alert_validation_title">このアクセスコードは簡単に推測される可能性があります</string>
|
||||
<string name="access_code_check_title">アクセスコードを入力</string>
|
||||
|
|
@ -96,17 +96,17 @@
|
|||
<string name="alert_manage_tokens_unsupported_message">%1$s ネットワークのトークンは、ファームウェアの制限により、このカードまたはリングではサポートされていません。</string>
|
||||
<string name="alert_troubleshooting_scan_card_title">カードまたはリングのスキャンに問題がありますか?</string>
|
||||
<string name="alert_unsupported_card">このカードはこのアプリでは使用できません。</string>
|
||||
<string name="app_settings_access_code_warning">まずアクセスコードを設定してから、生体認証を有効にしてください</string>
|
||||
<string name="app_settings_access_code_warning">生体認証を有効にするには、アクセスコードを設定してください</string>
|
||||
<string name="app_settings_biometrics_footer">%1$sを使用してウォレットのロックを解除し、取引の署名などの機密性の高い操作を承認してください。ハードウェアウォレットの場合は、署名にカードまたはリングが必要です。</string>
|
||||
<string name="app_settings_default_fee">デフォルト手数料</string>
|
||||
<string name="app_settings_default_fee_footer">デフォルト手数料を有効にすると、取引手数料が自動的に設定され、送金時に手数料ページを表示する必要がなくなります。必要に応じて、いつでもこのページに戻ることができます。</string>
|
||||
<string name="app_settings_enable_biometrics_description">設定に移動して、Tangemアプリで生体認証を有効にします。</string>
|
||||
<string name="app_settings_enable_biometrics_title">生体認証を有効にする</string>
|
||||
<string name="app_settings_off_biometrics_alert_message">%1$sが無効になると、アプリのロックを解除してウォレットを操作するために、パスコードを入力する必要があります。</string>
|
||||
<string name="app_settings_off_require_access_code_alert_message">後でウォレットのアクセスコードを入力してもらいます。それを安全に保存し、今後の利用に備えるためです。</string>
|
||||
<string name="app_settings_off_saved_access_code_alert_message">これにより、保存されているウォレットアクセスコードがすべて削除されます。ウォレットでの今後の操作には、アクセスコードの送信が必要になります。</string>
|
||||
<string name="app_settings_off_require_access_code_alert_message">安全に保管するため、後ほどアクセスコードの入力を求められます。</string>
|
||||
<string name="app_settings_off_saved_access_code_alert_message">保存されているすべてのウォレットのアクセスコードが削除されます。ウォレットを使用するには、再度アクセスコードを入力する必要があります。</string>
|
||||
<string name="app_settings_off_saved_wallet_alert_message">保存したデバイスを削除すると、保存されているすべてのウォレットとそのアクセスコードがアプリから削除されます。</string>
|
||||
<string name="app_settings_on_require_access_code_alert_message">これにより、保存されているウォレットアクセスコードがすべて削除されます。今後ウォレットを操作するには、アクセスコードの送信が必要になります。</string>
|
||||
<string name="app_settings_on_require_access_code_alert_message">保存されているすべてのウォレットのアクセスコードが削除されます。ウォレットを使用するには、再度アクセスコードを入力する必要があります。</string>
|
||||
<string name="app_settings_require_access_code">アクセスコードを要求する</string>
|
||||
<string name="app_settings_require_access_code_footer">このオプションを選択すると、機密性の高い操作における生体認証が無効になります。取引に署名するたびにアクセスコードを入力する必要があります。</string>
|
||||
<string name="app_settings_saved_access_codes">アクセスコードを保存</string>
|
||||
|
|
@ -136,7 +136,7 @@
|
|||
<string name="backup_info_title">リカバリーフレーズ</string>
|
||||
<string name="backup_seed_caution">これらの単語は誰にも教えないでください。Tangemがこれらの単語を尋ねることは、絶対にありません。以下の%s個の単語はウォレットのリカバリーフレーズです。デバイスを紛失した場合、これらの単語を使用してウォレットを復元できます。</string>
|
||||
<string name="backup_seed_description">これらの%s個の単語を番号順に書き留め、安全かつ他人の目に触れない場所に保管してください</string>
|
||||
<string name="backup_seed_responsibility">ウォレットおよびリカバリーフレーズの保護とバックアップは、すべてあなた自身の責任となります。</string>
|
||||
<string name="backup_seed_responsibility">ウォレットの安全確保およびリカバリーフレーズの安全なバックアップは、すべてご自身の責任となります。</string>
|
||||
<string name="backup_seed_title">リカバリーフレーズ</string>
|
||||
<string name="balance_hidden_description">残高を表示または非表示にするには、デバイスの画面を下向きにするか、設定でオフにしてください。</string>
|
||||
<string name="balance_hidden_do_not_show_button">今後表示しない</string>
|
||||
|
|
@ -144,10 +144,10 @@
|
|||
<string name="balance_hidden_title">残高は非表示</string>
|
||||
<string name="beta_mode_warning_message">ブロックチェーン開発者によると、Kaspaトークンは現在ベータ版です。アップデートをお楽しみに!</string>
|
||||
<string name="beta_mode_warning_title">ベータモード</string>
|
||||
<string name="biometric_disabled_warning_description">デバイスで生体認証がオフになっているため、ウォレットのロック解除に使用できません。生体認証を再度利用するには、デバイスの設定で有効にしてください。</string>
|
||||
<string name="biometric_disabled_warning_description">この端末では生体認証がオフになっているため、ウォレットの解除に使用できません。再度この方法を使用するには、端末の設定で生体認証を有効にしてください。</string>
|
||||
<string name="biometric_disabled_warning_title">生体認証が無効になっています</string>
|
||||
<string name="biometric_lockout_permanent_warning_description">カードまたはリングをスキャンしてください</string>
|
||||
<string name="biometric_lockout_permanent_warning_description_2">生体認証の試行回数が上限に達しました。端末タップまたはアクセスコードでウォレットを解除してください。</string>
|
||||
<string name="biometric_lockout_permanent_warning_description_2">生体認証の試行回数の上限に達しました。カード/リングでウォレットを解除するか、アクセスコードを入力してください。</string>
|
||||
<string name="biometric_lockout_permanent_warning_title">生体認証がロックされています</string>
|
||||
<string name="biometric_lockout_warning_description">30秒後に再試行するか、カードまたはリングをスキャンしてください</string>
|
||||
<string name="biometric_lockout_warning_description_2">生体認証ログインは一時的にロックされています。30秒後にもう一度お試しいただくか、端末タップまたはアクセスコードでウォレットを解除してください。</string>
|
||||
|
|
@ -179,7 +179,7 @@
|
|||
<string name="card_reset_alert_finish_ok_button">もう一度アップグレードしてください</string>
|
||||
<string name="card_reset_alert_finish_title">リセット完了</string>
|
||||
<string name="card_reset_alert_incomplete_message">このウォレット内のすべてのTangemデバイスで、リセット処理を完了することを推奨します。</string>
|
||||
<string name="card_reset_alert_incomplete_title">すべてのTangemデバイスをリセットしていません</string>
|
||||
<string name="card_reset_alert_incomplete_title">リセットが必要なTangemデバイスがあります。</string>
|
||||
<string name="card_settings_access_code_recovery_disabled_description">このカードを使用して、このウォレット内の他のカードまたはリングのアクセスコードをリセットしたくない場合は、このオプションを無効にしてください。これにより、このカードのアクセスコードもリセットできなくなりますのでご注意ください。</string>
|
||||
<string name="card_settings_access_code_recovery_enabled_description">このカードを使用して、このウォレット内の他のカードのアクセスコードをリセットできます。</string>
|
||||
<string name="card_settings_access_code_recovery_title">アクセスコードの復元</string>
|
||||
|
|
@ -378,6 +378,7 @@
|
|||
<string name="common_transfer">送金</string>
|
||||
<string name="common_unable_to_load">データを読み込めません…</string>
|
||||
<string name="common_understand">わかりました</string>
|
||||
<string name="common_understand_continue">理解して続行</string>
|
||||
<string name="common_unknown_error">エラーが発生しました。もう一度お試しください。</string>
|
||||
<string name="common_unreachable">アクセスできません</string>
|
||||
<string name="common_unstake">ステーキング解除</string>
|
||||
|
|
@ -386,6 +387,7 @@
|
|||
<string name="common_week">週</string>
|
||||
<string name="common_with">で</string>
|
||||
<string name="common_yes">はい</string>
|
||||
<string name="common_yield_mode">利息モード</string>
|
||||
<string name="contract_address_copied_message">コントラクトアドレスをコピーしました!</string>
|
||||
<string name="currency_subtitle_expanded">利用可能なネットワーク</string>
|
||||
<string name="custom_token_another_account_dialog_description">このトークンの導出パスが%1$sの導出パスと一致しています。トークンはこのアカウントに追加されます。</string>
|
||||
|
|
@ -566,7 +568,7 @@
|
|||
<string name="home_button_create_new_wallet">新しいウォレットを作成する</string>
|
||||
<string name="home_button_order">Tangemを注文</string>
|
||||
<string name="home_button_scan">Tangemをスキャン</string>
|
||||
<string name="hot_access_code_set_biometric_ask">「Tangem」に生体認証の使用を許可しますか?\n本人確認とアプリの起動のために使用されます。</string>
|
||||
<string name="hot_access_code_set_biometric_ask">「Tangem」が生体認証を使用して本人確認を行い、アプリを開くことを許可しますか?</string>
|
||||
<string name="hot_crypto_add_token_subtitle">%sへ</string>
|
||||
<string name="hot_crypto_token_network">%sネットワーク</string>
|
||||
<string name="hw_access_code_create_alert_title">アクセスコードの設定をキャンセルしてもよろしいですか?</string>
|
||||
|
|
@ -575,8 +577,8 @@
|
|||
<string name="hw_activation_need_finish">今すぐ確定</string>
|
||||
<string name="hw_activation_need_title">ウォレットの設定を完了する</string>
|
||||
<string name="hw_activation_need_warning_description">アクセスコードでアプリを保護して、設定を完了してください。</string>
|
||||
<string name="hw_backup_alert_description">そうした場合は、最初からやり直す必要があります。</string>
|
||||
<string name="hw_backup_alert_title">本当にアクティベーション処理を終了してもよろしいですか?</string>
|
||||
<string name="hw_backup_alert_description">終了すると、最初からやり直す必要があります。</string>
|
||||
<string name="hw_backup_alert_title">セットアップを中止してもよろしいですか?</string>
|
||||
<string name="hw_backup_banner_description">暗号資産をオフラインで厳重保管。カードサイズで、金庫以上の安心を。</string>
|
||||
<string name="hw_backup_close_description">実行すると、最初からやり直す必要があります。</string>
|
||||
<string name="hw_backup_google_drive_description">Googleドライブのバックアップから既存のウォレットを復元する</string>
|
||||
|
|
@ -585,18 +587,19 @@
|
|||
<string name="hw_backup_hardware_create_title">新しいウォレットを作成</string>
|
||||
<string name="hw_backup_hardware_description">Tangemの高性能ハードウェアウォレットで、セキュリティをさらに強化しましょう。</string>
|
||||
<string name="hw_backup_hardware_title">ハードウェアウォレット</string>
|
||||
<string name="hw_backup_hardware_upgrade_description">現在のウォレットをTangemウォレットに移します。</string>
|
||||
<string name="hw_backup_hardware_upgrade_description">現在のウォレットをTangemに移します。</string>
|
||||
<string name="hw_backup_hardware_upgrade_title">現在のウォレットをアップグレードする</string>
|
||||
<string name="hw_backup_need_action">バックアップへ移動</string>
|
||||
<string name="hw_backup_need_description">アクセスコードを作成する前にウォレットをバックアップしてください。</string>
|
||||
<string name="hw_backup_need_finish_first">まずバックアップを完了する</string>
|
||||
<string name="hw_backup_need_finish_first">先にバックアップを完了してください</string>
|
||||
<string name="hw_backup_need_title">まずバックアップを完了する</string>
|
||||
<string name="hw_backup_section_other_title">その他の方法</string>
|
||||
<string name="hw_backup_seed_description">リカバリーフレーズは、ご自身で安全な場所に保管し、資金を守るために他人には絶対に共有しないでください。</string>
|
||||
<string name="hw_backup_seed_description">資金を保護するため、リカバリーフレーズは安全な場所に保管し、他人に知られないようにしてください。</string>
|
||||
<string name="hw_backup_seed_title">リカバリーフレーズ</string>
|
||||
<string name="hw_backup_to_secure_description">アクセスコードを使用してウォレットを保護するには、まずバックアップを完了してください。</string>
|
||||
<string name="hw_backup_to_upgrade_description">ウォレットをハードウェアにアップグレードするには、まずバックアップしてください。</string>
|
||||
<string name="hw_create_keys_title">鍵はアプリに保存されます</string>
|
||||
<string name="hw_backup_to_secure_description">アクセスコードでウォレットを保護するには、バックアップの手続きを完了してください。</string>
|
||||
<string name="hw_backup_to_upgrade_description">ハードウェアウォレットにアップグレードするには、バックアップの手続きを完了してください。</string>
|
||||
<string name="hw_create_keys_title">秘密鍵はデバイス上に保持されます</string>
|
||||
<string name="hw_create_seed_description">リカバリーフレーズを使ってウォレットを作成または復元してください。</string>
|
||||
<string name="hw_create_seed_title">シードフレーズのバックアップ</string>
|
||||
<string name="hw_create_title">モバイルウォレットを作成する</string>
|
||||
<string name="hw_import_existing_wallet">既存のウォレットをインポートする</string>
|
||||
|
|
@ -610,25 +613,25 @@
|
|||
<string name="hw_remove_wallet_notification_action_backup_view">バックアップを表示</string>
|
||||
<string name="hw_remove_wallet_notification_action_forget">ウォレット削除</string>
|
||||
<string name="hw_remove_wallet_notification_action_forget_anyway">とにかく削除する</string>
|
||||
<string name="hw_remove_wallet_notification_description_has_backup">このウォレットにはバックアップが存在します。後で復元できるように、削除する前に内容を確認してください。</string>
|
||||
<string name="hw_remove_wallet_notification_description_without_backup">バックアップなしでこのウォレットを削除すると、資金へのアクセスを永久に失います。</string>
|
||||
<string name="hw_remove_wallet_notification_title">このウォレットを削除しますか?</string>
|
||||
<string name="hw_remove_wallet_warning_access">ウォレットの削除前にバックアップをしていなければ、アクセスを失う可能性があることを理解しています。</string>
|
||||
<string name="hw_remove_wallet_warning_device">ウォレットを削除しても中身自体が消えるわけではなく、単にこのデバイスから表示が消えるだけであることを理解しています。</string>
|
||||
<string name="hw_upgrade_backup_description">もはや、シードフレーズは不要。Tangemカードやリングが、そのまま安全なバックアップになります。</string>
|
||||
<string name="hw_remove_wallet_notification_description_has_backup">このウォレットにはバックアップがあります。ウォレットを削除する前に、復元できることを確認してください。</string>
|
||||
<string name="hw_remove_wallet_notification_description_without_backup">バックアップを行わずにこのウォレットを削除すると、資金へのアクセスを永久に失います。</string>
|
||||
<string name="hw_remove_wallet_notification_title">このウォレットを削除してもよろしいですか?</string>
|
||||
<string name="hw_remove_wallet_warning_access">ウォレットを削除する前にバックアップを行っていない場合、ウォレットへのアクセスを失うことを理解しています。</string>
|
||||
<string name="hw_remove_wallet_warning_device">ウォレットを削除しても中身自体が消えるわけではなく、このデバイスから表示が消えるだけであることを理解しています。</string>
|
||||
<string name="hw_upgrade_backup_description">シードフレーズは不要です。Tangemカードまたはリングが安全なバックアップとなります。</string>
|
||||
<string name="hw_upgrade_backup_title">Tangemでバックアップ</string>
|
||||
<string name="hw_upgrade_error_card_already_has_wallet">アップグレードできません。このデバイスにはすでにウォレットが存在します。</string>
|
||||
<string name="hw_upgrade_error_card_key_import">別のデバイスを選択してください。このデバイスはアップグレードに使用できません。</string>
|
||||
<string name="hw_upgrade_error_wallet2_card_required">操作中にエラーが発生しました。</string>
|
||||
<string name="hw_upgrade_funds_access_description">処理中も、資金は安全に保たれ、常にアクセス可能です。</string>
|
||||
<string name="hw_upgrade_funds_access_title">資金へのアクセス</string>
|
||||
<string name="hw_upgrade_general_security_description">ウォレットのデータはアプリから削除され、Tangemデバイスに保存されます。</string>
|
||||
<string name="hw_upgrade_general_security_description">ウォレットのデータはアプリから削除され、ハードウェアウォレットに保存されます</string>
|
||||
<string name="hw_upgrade_general_security_title">セキュリティ全般</string>
|
||||
<string name="hw_upgrade_key_migration_description">秘密鍵はアプリからTangemデバイスに移動されます。</string>
|
||||
<string name="hw_upgrade_key_migration_title">鍵の移行</string>
|
||||
<string name="hw_upgrade_key_migration_description">秘密鍵はアプリからTangemハードウェアウォレットへ移行されます</string>
|
||||
<string name="hw_upgrade_key_migration_title">キーの移行</string>
|
||||
<string name="hw_upgrade_scan_device">デバイスをスキャン</string>
|
||||
<string name="hw_upgrade_start_action">アップグレードを開始</string>
|
||||
<string name="hw_upgrade_start_description">ハードウェアウォレットにアップグレードすると、資産がコールドストレージで安全に保管されます。</string>
|
||||
<string name="hw_upgrade_start_description">ハードウェアウォレットへのアップグレードを行います。資産はコールドストレージで安全に保管されます。</string>
|
||||
<string name="hw_upgrade_start_title">Tangemウォレット</string>
|
||||
<string name="hw_upgrade_title">ハードウェアウォレットにアップグレード</string>
|
||||
<string name="hw_upgrade_to_cold_banner_description">Tangemの業界最高クラスのハードウェアウォレットで、暗号資産を安全に保管しましょう。</string>
|
||||
|
|
@ -714,6 +717,7 @@
|
|||
<string name="markets_sort_by_top_gainers_title">上昇率上位</string>
|
||||
<string name="markets_sort_by_top_losers_title">下落率上位</string>
|
||||
<string name="markets_sort_by_trending_title">トレンド</string>
|
||||
<string name="markets_sort_by_yield_mode_title">利息モード</string>
|
||||
<string name="markets_staking_banner_description_placeholder">ステーキングは暗号資産で報酬を受け取る最も簡単な方法です。 %s</string>
|
||||
<string name="markets_staking_banner_title">最大%s APYを獲得</string>
|
||||
<string name="markets_token_added">トークンを追加しました</string>
|
||||
|
|
@ -848,8 +852,8 @@
|
|||
<string name="notification_black_friday_text">ウォレットごとに$10相当のBTCをプレゼント\nお早めに!</string>
|
||||
<string name="notification_black_friday_title">ブラックフライデー:最大30% オフ</string>
|
||||
<string name="notification_one_plus_one_button">今すぐチェック</string>
|
||||
<string name="notification_one_plus_one_text">理想のTangemセットを揃えよう。期間限定。</string>
|
||||
<string name="notification_one_plus_one_title">1+1:1つ購入で、2つ目が50%オフ</string>
|
||||
<string name="notification_one_plus_one_text">期間限定!</string>
|
||||
<string name="notification_one_plus_one_title">1+1:ウォレット1つ購入で、2つ目が50%オフ</string>
|
||||
<string name="notification_referral_promo_button">今すぐ参加</string>
|
||||
<string name="notification_referral_promo_text">コードを共有すると、販売ごとに5 USDTを獲得できます。お友達は10%割引になります。</string>
|
||||
<string name="notification_referral_promo_title">友達への紹介で報酬を獲得しよう!</string>
|
||||
|
|
@ -963,6 +967,7 @@
|
|||
<string name="onramp_currency_other">その他の通貨</string>
|
||||
<string name="onramp_currency_popular">人気の法定通貨</string>
|
||||
<string name="onramp_currency_search">通貨で検索</string>
|
||||
<string name="onramp_error_transaction_already_processed">この取引はすでに処理されています。これ以上の対応は必要ありません。</string>
|
||||
<string name="onramp_fetching_best_rates">最良のレートを取得しています...</string>
|
||||
<string name="onramp_instant_status">即時</string>
|
||||
<string name="onramp_legal">オンランプ機能を使用することにより、プロバイダの%1$sおよび%2$sに同意するものとします</string>
|
||||
|
|
@ -1055,6 +1060,7 @@
|
|||
<string name="reset_card_to_factory_button_title">カードをリセットする</string>
|
||||
<string name="reset_card_to_factory_condition_1">この操作を実行すると、現在のウォレットにアクセスできなくなることを理解しています。</string>
|
||||
<string name="reset_card_to_factory_condition_2">このカードを使用して、現在のウォレットの他のカードのアクセスコードを回復させられないことを認識しています。</string>
|
||||
<string name="reset_card_to_factory_condition_3">復元は不可能であり、Tangem Payカードおよびカード上のすべての資金へのアクセスを完全に失うことを理解しました</string>
|
||||
<string name="reset_card_with_backup_to_factory_message">工場出荷時設定にリセットすると、選択したカードやリングからウォレットが完全に削除されます。現在のウォレットを復元したり、カードやリングを使用してアクセスコードを復元することはできません。</string>
|
||||
<string name="reset_card_without_backup_to_factory_message">工場出荷時の状態にリセットすると、選択したカードやリングからウォレットが完全に削除され、アプリから削除されます。現在のウォレットを復元することはできません。</string>
|
||||
<string name="reset_cards_dialog_complete_description">すべてのTangemデバイスがリセットされました。</string>
|
||||
|
|
@ -1063,6 +1069,8 @@
|
|||
<string name="reset_cards_dialog_next_device_description">続行するには次のデバイスをリセットしてください</string>
|
||||
<string name="ring_promo_text">Tangem Ringユーザーは、11/15日までChangelly経由でスワップを3回手数料ゼロで行えます!</string>
|
||||
<string name="ring_promo_title">今すぐ手数料0% でスワップしましょう!</string>
|
||||
<string name="root_detected_warning_description">Rootアクセスが有効な端末は、セキュリティが低いと判断されます。データが追加のリスクにさらされる可能性があります。</string>
|
||||
<string name="root_detected_warning_title">Rootアクセスが検出されました</string>
|
||||
<string name="save_user_wallet_agreement_access_description">アプリにログインして、カードまたはリングをスキャンせずに残高を確認できます</string>
|
||||
<string name="save_user_wallet_agreement_access_title">アプリにアクセスする</string>
|
||||
<string name="save_user_wallet_agreement_allow_biometrics">生体認証の使用を許可する</string>
|
||||
|
|
@ -1391,13 +1399,14 @@
|
|||
<string name="tangem_pay_explore_transaction">取引を表示</string>
|
||||
<string name="tangem_pay_fee_subtitle">サービス手数料</string>
|
||||
<string name="tangem_pay_fee_title">手数料</string>
|
||||
<string name="tangem_pay_freeze_card_alert_body">カードを紛失したり盗まれたりしても、資金を安全に保てます。一時停止はいつでも解除できます。</string>
|
||||
<string name="tangem_pay_freeze_card_alert_body">資金を安全に保管します。凍結はいつでも解除できます。</string>
|
||||
<string name="tangem_pay_freeze_card_alert_title">カードを一時停止しますか?</string>
|
||||
<string name="tangem_pay_freeze_card_failed">カードを凍結できませんでした。しばらくしてからもう一度お試しください。</string>
|
||||
<string name="tangem_pay_freeze_card_freeze">一時停止</string>
|
||||
<string name="tangem_pay_freeze_card_success">カードが凍結されています</string>
|
||||
<string name="tangem_pay_get_help">サポートを受ける</string>
|
||||
<string name="tangem_pay_other">その他</string>
|
||||
<string name="tangem_pay_rooted_device_subtitle">Root化された端末では使用できません</string>
|
||||
<string name="tangem_pay_status_completed">完了</string>
|
||||
<string name="tangem_pay_status_declined">拒否</string>
|
||||
<string name="tangem_pay_status_pending">保留中</string>
|
||||
|
|
@ -1410,6 +1419,8 @@
|
|||
<string name="tangem_pay_unfreeze_card_failed">カードの凍結解除に失敗しました。しばらくしてからもう一度お試しください。</string>
|
||||
<string name="tangem_pay_unfreeze_card_success">カードの凍結が解除されました</string>
|
||||
<string name="tangem_pay_withdrawal">出金</string>
|
||||
<string name="tangempay_account_unable_to_use_rooted">Root化された端末では使用できません</string>
|
||||
<string name="tangempay_cancel_kyc">KYCをキャンセル</string>
|
||||
<string name="tangempay_card_details_add_funds">資金を追加</string>
|
||||
<string name="tangempay_card_details_add_funds_subtitle">入金オプション</string>
|
||||
<string name="tangempay_card_details_card_number">カード番号</string>
|
||||
|
|
@ -1437,6 +1448,7 @@
|
|||
<string name="tangempay_card_details_open_wallet_step_5">準備完了!カードはすぐに使用できます。</string>
|
||||
<string name="tangempay_card_details_open_wallet_title">Google Payにカードを追加する</string>
|
||||
<string name="tangempay_card_details_open_wallet_title_apple">Apple Payにカードを追加する</string>
|
||||
<string name="tangempay_card_details_pin_code">PINコード</string>
|
||||
<string name="tangempay_card_details_receive_description">アドレスを共有するか、QRコードを表示してください。</string>
|
||||
<string name="tangempay_card_details_receive_error_description">技術的な問題が検出されました。しばらくしてからもう一度お試しいただくか、サポートにお問い合わせください。</string>
|
||||
<string name="tangempay_card_details_receive_error_title">現在、受け取りは利用できません</string>
|
||||
|
|
@ -1445,12 +1457,15 @@
|
|||
<string name="tangempay_card_details_swap_description">ポートフォリオ内のあらゆる資産をカードと交換</string>
|
||||
<string name="tangempay_card_details_title">カードの詳細</string>
|
||||
<string name="tangempay_card_details_unfreeze_card">カードの一時停止を解除</string>
|
||||
<string name="tangempay_card_details_view_pin_code_description">忘れた場合は、アプリに戻ってください。</string>
|
||||
<string name="tangempay_card_details_view_pin_code_title">PINコード</string>
|
||||
<string name="tangempay_card_details_withdraw">出金</string>
|
||||
<string name="tangempay_card_details_withdraw_error_title">現在、出金ができません</string>
|
||||
<string name="tangempay_card_details_withdraw_in_progress_description">現在の処理が完了するまでは、スワップや新しい出金を開始することはできません。</string>
|
||||
<string name="tangempay_card_details_withdraw_in_progress_title">出金処理中です</string>
|
||||
<string name="tangempay_change_pin_code">PINコードを変更</string>
|
||||
<string name="tangempay_come_back_if_forget_pin">忘れた場合はアプリに戻って確認できます。</string>
|
||||
<string name="tangempay_factory_settings_warning_title">復元は不可能であり、Tangem Payカードおよびカード上のすべての資金へのアクセスを完全に失うことを理解しました</string>
|
||||
<string name="tangempay_failed_to_issue_card">カードの発行に失敗しました</string>
|
||||
<string name="tangempay_failed_to_issue_card_retry_description">技術的なエラーが発生しました。下のボタンをクリックして、もう一度お試しください。</string>
|
||||
<string name="tangempay_failed_to_issue_card_support_description">技術的なエラーが発生しました。サポートへお問い合わせください。</string>
|
||||
|
|
@ -1467,6 +1482,7 @@
|
|||
<string name="tangempay_kyc_in_progress">KYC進行中</string>
|
||||
<string name="tangempay_kyc_in_progress_notification_button">ステータスを表示</string>
|
||||
<string name="tangempay_kyc_in_progress_notification_title">Tangem PayのKYC手続き進行中</string>
|
||||
<string name="tangempay_kyc_in_progress_popup_description">以下のボタンから、現在のKYCステータスを確認するか、KYCをキャンセルできます。</string>
|
||||
<string name="tangempay_onboarding_banner_description">暗号資産を、リアルな支払いに。\n他とはまったく違う、新しいタイプの決済カード。</string>
|
||||
<string name="tangempay_onboarding_banner_title">Tangem Visaカード</string>
|
||||
<string name="tangempay_onboarding_get_card_button_text">カードをGET</string>
|
||||
|
|
@ -1481,10 +1497,10 @@
|
|||
<string name="tangempay_payment_account_sync_needed">支払いアカウントの同期が必要です</string>
|
||||
<string name="tangempay_service_unavailable_description">技術的な問題を修正しています。後でもう一度お試しください。</string>
|
||||
<string name="tangempay_service_unavailable_title">サービスは一時的に利用できません</string>
|
||||
<string name="tangempay_service_unreachable_try_later">現在サービスに接続できません。後ほどもう一度お試しください。</string>
|
||||
<string name="tangempay_service_unreachable_try_later">現在、データを表示できませんが、カードでのお支払いは引き続きご利用いただけます。</string>
|
||||
<string name="tangempay_sync_needed">同期が必要です</string>
|
||||
<string name="tangempay_tangem_visa_card">Tangem Visaカード</string>
|
||||
<string name="tangempay_temporarily_unavailable">Tangem Payは一時的に利用できません</string>
|
||||
<string name="tangempay_temporarily_unavailable">Tangem Payは現在一時的に利用できません。</string>
|
||||
<string name="tangempay_title">Tangem Pay</string>
|
||||
<string name="tangempay_use_tangem_device_to_restore_payment_account">カードまたはリングを使用して、支払いアカウントへのアクセスを復元してください。</string>
|
||||
<string name="tangempay_your_pin_code">PINコード</string>
|
||||
|
|
@ -1568,7 +1584,7 @@
|
|||
<string name="user_push_notification_agreement_header">プッシュ通知を使用しますか?</string>
|
||||
<string name="user_push_notification_banner_subtitle">プッシュ通知を有効にすると、ウォレットに着金したときにアラートを受信できます。</string>
|
||||
<string name="user_push_notification_banner_title">取引を見逃さない</string>
|
||||
<string name="user_wallet_list_add_button">新しいウォレットを追加</string>
|
||||
<string name="user_wallet_list_add_button">ウォレットを追加</string>
|
||||
<string name="user_wallet_list_delete_hw_prompt">バックアップせずにこのウォレットを削除すると、資金に永久にアクセスできなくなります。</string>
|
||||
<string name="user_wallet_list_delete_prompt">このウォレットを忘れてもよろしいですか?</string>
|
||||
<string name="user_wallet_list_error_unable_to_unlock">エラーが発生しました。カードまたはリングをスキャンしてログインしてください。</string>
|
||||
|
|
@ -1589,6 +1605,7 @@
|
|||
<string name="visa_onboarding_in_progress_issuer_description">長くはかかりません。アクティベーションを完了しています。</string>
|
||||
<string name="visa_onboarding_in_progress_title">準備完了です!</string>
|
||||
<string name="visa_onboarding_pin_code_description">4桁のコードを設定します。 \nお支払いの際に使用されます。</string>
|
||||
<string name="visa_onboarding_pin_code_title">PINコードを作成する</string>
|
||||
<string name="visa_onboarding_pin_not_accepted">PINの認証に失敗しました。もう一度お試しいただくか、別のコードを使用してください。</string>
|
||||
<string name="visa_onboarding_pin_validation_error_message">無効な暗証番号:連続や繰り返しを避けてください</string>
|
||||
<string name="visa_onboarding_wallet_connect_title">ウェブサイトに移動</string>
|
||||
|
|
@ -1874,7 +1891,7 @@
|
|||
<string name="wc_warning_transaction">不審な取引</string>
|
||||
<string name="welcome_create_wallet_already_have">すでにTangemウォレットをお持ちですか?</string>
|
||||
<string name="welcome_create_wallet_feature_assets">数千種類の資産</string>
|
||||
<string name="welcome_create_wallet_feature_class">業界最高水準のハードウェアウォレット</string>
|
||||
<string name="welcome_create_wallet_feature_class">最高水準のハードウェアウォレット</string>
|
||||
<string name="welcome_create_wallet_feature_delivery">迅速な配送</string>
|
||||
<string name="welcome_create_wallet_feature_one_tap">ワンタップで開始</string>
|
||||
<string name="welcome_create_wallet_feature_seamless">シームレスで安全</string>
|
||||
|
|
|
|||
|
|
@ -16,8 +16,10 @@
|
|||
<string name="access_code_navtitle">Код доступа</string>
|
||||
<string name="account_add_limit_dialog_description">Превышен лимит %1$s аккаунтов. Архивируйте, чтобы создать новый.</string>
|
||||
<string name="account_add_limit_dialog_title">Невозможно добавить новый аккаунт.</string>
|
||||
<string name="account_archive_success_message">Аккаунт заархивирован</string>
|
||||
<string name="account_archived_accounts">Архивированные аккаунты</string>
|
||||
<string name="account_archived_recover">Восстановить</string>
|
||||
<string name="account_archived_recover_dialog_description">Вы собираетесь восстановить \"%1$s\".</string>
|
||||
<string name="account_archived_recover_dialog_title">Восстановить аккаунт</string>
|
||||
<string name="account_archived_recover_error_message">Лимит в 20 активных аккаунтов достигнут. Пожалуйста, зархивируйте один аккаунт, чтобы продолжить.</string>
|
||||
<string name="account_archived_recover_error_title">Невозможно восстановить аккаунт</string>
|
||||
|
|
@ -26,12 +28,14 @@
|
|||
<string name="account_could_not_archive_referral_program_message">Этот аккаунт участвует в реферальной программе</string>
|
||||
<string name="account_could_not_archive_referral_program_title">Этот аккаунт не может быть архивирован.</string>
|
||||
<string name="account_could_not_create">Мы не смогли создать аккаунт. Пожалуйста, попробуйте позже.</string>
|
||||
<string name="account_create_success_message">Аккаунт создан</string>
|
||||
<string name="account_details_archive">Архивный аккаунт</string>
|
||||
<string name="account_details_archive_action">Архив</string>
|
||||
<string name="account_details_archive_description">Вы архивируете свой аккаунт, но в любое время можете вернуть его обратно</string>
|
||||
<string name="account_details_archiving">Архивация...</string>
|
||||
<string name="account_details_title">Аккаунт</string>
|
||||
<string name="account_edit_failure_dialog_title">Невозможно отредактировать аккаунт</string>
|
||||
<string name="account_edit_success_message">Аккаунт сохранен</string>
|
||||
<string name="account_for_rewards">Аккаунт для наград</string>
|
||||
<string name="account_form_create_button">Добавить аккаунт</string>
|
||||
<string name="account_form_edit_button">Сохранить</string>
|
||||
|
|
@ -46,6 +50,8 @@
|
|||
<string name="account_main_account_title">Основной аккаунт</string>
|
||||
<string name="account_recover_limit_dialog_description">Лимит %1$s активных аккаунтов превышен. Архивируйте один, чтобы продолжить.</string>
|
||||
<string name="account_recover_limit_dialog_title">Невозможно восстановить аккаунт</string>
|
||||
<string name="account_recover_success_message">Аккаунт восстановлен</string>
|
||||
<string name="account_reorder_description">Нажмите и удерживайте аккаунт, чтобы изменить порядок аккаунтов.</string>
|
||||
<string name="account_unsaved_dialog_action_first">Продолжить</string>
|
||||
<string name="account_unsaved_dialog_action_second">Отменить</string>
|
||||
<string name="account_unsaved_dialog_message_create">Вы уверены, что хотите создание нового аккаунта?</string>
|
||||
|
|
@ -87,14 +93,17 @@
|
|||
<string name="alert_manage_tokens_unsupported_message">Токены в сети %1$s не поддерживаются этой картой или кольцом из-за ограничений прошивки.</string>
|
||||
<string name="alert_troubleshooting_scan_card_title">У вас возникли трудности со сканированием карты или кольца?</string>
|
||||
<string name="alert_unsupported_card">Эта карта не предназначена для работы с этим приложением</string>
|
||||
<string name="app_settings_access_code_warning">Сначала установите код доступа, чтобы включить биометрию.</string>
|
||||
<string name="app_settings_biometrics_footer">Используйте %1$s, чтобы быстро и безопасно разблокировать кошелёк и выполнять чувствительные действия, например, подписывать транзакции. Для аппаратных кошельков всё ещё требуется карта или кольцо для подписи.</string>
|
||||
<string name="app_settings_default_fee">Комиссия по-умолчанию</string>
|
||||
<string name="app_settings_default_fee_footer">Подключите функцию комиссии по умолчанию и при формировании транзакции на отправку средств комиссия будет выставлена автоматически, а экран комиссии пропущен. Вы всегда сможете на него вернуться.</string>
|
||||
<string name="app_settings_enable_biometrics_description">Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem</string>
|
||||
<string name="app_settings_enable_biometrics_title">Включите биометрическую аутентификацию</string>
|
||||
<string name="app_settings_off_biometrics_alert_message">Отключение %1$s потребует ввода вашего кода доступа для разблокировки приложения и работы с кошельком.</string>
|
||||
<string name="app_settings_off_require_access_code_alert_message">Позже будет необходимо ввести код доступа к вашему кошельку, чтобы мы могли безопасно сохранить его для будущего использования.</string>
|
||||
<string name="app_settings_off_saved_access_code_alert_message">Все сохраненные коды доступа будут удалены. Вам потребуется вводить код доступа при работе с кошельком.</string>
|
||||
<string name="app_settings_off_saved_wallet_alert_message">При отключении функции сохранения кошелька все ранее сохраненные кошельки будут удалены из приложения.</string>
|
||||
<string name="app_settings_on_require_access_code_alert_message">Это удалит все сохранённые коды доступа к кошелькам. Любое дальнейшее взаимодействие с кошельком будет требовать ввода кода доступа.</string>
|
||||
<string name="app_settings_require_access_code">Требуется код доступа</string>
|
||||
<string name="app_settings_require_access_code_footer">Эта опция отключает использование биометрии для выполнения чувствительных действий. Каждый раз, например при подписании транзакции, вам потребуется вводить код доступа.</string>
|
||||
<string name="app_settings_saved_access_codes">Сохранение кода доступа</string>
|
||||
|
|
@ -144,6 +153,7 @@
|
|||
<string name="biometric_lockout_warning_description_2">Вход по биометрии временно заблокирован. Попробуйте снова через 30 секунд или разблокируйте кошелёк с помощью прикладывания устройства или кода доступа.</string>
|
||||
<string name="biometric_lockout_warning_title">Слишком много попыток</string>
|
||||
<string name="biometric_unavailable_warning">Вы отключили биометрическую аутентификацию на вашем телефоне и не сможете сохранять кошельки в приложении. Для сохранения кошельков, пожалуйста, включите функцию биометрической аутентификации в настройках телефона.</string>
|
||||
<string name="biometric_updated_warning_description">Биометрические данные на вашем устройстве были обновлены. Пожалуйста, выберите свой кошелёк и введите его код доступа, чтобы повторно включить вход по биометрии.</string>
|
||||
<string name="biometric_updated_warning_title">Внимание</string>
|
||||
<string name="bitcoin_promo_activation_error">При обработке промокода произошла ошибка. Пожалуйста, попробуйте позже.</string>
|
||||
<string name="bitcoin_promo_activation_error_title">Ошибка активации</string>
|
||||
|
|
@ -299,6 +309,7 @@
|
|||
<string name="common_later">Позже</string>
|
||||
<string name="common_learn_more">Узнать больше</string>
|
||||
<string name="common_left">Осталось %1$s</string>
|
||||
<string name="common_legacy_bitcoin_address">Легаси Bitcoin</string>
|
||||
<string name="common_locked">Заблокирован</string>
|
||||
<string name="common_locked_wallets">Заблокированные кошельки</string>
|
||||
<string name="common_main_network">Основная сеть</string>
|
||||
|
|
@ -311,6 +322,8 @@
|
|||
<item quantity="many">%d сетей</item>
|
||||
<item quantity="other">%d сети</item>
|
||||
</plurals>
|
||||
<string name="common_new_address">Новый адрес</string>
|
||||
<string name="common_news">Новости</string>
|
||||
<string name="common_next">Далее</string>
|
||||
<string name="common_nft">NFT</string>
|
||||
<string name="common_no">Нет</string>
|
||||
|
|
@ -363,6 +376,7 @@
|
|||
<string name="common_support">Поддержка</string>
|
||||
<string name="common_supported_networks">Поддерживаемые сети</string>
|
||||
<string name="common_swap">Обменять</string>
|
||||
<string name="common_tangem">Tangem</string>
|
||||
<string name="common_terms_and_conditions">условия участия</string>
|
||||
<string name="common_terms_of_use">Условиями использования</string>
|
||||
<string name="common_to">На</string>
|
||||
|
|
@ -380,6 +394,7 @@
|
|||
<string name="common_transfer">Перевод</string>
|
||||
<string name="common_unable_to_load">Невозможно загрузить данные…</string>
|
||||
<string name="common_understand">Я понял</string>
|
||||
<string name="common_understand_continue">Я понимаю, продолжить</string>
|
||||
<string name="common_unknown_error">Произошла ошибка. Пожалуйста, попробуйте снова.</string>
|
||||
<string name="common_unreachable">Недоступно</string>
|
||||
<string name="common_unstake">Завершить стейкинг</string>
|
||||
|
|
@ -388,9 +403,11 @@
|
|||
<string name="common_week">неделю</string>
|
||||
<string name="common_with">с</string>
|
||||
<string name="common_yes">Да</string>
|
||||
<string name="common_yield_mode">Режим доходности</string>
|
||||
<string name="contract_address_copied_message">Адрес контракта скопирован!</string>
|
||||
<string name="currency_subtitle_expanded">Доступные сети</string>
|
||||
<string name="custom_token_another_account_dialog_description">Деривация вашего токена совпадает с деривацией %1$s. Токен будет добавлен к этой учётной записи.</string>
|
||||
<string name="custom_token_another_account_dialog_title">Деривация принадлежит другому аккаунту</string>
|
||||
<string name="custom_token_another_account_snackbar_text">Токен добавлен в аккаунт %1$s</string>
|
||||
<string name="custom_token_contract_address_input_title">Адрес контракта</string>
|
||||
<string name="custom_token_creation_error_invalid_contract_address">Адрес контракта некорректен</string>
|
||||
|
|
@ -527,6 +544,8 @@
|
|||
<string name="express_transaction_id">ID: %s</string>
|
||||
<string name="express_transaction_id_copied">ID транзакции скопирован</string>
|
||||
<string name="exсhange_token_description">Обменяйте любой актив в вашем портфеле на этот токен</string>
|
||||
<string name="feed_market_and_news">Рынок и Новости</string>
|
||||
<string name="feed_trending_now">В тренде</string>
|
||||
<string name="feedback_data_collection_message">Информация ниже не является обязательной. Вы можете стереть её, если хотите.</string>
|
||||
<string name="feedback_preface_rate_negative">Расскажите, каких функций вам не хватает, и мы постараемся вам помочь.</string>
|
||||
<string name="feedback_preface_scan_failed">Скажите, пожалуйста, какая у вас карта или кольцо?</string>
|
||||
|
|
@ -687,6 +706,7 @@
|
|||
<string name="markets_apy_placeholder">APY %s</string>
|
||||
<string name="markets_common_my_portfolio">Мой портфель</string>
|
||||
<string name="markets_common_title">Рынок</string>
|
||||
<string name="markets_earn_common_title">Зарабатывай с Tangem</string>
|
||||
<string name="markets_generate_addresses_notification">Чтобы создать адреса для выбранных сетей, отсканируйте вашу карту или кольцо Tangem кошелька</string>
|
||||
<string name="markets_hint">Потяните вверх или коснитесь поисковой строки, чтобы добавить токен</string>
|
||||
<string name="markets_insights_info_description_message">Данные раздела получены из следующих сетей: %s</string>
|
||||
|
|
@ -714,6 +734,7 @@
|
|||
<string name="markets_sort_by_top_gainers_title">Лидеры роста</string>
|
||||
<string name="markets_sort_by_top_losers_title">Лидеры падения</string>
|
||||
<string name="markets_sort_by_trending_title">В тренде</string>
|
||||
<string name="markets_sort_by_yield_mode_title">Режим доходности</string>
|
||||
<string name="markets_staking_banner_description_placeholder">Стейкинг — простой способ получать доход с вашей криптовалюты. %s</string>
|
||||
<string name="markets_staking_banner_title">Получайте до %s APY</string>
|
||||
<string name="markets_token_added">Токен добавлен</string>
|
||||
|
|
@ -791,6 +812,8 @@
|
|||
<string name="markets_yield_supply_banner_title">Активировать режим доходности</string>
|
||||
<string name="mobile_wallet_requires_min_os_warning_body">Обновитесь до версии %1$s, чтобы создать мобильный кошелёк</string>
|
||||
<string name="mobile_wallet_requires_min_os_warning_title">Мобильный кошелек требует %1$s или новее.</string>
|
||||
<string name="news_all_news">Все новости</string>
|
||||
<string name="news_stay_in_the_loop">Будьте в курсе</string>
|
||||
<string name="nfc_error_unavailable">Функция NFC недоступна на вашем устройстве</string>
|
||||
<string name="nft_about_title">О NFT</string>
|
||||
<string name="nft_asset">NFT</string>
|
||||
|
|
@ -973,6 +996,7 @@
|
|||
<string name="onramp_currency_other">Другие валюты</string>
|
||||
<string name="onramp_currency_popular">Популярные фиаты</string>
|
||||
<string name="onramp_currency_search">Поиск по валюте</string>
|
||||
<string name="onramp_error_transaction_already_processed">Эта транзакция уже была обработана. Дополнительных действий не требуется.</string>
|
||||
<string name="onramp_fetching_best_rates">Получение лучших курсов...</string>
|
||||
<string name="onramp_instant_status">Моментально</string>
|
||||
<string name="onramp_legal">Пользуясь сервисом покупки, вы соглашаетесь с %1$s и %2$s</string>
|
||||
|
|
@ -1083,6 +1107,8 @@
|
|||
<string name="reset_cards_dialog_next_device_description">Сбросьте следующее устройство для продолжения.</string>
|
||||
<string name="ring_promo_text">Владельцам колец — 3 обмена без комиссии на Changelly до 15.11!</string>
|
||||
<string name="ring_promo_title">Обмен с 0% комиссией!</string>
|
||||
<string name="root_detected_warning_description">Устройства с root-доступом считаются менее безопасными. Ваши данные могут быть подвержены дополнительным рискам.</string>
|
||||
<string name="root_detected_warning_title">Обнаружен root-доступ</string>
|
||||
<string name="save_user_wallet_agreement_access_description">Войдите в приложение и следите за своим балансом без сканирования карты или кольца</string>
|
||||
<string name="save_user_wallet_agreement_access_title">Доступ в приложение</string>
|
||||
<string name="save_user_wallet_agreement_allow_biometrics">Использовать биометрию</string>
|
||||
|
|
@ -1198,6 +1224,7 @@
|
|||
<string name="send_tron_account_activation_error">%1$s — это монета в сети Tron. Чтобы рассчитать комиссию и совершить транзакцию, вам необходимо внести немного Tron (TRX) на свой адрес.</string>
|
||||
<string name="send_validation_amount_exceeds_balance">Сумма превышает остаток</string>
|
||||
<string name="send_validation_destination_tag_required_description">Для завершения транзакции на указанный адрес необходим тег назначения (мемо).</string>
|
||||
<string name="send_validation_destination_tag_required_title">Требуется Destination Tag</string>
|
||||
<string name="send_validation_invalid_amount">Недопустимая сумма</string>
|
||||
<string name="send_validation_invalid_fee">Комиссия превышает остаток</string>
|
||||
<string name="send_validation_invalid_total">Отправляемая сумма превышает остаток</string>
|
||||
|
|
@ -1225,6 +1252,8 @@
|
|||
<string name="staking_account_initialization_footer">Сетевая комиссия — это небольшая плата за обработку и подтверждение вашей транзакции в блокчейне.</string>
|
||||
<string name="staking_account_initialization_message">Чтобы начать стейкинг, ваш TON-аккаунт должен быть активирован транзакцией самому себе на 1 TON. Средства остаются в вашем кошельке — этот шаг лишь активирует ваш аккаунт для участия в стейкинге.</string>
|
||||
<string name="staking_account_initialization_title">Активация аккаунта</string>
|
||||
<string name="staking_alert_network_fee_updated_message">Сетевая комиссия изменилась. Пожалуйста, проверьте новую сумму перед продолжением.</string>
|
||||
<string name="staking_alert_network_fee_updated_title">Комиссия сети обновлена</string>
|
||||
<string name="staking_amount_requirement_error">Сумма для стейкинга должна быть не менее %s</string>
|
||||
<string name="staking_amount_tron_integer_error">Согласно правилам сети, сумма стейкинга будет округлена до %1$sTRX.</string>
|
||||
<string name="staking_amount_tron_integer_error_unstaking">Сумма для вывода из стейкинга будет округлена до %1$s TRX ввиду особенностей сети.</string>
|
||||
|
|
@ -1263,6 +1292,7 @@
|
|||
<string name="staking_give_permission_fee_footer">Комиссия сети за одобрение токена будет взиматься за подтверждение того, что именно вы разрешаете использовать ваш токен для стейкинга.</string>
|
||||
<string name="staking_legal">Пользуясь стейкинг сервисом, вы соглашаетесь с %1$s и %2$s</string>
|
||||
<string name="staking_locked">Заблокировано</string>
|
||||
<string name="staking_max_amount_requirement_error">Максимальная сумму: %s</string>
|
||||
<string name="staking_migrate">Переместить</string>
|
||||
<string name="staking_native">Нативный стейкинг</string>
|
||||
<string name="staking_no_validators_error_message">В данный момент нет доступных активных валидаторов для стейкинга. Пожалуйста, попробуйте позже.</string>
|
||||
|
|
@ -1426,7 +1456,7 @@
|
|||
<string name="tangem_pay_unfreeze_card_alert_title">Разморозить карту?</string>
|
||||
<string name="tangem_pay_unfreeze_card_failed">Не удалось разморозить карту, попробуйте еще раз</string>
|
||||
<string name="tangem_pay_unfreeze_card_success">Карта разморожена</string>
|
||||
<string name="tangem_pay_withdrawal">Вывод</string>
|
||||
<string name="tangem_pay_withdrawal">Вывести</string>
|
||||
<string name="tangempay_card_details_add_funds">Пополнить</string>
|
||||
<string name="tangempay_card_details_add_funds_subtitle">Способы пополнения</string>
|
||||
<string name="tangempay_card_details_card_number">Номер</string>
|
||||
|
|
@ -1462,12 +1492,13 @@
|
|||
<string name="tangempay_card_details_swap_description">Пополните карту любым активом через обмен </string>
|
||||
<string name="tangempay_card_details_title">Реквизиты</string>
|
||||
<string name="tangempay_card_details_unfreeze_card">Разморозить карту</string>
|
||||
<string name="tangempay_card_details_withdraw">Вывод</string>
|
||||
<string name="tangempay_card_details_withdraw">Вывести</string>
|
||||
<string name="tangempay_card_details_withdraw_error_title">Вывод сейчас недоступен</string>
|
||||
<string name="tangempay_card_details_withdraw_in_progress_description">Вы не можете начать обмен или новый вывод, пока не завершится текущий.</string>
|
||||
<string name="tangempay_card_details_withdraw_in_progress_title">Вывод выполняется</string>
|
||||
<string name="tangempay_change_pin_code">Изменить PIN-код</string>
|
||||
<string name="tangempay_come_back_if_forget_pin">Можно посмотреть здесь, если забудете его.</string>
|
||||
<string name="tangempay_factory_settings_warning_title">Я понимаю, что полностью потеряю доступ к своей карте Tangem Pay и ко всем средствам на ней без возможности восстановления.</string>
|
||||
<string name="tangempay_failed_to_issue_card">Не удалось выпустить карту</string>
|
||||
<string name="tangempay_failed_to_issue_card_retry_description">Техническая ошибка, попробуйте ещё раз, нажав кнопку ниже</string>
|
||||
<string name="tangempay_failed_to_issue_card_support_description">Техническая ошибка, свяжитесь с поддержкой</string>
|
||||
|
|
@ -1494,16 +1525,16 @@
|
|||
<string name="tangempay_onboarding_security_description">Мы создадим отдельный платежный счет без раскрытия ваших активов \nи их адресов</string>
|
||||
<string name="tangempay_onboarding_security_title">Абсолютная приватность</string>
|
||||
<string name="tangempay_onboarding_title">Откройте виртуальную \nTangem Pay Card</string>
|
||||
<string name="tangempay_payment_account">Payment account</string>
|
||||
<string name="tangempay_payment_account_sync_needed">Требуется синхронизация платежного счета</string>
|
||||
<string name="tangempay_payment_account">Платежный аккаунт</string>
|
||||
<string name="tangempay_payment_account_sync_needed">Требуется синхронизация платежного аккаунта</string>
|
||||
<string name="tangempay_service_unavailable_description">Мы устраняем техническую проблему. Пожалуйста, попробуйте позже.</string>
|
||||
<string name="tangempay_service_unavailable_title">Сервис временно недоступен</string>
|
||||
<string name="tangempay_service_unreachable_try_later">Сервис временно недоступен. Пожалуйста, попробуйте позже.</string>
|
||||
<string name="tangempay_service_unreachable_try_later">Не можем показать данные карты, но оплаты продолжают работать.</string>
|
||||
<string name="tangempay_sync_needed">Требуется синхронизация</string>
|
||||
<string name="tangempay_tangem_visa_card">Tangem Visa Card</string>
|
||||
<string name="tangempay_temporarily_unavailable">Tangem Pay временно недоступен</string>
|
||||
<string name="tangempay_title">Tangem Pay</string>
|
||||
<string name="tangempay_use_tangem_device_to_restore_payment_account">Используйте вашу карту или кольцо для восстановления доступа к платежному счету</string>
|
||||
<string name="tangempay_use_tangem_device_to_restore_payment_account">Используйте вашу карту или кольцо для восстановления доступа к платежному аккаунту</string>
|
||||
<string name="tangempay_your_pin_code">Ваш PIN-код</string>
|
||||
<string name="this_is_my_wallet_title">Это мой кошелек</string>
|
||||
<string name="toast_balances_hidden">Балансы скрыты</string>
|
||||
|
|
@ -1597,7 +1628,12 @@
|
|||
<string name="user_wallet_list_unlock_all_with">Разблокировать все с %s</string>
|
||||
<string name="visa_onboarding_access_code_navigation_title">Код доступа</string>
|
||||
<string name="visa_onboarding_account_activation_navigation_title">Активация аккаунта</string>
|
||||
<string name="visa_onboarding_pin_code_description">Установите 4-значный код.\nОн будет использоваться для платежей.</string>
|
||||
<string name="visa_onboarding_pin_code_navigation_title">ПИН-код</string>
|
||||
<string name="visa_onboarding_pin_code_title">Установить ПИН-код</string>
|
||||
<string name="visa_onboarding_pin_not_accepted">ПИН не принят. Попробуйте ещё раз или введите другой код.</string>
|
||||
<string name="visa_onboarding_pin_validation_error_message">Слабый ПИН: не используйте повторы или последовательности.</string>
|
||||
<string name="visa_unlock_notification_button">Разблокировать</string>
|
||||
<string name="wallet_add_common_title">Выберите способ добавления кошелька</string>
|
||||
<string name="wallet_add_hardware_description">Отсканируйте вашу карту или кольцо Tangem, чтобы восстановить её или импортировать из другого кошелька.</string>
|
||||
<string name="wallet_add_hardware_info_create">Создать аппаратный кошелёк</string>
|
||||
|
|
@ -1934,6 +1970,8 @@
|
|||
<string name="yield_module_fee_policy_sheet_title">Политика комиссий за пополнение</string>
|
||||
<string name="yield_module_fee_policy_tangem_service_fee_title">Tangem взимает комиссию за обслуживание в размере 15% от полученного дохода.</string>
|
||||
<string name="yield_module_high_fee_error">Ваши средства будут автоматически переведены в Aave, как только комиссия сети снизится или баланс достигнет минимально необходимой суммы.</string>
|
||||
<string name="yield_module_high_network_fees_notification_description">Комиссии сейчас выше обычного из-за высокой активности на рынке. Вы можете продолжить сейчас или вернуться позже, когда комиссии снизятся.</string>
|
||||
<string name="yield_module_high_network_fees_notification_title">Высокая сетевая комиссия</string>
|
||||
<string name="yield_module_historical_returns">Историческая доходность</string>
|
||||
<string name="yield_module_main_screen_promo_banner_message">Получай до %1$s APY на свой баланс</string>
|
||||
<string name="yield_module_main_view_approve_notification_description">Разрешение для вашего токена в режиме доходности было отозвано. Откройте токен, чтобы выдать разрешение снова.</string>
|
||||
|
|
@ -1986,7 +2024,7 @@
|
|||
<string name="yield_module_transaction_exit_subtitle">%1$s выведено из Aave</string>
|
||||
<string name="yield_module_transaction_initialize">Режим доходности инициализирован</string>
|
||||
<string name="yield_module_transaction_reactivate">Режим доходности реактивирован</string>
|
||||
<string name="yield_module_transaction_topup">Перевод средств в Aave</string>
|
||||
<string name="yield_module_transaction_topup">Перевод в Aave</string>
|
||||
<string name="yield_module_transaction_topup_subtitle">%1$s отправлено в Aave</string>
|
||||
<string name="yield_module_transaction_withdraw">Вывод из Aave</string>
|
||||
<string name="yield_module_transfer_mode_automatic">Автоматически</string>
|
||||
|
|
|
|||
|
|
@ -257,6 +257,7 @@
|
|||
<string name="common_week">тиждень</string>
|
||||
<string name="common_with">з</string>
|
||||
<string name="common_yes">Так</string>
|
||||
<string name="common_yield_mode">Режим дохідності</string>
|
||||
<string name="contract_address_copied_message">Адреса контракту скопійована!</string>
|
||||
<string name="currency_subtitle_expanded">Доступні мережі</string>
|
||||
<string name="custom_token_contract_address_input_title">Адреса контракту</string>
|
||||
|
|
|
|||
|
|
@ -405,11 +405,11 @@
|
|||
<string name="tangempay_onboarding_security_description">將創建單獨的支付帳戶,且不會透露您的地址和資產</string>
|
||||
<string name="tangempay_onboarding_security_title">無與倫比的隱私</string>
|
||||
<string name="tangempay_onboarding_title">在幾分鐘內獲得免費的 Tangem Pay 卡</string>
|
||||
<string name="tangempay_payment_account">Payment account</string>
|
||||
<string name="tangempay_payment_account">付款帳戶</string>
|
||||
<string name="tangempay_payment_account_sync_needed">需要同步支付账户</string>
|
||||
<string name="tangempay_service_unavailable_description">我们正在修复技术问题。请稍后再试。</string>
|
||||
<string name="tangempay_service_unavailable_title">服務暫時無法使用</string>
|
||||
<string name="tangempay_service_unreachable_try_later">服务当前不可用,请稍后再试</string>
|
||||
<string name="tangempay_service_unreachable_try_later">目前無法顯示資料,但卡片支付仍可正常使用。</string>
|
||||
<string name="tangempay_sync_needed">需要同步</string>
|
||||
<string name="tangempay_tangem_visa_card">Tangem Visa Card</string>
|
||||
<string name="tangempay_temporarily_unavailable">Tangem Pay暂时不可用</string>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<string name="access_code_alert_skip_ok">Skip anyway</string>
|
||||
<string name="access_code_alert_skip_title">Access code not set</string>
|
||||
<string name="access_code_alert_validation_cancel">Change code</string>
|
||||
<string name="access_code_alert_validation_description">Your access code will be used to unlock your wallet and to protect access to the assets</string>
|
||||
<string name="access_code_alert_validation_description">Your access code unlocks and protects access to your wallet</string>
|
||||
<string name="access_code_alert_validation_ok">Use anyway</string>
|
||||
<string name="access_code_alert_validation_title">This access code can be easily guessed</string>
|
||||
<string name="access_code_check_title">Enter access code</string>
|
||||
|
|
@ -96,17 +96,17 @@
|
|||
<string name="alert_manage_tokens_unsupported_message">Tokens in %1$s network are not supported by this card or ring due to firmware limitation.</string>
|
||||
<string name="alert_troubleshooting_scan_card_title">Are you having difficulty scanning your card or ring?</string>
|
||||
<string name="alert_unsupported_card">This card is not designed to work with this app</string>
|
||||
<string name="app_settings_access_code_warning">Set an access code first to enable biometrics</string>
|
||||
<string name="app_settings_access_code_warning">Set an access code to enable biometrics</string>
|
||||
<string name="app_settings_biometrics_footer">Use %1$s to unlock your wallet and approve sensitive actions, like signing transactions. For hardware wallets, a card or ring is still required to sign.</string>
|
||||
<string name="app_settings_default_fee">Default Fee</string>
|
||||
<string name="app_settings_default_fee_footer">Enable Default Fee to set transaction fees automatically and skip the Fee page when sending funds. You can always go back to this page if necessary.</string>
|
||||
<string name="app_settings_enable_biometrics_description">Go to settings to enable biometric authentication in the Tangem App</string>
|
||||
<string name="app_settings_enable_biometrics_title">Enable biometric authentication</string>
|
||||
<string name="app_settings_off_biometrics_alert_message">Disabling %1$s will require you to enter your passcode to unlock the app and to interact with your wallet.</string>
|
||||
<string name="app_settings_off_require_access_code_alert_message">You’ll be asked for your wallet’s access code later so we can securely store it for future use</string>
|
||||
<string name="app_settings_off_saved_access_code_alert_message">This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code.</string>
|
||||
<string name="app_settings_off_require_access_code_alert_message">You\'ll be asked for your access code later for secure storage.</string>
|
||||
<string name="app_settings_off_saved_access_code_alert_message">This will delete all saved wallet access codes. You\'ll need to enter the access code again to use the wallet.</string>
|
||||
<string name="app_settings_off_saved_wallet_alert_message">Removing the saved devices deletes all the saved wallets and their access codes from the app.</string>
|
||||
<string name="app_settings_on_require_access_code_alert_message">This will delete all the saved wallet access codes. Any further interaction with the wallet will require submitting the access code.</string>
|
||||
<string name="app_settings_on_require_access_code_alert_message">This will delete all saved wallet access codes. You’ll need to enter the access code again to use the wallet.</string>
|
||||
<string name="app_settings_require_access_code">Require Access Code</string>
|
||||
<string name="app_settings_require_access_code_footer">This option turns off biometrics for sensitive actions. You’ll need to enter your access code each time you sign a transaction.</string>
|
||||
<string name="app_settings_saved_access_codes">Save Access Code</string>
|
||||
|
|
@ -137,7 +137,7 @@
|
|||
<string name="backup_info_title">Recovery phrase</string>
|
||||
<string name="backup_seed_caution">Never share these words with anyone. Tangem will never ask you for them. The %s words below are your wallet\'s recovery phrase. Use them to restore your wallet if you lose your device.</string>
|
||||
<string name="backup_seed_description">Write down these %s words in numerical order and keep them safe and private</string>
|
||||
<string name="backup_seed_responsibility">You are fully responsible for securing and backing up your wallet and recovery phrase.</string>
|
||||
<string name="backup_seed_responsibility">You are fully responsible for securing your wallet and safely backing up your recovery phrase.</string>
|
||||
<string name="backup_seed_title">Recovery phrase</string>
|
||||
<string name="balance_hidden_description">To hide or show your balances, simply flip your device screen down, or switch it off in Settings</string>
|
||||
<string name="balance_hidden_do_not_show_button">Don\'t show again</string>
|
||||
|
|
@ -145,10 +145,10 @@
|
|||
<string name="balance_hidden_title">Balances are hidden</string>
|
||||
<string name="beta_mode_warning_message">According to the blockchain developers, Kaspa tokens are currently in beta. Stay tuned for updates!</string>
|
||||
<string name="beta_mode_warning_title">Beta Mode</string>
|
||||
<string name="biometric_disabled_warning_description">Biometrics are turned off on your device, so you can’t use them to unlock your wallets. Enable biometrics in your device settings to use this method again.</string>
|
||||
<string name="biometric_disabled_warning_description">Biometrics are turned off on your device, so you can\'t use them to unlock your wallets. Enable biometrics in your device settings to use this method again.</string>
|
||||
<string name="biometric_disabled_warning_title">Biometric authentication disabled</string>
|
||||
<string name="biometric_lockout_permanent_warning_description">Please scan the card or ring</string>
|
||||
<string name="biometric_lockout_permanent_warning_description_2">You’ve reached the limit of biometric attempts. Please unlock your wallet with a device tap or enter your access code.</string>
|
||||
<string name="biometric_lockout_permanent_warning_description_2">You\'ve reached the limit of biometric attempts. Please unlock your wallet with a card/ring or enter your access code.</string>
|
||||
<string name="biometric_lockout_permanent_warning_title">Biometric authentication locked</string>
|
||||
<string name="biometric_lockout_warning_description">Please try again in 30 seconds or scan the card or ring</string>
|
||||
<string name="biometric_lockout_warning_description_2">Biometric login is temporarily locked. Please try again in 30 seconds, or unlock your wallet with a device tap or access code.</string>
|
||||
|
|
@ -182,7 +182,7 @@
|
|||
<string name="card_reset_alert_finish_ok_button">Upgrade again</string>
|
||||
<string name="card_reset_alert_finish_title">Reset complete</string>
|
||||
<string name="card_reset_alert_incomplete_message">We recommend completing the reset process for all Tangem devices in this wallet.</string>
|
||||
<string name="card_reset_alert_incomplete_title">You haven’t reset all your Tangem devices</string>
|
||||
<string name="card_reset_alert_incomplete_title">Some Tangem devices still need to be reset.</string>
|
||||
<string name="card_settings_access_code_recovery_disabled_description">Disable this option if you don\'t want this card to be used to reset access codes on other cards or rings in this wallet. Please note that this will also prevent you from resetting the access code on this card.</string>
|
||||
<string name="card_settings_access_code_recovery_enabled_description">Allows you to use this card to reset access code on other cards in this wallet</string>
|
||||
<string name="card_settings_access_code_recovery_title">Access code recovery</string>
|
||||
|
|
@ -384,8 +384,9 @@
|
|||
<string name="common_transaction_status">Transaction status</string>
|
||||
<string name="common_transactions">Transactions</string>
|
||||
<string name="common_transfer">Transfer</string>
|
||||
<string name="common_unable_to_load">Unable to load the data…</string>
|
||||
<string name="common_unable_to_load">Unable to load data…</string>
|
||||
<string name="common_understand">I understand</string>
|
||||
<string name="common_understand_continue">I understand, continue</string>
|
||||
<string name="common_unknown_error">There was an error. Please try again.</string>
|
||||
<string name="common_unreachable">Unreachable</string>
|
||||
<string name="common_unstake">Unstake</string>
|
||||
|
|
@ -394,6 +395,7 @@
|
|||
<string name="common_week">week</string>
|
||||
<string name="common_with">with</string>
|
||||
<string name="common_yes">Yes</string>
|
||||
<string name="common_yield_mode">Yield Mode</string>
|
||||
<string name="contract_address_copied_message">Contract address copied!</string>
|
||||
<string name="currency_subtitle_expanded">Available networks</string>
|
||||
<string name="custom_token_another_account_dialog_description">Derivation of your token matches the derivation of %1$s. Your token will be added to this account.</string>
|
||||
|
|
@ -574,7 +576,7 @@
|
|||
<string name="home_button_create_new_wallet">Create New Wallet</string>
|
||||
<string name="home_button_order">Order Tangem</string>
|
||||
<string name="home_button_scan">Scan Tangem</string>
|
||||
<string name="hot_access_code_set_biometric_ask">Do you want to allow “Tangem” to use biometric authentication? To confirm your identity and open the app</string>
|
||||
<string name="hot_access_code_set_biometric_ask">Do you want to allow \"Tangem\" to use biometric authentication to confirm your identity and open the app?</string>
|
||||
<string name="hot_crypto_add_token_subtitle">to %s</string>
|
||||
<string name="hot_crypto_token_network">On %s network</string>
|
||||
<string name="hw_access_code_create_alert_title">Are you sure you want to cancel access code setup?</string>
|
||||
|
|
@ -583,31 +585,31 @@
|
|||
<string name="hw_activation_need_finish">Finalize now</string>
|
||||
<string name="hw_activation_need_title">Finalize wallet setup</string>
|
||||
<string name="hw_activation_need_warning_description">Complete setup by securing the app with an access code.</string>
|
||||
<string name="hw_backup_alert_description">If you do, you\'ll need to start over.</string>
|
||||
<string name="hw_backup_alert_title">Are you sure you want to quit the activation process?</string>
|
||||
<string name="hw_backup_banner_description">Keeps your crypto safe and offline. Slim as a credit card, safer than a bank vault.</string>
|
||||
<string name="hw_backup_alert_description">If you exit, you\'ll need to start over.</string>
|
||||
<string name="hw_backup_alert_title">Are you sure you want to quit the setup process?</string>
|
||||
<string name="hw_backup_banner_description">Keeps your crypto safe and offline. Slim as a credit card, safer than a bank vault.</string>
|
||||
<string name="hw_backup_close_description">If you do, you\'ll need to start over.</string>
|
||||
<string name="hw_backup_google_drive_description">Recover existing wallet via Google Drive backup</string>
|
||||
<string name="hw_backup_google_drive_title">Google Drive backup</string>
|
||||
<string name="hw_backup_hardware_create_description">Create a new secure wallet and transfer your funds for extra protection.</string>
|
||||
<string name="hw_backup_hardware_create_description">Create a secure wallet and transfer your funds for extra protection.</string>
|
||||
<string name="hw_backup_hardware_create_title">Create new wallet</string>
|
||||
<string name="hw_backup_hardware_description">Level up your security with the superior Tangem hardware wallet.</string>
|
||||
<string name="hw_backup_hardware_title">Hardware Wallet</string>
|
||||
<string name="hw_backup_hardware_upgrade_description">Move your current wallet into Tangem Wallet.</string>
|
||||
<string name="hw_backup_hardware_title">Hardware wallet</string>
|
||||
<string name="hw_backup_hardware_upgrade_description">Move your current wallet into Tangem.</string>
|
||||
<string name="hw_backup_hardware_upgrade_title">Upgrade current wallet</string>
|
||||
<string name="hw_backup_need_action">Go to backup</string>
|
||||
<string name="hw_backup_need_description">Please back up your wallet before creating an access code.</string>
|
||||
<string name="hw_backup_need_finish_first">Finish Backup First</string>
|
||||
<string name="hw_backup_need_finish_first">Finish backup first</string>
|
||||
<string name="hw_backup_need_title">Finalize backup first</string>
|
||||
<string name="hw_backup_no_backup">Incomplete</string>
|
||||
<string name="hw_backup_section_other_title">Other methods</string>
|
||||
<string name="hw_backup_seed_description">Manually save your recovery phrase in a secure place and keep it private to protect your funds.</string>
|
||||
<string name="hw_backup_seed_description">Save your recovery phrase in a secure place and keep it private to protect your funds.</string>
|
||||
<string name="hw_backup_seed_title">Recovery phrase</string>
|
||||
<string name="hw_backup_to_secure_description">To secure your wallet with an Access Code, complete the backup first.</string>
|
||||
<string name="hw_backup_to_upgrade_description">To upgrade your wallet to hardware, back it up first.</string>
|
||||
<string name="hw_backup_to_secure_description">To secure your wallet with an access code, complete the backup process.</string>
|
||||
<string name="hw_backup_to_upgrade_description">To upgrade to a hardware wallet, complete the backup process.</string>
|
||||
<string name="hw_create_keys_description">Your private keys are securely encrypted and stored on your phone</string>
|
||||
<string name="hw_create_keys_title">Keys are stored in the app</string>
|
||||
<string name="hw_create_seed_description">Create or restore your wallet using a recovery phrase — your built-in backup.</string>
|
||||
<string name="hw_create_keys_title">Private keys stay on your device</string>
|
||||
<string name="hw_create_seed_description">Create or restore your wallet with a recovery phrase.</string>
|
||||
<string name="hw_create_seed_title">Seed phrase backup</string>
|
||||
<string name="hw_create_title">Create Mobile Wallet</string>
|
||||
<string name="hw_import_existing_wallet">Import existing wallet</string>
|
||||
|
|
@ -620,28 +622,28 @@
|
|||
<string name="hw_remove_wallet_notification_action_backup_go">Go to backup</string>
|
||||
<string name="hw_remove_wallet_notification_action_backup_view">View backup</string>
|
||||
<string name="hw_remove_wallet_notification_action_forget">Forget wallet</string>
|
||||
<string name="hw_remove_wallet_notification_action_forget_anyway">Forget Anyway</string>
|
||||
<string name="hw_remove_wallet_notification_description_has_backup">A backup for this wallet exists. Review it before forgetting to make sure you can recover later.</string>
|
||||
<string name="hw_remove_wallet_notification_description_without_backup">If you forget this wallet without a backup, you’ll permanently lose access to your funds.</string>
|
||||
<string name="hw_remove_wallet_notification_title">Forget this wallet?</string>
|
||||
<string name="hw_remove_wallet_warning_access">I understand that if I haven\'t backed up my wallet before removing it, I may lose access to it.</string>
|
||||
<string name="hw_remove_wallet_warning_device">I understand that removing my wallet does not delete it—but simply removes it from my device.</string>
|
||||
<string name="hw_upgrade_backup_description">No seed phrase is needed anymore. Your Tangem card or ring becomes your secure backup.</string>
|
||||
<string name="hw_remove_wallet_notification_action_forget_anyway">Forget anyway</string>
|
||||
<string name="hw_remove_wallet_notification_description_has_backup">This wallet has a backup. Ensure you can recover it before forgetting the wallet.</string>
|
||||
<string name="hw_remove_wallet_notification_description_without_backup">If you forget this wallet without a backup, you\'ll permanently lose access to your funds.</string>
|
||||
<string name="hw_remove_wallet_notification_title">Are you sure you want to forget this wallet?</string>
|
||||
<string name="hw_remove_wallet_warning_access">I understand that if I haven\'t backed up my wallet before removing it, I will lose access to it.</string>
|
||||
<string name="hw_remove_wallet_warning_device">I understand that removing my wallet does not delete it, only removes it from my device.</string>
|
||||
<string name="hw_upgrade_backup_description">Seed phrase not required. Your Tangem card or ring becomes your secure backup.</string>
|
||||
<string name="hw_upgrade_backup_title">Backup with Tangem</string>
|
||||
<string name="hw_upgrade_error_card_already_has_wallet">Can’t upgrade. A wallet already exists on this device.</string>
|
||||
<string name="hw_upgrade_error_card_already_has_wallet">Can\'t upgrade. A wallet already exists on this device.</string>
|
||||
<string name="hw_upgrade_error_card_key_import">Pick another device. This one can’t be used for the upgrade.</string>
|
||||
<string name="hw_upgrade_error_wallet2_card_required">An error occurred during the operation.</string>
|
||||
<string name="hw_upgrade_funds_access_description">Your funds remain safe and fully accessible during the process</string>
|
||||
<string name="hw_upgrade_funds_access_title">Access to funds</string>
|
||||
<string name="hw_upgrade_general_security_description">Your wallet data will be erased from the app and stored on your Tangem device</string>
|
||||
<string name="hw_upgrade_general_security_description">Your wallet data will be erased from the app and stored on your hardware wallet</string>
|
||||
<string name="hw_upgrade_general_security_title">General security</string>
|
||||
<string name="hw_upgrade_key_migration_description">Private keys will be moved from the app to your Tangem device</string>
|
||||
<string name="hw_upgrade_key_migration_title">Key Migration</string>
|
||||
<string name="hw_upgrade_key_migration_description">Private keys will be moved from the app to your Tangem hardware wallet</string>
|
||||
<string name="hw_upgrade_key_migration_title">Key migration</string>
|
||||
<string name="hw_upgrade_scan_device">Scan device</string>
|
||||
<string name="hw_upgrade_start_action">Start upgrade</string>
|
||||
<string name="hw_upgrade_start_description">You’re about to upgrade to our hardware wallet. This will keep your assets safe in cold storage.</string>
|
||||
<string name="hw_upgrade_start_description">You\'re about to upgrade to our hardware wallet. It will keep your assets safe in cold storage.</string>
|
||||
<string name="hw_upgrade_start_title">Tangem Wallet</string>
|
||||
<string name="hw_upgrade_title">Upgrade to Hardware Wallet</string>
|
||||
<string name="hw_upgrade_title">Upgrade to our Hardware Wallet</string>
|
||||
<string name="hw_upgrade_to_cold_banner_description">Keep your crypto safe with Tangem\'s top-tier hardware wallet.</string>
|
||||
<string name="hw_upgrade_to_cold_banner_title">Upgrade your wallet to hardware security</string>
|
||||
<string name="information_generated_with_ai">This information was generated with AI.\nTap here, if you find any errors.</string>
|
||||
|
|
@ -726,6 +728,7 @@
|
|||
<string name="markets_sort_by_top_gainers_title">Top Gainers</string>
|
||||
<string name="markets_sort_by_top_losers_title">Top Losers</string>
|
||||
<string name="markets_sort_by_trending_title">Trending</string>
|
||||
<string name="markets_sort_by_yield_mode_title">Yield Mode</string>
|
||||
<string name="markets_staking_banner_description_placeholder">Staking is the easiest way to receive rewards on your crypto. %s</string>
|
||||
<string name="markets_staking_banner_title">Earn up to %s APY</string>
|
||||
<string name="markets_token_added">Token Added</string>
|
||||
|
|
@ -797,11 +800,11 @@
|
|||
<string name="markets_tooltip_title">Add tokens</string>
|
||||
<string name="markets_yield_supply_banner_description">Power up your assets while supplying them with instant access. %s</string>
|
||||
<string name="markets_yield_supply_banner_title">Activate Yield Mode</string>
|
||||
<string name="mobile_wallet_requires_min_os_warning_body">You must update to %1$s in order to create mobile wallet</string>
|
||||
<string name="mobile_wallet_requires_min_os_warning_body">You must update to %1$s before creating a mobile wallet</string>
|
||||
<string name="mobile_wallet_requires_min_os_warning_title">Mobile Wallet requires %1$s or later</string>
|
||||
<string name="news_all_news">All news</string>
|
||||
<plurals name="news_published_hours_ago">
|
||||
<item quantity="one"></item>
|
||||
<item quantity="one">%dh ago</item>
|
||||
<item quantity="other">%dh ago</item>
|
||||
</plurals>
|
||||
<plurals name="news_published_minutes_ago">
|
||||
|
|
@ -983,6 +986,7 @@
|
|||
<string name="onramp_currency_other">Other currencies</string>
|
||||
<string name="onramp_currency_popular">Popular Fiats</string>
|
||||
<string name="onramp_currency_search">Search by currency</string>
|
||||
<string name="onramp_error_transaction_already_processed">This transaction has already been processed. No further action is required.</string>
|
||||
<string name="onramp_fetching_best_rates">Fetching best rates...</string>
|
||||
<string name="onramp_instant_status">Instant</string>
|
||||
<string name="onramp_legal">By using onramp functionality, you agree with provider’s %1$s and %2$s</string>
|
||||
|
|
@ -1079,6 +1083,7 @@
|
|||
<string name="reset_card_to_factory_button_title">Reset the card</string>
|
||||
<string name="reset_card_to_factory_condition_1">I understand that after performing this action, I will no longer have access to the current wallet</string>
|
||||
<string name="reset_card_to_factory_condition_2">I realize that I can\'t use this card to recover my access code on the other cards of the current wallet</string>
|
||||
<string name="reset_card_to_factory_condition_3">I understand that I will completely lose access to my Tangem Pay Card and all funds on it without the possibility of recovery</string>
|
||||
<string name="reset_card_with_backup_to_factory_message">Factory Reset will completely delete the wallet from the selected card or ring. You will not be able to restore the current wallet or use the card or ring to recover the access code.</string>
|
||||
<string name="reset_card_without_backup_to_factory_message">Factory Reset will completely delete the wallet from the selected card or ring and remove it from the app. You will not be able to restore the current wallet.</string>
|
||||
<string name="reset_cards_dialog_complete_description">All Tangem devices have been reset.</string>
|
||||
|
|
@ -1087,6 +1092,8 @@
|
|||
<string name="reset_cards_dialog_next_device_description">Please reset the next device to continue</string>
|
||||
<string name="ring_promo_text">Ring owners get 3 commission-free swaps on Changelly until 15.11!</string>
|
||||
<string name="ring_promo_title">Swap With 0% Fees Now!</string>
|
||||
<string name="root_detected_warning_description">Devices with root access are considered less secure. Your data may be exposed to additional risks.</string>
|
||||
<string name="root_detected_warning_title">Root access detected</string>
|
||||
<string name="save_user_wallet_agreement_access_description">Log into the app and check your balance without scanning the card or ring</string>
|
||||
<string name="save_user_wallet_agreement_access_title">Access the app</string>
|
||||
<string name="save_user_wallet_agreement_allow_biometrics">Allow to use biometrics</string>
|
||||
|
|
@ -1423,6 +1430,7 @@
|
|||
<string name="tangem_pay_freeze_card_success">Your card is frozen.</string>
|
||||
<string name="tangem_pay_get_help">Get Help</string>
|
||||
<string name="tangem_pay_other">Other</string>
|
||||
<string name="tangem_pay_rooted_device_subtitle">Unable to use on rooted devices</string>
|
||||
<string name="tangem_pay_status_completed">Completed</string>
|
||||
<string name="tangem_pay_status_declined">Declined</string>
|
||||
<string name="tangem_pay_status_pending">Pending</string>
|
||||
|
|
@ -1435,6 +1443,8 @@
|
|||
<string name="tangem_pay_unfreeze_card_failed">Failed to unfreeze the card. Try again later.</string>
|
||||
<string name="tangem_pay_unfreeze_card_success">Your card is unfrozen.</string>
|
||||
<string name="tangem_pay_withdrawal">Withdrawal</string>
|
||||
<string name="tangempay_account_unable_to_use_rooted">Unable to use on rooted device</string>
|
||||
<string name="tangempay_cancel_kyc">Cancel KYC</string>
|
||||
<string name="tangempay_card_details_add_funds">Add funds</string>
|
||||
<string name="tangempay_card_details_add_funds_subtitle">Top-up options</string>
|
||||
<string name="tangempay_card_details_card_number">Card Number</string>
|
||||
|
|
@ -1462,6 +1472,7 @@
|
|||
<string name="tangempay_card_details_open_wallet_step_5">All set! Your card is ready to use.</string>
|
||||
<string name="tangempay_card_details_open_wallet_title">Add card to Google Pay</string>
|
||||
<string name="tangempay_card_details_open_wallet_title_apple">Add card to Apple Pay</string>
|
||||
<string name="tangempay_card_details_pin_code">PIN code</string>
|
||||
<string name="tangempay_card_details_receive_description">Share your address or show QR-code</string>
|
||||
<string name="tangempay_card_details_receive_error_description">Technical issues detected. Please try again later or contact support.</string>
|
||||
<string name="tangempay_card_details_receive_error_title">Receive unavailable now</string>
|
||||
|
|
@ -1470,12 +1481,15 @@
|
|||
<string name="tangempay_card_details_swap_description">Swap any asset in your portfolio for card</string>
|
||||
<string name="tangempay_card_details_title">Card details</string>
|
||||
<string name="tangempay_card_details_unfreeze_card">Unfreeze Card</string>
|
||||
<string name="tangempay_card_details_view_pin_code_description">Come back to the app if you forget it.</string>
|
||||
<string name="tangempay_card_details_view_pin_code_title">Your PIN code</string>
|
||||
<string name="tangempay_card_details_withdraw">Withdraw</string>
|
||||
<string name="tangempay_card_details_withdraw_error_title">Withdraw unavailable now</string>
|
||||
<string name="tangempay_card_details_withdraw_in_progress_description">You can\'t initiate swap or new withdrawal till the current one is finished</string>
|
||||
<string name="tangempay_card_details_withdraw_in_progress_title">Withdrawal in progress</string>
|
||||
<string name="tangempay_change_pin_code">Change PIN-code</string>
|
||||
<string name="tangempay_come_back_if_forget_pin">Come back to the app if you forget it.</string>
|
||||
<string name="tangempay_factory_settings_warning_title">I understand that I will completely lose access to my Tangem Pay Card and all funds on it without the possibility of recovery</string>
|
||||
<string name="tangempay_failed_to_issue_card">Failed to issue card</string>
|
||||
<string name="tangempay_failed_to_issue_card_retry_description">A technical error has occurred, please try again by clicking the button below.</string>
|
||||
<string name="tangempay_failed_to_issue_card_support_description">A technical error has occurred, please contact support.</string>
|
||||
|
|
@ -1492,6 +1506,7 @@
|
|||
<string name="tangempay_kyc_in_progress">KYC in progress</string>
|
||||
<string name="tangempay_kyc_in_progress_notification_button">View Status</string>
|
||||
<string name="tangempay_kyc_in_progress_notification_title">KYC in progress for Tangem Pay</string>
|
||||
<string name="tangempay_kyc_in_progress_popup_description">Use the buttons below to view your current KYC status or cancel it.</string>
|
||||
<string name="tangempay_onboarding_banner_description">Use your crypto for real world spending. \nIt’s a payment card unlike any other.</string>
|
||||
<string name="tangempay_onboarding_banner_title">Tangem Visa Card</string>
|
||||
<string name="tangempay_onboarding_get_card_button_text">Get card</string>
|
||||
|
|
@ -1506,10 +1521,10 @@
|
|||
<string name="tangempay_payment_account_sync_needed">Payment account sync needed</string>
|
||||
<string name="tangempay_service_unavailable_description">We’re fixing a technical issue. Please try again later.</string>
|
||||
<string name="tangempay_service_unavailable_title">Service temporarily unavailable</string>
|
||||
<string name="tangempay_service_unreachable_try_later">The service is currently unreachable. Please try again later.</string>
|
||||
<string name="tangempay_service_unreachable_try_later">Unable to display details. However, card payments are still working.</string>
|
||||
<string name="tangempay_sync_needed">Sync needed</string>
|
||||
<string name="tangempay_tangem_visa_card">Tangem Visa Card</string>
|
||||
<string name="tangempay_temporarily_unavailable">Tangem Pay is temporarily unavailable</string>
|
||||
<string name="tangempay_temporarily_unavailable">Tangem Pay is temporarily unreachable</string>
|
||||
<string name="tangempay_title">Tangem Pay</string>
|
||||
<string name="tangempay_use_tangem_device_to_restore_payment_account">Use your card or ring to restore access to your payment account</string>
|
||||
<string name="tangempay_your_pin_code">Your PIN code</string>
|
||||
|
|
@ -1593,7 +1608,7 @@
|
|||
<string name="user_push_notification_agreement_header">Would you like to use\nPush-notifications?</string>
|
||||
<string name="user_push_notification_banner_subtitle">Enable push notifications to receive alerts when funds arrive in your wallet.</string>
|
||||
<string name="user_push_notification_banner_title">Don\'t Miss a Transaction</string>
|
||||
<string name="user_wallet_list_add_button">Add new wallet</string>
|
||||
<string name="user_wallet_list_add_button">Add Wallet</string>
|
||||
<string name="user_wallet_list_delete_hw_prompt">If you delete this wallet without a backup, you will permanently lose access to your funds.</string>
|
||||
<string name="user_wallet_list_delete_prompt">Are you sure you want to forget this wallet?</string>
|
||||
<string name="user_wallet_list_error_unable_to_unlock">An error has occurred, please scan your card or ring to log in</string>
|
||||
|
|
@ -1727,7 +1742,7 @@
|
|||
<string name="wallet_create_scan_question">Using a Tangem Wallet already?</string>
|
||||
<string name="wallet_create_scan_title">Scan now</string>
|
||||
<string name="wallet_create_title">Pick a wallet setup method</string>
|
||||
<string name="wallet_import_buy_question">Want to purchase Tangem Wallet?</string>
|
||||
<string name="wallet_import_buy_question">Want to buy a Tangem Wallet?</string>
|
||||
<string name="wallet_import_buy_title">Buy now</string>
|
||||
<string name="wallet_import_google_drive_description">Recover existing wallet via Google Drive backup</string>
|
||||
<string name="wallet_import_google_drive_title">Import from Google Drive</string>
|
||||
|
|
@ -1754,7 +1769,7 @@
|
|||
<string name="wallet_settings_change_access_code_title">Change access code</string>
|
||||
<string name="wallet_settings_push_notifications_description">Stay notified on wallet incoming transactions and Tangem updates.</string>
|
||||
<string name="wallet_settings_push_notifications_huawei_warning">Push notifications may currently not work on Huawei devices. We\'re actively working on a solution and will release a fix in an upcoming update. Thank you for your understanding!</string>
|
||||
<string name="wallet_settings_push_notifications_title">Transaction Notifications</string>
|
||||
<string name="wallet_settings_push_notifications_title">Transaction notifications</string>
|
||||
<string name="wallet_settings_set_access_code_title">Set access code</string>
|
||||
<string name="wallet_settings_title">Wallet settings</string>
|
||||
<string name="wallet_title">Tangem</string>
|
||||
|
|
@ -1947,7 +1962,7 @@
|
|||
<string name="wc_warning_transaction">Suspicious transaction</string>
|
||||
<string name="welcome_create_wallet_already_have">Already have Tangem Wallet?</string>
|
||||
<string name="welcome_create_wallet_feature_assets">Thousands of assets</string>
|
||||
<string name="welcome_create_wallet_feature_class">Best in class hardware wallet</string>
|
||||
<string name="welcome_create_wallet_feature_class">Top-tier hardware wallet</string>
|
||||
<string name="welcome_create_wallet_feature_delivery">Fast delivery</string>
|
||||
<string name="welcome_create_wallet_feature_one_tap">Start in one tap</string>
|
||||
<string name="welcome_create_wallet_feature_seamless">Seamless and secure</string>
|
||||
|
|
@ -1956,8 +1971,8 @@
|
|||
<string name="welcome_create_wallet_mobile_description">Create or import a software wallet</string>
|
||||
<string name="welcome_create_wallet_mobile_description_full">Create or import a software wallet on your phone.</string>
|
||||
<string name="welcome_create_wallet_mobile_title">Start with Mobile Wallet</string>
|
||||
<string name="welcome_create_wallet_other_method">Other method</string>
|
||||
<string name="welcome_create_wallet_use_hardware_description">Use Tangem Hardware Wallet</string>
|
||||
<string name="welcome_create_wallet_other_method">Other methods</string>
|
||||
<string name="welcome_create_wallet_use_hardware_description">Use a Tangem hardware wallet</string>
|
||||
<string name="welcome_create_wallet_use_hardware_title">Learn more & buy</string>
|
||||
<string name="welcome_interrupted_backup_alert_discard">Discard</string>
|
||||
<string name="welcome_interrupted_backup_alert_message">You have an interrupted backup. Do you want to resume?</string>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.core.ui.components.token
|
|||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -18,6 +19,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import androidx.compose.ui.unit.Constraints
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.audits.AuditLabelUM
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
||||
|
|
@ -28,6 +30,7 @@ import com.tangem.core.ui.components.token.internal.*
|
|||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState.FiatAmountState
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState.Subtitle2State
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState.PromoBannerState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.rememberHapticFeedback
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
|
|
@ -44,7 +47,7 @@ private const val TITLE_MIN_WIDTH_COEFFICIENT = 0.3
|
|||
private const val PRICE_MIN_WIDTH_COEFFICIENT = 0.32
|
||||
|
||||
private enum class LayoutId {
|
||||
ICON, TITLE, FIAT_AMOUNT, CRYPTO_AMOUNT, CRYPTO_PRICE, NON_FIAT_CONTENT
|
||||
ICON, TITLE, FIAT_AMOUNT, CRYPTO_AMOUNT, CRYPTO_PRICE, NON_FIAT_CONTENT, PROMO_BANNER
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -109,6 +112,14 @@ fun TokenItem(
|
|||
.testTag(TokenElementsTestTags.TOKEN_ICON),
|
||||
)
|
||||
|
||||
YieldSupplyPromoBanner(
|
||||
state = state.promoBannerState,
|
||||
modifier = Modifier
|
||||
.layoutId(layoutId = LayoutId.PROMO_BANNER)
|
||||
.testTag(TokenElementsTestTags.TOKEN_YIELD_PROMO_BANNER)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
TokenTitle(
|
||||
state = state.titleState,
|
||||
modifier = Modifier
|
||||
|
|
@ -220,6 +231,13 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier
|
|||
|
||||
val nonFiatContent = measurables.measure(layoutId = LayoutId.NON_FIAT_CONTENT, constraints = constraints)
|
||||
|
||||
val promoBanner = when (state.promoBannerState) {
|
||||
is PromoBannerState.Content -> measurables.measure(
|
||||
layoutId = LayoutId.PROMO_BANNER,
|
||||
constraints = constraints,
|
||||
)
|
||||
else -> null
|
||||
}
|
||||
var firstRowRemainingFreeSpace: Int? = null
|
||||
var secondRowRemainingFreeSpace: Int? = null
|
||||
|
||||
|
|
@ -283,10 +301,18 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier
|
|||
)
|
||||
}
|
||||
|
||||
val promoBannerHeight = promoBanner?.height ?: 0
|
||||
val promoOffset = if (promoBannerHeight > 0) {
|
||||
promoBannerHeight - 8.dp.roundToPx()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
|
||||
val layoutHeight = calculateLayoutHeight(
|
||||
state = state,
|
||||
minLayoutHeight = with(density) { dimens.size68.roundToPx() },
|
||||
layoutPadding = verticalPadding,
|
||||
promoOffset = promoOffset,
|
||||
title = title,
|
||||
fiatAmount = fiatAmount,
|
||||
cryptoAmount = cryptoAmount,
|
||||
|
|
@ -294,16 +320,22 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier
|
|||
)
|
||||
|
||||
layout(width = constraints.maxWidth, height = layoutHeight) {
|
||||
icon.placeRelative(x = 0, y = (layoutHeight - icon.height).div(other = 2))
|
||||
promoBanner?.placeRelative(x = 0, y = 0)
|
||||
|
||||
icon.placeRelative(
|
||||
x = 0,
|
||||
y = promoOffset + (layoutHeight - promoOffset - icon.height)
|
||||
.div(other = 2),
|
||||
)
|
||||
|
||||
title.placeRelative(
|
||||
x = icon.width,
|
||||
y = when (state) {
|
||||
y = promoOffset + when (state) {
|
||||
is TokenItemState.NoAddress,
|
||||
is TokenItemState.Unreachable,
|
||||
-> {
|
||||
if (state.subtitleState == null) {
|
||||
(layoutHeight - title.height).div(other = 2)
|
||||
(layoutHeight - promoOffset - title.height).div(other = 2)
|
||||
} else {
|
||||
verticalPadding
|
||||
}
|
||||
|
|
@ -314,8 +346,8 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier
|
|||
|
||||
fiatAmount?.placeRelative(
|
||||
x = layoutWidth - fiatAmount.width,
|
||||
y = when (state.subtitle2State) {
|
||||
null -> (layoutHeight - fiatAmount.height).div(other = 2)
|
||||
y = promoOffset + when (state.subtitle2State) {
|
||||
null -> (layoutHeight - promoOffset - fiatAmount.height).div(other = 2)
|
||||
else -> verticalPadding
|
||||
},
|
||||
)
|
||||
|
|
@ -335,7 +367,7 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier
|
|||
|
||||
nonFiatContent.placeRelative(
|
||||
x = layoutWidth - nonFiatContent.width,
|
||||
y = (layoutHeight - nonFiatContent.height).div(other = 2),
|
||||
y = promoOffset + (layoutHeight - promoOffset - nonFiatContent.height).div(other = 2),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -443,6 +475,7 @@ private fun calculateLayoutHeight(
|
|||
state: TokenItemState,
|
||||
minLayoutHeight: Int,
|
||||
layoutPadding: Int,
|
||||
promoOffset: Int,
|
||||
title: Placeable,
|
||||
fiatAmount: Placeable?,
|
||||
cryptoAmount: Placeable?,
|
||||
|
|
@ -468,7 +501,7 @@ private fun calculateLayoutHeight(
|
|||
}
|
||||
}
|
||||
|
||||
return max(firstColumnHeight, secondColumnHeight).coerceAtLeast(minLayoutHeight)
|
||||
return (promoOffset + max(firstColumnHeight, secondColumnHeight)).coerceAtLeast(promoOffset + minLayoutHeight)
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
|
|
@ -587,6 +620,11 @@ private class TokenItemStateProvider : CollectionPreviewParameterProvider<TokenI
|
|||
),
|
||||
onItemClick = {},
|
||||
onItemLongClick = {},
|
||||
promoBannerState = PromoBannerState.Content(
|
||||
title = TextReference.Str(value = "Trusted"),
|
||||
onPromoBannerClick = {},
|
||||
onCloseClick = {},
|
||||
),
|
||||
),
|
||||
TokenItemState.Loading(
|
||||
id = "Loading#1",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,110 @@
|
|||
package com.tangem.core.ui.components.token.internal
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.material3.ripple
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState.PromoBannerState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
||||
@Composable
|
||||
internal fun YieldSupplyPromoBanner(state: PromoBannerState, modifier: Modifier = Modifier) {
|
||||
when (state) {
|
||||
is PromoBannerState.Content -> YieldSupplyPromoBanner(state = state, modifier = modifier)
|
||||
is PromoBannerState.Empty -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun YieldSupplyPromoBanner(state: PromoBannerState.Content, modifier: Modifier = Modifier) {
|
||||
val bgColor = TangemTheme.colors.control.unchecked
|
||||
Column(modifier = modifier) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.background(color = bgColor, shape = TangemTheme.shapes.roundedCornersXMedium)
|
||||
.clickable(onClick = state.onPromoBannerClick)
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp)
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.ic_analytics_up_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
modifier = Modifier
|
||||
.padding(end = TangemTheme.dimens.spacing8)
|
||||
.size(TangemTheme.dimens.size16),
|
||||
)
|
||||
Text(
|
||||
text = state.title.resolveReference(),
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(end = TangemTheme.dimens.spacing8),
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_close_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.text.secondary,
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size16)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = ripple(bounded = false),
|
||||
onClick = { state.onCloseClick() },
|
||||
),
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_rectangle_bottom),
|
||||
contentDescription = null,
|
||||
tint = bgColor,
|
||||
modifier = Modifier
|
||||
.size(width = 12.dp, height = 8.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_YieldSupplyPromoBanner() {
|
||||
TangemThemePreview {
|
||||
YieldSupplyPromoBanner(
|
||||
state = PromoBannerState.Content(
|
||||
title = TextReference.Str(value = "Earn up to 5% APY"),
|
||||
onPromoBannerClick = {},
|
||||
onCloseClick = {},
|
||||
),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -34,6 +34,8 @@ sealed class TokenItemState {
|
|||
*/
|
||||
abstract val subtitle2State: Subtitle2State?
|
||||
|
||||
abstract val promoBannerState: PromoBannerState
|
||||
|
||||
/** Callback which will be called when an item is clicked */
|
||||
abstract val onItemClick: ((TokenItemState) -> Unit)?
|
||||
|
||||
|
|
@ -59,6 +61,7 @@ sealed class TokenItemState {
|
|||
) : TokenItemState() {
|
||||
override val fiatAmountState: FiatAmountState = FiatAmountState.Loading
|
||||
override val subtitle2State: Subtitle2State = Subtitle2State.Loading
|
||||
override val promoBannerState: PromoBannerState = PromoBannerState.Empty
|
||||
override val onItemClick: ((TokenItemState) -> Unit)? = null
|
||||
override val onItemLongClick: ((TokenItemState) -> Unit)? = null
|
||||
override val onApyLabelClick: ((TokenItemState) -> Unit)? = null
|
||||
|
|
@ -75,6 +78,7 @@ sealed class TokenItemState {
|
|||
override val subtitleState: SubtitleState = SubtitleState.Locked
|
||||
override val fiatAmountState: FiatAmountState = FiatAmountState.Locked
|
||||
override val subtitle2State: Subtitle2State = Subtitle2State.Locked
|
||||
override val promoBannerState: PromoBannerState = PromoBannerState.Empty
|
||||
override val onItemClick: ((TokenItemState) -> Unit)? = null
|
||||
override val onItemLongClick: ((TokenItemState) -> Unit)? = null
|
||||
override val onApyLabelClick: ((TokenItemState) -> Unit)? = null
|
||||
|
|
@ -99,6 +103,7 @@ sealed class TokenItemState {
|
|||
override val subtitleState: SubtitleState,
|
||||
override val fiatAmountState: FiatAmountState?,
|
||||
override val subtitle2State: Subtitle2State?,
|
||||
override val promoBannerState: PromoBannerState = PromoBannerState.Empty,
|
||||
override val onItemClick: ((TokenItemState) -> Unit)?,
|
||||
override val onItemLongClick: ((TokenItemState) -> Unit)?,
|
||||
override val onApyLabelClick: ((TokenItemState) -> Unit)? = null,
|
||||
|
|
@ -120,6 +125,7 @@ sealed class TokenItemState {
|
|||
) : TokenItemState() {
|
||||
override val subtitleState: SubtitleState? = null
|
||||
override val fiatAmountState: FiatAmountState? = null
|
||||
override val promoBannerState: PromoBannerState = PromoBannerState.Empty
|
||||
override val onItemClick: ((TokenItemState) -> Unit)? = null
|
||||
override val onItemLongClick: ((TokenItemState) -> Unit)? = null
|
||||
override val onApyLabelClick: ((TokenItemState) -> Unit)? = null
|
||||
|
|
@ -146,6 +152,7 @@ sealed class TokenItemState {
|
|||
) : TokenItemState() {
|
||||
override val fiatAmountState: FiatAmountState? = null
|
||||
override val subtitle2State: Subtitle2State? = null
|
||||
override val promoBannerState: PromoBannerState = PromoBannerState.Empty
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -168,6 +175,7 @@ sealed class TokenItemState {
|
|||
override val subtitle2State: Subtitle2State? = null
|
||||
override val onItemClick: ((TokenItemState) -> Unit)? = null
|
||||
override val onApyLabelClick: ((TokenItemState) -> Unit)? = null
|
||||
override val promoBannerState: PromoBannerState = PromoBannerState.Empty
|
||||
}
|
||||
|
||||
@Immutable
|
||||
|
|
@ -254,4 +262,15 @@ sealed class TokenItemState {
|
|||
|
||||
data object Locked : Subtitle2State()
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed class PromoBannerState {
|
||||
data class Content(
|
||||
val title: TextReference,
|
||||
val onPromoBannerClick: () -> Unit,
|
||||
val onCloseClick: () -> Unit,
|
||||
) : PromoBannerState()
|
||||
|
||||
data object Empty : PromoBannerState()
|
||||
}
|
||||
}
|
||||
|
|
@ -19,10 +19,12 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.constraintlayout.compose.ChainStyle
|
||||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
import androidx.constraintlayout.compose.Dimension
|
||||
import androidx.constraintlayout.compose.Visibility
|
||||
import androidx.constraintlayout.compose.atLeast
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.CircleShimmer
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
|
|
@ -94,7 +96,7 @@ fun Transaction(state: TransactionState, isBalanceHidden: Boolean, modifier: Mod
|
|||
bottom.linkTo(subtitleItem.top)
|
||||
start.linkTo(iconItem.end)
|
||||
end.linkTo(amountItem.start)
|
||||
width = Dimension.fillToConstraints
|
||||
width = Dimension.fillToConstraints.atLeast(50.dp)
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -122,7 +124,6 @@ fun Transaction(state: TransactionState, isBalanceHidden: Boolean, modifier: Mod
|
|||
visibility = state.isGoneIf { amount.isEmpty() }
|
||||
top.linkTo(parent.top)
|
||||
bottom.linkTo(timestampItem.top)
|
||||
start.linkTo(titleItem.end)
|
||||
end.linkTo(parent.end)
|
||||
width = Dimension.fillToConstraints
|
||||
},
|
||||
|
|
@ -436,7 +437,7 @@ private class TransactionItemStateProvider : CollectionPreviewParameterProvider<
|
|||
),
|
||||
TransactionState.Content(
|
||||
txHash = UUID.randomUUID().toString(),
|
||||
amount = "0.625 USDT",
|
||||
amount = "0.62521313 USDT",
|
||||
time = "€0.50",
|
||||
status = Status.Confirmed,
|
||||
direction = Direction.OUTGOING,
|
||||
|
|
|
|||
|
|
@ -7,4 +7,5 @@ object TokenElementsTestTags {
|
|||
const val TOKEN_FIAT_AMOUNT = "TOKEN_FIAT_AMOUNT"
|
||||
const val TOKEN_CRYPTO_AMOUNT = "TOKEN_CRYPTO_AMOUNT"
|
||||
const val TOKEN_NON_FIAT_BLOCK = "TOKEN_NON_FIAT_BLOCK"
|
||||
const val TOKEN_YIELD_PROMO_BANNER = "TOKEN_YIELD_PROMO_BANNER"
|
||||
}
|
||||
9
core/ui/src/main/res/drawable/ic_connect_24.xml
Normal file
9
core/ui/src/main/res/drawable/ic_connect_24.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M11.799,8.899C12.047,8.469 12.597,8.322 13.028,8.57C14.276,9.291 15.268,10.385 15.863,11.698C16.458,13.011 16.627,14.478 16.345,15.892C16.064,17.306 15.347,18.597 14.295,19.582C13.242,20.567 11.908,21.199 10.478,21.387C9.049,21.575 7.596,21.31 6.325,20.631C5.054,19.951 4.027,18.891 3.389,17.598C2.752,16.305 2.535,14.844 2.77,13.422C3.005,12 3.68,10.686 4.699,9.667C5.05,9.316 5.62,9.316 5.971,9.667C6.323,10.019 6.323,10.588 5.971,10.939C5.218,11.693 4.719,12.664 4.546,13.715C4.372,14.766 4.532,15.845 5.004,16.801C5.475,17.756 6.234,18.541 7.173,19.043C8.113,19.545 9.187,19.741 10.244,19.602C11.3,19.462 12.286,18.996 13.064,18.268C13.842,17.539 14.372,16.585 14.58,15.54C14.788,14.495 14.663,13.411 14.223,12.441C13.783,11.47 13.05,10.662 12.128,10.129C11.697,9.88 11.55,9.33 11.799,8.899ZM13.766,2.615C15.196,2.427 16.648,2.692 17.92,3.371C19.191,4.051 20.218,5.111 20.856,6.404C21.494,7.697 21.71,9.158 21.475,10.58C21.24,12.002 20.566,13.316 19.546,14.335C19.195,14.686 18.625,14.686 18.273,14.335C17.922,13.984 17.922,13.414 18.273,13.063C19.027,12.309 19.525,11.338 19.699,10.287C19.872,9.236 19.712,8.156 19.241,7.2C18.77,6.245 18.011,5.46 17.071,4.958C16.131,4.456 15.058,4.26 14.002,4.399C12.945,4.539 11.959,5.005 11.181,5.733C10.404,6.462 9.874,7.416 9.666,8.461C9.458,9.506 9.582,10.59 10.022,11.561C10.462,12.531 11.194,13.34 12.117,13.873C12.547,14.122 12.695,14.672 12.447,15.103C12.198,15.533 11.648,15.68 11.217,15.432C9.969,14.711 8.977,13.617 8.382,12.304C7.788,10.991 7.619,9.524 7.9,8.11C8.181,6.696 8.899,5.406 9.951,4.42C11.003,3.435 12.337,2.804 13.766,2.615Z"
|
||||
android:fillColor="#1E1E1E"/>
|
||||
</vector>
|
||||
15
core/ui/src/main/res/drawable/ic_disconnect_24.xml
Normal file
15
core/ui/src/main/res/drawable/ic_disconnect_24.xml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M5.91,11.001C5.191,11.745 4.714,12.691 4.546,13.714C4.372,14.764 4.533,15.844 5.004,16.799C5.475,17.755 6.234,18.539 7.173,19.042C8.113,19.544 9.188,19.739 10.244,19.6C11.3,19.461 12.286,18.994 13.064,18.266C13.083,18.249 13.1,18.229 13.119,18.212L14.392,19.485C14.36,19.517 14.328,19.549 14.295,19.581C13.242,20.566 11.907,21.197 10.478,21.385C9.049,21.573 7.596,21.309 6.325,20.629C5.054,19.95 4.027,18.889 3.389,17.596C2.752,16.303 2.535,14.843 2.77,13.42C3,12.027 3.652,10.738 4.637,9.728L5.91,11.001Z"
|
||||
android:fillColor="#1E1E1E"/>
|
||||
<path
|
||||
android:pathData="M4.164,4.164C4.515,3.812 5.086,3.812 5.437,4.164L19.837,18.564C20.188,18.916 20.188,19.485 19.837,19.837C19.485,20.188 18.916,20.188 18.564,19.837L4.164,5.437C3.812,5.086 3.812,4.515 4.164,4.164Z"
|
||||
android:fillColor="#1E1E1E"/>
|
||||
<path
|
||||
android:pathData="M13.766,2.614C15.195,2.426 16.648,2.69 17.92,3.37C19.191,4.049 20.219,5.11 20.856,6.403C21.493,7.696 21.71,9.157 21.475,10.579C21.24,12.001 20.566,13.314 19.546,14.334C19.526,14.354 19.503,14.373 19.481,14.391L18.215,13.125C18.233,13.103 18.253,13.082 18.273,13.061C19.027,12.308 19.525,11.337 19.699,10.286C19.872,9.235 19.712,8.154 19.241,7.199C18.77,6.244 18.01,5.459 17.071,4.957C16.131,4.455 15.058,4.259 14.002,4.398C12.945,4.537 11.959,5.004 11.181,5.732C11.12,5.79 11.059,5.848 11.001,5.909L9.728,4.636C9.801,4.562 9.875,4.49 9.951,4.419C11.003,3.433 12.337,2.802 13.766,2.614Z"
|
||||
android:fillColor="#1E1E1E"/>
|
||||
</vector>
|
||||
14
core/ui/src/main/res/drawable/ic_gear_24.xml
Normal file
14
core/ui/src/main/res/drawable/ic_gear_24.xml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M12,7.6C14.43,7.6 16.4,9.57 16.4,12C16.4,14.43 14.43,16.4 12,16.4C9.57,16.4 7.6,14.43 7.6,12C7.6,9.57 9.57,7.6 12,7.6ZM12,9.4C10.564,9.4 9.4,10.564 9.4,12C9.4,13.436 10.564,14.6 12,14.6C13.436,14.6 14.6,13.436 14.6,12C14.6,10.564 13.436,9.4 12,9.4Z"
|
||||
android:fillColor="#1E1E1E"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M13.938,2.1C14.721,2.1 15.374,2.099 15.911,2.151C16.468,2.206 16.972,2.324 17.451,2.6C17.931,2.876 18.285,3.252 18.611,3.706C18.926,4.144 19.253,4.708 19.646,5.385L21.569,8.7C21.964,9.38 22.292,9.946 22.517,10.439C22.749,10.949 22.9,11.445 22.9,12C22.9,12.555 22.749,13.051 22.517,13.561C22.292,14.054 21.964,14.62 21.569,15.3L19.646,18.615C19.253,19.292 18.926,19.856 18.611,20.294C18.285,20.748 17.931,21.125 17.451,21.4C16.972,21.676 16.468,21.794 15.911,21.849C15.374,21.901 14.721,21.9 13.938,21.9H10.063C9.279,21.9 8.626,21.901 8.089,21.849C7.532,21.794 7.028,21.676 6.549,21.4C6.069,21.125 5.715,20.748 5.389,20.294C5.074,19.856 4.747,19.292 4.354,18.615L2.431,15.3C2.036,14.62 1.708,14.054 1.483,13.561C1.251,13.051 1.1,12.555 1.1,12C1.1,11.445 1.251,10.949 1.483,10.439C1.708,9.946 2.036,9.38 2.431,8.7L4.354,5.385C4.747,4.708 5.074,4.144 5.389,3.706C5.715,3.252 6.069,2.876 6.549,2.6C7.028,2.324 7.532,2.206 8.089,2.151C8.626,2.099 9.279,2.1 10.063,2.1H13.938ZM10.063,3.9C9.244,3.9 8.694,3.901 8.265,3.943C7.855,3.984 7.628,4.056 7.447,4.16C7.267,4.264 7.089,4.424 6.85,4.757C6.598,5.106 6.321,5.581 5.911,6.288L3.988,9.604C3.576,10.314 3.3,10.79 3.121,11.184C2.95,11.559 2.9,11.792 2.9,12C2.9,12.208 2.95,12.441 3.121,12.816C3.3,13.21 3.576,13.686 3.988,14.396L5.911,17.712C6.321,18.419 6.598,18.894 6.85,19.243C7.089,19.576 7.267,19.736 7.447,19.84C7.628,19.944 7.855,20.016 8.265,20.057C8.694,20.099 9.244,20.1 10.063,20.1H13.938C14.756,20.1 15.306,20.099 15.735,20.057C16.145,20.016 16.372,19.944 16.553,19.84C16.733,19.736 16.911,19.576 17.15,19.243C17.402,18.894 17.678,18.419 18.089,17.712L20.012,14.396C20.424,13.686 20.7,13.21 20.879,12.816C21.05,12.441 21.1,12.208 21.1,12C21.1,11.792 21.05,11.559 20.879,11.184C20.7,10.79 20.424,10.314 20.012,9.604L18.089,6.288C17.678,5.581 17.402,5.106 17.15,4.757C16.911,4.424 16.733,4.264 16.553,4.16C16.372,4.056 16.145,3.984 15.735,3.943C15.306,3.901 14.756,3.9 13.938,3.9H10.063Z"
|
||||
android:fillColor="#1E1E1E"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
9
core/ui/src/main/res/drawable/ic_rectangle_bottom.xml
Normal file
9
core/ui/src/main/res/drawable/ic_rectangle_bottom.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="12dp"
|
||||
android:height="8dp"
|
||||
android:viewportWidth="12"
|
||||
android:viewportHeight="8">
|
||||
<path
|
||||
android:pathData="M0.198,1.178C0.034,0.976 -0.028,0.695 0.012,0.433C0.058,0.135 0.373,0 0.675,0H6.183H11.325C11.627,0 11.943,0.135 11.988,0.433C12.028,0.695 11.966,0.976 11.802,1.178L6.477,7.756C6.214,8.081 5.786,8.081 5.523,7.756L0.198,1.178Z"
|
||||
android:fillColor="#EBEBEB"/>
|
||||
</vector>
|
||||
|
|
@ -87,4 +87,27 @@ inline fun <T1, T2, T3, T4, T5, T6, R> combine6(
|
|||
arr[4] as T5,
|
||||
arr[5] as T6,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList", "MagicNumber")
|
||||
inline fun <T1, T2, T3, T4, T5, T6, T7, R> combine7(
|
||||
flow1: Flow<T1>,
|
||||
flow2: Flow<T2>,
|
||||
flow3: Flow<T3>,
|
||||
flow4: Flow<T4>,
|
||||
flow5: Flow<T5>,
|
||||
flow6: Flow<T6>,
|
||||
flow7: Flow<T7>,
|
||||
crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R,
|
||||
): Flow<R> = combine(flow1, flow2, flow3, flow4, flow5, flow6, flow7) { arr ->
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
transform(
|
||||
arr[0] as T1,
|
||||
arr[1] as T2,
|
||||
arr[2] as T3,
|
||||
arr[3] as T4,
|
||||
arr[4] as T5,
|
||||
arr[5] as T6,
|
||||
arr[6] as T7,
|
||||
)
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import com.tangem.datasource.local.preferences.PreferencesKeys.getShouldShowStor
|
|||
import com.tangem.datasource.local.preferences.utils.get
|
||||
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
|
||||
import com.tangem.datasource.local.preferences.utils.store
|
||||
import com.tangem.datasource.local.promo.PromoBannerStore
|
||||
import com.tangem.datasource.local.promo.PromoStoriesStore
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.promo.PromoRepository
|
||||
|
|
@ -27,6 +28,7 @@ internal class DefaultPromoRepository(
|
|||
private val tangemApi: TangemTechApi,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val promoStoriesStore: PromoStoriesStore,
|
||||
private val promoBannerStore: PromoBannerStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val referralRepository: ReferralRepository,
|
||||
) : PromoRepository {
|
||||
|
|
@ -101,6 +103,18 @@ internal class DefaultPromoRepository(
|
|||
)
|
||||
}
|
||||
|
||||
override suspend fun isMoonpayPromoActive(): Boolean {
|
||||
val banner = runCatching(dispatchers.io) {
|
||||
val response = promoBannerStore.getSyncOrNull(MOONPAY_NAME) ?: run {
|
||||
val apiResponse = tangemApi.getPromoBanner(MOONPAY_NAME).getOrThrow()
|
||||
promoBannerStore.store(MOONPAY_NAME, apiResponse)
|
||||
apiResponse
|
||||
}
|
||||
promoBannerConverter.convert(response)
|
||||
}.getOrNull()
|
||||
return banner?.isActive == true
|
||||
}
|
||||
|
||||
override fun getStoryById(id: String): Flow<StoryContent?> = isReadyToShowStories(id).mapLatest {
|
||||
getStoryByIdSync(id = id, refresh = false)
|
||||
}
|
||||
|
|
@ -180,6 +194,7 @@ internal class DefaultPromoRepository(
|
|||
const val VISA_NAME = "visa-waitlist"
|
||||
const val BLACK_FRIDAY_NAME = "black-friday"
|
||||
const val ONE_PLUS_ONE_NAME = "one-plus-one"
|
||||
const val MOONPAY_NAME = "moonpay"
|
||||
const val STORIES_LOAD_DELAY = 1000L
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.data.promo.di
|
|||
import com.tangem.data.promo.DefaultPromoRepository
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.promo.PromoBannerStore
|
||||
import com.tangem.datasource.local.promo.PromoStoriesStore
|
||||
import com.tangem.domain.promo.PromoRepository
|
||||
import com.tangem.feature.referral.domain.ReferralRepository
|
||||
|
|
@ -23,6 +24,7 @@ internal object PromoDataModule {
|
|||
tangemTechApi: TangemTechApi,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
promoStoriesStore: PromoStoriesStore,
|
||||
promoBannerStore: PromoBannerStore,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
referralRepository: ReferralRepository,
|
||||
): PromoRepository {
|
||||
|
|
@ -32,6 +34,7 @@ internal object PromoDataModule {
|
|||
promoStoriesStore = promoStoriesStore,
|
||||
dispatchers = dispatchers,
|
||||
referralRepository = referralRepository,
|
||||
promoBannerStore = promoBannerStore,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -272,7 +272,7 @@ internal class DefaultSwapRepositoryV2 @Inject constructor(
|
|||
override suspend fun swapTransactionSent(
|
||||
userWallet: UserWallet,
|
||||
fromCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
toAddress: String,
|
||||
payInAddress: String,
|
||||
txId: String,
|
||||
txHash: String,
|
||||
txExtraId: String?,
|
||||
|
|
@ -288,7 +288,7 @@ internal class DefaultSwapRepositoryV2 @Inject constructor(
|
|||
txId = txId,
|
||||
fromNetwork = fromCryptoCurrencyStatus.currency.network.backendId,
|
||||
fromAddress = fromCryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value.orEmpty(),
|
||||
payinAddress = toAddress,
|
||||
payinAddress = payInAddress,
|
||||
payinExtraId = txExtraId,
|
||||
txHash = txHash,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -139,13 +139,13 @@ internal class DefaultOnboardingRepository @Inject constructor(
|
|||
response: CustomerMeResponse.Result?,
|
||||
): CustomerInfo {
|
||||
val card = response?.card
|
||||
val balance = response?.balance
|
||||
val fiatBalance = response?.balance?.fiat
|
||||
val paymentAccount = response?.paymentAccount
|
||||
val cardInfo = if (paymentAccount != null && card != null && balance != null) {
|
||||
val cardInfo = if (paymentAccount != null && card != null && fiatBalance != null) {
|
||||
CardInfo(
|
||||
lastFourDigits = card.cardNumberEnd,
|
||||
balance = balance.fiat.availableBalance,
|
||||
currencyCode = balance.fiat.currency,
|
||||
balance = fiatBalance.availableBalance,
|
||||
currencyCode = fiatBalance.currency,
|
||||
customerWalletAddress = paymentAccount.customerWalletAddress,
|
||||
depositAddress = response.depositAddress,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -52,20 +52,29 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor(
|
|||
private val storePollingMutex = Mutex()
|
||||
|
||||
override suspend fun getCardBalance(userWalletId: UserWalletId): Either<UniversalError, TangemPayCardBalance> {
|
||||
return requestHelper.runWithErrorLogs(TAG) {
|
||||
val result = requestHelper.request(userWalletId) { authHeader ->
|
||||
tangemPayApi.getCardBalance(authHeader)
|
||||
}.result ?: error("Cannot get card balance")
|
||||
TangemPayCardBalance(
|
||||
fiatBalance = result.fiat.availableBalance,
|
||||
currencyCode = result.fiat.currency,
|
||||
cryptoBalance = result.crypto.balance,
|
||||
availableForWithdrawal = result.availableForWithdrawal.amount,
|
||||
chainId = result.crypto.chainId,
|
||||
depositAddress = result.crypto.depositAddress,
|
||||
contractAddress = result.crypto.tokenContractAddress,
|
||||
)
|
||||
}
|
||||
return catch(
|
||||
block = {
|
||||
val response = requestHelper.performRequest(userWalletId) { authHeader ->
|
||||
tangemPayApi.getCardBalance(authHeader)
|
||||
}.getOrNull()
|
||||
|
||||
val fiatBalance = requireNotNull(response?.result?.fiat) { "Cannot get card balance fiat" }
|
||||
val cryptoBalance = requireNotNull(response.result?.crypto) { "Cannot get card balance crypto" }
|
||||
val withdrawalAmount = requireNotNull(response.result?.availableForWithdrawal) {
|
||||
"Cannot get card balance availableForWithdrawal"
|
||||
}
|
||||
TangemPayCardBalance(
|
||||
fiatBalance = fiatBalance.availableBalance,
|
||||
currencyCode = fiatBalance.currency,
|
||||
cryptoBalance = cryptoBalance.balance,
|
||||
availableForWithdrawal = withdrawalAmount.amount,
|
||||
chainId = cryptoBalance.chainId,
|
||||
depositAddress = cryptoBalance.depositAddress,
|
||||
contractAddress = cryptoBalance.tokenContractAddress,
|
||||
).right()
|
||||
},
|
||||
catch = ::catchException,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun revealCardDetails(userWalletId: UserWalletId): Either<UniversalError, TangemPayCardDetails> {
|
||||
|
|
@ -100,7 +109,7 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor(
|
|||
expirationMonth = result.expirationMonth,
|
||||
).right()
|
||||
},
|
||||
catch = { errorConverter.convert(it).left() },
|
||||
catch = ::catchException,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -285,6 +294,11 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun <T> catchException(throwable: Throwable): Either<UniversalError, T> {
|
||||
Timber.tag(TAG).e(throwable)
|
||||
return errorConverter.convert(throwable).left()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val MAX_POLLING_RETRIES = 3
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,10 +159,11 @@ internal class TangemPayRequestPerformer @Inject constructor(
|
|||
refreshToken = response.refreshToken,
|
||||
refreshExpiresAt = response.refreshExpiresAt,
|
||||
)
|
||||
}.mapLeft { error ->
|
||||
Timber.tag(TAG).e("Can not refresh auth tokens: $error")
|
||||
if (error is VisaApiError.ServerUnavailable) error else VisaApiError.RefreshTokenExpired
|
||||
}.onRight { tokens ->
|
||||
tangemPayStorage.storeAuthTokens(customerWalletAddress = customerWalletAddress, tokens = tokens)
|
||||
}.onLeft {
|
||||
Timber.tag(TAG).e("Can not refresh auth tokens: $it")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ internal class TangemPayErrorConverter @Inject constructor(
|
|||
|
||||
override fun convert(value: Throwable): VisaApiError {
|
||||
return if (value is ApiResponseError.HttpException) {
|
||||
if (value.isServerError()) return VisaApiError.ServerUnavailable
|
||||
if (value.code == ApiResponseError.HttpException.Code.NOT_FOUND) return VisaApiError.NotPaeraCustomer
|
||||
if (value.code == ApiResponseError.HttpException.Code.UNAUTHORIZED) return VisaApiError.RefreshTokenExpired
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.data.pay.util
|
||||
|
||||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
|
|
@ -32,7 +33,7 @@ class TangemPayWalletsManager @Inject constructor(
|
|||
|
||||
private fun findColdWallet(userWallets: List<UserWallet>?): UserWallet.Cold {
|
||||
return userWallets?.find {
|
||||
it is UserWallet.Cold && it.isMultiCurrency
|
||||
it is UserWallet.Cold && it.scanResponse.card.firmwareVersion >= FirmwareVersion.HDWalletAvailable
|
||||
} as? UserWallet.Cold ?: error("Cannot find cold user wallet")
|
||||
}
|
||||
}
|
||||
|
|
@ -148,14 +148,12 @@ internal object WalletConnectDataModule {
|
|||
@SdkMoshi moshi: Moshi,
|
||||
sessionsManager: WcSessionsManager,
|
||||
factories: WcEthNetwork.Factories,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
wcNetworksConverter: WcNetworksConverter,
|
||||
): WcEthNetwork = WcEthNetwork(
|
||||
moshi = moshi,
|
||||
networksConverter = wcNetworksConverter,
|
||||
sessionsManager = sessionsManager,
|
||||
factories = factories,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
)
|
||||
|
||||
@Provides
|
||||
|
|
@ -165,24 +163,20 @@ internal object WalletConnectDataModule {
|
|||
wcNetworksConverter: WcNetworksConverter,
|
||||
sessionsManager: WcSessionsManager,
|
||||
factories: WcSolanaNetwork.Factories,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
): WcSolanaNetwork = WcSolanaNetwork(
|
||||
moshi = moshi,
|
||||
sessionsManager = sessionsManager,
|
||||
factories = factories,
|
||||
networksConverter = wcNetworksConverter,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun caipNamespaceDelegate(
|
||||
namespaceConverters: Set<@JvmSuppressWildcards WcNamespaceConverter>,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
wcNetworksConverter: WcNetworksConverter,
|
||||
): CaipNamespaceDelegate = CaipNamespaceDelegate(
|
||||
namespaceConverters = namespaceConverters,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
wcNetworksConverter = wcNetworksConverter,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import com.tangem.domain.walletconnect.model.*
|
|||
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
|
||||
import com.tangem.domain.walletconnect.repository.WcSessionsManager
|
||||
import com.tangem.domain.walletconnect.usecase.method.WcMethodUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import jakarta.inject.Inject
|
||||
|
||||
internal class WcEthNetwork(
|
||||
|
|
@ -26,7 +25,6 @@ internal class WcEthNetwork(
|
|||
private val sessionsManager: WcSessionsManager,
|
||||
private val factories: Factories,
|
||||
private val networksConverter: WcNetworksConverter,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
) : WcRequestToUseCaseConverter {
|
||||
|
||||
override fun toWcMethodName(request: WcSdkSessionRequest): WcEthMethodName? {
|
||||
|
|
@ -57,7 +55,7 @@ internal class WcEthNetwork(
|
|||
is WcEthMethod.SwitchEthereumChain,
|
||||
->
|
||||
anyExistNetwork()
|
||||
?.let { network -> walletManagersFacade.getDefaultAddress(wallet.walletId, network).orEmpty() }
|
||||
?.let { network -> networksConverter.getAddressForWC(wallet.walletId, network).orEmpty() }
|
||||
.orEmpty()
|
||||
}
|
||||
val walletNetwork = when (method) {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import com.tangem.domain.walletconnect.model.WcSolanaMethodName
|
|||
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
|
||||
import com.tangem.domain.walletconnect.repository.WcSessionsManager
|
||||
import com.tangem.domain.walletconnect.usecase.method.WcMethodUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import jakarta.inject.Inject
|
||||
|
||||
internal class WcSolanaNetwork(
|
||||
|
|
@ -30,7 +29,6 @@ internal class WcSolanaNetwork(
|
|||
private val sessionsManager: WcSessionsManager,
|
||||
private val factories: Factories,
|
||||
private val networksConverter: WcNetworksConverter,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
) : WcRequestToUseCaseConverter {
|
||||
|
||||
override fun toWcMethodName(request: WcSdkSessionRequest): WcSolanaMethodName? {
|
||||
|
|
@ -52,7 +50,7 @@ internal class WcSolanaNetwork(
|
|||
val chainId = request.chainId.orEmpty()
|
||||
suspend fun anyExistNetwork() = networksConverter.mainOrAnyWalletNetworkForRequest(chainId, wallet)
|
||||
suspend fun anyAddress() = anyExistNetwork()
|
||||
?.let { network -> walletManagersFacade.getDefaultAddress(wallet.walletId, network).orEmpty() }
|
||||
?.let { network -> networksConverter.getAddressForWC(wallet.walletId, network).orEmpty() }
|
||||
.orEmpty()
|
||||
|
||||
val accountAddress = when (method) {
|
||||
|
|
|
|||
|
|
@ -9,11 +9,9 @@ import com.tangem.data.walletconnect.utils.WcNetworksConverter
|
|||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.walletconnect.model.WcSessionApprove
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
|
||||
internal class CaipNamespaceDelegate(
|
||||
private val namespaceConverters: Set<WcNamespaceConverter>,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val wcNetworksConverter: WcNetworksConverter,
|
||||
) {
|
||||
|
||||
|
|
@ -36,7 +34,7 @@ internal class CaipNamespaceDelegate(
|
|||
}
|
||||
|
||||
suspend fun createCAIP10(userWalletId: UserWalletId, network: Network): CAIP10? {
|
||||
val address = walletManagersFacade.getDefaultAddress(userWalletId, network)
|
||||
val address = wcNetworksConverter.getAddressForWC(userWalletId, network)
|
||||
val chainId = allWcNetworks
|
||||
.find { (wcNetwork, _) -> network.rawId == wcNetwork.rawId }
|
||||
?.second
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
package com.tangem.data.walletconnect.utils
|
||||
|
||||
import com.reown.walletkit.client.Wallet
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.data.common.currency.isCustomCoin
|
||||
import com.tangem.data.walletconnect.model.CAIP10
|
||||
import com.tangem.domain.account.producer.SingleAccountProducer
|
||||
|
|
@ -44,7 +47,7 @@ internal class WcNetworksConverter @Inject constructor(
|
|||
val allCoinNetwork = filterWalletNetworkForRequest(request.chainId.orEmpty(), session.wallet)
|
||||
|
||||
val requestNetwork = allCoinNetwork.find { network ->
|
||||
val address = walletManagersFacade.getDefaultAddress(wallet.walletId, network)
|
||||
val address = getAddressForWC(wallet.walletId, network)
|
||||
requestAddress.lowercase() == address?.lowercase()
|
||||
}
|
||||
return requestNetwork
|
||||
|
|
@ -60,7 +63,18 @@ internal class WcNetworksConverter @Inject constructor(
|
|||
|
||||
suspend fun allAddressForChain(rawChainId: String, wallet: UserWallet): List<String> {
|
||||
return filterWalletNetworkForRequest(rawChainId, wallet)
|
||||
.mapNotNull { walletManagersFacade.getDefaultAddress(wallet.walletId, it)?.lowercase() }
|
||||
.mapNotNull { getAddressForWC(wallet.walletId, it)?.lowercase() }
|
||||
}
|
||||
|
||||
suspend fun getAddressForWC(userWalletId: UserWalletId, network: Network): String? {
|
||||
return when (network.toBlockchain()) {
|
||||
Blockchain.XDC,
|
||||
Blockchain.XDCTestnet,
|
||||
-> walletManagersFacade.getAddresses(userWalletId, network)
|
||||
.find { address -> address.type == AddressType.Legacy }
|
||||
?.value
|
||||
else -> walletManagersFacade.getDefaultAddress(userWalletId, network)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -94,8 +108,8 @@ internal class WcNetworksConverter @Inject constructor(
|
|||
// find all derivation
|
||||
.filter { it.rawId == blockchain.id }
|
||||
// find equal address
|
||||
.firstOrNull {
|
||||
val walletAddress = walletManagersFacade.getDefaultAddress(wallet.walletId, it)
|
||||
.firstOrNull { network ->
|
||||
val walletAddress = getAddressForWC(wallet.walletId, network)
|
||||
walletAddress?.lowercase() == caip10.accountAddress.lowercase()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -276,8 +276,10 @@ internal class DefaultWalletManagersFacade @Inject constructor(
|
|||
is Result.Success -> PaginationWrapper(
|
||||
currentPage = sdkPageConverter.convert(page),
|
||||
nextPage = sdkPageConverter.convert(itemsResult.data.nextPage),
|
||||
items = SdkTransactionHistoryItemConverter(smartContractMethods = readSmartContractMethods())
|
||||
.convertList(itemsResult.data.items),
|
||||
items = SdkTransactionHistoryItemConverter(
|
||||
smartContractMethods = readSmartContractMethods(),
|
||||
yieldSupplyAddresses = YIELD_SUPPLY_ADDRESSES,
|
||||
).convertList(itemsResult.data.items),
|
||||
)
|
||||
is Result.Failure -> error(itemsResult.error.message ?: itemsResult.error.customMessage)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,13 @@ import com.tangem.blockchain.transactionhistory.models.TransactionHistoryItem as
|
|||
|
||||
internal class SdkTransactionHistoryItemConverter(
|
||||
smartContractMethods: Map<String, SmartContractMethod>,
|
||||
yieldSupplyAddresses: Set<String>,
|
||||
) : Converter<SdkTransactionHistoryItem, TxInfo> {
|
||||
|
||||
private val typeConverter by lazy { SdkTransactionTypeConverter(smartContractMethods) }
|
||||
private val typeConverter by lazy { SdkTransactionTypeConverter(
|
||||
smartContractMethods = smartContractMethods,
|
||||
yieldSupplyAddresses = yieldSupplyAddresses,
|
||||
) }
|
||||
|
||||
override fun convert(value: SdkTransactionHistoryItem): TxInfo = TxInfo(
|
||||
txHash = value.txHash,
|
||||
|
|
@ -24,7 +28,7 @@ internal class SdkTransactionHistoryItemConverter(
|
|||
SdkTransactionHistoryItem.TransactionStatus.Failed -> TxInfo.TransactionStatus.Failed
|
||||
SdkTransactionHistoryItem.TransactionStatus.Unconfirmed -> TxInfo.TransactionStatus.Unconfirmed
|
||||
},
|
||||
type = typeConverter.convert(value.type),
|
||||
type = typeConverter.convert(value),
|
||||
amount = requireNotNull(value.amount.value) { "Transaction amount value must not be null" },
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,40 @@
|
|||
package com.tangem.data.walletmanager.utils
|
||||
|
||||
import com.tangem.blockchain.transactionhistory.models.TransactionHistoryItem
|
||||
import com.tangem.blockchain.transactionhistory.models.TransactionHistoryItem.TransactionType
|
||||
import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyEnterCallData
|
||||
import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyExitCallData
|
||||
import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyInitTokenCallData
|
||||
import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyReactivateTokenCallData
|
||||
import com.tangem.domain.models.network.TxInfo
|
||||
import com.tangem.domain.walletmanager.model.SmartContractMethod
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class SdkTransactionTypeConverter(
|
||||
private val smartContractMethods: Map<String, SmartContractMethod>,
|
||||
) : Converter<TransactionType, TxInfo.TransactionType> {
|
||||
private val yieldSupplyAddresses: Set<String>,
|
||||
) : Converter<TransactionHistoryItem, TxInfo.TransactionType> {
|
||||
|
||||
override fun convert(value: TransactionType): TxInfo.TransactionType {
|
||||
return when (value) {
|
||||
override fun convert(value: TransactionHistoryItem): TxInfo.TransactionType {
|
||||
val (type, destination) = value.type to value.destinationType
|
||||
val source = value.sourceType
|
||||
|
||||
return when (type) {
|
||||
is TransactionType.ContractMethod -> {
|
||||
getTransactionType(methodName = smartContractMethods[value.id]?.name)
|
||||
getTransactionType(
|
||||
methodName = smartContractMethods[type.id]?.name,
|
||||
callData = type.callData,
|
||||
destination = destination,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
is TransactionType.ContractMethodName -> {
|
||||
getTransactionType(methodName = value.name)
|
||||
getTransactionType(
|
||||
methodName = type.name,
|
||||
callData = type.callData,
|
||||
destination = destination,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
is TransactionType.Transfer -> {
|
||||
TxInfo.TransactionType.Transfer
|
||||
|
|
@ -27,7 +46,7 @@ internal class SdkTransactionTypeConverter(
|
|||
TxInfo.TransactionType.Staking.Unstake
|
||||
}
|
||||
is TransactionType.TronStakingTransactionType.VoteWitnessContract -> {
|
||||
TxInfo.TransactionType.Staking.Vote(value.validatorAddress)
|
||||
TxInfo.TransactionType.Staking.Vote(type.validatorAddress)
|
||||
}
|
||||
is TransactionType.TronStakingTransactionType.WithdrawBalanceContract -> {
|
||||
TxInfo.TransactionType.Staking.ClaimRewards
|
||||
|
|
@ -38,7 +57,13 @@ internal class SdkTransactionTypeConverter(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getTransactionType(methodName: String?): TxInfo.TransactionType {
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
private fun getTransactionType(
|
||||
methodName: String?,
|
||||
callData: String?,
|
||||
destination: TransactionHistoryItem.DestinationType,
|
||||
source: TransactionHistoryItem.SourceType,
|
||||
): TxInfo.TransactionType {
|
||||
return when (methodName) {
|
||||
"transfer" -> TxInfo.TransactionType.Transfer
|
||||
"approve" -> TxInfo.TransactionType.Approve
|
||||
|
|
@ -59,11 +84,47 @@ internal class SdkTransactionTypeConverter(
|
|||
"withdrawRewardsPOL",
|
||||
-> TxInfo.TransactionType.Staking.ClaimRewards
|
||||
"redelegate" -> TxInfo.TransactionType.Staking.Restake
|
||||
"supplyEnter" -> TxInfo.TransactionType.YieldSupply.Enter
|
||||
"supplyExit" -> TxInfo.TransactionType.YieldSupply.Exit
|
||||
"yieldSend" -> {
|
||||
val sourceAddresses = when (source) {
|
||||
is TransactionHistoryItem.SourceType.Multiple -> source.addresses
|
||||
is TransactionHistoryItem.SourceType.Single -> listOf(source.address)
|
||||
}.map {
|
||||
it.lowercase()
|
||||
}.toSet()
|
||||
|
||||
val isYieldSupplyWithdraw =
|
||||
yieldSupplyAddresses.intersect(sourceAddresses).isNotEmpty()
|
||||
|
||||
TxInfo.TransactionType.YieldSupply.Send(
|
||||
isYieldSupplyWithdraw = isYieldSupplyWithdraw,
|
||||
)
|
||||
}
|
||||
"enterProtocolByOwner" -> callData?.let { data ->
|
||||
TxInfo.TransactionType.YieldSupply.Enter(
|
||||
EthereumYieldSupplyEnterCallData.decode(data)?.tokenContractAddress.orEmpty(),
|
||||
)
|
||||
}
|
||||
"withdrawAndDeactivate" -> callData?.let { data ->
|
||||
TxInfo.TransactionType.YieldSupply.Exit(
|
||||
EthereumYieldSupplyExitCallData.decode(data)?.tokenContractAddress.orEmpty(),
|
||||
)
|
||||
}
|
||||
"deployYieldModule" -> TxInfo.TransactionType.YieldSupply.DeployContract(
|
||||
(destination as? TransactionHistoryItem.DestinationType.Single)?.addressType?.address.orEmpty(),
|
||||
)
|
||||
"initYieldToken" -> callData?.let { data ->
|
||||
TxInfo.TransactionType.YieldSupply.InitializeToken(
|
||||
EthereumYieldSupplyInitTokenCallData.decode(data)?.tokenContractAddress.orEmpty(),
|
||||
)
|
||||
}
|
||||
"reactivateToken" -> callData?.let { data ->
|
||||
TxInfo.TransactionType.YieldSupply.ReactivateToken(
|
||||
EthereumYieldSupplyReactivateTokenCallData.decode(data)?.tokenContractAddress.orEmpty(),
|
||||
)
|
||||
}
|
||||
"supplyTopUp" -> TxInfo.TransactionType.YieldSupply.Topup
|
||||
null -> TxInfo.TransactionType.UnknownOperation
|
||||
else -> TxInfo.TransactionType.Operation(name = methodName.replaceFirstChar { it.titlecase() })
|
||||
}
|
||||
} ?: TxInfo.TransactionType.Operation(name = methodName?.replaceFirstChar { it.titlecase() }.orEmpty())
|
||||
}
|
||||
}
|
||||
|
|
@ -45,7 +45,7 @@ internal class TransactionDataToTxHistoryItemConverter(
|
|||
TransactionStatus.Confirmed -> TxInfo.TransactionStatus.Confirmed
|
||||
TransactionStatus.Unconfirmed -> TxInfo.TransactionStatus.Unconfirmed
|
||||
},
|
||||
type = getTransactionType(value.extras),
|
||||
type = getTransactionType(value),
|
||||
amount = amount,
|
||||
)
|
||||
}
|
||||
|
|
@ -99,16 +99,25 @@ internal class TransactionDataToTxHistoryItemConverter(
|
|||
)
|
||||
}
|
||||
|
||||
private fun getTransactionType(extras: TransactionExtras?): TxInfo.TransactionType {
|
||||
return when (extras) {
|
||||
private fun getTransactionType(transactionData: TransactionData.Uncompiled?): TxInfo.TransactionType {
|
||||
return when (val extras = transactionData?.extras) {
|
||||
is EthereumTransactionExtras -> {
|
||||
when (extras.callData) {
|
||||
is EthereumYieldSupplyDeployCallData,
|
||||
is EthereumYieldSupplyReactivateTokenCallData,
|
||||
is EthereumYieldSupplyInitTokenCallData,
|
||||
is EthereumYieldSupplyEnterCallData,
|
||||
-> TxInfo.TransactionType.YieldSupply.Enter
|
||||
is EthereumYieldSupplyExitCallData -> TxInfo.TransactionType.YieldSupply.Exit
|
||||
when (val callData = extras.callData) {
|
||||
is EthereumYieldSupplyDeployCallData -> TxInfo.TransactionType.YieldSupply.DeployContract(
|
||||
transactionData.destinationAddress,
|
||||
)
|
||||
is EthereumYieldSupplyReactivateTokenCallData -> TxInfo.TransactionType.YieldSupply.ReactivateToken(
|
||||
callData.tokenContractAddress,
|
||||
)
|
||||
is EthereumYieldSupplyInitTokenCallData -> TxInfo.TransactionType.YieldSupply.InitializeToken(
|
||||
callData.tokenContractAddress,
|
||||
)
|
||||
is EthereumYieldSupplyEnterCallData -> TxInfo.TransactionType.YieldSupply.Enter(
|
||||
callData.tokenContractAddress,
|
||||
)
|
||||
is EthereumYieldSupplyExitCallData -> TxInfo.TransactionType.YieldSupply.Exit(
|
||||
callData.tokenContractAddress,
|
||||
)
|
||||
is ApprovalERC20TokenCallData -> TxInfo.TransactionType.Approve
|
||||
else -> TxInfo.TransactionType.Transfer
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,113 @@
|
|||
package com.tangem.data.walletmanager.utils
|
||||
|
||||
// Remove after moving this data to BE
|
||||
internal val YIELD_SUPPLY_ADDRESSES: Set<String> = setOf(
|
||||
"0x00901a076785e0906d1028c7d6372d247bec7d61",
|
||||
"0x00907f9921424583e7ffbfedf84f92b7b2be4977",
|
||||
"0x018008bfb33d285247a21d44e50697654f754e63",
|
||||
"0x067ae75628177fd257c2b1e500993e1a0babcbd1",
|
||||
"0x078f358208685046a11c85e8ad32895ded33a249",
|
||||
"0x0a1d576f3efef75b330424287a95a366e8281d54",
|
||||
"0x0b925ed163218f6662a35e0f0371ac234f9e9371",
|
||||
"0x0c0d01abf3e6adfca0989ebba9d6e85dd58eab1e",
|
||||
"0x10ac93971cdb1f5c778144084242374473c350da",
|
||||
"0x191c10aa4af7c30e871e70c95db0e4eb77237530",
|
||||
"0x1ba9843bd4327c6c77011406de5fa8749f7e3479",
|
||||
"0x1c0e06a0b1a4c160c17545ff2a951bfca57c0002",
|
||||
"0x23878914efe38d27c4d67ab83ed1b93a74d4086a",
|
||||
"0x24ab03a9a5bc2c49e5523e8d915a3536ac38b91d",
|
||||
"0x2516e7b3f76294e03c42aa4c5b5b4dce9c436fb8",
|
||||
"0x285866acb0d60105b4ed350a463361c2d9afa0e2",
|
||||
"0x2d62109243b87c4ba3ee7ba1d91b0dd0a074d7b1",
|
||||
"0x2e94171493fabe316b6205f1585779c887771e2f",
|
||||
"0x2edff5af94334fbd7c38ae318edf1c40e072b73b",
|
||||
"0x312ffc57778cefa11989733e6e08143e7e229c1c",
|
||||
"0x32a6268f9ba3642dda7892add74f1d34469a4259",
|
||||
"0x38a5357ce55c81add62abc84fb32981e2626adef",
|
||||
"0x38c503a438185cde29b5cf4dc1442fd6f074f1cc",
|
||||
"0x38d693ce1df5aadf7bc62595a37d667ad57922e5",
|
||||
"0x3fe6a295459fae07df8a0cecc36f37160fe86aa9",
|
||||
"0x40b4baecc69b882e8804f9286b12228c27f8c9bf",
|
||||
"0x4199cc1f5ed0d796563d7ccb2e036253e2c18281",
|
||||
"0x44705f578135cc5d703b4c9c122528c73eb87145",
|
||||
"0x4579a27af00a62c0eb156349f31b345c08386419",
|
||||
"0x481a2acf3a72ffdc602a9541896ca1db87f86cf7",
|
||||
"0x4b0821e768ed9039a70ed1e80e15e76a5be5df5f",
|
||||
"0x4c612e3b15b96ff9a6faed838f8d07d479a8dd4c",
|
||||
"0x4d5f47fa6a74757f35c14fd3a6ef8e3c9bc514e8",
|
||||
"0x4e2a4d9b3df7aae73b418bd39f3af9e148e3f479",
|
||||
"0x4e65fe4dba92790696d040ac24aa414708f5c0ab",
|
||||
"0x4f5923fc5fd4a93352581b38b7cd26943012decf",
|
||||
"0x513c7e3a9c69ca3e22550ef58ac1c0088e918fff",
|
||||
"0x545bd6c032efdde65a377a6719def2796c8e0f2e",
|
||||
"0x56a7ddc4e848ebf43845854205ad71d5d5f72d3d",
|
||||
"0x5b502e3796385e1e9755d7043b9c945c3accec9c",
|
||||
"0x5c647ce0ae10658ec44fa4e11a51c96e94efd1dd",
|
||||
"0x5e8c8a7243651db1384c0ddfdbe39761e8e7e51a",
|
||||
"0x5ee5bf7ae06d1be5997a1a72006fe6c607ec6de8",
|
||||
"0x5f4a0873a3a02f7c0cb0e13a1d4362a1ad90e751",
|
||||
"0x5f9190496e0dfc831c3bd307978de4a245e2f5cd",
|
||||
"0x5fefd7069a7d91d01f269dade14526ccf3487810",
|
||||
"0x625e7708f30ca75bfd92586e17077590c60eb4cd",
|
||||
"0x62fc96b27a510cf4977b59ff952dc32378cc221d",
|
||||
"0x6533afac2e7bccb20dca161449a13a32d391fb00",
|
||||
"0x65906988adee75306021c417a1a3458040239602",
|
||||
"0x67eaf2bee4384a2f84da9eb8105c661c123736ba",
|
||||
"0x6ab707aca953edaefbc4fd23ba73294241490620",
|
||||
"0x6b030ff3fb9956b1b69f475b77ae0d3cf2cc5afa",
|
||||
"0x6d80113e533a2c0fe82eabd35f1875dcea89ea97",
|
||||
"0x71aef7b30728b9bb371578f36c5a1f1502a5723e",
|
||||
"0x724dc807b04555b71ed48a6896b6f41593b8c637",
|
||||
"0x75bd1a659bdc62e4c313950d44a2416fab43e785",
|
||||
"0x7b95ec873268a6bfc6427e7a28e396db9d0ebc65",
|
||||
"0x7c307e128efa31f540f2e2d976c995e0b65f51f6",
|
||||
"0x80a94c36747cf51b2fbabdff045f6d22c1930ed1",
|
||||
"0x80ca0d8c38d2e2bcbab66aa1648bd1c7160500fe",
|
||||
"0x82e64f49ed5ec1bc6e43dad4fc8af9bb3a2312ee",
|
||||
"0x82f9c5ad306bba1ad0de49bb5fa6f01bf61085ef",
|
||||
"0x8437d7c167dfb82ed4cb79cd44b7a32a1dd95c77",
|
||||
"0x8a2b6f94ff3a89a03e8c02ee92b55af90c9454a2",
|
||||
"0x8a458a9dc9048e005d22849f470891b840296619",
|
||||
"0x8a9fde6925a839f6b1932d16b36ac026f8d3fbdb",
|
||||
"0x8eb270e296023e9d92081fdf967ddd7878724424",
|
||||
"0x8ffdf2de812095b1d19cb146e4c004587c0a0692",
|
||||
"0x90072a4aa69b5eb74984ab823efc5f91e90b3a72",
|
||||
"0x90da57e0a6c0d166bf15764e03b83745dc90025b",
|
||||
"0x927709711794f3de5ddbf1d176bee2d55ba13c21",
|
||||
"0x977b6fc5de62598b08c85ac8cf2b745874e8b78c",
|
||||
"0x98c23e9d8f34fefb1b7bd6a91b7ff122f4e16f5c",
|
||||
"0x99cbc45ea5bb7ef3a5bc08fb1b7e56bb2442ef0d",
|
||||
"0x9a44fd41566876a39655f74971a3a6ea0a17a454",
|
||||
"0x9b00a09492a626678e5a3009982191586c444df9",
|
||||
"0xa4d94019934d8333ef880abffbf2fdd611c762bd",
|
||||
"0xa700b4eb416be35b2911fd5dee80678ff64ff6c9",
|
||||
"0xa9251ca9de909cb71783723713b21e4233fbf1b1",
|
||||
"0xaa0200d169ff3ba9385c12e073c5d1d30434ae7b",
|
||||
"0xaa6e91c82942aeae040303bf96c15a6dbcb82ca0",
|
||||
"0xb76cf92076adbf1d9c39294fa8e7a67579fde357",
|
||||
"0xb82fa9f31612989525992fcfbb09ab22eff5c85a",
|
||||
"0xbcffb4b3beadc989bd1458740952af6ec8fbe431",
|
||||
"0xbdb9300b7cde636d9cd4aff00f6f009ffbbc8ee6",
|
||||
"0xbdfa7b7893081b35fb54027489e2bc7a38275129",
|
||||
"0xbdfd4e51d3c14a232135f04988a42576efb31519",
|
||||
"0xbe54767735fb7acca2aa7e2d209a6f705073536d",
|
||||
"0xc45a479877e1e9dfe9fcd4056c699575a1045daa",
|
||||
"0xc7b4c17861357b8abb91f25581e7263e08dcb59c",
|
||||
"0xcc9ee9483f662091a1de4795249e24ac0ac2630f",
|
||||
"0xcca43cef272c30415866914351fdfc3e881bb7c2",
|
||||
"0xcf3d55c10db69f28fd1a75bd73f3d8a2d9c595ad",
|
||||
"0xd4a0e0b9149bcee3c920d2e00b5de09138fd8bb7",
|
||||
"0xd4e245848d6e1220dbe62e155d89fa327e43cb06",
|
||||
"0xdd5745756c2de109183c6b5bb886f9207bef114d",
|
||||
"0xde6ef6cb4abd3a473ffc2942eef5d84536f8e864",
|
||||
"0xe50fa9b3c56ffb159cb0fca61f5c9d750e8128c8",
|
||||
"0xe728577e9a1fe7032bc309b4541f58f45443866e",
|
||||
"0xea1132120ddcdda2f119e99fa7a27a0d036f7ac9",
|
||||
"0xebe517846d0f36eced99c735cbf6131e1feb775d",
|
||||
"0xec4ef66d4fceeba34abb4de69db391bc5476ccc8",
|
||||
"0xf329e36c7bf6e5e86ce2150875a84ce77f477375",
|
||||
"0xf59036caebea7dc4b86638dfa2e3c97da9fccd40",
|
||||
"0xf611aeb5013fd2c0511c9cd55c7dc5c1140741a6",
|
||||
"0xf6d2224916ddfbbab6e6bd0d1b7034f4ae0cab18",
|
||||
"0xfa82580c16a31d0c1bc632a36f82e83efef3eec0",
|
||||
)
|
||||
|
|
@ -19,6 +19,10 @@ dependencies {
|
|||
/** Tangem SDKs */
|
||||
implementation(tangemDeps.blockchain)
|
||||
|
||||
// region AndroidX libraries
|
||||
implementation(deps.androidx.datastore)
|
||||
// endregion
|
||||
|
||||
/** Core */
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.core.utils)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@ import com.tangem.data.yield.supply.converters.YieldTokenChartConverter
|
|||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.tangemTech.YieldSupplyApi
|
||||
import com.tangem.datasource.api.tangemTech.models.YieldSupplyChangeTokenStatusBody
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.get
|
||||
import com.tangem.datasource.local.preferences.utils.store
|
||||
import com.tangem.datasource.local.yieldsupply.YieldMarketsStore
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
|
|
@ -35,6 +39,7 @@ internal class DefaultYieldSupplyRepository(
|
|||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
) : YieldSupplyRepository {
|
||||
|
||||
private val statusMap: MutableMap<String, YieldSupplyEnterStatus> = ConcurrentHashMap()
|
||||
|
|
@ -174,6 +179,14 @@ internal class DefaultYieldSupplyRepository(
|
|||
null
|
||||
}
|
||||
|
||||
override fun getShouldShowYieldPromoBanner(): Flow<Boolean> {
|
||||
return appPreferencesStore.get(PreferencesKeys.YIELD_SUPPLY_SHOULD_SHOW_MAIN_PROMO_KEY, true)
|
||||
}
|
||||
|
||||
override suspend fun setShouldShowYieldPromoBanner(shouldShow: Boolean) {
|
||||
appPreferencesStore.store(PreferencesKeys.YIELD_SUPPLY_SHOULD_SHOW_MAIN_PROMO_KEY, shouldShow)
|
||||
}
|
||||
|
||||
private fun Set<TxInfo>.hasYieldEnterTransactions(yieldAddress: String) = any {
|
||||
it.type == TxInfo.TransactionType.YieldSupply.Enter ||
|
||||
it.type == TxInfo.TransactionType.Approve &&
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.tangem.data.yield.supply.DefaultYieldSupplyRepository
|
|||
import com.tangem.data.yield.supply.DefaultYieldSupplyErrorResolver
|
||||
import com.tangem.data.yield.supply.DefaultYieldSupplyTransactionRepository
|
||||
import com.tangem.datasource.api.tangemTech.YieldSupplyApi
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.yieldsupply.YieldMarketsStore
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||
|
|
@ -41,6 +42,7 @@ internal object YieldSupplyDataModule {
|
|||
walletManagersFacade: WalletManagersFacade,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
analyticsExceptionHandler: AnalyticsExceptionHandler,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
): YieldSupplyRepository {
|
||||
return DefaultYieldSupplyRepository(
|
||||
yieldSupplyApi = yieldSupplyApi,
|
||||
|
|
@ -48,6 +50,7 @@ internal object YieldSupplyDataModule {
|
|||
dispatchers = dispatchers,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
analyticsExceptionHandler = analyticsExceptionHandler,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,21 +3,21 @@ package com.tangem.domain.apptheme.model
|
|||
/**
|
||||
* Enumerates the possible modes for the application's theme.
|
||||
*/
|
||||
enum class AppThemeMode {
|
||||
enum class AppThemeMode(val value: String) {
|
||||
/**
|
||||
* Forces the dark theme mode regardless of system settings.
|
||||
*/
|
||||
FORCE_DARK,
|
||||
FORCE_DARK("Dark"),
|
||||
|
||||
/**
|
||||
* Forces the light theme mode regardless of system settings.
|
||||
*/
|
||||
FORCE_LIGHT,
|
||||
FORCE_LIGHT("Light"),
|
||||
|
||||
/**
|
||||
* Follows the system-wide theme mode.
|
||||
*/
|
||||
FOLLOW_SYSTEM,
|
||||
FOLLOW_SYSTEM("System"),
|
||||
|
||||
;
|
||||
|
||||
|
|
@ -30,6 +30,6 @@ enum class AppThemeMode {
|
|||
/**
|
||||
* List of available [AppThemeMode]s.
|
||||
* */
|
||||
val available: List<AppThemeMode> = values().toList()
|
||||
val available: List<AppThemeMode> = entries
|
||||
}
|
||||
}
|
||||
|
|
@ -205,32 +205,32 @@
|
|||
"0xcbeda14c": {
|
||||
"info": "yieldModule",
|
||||
"source": "https://github.com/tangem/tangem-yield-module-contracts/",
|
||||
"name": "supply"
|
||||
"name": "deployYieldModule"
|
||||
},
|
||||
"0x79be55f7": {
|
||||
"info": "yieldModule",
|
||||
"source": "https://github.com/tangem/tangem-yield-module-contracts/",
|
||||
"name": "supplyEnter"
|
||||
"name": "enterProtocolByOwner"
|
||||
},
|
||||
"0xc65e6dcf": {
|
||||
"info": "yieldModule",
|
||||
"source": "https://github.com/tangem/tangem-yield-module-contracts/",
|
||||
"name": "supplyExit"
|
||||
"name": "withdrawAndDeactivate"
|
||||
},
|
||||
"0xebd4b81c": {
|
||||
"info": "yieldModule",
|
||||
"source": "https://github.com/tangem/tangem-yield-module-contracts/",
|
||||
"name": "supply"
|
||||
"name": "initYieldToken"
|
||||
},
|
||||
"0xc478e956": {
|
||||
"info": "yieldModule",
|
||||
"source": "https://github.com/tangem/tangem-yield-module-contracts/",
|
||||
"name": "supply"
|
||||
"name": "reactivateToken"
|
||||
},
|
||||
"0x0779afe6": {
|
||||
"info": "yieldModule",
|
||||
"source": "https://github.com/tangem/tangem-yield-module-contracts/",
|
||||
"name": "transfer"
|
||||
"name": "yieldSend"
|
||||
},
|
||||
"0xb9de6a93": {
|
||||
"info": "yieldModule",
|
||||
|
|
|
|||
|
|
@ -7,16 +7,16 @@ fun CryptoCurrency.Token.yieldSupplyKey(): String {
|
|||
}
|
||||
|
||||
fun CryptoCurrencyStatus.hasNotSuppliedAmount(): Boolean {
|
||||
val notSupplied = notSuppliedAmountOrNull() ?: return false
|
||||
val notSupplied = notSuppliedCryptoAmountOrNull() ?: return false
|
||||
return notSupplied > BigDecimal.ZERO
|
||||
}
|
||||
|
||||
fun CryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(minAmount: BigDecimal): Boolean {
|
||||
val notSupplied = notSuppliedAmountOrNull() ?: return false
|
||||
return notSupplied >= minAmount
|
||||
fun CryptoCurrencyStatus.shouldShowNotSuppliedNotification(dustAmount: BigDecimal): Boolean {
|
||||
val notSupplied = notSuppliedCryptoAmountOrNull()?.multiply(this.value.fiatRate) ?: return false
|
||||
return notSupplied >= dustAmount
|
||||
}
|
||||
|
||||
fun CryptoCurrencyStatus.notSuppliedAmountOrNull(): BigDecimal? {
|
||||
fun CryptoCurrencyStatus.notSuppliedCryptoAmountOrNull(): BigDecimal? {
|
||||
if (this.currency !is CryptoCurrency.Token) return null
|
||||
|
||||
val supplyStatus = this.value.yieldSupplyStatus
|
||||
|
|
|
|||
|
|
@ -110,14 +110,33 @@ data class TxInfo(
|
|||
@Serializable
|
||||
sealed interface YieldSupply : TransactionType {
|
||||
|
||||
@Serializable
|
||||
data object Enter : YieldSupply
|
||||
val address: String?
|
||||
|
||||
@Serializable
|
||||
data object Exit : YieldSupply
|
||||
data class Enter(override val address: String) : YieldSupply
|
||||
|
||||
@Serializable
|
||||
data object Topup : YieldSupply
|
||||
data class Exit(override val address: String) : YieldSupply
|
||||
|
||||
@Serializable
|
||||
data object Topup : YieldSupply {
|
||||
override val address: String? = null
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class Send(
|
||||
override val address: String? = null,
|
||||
val isYieldSupplyWithdraw: Boolean,
|
||||
) : YieldSupply
|
||||
|
||||
@Serializable
|
||||
data class DeployContract(override val address: String) : YieldSupply
|
||||
|
||||
@Serializable
|
||||
data class ReactivateToken(override val address: String) : YieldSupply
|
||||
|
||||
@Serializable
|
||||
data class InitializeToken(override val address: String) : YieldSupply
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ enum class NetworkType {
|
|||
BAND_PROTOCOL,
|
||||
BITSONG,
|
||||
CANTO,
|
||||
CARDANO,
|
||||
CHIHUAHUA,
|
||||
COMDEX,
|
||||
COREUM,
|
||||
|
|
|
|||
|
|
@ -86,6 +86,13 @@ fun UserWallet.requireHotWallet(): UserWallet.Hot {
|
|||
?: error("This user wallet is not a hot wallet")
|
||||
}
|
||||
|
||||
fun UserWallet.isImported(): Boolean {
|
||||
return when (this) {
|
||||
is UserWallet.Cold -> isImported
|
||||
is UserWallet.Hot -> true
|
||||
}
|
||||
}
|
||||
|
||||
fun UserWallet.copy(name: String = this.name, walletId: UserWalletId = this.walletId): UserWallet = when (this) {
|
||||
is UserWallet.Cold -> this.copy(name = name, walletId = walletId)
|
||||
is UserWallet.Hot -> this.copy(name = name, walletId = walletId)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,417 @@
|
|||
package com.tangem.domain.models.currency
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.domain.models.yield.supply.YieldSupplyStatus
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import java.math.BigDecimal
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class CryptoCurrencyExtensionsTest {
|
||||
|
||||
@Test
|
||||
fun `GIVEN currency is Coin WHEN hasNotSuppliedAmount THEN returns false`() {
|
||||
val status = createCryptoCurrencyStatus(
|
||||
currency = mockk<CryptoCurrency.Coin>(),
|
||||
)
|
||||
|
||||
val result = status.hasNotSuppliedAmount()
|
||||
|
||||
assertThat(result).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN yieldSupplyStatus is null WHEN hasNotSuppliedAmount THEN returns false`() {
|
||||
val status = createCryptoCurrencyStatus(
|
||||
currency = mockk<CryptoCurrency.Token>(),
|
||||
yieldSupplyStatus = null,
|
||||
)
|
||||
|
||||
val result = status.hasNotSuppliedAmount()
|
||||
|
||||
assertThat(result).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN yieldSupplyStatus isActive is false WHEN hasNotSuppliedAmount THEN returns false`() {
|
||||
val status = createCryptoCurrencyStatus(
|
||||
currency = mockk<CryptoCurrency.Token>(),
|
||||
yieldSupplyStatus = YieldSupplyStatus(
|
||||
isActive = false,
|
||||
isInitialized = true,
|
||||
isAllowedToSpend = true,
|
||||
effectiveProtocolBalance = BigDecimal.TEN,
|
||||
),
|
||||
)
|
||||
|
||||
val result = status.hasNotSuppliedAmount()
|
||||
|
||||
assertThat(result).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN effectiveProtocolBalance is null WHEN hasNotSuppliedAmount THEN returns false`() {
|
||||
val status = createCryptoCurrencyStatus(
|
||||
currency = mockk<CryptoCurrency.Token>(),
|
||||
yieldSupplyStatus = YieldSupplyStatus(
|
||||
isActive = true,
|
||||
isInitialized = true,
|
||||
isAllowedToSpend = true,
|
||||
effectiveProtocolBalance = null,
|
||||
),
|
||||
amount = BigDecimal.TEN,
|
||||
)
|
||||
|
||||
val result = status.hasNotSuppliedAmount()
|
||||
|
||||
assertThat(result).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN amount is null WHEN hasNotSuppliedAmount THEN returns false`() {
|
||||
val status = createCryptoCurrencyStatus(
|
||||
currency = mockk<CryptoCurrency.Token>(),
|
||||
yieldSupplyStatus = YieldSupplyStatus(
|
||||
isActive = true,
|
||||
isInitialized = true,
|
||||
isAllowedToSpend = true,
|
||||
effectiveProtocolBalance = BigDecimal.TEN,
|
||||
),
|
||||
amount = null,
|
||||
)
|
||||
|
||||
val result = status.hasNotSuppliedAmount()
|
||||
|
||||
assertThat(result).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN notSupplied is zero WHEN hasNotSuppliedAmount THEN returns false`() {
|
||||
val status = createCryptoCurrencyStatus(
|
||||
currency = mockk<CryptoCurrency.Token>(),
|
||||
yieldSupplyStatus = YieldSupplyStatus(
|
||||
isActive = true,
|
||||
isInitialized = true,
|
||||
isAllowedToSpend = true,
|
||||
effectiveProtocolBalance = BigDecimal.TEN,
|
||||
),
|
||||
amount = BigDecimal.TEN,
|
||||
)
|
||||
|
||||
val result = status.hasNotSuppliedAmount()
|
||||
|
||||
assertThat(result).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN notSupplied is negative WHEN hasNotSuppliedAmount THEN returns false`() {
|
||||
val status = createCryptoCurrencyStatus(
|
||||
currency = mockk<CryptoCurrency.Token>(),
|
||||
yieldSupplyStatus = YieldSupplyStatus(
|
||||
isActive = true,
|
||||
isInitialized = true,
|
||||
isAllowedToSpend = true,
|
||||
effectiveProtocolBalance = BigDecimal.TEN,
|
||||
),
|
||||
amount = BigDecimal.ONE,
|
||||
)
|
||||
|
||||
val result = status.hasNotSuppliedAmount()
|
||||
|
||||
assertThat(result).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN notSupplied is positive WHEN hasNotSuppliedAmount THEN returns true`() {
|
||||
val status = createCryptoCurrencyStatus(
|
||||
currency = mockk<CryptoCurrency.Token>(),
|
||||
yieldSupplyStatus = YieldSupplyStatus(
|
||||
isActive = true,
|
||||
isInitialized = true,
|
||||
isAllowedToSpend = true,
|
||||
effectiveProtocolBalance = BigDecimal.ONE,
|
||||
),
|
||||
amount = BigDecimal.TEN,
|
||||
)
|
||||
|
||||
val result = status.hasNotSuppliedAmount()
|
||||
|
||||
assertThat(result).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN currency is Coin WHEN shouldShowNotSuppliedNotification THEN returns false`() {
|
||||
val status = createCryptoCurrencyStatus(
|
||||
currency = mockk<CryptoCurrency.Coin>(),
|
||||
)
|
||||
|
||||
val result = status.shouldShowNotSuppliedNotification(dustAmount = BigDecimal.ONE)
|
||||
|
||||
assertThat(result).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN yieldSupplyStatus is null WHEN shouldShowNotSuppliedNotification THEN returns false`() {
|
||||
val status = createCryptoCurrencyStatus(
|
||||
currency = mockk<CryptoCurrency.Token>(),
|
||||
yieldSupplyStatus = null,
|
||||
)
|
||||
|
||||
val result = status.shouldShowNotSuppliedNotification(dustAmount = BigDecimal.ONE)
|
||||
|
||||
assertThat(result).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN yieldSupplyStatus isActive is false WHEN shouldShowNotSuppliedNotification THEN returns false`() {
|
||||
val status = createCryptoCurrencyStatus(
|
||||
currency = mockk<CryptoCurrency.Token>(),
|
||||
yieldSupplyStatus = YieldSupplyStatus(
|
||||
isActive = false,
|
||||
isInitialized = true,
|
||||
isAllowedToSpend = true,
|
||||
effectiveProtocolBalance = BigDecimal.TEN,
|
||||
),
|
||||
)
|
||||
|
||||
val result = status.shouldShowNotSuppliedNotification(dustAmount = BigDecimal.ONE)
|
||||
|
||||
assertThat(result).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN effectiveProtocolBalance is null WHEN shouldShowNotSuppliedNotification THEN returns false`() {
|
||||
val status = createCryptoCurrencyStatus(
|
||||
currency = mockk<CryptoCurrency.Token>(),
|
||||
yieldSupplyStatus = YieldSupplyStatus(
|
||||
isActive = true,
|
||||
isInitialized = true,
|
||||
isAllowedToSpend = true,
|
||||
effectiveProtocolBalance = null,
|
||||
),
|
||||
amount = BigDecimal.TEN,
|
||||
fiatRate = BigDecimal.ONE,
|
||||
)
|
||||
|
||||
val result = status.shouldShowNotSuppliedNotification(dustAmount = BigDecimal.ONE)
|
||||
|
||||
assertThat(result).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN notSupplied in fiat is less than dustAmount WHEN shouldShowNotSuppliedNotification THEN returns false`() {
|
||||
val status = createCryptoCurrencyStatus(
|
||||
currency = mockk<CryptoCurrency.Token>(),
|
||||
yieldSupplyStatus = YieldSupplyStatus(
|
||||
isActive = true,
|
||||
isInitialized = true,
|
||||
isAllowedToSpend = true,
|
||||
effectiveProtocolBalance = BigDecimal("9"),
|
||||
),
|
||||
amount = BigDecimal.TEN,
|
||||
fiatRate = BigDecimal.ONE,
|
||||
)
|
||||
|
||||
val result = status.shouldShowNotSuppliedNotification(dustAmount = BigDecimal.TEN)
|
||||
|
||||
assertThat(result).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN notSupplied in fiat equals dustAmount WHEN shouldShowNotSuppliedNotification THEN returns true`() {
|
||||
val status = createCryptoCurrencyStatus(
|
||||
currency = mockk<CryptoCurrency.Token>(),
|
||||
yieldSupplyStatus = YieldSupplyStatus(
|
||||
isActive = true,
|
||||
isInitialized = true,
|
||||
isAllowedToSpend = true,
|
||||
effectiveProtocolBalance = BigDecimal("5"),
|
||||
),
|
||||
amount = BigDecimal.TEN,
|
||||
fiatRate = BigDecimal("2"),
|
||||
)
|
||||
|
||||
val result = status.shouldShowNotSuppliedNotification(dustAmount = BigDecimal.TEN)
|
||||
|
||||
assertThat(result).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN notSupplied in fiat is greater than dustAmount WHEN shouldShowNotSuppliedNotification THEN returns true`() {
|
||||
val status = createCryptoCurrencyStatus(
|
||||
currency = mockk<CryptoCurrency.Token>(),
|
||||
yieldSupplyStatus = YieldSupplyStatus(
|
||||
isActive = true,
|
||||
isInitialized = true,
|
||||
isAllowedToSpend = true,
|
||||
effectiveProtocolBalance = BigDecimal.ONE,
|
||||
),
|
||||
amount = BigDecimal.TEN,
|
||||
fiatRate = BigDecimal("2"),
|
||||
)
|
||||
|
||||
val result = status.shouldShowNotSuppliedNotification(dustAmount = BigDecimal.TEN)
|
||||
|
||||
assertThat(result).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN USDT with EUR fiat rate and notSupplied below dust WHEN shouldShowNotSuppliedNotification THEN returns false`() {
|
||||
val usdtToEurRate = BigDecimal("0.93")
|
||||
val notSuppliedUsdt = BigDecimal("0.05")
|
||||
val protocolBalance = BigDecimal("100")
|
||||
val totalAmount = protocolBalance.add(notSuppliedUsdt)
|
||||
val dustAmountEur = BigDecimal("0.1")
|
||||
|
||||
val status = createCryptoCurrencyStatus(
|
||||
currency = mockk<CryptoCurrency.Token>(),
|
||||
yieldSupplyStatus = YieldSupplyStatus(
|
||||
isActive = true,
|
||||
isInitialized = true,
|
||||
isAllowedToSpend = true,
|
||||
effectiveProtocolBalance = protocolBalance,
|
||||
),
|
||||
amount = totalAmount,
|
||||
fiatRate = usdtToEurRate,
|
||||
)
|
||||
|
||||
val result = status.shouldShowNotSuppliedNotification(dustAmount = dustAmountEur)
|
||||
|
||||
assertThat(result).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN USDT with EUR fiat rate and notSupplied above dust WHEN shouldShowNotSuppliedNotification THEN returns true`() {
|
||||
val usdtToEurRate = BigDecimal("0.93")
|
||||
val notSuppliedUsdt = BigDecimal("0.15")
|
||||
val protocolBalance = BigDecimal("100")
|
||||
val totalAmount = protocolBalance.add(notSuppliedUsdt)
|
||||
val dustAmountEur = BigDecimal("0.1")
|
||||
|
||||
val status = createCryptoCurrencyStatus(
|
||||
currency = mockk<CryptoCurrency.Token>(),
|
||||
yieldSupplyStatus = YieldSupplyStatus(
|
||||
isActive = true,
|
||||
isInitialized = true,
|
||||
isAllowedToSpend = true,
|
||||
effectiveProtocolBalance = protocolBalance,
|
||||
),
|
||||
amount = totalAmount,
|
||||
fiatRate = usdtToEurRate,
|
||||
)
|
||||
|
||||
val result = status.shouldShowNotSuppliedNotification(dustAmount = dustAmountEur)
|
||||
|
||||
assertThat(result).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN currency is Coin WHEN notSuppliedCryptoAmountOrNull THEN returns null`() {
|
||||
val status = createCryptoCurrencyStatus(
|
||||
currency = mockk<CryptoCurrency.Coin>(),
|
||||
)
|
||||
|
||||
val result = status.notSuppliedCryptoAmountOrNull()
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN yieldSupplyStatus is null WHEN notSuppliedCryptoAmountOrNull THEN returns null`() {
|
||||
val status = createCryptoCurrencyStatus(
|
||||
currency = mockk<CryptoCurrency.Token>(),
|
||||
yieldSupplyStatus = null,
|
||||
)
|
||||
|
||||
val result = status.notSuppliedCryptoAmountOrNull()
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN yieldSupplyStatus isActive is false WHEN notSuppliedCryptoAmountOrNull THEN returns null`() {
|
||||
val status = createCryptoCurrencyStatus(
|
||||
currency = mockk<CryptoCurrency.Token>(),
|
||||
yieldSupplyStatus = YieldSupplyStatus(
|
||||
isActive = false,
|
||||
isInitialized = true,
|
||||
isAllowedToSpend = true,
|
||||
effectiveProtocolBalance = BigDecimal.TEN,
|
||||
),
|
||||
)
|
||||
|
||||
val result = status.notSuppliedCryptoAmountOrNull()
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN effectiveProtocolBalance is null WHEN notSuppliedCryptoAmountOrNull THEN returns null`() {
|
||||
val status = createCryptoCurrencyStatus(
|
||||
currency = mockk<CryptoCurrency.Token>(),
|
||||
yieldSupplyStatus = YieldSupplyStatus(
|
||||
isActive = true,
|
||||
isInitialized = true,
|
||||
isAllowedToSpend = true,
|
||||
effectiveProtocolBalance = null,
|
||||
),
|
||||
amount = BigDecimal.TEN,
|
||||
)
|
||||
|
||||
val result = status.notSuppliedCryptoAmountOrNull()
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN amount is null WHEN notSuppliedCryptoAmountOrNull THEN returns null`() {
|
||||
val status = createCryptoCurrencyStatus(
|
||||
currency = mockk<CryptoCurrency.Token>(),
|
||||
yieldSupplyStatus = YieldSupplyStatus(
|
||||
isActive = true,
|
||||
isInitialized = true,
|
||||
isAllowedToSpend = true,
|
||||
effectiveProtocolBalance = BigDecimal.TEN,
|
||||
),
|
||||
amount = null,
|
||||
)
|
||||
|
||||
val result = status.notSuppliedCryptoAmountOrNull()
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN valid data WHEN notSuppliedCryptoAmountOrNull THEN returns amount minus protocolBalance`() {
|
||||
val status = createCryptoCurrencyStatus(
|
||||
currency = mockk<CryptoCurrency.Token>(),
|
||||
yieldSupplyStatus = YieldSupplyStatus(
|
||||
isActive = true,
|
||||
isInitialized = true,
|
||||
isAllowedToSpend = true,
|
||||
effectiveProtocolBalance = BigDecimal("3"),
|
||||
),
|
||||
amount = BigDecimal.TEN,
|
||||
)
|
||||
|
||||
val result = status.notSuppliedCryptoAmountOrNull()
|
||||
|
||||
assertThat(result).isEqualTo(BigDecimal("7"))
|
||||
}
|
||||
|
||||
private fun createCryptoCurrencyStatus(
|
||||
currency: CryptoCurrency,
|
||||
yieldSupplyStatus: YieldSupplyStatus? = null,
|
||||
amount: BigDecimal? = null,
|
||||
fiatRate: BigDecimal? = null,
|
||||
): CryptoCurrencyStatus {
|
||||
val value = mockk<CryptoCurrencyStatus.Value> {
|
||||
every { this@mockk.yieldSupplyStatus } returns yieldSupplyStatus
|
||||
every { this@mockk.amount } returns amount
|
||||
every { this@mockk.fiatRate } returns fiatRate
|
||||
}
|
||||
return CryptoCurrencyStatus(currency = currency, value = value)
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ dependencies {
|
|||
api(projects.domain.core)
|
||||
api(projects.domain.settings)
|
||||
implementation(deps.kotlin.serialization)
|
||||
implementation(projects.domain.promo)
|
||||
|
||||
/** Tests */
|
||||
testImplementation(deps.test.coroutine)
|
||||
|
|
|
|||
|
|
@ -11,9 +11,11 @@ import com.tangem.domain.onramp.repositories.OnrampRepository
|
|||
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
|
||||
import com.tangem.domain.onramp.utils.calculateRateDif
|
||||
import com.tangem.domain.onramp.utils.compareOffersByRateSpeedAndPriority
|
||||
import com.tangem.domain.promo.PromoRepository
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class GetOnrampOffersUseCase(
|
||||
|
|
@ -21,14 +23,16 @@ class GetOnrampOffersUseCase(
|
|||
private val onrampTransactionRepository: OnrampTransactionRepository,
|
||||
private val errorResolver: OnrampErrorResolver,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val promoRepository: PromoRepository,
|
||||
) {
|
||||
|
||||
operator fun invoke(): EitherFlow<OnrampError, List<OnrampOffersBlock>> {
|
||||
return combine(
|
||||
onrampRepository.getQuotes(),
|
||||
onrampTransactionRepository.getAllTransactions(),
|
||||
) { quotes, transactions ->
|
||||
processOffers(quotes, transactions)
|
||||
flow { emit(promoRepository.isMoonpayPromoActive()) },
|
||||
) { quotes, transactions, isMoonpayPromoActive ->
|
||||
processOffers(quotes, transactions, isMoonpayPromoActive)
|
||||
}
|
||||
.map { offers -> offers.right() }
|
||||
.catch { throwable -> errorResolver.resolve(throwable).left() }
|
||||
|
|
@ -37,6 +41,7 @@ class GetOnrampOffersUseCase(
|
|||
private suspend fun processOffers(
|
||||
quotes: List<OnrampQuote>,
|
||||
transactions: List<OnrampTransaction>,
|
||||
isMoonpayPromoActive: Boolean,
|
||||
): List<OnrampOffersBlock> {
|
||||
val validQuotes = quotes.filterIsInstance<OnrampQuote.Data>()
|
||||
if (validQuotes.isEmpty()) return emptyList()
|
||||
|
|
@ -57,7 +62,7 @@ class GetOnrampOffersUseCase(
|
|||
|
||||
val recentOffer = findRecentOffer(offers, transactions)
|
||||
val bestRateOffer = findBestRateOffer(offers, isGooglePayAvailable)
|
||||
val fastestOffer = findFastestOffer(offers, isGooglePayAvailable)
|
||||
val fastestOffer = findFastestOffer(offers, isGooglePayAvailable, isMoonpayPromoActive)
|
||||
|
||||
return buildOffersBlocks(
|
||||
recentOffer = recentOffer,
|
||||
|
|
@ -85,8 +90,24 @@ class GetOnrampOffersUseCase(
|
|||
return offers.maxWithOrNull(offerComparator(isGooglePayAvailable))
|
||||
}
|
||||
|
||||
private fun findFastestOffer(offers: List<OnrampOffer>, isGooglePayAvailable: Boolean): OnrampOffer? {
|
||||
val instantOffers = offers.filter { it.quote.paymentMethod.type.isInstant() }
|
||||
private fun findFastestOffer(
|
||||
offers: List<OnrampOffer>,
|
||||
isGooglePayAvailable: Boolean,
|
||||
isMoonpayPromoActive: Boolean,
|
||||
): OnrampOffer? {
|
||||
val moonpayPromoOffers = if (isMoonpayPromoActive) {
|
||||
offers.filter {
|
||||
it.quote.provider.id == MOONPAY_PROMO_PROVIDER_ID &&
|
||||
it.quote.paymentMethod.type == PaymentMethodType.GOOGLE_PAY
|
||||
}
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
val instantOffers = moonpayPromoOffers.ifEmpty {
|
||||
offers.filter { it.quote.paymentMethod.type.isInstant() }
|
||||
}
|
||||
|
||||
return if (instantOffers.isNotEmpty()) {
|
||||
instantOffers.maxWithOrNull(fastestOfferComparator(isGooglePayAvailable))
|
||||
} else {
|
||||
|
|
@ -287,4 +308,8 @@ class GetOnrampOffersUseCase(
|
|||
-> true
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val MOONPAY_PROMO_PROVIDER_ID = "moonpay"
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import com.tangem.domain.onramp.model.cache.OnrampTransaction
|
|||
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
|
||||
import com.tangem.domain.promo.PromoRepository
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
|
@ -24,6 +25,7 @@ class GetOnrampOffersUseCaseTest {
|
|||
private val errorResolver: OnrampErrorResolver = mockk(relaxUnitFun = true)
|
||||
private val settingsRepository: SettingsRepository = mockk(relaxUnitFun = true)
|
||||
private val cryptoCurrencyId: CryptoCurrency.ID = mockk(relaxUnitFun = true)
|
||||
private val promoRepository: PromoRepository = mockk(relaxUnitFun = true)
|
||||
|
||||
private lateinit var useCase: GetOnrampOffersUseCase
|
||||
|
||||
|
|
@ -35,6 +37,7 @@ class GetOnrampOffersUseCaseTest {
|
|||
onrampTransactionRepository = onrampTransactionRepository,
|
||||
errorResolver = errorResolver,
|
||||
settingsRepository = settingsRepository,
|
||||
promoRepository = promoRepository,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -225,6 +228,7 @@ class GetOnrampOffersUseCaseTest {
|
|||
|
||||
val transactions = emptyList<OnrampTransaction>()
|
||||
|
||||
coEvery { promoRepository.isMoonpayPromoActive() } returns false
|
||||
coEvery { settingsRepository.isGooglePayAvailability() } returns false
|
||||
coEvery { onrampRepository.getQuotes() } returns flowOf(quotes)
|
||||
coEvery { onrampTransactionRepository.getAllTransactions() } returns flowOf(
|
||||
|
|
@ -245,6 +249,107 @@ class GetOnrampOffersUseCaseTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should fallback to standard instant offers when promo is active but no Moonpay offers exist`() =
|
||||
runTest {
|
||||
val instantMethod = createMockPaymentMethod("gpay", "Google Pay", PaymentMethodType.GOOGLE_PAY)
|
||||
val slowMethod = createMockPaymentMethod("bank", "Bank Transfer", PaymentMethodType.CARD)
|
||||
val provider = createMockProvider("other", "Other Provider")
|
||||
|
||||
val quotes = listOf(
|
||||
createMockQuote(instantMethod, provider, BigDecimal("95.0")),
|
||||
createMockQuote(slowMethod, provider, BigDecimal("100.0")),
|
||||
)
|
||||
|
||||
val transactions = emptyList<OnrampTransaction>()
|
||||
|
||||
coEvery { promoRepository.isMoonpayPromoActive() } returns true
|
||||
coEvery { settingsRepository.isGooglePayAvailability() } returns true
|
||||
coEvery { onrampRepository.getQuotes() } returns flowOf(quotes)
|
||||
coEvery { onrampTransactionRepository.getAllTransactions() } returns flowOf(transactions)
|
||||
|
||||
val result = useCase()
|
||||
|
||||
result.collect { either ->
|
||||
Truth.assertThat(either.isRight()).isTrue()
|
||||
either.fold(
|
||||
ifLeft = { error -> Truth.assertThat(error).isNull() },
|
||||
ifRight = { offers ->
|
||||
Truth.assertThat(offers).hasSize(1)
|
||||
|
||||
val recommendedBlock = offers.find { it.category == OnrampOfferCategory.Recommended }
|
||||
Truth.assertThat(recommendedBlock).isNotNull()
|
||||
Truth.assertThat(recommendedBlock?.offers).hasSize(2)
|
||||
|
||||
val fastestOffer =
|
||||
recommendedBlock?.offers?.find { it.advantages == OnrampOfferAdvantages.Fastest }
|
||||
Truth.assertThat(fastestOffer).isNotNull()
|
||||
|
||||
when (val quote = fastestOffer?.quote) {
|
||||
is OnrampQuote.Data -> {
|
||||
Truth.assertThat(quote.provider.id).isNotEqualTo("moonpay")
|
||||
Truth.assertThat(quote.paymentMethod.type).isEqualTo(PaymentMethodType.GOOGLE_PAY)
|
||||
}
|
||||
else -> Truth.assertThat(false).isTrue()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should show Moonpay fastest offer when promo is active`() = runTest {
|
||||
val moonpayGooglePayMethod = createMockPaymentMethod("moonpay-gpay", "Google Pay", PaymentMethodType.GOOGLE_PAY)
|
||||
val otherGooglePayMethod = createMockPaymentMethod(
|
||||
"other-gpay",
|
||||
"Other Google Pay",
|
||||
PaymentMethodType.GOOGLE_PAY,
|
||||
)
|
||||
val slowMethod = createMockPaymentMethod("bank", "Bank Transfer", PaymentMethodType.CARD)
|
||||
val moonpayProvider = createMockProvider("moonpay", "Moonpay")
|
||||
val otherProvider = createMockProvider("other", "Other Provider")
|
||||
|
||||
val quotes = listOf(
|
||||
createMockQuote(moonpayGooglePayMethod, moonpayProvider, BigDecimal("100.0")),
|
||||
createMockQuote(otherGooglePayMethod, otherProvider, BigDecimal("95.0")),
|
||||
createMockQuote(slowMethod, otherProvider, BigDecimal("105.0")),
|
||||
)
|
||||
|
||||
val transactions = emptyList<OnrampTransaction>()
|
||||
|
||||
coEvery { promoRepository.isMoonpayPromoActive() } returns true
|
||||
coEvery { settingsRepository.isGooglePayAvailability() } returns true
|
||||
coEvery { onrampRepository.getQuotes() } returns flowOf(quotes)
|
||||
coEvery { onrampTransactionRepository.getAllTransactions() } returns flowOf(transactions)
|
||||
|
||||
val result = useCase()
|
||||
|
||||
result.collect { either ->
|
||||
Truth.assertThat(either.isRight()).isTrue()
|
||||
either.fold(
|
||||
ifLeft = { error -> Truth.assertThat(error).isNull() },
|
||||
ifRight = { offers ->
|
||||
Truth.assertThat(offers).hasSize(1)
|
||||
|
||||
val recommendedBlock = offers.find { it.category == OnrampOfferCategory.Recommended }
|
||||
Truth.assertThat(recommendedBlock).isNotNull()
|
||||
|
||||
val fastestOffer = recommendedBlock?.offers?.find { it.advantages == OnrampOfferAdvantages.Fastest }
|
||||
Truth.assertThat(fastestOffer).isNotNull()
|
||||
|
||||
when (val quote = fastestOffer?.quote) {
|
||||
is OnrampQuote.Data -> {
|
||||
Truth.assertThat(quote.provider.id).isEqualTo("moonpay")
|
||||
Truth.assertThat(quote.paymentMethod.type).isEqualTo(PaymentMethodType.GOOGLE_PAY)
|
||||
Truth.assertThat(quote.toAmount.value).isEqualTo(BigDecimal("100.0"))
|
||||
}
|
||||
else -> Truth.assertThat(false).isTrue()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createMockPaymentMethod(
|
||||
id: String,
|
||||
name: String,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ interface PromoRepository {
|
|||
suspend fun isMarketsStakingNotificationHideClicked(): Flow<Boolean>
|
||||
|
||||
suspend fun setMarketsStakingNotificationHideClicked()
|
||||
|
||||
suspend fun isMoonpayPromoActive(): Boolean
|
||||
// endregion
|
||||
|
||||
// region Stories
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
package com.tangem.domain.staking.usecase
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.toCoinId
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.staking.repositories.StakeKitRepository
|
||||
import com.tangem.domain.staking.toggles.StakingFeatureToggles
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
/**
|
||||
* Emits a map of Validators values per currency for staking.
|
||||
*
|
||||
* Return map:
|
||||
* - key: currency staking key (network.backendId + "_" + symbol)
|
||||
* - value: validators
|
||||
*/
|
||||
class StakingApyFlowUseCase(
|
||||
private val stakeKitRepository: StakeKitRepository,
|
||||
private val stakingFeatureToggles: StakingFeatureToggles,
|
||||
) {
|
||||
|
||||
operator fun invoke(): Flow<Map<String, List<Yield.Validator>>> {
|
||||
return stakeKitRepository.getEnabledYields()
|
||||
.map { yields ->
|
||||
yields.filterNot { yield ->
|
||||
val isCardanoYield = yield.token.coinGeckoId == Blockchain.Cardano.toCoinId()
|
||||
isCardanoYield && !stakingFeatureToggles.isCardanoStakingEnabled
|
||||
}.associate { yield ->
|
||||
val key = "${yield.token.coinGeckoId}_${yield.token.symbol}"
|
||||
val apy = yield.validators
|
||||
key to apy
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.domain.staking.usecase
|
||||
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
|
||||
/**
|
||||
* Returns staking availability for a list of crypto currencies for a specific user wallet
|
||||
*
|
||||
* Return map:
|
||||
* - key: crypto currency
|
||||
* - value: staking availability for the currency
|
||||
*/
|
||||
class StakingAvailabilityListUseCase(
|
||||
private val stakingRepository: StakingRepository,
|
||||
) {
|
||||
|
||||
suspend fun invokeSync(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyList: List<CryptoCurrency>,
|
||||
): Map<CryptoCurrency, StakingAvailability> {
|
||||
return coroutineScope {
|
||||
cryptoCurrencyList.map { cryptoCurrency ->
|
||||
async {
|
||||
cryptoCurrency to stakingRepository.getStakingAvailabilitySync(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
)
|
||||
}
|
||||
}.awaitAll().toMap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -93,7 +93,7 @@ interface SwapRepositoryV2 {
|
|||
*
|
||||
* @param userWallet selected user wallet
|
||||
* @param fromCryptoCurrencyStatus currency status being swapped from
|
||||
* @param toAddress swap destination address
|
||||
* @param payInAddress swap destination address
|
||||
* @param txId transaction id in ExpressApi
|
||||
* @param txHash transaction hash in blockchain
|
||||
* @param txExtraId extra transaction id in ExpressApi
|
||||
|
|
@ -101,7 +101,7 @@ interface SwapRepositoryV2 {
|
|||
suspend fun swapTransactionSent(
|
||||
userWallet: UserWallet,
|
||||
fromCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
toAddress: String,
|
||||
payInAddress: String,
|
||||
txId: String,
|
||||
txHash: String,
|
||||
txExtraId: String?,
|
||||
|
|
|
|||
|
|
@ -32,7 +32,10 @@ class GetSwapSupportedPairsUseCase(
|
|||
swapTxType = swapTxType,
|
||||
)
|
||||
|
||||
val filteredOutInitial = cryptoCurrencyList.filterNot { it.id.rawNetworkId == initialCurrency.id.rawNetworkId }
|
||||
val filteredOutInitial = cryptoCurrencyList.filterNot { currency ->
|
||||
currency.id.rawNetworkId == initialCurrency.id.rawNetworkId &&
|
||||
currency.id.rawCurrencyId == initialCurrency.id.rawCurrencyId
|
||||
}
|
||||
|
||||
val fromGroup = pairs.groupPairs(
|
||||
initialCurrency = initialCurrency,
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ class SwapTransactionSentUseCase(
|
|||
swapDataTransactionModel: SwapDataTransactionModel,
|
||||
provider: ExpressProvider,
|
||||
txHash: String,
|
||||
payInAddress: String,
|
||||
timestamp: Long,
|
||||
swapTxType: SwapTxType,
|
||||
) = Either.catch {
|
||||
|
|
@ -63,7 +64,7 @@ class SwapTransactionSentUseCase(
|
|||
swapRepositoryV2.swapTransactionSent(
|
||||
userWallet = userWallet,
|
||||
fromCryptoCurrencyStatus = fromCryptoCurrencyStatus,
|
||||
toAddress = swapDataTransactionModel.txTo,
|
||||
payInAddress = payInAddress,
|
||||
txId = swapDataTransactionModel.txId,
|
||||
txHash = txHash,
|
||||
txExtraId = swapDataTransactionModel.txExtraId,
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ sealed class VisaApiError(
|
|||
data object WithdrawalDataError : VisaApiError(104004003)
|
||||
data object SignWithdrawError : VisaApiError(104004004)
|
||||
data object WithdrawError : VisaApiError(104004005)
|
||||
data object ServerUnavailable : VisaApiError(104004006)
|
||||
|
||||
companion object {
|
||||
fun fromBackendError(backendErrorCode: Int): VisaApiError {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.domain.yield.supply.models
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class YieldSupplyFee(
|
||||
val value: BigDecimal,
|
||||
val isHighFee: Boolean = false,
|
||||
)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.domain.yield.supply.models
|
||||
|
||||
data class YieldSupplyRewardBalance(
|
||||
val fiatBalance: String?,
|
||||
val cryptoBalance: String?,
|
||||
) {
|
||||
companion object {
|
||||
fun empty() = YieldSupplyRewardBalance(null, null)
|
||||
}
|
||||
}
|
||||
|
|
@ -108,4 +108,8 @@ interface YieldSupplyRepository {
|
|||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
): YieldSupplyEnterStatus?
|
||||
|
||||
fun getShouldShowYieldPromoBanner(): Flow<Boolean>
|
||||
|
||||
suspend fun setShouldShowYieldPromoBanner(shouldShow: Boolean)
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.domain.yield.supply.usecase
|
|||
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Emits a map of APY values per token.
|
||||
|
|
@ -15,11 +16,11 @@ class YieldSupplyApyFlowUseCase(
|
|||
private val yieldSupplyRepository: YieldSupplyRepository,
|
||||
) {
|
||||
|
||||
operator fun invoke(): Flow<Map<String, String>> {
|
||||
operator fun invoke(): Flow<Map<String, BigDecimal>> {
|
||||
return yieldSupplyRepository.getMarketsFlow()
|
||||
.map { yieldMarketTokenList ->
|
||||
yieldMarketTokenList.filter { it.isActive }.associate { token ->
|
||||
token.yieldSupplyKey to token.apy.toString()
|
||||
token.yieldSupplyKey to token.apy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package com.tangem.domain.yield.supply.usecase
|
|||
|
||||
import arrow.core.Either
|
||||
import arrow.core.Either.Companion.catch
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.quote.QuoteStatus
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
|
@ -10,6 +12,7 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository
|
|||
import com.tangem.domain.transaction.FeeRepository
|
||||
import com.tangem.domain.yield.supply.YieldSupplyConst.YIELD_SUPPLY_EVM_CONSTANT_GAS_LIMIT
|
||||
import com.tangem.domain.yield.supply.fixFee
|
||||
import com.tangem.domain.yield.supply.models.YieldSupplyFee
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
|
||||
|
|
@ -25,7 +28,7 @@ class YieldSupplyGetCurrentFeeUseCase(
|
|||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
): Either<Throwable, BigDecimal> = catch {
|
||||
): Either<Throwable, YieldSupplyFee> = catch {
|
||||
val feeWithoutGas = feeRepository.getEthereumFeeWithoutGas(userWalletId, cryptoCurrencyStatus.currency)
|
||||
|
||||
val fiatRate = cryptoCurrencyStatus.value.fiatRate ?: error("Fiat rate is missing")
|
||||
|
|
@ -60,6 +63,23 @@ class YieldSupplyGetCurrentFeeUseCase(
|
|||
|
||||
val tokenValue = rateRatio.multiply(nativeGas.amount.value)
|
||||
|
||||
tokenValue.stripTrailingZeros()
|
||||
val isEthereum = cryptoCurrencyStatus.currency
|
||||
.network.id.rawId.value == Blockchain.Ethereum.id
|
||||
|
||||
val isHighFee = if (isEthereum) {
|
||||
val maxFeePerGas = (feeWithoutGas as? Fee.Ethereum.EIP1559)?.maxFeePerGas ?: 0.toBigInteger()
|
||||
maxFeePerGas >= HIGH_ETHEREUM_FEE
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
YieldSupplyFee(
|
||||
value = tokenValue.stripTrailingZeros(),
|
||||
isHighFee = isHighFee,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val HIGH_ETHEREUM_FEE = 400_000_000.toBigInteger()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.tangem.domain.yield.supply.usecase
|
||||
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Use case for getting dust minimum amount for yield supply.
|
||||
*
|
||||
* Returns the minimum amount based on the selected [AppCurrency].
|
||||
* If [AppCurrency] is in [SUPPORTED_DUST_CURRENCIES], returns [DUST_MIN_AMOUNT],
|
||||
* otherwise returns the original [minAmount] with trailing zeros stripped.
|
||||
*/
|
||||
class YieldSupplyGetDustMinAmountUseCase {
|
||||
|
||||
operator fun invoke(
|
||||
minAmountTokenCurrency: BigDecimal,
|
||||
appCurrency: AppCurrency,
|
||||
tokenCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
): BigDecimal {
|
||||
return if (appCurrency.code in SUPPORTED_DUST_CURRENCIES) {
|
||||
DUST_MIN_AMOUNT
|
||||
} else {
|
||||
minAmountTokenCurrency.multiply(tokenCryptoCurrencyStatus.value.fiatRate)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DUST_MIN_AMOUNT = BigDecimal("0.1")
|
||||
private val SUPPORTED_DUST_CURRENCIES = setOf("EUR", "USD", "AUD", "CAD", "GBP")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,13 @@
|
|||
package com.tangem.domain.yield.supply.usecase
|
||||
|
||||
import com.tangem.core.ui.format.bigdecimal.anyDecimals
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.yield.supply.models.YieldSupplyRewardBalance
|
||||
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.delay
|
||||
|
|
@ -22,19 +24,18 @@ class YieldSupplyGetRewardsBalanceUseCase(
|
|||
private val dispatcherProvider: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
operator fun invoke(status: CryptoCurrencyStatus, appCurrency: AppCurrency): Flow<String> = flow {
|
||||
operator fun invoke(status: CryptoCurrencyStatus, appCurrency: AppCurrency): Flow<YieldSupplyRewardBalance> = flow {
|
||||
val cryptoAmount = status.value.amount
|
||||
val fiatRate = status.value.fiatRate
|
||||
|
||||
if (cryptoAmount?.compareTo(BigDecimal.ZERO) == 0) {
|
||||
emit(
|
||||
YieldSupplyRewardBalance(fiatBalance = null, cryptoBalance = null),
|
||||
)
|
||||
return@flow
|
||||
}
|
||||
|
||||
val amount = if (cryptoAmount != null && fiatRate != null) {
|
||||
cryptoAmount.multiply(fiatRate)
|
||||
} else {
|
||||
return@flow
|
||||
}
|
||||
if (cryptoAmount == null) return@flow
|
||||
|
||||
val tokenAddress = (status.currency as? CryptoCurrency.Token)?.contractAddress ?: return@flow
|
||||
val apy = try {
|
||||
|
|
@ -50,37 +51,57 @@ class YieldSupplyGetRewardsBalanceUseCase(
|
|||
return@flow
|
||||
}
|
||||
|
||||
val initialPerTickDelta = amount
|
||||
.multiply(apyFraction)
|
||||
.multiply(TICK_SECONDS_BD)
|
||||
.divide(SECONDS_PER_YEAR_BD, SCALE, RoundingMode.HALF_UP)
|
||||
.abs()
|
||||
val initialPerTickDeltaCrypto = perTickDelta(cryptoAmount, apyFraction).abs()
|
||||
|
||||
val minVisibleDecimals = calculateMinVisibleDecimals(initialPerTickDelta)
|
||||
val minVisibleDecimalsCrypto = calculateMinVisibleDecimals(
|
||||
perTickDeltaAbs = initialPerTickDeltaCrypto,
|
||||
maxDecimals = status.currency.decimals,
|
||||
)
|
||||
|
||||
var currentBalance: BigDecimal = amount
|
||||
val fiatAmountStart = fiatRate?.let { cryptoAmount.multiply(it) }
|
||||
val minVisibleDecimalsFiat = fiatAmountStart?.let { amount ->
|
||||
val initialPerTickDeltaFiat = perTickDelta(amount, apyFraction).abs()
|
||||
calculateMinVisibleDecimals(
|
||||
perTickDeltaAbs = initialPerTickDeltaFiat,
|
||||
maxDecimals = FIAT_MAX_DECIMALS,
|
||||
)
|
||||
}
|
||||
|
||||
var currentCryptoBalance: BigDecimal = cryptoAmount
|
||||
var currentFiatBalance: BigDecimal? = fiatAmountStart
|
||||
while (true) {
|
||||
val fiatBalanceFormatted: String? = currentFiatBalance?.format {
|
||||
fiat(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
).anyDecimals(decimals = minVisibleDecimalsFiat ?: FIAT_MIN_DECIMALS)
|
||||
}
|
||||
|
||||
val cryptoBalanceFormatted: String = currentCryptoBalance.format {
|
||||
crypto(status.currency).anyDecimals(
|
||||
maxDecimals = minVisibleDecimalsCrypto,
|
||||
minDecimals = minVisibleDecimalsCrypto,
|
||||
)
|
||||
}
|
||||
|
||||
emit(
|
||||
currentBalance.format {
|
||||
fiat(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
).anyDecimals(decimals = minVisibleDecimals)
|
||||
},
|
||||
YieldSupplyRewardBalance(fiatBalance = fiatBalanceFormatted, cryptoBalance = cryptoBalanceFormatted),
|
||||
)
|
||||
|
||||
val perTickDelta = currentBalance
|
||||
.multiply(apyFraction)
|
||||
.multiply(TICK_SECONDS_BD)
|
||||
.divide(SECONDS_PER_YEAR_BD, SCALE, RoundingMode.HALF_UP)
|
||||
val perTickDeltaCrypto = perTickDelta(currentCryptoBalance, apyFraction)
|
||||
|
||||
currentBalance = currentBalance.add(perTickDelta)
|
||||
currentCryptoBalance = currentCryptoBalance.add(perTickDeltaCrypto)
|
||||
|
||||
currentFiatBalance = currentFiatBalance?.let { current ->
|
||||
val perTickDeltaFiat = perTickDelta(current, apyFraction)
|
||||
current.add(perTickDeltaFiat)
|
||||
}
|
||||
|
||||
delay(TICK_MILLIS)
|
||||
}
|
||||
}.flowOn(dispatcherProvider.default)
|
||||
|
||||
private fun calculateMinVisibleDecimals(perTickDeltaAbs: BigDecimal): Int {
|
||||
private fun calculateMinVisibleDecimals(perTickDeltaAbs: BigDecimal, maxDecimals: Int): Int {
|
||||
if (perTickDeltaAbs <= BigDecimal.ZERO) return MIN_DECIMALS
|
||||
|
||||
val perTickAsDouble = perTickDeltaAbs.toDouble()
|
||||
|
|
@ -88,20 +109,28 @@ class YieldSupplyGetRewardsBalanceUseCase(
|
|||
|
||||
val safe = if (perTickAsDouble <= 0.0) EPSILON else perTickAsDouble
|
||||
val raw = ceil(-ln(safe) / LN_10)
|
||||
return raw.toInt().coerceIn(MIN_DECIMALS, MAX_DECIMALS)
|
||||
return raw.toInt().coerceIn(MIN_DECIMALS, maxDecimals)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val TICK_MILLIS: Long = 300
|
||||
private val TICK_SECONDS_BD = BigDecimal("0.3")
|
||||
private val SECONDS_PER_YEAR_BD = BigDecimal("31536000") // 365 * 24 * 60 * 60
|
||||
private val HUNDRED_BD = BigDecimal("100")
|
||||
private const val SCALE = 18
|
||||
private fun perTickDelta(amount: BigDecimal, apyFraction: BigDecimal): BigDecimal {
|
||||
return amount
|
||||
.multiply(apyFraction)
|
||||
.multiply(TICK_SECONDS_BD)
|
||||
.divide(SECONDS_PER_YEAR_BD, SCALE, RoundingMode.HALF_UP)
|
||||
}
|
||||
|
||||
private const val MIN_DECIMALS = 3
|
||||
private const val MAX_DECIMALS = 12
|
||||
companion object {
|
||||
internal const val TICK_MILLIS: Long = 800
|
||||
internal val TICK_SECONDS_BD: BigDecimal = BigDecimal("0.8")
|
||||
internal val SECONDS_PER_YEAR_BD: BigDecimal = BigDecimal("31536000") // 365 * 24 * 60 * 60
|
||||
internal val HUNDRED_BD: BigDecimal = BigDecimal("100")
|
||||
internal const val SCALE: Int = 18
|
||||
|
||||
private val LN_10 = ln(10.0)
|
||||
private const val EPSILON = 1e-18
|
||||
internal const val MIN_DECIMALS: Int = 3
|
||||
internal const val FIAT_MIN_DECIMALS: Int = 2
|
||||
internal const val FIAT_MAX_DECIMALS: Int = 12
|
||||
|
||||
internal val LN_10: Double = ln(10.0)
|
||||
internal const val EPSILON: Double = 1e-18
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.domain.yield.supply.usecase
|
||||
|
||||
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class YieldSupplyGetShouldShowMainPromoUseCase(
|
||||
private val yieldSupplyRepository: YieldSupplyRepository,
|
||||
) {
|
||||
|
||||
operator fun invoke(): Flow<Boolean> {
|
||||
return yieldSupplyRepository.getShouldShowYieldPromoBanner()
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,14 @@ import com.tangem.domain.yield.supply.fixFee
|
|||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
|
||||
/**
|
||||
* Use case that calculates the minimum amount required for yield supply operations.
|
||||
*
|
||||
* The calculation is based on the estimated transaction fee converted to the token currency,
|
||||
* with a buffer multiplier applied to account for fee fluctuations.
|
||||
*
|
||||
* @return [BigDecimal] minimum amount in token currency (not native/network currency)
|
||||
*/
|
||||
class YieldSupplyMinAmountUseCase(
|
||||
private val feeRepository: FeeRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.domain.yield.supply.usecase
|
||||
|
||||
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||
|
||||
class YieldSupplySetShouldShowMainPromoUseCase(
|
||||
private val yieldSupplyRepository: YieldSupplyRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(shouldShow: Boolean) {
|
||||
yieldSupplyRepository.setShouldShowYieldPromoBanner(shouldShow)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,384 @@
|
|||
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.models.StatusSource
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
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.quote.QuoteStatus
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.quotes.QuotesRepository
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.transaction.FeeRepository
|
||||
import com.tangem.domain.utils.convertToSdkAmount
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
import java.math.RoundingMode
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class YieldSupplyGetCurrentFeeUseCaseTest {
|
||||
|
||||
private val feeRepository: FeeRepository = mockk()
|
||||
private val quotesRepository: QuotesRepository = mockk()
|
||||
private val currenciesRepository: CurrenciesRepository = mockk()
|
||||
|
||||
private lateinit var useCase: YieldSupplyGetCurrentFeeUseCase
|
||||
|
||||
private val userWalletId = UserWalletId("abcdef012345")
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
useCase = YieldSupplyGetCurrentFeeUseCase(
|
||||
feeRepository = feeRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
currenciesRepository = currenciesRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN valid inputs on non-ethereum WHEN invoke THEN returns fee value and not high`() = runTest {
|
||||
val rawNetworkId = Blockchain.BSC.id
|
||||
val tokenDecimals = 8
|
||||
val nativeDecimals = 18
|
||||
val token = createToken(rawNetworkId = rawNetworkId, decimals = tokenDecimals)
|
||||
val cryptoStatus = createStatus(token = token, fiatRate = BigDecimal("2.00"))
|
||||
val nativeCoin = createCoin(rawNetworkId = rawNetworkId, decimals = nativeDecimals)
|
||||
val nativeFiatRate = BigDecimal("4.00")
|
||||
|
||||
val maxFeePerGas = BigInteger.valueOf(1_000_000_000L)
|
||||
val feeWithoutGas = Fee.Ethereum.EIP1559(
|
||||
maxFeePerGas = maxFeePerGas,
|
||||
priorityFee = BigInteger.ONE,
|
||||
gasLimit = BigInteger.ZERO,
|
||||
amount = BigDecimal.ZERO.convertToSdkAmount(cryptoStatus),
|
||||
)
|
||||
|
||||
coEvery { feeRepository.getEthereumFeeWithoutGas(userWalletId, token) } returns feeWithoutGas
|
||||
coEvery {
|
||||
currenciesRepository.getNetworkCoin(
|
||||
userWalletId = userWalletId,
|
||||
networkId = token.network.id,
|
||||
derivationPath = token.network.derivationPath,
|
||||
)
|
||||
} returns nativeCoin
|
||||
coEvery {
|
||||
quotesRepository.getMultiQuoteSyncOrNull(setOf(nativeCoin.id.rawCurrencyId!!))
|
||||
} returns setOf(
|
||||
QuoteStatus(
|
||||
rawCurrencyId = nativeCoin.id.rawCurrencyId!!,
|
||||
value = QuoteStatus.Data(
|
||||
source = StatusSource.ACTUAL,
|
||||
fiatRate = nativeFiatRate,
|
||||
priceChange = BigDecimal.ZERO,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
val gasLimit = BigInteger.valueOf(350_000)
|
||||
val nativeGasValue = maxFeePerGas.multiply(gasLimit).toBigDecimal().movePointLeft(nativeDecimals)
|
||||
val rateRatio = nativeFiatRate.divide(cryptoStatus.value.fiatRate!!, tokenDecimals, RoundingMode.HALF_UP)
|
||||
val expectedTokenValue = rateRatio.multiply(nativeGasValue).stripTrailingZeros()
|
||||
|
||||
val result = useCase(userWalletId, cryptoStatus)
|
||||
|
||||
assertThat(result.isRight()).isTrue()
|
||||
val fee = (result as Either.Right).value
|
||||
assertThat(fee.value).isEqualTo(expectedTokenValue)
|
||||
assertThat(fee.isHighFee).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN ethereum with high gas WHEN invoke THEN returns high fee flag`() = runTest {
|
||||
val rawNetworkId = Blockchain.Ethereum.id
|
||||
val tokenDecimals = 8
|
||||
val nativeDecimals = 18
|
||||
val token = createToken(rawNetworkId = rawNetworkId, decimals = tokenDecimals)
|
||||
val cryptoStatus = createStatus(token = token, fiatRate = BigDecimal("2.00"))
|
||||
val nativeCoin = createCoin(rawNetworkId = rawNetworkId, decimals = nativeDecimals)
|
||||
val nativeFiatRate = BigDecimal("4.00")
|
||||
|
||||
val maxFeePerGas = BigInteger.valueOf(400_000_000L) // threshold
|
||||
val feeWithoutGas = Fee.Ethereum.EIP1559(
|
||||
maxFeePerGas = maxFeePerGas,
|
||||
priorityFee = BigInteger.ONE,
|
||||
gasLimit = BigInteger.ZERO,
|
||||
amount = BigDecimal.ZERO.convertToSdkAmount(cryptoStatus),
|
||||
)
|
||||
|
||||
coEvery { feeRepository.getEthereumFeeWithoutGas(userWalletId, token) } returns feeWithoutGas
|
||||
coEvery {
|
||||
currenciesRepository.getNetworkCoin(
|
||||
userWalletId = userWalletId,
|
||||
networkId = token.network.id,
|
||||
derivationPath = token.network.derivationPath,
|
||||
)
|
||||
} returns nativeCoin
|
||||
coEvery {
|
||||
quotesRepository.getMultiQuoteSyncOrNull(setOf(nativeCoin.id.rawCurrencyId!!))
|
||||
} returns setOf(
|
||||
QuoteStatus(
|
||||
rawCurrencyId = nativeCoin.id.rawCurrencyId!!,
|
||||
value = QuoteStatus.Data(
|
||||
source = StatusSource.ACTUAL,
|
||||
fiatRate = nativeFiatRate,
|
||||
priceChange = BigDecimal.ZERO,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
val gasLimit = BigInteger.valueOf(350_000)
|
||||
val nativeGasValue = maxFeePerGas.multiply(gasLimit).toBigDecimal().movePointLeft(nativeDecimals)
|
||||
val rateRatio = nativeFiatRate.divide(cryptoStatus.value.fiatRate!!, tokenDecimals, RoundingMode.HALF_UP)
|
||||
val expectedTokenValue = rateRatio.multiply(nativeGasValue).stripTrailingZeros()
|
||||
|
||||
val result = useCase(userWalletId, cryptoStatus)
|
||||
|
||||
assertThat(result.isRight()).isTrue()
|
||||
val fee = (result as Either.Right).value
|
||||
assertThat(fee.value).isEqualTo(expectedTokenValue)
|
||||
assertThat(fee.isHighFee).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN token fiat rate missing WHEN invoke THEN returns error`() = runTest {
|
||||
val rawNetworkId = Blockchain.BSC.id
|
||||
val token = createToken(rawNetworkId = rawNetworkId, decimals = 8)
|
||||
val cryptoStatus = createStatus(token = token, fiatRate = null)
|
||||
|
||||
coEvery { feeRepository.getEthereumFeeWithoutGas(userWalletId, token) } returns Fee.Ethereum.EIP1559(
|
||||
maxFeePerGas = BigInteger.ONE,
|
||||
priorityFee = BigInteger.ONE,
|
||||
gasLimit = BigInteger.ZERO,
|
||||
amount = BigDecimal.ZERO.convertToSdkAmount(
|
||||
createStatus(token = token, fiatRate = BigDecimal.ONE),
|
||||
),
|
||||
)
|
||||
|
||||
val result = useCase(userWalletId, cryptoStatus)
|
||||
|
||||
assertThat(result.isLeft()).isTrue()
|
||||
val error = (result as Either.Left).value
|
||||
assertThat(error).isInstanceOf(IllegalStateException::class.java)
|
||||
assertThat(error.message).isEqualTo("Fiat rate is missing")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN native quotes unavailable WHEN invoke THEN returns error`() = runTest {
|
||||
val rawNetworkId = Blockchain.BSC.id
|
||||
val token = createToken(rawNetworkId = rawNetworkId, decimals = 8)
|
||||
val cryptoStatus = createStatus(token = token, fiatRate = BigDecimal("2.00"))
|
||||
val nativeCoin = createCoin(rawNetworkId = rawNetworkId, decimals = 18)
|
||||
|
||||
coEvery { feeRepository.getEthereumFeeWithoutGas(userWalletId, token) } returns Fee.Ethereum.EIP1559(
|
||||
maxFeePerGas = BigInteger.valueOf(1_000_000_000L),
|
||||
priorityFee = BigInteger.ONE,
|
||||
gasLimit = BigInteger.ZERO,
|
||||
amount = BigDecimal.ZERO.convertToSdkAmount(cryptoStatus),
|
||||
)
|
||||
coEvery {
|
||||
currenciesRepository.getNetworkCoin(
|
||||
userWalletId = userWalletId,
|
||||
networkId = token.network.id,
|
||||
derivationPath = token.network.derivationPath,
|
||||
)
|
||||
} returns nativeCoin
|
||||
coEvery { quotesRepository.getMultiQuoteSyncOrNull(setOf(nativeCoin.id.rawCurrencyId!!)) } returns null
|
||||
|
||||
val result = useCase(userWalletId, cryptoStatus)
|
||||
|
||||
assertThat(result.isLeft()).isTrue()
|
||||
val error = (result as Either.Left).value
|
||||
assertThat(error).isInstanceOf(IllegalStateException::class.java)
|
||||
assertThat(error.message).isEqualTo("Quotes for native coin are unavailable")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN empty quotes list WHEN invoke THEN returns error`() = runTest {
|
||||
val rawNetworkId = Blockchain.BSC.id
|
||||
val token = createToken(rawNetworkId = rawNetworkId, decimals = 8)
|
||||
val cryptoStatus = createStatus(token = token, fiatRate = BigDecimal("2.00"))
|
||||
val nativeCoin = createCoin(rawNetworkId = rawNetworkId, decimals = 18)
|
||||
|
||||
coEvery { feeRepository.getEthereumFeeWithoutGas(userWalletId, token) } returns Fee.Ethereum.EIP1559(
|
||||
maxFeePerGas = BigInteger.valueOf(1_000_000_000L),
|
||||
priorityFee = BigInteger.ONE,
|
||||
gasLimit = BigInteger.ZERO,
|
||||
amount = BigDecimal.ZERO.convertToSdkAmount(cryptoStatus),
|
||||
)
|
||||
coEvery {
|
||||
currenciesRepository.getNetworkCoin(
|
||||
userWalletId = userWalletId,
|
||||
networkId = token.network.id,
|
||||
derivationPath = token.network.derivationPath,
|
||||
)
|
||||
} returns nativeCoin
|
||||
coEvery { quotesRepository.getMultiQuoteSyncOrNull(setOf(nativeCoin.id.rawCurrencyId!!)) } returns emptySet()
|
||||
|
||||
val result = useCase(userWalletId, cryptoStatus)
|
||||
|
||||
assertThat(result.isLeft()).isTrue()
|
||||
val error = (result as Either.Left).value
|
||||
assertThat(error).isInstanceOf(IllegalStateException::class.java)
|
||||
assertThat(error.message).isEqualTo("Empty quotes list for native coin")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN native fiat rate non-positive WHEN invoke THEN returns error`() = runTest {
|
||||
val rawNetworkId = Blockchain.BSC.id
|
||||
val token = createToken(rawNetworkId = rawNetworkId, decimals = 8)
|
||||
val cryptoStatus = createStatus(token = token, fiatRate = BigDecimal("2.00"))
|
||||
val nativeCoin = createCoin(rawNetworkId = rawNetworkId, decimals = 18)
|
||||
|
||||
coEvery { feeRepository.getEthereumFeeWithoutGas(userWalletId, token) } returns Fee.Ethereum.EIP1559(
|
||||
maxFeePerGas = BigInteger.valueOf(1_000_000_000L),
|
||||
priorityFee = BigInteger.ONE,
|
||||
gasLimit = BigInteger.ZERO,
|
||||
amount = BigDecimal.ZERO.convertToSdkAmount(cryptoStatus),
|
||||
)
|
||||
coEvery {
|
||||
currenciesRepository.getNetworkCoin(
|
||||
userWalletId = userWalletId,
|
||||
networkId = token.network.id,
|
||||
derivationPath = token.network.derivationPath,
|
||||
)
|
||||
} returns nativeCoin
|
||||
coEvery {
|
||||
quotesRepository.getMultiQuoteSyncOrNull(setOf(nativeCoin.id.rawCurrencyId!!))
|
||||
} returns setOf(
|
||||
QuoteStatus(
|
||||
rawCurrencyId = nativeCoin.id.rawCurrencyId!!,
|
||||
value = QuoteStatus.Data(
|
||||
source = StatusSource.ACTUAL,
|
||||
fiatRate = BigDecimal.ZERO, // non-positive
|
||||
priceChange = BigDecimal.ZERO,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
val result = useCase(userWalletId, cryptoStatus)
|
||||
|
||||
assertThat(result.isLeft()).isTrue()
|
||||
val error = (result as Either.Left).value
|
||||
assertThat(error).isInstanceOf(IllegalArgumentException::class.java)
|
||||
assertThat(error.message).isEqualTo("Native fiat rate must be > 0")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN token fiat rate non-positive WHEN invoke THEN returns error`() = runTest {
|
||||
val rawNetworkId = Blockchain.BSC.id
|
||||
val token = createToken(rawNetworkId = rawNetworkId, decimals = 8)
|
||||
val cryptoStatus = createStatus(token = token, fiatRate = BigDecimal.ZERO) // non-positive
|
||||
|
||||
coEvery { feeRepository.getEthereumFeeWithoutGas(userWalletId, token) } returns Fee.Ethereum.EIP1559(
|
||||
maxFeePerGas = BigInteger.ONE,
|
||||
priorityFee = BigInteger.ONE,
|
||||
gasLimit = BigInteger.ZERO,
|
||||
amount = BigDecimal.ZERO.convertToSdkAmount(
|
||||
createStatus(token = token, fiatRate = BigDecimal.ONE),
|
||||
),
|
||||
)
|
||||
|
||||
val result = useCase(userWalletId, cryptoStatus)
|
||||
|
||||
assertThat(result.isLeft()).isTrue()
|
||||
val error = (result as Either.Left).value
|
||||
assertThat(error).isInstanceOf(IllegalArgumentException::class.java)
|
||||
assertThat(error.message).isEqualTo("Fiat rate for token must be > 0")
|
||||
}
|
||||
|
||||
private fun createToken(rawNetworkId: String, decimals: Int): CryptoCurrency.Token {
|
||||
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,
|
||||
isTestnet = false,
|
||||
standardType = Network.StandardType.Unspecified("UNSPECIFIED"),
|
||||
hasFiatFeeRate = true,
|
||||
canHandleTokens = true,
|
||||
transactionExtrasType = Network.TransactionExtrasType.NONE,
|
||||
nameResolvingType = Network.NameResolvingType.NONE,
|
||||
)
|
||||
|
||||
return CryptoCurrency.Token(
|
||||
id = CryptoCurrency.ID(
|
||||
prefix = CryptoCurrency.ID.Prefix.TOKEN_PREFIX,
|
||||
body = CryptoCurrency.ID.Body.NetworkId(rawNetworkId),
|
||||
suffix = CryptoCurrency.ID.Suffix.RawID(rawNetworkId),
|
||||
),
|
||||
network = network,
|
||||
name = "TEST_TOKEN",
|
||||
symbol = "TTK",
|
||||
decimals = decimals,
|
||||
iconUrl = null,
|
||||
isCustom = false,
|
||||
contractAddress = "0xToken",
|
||||
)
|
||||
}
|
||||
|
||||
private fun createCoin(rawNetworkId: String, decimals: Int): CryptoCurrency.Coin {
|
||||
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,
|
||||
isTestnet = false,
|
||||
standardType = Network.StandardType.Unspecified("UNSPECIFIED"),
|
||||
hasFiatFeeRate = true,
|
||||
canHandleTokens = true,
|
||||
transactionExtrasType = Network.TransactionExtrasType.NONE,
|
||||
nameResolvingType = Network.NameResolvingType.NONE,
|
||||
)
|
||||
|
||||
return CryptoCurrency.Coin(
|
||||
id = CryptoCurrency.ID(
|
||||
prefix = CryptoCurrency.ID.Prefix.COIN_PREFIX,
|
||||
body = CryptoCurrency.ID.Body.NetworkId(rawNetworkId),
|
||||
suffix = CryptoCurrency.ID.Suffix.RawID(rawNetworkId),
|
||||
),
|
||||
network = network,
|
||||
name = "TEST_COIN",
|
||||
symbol = "TCN",
|
||||
decimals = decimals,
|
||||
iconUrl = null,
|
||||
isCustom = false,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createStatus(token: CryptoCurrency.Token, fiatRate: BigDecimal?): CryptoCurrencyStatus {
|
||||
return CryptoCurrencyStatus(
|
||||
currency = token,
|
||||
value = CryptoCurrencyStatus.Custom(
|
||||
amount = BigDecimal.ZERO,
|
||||
fiatAmount = BigDecimal.ZERO,
|
||||
fiatRate = fiatRate,
|
||||
priceChange = BigDecimal.ZERO,
|
||||
yieldBalance = null,
|
||||
yieldSupplyStatus = null,
|
||||
hasCurrentNetworkTransactions = false,
|
||||
pendingTransactions = emptySet(),
|
||||
networkAddress = NetworkAddress.Single(
|
||||
defaultAddress = NetworkAddress.Address(
|
||||
value = "0x0000000000000000000000000000000000000000",
|
||||
type = NetworkAddress.Address.Type.Primary,
|
||||
),
|
||||
),
|
||||
sources = CryptoCurrencyStatus.Sources(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
package com.tangem.domain.yield.supply.usecase
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.network.NetworkAddress
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.math.BigDecimal
|
||||
|
||||
class YieldSupplyGetDustMinAmountUseCaseTest {
|
||||
|
||||
private val useCase = YieldSupplyGetDustMinAmountUseCase()
|
||||
|
||||
@Test
|
||||
fun `GIVEN supported currency WHEN invoke THEN return dust min amount`() {
|
||||
val minAmount = BigDecimal("123.456")
|
||||
val appCurrency = AppCurrency(code = "EUR", name = "Euro", symbol = "€")
|
||||
val tokenStatus = createTokenStatus(fiatRate = BigDecimal("2.0"))
|
||||
|
||||
val result = useCase(minAmount, appCurrency, tokenStatus)
|
||||
|
||||
assertThat(result).isEqualTo(BigDecimal("0.1"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN unsupported currency WHEN invoke THEN return min amount multiplied by fiat rate`() {
|
||||
val minAmount = BigDecimal("1.23")
|
||||
val fiatRate = BigDecimal("150.0")
|
||||
val appCurrency = AppCurrency(code = "JPY", name = "Japanese Yen", symbol = "¥")
|
||||
val tokenStatus = createTokenStatus(fiatRate = fiatRate)
|
||||
|
||||
val result = useCase(minAmount, appCurrency, tokenStatus)
|
||||
|
||||
assertThat(result).isEqualTo(minAmount.multiply(fiatRate))
|
||||
}
|
||||
|
||||
private fun createTokenStatus(fiatRate: BigDecimal): CryptoCurrencyStatus {
|
||||
val network = createNetwork()
|
||||
val token = createToken(network)
|
||||
return CryptoCurrencyStatus(
|
||||
currency = token,
|
||||
value = CryptoCurrencyStatus.Loaded(
|
||||
amount = BigDecimal.ZERO,
|
||||
fiatAmount = BigDecimal.ZERO,
|
||||
fiatRate = fiatRate,
|
||||
priceChange = BigDecimal.ZERO,
|
||||
yieldBalance = null,
|
||||
yieldSupplyStatus = null,
|
||||
hasCurrentNetworkTransactions = false,
|
||||
pendingTransactions = emptySet(),
|
||||
networkAddress = NetworkAddress.Single(
|
||||
NetworkAddress.Address(value = "0xabc", type = NetworkAddress.Address.Type.Primary),
|
||||
),
|
||||
sources = CryptoCurrencyStatus.Sources(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createNetwork(): Network {
|
||||
val derivationPath = Network.DerivationPath.None
|
||||
return Network(
|
||||
id = Network.ID(Network.RawID("polygon"), derivationPath),
|
||||
backendId = "polygon",
|
||||
name = "Polygon",
|
||||
currencySymbol = "MATIC",
|
||||
derivationPath = derivationPath,
|
||||
isTestnet = false,
|
||||
standardType = Network.StandardType.ERC20,
|
||||
hasFiatFeeRate = false,
|
||||
canHandleTokens = true,
|
||||
transactionExtrasType = Network.TransactionExtrasType.NONE,
|
||||
nameResolvingType = Network.NameResolvingType.ENS,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createToken(network: Network): CryptoCurrency.Token {
|
||||
val tokenId = CryptoCurrency.ID(
|
||||
prefix = CryptoCurrency.ID.Prefix.TOKEN_PREFIX,
|
||||
body = CryptoCurrency.ID.Body.NetworkId(network.rawId),
|
||||
suffix = CryptoCurrency.ID.Suffix.RawID("test-token", "0xContract"),
|
||||
)
|
||||
return CryptoCurrency.Token(
|
||||
id = tokenId,
|
||||
network = network,
|
||||
name = "Test Token",
|
||||
symbol = "TT",
|
||||
decimals = 18,
|
||||
iconUrl = null,
|
||||
isCustom = false,
|
||||
contractAddress = "0xContract",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import com.google.common.truth.Truth.assertThat
|
|||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.anyDecimals
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
|
|
@ -11,6 +12,7 @@ import com.tangem.domain.models.network.Network
|
|||
import com.tangem.domain.models.network.NetworkAddress
|
||||
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||
import com.tangem.domain.yield.supply.models.YieldMarketToken
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetRewardsBalanceUseCase.Companion.TICK_MILLIS
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.mockk
|
||||
|
|
@ -26,7 +28,6 @@ import org.junit.jupiter.api.Test
|
|||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.ln
|
||||
|
||||
class YieldSupplyGetRewardsBalanceUseCaseTest {
|
||||
|
||||
|
|
@ -48,6 +49,38 @@ class YieldSupplyGetRewardsBalanceUseCaseTest {
|
|||
assertThat(emissions).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN zero amount WHEN invoke THEN emit null balances`() = runTest {
|
||||
val network = createNetwork()
|
||||
val token = createToken(network)
|
||||
val status = CryptoCurrencyStatus(
|
||||
currency = token,
|
||||
value = CryptoCurrencyStatus.Custom(
|
||||
amount = BigDecimal.ZERO,
|
||||
fiatAmount = null,
|
||||
fiatRate = BigDecimal.ONE,
|
||||
priceChange = null,
|
||||
yieldBalance = null,
|
||||
yieldSupplyStatus = null,
|
||||
hasCurrentNetworkTransactions = false,
|
||||
pendingTransactions = emptySet(),
|
||||
networkAddress = NetworkAddress.Single(
|
||||
NetworkAddress.Address(value = "0xabc", type = NetworkAddress.Address.Type.Primary),
|
||||
),
|
||||
sources = CryptoCurrencyStatus.Sources(),
|
||||
),
|
||||
)
|
||||
|
||||
val dispatcherProvider = testDispatcherProvider(this)
|
||||
val useCase = YieldSupplyGetRewardsBalanceUseCase(repository, dispatcherProvider)
|
||||
val appCurrency = AppCurrency.Default
|
||||
|
||||
val emissions = useCase(status, appCurrency).toList()
|
||||
assertThat(emissions).hasSize(1)
|
||||
assertThat(emissions[0].fiatBalance).isNull()
|
||||
assertThat(emissions[0].cryptoBalance).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN coin currency WHEN invoke THEN emit nothing`() = runTest {
|
||||
val network = createNetwork()
|
||||
|
|
@ -165,9 +198,9 @@ class YieldSupplyGetRewardsBalanceUseCaseTest {
|
|||
val deferred = async { useCase(status, appCurrency).take(3).toList() }
|
||||
|
||||
testScheduler.advanceUntilIdle()
|
||||
advanceTimeBy(300)
|
||||
advanceTimeBy(TICK_MILLIS)
|
||||
testScheduler.advanceUntilIdle()
|
||||
advanceTimeBy(300)
|
||||
advanceTimeBy(TICK_MILLIS)
|
||||
testScheduler.advanceUntilIdle()
|
||||
|
||||
val collected = deferred.await()
|
||||
|
|
@ -175,25 +208,147 @@ class YieldSupplyGetRewardsBalanceUseCaseTest {
|
|||
|
||||
val expectedDecimals = calculateMinVisibleDecimalsForTest(initialPerTickDelta(amount, apy))
|
||||
|
||||
val firstExpected = amount.format { fiat(
|
||||
appCurrency.code,
|
||||
appCurrency.symbol,
|
||||
).anyDecimals(decimals = expectedDecimals) }
|
||||
assertThat(collected[0]).isEqualTo(firstExpected)
|
||||
val firstExpected = amount.format {
|
||||
fiat(
|
||||
appCurrency.code,
|
||||
appCurrency.symbol,
|
||||
).anyDecimals(decimals = expectedDecimals)
|
||||
}
|
||||
assertThat(collected[0].fiatBalance).isEqualTo(firstExpected)
|
||||
|
||||
val firstNext = nextBalance(amount, apy)
|
||||
val secondExpected = firstNext.format { fiat(
|
||||
appCurrency.code,
|
||||
appCurrency.symbol,
|
||||
).anyDecimals(decimals = expectedDecimals) }
|
||||
assertThat(collected[1]).isEqualTo(secondExpected)
|
||||
val secondExpected = firstNext.format {
|
||||
fiat(
|
||||
appCurrency.code,
|
||||
appCurrency.symbol,
|
||||
).anyDecimals(decimals = expectedDecimals)
|
||||
}
|
||||
assertThat(collected[1].fiatBalance).isEqualTo(secondExpected)
|
||||
|
||||
val secondNext = nextBalance(firstNext, apy)
|
||||
val thirdExpected = secondNext.format { fiat(
|
||||
appCurrency.code,
|
||||
appCurrency.symbol,
|
||||
).anyDecimals(decimals = expectedDecimals) }
|
||||
assertThat(collected[2]).isEqualTo(thirdExpected)
|
||||
val thirdExpected = secondNext.format {
|
||||
fiat(
|
||||
appCurrency.code,
|
||||
appCurrency.symbol,
|
||||
).anyDecimals(decimals = expectedDecimals)
|
||||
}
|
||||
assertThat(collected[2].fiatBalance).isEqualTo(thirdExpected)
|
||||
}
|
||||
|
||||
@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",
|
||||
name = "Polygon",
|
||||
currencySymbol = "POL",
|
||||
derivationPath = Network.DerivationPath.Card("m/44'/60'/0'/0/0"),
|
||||
isTestnet = false,
|
||||
standardType = Network.StandardType.Unspecified("Polygon"),
|
||||
hasFiatFeeRate = true,
|
||||
canHandleTokens = true,
|
||||
transactionExtrasType = Network.TransactionExtrasType.NONE,
|
||||
nameResolvingType = Network.NameResolvingType.NONE,
|
||||
)
|
||||
val tokenId = CryptoCurrency.ID(
|
||||
prefix = CryptoCurrency.ID.Prefix.TOKEN_PREFIX,
|
||||
body = CryptoCurrency.ID.Body.NetworkId(network.rawId),
|
||||
suffix = CryptoCurrency.ID.Suffix.RawID("usdt0", "0xc2132d05d31c914a87c6611c10748aeb04b58e8f"),
|
||||
)
|
||||
val currency = CryptoCurrency.Token(
|
||||
id = tokenId,
|
||||
network = network,
|
||||
name = "USDT0",
|
||||
symbol = "USDT0",
|
||||
decimals = 6,
|
||||
iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/usdt0.png",
|
||||
isCustom = false,
|
||||
contractAddress = "0xc2132d05d31c914a87c6611c10748aeb04b58e8f",
|
||||
)
|
||||
|
||||
val amount = BigDecimal("9.241136")
|
||||
val fiatRate = BigDecimal("0.9999761277273864")
|
||||
val status = CryptoCurrencyStatus(
|
||||
currency = currency,
|
||||
value = CryptoCurrencyStatus.Loaded(
|
||||
amount = amount,
|
||||
fiatAmount = amount.multiply(fiatRate),
|
||||
fiatRate = fiatRate,
|
||||
priceChange = BigDecimal("-0.000058200000000008245"),
|
||||
yieldBalance = null,
|
||||
yieldSupplyStatus = null,
|
||||
hasCurrentNetworkTransactions = false,
|
||||
pendingTransactions = emptySet(),
|
||||
networkAddress = NetworkAddress.Single(
|
||||
NetworkAddress.Address(
|
||||
value = "0xb71fa0E20ba8579B3ec51cC79aaa84Bf5982BB49",
|
||||
type = NetworkAddress.Address.Type.Primary,
|
||||
),
|
||||
),
|
||||
sources = CryptoCurrencyStatus.Sources(),
|
||||
),
|
||||
)
|
||||
|
||||
val apy = BigDecimal("5.0")
|
||||
coEvery { repository.getCachedMarkets() } returns listOf(
|
||||
YieldMarketToken(
|
||||
tokenAddress = currency.contractAddress,
|
||||
chainId = 137,
|
||||
apy = apy,
|
||||
isActive = true,
|
||||
maxFeeNative = BigDecimal.ZERO,
|
||||
maxFeeUSD = BigDecimal.ZERO,
|
||||
backendId = "polygon-pos",
|
||||
),
|
||||
)
|
||||
|
||||
val dispatcherProvider = testDispatcherProvider(this)
|
||||
val useCase = YieldSupplyGetRewardsBalanceUseCase(repository, dispatcherProvider)
|
||||
val appCurrency = AppCurrency.Default
|
||||
|
||||
val deferred = async { useCase(status, appCurrency).take(2).toList() }
|
||||
|
||||
testScheduler.advanceUntilIdle()
|
||||
advanceTimeBy(TICK_MILLIS)
|
||||
testScheduler.advanceUntilIdle()
|
||||
|
||||
val emissions = deferred.await()
|
||||
assertThat(emissions).hasSize(2)
|
||||
|
||||
val apyFraction = apy.divide(BigDecimal("100"), 18, RoundingMode.HALF_UP)
|
||||
val perTickCrypto = amount.multiply(apyFraction)
|
||||
.multiply(YieldSupplyGetRewardsBalanceUseCase.TICK_SECONDS_BD)
|
||||
.divide(
|
||||
YieldSupplyGetRewardsBalanceUseCase.SECONDS_PER_YEAR_BD,
|
||||
YieldSupplyGetRewardsBalanceUseCase.SCALE,
|
||||
RoundingMode.HALF_UP,
|
||||
)
|
||||
.abs()
|
||||
val minCryptoDecimals = calculateMinVisibleDecimalsForTest(perTickCrypto).coerceAtMost(currency.decimals)
|
||||
|
||||
val fiatAmountStart = amount.multiply(fiatRate)
|
||||
val perTickFiat = fiatAmountStart.multiply(apyFraction)
|
||||
.multiply(YieldSupplyGetRewardsBalanceUseCase.TICK_SECONDS_BD)
|
||||
.divide(
|
||||
YieldSupplyGetRewardsBalanceUseCase.SECONDS_PER_YEAR_BD,
|
||||
YieldSupplyGetRewardsBalanceUseCase.SCALE,
|
||||
RoundingMode.HALF_UP,
|
||||
)
|
||||
.abs()
|
||||
val minFiatDecimals = calculateMinVisibleDecimalsForTest(perTickFiat)
|
||||
|
||||
val expectedCrypto0 = amount.format {
|
||||
crypto(currency).anyDecimals(
|
||||
maxDecimals = minCryptoDecimals,
|
||||
minDecimals = minCryptoDecimals,
|
||||
)
|
||||
}
|
||||
val expectedFiat0 = fiatAmountStart.format {
|
||||
fiat(appCurrency.code, appCurrency.symbol).anyDecimals(decimals = minFiatDecimals)
|
||||
}
|
||||
|
||||
assertThat(emissions[0].cryptoBalance).isEqualTo(expectedCrypto0)
|
||||
assertThat(emissions[0].fiatBalance).isEqualTo(expectedFiat0)
|
||||
}
|
||||
|
||||
private fun testDispatcherProvider(scope: TestScope): CoroutineDispatcherProvider {
|
||||
|
|
@ -260,42 +415,48 @@ class YieldSupplyGetRewardsBalanceUseCaseTest {
|
|||
}
|
||||
|
||||
private fun initialPerTickDelta(amount: BigDecimal, apy: BigDecimal): BigDecimal {
|
||||
val apyFraction = apy.divide(HUNDRED_BD, SCALE, RoundingMode.HALF_UP)
|
||||
val apyFraction = apy.divide(
|
||||
YieldSupplyGetRewardsBalanceUseCase.HUNDRED_BD,
|
||||
YieldSupplyGetRewardsBalanceUseCase.SCALE,
|
||||
RoundingMode.HALF_UP,
|
||||
)
|
||||
return amount
|
||||
.multiply(apyFraction)
|
||||
.multiply(TICK_SECONDS_BD)
|
||||
.divide(SECONDS_PER_YEAR_BD, SCALE, RoundingMode.HALF_UP)
|
||||
.multiply(YieldSupplyGetRewardsBalanceUseCase.TICK_SECONDS_BD)
|
||||
.divide(
|
||||
YieldSupplyGetRewardsBalanceUseCase.SECONDS_PER_YEAR_BD,
|
||||
YieldSupplyGetRewardsBalanceUseCase.SCALE,
|
||||
RoundingMode.HALF_UP,
|
||||
)
|
||||
.abs()
|
||||
}
|
||||
|
||||
private fun nextBalance(current: BigDecimal, apy: BigDecimal): BigDecimal {
|
||||
val apyFraction = apy.divide(HUNDRED_BD, SCALE, RoundingMode.HALF_UP)
|
||||
val apyFraction = apy.divide(
|
||||
YieldSupplyGetRewardsBalanceUseCase.HUNDRED_BD,
|
||||
YieldSupplyGetRewardsBalanceUseCase.SCALE,
|
||||
RoundingMode.HALF_UP,
|
||||
)
|
||||
val perTickDelta = current
|
||||
.multiply(apyFraction)
|
||||
.multiply(TICK_SECONDS_BD)
|
||||
.divide(SECONDS_PER_YEAR_BD, SCALE, RoundingMode.HALF_UP)
|
||||
.multiply(YieldSupplyGetRewardsBalanceUseCase.TICK_SECONDS_BD)
|
||||
.divide(
|
||||
YieldSupplyGetRewardsBalanceUseCase.SECONDS_PER_YEAR_BD,
|
||||
YieldSupplyGetRewardsBalanceUseCase.SCALE,
|
||||
RoundingMode.HALF_UP,
|
||||
)
|
||||
return current.add(perTickDelta)
|
||||
}
|
||||
|
||||
private fun calculateMinVisibleDecimalsForTest(perTickDeltaAbs: BigDecimal): Int {
|
||||
if (perTickDeltaAbs <= BigDecimal.ZERO) return MIN_DECIMALS
|
||||
if (perTickDeltaAbs <= BigDecimal.ZERO) return YieldSupplyGetRewardsBalanceUseCase.MIN_DECIMALS
|
||||
val perTickAsDouble = perTickDeltaAbs.toDouble()
|
||||
if (perTickAsDouble.isNaN() || perTickAsDouble.isInfinite()) return MIN_DECIMALS
|
||||
val safe = if (perTickAsDouble <= 0.0) EPSILON else perTickAsDouble
|
||||
val raw = ceil(-ln(safe) / LN_10)
|
||||
return raw.toInt().coerceIn(MIN_DECIMALS, MAX_DECIMALS)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val SCALE = 18
|
||||
private val TICK_SECONDS_BD = BigDecimal("0.3")
|
||||
private val SECONDS_PER_YEAR_BD = BigDecimal("31536000")
|
||||
private val HUNDRED_BD = BigDecimal("100")
|
||||
|
||||
private const val MIN_DECIMALS = 3
|
||||
private const val MAX_DECIMALS = 8
|
||||
|
||||
private val LN_10 = ln(10.0)
|
||||
private const val EPSILON = 1e-18
|
||||
if (perTickAsDouble.isNaN() || perTickAsDouble.isInfinite()) return YieldSupplyGetRewardsBalanceUseCase.MIN_DECIMALS
|
||||
val safe = if (perTickAsDouble <= 0.0) YieldSupplyGetRewardsBalanceUseCase.EPSILON else perTickAsDouble
|
||||
val raw = ceil(-kotlin.math.ln(safe) / YieldSupplyGetRewardsBalanceUseCase.LN_10)
|
||||
return raw.toInt().coerceIn(
|
||||
YieldSupplyGetRewardsBalanceUseCase.MIN_DECIMALS,
|
||||
YieldSupplyGetRewardsBalanceUseCase.FIAT_MAX_DECIMALS,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -93,6 +93,7 @@ import com.tangem.utils.extensions.isSingleItem
|
|||
import com.tangem.utils.extensions.orZero
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -601,10 +602,20 @@ internal class StakingModel @Inject constructor(
|
|||
|
||||
override fun onActiveStake(activeStake: BalanceState) {
|
||||
val networkId = cryptoCurrencyStatus.currency.network.rawId
|
||||
if (isSingleAction(networkId, activeStake)) {
|
||||
val preferredValidators = yield.validators.filter { it.preferred }
|
||||
val pendingActions = activeStake.pendingActions.mapNotNull { action ->
|
||||
if (action.type in listOf(StakingActionType.RESTAKE, StakingActionType.STAKE) &&
|
||||
preferredValidators.isSingleItem()
|
||||
) {
|
||||
null
|
||||
} else {
|
||||
action
|
||||
}
|
||||
}.toImmutableList()
|
||||
if (isSingleAction(networkId, pendingActions)) {
|
||||
prepareForConfirmation(
|
||||
balanceType = activeStake.type,
|
||||
pendingActions = activeStake.pendingActions,
|
||||
pendingActions = pendingActions,
|
||||
balanceState = activeStake,
|
||||
validator = activeStake.validator,
|
||||
amountValue = activeStake.cryptoValue,
|
||||
|
|
@ -613,7 +624,7 @@ internal class StakingModel @Inject constructor(
|
|||
} else {
|
||||
stateController.update(
|
||||
ShowActionSelectorBottomSheetTransformer(
|
||||
pendingActions = withStubUnstakeAction(networkId, activeStake),
|
||||
pendingActions = withStubUnstakeAction(networkId, pendingActions, activeStake),
|
||||
onActionSelect = { action ->
|
||||
prepareForConfirmation(
|
||||
balanceType = activeStake.type,
|
||||
|
|
|
|||
|
|
@ -291,6 +291,8 @@ internal class AddStakingNotificationsTransformer(
|
|||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.addTonInitializeAccountNotification(prevState: StakingUiState) {
|
||||
if (prevState.actionType !is StakingActionCommonType.Enter) return
|
||||
|
||||
val isAccountInitialized = isAccountInitializedProvider.invoke()
|
||||
val cryptoCurrencyNetworkIdValue = cryptoCurrencyStatusProvider().currency.network.rawId
|
||||
|
||||
|
|
|
|||
|
|
@ -37,17 +37,21 @@ internal fun StakingActionType?.getPendingActionTitle(): TextReference = when (t
|
|||
null -> TextReference.EMPTY
|
||||
}
|
||||
|
||||
internal fun isSingleAction(networkId: String, activeStake: BalanceState): Boolean {
|
||||
val isSingleAction = activeStake.pendingActions.size <= 1 // Either single or none pending actions
|
||||
val isCompositePendingActions = isCompositePendingActions(networkId, activeStake.pendingActions)
|
||||
val isRestake = activeStake.pendingActions.any { it.type.isRestake }
|
||||
internal fun isSingleAction(networkId: String, pendingActions: List<PendingAction>): Boolean {
|
||||
val isSingleAction = pendingActions.size <= 1 // Either single or none pending actions
|
||||
val isCompositePendingActions = isCompositePendingActions(networkId, pendingActions.toPersistentList())
|
||||
val isRestake = pendingActions.any { it.type.isRestake }
|
||||
|
||||
return isSingleAction && !isRestake || isCompositePendingActions
|
||||
}
|
||||
|
||||
internal fun withStubUnstakeAction(networkId: String, activeStake: BalanceState): ImmutableList<PendingAction> {
|
||||
internal fun withStubUnstakeAction(
|
||||
networkId: String,
|
||||
pendingActions: List<PendingAction>,
|
||||
activeStake: BalanceState,
|
||||
): ImmutableList<PendingAction> {
|
||||
return if (isStubUnstakeAction(networkId) && activeStake.type != BalanceType.REWARDS) {
|
||||
activeStake.pendingActions.plus(
|
||||
pendingActions.plus(
|
||||
PendingAction(
|
||||
type = StakingActionType.UNSTAKE,
|
||||
passthrough = "",
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ internal fun getRewardScheduleText(
|
|||
-> getCustomRewardSchedule(
|
||||
networkId = networkId,
|
||||
decapitalize = decapitalize,
|
||||
)
|
||||
) ?: stringReference(rewardSchedule.name.lowercase().capitalize())
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.features.swap.v2.impl.sendviaswap.confirm.model
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplySendCallData
|
||||
import com.tangem.domain.express.models.ExpressError
|
||||
import com.tangem.domain.express.models.ExpressOperationType
|
||||
import com.tangem.domain.express.models.ExpressProvider
|
||||
|
|
@ -152,6 +154,13 @@ internal class SwapTransactionSender @AssistedInject constructor(
|
|||
return
|
||||
}
|
||||
|
||||
val ethereumCallData = (txData.extras as? EthereumTransactionExtras)?.callData
|
||||
val payInAddress = if (ethereumCallData is EthereumYieldSupplySendCallData) {
|
||||
ethereumCallData.destinationAddress
|
||||
} else {
|
||||
txData.destinationAddress
|
||||
}
|
||||
|
||||
sendTransactionUseCase(
|
||||
txData = txData,
|
||||
userWallet = userWallet,
|
||||
|
|
@ -169,6 +178,7 @@ internal class SwapTransactionSender @AssistedInject constructor(
|
|||
swapDataTransactionModel = swapTransaction,
|
||||
provider = provider,
|
||||
txHash = txHash,
|
||||
payInAddress = payInAddress,
|
||||
timestamp = timestamp,
|
||||
swapTxType = SwapTxType.SendWithSwap,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.feature.swap.domain
|
|||
import android.util.Base64
|
||||
import arrow.core.Either
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.solana.SolanaTransactionHelper
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
|
|
@ -11,6 +12,7 @@ import com.tangem.blockchain.common.TransactionExtras
|
|||
import com.tangem.blockchain.common.smartcontract.SmartContractCallDataProviderFactory
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplySendCallData
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
|
|
@ -870,6 +872,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
Timber.e(it, "Failed to create swap dex tx data")
|
||||
return SwapTransactionState.Error.UnknownError
|
||||
}
|
||||
|
||||
return handleSwapResult(
|
||||
provider = provider,
|
||||
networkId = networkId,
|
||||
|
|
@ -881,7 +884,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
amount = amount,
|
||||
derivationPath = derivationPath,
|
||||
txData = txData,
|
||||
payInAddress = txData.destinationAddress,
|
||||
payInAddress = getPayoutAddress(txData),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1087,7 +1090,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
txId = exchangeDataCex.txId,
|
||||
fromNetwork = currencyToSend.currency.network.backendId,
|
||||
fromAddress = currencyToSend.value.networkAddress?.defaultAddress?.value.orEmpty(),
|
||||
payInAddress = txData.destinationAddress,
|
||||
payInAddress = getPayoutAddress(txData),
|
||||
txHash = txHash,
|
||||
payInExtraId = exchangeDataCex.txExtraId,
|
||||
)
|
||||
|
|
@ -2307,6 +2310,15 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getPayoutAddress(txData: TransactionData.Uncompiled): String {
|
||||
val ethereumCallData = (txData.extras as? EthereumTransactionExtras)?.callData
|
||||
return if (ethereumCallData is EthereumYieldSupplySendCallData) {
|
||||
ethereumCallData.destinationAddress
|
||||
} else {
|
||||
txData.destinationAddress
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val INCREASE_GAS_LIMIT_FOR_DEX = 112 // 12%
|
||||
private const val INCREASE_GAS_LIMIT_FOR_SEND = 105 // 5%
|
||||
|
|
|
|||
|
|
@ -43,18 +43,31 @@ sealed class SwapEvents(
|
|||
params = mapOf("Send Token" to sendToken, "Receive Token" to receiveToken),
|
||||
)
|
||||
|
||||
data object ButtonGivePermissionClicked : SwapEvents(event = "Button - Give permission")
|
||||
data class ButtonGivePermissionClicked(
|
||||
val sendToken: String,
|
||||
val receiveToken: String,
|
||||
val provider: SwapProvider,
|
||||
) : SwapEvents(
|
||||
event = "Button - Give permission",
|
||||
params = mapOf(
|
||||
"Send Token" to sendToken,
|
||||
"Receive Token" to receiveToken,
|
||||
"Provider" to provider.name,
|
||||
),
|
||||
)
|
||||
|
||||
data class ButtonPermissionApproveClicked(
|
||||
val sendToken: String,
|
||||
val receiveToken: String,
|
||||
val approveType: ApproveType,
|
||||
val provider: SwapProvider,
|
||||
) : SwapEvents(
|
||||
event = "Button - Permission Approve",
|
||||
params = mapOf(
|
||||
"Send Token" to sendToken,
|
||||
"Receive Token" to receiveToken,
|
||||
"Type" to if (approveType == ApproveType.LIMITED) "Current Transaction" else "Unlimited",
|
||||
"Provider" to provider.name,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -141,4 +154,17 @@ sealed class SwapEvents(
|
|||
data object Recommended : PromoState("Recommended")
|
||||
}
|
||||
}
|
||||
|
||||
data class NoticePermissionNeeded(
|
||||
val sendToken: String,
|
||||
val receiveToken: String,
|
||||
val provider: SwapProvider,
|
||||
) : SwapEvents(
|
||||
event = "Notice - Permission Needed",
|
||||
params = mapOf(
|
||||
"Send Token" to sendToken,
|
||||
"Receive Token" to receiveToken,
|
||||
"Provider" to provider.name,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -189,6 +189,7 @@ internal class SwapModel @Inject constructor(
|
|||
private val toTokenBalanceJobHolder = JobHolder()
|
||||
|
||||
private var isAmountChangedByUser: Boolean = false
|
||||
private var lastPermissionNotificationTokens: Pair<String, String>? = null
|
||||
|
||||
val currentScreen: SwapNavScreen
|
||||
get() = swapRouter.currentScreen
|
||||
|
|
@ -544,60 +545,101 @@ internal class SwapModel @Inject constructor(
|
|||
private fun setupLoadedState(provider: SwapProvider, state: SwapState, fromToken: CryptoCurrencyStatus) {
|
||||
when (state) {
|
||||
is SwapState.QuotesLoadedState -> {
|
||||
fillLoadedDataState(state, state.permissionState, state.swapDataModel)
|
||||
val loadedStates = dataState.lastLoadedSwapStates.getLastLoadedSuccessStates()
|
||||
val bestRatedProviderId = findBestQuoteProvider(loadedStates)?.providerId ?: provider.providerId
|
||||
uiState = stateBuilder.createQuotesLoadedState(
|
||||
uiStateHolder = uiState,
|
||||
quoteModel = state,
|
||||
fromToken = fromToken.currency,
|
||||
feeCryptoCurrencyStatus = dataState.feePaidCryptoCurrency,
|
||||
swapProvider = provider,
|
||||
bestRatedProviderId = bestRatedProviderId,
|
||||
isNeedBestRateBadge = dataState.lastLoadedSwapStates.consideredProvidersStates().size > 1,
|
||||
selectedFeeType = dataState.selectedFee?.feeType ?: FeeType.NORMAL,
|
||||
isReverseSwapPossible = isReverseSwapPossible(),
|
||||
needApplyFCARestrictions = userCountry.needApplyFCARestrictions(),
|
||||
hideFee = tangemPayInput?.isWithdrawal == true,
|
||||
)
|
||||
if (uiState.notifications.any { it is SwapNotificationUM.Error.UnableToCoverFeeWarning }) {
|
||||
analyticsEventHandler.send(
|
||||
SwapEvents.NoticeNotEnoughFee(
|
||||
token = initialCurrencyFrom.symbol,
|
||||
blockchain = fromToken.currency.network.name,
|
||||
),
|
||||
)
|
||||
}
|
||||
setupQuotesLoadedUiState(provider, state, fromToken)
|
||||
sendAnalyticsForNotifications(fromToken)
|
||||
updatePermissionNotificationState(state)
|
||||
}
|
||||
is SwapState.EmptyAmountState -> {
|
||||
val toTokenStatus = dataState.toCryptoCurrency
|
||||
uiState = stateBuilder.createQuotesEmptyAmountState(
|
||||
uiStateHolder = uiState,
|
||||
emptyAmountState = state,
|
||||
fromTokenStatus = fromToken,
|
||||
toTokenStatus = toTokenStatus,
|
||||
isReverseSwapPossible = isReverseSwapPossible(),
|
||||
toAccount = dataState.toAccount,
|
||||
)
|
||||
setupEmptyAmountUiState(state, fromToken)
|
||||
lastPermissionNotificationTokens = null
|
||||
}
|
||||
is SwapState.SwapError -> {
|
||||
singleTaskScheduler.cancelTask()
|
||||
uiState = stateBuilder.createQuotesErrorState(
|
||||
uiStateHolder = uiState,
|
||||
swapProvider = provider,
|
||||
fromToken = state.fromTokenInfo,
|
||||
toToken = dataState.toCryptoCurrency,
|
||||
expressDataError = state.error,
|
||||
includeFeeInAmount = state.includeFeeInAmount,
|
||||
isReverseSwapPossible = isReverseSwapPossible(),
|
||||
needApplyFCARestrictions = userCountry.needApplyFCARestrictions(),
|
||||
toAccount = dataState.toAccount,
|
||||
)
|
||||
sendErrorAnalyticsEvent(state.error, provider)
|
||||
setupErrorUiState(provider, state)
|
||||
lastPermissionNotificationTokens = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupQuotesLoadedUiState(
|
||||
provider: SwapProvider,
|
||||
state: SwapState.QuotesLoadedState,
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
) {
|
||||
fillLoadedDataState(state, state.permissionState, state.swapDataModel)
|
||||
val loadedStates = dataState.lastLoadedSwapStates.getLastLoadedSuccessStates()
|
||||
val bestRatedProviderId = findBestQuoteProvider(loadedStates)?.providerId ?: provider.providerId
|
||||
uiState = stateBuilder.createQuotesLoadedState(
|
||||
uiStateHolder = uiState,
|
||||
quoteModel = state,
|
||||
fromToken = fromToken.currency,
|
||||
feeCryptoCurrencyStatus = dataState.feePaidCryptoCurrency,
|
||||
swapProvider = provider,
|
||||
bestRatedProviderId = bestRatedProviderId,
|
||||
isNeedBestRateBadge = dataState.lastLoadedSwapStates.consideredProvidersStates().size > 1,
|
||||
selectedFeeType = dataState.selectedFee?.feeType ?: FeeType.NORMAL,
|
||||
isReverseSwapPossible = isReverseSwapPossible(),
|
||||
needApplyFCARestrictions = userCountry.needApplyFCARestrictions(),
|
||||
hideFee = tangemPayInput?.isWithdrawal == true,
|
||||
)
|
||||
}
|
||||
|
||||
private fun sendAnalyticsForNotifications(fromToken: CryptoCurrencyStatus) {
|
||||
if (uiState.notifications.any { it is SwapNotificationUM.Error.UnableToCoverFeeWarning }) {
|
||||
analyticsEventHandler.send(
|
||||
SwapEvents.NoticeNotEnoughFee(
|
||||
token = initialCurrencyFrom.symbol,
|
||||
blockchain = fromToken.currency.network.name,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePermissionNotificationState(state: SwapState.QuotesLoadedState) {
|
||||
val fromTokenId = state.fromTokenInfo.cryptoCurrencyStatus
|
||||
.currency.id.value
|
||||
val toTokenId = state.toTokenInfo.cryptoCurrencyStatus
|
||||
.currency.id.value
|
||||
val currentTokenPair = Pair(fromTokenId, toTokenId)
|
||||
|
||||
when {
|
||||
uiState.notifications.none { it is SwapNotificationUM.Info.PermissionNeeded } -> {
|
||||
lastPermissionNotificationTokens = null
|
||||
}
|
||||
lastPermissionNotificationTokens != currentTokenPair -> {
|
||||
sendNoticePermissionNeededEvent()
|
||||
lastPermissionNotificationTokens = currentTokenPair
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupEmptyAmountUiState(state: SwapState.EmptyAmountState, fromToken: CryptoCurrencyStatus) {
|
||||
val toTokenStatus = dataState.toCryptoCurrency
|
||||
uiState = stateBuilder.createQuotesEmptyAmountState(
|
||||
uiStateHolder = uiState,
|
||||
emptyAmountState = state,
|
||||
fromTokenStatus = fromToken,
|
||||
toTokenStatus = toTokenStatus,
|
||||
isReverseSwapPossible = isReverseSwapPossible(),
|
||||
toAccount = dataState.toAccount,
|
||||
)
|
||||
}
|
||||
|
||||
private fun setupErrorUiState(provider: SwapProvider, state: SwapState.SwapError) {
|
||||
singleTaskScheduler.cancelTask()
|
||||
uiState = stateBuilder.createQuotesErrorState(
|
||||
uiStateHolder = uiState,
|
||||
swapProvider = provider,
|
||||
fromToken = state.fromTokenInfo,
|
||||
toToken = dataState.toCryptoCurrency,
|
||||
expressDataError = state.error,
|
||||
includeFeeInAmount = state.includeFeeInAmount,
|
||||
isReverseSwapPossible = isReverseSwapPossible(),
|
||||
needApplyFCARestrictions = userCountry.needApplyFCARestrictions(),
|
||||
toAccount = dataState.toAccount,
|
||||
)
|
||||
sendErrorAnalyticsEvent(state.error, provider)
|
||||
}
|
||||
|
||||
private fun sendErrorAnalyticsEvent(error: ExpressDataError, provider: SwapProvider) {
|
||||
val receiveToken = dataState.toCryptoCurrency?.currency?.let {
|
||||
"${it.network.backendId}:${it.symbol}"
|
||||
|
|
@ -1331,7 +1373,7 @@ internal class SwapModel @Inject constructor(
|
|||
onReduceByAmount = ::onReduceAmountClicked,
|
||||
openPermissionBottomSheet = {
|
||||
singleTaskScheduler.cancelTask()
|
||||
analyticsEventHandler.send(SwapEvents.ButtonGivePermissionClicked)
|
||||
sendGivePermissionClickedEvent()
|
||||
uiState = stateBuilder.showPermissionBottomSheet(uiState) {
|
||||
startLoadingQuotesFromLastState(isSilent = true)
|
||||
analyticsEventHandler.send(SwapEvents.ButtonPermissionCancelClicked)
|
||||
|
|
@ -1579,19 +1621,46 @@ internal class SwapModel @Inject constructor(
|
|||
}.map { it.currency }.contains(chosen.currency)
|
||||
}
|
||||
|
||||
private fun sendNoticePermissionNeededEvent() {
|
||||
val sendTokenSymbol = dataState.fromCryptoCurrency?.currency?.symbol ?: return
|
||||
val receiveTokenSymbol = dataState.toCryptoCurrency?.currency?.symbol ?: return
|
||||
val provider = dataState.selectedProvider ?: return
|
||||
analyticsEventHandler.send(
|
||||
SwapEvents.NoticePermissionNeeded(
|
||||
sendToken = sendTokenSymbol,
|
||||
receiveToken = receiveTokenSymbol,
|
||||
provider = provider,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun sendGivePermissionClickedEvent() {
|
||||
val sendTokenSymbol = dataState.fromCryptoCurrency?.currency?.symbol ?: return
|
||||
val receiveTokenSymbol = dataState.toCryptoCurrency?.currency?.symbol ?: return
|
||||
val provider = dataState.selectedProvider ?: return
|
||||
analyticsEventHandler.send(
|
||||
SwapEvents.ButtonGivePermissionClicked(
|
||||
sendToken = sendTokenSymbol,
|
||||
receiveToken = receiveTokenSymbol,
|
||||
provider = provider,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun sendPermissionApproveClickedEvent() {
|
||||
val sendTokenSymbol = dataState.fromCryptoCurrency?.currency?.symbol
|
||||
val receiveTokenSymbol = dataState.toCryptoCurrency?.currency?.symbol
|
||||
val approveType = uiState.permissionState.getApproveTypeOrNull()
|
||||
if (sendTokenSymbol != null && receiveTokenSymbol != null && approveType != null) {
|
||||
analyticsEventHandler.send(
|
||||
SwapEvents.ButtonPermissionApproveClicked(
|
||||
sendToken = sendTokenSymbol,
|
||||
receiveToken = receiveTokenSymbol,
|
||||
approveType = approveType,
|
||||
),
|
||||
)
|
||||
}
|
||||
val sendTokenSymbol = dataState.fromCryptoCurrency?.currency?.symbol ?: return
|
||||
val receiveTokenSymbol = dataState.toCryptoCurrency?.currency?.symbol ?: return
|
||||
val approveType = uiState.permissionState.getApproveTypeOrNull() ?: return
|
||||
val provider = dataState.selectedProvider ?: return
|
||||
|
||||
analyticsEventHandler.send(
|
||||
SwapEvents.ButtonPermissionApproveClicked(
|
||||
sendToken = sendTokenSymbol,
|
||||
receiveToken = receiveTokenSymbol,
|
||||
approveType = approveType,
|
||||
provider = provider,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateWalletBalance() {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ internal sealed class TangemPayDetailsBalanceBlockState {
|
|||
|
||||
data class Content(
|
||||
override val actionButtons: ImmutableList<ActionButtonConfig>,
|
||||
val cryptoBalance: String,
|
||||
val fiatBalance: String,
|
||||
val isBalanceFlickering: Boolean,
|
||||
) : TangemPayDetailsBalanceBlockState()
|
||||
|
|
|
|||
|
|
@ -2,10 +2,8 @@ package com.tangem.features.tangempay.model.transformers
|
|||
|
||||
import arrow.core.Either
|
||||
import com.tangem.core.error.UniversalError
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.pay.TangemPayCryptoCurrencyFactory
|
||||
import com.tangem.domain.pay.model.TangemPayCardBalance
|
||||
|
|
@ -13,7 +11,6 @@ import com.tangem.features.tangempay.entity.TangemPayDetailsBalanceBlockState
|
|||
import com.tangem.features.tangempay.entity.TangemPayDetailsUM
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import java.math.BigDecimal
|
||||
import java.util.Currency
|
||||
|
||||
internal class DetailsBalanceTransformer(
|
||||
|
|
@ -37,7 +34,6 @@ internal class DetailsBalanceTransformer(
|
|||
TangemPayDetailsBalanceBlockState.Content(
|
||||
isBalanceFlickering = false,
|
||||
fiatBalance = getFiatBalanceText(balance.value),
|
||||
cryptoBalance = getCryptoBalanceText(balance.value.cryptoBalance, cryptoCurrency),
|
||||
actionButtons = prevState.balanceBlockState.actionButtons,
|
||||
)
|
||||
}
|
||||
|
|
@ -52,8 +48,4 @@ internal class DetailsBalanceTransformer(
|
|||
fiat(fiatCurrencyCode = currency.currencyCode, fiatCurrencySymbol = currency.symbol)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCryptoBalanceText(cryptoBalance: BigDecimal, cryptoCurrency: CryptoCurrency): String {
|
||||
return cryptoBalance.format { crypto(cryptoCurrency = cryptoCurrency) }
|
||||
}
|
||||
}
|
||||
|
|
@ -175,11 +175,6 @@ private fun TangemPayDetailsBalanceBlock(
|
|||
state = state,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
)
|
||||
CryptoBalance(
|
||||
modifier = Modifier.padding(start = 12.dp, top = 4.dp),
|
||||
state = state,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
)
|
||||
if (state.actionButtons.isNotEmpty()) {
|
||||
HorizontalActionChips(
|
||||
modifier = Modifier.padding(top = 12.dp),
|
||||
|
|
@ -220,37 +215,6 @@ private fun FiatBalance(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
@Composable
|
||||
private fun CryptoBalance(
|
||||
state: TangemPayDetailsBalanceBlockState,
|
||||
isBalanceHidden: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
when (state) {
|
||||
is TangemPayDetailsBalanceBlockState.Loading -> RectangleShimmer(
|
||||
modifier = modifier.size(
|
||||
width = TangemTheme.dimens.size70,
|
||||
height = TangemTheme.dimens.size16,
|
||||
),
|
||||
)
|
||||
is TangemPayDetailsBalanceBlockState.Content -> Text(
|
||||
modifier = modifier,
|
||||
text = state.cryptoBalance.orMaskWithStars(isBalanceHidden),
|
||||
style = TangemTheme.typography.caption2.applyBladeBrush(
|
||||
isEnabled = state.isBalanceFlickering,
|
||||
textColor = TangemTheme.colors.text.tertiary,
|
||||
),
|
||||
)
|
||||
is TangemPayDetailsBalanceBlockState.Error -> Text(
|
||||
modifier = modifier,
|
||||
text = DASH_SIGN.orMaskWithStars(isBalanceHidden),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
|
|
@ -345,7 +309,6 @@ private class TangemPayDetailsUMProvider : CollectionPreviewParameterProvider<Ta
|
|||
onClick = {},
|
||||
),
|
||||
),
|
||||
cryptoBalance = "1234.56 USDT",
|
||||
fiatBalance = "$1234.56",
|
||||
isBalanceFlickering = false,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -6,13 +6,10 @@ import com.tangem.core.ui.R
|
|||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.networkIconResId
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.domain.models.ReceiveAddressModel
|
||||
import com.tangem.domain.models.TokenReceiveType
|
||||
import com.tangem.domain.models.TokenReceiveNotification
|
||||
import com.tangem.domain.models.TokenReceiveType
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.ens.EnsAddress
|
||||
import com.tangem.features.tokenreceive.entity.ReceiveAddress.Type.Ens
|
||||
|
|
@ -111,7 +108,10 @@ internal class TokenReceiveStateFactory(
|
|||
}
|
||||
ReceiveAddressModel.NameService.Ens -> Ens
|
||||
ReceiveAddressModel.NameService.Legacy -> Primary.Legacy(
|
||||
displayName = TextReference.Res(R.string.domain_receive_assets_legacy_address),
|
||||
displayName = resourceReference(
|
||||
R.string.domain_receive_assets_legacy_address,
|
||||
WrappedList(listOf(cryptoCurrency.name)),
|
||||
),
|
||||
)
|
||||
}
|
||||
ReceiveAddress(
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ dependencies {
|
|||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.yieldSupply)
|
||||
implementation(projects.domain.yieldSupply.models)
|
||||
|
||||
/** Temp dependency to swap domain */
|
||||
implementation(projects.features.swap.domain)
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ internal object TokenDetailsPreviewData {
|
|||
selectedBalanceType = BalanceType.ALL,
|
||||
onBalanceSelect = {},
|
||||
displayCryptoBalance = "966,96 XLM",
|
||||
displayYeildSupplyCryptoBalance = null,
|
||||
displayYieldSupplyFiatBalance = null,
|
||||
displayFiatBalance = "91,50$",
|
||||
isBalanceSelectorEnabled = true,
|
||||
isBalanceFlickering = false,
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue