Updated on 2026-08-14
This commit is contained in:
commit
eee72adcf1
19 changed files with 516 additions and 138 deletions
|
|
@ -1,10 +1,6 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.settings.*
|
||||
import com.tangem.domain.staking.GetStakingAvailabilityUseCase
|
||||
import com.tangem.domain.staking.GetStakingEntryInfoUseCase
|
||||
import com.tangem.domain.staking.FetchStakingTokensUseCase
|
||||
import com.tangem.domain.staking.GetYieldUseCase
|
||||
import com.tangem.domain.staking.*
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -47,4 +43,16 @@ internal object StakingDomainModule {
|
|||
stakingRepository = stakingRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFetchStakingYieldBalanceUseCase(stakingRepository: StakingRepository): FetchStakingYieldBalanceUseCase {
|
||||
return FetchStakingYieldBalanceUseCase(stakingRepository = stakingRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetStakingYieldBalanceUseCase(stakingRepository: StakingRepository): GetStakingYieldBalanceUseCase {
|
||||
return GetStakingYieldBalanceUseCase(stakingRepository = stakingRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
package com.tangem.datasource.api.common.adapter
|
||||
|
||||
import com.squareup.moshi.*
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.adapters.EnumJsonAdapter
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.*
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.NetworkTypeDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionStatusDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionTypeDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingTransactionStatusDTO
|
||||
|
|
@ -24,8 +27,7 @@ fun Moshi.Builder.addStakeKitEnumFallbackAdapters(): Moshi.Builder {
|
|||
NetworkTypeDTO::class.java to NetworkTypeDTO.UNKNOWN,
|
||||
StakingActionTypeDTO::class.java to StakingActionTypeDTO.UNKNOWN,
|
||||
YieldDTO.RewardTypeDTO::class.java to YieldDTO.RewardTypeDTO.UNKNOWN,
|
||||
YieldBalanceWrapperDTO.BalanceDTO.BalanceType::class.java to
|
||||
YieldBalanceWrapperDTO.BalanceDTO.BalanceType.UNKNOWN,
|
||||
BalanceDTO.BalanceType::class.java to BalanceDTO.BalanceType.UNKNOWN,
|
||||
StakingTransactionTypeDTO::class.java to StakingTransactionTypeDTO.UNKNOWN,
|
||||
StakingTransactionStatusDTO::class.java to StakingTransactionStatusDTO.UNKNOWN,
|
||||
StakingActionStatusDTO::class.java to StakingActionStatusDTO.UNKNOWN,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.tangem.datasource.api.common.response.ApiResponse
|
|||
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.EnterActionResponse
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingTransactionDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.TokenWithYieldDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
|
||||
|
|
@ -29,16 +30,16 @@ interface StakeKitApi {
|
|||
@Query("ledgerWalletAPICompatible") ledgerWalletAPICompatible: Boolean = false,
|
||||
): ApiResponse<YieldDTO>
|
||||
|
||||
@GET("yields/balances")
|
||||
@POST("yields/balances")
|
||||
suspend fun getMultipleYieldBalances(
|
||||
@Body body: List<YieldBalanceRequestBody>,
|
||||
): ApiResponse<List<YieldBalanceWrapperDTO>>
|
||||
|
||||
@GET("yields/{integrationId}/balances")
|
||||
@POST("yields/{integrationId}/balances")
|
||||
suspend fun getSingleYieldBalance(
|
||||
@Path("integrationId") integrationId: String,
|
||||
@Body body: YieldBalanceRequestBody,
|
||||
): ApiResponse<YieldBalanceWrapperDTO>
|
||||
): ApiResponse<List<BalanceDTO>>
|
||||
|
||||
@GET("tokens")
|
||||
suspend fun getTokens(): ApiResponse<List<TokenWithYieldDTO>>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.datasource.api.stakekit.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.TokenDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
||||
|
||||
data class EnterActionRequestBody(
|
||||
@Json(name = "integrationId")
|
||||
|
|
@ -27,7 +27,7 @@ data class EnterActionRequestBody(
|
|||
@Json(name = "duration")
|
||||
val duration: String? = null,
|
||||
@Json(name = "nfts")
|
||||
val nfts: List<YieldBalanceWrapperDTO.BalanceDTO.PendingAction.PendingActionArgs.Nft>? = null,
|
||||
val nfts: List<BalanceDTO.PendingAction.PendingActionArgs.Nft>? = null,
|
||||
@Json(name = "ledgerWalletAPICompatible")
|
||||
val ledgerWalletAPICompatible: Boolean? = null,
|
||||
@Json(name = "tronResource")
|
||||
|
|
|
|||
|
|
@ -12,130 +12,129 @@ data class YieldBalanceWrapperDTO(
|
|||
val balances: List<BalanceDTO>,
|
||||
@Json(name = "integrationId")
|
||||
val integrationId: String?,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class BalanceDTO(
|
||||
@Json(name = "groupId")
|
||||
val groupId: String,
|
||||
@Json(name = "type")
|
||||
val type: BalanceType,
|
||||
@Json(name = "amount")
|
||||
val amount: BigDecimal,
|
||||
@Json(name = "date")
|
||||
val date: DateTime?,
|
||||
@Json(name = "pricePerShare")
|
||||
val pricePerShare: BigDecimal,
|
||||
@Json(name = "pendingActions")
|
||||
val pendingActions: List<PendingAction>,
|
||||
@Json(name = "token")
|
||||
val tokenDTO: TokenDTO,
|
||||
@Json(name = "validatorAddress")
|
||||
val validatorAddress: String?,
|
||||
@Json(name = "validatorAddresses")
|
||||
val validatorAddresses: List<String>?,
|
||||
@Json(name = "providerId")
|
||||
val providerId: String?,
|
||||
) {
|
||||
|
||||
enum class BalanceType {
|
||||
@Json(name = "available")
|
||||
AVAILABLE,
|
||||
|
||||
@Json(name = "staked")
|
||||
STAKED,
|
||||
|
||||
@Json(name = "unstaking")
|
||||
UNSTAKING,
|
||||
|
||||
@Json(name = "unstaked")
|
||||
UNSTAKED,
|
||||
|
||||
@Json(name = "preparing")
|
||||
PREPARING,
|
||||
|
||||
@Json(name = "rewards")
|
||||
REWARDS,
|
||||
|
||||
@Json(name = "locked")
|
||||
LOCKED,
|
||||
|
||||
@Json(name = "unlocking")
|
||||
UNLOCKING,
|
||||
|
||||
UNKNOWN,
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class BalanceDTO(
|
||||
@Json(name = "groupId")
|
||||
val groupId: String,
|
||||
data class PendingAction(
|
||||
@Json(name = "type")
|
||||
val type: BalanceType,
|
||||
@Json(name = "amount")
|
||||
val amount: BigDecimal,
|
||||
@Json(name = "date")
|
||||
val date: DateTime?,
|
||||
@Json(name = "pricePerShare")
|
||||
val pricePerShare: BigDecimal,
|
||||
@Json(name = "pendingActions")
|
||||
val pendingActions: List<PendingAction>,
|
||||
@Json(name = "token")
|
||||
val tokenDTO: TokenDTO,
|
||||
@Json(name = "validatorAddress")
|
||||
val validatorAddress: String?,
|
||||
@Json(name = "validatorAddresses")
|
||||
val validatorAddresses: List<String>?,
|
||||
@Json(name = "providerId")
|
||||
val providerId: String?,
|
||||
val type: StakingActionTypeDTO,
|
||||
@Json(name = "passthrough")
|
||||
val passthrough: String,
|
||||
@Json(name = "args")
|
||||
val args: PendingActionArgs?,
|
||||
) {
|
||||
|
||||
enum class BalanceType {
|
||||
@Json(name = "available")
|
||||
AVAILABLE,
|
||||
|
||||
@Json(name = "staked")
|
||||
STAKED,
|
||||
|
||||
@Json(name = "unstaking")
|
||||
UNSTAKING,
|
||||
|
||||
@Json(name = "unstaked")
|
||||
UNSTAKED,
|
||||
|
||||
@Json(name = "preparing")
|
||||
PREPARING,
|
||||
|
||||
@Json(name = "rewards")
|
||||
REWARDS,
|
||||
|
||||
@Json(name = "locked")
|
||||
LOCKED,
|
||||
|
||||
@Json(name = "unlocking")
|
||||
UNLOCKING,
|
||||
|
||||
UNKNOWN,
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class PendingAction(
|
||||
@Json(name = "type")
|
||||
val type: StakingActionTypeDTO,
|
||||
@Json(name = "passthrough")
|
||||
val passthrough: String,
|
||||
@Json(name = "args")
|
||||
val args: PendingActionArgs?,
|
||||
data class PendingActionArgs(
|
||||
@Json(name = "amount")
|
||||
val amount: Amount?,
|
||||
@Json(name = "duration")
|
||||
val duration: Duration?,
|
||||
@Json(name = "validatorAddress")
|
||||
val validatorAddress: Required?,
|
||||
@Json(name = "validatorAddresses")
|
||||
val validatorAddresses: Required?,
|
||||
@Json(name = "nfts")
|
||||
val nfts: List<Nft>?,
|
||||
@Json(name = "tronResource")
|
||||
val tronResource: TronResource?,
|
||||
@Json(name = "signatureVerification")
|
||||
val signatureVerification: Required?,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class PendingActionArgs(
|
||||
@Json(name = "amount")
|
||||
val amount: Amount?,
|
||||
@Json(name = "duration")
|
||||
val duration: Duration?,
|
||||
@Json(name = "validatorAddress")
|
||||
val validatorAddress: Required?,
|
||||
@Json(name = "validatorAddresses")
|
||||
val validatorAddresses: Required?,
|
||||
@Json(name = "nfts")
|
||||
val nfts: List<Nft>?,
|
||||
@Json(name = "tronResource")
|
||||
val tronResource: TronResource?,
|
||||
@Json(name = "signatureVerification")
|
||||
val signatureVerification: Required?,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Amount(
|
||||
@Json(name = "required")
|
||||
val required: Boolean,
|
||||
@Json(name = "minimum")
|
||||
val minimum: BigDecimal?,
|
||||
@Json(name = "maximum")
|
||||
val maximum: BigDecimal?,
|
||||
)
|
||||
data class Amount(
|
||||
@Json(name = "required")
|
||||
val required: Boolean,
|
||||
@Json(name = "minimum")
|
||||
val minimum: BigDecimal?,
|
||||
@Json(name = "maximum")
|
||||
val maximum: BigDecimal?,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Duration(
|
||||
@Json(name = "required")
|
||||
val required: Boolean,
|
||||
@Json(name = "minimum")
|
||||
val minimum: Int?,
|
||||
@Json(name = "maximum")
|
||||
val maximum: Int?,
|
||||
)
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Duration(
|
||||
@Json(name = "required")
|
||||
val required: Boolean,
|
||||
@Json(name = "minimum")
|
||||
val minimum: Int?,
|
||||
@Json(name = "maximum")
|
||||
val maximum: Int?,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Nft(
|
||||
@Json(name = "baycId")
|
||||
val baycId: Required?,
|
||||
@Json(name = "maycId")
|
||||
val maycId: Required?,
|
||||
@Json(name = "bakcId")
|
||||
val bakcId: Required?,
|
||||
)
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Nft(
|
||||
@Json(name = "baycId")
|
||||
val baycId: Required?,
|
||||
@Json(name = "maycId")
|
||||
val maycId: Required?,
|
||||
@Json(name = "bakcId")
|
||||
val bakcId: Required?,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TronResource(
|
||||
@Json(name = "required")
|
||||
val required: Boolean,
|
||||
@Json(name = "options")
|
||||
val options: List<String>,
|
||||
)
|
||||
}
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TronResource(
|
||||
@Json(name = "required")
|
||||
val required: Boolean,
|
||||
@Json(name = "options")
|
||||
val options: List<String>,
|
||||
)
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Required(
|
||||
@Json(name = "required")
|
||||
val required: Boolean,
|
||||
)
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Required(
|
||||
@Json(name = "required")
|
||||
val required: Boolean,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||
import com.tangem.datasource.local.token.DefaultStakingBalanceStore
|
||||
import com.tangem.datasource.local.token.StakingBalanceStore
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object StakingBalanceStoreModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideStakingBalanceStore(): StakingBalanceStore {
|
||||
return DefaultStakingBalanceStore(dataStore = RuntimeDataStore())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package com.tangem.datasource.local.token
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||
import com.tangem.utils.extensions.addOrReplace
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
internal class DefaultStakingBalanceStore(
|
||||
private val dataStore: StringKeyDataStore<List<YieldBalanceWrapperDTO>>,
|
||||
) : StakingBalanceStore {
|
||||
|
||||
override fun get(): Flow<List<YieldBalanceWrapperDTO>> {
|
||||
return dataStore.get(STAKING_BALANCE_KEY)
|
||||
}
|
||||
|
||||
override suspend fun getSyncOrNull(): List<YieldBalanceWrapperDTO>? {
|
||||
return dataStore.getSyncOrNull(STAKING_BALANCE_KEY)
|
||||
}
|
||||
|
||||
override suspend fun store(items: List<YieldBalanceWrapperDTO>) {
|
||||
return dataStore.store(STAKING_BALANCE_KEY, items)
|
||||
}
|
||||
|
||||
override fun get(integrationId: String): Flow<List<BalanceDTO>> {
|
||||
return dataStore.get(STAKING_BALANCE_KEY)
|
||||
.map { balances ->
|
||||
balances.filter { it.integrationId == integrationId }
|
||||
.flatMap { it.balances }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getSyncOrNull(integrationId: String): List<BalanceDTO>? {
|
||||
return dataStore.getSyncOrNull(STAKING_BALANCE_KEY)
|
||||
?.firstOrNull { it.integrationId == integrationId }?.balances
|
||||
}
|
||||
|
||||
override suspend fun store(integrationId: String, item: YieldBalanceWrapperDTO) {
|
||||
val balances = dataStore.getSyncOrNull(STAKING_BALANCE_KEY)
|
||||
?.toMutableList()
|
||||
?.addOrReplace(item) { item.integrationId == integrationId }
|
||||
?: listOf(item)
|
||||
|
||||
return dataStore.store(STAKING_BALANCE_KEY, balances)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val STAKING_BALANCE_KEY = "STAKING_BALANCE_KEY"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.datasource.local.token
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface StakingBalanceStore {
|
||||
|
||||
fun get(): Flow<List<YieldBalanceWrapperDTO>>
|
||||
|
||||
suspend fun getSyncOrNull(): List<YieldBalanceWrapperDTO>?
|
||||
|
||||
suspend fun store(items: List<YieldBalanceWrapperDTO>)
|
||||
|
||||
fun get(integrationId: String): Flow<List<BalanceDTO>>
|
||||
|
||||
suspend fun getSyncOrNull(integrationId: String): List<BalanceDTO>?
|
||||
|
||||
suspend fun store(integrationId: String, item: YieldBalanceWrapperDTO)
|
||||
}
|
||||
|
|
@ -12,10 +12,17 @@ android {
|
|||
|
||||
dependencies {
|
||||
|
||||
/** Core modules */
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.core.utils)
|
||||
|
||||
/** Common modules */
|
||||
implementation(projects.data.common)
|
||||
|
||||
/** Domain modules */
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.staking)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
||||
|
||||
// region DI
|
||||
|
|
|
|||
|
|
@ -2,23 +2,33 @@ package com.tangem.data.staking
|
|||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.toCoinId
|
||||
import com.tangem.data.staking.converters.StakingNetworkTypeConverter
|
||||
import com.tangem.data.staking.converters.TokenConverter
|
||||
import com.tangem.data.staking.converters.YieldConverter
|
||||
import com.tangem.data.common.cache.CacheRegistry
|
||||
import com.tangem.data.staking.converters.*
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.stakekit.StakeKitApi
|
||||
import com.tangem.datasource.api.stakekit.models.request.Address
|
||||
import com.tangem.datasource.api.stakekit.models.request.YieldBalanceRequestBody
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
||||
import com.tangem.datasource.local.token.StakingBalanceStore
|
||||
import com.tangem.datasource.local.token.StakingYieldsStore
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.domain.staking.model.StakingEntryInfo
|
||||
import com.tangem.domain.staking.model.Yield
|
||||
import com.tangem.domain.staking.model.*
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.cancellable
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
internal class DefaultStakingRepository(
|
||||
private val stakeKitApi: StakeKitApi,
|
||||
private val stakingYieldsStore: StakingYieldsStore,
|
||||
private val stakingBalanceStore: StakingBalanceStore,
|
||||
private val cacheRegistry: CacheRegistry,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : StakingRepository {
|
||||
|
||||
|
|
@ -31,6 +41,10 @@ internal class DefaultStakingRepository(
|
|||
tokenConverter = tokenConverter,
|
||||
)
|
||||
|
||||
private val yieldBalanceConverter = YieldBalanceConverter()
|
||||
|
||||
private val yieldBalanceListConverter = YieldBalanceListConverter()
|
||||
|
||||
override fun isStakingSupported(currencyId: String): Boolean {
|
||||
return integrationIds.contains(currencyId)
|
||||
}
|
||||
|
|
@ -94,9 +108,93 @@ internal class DefaultStakingRepository(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun fetchSingleYieldBalance(
|
||||
userWalletId: UserWalletId,
|
||||
networkStatus: NetworkStatus,
|
||||
integrationId: String,
|
||||
refresh: Boolean,
|
||||
) = withContext(dispatchers.io) {
|
||||
cacheRegistry.invokeOnExpire(
|
||||
key = getYieldBalancesKey(userWalletId),
|
||||
skipCache = refresh,
|
||||
block = {
|
||||
val result = stakeKitApi.getSingleYieldBalance(
|
||||
integrationId = integrationId,
|
||||
body = getBalanceRequestData(networkStatus),
|
||||
).getOrThrow()
|
||||
|
||||
stakingBalanceStore.store(
|
||||
integrationId,
|
||||
YieldBalanceWrapperDTO(
|
||||
balances = result,
|
||||
integrationId = integrationId,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override fun getSingleYieldBalanceFlow(
|
||||
userWalletId: UserWalletId,
|
||||
networkStatus: NetworkStatus,
|
||||
integrationId: String,
|
||||
): Flow<List<YieldBalance>> = channelFlow {
|
||||
launch(dispatchers.io) {
|
||||
stakingBalanceStore.get(integrationId)
|
||||
.collectLatest {
|
||||
send(yieldBalanceConverter.convertList(it))
|
||||
}
|
||||
}
|
||||
|
||||
withContext(dispatchers.io) {
|
||||
fetchSingleYieldBalance(
|
||||
userWalletId,
|
||||
networkStatus,
|
||||
integrationId,
|
||||
)
|
||||
}
|
||||
}.cancellable()
|
||||
|
||||
override suspend fun fetchMultiYieldBalance(
|
||||
userWalletId: UserWalletId,
|
||||
networks: Set<NetworkStatus>,
|
||||
integrationId: String,
|
||||
refresh: Boolean,
|
||||
) = withContext(dispatchers.io) {
|
||||
cacheRegistry.invokeOnExpire(
|
||||
key = getYieldBalancesKey(userWalletId),
|
||||
skipCache = refresh,
|
||||
block = {
|
||||
val result = stakeKitApi.getMultipleYieldBalances(
|
||||
networks.map(::getBalanceRequestData),
|
||||
).getOrThrow()
|
||||
|
||||
stakingBalanceStore.store(result)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override fun getMultiYieldBalanceFlow(
|
||||
userWalletId: UserWalletId,
|
||||
networks: Set<NetworkStatus>,
|
||||
integrationId: String,
|
||||
): Flow<List<YieldBalanceList>> = channelFlow {
|
||||
launch(dispatchers.io) {
|
||||
stakingBalanceStore.get()
|
||||
.collectLatest { send(yieldBalanceListConverter.convertList(it)) }
|
||||
}
|
||||
|
||||
withContext(dispatchers.io) {
|
||||
fetchMultiYieldBalance(
|
||||
userWalletId,
|
||||
networks,
|
||||
integrationId,
|
||||
)
|
||||
}
|
||||
}.cancellable()
|
||||
|
||||
private fun findPrefetchedYield(yields: List<Yield>, currencyId: String, symbol: String): Yield? {
|
||||
return yields
|
||||
.find { it.token.coinGeckoId == currencyId && it.token.symbol == symbol }
|
||||
return yields.find { it.token.coinGeckoId == currencyId && it.token.symbol == symbol }
|
||||
}
|
||||
|
||||
private suspend fun getEnabledYields(): List<Yield>? {
|
||||
|
|
@ -104,6 +202,28 @@ internal class DefaultStakingRepository(
|
|||
return yields.map { yieldConverter.convert(it) }
|
||||
}
|
||||
|
||||
private fun getBalanceRequestData(networkStatus: NetworkStatus): YieldBalanceRequestBody {
|
||||
val networkAddress = when (val network = networkStatus.value) {
|
||||
NetworkStatus.MissedDerivation -> null
|
||||
is NetworkStatus.NoAccount -> network.address
|
||||
is NetworkStatus.Unreachable -> network.address
|
||||
is NetworkStatus.Verified -> network.address
|
||||
}
|
||||
val address = networkAddress?.defaultAddress?.value.orEmpty()
|
||||
return YieldBalanceRequestBody(
|
||||
addresses = Address(
|
||||
address = address,
|
||||
additionalAddresses = null, // todo fill additional addresses metadata if needed
|
||||
explorerUrl = "", // todo fill exporer url
|
||||
),
|
||||
args = YieldBalanceRequestBody.YieldBalanceRequestArgs(
|
||||
validatorAddresses = listOf(), // todo add validator addresses
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getYieldBalancesKey(userWalletId: UserWalletId) = "yield_balance_${userWalletId.stringValue}"
|
||||
|
||||
companion object {
|
||||
private val integrationIds = setOf(
|
||||
Blockchain.Solana.toCoinId(),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.data.staking.converters
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO
|
||||
import com.tangem.domain.staking.model.BalanceType
|
||||
import com.tangem.domain.staking.model.YieldBalance
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class YieldBalanceConverter : Converter<BalanceDTO, YieldBalance> {
|
||||
override fun convert(value: BalanceDTO): YieldBalance {
|
||||
return YieldBalance(
|
||||
type = BalanceType.valueOf(value.type.name),
|
||||
amount = value.amount,
|
||||
pricePerShare = value.pricePerShare,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.data.staking.converters
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
||||
import com.tangem.domain.staking.model.YieldBalanceList
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class YieldBalanceListConverter : Converter<YieldBalanceWrapperDTO, YieldBalanceList> {
|
||||
|
||||
internal val converter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
YieldBalanceConverter()
|
||||
}
|
||||
|
||||
override fun convert(value: YieldBalanceWrapperDTO): YieldBalanceList {
|
||||
return YieldBalanceList(
|
||||
balances = converter.convertList(value.balances),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.data.staking.di
|
||||
|
||||
import com.tangem.data.common.cache.CacheRegistry
|
||||
import com.tangem.data.staking.DefaultStakingRepository
|
||||
import com.tangem.datasource.api.stakekit.StakeKitApi
|
||||
import com.tangem.datasource.local.token.StakingBalanceStore
|
||||
import com.tangem.datasource.local.token.StakingYieldsStore
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -20,12 +22,16 @@ internal object StakingDataModule {
|
|||
fun provideStakingRepository(
|
||||
stakeKitApi: StakeKitApi,
|
||||
stakingTokenStore: StakingYieldsStore,
|
||||
stakingBalanceStore: StakingBalanceStore,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
cacheRegistry: CacheRegistry,
|
||||
): StakingRepository {
|
||||
return DefaultStakingRepository(
|
||||
stakeKitApi = stakeKitApi,
|
||||
stakingYieldsStore = stakingTokenStore,
|
||||
stakingBalanceStore = stakingBalanceStore,
|
||||
dispatchers = dispatchers,
|
||||
cacheRegistry = cacheRegistry,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -17,4 +17,5 @@ dependencies {
|
|||
implementation(deps.kotlin.serialization)
|
||||
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.domain.staking.model
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class YieldBalance(
|
||||
val type: BalanceType,
|
||||
val amount: BigDecimal,
|
||||
val pricePerShare: BigDecimal,
|
||||
)
|
||||
|
||||
enum class BalanceType {
|
||||
AVAILABLE,
|
||||
STAKED,
|
||||
UNSTAKING,
|
||||
UNSTAKED,
|
||||
PREPARING,
|
||||
REWARDS,
|
||||
LOCKED,
|
||||
UNLOCKING,
|
||||
UNKNOWN,
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.domain.staking.model
|
||||
|
||||
data class YieldBalanceList(
|
||||
val balances: List<YieldBalance>,
|
||||
)
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.domain.staking
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
class FetchStakingYieldBalanceUseCase(
|
||||
private val stakingRepository: StakingRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
networkStatus: NetworkStatus,
|
||||
integrationId: String,
|
||||
refresh: Boolean = false,
|
||||
): Either<Throwable, Unit> = Either.catch {
|
||||
stakingRepository.fetchSingleYieldBalance(
|
||||
userWalletId = userWalletId,
|
||||
networkStatus = networkStatus,
|
||||
integrationId = integrationId,
|
||||
refresh = refresh,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.domain.staking
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.core.utils.EitherFlow
|
||||
import com.tangem.domain.staking.model.YieldBalance
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class GetStakingYieldBalanceUseCase(
|
||||
private val stakingRepository: StakingRepository,
|
||||
) {
|
||||
|
||||
operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
networkStatus: NetworkStatus,
|
||||
integrationId: String,
|
||||
): EitherFlow<Throwable, List<YieldBalance>> {
|
||||
return stakingRepository.getSingleYieldBalanceFlow(
|
||||
userWalletId = userWalletId,
|
||||
networkStatus = networkStatus,
|
||||
integrationId = integrationId,
|
||||
).map<List<YieldBalance>, Either<Throwable, List<YieldBalance>>> { it.right() }
|
||||
.catch { emit(it.left()) }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
package com.tangem.domain.staking.repositories
|
||||
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.domain.staking.model.StakingEntryInfo
|
||||
import com.tangem.domain.staking.model.Yield
|
||||
import com.tangem.domain.staking.model.*
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface StakingRepository {
|
||||
|
||||
|
|
@ -19,4 +20,30 @@ interface StakingRepository {
|
|||
cryptoCurrencyId: CryptoCurrency.ID,
|
||||
symbol: String,
|
||||
): StakingAvailability
|
||||
|
||||
suspend fun fetchSingleYieldBalance(
|
||||
userWalletId: UserWalletId,
|
||||
networkStatus: NetworkStatus,
|
||||
integrationId: String,
|
||||
refresh: Boolean = false,
|
||||
)
|
||||
|
||||
fun getSingleYieldBalanceFlow(
|
||||
userWalletId: UserWalletId,
|
||||
networkStatus: NetworkStatus,
|
||||
integrationId: String,
|
||||
): Flow<List<YieldBalance>>
|
||||
|
||||
suspend fun fetchMultiYieldBalance(
|
||||
userWalletId: UserWalletId,
|
||||
networks: Set<NetworkStatus>,
|
||||
integrationId: String,
|
||||
refresh: Boolean = false,
|
||||
)
|
||||
|
||||
fun getMultiYieldBalanceFlow(
|
||||
userWalletId: UserWalletId,
|
||||
networks: Set<NetworkStatus>,
|
||||
integrationId: String,
|
||||
): Flow<List<YieldBalanceList>>
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue