Fix Polkadot staking, add USDT bridge, update dashboard card

- Restore staking module to Nova upstream (fix Polkadot ACTIVE/shimmer)
- Add USDT(DOT) <-> USDT(HEZ) bridge option to Buy/Sell screen
- Dashboard card: add telegram_welcome image, info text, Non-Citizen role
- Add non-citizen info text to all 12 locale files
- Simplify ComputationalCache (remove stale scope recreation)
This commit is contained in:
2026-02-18 02:43:53 +03:00
parent 9c7bb7c6e9
commit 14519d7818
39 changed files with 458 additions and 315 deletions
@@ -38,24 +38,19 @@ suspend fun BagListRepository.bagListLocatorOrThrow(chainId: ChainId): BagListLo
class LocalBagListRepository(
private val localStorage: StorageDataSource,
private val remoteStorage: StorageDataSource,
private val chainRegistry: ChainRegistry
) : BagListRepository {
override suspend fun bagThresholds(chainId: ChainId): List<BagListNode.Score>? {
return runCatching {
chainRegistry.withRuntime(chainId) {
runtime.metadata.voterListOrNull()?.constant("BagThresholds")?.getAs(collectionOf(::score))
}
}.getOrNull()
return chainRegistry.withRuntime(chainId) {
runtime.metadata.voterListOrNull()?.constant("BagThresholds")?.getAs(collectionOf(::score))
}
}
override suspend fun bagListSize(chainId: ChainId): BigInteger? {
return runCatching {
remoteStorage.query(chainId) {
runtime.metadata.voterListOrNull()?.storage("CounterForListNodes")?.query(binding = ::bindNumber)
}
}.getOrNull()
return localStorage.query(chainId) {
runtime.metadata.voterListOrNull()?.storage("CounterForListNodes")?.query(binding = ::bindNumber)
}
}
override suspend fun maxElectingVotes(chainId: ChainId): BigInteger? {
@@ -6,7 +6,7 @@ import io.novafoundation.nova.common.data.network.runtime.binding.bindNumber
import io.novafoundation.nova.common.utils.parasOrNull
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
import io.novafoundation.nova.runtime.storage.source.StorageDataSource
import io.novasama.substrate_sdk_android.runtime.metadata.storageOrNull
import io.novasama.substrate_sdk_android.runtime.metadata.storage
interface ParasRepository {
@@ -21,7 +21,7 @@ class RealParasRepository(
override suspend fun activePublicParachains(chainId: ChainId): Int? {
return localSource.query(chainId) {
val parachains = runtime.metadata.parasOrNull()?.storageOrNull("Parachains")
val parachains = runtime.metadata.parasOrNull()?.storage("Parachains")
?.query(binding = ::bindParachains) ?: return@query null
parachains.count { it >= LOWEST_PUBLIC_ID }
@@ -13,6 +13,7 @@ import io.novafoundation.nova.common.utils.metadata
import io.novafoundation.nova.common.utils.numberConstant
import io.novafoundation.nova.common.utils.numberConstantOrNull
import io.novafoundation.nova.common.utils.staking
import io.novafoundation.nova.core.storage.StorageCache
import io.novafoundation.nova.core_db.dao.AccountStakingDao
import io.novafoundation.nova.core_db.model.AccountStakingLocal
import io.novafoundation.nova.feature_account_api.data.model.AccountIdMap
@@ -49,6 +50,7 @@ import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindin
import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings.bindSlashDeferDuration
import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings.bindSlashingSpans
import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings.bindValidatorPrefs
import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.updaters.ValidatorExposureUpdater
import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.updaters.activeEraStorageKey
import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletConstants
import io.novafoundation.nova.runtime.call.MultiChainRuntimeCallsApi
@@ -86,6 +88,7 @@ class StakingRepositoryImpl(
private val localStorage: StorageDataSource,
private val walletConstants: WalletConstants,
private val chainRegistry: ChainRegistry,
private val storageCache: StorageCache,
private val multiChainRuntimeCallsApi: MultiChainRuntimeCallsApi,
) : StakingRepository {
@@ -124,7 +127,7 @@ class StakingRepositoryImpl(
return runtime.metadata.staking().numberConstant("SessionsPerEra", runtime) // How many sessions per era
}
override suspend fun getActiveEraIndex(chainId: ChainId): EraIndex = remoteStorage.query(chainId) {
override suspend fun getActiveEraIndex(chainId: ChainId): EraIndex = localStorage.query(chainId) {
metadata.staking.activeEra.queryNonNull()
}
@@ -161,7 +164,7 @@ class StakingRepositoryImpl(
}
}
private suspend fun fetchPagedEraStakers(chainId: ChainId, eraIndex: EraIndex): AccountIdMap<Exposure> = remoteStorage.query(chainId) {
private suspend fun fetchPagedEraStakers(chainId: ChainId, eraIndex: EraIndex): AccountIdMap<Exposure> = localStorage.query(chainId) {
val eraStakersOverview = metadata.staking().storage("ErasStakersOverview").entries(
eraIndex,
keyExtractor = { (_: BigInteger, accountId: ByteArray) -> accountId.toHexString() },
@@ -204,7 +207,7 @@ class StakingRepositoryImpl(
}
}
private suspend fun fetchLegacyEraStakers(chainId: ChainId, eraIndex: EraIndex): AccountIdMap<Exposure> = remoteStorage.query(chainId) {
private suspend fun fetchLegacyEraStakers(chainId: ChainId, eraIndex: EraIndex): AccountIdMap<Exposure> = localStorage.query(chainId) {
runtime.metadata.staking().storage("ErasStakers").entries(
eraIndex,
keyExtractor = { (_: BigInteger, accountId: ByteArray) -> accountId.toHexString() },
@@ -346,7 +349,7 @@ class StakingRepositoryImpl(
val runtime = runtimeFor(chainId)
return runtime.metadata.staking().storageOrNull(storageName)?.let { storageEntry ->
remoteStorage.query(
localStorage.query(
keyBuilder = { storageEntry.storageKey() },
binding = { scale, _ -> scale?.let { binder(scale, runtime, storageEntry.returnType()) } },
chainId = chainId
@@ -402,8 +405,9 @@ class StakingRepositoryImpl(
}
private suspend fun isPagedExposuresUsed(chainId: ChainId): Boolean {
val runtime = runtimeFor(chainId)
return runtime.metadata.staking().storageOrNull("ErasStakersOverview") != null
val isPagedExposuresValue = storageCache.getEntry(ValidatorExposureUpdater.STORAGE_KEY_PAGED_EXPOSURES, chainId)
return ValidatorExposureUpdater.decodeIsPagedExposuresValue(isPagedExposuresValue.content)
}
private fun observeAccountValidatorPrefs(chainId: ChainId, stashId: AccountId): Flow<ValidatorPrefs?> {
@@ -11,6 +11,7 @@ import io.novafoundation.nova.common.data.network.rpc.BulkRetriever
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.common.presentation.AssetIconProvider
import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.core.storage.StorageCache
import io.novafoundation.nova.core_db.dao.AccountStakingDao
import io.novafoundation.nova.core_db.dao.ExternalBalanceDao
import io.novafoundation.nova.core_db.dao.StakingRewardPeriodDao
@@ -189,6 +190,7 @@ class StakingFeatureModule {
@Named(REMOTE_STORAGE_SOURCE) remoteStorageSource: StorageDataSource,
walletConstants: WalletConstants,
chainRegistry: ChainRegistry,
storageCache: StorageCache,
multiChainRuntimeCallsApi: MultiChainRuntimeCallsApi
): StakingRepository = StakingRepositoryImpl(
accountStakingDao = accountStakingDao,
@@ -196,6 +198,7 @@ class StakingFeatureModule {
localStorage = localStorageSource,
walletConstants = walletConstants,
chainRegistry = chainRegistry,
storageCache = storageCache,
multiChainRuntimeCallsApi = multiChainRuntimeCallsApi
)
@@ -225,9 +228,8 @@ class StakingFeatureModule {
@FeatureScope
fun provideBagListRepository(
@Named(LOCAL_STORAGE_SOURCE) localStorageSource: StorageDataSource,
@Named(REMOTE_STORAGE_SOURCE) remoteStorageSource: StorageDataSource,
chainRegistry: ChainRegistry
): BagListRepository = LocalBagListRepository(localStorageSource, remoteStorageSource, chainRegistry)
): BagListRepository = LocalBagListRepository(localStorageSource, chainRegistry)
@Provides
@FeatureScope
@@ -26,7 +26,6 @@ import io.novafoundation.nova.feature_staking_impl.data.nominationPools.reposito
import io.novafoundation.nova.feature_staking_impl.data.nominationPools.repository.RealNominationPoolMembersRepository
import io.novafoundation.nova.feature_staking_impl.data.nominationPools.repository.RealNominationPoolStateRepository
import io.novafoundation.nova.feature_staking_impl.data.nominationPools.repository.RealNominationPoolUnbondRepository
import io.novafoundation.nova.feature_staking_api.domain.api.StakingRepository
import io.novafoundation.nova.feature_staking_impl.data.repository.StakingConstantsRepository
import io.novafoundation.nova.feature_staking_impl.data.repository.StakingRewardsRepository
import io.novafoundation.nova.feature_staking_impl.domain.StakingInteractor
@@ -187,12 +186,10 @@ class NominationPoolModule {
fun provideNominationPoolRewardCalculatorFactory(
stakingSharedComputation: StakingSharedComputation,
nominationPoolSharedComputation: NominationPoolSharedComputation,
stakingRepository: StakingRepository,
): NominationPoolRewardCalculatorFactory {
return NominationPoolRewardCalculatorFactory(
sharedStakingSharedComputation = stakingSharedComputation,
nominationPoolSharedComputation = nominationPoolSharedComputation,
stakingRepository = stakingRepository
nominationPoolSharedComputation = nominationPoolSharedComputation
)
}
@@ -26,7 +26,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.transformLatest
@@ -62,12 +61,7 @@ class StakingSharedComputation(
val key = "ACTIVE_ERA:$chainId"
return computationalCache.useSharedFlow(key, scope) {
flow {
val era = stakingRepository.getActiveEraIndex(chainId)
emit(era)
emitAll(stakingRepository.observeActiveEraIndex(chainId))
}
stakingRepository.observeActiveEraIndex(chainId)
}
}
@@ -76,8 +70,7 @@ class StakingSharedComputation(
return computationalCache.useSharedFlow(key, scope) {
activeEraFlow(chainId, scope).map { eraIndex ->
val exposures = stakingRepository.getElectedValidatorsExposure(chainId, eraIndex)
exposures to eraIndex
stakingRepository.getElectedValidatorsExposure(chainId, eraIndex) to eraIndex
}
}
}
@@ -86,14 +79,14 @@ class StakingSharedComputation(
val key = "MIN_STAKE:$chainId"
return computationalCache.useSharedFlow(key, scope) {
electedExposuresWithActiveEraFlow(chainId, scope).map { (exposures, activeEraIndex) ->
val minBond = stakingRepository.minimumNominatorBond(chainId)
val bagListLocator = bagListRepository.bagListLocatorOrNull(chainId)
val totalIssuance = totalIssuanceRepository.getTotalIssuance(chainId)
val bagListScoreConverter = BagListScoreConverter.U128(totalIssuance)
val maxElectingVoters = bagListRepository.maxElectingVotes(chainId)
val bagListSize = bagListRepository.bagListSize(chainId)
val minBond = stakingRepository.minimumNominatorBond(chainId)
val bagListLocator = bagListRepository.bagListLocatorOrNull(chainId)
val totalIssuance = totalIssuanceRepository.getTotalIssuance(chainId)
val bagListScoreConverter = BagListScoreConverter.U128(totalIssuance)
val maxElectingVoters = bagListRepository.maxElectingVotes(chainId)
val bagListSize = bagListRepository.bagListSize(chainId)
electedExposuresWithActiveEraFlow(chainId, scope).map { (exposures, activeEraIndex) ->
val minStake = minimumStake(
exposures = exposures.values,
minimumNominatorBond = minBond,
@@ -11,12 +11,12 @@ import io.novafoundation.nova.common.utils.reversed
import io.novafoundation.nova.feature_account_api.data.model.AccountIdKeyMap
import io.novafoundation.nova.feature_account_api.data.model.AccountIdMap
import io.novafoundation.nova.feature_staking_api.domain.model.Exposure
import io.novafoundation.nova.feature_staking_api.domain.api.StakingRepository
import io.novafoundation.nova.feature_staking_impl.data.StakingOption
import io.novafoundation.nova.feature_staking_impl.data.chain
import io.novafoundation.nova.feature_staking_api.domain.nominationPool.model.PoolId
import io.novafoundation.nova.feature_staking_impl.data.unwrapNominationPools
import io.novafoundation.nova.feature_staking_impl.domain.common.StakingSharedComputation
import io.novafoundation.nova.feature_staking_impl.domain.common.electedExposuresInActiveEra
import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.common.NominationPoolSharedComputation
import io.novafoundation.nova.feature_staking_impl.domain.rewards.RewardCalculator
import kotlinx.coroutines.CoroutineScope
@@ -24,29 +24,21 @@ import kotlinx.coroutines.CoroutineScope
class NominationPoolRewardCalculatorFactory(
private val sharedStakingSharedComputation: StakingSharedComputation,
private val nominationPoolSharedComputation: NominationPoolSharedComputation,
private val stakingRepository: StakingRepository,
) {
suspend fun create(stakingOption: StakingOption, sharedComputationScope: CoroutineScope): NominationPoolRewardCalculator {
val chain = stakingOption.chain
val chainId = chain.id
// For parachains, staking exposures live on the parent relay chain
val exposureChainId = chain.parentId ?: chainId
val chainId = stakingOption.chain.id
val delegateOption = stakingOption.unwrapNominationPools()
val delegate = sharedStakingSharedComputation.rewardCalculator(delegateOption, sharedComputationScope)
val allPoolAccounts = nominationPoolSharedComputation.allBondedPoolAccounts(chainId, sharedComputationScope)
val poolCommissions = nominationPoolSharedComputation.allBondedPools(chainId, sharedComputationScope)
.mapValues { (_, pool) -> pool.commission?.current?.perbill }
val activeEra = stakingRepository.getActiveEraIndex(exposureChainId)
val exposures = stakingRepository.getElectedValidatorsExposure(exposureChainId, activeEra)
return RealNominationPoolRewardCalculator(
directStakingDelegate = delegate,
exposures = exposures,
exposures = sharedStakingSharedComputation.electedExposuresInActiveEra(stakingOption.assetWithChain.chain.id, sharedComputationScope),
commissions = poolCommissions,
poolStashesById = allPoolAccounts
)
@@ -49,13 +49,11 @@ class RealNominationPoolsAlertsInteractor(
return flowOfAll {
val poolId = poolMember.poolId
val poolStash = poolAccountDerivation.bondedAccountOf(poolId, chain.id)
// Staking exposures live on the relay chain, not on parachains like Asset Hub
val exposureChainId = chain.parentId ?: chain.id
combine(
nominationPoolsSharedComputation.participatingPoolNominationsFlow(poolStash, poolId, chain.id, shareComputationScope),
nominationPoolsSharedComputation.unbondingPoolsFlow(poolId, chain.id, shareComputationScope),
stakingSharedComputation.electedExposuresWithActiveEraFlow(exposureChainId, shareComputationScope),
stakingSharedComputation.electedExposuresWithActiveEraFlow(chain.id, shareComputationScope),
) { poolNominations, unbondingPools, (eraStakers, activeEra) ->
val alertsContext = AlertsResolutionContext(
eraStakers = eraStakers,
@@ -47,16 +47,13 @@ class RealNominationPoolStakeSummaryInteractor(
stakingOption: StakingOption,
sharedComputationScope: CoroutineScope,
): Flow<StakeSummary<PoolMemberStatus>> = flowOfAll {
val chain = stakingOption.assetWithChain.chain
val chainId = chain.id
// Staking exposures live on the relay chain, not on parachains like Asset Hub
val exposureChainId = chain.parentId ?: chainId
val chainId = stakingOption.assetWithChain.chain.id
val poolStash = poolAccountDerivation.bondedAccountOf(poolMember.poolId, chainId)
combineTransform(
nominationPoolSharedComputation.participatingBondedPoolStateFlow(poolStash, poolMember.poolId, chainId, sharedComputationScope),
nominationPoolSharedComputation.participatingPoolNominationsFlow(poolStash, poolMember.poolId, chainId, sharedComputationScope),
stakingSharedComputation.electedExposuresWithActiveEraFlow(exposureChainId, sharedComputationScope)
stakingSharedComputation.electedExposuresWithActiveEraFlow(chainId, sharedComputationScope)
) { bondedPoolState, poolNominations, (eraStakers, activeEra) ->
val activeStaked = bondedPoolState.amountOf(poolMember.points)
@@ -52,9 +52,6 @@ class RealNominationPoolsUserRewardsInteractor(
private fun pendingRewardsFlow(accountId: AccountId, chainId: ChainId): Flow<Balance> {
return flowOf { repository.getPendingRewards(accountId, chainId) }
.catch {
Log.e("NominationPoolsUserRewardsInteractor", "Failed to fetch pending rewards", it)
emit(Balance.ZERO)
}
.catch { Log.e("NominationPoolsUserRewardsInteractor", "Failed to fetch pending rewards", it) }
}
}
@@ -13,6 +13,7 @@ import io.novafoundation.nova.feature_staking_impl.data.repository.VaraRepositor
import io.novafoundation.nova.feature_staking_impl.data.stakingType
import io.novafoundation.nova.feature_staking_impl.data.unwrapNominationPools
import io.novafoundation.nova.feature_staking_impl.domain.common.StakingSharedComputation
import io.novafoundation.nova.feature_staking_impl.domain.common.electedExposuresInActiveEra
import io.novafoundation.nova.feature_staking_impl.domain.common.eraTimeCalculator
import io.novafoundation.nova.feature_staking_impl.domain.error.accountIdNotFound
import io.novafoundation.nova.runtime.ext.Geneses
@@ -46,10 +47,7 @@ class RewardCalculatorFactory(
validatorsPrefs: AccountIdMap<ValidatorPrefs?>,
scope: CoroutineScope
): RewardCalculator = withContext(Dispatchers.Default) {
// For parachains (e.g. Asset Hub), staking lives on the parent relay chain.
// TotalIssuance must come from there, not from the parachain.
val stakingChainId = stakingOption.assetWithChain.chain.parentId ?: stakingOption.assetWithChain.chain.id
val totalIssuance = totalIssuanceRepository.getTotalIssuance(stakingChainId)
val totalIssuance = totalIssuanceRepository.getTotalIssuance(stakingOption.assetWithChain.chain.id)
val validators = exposures.keys.mapNotNull { accountIdHex ->
val exposure = exposures[accountIdHex] ?: accountIdNotFound(accountIdHex)
@@ -62,20 +60,14 @@ class RewardCalculatorFactory(
)
}
stakingOption.createRewardCalculator(validators, totalIssuance, stakingChainId, scope)
stakingOption.createRewardCalculator(validators, totalIssuance, scope)
}
suspend fun create(stakingOption: StakingOption, scope: CoroutineScope): RewardCalculator = withContext(Dispatchers.Default) {
val chain = stakingOption.assetWithChain.chain
val chainId = chain.id
// For parachains with a parent relay chain, staking exposures live on the relay chain
val exposureChainId = chain.parentId ?: chainId
val chainId = stakingOption.assetWithChain.chain.id
val activeEra = stakingRepository.getActiveEraIndex(exposureChainId)
val exposures = stakingRepository.getElectedValidatorsExposure(exposureChainId, activeEra)
val validatorsPrefs = stakingRepository.getValidatorPrefs(exposureChainId, exposures.keys)
val exposures = shareStakingSharedComputation.get().electedExposuresInActiveEra(chainId, scope)
val validatorsPrefs = stakingRepository.getValidatorPrefs(chainId, exposures.keys)
create(stakingOption, exposures, validatorsPrefs, scope)
}
@@ -83,7 +75,6 @@ class RewardCalculatorFactory(
private suspend fun StakingOption.createRewardCalculator(
validators: List<RewardCalculationTarget>,
totalIssuance: BigInteger,
stakingChainId: ChainId,
scope: CoroutineScope
): RewardCalculator {
return when (unwrapNominationPools().stakingType) {
@@ -91,9 +82,8 @@ class RewardCalculatorFactory(
val custom = customRelayChainCalculator(validators, totalIssuance, scope)
if (custom != null) return custom
// Query parachains from the relay chain, not from Asset Hub
val activePublicParachains = parasRepository.activePublicParachains(stakingChainId)
val inflationConfig = InflationConfig.create(stakingChainId, activePublicParachains)
val activePublicParachains = parasRepository.activePublicParachains(assetWithChain.chain.id)
val inflationConfig = InflationConfig.create(chain.id, activePublicParachains)
RewardCurveInflationRewardCalculator(validators, totalIssuance, inflationConfig)
}
@@ -26,8 +26,7 @@ class RealStartMultiStakingInteractor(
override suspend fun calculateFee(selection: StartMultiStakingSelection): Fee {
return withContext(Dispatchers.IO) {
val chain = selection.stakingOption.chain
extrinsicService.estimateFee(chain, TransactionOrigin.SelectedWallet) {
extrinsicService.estimateFee(selection.stakingOption.chain, TransactionOrigin.SelectedWallet) {
startStaking(selection)
}
}
@@ -35,8 +34,7 @@ class RealStartMultiStakingInteractor(
override suspend fun startStaking(selection: StartMultiStakingSelection): Result<ExtrinsicExecutionResult> {
return withContext(Dispatchers.IO) {
val chain = selection.stakingOption.chain
extrinsicService.submitExtrinsicAndAwaitExecution(chain, TransactionOrigin.SelectedWallet) {
extrinsicService.submitExtrinsicAndAwaitExecution(selection.stakingOption.chain, TransactionOrigin.SelectedWallet) {
startStaking(selection)
}.requireOk()
}
@@ -98,10 +98,8 @@ private class DirectStakingProperties(
enoughAvailableToStake()
}
private val stakingChainId = stakingOption.chain.parentId ?: stakingOption.chain.id
override suspend fun minStake(): Balance {
return stakingSharedComputation.minStake(stakingChainId, scope)
return stakingSharedComputation.minStake(stakingOption.chain.id, scope)
}
private fun StartMultiStakingValidationSystemBuilder.noConflictingStaking() {
@@ -127,7 +125,7 @@ private class DirectStakingProperties(
private fun StartMultiStakingValidationSystemBuilder.maximumNominatorsReached() {
maximumNominatorsReached(
stakingRepository = stakingRepository,
chainId = { stakingChainId },
chainId = { stakingOption.chain.id },
errorProducer = { StartMultiStakingValidationFailure.MaxNominatorsReached(stakingType) }
)
}
@@ -29,8 +29,7 @@ class DirectStakingRecommendation(
override suspend fun recommendedSelection(stake: Balance): StartMultiStakingSelection {
val provider = recommendationSettingsProvider.await()
val stakingChainId = stakingOption.chain.parentId ?: stakingOption.chain.id
val maximumValidatorsPerNominator = stakingConstantsRepository.maxValidatorsPerNominator(stakingChainId, stake)
val maximumValidatorsPerNominator = stakingConstantsRepository.maxValidatorsPerNominator(stakingOption.chain.id, stake)
val recommendationSettings = provider.recommendedSettings(maximumValidatorsPerNominator)
val recommendator = recommendator.await()
@@ -13,7 +13,6 @@ import io.novafoundation.nova.feature_staking_impl.domain.staking.start.setupAmo
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
import io.novafoundation.nova.feature_wallet_api.data.repository.BalanceLocksRepository
import io.novafoundation.nova.feature_wallet_api.domain.model.Asset
import kotlin.coroutines.cancellation.CancellationException
class AutomaticMultiStakingSelectionType(
private val candidates: List<SingleStakingProperties>,
@@ -58,16 +57,7 @@ class AutomaticMultiStakingSelectionType(
}
private suspend fun typePropertiesFor(stake: Balance): SingleStakingProperties {
for (candidate in candidates) {
try {
val minStake = candidate.minStake()
if (minStake <= stake) return candidate
} catch (e: CancellationException) {
throw e
} catch (_: Exception) {
}
}
return candidates.findWithMinimumStake()
return candidates.firstAllowingToStake(stake) ?: candidates.findWithMinimumStake()
}
private suspend fun List<SingleStakingProperties>.firstAllowingToStake(stake: Balance): SingleStakingProperties? {
@@ -45,22 +45,20 @@ class ValidatorProvider(
): List<Validator> {
val chain = stakingOption.assetWithChain.chain
val chainId = chain.id
// For parachains (e.g. Asset Hub), staking validators live on the parent relay chain
val stakingChainId = chain.parentId ?: chainId
val novaValidatorIds = validatorsPreferencesSource.getRecommendedValidatorIds(chainId)
val electedValidatorExposures = stakingSharedComputation.electedExposuresInActiveEra(stakingChainId, scope)
val electedValidatorExposures = stakingSharedComputation.electedExposuresInActiveEra(chainId, scope)
val requestedValidatorIds = sources.allValidatorIds(chainId, electedValidatorExposures, novaValidatorIds)
// we always need validator prefs for elected validators to construct reward calculator
val validatorIdsToQueryPrefs = electedValidatorExposures.keys + requestedValidatorIds
val validatorPrefs = stakingRepository.getValidatorPrefs(stakingChainId, validatorIdsToQueryPrefs)
val validatorPrefs = stakingRepository.getValidatorPrefs(chainId, validatorIdsToQueryPrefs)
val identities = identityRepository.getIdentitiesFromIdsHex(chainId, requestedValidatorIds)
val slashes = stakingRepository.getSlashes(stakingChainId, requestedValidatorIds)
val slashes = stakingRepository.getSlashes(chain.id, requestedValidatorIds)
val rewardCalculator = rewardCalculatorFactory.create(stakingOption, electedValidatorExposures, validatorPrefs, scope)
val maxNominators = stakingConstantsRepository.maxRewardedNominatorPerValidator(stakingChainId)
val maxNominators = stakingConstantsRepository.maxRewardedNominatorPerValidator(chainId)
return requestedValidatorIds.map { accountIdHex ->
val accountId = AccountIdKey.fromHex(accountIdHex).getOrThrow()