mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-04-23 07:17:57 +00:00
Initial commit: Pezkuwi Wallet Android
Complete rebrand of Nova Wallet for Pezkuwichain ecosystem. ## Features - Full Pezkuwichain support (HEZ & PEZ tokens) - Polkadot ecosystem compatibility - Staking, Governance, DeFi, NFTs - XCM cross-chain transfers - Hardware wallet support (Ledger, Polkadot Vault) - WalletConnect v2 - Push notifications ## Languages - English, Turkish, Kurmanci (Kurdish), Spanish, French, German, Russian, Japanese, Chinese, Korean, Portuguese, Vietnamese Based on Nova Wallet by Novasama Technologies GmbH © Dijital Kurdistan Tech Institute 2026
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest>
|
||||
|
||||
</manifest>
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package io.novafoundation.nova.feature_staking_api.data.dashboard
|
||||
|
||||
import io.novafoundation.nova.feature_staking_api.domain.dashboard.model.AggregatedStakingDashboardOption.SyncingStage
|
||||
import io.novafoundation.nova.feature_staking_api.domain.dashboard.model.StakingOptionId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface StakingDashboardSyncTracker {
|
||||
|
||||
val syncedItemsFlow: Flow<SyncingStageMap>
|
||||
}
|
||||
|
||||
typealias SyncingStageMap = Map<StakingOptionId, SyncingStage>
|
||||
|
||||
fun SyncingStageMap.getSyncingStage(stakingOptionId: StakingOptionId): SyncingStage {
|
||||
return get(stakingOptionId) ?: SyncingStage.SYNCING_ALL
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package io.novafoundation.nova.feature_staking_api.data.dashboard
|
||||
|
||||
import io.novafoundation.nova.core.updater.UpdateSystem
|
||||
|
||||
interface StakingDashboardUpdateSystem : UpdateSystem, StakingDashboardSyncTracker
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package io.novafoundation.nova.feature_staking_api.data.dashboard.common
|
||||
|
||||
import io.novafoundation.nova.runtime.ext.isEnabled
|
||||
import io.novafoundation.nova.runtime.ext.supportedStakingOptions
|
||||
import io.novafoundation.nova.runtime.ext.utilityAsset
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainsById
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.findChains
|
||||
import io.novafoundation.nova.runtime.multiNetwork.findChainsById
|
||||
|
||||
suspend fun ChainRegistry.stakingChains(): List<Chain> {
|
||||
return findChains { it.isEnabled && it.supportedStakingOptions() }
|
||||
}
|
||||
|
||||
suspend fun ChainRegistry.stakingChainsById(): ChainsById {
|
||||
return findChainsById { it.isEnabled && it.supportedStakingOptions() }
|
||||
}
|
||||
|
||||
fun Chain.supportedStakingOptions(): Boolean {
|
||||
return utilityAsset.supportedStakingOptions().isNotEmpty()
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package io.novafoundation.nova.feature_staking_api.data.mythos
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.domain.account.system.SystemAccountMatcher
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
|
||||
interface MythosMainPotMatcherFactory {
|
||||
|
||||
suspend fun create(chainAsset: Chain.Asset): SystemAccountMatcher?
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package io.novafoundation.nova.feature_staking_api.data.network.blockhain.updaters
|
||||
|
||||
import io.novafoundation.nova.core.updater.Updater
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
|
||||
interface PooledBalanceUpdaterFactory {
|
||||
|
||||
fun create(chain: Chain): Updater<MetaAccount>
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package io.novafoundation.nova.feature_staking_api.data.nominationPools.pool
|
||||
|
||||
import io.novafoundation.nova.common.address.AccountIdKey
|
||||
import io.novafoundation.nova.feature_account_api.domain.account.system.SystemAccountMatcher
|
||||
import io.novafoundation.nova.feature_staking_api.domain.nominationPool.model.PoolId
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
|
||||
interface PoolAccountDerivation {
|
||||
|
||||
enum class PoolAccountType {
|
||||
|
||||
BONDED, REWARD
|
||||
}
|
||||
|
||||
suspend fun derivePoolAccount(poolId: PoolId, derivationType: PoolAccountType, chainId: ChainId): AccountId
|
||||
|
||||
/**
|
||||
* Derives pool accounts with poolId from range 1..[numberOfPools] (end-inclusive)
|
||||
*/
|
||||
suspend fun derivePoolAccountsRange(numberOfPools: Int, derivationType: PoolAccountType, chainId: ChainId): Map<PoolId, AccountIdKey>
|
||||
|
||||
suspend fun poolAccountMatcher(derivationType: PoolAccountType, chainId: ChainId): SystemAccountMatcher?
|
||||
}
|
||||
|
||||
suspend fun PoolAccountDerivation.poolRewardAccountMatcher(chainId: ChainId): SystemAccountMatcher? {
|
||||
return poolAccountMatcher(PoolAccountDerivation.PoolAccountType.REWARD, chainId)
|
||||
}
|
||||
|
||||
suspend fun PoolAccountDerivation.bondedAccountOf(poolId: PoolId, chainId: ChainId): AccountId {
|
||||
return derivePoolAccount(poolId, PoolAccountDerivation.PoolAccountType.BONDED, chainId)
|
||||
}
|
||||
|
||||
suspend fun PoolAccountDerivation.deriveAllBondedPools(lastPoolId: PoolId, chainId: ChainId): Map<PoolId, AccountIdKey> {
|
||||
return derivePoolAccountsRange(numberOfPools = lastPoolId.value.toInt(), PoolAccountDerivation.PoolAccountType.BONDED, chainId)
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package io.novafoundation.nova.feature_staking_api.data.parachainStaking.turing.repository
|
||||
|
||||
import java.math.BigInteger
|
||||
|
||||
class OptimalAutomationRequest(
|
||||
val collator: String,
|
||||
val amount: BigInteger,
|
||||
)
|
||||
|
||||
data class OptimalAutomationResponse(
|
||||
val apy: Double,
|
||||
val period: Int,
|
||||
)
|
||||
|
||||
enum class AutomationAction(val rpcParamName: String) {
|
||||
NOTIFY("Notify"),
|
||||
NATIVE_TRANSFER("NativeTransfer"),
|
||||
XCMP("XCMP"),
|
||||
AUTO_COMPOUND_DELEGATED_STAKE("AutoCompoundDelegatedStake"),
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package io.novafoundation.nova.feature_staking_api.data.parachainStaking.turing.repository
|
||||
|
||||
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
|
||||
import java.math.BigInteger
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
|
||||
class TuringAutomationTask(
|
||||
val id: String,
|
||||
val delegator: AccountId,
|
||||
val collator: AccountId,
|
||||
val accountMinimum: Balance,
|
||||
val schedule: Schedule
|
||||
) {
|
||||
|
||||
sealed interface Schedule {
|
||||
object Unknown : Schedule
|
||||
|
||||
class Recurring(val frequency: BigInteger) : Schedule
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package io.novafoundation.nova.feature_staking_api.data.parachainStaking.turing.repository
|
||||
|
||||
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface TuringAutomationTasksRepository {
|
||||
|
||||
fun automationTasksFlow(chainId: ChainId, accountId: AccountId): Flow<List<TuringAutomationTask>>
|
||||
|
||||
suspend fun calculateOptimalAutomation(chainId: ChainId, request: OptimalAutomationRequest): OptimalAutomationResponse
|
||||
|
||||
suspend fun getTimeAutomationFees(chainId: ChainId, action: AutomationAction, executions: Int): Balance
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package io.novafoundation.nova.feature_staking_api.di
|
||||
|
||||
import javax.inject.Qualifier
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class Staking
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package io.novafoundation.nova.feature_staking_api.di
|
||||
|
||||
import io.novafoundation.nova.feature_staking_api.data.dashboard.StakingDashboardUpdateSystem
|
||||
import io.novafoundation.nova.feature_staking_api.data.mythos.MythosMainPotMatcherFactory
|
||||
import io.novafoundation.nova.feature_staking_api.data.network.blockhain.updaters.PooledBalanceUpdaterFactory
|
||||
import io.novafoundation.nova.feature_staking_api.data.nominationPools.pool.PoolAccountDerivation
|
||||
import io.novafoundation.nova.feature_staking_api.data.parachainStaking.turing.repository.TuringAutomationTasksRepository
|
||||
import io.novafoundation.nova.feature_staking_api.di.deeplinks.StakingDeepLinks
|
||||
import io.novafoundation.nova.feature_staking_api.domain.api.StakingRepository
|
||||
import io.novafoundation.nova.feature_staking_api.domain.dashboard.StakingDashboardInteractor
|
||||
import io.novafoundation.nova.feature_staking_api.presentation.nominationPools.display.PoolDisplayUseCase
|
||||
|
||||
interface StakingFeatureApi {
|
||||
|
||||
fun repository(): StakingRepository
|
||||
|
||||
val turingAutomationRepository: TuringAutomationTasksRepository
|
||||
|
||||
val dashboardInteractor: StakingDashboardInteractor
|
||||
|
||||
val dashboardUpdateSystem: StakingDashboardUpdateSystem
|
||||
|
||||
val pooledBalanceUpdaterFactory: PooledBalanceUpdaterFactory
|
||||
|
||||
val poolDisplayUseCase: PoolDisplayUseCase
|
||||
|
||||
val poolAccountDerivation: PoolAccountDerivation
|
||||
|
||||
val mythosMainPotMatcherFactory: MythosMainPotMatcherFactory
|
||||
|
||||
val stakingDeepLinks: StakingDeepLinks
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package io.novafoundation.nova.feature_staking_api.di.deeplinks
|
||||
|
||||
import io.novafoundation.nova.feature_deep_linking.presentation.handling.DeepLinkHandler
|
||||
|
||||
class StakingDeepLinks(val deepLinkHandlers: List<DeepLinkHandler>)
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package io.novafoundation.nova.feature_staking_api.domain.api
|
||||
|
||||
import io.novafoundation.nova.common.address.AccountIdKey
|
||||
import io.novafoundation.nova.feature_account_api.data.model.AccountIdMap
|
||||
import io.novafoundation.nova.feature_staking_api.domain.model.EraIndex
|
||||
import io.novafoundation.nova.feature_staking_api.domain.model.Exposure
|
||||
import io.novafoundation.nova.feature_staking_api.domain.model.InflationPredictionInfo
|
||||
import io.novafoundation.nova.feature_staking_api.domain.model.RewardDestination
|
||||
import io.novafoundation.nova.feature_staking_api.domain.model.SlashingSpans
|
||||
import io.novafoundation.nova.feature_staking_api.domain.model.StakingLedger
|
||||
import io.novafoundation.nova.feature_staking_api.domain.model.ValidatorPrefs
|
||||
import io.novafoundation.nova.feature_staking_api.domain.model.relaychain.StakingState
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import java.math.BigInteger
|
||||
|
||||
typealias ExposuresWithEraIndex = Pair<AccountIdMap<Exposure>, EraIndex>
|
||||
|
||||
interface StakingRepository {
|
||||
|
||||
suspend fun eraStartSessionIndex(chainId: ChainId, era: EraIndex): EraIndex
|
||||
|
||||
suspend fun eraLength(chain: Chain): BigInteger
|
||||
|
||||
suspend fun getActiveEraIndex(chainId: ChainId): EraIndex
|
||||
|
||||
suspend fun getCurrentEraIndex(chainId: ChainId): EraIndex
|
||||
|
||||
suspend fun getHistoryDepth(chainId: ChainId): BigInteger
|
||||
|
||||
fun observeActiveEraIndex(chainId: ChainId): Flow<EraIndex>
|
||||
|
||||
suspend fun getElectedValidatorsExposure(chainId: ChainId, eraIndex: EraIndex): AccountIdMap<Exposure>
|
||||
|
||||
suspend fun getValidatorPrefs(chainId: ChainId, accountIdsHex: Collection<String>): AccountIdMap<ValidatorPrefs?>
|
||||
|
||||
suspend fun getSlashes(chainId: ChainId, accountIdsHex: Collection<String>): Set<AccountIdKey>
|
||||
|
||||
suspend fun getSlashingSpan(chainId: ChainId, accountId: AccountId): SlashingSpans?
|
||||
|
||||
fun stakingStateFlow(
|
||||
chain: Chain,
|
||||
chainAsset: Chain.Asset,
|
||||
accountId: AccountId
|
||||
): Flow<StakingState>
|
||||
|
||||
fun ledgerFlow(stakingState: StakingState.Stash): Flow<StakingLedger>
|
||||
|
||||
suspend fun ledger(chainId: ChainId, accountId: AccountId): StakingLedger?
|
||||
|
||||
suspend fun getRewardDestination(stakingState: StakingState.Stash): RewardDestination
|
||||
|
||||
suspend fun minimumNominatorBond(chainId: ChainId): BigInteger
|
||||
|
||||
suspend fun maxNominators(chainId: ChainId): BigInteger?
|
||||
|
||||
suspend fun nominatorsCount(chainId: ChainId): BigInteger?
|
||||
|
||||
suspend fun getInflationPredictionInfo(chainId: ChainId): InflationPredictionInfo
|
||||
}
|
||||
|
||||
suspend fun StakingRepository.historicalEras(chainId: ChainId): List<BigInteger> {
|
||||
val activeEra = getActiveEraIndex(chainId).toInt()
|
||||
val currentEra = getCurrentEraIndex(chainId).toInt()
|
||||
val historyDepth = getHistoryDepth(chainId).toInt()
|
||||
|
||||
val startingIndex = (currentEra - historyDepth).coerceAtLeast(0)
|
||||
val historicalRange = startingIndex until activeEra
|
||||
|
||||
return historicalRange.map(Int::toBigInteger)
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package io.novafoundation.nova.feature_staking_api.domain.dashboard
|
||||
|
||||
import io.novafoundation.nova.common.domain.ExtendedLoadingState
|
||||
import io.novafoundation.nova.feature_staking_api.domain.dashboard.model.MoreStakingOptions
|
||||
import io.novafoundation.nova.feature_staking_api.domain.dashboard.model.StakingDashboard
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface StakingDashboardInteractor {
|
||||
|
||||
suspend fun syncDapps()
|
||||
|
||||
fun stakingDashboardFlow(): Flow<ExtendedLoadingState<StakingDashboard>>
|
||||
|
||||
fun moreStakingOptionsFlow(): Flow<MoreStakingOptions>
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package io.novafoundation.nova.feature_staking_api.domain.dashboard.model
|
||||
|
||||
import io.novafoundation.nova.common.domain.ExtendedLoadingState
|
||||
import io.novafoundation.nova.common.utils.Percent
|
||||
import io.novafoundation.nova.feature_staking_api.domain.dashboard.model.AggregatedStakingDashboardOption.NoStake.FlowType
|
||||
import io.novafoundation.nova.feature_staking_api.domain.dashboard.model.AggregatedStakingDashboardOption.SyncingStage
|
||||
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.model.Token
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
|
||||
class AggregatedStakingDashboardOption<out S>(
|
||||
val chain: Chain,
|
||||
val token: Token,
|
||||
val stakingState: S,
|
||||
val syncingStage: SyncingStage
|
||||
) {
|
||||
|
||||
class HasStake(
|
||||
val showStakingType: Boolean,
|
||||
val stakingType: Chain.Asset.StakingType,
|
||||
val stake: Balance,
|
||||
val stats: ExtendedLoadingState<Stats>,
|
||||
) {
|
||||
|
||||
class Stats(val rewards: Balance, val estimatedEarnings: Percent, val status: StakingStatus)
|
||||
|
||||
enum class StakingStatus {
|
||||
ACTIVE, INACTIVE, WAITING
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface WithoutStake
|
||||
|
||||
class NoStake(val stats: ExtendedLoadingState<Stats>, val flowType: FlowType, val availableBalance: Balance) : WithoutStake {
|
||||
|
||||
sealed class FlowType {
|
||||
|
||||
class Aggregated(val stakingTypes: List<Chain.Asset.StakingType>) : FlowType()
|
||||
|
||||
class Single(val stakingType: Chain.Asset.StakingType, val showStakingType: Boolean) : FlowType()
|
||||
}
|
||||
|
||||
class Stats(val estimatedEarnings: Percent)
|
||||
}
|
||||
|
||||
object NotYetResolved : WithoutStake
|
||||
|
||||
enum class SyncingStage {
|
||||
SYNCING_ALL, SYNCING_SECONDARY, SYNCED
|
||||
}
|
||||
}
|
||||
|
||||
val FlowType.allStakingTypes: List<Chain.Asset.StakingType>
|
||||
get() = when (this) {
|
||||
is FlowType.Aggregated -> stakingTypes
|
||||
is FlowType.Single -> listOf(stakingType)
|
||||
}
|
||||
|
||||
fun SyncingStage.isSyncing(): Boolean {
|
||||
return this != SyncingStage.SYNCED
|
||||
}
|
||||
|
||||
fun SyncingStage.isSyncingPrimary(): Boolean {
|
||||
return this == SyncingStage.SYNCING_ALL
|
||||
}
|
||||
|
||||
fun SyncingStage.isSyncingSecondary(): Boolean {
|
||||
return this < SyncingStage.SYNCED
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package io.novafoundation.nova.feature_staking_api.domain.dashboard.model
|
||||
|
||||
import io.novafoundation.nova.common.domain.ExtendedLoadingState
|
||||
import io.novafoundation.nova.feature_staking_api.domain.dashboard.model.AggregatedStakingDashboardOption.HasStake
|
||||
import io.novafoundation.nova.feature_staking_api.domain.dashboard.model.AggregatedStakingDashboardOption.WithoutStake
|
||||
|
||||
class StakingDashboard(
|
||||
val hasStake: List<AggregatedStakingDashboardOption<HasStake>>,
|
||||
val withoutStake: List<AggregatedStakingDashboardOption<WithoutStake>>,
|
||||
)
|
||||
|
||||
class MoreStakingOptions(
|
||||
val inAppStaking: List<AggregatedStakingDashboardOption<WithoutStake>>,
|
||||
val browserStaking: ExtendedLoadingState<List<StakingDApp>>,
|
||||
)
|
||||
|
||||
class StakingDApp(
|
||||
val url: String,
|
||||
val iconUrl: String,
|
||||
val name: String
|
||||
)
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package io.novafoundation.nova.feature_staking_api.domain.dashboard.model
|
||||
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainAssetId
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
|
||||
data class StakingOptionId(val chainId: ChainId, val chainAssetId: ChainAssetId, val stakingType: Chain.Asset.StakingType)
|
||||
|
||||
data class MultiStakingOptionIds(val chainId: ChainId, val chainAssetId: ChainAssetId, val stakingTypes: List<Chain.Asset.StakingType>)
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package io.novafoundation.nova.feature_staking_api.domain.model
|
||||
|
||||
@JvmInline
|
||||
value class BondedEras(val value: List<BondedEra>) {
|
||||
|
||||
companion object
|
||||
}
|
||||
|
||||
class BondedEra(val era: EraIndex, val startSessionIndex: SessionIndex) {
|
||||
|
||||
companion object
|
||||
}
|
||||
|
||||
fun BondedEras.findStartSessionIndexOf(era: EraIndex): SessionIndex? {
|
||||
return value.find { it.era == era }?.startSessionIndex
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package io.novafoundation.nova.feature_staking_api.domain.model
|
||||
|
||||
import java.math.BigInteger
|
||||
|
||||
typealias EraIndex = BigInteger
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package io.novafoundation.nova.feature_staking_api.domain.model
|
||||
|
||||
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
|
||||
import java.math.BigInteger
|
||||
|
||||
interface EraRedeemable {
|
||||
|
||||
val redeemEra: EraIndex
|
||||
|
||||
companion object
|
||||
}
|
||||
|
||||
interface RedeemableAmount : EraRedeemable {
|
||||
|
||||
val amount: Balance
|
||||
}
|
||||
|
||||
fun EraRedeemable.isUnbondingIn(activeEraIndex: BigInteger) = redeemEra > activeEraIndex
|
||||
|
||||
fun EraRedeemable.isRedeemableIn(activeEraIndex: BigInteger) = redeemEra <= activeEraIndex
|
||||
|
||||
fun EraRedeemable.Companion.of(eraIndex: EraIndex): EraRedeemable = InlineEraRedeemable(eraIndex)
|
||||
|
||||
@JvmInline
|
||||
private value class InlineEraRedeemable(override val redeemEra: EraIndex) : EraRedeemable
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package io.novafoundation.nova.feature_staking_api.domain.model
|
||||
|
||||
import java.math.BigInteger
|
||||
|
||||
class IndividualExposure(val who: ByteArray, val value: BigInteger)
|
||||
|
||||
class Exposure(val total: BigInteger, val own: BigInteger, val others: List<IndividualExposure>)
|
||||
|
||||
class ExposureOverview(val total: BigInteger, val own: BigInteger, val pageCount: BigInteger, val nominatorCount: BigInteger)
|
||||
|
||||
class ExposurePage(val others: List<IndividualExposure>)
|
||||
|
||||
class PagedExposure(
|
||||
val overview: ExposureOverview,
|
||||
val pages: List<ExposurePage>
|
||||
)
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package io.novafoundation.nova.feature_staking_api.domain.model
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.bindNumber
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.castToList
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.castToStruct
|
||||
import io.novafoundation.nova.common.utils.divideToDecimal
|
||||
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.days
|
||||
|
||||
class InflationPredictionInfo(
|
||||
val nextMint: NextMint
|
||||
) {
|
||||
|
||||
class NextMint(
|
||||
val toStakers: Balance,
|
||||
val toTreasury: Balance
|
||||
)
|
||||
|
||||
companion object {
|
||||
|
||||
fun fromDecoded(decoded: Any?): InflationPredictionInfo {
|
||||
val asStruct = decoded.castToStruct()
|
||||
|
||||
return InflationPredictionInfo(
|
||||
nextMint = bindNextMint(asStruct["nextMint"])
|
||||
)
|
||||
}
|
||||
|
||||
private fun bindNextMint(decoded: Any?): NextMint {
|
||||
val (toStakersRaw, toTreasuryRaw) = decoded.castToList()
|
||||
|
||||
return NextMint(
|
||||
toStakers = bindNumber(toStakersRaw),
|
||||
toTreasury = bindNumber(toTreasuryRaw)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun InflationPredictionInfo.calculateStakersInflation(totalIssuance: Balance, eraDuration: Duration): Double {
|
||||
val periodsInYear = (365.days / eraDuration).roundToInt()
|
||||
val inflationPerMint = nextMint.toStakers.divideToDecimal(totalIssuance)
|
||||
|
||||
return inflationPerMint.toDouble() * periodsInYear
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package io.novafoundation.nova.feature_staking_api.domain.model
|
||||
|
||||
import java.math.BigInteger
|
||||
|
||||
class NominatedValidator(
|
||||
val validator: Validator,
|
||||
val status: Status,
|
||||
) {
|
||||
|
||||
sealed class Status {
|
||||
|
||||
class Active(val nomination: BigInteger, val willUserBeRewarded: Boolean) : Status()
|
||||
object Elected : Status()
|
||||
object Inactive : Status()
|
||||
object WaitingForNextEra : Status()
|
||||
|
||||
sealed class Group(val numberOfValidators: Int, val position: Int) {
|
||||
companion object {
|
||||
val COMPARATOR = Comparator.comparingInt<Group> { it.position }
|
||||
}
|
||||
|
||||
class Active(numberOfValidators: Int) : Group(numberOfValidators, 0)
|
||||
class Elected(numberOfValidators: Int) : Group(numberOfValidators, 1)
|
||||
class Inactive(numberOfValidators: Int) : Group(numberOfValidators, 2)
|
||||
class WaitingForNextEra(val maxValidatorsPerNominator: Int, numberOfValidators: Int) : Group(numberOfValidators, 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package io.novafoundation.nova.feature_staking_api.domain.model
|
||||
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
|
||||
class Nominations(
|
||||
val targets: List<AccountId>,
|
||||
val submittedInEra: EraIndex,
|
||||
val suppressed: Boolean
|
||||
)
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package io.novafoundation.nova.feature_staking_api.domain.model
|
||||
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
|
||||
sealed class RewardDestination {
|
||||
|
||||
object Restake : RewardDestination()
|
||||
|
||||
class Payout(val targetAccountId: AccountId) : RewardDestination()
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package io.novafoundation.nova.feature_staking_api.domain.model
|
||||
|
||||
import java.math.BigInteger
|
||||
|
||||
typealias SessionIndex = BigInteger
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package io.novafoundation.nova.feature_staking_api.domain.model
|
||||
|
||||
import java.math.BigInteger
|
||||
|
||||
class SlashingSpans(
|
||||
val lastNonZeroSlash: EraIndex,
|
||||
val prior: List<EraIndex>
|
||||
)
|
||||
|
||||
fun SlashingSpans?.numberOfSlashingSpans(): BigInteger {
|
||||
if (this == null) return BigInteger.ZERO
|
||||
|
||||
// all from prior + one for lastNonZeroSlash
|
||||
val numberOfSlashingSpans = prior.size + 1
|
||||
|
||||
return numberOfSlashingSpans.toBigInteger()
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package io.novafoundation.nova.feature_staking_api.domain.model
|
||||
|
||||
class StakingAccount(
|
||||
val address: String,
|
||||
val name: String?,
|
||||
)
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package io.novafoundation.nova.feature_staking_api.domain.model
|
||||
|
||||
import io.novafoundation.nova.common.utils.orZero
|
||||
import io.novafoundation.nova.common.utils.sumByBigInteger
|
||||
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
import java.math.BigInteger
|
||||
|
||||
class StakingLedger(
|
||||
val stashId: AccountId,
|
||||
val total: BigInteger,
|
||||
val active: BigInteger,
|
||||
val unlocking: List<UnlockChunk>,
|
||||
val claimedRewards: List<BigInteger>
|
||||
)
|
||||
|
||||
class UnlockChunk(override val amount: BigInteger, val era: BigInteger) : RedeemableAmount {
|
||||
override val redeemEra: EraIndex = era
|
||||
}
|
||||
|
||||
fun List<UnlockChunk>.totalRedeemableIn(activeEra: EraIndex): Balance = sumStaking { it.isRedeemableIn(activeEra) }
|
||||
|
||||
fun List<UnlockChunk>.sumStaking(
|
||||
condition: (chunk: UnlockChunk) -> Boolean
|
||||
): BigInteger {
|
||||
return filter { condition(it) }
|
||||
.sumByBigInteger(UnlockChunk::amount)
|
||||
}
|
||||
|
||||
fun StakingLedger?.activeBalance(): Balance {
|
||||
return this?.active.orZero()
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package io.novafoundation.nova.feature_staking_api.domain.model
|
||||
|
||||
import io.novafoundation.nova.common.utils.Identifiable
|
||||
import io.novafoundation.nova.feature_account_api.data.model.OnChainIdentity
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
|
||||
typealias Commission = BigDecimal
|
||||
|
||||
class ValidatorPrefs(val commission: Commission, val blocked: Boolean)
|
||||
|
||||
class Validator(
|
||||
val address: String,
|
||||
val slashed: Boolean,
|
||||
val accountIdHex: String,
|
||||
val prefs: ValidatorPrefs?,
|
||||
val electedInfo: ElectedInfo?,
|
||||
val identity: OnChainIdentity?,
|
||||
val isNovaValidator: Boolean
|
||||
) : Identifiable {
|
||||
|
||||
class ElectedInfo(
|
||||
val totalStake: BigInteger,
|
||||
val ownStake: BigInteger,
|
||||
val nominatorStakes: List<IndividualExposure>,
|
||||
val apy: BigDecimal,
|
||||
val isOversubscribed: Boolean
|
||||
)
|
||||
|
||||
override val identifier: String = address
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
package io.novafoundation.nova.feature_staking_api.domain.model.parachain
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.BalanceOf
|
||||
import io.novafoundation.nova.common.utils.castOrNull
|
||||
import io.novafoundation.nova.common.utils.orZero
|
||||
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.model.amountFromPlanks
|
||||
import io.novafoundation.nova.runtime.ext.addressOf
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novasama.substrate_sdk_android.extensions.toHexString
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
|
||||
sealed class DelegatorState(
|
||||
val chain: Chain,
|
||||
val chainAsset: Chain.Asset,
|
||||
) {
|
||||
|
||||
class Delegator(
|
||||
val accountId: AccountId,
|
||||
chain: Chain,
|
||||
chainAsset: Chain.Asset,
|
||||
val delegations: List<DelegatorBond>,
|
||||
val total: Balance,
|
||||
val lessTotal: Balance,
|
||||
) : DelegatorState(chain, chainAsset)
|
||||
|
||||
class None(chain: Chain, chainAsset: Chain.Asset) : DelegatorState(chain, chainAsset)
|
||||
}
|
||||
|
||||
fun DelegatorState.stakeablePlanks(freeBalance: BigInteger): BigInteger {
|
||||
return (freeBalance - totalBonded).coerceAtLeast(BigInteger.ZERO)
|
||||
}
|
||||
|
||||
fun DelegatorState.stakeableAmount(freeBalance: BigInteger): BigDecimal {
|
||||
return chainAsset.amountFromPlanks(stakeablePlanks(freeBalance))
|
||||
}
|
||||
|
||||
private val DelegatorState.totalBonded: BigInteger
|
||||
get() = asDelegator()?.total.orZero()
|
||||
|
||||
val DelegatorState.activeBonded: Balance
|
||||
get() = when (this) {
|
||||
is DelegatorState.Delegator -> total - lessTotal
|
||||
is DelegatorState.None -> Balance.ZERO
|
||||
}
|
||||
|
||||
val DelegatorState.delegationsCount
|
||||
get() = when (this) {
|
||||
is DelegatorState.Delegator -> delegations.size
|
||||
is DelegatorState.None -> 0
|
||||
}
|
||||
|
||||
fun DelegatorState.Delegator.delegatedCollatorIds() = delegations.map { it.owner }
|
||||
fun DelegatorState.Delegator.delegatedCollatorIdsHex() = delegations.map { it.owner.toHexString() }
|
||||
|
||||
fun DelegatorState.delegationAmountTo(collatorId: AccountId): BalanceOf? {
|
||||
return castOrNull<DelegatorState.Delegator>()?.delegations?.find { it.owner.contentEquals(collatorId) }?.balance
|
||||
}
|
||||
|
||||
fun DelegatorState.hasDelegation(collatorId: AccountId): Boolean = this is DelegatorState.Delegator && delegations.any { it.owner.contentEquals(collatorId) }
|
||||
|
||||
fun DelegatorState.asDelegator(): DelegatorState.Delegator? = castOrNull<DelegatorState.Delegator>()
|
||||
|
||||
class DelegatorBond(
|
||||
val owner: AccountId,
|
||||
val balance: BalanceOf,
|
||||
)
|
||||
|
||||
class ScheduledDelegationRequest(
|
||||
val collator: AccountId,
|
||||
val delegator: AccountId,
|
||||
val whenExecutable: RoundIndex,
|
||||
val action: DelegationAction
|
||||
)
|
||||
|
||||
fun ScheduledDelegationRequest.redeemableIn(roundIndex: RoundIndex): Boolean = whenExecutable <= roundIndex
|
||||
fun ScheduledDelegationRequest.unbondingIn(roundIndex: RoundIndex): Boolean = whenExecutable > roundIndex
|
||||
|
||||
sealed class DelegationAction(val amount: BalanceOf) {
|
||||
class Revoke(amount: Balance) : DelegationAction(amount)
|
||||
|
||||
class Decrease(amount: Balance) : DelegationAction(amount)
|
||||
}
|
||||
|
||||
typealias RoundIndex = BigInteger
|
||||
|
||||
fun DelegatorState.Delegator.address() = chain.addressOf(accountId)
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package io.novafoundation.nova.feature_staking_api.domain.model.relaychain
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin
|
||||
import io.novafoundation.nova.feature_staking_api.domain.model.Nominations
|
||||
import io.novafoundation.nova.feature_staking_api.domain.model.ValidatorPrefs
|
||||
import io.novafoundation.nova.runtime.ext.addressOf
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
|
||||
sealed class StakingState(
|
||||
val chain: Chain,
|
||||
val chainAsset: Chain.Asset,
|
||||
) {
|
||||
|
||||
class NonStash(chain: Chain, chainAsset: Chain.Asset) : StakingState(chain, chainAsset)
|
||||
|
||||
sealed class Stash(
|
||||
chain: Chain,
|
||||
chainAsset: Chain.Asset,
|
||||
val accountId: AccountId,
|
||||
val controllerId: AccountId,
|
||||
val stashId: AccountId
|
||||
) : StakingState(chain, chainAsset) {
|
||||
|
||||
val accountAddress: String = chain.addressOf(accountId)
|
||||
|
||||
val stashAddress = chain.addressOf(stashId)
|
||||
val controllerAddress = chain.addressOf(controllerId)
|
||||
|
||||
class None(
|
||||
chain: Chain,
|
||||
chainAsset: Chain.Asset,
|
||||
accountId: AccountId,
|
||||
controllerId: AccountId,
|
||||
stashId: AccountId,
|
||||
) : Stash(chain, chainAsset, accountId, controllerId, stashId)
|
||||
|
||||
class Validator(
|
||||
chain: Chain,
|
||||
chainAsset: Chain.Asset,
|
||||
accountId: AccountId,
|
||||
controllerId: AccountId,
|
||||
stashId: AccountId,
|
||||
val prefs: ValidatorPrefs,
|
||||
) : Stash(chain, chainAsset, accountId, controllerId, stashId)
|
||||
|
||||
class Nominator(
|
||||
chain: Chain,
|
||||
chainAsset: Chain.Asset,
|
||||
accountId: AccountId,
|
||||
controllerId: AccountId,
|
||||
stashId: AccountId,
|
||||
val nominations: Nominations,
|
||||
) : Stash(chain, chainAsset, accountId, controllerId, stashId)
|
||||
}
|
||||
}
|
||||
|
||||
fun StakingState.Stash.stashTransactionOrigin(): TransactionOrigin = TransactionOrigin.WalletWithAccount(stashId)
|
||||
|
||||
fun StakingState.Stash.controllerTransactionOrigin(): TransactionOrigin = TransactionOrigin.WalletWithAccount(controllerId)
|
||||
|
||||
fun StakingState.Stash.accountIsStash(): Boolean = accountId.contentEquals(stashId)
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package io.novafoundation.nova.feature_staking_api.domain.nominationPool.model
|
||||
|
||||
import java.math.BigInteger
|
||||
|
||||
@JvmInline
|
||||
value class PoolId(val value: PoolIdRaw)
|
||||
|
||||
typealias PoolIdRaw = BigInteger
|
||||
|
||||
fun PoolId(id: Int) = PoolId(id.toBigInteger())
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package io.novafoundation.nova.feature_staking_api.presentation.nominationPools.display
|
||||
|
||||
import io.novafoundation.nova.common.utils.images.Icon
|
||||
import io.novafoundation.nova.common.view.TableCellView
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
|
||||
class PoolDisplayModel(
|
||||
val icon: Icon,
|
||||
val title: String,
|
||||
val poolAccountId: AccountId,
|
||||
val address: String
|
||||
)
|
||||
|
||||
fun TableCellView.showPool(poolDisplayModel: PoolDisplayModel) {
|
||||
loadImage(poolDisplayModel.icon)
|
||||
showValue(poolDisplayModel.title)
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package io.novafoundation.nova.feature_staking_api.presentation.nominationPools.display
|
||||
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
|
||||
interface PoolDisplayUseCase {
|
||||
|
||||
suspend fun getPoolDisplay(poolId: Int, chain: Chain): PoolDisplayModel
|
||||
}
|
||||
Reference in New Issue
Block a user