Updated on 2026-08-14
This commit is contained in:
parent
aa6c1c95f2
commit
df32ea7ab6
32 changed files with 704 additions and 637 deletions
|
|
@ -12,10 +12,15 @@ import com.tangem.datasource.api.ethpool.models.request.P2PEthPoolDepositRequest
|
|||
import com.tangem.datasource.api.ethpool.models.request.P2PEthPoolUnstakeRequest
|
||||
import com.tangem.datasource.api.ethpool.models.request.P2PEthPoolWithdrawRequest
|
||||
import com.tangem.datasource.local.token.P2PEthPoolVaultsStore
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.domain.staking.model.StakingOption
|
||||
import com.tangem.domain.staking.model.ethpool.*
|
||||
import com.tangem.domain.staking.repositories.P2PEthPoolRepository
|
||||
import com.tangem.domain.staking.model.stakekit.StakingError
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
|
|
@ -192,7 +197,12 @@ internal class DefaultP2PEthPoolRepository(
|
|||
period: Int?,
|
||||
): Either<StakingError, List<P2PEthPoolReward>> = either {
|
||||
withContext(dispatchers.io) {
|
||||
val response = p2pApi.getRewards(network.value, delegatorAddress, vaultAddress, period)
|
||||
val response = p2pApi.getRewards(
|
||||
network = network.value,
|
||||
delegatorAddress = delegatorAddress,
|
||||
vaultAddress = vaultAddress,
|
||||
period = period,
|
||||
)
|
||||
when (response) {
|
||||
is ApiResponse.Success -> {
|
||||
val data = response.data
|
||||
|
|
@ -206,4 +216,49 @@ internal class DefaultP2PEthPoolRepository(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getStakingAvailability(): Flow<StakingAvailability> {
|
||||
return getVaultsFlow()
|
||||
.distinctUntilChanged()
|
||||
.map { vaults ->
|
||||
if (vaults.isEmpty()) {
|
||||
return@map StakingAvailability.TemporaryUnavailable
|
||||
}
|
||||
|
||||
val vault = findPublicVault(vaults = vaults)
|
||||
|
||||
if (vault != null) {
|
||||
StakingAvailability.Available(StakingOption.P2P(vault))
|
||||
} else {
|
||||
StakingAvailability.TemporaryUnavailable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getStakingAvailabilitySync(): StakingAvailability {
|
||||
val vaults = getVaultsSync()
|
||||
if (vaults.isEmpty()) {
|
||||
return StakingAvailability.TemporaryUnavailable
|
||||
}
|
||||
|
||||
val vault = findPublicVault(vaults = vaults)
|
||||
|
||||
return if (vault != null) {
|
||||
StakingAvailability.Available(StakingOption.P2P(vault))
|
||||
} else {
|
||||
StakingAvailability.TemporaryUnavailable
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getVaultsSync(): List<P2PEthPoolVault> {
|
||||
return p2pEthPoolVaultsStore.getSync()
|
||||
}
|
||||
|
||||
private fun getVaultsFlow(): Flow<List<P2PEthPoolVault>> {
|
||||
return p2pEthPoolVaultsStore.get()
|
||||
}
|
||||
|
||||
private fun findPublicVault(vaults: List<P2PEthPoolVault>): P2PEthPoolVault? {
|
||||
return vaults.firstOrNull { vault -> !vault.isPrivate }
|
||||
}
|
||||
}
|
||||
|
|
@ -4,15 +4,15 @@ import com.tangem.datasource.local.token.StakingActionsStore
|
|||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingAction
|
||||
import com.tangem.domain.staking.repositories.StakingActionRepository
|
||||
import com.tangem.domain.staking.repositories.StakeKitActionRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
internal class DefaultStakingActionRepository(
|
||||
internal class DefaultStakeKitActionRepository(
|
||||
private val stakingActionsStore: StakingActionsStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : StakingActionRepository {
|
||||
) : StakeKitActionRepository {
|
||||
|
||||
override suspend fun store(
|
||||
userWalletId: UserWalletId,
|
||||
|
|
@ -0,0 +1,421 @@
|
|||
package com.tangem.data.staking
|
||||
|
||||
import android.util.Base64
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.TransactionStatus
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toCompressedPublicKey
|
||||
import com.tangem.data.staking.converters.YieldConverter
|
||||
import com.tangem.data.staking.converters.action.ActionStatusConverter
|
||||
import com.tangem.data.staking.converters.action.EnterActionResponseConverter
|
||||
import com.tangem.data.staking.converters.transaction.GasEstimateConverter
|
||||
import com.tangem.data.staking.converters.transaction.StakingTransactionConverter
|
||||
import com.tangem.data.staking.converters.transaction.StakingTransactionStatusConverter
|
||||
import com.tangem.data.staking.converters.transaction.StakingTransactionTypeConverter
|
||||
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
|
||||
import com.tangem.datasource.local.token.StakingYieldsStore
|
||||
import com.tangem.datasource.local.token.converter.StakingNetworkTypeConverter
|
||||
import com.tangem.datasource.local.token.converter.YieldTokenConverter
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.staking.NetworkType
|
||||
import com.tangem.domain.models.staking.action.StakingActionType
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.domain.staking.model.StakingEntryInfo
|
||||
import com.tangem.domain.staking.model.StakingIntegrationID
|
||||
import com.tangem.domain.staking.model.StakingOption
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingAction
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus
|
||||
import com.tangem.domain.staking.model.stakekit.transaction.ActionParams
|
||||
import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate
|
||||
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction
|
||||
import com.tangem.domain.staking.repositories.StakeKitRepository
|
||||
import com.tangem.domain.staking.toggles.StakingFeatureToggles
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
@Suppress("LargeClass", "LongParameterList", "TooManyFunctions")
|
||||
internal class DefaultStakeKitRepository(
|
||||
private val stakeKitApi: StakeKitApi,
|
||||
private val stakingYieldsStore: StakingYieldsStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val stakingFeatureToggles: StakingFeatureToggles,
|
||||
moshi: Moshi,
|
||||
) : StakeKitRepository {
|
||||
|
||||
private val transactionStatusConverter = StakingTransactionStatusConverter()
|
||||
private val transactionTypeConverter = StakingTransactionTypeConverter()
|
||||
private val actionStatusConverter = ActionStatusConverter()
|
||||
|
||||
private val transactionConverter = StakingTransactionConverter(
|
||||
transactionStatusConverter = transactionStatusConverter,
|
||||
transactionTypeConverter = transactionTypeConverter,
|
||||
)
|
||||
private val enterActionResponseConverter = EnterActionResponseConverter(
|
||||
actionStatusConverter = actionStatusConverter,
|
||||
transactionConverter = transactionConverter,
|
||||
)
|
||||
|
||||
private val tronStakeKitTransactionAdapter by lazy { moshi.adapter(TronStakeKitTransaction::class.java) }
|
||||
private val networkTypeAdapter by lazy { moshi.adapter(NetworkTypeDTO::class.java) }
|
||||
private val stakingActionStatusAdapter by lazy { moshi.adapter(StakingActionStatusDTO::class.java) }
|
||||
|
||||
override suspend fun fetchYields() {
|
||||
withContext(dispatchers.io) {
|
||||
val yieldsResponses = getAvailableStakeKitIntegrationsIds().map {
|
||||
async { it.getYieldRequest() }
|
||||
}.awaitAll()
|
||||
|
||||
val yields = yieldsResponses.flatMap { response ->
|
||||
when (response) {
|
||||
is ApiResponse.Success -> response.data.data.filter { yield -> yield.isAvailable == true }
|
||||
is ApiResponse.Error -> {
|
||||
Timber.e("Error fetching enabled yields: ${response.cause}")
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
stakingYieldsStore.store(yields)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getYield(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): Yield {
|
||||
return withContext(dispatchers.io) {
|
||||
val rawCurrencyId = cryptoCurrencyId.rawCurrencyId ?: error("Staking custom tokens is not available")
|
||||
|
||||
val prefetchedYield = findPrefetchedYield(
|
||||
yields = getEnabledYieldsSync(),
|
||||
currencyId = rawCurrencyId,
|
||||
symbol = symbol,
|
||||
)
|
||||
|
||||
prefetchedYield ?: error("Staking is unavailable")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getYield(yieldId: String): Yield {
|
||||
return withContext(dispatchers.io) {
|
||||
getEnabledYieldsSync().find { it.id == yieldId } ?: error("Staking is unavailable")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getActions(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
networkType: NetworkType,
|
||||
stakingActionStatus: StakingActionStatus,
|
||||
): List<StakingAction> {
|
||||
return withContext(dispatchers.io) {
|
||||
val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network).orEmpty()
|
||||
|
||||
val networkTypeDto = StakingNetworkTypeConverter.convertBack(networkType)
|
||||
val networkTypeString = networkTypeDto.extractJsonName()
|
||||
|
||||
val actionStatusDTO = actionStatusConverter.convertBack(stakingActionStatus)
|
||||
val actionStatusString = actionStatusDTO.extractJsonName()
|
||||
|
||||
enterActionResponseConverter.convertListIgnoreErrors(
|
||||
input = stakeKitApi.getActions(
|
||||
walletAddress = address,
|
||||
network = networkTypeString,
|
||||
status = actionStatusString,
|
||||
).getOrThrow().data,
|
||||
onError = { Timber.e("Error converting staking actions list: $it") },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun StakingIntegrationID.StakeKit.getYieldRequest(): ApiResponse<EnabledYieldsResponse> {
|
||||
return when (this) {
|
||||
is StakingIntegrationID.StakeKit.Coin -> stakeKitApi.getEnabledYields(
|
||||
preferredValidatorsOnly = false,
|
||||
network = networkId,
|
||||
)
|
||||
is StakingIntegrationID.StakeKit.EthereumToken -> stakeKitApi.getEnabledYields(
|
||||
preferredValidatorsOnly = false,
|
||||
yieldId = value,
|
||||
network = networkId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAvailableStakeKitIntegrationsIds(): List<StakingIntegrationID.StakeKit> {
|
||||
return StakingIntegrationID.StakeKit.entries.filterNot {
|
||||
it.blockchain == Blockchain.Cardano && !stakingFeatureToggles.isCardanoStakingEnabled
|
||||
}
|
||||
}
|
||||
|
||||
private fun NetworkTypeDTO.extractJsonName(): String {
|
||||
return networkTypeAdapter.toJson(this).replace("\"", "")
|
||||
}
|
||||
|
||||
private fun StakingActionStatusDTO.extractJsonName(): String {
|
||||
return stakingActionStatusAdapter.toJson(this).replace("\"", "")
|
||||
}
|
||||
|
||||
override suspend fun getEntryInfo(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): StakingEntryInfo {
|
||||
return withContext(dispatchers.io) {
|
||||
val yield = getYield(cryptoCurrencyId, symbol)
|
||||
|
||||
StakingEntryInfo(
|
||||
tokenSymbol = yield.token.symbol,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun createAction(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
params: ActionParams,
|
||||
): StakingAction {
|
||||
return withContext(dispatchers.io) {
|
||||
val response = when (params.actionCommonType) {
|
||||
is StakingActionCommonType.Enter -> stakeKitApi.createEnterAction(
|
||||
createActionRequestBody(
|
||||
userWalletId,
|
||||
network,
|
||||
params,
|
||||
),
|
||||
)
|
||||
is StakingActionCommonType.Exit -> stakeKitApi.createExitAction(
|
||||
createActionRequestBody(
|
||||
userWalletId,
|
||||
network,
|
||||
params,
|
||||
),
|
||||
)
|
||||
is StakingActionCommonType.Pending -> stakeKitApi.createPendingAction(
|
||||
createPendingActionRequestBody(params),
|
||||
)
|
||||
}
|
||||
|
||||
enterActionResponseConverter.convert(response.getOrThrow())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun estimateGas(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
params: ActionParams,
|
||||
): StakingGasEstimate {
|
||||
return withContext(dispatchers.io) {
|
||||
val gasEstimateDTO = when (params.actionCommonType) {
|
||||
is StakingActionCommonType.Enter -> stakeKitApi.estimateGasOnEnter(
|
||||
createActionRequestBody(
|
||||
userWalletId,
|
||||
network,
|
||||
params,
|
||||
),
|
||||
)
|
||||
is StakingActionCommonType.Exit -> stakeKitApi.estimateGasOnExit(
|
||||
createActionRequestBody(
|
||||
userWalletId,
|
||||
network,
|
||||
params,
|
||||
),
|
||||
)
|
||||
is StakingActionCommonType.Pending -> stakeKitApi.estimateGasOnPending(
|
||||
createPendingActionRequestBody(params),
|
||||
)
|
||||
}
|
||||
|
||||
GasEstimateConverter.convert(gasEstimateDTO.getOrThrow())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun constructTransaction(
|
||||
networkId: String,
|
||||
fee: Fee,
|
||||
amount: Amount,
|
||||
transactionId: String,
|
||||
): Pair<StakingTransaction, TransactionData.Compiled> {
|
||||
return withContext(dispatchers.io) {
|
||||
val transactionResponse = stakeKitApi.constructTransaction(
|
||||
transactionId = transactionId,
|
||||
body = ConstructTransactionRequestBody(),
|
||||
)
|
||||
|
||||
val transaction = transactionConverter.convert(transactionResponse.getOrThrow())
|
||||
val unsignedTransaction =
|
||||
transaction.unsignedTransaction ?: error("No unsigned transaction available")
|
||||
val transactionData = TransactionData.Compiled(
|
||||
value = getTransactionDataType(networkId, unsignedTransaction),
|
||||
fee = fee,
|
||||
amount = amount,
|
||||
status = TransactionStatus.Unconfirmed,
|
||||
)
|
||||
|
||||
transaction to transactionData
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun createActionRequestBody(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
params: ActionParams,
|
||||
): ActionRequestBody {
|
||||
return ActionRequestBody(
|
||||
integrationId = params.integrationId,
|
||||
addresses = Address(
|
||||
address = params.address,
|
||||
additionalAddresses = createAdditionalAddresses(userWalletId, network, params),
|
||||
),
|
||||
args = ActionRequestBodyArgs(
|
||||
amount = params.amount.toPlainString(),
|
||||
inputToken = YieldTokenConverter.convertBack(params.token),
|
||||
validatorAddress = params.validatorAddress,
|
||||
validatorAddresses = listOf(params.validatorAddress), // check on other networks
|
||||
tronResource = getTronResource(network),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createPendingActionRequestBody(params: ActionParams): PendingActionRequestBody {
|
||||
return PendingActionRequestBody(
|
||||
integrationId = params.integrationId,
|
||||
type = params.type ?: StakingActionType.UNKNOWN,
|
||||
passthrough = params.passthrough.orEmpty(),
|
||||
args = ActionRequestBodyArgs(
|
||||
amount = params.amount.toPlainString(),
|
||||
validatorAddress = params.validatorAddress,
|
||||
validatorAddresses = listOf(params.validatorAddress),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun createAdditionalAddresses(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
params: ActionParams,
|
||||
): Address.AdditionalAddresses? {
|
||||
val selectedWallet = walletManagersFacade.getOrCreateWalletManager(userWalletId, network)
|
||||
return when (params.token.network) {
|
||||
NetworkType.COSMOS -> Address.AdditionalAddresses(
|
||||
cosmosPubKey = Base64.encodeToString(
|
||||
/* input = */ selectedWallet?.wallet?.publicKey?.blockchainKey?.toCompressedPublicKey(),
|
||||
/* flags = */ Base64.NO_WRAP,
|
||||
),
|
||||
)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTransactionDataType(networkId: String, unsignedTransaction: String): TransactionData.Compiled.Data {
|
||||
return when (Blockchain.fromId(networkId)) {
|
||||
Blockchain.Solana,
|
||||
Blockchain.Cosmos,
|
||||
-> TransactionData.Compiled.Data.Bytes(unsignedTransaction.hexToBytes())
|
||||
Blockchain.BSC,
|
||||
Blockchain.Ethereum,
|
||||
Blockchain.TON,
|
||||
Blockchain.Cardano,
|
||||
-> TransactionData.Compiled.Data.RawString(unsignedTransaction)
|
||||
Blockchain.Tron -> {
|
||||
val tronStakeKitTransaction = tronStakeKitTransactionAdapter.fromJson(unsignedTransaction)
|
||||
?: error("Failed to parse Tron StakeKit transaction")
|
||||
TransactionData.Compiled.Data.RawString(tronStakeKitTransaction.rawDataHex)
|
||||
}
|
||||
else -> error("Unsupported blockchain")
|
||||
}
|
||||
}
|
||||
|
||||
private fun findPrefetchedYield(yields: List<Yield>, currencyId: CryptoCurrency.RawID, symbol: String): Yield? {
|
||||
return yields.find { yield ->
|
||||
yield.tokens.any { it.coinGeckoId == currencyId.value && it.symbol == symbol }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getEnabledYieldsSync(): List<Yield> {
|
||||
return YieldConverter.convertListIgnoreErrors(
|
||||
input = stakingYieldsStore.getSync(),
|
||||
onError = { Timber.e("Error converting one of the items in enabled yields: $it") },
|
||||
)
|
||||
}
|
||||
|
||||
override fun getEnabledYields(): Flow<List<Yield>> {
|
||||
return stakingYieldsStore.get().map { yields ->
|
||||
YieldConverter.convertListIgnoreErrors(
|
||||
input = yields,
|
||||
onError = { Timber.e("Error converting one of the items in enabled yields: $it") },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getStakingAvailability(
|
||||
rawCurrencyId: CryptoCurrency.RawID,
|
||||
symbol: String,
|
||||
): Flow<StakingAvailability> {
|
||||
return getEnabledYields()
|
||||
.distinctUntilChanged()
|
||||
.map { yields ->
|
||||
if (yields.isEmpty()) {
|
||||
return@map StakingAvailability.TemporaryUnavailable
|
||||
}
|
||||
|
||||
val prefetchedYield = findPrefetchedYield(
|
||||
yields = yields,
|
||||
currencyId = rawCurrencyId,
|
||||
symbol = symbol,
|
||||
)
|
||||
|
||||
if (prefetchedYield != null) {
|
||||
StakingAvailability.Available(StakingOption.StakeKit(prefetchedYield))
|
||||
} else {
|
||||
StakingAvailability.TemporaryUnavailable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getStakingAvailabilitySync(
|
||||
rawCurrencyId: CryptoCurrency.RawID,
|
||||
symbol: String,
|
||||
): StakingAvailability {
|
||||
val yields = getEnabledYieldsSync()
|
||||
if (yields.isEmpty()) {
|
||||
return StakingAvailability.TemporaryUnavailable
|
||||
}
|
||||
|
||||
val prefetchedYield = findPrefetchedYield(
|
||||
yields = yields,
|
||||
currencyId = rawCurrencyId,
|
||||
symbol = symbol,
|
||||
)
|
||||
|
||||
return if (prefetchedYield != null) {
|
||||
StakingAvailability.Available(StakingOption.StakeKit(prefetchedYield))
|
||||
} else {
|
||||
StakingAvailability.TemporaryUnavailable
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTronResource(network: Network): TronResource? {
|
||||
val blockchain = Blockchain.fromNetworkId(network.backendId)
|
||||
|
||||
return if (blockchain == Blockchain.Tron || blockchain == Blockchain.TronTestnet) {
|
||||
TronResource.ENERGY
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,16 +6,16 @@ import com.tangem.datasource.local.preferences.AppPreferencesStore
|
|||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectListSync
|
||||
import com.tangem.domain.staking.model.UnsubmittedTransactionMetadata
|
||||
import com.tangem.domain.staking.repositories.StakingTransactionHashRepository
|
||||
import com.tangem.domain.staking.repositories.StakeKitTransactionHashRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
internal class DefaultStakingTransactionHashRepository(
|
||||
internal class DefaultStakeKitTransactionHashRepository(
|
||||
private val stakeKitApi: StakeKitApi,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : StakingTransactionHashRepository {
|
||||
) : StakeKitTransactionHashRepository {
|
||||
|
||||
override suspend fun submitHash(transactionId: String, transactionHash: String) {
|
||||
withContext(dispatchers.io) {
|
||||
|
|
@ -1,204 +1,38 @@
|
|||
package com.tangem.data.staking
|
||||
|
||||
import android.util.Base64
|
||||
import arrow.core.getOrElse
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.TransactionStatus
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toCompressedPublicKey
|
||||
import com.tangem.data.staking.converters.YieldConverter
|
||||
import com.tangem.data.staking.converters.action.ActionStatusConverter
|
||||
import com.tangem.data.staking.converters.action.EnterActionResponseConverter
|
||||
import com.tangem.data.staking.converters.transaction.GasEstimateConverter
|
||||
import com.tangem.data.staking.converters.transaction.StakingTransactionConverter
|
||||
import com.tangem.data.staking.converters.transaction.StakingTransactionStatusConverter
|
||||
import com.tangem.data.staking.converters.transaction.StakingTransactionTypeConverter
|
||||
import com.tangem.data.staking.store.YieldsBalancesStore
|
||||
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
|
||||
import com.tangem.datasource.local.token.P2PEthPoolVaultsStore
|
||||
import com.tangem.datasource.local.token.StakingYieldsStore
|
||||
import com.tangem.datasource.local.token.converter.StakingNetworkTypeConverter
|
||||
import com.tangem.datasource.local.token.converter.YieldTokenConverter
|
||||
import com.tangem.domain.card.common.TapWorkarounds.isWallet2
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.staking.NetworkType
|
||||
import com.tangem.domain.models.staking.YieldBalance
|
||||
import com.tangem.domain.models.staking.action.StakingActionType
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.domain.staking.model.StakingEntryInfo
|
||||
import com.tangem.domain.staking.model.StakingIntegrationID
|
||||
import com.tangem.domain.staking.model.StakingOption
|
||||
import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingAction
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus
|
||||
import com.tangem.domain.staking.model.stakekit.transaction.ActionParams
|
||||
import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate
|
||||
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction
|
||||
import com.tangem.domain.staking.repositories.P2PEthPoolRepository
|
||||
import com.tangem.domain.staking.repositories.StakeKitRepository
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.staking.toggles.StakingFeatureToggles
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isCardano
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isSolana
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.channels.ProducerScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
@Suppress("LargeClass", "LongParameterList", "TooManyFunctions")
|
||||
internal class DefaultStakingRepository(
|
||||
private val stakeKitApi: StakeKitApi,
|
||||
private val stakingYieldsStore: StakingYieldsStore,
|
||||
private val p2pEthPoolVaultsStore: P2PEthPoolVaultsStore,
|
||||
private val stakeKitRepository: StakeKitRepository,
|
||||
private val p2pEthPoolRepository: P2PEthPoolRepository,
|
||||
private val stakingBalanceStoreV2: YieldsBalancesStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val stakingFeatureToggles: StakingFeatureToggles,
|
||||
moshi: Moshi,
|
||||
) : StakingRepository {
|
||||
|
||||
private val transactionStatusConverter = StakingTransactionStatusConverter()
|
||||
private val transactionTypeConverter = StakingTransactionTypeConverter()
|
||||
private val actionStatusConverter = ActionStatusConverter()
|
||||
|
||||
private val transactionConverter = StakingTransactionConverter(
|
||||
transactionStatusConverter = transactionStatusConverter,
|
||||
transactionTypeConverter = transactionTypeConverter,
|
||||
)
|
||||
private val enterActionResponseConverter = EnterActionResponseConverter(
|
||||
actionStatusConverter = actionStatusConverter,
|
||||
transactionConverter = transactionConverter,
|
||||
)
|
||||
|
||||
private val tronStakeKitTransactionAdapter by lazy { moshi.adapter(TronStakeKitTransaction::class.java) }
|
||||
private val networkTypeAdapter by lazy { moshi.adapter(NetworkTypeDTO::class.java) }
|
||||
private val stakingActionStatusAdapter by lazy { moshi.adapter(StakingActionStatusDTO::class.java) }
|
||||
|
||||
override suspend fun fetchYields() {
|
||||
withContext(dispatchers.io) {
|
||||
val yieldsResponses = getAvailableStakeKitIntegrationsIds().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)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getYield(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): Yield {
|
||||
return withContext(dispatchers.io) {
|
||||
val rawCurrencyId = cryptoCurrencyId.rawCurrencyId ?: error("Staking custom tokens is not available")
|
||||
|
||||
val prefetchedYield = findPrefetchedYield(
|
||||
yields = getEnabledYieldsSync(),
|
||||
currencyId = rawCurrencyId,
|
||||
symbol = symbol,
|
||||
)
|
||||
|
||||
prefetchedYield ?: error("Staking is unavailable")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getYield(yieldId: String): Yield {
|
||||
return withContext(dispatchers.io) {
|
||||
getEnabledYieldsSync().find { it.id == yieldId } ?: error("Staking is unavailable")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getActions(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
networkType: NetworkType,
|
||||
stakingActionStatus: StakingActionStatus,
|
||||
): List<StakingAction> {
|
||||
return withContext(dispatchers.io) {
|
||||
val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network).orEmpty()
|
||||
|
||||
val networkTypeDto = StakingNetworkTypeConverter.convertBack(networkType)
|
||||
val networkTypeString = networkTypeDto.extractJsonName()
|
||||
|
||||
val actionStatusDTO = actionStatusConverter.convertBack(stakingActionStatus)
|
||||
val actionStatusString = actionStatusDTO.extractJsonName()
|
||||
|
||||
enterActionResponseConverter.convertListIgnoreErrors(
|
||||
input = stakeKitApi.getActions(
|
||||
walletAddress = address,
|
||||
network = networkTypeString,
|
||||
status = actionStatusString,
|
||||
).getOrThrow().data,
|
||||
onError = { Timber.e("Error converting staking actions list: $it") },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun StakingIntegrationID.StakeKit.getYieldRequest(): ApiResponse<EnabledYieldsResponse> {
|
||||
return when (this) {
|
||||
is StakingIntegrationID.StakeKit.Coin -> stakeKitApi.getEnabledYields(
|
||||
preferredValidatorsOnly = false,
|
||||
network = networkId,
|
||||
)
|
||||
is StakingIntegrationID.StakeKit.EthereumToken -> stakeKitApi.getEnabledYields(
|
||||
preferredValidatorsOnly = false,
|
||||
yieldId = value,
|
||||
network = networkId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAvailableStakeKitIntegrationsIds(): List<StakingIntegrationID.StakeKit> {
|
||||
return StakingIntegrationID.StakeKit.entries.filterNot {
|
||||
it.blockchain == Blockchain.Cardano && !stakingFeatureToggles.isCardanoStakingEnabled
|
||||
}
|
||||
}
|
||||
|
||||
private fun NetworkTypeDTO.extractJsonName(): String {
|
||||
return networkTypeAdapter.toJson(this).replace("\"", "")
|
||||
}
|
||||
|
||||
private fun StakingActionStatusDTO.extractJsonName(): String {
|
||||
return stakingActionStatusAdapter.toJson(this).replace("\"", "")
|
||||
}
|
||||
|
||||
override suspend fun getEntryInfo(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): StakingEntryInfo {
|
||||
return withContext(dispatchers.io) {
|
||||
val yield = getYield(cryptoCurrencyId, symbol)
|
||||
|
||||
StakingEntryInfo(
|
||||
tokenSymbol = yield.token.symbol,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getStakingAvailability(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
|
|
@ -222,14 +56,16 @@ internal class DefaultStakingRepository(
|
|||
|
||||
val stakingIntegration = StakingIntegrationID.create(currencyId = cryptoCurrency.id)
|
||||
|
||||
when (stakingIntegration) {
|
||||
is StakingIntegrationID.P2P -> subscribeToP2PStakingAvailability()
|
||||
is StakingIntegrationID.StakeKit -> subscribeToStakeKitStakingAvailability(
|
||||
val availabilityFlow = when (stakingIntegration) {
|
||||
is StakingIntegrationID.P2P -> p2pEthPoolRepository.getStakingAvailability()
|
||||
is StakingIntegrationID.StakeKit -> stakeKitRepository.getStakingAvailability(
|
||||
rawCurrencyId,
|
||||
cryptoCurrency,
|
||||
cryptoCurrency.symbol,
|
||||
)
|
||||
null -> send(StakingAvailability.Unavailable)
|
||||
null -> flowOf(StakingAvailability.Unavailable)
|
||||
}
|
||||
|
||||
availabilityFlow.collect { send(it) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -254,39 +90,25 @@ internal class DefaultStakingRepository(
|
|||
?: return StakingAvailability.Unavailable
|
||||
|
||||
return when (stakingIntegration) {
|
||||
is StakingIntegrationID.P2P -> {
|
||||
val vaults = getP2PEthPoolVaultsSync()
|
||||
if (vaults.isEmpty()) {
|
||||
return StakingAvailability.TemporaryUnavailable
|
||||
}
|
||||
is StakingIntegrationID.P2P -> p2pEthPoolRepository.getStakingAvailabilitySync()
|
||||
is StakingIntegrationID.StakeKit -> stakeKitRepository.getStakingAvailabilitySync(
|
||||
rawCurrencyId,
|
||||
cryptoCurrency.symbol,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val vault = findP2PEthPoolVault(
|
||||
vaults = vaults,
|
||||
)
|
||||
override suspend fun isAnyTokenStaked(userWalletId: UserWalletId): Boolean {
|
||||
return withContext(dispatchers.default) {
|
||||
val balances = stakingBalanceStoreV2.getAllSyncOrNull(userWalletId) ?: return@withContext false
|
||||
|
||||
if (vault != null) {
|
||||
StakingAvailability.Available(StakingOption.P2P(vault))
|
||||
} else {
|
||||
StakingAvailability.TemporaryUnavailable
|
||||
val hasDataYieldBalance by lazy {
|
||||
balances.any { yieldBalance ->
|
||||
(yieldBalance as? YieldBalance.Data)?.balance?.items?.isNotEmpty() == true
|
||||
}
|
||||
}
|
||||
is StakingIntegrationID.StakeKit -> {
|
||||
val yields = getEnabledYieldsSync()
|
||||
if (yields.isEmpty()) {
|
||||
return StakingAvailability.TemporaryUnavailable
|
||||
}
|
||||
|
||||
val prefetchedYield = findPrefetchedYield(
|
||||
yields = yields,
|
||||
currencyId = rawCurrencyId,
|
||||
symbol = cryptoCurrency.symbol,
|
||||
)
|
||||
|
||||
when {
|
||||
prefetchedYield != null -> StakingAvailability.Available(StakingOption.StakeKit(prefetchedYield))
|
||||
else -> StakingAvailability.TemporaryUnavailable
|
||||
}
|
||||
}
|
||||
balances.isNotEmpty() && hasDataYieldBalance
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -316,267 +138,6 @@ internal class DefaultStakingRepository(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun createAction(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
params: ActionParams,
|
||||
): StakingAction {
|
||||
return withContext(dispatchers.io) {
|
||||
val response = when (params.actionCommonType) {
|
||||
is StakingActionCommonType.Enter -> stakeKitApi.createEnterAction(
|
||||
createActionRequestBody(
|
||||
userWalletId,
|
||||
network,
|
||||
params,
|
||||
),
|
||||
)
|
||||
is StakingActionCommonType.Exit -> stakeKitApi.createExitAction(
|
||||
createActionRequestBody(
|
||||
userWalletId,
|
||||
network,
|
||||
params,
|
||||
),
|
||||
)
|
||||
is StakingActionCommonType.Pending -> stakeKitApi.createPendingAction(
|
||||
createPendingActionRequestBody(params),
|
||||
)
|
||||
}
|
||||
|
||||
enterActionResponseConverter.convert(response.getOrThrow())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun estimateGas(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
params: ActionParams,
|
||||
): StakingGasEstimate {
|
||||
return withContext(dispatchers.io) {
|
||||
val gasEstimateDTO = when (params.actionCommonType) {
|
||||
is StakingActionCommonType.Enter -> stakeKitApi.estimateGasOnEnter(
|
||||
createActionRequestBody(
|
||||
userWalletId,
|
||||
network,
|
||||
params,
|
||||
),
|
||||
)
|
||||
is StakingActionCommonType.Exit -> stakeKitApi.estimateGasOnExit(
|
||||
createActionRequestBody(
|
||||
userWalletId,
|
||||
network,
|
||||
params,
|
||||
),
|
||||
)
|
||||
is StakingActionCommonType.Pending -> stakeKitApi.estimateGasOnPending(
|
||||
createPendingActionRequestBody(params),
|
||||
)
|
||||
}
|
||||
|
||||
GasEstimateConverter.convert(gasEstimateDTO.getOrThrow())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun constructTransaction(
|
||||
networkId: String,
|
||||
fee: Fee,
|
||||
amount: Amount,
|
||||
transactionId: String,
|
||||
): Pair<StakingTransaction, TransactionData.Compiled> {
|
||||
return withContext(dispatchers.io) {
|
||||
val transactionResponse = stakeKitApi.constructTransaction(
|
||||
transactionId = transactionId,
|
||||
body = ConstructTransactionRequestBody(),
|
||||
)
|
||||
|
||||
val transaction = transactionConverter.convert(transactionResponse.getOrThrow())
|
||||
val unsignedTransaction =
|
||||
transaction.unsignedTransaction ?: error("No unsigned transaction available")
|
||||
val transactionData = TransactionData.Compiled(
|
||||
value = getTransactionDataType(networkId, unsignedTransaction),
|
||||
fee = fee,
|
||||
amount = amount,
|
||||
status = TransactionStatus.Unconfirmed,
|
||||
)
|
||||
|
||||
transaction to transactionData
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun isAnyTokenStaked(userWalletId: UserWalletId): Boolean {
|
||||
return withContext(dispatchers.default) {
|
||||
val balances = stakingBalanceStoreV2.getAllSyncOrNull(userWalletId) ?: return@withContext false
|
||||
|
||||
val hasDataYieldBalance by lazy {
|
||||
balances.any { yieldBalance ->
|
||||
(yieldBalance as? YieldBalance.Data)?.balance?.items?.isNotEmpty() == true
|
||||
}
|
||||
}
|
||||
|
||||
balances.isNotEmpty() && hasDataYieldBalance
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun createActionRequestBody(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
params: ActionParams,
|
||||
): ActionRequestBody {
|
||||
return ActionRequestBody(
|
||||
integrationId = params.integrationId,
|
||||
addresses = Address(
|
||||
address = params.address,
|
||||
additionalAddresses = createAdditionalAddresses(userWalletId, network, params),
|
||||
),
|
||||
args = ActionRequestBodyArgs(
|
||||
amount = params.amount.toPlainString(),
|
||||
inputToken = YieldTokenConverter.convertBack(params.token),
|
||||
validatorAddress = params.validatorAddress,
|
||||
validatorAddresses = listOf(params.validatorAddress), // check on other networks
|
||||
tronResource = getTronResource(network),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createPendingActionRequestBody(params: ActionParams): PendingActionRequestBody {
|
||||
return PendingActionRequestBody(
|
||||
integrationId = params.integrationId,
|
||||
type = params.type ?: StakingActionType.UNKNOWN,
|
||||
passthrough = params.passthrough.orEmpty(),
|
||||
args = ActionRequestBodyArgs(
|
||||
amount = params.amount.toPlainString(),
|
||||
validatorAddress = params.validatorAddress,
|
||||
validatorAddresses = listOf(params.validatorAddress),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun createAdditionalAddresses(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
params: ActionParams,
|
||||
): Address.AdditionalAddresses? {
|
||||
val selectedWallet = walletManagersFacade.getOrCreateWalletManager(userWalletId, network)
|
||||
return when (params.token.network) {
|
||||
NetworkType.COSMOS -> Address.AdditionalAddresses(
|
||||
cosmosPubKey = Base64.encodeToString(
|
||||
/* input = */ selectedWallet?.wallet?.publicKey?.blockchainKey?.toCompressedPublicKey(),
|
||||
/* flags = */ Base64.NO_WRAP,
|
||||
),
|
||||
)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTransactionDataType(networkId: String, unsignedTransaction: String): TransactionData.Compiled.Data {
|
||||
return when (Blockchain.fromId(networkId)) {
|
||||
Blockchain.Solana,
|
||||
Blockchain.Cosmos,
|
||||
-> TransactionData.Compiled.Data.Bytes(unsignedTransaction.hexToBytes())
|
||||
Blockchain.BSC,
|
||||
Blockchain.Ethereum,
|
||||
Blockchain.TON,
|
||||
Blockchain.Cardano,
|
||||
-> TransactionData.Compiled.Data.RawString(unsignedTransaction)
|
||||
Blockchain.Tron -> {
|
||||
val tronStakeKitTransaction = tronStakeKitTransactionAdapter.fromJson(unsignedTransaction)
|
||||
?: error("Failed to parse Tron StakeKit transaction")
|
||||
TransactionData.Compiled.Data.RawString(tronStakeKitTransaction.rawDataHex)
|
||||
}
|
||||
else -> error("Unsupported blockchain")
|
||||
}
|
||||
}
|
||||
|
||||
private fun findPrefetchedYield(yields: List<Yield>, currencyId: CryptoCurrency.RawID, symbol: String): Yield? {
|
||||
return yields.find { yield ->
|
||||
yield.tokens.any { it.coinGeckoId == currencyId.value && it.symbol == symbol }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getEnabledYieldsSync(): List<Yield> {
|
||||
return YieldConverter.convertListIgnoreErrors(
|
||||
input = stakingYieldsStore.getSync(),
|
||||
onError = { Timber.e("Error converting one of the items in enabled yields: $it") },
|
||||
)
|
||||
}
|
||||
|
||||
override fun getEnabledYields(): Flow<List<Yield>> {
|
||||
return stakingYieldsStore.get().map {
|
||||
YieldConverter.convertListIgnoreErrors(
|
||||
input = it,
|
||||
onError = { Timber.e("Error converting one of the items in enabled yields: $it") },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getP2PEthPoolVaultsSync(): List<P2PEthPoolVault> {
|
||||
return p2pEthPoolVaultsStore.getSync()
|
||||
}
|
||||
|
||||
private fun getP2PEthPoolVaults(): Flow<List<P2PEthPoolVault>> {
|
||||
return p2pEthPoolVaultsStore.get()
|
||||
}
|
||||
|
||||
private fun findP2PEthPoolVault(vaults: List<P2PEthPoolVault>): P2PEthPoolVault? {
|
||||
return vaults.firstOrNull { vault -> !vault.isPrivate }
|
||||
}
|
||||
|
||||
private fun ProducerScope<StakingAvailability>.subscribeToP2PStakingAvailability() {
|
||||
getP2PEthPoolVaults()
|
||||
.distinctUntilChanged()
|
||||
.onEach { vaults ->
|
||||
if (vaults.isEmpty()) {
|
||||
send(StakingAvailability.TemporaryUnavailable)
|
||||
return@onEach
|
||||
}
|
||||
|
||||
val vault = findP2PEthPoolVault(vaults = vaults)
|
||||
|
||||
if (vault != null) {
|
||||
send(StakingAvailability.Available(StakingOption.P2P(vault)))
|
||||
} else {
|
||||
send(StakingAvailability.TemporaryUnavailable)
|
||||
}
|
||||
}
|
||||
.launchIn(this)
|
||||
}
|
||||
|
||||
private fun ProducerScope<StakingAvailability>.subscribeToStakeKitStakingAvailability(
|
||||
rawCurrencyId: CryptoCurrency.RawID,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
) {
|
||||
getEnabledYields()
|
||||
.distinctUntilChanged()
|
||||
.onEach { yields ->
|
||||
if (yields.isEmpty()) {
|
||||
send(StakingAvailability.TemporaryUnavailable)
|
||||
return@onEach
|
||||
}
|
||||
|
||||
val prefetchedYield = findPrefetchedYield(
|
||||
yields = yields,
|
||||
currencyId = rawCurrencyId,
|
||||
symbol = cryptoCurrency.symbol,
|
||||
)
|
||||
|
||||
if (prefetchedYield != null) {
|
||||
send(StakingAvailability.Available(StakingOption.StakeKit(prefetchedYield)))
|
||||
} else {
|
||||
send(StakingAvailability.TemporaryUnavailable)
|
||||
}
|
||||
}
|
||||
.launchIn(this)
|
||||
}
|
||||
|
||||
private fun getTronResource(network: Network): TronResource? {
|
||||
val blockchain = Blockchain.fromNetworkId(network.backendId)
|
||||
|
||||
return if (blockchain == Blockchain.Tron || blockchain == Blockchain.TronTestnet) {
|
||||
TronResource.ENERGY
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val INVALID_BATCHES_FOR_SOLANA = listOf("AC01", "CB79")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,11 @@ import com.squareup.moshi.Moshi
|
|||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.data.staking.DefaultP2PEthPoolRepository
|
||||
import com.tangem.data.staking.DefaultStakingActionRepository
|
||||
import com.tangem.data.staking.DefaultStakeKitActionRepository
|
||||
import com.tangem.data.staking.DefaultStakingErrorResolver
|
||||
import com.tangem.data.staking.DefaultStakeKitRepository
|
||||
import com.tangem.data.staking.DefaultStakingRepository
|
||||
import com.tangem.data.staking.DefaultStakingTransactionHashRepository
|
||||
import com.tangem.data.staking.DefaultStakeKitTransactionHashRepository
|
||||
import com.tangem.data.staking.converters.error.StakeKitErrorConverter
|
||||
import com.tangem.data.staking.store.YieldsBalancesStore
|
||||
import com.tangem.data.staking.toggles.DefaultStakingFeatureToggles
|
||||
|
|
@ -21,10 +22,11 @@ import com.tangem.datasource.local.token.P2PEthPoolVaultsStore
|
|||
import com.tangem.datasource.local.token.StakingActionsStore
|
||||
import com.tangem.datasource.local.token.StakingYieldsStore
|
||||
import com.tangem.domain.staking.repositories.P2PEthPoolRepository
|
||||
import com.tangem.domain.staking.repositories.StakingActionRepository
|
||||
import com.tangem.domain.staking.repositories.StakeKitActionRepository
|
||||
import com.tangem.domain.staking.repositories.StakingErrorResolver
|
||||
import com.tangem.domain.staking.repositories.StakeKitRepository
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.staking.repositories.StakingTransactionHashRepository
|
||||
import com.tangem.domain.staking.repositories.StakeKitTransactionHashRepository
|
||||
import com.tangem.domain.staking.toggles.StakingFeatureToggles
|
||||
import com.tangem.domain.staking.utils.StakingCleaner
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
|
|
@ -42,30 +44,44 @@ internal object StakingDataModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideStakingRepository(
|
||||
fun provideStakeKitRepository(
|
||||
stakeKitApi: StakeKitApi,
|
||||
stakingYieldsStore: StakingYieldsStore,
|
||||
p2pEthPoolVaultsStore: P2PEthPoolVaultsStore,
|
||||
yieldsBalancesStore: YieldsBalancesStore,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
getUserWalletUseCase: GetUserWalletUseCase,
|
||||
stakingFeatureToggles: StakingFeatureToggles,
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
): StakingRepository {
|
||||
return DefaultStakingRepository(
|
||||
): StakeKitRepository {
|
||||
return DefaultStakeKitRepository(
|
||||
stakeKitApi = stakeKitApi,
|
||||
stakingYieldsStore = stakingYieldsStore,
|
||||
p2pEthPoolVaultsStore = p2pEthPoolVaultsStore,
|
||||
stakingBalanceStoreV2 = yieldsBalancesStore,
|
||||
dispatchers = dispatchers,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
getUserWalletUseCase = getUserWalletUseCase,
|
||||
stakingFeatureToggles = stakingFeatureToggles,
|
||||
moshi = moshi,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideStakingRepository(
|
||||
stakeKitRepository: StakeKitRepository,
|
||||
p2pEthPoolRepository: P2PEthPoolRepository,
|
||||
yieldsBalancesStore: YieldsBalancesStore,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
getUserWalletUseCase: GetUserWalletUseCase,
|
||||
stakingFeatureToggles: StakingFeatureToggles,
|
||||
): StakingRepository {
|
||||
return DefaultStakingRepository(
|
||||
stakeKitRepository = stakeKitRepository,
|
||||
p2pEthPoolRepository = p2pEthPoolRepository,
|
||||
stakingBalanceStoreV2 = yieldsBalancesStore,
|
||||
dispatchers = dispatchers,
|
||||
getUserWalletUseCase = getUserWalletUseCase,
|
||||
stakingFeatureToggles = stakingFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideP2PEthPoolRepository(
|
||||
|
|
@ -86,8 +102,8 @@ internal object StakingDataModule {
|
|||
stakeKitApi: StakeKitApi,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): StakingTransactionHashRepository {
|
||||
return DefaultStakingTransactionHashRepository(
|
||||
): StakeKitTransactionHashRepository {
|
||||
return DefaultStakeKitTransactionHashRepository(
|
||||
stakeKitApi = stakeKitApi,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
dispatchers = dispatchers,
|
||||
|
|
@ -99,8 +115,8 @@ internal object StakingDataModule {
|
|||
fun provideStakingActionRepository(
|
||||
stakingActionsStore: StakingActionsStore,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): StakingActionRepository {
|
||||
return DefaultStakingActionRepository(
|
||||
): StakeKitActionRepository {
|
||||
return DefaultStakeKitActionRepository(
|
||||
stakingActionsStore = stakingActionsStore,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -69,8 +69,8 @@ internal class DefaultMultiYieldBalanceFetcher @Inject constructor(
|
|||
|
||||
fetch(userWalletId = params.userWalletId, stakingIds = availableStakingIds)
|
||||
}
|
||||
.onLeft {
|
||||
Timber.e(it, "Unable to fetch yield balances $params")
|
||||
.onLeft { throwable ->
|
||||
Timber.e(throwable, "Unable to fetch yield balances $params")
|
||||
|
||||
yieldsBalancesStore.storeError(userWalletId = params.userWalletId, stakingIds = stakingIds)
|
||||
}
|
||||
|
|
@ -163,21 +163,21 @@ internal class DefaultMultiYieldBalanceFetcher @Inject constructor(
|
|||
|
||||
if (!allResponsesReceived(requests, yieldBalances)) {
|
||||
val values = stakingIds.filter { stakingId ->
|
||||
yieldBalances.none {
|
||||
stakingId.integrationId == it.integrationId &&
|
||||
stakingId.address == it.addresses.address
|
||||
yieldBalances.none { balanceWrapper ->
|
||||
stakingId.integrationId == balanceWrapper.integrationId &&
|
||||
stakingId.address == balanceWrapper.addresses.address
|
||||
}
|
||||
}
|
||||
|
||||
yieldsBalancesStore.storeError(userWalletId = userWalletId, stakingIds = values.toSet())
|
||||
}
|
||||
},
|
||||
onError = {
|
||||
Timber.e(it, "Unable to fetch yield balances $userWalletId")
|
||||
onError = { throwable ->
|
||||
Timber.e(throwable, "Unable to fetch yield balances $userWalletId")
|
||||
|
||||
yieldsBalancesStore.storeError(userWalletId = userWalletId, stakingIds = stakingIds)
|
||||
|
||||
throw it
|
||||
throw throwable
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -187,9 +187,9 @@ internal class DefaultMultiYieldBalanceFetcher @Inject constructor(
|
|||
yieldBalances: Set<YieldBalanceWrapperDTO>,
|
||||
): Boolean {
|
||||
return requests.all { request ->
|
||||
yieldBalances.any {
|
||||
request.integrationId == it.integrationId &&
|
||||
request.addresses.address == it.addresses.address
|
||||
yieldBalances.any { balance ->
|
||||
request.integrationId == balance.integrationId &&
|
||||
request.addresses.address == balance.addresses.address
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ internal object YieldBalanceRequestBodyFactory {
|
|||
return YieldBalanceRequestBody(
|
||||
addresses = YieldBalanceRequestBodyAddressFactory.create(stakingID),
|
||||
args = YieldBalanceRequestBody.YieldBalanceRequestArgs(
|
||||
validatorAddresses = listOf(), // todo add validators [REDACTED_JIRA]
|
||||
validatorAddresses = emptyList(), // todo add validators [REDACTED_JIRA]
|
||||
),
|
||||
integrationId = stakingID.integrationId,
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue