Skip to content

Commit

Permalink
Merge pull request #1771 from novasamatech/myth_staking/setup_start_s…
Browse files Browse the repository at this point in the history
…taking

Myth staking/setup start staking
  • Loading branch information
valentunn authored Jan 27, 2025
2 parents 4a01295 + b33b2b8 commit cf205af
Show file tree
Hide file tree
Showing 108 changed files with 2,309 additions and 642 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.novafoundation.nova.app.di.app.navigation.staking

import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.staking.mythos.MythosStakingNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_staking_impl.presentation.MythosStakingRouter

@Module
class MythosStakingNavigationModule {

@Provides
@ApplicationScope
fun provideMythosStakingRouter(
navigationHoldersRegistry: NavigationHoldersRegistry,
): MythosStakingRouter {
return MythosStakingNavigator(navigationHoldersRegistry)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import io.novafoundation.nova.feature_staking_impl.presentation.StartMultiStakin
includes = [
ParachainStakingNavigationModule::class,
RelayStakingNavigationModule::class,
NominationPoolsStakingNavigationModule::class
NominationPoolsStakingNavigationModule::class,
MythosStakingNavigationModule::class
]
)
class StakingNavigationModule {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ class StartMultiStakingNavigator(
.navigateInFirstAttachedContext()
}

override fun openStartMythosStaking() {
navigationBuilder().action(R.id.action_startStakingLandingFragment_to_staking_mythos_start_graph)
.navigateInFirstAttachedContext()
}

override fun openStartMultiStaking(payload: SetupAmountMultiStakingPayload) {
navigationBuilder().action(R.id.action_startStakingLandingFragment_to_start_multi_staking_nav_graph)
.setArgs(SetupAmountMultiStakingFragment.getBundle(payload))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.novafoundation.nova.app.root.navigation.navigators.staking.mythos

import io.novafoundation.nova.app.root.navigation.navigators.BaseNavigator
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.feature_staking_impl.presentation.MythosStakingRouter

class MythosStakingNavigator(
navigationHoldersRegistry: NavigationHoldersRegistry,
) : BaseNavigator(navigationHoldersRegistry), MythosStakingRouter
22 changes: 22 additions & 0 deletions app/src/main/res/navigation/staking_mythos_start_graph.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/staking_mythos_start_graph"
app:startDestination="@id/startMythosStakingFragment">

<action
android:id="@+id/action_return_to_start_staking"
app:enterAnim="@anim/fragment_close_enter"
app:exitAnim="@anim/fragment_close_exit"
app:popEnterAnim="@anim/fragment_open_enter"
app:popExitAnim="@anim/fragment_open_exit"
app:popUpTo="@id/startMythosStakingFragment" />

<fragment
android:id="@+id/startMythosStakingFragment"
android:name="io.novafoundation.nova.feature_staking_impl.presentation.mythos.start.setup.SetupStartMythosStakingFragment"
android:label="StartMythosStakingFragment"
tools:layout="@layout/fragment_parachain_staking_start">
</fragment>
</navigation>
10 changes: 10 additions & 0 deletions app/src/main/res/navigation/start_staking_nav_graph.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,20 @@
android:id="@+id/action_startStakingLandingFragment_to_staking_parachain_start_graph"
app:destination="@id/staking_parachain_start_graph" />

<action
app:enterAnim="@anim/fragment_open_enter"
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_close_enter"
app:popExitAnim="@anim/fragment_close_exit"
android:id="@+id/action_startStakingLandingFragment_to_staking_mythos_start_graph"
app:destination="@id/staking_mythos_start_graph" />

</fragment>

<include app:graph="@navigation/staking_parachain_start_graph" />

<include app:graph="@navigation/staking_mythos_start_graph" />

<include app:graph="@navigation/start_multi_staking_nav_graph" />

</navigation>
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import io.novasama.substrate_sdk_android.runtime.AccountId

class AccountIdKey(val value: AccountId) {

companion object;

override fun equals(other: Any?): Boolean {
return this === other || other is AccountIdKey && this.value contentEquals other.value
}
Expand All @@ -17,3 +19,8 @@ fun AccountId.intoKey() = AccountIdKey(this)

operator fun <T> Map<AccountIdKey, T>.get(key: AccountId) = get(AccountIdKey(key))
fun <T> Map<AccountIdKey, T>.getValue(key: AccountId) = getValue(AccountIdKey(key))

interface WithAccountId {

val accountId: AccountIdKey
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.novafoundation.nova.common.data.memory

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow

abstract class SharedComputation(
Expand All @@ -15,4 +16,14 @@ abstract class SharedComputation(

return computationalCache.useSharedFlow(key, flowLazy)
}

context(ComputationalScope)
protected suspend fun <T> cachedValue(
vararg keyArgs: String,
valueLazy: suspend CoroutineScope.() -> T
): T {
val key = keyArgs.joinToString(separator = ".")

return computationalCache.useCache(key, valueLazy)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.novafoundation.nova.common.mixin.hints

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf

class NoHintsMixin : HintsMixin {

override val hintsFlow: Flow<List<CharSequence>> = flowOf(emptyList())
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ fun BigInteger.toUnsignedLittleEndian(): ByteArray {
return toUnsignedBytes().reversedArray()
}

fun BigInteger.takeUnlessZero(): BigInteger? {
return takeUnless { isZero }
}

fun ByteArray.toBigEndianU32(): UInt = ByteBuffer.wrap(this).order(ByteOrder.BIG_ENDIAN).int.toUInt()

fun <T> DataType<T>.fromHex(hex: String): T {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,10 @@ inline fun <K, V> Map<K, V?>.filterNotNull(): Map<K, V> {
return filterValues { it != null } as Map<K, V>
}

inline fun <K1, K2, V> Map<Pair<K1, K2>, V>.dropSecondKey(): Map<K1, V> {
return mapKeys { (keys, _) -> keys.first }
}

inline fun <T, R> Array<T>.tryFindNonNull(transform: (T) -> R?): R? {
for (item in this) {
val transformed = transform(item)
Expand Down Expand Up @@ -493,6 +497,11 @@ fun <T> List<T>.added(toAdd: T): List<T> {
return toMutableList().apply { add(toAdd) }
}

fun <T : Any> MutableList<T>.removeFirstOrNull(condition: (T) -> Boolean): T? {
val index = indexOfFirstOrNull(condition) ?: return null
return removeAt(index)
}

fun <T> List<T>.prepended(toPrepend: T): List<T> {
return toMutableList().apply { add(0, toPrepend) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ interface StorageCache {
* First result will be emitted when all keys are found in the cache
* Thus, result.size == fullKeys.size
*/
suspend fun observeEntries(keys: List<String>, chainId: String): Flow<List<StorageEntry>>
fun observeEntries(keys: List<String>, chainId: String): Flow<List<StorageEntry>>

suspend fun observeEntries(keyPrefix: String, chainId: String): Flow<List<StorageEntry>>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ class RealOnChainIdentityRepository(
chainId: ChainId
): AccountIdKeyMap<OnChainIdentity?> = withContext(Dispatchers.Default) {
val identityChainId = findIdentityChain(chainId)
val distinctKeys = accountIds.mapToSet(::AccountIdKey)

storageDataSource.query(identityChainId) {
if (!runtime.metadata.hasModule(Modules.IDENTITY)) {
return@query emptyMap()
}

val distinctKeys = accountIds.mapToSet(::AccountIdKey)

val superOfArguments = distinctKeys.map { listOf(it.value) }
val superOfValues = runtime.metadata.identity().storage("SuperOf").entries(
keysArguments = superOfArguments,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.novafoundation.nova.feature_staking_impl.data.dashboard.network.updaters.chain

import io.novafoundation.nova.common.utils.findById
import io.novafoundation.nova.common.utils.orZero
import io.novafoundation.nova.common.utils.takeUnlessZero
import io.novafoundation.nova.core.storage.StorageCache
import io.novafoundation.nova.core.updater.SharedRequestsBuilder
import io.novafoundation.nova.core.updater.Updater
Expand All @@ -10,11 +10,11 @@ import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
import io.novafoundation.nova.feature_staking_impl.data.dashboard.cache.StakingDashboardCache
import io.novafoundation.nova.feature_staking_impl.data.mythos.network.blockchain.api.collatorStaking
import io.novafoundation.nova.feature_staking_impl.data.mythos.network.blockchain.api.userStake
import io.novafoundation.nova.feature_staking_impl.data.mythos.network.blockchain.model.MythosStakingFreezeIds
import io.novafoundation.nova.feature_staking_impl.data.mythos.network.blockchain.model.UserStakeInfo
import io.novafoundation.nova.feature_staking_impl.data.mythos.repository.observeMythosLocks
import io.novafoundation.nova.feature_staking_impl.data.mythos.repository.total
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.BalanceLock
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novafoundation.nova.runtime.storage.cache.StorageCachingContext
import io.novafoundation.nova.runtime.storage.cache.cacheValues
Expand Down Expand Up @@ -74,8 +74,8 @@ class StakingDashboardMythosUpdater(
}

private fun subscribeToTotalStake(): Flow<Balance?> {
return balanceLocksRepository.observeBalanceLocks(metaAccount.id, chain, chainAsset).map { locks ->
locks.getTotalStake()
return balanceLocksRepository.observeMythosLocks(metaAccount.id, chain, chainAsset).map { mythosLocks ->
mythosLocks.total.takeUnlessZero()
}
}

Expand Down Expand Up @@ -116,15 +116,6 @@ class StakingDashboardMythosUpdater(
}
}

private fun List<BalanceLock>.getTotalStake(): Balance? {
val stakingLock = findById(MythosStakingFreezeIds.STAKING)
val releasingLock = findById(MythosStakingFreezeIds.RELEASING)

if (stakingLock == null && releasingLock == null) return null

return stakingLock?.amountInPlanks.orZero() + releasingLock?.amountInPlanks.orZero()
}

private class OnChainInfo(
val activeStake: Balance,
val accountId: AccountId
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
package io.novafoundation.nova.feature_staking_impl.data.mythos.network.blockchain.api

import io.novafoundation.nova.common.address.AccountIdKey
import io.novafoundation.nova.common.data.network.runtime.binding.bindAccountIdKey
import io.novafoundation.nova.common.data.network.runtime.binding.bindNumber
import io.novafoundation.nova.common.utils.RuntimeContext
import io.novafoundation.nova.common.utils.collatorStaking
import io.novafoundation.nova.feature_staking_impl.data.mythos.network.blockchain.model.MythCandidateInfo
import io.novafoundation.nova.feature_staking_impl.data.mythos.network.blockchain.model.MythDelegation
import io.novafoundation.nova.feature_staking_impl.data.mythos.network.blockchain.model.UserStakeInfo
import io.novafoundation.nova.feature_staking_impl.data.mythos.network.blockchain.model.bindDelegationInfo
import io.novafoundation.nova.feature_staking_impl.data.mythos.network.blockchain.model.bindMythCandidateInfo
import io.novafoundation.nova.feature_staking_impl.data.mythos.network.blockchain.model.bindUserStakeInfo
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
import io.novafoundation.nova.runtime.storage.source.query.api.QueryableModule
import io.novafoundation.nova.runtime.storage.source.query.api.QueryableStorageEntry0
import io.novafoundation.nova.runtime.storage.source.query.api.QueryableStorageEntry1
import io.novafoundation.nova.runtime.storage.source.query.api.QueryableStorageEntry2
import io.novafoundation.nova.runtime.storage.source.query.api.converters.scaleDecoder
import io.novafoundation.nova.runtime.storage.source.query.api.converters.scaleEncoder
import io.novafoundation.nova.runtime.storage.source.query.api.storage0
import io.novafoundation.nova.runtime.storage.source.query.api.storage1
import io.novafoundation.nova.runtime.storage.source.query.api.storage2
import io.novasama.substrate_sdk_android.runtime.AccountId
import io.novasama.substrate_sdk_android.runtime.metadata.RuntimeMetadata
import io.novasama.substrate_sdk_android.runtime.metadata.module.Module
Expand All @@ -29,3 +39,22 @@ val CollatorStakingRuntimeApi.userStake: QueryableStorageEntry1<AccountId, UserS
context(RuntimeContext)
val CollatorStakingRuntimeApi.minStake: QueryableStorageEntry0<Balance>
get() = storage0("MinStake", binding = ::bindNumber)

context(RuntimeContext)
val CollatorStakingRuntimeApi.candidates: QueryableStorageEntry1<AccountIdKey, MythCandidateInfo>
get() = storage1(
"Candidates",
binding = { decoded, _ -> bindMythCandidateInfo(decoded) },
keyBinding = ::bindAccountIdKey
)

context(RuntimeContext)
val CollatorStakingRuntimeApi.candidateStake: QueryableStorageEntry2<AccountIdKey, AccountIdKey, MythDelegation>
get() = storage2(
"CandidateStake",
binding = { decoded, _, _, -> bindDelegationInfo(decoded) },
key1ToInternalConverter = AccountIdKey.scaleEncoder,
key2ToInternalConverter = AccountIdKey.scaleEncoder,
key1FromInternalConverter = AccountIdKey.scaleDecoder,
key2FromInternalConverter = AccountIdKey.scaleDecoder
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package io.novafoundation.nova.feature_staking_impl.data.mythos.network.blockchain.calls

import io.novafoundation.nova.common.address.AccountIdKey
import io.novafoundation.nova.common.utils.Modules
import io.novafoundation.nova.common.utils.structOf
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
import io.novasama.substrate_sdk_android.runtime.extrinsic.ExtrinsicBuilder

@JvmInline
value class CollatorStakingCalls(val builder: ExtrinsicBuilder)

val ExtrinsicBuilder.collatorStaking: CollatorStakingCalls
get() = CollatorStakingCalls(this)

fun CollatorStakingCalls.lock(amount: Balance) {
builder.call(
moduleName = Modules.COLLATOR_STAKING,
callName = "lock",
arguments = mapOf(
"amount" to amount
)
)
}

data class StakingIntent(val candidate: AccountIdKey, val stake: Balance) {

companion object {

fun zero(candidate: AccountIdKey) = StakingIntent(candidate, Balance.ZERO)
}
}

fun CollatorStakingCalls.stake(intents: List<StakingIntent>) {
val targets = intents.map(StakingIntent::toEncodableInstance)

builder.call(
moduleName = Modules.COLLATOR_STAKING,
callName = "stake",
arguments = mapOf(
"targets" to targets
)
)
}

private fun StakingIntent.toEncodableInstance(): Any? {
return structOf(
"candidate" to candidate.value,
"stake" to stake
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.novafoundation.nova.feature_staking_impl.data.mythos.network.blockchain.model

import io.novafoundation.nova.common.data.network.runtime.binding.bindInt
import io.novafoundation.nova.common.data.network.runtime.binding.bindNumber
import io.novafoundation.nova.common.data.network.runtime.binding.castToStruct
import io.novafoundation.nova.feature_account_api.data.model.AccountIdKeyMap
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance

class MythCandidateInfo(
val stake: Balance,
val stakers: Int
)

typealias MythCandidateInfos = AccountIdKeyMap<MythCandidateInfo>

fun bindMythCandidateInfo(decoded: Any?): MythCandidateInfo {
val asStruct = decoded.castToStruct()
return MythCandidateInfo(
stake = bindNumber(asStruct["stake"]),
stakers = bindInt(asStruct["stakers"])
)
}
Loading

0 comments on commit cf205af

Please sign in to comment.