mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-04-22 12:38:06 +00:00
feat: add Pezkuwi Dashboard card with live People Chain data
- Dashboard card on Assets page showing roles, trust score, referral, staking, and perwerde points from People Chain pallets - Repository queries: Tiki, Trust, Referral, StakingScore, Perwerde - CachedStakingDetails double map query (RelayChain + AssetHub sources) - Full i18n support across all 15 locales including new Turkish locale - "Apply & Actions" button opens Telegram bot - Staking improvements for split ecosystem multi-endpoint stats
This commit is contained in:
+2
-1
@@ -38,6 +38,7 @@ suspend fun BagListRepository.bagListLocatorOrThrow(chainId: ChainId): BagListLo
|
||||
|
||||
class LocalBagListRepository(
|
||||
private val localStorage: StorageDataSource,
|
||||
private val remoteStorage: StorageDataSource,
|
||||
private val chainRegistry: ChainRegistry
|
||||
) : BagListRepository {
|
||||
|
||||
@@ -51,7 +52,7 @@ class LocalBagListRepository(
|
||||
|
||||
override suspend fun bagListSize(chainId: ChainId): BigInteger? {
|
||||
return runCatching {
|
||||
localStorage.query(chainId) {
|
||||
remoteStorage.query(chainId) {
|
||||
runtime.metadata.voterListOrNull()?.storage("CounterForListNodes")?.query(binding = ::bindNumber)
|
||||
}
|
||||
}.getOrNull()
|
||||
|
||||
+1
-1
@@ -346,7 +346,7 @@ class StakingRepositoryImpl(
|
||||
val runtime = runtimeFor(chainId)
|
||||
|
||||
return runtime.metadata.staking().storageOrNull(storageName)?.let { storageEntry ->
|
||||
localStorage.query(
|
||||
remoteStorage.query(
|
||||
keyBuilder = { storageEntry.storageKey() },
|
||||
binding = { scale, _ -> scale?.let { binder(scale, runtime, storageEntry.returnType()) } },
|
||||
chainId = chainId
|
||||
|
||||
+2
-1
@@ -225,8 +225,9 @@ class StakingFeatureModule {
|
||||
@FeatureScope
|
||||
fun provideBagListRepository(
|
||||
@Named(LOCAL_STORAGE_SOURCE) localStorageSource: StorageDataSource,
|
||||
@Named(REMOTE_STORAGE_SOURCE) remoteStorageSource: StorageDataSource,
|
||||
chainRegistry: ChainRegistry
|
||||
): BagListRepository = LocalBagListRepository(localStorageSource, chainRegistry)
|
||||
): BagListRepository = LocalBagListRepository(localStorageSource, remoteStorageSource, chainRegistry)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
|
||||
+45
-18
@@ -22,10 +22,12 @@ import io.novafoundation.nova.runtime.multiNetwork.ChainWithAsset
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import io.novafoundation.nova.runtime.repository.TotalIssuanceRepository
|
||||
import android.util.Log
|
||||
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
|
||||
|
||||
@@ -61,7 +63,15 @@ class StakingSharedComputation(
|
||||
val key = "ACTIVE_ERA:$chainId"
|
||||
|
||||
return computationalCache.useSharedFlow(key, scope) {
|
||||
stakingRepository.observeActiveEraIndex(chainId)
|
||||
flow {
|
||||
Log.d("PEZ_STAKE", "activeEraFlow: fetching remote activeEra for chainId=$chainId")
|
||||
val era = stakingRepository.getActiveEraIndex(chainId)
|
||||
Log.d("PEZ_STAKE", "activeEraFlow: got remote activeEra=$era")
|
||||
emit(era)
|
||||
|
||||
Log.d("PEZ_STAKE", "activeEraFlow: starting local observation for chainId=$chainId")
|
||||
emitAll(stakingRepository.observeActiveEraIndex(chainId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +80,15 @@ class StakingSharedComputation(
|
||||
|
||||
return computationalCache.useSharedFlow(key, scope) {
|
||||
activeEraFlow(chainId, scope).map { eraIndex ->
|
||||
stakingRepository.getElectedValidatorsExposure(chainId, eraIndex) to eraIndex
|
||||
Log.d("PEZ_STAKE", "electedExposures: fetching validators for chainId=$chainId, era=$eraIndex")
|
||||
try {
|
||||
val exposures = stakingRepository.getElectedValidatorsExposure(chainId, eraIndex)
|
||||
Log.d("PEZ_STAKE", "electedExposures: got ${exposures.size} validators for chainId=$chainId")
|
||||
exposures to eraIndex
|
||||
} catch (e: Exception) {
|
||||
Log.e("PEZ_STAKE", "electedExposures: FAILED for chainId=$chainId, era=$eraIndex", e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,24 +97,33 @@ class StakingSharedComputation(
|
||||
val key = "MIN_STAKE:$chainId"
|
||||
|
||||
return computationalCache.useSharedFlow(key, scope) {
|
||||
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,
|
||||
bagListLocator = bagListLocator,
|
||||
bagListScoreConverter = bagListScoreConverter,
|
||||
bagListSize = bagListSize,
|
||||
maxElectingVoters = maxElectingVoters
|
||||
)
|
||||
Log.d("PEZ_STAKE", "activeEraInfo: calculating minStake for chainId=$chainId, era=$activeEraIndex, validators=${exposures.size}")
|
||||
try {
|
||||
val minBond = stakingRepository.minimumNominatorBond(chainId)
|
||||
Log.d("PEZ_STAKE", "activeEraInfo: minBond=$minBond")
|
||||
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)
|
||||
Log.d("PEZ_STAKE", "activeEraInfo: bagListSize=$bagListSize, maxElectingVoters=$maxElectingVoters")
|
||||
|
||||
ActiveEraInfo(activeEraIndex, exposures, minStake)
|
||||
val minStake = minimumStake(
|
||||
exposures = exposures.values,
|
||||
minimumNominatorBond = minBond,
|
||||
bagListLocator = bagListLocator,
|
||||
bagListScoreConverter = bagListScoreConverter,
|
||||
bagListSize = bagListSize,
|
||||
maxElectingVoters = maxElectingVoters
|
||||
)
|
||||
Log.d("PEZ_STAKE", "activeEraInfo: minStake=$minStake")
|
||||
|
||||
ActiveEraInfo(activeEraIndex, exposures, minStake)
|
||||
} catch (e: Exception) {
|
||||
Log.e("PEZ_STAKE", "activeEraInfo: FAILED for chainId=$chainId", e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-4
@@ -33,20 +33,16 @@ class NominationPoolRewardCalculatorFactory(
|
||||
// For parachains, staking exposures live on the parent relay chain
|
||||
val exposureChainId = chain.parentId ?: chainId
|
||||
|
||||
android.util.Log.d("PEZ_STAKING", "NomPoolRewardCalcFactory.create() chainId=${chainId.take(12)} exposureChainId=${exposureChainId.take(12)}")
|
||||
|
||||
val delegateOption = stakingOption.unwrapNominationPools()
|
||||
|
||||
val delegate = sharedStakingSharedComputation.rewardCalculator(delegateOption, sharedComputationScope)
|
||||
val allPoolAccounts = nominationPoolSharedComputation.allBondedPoolAccounts(chainId, sharedComputationScope)
|
||||
android.util.Log.d("PEZ_STAKING", "Pool accounts: ${allPoolAccounts.size}")
|
||||
|
||||
val poolCommissions = nominationPoolSharedComputation.allBondedPools(chainId, sharedComputationScope)
|
||||
.mapValues { (_, pool) -> pool.commission?.current?.perbill }
|
||||
|
||||
val activeEra = stakingRepository.getActiveEraIndex(exposureChainId)
|
||||
val exposures = stakingRepository.getElectedValidatorsExposure(exposureChainId, activeEra)
|
||||
android.util.Log.d("PEZ_STAKING", "NomPool exposures: ${exposures.size} (era=$activeEra)")
|
||||
|
||||
return RealNominationPoolRewardCalculator(
|
||||
directStakingDelegate = delegate,
|
||||
|
||||
+4
-2
@@ -26,7 +26,8 @@ class RealStartMultiStakingInteractor(
|
||||
|
||||
override suspend fun calculateFee(selection: StartMultiStakingSelection): Fee {
|
||||
return withContext(Dispatchers.IO) {
|
||||
extrinsicService.estimateFee(selection.stakingOption.chain, TransactionOrigin.SelectedWallet) {
|
||||
val chain = selection.stakingOption.chain
|
||||
extrinsicService.estimateFee(chain, TransactionOrigin.SelectedWallet) {
|
||||
startStaking(selection)
|
||||
}
|
||||
}
|
||||
@@ -34,7 +35,8 @@ class RealStartMultiStakingInteractor(
|
||||
|
||||
override suspend fun startStaking(selection: StartMultiStakingSelection): Result<ExtrinsicExecutionResult> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
extrinsicService.submitExtrinsicAndAwaitExecution(selection.stakingOption.chain, TransactionOrigin.SelectedWallet) {
|
||||
val chain = selection.stakingOption.chain
|
||||
extrinsicService.submitExtrinsicAndAwaitExecution(chain, TransactionOrigin.SelectedWallet) {
|
||||
startStaking(selection)
|
||||
}.requireOk()
|
||||
}
|
||||
|
||||
+5
-1
@@ -29,6 +29,7 @@ import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Ba
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.model.Asset
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.validation.sufficientBalance
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
class DirectStakingPropertiesFactory(
|
||||
@@ -101,7 +102,10 @@ private class DirectStakingProperties(
|
||||
private val stakingChainId = stakingOption.chain.parentId ?: stakingOption.chain.id
|
||||
|
||||
override suspend fun minStake(): Balance {
|
||||
return stakingSharedComputation.minStake(stakingChainId, scope)
|
||||
Log.d("PEZ_STAKE", "DirectStaking.minStake() called, stakingChainId=$stakingChainId")
|
||||
val result = stakingSharedComputation.minStake(stakingChainId, scope)
|
||||
Log.d("PEZ_STAKE", "DirectStaking.minStake() returned: $result")
|
||||
return result
|
||||
}
|
||||
|
||||
private fun StartMultiStakingValidationSystemBuilder.noConflictingStaking() {
|
||||
|
||||
+24
-2
@@ -8,6 +8,7 @@ import io.novafoundation.nova.feature_staking_impl.domain.recommendations.settin
|
||||
import io.novafoundation.nova.feature_staking_impl.domain.staking.start.common.selection.StartMultiStakingSelection
|
||||
import io.novafoundation.nova.feature_staking_impl.domain.staking.start.setupAmount.SingleStakingRecommendation
|
||||
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.async
|
||||
|
||||
@@ -20,21 +21,42 @@ class DirectStakingRecommendation(
|
||||
) : SingleStakingRecommendation {
|
||||
|
||||
private val recommendator = scope.async {
|
||||
validatorRecommenderFactory.create(scope)
|
||||
Log.d("PEZ_STAKE", "DirectRecommendation: creating validator recommender...")
|
||||
try {
|
||||
val result = validatorRecommenderFactory.create(scope)
|
||||
Log.d("PEZ_STAKE", "DirectRecommendation: validator recommender created")
|
||||
result
|
||||
} catch (e: Exception) {
|
||||
Log.e("PEZ_STAKE", "DirectRecommendation: validator recommender FAILED", e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
private val recommendationSettingsProvider = scope.async {
|
||||
recommendationSettingsProviderFactory.create(scope)
|
||||
Log.d("PEZ_STAKE", "DirectRecommendation: creating settings provider...")
|
||||
try {
|
||||
val result = recommendationSettingsProviderFactory.create(scope)
|
||||
Log.d("PEZ_STAKE", "DirectRecommendation: settings provider created")
|
||||
result
|
||||
} catch (e: Exception) {
|
||||
Log.e("PEZ_STAKE", "DirectRecommendation: settings provider FAILED", e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun recommendedSelection(stake: Balance): StartMultiStakingSelection {
|
||||
Log.d("PEZ_STAKE", "DirectRecommendation: awaiting settings provider...")
|
||||
val provider = recommendationSettingsProvider.await()
|
||||
Log.d("PEZ_STAKE", "DirectRecommendation: got settings provider")
|
||||
val stakingChainId = stakingOption.chain.parentId ?: stakingOption.chain.id
|
||||
val maximumValidatorsPerNominator = stakingConstantsRepository.maxValidatorsPerNominator(stakingChainId, stake)
|
||||
val recommendationSettings = provider.recommendedSettings(maximumValidatorsPerNominator)
|
||||
Log.d("PEZ_STAKE", "DirectRecommendation: awaiting recommender...")
|
||||
val recommendator = recommendator.await()
|
||||
Log.d("PEZ_STAKE", "DirectRecommendation: got recommender, getting recommendations...")
|
||||
|
||||
val recommendedValidators = recommendator.recommendations(recommendationSettings)
|
||||
Log.d("PEZ_STAKE", "DirectRecommendation: got ${recommendedValidators.size} recommended validators")
|
||||
|
||||
return DirectStakingSelection(
|
||||
validators = recommendedValidators,
|
||||
|
||||
+16
-2
@@ -5,6 +5,7 @@ import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.pools.
|
||||
import io.novafoundation.nova.feature_staking_impl.domain.staking.start.common.selection.StartMultiStakingSelection
|
||||
import io.novafoundation.nova.feature_staking_impl.domain.staking.start.setupAmount.SingleStakingRecommendation
|
||||
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.async
|
||||
|
||||
@@ -15,11 +16,24 @@ class NominationPoolRecommendation(
|
||||
) : SingleStakingRecommendation {
|
||||
|
||||
private val recommendator = scope.async {
|
||||
nominationPoolRecommenderFactory.create(stakingOption, scope)
|
||||
Log.d("PEZ_STAKE", "NomPoolRecommendation: creating recommender...")
|
||||
try {
|
||||
val result = nominationPoolRecommenderFactory.create(stakingOption, scope)
|
||||
Log.d("PEZ_STAKE", "NomPoolRecommendation: recommender created successfully")
|
||||
result
|
||||
} catch (e: Exception) {
|
||||
Log.e("PEZ_STAKE", "NomPoolRecommendation: recommender creation FAILED", e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun recommendedSelection(stake: Balance): StartMultiStakingSelection? {
|
||||
val recommendedPool = recommendator.await().recommendedPool() ?: return null
|
||||
Log.d("PEZ_STAKE", "NomPoolRecommendation: awaiting recommender...")
|
||||
val recommendedPool = recommendator.await().recommendedPool() ?: run {
|
||||
Log.d("PEZ_STAKE", "NomPoolRecommendation: no recommended pool found")
|
||||
return null
|
||||
}
|
||||
Log.d("PEZ_STAKE", "NomPoolRecommendation: recommended pool=${recommendedPool.id}")
|
||||
|
||||
return NominationPoolSelection(recommendedPool, stakingOption, stake)
|
||||
}
|
||||
|
||||
+26
-2
@@ -13,6 +13,8 @@ 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 android.util.Log
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
|
||||
class AutomaticMultiStakingSelectionType(
|
||||
private val candidates: List<SingleStakingProperties>,
|
||||
@@ -44,8 +46,14 @@ class AutomaticMultiStakingSelectionType(
|
||||
}
|
||||
|
||||
override suspend fun updateSelectionFor(stake: Balance) {
|
||||
Log.d("PEZ_STAKE", "updateSelectionFor: stake=$stake")
|
||||
val stakingProperties = typePropertiesFor(stake)
|
||||
val candidates = stakingProperties.recommendation.recommendedSelection(stake) ?: return
|
||||
Log.d("PEZ_STAKE", "updateSelectionFor: got properties type=${stakingProperties.stakingType}")
|
||||
val candidates = stakingProperties.recommendation.recommendedSelection(stake) ?: run {
|
||||
Log.d("PEZ_STAKE", "updateSelectionFor: recommendedSelection returned null, returning")
|
||||
return
|
||||
}
|
||||
Log.d("PEZ_STAKE", "updateSelectionFor: got recommended selection")
|
||||
|
||||
val recommendableSelection = RecommendableMultiStakingSelection(
|
||||
source = SelectionTypeSource.Automatic,
|
||||
@@ -54,10 +62,26 @@ class AutomaticMultiStakingSelectionType(
|
||||
)
|
||||
|
||||
selectionStore.updateSelection(recommendableSelection)
|
||||
Log.d("PEZ_STAKE", "updateSelectionFor: selection updated successfully")
|
||||
}
|
||||
|
||||
private suspend fun typePropertiesFor(stake: Balance): SingleStakingProperties {
|
||||
return candidates.firstAllowingToStake(stake) ?: candidates.findWithMinimumStake()
|
||||
Log.d("PEZ_STAKE", "typePropertiesFor: trying ${candidates.size} candidates")
|
||||
for ((index, candidate) in candidates.withIndex()) {
|
||||
Log.d("PEZ_STAKE", "typePropertiesFor: checking candidate $index type=${candidate.stakingType}")
|
||||
try {
|
||||
val minStake = candidate.minStake()
|
||||
Log.d("PEZ_STAKE", "typePropertiesFor: candidate $index minStake=$minStake, stake=$stake, allows=${minStake <= stake}")
|
||||
if (minStake <= stake) return candidate
|
||||
} catch (e: CancellationException) {
|
||||
Log.d("PEZ_STAKE", "typePropertiesFor: candidate $index cancelled, rethrowing")
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
Log.e("PEZ_STAKE", "typePropertiesFor: candidate $index minStake() threw", e)
|
||||
}
|
||||
}
|
||||
Log.d("PEZ_STAKE", "typePropertiesFor: no candidate allows, finding minimum")
|
||||
return candidates.findWithMinimumStake()
|
||||
}
|
||||
|
||||
private suspend fun List<SingleStakingProperties>.firstAllowingToStake(stake: Balance): SingleStakingProperties? {
|
||||
|
||||
Reference in New Issue
Block a user