Initial commit: Pezkuwi Wallet Android

Security hardened release:
- Code obfuscation enabled (minifyEnabled=true, shrinkResources=true)
- Sensitive files excluded (google-services.json, keystores)
- Branch.io key moved to BuildConfig placeholder
- Updated dependencies: OkHttp 4.12.0, Gson 2.10.1, BouncyCastle 1.77
- Comprehensive ProGuard rules for crypto wallet
- Navigation 2.7.7, Lifecycle 2.7.0, ConstraintLayout 2.1.4
This commit is contained in:
2026-02-12 05:19:41 +03:00
commit a294aa1a6b
7687 changed files with 441811 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/build
+68
View File
@@ -0,0 +1,68 @@
apply plugin: 'kotlin-parcelize'
apply from: '../scripts/secrets.gradle'
android {
namespace 'io.novafoundation.nova.feature_governance_impl'
defaultConfig {
buildConfigField "String", "GOVERNANCE_DAPPS_URL", "\"https://raw.githubusercontent.com/pezkuwichain/pezkuwi-wallet-utils/master/governance/v2/dapps_dev.json\""
buildConfigField "String", "DELEGATION_TUTORIAL_URL", "\"https://docs.pezkuwichain.io/pezkuwi-wallet-wiki/governance/add-delegate-information\""
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
buildConfigField "String", "GOVERNANCE_DAPPS_URL", "\"https://raw.githubusercontent.com/pezkuwichain/pezkuwi-wallet-utils/master/governance/v2/dapps.json\""
}
}
buildFeatures {
viewBinding true
}
}
dependencies {
implementation project(':feature-account-api')
implementation project(':feature-wallet-api')
implementation project(':feature-governance-api')
implementation project(':feature-dapp-api')
implementation project(':feature-xcm:api')
implementation project(':feature-deep-linking')
implementation project(":common")
implementation project(":runtime")
implementation markwonDep
implementation materialDep
implementation substrateSdkDep
implementation kotlinDep
implementation androidDep
implementation coroutinesDep
implementation coroutinesAndroidDep
implementation lifeCycleKtxDep
implementation flexBoxDep
implementation project(":core-db")
implementation viewModelKtxDep
implementation shimmerDep
implementation cardStackView
implementation daggerDep
ksp daggerCompiler
testImplementation jUnitDep
testImplementation mockitoDep
}
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />
@@ -0,0 +1,55 @@
package io.novafoundation.nova.feature_governance_impl.data
import io.novafoundation.nova.common.data.storage.Preferences
import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.feature_governance_api.data.MutableGovernanceState
import io.novafoundation.nova.feature_governance_api.data.source.GovernanceAdditionalState
import io.novafoundation.nova.feature_governance_impl.R
import io.novafoundation.nova.runtime.ext.isUtilityAsset
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
import io.novafoundation.nova.runtime.state.SelectableSingleAssetSharedState
private const val GOVERNANCE_SHARED_STATE = "GOVERNANCE_SHARED_STATE"
class GovernanceSharedState(
chainRegistry: ChainRegistry,
preferences: Preferences,
) : SelectableSingleAssetSharedState<GovernanceAdditionalState>(
preferences = preferences,
chainRegistry = chainRegistry,
supportedOptions = { chain, asset ->
if (asset.isUtilityAsset) {
val multipleGovernanceTypesPresent = chain.governance.size > 1
chain.governance.map { RealGovernanceAdditionalState(it, multipleGovernanceTypesPresent) }
} else {
emptyList()
}
},
preferencesKey = GOVERNANCE_SHARED_STATE
),
MutableGovernanceState {
override fun update(chainId: ChainId, assetId: Int, governanceType: Chain.Governance) {
update(chainId, assetId, governanceType.name)
}
}
class RealGovernanceAdditionalState(
override val governanceType: Chain.Governance,
private val multipleGovernanceTypesPresent: Boolean
) : GovernanceAdditionalState {
override val identifier: String = governanceType.name
override fun format(resourceManager: ResourceManager): String? {
val shouldShowSuffix = multipleGovernanceTypesPresent || governanceType == Chain.Governance.V2
if (!shouldShowSuffix) return null
return when (governanceType) {
Chain.Governance.V1 -> resourceManager.getString(R.string.assets_balance_details_locks_democrac_v1)
Chain.Governance.V2 -> resourceManager.getString(R.string.assets_balance_details_locks_democrac_v2)
}
}
}
@@ -0,0 +1,39 @@
package io.novafoundation.nova.feature_governance_impl.data.dapps
import io.novafoundation.nova.common.utils.retryUntilDone
import io.novafoundation.nova.core_db.dao.GovernanceDAppsDao
import io.novafoundation.nova.core_db.model.GovernanceDAppLocal
import io.novafoundation.nova.feature_governance_impl.data.dapps.remote.GovernanceDappsFetcher
import io.novafoundation.nova.feature_governance_impl.data.dapps.remote.model.GovernanceChainDappsRemote
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class GovernanceDAppsSyncService(
private val dao: GovernanceDAppsDao,
private val dappsFetcher: GovernanceDappsFetcher
) {
suspend fun syncDapps() = withContext(Dispatchers.Default) {
val newDapps = retryUntilDone {
mapRemoteDappsToLocal(dappsFetcher.getDapps())
}
dao.update(newDapps)
}
private fun mapRemoteDappsToLocal(
chainDapps: List<GovernanceChainDappsRemote>
): List<GovernanceDAppLocal> {
return chainDapps.flatMap { chainWithDapp ->
chainWithDapp.dapps.map {
GovernanceDAppLocal(
chainWithDapp.chainId,
it.title,
it.urlV1,
it.urlV2,
it.icon,
it.details
)
}
}
}
}
@@ -0,0 +1,11 @@
package io.novafoundation.nova.feature_governance_impl.data.dapps.remote
import io.novafoundation.nova.feature_governance_impl.BuildConfig
import io.novafoundation.nova.feature_governance_impl.data.dapps.remote.model.GovernanceChainDappsRemote
import retrofit2.http.GET
interface GovernanceDappsFetcher {
@GET(BuildConfig.GOVERNANCE_DAPPS_URL)
suspend fun getDapps(): List<GovernanceChainDappsRemote>
}
@@ -0,0 +1,14 @@
package io.novafoundation.nova.feature_governance_impl.data.dapps.remote.model
class GovernanceChainDappsRemote(
val chainId: String,
val dapps: List<GovernanceDappRemote>
)
class GovernanceDappRemote(
val title: String,
val urlV1: String?,
val urlV2: String?,
val icon: String,
val details: String
)
@@ -0,0 +1,184 @@
package io.novafoundation.nova.feature_governance_impl.data.network.blockchain.extrinsic
import io.novafoundation.nova.common.utils.Modules
import io.novafoundation.nova.common.utils.argumentType
import io.novafoundation.nova.common.utils.democracy
import io.novafoundation.nova.common.utils.structOf
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.AccountVote
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.TrackId
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
import io.novafoundation.nova.runtime.extrinsic.multi.CallBuilder
import io.novafoundation.nova.runtime.multiNetwork.runtime.types.custom.vote.Conviction
import io.novafoundation.nova.runtime.util.constructAccountLookupInstance
import io.novasama.substrate_sdk_android.runtime.AccountId
import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.DictEnum
import io.novafoundation.nova.common.utils.PezkuwiAddressConstructor
import io.novasama.substrate_sdk_android.runtime.extrinsic.builder.ExtrinsicBuilder
import io.novasama.substrate_sdk_android.runtime.extrinsic.call
import io.novasama.substrate_sdk_android.runtime.metadata.call
fun ExtrinsicBuilder.convictionVotingVote(
referendumId: ReferendumId,
vote: AccountVote
): ExtrinsicBuilder {
return call(
moduleName = Modules.CONVICTION_VOTING,
callName = "vote",
arguments = mapOf(
"poll_index" to referendumId.value,
"vote" to vote.prepareForEncoding()
)
)
}
fun CallBuilder.convictionVotingVote(
referendumId: ReferendumId,
vote: AccountVote
): CallBuilder {
return addCall(
moduleName = Modules.CONVICTION_VOTING,
callName = "vote",
arguments = mapOf(
"poll_index" to referendumId.value,
"vote" to vote.prepareForEncoding()
)
)
}
fun ExtrinsicBuilder.convictionVotingUnlock(
trackId: TrackId,
accountId: AccountId
): ExtrinsicBuilder {
return call(
moduleName = Modules.CONVICTION_VOTING,
callName = "unlock",
arguments = mapOf(
"class" to trackId.value,
"target" to PezkuwiAddressConstructor.constructInstance(runtime.typeRegistry, accountId)
)
)
}
fun ExtrinsicBuilder.convictionVotingRemoveVote(
trackId: TrackId,
referendumId: ReferendumId,
): ExtrinsicBuilder {
return call(
moduleName = Modules.CONVICTION_VOTING,
callName = "remove_vote",
arguments = mapOf(
"class" to trackId.value,
"index" to referendumId.value
)
)
}
fun ExtrinsicBuilder.democracyVote(
referendumId: ReferendumId,
vote: AccountVote
): ExtrinsicBuilder {
return call(
moduleName = Modules.DEMOCRACY,
callName = "vote",
arguments = mapOf(
"ref_index" to referendumId.value,
"vote" to vote.prepareForEncoding()
)
)
}
fun CallBuilder.democracyVote(
referendumId: ReferendumId,
vote: AccountVote
): CallBuilder {
return addCall(
moduleName = Modules.DEMOCRACY,
callName = "vote",
arguments = mapOf(
"ref_index" to referendumId.value,
"vote" to vote.prepareForEncoding()
)
)
}
fun ExtrinsicBuilder.democracyUnlock(accountId: AccountId): ExtrinsicBuilder {
val accountLookupType = runtime.metadata.democracy().call("unlock").argumentType("target")
return call(
moduleName = Modules.DEMOCRACY,
callName = "unlock",
arguments = mapOf(
"target" to accountLookupType.constructAccountLookupInstance(accountId)
)
)
}
fun ExtrinsicBuilder.democracyRemoveVote(
referendumId: ReferendumId,
): ExtrinsicBuilder {
return call(
moduleName = Modules.DEMOCRACY,
callName = "remove_vote",
arguments = mapOf(
"index" to referendumId.value
)
)
}
fun CallBuilder.convictionVotingDelegate(
delegate: AccountId,
trackId: TrackId,
amount: Balance,
conviction: Conviction
): CallBuilder {
return addCall(
moduleName = Modules.CONVICTION_VOTING,
callName = "delegate",
arguments = mapOf(
"class" to trackId.value,
"to" to PezkuwiAddressConstructor.constructInstance(runtime.typeRegistry, delegate),
"conviction" to conviction.prepareForEncoding(),
"balance" to amount
)
)
}
fun CallBuilder.convictionVotingUndelegate(trackId: TrackId): CallBuilder {
return addCall(
moduleName = Modules.CONVICTION_VOTING,
callName = "undelegate",
arguments = mapOf(
"class" to trackId.value,
)
)
}
private fun Conviction.prepareForEncoding(): Any {
return DictEnum.Entry(name, null)
}
private fun AccountVote.prepareForEncoding(): Any {
return when (this) {
AccountVote.Unsupported -> error("Not yet supported")
is AccountVote.Standard -> DictEnum.Entry(
name = "Standard",
value = structOf(
"vote" to vote,
"balance" to balance
)
)
is AccountVote.SplitAbstain -> DictEnum.Entry(
name = "SplitAbstain",
value = structOf(
"aye" to this.aye,
"nay" to this.nay,
"abstain" to this.abstain
)
)
else -> error("Not supported yet")
}
}
@@ -0,0 +1,12 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId
import io.novafoundation.nova.feature_governance_api.data.network.offchain.model.referendum.OffChainReferendumDetails
import io.novafoundation.nova.feature_governance_api.data.network.offchain.model.referendum.OffChainReferendumPreview
interface OffChainReferendaDataSource<O> {
suspend fun referendumPreviews(baseUrl: String, options: O): List<OffChainReferendumPreview>
suspend fun referendumDetails(referendumId: ReferendumId, baseUrl: String, options: O): OffChainReferendumDetails?
}
@@ -0,0 +1,28 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.metadata
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.metadata.response.DelegateMetadataRemote
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import retrofit2.http.GET
import retrofit2.http.Path
interface DelegateMetadataApi {
companion object {
const val BASE_URL = "https://raw.githubusercontent.com/pezkuwichain/opengov-delegate-registry/master/registry/"
}
@GET("{fileName}")
suspend fun getDelegatesMetadata(
@Path("fileName") fileName: String,
): List<DelegateMetadataRemote>
}
suspend fun DelegateMetadataApi.getDelegatesMetadata(chain: Chain): List<DelegateMetadataRemote> {
return getDelegatesMetadata(fileNameFor(chain))
}
private fun fileNameFor(chain: Chain): String {
val withoutExtension = chain.name.lowercase()
return "$withoutExtension.json"
}
@@ -0,0 +1,10 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.metadata.response
class DelegateMetadataRemote(
val address: String,
val name: String,
val image: String,
val shortDescription: String,
val longDescription: String?,
val isOrganization: Boolean
)
@@ -0,0 +1,80 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats
import io.novafoundation.nova.common.data.network.subquery.SubQueryResponse
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request.AllHistoricalVotesRequest
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request.ReferendumVotersRequest
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request.DelegateDelegatorsRequest
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request.DelegateDetailedStatsRequest
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request.DelegateStatsByAddressesRequest
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request.DelegateStatsRequest
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request.DirectHistoricalVotesRequest
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request.ReferendumSplitAbstainVotersRequest
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request.ReferendumVotesRequest
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.response.AllVotesResponse
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.response.DelegateDelegatorsResponse
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.response.DelegateDetailedStatsResponse
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.response.DelegateStatsResponse
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.response.DirectVotesResponse
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.response.ReferendumSplitAbstainVotersResponse
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.response.ReferendumVotersResponse
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.response.ReferendumVotesResponse
import retrofit2.http.Body
import retrofit2.http.POST
import retrofit2.http.Url
interface DelegationsSubqueryApi {
@POST
suspend fun getDelegateStats(
@Url url: String,
@Body body: DelegateStatsRequest
): SubQueryResponse<DelegateStatsResponse>
@POST
suspend fun getDelegateStats(
@Url url: String,
@Body body: DelegateStatsByAddressesRequest
): SubQueryResponse<DelegateStatsResponse>
@POST
suspend fun getDetailedDelegateStats(
@Url url: String,
@Body body: DelegateDetailedStatsRequest
): SubQueryResponse<DelegateDetailedStatsResponse>
@POST
suspend fun getDelegateDelegators(
@Url url: String,
@Body body: DelegateDelegatorsRequest
): SubQueryResponse<DelegateDelegatorsResponse>
@POST
suspend fun getAllHistoricalVotes(
@Url url: String,
@Body body: AllHistoricalVotesRequest
): SubQueryResponse<AllVotesResponse>
@POST
suspend fun getDirectHistoricalVotes(
@Url url: String,
@Body body: DirectHistoricalVotesRequest
): SubQueryResponse<DirectVotesResponse>
@POST
suspend fun getReferendumVoters(
@Url url: String,
@Body body: ReferendumVotersRequest
): SubQueryResponse<ReferendumVotersResponse>
@POST
suspend fun getReferendumVotes(
@Url url: String,
@Body body: ReferendumVotesRequest
): SubQueryResponse<ReferendumVotesResponse>
@POST
suspend fun getReferendumAbstainVoters(
@Url url: String,
@Body body: ReferendumSplitAbstainVotersRequest
): SubQueryResponse<ReferendumSplitAbstainVotersResponse>
}
@@ -0,0 +1,47 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request
import io.novafoundation.nova.feature_governance_api.data.repository.common.RecentVotesDateThreshold
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request.common.createSubqueryFilter
class AllHistoricalVotesRequest(address: String) {
val query = """
query {
castingVotings(filter: { voter: {equalTo: "$address"}}) {
nodes {
referendumId
standardVote
splitVote
splitAbstainVote
}
}
delegatorVotings(filter: {delegator: {equalTo: "$address"}}) {
nodes {
vote
parent {
referendumId
delegateId
standardVote
}
}
}
}
""".trimIndent()
}
class DirectHistoricalVotesRequest(address: String, recentVotesDateThreshold: RecentVotesDateThreshold) {
val query = """
query {
castingVotings(filter: { and: { voter: {equalTo: "$address"}, ${recentVotesDateThreshold.createSubqueryFilter()}}}) {
nodes {
referendumId
standardVote
splitVote
splitAbstainVote
}
}
}
""".trimIndent()
}
@@ -0,0 +1,14 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request
class DelegateDelegatorsRequest(delegateAddress: String) {
val query = """
query {
delegations(filter: {delegateId: {equalTo: "$delegateAddress" }}) {
nodes {
delegator
delegation
}
}
}
""".trimIndent()
}
@@ -0,0 +1,24 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request
import io.novafoundation.nova.feature_governance_api.data.repository.common.RecentVotesDateThreshold
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request.common.createSubqueryFilter
class DelegateDetailedStatsRequest(delegateAddress: String, recentVotesDateThreshold: RecentVotesDateThreshold) {
val query = """
query {
delegates(filter: {accountId: {equalTo: "$delegateAddress"}}) {
nodes {
accountId
delegators
delegatorVotes
allVotes: delegateVotes {
totalCount
}
recentVotes: delegateVotes(filter: {${recentVotesDateThreshold.createSubqueryFilter()}}) {
totalCount
}
}
}
}
""".trimIndent()
}
@@ -0,0 +1,26 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request
import io.novafoundation.nova.feature_governance_api.data.repository.common.RecentVotesDateThreshold
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request.common.createSubqueryFilter
class DelegateStatsByAddressesRequest(recentVotesDateThreshold: RecentVotesDateThreshold, val addresses: List<String>) {
val query = """
query {
delegates(filter:{accountId:{in:[${getAddresses()}]}}) {
totalCount
nodes {
accountId
delegators
delegatorVotes
delegateVotes(filter: {${recentVotesDateThreshold.createSubqueryFilter()}}) {
totalCount
}
}
}
}
""".trimIndent()
private fun getAddresses(): String {
return addresses.joinToString { "\"$it\"" }
}
}
@@ -0,0 +1,22 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request
import io.novafoundation.nova.feature_governance_api.data.repository.common.RecentVotesDateThreshold
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request.common.createSubqueryFilter
class DelegateStatsRequest(recentVotesDateThreshold: RecentVotesDateThreshold) {
val query = """
query {
delegates {
totalCount
nodes {
accountId
delegators
delegatorVotes
delegateVotes(filter: {${recentVotesDateThreshold.createSubqueryFilter()}}) {
totalCount
}
}
}
}
""".trimIndent()
}
@@ -0,0 +1,18 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request
import java.math.BigInteger
class ReferendumSplitAbstainVotersRequest(referendumId: BigInteger) {
val query = """
query {
referendum(id:"$referendumId") {
trackId
castingVotings(filter: {splitAbstainVote: {isNull: false}}) {
nodes {
splitAbstainVote
}
}
}
}
""".trimIndent()
}
@@ -0,0 +1,43 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request
import io.novafoundation.nova.common.data.network.subquery.SubqueryExpressions.anyOf
import java.math.BigInteger
class ReferendumVotersRequest(referendumId: BigInteger, isAye: Boolean) {
val query = """
query {
castingVotings(filter:{referendumId:{equalTo:"$referendumId"}, ${voteTypeFilter(isAye)}}) {
nodes {
voter
standardVote
splitVote
splitAbstainVote
delegateId
delegatorVotes {
nodes {
delegator
vote
}
}
}
}
}
""".trimIndent()
private fun voteTypeFilter(isAye: Boolean): String {
return anyOf(standardVoteFilter(isAye), splitVoteFilter(), splitAbstainVote())
}
// we cannot filter JSON field by checking splitVote.ayeAmount > 0 so it should be done after request
private fun splitVoteFilter(): String {
return "splitVote: {isNull: false}"
}
private fun splitAbstainVote(): String {
return "splitAbstainVote: {isNull: false}"
}
private fun standardVoteFilter(isAye: Boolean): String {
return "standardVote: {contains: {aye: $isAye}}"
}
}
@@ -0,0 +1,25 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request
import java.math.BigInteger
class ReferendumVotesRequest(referendumId: BigInteger) {
val query = """
query {
referendum(id:"$referendumId") {
trackId
castingVotings {
nodes {
splitVote
splitAbstainVote
standardVote
delegatorVotes {
nodes {
vote
}
}
}
}
}
}
""".trimIndent()
}
@@ -0,0 +1,11 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request.common
import io.novafoundation.nova.feature_governance_api.data.repository.common.RecentVotesDateThreshold
import kotlin.time.Duration.Companion.milliseconds
fun RecentVotesDateThreshold.createSubqueryFilter(): String {
return when (this) {
is RecentVotesDateThreshold.BlockNumber -> "at: {greaterThanOrEqualTo: ${number.toLong()}}"
is RecentVotesDateThreshold.Timestamp -> "timestamp: {greaterThanOrEqualTo: ${timestampMs.milliseconds.inWholeSeconds}}"
}
}
@@ -0,0 +1,33 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.response
import com.google.gson.annotations.SerializedName
import io.novafoundation.nova.common.data.network.subquery.SubQueryNodes
import java.math.BigInteger
class AllVotesResponse(
@SerializedName("castingVotings") val direct: SubQueryNodes<DirectVoteRemote>,
@SerializedName("delegatorVotings") val delegated: SubQueryNodes<DelegatedVoteRemote>
)
class DirectVotesResponse(
@SerializedName("castingVotings") val direct: SubQueryNodes<DirectVoteRemote>,
)
class DirectVoteRemote(
val referendumId: BigInteger,
override val standardVote: StandardVoteRemote?,
override val splitVote: SplitVoteRemote?,
override val splitAbstainVote: SplitAbstainVoteRemote?
) : MultiVoteRemote
class DelegatedVoteRemote(
val vote: VoteRemote,
val parent: Parent
) {
class Parent(
val referendumId: BigInteger,
val delegateId: String,
val standardVote: StandardVoteRemote?
)
}
@@ -0,0 +1,14 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.response
import com.google.gson.annotations.SerializedName
import io.novafoundation.nova.common.data.network.subquery.SubQueryNodes
class DelegateDelegatorsResponse(
val delegations: SubQueryNodes<DelegatorRemote>
) {
class DelegatorRemote(
@SerializedName("delegator") val address: String,
val delegation: VoteRemote
)
}
@@ -0,0 +1,17 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.response
import io.novafoundation.nova.common.data.network.subquery.SubQueryNodes
import io.novafoundation.nova.common.data.network.subquery.SubQueryTotalCount
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
class DelegateDetailedStatsResponse(
val delegates: SubQueryNodes<Delegate>
) {
class Delegate(
val delegators: Int,
val delegatorVotes: Balance,
val recentVotes: SubQueryTotalCount,
val allVotes: SubQueryTotalCount
)
}
@@ -0,0 +1,18 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.response
import com.google.gson.annotations.SerializedName
import io.novafoundation.nova.common.data.network.subquery.SubQueryNodes
import io.novafoundation.nova.common.data.network.subquery.SubQueryTotalCount
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
class DelegateStatsResponse(
val delegates: SubQueryNodes<Delegate>
) {
class Delegate(
@SerializedName("accountId") val address: String,
val delegators: Int,
val delegatorVotes: Balance,
val delegateVotes: SubQueryTotalCount
)
}
@@ -0,0 +1,15 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.response
import io.novafoundation.nova.common.data.network.subquery.SubQueryNodes
import java.math.BigInteger
class ReferendumSplitAbstainVotersResponse(
val referendum: Referendum
) {
class Referendum(val trackId: BigInteger, val castingVotings: SubQueryNodes<Voter>)
class Voter(
val splitAbstainVote: SplitAbstainVoteRemote?
)
}
@@ -0,0 +1,22 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.response
import com.google.gson.annotations.SerializedName
import io.novafoundation.nova.common.data.network.subquery.SubQueryNodes
class ReferendumVotersResponse(
@SerializedName("castingVotings") val voters: SubQueryNodes<ReferendumVoterRemote>
)
class ReferendumVoterRemote(
@SerializedName("voter") val voterId: String,
val delegateId: String,
override val standardVote: StandardVoteRemote?,
override val splitVote: SplitVoteRemote?,
override val splitAbstainVote: SplitAbstainVoteRemote?,
val delegatorVotes: SubQueryNodes<ReferendumDelegatorVoteRemote>
) : MultiVoteRemote
class ReferendumDelegatorVoteRemote(
@SerializedName("delegator") val delegatorId: String,
val vote: VoteRemote
)
@@ -0,0 +1,22 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.response
import io.novafoundation.nova.common.data.network.subquery.SubQueryNodes
import java.math.BigInteger
class ReferendumVotesResponse(
val referendum: Referendum
) {
class Referendum(val trackId: BigInteger, val castingVotings: SubQueryNodes<Vote>)
class Vote(
override val standardVote: StandardVoteRemote?,
override val splitVote: SplitVoteRemote?,
override val splitAbstainVote: SplitAbstainVoteRemote?,
val delegatorVotes: SubQueryNodes<DelegatorVote>
) : MultiVoteRemote
class DelegatorVote(
val vote: VoteRemote
)
}
@@ -0,0 +1,50 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.response
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.AccountVote
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
import io.novafoundation.nova.runtime.multiNetwork.runtime.types.custom.vote.Vote
import io.novafoundation.nova.runtime.multiNetwork.runtime.types.custom.vote.mapConvictionFromString
import java.math.BigInteger
class StandardVoteRemote(val aye: Boolean, val vote: VoteRemote)
class SplitVoteRemote(val ayeAmount: Balance, val nayAmount: Balance)
class SplitAbstainVoteRemote(val ayeAmount: Balance, val nayAmount: Balance, val abstainAmount: Balance)
interface MultiVoteRemote {
val standardVote: StandardVoteRemote?
val splitVote: SplitVoteRemote?
val splitAbstainVote: SplitAbstainVoteRemote?
}
fun mapMultiVoteRemoteToAccountVote(vote: MultiVoteRemote): AccountVote {
val standard = vote.standardVote
val split = vote.splitVote
val splitAbstain = vote.splitAbstainVote
return when {
standard != null -> AccountVote.Standard(
balance = standard.vote.amount,
vote = Vote(
aye = standard.aye,
conviction = mapConvictionFromString(standard.vote.conviction)
)
)
split != null -> AccountVote.Split(
aye = split.ayeAmount,
nay = split.nayAmount
)
splitAbstain != null -> AccountVote.SplitAbstain(
aye = splitAbstain.ayeAmount,
nay = splitAbstain.nayAmount,
abstain = splitAbstain.abstainAmount
)
else -> AccountVote.Unsupported
}
}
class VoteRemote(val amount: BigInteger, val conviction: String)
@@ -0,0 +1,40 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v1
import io.novafoundation.nova.common.data.network.subquery.SubQueryResponse
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v1.request.ParachainReferendumDetailsRequest
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v1.request.ReferendumDetailsRequest
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v1.request.ParachainReferendumPreviewRequest
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v1.request.ReferendumPreviewRequest
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v1.response.ParachainReferendaPreviewResponse
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v1.response.ReferendaPreviewResponse
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v1.response.ReferendumDetailsResponse
import retrofit2.http.Body
import retrofit2.http.POST
import retrofit2.http.Url
interface PolkassemblyV1Api {
@POST
suspend fun getReferendumPreviews(
@Url url: String,
@Body body: ReferendumPreviewRequest
): SubQueryResponse<ReferendaPreviewResponse>
@POST
suspend fun getParachainReferendumPreviews(
@Url url: String,
@Body body: ParachainReferendumPreviewRequest
): SubQueryResponse<ParachainReferendaPreviewResponse>
@POST
suspend fun getReferendumDetails(
@Url url: String,
@Body body: ReferendumDetailsRequest
): SubQueryResponse<ReferendumDetailsResponse>
@POST
suspend fun getParachainReferendumDetails(
@Url url: String,
@Body body: ParachainReferendumDetailsRequest
): SubQueryResponse<ReferendumDetailsResponse>
}
@@ -0,0 +1,118 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v1
import io.novafoundation.nova.common.utils.formatting.parseDateISO_8601
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId
import io.novafoundation.nova.feature_governance_api.data.network.offchain.model.referendum.OffChainReferendumDetails
import io.novafoundation.nova.feature_governance_api.data.network.offchain.model.referendum.OffChainReferendumPreview
import io.novafoundation.nova.feature_governance_api.domain.referendum.details.ReferendumTimeline
import io.novafoundation.nova.feature_governance_impl.data.offchain.OffChainReferendaDataSource
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v1.request.ParachainReferendumDetailsRequest
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v1.request.ParachainReferendumPreviewRequest
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v1.request.ReferendumDetailsRequest
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v1.request.ReferendumPreviewRequest
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v1.response.ParachainReferendaPreviewResponse
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v1.response.ReferendaPreviewResponse
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v1.response.ReferendumDetailsResponse
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v1.response.getId
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain.ExternalApi.GovernanceReferenda.Source
class PolkassemblyV1ReferendaDataSource(
private val polkassemblyApi: PolkassemblyV1Api,
) : OffChainReferendaDataSource<Source.Polkassembly> {
override suspend fun referendumPreviews(baseUrl: String, options: Source.Polkassembly): List<OffChainReferendumPreview> {
val polkassemblyNetwork = options.network
return if (polkassemblyNetwork == null) {
referendaRelaychainRequest(baseUrl)
} else {
referendaParachainRequest(baseUrl, polkassemblyNetwork)
}
}
override suspend fun referendumDetails(referendumId: ReferendumId, baseUrl: String, options: Source.Polkassembly): OffChainReferendumDetails? {
val polkassemblyNetwork = options.network
val referendumDetails = if (polkassemblyNetwork == null) {
detailsRelaychain(baseUrl, referendumId)
} else {
detailsParachain(baseUrl, polkassemblyNetwork, referendumId)
}
return referendumDetails?.let(::mapPolkassemblyPostToDetails)
}
private suspend fun referendaRelaychainRequest(url: String): List<OffChainReferendumPreview> {
val request = ReferendumPreviewRequest()
val response = polkassemblyApi.getReferendumPreviews(url, request)
return response.data.posts.map {
mapPolkassemblyPostToPreview(it)
}
}
private suspend fun referendaParachainRequest(url: String, network: String): List<OffChainReferendumPreview> {
val request = ParachainReferendumPreviewRequest(network)
val response = polkassemblyApi.getParachainReferendumPreviews(url, request)
return response.data.posts.map {
mapParachainPolkassemblyPostToPreview(it)
}
}
private suspend fun detailsRelaychain(url: String, referendumId: ReferendumId): ReferendumDetailsResponse.Post? {
val request = ReferendumDetailsRequest(referendumId.value)
val response = polkassemblyApi.getReferendumDetails(url, request)
return response.data.posts.firstOrNull()
}
private suspend fun detailsParachain(url: String, network: String, referendumId: ReferendumId): ReferendumDetailsResponse.Post? {
val request = ParachainReferendumDetailsRequest(network, referendumId.value)
val response = polkassemblyApi.getParachainReferendumDetails(url, request)
return response.data.posts.firstOrNull()
}
private fun mapPolkassemblyPostToPreview(post: ReferendaPreviewResponse.Post): OffChainReferendumPreview {
return OffChainReferendumPreview(
post.title,
ReferendumId(post.getId()),
)
}
private fun mapParachainPolkassemblyPostToPreview(post: ParachainReferendaPreviewResponse.Post): OffChainReferendumPreview {
return OffChainReferendumPreview(
post.title,
ReferendumId(post.getId()),
)
}
private fun mapPolkassemblyPostToDetails(post: ReferendumDetailsResponse.Post): OffChainReferendumDetails {
val timeline = post.onchainLink
?.onchainReferendum
?.getOrNull(0)
?.referendumStatus
?.map {
mapReferendumStatusToTimelineEntry(it)
}
return OffChainReferendumDetails(
title = post.title,
description = post.content,
proposerName = null, // author of the post on PA might not be equal to on-chain submitter so we want to be safe here
proposerAddress = post.onchainLink?.proposerAddress,
timeLine = timeline
)
}
private fun mapReferendumStatusToTimelineEntry(status: ReferendumDetailsResponse.Status): ReferendumTimeline.Entry {
val timelineState = when (status.status) {
"Started" -> ReferendumTimeline.State.CREATED
"Passed" -> ReferendumTimeline.State.APPROVED
"NotPassed" -> ReferendumTimeline.State.REJECTED
"Executed" -> ReferendumTimeline.State.EXECUTED
else -> error("Unkonown referendum status")
}
val statusDate = parseDateISO_8601(status.blockNumber.startDateTime)
return ReferendumTimeline.Entry(timelineState, statusDate?.time)
}
}
@@ -0,0 +1,30 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v1.request
import java.math.BigInteger
class ParachainReferendumDetailsRequest(network: String, id: BigInteger) {
val query = """
query {
posts(
where: {onchain_link: {onchain_network_referendum_id: {_eq: "${network}_$id"}}}
) {
title
content
author {
username
}
onchain_link {
onchain_referendum {
referendumStatus {
blockNumber {
startDateTime
number
}
status
}
}
}
}
}
""".trimIndent()
}
@@ -0,0 +1,18 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v1.request
class ParachainReferendumPreviewRequest(networkName: String?) {
val query = """
query {
posts(
where: {type: {id: {_eq: 2}}, network: {_eq: $networkName}, onchain_link: {onchain_network_referendum_id: {_is_null: false}}}
) {
title
onchain_link {
onchain_referendum {
referendumId
}
}
}
}
""".trimIndent()
}
@@ -0,0 +1,31 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v1.request
import java.math.BigInteger
class ReferendumDetailsRequest(id: BigInteger) {
val query = """
query {
posts(
where: {onchain_link: {onchain_referendum_id: {_eq: $id}}}
) {
title
content
author {
username
}
onchain_link {
onchain_referendum {
referendumStatus {
blockNumber {
startDateTime
number
}
status
}
}
proposer_address
}
}
}
""".trimIndent()
}
@@ -0,0 +1,16 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v1.request
class ReferendumPreviewRequest {
val query = """
query {
posts(
where: {type: {id: {_eq: 2}}, onchain_link: {onchain_referendum_id: {_is_null: false}}}
) {
title
onchain_link {
onchain_referendum_id
}
}
}
""".trimIndent()
}
@@ -0,0 +1,22 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v1.response
import com.google.gson.annotations.SerializedName
import java.math.BigInteger
class ParachainReferendaPreviewResponse(
val posts: List<Post>
) {
class Post(
val title: String?,
@SerializedName("onchain_link") val onChainLink: OnChainLink
)
class OnChainLink(
@SerializedName("onchain_referendum") val onChainReferendum: List<OnChainReferendum>
)
class OnChainReferendum(
val referendumId: BigInteger
)
}
@@ -0,0 +1,18 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v1.response
import com.google.gson.annotations.SerializedName
import java.math.BigInteger
class ReferendaPreviewResponse(
val posts: List<Post>
) {
class Post(
val title: String?,
@SerializedName("onchain_link") val onChainLink: OnChainLink
)
class OnChainLink(
@SerializedName("onchain_referendum_id") val onChainReferendumId: BigInteger,
)
}
@@ -0,0 +1,11 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v1.response
import java.math.BigInteger
fun ParachainReferendaPreviewResponse.Post.getId(): BigInteger {
return onChainLink.onChainReferendum[0].referendumId
}
fun ReferendaPreviewResponse.Post.getId(): BigInteger {
return onChainLink.onChainReferendumId
}
@@ -0,0 +1,37 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v1.response
import com.google.gson.annotations.SerializedName
import java.math.BigInteger
class ReferendumDetailsResponse(
val posts: List<Post>
) {
class Post(
val title: String?,
val content: String,
val author: Author,
@SerializedName("onchain_link") val onchainLink: OnChainLink?
)
class Author(val username: String)
class OnChainLink(
@SerializedName("onchain_referendum") val onchainReferendum: List<OnChainReferendum>?,
@SerializedName("proposer_address") val proposerAddress: String,
)
class OnChainReferendum(
val referendumStatus: List<Status>
)
class Status(
val blockNumber: BlockNumber,
val status: String
)
class BlockNumber(
val startDateTime: String,
val number: BigInteger
)
}
@@ -0,0 +1,25 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v2
import io.novafoundation.nova.common.data.network.subquery.SubQueryResponse
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v2.request.ReferendumDetailsV2Request
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v2.request.ReferendumPreviewV2Request
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v2.response.ReferendaPreviewV2Response
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v2.response.ReferendumDetailsV2Response
import retrofit2.http.Body
import retrofit2.http.POST
import retrofit2.http.Url
interface PolkassemblyV2Api {
@POST
suspend fun getReferendumPreviews(
@Url url: String,
@Body body: ReferendumPreviewV2Request
): SubQueryResponse<ReferendaPreviewV2Response>
@POST
suspend fun getReferendumDetails(
@Url url: String,
@Body body: ReferendumDetailsV2Request
): SubQueryResponse<ReferendumDetailsV2Response>
}
@@ -0,0 +1,77 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v2
import io.novafoundation.nova.common.utils.formatting.parseDateISO_8601
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId
import io.novafoundation.nova.feature_governance_api.data.network.offchain.model.referendum.OffChainReferendumDetails
import io.novafoundation.nova.feature_governance_api.data.network.offchain.model.referendum.OffChainReferendumPreview
import io.novafoundation.nova.feature_governance_api.domain.referendum.details.ReferendumTimeline
import io.novafoundation.nova.feature_governance_impl.data.offchain.OffChainReferendaDataSource
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v2.request.ReferendumDetailsV2Request
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v2.request.ReferendumPreviewV2Request
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v2.response.ReferendaPreviewV2Response
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v2.response.ReferendumDetailsV2Response
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain.ExternalApi.GovernanceReferenda.Source
class PolkassemblyV2ReferendaDataSource(
private val polkassemblyApi: PolkassemblyV2Api
) : OffChainReferendaDataSource<Source.Polkassembly> {
override suspend fun referendumPreviews(baseUrl: String, options: Source.Polkassembly): List<OffChainReferendumPreview> {
val request = ReferendumPreviewV2Request()
val response = polkassemblyApi.getReferendumPreviews(baseUrl, request)
return response.data.posts.map(::mapPolkassemblyPostToPreview)
}
override suspend fun referendumDetails(referendumId: ReferendumId, baseUrl: String, options: Source.Polkassembly): OffChainReferendumDetails? {
val request = ReferendumDetailsV2Request(referendumId.value)
val response = polkassemblyApi.getReferendumDetails(baseUrl, request)
val referendumDetails = response.data.posts.firstOrNull()
return referendumDetails?.let(::mapPolkassemblyPostToDetails)
}
private fun mapPolkassemblyPostToPreview(post: ReferendaPreviewV2Response.Post): OffChainReferendumPreview {
return OffChainReferendumPreview(
title = post.title,
referendumId = ReferendumId(post.onChainLink.onChainReferendumId),
)
}
private fun mapPolkassemblyPostToDetails(post: ReferendumDetailsV2Response.Post): OffChainReferendumDetails {
val timeline = post.onchainLink.onchainReferendum
.firstOrNull()
?.referendumStatus
?.mapNotNull {
mapReferendumStatusToTimelineEntry(it)
}
return OffChainReferendumDetails(
title = post.title,
description = post.content,
proposerAddress = null,
proposerName = post.author.username,
timeLine = timeline
)
}
private fun mapReferendumStatusToTimelineEntry(status: ReferendumDetailsV2Response.Status): ReferendumTimeline.Entry? {
val timelineState = when (status.status) {
"Submitted" -> ReferendumTimeline.State.CREATED
"Ongoing" -> ReferendumTimeline.State.CREATED
"Approved" -> ReferendumTimeline.State.APPROVED
"Rejected" -> ReferendumTimeline.State.REJECTED
"Cancelled" -> ReferendumTimeline.State.CANCELLED
"TimedOut" -> ReferendumTimeline.State.TIMED_OUT
"Killed" -> ReferendumTimeline.State.KILLED
"Executed" -> ReferendumTimeline.State.EXECUTED
else -> null
}
val statusDate = parseDateISO_8601(status.blockNumber.startDateTime)
return timelineState?.let {
ReferendumTimeline.Entry(timelineState, statusDate?.time)
}
}
}
@@ -0,0 +1,30 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v2.request
import java.math.BigInteger
class ReferendumDetailsV2Request(id: BigInteger) {
val query = """
query {
posts(
where: {onchain_link: {onchain_referendumv2_id: {_eq: $id}}}
) {
title
content
author {
username
}
onchain_link {
onchain_referendumv2 {
referendumStatus {
blockNumber {
startDateTime
number
}
status
}
}
}
}
}
""".trimIndent()
}
@@ -0,0 +1,16 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v2.request
class ReferendumPreviewV2Request {
val query = """
query {
posts(
where: {onchain_link: {onchain_referendumv2_id: {_is_null: false}}}
) {
title
onchain_link {
onchain_referendumv2_id
}
}
}
""".trimIndent()
}
@@ -0,0 +1,16 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v2.response
import com.google.gson.annotations.SerializedName
import java.math.BigInteger
class ReferendaPreviewV2Response(
val posts: List<Post>
) {
class Post(
val title: String?,
@SerializedName("onchain_link") val onChainLink: OnChainLink
)
class OnChainLink(@SerializedName("onchain_referendumv2_id") val onChainReferendumId: BigInteger)
}
@@ -0,0 +1,36 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v2.response
import com.google.gson.annotations.SerializedName
import java.math.BigInteger
class ReferendumDetailsV2Response(
val posts: List<Post>
) {
class Post(
val title: String?,
val content: String,
val author: Author,
@SerializedName("onchain_link") val onchainLink: OnChainLink
)
class Author(val username: String)
class OnChainLink(
@SerializedName("onchain_referendumv2") val onchainReferendum: List<OnChainReferendum>
)
class OnChainReferendum(
val referendumStatus: List<Status>
)
class Status(
val blockNumber: BlockNumber,
val status: String
)
class BlockNumber(
val startDateTime: String,
val number: BigInteger
)
}
@@ -0,0 +1,19 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.subsquare.v1
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.subsquare.v1.response.ReferendaPreviewV1Response
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.subsquare.v1.response.ReferendumDetailsV1Response
import retrofit2.http.GET
import retrofit2.http.Query
import retrofit2.http.Url
interface SubSquareV1Api {
@GET
suspend fun getReferendumPreviews(
@Url url: String,
@Query("page_size") pageSize: Int = 1000
): ReferendaPreviewV1Response
@GET
suspend fun getReferendumDetails(@Url url: String): ReferendumDetailsV1Response
}
@@ -0,0 +1,69 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.subsquare.v1
import io.novafoundation.nova.common.utils.ensureSuffix
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId
import io.novafoundation.nova.feature_governance_api.data.network.offchain.model.referendum.OffChainReferendumDetails
import io.novafoundation.nova.feature_governance_api.data.network.offchain.model.referendum.OffChainReferendumPreview
import io.novafoundation.nova.feature_governance_api.domain.referendum.details.ReferendumTimeline
import io.novafoundation.nova.feature_governance_impl.data.offchain.OffChainReferendaDataSource
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.subsquare.v1.response.ReferendaPreviewV1Response
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.subsquare.v1.response.ReferendumDetailsV1Response
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain.ExternalApi.GovernanceReferenda.Source
class SubSquareV1ReferendaDataSource(
private val subSquareApi: SubSquareV1Api,
) : OffChainReferendaDataSource<Source.SubSquare> {
override suspend fun referendumPreviews(baseUrl: String, options: Source.SubSquare): List<OffChainReferendumPreview> {
val fullUrl = previewsUrlOf(baseUrl)
val response = subSquareApi.getReferendumPreviews(fullUrl)
return response.items.map(::mapReferendumPreviewResponseToPreview)
}
override suspend fun referendumDetails(referendumId: ReferendumId, baseUrl: String, options: Source.SubSquare): OffChainReferendumDetails? {
val fullUrl = detailsUrlOf(baseUrl, referendumId)
val response = subSquareApi.getReferendumDetails(fullUrl)
return mapReferendumDetailsResponseToDetails(response)
}
private fun mapReferendumPreviewResponseToPreview(post: ReferendaPreviewV1Response.Referendum): OffChainReferendumPreview {
return OffChainReferendumPreview(
title = post.title,
referendumId = ReferendumId(post.referendumIndex),
)
}
private fun mapReferendumDetailsResponseToDetails(referendum: ReferendumDetailsV1Response): OffChainReferendumDetails {
val timeline = referendum.onchainData.timeline.mapNotNull(::mapReferendumStatusToTimelineEntry)
return OffChainReferendumDetails(
title = referendum.title,
description = referendum.content,
proposerAddress = referendum.author?.address,
proposerName = referendum.author?.username,
timeLine = timeline
)
}
private fun mapReferendumStatusToTimelineEntry(status: ReferendumDetailsV1Response.Status): ReferendumTimeline.Entry? {
val timelineState = when (status.method) {
"Started" -> ReferendumTimeline.State.CREATED
"Passed" -> ReferendumTimeline.State.APPROVED
"NotPassed" -> ReferendumTimeline.State.REJECTED
"Executed" -> ReferendumTimeline.State.EXECUTED
else -> null
}
return timelineState?.let {
ReferendumTimeline.Entry(timelineState, status.indexer.blockTime)
}
}
private fun previewsUrlOf(baseUrl: String) = baseUrl.ensureSuffix("/") + "democracy/referendums"
private fun detailsUrlOf(baseUrl: String, referendumId: ReferendumId) = baseUrl.ensureSuffix("/") + "democracy/referendums/${referendumId.value}"
}
@@ -0,0 +1,13 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.subsquare.v1.response
import java.math.BigInteger
class ReferendaPreviewV1Response(
val items: List<Referendum>
) {
class Referendum(
val title: String?,
val referendumIndex: BigInteger
)
}
@@ -0,0 +1,22 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.subsquare.v1.response
class ReferendumDetailsV1Response(
val title: String?,
val content: String?,
val author: Author?,
val onchainData: OnChainData
) {
class Author(val username: String?, val address: String?)
class OnChainData(val timeline: List<Status>)
class Status(
val indexer: IndexerState,
val method: String
)
class IndexerState(
val blockTime: Long
)
}
@@ -0,0 +1,19 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.subsquare.v2
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.subsquare.v2.response.ReferendaPreviewV2Response
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.subsquare.v2.response.ReferendumDetailsV2Response
import retrofit2.http.GET
import retrofit2.http.Query
import retrofit2.http.Url
interface SubSquareV2Api {
@GET
suspend fun getReferendumPreviews(
@Url url: String,
@Query("page_size") pageSize: Int = 100
): ReferendaPreviewV2Response
@GET
suspend fun getReferendumDetails(@Url url: String): ReferendumDetailsV2Response
}
@@ -0,0 +1,72 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.subsquare.v2
import io.novafoundation.nova.common.utils.ensureSuffix
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId
import io.novafoundation.nova.feature_governance_api.data.network.offchain.model.referendum.OffChainReferendumDetails
import io.novafoundation.nova.feature_governance_api.data.network.offchain.model.referendum.OffChainReferendumPreview
import io.novafoundation.nova.feature_governance_api.domain.referendum.details.ReferendumTimeline
import io.novafoundation.nova.feature_governance_impl.data.offchain.OffChainReferendaDataSource
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.subsquare.v2.response.ReferendaPreviewV2Response
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.subsquare.v2.response.ReferendumDetailsV2Response
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain.ExternalApi.GovernanceReferenda.Source
class SubSquareV2ReferendaDataSource(
private val subSquareApi: SubSquareV2Api
) : OffChainReferendaDataSource<Source.SubSquare> {
override suspend fun referendumPreviews(baseUrl: String, options: Source.SubSquare): List<OffChainReferendumPreview> {
val fullUrl = previewsUrlOf(baseUrl)
val response = subSquareApi.getReferendumPreviews(fullUrl)
return response.items.map(::mapPolkassemblyPostToPreview)
}
override suspend fun referendumDetails(referendumId: ReferendumId, baseUrl: String, options: Source.SubSquare): OffChainReferendumDetails {
val detailsUrl = detailsUrlOf(baseUrl, referendumId)
val referendaDetails = subSquareApi.getReferendumDetails(detailsUrl)
return mapPolkassemblyPostToDetails(referendaDetails)
}
private fun mapPolkassemblyPostToPreview(post: ReferendaPreviewV2Response.Referendum): OffChainReferendumPreview {
return OffChainReferendumPreview(
title = post.title,
referendumId = ReferendumId(post.referendumIndex),
)
}
private fun mapPolkassemblyPostToDetails(
referendum: ReferendumDetailsV2Response
): OffChainReferendumDetails {
val timeline = referendum.onchainData.timeline.mapNotNull(::mapReferendumStatusToTimelineEntry)
return OffChainReferendumDetails(
title = referendum.title,
description = referendum.content,
proposerAddress = referendum.author?.address,
proposerName = referendum.author?.username,
timeLine = timeline
)
}
private fun mapReferendumStatusToTimelineEntry(status: ReferendumDetailsV2Response.Status): ReferendumTimeline.Entry? {
val timelineState = when (status.name) {
"Submitted" -> ReferendumTimeline.State.CREATED
"Confirmed" -> ReferendumTimeline.State.APPROVED
"Rejected" -> ReferendumTimeline.State.REJECTED
"Cancelled" -> ReferendumTimeline.State.CANCELLED
"TimedOut" -> ReferendumTimeline.State.TIMED_OUT
"Killed" -> ReferendumTimeline.State.KILLED
else -> null
}
return timelineState?.let {
ReferendumTimeline.Entry(timelineState, status.indexer.blockTime)
}
}
private fun previewsUrlOf(baseUrl: String) = baseUrl.ensureSuffix("/") + "gov2/referendums"
private fun detailsUrlOf(baseUrl: String, referendumId: ReferendumId) = baseUrl.ensureSuffix("/") + "gov2/referendums/${referendumId.value}"
}
@@ -0,0 +1,13 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.subsquare.v2.response
import java.math.BigInteger
class ReferendaPreviewV2Response(
val items: List<Referendum>
) {
class Referendum(
val title: String?,
val referendumIndex: BigInteger
)
}
@@ -0,0 +1,22 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.subsquare.v2.response
class ReferendumDetailsV2Response(
val title: String?,
val content: String?,
val author: Author?,
val onchainData: OnChainData
) {
class Author(val username: String?, val address: String?)
class OnChainData(val timeline: List<Status>)
class Status(
val indexer: IndexerState,
val name: String
)
class IndexerState(
val blockTime: Long
)
}
@@ -0,0 +1,8 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.subsquare.v2.response
import java.math.BigInteger
class ReferendumVoteV2Response(
val isSplitAbstain: Boolean,
val abstainVotes: BigInteger?
)
@@ -0,0 +1,16 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.request.ReferendumSummariesRequest
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.response.ReferendumSummaryResponse
import retrofit2.http.Body
import retrofit2.http.POST
import retrofit2.http.Url
interface ReferendumSummaryApi {
@POST
suspend fun getReferendumSummaries(
@Url url: String,
@Body body: ReferendumSummariesRequest
): List<ReferendumSummaryResponse>
}
@@ -0,0 +1,32 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.request.ReferendumSummariesRequest
import io.novafoundation.nova.runtime.ext.summaryApiOrNull
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
interface ReferendumSummaryDataSource {
suspend fun loadSummaries(chain: Chain, ids: List<ReferendumId>, languageCode: String): Map<ReferendumId, String>?
}
class RealReferendumSummaryDataSource(
val api: ReferendumSummaryApi
) : ReferendumSummaryDataSource {
override suspend fun loadSummaries(chain: Chain, ids: List<ReferendumId>, languageCode: String): Map<ReferendumId, String>? {
val summaryApi = chain.summaryApiOrNull() ?: return null
val response = api.getReferendumSummaries(
summaryApi.url,
ReferendumSummariesRequest(
chainId = chain.id,
languageIsoCode = languageCode,
referendumIds = ids.map { it.value.toString() }
)
)
return response.associateBy { ReferendumId(it.referendumId.toBigInteger()) }
.mapValues { it.value.summary }
}
}
@@ -0,0 +1,7 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.request
class ReferendumSummariesRequest(
val chainId: String,
val languageIsoCode: String,
val referendumIds: List<String>
)
@@ -0,0 +1,3 @@
package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.response
class ReferendumSummaryResponse(val referendumId: Int, val summary: String)
@@ -0,0 +1,25 @@
package io.novafoundation.nova.feature_governance_impl.data.preimage
import io.novafoundation.nova.common.utils.kilobytes
import java.math.BigInteger
interface PreImageSizer {
enum class SizeConstraint {
SMALL
}
fun satisfiesSizeConstraint(preImageSize: BigInteger, constraint: SizeConstraint): Boolean
}
class RealPreImageSizer : PreImageSizer {
override fun satisfiesSizeConstraint(preImageSize: BigInteger, constraint: PreImageSizer.SizeConstraint): Boolean {
return preImageSize < constraint.threshold
}
private val PreImageSizer.SizeConstraint.threshold: BigInteger
get() = when (this) {
PreImageSizer.SizeConstraint.SMALL -> 1.kilobytes
}
}
@@ -0,0 +1,52 @@
package io.novafoundation.nova.feature_governance_impl.data.repository
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId
import io.novafoundation.nova.feature_governance_api.data.network.offchain.model.referendum.OffChainReferendumDetails
import io.novafoundation.nova.feature_governance_api.data.network.offchain.model.referendum.OffChainReferendumPreview
import io.novafoundation.nova.feature_governance_api.data.repository.OffChainReferendaInfoRepository
import io.novafoundation.nova.feature_governance_impl.data.offchain.OffChainReferendaDataSource
import io.novafoundation.nova.runtime.ext.externalApi
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain.ExternalApi.GovernanceReferenda.Source
class MultiSourceOffChainReferendaInfoRepository(
private val subSquareReferendaDataSource: OffChainReferendaDataSource<Source.SubSquare>,
private val polkassemblyReferendaDataSource: OffChainReferendaDataSource<Source.Polkassembly>
) : OffChainReferendaInfoRepository {
override suspend fun referendumPreviews(chain: Chain): List<OffChainReferendumPreview> {
return runCatching {
val dataSource = chain.carriedGovernanceDataSource() ?: return emptyList()
dataSource.referendumPreviews()
}.getOrDefault(emptyList())
}
override suspend fun referendumDetails(referendumId: ReferendumId, chain: Chain): OffChainReferendumDetails? {
return runCatching {
val dataSource = chain.carriedGovernanceDataSource() ?: return null
dataSource.referendumDetails(referendumId)
}.getOrNull()
}
private fun Chain.carriedGovernanceDataSource(): OffChainReferendaDataSourceCarried<*>? {
val governanceApi = externalApi<Chain.ExternalApi.GovernanceReferenda>() ?: return null
val baseUrl = governanceApi.url
return when (val source = governanceApi.source) {
is Source.Polkassembly -> OffChainReferendaDataSourceCarried(polkassemblyReferendaDataSource, baseUrl, source)
is Source.SubSquare -> OffChainReferendaDataSourceCarried(subSquareReferendaDataSource, baseUrl, source)
}
}
private class OffChainReferendaDataSourceCarried<O>(
private val dataSource: OffChainReferendaDataSource<O>,
private val baseUrl: String,
private val options: O
) {
suspend fun referendumPreviews(): List<OffChainReferendumPreview> = dataSource.referendumPreviews(baseUrl, options)
suspend fun referendumDetails(referendumId: ReferendumId): OffChainReferendumDetails? = dataSource.referendumDetails(referendumId, baseUrl, options)
}
}
@@ -0,0 +1,37 @@
package io.novafoundation.nova.feature_governance_impl.data.repository
import io.novafoundation.nova.common.data.network.runtime.binding.bindAccountId
import io.novafoundation.nova.common.data.network.runtime.binding.bindNumber
import io.novafoundation.nova.common.data.network.runtime.binding.castToStructOrNull
import io.novafoundation.nova.common.utils.treasury
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.TreasuryProposal
import io.novafoundation.nova.feature_governance_api.data.repository.TreasuryRepository
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.storage
class RealTreasuryRepository(
private val remoteSource: StorageDataSource
) : TreasuryRepository {
override suspend fun getTreasuryProposal(chainId: ChainId, id: TreasuryProposal.Id): TreasuryProposal? {
return remoteSource.query(chainId) {
runtime.metadata.treasury().storage("Proposals").query(
id.value,
binding = { bindProposal(id, it) }
)
}
}
private fun bindProposal(id: TreasuryProposal.Id, decoded: Any?): TreasuryProposal? {
val asStruct = decoded.castToStructOrNull() ?: return null
return TreasuryProposal(
id = id,
proposer = bindAccountId(asStruct["proposer"]),
amount = bindNumber(asStruct["value"]),
beneficiary = bindAccountId(asStruct["beneficiary"]),
bond = bindNumber(asStruct["bond"])
)
}
}
@@ -0,0 +1,29 @@
package io.novafoundation.nova.feature_governance_impl.data.repository
import io.novafoundation.nova.common.data.storage.Preferences
interface RemoveVotesSuggestionRepository {
fun isAllowedToShowRemoveVotesSuggestion(): Boolean
suspend fun disallowShowRemoveVotesSuggestion()
}
class RealRemoveVotesSuggestionRepository(
private val preferences: Preferences
) : RemoveVotesSuggestionRepository {
companion object {
private const val PREFS_KEY = "RemoveVotesSuggestionRepository.ShouldShowSuggestion"
private const val ALLOWED_DEFAULT = true
}
override fun isAllowedToShowRemoveVotesSuggestion(): Boolean {
return preferences.getBoolean(PREFS_KEY, ALLOWED_DEFAULT)
}
override suspend fun disallowShowRemoveVotesSuggestion() {
preferences.putBoolean(PREFS_KEY, value = false)
}
}
@@ -0,0 +1,79 @@
package io.novafoundation.nova.feature_governance_impl.data.repository
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.Delegation
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.TrackId
import io.novafoundation.nova.feature_governance_api.data.network.offchain.model.delegation.DelegateDetailedStats
import io.novafoundation.nova.feature_governance_api.data.network.offchain.model.delegation.DelegateMetadata
import io.novafoundation.nova.feature_governance_api.data.network.offchain.model.delegation.DelegateStats
import io.novafoundation.nova.feature_governance_api.data.network.offchain.model.vote.UserVote
import io.novafoundation.nova.feature_governance_api.data.repository.DelegationsRepository
import io.novafoundation.nova.feature_governance_api.data.repository.common.RecentVotesDateThreshold
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
import io.novafoundation.nova.runtime.extrinsic.multi.CallBuilder
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novafoundation.nova.runtime.multiNetwork.runtime.types.custom.vote.Conviction
import io.novasama.substrate_sdk_android.runtime.AccountId
class UnsupportedDelegationsRepository : DelegationsRepository {
override suspend fun isDelegationSupported(chain: Chain): Boolean {
return false
}
override suspend fun getDelegatesStats(recentVotesBlockThreshold: RecentVotesDateThreshold, chain: Chain): List<DelegateStats> {
return emptyList()
}
override suspend fun getDelegatesStatsByAccountIds(
recentVotesBlockThreshold: RecentVotesDateThreshold,
accountIds: List<AccountId>,
chain: Chain
): List<DelegateStats> {
return emptyList()
}
override suspend fun getDetailedDelegateStats(
delegateAddress: String,
recentVotesDateThreshold: RecentVotesDateThreshold,
chain: Chain
): DelegateDetailedStats? {
return null
}
override suspend fun getDelegatesMetadata(chain: Chain): List<DelegateMetadata> {
return emptyList()
}
override suspend fun getDelegateMetadata(chain: Chain, delegate: AccountId): DelegateMetadata? {
return null
}
override suspend fun getDelegationsTo(delegate: AccountId, chain: Chain): List<Delegation> {
return emptyList()
}
override suspend fun allHistoricalVotesOf(user: AccountId, chain: Chain): Map<ReferendumId, UserVote>? {
return null
}
override suspend fun historicalVoteOf(user: AccountId, referendumId: ReferendumId, chain: Chain): UserVote? {
return null
}
override suspend fun directHistoricalVotesOf(
user: AccountId,
chain: Chain,
recentVotesDateThreshold: RecentVotesDateThreshold?
): Map<ReferendumId, UserVote.Direct>? {
return null
}
override suspend fun CallBuilder.delegate(delegate: AccountId, trackId: TrackId, amount: Balance, conviction: Conviction) {
error("Unsupported")
}
override suspend fun CallBuilder.undelegate(trackId: TrackId) {
error("Unsupported")
}
}
@@ -0,0 +1,174 @@
package io.novafoundation.nova.feature_governance_impl.data.repository.common
import io.novafoundation.nova.common.data.network.runtime.binding.bindAccountId
import io.novafoundation.nova.common.data.network.runtime.binding.bindBlockNumber
import io.novafoundation.nova.common.data.network.runtime.binding.bindByteArray
import io.novafoundation.nova.common.data.network.runtime.binding.bindCollectionEnum
import io.novafoundation.nova.common.data.network.runtime.binding.bindList
import io.novafoundation.nova.common.data.network.runtime.binding.bindNumber
import io.novafoundation.nova.common.data.network.runtime.binding.cast
import io.novafoundation.nova.common.data.network.runtime.binding.castToDictEnum
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.data.network.runtime.binding.incompatible
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.AccountVote
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.PriorLock
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.Proposal
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.Tally
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.Voting
import io.novafoundation.nova.runtime.multiNetwork.runtime.types.custom.vote.Conviction
import io.novafoundation.nova.runtime.multiNetwork.runtime.types.custom.vote.Vote
import io.novasama.substrate_sdk_android.runtime.RuntimeSnapshot
import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.DictEnum
import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.Struct
import io.novasama.substrate_sdk_android.runtime.definitions.types.fromByteArray
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericCall
fun bindProposal(decoded: Any?, runtimeSnapshot: RuntimeSnapshot): Proposal {
return when (decoded) {
is ByteArray -> bindProposalLegacy(decoded)
is DictEnum.Entry<*> -> bindProposalBound(decoded, runtimeSnapshot)
else -> incompatible()
}
}
private fun bindProposalLegacy(decoded: ByteArray): Proposal {
return Proposal.Legacy(decoded)
}
private fun bindProposalBound(decoded: DictEnum.Entry<*>, runtime: RuntimeSnapshot): Proposal {
return when (decoded.name) {
"Legacy" -> {
val valueAsStruct = decoded.value.castToStruct()
Proposal.Legacy(bindByteArray(valueAsStruct["hash"]))
}
"Inline" -> {
val bytes = bindByteArray(decoded.value)
val call = GenericCall.fromByteArray(runtime, bytes)
Proposal.Inline(bytes, call)
}
"Lookup" -> {
val valueAsStruct = decoded.value.castToStruct()
Proposal.Lookup(
hash = bindByteArray(valueAsStruct["hash"]),
callLength = bindNumber(valueAsStruct["len"])
)
}
else -> incompatible()
}
}
fun bindTally(decoded: Struct.Instance): Tally {
return Tally(
ayes = bindNumber(decoded["ayes"]),
nays = bindNumber(decoded["nays"]),
support = bindNumber(decoded["support"] ?: decoded["turnout"])
)
}
fun bindVoting(decoded: Any): Voting {
decoded.castToDictEnum()
return when (decoded.name) {
"Casting", "Direct" -> {
val casting = decoded.value.castToStruct()
val votes = bindVotes(casting["votes"])
val prior = bindPriorLock(casting["prior"])
Voting.Casting(votes, prior)
}
"Delegating" -> {
val delegating = decoded.value.castToStruct()
val balance = bindNumber(delegating["balance"])
val target = bindAccountId(delegating["target"])
val conviction = bindConvictionEnum(delegating["conviction"])
val prior = bindPriorLock(delegating["prior"])
Voting.Delegating(balance, target, conviction, prior)
}
else -> incompatible()
}
}
fun bindConvictionEnum(decoded: Any?): Conviction {
return bindCollectionEnum(decoded) { name ->
when (name) {
"None" -> Conviction.None
"Locked1x" -> Conviction.Locked1x
"Locked2x" -> Conviction.Locked2x
"Locked3x" -> Conviction.Locked3x
"Locked4x" -> Conviction.Locked4x
"Locked5x" -> Conviction.Locked5x
"Locked6x" -> Conviction.Locked6x
else -> incompatible()
}
}
}
private fun bindVotes(decoded: Any?): Map<ReferendumId, AccountVote> {
return bindList(decoded) { item ->
val (referendumId, accountVote) = item.castToList()
ReferendumId(bindNumber(referendumId)) to bindAccountVote(accountVote)
}.toMap()
}
private fun bindAccountVote(decoded: Any?): AccountVote {
decoded.castToDictEnum()
return when (decoded.name) {
"Standard" -> {
val standardVote = decoded.value.castToStruct()
AccountVote.Standard(
vote = bindVote(standardVote["vote"]),
balance = bindNumber(standardVote["balance"])
)
}
"Split" -> {
val splitVote = decoded.value.castToStruct()
AccountVote.Split(
aye = bindNumber(splitVote["aye"]),
nay = bindNumber(splitVote["nay"])
)
}
"SplitAbstain" -> {
val splitVote = decoded.value.castToStruct()
AccountVote.SplitAbstain(
aye = bindNumber(splitVote["aye"]),
nay = bindNumber(splitVote["nay"]),
abstain = bindNumber(splitVote["abstain"])
)
}
else -> AccountVote.Unsupported
}
}
private fun bindPriorLock(decoded: Any?): PriorLock {
// 2-tuple
val (unlockAt, amount) = decoded.castToList()
return PriorLock(
unlockAt = bindBlockNumber(unlockAt),
amount = bindNumber(amount)
)
}
private fun bindVote(decoded: Any?): Vote {
return decoded.cast()
}
@@ -0,0 +1,88 @@
package io.novafoundation.nova.feature_governance_impl.data.repository.common
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.amountMultiplier
import io.novafoundation.nova.feature_governance_api.data.network.offchain.model.referendum.OffChainReferendumVotingDetails.VotingInfo
import io.novafoundation.nova.feature_governance_api.data.network.offchain.model.referendum.empty
import io.novafoundation.nova.feature_governance_api.data.network.offchain.model.referendum.plus
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.response.ReferendumVotesResponse
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.response.SplitAbstainVoteRemote
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.response.SplitVoteRemote
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.response.StandardVoteRemote
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
import io.novafoundation.nova.runtime.multiNetwork.runtime.types.custom.vote.Conviction
import io.novafoundation.nova.runtime.multiNetwork.runtime.types.custom.vote.mapConvictionFromString
import java.math.BigDecimal
fun StandardVoteRemote?.toOffChainVotes(): VotingInfo.Full {
if (this == null) return VotingInfo.Full.empty()
val conviction = mapConvictionFromString(this.vote.conviction)
val amount = this.vote.amount.toBigDecimal() * conviction.amountMultiplier()
return VotingInfo.Full(
aye = if (this.aye) amount else BigDecimal.ZERO,
nay = if (!this.aye) amount else BigDecimal.ZERO,
abstain = BigDecimal.ZERO,
support = this.vote.amount
)
}
fun SplitVoteRemote?.toOffChainVotes(): VotingInfo.Full {
if (this == null) return VotingInfo.Full.empty()
val amountMultiplier = Conviction.None.amountMultiplier()
return VotingInfo.Full(
aye = this.ayeAmount.toBigDecimal() * amountMultiplier,
nay = this.nayAmount.toBigDecimal() * amountMultiplier,
abstain = BigDecimal.ZERO,
support = this.ayeAmount + this.nayAmount
)
}
fun SplitAbstainVoteRemote?.toOffChainVotes(): VotingInfo.Full {
if (this == null) return VotingInfo.Full.empty()
val amountMultiplier = Conviction.None.amountMultiplier()
return VotingInfo.Full(
aye = this.ayeAmount.toBigDecimal() * amountMultiplier,
nay = this.nayAmount.toBigDecimal() * amountMultiplier,
abstain = this.abstainAmount.toBigDecimal() * amountMultiplier,
support = this.ayeAmount + this.nayAmount + this.abstainAmount
)
}
fun ReferendumVotesResponse.Vote.toOffChainVotes(): VotingInfo.Full {
var delegatorsVoteSum = BigDecimal.ZERO
var delegatorSupportSum = Balance.ZERO
this.delegatorVotes.nodes.forEach { delegatorVote ->
val conviction = mapConvictionFromString(delegatorVote.vote.conviction)
delegatorsVoteSum += delegatorVote.vote.amount.toBigDecimal() * conviction.amountMultiplier()
delegatorSupportSum += delegatorVote.vote.amount
}
return standardVote.toOffChainVotes() + splitVote.toOffChainVotes() + splitAbstainVote.toOffChainVotes() + getDelegationVotes()
}
private fun ReferendumVotesResponse.Vote.getDelegationVotes(): VotingInfo.Full {
return if (standardVote != null) {
var delegatorsVoteSum = BigDecimal.ZERO
var delegatorSupportSum = Balance.ZERO
this.delegatorVotes.nodes.forEach { delegatorVote ->
val conviction = mapConvictionFromString(delegatorVote.vote.conviction)
delegatorsVoteSum += delegatorVote.vote.amount.toBigDecimal() * conviction.amountMultiplier()
delegatorSupportSum += delegatorVote.vote.amount
}
return VotingInfo.Full(
aye = if (standardVote.aye) delegatorsVoteSum else BigDecimal.ZERO,
nay = if (!standardVote.aye) delegatorsVoteSum else BigDecimal.ZERO,
abstain = BigDecimal.ZERO,
support = delegatorSupportSum
)
} else {
VotingInfo.Full.empty()
}
}
@@ -0,0 +1,23 @@
package io.novafoundation.nova.feature_governance_impl.data.repository.common
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumVoter
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.Voting
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.votes
import io.novafoundation.nova.runtime.storage.source.query.StorageKeyComponents
import io.novasama.substrate_sdk_android.runtime.AccountId
fun Map<StorageKeyComponents, Voting?>.votersFor(referendumId: ReferendumId): List<ReferendumVoter> {
return mapNotNull { (keyComponents, voting) ->
val voterId = keyComponents.component1<AccountId>()
val votes = voting?.votes()
votes?.get(referendumId)?.let { accountVote ->
ReferendumVoter(
accountId = voterId,
vote = accountVote,
delegators = emptyList()
)
}
}
}
@@ -0,0 +1,38 @@
package io.novafoundation.nova.feature_governance_impl.data.repository.filters
import io.novafoundation.nova.feature_governance_api.domain.referendum.filters.ReferendumType
import io.novafoundation.nova.feature_governance_api.domain.referendum.filters.ReferendumTypeFilter
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
private const val PREF_REFERENDUM_TYPE_FILTER = "PREF_REFERENDUM_TYPE_FILTER"
interface ReferendaFiltersRepository {
fun getReferendumTypeFilter(): ReferendumTypeFilter
fun observeReferendumTypeFilter(): Flow<ReferendumTypeFilter>
fun updateReferendumTypeFilter(filter: ReferendumTypeFilter)
}
class PreferencesReferendaFiltersRepository : ReferendaFiltersRepository {
private var referendumTypeFilter = MutableStateFlow(getDefaultReferendaTypeFilter())
override fun getReferendumTypeFilter(): ReferendumTypeFilter {
return referendumTypeFilter.value
}
override fun observeReferendumTypeFilter(): Flow<ReferendumTypeFilter> {
return referendumTypeFilter
}
override fun updateReferendumTypeFilter(filter: ReferendumTypeFilter) {
referendumTypeFilter.value = filter
}
private fun getDefaultReferendaTypeFilter(): ReferendumTypeFilter {
return ReferendumTypeFilter(ReferendumType.ALL)
}
}
@@ -0,0 +1,24 @@
package io.novafoundation.nova.feature_governance_impl.data.repository.tindergov
import io.novafoundation.nova.core_db.model.common.ConvictionLocal
import io.novafoundation.nova.runtime.multiNetwork.runtime.types.custom.vote.Conviction
fun Conviction.toLocal() = when (this) {
Conviction.None -> ConvictionLocal.NONE
Conviction.Locked1x -> ConvictionLocal.LOCKED_1X
Conviction.Locked2x -> ConvictionLocal.LOCKED_2X
Conviction.Locked3x -> ConvictionLocal.LOCKED_3X
Conviction.Locked4x -> ConvictionLocal.LOCKED_4X
Conviction.Locked5x -> ConvictionLocal.LOCKED_5X
Conviction.Locked6x -> ConvictionLocal.LOCKED_6X
}
fun ConvictionLocal.toDomain() = when (this) {
ConvictionLocal.NONE -> Conviction.None
ConvictionLocal.LOCKED_1X -> Conviction.Locked1x
ConvictionLocal.LOCKED_2X -> Conviction.Locked2x
ConvictionLocal.LOCKED_3X -> Conviction.Locked3x
ConvictionLocal.LOCKED_4X -> Conviction.Locked4x
ConvictionLocal.LOCKED_5X -> Conviction.Locked5x
ConvictionLocal.LOCKED_6X -> Conviction.Locked6x
}
@@ -0,0 +1,96 @@
package io.novafoundation.nova.feature_governance_impl.data.repository.tindergov
import io.novafoundation.nova.common.utils.mapList
import io.novafoundation.nova.core_db.dao.TinderGovDao
import io.novafoundation.nova.core_db.model.TinderGovBasketItemLocal
import io.novafoundation.nova.feature_governance_api.data.model.TinderGovBasketItem
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId
import io.novafoundation.nova.core_db.model.TinderGovBasketItemLocal.VoteType as LocalVoteType
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.VoteType
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.withContext
interface TinderGovBasketRepository {
suspend fun add(item: TinderGovBasketItem)
suspend fun remove(item: TinderGovBasketItem)
suspend fun remove(items: Collection<TinderGovBasketItem>)
suspend fun isBasketEmpty(metaId: Long, chainId: ChainId): Boolean
suspend fun clearBasket(metaId: Long, chainId: ChainId)
suspend fun getBasket(metaId: Long, chainId: ChainId): List<TinderGovBasketItem>
fun observeBasket(metaId: Long, chainId: String): Flow<List<TinderGovBasketItem>>
}
class RealTinderGovBasketRepository(private val dao: TinderGovDao) : TinderGovBasketRepository {
override suspend fun add(item: TinderGovBasketItem) {
dao.addToBasket(item.toLocal())
}
override suspend fun remove(item: TinderGovBasketItem) {
withContext(Dispatchers.Default) { dao.removeFromBasket(item.toLocal()) }
}
override suspend fun remove(items: Collection<TinderGovBasketItem>) {
withContext(Dispatchers.Default) { dao.removeFromBasket(items.map { it.toLocal() }) }
}
override fun observeBasket(metaId: Long, chainId: String): Flow<List<TinderGovBasketItem>> {
return dao.observeBasket(metaId, chainId)
.mapList { it.toDomain() }
}
override suspend fun getBasket(metaId: Long, chainId: ChainId): List<TinderGovBasketItem> {
return withContext(Dispatchers.Default) { dao.getBasket(metaId, chainId).map { it.toDomain() } }
}
override suspend fun isBasketEmpty(metaId: Long, chainId: ChainId): Boolean {
return withContext(Dispatchers.Default) { dao.basketSize(metaId, chainId) == 0 }
}
override suspend fun clearBasket(metaId: Long, chainId: ChainId) {
withContext(Dispatchers.Default) { dao.clearBasket(metaId, chainId) }
}
private fun TinderGovBasketItem.toLocal(): TinderGovBasketItemLocal {
return TinderGovBasketItemLocal(
metaId = this.metaId,
chainId = this.chainId,
referendumId = this.referendumId.value,
voteType = this.voteTypeToLocal(),
conviction = this.conviction.toLocal(),
amount = this.amount
)
}
private fun TinderGovBasketItem.voteTypeToLocal() = when (this.voteType) {
VoteType.AYE -> LocalVoteType.AYE
VoteType.NAY -> LocalVoteType.NAY
VoteType.ABSTAIN -> LocalVoteType.ABSTAIN
}
private fun TinderGovBasketItemLocal.toDomain(): TinderGovBasketItem {
return TinderGovBasketItem(
metaId = this.metaId,
chainId = this.chainId,
referendumId = ReferendumId(this.referendumId),
voteType = this.voteTypeToDomain(),
conviction = this.conviction.toDomain(),
amount = this.amount
)
}
private fun TinderGovBasketItemLocal.voteTypeToDomain() = when (this.voteType) {
LocalVoteType.AYE -> VoteType.AYE
LocalVoteType.NAY -> VoteType.NAY
LocalVoteType.ABSTAIN -> VoteType.ABSTAIN
}
}
@@ -0,0 +1,39 @@
package io.novafoundation.nova.feature_governance_impl.data.repository.tindergov
import io.novafoundation.nova.core_db.dao.TinderGovDao
import io.novafoundation.nova.core_db.model.TinderGovVotingPowerLocal
import io.novafoundation.nova.feature_governance_api.data.model.VotingPower
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
interface TinderGovVotingPowerRepository {
suspend fun setVotingPower(votingPower: VotingPower)
suspend fun getVotingPower(metaId: Long, chainId: ChainId): VotingPower?
}
class RealTinderGovVotingPowerRepository(
private val tinderGovDao: TinderGovDao
) : TinderGovVotingPowerRepository {
override suspend fun setVotingPower(votingPower: VotingPower) {
val local = TinderGovVotingPowerLocal(
metaId = votingPower.metaId,
chainId = votingPower.chainId,
amount = votingPower.amount,
conviction = votingPower.conviction.toLocal()
)
tinderGovDao.setVotingPower(local)
}
override suspend fun getVotingPower(metaId: Long, chainId: ChainId): VotingPower? {
val local = tinderGovDao.getVotingPower(metaId, chainId) ?: return null
return VotingPower(
metaId = local.metaId,
chainId = local.chainId,
amount = local.amount,
conviction = local.conviction.toDomain()
)
}
}
@@ -0,0 +1,8 @@
package io.novafoundation.nova.feature_governance_impl.data.repository.v1
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.TrackId
import io.novafoundation.nova.feature_wallet_api.domain.model.BalanceLockId
import java.math.BigInteger
val DemocracyTrackId = TrackId(BigInteger.ZERO)
val DEMOCRACY_ID = BalanceLockId.fromFullId("democrac")
@@ -0,0 +1,153 @@
package io.novafoundation.nova.feature_governance_impl.data.repository.v1
import io.novafoundation.nova.common.data.network.runtime.binding.BlockNumber
import io.novafoundation.nova.common.utils.democracy
import io.novafoundation.nova.common.utils.flowOfAll
import io.novafoundation.nova.common.utils.numberConstant
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.AccountVote
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumVoter
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.TrackId
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.VoteType
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.Voting
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.votedFor
import io.novafoundation.nova.feature_governance_api.data.network.offchain.model.referendum.OffChainReferendumVotingDetails
import io.novafoundation.nova.feature_governance_api.data.repository.ConvictionVotingRepository
import io.novafoundation.nova.feature_governance_api.domain.locks.ClaimSchedule
import io.novafoundation.nova.feature_governance_impl.data.network.blockchain.extrinsic.democracyRemoveVote
import io.novafoundation.nova.feature_governance_impl.data.network.blockchain.extrinsic.democracyUnlock
import io.novafoundation.nova.feature_governance_impl.data.network.blockchain.extrinsic.democracyVote
import io.novafoundation.nova.feature_governance_impl.data.repository.common.bindVoting
import io.novafoundation.nova.feature_governance_impl.data.repository.common.votersFor
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 io.novafoundation.nova.runtime.extrinsic.multi.CallBuilder
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.multiNetwork.asset
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
import io.novafoundation.nova.runtime.multiNetwork.chain.model.FullChainAssetId
import io.novafoundation.nova.runtime.multiNetwork.getRuntime
import io.novafoundation.nova.runtime.storage.source.StorageDataSource
import io.novasama.substrate_sdk_android.runtime.AccountId
import io.novasama.substrate_sdk_android.runtime.extrinsic.builder.ExtrinsicBuilder
import io.novasama.substrate_sdk_android.runtime.metadata.storage
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import java.math.BigInteger
class GovV1ConvictionVotingRepository(
private val remoteStorageSource: StorageDataSource,
private val chainRegistry: ChainRegistry,
private val balanceLocksRepository: BalanceLocksRepository,
) : ConvictionVotingRepository {
override val voteLockId = DEMOCRACY_ID
override suspend fun maxAvailableForVote(asset: Asset): Balance {
return asset.freeInPlanks
}
override suspend fun voteLockingPeriod(chainId: ChainId): BlockNumber {
val runtime = chainRegistry.getRuntime(chainId)
return runtime.metadata.democracy().numberConstant("VoteLockingPeriod", runtime)
}
override suspend fun maxTrackVotes(chainId: ChainId): BigInteger {
val runtime = chainRegistry.getRuntime(chainId)
return runtime.metadata.democracy().numberConstant("MaxVotes", runtime)
}
override fun trackLocksFlow(accountId: AccountId, chainAssetId: FullChainAssetId): Flow<Map<TrackId, Balance>> {
return flowOfAll {
val chainAsset = chainRegistry.asset(chainAssetId)
balanceLocksRepository.observeBalanceLock(chainAsset, voteLockId)
.map { lock -> lock?.amountInPlanks.associatedWithTrack() }
}
}
override suspend fun votingFor(accountId: AccountId, chainId: ChainId): Map<TrackId, Voting> {
return votingFor(accountId, chainId, DemocracyTrackId).associatedWithTrack()
}
override suspend fun votingFor(accountId: AccountId, chainId: ChainId, trackId: TrackId): Voting? {
if (trackId != DemocracyTrackId) return null
return remoteStorageSource.query(chainId) {
runtime.metadata.democracy().storage("VotingOf").query(
accountId,
binding = { decoded -> decoded?.let(::bindVoting) }
)
}
}
override suspend fun votingFor(accountId: AccountId, chainId: ChainId, trackIds: Collection<TrackId>): Map<TrackId, Voting> {
unsupported()
}
override suspend fun votersOf(referendumId: ReferendumId, chain: Chain, type: VoteType): List<ReferendumVoter> {
val allVotings = remoteStorageSource.query(chain.id) {
runtime.metadata.democracy().storage("VotingOf").entries(
keyExtractor = { it },
binding = { decoded, _ -> bindVoting(decoded!!) }
)
}
return allVotings.votersFor(referendumId)
.filter { it.vote.votedFor(type) }
}
override suspend fun abstainVotingDetails(referendumId: ReferendumId, chain: Chain): OffChainReferendumVotingDetails? {
return null
}
override fun ExtrinsicBuilder.unlock(accountId: AccountId, claimable: ClaimSchedule.UnlockChunk.Claimable) {
claimable.actions.forEach { claimAction ->
when (claimAction) {
is ClaimSchedule.ClaimAction.RemoveVote -> {
removeVote(claimAction.trackId, claimAction.referendumId)
}
is ClaimSchedule.ClaimAction.Unlock -> {
democracyUnlock(accountId)
}
}
}
}
override fun ExtrinsicBuilder.vote(referendumId: ReferendumId, vote: AccountVote) {
democracyVote(referendumId, vote)
}
override fun CallBuilder.vote(referendumId: ReferendumId, vote: AccountVote) {
democracyVote(referendumId, vote)
}
override fun ExtrinsicBuilder.removeVote(trackId: TrackId, referendumId: ReferendumId) {
democracyRemoveVote(referendumId)
}
override fun isAbstainVotingAvailable(): Boolean {
return false
}
override suspend fun fullVotingDetails(referendumId: ReferendumId, chain: Chain): OffChainReferendumVotingDetails? {
return null
}
private fun <T> T?.associatedWithTrack(): Map<TrackId, T> {
return if (this != null) {
mapOf(DemocracyTrackId to this)
} else {
emptyMap()
}
}
private fun unsupported(): Nothing {
error("Unsupported operation for Governance 1 voting")
}
}
@@ -0,0 +1,36 @@
package io.novafoundation.nova.feature_governance_impl.data.repository.v1
import io.novafoundation.nova.common.utils.formatNamed
import io.novafoundation.nova.core_db.dao.GovernanceDAppsDao
import io.novafoundation.nova.core_db.model.GovernanceDAppLocal
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId
import io.novafoundation.nova.feature_governance_api.data.repository.GovernanceDAppsRepository
import io.novafoundation.nova.feature_governance_api.domain.referendum.details.ReferendumDApp
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
class GovV1DAppsRepository(
private val governanceDAppsDao: GovernanceDAppsDao
) : GovernanceDAppsRepository {
override fun observeReferendumDApps(chainId: ChainId, referendumId: ReferendumId): Flow<List<ReferendumDApp>> {
return governanceDAppsDao.observeChainDapps(chainId)
.map { dapps ->
val v1Dapps = dapps.filter { it.referendumUrlV1 != null }
mapV1DappsLocalToDomain(referendumId, v1Dapps)
}
}
}
private fun mapV1DappsLocalToDomain(referendumId: ReferendumId, dapps: List<GovernanceDAppLocal>): List<ReferendumDApp> {
return dapps.map {
ReferendumDApp(
it.chainId,
it.name,
it.referendumUrlV1?.formatNamed("referendumId" to referendumId.value.toString())!!,
it.iconUrl,
it.details
)
}
}
@@ -0,0 +1,268 @@
package io.novafoundation.nova.feature_governance_impl.data.repository.v1
import android.util.Log
import io.novafoundation.nova.common.address.intoKey
import io.novafoundation.nova.common.data.network.runtime.binding.BlockNumber
import io.novafoundation.nova.common.data.network.runtime.binding.bindBlockNumber
import io.novafoundation.nova.common.data.network.runtime.binding.bindBoolean
import io.novafoundation.nova.common.data.network.runtime.binding.bindCollectionEnum
import io.novafoundation.nova.common.data.network.runtime.binding.castToDictEnum
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.data.network.runtime.binding.getTyped
import io.novafoundation.nova.common.data.network.runtime.binding.incompatible
import io.novafoundation.nova.common.utils.LOG_TAG
import io.novafoundation.nova.common.utils.democracy
import io.novafoundation.nova.common.utils.filterNotNull
import io.novafoundation.nova.common.utils.numberConstant
import io.novafoundation.nova.common.utils.padEnd
import io.novafoundation.nova.common.utils.scheduler
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ConfirmingSource
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.DecidingStatus
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.OnChainReferendum
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.OnChainReferendumStatus
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.TrackId
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.TrackInfo
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.TrackQueue
import io.novafoundation.nova.feature_governance_api.data.repository.OnChainReferendaRepository
import io.novafoundation.nova.feature_governance_api.data.thresold.gov1.Gov1VotingThreshold
import io.novafoundation.nova.feature_governance_impl.data.repository.common.bindProposal
import io.novafoundation.nova.feature_governance_impl.data.repository.common.bindTally
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
import io.novafoundation.nova.runtime.multiNetwork.getRuntime
import io.novafoundation.nova.runtime.repository.TotalIssuanceRepository
import io.novafoundation.nova.runtime.storage.source.StorageDataSource
import io.novasama.substrate_sdk_android.extensions.pad
import io.novasama.substrate_sdk_android.hash.Hasher.blake2b256
import io.novasama.substrate_sdk_android.runtime.RuntimeSnapshot
import io.novasama.substrate_sdk_android.runtime.definitions.types.primitives.FixedByteArray
import io.novasama.substrate_sdk_android.runtime.definitions.types.primitives.u32
import io.novasama.substrate_sdk_android.runtime.definitions.types.toByteArray
import io.novasama.substrate_sdk_android.runtime.metadata.RuntimeMetadata
import io.novasama.substrate_sdk_android.runtime.metadata.keys
import io.novasama.substrate_sdk_android.runtime.metadata.storage
import kotlinx.coroutines.flow.Flow
import java.math.BigInteger
private const val SCHEDULER_KEY_BOUND = 32
private enum class SchedulerVersion {
V3, V4
}
class GovV1OnChainReferendaRepository(
private val remoteStorageSource: StorageDataSource,
private val chainRegistry: ChainRegistry,
private val totalIssuanceRepository: TotalIssuanceRepository,
) : OnChainReferendaRepository {
override suspend fun electorate(chainId: ChainId): Balance {
return totalIssuanceRepository.getTotalIssuance(chainId)
}
override suspend fun undecidingTimeout(chainId: ChainId): BlockNumber {
// we do not support `in queue` status for gov v1 yet
return Balance.ZERO
}
override suspend fun getTracks(chainId: ChainId): Collection<TrackInfo> {
val runtime = chainRegistry.getRuntime(chainId)
val track = TrackInfo(
id = DemocracyTrackId,
name = "root",
preparePeriod = Balance.ZERO,
decisionPeriod = runtime.votingPeriod(),
confirmPeriod = Balance.ZERO,
minSupport = null,
minApproval = null
)
return listOf(track)
}
override suspend fun getTrackQueues(trackIds: Set<TrackId>, chainId: ChainId): Map<TrackId, TrackQueue> {
// we do not support `in queue` status for gov v1 yet
return emptyMap()
}
override suspend fun getAllOnChainReferenda(chainId: ChainId): Collection<OnChainReferendum> {
return remoteStorageSource.query(chainId) {
val votingPeriod = runtime.votingPeriod()
runtime.metadata.democracy().storage("ReferendumInfoOf").entries(
keyExtractor = { (id: BigInteger) -> ReferendumId(id) },
binding = { decoded, id -> bindReferendum(decoded, id, votingPeriod, runtime) }
)
}.values.filterNotNull()
}
override suspend fun getOnChainReferenda(chainId: ChainId, referendaIds: Collection<ReferendumId>): Map<ReferendumId, OnChainReferendum> {
return remoteStorageSource.query(chainId) {
val votingPeriod = runtime.votingPeriod()
runtime.metadata.democracy().storage("ReferendumInfoOf").entries(
keysArguments = referendaIds.map { listOf(it.value) },
keyExtractor = { (id: BigInteger) -> ReferendumId(id) },
binding = { decoded, id -> bindReferendum(decoded, id, votingPeriod, runtime) }
)
}.filterNotNull()
}
override suspend fun onChainReferendumFlow(chainId: ChainId, referendumId: ReferendumId): Flow<OnChainReferendum?> {
return remoteStorageSource.subscribe(chainId) {
val votingPeriod = runtime.votingPeriod()
runtime.metadata.democracy().storage("ReferendumInfoOf").observe(
referendumId.value,
binding = { bindReferendum(it, referendumId, votingPeriod, runtime) }
)
}
}
override suspend fun getReferendaExecutionBlocks(
chainId: ChainId,
approvedReferendaIds: Collection<ReferendumId>
): Map<ReferendumId, BlockNumber> {
if (approvedReferendaIds.isEmpty()) return emptyMap()
return remoteStorageSource.query(chainId) {
val schedulerVersion = runtime.metadata.schedulerVersion()
val referendaIdBySchedulerId = approvedReferendaIds.flatMap { referendumId ->
referendumId.versionedEnactmentSchedulerIdVariants(runtime, schedulerVersion).map { enactmentKeyVariant ->
enactmentKeyVariant.intoKey() to referendumId
}
}.toMap()
// We do not extract referendumId as a key here since we request multiple keys per each referendumId (pre- and post- v4 migration)
// Thus, we should firstly filter out null values from map.
// Otherwise key candidate that resulted in null value may shadow another which resulted in value and we will loose that value
val schedulerKeysBySchedulerKey = runtime.metadata.scheduler().storage("Lookup").entries(
keysArguments = referendaIdBySchedulerId.keys.map { schedulerIdKey -> listOf(schedulerIdKey.value) },
keyExtractor = { (schedulerId: ByteArray) -> schedulerId.intoKey() },
binding = { decoded, _ ->
decoded?.let {
val (blockNumber, _) = decoded.castToList()
bindBlockNumber(blockNumber)
}
}
).filterNotNull()
schedulerKeysBySchedulerKey.mapKeys { (schedulerKey, _) -> referendaIdBySchedulerId.getValue(schedulerKey) }
}
}
private fun bindReferendum(
decoded: Any?,
id: ReferendumId,
votingPeriod: BlockNumber,
runtime: RuntimeSnapshot,
): OnChainReferendum? = runCatching {
val asDictEnum = decoded.castToDictEnum()
val referendumStatus = when (asDictEnum.name) {
"Ongoing" -> {
val status = asDictEnum.value.castToStruct()
val end = bindBlockNumber(status["end"])
val submittedIn = end - votingPeriod
val threshold = bindThreshold(status["threshold"])
OnChainReferendumStatus.Ongoing(
track = DemocracyTrackId,
proposal = bindProposal(status["proposalHash"] ?: status["proposal"], runtime),
submitted = submittedIn,
submissionDeposit = null,
decisionDeposit = null,
deciding = DecidingStatus(
since = submittedIn,
confirming = ConfirmingSource.FromThreshold(end = end)
),
tally = bindTally(status.getTyped("tally")),
inQueue = false,
threshold = threshold
)
}
"Finished" -> {
val status = asDictEnum.value.castToStruct()
val approved = bindBoolean(status["approved"])
val end = bindBlockNumber(status["end"])
if (approved) {
OnChainReferendumStatus.Approved(end)
} else {
OnChainReferendumStatus.Rejected(end)
}
}
else -> throw IllegalArgumentException("Unsupported referendum status")
}
OnChainReferendum(
id = id,
status = referendumStatus
)
}
.onFailure { Log.e(this.LOG_TAG, "Failed to decode on-chain referendum", it) }
.getOrNull()
private fun bindThreshold(decoded: Any?) = bindCollectionEnum(decoded) { name ->
when (name) {
"SimpleMajority" -> Gov1VotingThreshold.SIMPLE_MAJORITY
"SuperMajorityApprove" -> Gov1VotingThreshold.SUPER_MAJORITY_APPROVE
"SuperMajorityAgainst" -> Gov1VotingThreshold.SUPER_MAJORITY_AGAINST
else -> incompatible()
}
}
private fun RuntimeSnapshot.votingPeriod(): BlockNumber {
return metadata.democracy().numberConstant("VotingPeriod", this)
}
private fun ReferendumId.versionedEnactmentSchedulerIdVariants(runtime: RuntimeSnapshot, schedulerVersion: SchedulerVersion): List<ByteArray> {
return when (schedulerVersion) {
SchedulerVersion.V3 -> listOf(v3EnactmentSchedulerId(runtime))
SchedulerVersion.V4 -> listOf(v4EnactmentSchedulerId(runtime), v4MigratedEnactmentSchedulerId(runtime))
}
}
// https:github.com/paritytech/substrate/blob/0d64ba4268106fffe430d41b541c1aeedd4f8da5/frame/democracy/src/lib.rs#L1476
private fun ReferendumId.v3EnactmentSchedulerId(runtime: RuntimeSnapshot): ByteArray {
val encodedAssemblyId = DEMOCRACY_ID.value.encodeToByteArray() // 'const bytes' in rust
val encodedIndex = u32.toByteArray(runtime, value)
return encodedAssemblyId + encodedIndex
}
private fun ReferendumId.v4EnactmentSchedulerId(runtime: RuntimeSnapshot): ByteArray {
val oldId = v3EnactmentSchedulerId(runtime)
return if (oldId.size > SCHEDULER_KEY_BOUND) {
oldId.blake2b256().pad(SCHEDULER_KEY_BOUND, padding = 0)
} else {
oldId.padEnd(SCHEDULER_KEY_BOUND, padding = 0)
}
}
private fun ReferendumId.v4MigratedEnactmentSchedulerId(runtime: RuntimeSnapshot): ByteArray {
return v3EnactmentSchedulerId(runtime).blake2b256()
}
private fun RuntimeMetadata.schedulerVersion(): SchedulerVersion {
val lookupStorage = scheduler().storage("Lookup")
val lookupArgumentType = lookupStorage.keys.first()
return if (lookupArgumentType is FixedByteArray) {
// bounded type is used
SchedulerVersion.V4
} else {
SchedulerVersion.V3
}
}
}
@@ -0,0 +1,72 @@
package io.novafoundation.nova.feature_governance_impl.data.repository.v1
import io.novafoundation.nova.common.data.network.runtime.binding.bindByteArray
import io.novafoundation.nova.common.data.network.runtime.binding.castToDictEnum
import io.novafoundation.nova.common.data.network.runtime.binding.castToStruct
import io.novafoundation.nova.common.utils.democracy
import io.novafoundation.nova.common.utils.hasStorage
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.PreImage
import io.novafoundation.nova.feature_governance_api.data.repository.HexHash
import io.novafoundation.nova.feature_governance_api.data.repository.PreImageRepository
import io.novafoundation.nova.feature_governance_api.data.repository.PreImageRequest
import io.novafoundation.nova.feature_governance_impl.data.repository.v2.Gov2PreImageRepository
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
import io.novafoundation.nova.runtime.storage.source.StorageDataSource
import io.novasama.substrate_sdk_android.runtime.RuntimeSnapshot
import io.novasama.substrate_sdk_android.runtime.definitions.types.fromByteArray
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericCall
import io.novasama.substrate_sdk_android.runtime.metadata.storage
class GovV1PreImageRepository(
private val remoteStorageSource: StorageDataSource,
private val v2Delegate: Gov2PreImageRepository,
) : PreImageRepository {
override suspend fun getPreimageFor(request: PreImageRequest, chainId: ChainId): PreImage? {
return remoteStorageSource.query(chainId) {
// The most recent democracy pallet version stores preimages the same way as gov v2 does
// However, those changes are not yet live on Kusama & Polkadot
if (runtime.metadata.democracy().hasStorage("Preimages")) {
runtime.metadata.democracy().storage("Preimages").query(
request.hash,
binding = { bindPreimage(it, runtime) }
)
} else {
// just in case something will fail during v2 delegate execution
runCatching { v2Delegate.getPreimageFor(request, chainId) }.getOrNull()
}
}
}
override suspend fun getPreimagesFor(requests: Collection<PreImageRequest>, chainId: ChainId): Map<HexHash, PreImage?> {
return remoteStorageSource.query(chainId) {
if (runtime.metadata.democracy().hasStorage("Preimages")) {
// Since it democracy pallet preimages stored un-sized - we do not implement bulk fetch due to possible big calls being stored
// We cant efficiently use `state_getStorageSize` since it only allows to query one key at the time
emptyMap()
} else {
runCatching { v2Delegate.getPreimagesFor(requests, chainId) }.getOrDefault(emptyMap())
}
}
}
private fun bindPreimage(
decoded: Any?,
runtime: RuntimeSnapshot,
): PreImage? = runCatching {
val asDictEnum = decoded.castToDictEnum()
when (asDictEnum.name) {
"Available" -> {
val valueStruct = asDictEnum.value.castToStruct()
val callData = bindByteArray(valueStruct["data"])
val runtimeCall = GenericCall.fromByteArray(runtime, callData)
PreImage(encodedCall = callData, call = runtimeCall)
}
else -> null
}
}.getOrNull()
}
@@ -0,0 +1,243 @@
package io.novafoundation.nova.feature_governance_impl.data.repository.v2
import android.util.Log
import io.novafoundation.nova.common.data.network.subquery.SubQueryNodes
import io.novafoundation.nova.common.utils.LOG_TAG
import io.novafoundation.nova.common.utils.filterNotNull
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.AccountVote
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.Delegation
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.TrackId
import io.novafoundation.nova.feature_governance_api.data.network.offchain.model.delegation.DelegateDetailedStats
import io.novafoundation.nova.feature_governance_api.data.network.offchain.model.delegation.DelegateMetadata
import io.novafoundation.nova.feature_governance_api.data.network.offchain.model.delegation.DelegateStats
import io.novafoundation.nova.feature_governance_api.data.network.offchain.model.vote.UserVote
import io.novafoundation.nova.feature_governance_api.data.repository.DelegationsRepository
import io.novafoundation.nova.feature_governance_impl.data.network.blockchain.extrinsic.convictionVotingDelegate
import io.novafoundation.nova.feature_governance_impl.data.network.blockchain.extrinsic.convictionVotingUndelegate
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.metadata.DelegateMetadataApi
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.metadata.getDelegatesMetadata
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.DelegationsSubqueryApi
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request.AllHistoricalVotesRequest
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request.DelegateDelegatorsRequest
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request.DelegateDetailedStatsRequest
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request.DelegateStatsByAddressesRequest
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request.DelegateStatsRequest
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request.DirectHistoricalVotesRequest
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.response.DelegateDelegatorsResponse
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.response.DelegateStatsResponse
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.response.DelegatedVoteRemote
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.response.DirectVoteRemote
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.response.mapMultiVoteRemoteToAccountVote
import io.novafoundation.nova.feature_governance_api.data.repository.common.RecentVotesDateThreshold
import io.novafoundation.nova.feature_governance_api.data.repository.common.zeroPoint
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
import io.novafoundation.nova.runtime.ext.accountIdOf
import io.novafoundation.nova.runtime.ext.accountIdOrNull
import io.novafoundation.nova.runtime.ext.addressOf
import io.novafoundation.nova.runtime.ext.externalApi
import io.novafoundation.nova.runtime.extrinsic.multi.CallBuilder
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain.ExternalApi.GovernanceDelegations
import io.novafoundation.nova.runtime.multiNetwork.runtime.types.custom.vote.Conviction
import io.novafoundation.nova.runtime.multiNetwork.runtime.types.custom.vote.Vote
import io.novafoundation.nova.runtime.multiNetwork.runtime.types.custom.vote.mapConvictionFromString
import io.novasama.substrate_sdk_android.runtime.AccountId
class Gov2DelegationsRepository(
private val delegationsSubqueryApi: DelegationsSubqueryApi,
private val delegateMetadataApi: DelegateMetadataApi,
) : DelegationsRepository {
override suspend fun isDelegationSupported(chain: Chain): Boolean {
// we heavy rely on SubQuery API for delegations so we require it to be present
return chain.externalApi<GovernanceDelegations>() != null
}
override suspend fun getDelegatesStats(
recentVotesDateThreshold: RecentVotesDateThreshold,
chain: Chain
): List<DelegateStats> {
return runCatching {
val externalApiLink = chain.externalApi<GovernanceDelegations>()?.url ?: return emptyList()
val request = DelegateStatsRequest(recentVotesDateThreshold)
val response = delegationsSubqueryApi.getDelegateStats(externalApiLink, request)
val delegateStats = response.data.delegates.nodes
mapDelegateStats(delegateStats, chain)
}.getOrNull()
.orEmpty()
}
override suspend fun getDelegatesStatsByAccountIds(
recentVotesDateThreshold: RecentVotesDateThreshold,
accountIds: List<AccountId>,
chain: Chain
): List<DelegateStats> {
return runCatching {
val externalApiLink = chain.externalApi<GovernanceDelegations>()?.url ?: return emptyList()
val addresses = accountIds.map { chain.addressOf(it) }
val request = DelegateStatsByAddressesRequest(recentVotesDateThreshold, addresses = addresses)
val response = delegationsSubqueryApi.getDelegateStats(externalApiLink, request)
val delegateStats = response.data.delegates.nodes
mapDelegateStats(delegateStats, chain)
}.getOrNull()
.orEmpty()
}
override suspend fun getDetailedDelegateStats(
delegateAddress: String,
recentVotesDateThreshold: RecentVotesDateThreshold,
chain: Chain
): DelegateDetailedStats? {
val externalApiLink = chain.externalApi<GovernanceDelegations>()?.url ?: return null
val request = DelegateDetailedStatsRequest(delegateAddress, recentVotesDateThreshold)
val response = delegationsSubqueryApi.getDetailedDelegateStats(externalApiLink, request)
val delegateStats = response.data.delegates.nodes.firstOrNull() ?: return null
return DelegateDetailedStats(
accountId = chain.accountIdOf(delegateAddress),
delegationsCount = delegateStats.delegators,
delegatedVotes = delegateStats.delegatorVotes,
recentVotes = delegateStats.recentVotes.totalCount,
allVotes = delegateStats.allVotes.totalCount
)
}
override suspend fun getDelegatesMetadata(chain: Chain): List<DelegateMetadata> {
return delegateMetadataApi.getDelegatesMetadata(chain).mapNotNull {
val accountId = chain.accountIdOrNull(it.address) ?: return@mapNotNull null
DelegateMetadata(
accountId = accountId,
shortDescription = it.shortDescription,
longDescription = it.longDescription,
profileImageUrl = it.image,
isOrganization = it.isOrganization,
name = it.name
)
}
}
override suspend fun getDelegateMetadata(chain: Chain, delegate: AccountId): DelegateMetadata? {
return getDelegatesMetadata(chain)
.find { it.accountId.contentEquals(delegate) }
}
override suspend fun getDelegationsTo(delegate: AccountId, chain: Chain): List<Delegation> {
return accountSubQueryRequest(delegate, chain) { externalApiLink, delegateAddress ->
val request = DelegateDelegatorsRequest(delegateAddress)
val response = delegationsSubqueryApi.getDelegateDelegators(externalApiLink, request)
response.data.delegations.nodes.map { mapDelegationFromRemote(it, chain, delegate) }
}.orEmpty()
}
override suspend fun allHistoricalVotesOf(user: AccountId, chain: Chain): Map<ReferendumId, UserVote>? {
return accountSubQueryRequest(user, chain) { externalApiLink, userAddress ->
val request = AllHistoricalVotesRequest(userAddress)
val response = delegationsSubqueryApi.getAllHistoricalVotes(externalApiLink, request)
val direct = response.data.direct.toUserVoteMap()
val delegated = response.data.delegated.toUserVoteMap(chain)
(direct + delegated).filterNotNull()
}
}
override suspend fun historicalVoteOf(user: AccountId, referendumId: ReferendumId, chain: Chain): UserVote? {
return allHistoricalVotesOf(user, chain)?.get(referendumId)
}
override suspend fun directHistoricalVotesOf(
user: AccountId,
chain: Chain,
recentVotesDateThreshold: RecentVotesDateThreshold?
): Map<ReferendumId, UserVote.Direct>? {
val timePointThreshold = recentVotesDateThreshold ?: RecentVotesDateThreshold.zeroPoint()
return accountSubQueryRequest(user, chain) { externalApiLink, userAddress ->
val request = DirectHistoricalVotesRequest(userAddress, timePointThreshold)
val response = delegationsSubqueryApi.getDirectHistoricalVotes(externalApiLink, request)
response.data.direct.toUserVoteMap().filterNotNull()
}
}
override suspend fun CallBuilder.delegate(delegate: AccountId, trackId: TrackId, amount: Balance, conviction: Conviction) {
convictionVotingDelegate(delegate, trackId, amount, conviction)
}
override suspend fun CallBuilder.undelegate(trackId: TrackId) {
convictionVotingUndelegate(trackId)
}
private fun SubQueryNodes<DirectVoteRemote>.toUserVoteMap(): Map<ReferendumId, UserVote.Direct?> {
return nodes.associateBy(
keySelector = { ReferendumId(it.referendumId) },
valueTransform = { directVoteRemote -> UserVote.Direct(mapMultiVoteRemoteToAccountVote(directVoteRemote)) }
)
}
private fun SubQueryNodes<DelegatedVoteRemote>.toUserVoteMap(chain: Chain): Map<ReferendumId, UserVote.Delegated?> {
return nodes.associateBy(
keySelector = { ReferendumId(it.parent.referendumId) },
valueTransform = { delegatedVoteRemote ->
// delegated votes do not participate in any vote rather than standard
val aye = delegatedVoteRemote.parent.standardVote?.aye ?: return@associateBy null
val standardVote = delegatedVoteRemote.vote
UserVote.Delegated(
delegate = chain.accountIdOf(delegatedVoteRemote.parent.delegateId),
vote = AccountVote.Standard(
balance = standardVote.amount,
vote = Vote(
aye = aye,
conviction = mapConvictionFromString(delegatedVoteRemote.vote.conviction)
)
),
)
}
)
}
private fun mapDelegationFromRemote(
delegation: DelegateDelegatorsResponse.DelegatorRemote,
chain: Chain,
delegate: AccountId
): Delegation {
return Delegation(
vote = Delegation.Vote(
amount = delegation.delegation.amount,
conviction = mapConvictionFromString(delegation.delegation.conviction)
),
delegator = chain.accountIdOf(delegation.address),
delegate = delegate
)
}
private inline fun <R> accountSubQueryRequest(
accountId: AccountId,
chain: Chain,
action: (url: String, address: String) -> R
): R? {
val externalApiLink = chain.externalApi<GovernanceDelegations>()?.url ?: return null
val address = chain.addressOf(accountId)
return runCatching { action(externalApiLink, address) }
.onFailure { Log.e(LOG_TAG, "Failed to execute subquery request", it) }
.getOrNull()
}
private fun mapDelegateStats(delegateStats: List<DelegateStatsResponse.Delegate>, chain: Chain): List<DelegateStats> {
return delegateStats.map { delegate ->
DelegateStats(
accountId = chain.accountIdOf(delegate.address),
delegationsCount = delegate.delegators,
delegatedVotes = delegate.delegatorVotes,
recentVotes = delegate.delegateVotes.totalCount
)
}
}
}
@@ -0,0 +1,178 @@
package io.novafoundation.nova.feature_governance_impl.data.repository.v2
import io.novafoundation.nova.common.data.network.runtime.binding.bindByteArray
import io.novafoundation.nova.common.data.network.runtime.binding.bindNumber
import io.novafoundation.nova.common.data.network.runtime.binding.cast
import io.novafoundation.nova.common.data.network.runtime.binding.castToDictEnum
import io.novafoundation.nova.common.data.network.runtime.binding.castToStruct
import io.novafoundation.nova.common.utils.castOrNull
import io.novafoundation.nova.common.utils.preImage
import io.novafoundation.nova.common.utils.storageOrFallback
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.PreImage
import io.novafoundation.nova.feature_governance_api.data.repository.HexHash
import io.novafoundation.nova.feature_governance_api.data.repository.PreImageRepository
import io.novafoundation.nova.feature_governance_api.data.repository.PreImageRequest
import io.novafoundation.nova.feature_governance_api.data.repository.PreImageRequest.FetchCondition
import io.novafoundation.nova.feature_governance_impl.data.preimage.PreImageSizer
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
import io.novafoundation.nova.runtime.storage.source.StorageDataSource
import io.novafoundation.nova.runtime.storage.source.query.StorageQueryContext
import io.novafoundation.nova.runtime.storage.source.query.wrapSingleArgumentKeys
import io.novasama.substrate_sdk_android.extensions.toHexString
import io.novasama.substrate_sdk_android.runtime.RuntimeSnapshot
import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.Tuple
import io.novasama.substrate_sdk_android.runtime.definitions.types.fromByteArray
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericCall
import io.novasama.substrate_sdk_android.runtime.metadata.RuntimeMetadata
import io.novasama.substrate_sdk_android.runtime.metadata.module.StorageEntry
import io.novasama.substrate_sdk_android.runtime.metadata.module.StorageEntryType
import io.novasama.substrate_sdk_android.runtime.metadata.storage
import java.math.BigInteger
class Gov2PreImageRepository(
private val remoteSource: StorageDataSource,
private val preImageSizer: PreImageSizer,
) : PreImageRepository {
override suspend fun getPreimageFor(request: PreImageRequest, chainId: ChainId): PreImage? {
return remoteSource.query(chainId) {
val storage = runtime.metadata.preImageForStorage()
val shouldKnowSize = storage.shouldKnowSizeExecuting(request)
val preImageSize = if (shouldKnowSize) {
request.knownSize ?: fetchPreImageLength(request.hash)
} else {
request.knownSize
}
val shouldFetch = request.fetchIf.shouldFetch(actualPreImageSize = preImageSize)
val canFetchPreimage = if (storage.requiresSize()) preImageSize != null else true
val canReturnValue = shouldFetch && canFetchPreimage
if (!canReturnValue) return@query null
val key = storage.preImageStorageKey(request.hash, preImageSize)
storage.query(key, binding = { bindPreimage(it, runtime) })
}
}
override suspend fun getPreimagesFor(requests: Collection<PreImageRequest>, chainId: ChainId): Map<HexHash, PreImage?> {
return remoteSource.query(chainId) {
val storage = runtime.metadata.preImageForStorage()
val shouldKnowSizes = requests.associateBy(
keySelector = { it.hashHex },
valueTransform = { request -> storage.shouldKnowSizeExecuting(request) }
)
val hashesToFetchSize = requests.mapNotNull {
if (it.hashHex in shouldKnowSizes && it.knownSize == null) {
it.hash
} else {
null
}
}
val fetchedSizes = fetchPreImagesLength(hashesToFetchSize)
val preKnownSizes = requests.associateBy(
keySelector = { it.hashHex },
valueTransform = { it.knownSize }
)
val allKnownSizes = preKnownSizes + fetchedSizes
val keysToFetch = requests.mapNotNull { request ->
val preImageSize = allKnownSizes[request.hashHex]
val shouldFetch = request.fetchIf.shouldFetch(actualPreImageSize = preImageSize)
val canFetchPreimage = if (storage.requiresSize()) preImageSize != null else true
if (shouldFetch && canFetchPreimage) {
storage.preImageStorageKey(request.hash, preImageSize)
} else {
null
}
}
storage.entries(
keysArguments = keysToFetch.wrapSingleArgumentKeys(),
keyExtractor = { (hashAndLen: List<*>) -> bindByteArray(hashAndLen.first()).toHexString() },
binding = { decoded, _ -> bindPreimage(decoded, runtime) }
)
}
}
private fun StorageEntry.shouldKnowSizeExecuting(request: PreImageRequest): Boolean {
return requiresSize() || request.fetchIf == FetchCondition.SMALL_SIZE
}
private fun StorageEntry.preImageStorageKey(hash: ByteArray, preImageSize: BigInteger?): Any {
return if (requiresSize()) {
listOf(hash, preImageSize!!)
} else {
hash
}
}
private fun FetchCondition.shouldFetch(actualPreImageSize: BigInteger?): Boolean {
return when (this) {
FetchCondition.ALWAYS -> true
FetchCondition.SMALL_SIZE ->
actualPreImageSize != null &&
preImageSizer.satisfiesSizeConstraint(actualPreImageSize, PreImageSizer.SizeConstraint.SMALL)
}
}
private suspend fun StorageQueryContext.fetchPreImageLength(callHash: ByteArray): BigInteger? {
return runtime.metadata.preImage().storageOrFallback("RequestStatusFor", "StatusFor")
.query(
callHash,
binding = ::bindPreImageLength
)
}
private suspend fun StorageQueryContext.fetchPreImagesLength(callHashes: Collection<ByteArray>): Map<HexHash, BigInteger?> {
return runtime.metadata.preImage().storageOrFallback("RequestStatusFor", "StatusFor")
.entries(
keysArguments = callHashes.wrapSingleArgumentKeys(),
keyExtractor = { (callHash: ByteArray) -> callHash.toHexString() },
binding = { decoded, _ -> bindPreImageLength(decoded) }
)
}
private fun bindPreImageLength(decoded: Any?): BigInteger? = runCatching {
val asDictEnum = decoded.castToDictEnum()
// every variant of RequestStatus is struct that has len field
val valueStruct = asDictEnum.value.castToStruct()
bindNumber(valueStruct["len"])
}
.getOrNull()
private fun bindPreimage(
decoded: Any?,
runtime: RuntimeSnapshot,
): PreImage? {
val asByteArray = decoded.castOrNull<ByteArray>() ?: return null
val runtimeCall = runCatching {
GenericCall.fromByteArray(runtime, asByteArray)
}.getOrNull()
return runtimeCall?.let {
PreImage(
encodedCall = asByteArray,
call = it,
)
}
}
private fun RuntimeMetadata.preImageForStorage(): StorageEntry {
return preImage().storage("PreimageFor")
}
private fun StorageEntry.requiresSize(): Boolean {
val keys = type.cast<StorageEntryType.NMap>().keys
val argument = keys.first()
// for newer version of the pallet key is (CallHash, Length)
return argument is Tuple
}
}
@@ -0,0 +1,254 @@
package io.novafoundation.nova.feature_governance_impl.data.repository.v2
import io.novafoundation.nova.common.data.network.runtime.binding.BlockNumber
import io.novafoundation.nova.common.data.network.runtime.binding.bindList
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.utils.convictionVoting
import io.novafoundation.nova.common.utils.filterNotNull
import io.novafoundation.nova.common.utils.numberConstant
import io.novafoundation.nova.common.utils.sum
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.AccountVote
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.Delegation
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumVoter
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.TrackId
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.VoteType
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.Voting
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.amountMultiplier
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.isAye
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.votedFor
import io.novafoundation.nova.feature_governance_api.data.network.offchain.model.referendum.OffChainReferendumVotingDetails
import io.novafoundation.nova.feature_governance_api.data.network.offchain.model.referendum.OffChainReferendumVotingDetails.VotingInfo
import io.novafoundation.nova.feature_governance_api.data.network.offchain.model.referendum.empty
import io.novafoundation.nova.feature_governance_api.data.network.offchain.model.referendum.plus
import io.novafoundation.nova.feature_governance_api.data.repository.ConvictionVotingRepository
import io.novafoundation.nova.feature_governance_api.domain.locks.ClaimSchedule
import io.novafoundation.nova.feature_governance_impl.data.network.blockchain.extrinsic.convictionVotingRemoveVote
import io.novafoundation.nova.feature_governance_impl.data.network.blockchain.extrinsic.convictionVotingUnlock
import io.novafoundation.nova.feature_governance_impl.data.network.blockchain.extrinsic.convictionVotingVote
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.DelegationsSubqueryApi
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request.ReferendumSplitAbstainVotersRequest
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request.ReferendumVotersRequest
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.request.ReferendumVotesRequest
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.response.ReferendumVoterRemote
import io.novafoundation.nova.feature_governance_impl.data.offchain.delegation.v2.stats.response.mapMultiVoteRemoteToAccountVote
import io.novafoundation.nova.feature_governance_impl.data.repository.common.bindVoting
import io.novafoundation.nova.feature_governance_impl.data.repository.common.toOffChainVotes
import io.novafoundation.nova.feature_governance_impl.data.repository.common.votersFor
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
import io.novafoundation.nova.feature_wallet_api.domain.model.Asset
import io.novafoundation.nova.feature_wallet_api.domain.model.BalanceLockId
import io.novafoundation.nova.runtime.ext.accountIdOf
import io.novafoundation.nova.runtime.ext.externalApi
import io.novafoundation.nova.runtime.extrinsic.multi.CallBuilder
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
import io.novafoundation.nova.runtime.multiNetwork.chain.model.FullChainAssetId
import io.novafoundation.nova.runtime.multiNetwork.getRuntime
import io.novafoundation.nova.runtime.multiNetwork.runtime.types.custom.vote.Conviction
import io.novafoundation.nova.runtime.multiNetwork.runtime.types.custom.vote.mapConvictionFromString
import io.novafoundation.nova.runtime.storage.source.StorageDataSource
import io.novasama.substrate_sdk_android.runtime.AccountId
import io.novasama.substrate_sdk_android.runtime.extrinsic.builder.ExtrinsicBuilder
import io.novasama.substrate_sdk_android.runtime.metadata.storage
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import java.math.BigInteger
class GovV2ConvictionVotingRepository(
private val remoteStorageSource: StorageDataSource,
private val chainRegistry: ChainRegistry,
private val delegateSubqueryApi: DelegationsSubqueryApi
) : ConvictionVotingRepository {
override val voteLockId = BalanceLockId.fromFullId("pyconvot")
override suspend fun maxAvailableForVote(asset: Asset): Balance {
return asset.totalInPlanks
}
override suspend fun voteLockingPeriod(chainId: ChainId): BlockNumber {
val runtime = chainRegistry.getRuntime(chainId)
return runtime.metadata.convictionVoting().numberConstant("VoteLockingPeriod", runtime)
}
override suspend fun maxTrackVotes(chainId: ChainId): BigInteger {
val runtime = chainRegistry.getRuntime(chainId)
return runtime.metadata.convictionVoting().numberConstant("MaxVotes", runtime)
}
override fun trackLocksFlow(accountId: AccountId, chainAssetId: FullChainAssetId): Flow<Map<TrackId, Balance>> {
return remoteStorageSource.subscribe(chainAssetId.chainId) {
runtime.metadata.convictionVoting().storage("ClassLocksFor").observe(accountId, binding = ::bindTrackLocks)
.map { it.toMap() }
}
}
override suspend fun votingFor(accountId: AccountId, chainId: ChainId): Map<TrackId, Voting> {
return remoteStorageSource.query(chainId) {
runtime.metadata.convictionVoting().storage("VotingFor").entries(
accountId,
keyExtractor = { (_: AccountId, trackId: BigInteger) -> TrackId(trackId) },
binding = { decoded, _ -> bindVoting(decoded!!) }
)
}
}
override suspend fun votingFor(accountId: AccountId, chainId: ChainId, trackId: TrackId): Voting? {
return remoteStorageSource.query(chainId) {
runtime.metadata.convictionVoting().storage("VotingFor").query(
accountId,
trackId.value,
binding = { decoded -> decoded?.let(::bindVoting) }
)
}
}
override suspend fun votingFor(accountId: AccountId, chainId: ChainId, trackIds: Collection<TrackId>): Map<TrackId, Voting> {
val keys = trackIds.map { listOf(accountId, it.value) }
return remoteStorageSource.query(chainId) {
runtime.metadata.convictionVoting().storage("VotingFor").entries(
keysArguments = keys,
keyExtractor = { (_: AccountId, trackId: BigInteger) -> TrackId(trackId) },
binding = { decoded, _ -> decoded?.let(::bindVoting) }
)
}.filterNotNull()
}
override suspend fun votersOf(referendumId: ReferendumId, chain: Chain, type: VoteType): List<ReferendumVoter> {
val governanceDelegationsExternalApi = chain.externalApi<Chain.ExternalApi.GovernanceDelegations>()
return if (governanceDelegationsExternalApi != null) {
runCatching { getVotersFromIndexer(referendumId, chain, governanceDelegationsExternalApi, type) }
.getOrElse { getVotersFromChain(referendumId, chain, type) }
} else {
getVotersFromChain(referendumId, chain, type)
}
}
override suspend fun abstainVotingDetails(referendumId: ReferendumId, chain: Chain): OffChainReferendumVotingDetails? {
val api = chain.externalApi<Chain.ExternalApi.GovernanceDelegations>() ?: return null
return runCatching {
val request = ReferendumSplitAbstainVotersRequest(referendumId.value)
val response = delegateSubqueryApi.getReferendumAbstainVoters(api.url, request)
val trackId = TrackId(response.data.referendum.trackId)
val abstainAmountSum = response.data
.referendum
.castingVotings
.nodes
.mapNotNull { it.splitAbstainVote?.abstainAmount }
.sum()
val abstainVotes = abstainAmountSum.toBigDecimal() * Conviction.None.amountMultiplier()
OffChainReferendumVotingDetails(trackId, VotingInfo.Abstain(abstainVotes))
}.getOrNull()
}
private suspend fun getVotersFromIndexer(
referendumId: ReferendumId,
chain: Chain,
api: Chain.ExternalApi.GovernanceDelegations,
type: VoteType
): List<ReferendumVoter> {
val request = ReferendumVotersRequest(referendumId.value, type.isAye())
val response = delegateSubqueryApi.getReferendumVoters(api.url, request)
return response.data
.voters
.nodes
.mapNotNull { mapVoterFromRemote(it, chain, type) }
}
private suspend fun getVotersFromChain(referendumId: ReferendumId, chain: Chain, type: VoteType): List<ReferendumVoter> {
val allVotings = remoteStorageSource.query(chain.id) {
runtime.metadata.convictionVoting().storage("VotingFor").entries(
keyExtractor = { it },
binding = { decoded, _ -> bindVoting(decoded!!) }
)
}
return allVotings.votersFor(referendumId)
.filter { it.vote.votedFor(type) }
}
override fun ExtrinsicBuilder.unlock(accountId: AccountId, claimable: ClaimSchedule.UnlockChunk.Claimable) {
claimable.actions.forEach { claimAction ->
when (claimAction) {
is ClaimSchedule.ClaimAction.RemoveVote -> {
removeVote(claimAction.trackId, claimAction.referendumId)
}
is ClaimSchedule.ClaimAction.Unlock -> {
convictionVotingUnlock(claimAction.trackId, accountId)
}
}
}
}
override fun ExtrinsicBuilder.vote(referendumId: ReferendumId, vote: AccountVote) {
convictionVotingVote(referendumId, vote)
}
override fun CallBuilder.vote(referendumId: ReferendumId, vote: AccountVote) {
convictionVotingVote(referendumId, vote)
}
override fun ExtrinsicBuilder.removeVote(trackId: TrackId, referendumId: ReferendumId) {
convictionVotingRemoveVote(trackId, referendumId)
}
override fun isAbstainVotingAvailable(): Boolean {
return true
}
override suspend fun fullVotingDetails(referendumId: ReferendumId, chain: Chain): OffChainReferendumVotingDetails? {
val api = chain.externalApi<Chain.ExternalApi.GovernanceDelegations>() ?: return null
return runCatching {
val referendum = delegateSubqueryApi.getReferendumVotes(api.url, ReferendumVotesRequest(referendumId.value))
.data
.referendum
val voters = referendum.castingVotings.nodes
var totalVoting = VotingInfo.Full.empty()
voters.forEach {
totalVoting += it.toOffChainVotes()
}
OffChainReferendumVotingDetails(TrackId(referendum.trackId), totalVoting)
}.getOrNull()
}
private fun bindTrackLocks(decoded: Any?): List<Pair<TrackId, Balance>> {
return bindList(decoded) { item ->
val (trackId, balance) = item.castToList()
TrackId(bindNumber(trackId)) to bindNumber(balance)
}
}
private fun mapVoterFromRemote(voter: ReferendumVoterRemote, chain: Chain, expectedType: VoteType): ReferendumVoter? {
val accountVote = mapMultiVoteRemoteToAccountVote(voter)
if (!accountVote.votedFor(expectedType)) return null
val delegators = voter.delegatorVotes.nodes
return ReferendumVoter(
accountId = chain.accountIdOf(voter.voterId),
vote = accountVote,
delegators = delegators.map {
Delegation(
vote = Delegation.Vote(it.vote.amount, mapConvictionFromString(it.vote.conviction)),
delegator = chain.accountIdOf(it.delegatorId),
delegate = chain.accountIdOf(voter.voterId),
)
}
)
}
}
@@ -0,0 +1,36 @@
package io.novafoundation.nova.feature_governance_impl.data.repository.v2
import io.novafoundation.nova.common.utils.formatNamed
import io.novafoundation.nova.core_db.dao.GovernanceDAppsDao
import io.novafoundation.nova.core_db.model.GovernanceDAppLocal
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId
import io.novafoundation.nova.feature_governance_api.data.repository.GovernanceDAppsRepository
import io.novafoundation.nova.feature_governance_api.domain.referendum.details.ReferendumDApp
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
class GovV2DAppsRepository(
private val governanceDAppsDao: GovernanceDAppsDao
) : GovernanceDAppsRepository {
override fun observeReferendumDApps(chainId: ChainId, referendumId: ReferendumId): Flow<List<ReferendumDApp>> {
return governanceDAppsDao.observeChainDapps(chainId)
.map { dapps ->
val v1Dapps = dapps.filter { it.referendumUrlV2 != null }
mapV2DappsLocalToDomain(referendumId, v1Dapps)
}
}
}
private fun mapV2DappsLocalToDomain(referendumId: ReferendumId, dapps: List<GovernanceDAppLocal>): List<ReferendumDApp> {
return dapps.map {
ReferendumDApp(
it.chainId,
it.name,
it.referendumUrlV2?.formatNamed("referendumId" to referendumId.value.toString())!!,
it.iconUrl,
it.details
)
}
}
@@ -0,0 +1,306 @@
package io.novafoundation.nova.feature_governance_impl.data.repository.v2
import android.util.Log
import io.novafoundation.nova.common.data.network.runtime.binding.BlockNumber
import io.novafoundation.nova.common.data.network.runtime.binding.bindAccountId
import io.novafoundation.nova.common.data.network.runtime.binding.bindBlockNumber
import io.novafoundation.nova.common.data.network.runtime.binding.bindBoolean
import io.novafoundation.nova.common.data.network.runtime.binding.bindFixedI64
import io.novafoundation.nova.common.data.network.runtime.binding.bindList
import io.novafoundation.nova.common.data.network.runtime.binding.bindNumber
import io.novafoundation.nova.common.data.network.runtime.binding.bindPerbill
import io.novafoundation.nova.common.data.network.runtime.binding.bindString
import io.novafoundation.nova.common.data.network.runtime.binding.castToDictEnum
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.data.network.runtime.binding.getTyped
import io.novafoundation.nova.common.data.network.runtime.binding.incompatible
import io.novafoundation.nova.common.utils.LOG_TAG
import io.novafoundation.nova.common.utils.constant
import io.novafoundation.nova.common.utils.decodedValue
import io.novafoundation.nova.common.utils.filterNotNull
import io.novafoundation.nova.common.utils.numberConstant
import io.novafoundation.nova.common.utils.referenda
import io.novafoundation.nova.common.utils.scheduler
import io.novafoundation.nova.common.utils.toByteArray
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ConfirmingSource
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ConfirmingStatus
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.DecidingStatus
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.OnChainReferendum
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.OnChainReferendumStatus
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumDeposit
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.TrackId
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.TrackInfo
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.TrackQueue
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.VotingCurve
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.empty
import io.novafoundation.nova.feature_governance_api.data.repository.OnChainReferendaRepository
import io.novafoundation.nova.feature_governance_api.data.repository.getTracksById
import io.novafoundation.nova.feature_governance_api.data.thresold.gov2.Gov2VotingThreshold
import io.novafoundation.nova.feature_governance_api.data.thresold.gov2.curve.LinearDecreasingCurve
import io.novafoundation.nova.feature_governance_api.data.thresold.gov2.curve.ReciprocalCurve
import io.novafoundation.nova.feature_governance_api.data.thresold.gov2.curve.SteppedDecreasingCurve
import io.novafoundation.nova.feature_governance_impl.data.repository.common.bindProposal
import io.novafoundation.nova.feature_governance_impl.data.repository.common.bindTally
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
import io.novafoundation.nova.runtime.multiNetwork.getRuntime
import io.novafoundation.nova.runtime.repository.TotalIssuanceRepository
import io.novafoundation.nova.runtime.repository.getActiveIssuance
import io.novafoundation.nova.runtime.storage.source.StorageDataSource
import io.novasama.substrate_sdk_android.extensions.fromHex
import io.novasama.substrate_sdk_android.extensions.toHexString
import io.novasama.substrate_sdk_android.hash.Hasher.blake2b256
import io.novasama.substrate_sdk_android.runtime.RuntimeSnapshot
import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.DictEnum
import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.Struct
import io.novasama.substrate_sdk_android.runtime.definitions.types.primitives.u32
import io.novasama.substrate_sdk_android.runtime.definitions.types.toByteArray
import io.novasama.substrate_sdk_android.runtime.metadata.storage
import io.novasama.substrate_sdk_android.scale.dataType.string
import kotlinx.coroutines.flow.Flow
import java.math.BigInteger
private const val ASSEMBLY_ID = "assembly"
class GovV2OnChainReferendaRepository(
private val remoteStorageSource: StorageDataSource,
private val chainRegistry: ChainRegistry,
private val totalIssuanceRepository: TotalIssuanceRepository,
) : OnChainReferendaRepository {
override suspend fun electorate(chainId: ChainId): Balance {
return totalIssuanceRepository.getActiveIssuance(chainId)
}
override suspend fun undecidingTimeout(chainId: ChainId): BlockNumber {
val runtime = chainRegistry.getRuntime(chainId)
return runtime.metadata.referenda().numberConstant("UndecidingTimeout", runtime)
}
override suspend fun getTracks(chainId: ChainId): Collection<TrackInfo> {
val runtime = chainRegistry.getRuntime(chainId)
val tracksConstant = runtime.metadata.referenda().constant("Tracks").decodedValue(runtime)
return bindTracks(tracksConstant)
}
override suspend fun getTrackQueues(trackIds: Set<TrackId>, chainId: ChainId): Map<TrackId, TrackQueue> {
if (trackIds.isEmpty()) return emptyMap()
return remoteStorageSource.query(chainId) {
runtime.metadata.referenda().storage("TrackQueue").entries(
keysArguments = trackIds.map { listOf(it.value) },
keyExtractor = { (trackIdRaw: BigInteger) -> TrackId(trackIdRaw) },
binding = { decoded, _ -> bindTrackQueue(decoded) }
)
}
}
override suspend fun getAllOnChainReferenda(chainId: ChainId): Collection<OnChainReferendum> {
return remoteStorageSource.query(chainId) {
val allTracks = getTracksById(chainId)
runtime.metadata.referenda().storage("ReferendumInfoFor").entries(
prefixArgs = emptyArray(),
keyExtractor = { (id: BigInteger) -> ReferendumId(id) },
binding = { decoded, id -> bindReferendum(decoded, id, allTracks, runtime) }
).values.filterNotNull()
}
}
override suspend fun getOnChainReferenda(chainId: ChainId, referendaIds: Collection<ReferendumId>): Map<ReferendumId, OnChainReferendum> {
return remoteStorageSource.query(chainId) {
val allTracks = getTracksById(chainId)
runtime.metadata.referenda().storage("ReferendumInfoFor").entries(
keysArguments = referendaIds.map { id -> listOf(id.value) },
keyExtractor = { (id: BigInteger) -> ReferendumId(id) },
binding = { decoded, id -> bindReferendum(decoded, id, allTracks, runtime) }
)
}.filterNotNull()
}
override suspend fun onChainReferendumFlow(chainId: ChainId, referendumId: ReferendumId): Flow<OnChainReferendum?> {
return remoteStorageSource.subscribe(chainId) {
val allTracks = getTracksById(chainId)
runtime.metadata.referenda().storage("ReferendumInfoFor").observe(
referendumId.value,
binding = { bindReferendum(it, referendumId, allTracks, runtime) }
)
}
}
override suspend fun getReferendaExecutionBlocks(
chainId: ChainId,
approvedReferendaIds: Collection<ReferendumId>
): Map<ReferendumId, BlockNumber> {
if (approvedReferendaIds.isEmpty()) return emptyMap()
return remoteStorageSource.query(chainId) {
val referendaIdBySchedulerId = approvedReferendaIds.associateBy { it.enactmentSchedulerId(runtime).toHexString() }
runtime.metadata.scheduler().storage("Lookup").entries(
keysArguments = referendaIdBySchedulerId.keys.map { schedulerIdHex -> listOf(schedulerIdHex.fromHex()) },
keyExtractor = { (schedulerId: ByteArray) -> referendaIdBySchedulerId.getValue(schedulerId.toHexString()) },
binding = { decoded, _ ->
decoded?.let {
val (blockNumber, _) = decoded.castToList()
bindBlockNumber(blockNumber)
}
}
).filterNotNull()
}
}
private fun bindReferendum(
decoded: Any?,
id: ReferendumId,
tracksById: Map<TrackId, TrackInfo>,
runtime: RuntimeSnapshot
): OnChainReferendum? = runCatching {
val asDictEnum = decoded.castToDictEnum()
val referendumStatus = when (asDictEnum.name) {
"Ongoing" -> {
val status = asDictEnum.value.castToStruct()
val trackId = TrackId(bindNumber(status["track"]))
val track = tracksById.getValue(trackId)
OnChainReferendumStatus.Ongoing(
track = trackId,
proposal = bindProposal(status["proposal"], runtime),
submitted = bindBlockNumber(status["submitted"]),
submissionDeposit = bindReferendumDeposit(status["submissionDeposit"])!!,
decisionDeposit = bindReferendumDeposit(status["decisionDeposit"]),
deciding = bindDecidingStatus(status["deciding"]),
tally = bindTally(status.getTyped("tally")),
inQueue = bindBoolean(status["inQueue"]),
threshold = Gov2VotingThreshold(track),
)
}
"Approved" -> OnChainReferendumStatus.Approved(bindCompletedReferendumSince(asDictEnum.value))
"Rejected" -> OnChainReferendumStatus.Rejected(bindCompletedReferendumSince(asDictEnum.value))
"Cancelled" -> OnChainReferendumStatus.Cancelled(bindCompletedReferendumSince(asDictEnum.value))
"TimedOut" -> OnChainReferendumStatus.TimedOut(bindCompletedReferendumSince(asDictEnum.value))
"Killed" -> OnChainReferendumStatus.Killed(bindNumber(asDictEnum.value))
else -> throw IllegalArgumentException("Unsupported referendum status")
}
OnChainReferendum(
id = id,
status = referendumStatus
)
}
.onFailure { Log.e(this.LOG_TAG, "Failed to decode on-chain referendum $id", it) }
.getOrNull()
private fun bindDecidingStatus(decoded: Any?): DecidingStatus? {
if (decoded == null) return null
val decodedStruct = decoded.castToStruct()
val confirming = decodedStruct.get<Any?>("confirming")?.let {
ConfirmingStatus(
till = bindBlockNumber(it)
)
}
return DecidingStatus(
since = bindBlockNumber(decodedStruct["since"]),
confirming = ConfirmingSource.OnChain(confirming)
)
}
private fun bindCompletedReferendumSince(decoded: Any?): BlockNumber {
// first element in tuple
val since = decoded.castToList().first()
return bindNumber(since)
}
private fun bindReferendumDeposit(decoded: Struct.Instance?): ReferendumDeposit? {
return decoded?.let {
ReferendumDeposit(
who = bindAccountId(it["who"]),
amount = bindNumber(it["amount"])
)
}
}
private fun bindTracks(decoded: Any?): List<TrackInfo> {
return bindList(decoded) {
val (id, content) = it.castToList()
val trackInfoStruct = content.castToStruct()
TrackInfo(
id = TrackId(bindNumber(id)),
name = bindString(trackInfoStruct["name"]),
preparePeriod = bindBlockNumber(trackInfoStruct["preparePeriod"]),
decisionPeriod = bindBlockNumber(trackInfoStruct["decisionPeriod"]),
confirmPeriod = bindBlockNumber(trackInfoStruct["confirmPeriod"]),
minApproval = bindCurve(trackInfoStruct.getTyped("minApproval")),
minSupport = bindCurve(trackInfoStruct.getTyped("minSupport"))
)
}
}
private fun bindCurve(decoded: DictEnum.Entry<*>): VotingCurve {
val valueStruct = decoded.value.castToStruct()
return when (decoded.name) {
"Reciprocal" -> {
ReciprocalCurve(
factor = bindFixedI64(valueStruct["factor"]),
xOffset = bindFixedI64(valueStruct["x_offset"]),
yOffset = bindFixedI64(valueStruct["y_offset"])
)
}
"LinearDecreasing" -> {
LinearDecreasingCurve(
length = bindPerbill(valueStruct["length"]),
floor = bindPerbill(valueStruct["floor"]),
ceil = bindPerbill(valueStruct["ceil"])
)
}
"SteppedDecreasing" -> {
SteppedDecreasingCurve(
begin = bindPerbill(valueStruct["begin"]),
end = bindPerbill(valueStruct["end"]),
step = bindPerbill(valueStruct["step"]),
period = bindPerbill(valueStruct["period"])
)
}
else -> incompatible()
}
}
private fun bindTrackQueue(decoded: Any?): TrackQueue {
if (decoded == null) return TrackQueue.empty()
val referendumIds = bindList(decoded) {
val (referendumIndex, _) = it.castToList()
ReferendumId(bindNumber(referendumIndex))
}
return TrackQueue(referendumIds)
}
// https://github.com/paritytech/substrate/blob/fc67cbb66d8c484bc7b7506fc1300344d12ecbad/frame/referenda/src/lib.rs#L716
private fun ReferendumId.enactmentSchedulerId(runtime: RuntimeSnapshot): ByteArray {
val encodedAssemblyId = ASSEMBLY_ID.encodeToByteArray() // 'const bytes' in rust
val encodedEnactment = string.toByteArray("enactment") // 'string' in rust
val encodedIndex = u32.toByteArray(runtime, value)
val toHash = encodedAssemblyId + encodedEnactment + encodedIndex
return toHash.blake2b256()
}
}
@@ -0,0 +1,23 @@
package io.novafoundation.nova.feature_governance_impl.data.source
import io.novafoundation.nova.feature_governance_api.data.source.GovernanceSource
import io.novafoundation.nova.feature_governance_api.data.source.GovernanceSourceRegistry
import io.novafoundation.nova.feature_governance_api.data.source.SupportedGovernanceOption
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
internal class RealGovernanceSourceRegistry(
private val governanceV2Source: GovernanceSource,
private val governanceV1Source: GovernanceSource,
) : GovernanceSourceRegistry {
override suspend fun sourceFor(option: SupportedGovernanceOption): GovernanceSource {
return sourceFor(option.additional.governanceType)
}
override suspend fun sourceFor(option: Chain.Governance): GovernanceSource {
return when (option) {
Chain.Governance.V1 -> governanceV1Source
Chain.Governance.V2 -> governanceV2Source
}
}
}
@@ -0,0 +1,18 @@
package io.novafoundation.nova.feature_governance_impl.data.source
import io.novafoundation.nova.feature_governance_api.data.repository.ConvictionVotingRepository
import io.novafoundation.nova.feature_governance_api.data.repository.DelegationsRepository
import io.novafoundation.nova.feature_governance_api.data.repository.GovernanceDAppsRepository
import io.novafoundation.nova.feature_governance_api.data.repository.OffChainReferendaInfoRepository
import io.novafoundation.nova.feature_governance_api.data.repository.OnChainReferendaRepository
import io.novafoundation.nova.feature_governance_api.data.repository.PreImageRepository
import io.novafoundation.nova.feature_governance_api.data.source.GovernanceSource
internal class StaticGovernanceSource(
override val referenda: OnChainReferendaRepository,
override val convictionVoting: ConvictionVotingRepository,
override val offChainInfo: OffChainReferendaInfoRepository,
override val preImageRepository: PreImageRepository,
override val dappsRepository: GovernanceDAppsRepository,
override val delegationsRepository: DelegationsRepository
) : GovernanceSource
@@ -0,0 +1,141 @@
package io.novafoundation.nova.feature_governance_impl.di
import dagger.BindsInstance
import dagger.Component
import io.novafoundation.nova.common.di.CommonApi
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.core_db.di.DbApi
import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi
import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectTracksCommunicator
import io.novafoundation.nova.feature_dapp_api.di.DAppFeatureApi
import io.novafoundation.nova.feature_deep_linking.di.DeepLinkingFeatureApi
import io.novafoundation.nova.feature_governance_api.di.GovernanceFeatureApi
import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter
import io.novafoundation.nova.feature_governance_impl.presentation.common.description.di.DescriptionComponent
import io.novafoundation.nova.feature_governance_impl.presentation.common.description.di.ReferendumInfoComponent
import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegate.delegators.di.DelegateDelegatorsComponent
import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegate.detail.main.di.DelegateDetailsComponent
import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegate.detail.votedReferenda.di.VotedReferendaComponent
import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegate.list.di.DelegateListComponent
import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegate.search.di.DelegateSearchComponent
import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegated.di.YourDelegationsComponent
import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.create.chooseAmount.di.NewDelegationChooseAmountComponent
import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.create.chooseTrack.di.NewDelegationChooseTracksComponent
import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.create.confirm.di.NewDelegationConfirmComponent
import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.removeVotes.di.RemoveVotesComponent
import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.revoke.chooseTracks.di.RevokeDelegationChooseTracksComponent
import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.revoke.confirm.di.RevokeDelegationConfirmComponent
import io.novafoundation.nova.feature_governance_impl.presentation.referenda.details.di.ReferendumDetailsComponent
import io.novafoundation.nova.feature_governance_impl.presentation.referenda.filters.di.ReferendaFiltersComponent
import io.novafoundation.nova.feature_governance_impl.presentation.referenda.full.di.ReferendumFullDetailsComponent
import io.novafoundation.nova.feature_governance_impl.presentation.referenda.list.di.ReferendaListComponent
import io.novafoundation.nova.feature_governance_impl.presentation.referenda.search.di.ReferendaSearchComponent
import io.novafoundation.nova.feature_governance_impl.presentation.referenda.vote.confirm.di.ConfirmReferendumVoteComponent
import io.novafoundation.nova.feature_governance_impl.presentation.referenda.vote.setup.referenda.di.SetupReferendumVoteComponent
import io.novafoundation.nova.feature_governance_impl.presentation.referenda.vote.setup.tindergov.di.SetupTinderGovVoteComponent
import io.novafoundation.nova.feature_governance_impl.presentation.referenda.vote.setup.tindergov.TinderGovVoteCommunicator
import io.novafoundation.nova.feature_governance_impl.presentation.referenda.voters.di.ReferendumVotersComponent
import io.novafoundation.nova.feature_governance_impl.presentation.tindergov.basket.di.TinderGovBasketComponent
import io.novafoundation.nova.feature_governance_impl.presentation.tindergov.cards.di.TinderGovCardsComponent
import io.novafoundation.nova.feature_governance_impl.presentation.tindergov.confirm.di.ConfirmTinderGovVoteComponent
import io.novafoundation.nova.feature_governance_impl.presentation.tracks.select.governanceTracks.di.SelectGovernanceTracksComponent
import io.novafoundation.nova.feature_governance_impl.presentation.unlock.confirm.di.ConfirmGovernanceUnlockComponent
import io.novafoundation.nova.feature_governance_impl.presentation.unlock.list.di.GovernanceLocksOverviewComponent
import io.novafoundation.nova.feature_wallet_api.di.WalletFeatureApi
import io.novafoundation.nova.feature_xcm_api.di.XcmFeatureApi
import io.novafoundation.nova.runtime.di.RuntimeApi
@Component(
dependencies = [
GovernanceFeatureDependencies::class,
],
modules = [
GovernanceFeatureModule::class,
]
)
@FeatureScope
interface GovernanceFeatureComponent : GovernanceFeatureApi {
fun referendaListFactory(): ReferendaListComponent.Factory
fun referendaSearchFactory(): ReferendaSearchComponent.Factory
fun referendumDetailsFactory(): ReferendumDetailsComponent.Factory
fun descriptionFactory(): DescriptionComponent.Factory
fun referendumInfoFactory(): ReferendumInfoComponent.Factory
fun referendumFullDetailsFactory(): ReferendumFullDetailsComponent.Factory
fun setupReferendumVoteFactory(): SetupReferendumVoteComponent.Factory
fun setupTinderGovVoteFactory(): SetupTinderGovVoteComponent.Factory
fun confirmReferendumVoteFactory(): ConfirmReferendumVoteComponent.Factory
fun confirmTinderGovVoteFactory(): ConfirmTinderGovVoteComponent.Factory
fun referendumVotersFactory(): ReferendumVotersComponent.Factory
fun confirmGovernanceUnlockFactory(): ConfirmGovernanceUnlockComponent.Factory
fun governanceLocksOverviewFactory(): GovernanceLocksOverviewComponent.Factory
fun delegateListFactory(): DelegateListComponent.Factory
fun delegateSearchFactory(): DelegateSearchComponent.Factory
fun delegateDetailsFactory(): DelegateDetailsComponent.Factory
fun votedReferendaFactory(): VotedReferendaComponent.Factory
fun removeVoteFactory(): RemoveVotesComponent.Factory
fun delegateDelegatorsFactory(): DelegateDelegatorsComponent.Factory
fun yourDelegationsFactory(): YourDelegationsComponent.Factory
fun newDelegationChooseTracks(): NewDelegationChooseTracksComponent.Factory
fun selectGovernanceTracks(): SelectGovernanceTracksComponent.Factory
fun newDelegationChooseAmountFactory(): NewDelegationChooseAmountComponent.Factory
fun newDelegationConfirmFactory(): NewDelegationConfirmComponent.Factory
fun revokeDelegationChooseTracksFactory(): RevokeDelegationChooseTracksComponent.Factory
fun revokeDelegationConfirmFactory(): RevokeDelegationConfirmComponent.Factory
fun referendaFiltersFactory(): ReferendaFiltersComponent.Factory
fun tinderGovCardsFactory(): TinderGovCardsComponent.Factory
fun tinderGovBasketFactory(): TinderGovBasketComponent.Factory
@Component.Factory
interface Factory {
fun create(
deps: GovernanceFeatureDependencies,
@BindsInstance router: GovernanceRouter,
@BindsInstance selectTracksCommunicator: SelectTracksCommunicator,
@BindsInstance tinderGovVoteCommunicator: TinderGovVoteCommunicator
): GovernanceFeatureComponent
}
@Component(
dependencies = [
CommonApi::class,
RuntimeApi::class,
WalletFeatureApi::class,
AccountFeatureApi::class,
DAppFeatureApi::class,
DbApi::class,
XcmFeatureApi::class,
DeepLinkingFeatureApi::class
]
)
interface GovernanceFeatureDependenciesComponent : GovernanceFeatureDependencies
}
@@ -0,0 +1,173 @@
package io.novafoundation.nova.feature_governance_impl.di
import android.content.Context
import coil.ImageLoader
import com.google.gson.Gson
import io.novafoundation.nova.common.address.AddressIconGenerator
import io.novafoundation.nova.common.data.memory.ComputationalCache
import io.novafoundation.nova.common.data.network.NetworkApiCreator
import io.novafoundation.nova.common.data.repository.BannerVisibilityRepository
import io.novafoundation.nova.common.data.storage.Preferences
import io.novafoundation.nova.common.di.modules.Caching
import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin
import io.novafoundation.nova.common.mixin.copy.CopyTextLauncher
import io.novafoundation.nova.common.mixin.hints.ResourcesHintsMixinFactory
import io.novafoundation.nova.common.presentation.AssetIconProvider
import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.common.utils.multiResult.PartialRetriableMixin
import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate
import io.novafoundation.nova.common.validation.ValidationExecutor
import io.novafoundation.nova.common.view.input.chooser.ListChooserMixin
import io.novafoundation.nova.core.storage.StorageCache
import io.novafoundation.nova.core_db.dao.GovernanceDAppsDao
import io.novafoundation.nova.core_db.dao.TinderGovDao
import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService
import io.novafoundation.nova.feature_account_api.data.repository.OnChainIdentityRepository
import io.novafoundation.nova.feature_account_api.domain.account.identity.IdentityProvider
import io.novafoundation.nova.feature_account_api.domain.account.identity.LocalIdentity
import io.novafoundation.nova.feature_account_api.domain.account.identity.OnChainIdentity
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase
import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.WalletUiUseCase
import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions
import io.novafoundation.nova.feature_account_api.presenatation.mixin.identity.IdentityMixin
import io.novafoundation.nova.feature_account_api.presenatation.navigation.ExtrinsicNavigationWrapper
import io.novafoundation.nova.feature_dapp_api.data.repository.DAppMetadataRepository
import io.novafoundation.nova.feature_deep_linking.presentation.configuring.LinkBuilderFactory
import io.novafoundation.nova.feature_wallet_api.data.repository.BalanceLocksRepository
import io.novafoundation.nova.feature_wallet_api.domain.interfaces.TokenRepository
import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.AmountFormatter
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.TokenFormatter
import io.novafoundation.nova.common.presentation.masking.formatter.MaskableValueFormatterFactory
import io.novafoundation.nova.common.presentation.masking.formatter.MaskableValueFormatterProvider
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.AssetModelFormatter
import io.novafoundation.nova.feature_wallet_api.presentation.mixin.amountChooser.AmountChooserMixin
import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin
import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.v2.FeeLoaderMixinV2
import io.novafoundation.nova.feature_wallet_api.presentation.mixin.maxAction.MaxActionProviderFactory
import io.novafoundation.nova.feature_xcm_api.converter.MultiLocationConverterFactory
import io.novafoundation.nova.feature_xcm_api.converter.chain.ChainMultiLocationConverterFactory
import io.novafoundation.nova.runtime.di.ExtrinsicSerialization
import io.novafoundation.nova.runtime.di.REMOTE_STORAGE_SOURCE
import io.novafoundation.nova.runtime.ethereum.StorageSharedRequestsBuilderFactory
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.repository.ChainStateRepository
import io.novafoundation.nova.runtime.repository.TotalIssuanceRepository
import io.novafoundation.nova.runtime.storage.SampledBlockTimeStorage
import io.novafoundation.nova.runtime.storage.source.StorageDataSource
import javax.inject.Named
interface GovernanceFeatureDependencies {
val maskableValueFormatterFactory: MaskableValueFormatterFactory
val maskableValueFormatterProvider: MaskableValueFormatterProvider
val amountFormatter: AmountFormatter
val tokenFormatter: TokenFormatter
val onChainIdentityRepository: OnChainIdentityRepository
val listChooserMixinFactory: ListChooserMixin.Factory
val identityMixinFactory: IdentityMixin.Factory
val partialRetriableMixinFactory: PartialRetriableMixin.Factory
val storageStorageSharedRequestsBuilderFactory: StorageSharedRequestsBuilderFactory
val bannerVisibilityRepository: BannerVisibilityRepository
val assetModelFormatter: AssetModelFormatter
val chainMultiLocationConverterFactory: ChainMultiLocationConverterFactory
val assetMultiLocationConverterFactory: MultiLocationConverterFactory
val assetIconProvider: AssetIconProvider
val feeLoaderMixinFactory: FeeLoaderMixin.Factory
val validationExecutor: ValidationExecutor
val preferences: Preferences
val walletRepository: WalletRepository
val chainRegistry: ChainRegistry
val imageLoader: ImageLoader
val addressIconGenerator: AddressIconGenerator
val resourceManager: ResourceManager
val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory
val tokenRepository: TokenRepository
val accountRepository: AccountRepository
val selectedAccountUseCase: SelectedAccountUseCase
val chainStateRepository: ChainStateRepository
val totalIssuanceRepository: TotalIssuanceRepository
val storageCache: StorageCache
val sampledBlockTimeStorage: SampledBlockTimeStorage
val dAppMetadataRepository: DAppMetadataRepository
val externalAccountActions: ExternalActions.Presentation
val context: Context
val amountMixinFactory: AmountChooserMixin.Factory
val extrinsicService: ExtrinsicService
val resourceHintsMixinFactory: ResourcesHintsMixinFactory
val walletUiUseCase: WalletUiUseCase
val balanceLocksRepository: BalanceLocksRepository
val computationalCache: ComputationalCache
val governanceDAppsDao: GovernanceDAppsDao
val tinderGovDao: TinderGovDao
val networkApiCreator: NetworkApiCreator
val feeLoaderMixinV2Factory: FeeLoaderMixinV2.Factory
val maxActionProviderFactory: MaxActionProviderFactory
val automaticInteractionGate: AutomaticInteractionGate
val linkBuilderFactory: LinkBuilderFactory
val extrinsicNavigationWrapper: ExtrinsicNavigationWrapper
val copyTextLauncher: CopyTextLauncher.Presentation
@Caching
fun cachingIconGenerator(): AddressIconGenerator
@ExtrinsicSerialization
fun extrinsicGson(): Gson
@LocalIdentity
fun localIdentityProvider(): IdentityProvider
@OnChainIdentity
fun onChainIdentityProvider(): IdentityProvider
@Named(REMOTE_STORAGE_SOURCE)
fun remoteStorageDataSource(): StorageDataSource
}
@@ -0,0 +1,46 @@
package io.novafoundation.nova.feature_governance_impl.di
import io.novafoundation.nova.common.di.FeatureApiHolder
import io.novafoundation.nova.common.di.FeatureContainer
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.core_db.di.DbApi
import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi
import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectTracksCommunicator
import io.novafoundation.nova.feature_dapp_api.di.DAppFeatureApi
import io.novafoundation.nova.feature_deep_linking.di.DeepLinkingFeatureApi
import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter
import io.novafoundation.nova.feature_governance_impl.presentation.referenda.vote.setup.tindergov.TinderGovVoteCommunicator
import io.novafoundation.nova.feature_wallet_api.di.WalletFeatureApi
import io.novafoundation.nova.feature_xcm_api.di.XcmFeatureApi
import io.novafoundation.nova.runtime.di.RuntimeApi
import javax.inject.Inject
@ApplicationScope
class GovernanceFeatureHolder @Inject constructor(
featureContainer: FeatureContainer,
private val router: GovernanceRouter,
private val selectTracksCommunicator: SelectTracksCommunicator,
private val tinderGovVoteCommunicator: TinderGovVoteCommunicator
) : FeatureApiHolder(featureContainer) {
override fun initializeDependencies(): Any {
val accountFeatureDependencies = DaggerGovernanceFeatureComponent_GovernanceFeatureDependenciesComponent.builder()
.commonApi(commonApi())
.dbApi(getFeature(DbApi::class.java))
.runtimeApi(getFeature(RuntimeApi::class.java))
.walletFeatureApi(getFeature(WalletFeatureApi::class.java))
.accountFeatureApi(getFeature(AccountFeatureApi::class.java))
.dAppFeatureApi(getFeature(DAppFeatureApi::class.java))
.xcmFeatureApi(getFeature(XcmFeatureApi::class.java))
.deepLinkingFeatureApi(getFeature(DeepLinkingFeatureApi::class.java))
.build()
return DaggerGovernanceFeatureComponent.factory()
.create(
accountFeatureDependencies,
router,
selectTracksCommunicator = selectTracksCommunicator,
tinderGovVoteCommunicator = tinderGovVoteCommunicator
)
}
}
@@ -0,0 +1,234 @@
package io.novafoundation.nova.feature_governance_impl.di
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.common.address.AddressIconGenerator
import io.novafoundation.nova.common.data.memory.ComputationalCache
import io.novafoundation.nova.common.data.model.MaskingMode
import io.novafoundation.nova.common.data.storage.Preferences
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.common.presentation.AssetIconProvider
import io.novafoundation.nova.common.presentation.masking.formatter.MaskableValueFormatterFactory
import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.feature_account_api.data.repository.OnChainIdentityRepository
import io.novafoundation.nova.feature_account_api.domain.account.identity.IdentityProvider
import io.novafoundation.nova.feature_account_api.domain.account.identity.LocalIdentity
import io.novafoundation.nova.feature_account_api.domain.account.identity.OnChainIdentity
import io.novafoundation.nova.feature_governance_api.data.MutableGovernanceState
import io.novafoundation.nova.feature_governance_api.data.repository.TreasuryRepository
import io.novafoundation.nova.feature_governance_api.data.source.GovernanceSource
import io.novafoundation.nova.feature_governance_api.data.source.GovernanceSourceRegistry
import io.novafoundation.nova.feature_governance_api.presentation.referenda.common.ReferendaStatusFormatter
import io.novafoundation.nova.feature_governance_api.presentation.referenda.details.deeplink.configurators.ReferendumDetailsDeepLinkConfigurator
import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState
import io.novafoundation.nova.feature_governance_impl.data.preimage.PreImageSizer
import io.novafoundation.nova.feature_governance_impl.data.preimage.RealPreImageSizer
import io.novafoundation.nova.feature_governance_impl.data.repository.RealTreasuryRepository
import io.novafoundation.nova.feature_governance_impl.data.source.RealGovernanceSourceRegistry
import io.novafoundation.nova.feature_governance_impl.di.modules.GovernanceDAppsModule
import io.novafoundation.nova.feature_governance_impl.di.modules.GovernanceUpdatersModule
import io.novafoundation.nova.feature_governance_impl.di.modules.deeplink.DeepLinkModule
import io.novafoundation.nova.feature_governance_impl.di.modules.screens.DelegateModule
import io.novafoundation.nova.feature_governance_impl.di.modules.screens.ReferendumDetailsModule
import io.novafoundation.nova.feature_governance_impl.di.modules.screens.ReferendumListModule
import io.novafoundation.nova.feature_governance_impl.di.modules.screens.ReferendumUnlockModule
import io.novafoundation.nova.feature_governance_impl.di.modules.screens.ReferendumVoteModule
import io.novafoundation.nova.feature_governance_impl.di.modules.screens.ReferendumVotersModule
import io.novafoundation.nova.feature_governance_impl.di.modules.screens.TinderGovModule
import io.novafoundation.nova.feature_governance_impl.di.modules.v1.GovernanceV1
import io.novafoundation.nova.feature_governance_impl.di.modules.v1.GovernanceV1Module
import io.novafoundation.nova.feature_governance_impl.di.modules.v2.GovernanceV2
import io.novafoundation.nova.feature_governance_impl.di.modules.v2.GovernanceV2Module
import io.novafoundation.nova.feature_governance_impl.domain.delegation.delegate.common.repository.DelegateCommonRepository
import io.novafoundation.nova.feature_governance_impl.domain.identity.GovernanceIdentityProviderFactory
import io.novafoundation.nova.feature_governance_impl.domain.referendum.common.RealReferendaConstructor
import io.novafoundation.nova.feature_governance_impl.domain.referendum.common.ReferendaConstructor
import io.novafoundation.nova.feature_governance_impl.domain.track.RealTracksUseCase
import io.novafoundation.nova.feature_governance_impl.domain.track.TracksUseCase
import io.novafoundation.nova.feature_governance_impl.domain.track.category.RealTrackCategorizer
import io.novafoundation.nova.feature_governance_impl.domain.track.category.TrackCategorizer
import io.novafoundation.nova.feature_governance_impl.presentation.common.conviction.ConvictionValuesProvider
import io.novafoundation.nova.feature_governance_impl.presentation.common.conviction.RealConvictionValuesProvider
import io.novafoundation.nova.feature_governance_impl.presentation.common.locks.LocksFormatter
import io.novafoundation.nova.feature_governance_impl.presentation.common.locks.RealLocksFormatter
import io.novafoundation.nova.feature_governance_impl.presentation.common.share.RealShareReferendumMixin
import io.novafoundation.nova.feature_governance_impl.presentation.common.share.ShareReferendumMixin
import io.novafoundation.nova.feature_governance_impl.presentation.common.voters.RealVotersFormatter
import io.novafoundation.nova.feature_governance_impl.presentation.common.voters.VotersFormatter
import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegate.detail.DelegatesSharedComputation
import io.novafoundation.nova.feature_governance_impl.presentation.referenda.common.RealReferendaStatusFormatter
import io.novafoundation.nova.feature_governance_impl.presentation.referenda.common.ReferendumFormatter
import io.novafoundation.nova.feature_governance_impl.presentation.referenda.common.ReferendumFormatterFactory
import io.novafoundation.nova.feature_governance_impl.presentation.track.RealTrackFormatter
import io.novafoundation.nova.feature_governance_impl.presentation.track.TrackFormatter
import io.novafoundation.nova.feature_wallet_api.di.common.SelectableAssetUseCaseModule
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.AmountFormatter
import io.novafoundation.nova.runtime.di.REMOTE_STORAGE_SOURCE
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.network.updaters.multiChain.DelegateToTimelineChainIdHolder
import io.novafoundation.nova.runtime.repository.ChainStateRepository
import io.novafoundation.nova.runtime.state.SelectableSingleAssetSharedState
import io.novafoundation.nova.runtime.storage.source.StorageDataSource
import javax.inject.Named
@Module(
includes = [
SelectableAssetUseCaseModule::class,
GovernanceV2Module::class,
GovernanceV1Module::class,
GovernanceUpdatersModule::class,
ReferendumDetailsModule::class,
ReferendumListModule::class,
ReferendumVotersModule::class,
ReferendumVoteModule::class,
ReferendumUnlockModule::class,
DelegateModule::class,
GovernanceDAppsModule::class,
TinderGovModule::class,
DeepLinkModule::class
]
)
class GovernanceFeatureModule {
@Provides
@FeatureScope
fun provideTimelineDelegatingHolder(stakingSharedState: GovernanceSharedState) = DelegateToTimelineChainIdHolder(stakingSharedState)
@Provides
@FeatureScope
fun provideDelegatesSharedComputation(
computationalCache: ComputationalCache,
delegateCommonRepository: DelegateCommonRepository,
chainStateRepository: ChainStateRepository,
identityRepository: OnChainIdentityRepository
) = DelegatesSharedComputation(
computationalCache,
delegateCommonRepository,
chainStateRepository,
identityRepository
)
@Provides
@FeatureScope
fun provideAssetSharedState(
chainRegistry: ChainRegistry,
preferences: Preferences,
) = GovernanceSharedState(chainRegistry, preferences)
@Provides
@FeatureScope
fun provideGovernanceStateUpdater(
governanceSharedState: GovernanceSharedState
): MutableGovernanceState = governanceSharedState
@Provides
@FeatureScope
fun provideSelectableSharedState(governanceSharedState: GovernanceSharedState): SelectableSingleAssetSharedState<*> = governanceSharedState
@Provides
@FeatureScope
fun provideGovernanceSourceRegistry(
@GovernanceV2 governanceV2Source: GovernanceSource,
@GovernanceV1 governanceV1Source: GovernanceSource,
): GovernanceSourceRegistry = RealGovernanceSourceRegistry(
governanceV2Source = governanceV2Source,
governanceV1Source = governanceV1Source
)
@Provides
@FeatureScope
fun provideTreasuryRepository(
@Named(REMOTE_STORAGE_SOURCE) storageSource: StorageDataSource
): TreasuryRepository = RealTreasuryRepository(storageSource)
@Provides
@FeatureScope
fun provideReferendumConstructor(
governanceSourceRegistry: GovernanceSourceRegistry,
chainStateRepository: ChainStateRepository
): ReferendaConstructor = RealReferendaConstructor(governanceSourceRegistry, chainStateRepository)
@Provides
@FeatureScope
fun provideGovernanceIdentityProviderFactory(
@LocalIdentity localProvider: IdentityProvider,
@OnChainIdentity onChainProvider: IdentityProvider
): GovernanceIdentityProviderFactory = GovernanceIdentityProviderFactory(
localProvider = localProvider,
onChainProvider = onChainProvider
)
@Provides
@FeatureScope
fun providePreImageSizer(): PreImageSizer = RealPreImageSizer()
@Provides
@FeatureScope
fun provideTrackCategorizer(): TrackCategorizer = RealTrackCategorizer()
@Provides
@FeatureScope
fun provideTracksFormatter(
trackCategorizer: TrackCategorizer,
resourceManager: ResourceManager,
assetIconProvider: AssetIconProvider
): TrackFormatter = RealTrackFormatter(trackCategorizer, resourceManager, assetIconProvider)
@Provides
@FeatureScope
fun provideReferendaStatusFormatter(
resourceManager: ResourceManager
): ReferendaStatusFormatter = RealReferendaStatusFormatter(resourceManager)
@Provides
@FeatureScope
fun provideReferendumFormatterFactory(
resourceManager: ResourceManager,
trackFormatter: TrackFormatter,
referendaStatusFormatter: ReferendaStatusFormatter,
amountFormatter: AmountFormatter
) = ReferendumFormatterFactory(
resourceManager,
trackFormatter,
referendaStatusFormatter,
amountFormatter
)
@Provides
@FeatureScope
fun provideDefaultReferendumFormatter(
referendumFormatterFactory: ReferendumFormatterFactory,
maskableValueFormatterFactory: MaskableValueFormatterFactory
): ReferendumFormatter = referendumFormatterFactory.create(maskableValueFormatterFactory.create(MaskingMode.DISABLED))
@Provides
@FeatureScope
fun provideVotersFormatter(
resourceManager: ResourceManager,
addressIconGenerator: AddressIconGenerator
): VotersFormatter = RealVotersFormatter(addressIconGenerator, resourceManager)
@Provides
@FeatureScope
fun provideLocksFormatter(
resourceManager: ResourceManager,
amountFormatter: AmountFormatter
): LocksFormatter = RealLocksFormatter(resourceManager, amountFormatter)
@Provides
@FeatureScope
fun provideConvictionValuesProvider(): ConvictionValuesProvider = RealConvictionValuesProvider()
@Provides
@FeatureScope
fun provideTracksUseCase(
governanceSharedState: GovernanceSharedState,
governanceSourceRegistry: GovernanceSourceRegistry,
): TracksUseCase = RealTracksUseCase(governanceSharedState, governanceSourceRegistry)
@Provides
@FeatureScope
fun provideShareReferendumMixin(
referendumLinkConfigurator: ReferendumDetailsDeepLinkConfigurator
): ShareReferendumMixin = RealShareReferendumMixin(referendumLinkConfigurator)
}
@@ -0,0 +1,36 @@
package io.novafoundation.nova.feature_governance_impl.di.modules
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.common.data.network.NetworkApiCreator
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.core_db.dao.GovernanceDAppsDao
import io.novafoundation.nova.feature_governance_api.data.source.GovernanceSourceRegistry
import io.novafoundation.nova.feature_governance_impl.data.dapps.GovernanceDAppsSyncService
import io.novafoundation.nova.feature_governance_impl.data.dapps.remote.GovernanceDappsFetcher
import io.novafoundation.nova.feature_governance_impl.domain.dapp.GovernanceDAppsInteractor
@Module
class GovernanceDAppsModule {
@Provides
@FeatureScope
fun provideGovernanceDappsFetcher(apiCreator: NetworkApiCreator) = apiCreator.create(GovernanceDappsFetcher::class.java)
@Provides
@FeatureScope
fun provideGovernanceSyncService(
dao: GovernanceDAppsDao,
chainFetcher: GovernanceDappsFetcher
) = GovernanceDAppsSyncService(dao, chainFetcher)
@Provides
@FeatureScope
fun provideGovernanceDAppInteractor(
governanceDAppsSyncService: GovernanceDAppsSyncService,
governanceSourceRegistry: GovernanceSourceRegistry
): GovernanceDAppsInteractor = GovernanceDAppsInteractor(
governanceDAppsSyncService = governanceDAppsSyncService,
governanceSourceRegistry = governanceSourceRegistry
)
}
@@ -0,0 +1,89 @@
package io.novafoundation.nova.feature_governance_impl.di.modules
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.core.storage.StorageCache
import io.novafoundation.nova.core.updater.UpdateSystem
import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState
import io.novafoundation.nova.runtime.di.REMOTE_STORAGE_SOURCE
import io.novafoundation.nova.runtime.ethereum.StorageSharedRequestsBuilderFactory
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.network.updaters.BlockTimeUpdater
import io.novafoundation.nova.runtime.network.updaters.InactiveIssuanceUpdater
import io.novafoundation.nova.runtime.network.updaters.SharedAssetBlockNumberUpdater
import io.novafoundation.nova.runtime.network.updaters.TotalIssuanceUpdater
import io.novafoundation.nova.runtime.network.updaters.multiChain.AsSharedStateUpdater
import io.novafoundation.nova.runtime.network.updaters.multiChain.DelegateToTimeLineChainUpdater
import io.novafoundation.nova.runtime.network.updaters.multiChain.DelegateToTimelineChainIdHolder
import io.novafoundation.nova.runtime.network.updaters.multiChain.GroupBySyncChainMultiChainUpdateSystem
import io.novafoundation.nova.runtime.storage.SampledBlockTimeStorage
import io.novafoundation.nova.runtime.storage.source.StorageDataSource
import javax.inject.Named
@Module
class GovernanceUpdatersModule {
@Provides
@FeatureScope
fun provideUpdateSystem(
totalIssuanceUpdater: TotalIssuanceUpdater,
inactiveIssuanceUpdater: InactiveIssuanceUpdater,
blockNumberUpdater: SharedAssetBlockNumberUpdater,
blockTimeUpdater: BlockTimeUpdater,
chainRegistry: ChainRegistry,
singleAssetSharedState: GovernanceSharedState,
storageSharedRequestsBuilderFactory: StorageSharedRequestsBuilderFactory,
): UpdateSystem = GroupBySyncChainMultiChainUpdateSystem(
updaters = listOf(
AsSharedStateUpdater(totalIssuanceUpdater),
AsSharedStateUpdater(inactiveIssuanceUpdater),
DelegateToTimeLineChainUpdater(blockNumberUpdater),
DelegateToTimeLineChainUpdater(blockTimeUpdater),
),
chainRegistry = chainRegistry,
singleAssetSharedState = singleAssetSharedState,
storageSharedRequestsBuilderFactory = storageSharedRequestsBuilderFactory,
)
@Provides
@FeatureScope
fun blockTimeUpdater(
chainIdHolder: DelegateToTimelineChainIdHolder,
chainRegistry: ChainRegistry,
sampledBlockTimeStorage: SampledBlockTimeStorage,
@Named(REMOTE_STORAGE_SOURCE) remoteStorage: StorageDataSource,
) = BlockTimeUpdater(chainIdHolder, chainRegistry, sampledBlockTimeStorage, remoteStorage)
@Provides
@FeatureScope
fun provideBlockNumberUpdater(
chainRegistry: ChainRegistry,
chainIdHolder: DelegateToTimelineChainIdHolder,
storageCache: StorageCache,
) = SharedAssetBlockNumberUpdater(chainRegistry, chainIdHolder, storageCache)
@Provides
@FeatureScope
fun provideTotalInsuranceUpdater(
sharedState: GovernanceSharedState,
chainRegistry: ChainRegistry,
storageCache: StorageCache,
) = TotalIssuanceUpdater(
sharedState,
storageCache,
chainRegistry
)
@Provides
@FeatureScope
fun provideInactiveInsuranceUpdater(
sharedState: GovernanceSharedState,
chainRegistry: ChainRegistry,
storageCache: StorageCache,
) = InactiveIssuanceUpdater(
sharedState,
storageCache,
chainRegistry
)
}
@@ -0,0 +1,48 @@
package io.novafoundation.nova.feature_governance_impl.di.modules.deeplink
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate
import io.novafoundation.nova.feature_deep_linking.presentation.configuring.LinkBuilderFactory
import io.novafoundation.nova.feature_governance_api.data.MutableGovernanceState
import io.novafoundation.nova.feature_governance_api.di.deeplinks.GovernanceDeepLinks
import io.novafoundation.nova.feature_governance_api.presentation.referenda.details.deeplink.configurators.ReferendumDetailsDeepLinkConfigurator
import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter
import io.novafoundation.nova.feature_governance_impl.presentation.referenda.details.deeplink.ReferendumDeepLinkHandler
import io.novafoundation.nova.feature_governance_impl.presentation.referenda.details.deeplink.RealReferendumDetailsDeepLinkConfigurator
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
@Module
class DeepLinkModule {
@Provides
@FeatureScope
fun provideDeepLinkConfigurator(
linkBuilderFactory: LinkBuilderFactory
): ReferendumDetailsDeepLinkConfigurator {
return RealReferendumDetailsDeepLinkConfigurator(linkBuilderFactory)
}
@Provides
@FeatureScope
fun provideReferendumDeepLinkHandler(
router: GovernanceRouter,
chainRegistry: ChainRegistry,
mutableGovernanceState: MutableGovernanceState,
automaticInteractionGate: AutomaticInteractionGate
): ReferendumDeepLinkHandler {
return ReferendumDeepLinkHandler(
router,
chainRegistry,
mutableGovernanceState,
automaticInteractionGate
)
}
@Provides
@FeatureScope
fun provideDeepLinks(referendum: ReferendumDeepLinkHandler): GovernanceDeepLinks {
return GovernanceDeepLinks(listOf(referendum))
}
}
@@ -0,0 +1,180 @@
package io.novafoundation.nova.feature_governance_impl.di.modules.screens
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.common.address.AddressIconGenerator
import io.novafoundation.nova.common.data.memory.ComputationalCache
import io.novafoundation.nova.common.data.repository.BannerVisibilityRepository
import io.novafoundation.nova.common.data.storage.Preferences
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.common.validation.ValidationSystem
import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService
import io.novafoundation.nova.feature_account_api.data.repository.OnChainIdentityRepository
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
import io.novafoundation.nova.feature_governance_api.data.source.GovernanceSourceRegistry
import io.novafoundation.nova.feature_governance_api.domain.delegation.delegate.delegators.DelegateDelegatorsInteractor
import io.novafoundation.nova.feature_governance_api.domain.delegation.delegate.details.model.DelegateDetailsInteractor
import io.novafoundation.nova.feature_governance_api.domain.delegation.delegate.label.DelegateLabelUseCase
import io.novafoundation.nova.feature_governance_api.domain.delegation.delegate.list.DelegateListInteractor
import io.novafoundation.nova.feature_governance_api.domain.delegation.delegation.common.chooseTrack.ChooseTrackInteractor
import io.novafoundation.nova.feature_governance_api.domain.delegation.delegation.create.chooseAmount.NewDelegationChooseAmountInteractor
import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState
import io.novafoundation.nova.feature_governance_impl.data.repository.RealRemoveVotesSuggestionRepository
import io.novafoundation.nova.feature_governance_impl.data.repository.RemoveVotesSuggestionRepository
import io.novafoundation.nova.feature_governance_impl.domain.delegation.delegate.common.RecentVotesTimePointProvider
import io.novafoundation.nova.feature_governance_impl.domain.delegation.delegate.common.repository.DelegateCommonRepository
import io.novafoundation.nova.feature_governance_impl.domain.delegation.delegate.common.repository.RealDelegateCommonRepository
import io.novafoundation.nova.feature_governance_impl.domain.delegation.delegate.delegators.RealDelegateDelegatorsInteractor
import io.novafoundation.nova.feature_governance_impl.domain.delegation.delegate.details.RealDelegateDetailsInteractor
import io.novafoundation.nova.feature_governance_impl.domain.delegation.delegate.label.RealDelegateLabelUseCase
import io.novafoundation.nova.feature_governance_impl.domain.delegation.delegate.list.RealDelegateListInteractor
import io.novafoundation.nova.feature_governance_impl.domain.delegation.delegation.create.chooseAmount.RealNewDelegationChooseAmountInteractor
import io.novafoundation.nova.feature_governance_impl.domain.delegation.delegation.create.chooseAmount.validation.ChooseDelegationAmountValidationSystem
import io.novafoundation.nova.feature_governance_impl.domain.delegation.delegation.create.chooseAmount.validation.chooseDelegationAmount
import io.novafoundation.nova.feature_governance_impl.domain.delegation.delegation.create.chooseTrack.RealChooseTrackInteractor
import io.novafoundation.nova.feature_governance_impl.domain.track.category.TrackCategorizer
import io.novafoundation.nova.feature_governance_impl.presentation.common.voters.VotersFormatter
import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegate.common.DelegateMappers
import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegate.common.RealDelegateMappers
import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegate.detail.DelegatesSharedComputation
import io.novafoundation.nova.feature_governance_impl.presentation.track.TrackFormatter
import io.novafoundation.nova.feature_wallet_api.data.repository.BalanceLocksRepository
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.repository.ChainStateRepository
@Module
class DelegateModule {
@Provides
@FeatureScope
fun provideRecentVotesTimePointProvider(
chainStateRepository: ChainStateRepository
): RecentVotesTimePointProvider {
return RecentVotesTimePointProvider(chainStateRepository)
}
@Provides
@FeatureScope
fun provideDelegateCommonRepository(
governanceSourceRegistry: GovernanceSourceRegistry,
accountRepository: AccountRepository,
recentVotesTimePointProvider: RecentVotesTimePointProvider
): DelegateCommonRepository = RealDelegateCommonRepository(
governanceSourceRegistry = governanceSourceRegistry,
accountRepository = accountRepository,
recentVotesTimePointProvider = recentVotesTimePointProvider
)
@Provides
@FeatureScope
fun provideDelegateListInteractor(
bannerVisibilityRepository: BannerVisibilityRepository,
delegatesSharedComputation: DelegatesSharedComputation
): DelegateListInteractor = RealDelegateListInteractor(
bannerVisibilityRepository = bannerVisibilityRepository,
delegatesSharedComputation = delegatesSharedComputation
)
@Provides
@FeatureScope
fun provideDelegateDetailsInteractor(
governanceSourceRegistry: GovernanceSourceRegistry,
chainStateRepository: ChainStateRepository,
identityRepository: OnChainIdentityRepository,
governanceSharedState: GovernanceSharedState,
accountRepository: AccountRepository,
recentVotesTimePointProvider: RecentVotesTimePointProvider
): DelegateDetailsInteractor = RealDelegateDetailsInteractor(
governanceSourceRegistry = governanceSourceRegistry,
chainStateRepository = chainStateRepository,
identityRepository = identityRepository,
governanceSharedState = governanceSharedState,
accountRepository = accountRepository,
recentVotesTimePointProvider = recentVotesTimePointProvider
)
@Provides
@FeatureScope
fun provideNewDelegationChooseTrackInteractor(
governanceSharedState: GovernanceSharedState,
governanceSourceRegistry: GovernanceSourceRegistry,
chainStateRepository: ChainStateRepository,
accountRepository: AccountRepository,
trackCategorizer: TrackCategorizer,
removeVotesSuggestionRepository: RemoveVotesSuggestionRepository,
chainRegistry: ChainRegistry
): ChooseTrackInteractor = RealChooseTrackInteractor(
governanceSharedState = governanceSharedState,
governanceSourceRegistry = governanceSourceRegistry,
chainStateRepository = chainStateRepository,
accountRepository = accountRepository,
trackCategorizer = trackCategorizer,
removeVotesSuggestionRepository = removeVotesSuggestionRepository,
chainRegistry = chainRegistry
)
@Provides
@FeatureScope
fun provideDelegateDelegatorsInteractor(
governanceSharedState: GovernanceSharedState,
governanceSourceRegistry: GovernanceSourceRegistry,
identityRepository: OnChainIdentityRepository,
): DelegateDelegatorsInteractor = RealDelegateDelegatorsInteractor(
governanceSharedState = governanceSharedState,
governanceSourceRegistry = governanceSourceRegistry,
identityRepository = identityRepository
)
@Provides
@FeatureScope
fun provideDelegateMappers(
resourceManager: ResourceManager,
addressIconGenerator: AddressIconGenerator,
trackFormatter: TrackFormatter,
votersFormatter: VotersFormatter
): DelegateMappers = RealDelegateMappers(resourceManager, addressIconGenerator, trackFormatter, votersFormatter)
@Provides
@FeatureScope
fun provideNewDelegationChooseAmountInteractor(
governanceSourceRegistry: GovernanceSourceRegistry,
chainStateRepository: ChainStateRepository,
selectedChainState: GovernanceSharedState,
extrinsicService: ExtrinsicService,
locksRepository: BalanceLocksRepository,
computationalCache: ComputationalCache,
accountRepository: AccountRepository,
): NewDelegationChooseAmountInteractor {
return RealNewDelegationChooseAmountInteractor(
governanceSourceRegistry = governanceSourceRegistry,
chainStateRepository = chainStateRepository,
selectedChainState = selectedChainState,
extrinsicService = extrinsicService,
locksRepository = locksRepository,
computationalCache = computationalCache,
accountRepository = accountRepository
)
}
@Provides
@FeatureScope
fun provideNewDelegationChooseAmountValidationSystem(
governanceSharedState: GovernanceSharedState,
accountRepository: AccountRepository
): ChooseDelegationAmountValidationSystem {
return ValidationSystem.chooseDelegationAmount(governanceSharedState, accountRepository)
}
@Provides
@FeatureScope
fun provideDelegateLabelUseCase(
governanceSharedState: GovernanceSharedState,
governanceSourceRegistry: GovernanceSourceRegistry,
identityRepository: OnChainIdentityRepository,
): DelegateLabelUseCase = RealDelegateLabelUseCase(governanceSharedState, governanceSourceRegistry, identityRepository)
@Provides
@FeatureScope
fun provideRemoveVotesSuggestionRepository(preferences: Preferences): RemoveVotesSuggestionRepository = RealRemoveVotesSuggestionRepository(preferences)
}
@@ -0,0 +1,93 @@
package io.novafoundation.nova.feature_governance_impl.di.modules.screens
import com.google.gson.Gson
import dagger.Module
import dagger.Provides
import dagger.multibindings.IntoSet
import io.novafoundation.nova.common.data.memory.ComputationalCache
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.feature_account_api.data.repository.OnChainIdentityRepository
import io.novafoundation.nova.feature_governance_api.data.repository.TreasuryRepository
import io.novafoundation.nova.feature_governance_api.data.source.GovernanceSourceRegistry
import io.novafoundation.nova.feature_governance_api.domain.referendum.details.ReferendumDetailsInteractor
import io.novafoundation.nova.feature_governance_impl.data.preimage.PreImageSizer
import io.novafoundation.nova.feature_governance_impl.domain.referendum.common.ReferendaConstructor
import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.OffChainReferendumVotingSharedComputation
import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.RealReferendumDetailsInteractor
import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.RealReferendumPreImageParser
import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.ReferendumCallAdapter
import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.ReferendumPreImageParser
import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.batch.BatchAdapter
import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.treasury.TreasuryApproveProposalAdapter
import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.treasury.TreasurySpendAdapter
import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.treasury.TreasurySpendLocalAdapter
import io.novafoundation.nova.feature_xcm_api.converter.MultiLocationConverterFactory
import io.novafoundation.nova.feature_xcm_api.converter.chain.ChainMultiLocationConverterFactory
import io.novafoundation.nova.runtime.di.ExtrinsicSerialization
import io.novafoundation.nova.runtime.repository.ChainStateRepository
@Module
class ReferendumDetailsModule {
@Provides
@FeatureScope
@IntoSet
fun provideTreasuryApproveParser(
treasuryRepository: TreasuryRepository
): ReferendumCallAdapter = TreasuryApproveProposalAdapter(treasuryRepository)
@Provides
@FeatureScope
@IntoSet
fun provideTreasurySpendParser(
chainLocationConverter: ChainMultiLocationConverterFactory,
assetLocationConverter: MultiLocationConverterFactory
): ReferendumCallAdapter = TreasurySpendAdapter(chainLocationConverter, assetLocationConverter)
@Provides
@FeatureScope
@IntoSet
fun provideTreasurySpendLocalParser(): ReferendumCallAdapter = TreasurySpendLocalAdapter()
@Provides
@FeatureScope
@IntoSet
fun provideBatchAdapter(): ReferendumCallAdapter = BatchAdapter()
@Provides
@FeatureScope
fun providePreImageParser(
callAdapters: Set<@JvmSuppressWildcards ReferendumCallAdapter>
): ReferendumPreImageParser {
return RealReferendumPreImageParser(callAdapters)
}
@Provides
@FeatureScope
fun provideReferendumDetailsInteractor(
preImageParser: ReferendumPreImageParser,
governanceSourceRegistry: GovernanceSourceRegistry,
chainStateRepository: ChainStateRepository,
referendaConstructor: ReferendaConstructor,
preImageSizer: PreImageSizer,
@ExtrinsicSerialization callFormatter: Gson,
identityRepository: OnChainIdentityRepository,
offChainReferendumVotingSharedComputation: OffChainReferendumVotingSharedComputation
): ReferendumDetailsInteractor = RealReferendumDetailsInteractor(
preImageParser = preImageParser,
governanceSourceRegistry = governanceSourceRegistry,
chainStateRepository = chainStateRepository,
referendaConstructor = referendaConstructor,
preImageSizer = preImageSizer,
callFormatter = callFormatter,
identityRepository = identityRepository,
offChainReferendumVotingSharedComputation = offChainReferendumVotingSharedComputation
)
@Provides
@FeatureScope
fun provideOffChainReferendumVotingSharedComputation(
computationalCache: ComputationalCache,
governanceSourceRegistry: GovernanceSourceRegistry,
) = OffChainReferendumVotingSharedComputation(computationalCache, governanceSourceRegistry)
}
@@ -0,0 +1,102 @@
package io.novafoundation.nova.feature_governance_impl.di.modules.screens
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.common.data.memory.ComputationalCache
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.feature_account_api.data.repository.OnChainIdentityRepository
import io.novafoundation.nova.feature_governance_api.data.source.GovernanceSourceRegistry
import io.novafoundation.nova.feature_governance_api.domain.referendum.list.ReferendaListInteractor
import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState
import io.novafoundation.nova.feature_governance_impl.data.repository.filters.PreferencesReferendaFiltersRepository
import io.novafoundation.nova.feature_governance_impl.data.repository.filters.ReferendaFiltersRepository
import io.novafoundation.nova.feature_governance_impl.domain.delegation.delegate.common.RecentVotesTimePointProvider
import io.novafoundation.nova.feature_governance_impl.domain.filters.RealReferendaFiltersInteractor
import io.novafoundation.nova.feature_governance_impl.domain.filters.ReferendaFiltersInteractor
import io.novafoundation.nova.feature_governance_impl.domain.referendum.common.ReferendaConstructor
import io.novafoundation.nova.feature_governance_impl.domain.referendum.list.RealReferendaListInteractor
import io.novafoundation.nova.feature_governance_impl.domain.referendum.list.ReferendaSharedComputation
import io.novafoundation.nova.feature_governance_impl.domain.referendum.list.filtering.RealReferendaFilteringProvider
import io.novafoundation.nova.feature_governance_impl.domain.referendum.list.filtering.ReferendaFilteringProvider
import io.novafoundation.nova.feature_governance_impl.domain.referendum.list.repository.RealReferendaCommonRepository
import io.novafoundation.nova.feature_governance_impl.domain.referendum.list.repository.ReferendaCommonRepository
import io.novafoundation.nova.feature_governance_impl.domain.referendum.list.sorting.RealReferendaSortingProvider
import io.novafoundation.nova.feature_governance_impl.domain.referendum.list.sorting.ReferendaSortingProvider
import io.novafoundation.nova.runtime.repository.ChainStateRepository
@Module
class ReferendumListModule {
@Provides
@FeatureScope
fun provideReferendaFiltersRepository(): ReferendaFiltersRepository {
return PreferencesReferendaFiltersRepository()
}
@Provides
@FeatureScope
fun provideReferendaFiltersInteractor(
referendaFiltersRepository: ReferendaFiltersRepository
): ReferendaFiltersInteractor {
return RealReferendaFiltersInteractor(referendaFiltersRepository)
}
@Provides
@FeatureScope
fun provideReferendaSortingProvider(): ReferendaSortingProvider {
return RealReferendaSortingProvider()
}
@Provides
@FeatureScope
fun provideReferendaFilteringProvider(): ReferendaFilteringProvider {
return RealReferendaFilteringProvider()
}
@Provides
@FeatureScope
fun provideReferendaCommonRepository(
chainStateRepository: ChainStateRepository,
governanceSourceRegistry: GovernanceSourceRegistry,
referendaConstructor: ReferendaConstructor,
referendaSortingProvider: ReferendaSortingProvider,
identityRepository: OnChainIdentityRepository,
recentVotesTimePointProvider: RecentVotesTimePointProvider
): ReferendaCommonRepository {
return RealReferendaCommonRepository(
chainStateRepository = chainStateRepository,
governanceSourceRegistry = governanceSourceRegistry,
referendaConstructor = referendaConstructor,
referendaSortingProvider = referendaSortingProvider,
identityRepository = identityRepository,
recentVotesTimePointProvider = recentVotesTimePointProvider
)
}
@Provides
@FeatureScope
fun provideReferendaSharedComputation(
computationalCache: ComputationalCache,
referendaCommonRepository: ReferendaCommonRepository
): ReferendaSharedComputation {
return ReferendaSharedComputation(computationalCache, referendaCommonRepository)
}
@Provides
@FeatureScope
fun provideReferendaListInteractor(
referendaCommonRepository: ReferendaCommonRepository,
governanceSharedState: GovernanceSharedState,
referendaSharedComputation: ReferendaSharedComputation,
governanceSourceRegistry: GovernanceSourceRegistry,
referendaSortingProvider: ReferendaSortingProvider,
referendaFilteringProvider: ReferendaFilteringProvider
): ReferendaListInteractor = RealReferendaListInteractor(
referendaCommonRepository = referendaCommonRepository,
governanceSharedState = governanceSharedState,
referendaSharedComputation = referendaSharedComputation,
governanceSourceRegistry = governanceSourceRegistry,
referendaSortingProvider = referendaSortingProvider,
referendaFilteringProvider = referendaFilteringProvider
)
}
@@ -0,0 +1,40 @@
package io.novafoundation.nova.feature_governance_impl.di.modules.screens
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.common.data.memory.ComputationalCache
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
import io.novafoundation.nova.feature_governance_api.data.source.GovernanceSourceRegistry
import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState
import io.novafoundation.nova.feature_governance_impl.domain.referendum.unlock.GovernanceUnlockInteractor
import io.novafoundation.nova.feature_governance_impl.domain.referendum.unlock.RealGovernanceUnlockInteractor
import io.novafoundation.nova.feature_wallet_api.data.repository.BalanceLocksRepository
import io.novafoundation.nova.runtime.repository.ChainStateRepository
@Module
class ReferendumUnlockModule {
@Provides
@FeatureScope
fun provideGovernanceLocksOverviewInteractor(
selectedAssetState: GovernanceSharedState,
governanceSourceRegistry: GovernanceSourceRegistry,
chainStateRepository: ChainStateRepository,
computationalCache: ComputationalCache,
accountRepository: AccountRepository,
balanceLocksRepository: BalanceLocksRepository,
extrinsicService: ExtrinsicService,
): GovernanceUnlockInteractor {
return RealGovernanceUnlockInteractor(
selectedAssetState = selectedAssetState,
governanceSourceRegistry = governanceSourceRegistry,
chainStateRepository = chainStateRepository,
computationalCache = computationalCache,
accountRepository = accountRepository,
balanceLocksRepository = balanceLocksRepository,
extrinsicService = extrinsicService
)
}
}
@@ -0,0 +1,69 @@
package io.novafoundation.nova.feature_governance_impl.di.modules.screens
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.common.data.memory.ComputationalCache
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.common.mixin.hints.ResourcesHintsMixinFactory
import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.common.validation.ValidationSystem
import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
import io.novafoundation.nova.feature_governance_api.data.source.GovernanceSourceRegistry
import io.novafoundation.nova.feature_governance_api.domain.referendum.vote.VoteReferendumInteractor
import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState
import io.novafoundation.nova.feature_governance_impl.domain.referendum.vote.RealVoteReferendumInteractor
import io.novafoundation.nova.feature_governance_impl.domain.referendum.vote.validations.referendum.VoteReferendumValidationSystem
import io.novafoundation.nova.feature_governance_impl.domain.referendum.vote.validations.referendum.voteReferendumValidationSystem
import io.novafoundation.nova.feature_governance_impl.presentation.referenda.vote.common.LocksChangeFormatter
import io.novafoundation.nova.feature_governance_impl.presentation.referenda.vote.common.RealLocksChangeFormatter
import io.novafoundation.nova.feature_governance_impl.presentation.referenda.vote.hints.ReferendumVoteHintsMixinFactory
import io.novafoundation.nova.feature_wallet_api.data.repository.BalanceLocksRepository
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.AmountFormatter
import io.novafoundation.nova.runtime.repository.ChainStateRepository
@Module
class ReferendumVoteModule {
@Provides
@FeatureScope
fun provideReferendumVoteInteractor(
governanceSourceRegistry: GovernanceSourceRegistry,
chainStateRepository: ChainStateRepository,
selectedChainState: GovernanceSharedState,
accountRepository: AccountRepository,
locksRepository: BalanceLocksRepository,
extrinsicService: ExtrinsicService,
computationalCache: ComputationalCache
): VoteReferendumInteractor {
return RealVoteReferendumInteractor(
governanceSourceRegistry = governanceSourceRegistry,
chainStateRepository = chainStateRepository,
selectedChainState = selectedChainState,
accountRepository = accountRepository,
extrinsicService = extrinsicService,
locksRepository = locksRepository,
computationalCache = computationalCache
)
}
@Provides
@FeatureScope
fun provideHintsMixinFactory(
resHintsMixinFactory: ResourcesHintsMixinFactory
): ReferendumVoteHintsMixinFactory = ReferendumVoteHintsMixinFactory(resHintsMixinFactory)
@Provides
@FeatureScope
fun provideLocksChangeFormatter(
resourceManager: ResourceManager,
amountFormatter: AmountFormatter
): LocksChangeFormatter = RealLocksChangeFormatter(resourceManager, amountFormatter)
@Provides
@FeatureScope
fun provideValidationSystem(
governanceSourceRegistry: GovernanceSourceRegistry,
governanceSharedState: GovernanceSharedState,
): VoteReferendumValidationSystem = ValidationSystem.voteReferendumValidationSystem(governanceSourceRegistry, governanceSharedState)
}
@@ -0,0 +1,27 @@
package io.novafoundation.nova.feature_governance_impl.di.modules.screens
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.feature_account_api.domain.account.identity.IdentityProvider
import io.novafoundation.nova.feature_account_api.domain.account.identity.OnChainIdentity
import io.novafoundation.nova.feature_governance_api.data.source.GovernanceSourceRegistry
import io.novafoundation.nova.feature_governance_api.domain.referendum.voters.ReferendumVotersInteractor
import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState
import io.novafoundation.nova.feature_governance_impl.domain.referendum.voters.RealReferendumVotersInteractor
@Module
class ReferendumVotersModule {
@Provides
@FeatureScope
fun provideReferendaVotersInteractor(
governanceSourceRegistry: GovernanceSourceRegistry,
@OnChainIdentity identityProvider: IdentityProvider,
governanceSharedState: GovernanceSharedState,
): ReferendumVotersInteractor = RealReferendumVotersInteractor(
governanceSourceRegistry = governanceSourceRegistry,
identityProvider = identityProvider,
governanceSharedState = governanceSharedState,
)
}
@@ -0,0 +1,135 @@
package io.novafoundation.nova.feature_governance_impl.di.modules.screens
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.common.data.memory.ComputationalCache
import io.novafoundation.nova.common.data.network.NetworkApiCreator
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.core_db.dao.TinderGovDao
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
import io.novafoundation.nova.feature_governance_api.data.source.GovernanceSourceRegistry
import io.novafoundation.nova.feature_governance_impl.domain.summary.ReferendaSummaryInteractor
import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.TinderGovBasketInteractor
import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.TinderGovInteractor
import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.RealReferendumSummaryDataSource
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.ReferendumSummaryApi
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.ReferendumSummaryDataSource
import io.novafoundation.nova.feature_governance_impl.data.repository.tindergov.RealTinderGovBasketRepository
import io.novafoundation.nova.feature_governance_impl.data.repository.tindergov.RealTinderGovVotingPowerRepository
import io.novafoundation.nova.feature_governance_impl.data.repository.tindergov.TinderGovBasketRepository
import io.novafoundation.nova.feature_governance_impl.data.repository.tindergov.TinderGovVotingPowerRepository
import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.RealReferendumDetailsRepository
import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.ReferendumDetailsRepository
import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.ReferendumPreImageParser
import io.novafoundation.nova.feature_governance_impl.domain.referendum.list.ReferendaSharedComputation
import io.novafoundation.nova.feature_governance_impl.domain.referendum.list.filtering.ReferendaFilteringProvider
import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.RealTinderGovBasketInteractor
import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.RealTinderGovInteractor
import io.novafoundation.nova.feature_governance_impl.domain.summary.RealReferendaSummaryInteractor
import io.novafoundation.nova.feature_governance_impl.domain.summary.ReferendaSummarySharedComputation
import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase
@Module
class TinderGovModule {
@Provides
@FeatureScope
fun provideReferendumSummaryApi(apiCreator: NetworkApiCreator): ReferendumSummaryApi {
return apiCreator.create(ReferendumSummaryApi::class.java)
}
@Provides
@FeatureScope
fun provideSummaryDataSource(
referendumSummaryApi: ReferendumSummaryApi
): ReferendumSummaryDataSource {
return RealReferendumSummaryDataSource(referendumSummaryApi)
}
@Provides
@FeatureScope
fun provideTinderGovBasketRepository(dao: TinderGovDao): TinderGovBasketRepository {
return RealTinderGovBasketRepository(dao)
}
@Provides
@FeatureScope
fun provideTinderGovVotingPowerRepository(
tinderGovDao: TinderGovDao
): TinderGovVotingPowerRepository {
return RealTinderGovVotingPowerRepository(tinderGovDao)
}
@Provides
@FeatureScope
fun provideReferendumDetailsRepository(
dataSource: ReferendumSummaryDataSource
): ReferendumDetailsRepository {
return RealReferendumDetailsRepository(dataSource)
}
@Provides
@FeatureScope
fun provideTinderGovInteractor(
governanceSharedState: GovernanceSharedState,
referendaSharedComputation: ReferendaSharedComputation,
accountRepository: AccountRepository,
preImageParser: ReferendumPreImageParser,
tinderGovVotingPowerRepository: TinderGovVotingPowerRepository,
referendaFilteringProvider: ReferendaFilteringProvider,
assetUseCase: AssetUseCase,
governanceSourceRegistry: GovernanceSourceRegistry,
): TinderGovInteractor = RealTinderGovInteractor(
governanceSharedState,
referendaSharedComputation,
accountRepository,
preImageParser,
tinderGovVotingPowerRepository,
referendaFilteringProvider,
governanceSourceRegistry,
assetUseCase,
)
@Provides
@FeatureScope
fun provideReferendaSummarySharedComputation(
computationalCache: ComputationalCache,
referendumDetailsRepository: ReferendumDetailsRepository,
accountRepository: AccountRepository
) = ReferendaSummarySharedComputation(
computationalCache,
referendumDetailsRepository,
accountRepository
)
@Provides
@FeatureScope
fun provideReferendaSummaryInteractor(
governanceSharedState: GovernanceSharedState,
referendaSummarySharedComputation: ReferendaSummarySharedComputation
): ReferendaSummaryInteractor = RealReferendaSummaryInteractor(
governanceSharedState,
referendaSummarySharedComputation
)
@Provides
@FeatureScope
fun provideTinderGovBasketInteractor(
governanceSharedState: GovernanceSharedState,
accountRepository: AccountRepository,
tinderGovBasketRepository: TinderGovBasketRepository,
tinderGovVotingPowerRepository: TinderGovVotingPowerRepository,
assetUseCase: AssetUseCase,
tinderGovInteractor: TinderGovInteractor,
governanceSourceRegistry: GovernanceSourceRegistry,
): TinderGovBasketInteractor = RealTinderGovBasketInteractor(
governanceSharedState = governanceSharedState,
accountRepository = accountRepository,
tinderGovBasketRepository = tinderGovBasketRepository,
tinderGovVotingPowerRepository = tinderGovVotingPowerRepository,
assetUseCase = assetUseCase,
tinderGovInteractor = tinderGovInteractor,
governanceSourceRegistry = governanceSourceRegistry
)
}
@@ -0,0 +1,89 @@
package io.novafoundation.nova.feature_governance_impl.di.modules.v1
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.core_db.dao.GovernanceDAppsDao
import io.novafoundation.nova.feature_governance_api.data.source.GovernanceSource
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v1.PolkassemblyV1ReferendaDataSource
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.subsquare.v1.SubSquareV1ReferendaDataSource
import io.novafoundation.nova.feature_governance_impl.data.repository.MultiSourceOffChainReferendaInfoRepository
import io.novafoundation.nova.feature_governance_impl.data.repository.UnsupportedDelegationsRepository
import io.novafoundation.nova.feature_governance_impl.data.repository.v1.GovV1ConvictionVotingRepository
import io.novafoundation.nova.feature_governance_impl.data.repository.v1.GovV1DAppsRepository
import io.novafoundation.nova.feature_governance_impl.data.repository.v1.GovV1OnChainReferendaRepository
import io.novafoundation.nova.feature_governance_impl.data.repository.v1.GovV1PreImageRepository
import io.novafoundation.nova.feature_governance_impl.data.repository.v2.Gov2PreImageRepository
import io.novafoundation.nova.feature_governance_impl.data.source.StaticGovernanceSource
import io.novafoundation.nova.feature_wallet_api.data.repository.BalanceLocksRepository
import io.novafoundation.nova.runtime.di.REMOTE_STORAGE_SOURCE
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.repository.TotalIssuanceRepository
import io.novafoundation.nova.runtime.storage.source.StorageDataSource
import javax.inject.Named
import javax.inject.Qualifier
@Qualifier
annotation class GovernanceV1
@Module(includes = [PolkassemblyV1Module::class, SubSquareV1Module::class])
class GovernanceV1Module {
@Provides
@FeatureScope
fun provideOnChainReferendaRepository(
@Named(REMOTE_STORAGE_SOURCE) storageSource: StorageDataSource,
chainRegistry: ChainRegistry,
totalIssuanceRepository: TotalIssuanceRepository,
) = GovV1OnChainReferendaRepository(storageSource, chainRegistry, totalIssuanceRepository)
@Provides
@FeatureScope
fun provideConvictionVotingRepository(
@Named(REMOTE_STORAGE_SOURCE) storageSource: StorageDataSource,
chainRegistry: ChainRegistry,
balanceLocksRepository: BalanceLocksRepository
) = GovV1ConvictionVotingRepository(storageSource, chainRegistry, balanceLocksRepository)
@Provides
@GovernanceV1
@FeatureScope
fun provideOffChainInfoRepository(
polkassembly: PolkassemblyV1ReferendaDataSource,
subSquare: SubSquareV1ReferendaDataSource,
) = MultiSourceOffChainReferendaInfoRepository(
subSquareReferendaDataSource = subSquare,
polkassemblyReferendaDataSource = polkassembly
)
@Provides
@FeatureScope
fun providePreImageRepository(
@Named(REMOTE_STORAGE_SOURCE) storageSource: StorageDataSource,
v2Delegate: Gov2PreImageRepository,
) = GovV1PreImageRepository(storageSource, v2Delegate)
@Provides
@FeatureScope
fun provideDappsRepository(governanceDAppsDao: GovernanceDAppsDao): GovV1DAppsRepository {
return GovV1DAppsRepository(governanceDAppsDao)
}
@Provides
@FeatureScope
@GovernanceV1
fun provideGovernanceSource(
referendaRepository: GovV1OnChainReferendaRepository,
convictionVotingRepository: GovV1ConvictionVotingRepository,
@GovernanceV1 offChainInfoRepository: MultiSourceOffChainReferendaInfoRepository,
preImageRepository: GovV1PreImageRepository,
governanceV1DAppsRepository: GovV1DAppsRepository
): GovernanceSource = StaticGovernanceSource(
referenda = referendaRepository,
convictionVoting = convictionVotingRepository,
offChainInfo = offChainInfoRepository,
preImageRepository = preImageRepository,
dappsRepository = governanceV1DAppsRepository,
delegationsRepository = UnsupportedDelegationsRepository()
)
}
@@ -0,0 +1,20 @@
package io.novafoundation.nova.feature_governance_impl.di.modules.v1
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.common.data.network.NetworkApiCreator
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v1.PolkassemblyV1Api
import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.polkassembly.v1.PolkassemblyV1ReferendaDataSource
@Module
class PolkassemblyV1Module {
@Provides
@FeatureScope
fun provideApi(apiCreator: NetworkApiCreator): PolkassemblyV1Api = apiCreator.create(PolkassemblyV1Api::class.java)
@Provides
@FeatureScope
fun provideDataSource(api: PolkassemblyV1Api): PolkassemblyV1ReferendaDataSource = PolkassemblyV1ReferendaDataSource(api)
}

Some files were not shown because too many files have changed in this diff Show More