Updated on 2026-08-14
This commit is contained in:
commit
62fca32e89
116 changed files with 1301 additions and 469 deletions
|
|
@ -1 +1 @@
|
|||
Subproject commit a8cf062a6bb58332458c0f5b43026e062378e5c7
|
||||
Subproject commit 4272136431c3629230803e70c4d2cf412365418d
|
||||
|
|
@ -6,6 +6,7 @@ import com.tangem.domain.staking.repositories.StakingErrorResolver
|
|||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.staking.repositories.StakingTransactionHashRepository
|
||||
import com.tangem.domain.staking.single.SingleYieldBalanceFetcher
|
||||
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -216,4 +217,10 @@ internal object StakingDomainModule {
|
|||
fun provideStakingIdFactory(walletManagersFacade: WalletManagersFacade): StakingIdFactory {
|
||||
return StakingIdFactory(walletManagersFacade = walletManagersFacade)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideStakingApyFlowUseCase(stakingRepository: StakingRepository): StakingApyFlowUseCase {
|
||||
return StakingApyFlowUseCase(stakingRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -148,4 +148,32 @@ internal object YieldSupplyDomainModule {
|
|||
currenciesRepository = currenciesRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideYieldSupplyGetCurrentFeeUseCase(
|
||||
feeRepository: FeeRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
): YieldSupplyGetCurrentFeeUseCase {
|
||||
return YieldSupplyGetCurrentFeeUseCase(
|
||||
feeRepository = feeRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
currenciesRepository = currenciesRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideYieldSupplyGetMaxFeeUseCase(
|
||||
yieldSupplyRepository: YieldSupplyRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
): YieldSupplyGetMaxFeeUseCase {
|
||||
return YieldSupplyGetMaxFeeUseCase(
|
||||
yieldSupplyRepository = yieldSupplyRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
currenciesRepository = currenciesRepository,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -112,6 +112,7 @@ internal class DefaultRampManager(
|
|||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
): ScenarioUnavailabilityReason {
|
||||
val yieldSupplyStatus = cryptoCurrencyStatus.value.yieldSupplyStatus
|
||||
return when {
|
||||
cryptoCurrencyStatus.value.amount.isNullOrZero() -> {
|
||||
ScenarioUnavailabilityReason.EmptyBalance(ScenarioUnavailabilityReason.WithdrawalScenario.SEND)
|
||||
|
|
@ -125,6 +126,9 @@ internal class DefaultRampManager(
|
|||
networkName = cryptoCurrencyStatus.currency.network.name,
|
||||
)
|
||||
}
|
||||
yieldSupplyStatus?.isAllowedToSpend == false && yieldSupplyStatus.isActive -> {
|
||||
ScenarioUnavailabilityReason.YieldSupplyApprovalRequired
|
||||
}
|
||||
else -> ScenarioUnavailabilityReason.None
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ import com.tangem.features.send.v2.api.SendComponent
|
|||
import com.tangem.features.send.v2.api.SendEntryPointComponent
|
||||
import com.tangem.features.staking.api.StakingComponent
|
||||
import com.tangem.features.swap.SwapComponent
|
||||
import com.tangem.features.swap.v2.api.SendWithSwapComponent
|
||||
import com.tangem.features.tangempay.components.TangemPayDetailsContainerComponent
|
||||
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent
|
||||
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Params.ContinueOnboarding
|
||||
|
|
@ -111,7 +110,6 @@ internal class ChildFactory @Inject constructor(
|
|||
private val updateAccessCodeComponentFactory: UpdateAccessCodeComponent.Factory,
|
||||
private val viewPhraseComponentFactory: ViewPhraseComponent.Factory,
|
||||
private val forgetWalletComponentFactory: ForgetWalletComponent.Factory,
|
||||
private val sendWithSwapComponentFactory: SendWithSwapComponent.Factory,
|
||||
private val sendEntryPointComponentFactory: SendEntryPointComponent.Factory,
|
||||
private val tangemPayDetailsContainerComponentFactory: TangemPayDetailsContainerComponent.Factory,
|
||||
private val tangemPayOnboardingComponentFactory: TangemPayOnboardingComponent.Factory,
|
||||
|
|
@ -585,16 +583,6 @@ internal class ChildFactory @Inject constructor(
|
|||
componentFactory = sendEntryPointComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.SendWithSwap -> {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
params = SendWithSwapComponent.Params(
|
||||
userWalletId = route.userWalletId,
|
||||
currency = route.currency,
|
||||
),
|
||||
componentFactory = sendWithSwapComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.CreateAccount -> {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
|
|
@ -661,7 +649,11 @@ internal class ChildFactory @Inject constructor(
|
|||
is AppRoute.YieldSupplyPromo -> {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
params = YieldSupplyPromoComponent.Params(route.userWalletId, route.cryptoCurrency),
|
||||
params = YieldSupplyPromoComponent.Params(
|
||||
userWalletId = route.userWalletId,
|
||||
currency = route.cryptoCurrency,
|
||||
apy = route.apy,
|
||||
),
|
||||
componentFactory = yieldSupplyPromoComponentFactory,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -372,12 +372,6 @@ sealed class AppRoute(val path: String) : Route {
|
|||
path = "/send_entry_point/${userWalletId.stringValue}/${currency.id.value}?",
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SendWithSwap(
|
||||
val userWalletId: UserWalletId,
|
||||
val currency: CryptoCurrency,
|
||||
) : AppRoute(path = "/send_with_swap/${userWalletId.stringValue}/${currency.symbol}")
|
||||
|
||||
@Serializable
|
||||
data class CreateAccount(
|
||||
val userWalletId: UserWalletId,
|
||||
|
|
@ -428,5 +422,6 @@ sealed class AppRoute(val path: String) : Route {
|
|||
data class YieldSupplyPromo(
|
||||
val userWalletId: UserWalletId,
|
||||
val cryptoCurrency: CryptoCurrency,
|
||||
val apy: String,
|
||||
) : AppRoute(path = "/yield_supply_promo/${userWalletId.stringValue}/${cryptoCurrency.symbol}")
|
||||
}
|
||||
|
|
@ -67,10 +67,12 @@ class MockUpdateWalletManagerResultFactory {
|
|||
value = BigDecimal.ONE,
|
||||
currencyRawId = CryptoCurrency.RawID("token"),
|
||||
contractAddress = "0xTokenAddress",
|
||||
yieldSupplyStatus = YieldSupplyStatus(
|
||||
yieldSupplyStatus =
|
||||
YieldSupplyStatus(
|
||||
isActive = true,
|
||||
isInitialized = true,
|
||||
isAllowedToSpend = false,
|
||||
effectiveProtocolBalance = BigDecimal.ONE,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -381,7 +381,11 @@ sealed class NotificationUM(val config: NotificationConfig) {
|
|||
title = TextReference.Res(R.string.send_notification_invalid_amount_title),
|
||||
subtitle = TextReference.Res(
|
||||
id = R.string.send_notification_invalid_amount_rent_fee,
|
||||
formatArgs = wrappedList(rentInfo.exemptionAmount),
|
||||
formatArgs = wrappedList(
|
||||
rentInfo.exemptionAmount.format {
|
||||
crypto(rentInfo.cryptoCurrency)
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -68,6 +68,9 @@ fun ScenarioUnavailabilityReason.getUnavailabilityReasonText(): TextReference {
|
|||
-> {
|
||||
resourceReference(id = R.string.token_button_unavailability_reason_loading)
|
||||
}
|
||||
ScenarioUnavailabilityReason.YieldSupplyApprovalRequired -> resourceReference(
|
||||
R.string.token_button_unavailability_reason_yield_supply_approval,
|
||||
)
|
||||
ScenarioUnavailabilityReason.None -> {
|
||||
throw IllegalArgumentException("The unavailability reason must be other than None")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.tangem.core.ui.components.icons.IconTint
|
|||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.components.marketprice.utils.PriceChangeConverter
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
|
|
@ -16,14 +17,14 @@ import com.tangem.core.ui.format.bigdecimal.format
|
|||
import com.tangem.core.ui.format.bigdecimal.percent
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
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.currency.yieldSupplyNotAllAmountSupplied
|
||||
import com.tangem.domain.models.staking.YieldBalance
|
||||
import com.tangem.domain.staking.utils.getTotalWithRewardsStakingBalance
|
||||
import com.tangem.utils.StringsSigns.DASH_SIGN
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.utils.extensions.isZero
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import java.math.BigDecimal
|
||||
|
|
@ -39,12 +40,13 @@ import java.math.BigDecimal
|
|||
*/
|
||||
class TokenItemStateConverter(
|
||||
private val appCurrency: AppCurrency,
|
||||
private val apyMap: Map<String, String> = emptyMap(),
|
||||
private val yieldModuleApyMap: Map<String, String> = emptyMap(),
|
||||
private val stakingApyMap: Map<String, BigDecimal> = emptyMap(),
|
||||
private val iconStateProvider: (CryptoCurrencyStatus) -> CurrencyIconState = {
|
||||
CryptoCurrencyToIconStateConverter().convert(it)
|
||||
},
|
||||
private val titleStateProvider: (CryptoCurrencyStatus) -> TokenItemState.TitleState = {
|
||||
createTitleState(it, apyMap)
|
||||
createTitleState(it, yieldModuleApyMap, stakingApyMap)
|
||||
},
|
||||
private val subtitleStateProvider: (CryptoCurrencyStatus) -> TokenItemState.SubtitleState? = {
|
||||
createSubtitleState(it, appCurrency)
|
||||
|
|
@ -153,7 +155,8 @@ class TokenItemStateConverter(
|
|||
|
||||
private fun createTitleState(
|
||||
currencyStatus: CryptoCurrencyStatus,
|
||||
apyMap: Map<String, String>,
|
||||
yieldModuleApyMap: Map<String, String>,
|
||||
stakingApyMap: Map<String, BigDecimal>,
|
||||
): TokenItemState.TitleState {
|
||||
return when (val value = currencyStatus.value) {
|
||||
is CryptoCurrencyStatus.Loading,
|
||||
|
|
@ -168,31 +171,51 @@ class TokenItemStateConverter(
|
|||
is CryptoCurrencyStatus.NoQuote,
|
||||
is CryptoCurrencyStatus.NoAccount,
|
||||
-> {
|
||||
val earnApyText = resolveEarnApy(currencyStatus, apyMap)?.let { apy ->
|
||||
resourceReference(
|
||||
R.string.yield_module_earn_badge,
|
||||
wrappedList(apy),
|
||||
)
|
||||
}
|
||||
val (earnApyText, isActive) = resolveEarnApy(
|
||||
cryptoCurrencyStatus = currencyStatus,
|
||||
yieldModuleApyMap = yieldModuleApyMap,
|
||||
stakingApyMap = stakingApyMap,
|
||||
)
|
||||
TokenItemState.TitleState.Content(
|
||||
text = stringReference(currencyStatus.currency.name),
|
||||
hasPending = value.hasCurrentNetworkTransactions,
|
||||
earnApy = earnApyText,
|
||||
earnApyIsActive = isActive,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveEarnApy(cryptoCurrencyStatus: CryptoCurrencyStatus, apyMap: Map<String, String>): String? {
|
||||
if (apyMap.isEmpty()) return null
|
||||
private fun resolveEarnApy(
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
yieldModuleApyMap: Map<String, String>,
|
||||
stakingApyMap: Map<String, BigDecimal>,
|
||||
): Pair<TextReference?, Boolean> {
|
||||
val token = cryptoCurrencyStatus.currency as? CryptoCurrency.Token
|
||||
if (token != null && yieldModuleApyMap.isNotEmpty()) {
|
||||
val yieldSupplyApy = yieldModuleApyMap[token.yieldSupplyKey()]
|
||||
if (yieldSupplyApy != null) {
|
||||
val isActive = cryptoCurrencyStatus.value.yieldSupplyStatus?.isActive ?: false
|
||||
return resourceReference(
|
||||
R.string.yield_module_earn_badge,
|
||||
wrappedList(yieldSupplyApy),
|
||||
) to isActive
|
||||
}
|
||||
}
|
||||
|
||||
val isYieldSupplyActive = (cryptoCurrencyStatus.value as? CryptoCurrencyStatus.Loaded)
|
||||
?.yieldSupplyStatus?.isActive == true
|
||||
if (isYieldSupplyActive) return null
|
||||
if (stakingApyMap.isNotEmpty()) {
|
||||
val stakingKey = cryptoCurrencyStatus.currency.stakingKey()
|
||||
val stakingApy = stakingApyMap[stakingKey]?.format { percent(withPercentSign = false) }
|
||||
if (stakingApy != null) {
|
||||
val hasStakedBalance = cryptoCurrencyStatus.value.yieldBalance is YieldBalance.Data
|
||||
return resourceReference(
|
||||
R.string.yield_module_earn_badge,
|
||||
wrappedList(stakingApy),
|
||||
) to hasStakedBalance
|
||||
}
|
||||
}
|
||||
|
||||
val token = cryptoCurrencyStatus.currency as? CryptoCurrency.Token ?: return null
|
||||
|
||||
return apyMap[token.yieldSupplyKey()]
|
||||
return null to false
|
||||
}
|
||||
|
||||
private fun createSubtitleState(
|
||||
|
|
@ -248,20 +271,14 @@ class TokenItemStateConverter(
|
|||
isFlickering = status.value.isFlickering(),
|
||||
icons = buildList {
|
||||
if (status.value.yieldSupplyStatus?.isActive == true &&
|
||||
status.value.yieldSupplyStatus?.isAllowedToSpend == false) {
|
||||
status.value.yieldSupplyStatus?.isAllowedToSpend == false ||
|
||||
status.yieldSupplyNotAllAmountSupplied()
|
||||
) {
|
||||
TokenItemState.FiatAmountState.Content.IconUM(
|
||||
iconRes = R.drawable.ic_alert_triangle_20,
|
||||
tint = IconTint.Warning,
|
||||
).let(::add)
|
||||
}
|
||||
if (!status.getStakedBalance().isZero()) {
|
||||
add(
|
||||
TokenItemState.FiatAmountState.Content.IconUM(
|
||||
iconRes = R.drawable.ic_staking_24,
|
||||
tint = IconTint.Accent,
|
||||
),
|
||||
)
|
||||
}
|
||||
if (status.value.sources.total == StatusSource.ONLY_CACHE) {
|
||||
add(
|
||||
TokenItemState.FiatAmountState.Content.IconUM(
|
||||
|
|
@ -307,5 +324,9 @@ class TokenItemStateConverter(
|
|||
}
|
||||
|
||||
fun CryptoCurrencyStatus.Value.isFlickering(): Boolean = sources.total == StatusSource.CACHE
|
||||
|
||||
private fun CryptoCurrency.stakingKey(): String {
|
||||
return "${network.backendId}_$symbol"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -41,32 +41,44 @@ internal class YieldSupply(
|
|||
private fun createDevEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.DEV,
|
||||
baseUrl = "[REDACTED_ENV_URL]",
|
||||
headers = createHeaders(),
|
||||
headers = createHeaders(ApiEnvironment.DEV),
|
||||
)
|
||||
|
||||
private fun createStageEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.STAGE,
|
||||
baseUrl = "[REDACTED_ENV_URL]",
|
||||
headers = createHeaders(),
|
||||
headers = createHeaders(ApiEnvironment.STAGE),
|
||||
)
|
||||
|
||||
private fun createMockedEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.MOCK,
|
||||
baseUrl = "[REDACTED_ENV_URL]",
|
||||
headers = createHeaders(),
|
||||
headers = createHeaders(ApiEnvironment.MOCK),
|
||||
)
|
||||
|
||||
private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://yield.tangem.org/",
|
||||
headers = createHeaders(),
|
||||
headers = createHeaders(ApiEnvironment.PROD),
|
||||
)
|
||||
|
||||
private fun createHeaders() = buildMap {
|
||||
private fun createHeaders(apiEnvironment: ApiEnvironment) = buildMap {
|
||||
put(key = "api-key", value = ProviderSuspend {
|
||||
environmentConfigStorage.getConfigSync().yieldModuleApiKey.orEmpty()
|
||||
getApiKey(apiEnvironment)
|
||||
})
|
||||
putAll(from = RequestHeader.AppVersionPlatformHeaders(appVersionProvider, appInfoProvider).values)
|
||||
putAll(from = RequestHeader.AuthenticationHeader(authProvider).values)
|
||||
}
|
||||
|
||||
private fun getApiKey(apiEnvironment: ApiEnvironment): String {
|
||||
return when (apiEnvironment) {
|
||||
ApiEnvironment.MOCK,
|
||||
ApiEnvironment.DEV,
|
||||
ApiEnvironment.DEV_2,
|
||||
ApiEnvironment.DEV_3,
|
||||
ApiEnvironment.STAGE,
|
||||
-> environmentConfigStorage.getConfigSync().yieldModuleApiKeyDev
|
||||
ApiEnvironment.PROD -> environmentConfigStorage.getConfigSync().yieldModuleApiKey
|
||||
} ?: error("No tangem tech api config provided")
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ interface StakeKitApi {
|
|||
@Query("preferredValidatorsOnly") preferredValidatorsOnly: Boolean? = null,
|
||||
@Query("ledgerWalletAPICompatible") ledgerWalletAPICompatible: Boolean? = null,
|
||||
@Query("type") type: YieldType? = null,
|
||||
@Query("yieldId") yieldId: String? = null,
|
||||
@Query("revenueOption") revenueOption: RevenueOption? = null,
|
||||
@Query("page") page: Int? = null,
|
||||
@Query("network") network: String? = null,
|
||||
|
|
|
|||
|
|
@ -21,4 +21,5 @@ data class EnvironmentConfig(
|
|||
val tangemApiKeyDev: String? = null,
|
||||
val tangemApiKeyStage: String? = null,
|
||||
val yieldModuleApiKey: String? = null,
|
||||
val yieldModuleApiKeyDev: String? = null,
|
||||
)
|
||||
|
|
@ -30,6 +30,7 @@ internal object EnvironmentConfigConverter : Converter<EnvironmentConfigModel, E
|
|||
tangemApiKeyDev = value.tangemApiKeyDev,
|
||||
tangemApiKeyStage = value.tangemApiKeyStage,
|
||||
yieldModuleApiKey = value.yieldModuleApiKey,
|
||||
yieldModuleApiKeyDev = value.yieldModuleApiKeyDev,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -45,6 +45,7 @@ class EnvironmentConfigModel(
|
|||
@Json(name = "tangemApiKeyStage") val tangemApiKeyStage: String?,
|
||||
@Json(name = "etherscanApiKey") val etherScanApiKey: String?,
|
||||
@Json(name = "yieldModuleApiKey") val yieldModuleApiKey: String?,
|
||||
@Json(name = "yieldModuleApiKeyDev") val yieldModuleApiKeyDev: String?,
|
||||
@Json(name = "blinkApiKey") val blinkApiKey: String?,
|
||||
@Json(name = "tatumApiKey") val tatumApiKey: String?,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -119,6 +119,7 @@ sealed interface NetworkStatusDM {
|
|||
@Json(name = "is_active") val isActive: Boolean,
|
||||
@Json(name = "is_initialized") val isInitialized: Boolean,
|
||||
@Json(name = "is_allowed_to_spend") val isAllowedToSpend: Boolean,
|
||||
@Json(name = "effective_protocol_balance") val effectiveProtocolBalance: BigDecimal? = null,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
|
|
|
|||
|
|
@ -24,8 +24,19 @@ internal class DefaultStakingYieldsStore(
|
|||
}
|
||||
|
||||
override suspend fun store(items: List<YieldDTO>) {
|
||||
dataStore.updateData { _ ->
|
||||
items
|
||||
dataStore.updateData { data ->
|
||||
val updatedItems = data.toMutableList()
|
||||
items.forEach { newItem ->
|
||||
val existingItemIndex = data.indexOfFirst { it.id == newItem.id }
|
||||
if (existingItemIndex != -1) {
|
||||
// Update existing item
|
||||
updatedItems[existingItemIndex] = newItem
|
||||
} else {
|
||||
// Add new item
|
||||
updatedItems.add(newItem)
|
||||
}
|
||||
}
|
||||
updatedItems
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ internal class MockEnvironmentConfigStorage : EnvironmentConfigStorage {
|
|||
tangemApiKeyDev = TANGEM_API_KEY_DEV,
|
||||
tangemApiKeyStage = TANGEM_API_KEY_STAGE,
|
||||
yieldModuleApiKey = YIELD_MODULE_KEY,
|
||||
yieldModuleApiKeyDev = YIELD_MODULE_KEY_DEV,
|
||||
)
|
||||
|
||||
override suspend fun initialize() = environmentConfig
|
||||
|
|
@ -34,5 +35,6 @@ internal class MockEnvironmentConfigStorage : EnvironmentConfigStorage {
|
|||
const val TANGEM_API_KEY_DEV = "tangem_api_key_dev"
|
||||
const val TANGEM_API_KEY_STAGE = "tangem_api_key_stage"
|
||||
const val YIELD_MODULE_KEY = "yield_module_api_key"
|
||||
const val YIELD_MODULE_KEY_DEV = "yield_module_api_key_dev"
|
||||
}
|
||||
}
|
||||
|
|
@ -1684,8 +1684,8 @@
|
|||
<string name="yield_module_historical_returns">Historische Renditen</string>
|
||||
<string name="yield_module_promo_screen_cash_out_title">Sofortige Auszahlung</string>
|
||||
<string name="yield_module_promo_screen_how_it_works_button_title">Wie funktioniert das?</string>
|
||||
<string name="yield_module_promo_screen_title">Verdiene %s%% jährlich</string>
|
||||
<string name="yield_module_promo_screen_variable_rate_info">Aave • Variabler Zinssatz</string>
|
||||
<string name="yield_module_promo_screen_title">Mit Aave verbinden</string>
|
||||
<string name="yield_module_promo_screen_variable_rate_info">Aave %1$s • Variabler Zinssatz</string>
|
||||
<string name="yield_module_provider">Aave</string>
|
||||
<string name="yield_module_rate_info_sheet_chart_average">Durchschnitt %s</string>
|
||||
<string name="yield_module_rate_info_sheet_chart_title">Renditen des letzten Jahres</string>
|
||||
|
|
@ -1698,7 +1698,7 @@
|
|||
<string name="yield_module_supply_apr">Effektiver Jahreszins für Versorgung</string>
|
||||
<string name="yield_module_token_details_earn_notification_description">Lass Dein Geld arbeiten – verdiene Zinsen auf Dein Guthaben.</string>
|
||||
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_title">Verdienst auf Dein Guthaben</string>
|
||||
<string name="yield_module_token_details_earn_notification_title">Verdienen %1$s%% pro Jahr</string>
|
||||
<string name="yield_module_token_details_earn_notification_title">Lass dein Guthaben arbeiten</string>
|
||||
<string name="yield_module_unavailable_subtitle">Der Stakingservice ist derzeit nicht verfügbar. Bitte versuche es später erneut.</string>
|
||||
<string name="yield_module_unavailable_title">Einnahmen nicht verfügbar</string>
|
||||
<string name="yield_supply_chart_loading_error">Chart konnte nicht geladen werden...</string>
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
<string name="account_archived_recover_error_message">すでにアクティブアカウントの上限(20件)に達しています。復元するには、1つをアーカイブしてください。</string>
|
||||
<string name="account_archived_recover_error_title">アカウントを復元できません</string>
|
||||
<string name="account_archived_title">アーカイブ済み</string>
|
||||
<string name="account_could_not_archive">アカウントをアーカイブできませんでした。しばらくしてからもう一度お試しください。</string>
|
||||
<string name="account_could_not_create">アカウントを作成できませんでした。しばらくしてからもう一度お試しください。</string>
|
||||
<string name="account_create_success_message">アカウントを作成しました</string>
|
||||
<string name="account_details_archive">アカウントをアーカイブする</string>
|
||||
|
|
@ -249,10 +250,12 @@
|
|||
<string name="common_fee_selector_option_slow">遅い</string>
|
||||
<string name="common_fee_selector_title">速度と料金</string>
|
||||
<string name="common_finish">終了</string>
|
||||
<string name="common_forget">忘れる</string>
|
||||
<string name="common_free">無料</string>
|
||||
<string name="common_from">送信元</string>
|
||||
<string name="common_generate_addresses">アドレスを同期する</string>
|
||||
<string name="common_get_started">はじめる</string>
|
||||
<string name="common_get_token">トークンを取得</string>
|
||||
<string name="common_go_to_provider">プロバイダーへ移動</string>
|
||||
<string name="common_go_to_token">トークンへ移動</string>
|
||||
<string name="common_got_it">わかりました</string>
|
||||
|
|
@ -526,14 +529,20 @@
|
|||
<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_banner_description">暗号資産をオフラインで厳重保管。カードサイズで、金庫以上の安心を。</string>
|
||||
<string name="hw_backup_close_description">実行すると、最初からやり直す必要があります。</string>
|
||||
<string name="hw_backup_google_drive_description">Googleドライブのバックアップから既存のウォレットを復元する</string>
|
||||
<string name="hw_backup_google_drive_title">Googleドライブのバックアップ</string>
|
||||
<string name="hw_backup_hardware_create_description">さらに強固なセキュリティのために、新しいウォレットを作成して資産を移動しましょう。</string>
|
||||
<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_title">現在のウォレットをアップグレードする</string>
|
||||
<string name="hw_backup_need_action">バックアップへ移動</string>
|
||||
<string name="hw_backup_need_description">アクセスコードを作成する前にウォレットをバックアップしてください。</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_title">リカバリーフレーズ</string>
|
||||
<string name="hw_create_keys_title">鍵はアプリに保存されます</string>
|
||||
|
|
@ -603,6 +612,7 @@
|
|||
<string name="manage_tokens_unavailable_description">選択したトークンは現在、暗号資産ウォレット内でのアクションには利用できません。しかし、心配しないでください。賛成票を投じることで関心を表明できます。</string>
|
||||
<string name="manage_tokens_unavailable_vote">賛成票を投じる</string>
|
||||
<string name="manage_tokens_wallet_support_only_one_network_title">ウォレットは複数のネットワークをサポートしていません。</string>
|
||||
<string name="markets_about_coin_header">コインについて</string>
|
||||
<string name="markets_add_to_my_portfolio_description">このアセットを購入・交換・受け取るには、ポートフォリオに追加してください。</string>
|
||||
<string name="markets_add_to_my_portfolio_unavailable_description">このアセットは現在ウォレットで利用できません</string>
|
||||
<string name="markets_add_to_my_portfolio_unavailable_for_wallet_description">このアセットはこのウォレットでは使用できません。</string>
|
||||
|
|
@ -638,6 +648,7 @@
|
|||
<string name="markets_sort_by_trending_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>
|
||||
<string name="markets_token_details_about_token_title">%sについて</string>
|
||||
<plurals name="markets_token_details_amount_exchanges">
|
||||
<item quantity="other">%d取引所</item>
|
||||
|
|
@ -895,7 +906,7 @@
|
|||
<item quantity="other">最大%d日</item>
|
||||
</plurals>
|
||||
<string name="onramp_timing_minutes">%s分</string>
|
||||
<string name="onramp_title_available_from">下記より利用可能</string>
|
||||
<string name="onramp_title_available_from">利用可能:</string>
|
||||
<string name="onramp_title_you_get">以下が手に入ります。</string>
|
||||
<string name="onramp_tos_external_providers">サービスは外部プロバイダーによって提供されます。 \nTangemは責任を負いません。</string>
|
||||
<string name="onramp_transaction_status_footer_text">この画面を閉じて、トークンの詳細画面で取引状況を確認できます。</string>
|
||||
|
|
@ -1275,7 +1286,20 @@
|
|||
<string name="swapping_to_title">受け取る</string>
|
||||
<string name="swapping_token_list_title">トークンを選択</string>
|
||||
<string name="swapping_token_not_available">利用不可</string>
|
||||
<string name="tangem_pay_deposit">入金</string>
|
||||
<string name="tangem_pay_dispute">異議申し立て</string>
|
||||
<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_status_completed">完了</string>
|
||||
<string name="tangem_pay_status_declined">拒否</string>
|
||||
<string name="tangem_pay_status_pending">保留中</string>
|
||||
<string name="tangem_pay_transaction_declined_notification_text">銀行がこの取引リクエストを拒否しました。</string>
|
||||
<string name="tangem_pay_transaction_fee_notification_text">この手数料は、送金処理にかかるコストをカバーするためのものです。</string>
|
||||
<string name="tangem_pay_withdrawal">出金</string>
|
||||
<string name="tangempay_card_details_change_pin">PINを変更する</string>
|
||||
<string name="tangempay_card_details_error_text">データの読み込みに失敗しました。しばらくしてからもう一度お試しください。</string>
|
||||
<string name="tangempay_card_details_freeze_card">カードの一時停止</string>
|
||||
<string name="tangempay_card_details_hide_text">非表示</string>
|
||||
<string name="tangempay_card_details_receive_error_description">技術的な問題が検出されました。しばらくしてからもう一度お試しいただくか、サポートにお問い合わせください。</string>
|
||||
<string name="tangempay_card_details_receive_error_title">現在、受け取りは利用できません</string>
|
||||
|
|
@ -1310,6 +1334,7 @@
|
|||
<string name="token_button_unavailability_reason_pending_transaction_send">ネットワーク%s内の保留中の取引が完了すると、送金が可能になります。</string>
|
||||
<string name="token_button_unavailability_reason_sell_unavailable">%sの売却は、現在のプロバイダーではサポートされていませんが、より多くのオプションを追加できるよう取り組んでいます。</string>
|
||||
<string name="token_button_unavailability_reason_staking_unavailable">%sのステーキングは、現在のプロバイダーではサポートされていませんが、より多くのオプションを追加できるよう取り組んでいます。</string>
|
||||
<string name="token_button_unavailability_reason_yield_supply_approval">ここにテキストを入力</string>
|
||||
<string name="token_details_generate_xpub">XPUBを生成する</string>
|
||||
<string name="token_details_hide_alert_hide">非表示</string>
|
||||
<string name="token_details_hide_alert_message">このトークンをメイン画面から非表示にします。トークンの管理ページからいつでも再度追加できます。</string>
|
||||
|
|
@ -1729,10 +1754,10 @@
|
|||
<string name="yield_module_fee_policy_sheet_description">今後の%sの入金はすべて、取引手数料が差し引かれて自動的にAaveに供給されます。</string>
|
||||
<string name="yield_module_fee_policy_sheet_max_fee_note">ネットワーク手数料が上限手数料を超えた場合、手数料が下がるまで取引は成立しません。この制限は後で変更できます。</string>
|
||||
<string name="yield_module_fee_policy_sheet_max_fee_title">最大手数料</string>
|
||||
<string name="yield_module_fee_policy_sheet_min_amount_note">取引手数料は、預入額の4%未満である必要があります。Tangemは、この条件を満たす十分な残高が貯まった時点で、Aaveへの資金移動を行います。</string>
|
||||
<string name="yield_module_fee_policy_sheet_min_amount_note">取引手数料は入金額の4%未満である必要があります。残高がこの条件を満たすのに十分な金額になった場合にのみ、TangemはAaveに資金を送ります。</string>
|
||||
<string name="yield_module_fee_policy_sheet_min_amount_title">最低入金額</string>
|
||||
<string name="yield_module_fee_policy_sheet_title">手数料ポリシー</string>
|
||||
<string name="yield_module_fee_policy_tangem_service_fee_title">Tangemはまた、得られた利回りに対して3% のサービス手数料を差し引きます。</string>
|
||||
<string name="yield_module_fee_policy_tangem_service_fee_title">Tangemはまた、得られた利回りに対して3%のサービス手数料を差し引きます。</string>
|
||||
<string name="yield_module_high_fee_error">ネットワーク手数料が現在高すぎます。設定した上限を下回るまで待機しています。</string>
|
||||
<string name="yield_module_historical_returns">過去のリターン</string>
|
||||
<string name="yield_module_main_view_approve_notification_description">ここに説明を入力してください。1〜3行が理想的です。[プレースホルダー]</string>
|
||||
|
|
@ -1747,8 +1772,8 @@
|
|||
<string name="yield_module_promo_screen_self_custodial_subtitle">Aaveは、総額819億ドル以上の資産を管理する分散型プロトコルです。</string>
|
||||
<string name="yield_module_promo_screen_self_custodial_title">分散型・自己管理型</string>
|
||||
<string name="yield_module_promo_screen_terms_disclaimer">サービスを利用することにより、プロバイダー\n %1$sおよび%2$sに同意したことになります</string>
|
||||
<string name="yield_module_promo_screen_title">年間%s%%の収益</string>
|
||||
<string name="yield_module_promo_screen_variable_rate_info">Aave • 変動金利</string>
|
||||
<string name="yield_module_promo_screen_title">Aave を接続</string>
|
||||
<string name="yield_module_promo_screen_variable_rate_info">Aave %1$s%% • 変動金利</string>
|
||||
<string name="yield_module_provider">Aave</string>
|
||||
<string name="yield_module_rate_info_sheet_chart_average">平均%s</string>
|
||||
<string name="yield_module_rate_info_sheet_chart_title">昨年のリターン</string>
|
||||
|
|
@ -1771,7 +1796,7 @@
|
|||
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_subtitle">利息は自動的に発生します</string>
|
||||
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_title">Aaveの利回り</string>
|
||||
<string name="yield_module_token_details_earn_notification_processing">入金の処理中</string>
|
||||
<string name="yield_module_token_details_earn_notification_title">年間%1$s%%の収益</string>
|
||||
<string name="yield_module_token_details_earn_notification_title">残高を活用</string>
|
||||
<string name="yield_module_transfer_mode_automatic">自動</string>
|
||||
<string name="yield_module_unable_to_cover_fee_description">取引のネットワーク手数料をカバーするために、 %1$s %2$sを入金してください</string>
|
||||
<string name="yield_module_unable_to_cover_fee_title">%s手数料を支払えません</string>
|
||||
|
|
|
|||
|
|
@ -192,8 +192,10 @@
|
|||
<string name="common_fee_selector_option_slow">Медленно</string>
|
||||
<string name="common_fee_selector_title">Скорость и комиссия</string>
|
||||
<string name="common_finish">Завершить</string>
|
||||
<string name="common_forget">Забыть</string>
|
||||
<string name="common_from">Из</string>
|
||||
<string name="common_generate_addresses">Синхронизировать адреса</string>
|
||||
<string name="common_get_started">Начать зарабатывать</string>
|
||||
<string name="common_go_to_provider">К провайдеру</string>
|
||||
<string name="common_go_to_token">Перейти в токен</string>
|
||||
<string name="common_got_it">Понятно</string>
|
||||
|
|
@ -527,6 +529,7 @@
|
|||
<string name="manage_tokens_unavailable_description">Выбранный токен не доступен в кошельке на данный момент. Но не переживайте, вы можете выразить свой интерес проголосовав за его добавление.</string>
|
||||
<string name="manage_tokens_unavailable_vote">Голосовать</string>
|
||||
<string name="manage_tokens_wallet_support_only_one_network_title">Кошелёк не поддерживает более одной сети</string>
|
||||
<string name="markets_about_coin_header">О монете</string>
|
||||
<string name="markets_add_to_my_portfolio_description">Чтобы купить, обменять или получить данный токен, вам нужно добавить его к себе в портфель</string>
|
||||
<string name="markets_add_to_my_portfolio_unavailable_description">Этот актив в настоящее время не поддерживается в кошельке</string>
|
||||
<string name="markets_add_to_my_portfolio_unavailable_for_wallet_description">Этот токен не доступен для данного кошелька</string>
|
||||
|
|
@ -561,6 +564,7 @@
|
|||
<string name="markets_sort_by_trending_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>
|
||||
<string name="markets_token_details_about_token_title">О %s</string>
|
||||
<plurals name="markets_token_details_amount_exchanges">
|
||||
<item quantity="one">%d биржа</item>
|
||||
|
|
@ -838,6 +842,7 @@
|
|||
<item quantity="other">до %d дней</item>
|
||||
</plurals>
|
||||
<string name="onramp_timing_minutes">%s мин</string>
|
||||
<string name="onramp_title_available_from">Доступно от</string>
|
||||
<string name="onramp_title_you_get">Вы получите</string>
|
||||
<string name="onramp_transaction_status_footer_text">Вы можете закрыть этот экран и проверить статус транзакции на экране информации о токене.</string>
|
||||
<string name="onramp_up_to_rate">До</string>
|
||||
|
|
@ -1214,6 +1219,7 @@
|
|||
<string name="swapping_to_title">Вы получите</string>
|
||||
<string name="swapping_token_list_title">Выберите токен</string>
|
||||
<string name="swapping_token_not_available">не доступен</string>
|
||||
<string name="tangem_pay_fee_title">Комиссия</string>
|
||||
<string name="this_is_my_wallet_title">Это мой кошелек</string>
|
||||
<string name="toast_balances_hidden">Балансы скрыты</string>
|
||||
<string name="toast_balances_shown">Балансы показаны</string>
|
||||
|
|
@ -1590,7 +1596,7 @@
|
|||
<string name="yield_module_fee_policy_sheet_min_amount_title">Минимальный депозит</string>
|
||||
<string name="yield_module_fee_policy_sheet_title">Политика комиссий</string>
|
||||
<string name="yield_module_fee_policy_tangem_service_fee_title">Tangem взимает комиссию за обслуживание в размере 3% от полученного дохода.</string>
|
||||
<string name="yield_module_high_fee_error">Комиссия в сети сейчас слишком высокая. Ждём, пока она упадёт ниже вашего лимита.</string>
|
||||
<string name="yield_module_high_fee_error">Ваши средства будут автоматически переведены в Aave, как только комиссия сети снизится или баланс достигнет минимально необходимой суммы.</string>
|
||||
<string name="yield_module_historical_returns">Историческая доходность</string>
|
||||
<string name="yield_module_main_view_approve_notification_title">Необходимо разрешение для токена</string>
|
||||
<string name="yield_module_network_fee_unreachable_notification_description">Проверьте ваше интернет соединение</string>
|
||||
|
|
@ -1603,10 +1609,10 @@
|
|||
<string name="yield_module_promo_screen_self_custodial_subtitle">Aave — это децентрализованный протокол, управляющий активами на сумму более 81,9 миллиарда долларов США.</string>
|
||||
<string name="yield_module_promo_screen_self_custodial_title">Децентрализованный и некастодиальный</string>
|
||||
<string name="yield_module_promo_screen_terms_disclaimer">Используя сервис, вы соглашаетесь с условиями провайдера %1$s и %2$s</string>
|
||||
<string name="yield_module_promo_screen_title">Зарабатывайте %s%% в год</string>
|
||||
<string name="yield_module_promo_screen_variable_rate_info">Aave • Ставка с плавающим процентом</string>
|
||||
<string name="yield_module_promo_screen_title">Подключить Aave</string>
|
||||
<string name="yield_module_promo_screen_variable_rate_info">Aave %1$s%%• Ставка с плавающим процентом</string>
|
||||
<string name="yield_module_provider">Aave</string>
|
||||
<string name="yield_module_rate_info_sheet_chart_average">Среднее %s</string>
|
||||
<string name="yield_module_rate_info_sheet_chart_average">Сред. %s</string>
|
||||
<string name="yield_module_rate_info_sheet_chart_title">Доходность за прошлый год</string>
|
||||
<string name="yield_module_rate_info_sheet_description">Текущая процентная ставка всегда переменная и автоматически рассчитывается смарт-контрактом AAVE в блокчейне на основе текущего спроса и предложения.</string>
|
||||
<string name="yield_module_rate_info_sheet_powered_by">При поддержке</string>
|
||||
|
|
@ -1618,16 +1624,16 @@
|
|||
<string name="yield_module_start_earning_sheet_next_deposits">Следующие пополнения вашего счёта автоматически поступят в Aave.</string>
|
||||
<string name="yield_module_status_active">Активен</string>
|
||||
<string name="yield_module_status_paused">На паузе</string>
|
||||
<string name="yield_module_stop_earning">Закончить зарабатывать</string>
|
||||
<string name="yield_module_stop_earning">Завершить заработок</string>
|
||||
<string name="yield_module_stop_earning_sheet_description">Выключив эту функцию, вы выведёте средства из Aave, получите их обратно в %s в кошельке и перестанете зарабатывать награды.</string>
|
||||
<string name="yield_module_stop_earning_sheet_fee_note">Комиссия сети будет вычтена из суммы вашего вывода.</string>
|
||||
<string name="yield_module_supply_apr">Годовая доходность (APY)</string>
|
||||
<string name="yield_module_token_details_earn_notification_apy">APY</string>
|
||||
<string name="yield_module_token_details_earn_notification_description">Пусть ваши деньги работают — зарабатывайте проценты на свой баланс.</string>
|
||||
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_subtitle">Проценты начисляются автоматически.</string>
|
||||
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_title">Доходность Aave</string>
|
||||
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_title">Доходность</string>
|
||||
<string name="yield_module_token_details_earn_notification_processing">Отправка ваших средств</string>
|
||||
<string name="yield_module_token_details_earn_notification_title">Зарабатывайте %1$s%% в год</string>
|
||||
<string name="yield_module_token_details_earn_notification_title">Пусть ваш баланс работает на вас!</string>
|
||||
<string name="yield_module_transfer_mode_automatic">Автоматически</string>
|
||||
<string name="yield_module_unable_to_cover_fee_description">Внесите немного %1$s %2$s, чтобы покрыть комиссию сети за транзакции.</string>
|
||||
<string name="yield_module_unable_to_cover_fee_title">Невозможно покрыть комиссию в %s</string>
|
||||
|
|
|
|||
|
|
@ -992,6 +992,10 @@
|
|||
<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_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>
|
||||
<string name="reset_cards_dialog_first_description">Something went wrong with activation process. Please reset cards one by one.</string>
|
||||
<string name="reset_cards_dialog_first_title">Card verification failed</string>
|
||||
<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="save_user_wallet_agreement_access_description">Log into the app and check your balance without scanning the card or ring</string>
|
||||
|
|
@ -1817,6 +1821,8 @@
|
|||
<string name="yield_module_earn_badge">APY %1$s%%</string>
|
||||
<string name="yield_module_earn_sheet_available_title">Available</string>
|
||||
<string name="yield_module_earn_sheet_current_apy_title">Current APY</string>
|
||||
<string name="yield_module_earn_sheet_fee_description">When topping up for lending, a network fee will be deducted from the amount — never more than %1$s</string>
|
||||
<string name="yield_module_earn_sheet_high_fee_description">The network fee is currently too high to execute lending. Funds will be supplied once it drops to %1$s or below. </string>
|
||||
<string name="yield_module_earn_sheet_my_funds_title">My funds</string>
|
||||
<string name="yield_module_earn_sheet_provider_description">Your %1$s is now deposited in Aave and earning interest. You hold a%2$s token, which represents your balance and grows over time. When you top up, funds go to Aave to earn interest, minus a transaction fee.</string>
|
||||
<string name="yield_module_earn_sheet_title">Earn</string>
|
||||
|
|
@ -1832,7 +1838,7 @@
|
|||
<string name="yield_module_fee_policy_sheet_min_amount_title">Minimal top-up</string>
|
||||
<string name="yield_module_fee_policy_sheet_title">Fee policy</string>
|
||||
<string name="yield_module_fee_policy_tangem_service_fee_title">Tangem also takes a 3% service fee on the yield earned.</string>
|
||||
<string name="yield_module_high_fee_error">Network fee is too high right now. Waiting until it falls below your limit.</string>
|
||||
<string name="yield_module_high_fee_error">Your funds will be automatically transferred to Aave once network fees are lower or your balance meets the minimum required amount.</string>
|
||||
<string name="yield_module_historical_returns">Historical returns</string>
|
||||
<string name="yield_module_main_view_approve_notification_description">Write description here. In one, two or three lines will be awesome. [PLACEHOLDER]</string>
|
||||
<string name="yield_module_main_view_approve_notification_title">Some token approve needed</string>
|
||||
|
|
@ -1861,16 +1867,16 @@
|
|||
<string name="yield_module_start_earning_sheet_next_deposits">Your next top-ups will be automatically supplied to Aave.</string>
|
||||
<string name="yield_module_status_active">Active</string>
|
||||
<string name="yield_module_status_paused">Paused</string>
|
||||
<string name="yield_module_stop_earning">Stop earning</string>
|
||||
<string name="yield_module_stop_earning">Disable yield mode</string>
|
||||
<string name="yield_module_stop_earning_sheet_description">Turning off will withdraw your funds from Aave, return them to %s in your wallet, and stop earning rewards.</string>
|
||||
<string name="yield_module_stop_earning_sheet_fee_note">The network fee will be deducted from the amount you withdraw.</string>
|
||||
<string name="yield_module_supply_apr">Supply APY</string>
|
||||
<string name="yield_module_token_details_earn_notification_apy">APY</string>
|
||||
<string name="yield_module_token_details_earn_notification_description">Make your money work — earn interest on your balance.</string>
|
||||
<string name="yield_module_token_details_earn_notification_description">Let your funds work in the background while you stay in control.</string>
|
||||
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_subtitle">Interest accrues automatically</string>
|
||||
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_title">Aave yield</string>
|
||||
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_title">Yield mode</string>
|
||||
<string name="yield_module_token_details_earn_notification_processing">Processing your deposit</string>
|
||||
<string name="yield_module_token_details_earn_notification_title">Earn %1$s%% per year</string>
|
||||
<string name="yield_module_token_details_earn_notification_title">Make your balance work for you</string>
|
||||
<string name="yield_module_transfer_mode_automatic">Automatic</string>
|
||||
<string name="yield_module_unable_to_cover_fee_description">Deposit some %1$s %2$s to cover the network fee for transactions</string>
|
||||
<string name="yield_module_unable_to_cover_fee_title">Unable to cover %s fee</string>
|
||||
|
|
|
|||
|
|
@ -31,7 +31,9 @@ internal class BoundCounter(
|
|||
}
|
||||
|
||||
fun addNextChar() {
|
||||
string += text[charPosition(string.count())]
|
||||
val nextIndex = charPosition(string.count())
|
||||
if (nextIndex < 0 || nextIndex >= text.length) return
|
||||
string += text[nextIndex]
|
||||
width += nextCharWidth()
|
||||
_nextCharWidth = null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,7 @@ package com.tangem.core.ui.components.token.internal
|
|||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
|
|
@ -63,6 +59,7 @@ private fun ContentTitle(state: TokenTitleState.Content, modifier: Modifier = Mo
|
|||
|
||||
YieldSupplyApyLabel(
|
||||
apy = state.earnApy,
|
||||
isActive = state.earnApyIsActive,
|
||||
modifier = Modifier.align(alignment = Alignment.CenterVertically),
|
||||
)
|
||||
}
|
||||
|
|
@ -81,18 +78,26 @@ private fun CurrencyNameText(name: String, isAvailable: Boolean, modifier: Modif
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun YieldSupplyApyLabel(apy: TextReference?, modifier: Modifier = Modifier) {
|
||||
private fun YieldSupplyApyLabel(apy: TextReference?, isActive: Boolean, modifier: Modifier = Modifier) {
|
||||
AnimatedVisibility(visible = apy != null, modifier = modifier) {
|
||||
Box(
|
||||
modifier = modifier.background(
|
||||
color = TangemTheme.colors.text.accent.copy(alpha = 0.1f),
|
||||
modifier = Modifier.background(
|
||||
color = if (isActive) {
|
||||
TangemTheme.colors.text.accent.copy(alpha = 0.1f)
|
||||
} else {
|
||||
TangemTheme.colors.control.unchecked
|
||||
},
|
||||
shape = TangemTheme.shapes.roundedCornersSmall2,
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
text = apy?.resolveReference().orEmpty(),
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.text.accent,
|
||||
color = if (isActive) {
|
||||
TangemTheme.colors.text.accent
|
||||
} else {
|
||||
TangemTheme.colors.text.secondary
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -169,6 +169,7 @@ sealed class TokenItemState {
|
|||
val hasPending: Boolean = false,
|
||||
val isAvailable: Boolean = true,
|
||||
val earnApy: TextReference? = null,
|
||||
val earnApyIsActive: Boolean = false,
|
||||
) : TitleState()
|
||||
|
||||
data object Loading : TitleState()
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import java.util.Locale
|
|||
|
||||
class BigDecimalPercentFormat(
|
||||
val isWithoutSign: Boolean = true,
|
||||
val withPercentSign: Boolean = true,
|
||||
val locale: Locale = Locale.getDefault(),
|
||||
) : BigDecimalFormat {
|
||||
override fun invoke(value: BigDecimal): String = default()(value)
|
||||
|
|
@ -16,24 +17,30 @@ class BigDecimalPercentFormat(
|
|||
|
||||
fun BigDecimalFormatScope.percent(
|
||||
withoutSign: Boolean = true,
|
||||
withPercentSign: Boolean = true,
|
||||
locale: Locale = Locale.getDefault(),
|
||||
): BigDecimalPercentFormat {
|
||||
return BigDecimalPercentFormat(
|
||||
isWithoutSign = withoutSign,
|
||||
locale = locale,
|
||||
withPercentSign = withPercentSign,
|
||||
)
|
||||
}
|
||||
|
||||
// == Formatters ==
|
||||
|
||||
private fun BigDecimalPercentFormat.default(): BigDecimalFormat = BigDecimalFormat { value ->
|
||||
val formatter = NumberFormat.getPercentInstance(locale).apply {
|
||||
val formatter = if (withPercentSign) {
|
||||
NumberFormat.getPercentInstance(locale)
|
||||
} else {
|
||||
NumberFormat.getNumberInstance(locale)
|
||||
}.apply {
|
||||
maximumFractionDigits = 2
|
||||
minimumFractionDigits = 2
|
||||
roundingMode = RoundingMode.HALF_UP
|
||||
}
|
||||
|
||||
val valueToFormat = if (isWithoutSign) value.abs() else value
|
||||
val finalValue = if (withPercentSign) valueToFormat else valueToFormat.movePointRight(2)
|
||||
|
||||
formatter.format(valueToFormat)
|
||||
formatter.format(finalValue)
|
||||
}
|
||||
|
|
@ -22,4 +22,6 @@ object TangemBlogUrlBuilder {
|
|||
const val RESOURCE_TO_LEARN_ABOUT_APPROVING_IN_SWAP = "https://tangem.com/en/blog/post/give-revoke-permission/"
|
||||
|
||||
const val YIELD_SUPPLY_HOW_IT_WORKS_URL = "https://tangem.com/en/blog/post/savings-account"
|
||||
const val YIELD_SUPPLY_TOS_URL = "https://aave.com/terms-of-service"
|
||||
const val YIELD_SUPPLY_PRIVACY_URL = "https://aave.com/privacy-policy"
|
||||
}
|
||||
|
|
@ -41,6 +41,7 @@ internal object TokenMarketListConverter : Converter<TokenMarketListResponse, To
|
|||
),
|
||||
tokenCharts = TokenMarket.Charts(h24 = null, week = null, month = null),
|
||||
stakingRate = stakingRate,
|
||||
updateTimestamp = value.timestamp,
|
||||
)
|
||||
}
|
||||
return TokenMarketListWithMaxApy(tokens, value.summary?.maxApy)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ internal class NetworkYieldSupplyStatusConverter(
|
|||
isActive = it.isActive,
|
||||
isInitialized = it.isInitialized,
|
||||
isAllowedToSpend = it.isAllowedToSpend,
|
||||
effectiveProtocolBalance = it.effectiveProtocolBalance,
|
||||
)
|
||||
|
||||
id to status
|
||||
|
|
@ -38,6 +39,7 @@ internal class NetworkYieldSupplyStatusConverter(
|
|||
isActive = yieldSupplyStatus.isActive,
|
||||
isInitialized = yieldSupplyStatus.isInitialized,
|
||||
isAllowedToSpend = yieldSupplyStatus.isAllowedToSpend,
|
||||
effectiveProtocolBalance = yieldSupplyStatus.effectiveProtocolBalance,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ internal class NetworkStatusDataModelConverterTest {
|
|||
isActive = false,
|
||||
isInitialized = false,
|
||||
isAllowedToSpend = false,
|
||||
effectiveProtocolBalance = BigDecimal.ONE,
|
||||
),
|
||||
ID(
|
||||
prefix = Prefix.COIN_PREFIX,
|
||||
|
|
@ -94,6 +95,7 @@ internal class NetworkStatusDataModelConverterTest {
|
|||
isActive = false,
|
||||
isInitialized = false,
|
||||
isAllowedToSpend = false,
|
||||
effectiveProtocolBalance = BigDecimal.ONE,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.tangem.domain.models.network.Network
|
|||
import com.tangem.domain.models.yield.supply.YieldSupplyStatus
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import java.math.BigDecimal
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class NetworkYieldSupplyStatusConverterTest {
|
||||
|
|
@ -21,6 +22,7 @@ internal class NetworkYieldSupplyStatusConverterTest {
|
|||
isActive = true,
|
||||
isInitialized = true,
|
||||
isAllowedToSpend = true,
|
||||
effectiveProtocolBalance = BigDecimal.ONE,
|
||||
)
|
||||
|
||||
@Test
|
||||
|
|
@ -70,6 +72,7 @@ internal class NetworkYieldSupplyStatusConverterTest {
|
|||
isActive = true,
|
||||
isInitialized = true,
|
||||
isAllowedToSpend = true,
|
||||
effectiveProtocolBalance = BigDecimal.ONE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -68,6 +68,7 @@ internal class SimpleNetworkStatusConverterTest {
|
|||
isActive = false,
|
||||
isInitialized = false,
|
||||
isAllowedToSpend = false,
|
||||
effectiveProtocolBalance = BigDecimal.ONE,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -103,6 +104,7 @@ internal class SimpleNetworkStatusConverterTest {
|
|||
isActive = false,
|
||||
isInitialized = false,
|
||||
isAllowedToSpend = false,
|
||||
effectiveProtocolBalance = BigDecimal.ONE,
|
||||
),
|
||||
),
|
||||
source = StatusSource.CACHE,
|
||||
|
|
|
|||
|
|
@ -272,6 +272,7 @@ internal class NetworkStatusFactoryTest(private val model: Model) {
|
|||
isActive = false,
|
||||
isInitialized = false,
|
||||
isAllowedToSpend = false,
|
||||
effectiveProtocolBalance = BigDecimal.ONE,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import com.tangem.datasource.api.common.response.ApiResponse
|
|||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.stakekit.StakeKitApi
|
||||
import com.tangem.datasource.api.stakekit.models.request.*
|
||||
import com.tangem.datasource.api.stakekit.models.response.EnabledYieldsResponse
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.NetworkTypeDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionStatusDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.transaction.tron.TronStakeKitTransaction
|
||||
|
|
@ -56,6 +57,8 @@ import com.tangem.lib.crypto.BlockchainUtils.isCardano
|
|||
import com.tangem.lib.crypto.BlockchainUtils.isSolana
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
|
@ -91,17 +94,20 @@ internal class DefaultStakingRepository(
|
|||
|
||||
override suspend fun fetchEnabledYields() {
|
||||
withContext(dispatchers.io) {
|
||||
when (val stakingTokensWithYields = stakeKitApi.getEnabledYields(preferredValidatorsOnly = false)) {
|
||||
is ApiResponse.Success -> stakingYieldsStore.store(
|
||||
stakingTokensWithYields.data.data.filter {
|
||||
it.isAvailable == true
|
||||
},
|
||||
)
|
||||
else -> {
|
||||
stakingYieldsStore.store(emptyList())
|
||||
throw (stakingTokensWithYields as ApiResponse.Error).cause
|
||||
val yieldsResponses = getAvailableIntegrationsIds().map {
|
||||
async { it.getYieldRequest() }
|
||||
}.awaitAll()
|
||||
|
||||
val yields = yieldsResponses.flatMap { response ->
|
||||
when (response) {
|
||||
is ApiResponse.Success -> response.data.data.filter { yield -> yield.isAvailable == true }
|
||||
else -> {
|
||||
Timber.e("Error fetching enabled yields: ${(response as? ApiResponse.Error)?.cause}")
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
stakingYieldsStore.store(yields)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -151,6 +157,26 @@ internal class DefaultStakingRepository(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun StakingIntegrationID.getYieldRequest(): ApiResponse<EnabledYieldsResponse> {
|
||||
return when (this) {
|
||||
is StakingIntegrationID.Coin -> stakeKitApi.getEnabledYields(
|
||||
preferredValidatorsOnly = false,
|
||||
network = networkId,
|
||||
)
|
||||
is StakingIntegrationID.EthereumToken -> stakeKitApi.getEnabledYields(
|
||||
preferredValidatorsOnly = false,
|
||||
yieldId = value,
|
||||
network = networkId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAvailableIntegrationsIds(): List<StakingIntegrationID> {
|
||||
return StakingIntegrationID.entries.filterNot {
|
||||
it.blockchain == Blockchain.Cardano && !stakingFeatureToggles.isCardanoStakingEnabled
|
||||
}
|
||||
}
|
||||
|
||||
private fun NetworkTypeDTO.extractJsonName(): String {
|
||||
return networkTypeAdapter.toJson(this).replace("\"", "")
|
||||
}
|
||||
|
|
@ -364,7 +390,8 @@ internal class DefaultStakingRepository(
|
|||
)
|
||||
|
||||
val transaction = transactionConverter.convert(transactionResponse.getOrThrow())
|
||||
val unsignedTransaction = transaction.unsignedTransaction ?: error("No unsigned transaction available")
|
||||
val unsignedTransaction =
|
||||
transaction.unsignedTransaction ?: error("No unsigned transaction available")
|
||||
val transactionData = TransactionData.Compiled(
|
||||
value = getTransactionDataType(networkId, unsignedTransaction),
|
||||
fee = fee,
|
||||
|
|
@ -473,7 +500,7 @@ internal class DefaultStakingRepository(
|
|||
)
|
||||
}
|
||||
|
||||
private fun getEnabledYields(): Flow<List<Yield>> {
|
||||
override fun getEnabledYields(): Flow<List<Yield>> {
|
||||
return stakingYieldsStore.get().map {
|
||||
YieldConverter.convertListIgnoreErrors(
|
||||
input = it,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
package com.tangem.data.tokens.repository
|
||||
|
||||
import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider
|
||||
import com.tangem.blockchain.common.FeeResourceAmountProvider
|
||||
import com.tangem.blockchain.common.MinimumSendAmountProvider
|
||||
import com.tangem.blockchain.common.ReserveAmountProvider
|
||||
import com.tangem.blockchain.common.UtxoAmountLimitProvider
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.data.tokens.converters.UtxoConverter
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
|
|
@ -21,6 +19,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
|||
import com.tangem.utils.extensions.isZero
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class DefaultCurrencyChecksRepository(
|
||||
|
|
@ -141,7 +140,11 @@ internal class DefaultCurrencyChecksRepository(
|
|||
return when {
|
||||
balanceValue.amount.isZero() && stakingTotalBalance.isZero() -> null
|
||||
balanceValue.amount < rentData.exemptionAmount && stakingTotalBalance.isZero() -> {
|
||||
CryptoCurrencyWarning.Rent(rentData.rent, rentData.exemptionAmount)
|
||||
CryptoCurrencyWarning.Rent(
|
||||
rent = rentData.rent,
|
||||
exemptionAmount = rentData.exemptionAmount,
|
||||
cryptoCurrency = currencyStatus.currency,
|
||||
)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
|
@ -156,9 +159,36 @@ internal class DefaultCurrencyChecksRepository(
|
|||
return when {
|
||||
balanceAfterTransaction.isZero() -> null
|
||||
balanceAfterTransaction < rentData.exemptionAmount -> {
|
||||
CryptoCurrencyWarning.Rent(rentData.rent, rentData.exemptionAmount)
|
||||
CryptoCurrencyWarning.Rent(
|
||||
rent = rentData.rent,
|
||||
exemptionAmount = rentData.exemptionAmount,
|
||||
cryptoCurrency = currencyStatus.currency,
|
||||
)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getProtocolBalance(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
): BigDecimal? {
|
||||
val token = cryptoCurrencyStatus.currency as? CryptoCurrency.Token ?: return null
|
||||
val isActive = cryptoCurrencyStatus.value.yieldSupplyStatus?.isActive ?: false
|
||||
if (!isActive) return null
|
||||
return runCatching {
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(
|
||||
userWalletId = userWalletId,
|
||||
blockchain = token.network.toBlockchain(),
|
||||
derivationPath = token.network.derivationPath.value,
|
||||
) ?: error("Wallet manager not found")
|
||||
walletManager.getEffectiveProtocolBalance(
|
||||
token = Token(
|
||||
symbol = token.symbol,
|
||||
contractAddress = token.contractAddress,
|
||||
decimals = token.decimals,
|
||||
),
|
||||
)
|
||||
}.onFailure(Timber::e).getOrThrow()
|
||||
}
|
||||
}
|
||||
|
|
@ -128,6 +128,7 @@ internal class UpdateWalletManagerResultFactory {
|
|||
isActive = type.isActive,
|
||||
isInitialized = type.isInitialized,
|
||||
isAllowedToSpend = type.isAllowedToSpend,
|
||||
effectiveProtocolBalance = type.effectiveProtocolBalance,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,24 +91,26 @@ internal class DefaultYieldSupplyTransactionRepository(
|
|||
)
|
||||
}
|
||||
|
||||
override suspend fun getProtocolBalance(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): BigDecimal? =
|
||||
withContext(dispatchers.io) {
|
||||
require(cryptoCurrency is CryptoCurrency.Token)
|
||||
runCatching {
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(
|
||||
userWalletId = userWalletId,
|
||||
blockchain = cryptoCurrency.network.toBlockchain(),
|
||||
derivationPath = cryptoCurrency.network.derivationPath.value,
|
||||
) ?: error("Wallet manager not found")
|
||||
walletManager.getProtocolBalance(
|
||||
token = Token(
|
||||
symbol = cryptoCurrency.symbol,
|
||||
contractAddress = cryptoCurrency.contractAddress,
|
||||
decimals = cryptoCurrency.decimals,
|
||||
),
|
||||
)
|
||||
}.onFailure(Timber::e).getOrThrow()
|
||||
}
|
||||
override suspend fun getEffectiveProtocolBalance(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): BigDecimal? = withContext(dispatchers.io) {
|
||||
require(cryptoCurrency is CryptoCurrency.Token)
|
||||
runCatching {
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(
|
||||
userWalletId = userWalletId,
|
||||
blockchain = cryptoCurrency.network.toBlockchain(),
|
||||
derivationPath = cryptoCurrency.network.derivationPath.value,
|
||||
) ?: error("Wallet manager not found")
|
||||
walletManager.getEffectiveProtocolBalance(
|
||||
token = Token(
|
||||
symbol = cryptoCurrency.symbol,
|
||||
contractAddress = cryptoCurrency.contractAddress,
|
||||
decimals = cryptoCurrency.decimals,
|
||||
),
|
||||
)
|
||||
}.onFailure(Timber::e).getOrThrow()
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
private suspend fun buildEnterTransactions(
|
||||
|
|
@ -222,6 +224,17 @@ internal class DefaultYieldSupplyTransactionRepository(
|
|||
): YieldSupplyStatus? = withContext(dispatchers.io) {
|
||||
runCatching {
|
||||
val sdkSupplyStatus = walletManager.getYieldSupplyStatus(cryptoCurrency.contractAddress)
|
||||
val protocolBalance = if (sdkSupplyStatus?.isActive == true) {
|
||||
walletManager.getEffectiveProtocolBalance(
|
||||
token = Token(
|
||||
symbol = cryptoCurrency.symbol,
|
||||
contractAddress = cryptoCurrency.contractAddress,
|
||||
decimals = cryptoCurrency.decimals,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val isAllowedToSpend = walletManager.isAllowedToSpend(
|
||||
Token(
|
||||
symbol = cryptoCurrency.symbol,
|
||||
|
|
@ -234,6 +247,7 @@ internal class DefaultYieldSupplyTransactionRepository(
|
|||
isActive = sdkSupplyStatus?.isActive == true,
|
||||
isInitialized = sdkSupplyStatus?.isInitialized == true,
|
||||
isAllowedToSpend = isAllowedToSpend,
|
||||
effectiveProtocolBalance = protocolBalance,
|
||||
)
|
||||
}.onFailure(Timber::e).getOrNull()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ data class TokenMarket(
|
|||
val tokenQuotesShort: TokenQuotesShort,
|
||||
val tokenCharts: Charts,
|
||||
val stakingRate: BigDecimal?,
|
||||
val updateTimestamp: Long?,
|
||||
private val imageHost: String,
|
||||
) {
|
||||
|
||||
|
|
|
|||
|
|
@ -2,4 +2,20 @@ package com.tangem.domain.models.currency
|
|||
|
||||
fun CryptoCurrency.Token.yieldSupplyKey(): String {
|
||||
return "${network.backendId}_$contractAddress"
|
||||
}
|
||||
|
||||
fun CryptoCurrencyStatus.yieldSupplyNotAllAmountSupplied(): Boolean {
|
||||
if (this.currency !is CryptoCurrency.Token) return false
|
||||
|
||||
val supplyStatus = this.value.yieldSupplyStatus
|
||||
if (supplyStatus?.isActive != true) return false
|
||||
|
||||
val protocolBalance = supplyStatus.effectiveProtocolBalance
|
||||
val amount = this.value.amount
|
||||
|
||||
return if (protocolBalance != null && amount != null) {
|
||||
amount > protocolBalance
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.domain.models.yield.supply
|
||||
|
||||
import com.tangem.domain.models.serialization.SerializedBigDecimal
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
|
|
@ -11,10 +12,12 @@ import kotlinx.serialization.Serializable
|
|||
* @property isActive Indicates if the yield token is currently active.
|
||||
* @property isInitialized Indicates if the yield token has been initialized.
|
||||
* @property isAllowedToSpend Indicates if spending from the yield module is permitted.
|
||||
*/
|
||||
* @property effectiveProtocolBalance Indicates the balance (excluding service fee)
|
||||
* */
|
||||
@Serializable
|
||||
data class YieldSupplyStatus(
|
||||
val isActive: Boolean,
|
||||
val isInitialized: Boolean,
|
||||
val isAllowedToSpend: Boolean,
|
||||
val effectiveProtocolBalance: SerializedBigDecimal?,
|
||||
)
|
||||
|
|
@ -23,31 +23,43 @@ sealed interface StakingIntegrationID {
|
|||
/** Approval requirements for the staking integration. Defaults to no approval needed */
|
||||
val approval: StakingApproval get() = StakingApproval.Empty
|
||||
|
||||
/**
|
||||
* Represents the network ID associated with the staking integration from provider
|
||||
* https://docs.yield.xyz/reference/yieldscontroller_getyields
|
||||
*/
|
||||
val networkId: String
|
||||
|
||||
/** Represents blockchains whose native coins can be staked */
|
||||
enum class Coin : StakingIntegrationID {
|
||||
Ton {
|
||||
override val value: String = "ton-ton-chorus-one-pools-staking"
|
||||
override val blockchain: Blockchain = Blockchain.TON
|
||||
override val networkId: String = "ton"
|
||||
},
|
||||
Solana {
|
||||
override val value: String = "solana-sol-native-multivalidator-staking"
|
||||
override val blockchain: Blockchain = Blockchain.Solana
|
||||
override val networkId: String = "solana"
|
||||
},
|
||||
Cosmos {
|
||||
override val value: String = "cosmos-atom-native-staking"
|
||||
override val blockchain: Blockchain = Blockchain.Cosmos
|
||||
override val networkId: String = "cosmos"
|
||||
},
|
||||
Tron {
|
||||
override val value: String = "tron-trx-native-staking"
|
||||
override val blockchain: Blockchain = Blockchain.Tron
|
||||
override val networkId: String = "tron"
|
||||
},
|
||||
BSC {
|
||||
override val value: String = "bsc-bnb-native-staking"
|
||||
override val blockchain: Blockchain = Blockchain.BSC
|
||||
override val networkId: String = "binance"
|
||||
},
|
||||
Cardano {
|
||||
override val value: String = "cardano-ada-native-staking"
|
||||
override val blockchain: Blockchain = Blockchain.Cardano
|
||||
override val networkId: String = "cardano"
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -61,6 +73,7 @@ sealed interface StakingIntegrationID {
|
|||
override val value: String = "ethereum-matic-native-staking"
|
||||
override val approval: StakingApproval.Needed =
|
||||
StakingApproval.Needed(spenderAddress = "0x5e3Ef299fDDf15eAa0432E6e66473ace8c13D908")
|
||||
override val networkId: String = "ethereum"
|
||||
},
|
||||
;
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ interface StakingRepository {
|
|||
|
||||
suspend fun fetchEnabledYields()
|
||||
|
||||
fun getEnabledYields(): Flow<List<Yield>>
|
||||
|
||||
suspend fun getEntryInfo(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): StakingEntryInfo
|
||||
|
||||
suspend fun getYield(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): Yield
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.domain.staking.usecase
|
||||
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Emits a map of APY values per currency for staking.
|
||||
*
|
||||
* Return map:
|
||||
* - key: currency staking key (network.backendId + "_" + symbol)
|
||||
* - value: APY as string
|
||||
*/
|
||||
class StakingApyFlowUseCase(private val stakingRepository: StakingRepository) {
|
||||
|
||||
operator fun invoke(): Flow<Map<String, BigDecimal>> {
|
||||
return stakingRepository.getEnabledYields()
|
||||
.map { yields ->
|
||||
yields.associate { yield ->
|
||||
val key = "${yield.token.network.name.lowercase()}_${yield.token.symbol}"
|
||||
val apy = calculateApy(yield)
|
||||
key to apy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateApy(yield: Yield): BigDecimal {
|
||||
val rates = yield.validators.mapNotNull { it.rewardInfo?.rate }
|
||||
return if (rates.isNotEmpty()) {
|
||||
rates.maxOf { it }
|
||||
} else {
|
||||
yield.apy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -56,6 +56,8 @@ sealed class ScenarioUnavailabilityReason {
|
|||
|
||||
data object TrustlineRequired : ScenarioUnavailabilityReason()
|
||||
|
||||
data object YieldSupplyApprovalRequired : ScenarioUnavailabilityReason()
|
||||
|
||||
enum class WithdrawalScenario {
|
||||
SELL, SEND // TODO staking create&process STAKING
|
||||
}
|
||||
|
|
|
|||
|
|
@ -192,6 +192,7 @@ sealed class TokenScreenAnalyticsEvent(
|
|||
is ScenarioUnavailabilityReason.NotExchangeable,
|
||||
is ScenarioUnavailabilityReason.NotSupportedBySellService,
|
||||
is ScenarioUnavailabilityReason.StakingUnavailable,
|
||||
ScenarioUnavailabilityReason.YieldSupplyApprovalRequired,
|
||||
-> UNAVAILABLE
|
||||
ScenarioUnavailabilityReason.UnassociatedAsset -> ASSET_REQUIREMENT
|
||||
ScenarioUnavailabilityReason.TrustlineRequired -> TRUSTLINE_REQUIREMENT
|
||||
|
|
|
|||
|
|
@ -39,8 +39,13 @@ sealed class CryptoCurrencyWarning {
|
|||
* @param rent Amount that will be charged in overtime if the blockchain does not have an amount greater than
|
||||
* the [exemptionAmount]
|
||||
* @param exemptionAmount Amount that should be on the blockchain balance not to pay rent
|
||||
* @param cryptoCurrency Currency in which the rent is charged
|
||||
*/
|
||||
data class Rent(val rent: BigDecimal, val exemptionAmount: BigDecimal) : CryptoCurrencyWarning()
|
||||
data class Rent(
|
||||
val rent: BigDecimal,
|
||||
val exemptionAmount: BigDecimal,
|
||||
val cryptoCurrency: CryptoCurrency,
|
||||
) : CryptoCurrencyWarning()
|
||||
|
||||
data class SwapPromo(
|
||||
val promoId: PromoId,
|
||||
|
|
@ -68,4 +73,10 @@ sealed class CryptoCurrencyWarning {
|
|||
val requiredAmount: BigDecimal,
|
||||
val currencyDecimals: Int,
|
||||
) : CryptoCurrencyWarning()
|
||||
|
||||
data class YieldSupplyNotDepositedAmount(
|
||||
val currency: CryptoCurrency,
|
||||
val currencySymbol: String,
|
||||
val amount: BigDecimal,
|
||||
) : CryptoCurrencyWarning()
|
||||
}
|
||||
|
|
@ -51,7 +51,8 @@ class GetCurrencyWarningsUseCase(
|
|||
flowOf(currencyChecksRepository.getRentInfoWarning(userWalletId, currencyStatus)),
|
||||
flowOf(currencyChecksRepository.getExistentialDeposit(userWalletId, currency.network)),
|
||||
flowOf(currencyChecksRepository.getFeeResourceAmount(userWalletId, currency.network)),
|
||||
) { coinRelatedWarnings, maybeRentWarning, maybeEdWarning, maybeFeeResource ->
|
||||
flowOf(currencyChecksRepository.getProtocolBalance(userWalletId, currencyStatus)),
|
||||
) { coinRelatedWarnings, maybeRentWarning, maybeEdWarning, maybeFeeResource, yieldSupplyProtocolBalance ->
|
||||
setOfNotNull(
|
||||
maybeRentWarning,
|
||||
maybeEdWarning?.let { getExistentialDepositWarning(currency, it) },
|
||||
|
|
@ -62,6 +63,7 @@ class GetCurrencyWarningsUseCase(
|
|||
getBeaconChainShutdownWarning(rawId = currency.network.id.rawId),
|
||||
getAssetRequirementsWarning(userWalletId = userWalletId, currency = currency),
|
||||
getMigrationFromMaticToPolWarning(currency),
|
||||
getYieldSupplyWarning(cryptoCurrencyStatus = currencyStatus, yieldSupplyProtocolBalance),
|
||||
)
|
||||
}.flowOn(dispatchers.io)
|
||||
}
|
||||
|
|
@ -261,6 +263,28 @@ class GetCurrencyWarningsUseCase(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getYieldSupplyWarning(
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
protocolBalance: BigDecimal?,
|
||||
): CryptoCurrencyWarning? {
|
||||
val value = cryptoCurrencyStatus.value
|
||||
val isActive = value.yieldSupplyStatus?.isActive == true
|
||||
val amount = value.amount
|
||||
|
||||
if (!isActive || protocolBalance == null || amount == null) return null
|
||||
|
||||
val notDepositedAmount = amount.minus(protocolBalance)
|
||||
return if (notDepositedAmount > BigDecimal.ZERO) {
|
||||
CryptoCurrencyWarning.YieldSupplyNotDepositedAmount(
|
||||
currency = cryptoCurrencyStatus.currency,
|
||||
amount = notDepositedAmount,
|
||||
currencySymbol = cryptoCurrencyStatus.currency.symbol,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun BigDecimal?.isZero(): Boolean {
|
||||
return this?.signum() == 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,15 +5,18 @@ import com.tangem.domain.tokens.repository.YieldSupplyWarningsViewedRepository
|
|||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@Suppress("UnusedPrivateProperty")
|
||||
class NeedShowYieldSupplyDepositedWarningUseCase(
|
||||
private val yieldSupplyWarningsViewedRepository: YieldSupplyWarningsViewedRepository,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(cryptoCurrencyStatus: CryptoCurrencyStatus?): Boolean = withContext(dispatchers.io) {
|
||||
val hasActiveLending = cryptoCurrencyStatus?.value?.yieldSupplyStatus?.isActive == true
|
||||
if (!hasActiveLending) return@withContext false
|
||||
val showedWarnings = yieldSupplyWarningsViewedRepository.getViewedWarnings()
|
||||
return@withContext !showedWarnings.contains(cryptoCurrencyStatus?.currency?.name)
|
||||
// TEMPORARY REQUIREMENTS
|
||||
return@withContext false
|
||||
// val hasActiveLending = cryptoCurrencyStatus?.value?.yieldSupplyStatus?.isActive == true
|
||||
// if (!hasActiveLending) return@withContext false
|
||||
// val showedWarnings = yieldSupplyWarningsViewedRepository.getViewedWarnings()
|
||||
// return@withContext !showedWarnings.contains(cryptoCurrencyStatus?.currency?.name)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.domain.tokens.actions
|
||||
|
||||
import com.tangem.domain.exchange.RampStateManager
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
|
@ -69,7 +68,7 @@ internal class CommonActionsFactory(
|
|||
async {
|
||||
getSwapUnavailabilityReason(
|
||||
userWalletId = userWallet.walletId,
|
||||
currency = cryptoCurrencyStatus.currency,
|
||||
currencyStatus = cryptoCurrencyStatus,
|
||||
requirementsDeferred = requirementsDeferred,
|
||||
)
|
||||
}
|
||||
|
|
@ -175,17 +174,21 @@ internal class CommonActionsFactory(
|
|||
|
||||
private suspend fun getSwapUnavailabilityReason(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
currencyStatus: CryptoCurrencyStatus,
|
||||
requirementsDeferred: Deferred<AssetRequirementsCondition?>?,
|
||||
): ScenarioUnavailabilityReason {
|
||||
val swapUnavailabilityReason = rampStateManager
|
||||
.availableForSwap(userWalletId = userWalletId, cryptoCurrency = currency)
|
||||
.availableForSwap(userWalletId = userWalletId, cryptoCurrency = currencyStatus.currency)
|
||||
val shouldCheckAssetRequirements =
|
||||
swapUnavailabilityReason == ScenarioUnavailabilityReason.None && requirementsDeferred != null
|
||||
return if (shouldCheckAssetRequirements) {
|
||||
getReceiveScenario(requirementsDeferred.await())
|
||||
} else {
|
||||
swapUnavailabilityReason
|
||||
|
||||
val yieldSupplyStatus = currencyStatus.value.yieldSupplyStatus
|
||||
val isUnavailableByYieldSupply = yieldSupplyStatus?.isAllowedToSpend == false && yieldSupplyStatus.isActive
|
||||
|
||||
return when {
|
||||
isUnavailableByYieldSupply -> ScenarioUnavailabilityReason.YieldSupplyApprovalRequired
|
||||
shouldCheckAssetRequirements -> getReceiveScenario(requirementsDeferred.await())
|
||||
else -> swapUnavailabilityReason
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -61,4 +61,11 @@ interface CurrencyChecksRepository {
|
|||
currencyStatus: CryptoCurrencyStatus,
|
||||
balanceAfterTransaction: BigDecimal,
|
||||
): CryptoCurrencyWarning.Rent?
|
||||
|
||||
/**
|
||||
* Returns the YieldSupplied protocol balance in Aave for the given `cryptoCurrency`.
|
||||
* This represents the amount supplied to the protocol for the specified `userWalletId`
|
||||
* (e.g., aTokens balance). Returns null if not applicable or unknown.
|
||||
*/
|
||||
suspend fun getProtocolBalance(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus): BigDecimal?
|
||||
}
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
package com.tangem.domain.tokens
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.network.NetworkAddress
|
||||
import com.tangem.domain.models.serialization.SerializedBigDecimal
|
||||
import com.tangem.domain.models.yield.supply.YieldSupplyStatus
|
||||
import com.tangem.domain.tokens.repository.YieldSupplyWarningsViewedRepository
|
||||
import com.tangem.domain.tokens.mock.MockTokens
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.impl.annotations.RelaxedMockK
|
||||
import io.mockk.junit5.MockKExtension
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@ExtendWith(MockKExtension::class)
|
||||
class NeedShowYieldSupplyDepositedWarningUseCaseTest {
|
||||
|
||||
@RelaxedMockK
|
||||
private lateinit var repository: YieldSupplyWarningsViewedRepository
|
||||
|
||||
private lateinit var dispatchers: TestingCoroutineDispatcherProvider
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
dispatchers = TestingCoroutineDispatcherProvider()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN null status WHEN invoke THEN returns false`() = runTest {
|
||||
val useCase = NeedShowYieldSupplyDepositedWarningUseCase(repository, dispatchers)
|
||||
|
||||
val result = useCase.invoke(null)
|
||||
|
||||
assertThat(result).isFalse()
|
||||
coVerify(exactly = 0) { repository.getViewedWarnings() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN inactive lending WHEN invoke THEN returns false`() = runTest {
|
||||
val useCase = NeedShowYieldSupplyDepositedWarningUseCase(repository, dispatchers)
|
||||
val status = createStatus(isActive = false)
|
||||
|
||||
val result = useCase.invoke(status)
|
||||
|
||||
assertThat(result).isFalse()
|
||||
coVerify(exactly = 0) { repository.getViewedWarnings() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN active lending and not viewed WHEN invoke THEN returns true`() = runTest {
|
||||
val useCase = NeedShowYieldSupplyDepositedWarningUseCase(repository, dispatchers)
|
||||
val status = createStatus(isActive = true)
|
||||
coEvery { repository.getViewedWarnings() } returns emptySet()
|
||||
|
||||
val result = useCase.invoke(status)
|
||||
|
||||
assertThat(result).isTrue()
|
||||
coVerify(exactly = 1) { repository.getViewedWarnings() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN active lending and already viewed WHEN invoke THEN returns false`() = runTest {
|
||||
val useCase = NeedShowYieldSupplyDepositedWarningUseCase(repository, dispatchers)
|
||||
val status = createStatus(isActive = true)
|
||||
coEvery { repository.getViewedWarnings() } returns setOf(status.currency.name)
|
||||
|
||||
val result = useCase.invoke(status)
|
||||
|
||||
assertThat(result).isFalse()
|
||||
coVerify(exactly = 1) { repository.getViewedWarnings() }
|
||||
}
|
||||
|
||||
private fun createStatus(isActive: Boolean): CryptoCurrencyStatus {
|
||||
val currency = MockTokens.token1
|
||||
val yieldSupplyStatus = YieldSupplyStatus(
|
||||
isActive = isActive,
|
||||
isInitialized = true,
|
||||
isAllowedToSpend = true,
|
||||
)
|
||||
val value = CryptoCurrencyStatus.NoQuote(
|
||||
amount = SerializedBigDecimal.ZERO,
|
||||
yieldBalance = null,
|
||||
yieldSupplyStatus = yieldSupplyStatus,
|
||||
hasCurrentNetworkTransactions = false,
|
||||
pendingTransactions = emptySet(),
|
||||
networkAddress = NetworkAddress.Single(
|
||||
defaultAddress = NetworkAddress.Address(
|
||||
value = "address",
|
||||
type = NetworkAddress.Address.Type.Primary,
|
||||
),
|
||||
),
|
||||
sources = CryptoCurrencyStatus.Sources(),
|
||||
)
|
||||
|
||||
return CryptoCurrencyStatus(
|
||||
currency = currency,
|
||||
value = value,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.domain.yield.supply
|
||||
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import java.math.BigInteger
|
||||
|
||||
fun Fee.fixFee(cryptoCurrency: CryptoCurrency, gasLimit: BigInteger): Fee = when (this) {
|
||||
is Fee.Ethereum.Legacy -> copy(
|
||||
gasLimit = gasLimit,
|
||||
amount = amount.copy(
|
||||
value = gasPrice.multiply(gasLimit)
|
||||
.toBigDecimal().movePointLeft(cryptoCurrency.decimals),
|
||||
),
|
||||
)
|
||||
is Fee.Ethereum.EIP1559 -> copy(
|
||||
gasLimit = gasLimit,
|
||||
amount = amount.copy(
|
||||
value = maxFeePerGas.multiply(gasLimit)
|
||||
.toBigDecimal().movePointLeft(cryptoCurrency.decimals),
|
||||
),
|
||||
)
|
||||
else -> this
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.domain.yield.supply
|
||||
|
||||
object YieldSupplyConst {
|
||||
val YIELD_SUPPLY_EVM_CONSTANT_GAS_LIMIT = 350_000.toBigInteger()
|
||||
}
|
||||
|
|
@ -23,5 +23,5 @@ interface YieldSupplyTransactionRepository {
|
|||
|
||||
suspend fun getYieldContractAddress(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): String?
|
||||
|
||||
suspend fun getProtocolBalance(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): BigDecimal?
|
||||
suspend fun getEffectiveProtocolBalance(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): BigDecimal?
|
||||
}
|
||||
|
|
@ -3,17 +3,16 @@ package com.tangem.domain.yield.supply.usecase
|
|||
import arrow.core.Either
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyEnterCallData
|
||||
import com.tangem.domain.blockaid.BlockAidGasEstimate
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.yield.supply.fixFee
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.transaction.FeeRepository
|
||||
import com.tangem.domain.transaction.error.FeeErrorResolver
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.utils.extensions.isSingleItem
|
||||
import timber.log.Timber
|
||||
import java.math.BigInteger
|
||||
|
||||
class YieldSupplyEstimateEnterFeeUseCase(
|
||||
private val feeRepository: FeeRepository,
|
||||
|
|
@ -107,24 +106,6 @@ class YieldSupplyEstimateEnterFeeUseCase(
|
|||
return withCalculatedFees + withEstimatedFees
|
||||
}
|
||||
|
||||
private fun Fee.fixFee(cryptoCurrency: CryptoCurrency, gasLimit: BigInteger) = when (this) {
|
||||
is Fee.Ethereum.Legacy -> copy(
|
||||
gasLimit = gasLimit,
|
||||
amount = amount.copy(
|
||||
value = gasPrice.multiply(gasLimit)
|
||||
.toBigDecimal().movePointLeft(cryptoCurrency.decimals),
|
||||
),
|
||||
)
|
||||
is Fee.Ethereum.EIP1559 -> copy(
|
||||
gasLimit = gasLimit,
|
||||
amount = amount.copy(
|
||||
value = maxFeePerGas.multiply(gasLimit)
|
||||
.toBigDecimal().movePointLeft(cryptoCurrency.decimals),
|
||||
),
|
||||
)
|
||||
else -> this
|
||||
}
|
||||
|
||||
private companion object {
|
||||
// Using constant gas limit to avoid fee calculation errors when contract address is not deployed yet
|
||||
val ETHEREUM_CONSTANT_GAS_LIMIT = 500_000.toBigInteger()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
package com.tangem.domain.yield.supply.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.Either.Companion.catch
|
||||
import com.tangem.domain.yield.supply.fixFee
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.quote.QuoteStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.quotes.QuotesRepository
|
||||
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 java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
|
||||
/**
|
||||
* Calculates current fee for Yield Supply enter transaction expressed in token units.
|
||||
*/
|
||||
class YieldSupplyGetCurrentFeeUseCase(
|
||||
private val feeRepository: FeeRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
userWallet: UserWallet,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
): Either<Throwable, BigDecimal> = catch {
|
||||
val feeWithoutGas = feeRepository.getEthereumFeeWithoutGas(userWallet, cryptoCurrencyStatus.currency)
|
||||
|
||||
val fiatRate = cryptoCurrencyStatus.value.fiatRate ?: error("Fiat rate is missing")
|
||||
require(fiatRate > BigDecimal.ZERO) { "Fiat rate for token must be > 0" }
|
||||
|
||||
val nativeCryptoCurrency = currenciesRepository.getNetworkCoin(
|
||||
userWalletId = userWallet.walletId,
|
||||
networkId = cryptoCurrencyStatus.currency.network.id,
|
||||
derivationPath = cryptoCurrencyStatus.currency.network.derivationPath,
|
||||
)
|
||||
|
||||
val quotes =
|
||||
quotesRepository.getMultiQuoteSyncOrNull(setOfNotNull(nativeCryptoCurrency.id.rawCurrencyId))
|
||||
?: error("Quotes for native coin are unavailable")
|
||||
|
||||
val quotesStatus = quotes.firstOrNull() ?: error("Empty quotes list for native coin")
|
||||
|
||||
val nativeFiatRate = (quotesStatus.value as? QuoteStatus.Data)?.fiatRate
|
||||
?: error("Native fiat rate is missing")
|
||||
require(nativeFiatRate > BigDecimal.ZERO) { "Native fiat rate must be > 0" }
|
||||
|
||||
val nativeGas = feeWithoutGas.fixFee(
|
||||
nativeCryptoCurrency,
|
||||
YIELD_SUPPLY_EVM_CONSTANT_GAS_LIMIT,
|
||||
)
|
||||
|
||||
val rateRatio = nativeFiatRate.divide(
|
||||
fiatRate,
|
||||
cryptoCurrencyStatus.currency.decimals,
|
||||
RoundingMode.HALF_UP,
|
||||
)
|
||||
|
||||
val tokenValue = rateRatio.multiply(nativeGas.amount.value)
|
||||
|
||||
tokenValue.stripTrailingZeros()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package com.tangem.domain.yield.supply.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.Either.Companion.catch
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.quote.QuoteStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.quotes.QuotesRepository
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
|
||||
/**
|
||||
* Calculates max allowed network fee for Yield Supply enter transaction expressed in token units.
|
||||
*
|
||||
* Uses YieldMarketToken.maxFeeNative (native coin units) and converts it to token units with the
|
||||
* same conversion logic as [YieldSupplyGetCurrentFeeUseCase]: based on fiat rate ratio
|
||||
* (nativeFiatRate / tokenFiatRate).
|
||||
*/
|
||||
class YieldSupplyGetMaxFeeUseCase(
|
||||
private val yieldSupplyRepository: YieldSupplyRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
userWallet: UserWallet,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
): Either<Throwable, BigDecimal> = catch {
|
||||
val token = cryptoCurrencyStatus.currency as? CryptoCurrency.Token
|
||||
?: error("CryptoCurrency must be token for max fee calculation")
|
||||
|
||||
val fiatRate = cryptoCurrencyStatus.value.fiatRate ?: error("Fiat rate is missing")
|
||||
require(fiatRate > BigDecimal.ZERO) { "Fiat rate for token must be > 0" }
|
||||
|
||||
val nativeCryptoCurrency = currenciesRepository.getNetworkCoin(
|
||||
userWalletId = userWallet.walletId,
|
||||
networkId = cryptoCurrencyStatus.currency.network.id,
|
||||
derivationPath = cryptoCurrencyStatus.currency.network.derivationPath,
|
||||
)
|
||||
|
||||
val quotes =
|
||||
quotesRepository.getMultiQuoteSyncOrNull(setOfNotNull(nativeCryptoCurrency.id.rawCurrencyId))
|
||||
?: error("Quotes for native coin are unavailable")
|
||||
|
||||
val quotesStatus = quotes.firstOrNull() ?: error("Empty quotes list for native coin")
|
||||
|
||||
val nativeFiatRate = (quotesStatus.value as? QuoteStatus.Data)?.fiatRate
|
||||
?: error("Native fiat rate is missing")
|
||||
require(nativeFiatRate > BigDecimal.ZERO) { "Native fiat rate must be > 0" }
|
||||
|
||||
val marketToken = yieldSupplyRepository.getTokenStatus(token)
|
||||
val maxFeeNative = marketToken.maxFeeNative.toBigDecimal()
|
||||
|
||||
val rateRatio = nativeFiatRate.divide(
|
||||
fiatRate,
|
||||
cryptoCurrencyStatus.currency.decimals,
|
||||
RoundingMode.HALF_UP,
|
||||
)
|
||||
|
||||
val tokenValue = rateRatio.multiply(maxFeeNative)
|
||||
|
||||
tokenValue.stripTrailingZeros()
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ class YieldSupplyGetProtocolBalanceUseCase(
|
|||
): Either<YieldSupplyError, BigDecimal?> = Either.catch {
|
||||
requireNotNull(cryptoCurrency as CryptoCurrency.Token)
|
||||
|
||||
yieldSupplyTransactionRepository.getProtocolBalance(
|
||||
yieldSupplyTransactionRepository.getEffectiveProtocolBalance(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,6 @@ class YieldSupplyGetTokenStatusUseCase(
|
|||
suspend operator fun invoke(token: CryptoCurrency.Token): Either<Throwable, YieldMarketToken> = Either.catch {
|
||||
val tokens = yieldSupplyRepository.getCachedMarkets().orEmpty()
|
||||
val cachedStatus = tokens.firstOrNull { it.yieldSupplyKey == token.yieldSupplyKey() }
|
||||
cachedStatus ?: error("YieldMarketToken not found")
|
||||
cachedStatus ?: yieldSupplyRepository.getTokenStatus(token)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,16 +2,15 @@ package com.tangem.domain.yield.supply.usecase
|
|||
|
||||
import arrow.core.Either
|
||||
import arrow.core.Either.Companion.catch
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.yield.supply.fixFee
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.quote.QuoteStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.quotes.QuotesRepository
|
||||
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 java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
import java.math.RoundingMode
|
||||
|
||||
class YieldSupplyMinAmountUseCase(
|
||||
|
|
@ -45,7 +44,10 @@ class YieldSupplyMinAmountUseCase(
|
|||
?: error("Native fiat rate is missing")
|
||||
require(nativeFiatRate > BigDecimal.ZERO) { "Native fiat rate must be > 0" }
|
||||
|
||||
val nativeGas = feeWithoutGas.fixFee(nativeCryptoCurrency, ETHEREUM_CONSTANT_GAS_LIMIT)
|
||||
val nativeGas = feeWithoutGas.fixFee(
|
||||
nativeCryptoCurrency,
|
||||
YIELD_SUPPLY_EVM_CONSTANT_GAS_LIMIT,
|
||||
)
|
||||
|
||||
val rateRatio = nativeFiatRate.divide(
|
||||
fiatRate,
|
||||
|
|
@ -62,27 +64,8 @@ class YieldSupplyMinAmountUseCase(
|
|||
.stripTrailingZeros()
|
||||
}
|
||||
|
||||
private fun Fee.fixFee(cryptoCurrency: CryptoCurrency, gasLimit: BigInteger) = when (this) {
|
||||
is Fee.Ethereum.Legacy -> copy(
|
||||
gasLimit = gasLimit,
|
||||
amount = amount.copy(
|
||||
value = gasPrice.multiply(gasLimit)
|
||||
.toBigDecimal().movePointLeft(cryptoCurrency.decimals),
|
||||
),
|
||||
)
|
||||
is Fee.Ethereum.EIP1559 -> copy(
|
||||
gasLimit = gasLimit,
|
||||
amount = amount.copy(
|
||||
value = maxFeePerGas.multiply(gasLimit)
|
||||
.toBigDecimal().movePointLeft(cryptoCurrency.decimals),
|
||||
),
|
||||
)
|
||||
else -> this
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val FEE_BUFFER_MULTIPLIER: BigDecimal = BigDecimal("1.25")
|
||||
val MAX_FEE_PERCENT: BigDecimal = BigDecimal("0.04")
|
||||
val ETHEREUM_CONSTANT_GAS_LIMIT = 350_000.toBigInteger()
|
||||
}
|
||||
}
|
||||
|
|
@ -41,6 +41,7 @@ internal class MarketsTokenItemConverter(
|
|||
stakingRate = value.stakingRate?.format { percent() }?.let {
|
||||
resourceReference(R.string.markets_apy_placeholder, wrappedList(it))
|
||||
},
|
||||
updateTimestamp = value.updateTimestamp,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,11 +23,11 @@ import com.tangem.core.ui.test.MarketsTestTags
|
|||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.features.markets.impl.R
|
||||
import com.tangem.features.markets.tokenlist.impl.ui.state.ListUM
|
||||
import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListItemUM.Companion.TOKEN_LAZY_LIST_ID_SEPARATOR
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
private const val LOAD_NEXT_PAGE_ON_END_INDEX = 50
|
||||
private const val LOAD_NEXT_PAGE_ON_END_INDEX_SEARCH = 25
|
||||
private const val TOKEN_LAZY_LIST_ID_SEPARATOR = "***"
|
||||
|
||||
@Composable
|
||||
@Suppress("LongMethod")
|
||||
|
|
@ -92,7 +92,7 @@ internal fun MarketsListLazyColumn(
|
|||
is ListUM.Content -> {
|
||||
items(
|
||||
items = state.items,
|
||||
key = { it.id.value + TOKEN_LAZY_LIST_ID_SEPARATOR + it.marketCap.toString() },
|
||||
key = { it.getComposeKey() },
|
||||
) { item ->
|
||||
MarketsListItem(
|
||||
model = item,
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet
|
|||
),
|
||||
isUnder100kMarketCap = false,
|
||||
stakingRate = stringReference("APY 12.34%"),
|
||||
updateTimestamp = 0,
|
||||
),
|
||||
MarketsListItemUM(
|
||||
id = CryptoCurrency.RawID("1"),
|
||||
|
|
@ -41,6 +42,7 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet
|
|||
chartData = null,
|
||||
isUnder100kMarketCap = false,
|
||||
stakingRate = stringReference("APY 12.34%"),
|
||||
updateTimestamp = 0,
|
||||
),
|
||||
MarketsListItemUM(
|
||||
id = CryptoCurrency.RawID("1"),
|
||||
|
|
@ -57,6 +59,7 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet
|
|||
),
|
||||
isUnder100kMarketCap = false,
|
||||
stakingRate = stringReference("APY 12.34%"),
|
||||
updateTimestamp = 0,
|
||||
),
|
||||
MarketsListItemUM(
|
||||
id = CryptoCurrency.RawID("1"),
|
||||
|
|
@ -73,6 +76,7 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet
|
|||
),
|
||||
isUnder100kMarketCap = false,
|
||||
stakingRate = stringReference("APY 12.34%"),
|
||||
updateTimestamp = 0,
|
||||
),
|
||||
MarketsListItemUM(
|
||||
id = CryptoCurrency.RawID("1"),
|
||||
|
|
@ -89,6 +93,7 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet
|
|||
),
|
||||
isUnder100kMarketCap = false,
|
||||
stakingRate = stringReference("APY 12.34%"),
|
||||
updateTimestamp = 0,
|
||||
),
|
||||
MarketsListItemUM(
|
||||
id = CryptoCurrency.RawID("1"),
|
||||
|
|
@ -105,6 +110,7 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet
|
|||
),
|
||||
isUnder100kMarketCap = false,
|
||||
stakingRate = stringReference("APY 12.34%"),
|
||||
updateTimestamp = 0,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -21,6 +21,7 @@ data class MarketsListItemUM(
|
|||
val chartData: MarketChartRawData?,
|
||||
val isUnder100kMarketCap: Boolean,
|
||||
val stakingRate: TextReference?,
|
||||
val updateTimestamp: Long?,
|
||||
) {
|
||||
val chartType: MarketChartLook.Type = when (trendType) {
|
||||
PriceChangeType.UP -> MarketChartLook.Type.Growing
|
||||
|
|
@ -33,4 +34,12 @@ data class MarketsListItemUM(
|
|||
val text: String,
|
||||
val changeType: PriceChangeType? = null,
|
||||
)
|
||||
|
||||
fun getComposeKey(): String {
|
||||
return id.value + TOKEN_LAZY_LIST_ID_SEPARATOR + marketCap.toString() + updateTimestamp
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TOKEN_LAZY_LIST_ID_SEPARATOR = "@"
|
||||
}
|
||||
}
|
||||
|
|
@ -402,7 +402,10 @@ internal class OnrampTokenListModel @Inject constructor(
|
|||
cryptoCurrency = status.currency,
|
||||
).isAvailable() && !status.currency.isCustom
|
||||
|
||||
isAvailable && status.value !is CryptoCurrencyStatus.NoQuote
|
||||
val supplyStatus = status.value.yieldSupplyStatus
|
||||
val isUnavailableByYieldSupply = supplyStatus?.isAllowedToSpend == false && supplyStatus.isActive
|
||||
|
||||
isAvailable && status.value !is CryptoCurrencyStatus.NoQuote && !isUnavailableByYieldSupply
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,12 +54,12 @@ internal class PushNotificationsModel @Inject constructor(
|
|||
modelScope.launch {
|
||||
neverRequestPermissionUseCase(PUSH_PERMISSION)
|
||||
neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION)
|
||||
params.modelCallbacks.onDenySystemPermission()
|
||||
if (params.isBottomSheet) {
|
||||
notificationsRepository.setUserAllowToSubscribeOnPushNotifications(false)
|
||||
} else {
|
||||
params.nextRoute?.let { appRouter.push(it) }
|
||||
}
|
||||
params.modelCallbacks.onDenySystemPermission()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.features.send.v2.api.entry
|
||||
|
||||
import com.tangem.core.decompose.navigation.Route
|
||||
|
||||
/**
|
||||
* Route for switching send and send via swap flows.
|
||||
*/
|
||||
sealed class SendEntryRoute : Route {
|
||||
|
||||
/** Route to send screen */
|
||||
data object Send : SendEntryRoute()
|
||||
/** Route to send via swap screen */
|
||||
data object SendWithSwap : SendEntryRoute()
|
||||
/** Route to choose token screen for send via swap */
|
||||
data class ChooseToken(
|
||||
val showSendViaSwapNotification: Boolean,
|
||||
) : SendEntryRoute()
|
||||
}
|
||||
|
|
@ -15,6 +15,8 @@ import com.arkivanov.decompose.extensions.compose.subscribeAsState
|
|||
import com.arkivanov.decompose.router.stack.StackNavigation
|
||||
import com.arkivanov.decompose.router.stack.childStack
|
||||
import com.arkivanov.decompose.router.stack.pop
|
||||
import com.arkivanov.decompose.value.ObserveLifecycleMode
|
||||
import com.arkivanov.decompose.value.subscribe
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.child
|
||||
import com.tangem.core.decompose.context.childByContext
|
||||
|
|
@ -25,11 +27,14 @@ import com.tangem.features.managetokens.component.ChooseManagedTokensComponent
|
|||
import com.tangem.features.send.v2.api.SendComponent
|
||||
import com.tangem.features.send.v2.api.SendEntryPointComponent
|
||||
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
|
||||
import com.tangem.features.send.v2.api.entry.SendEntryRoute
|
||||
import com.tangem.features.send.v2.entrypoint.model.SendEntryPointModel
|
||||
import com.tangem.features.swap.v2.api.SendWithSwapComponent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
internal class DefaultSendEntryPointComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
|
|
@ -54,6 +59,7 @@ internal class DefaultSendEntryPointComponent @AssistedInject constructor(
|
|||
userWalletId = params.userWalletId,
|
||||
currency = params.cryptoCurrency,
|
||||
callback = model,
|
||||
currentRoute = model.currentRoute.asStateFlow(),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -83,6 +89,17 @@ internal class DefaultSendEntryPointComponent @AssistedInject constructor(
|
|||
},
|
||||
)
|
||||
|
||||
init {
|
||||
childStack.subscribe(
|
||||
lifecycle = lifecycle,
|
||||
mode = ObserveLifecycleMode.CREATE_DESTROY,
|
||||
) { stack ->
|
||||
componentScope.launch {
|
||||
model.currentRoute.emit(stack.active.configuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val childStackValue by childStack.subscribeAsState()
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
package com.tangem.features.send.v2.entrypoint
|
||||
|
||||
import com.tangem.core.decompose.navigation.Route
|
||||
|
||||
internal sealed class SendEntryRoute : Route {
|
||||
data object Send : SendEntryRoute()
|
||||
data object SendWithSwap : SendEntryRoute()
|
||||
data class ChooseToken(
|
||||
val showSendViaSwapNotification: Boolean,
|
||||
) : SendEntryRoute()
|
||||
}
|
||||
|
|
@ -1,19 +1,22 @@
|
|||
package com.tangem.features.send.v2.entrypoint.model
|
||||
|
||||
import com.tangem.common.ui.notifications.NotificationId
|
||||
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.navigation.Router
|
||||
import com.tangem.domain.notifications.ShouldShowNotificationUseCase
|
||||
import com.tangem.features.managetokens.component.ChooseManagedTokensComponent
|
||||
import com.tangem.features.send.v2.api.SendComponent
|
||||
import com.tangem.features.send.v2.entrypoint.SendEntryRoute
|
||||
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
|
||||
import com.tangem.features.send.v2.api.entry.SendEntryRoute
|
||||
import com.tangem.features.send.v2.subcomponents.amount.SendAmountUpdateTrigger
|
||||
import com.tangem.features.swap.v2.api.SendWithSwapComponent
|
||||
import com.tangem.features.swap.v2.api.subcomponents.SwapAmountUpdateTrigger
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import jakarta.inject.Inject
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -24,6 +27,7 @@ internal class SendEntryPointModel @Inject constructor(
|
|||
private val sendAmountUpdateTrigger: SendAmountUpdateTrigger,
|
||||
private val swapAmountUpdateTrigger: SwapAmountUpdateTrigger,
|
||||
private val shouldShowNotificationUseCase: ShouldShowNotificationUseCase,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
) : Model(),
|
||||
SendComponent.ModelCallback,
|
||||
SendWithSwapComponent.ModelCallback,
|
||||
|
|
@ -32,6 +36,8 @@ internal class SendEntryPointModel @Inject constructor(
|
|||
private var lastSavedAmount = ""
|
||||
private var isEnterInFiat = false
|
||||
|
||||
val currentRoute = MutableStateFlow<SendEntryRoute>(SendEntryRoute.Send)
|
||||
|
||||
override fun onConvertToAnotherToken(lastAmount: String, isEnterInFiatSelected: Boolean) {
|
||||
lastSavedAmount = lastAmount
|
||||
isEnterInFiat = isEnterInFiatSelected
|
||||
|
|
@ -53,6 +59,12 @@ internal class SendEntryPointModel @Inject constructor(
|
|||
modelScope.launch {
|
||||
sendAmountUpdateTrigger.triggerUpdateAmount(lastAmount, isEnterInFiat)
|
||||
router.replaceAll(SendEntryRoute.Send)
|
||||
analyticsEventHandler.send(
|
||||
CommonSendAnalyticEvents.AmountScreenOpened(
|
||||
categoryName = CommonSendAnalyticEvents.SEND_CATEGORY,
|
||||
source = CommonSendAnalyticEvents.CommonSendSource.Send,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -64,6 +76,12 @@ internal class SendEntryPointModel @Inject constructor(
|
|||
router.pop()
|
||||
delay(10L)
|
||||
router.replaceAll(SendEntryRoute.SendWithSwap)
|
||||
analyticsEventHandler.send(
|
||||
CommonSendAnalyticEvents.AmountScreenOpened(
|
||||
categoryName = CommonSendAnalyticEvents.SEND_CATEGORY,
|
||||
source = CommonSendAnalyticEvents.CommonSendSource.SendWithSwap,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ dependencies {
|
|||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
|
||||
api(projects.features.sendV2.api)
|
||||
|
||||
/** Common */
|
||||
implementation(projects.common.ui)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import com.tangem.core.decompose.factory.ComponentFactory
|
|||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.send.v2.api.entry.SendEntryRoute
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
interface SendWithSwapComponent : ComposableContentComponent {
|
||||
|
||||
|
|
@ -11,6 +13,7 @@ interface SendWithSwapComponent : ComposableContentComponent {
|
|||
val userWalletId: UserWalletId,
|
||||
val currency: CryptoCurrency,
|
||||
val callback: ModelCallback? = null,
|
||||
val currentRoute: StateFlow<SendEntryRoute>,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, SendWithSwapComponent>
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import com.tangem.core.ui.extensions.resourceReference
|
|||
import com.tangem.domain.swap.models.R
|
||||
import com.tangem.domain.swap.models.SwapDirection
|
||||
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
|
||||
import com.tangem.features.send.v2.api.entry.SendEntryRoute
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.DestinationRoute
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationComponent
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationComponentParams
|
||||
|
|
@ -85,12 +86,17 @@ internal class DefaultSendWithSwapComponent @AssistedInject constructor(
|
|||
componentScope.launch {
|
||||
when (val activeComponent = stack.active.instance) {
|
||||
is SwapAmountComponent -> {
|
||||
analyticsEventHandler.send(
|
||||
CommonSendAnalyticEvents.AmountScreenOpened(
|
||||
categoryName = model.analyticCategoryName,
|
||||
source = model.analyticsSendSource,
|
||||
),
|
||||
)
|
||||
if (
|
||||
params.currentRoute.value is SendEntryRoute.SendWithSwap &&
|
||||
model.currentRoute.value != stack.active.configuration
|
||||
) {
|
||||
analyticsEventHandler.send(
|
||||
CommonSendAnalyticEvents.AmountScreenOpened(
|
||||
categoryName = model.analyticCategoryName,
|
||||
source = model.analyticsSendSource,
|
||||
),
|
||||
)
|
||||
}
|
||||
activeComponent.updateState(model.uiState.value.amountUM)
|
||||
}
|
||||
is SendDestinationComponent -> {
|
||||
|
|
|
|||
|
|
@ -64,4 +64,9 @@ sealed class ExpressDataError {
|
|||
override val code: Int = -2
|
||||
override val message: String = "tooLargeSolanaTransaction"
|
||||
}
|
||||
|
||||
data object DexActiveSupplyError : ExpressDataError() {
|
||||
override val code: Int = -3
|
||||
override val message: String = "dexActiveSupplyError"
|
||||
}
|
||||
}
|
||||
|
|
@ -334,6 +334,14 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
isBalanceWithoutFeeEnough: Boolean,
|
||||
expressOperationType: ExpressOperationType,
|
||||
): Pair<SwapProvider, SwapState> {
|
||||
if (fromToken.value.yieldSupplyStatus?.isActive == true) {
|
||||
return provider to produceDexSwapDataError(
|
||||
error = ExpressDataError.DexActiveSupplyError,
|
||||
fromToken = fromToken,
|
||||
amount = amount,
|
||||
)
|
||||
}
|
||||
|
||||
val maybeQuotes = repository.findBestQuote(
|
||||
userWallet = userWallet,
|
||||
fromContractAddress = fromToken.currency.getContractAddress(),
|
||||
|
|
|
|||
|
|
@ -1267,16 +1267,7 @@ internal class SwapModel @Inject constructor(
|
|||
toToken.currency.id.value
|
||||
}
|
||||
|
||||
return groupToFind.available.find { idToFind == it.currencyStatus.currency.id.value }?.providers
|
||||
?.filter { provider ->
|
||||
// !!!WARNING!!! Filter out dex provider if yield supply is active
|
||||
val yieldSupplyStatus = fromToken.value.yieldSupplyStatus
|
||||
if (yieldSupplyStatus != null && yieldSupplyStatus.isActive) {
|
||||
provider.type == ExchangeProviderType.CEX
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}.orEmpty()
|
||||
return groupToFind.available.find { idToFind == it.currencyStatus.currency.id.value }?.providers.orEmpty()
|
||||
}
|
||||
|
||||
private fun Map<SwapProvider, SwapState>.getLastLoadedSuccessStates(): SuccessLoadedSwapData {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.tangem.domain.models.currency.CryptoCurrency
|
|||
import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification
|
||||
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
|
||||
|
||||
internal class TokenDetailsNotificationsAnalyticsSender(
|
||||
private val cryptoCurrency: CryptoCurrency,
|
||||
|
|
@ -44,6 +45,10 @@ internal class TokenDetailsNotificationsAnalyticsSender(
|
|||
is TokenDetailsNotification.KaspaIncompleteTransactionWarning -> TokenDetailsAnalyticsEvent.Notice.Reveal(
|
||||
currency = cryptoCurrency,
|
||||
)
|
||||
is TokenDetailsNotification.YieldSupplyNotTransferedToAave -> YieldSupplyAnalytics.NoticeAmountNotDeposited(
|
||||
token = cryptoCurrency.symbol,
|
||||
blockchain = cryptoCurrency.network.name,
|
||||
)
|
||||
is TokenDetailsNotification.NetworksUnreachable,
|
||||
is TokenDetailsNotification.ExistentialDeposit,
|
||||
is TokenDetailsNotification.NetworksNoAccount,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ 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.format.bigdecimal.crypto
|
||||
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.tokens.model.warnings.CryptoCurrencyWarning
|
||||
|
|
@ -136,7 +138,12 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) {
|
|||
title = TextReference.Res(R.string.warning_rent_fee_title),
|
||||
subtitle = TextReference.Res(
|
||||
id = R.string.warning_solana_rent_fee_message,
|
||||
formatArgs = wrappedList(rentInfo.rent, rentInfo.exemptionAmount),
|
||||
formatArgs = wrappedList(
|
||||
rentInfo.rent,
|
||||
rentInfo.exemptionAmount.format {
|
||||
crypto(rentInfo.cryptoCurrency)
|
||||
},
|
||||
),
|
||||
),
|
||||
onCloseClick = onCloseClick,
|
||||
)
|
||||
|
|
@ -257,4 +264,14 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) {
|
|||
iconResId = R.drawable.ic_error_sync_24,
|
||||
),
|
||||
)
|
||||
|
||||
data class YieldSupplyNotTransferedToAave(val tokenName: String, val amount: String) : Warning(
|
||||
title = resourceReference(
|
||||
id = R.string.yield_module_amount_not_transfered_to_aave_title,
|
||||
wrappedList(amount, tokenName),
|
||||
),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.yield_module_high_fee_error,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
import kotlinx.collections.immutable.toImmutableList
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
import kotlin.String
|
||||
|
||||
internal class TokenDetailsNotificationConverter(
|
||||
private val userWalletId: UserWalletId,
|
||||
|
|
@ -155,6 +156,10 @@ internal class TokenDetailsNotificationConverter(
|
|||
)
|
||||
is CryptoCurrencyWarning.MigrationMaticToPol -> MigrationMaticToPol
|
||||
is CryptoCurrencyWarning.UsedOutdatedDataWarning -> UsedOutdatedData
|
||||
is CryptoCurrencyWarning.YieldSupplyNotDepositedAmount -> YieldSupplyNotTransferedToAave(
|
||||
tokenName = warning.currencySymbol,
|
||||
amount = warning.amount.format { crypto(symbol = "", decimals = warning.currency.decimals) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -399,7 +399,7 @@ internal class WalletSettingsModel @Inject constructor(
|
|||
),
|
||||
firstActionBuilder = {
|
||||
EventMessageAction(
|
||||
title = resourceReference(R.string.common_delete),
|
||||
title = resourceReference(R.string.common_forget),
|
||||
isWarning = true,
|
||||
onClick = ::forgetWallet,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -245,14 +245,14 @@ internal class WalletModel @Inject constructor(
|
|||
"isBiometricsEnabled $isBiometricsEnabled," +
|
||||
"isHuaweiDevice $isHuaweiDevice",
|
||||
)
|
||||
if (!isBiometricsEnabled) return@launch
|
||||
if (!shouldShow) {
|
||||
return@launch
|
||||
}
|
||||
if (!shouldAskNotificationPermissionsViaBs) {
|
||||
notificationsRepository.setShouldAskNotificationPermissionsViaBs(true)
|
||||
return@launch
|
||||
}
|
||||
if (!isBiometricsEnabled) return@launch
|
||||
if (!shouldShow) {
|
||||
return@launch
|
||||
}
|
||||
|
||||
delay(timeMillis = 1_800)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
|
|||
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||
|
|
@ -44,6 +45,7 @@ internal class MultiWalletContentLoader(
|
|||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val accountDependencies: AccountDependencies,
|
||||
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
|
||||
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
|
||||
) : WalletContentLoader(id = userWallet.walletId) {
|
||||
|
||||
override fun create(): List<WalletSubscriber> {
|
||||
|
|
@ -60,6 +62,7 @@ internal class MultiWalletContentLoader(
|
|||
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
|
||||
accountDependencies = accountDependencies,
|
||||
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
|
||||
stakingApyFlowUseCase = stakingApyFlowUseCase,
|
||||
).let(::add)
|
||||
|
||||
WalletNFTListSubscriber(
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
|
|||
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||
|
|
@ -42,6 +43,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
|
|||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val accountDependencies: AccountDependencies,
|
||||
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
|
||||
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
|
||||
) {
|
||||
|
||||
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): WalletContentLoader {
|
||||
|
|
@ -65,6 +67,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
|
|||
currenciesRepository = currenciesRepository,
|
||||
accountDependencies = accountDependencies,
|
||||
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
|
||||
stakingApyFlowUseCase = stakingApyFlowUseCase,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
|||
import com.tangem.domain.promo.GetStoryContentUseCase
|
||||
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
||||
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
|
|
@ -34,6 +35,7 @@ internal class SingleWalletWithTokenContentLoader(
|
|||
private val getStoryContentUseCase: GetStoryContentUseCase,
|
||||
private val accountDependencies: AccountDependencies,
|
||||
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
|
||||
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
|
||||
) : WalletContentLoader(id = userWallet.walletId) {
|
||||
|
||||
override fun create(): List<WalletSubscriber> {
|
||||
|
|
@ -49,6 +51,7 @@ internal class SingleWalletWithTokenContentLoader(
|
|||
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
|
||||
accountDependencies = accountDependencies,
|
||||
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
|
||||
stakingApyFlowUseCase = stakingApyFlowUseCase,
|
||||
).let(::add)
|
||||
MultiWalletWarningsSubscriber(
|
||||
userWallet = userWallet,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
|||
import com.tangem.domain.promo.GetStoryContentUseCase
|
||||
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
||||
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
|
|
@ -35,6 +36,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
|
|||
private val getStoryContentUseCase: GetStoryContentUseCase,
|
||||
private val accountDependencies: AccountDependencies,
|
||||
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
|
||||
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
|
||||
) {
|
||||
|
||||
fun create(userWallet: UserWallet.Cold, clickIntents: WalletClickIntents): SingleWalletWithTokenContentLoader {
|
||||
|
|
@ -54,6 +56,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
|
|||
shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase,
|
||||
accountDependencies = accountDependencies,
|
||||
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
|
||||
stakingApyFlowUseCase = stakingApyFlowUseCase,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.transformers.converte
|
|||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TokenListStateConverter
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.utils.enableButtons
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class SetTokenListTransformer(
|
||||
private val params: TokenConverterParams,
|
||||
|
|
@ -17,6 +18,7 @@ internal class SetTokenListTransformer(
|
|||
private val appCurrency: AppCurrency,
|
||||
private val clickIntents: WalletClickIntents,
|
||||
private val yieldSupplyApyMap: Map<String, String> = emptyMap(),
|
||||
private val stakingApyMap: Map<String, BigDecimal> = emptyMap(),
|
||||
) : WalletStateTransformer(userWallet.walletId) {
|
||||
|
||||
override fun transform(prevState: WalletState): WalletState {
|
||||
|
|
@ -59,7 +61,8 @@ internal class SetTokenListTransformer(
|
|||
selectedWallet = userWallet,
|
||||
appCurrency = appCurrency,
|
||||
clickIntents = clickIntents,
|
||||
apyMap = yieldSupplyApyMap,
|
||||
yieldModuleApyMap = yieldSupplyApyMap,
|
||||
stakingApyMap = stakingApyMap,
|
||||
).convert(value = this)
|
||||
}
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ import kotlinx.collections.immutable.PersistentList
|
|||
import kotlinx.collections.immutable.mutate
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import java.math.BigDecimal
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.OrganizeTokensButtonConfig as WalletOrganizeTokensButtonConfig
|
||||
|
||||
internal class TokenListStateConverter(
|
||||
|
|
@ -34,7 +35,8 @@ internal class TokenListStateConverter(
|
|||
private val params: TokenConverterParams,
|
||||
private val selectedWallet: UserWallet,
|
||||
private val clickIntents: WalletClickIntents,
|
||||
private val apyMap: Map<String, String>,
|
||||
private val yieldModuleApyMap: Map<String, String>,
|
||||
private val stakingApyMap: Map<String, BigDecimal>,
|
||||
) : Converter<WalletTokensListState, WalletTokensListState> {
|
||||
|
||||
private val onTokenClick: (accountId: AccountId?, currencyStatus: CryptoCurrencyStatus) -> Unit =
|
||||
|
|
@ -49,7 +51,8 @@ internal class TokenListStateConverter(
|
|||
|
||||
private fun tokenStatusConverter(accountId: AccountId? = null) = TokenItemStateConverter(
|
||||
appCurrency = appCurrency,
|
||||
apyMap = apyMap,
|
||||
yieldModuleApyMap = yieldModuleApyMap,
|
||||
stakingApyMap = stakingApyMap,
|
||||
onItemClick = { _, status -> onTokenClick(accountId, status) },
|
||||
onItemLongClick = { _, status -> onTokenLongClick(accountId, status) },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.tangem.domain.models.wallet.UserWallet
|
|||
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.account.AccountDependencies
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
|
|
@ -25,12 +26,14 @@ import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetToken
|
|||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTokenListTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TokenConverterParams
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.combine6
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.joinAll
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal abstract class BasicTokenListSubscriber : WalletSubscriber() {
|
||||
|
|
@ -43,6 +46,7 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() {
|
|||
protected abstract val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase
|
||||
protected abstract val accountDependencies: AccountDependencies
|
||||
protected abstract val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase
|
||||
protected abstract val stakingApyFlowUseCase: StakingApyFlowUseCase
|
||||
|
||||
private val sendAnalyticsJobHolder = JobHolder()
|
||||
private val onTokenListReceivedJobHolder = JobHolder()
|
||||
|
|
@ -81,12 +85,14 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() {
|
|||
},
|
||||
flow2 = appCurrencyFlow(),
|
||||
flow3 = yieldSupplyApyFlow(),
|
||||
transform = { maybeTokenList, appCurrency, yieldSupplyApyMap ->
|
||||
flow4 = stakingApyFlow(),
|
||||
transform = { maybeTokenList, appCurrency, yieldSupplyApyMap, stakingApyMap ->
|
||||
singleAccountTransform(
|
||||
maybeTokenList = maybeTokenList,
|
||||
appCurrency = appCurrency,
|
||||
portfolioId = PortfolioId(userWallet.walletId),
|
||||
yieldSupplyApyMap = yieldSupplyApyMap,
|
||||
stakingApyMap = stakingApyMap,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
@ -97,6 +103,7 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() {
|
|||
appCurrency: AppCurrency,
|
||||
portfolioId: PortfolioId,
|
||||
yieldSupplyApyMap: Map<String, String>,
|
||||
stakingApyMap: Map<String, BigDecimal>,
|
||||
) {
|
||||
val tokenList = maybeTokenList.getOrElse(
|
||||
ifLoading = { maybeContent ->
|
||||
|
|
@ -124,6 +131,7 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() {
|
|||
params = TokenConverterParams.Wallet(portfolioId, tokenList),
|
||||
appCurrency = appCurrency,
|
||||
yieldSupplyApyMap = yieldSupplyApyMap,
|
||||
stakingApyMap = stakingApyMap,
|
||||
)
|
||||
|
||||
walletWithFundsChecker.check(tokenList)
|
||||
|
|
@ -143,8 +151,8 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun createAccountListFlow(coroutineScope: CoroutineScope): Flow<*> = combine(
|
||||
flow = accountListFlow(coroutineScope)
|
||||
private fun createAccountListFlow(coroutineScope: CoroutineScope): Flow<*> = combine6(
|
||||
flow1 = accountListFlow(coroutineScope)
|
||||
.onEach { accountStatusList ->
|
||||
coroutineScope.launch {
|
||||
sendTokenListAnalytics(
|
||||
|
|
@ -165,7 +173,8 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() {
|
|||
flow3 = accountDependencies.expandedAccountsHolder.expandedAccounts(userWallet),
|
||||
flow4 = accountDependencies.isAccountsModeEnabledUseCase(),
|
||||
flow5 = yieldSupplyApyFlow(),
|
||||
transform = { accountList, appCurrency, expandedAccounts, isAccountMode, yieldSupplyApyMap ->
|
||||
flow6 = stakingApyFlow(),
|
||||
transform = { accountList, appCurrency, expandedAccounts, isAccountMode, yieldSupplyApyMap, stakingApyMap ->
|
||||
val accountFlattenTokensList = accountList.flattenTokens()
|
||||
val accountFlattenCurrencies = accountFlattenTokensList
|
||||
.map { it.flattenCurrencies() }
|
||||
|
|
@ -180,6 +189,7 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() {
|
|||
appCurrency,
|
||||
PortfolioId(mainAccount.account.accountId),
|
||||
yieldSupplyApyMap,
|
||||
stakingApyMap,
|
||||
)
|
||||
|
||||
when {
|
||||
|
|
@ -198,7 +208,7 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() {
|
|||
)
|
||||
false -> {
|
||||
val convertParams = TokenConverterParams.Account(accountList, expandedAccounts)
|
||||
updateContent(convertParams, appCurrency, yieldSupplyApyMap)
|
||||
updateContent(convertParams, appCurrency, yieldSupplyApyMap, stakingApyMap)
|
||||
accountFlattenTokensList
|
||||
.map { tokenList -> coroutineScope.launch { walletWithFundsChecker.check(tokenList) } }
|
||||
.joinAll()
|
||||
|
|
@ -232,6 +242,7 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() {
|
|||
params: TokenConverterParams,
|
||||
appCurrency: AppCurrency,
|
||||
yieldSupplyApyMap: Map<String, String>,
|
||||
stakingApyMap: Map<String, BigDecimal>,
|
||||
) {
|
||||
stateHolder.update(
|
||||
SetTokenListTransformer(
|
||||
|
|
@ -240,6 +251,7 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() {
|
|||
appCurrency = appCurrency,
|
||||
clickIntents = clickIntents,
|
||||
yieldSupplyApyMap = yieldSupplyApyMap,
|
||||
stakingApyMap = stakingApyMap,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -255,4 +267,7 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() {
|
|||
|
||||
private fun yieldSupplyApyFlow(): Flow<Map<String, String>> = yieldSupplyApyFlowUseCase()
|
||||
.distinctUntilChanged()
|
||||
|
||||
private fun stakingApyFlow(): Flow<Map<String, BigDecimal>> = stakingApyFlowUseCase()
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import com.tangem.domain.models.TotalFiatBalance
|
|||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
||||
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
|
||||
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
|
|
@ -36,6 +37,7 @@ internal class MultiWalletTokenListSubscriber(
|
|||
override val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
|
||||
override val accountDependencies: AccountDependencies,
|
||||
override val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
|
||||
override val stakingApyFlowUseCase: StakingApyFlowUseCase,
|
||||
) : BasicTokenListSubscriber() {
|
||||
|
||||
override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow<TokenListError, TokenList> {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.tangem.domain.core.lce.Lce
|
|||
import com.tangem.domain.core.lce.LceFlow
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
||||
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||
|
|
@ -31,6 +32,7 @@ internal class SingleWalletWithTokenListSubscriber(
|
|||
override val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
|
||||
override val accountDependencies: AccountDependencies,
|
||||
override val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
|
||||
override val stakingApyFlowUseCase: StakingApyFlowUseCase,
|
||||
) : BasicTokenListSubscriber() {
|
||||
|
||||
override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow<TokenListError, TokenList> {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,11 @@ import com.tangem.domain.models.wallet.UserWalletId
|
|||
import com.tangem.domain.models.wallet.isLocked
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.walletconnect.WcAnalyticEvents
|
||||
import com.tangem.domain.walletconnect.model.*
|
||||
import com.tangem.domain.walletconnect.model.WcPairError
|
||||
import com.tangem.domain.walletconnect.model.WcPairError.Unknown
|
||||
import com.tangem.domain.walletconnect.model.WcPairRequest
|
||||
import com.tangem.domain.walletconnect.model.WcSessionApprove
|
||||
import com.tangem.domain.walletconnect.model.WcSessionProposal
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcAppMetaData
|
||||
import com.tangem.domain.walletconnect.usecase.pair.WcPairState
|
||||
import com.tangem.domain.walletconnect.usecase.pair.WcPairUseCase
|
||||
|
|
@ -114,35 +118,40 @@ internal class WcPairModel @Inject constructor(
|
|||
val availableWallets = pairState.dAppSession.proposalNetwork.keys
|
||||
.filter { !it.isLocked && it.isMultiCurrency }
|
||||
sessionProposal = pairState.dAppSession
|
||||
proposalNetwork = sessionProposal.proposalNetwork.getValue(selectedUserWalletFlow.value)
|
||||
additionallyEnabledNetworks = proposalNetwork.available
|
||||
appInfoUiState.transformerUpdate(
|
||||
WcAppInfoTransformer(
|
||||
dAppSession = sessionProposal,
|
||||
dAppVerifiedStateConverter = dAppVerifiedStateConverter,
|
||||
onDismiss = ::rejectPairing,
|
||||
onConnect = ::onConnect,
|
||||
onWalletClick = {
|
||||
stackNavigation.pushNew(
|
||||
WcAppInfoRoutes.SelectWallet(selectedUserWalletFlow.value.walletId),
|
||||
)
|
||||
}.takeIf { availableWallets.size >= WC_WALLETS_SELECTOR_MIN_COUNT },
|
||||
onNetworksClick = {
|
||||
stackNavigation.pushNew(
|
||||
WcAppInfoRoutes.SelectNetworks(
|
||||
missingRequiredNetworks = proposalNetwork.missingRequired,
|
||||
requiredNetworks = proposalNetwork.required,
|
||||
availableNetworks = proposalNetwork.available,
|
||||
enabledAvailableNetworks = additionallyEnabledNetworks,
|
||||
notAddedNetworks = proposalNetwork.notAdded,
|
||||
),
|
||||
)
|
||||
},
|
||||
userWallet = selectedUserWalletFlow.value,
|
||||
proposalNetwork = proposalNetwork,
|
||||
additionallyEnabledNetworks = additionallyEnabledNetworks,
|
||||
),
|
||||
)
|
||||
val foundNetwork = sessionProposal.proposalNetwork[selectedUserWalletFlow.value]
|
||||
if (foundNetwork == null) {
|
||||
processError(Unknown("Selected wallet not found"))
|
||||
} else {
|
||||
proposalNetwork = foundNetwork
|
||||
additionallyEnabledNetworks = proposalNetwork.available
|
||||
appInfoUiState.transformerUpdate(
|
||||
WcAppInfoTransformer(
|
||||
dAppSession = sessionProposal,
|
||||
dAppVerifiedStateConverter = dAppVerifiedStateConverter,
|
||||
onDismiss = ::rejectPairing,
|
||||
onConnect = ::onConnect,
|
||||
onWalletClick = {
|
||||
stackNavigation.pushNew(
|
||||
WcAppInfoRoutes.SelectWallet(selectedUserWalletFlow.value.walletId),
|
||||
)
|
||||
}.takeIf { availableWallets.size >= WC_WALLETS_SELECTOR_MIN_COUNT },
|
||||
onNetworksClick = {
|
||||
stackNavigation.pushNew(
|
||||
WcAppInfoRoutes.SelectNetworks(
|
||||
missingRequiredNetworks = proposalNetwork.missingRequired,
|
||||
requiredNetworks = proposalNetwork.required,
|
||||
availableNetworks = proposalNetwork.available,
|
||||
enabledAvailableNetworks = additionallyEnabledNetworks,
|
||||
notAddedNetworks = proposalNetwork.notAdded,
|
||||
),
|
||||
)
|
||||
},
|
||||
userWallet = selectedUserWalletFlow.value,
|
||||
proposalNetwork = proposalNetwork,
|
||||
additionallyEnabledNetworks = additionallyEnabledNetworks,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -234,7 +243,7 @@ internal class WcPairModel @Inject constructor(
|
|||
|
||||
override fun onWalletSelected(userWalletId: UserWalletId) {
|
||||
val selectedUserWallet = sessionProposal.proposalNetwork.keys.first { it.walletId == userWalletId }
|
||||
proposalNetwork = sessionProposal.proposalNetwork.getValue(selectedUserWallet)
|
||||
proposalNetwork = sessionProposal.proposalNetwork[selectedUserWallet] ?: return
|
||||
selectedUserWalletFlow.update { selectedUserWallet }
|
||||
additionallyEnabledNetworks = proposalNetwork.available
|
||||
appInfoUiState.transformerUpdate(
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import com.tangem.features.send.v2.api.params.FeeSelectorParams.FeeSelectorDetai
|
|||
import com.tangem.features.walletconnect.connections.components.AlertsComponentV2
|
||||
import com.tangem.features.walletconnect.connections.utils.WcAlertsFactory.createCommonTransactionAppInfoAlertUM
|
||||
import com.tangem.features.walletconnect.transaction.components.send.WcCustomAllowanceComponent
|
||||
import com.tangem.features.walletconnect.transaction.components.send.WcSendingProcessComponent
|
||||
import com.tangem.features.walletconnect.transaction.components.send.WcSendMultipleTransactionsComponent
|
||||
import com.tangem.features.walletconnect.transaction.components.send.WcSendingProcessComponent
|
||||
import com.tangem.features.walletconnect.transaction.entity.common.WcCommonTransactionModel
|
||||
import com.tangem.features.walletconnect.transaction.model.WcSendTransactionModel
|
||||
import com.tangem.features.walletconnect.transaction.routes.WcTransactionRoutes
|
||||
|
|
@ -65,7 +65,7 @@ internal fun getWcCommonScreen(
|
|||
WcSendMultipleTransactionsComponent(
|
||||
appComponentContext = appComponentContext,
|
||||
model = model,
|
||||
onConfirm = config.onConfirm,
|
||||
onConfirm = { model.onMultiTransactionConfirm() },
|
||||
)
|
||||
}
|
||||
WcTransactionRoutes.TransactionProcess -> {
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ internal class WcSendTransactionModel @Inject constructor(
|
|||
sign = {
|
||||
if (isMultipleSignRequired(useCase)) {
|
||||
analytics.send(SolanaLargeTransaction(useCase.rawSdkRequest.dAppMetaData.name))
|
||||
openMultipleTransaction(useCase)
|
||||
openMultipleTransaction()
|
||||
} else {
|
||||
useCase.sign()
|
||||
}
|
||||
|
|
@ -173,15 +173,13 @@ internal class WcSendTransactionModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun openMultipleTransaction(useCase: WcSignUseCase<*>) {
|
||||
stackNavigation.pushNew(
|
||||
WcTransactionRoutes.MultipleTransactions(
|
||||
onConfirm = {
|
||||
useCase.sign()
|
||||
stackNavigation.pushNew(WcTransactionRoutes.TransactionProcess)
|
||||
},
|
||||
),
|
||||
)
|
||||
private fun openMultipleTransaction() {
|
||||
stackNavigation.pushNew(WcTransactionRoutes.MultipleTransactions)
|
||||
}
|
||||
|
||||
fun onMultiTransactionConfirm() {
|
||||
useCase.sign()
|
||||
stackNavigation.pushNew(WcTransactionRoutes.TransactionProcess)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -44,9 +44,7 @@ internal sealed class WcTransactionRoutes : TangemBottomSheetConfigContent, Rout
|
|||
}
|
||||
|
||||
@Serializable
|
||||
data class MultipleTransactions(
|
||||
val onConfirm: () -> Unit,
|
||||
) : WcTransactionRoutes()
|
||||
data object MultipleTransactions : WcTransactionRoutes()
|
||||
|
||||
@Serializable
|
||||
data object TransactionProcess : WcTransactionRoutes()
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ interface YieldSupplyPromoComponent : ComposableContentComponent {
|
|||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
val currency: CryptoCurrency,
|
||||
val apy: String,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, YieldSupplyPromoComponent>
|
||||
|
|
|
|||
|
|
@ -33,6 +33,17 @@ sealed class YieldSupplyAnalytics(
|
|||
),
|
||||
)
|
||||
|
||||
data class StopEarningScreen(
|
||||
val token: String,
|
||||
val blockchain: String,
|
||||
) : YieldSupplyAnalytics(
|
||||
event = "Stop Earning Screen",
|
||||
params = mapOf(
|
||||
TOKEN_PARAM to token,
|
||||
BLOCKCHAIN to blockchain,
|
||||
),
|
||||
)
|
||||
|
||||
data class ButtonStartEarning(
|
||||
val token: String,
|
||||
val blockchain: String,
|
||||
|
|
@ -55,6 +66,17 @@ sealed class YieldSupplyAnalytics(
|
|||
),
|
||||
)
|
||||
|
||||
data class ButtonGiveApprove(
|
||||
val token: String,
|
||||
val blockchain: String,
|
||||
) : YieldSupplyAnalytics(
|
||||
event = " Button - Give Approve",
|
||||
params = mapOf(
|
||||
TOKEN_PARAM to token,
|
||||
BLOCKCHAIN to blockchain,
|
||||
),
|
||||
)
|
||||
|
||||
data object ButtonFeePolicy : YieldSupplyAnalytics(
|
||||
event = "Button - Fee Policy",
|
||||
)
|
||||
|
|
@ -150,22 +172,11 @@ sealed class YieldSupplyAnalytics(
|
|||
event = "APY Chart",
|
||||
)
|
||||
|
||||
data class NoticeCommissionTooHigh(
|
||||
data class NoticeAmountNotDeposited(
|
||||
val token: String,
|
||||
val blockchain: String,
|
||||
) : YieldSupplyAnalytics(
|
||||
event = "Notice - Commission Is Too High",
|
||||
params = mapOf(
|
||||
TOKEN_PARAM to token,
|
||||
BLOCKCHAIN to blockchain,
|
||||
),
|
||||
)
|
||||
|
||||
data class NoticeNotEnoughMinAmount(
|
||||
val token: String,
|
||||
val blockchain: String,
|
||||
) : YieldSupplyAnalytics(
|
||||
event = "Notice - Not Enough Min Amount",
|
||||
event = "Notice - Amount Not Deposited",
|
||||
params = mapOf(
|
||||
TOKEN_PARAM to token,
|
||||
BLOCKCHAIN to blockchain,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.features.yield.supply.impl.common
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Trigger for entering/exiting protocol from other components
|
||||
*/
|
||||
interface YieldSupplyProtocolTrigger {
|
||||
suspend fun onEnterProtocol()
|
||||
suspend fun onExitProtocol()
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener to observe entering/exiting protocol events
|
||||
*/
|
||||
interface YieldSupplyProtocolListener {
|
||||
val enterProtocolTriggerFlow: Flow<Unit>
|
||||
val exitProtocolTriggerFlow: Flow<Unit>
|
||||
}
|
||||
|
||||
@Singleton
|
||||
internal class DefaultYieldSupplyProtocolTrigger @Inject constructor() :
|
||||
YieldSupplyProtocolTrigger,
|
||||
YieldSupplyProtocolListener {
|
||||
|
||||
override val enterProtocolTriggerFlow = MutableSharedFlow<Unit>()
|
||||
override val exitProtocolTriggerFlow = MutableSharedFlow<Unit>()
|
||||
|
||||
override suspend fun onEnterProtocol() {
|
||||
enterProtocolTriggerFlow.emit(Unit)
|
||||
}
|
||||
|
||||
override suspend fun onExitProtocol() {
|
||||
exitProtocolTriggerFlow.emit(Unit)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,24 +2,38 @@ package com.tangem.features.yield.supply.impl.common.formatter
|
|||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.format.bigdecimal.approximateAmount
|
||||
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.utils.StringsSigns
|
||||
import com.tangem.utils.StringsSigns.DOT
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class YieldSupplyMinAmountFormatter(
|
||||
internal class YieldSupplyAmountFormatter(
|
||||
private val feeCryptoCurrency: CryptoCurrency,
|
||||
private val appCurrency: AppCurrency,
|
||||
) {
|
||||
|
||||
operator fun invoke(feeValue: BigDecimal, fiatRate: BigDecimal?): TextReference {
|
||||
operator fun invoke(feeValue: BigDecimal, fiatRate: BigDecimal?, showCrypto: Boolean = true): TextReference {
|
||||
val cryptoFee = feeValue.format { crypto(feeCryptoCurrency) }
|
||||
val fiatFeeValue = fiatRate?.let(feeValue::multiply)
|
||||
val fiatFee = fiatFeeValue.format { fiat(appCurrency.code, appCurrency.symbol) }
|
||||
val fiatFee = if (showCrypto) {
|
||||
fiatFeeValue.format {
|
||||
fiat(appCurrency.code, appCurrency.symbol)
|
||||
.approximateAmount()
|
||||
}
|
||||
} else {
|
||||
fiatFeeValue.format {
|
||||
fiat(appCurrency.code, appCurrency.symbol)
|
||||
}
|
||||
}
|
||||
|
||||
return stringReference(cryptoFee + " ${StringsSigns.DOT} " + fiatFee)
|
||||
return if (showCrypto) {
|
||||
stringReference("$cryptoFee $DOT $fiatFee")
|
||||
} else {
|
||||
stringReference(fiatFee)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.SpacerWMax
|
||||
import com.tangem.core.ui.components.TextShimmer
|
||||
|
|
@ -46,6 +47,7 @@ internal fun YieldSupplyFeeRow(title: TextReference, value: TextReference?) {
|
|||
text = targetValue.resolveReference(),
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
textAlign = TextAlign.End,
|
||||
)
|
||||
} else {
|
||||
TextShimmer(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@ package com.tangem.features.yield.supply.impl.di
|
|||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.features.yield.supply.impl.DefaultYieldSupplyFeatureToggles
|
||||
import com.tangem.features.yield.supply.api.YieldSupplyFeatureToggles
|
||||
import com.tangem.features.yield.supply.impl.common.DefaultYieldSupplyProtocolTrigger
|
||||
import com.tangem.features.yield.supply.impl.common.YieldSupplyProtocolListener
|
||||
import com.tangem.features.yield.supply.impl.common.YieldSupplyProtocolTrigger
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -18,4 +22,17 @@ internal object YieldSupplyFeatureModule {
|
|||
fun provideYieldFeatureToggles(featureTogglesManager: FeatureTogglesManager): YieldSupplyFeatureToggles {
|
||||
return DefaultYieldSupplyFeatureToggles(featureTogglesManager)
|
||||
}
|
||||
}
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
internal interface YieldSupplyProtocolModuleBinds {
|
||||
|
||||
@Singleton
|
||||
@Binds
|
||||
fun bindYieldSupplyProtocolTrigger(impl: DefaultYieldSupplyProtocolTrigger): YieldSupplyProtocolTrigger
|
||||
|
||||
@Singleton
|
||||
@Binds
|
||||
fun bindYieldSupplyProtocolListener(impl: DefaultYieldSupplyProtocolTrigger): YieldSupplyProtocolListener
|
||||
}
|
||||
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