Initial commit: Pezkuwi Wallet Android

Complete rebrand of Nova Wallet for Pezkuwichain ecosystem.

## Features
- Full Pezkuwichain support (HEZ & PEZ tokens)
- Polkadot ecosystem compatibility
- Staking, Governance, DeFi, NFTs
- XCM cross-chain transfers
- Hardware wallet support (Ledger, Polkadot Vault)
- WalletConnect v2
- Push notifications

## Languages
- English, Turkish, Kurmanci (Kurdish), Spanish, French, German, Russian, Japanese, Chinese, Korean, Portuguese, Vietnamese

Based on Nova Wallet by Novasama Technologies GmbH
© Dijital Kurdistan Tech Institute 2026
This commit is contained in:
2026-01-23 01:31:12 +03:00
commit 31c8c5995f
7621 changed files with 425838 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/build
+51
View File
@@ -0,0 +1,51 @@
apply plugin: 'kotlin-parcelize'
apply from: '../tests.gradle'
apply from: '../scripts/secrets.gradle'
android {
namespace 'io.novafoundation.nova.feature_proxy'
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
buildFeatures {
viewBinding true
}
}
dependencies {
implementation project(':common')
implementation project(':runtime')
implementation project(':feature-proxy-api')
implementation kotlinDep
implementation androidDep
implementation materialDep
implementation constraintDep
implementation coroutinesDep
implementation coroutinesAndroidDep
implementation viewModelKtxDep
implementation lifeCycleKtxDep
implementation daggerDep
ksp daggerCompiler
testImplementation jUnitDep
testImplementation mockitoDep
implementation insetterDep
implementation shimmerDep
androidTestImplementation androidTestRunnerDep
androidTestImplementation androidTestRulesDep
androidTestImplementation androidJunitDep
}
+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 xmlns:android="http://schemas.android.com/apk/res/android"/>
@@ -0,0 +1,26 @@
package io.novafoundation.nova.feature_proxy_impl.data.common
import io.novafoundation.nova.feature_proxy_api.data.common.DepositBaseAndFactor
import io.novafoundation.nova.feature_proxy_api.data.common.ProxyDepositCalculator
import io.novafoundation.nova.feature_proxy_api.data.repository.ProxyConstantsRepository
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
import java.math.BigInteger
class RealProxyDepositCalculator(
private val proxyConstantsRepository: ProxyConstantsRepository
) : ProxyDepositCalculator {
override fun calculateProxyDepositForQuantity(baseAndFactor: DepositBaseAndFactor, proxiesCount: Int): BigInteger {
return if (proxiesCount == 0) {
BigInteger.ZERO
} else {
baseAndFactor.baseAmount + baseAndFactor.factorAmount * proxiesCount.toBigInteger()
}
}
override suspend fun calculateProxyDepositForQuantity(chainId: ChainId, proxiesCount: Int): BigInteger {
val depositAndFactor = proxyConstantsRepository.getDepositConstants(chainId)
return calculateProxyDepositForQuantity(depositAndFactor, proxiesCount)
}
}
@@ -0,0 +1,148 @@
package io.novafoundation.nova.feature_proxy_impl.data.repository
import io.novafoundation.nova.common.address.AccountIdKey
import io.novafoundation.nova.common.address.intoKey
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.getTyped
import io.novafoundation.nova.common.utils.Modules
import io.novafoundation.nova.common.utils.numberConstant
import io.novafoundation.nova.common.utils.proxy
import io.novafoundation.nova.feature_proxy_api.data.model.OnChainProxiedModel
import io.novafoundation.nova.feature_proxy_api.data.model.OnChainProxyModel
import io.novafoundation.nova.feature_proxy_api.data.model.ProxiesMap
import io.novafoundation.nova.feature_proxy_api.data.model.ProxyPermission
import io.novafoundation.nova.feature_proxy_api.data.repository.GetProxyRepository
import io.novafoundation.nova.feature_proxy_api.domain.model.ProxyType
import io.novafoundation.nova.feature_proxy_api.domain.model.fromString
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.getRuntime
import io.novafoundation.nova.runtime.storage.source.StorageDataSource
import io.novasama.substrate_sdk_android.runtime.AccountId
import io.novasama.substrate_sdk_android.runtime.metadata.module
import io.novasama.substrate_sdk_android.runtime.metadata.storage
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import java.math.BigInteger
class RealGetProxyRepository(
private val remoteSource: StorageDataSource,
private val localSource: StorageDataSource,
private val chainRegistry: ChainRegistry,
) : GetProxyRepository {
override suspend fun getAllProxies(chainId: ChainId): ProxiesMap {
return receiveAllProxiesInChain(chainId)
}
override suspend fun getDelegatedProxyTypesRemote(chainId: ChainId, proxiedAccountId: AccountId, proxyAccountId: AccountId): List<ProxyType> {
return getDelegatedProxyTypes(remoteSource, chainId, proxiedAccountId, proxyAccountId)
}
// TODO: use it for staking after merge "add staking proxy" branch
override suspend fun getDelegatedProxyTypesLocal(chainId: ChainId, proxiedAccountId: AccountId, proxyAccountId: AccountId): List<ProxyType> {
return getDelegatedProxyTypes(localSource, chainId, proxiedAccountId, proxyAccountId)
}
override suspend fun getProxiesQuantity(chainId: ChainId, proxiedAccountId: AccountId): Int {
val proxied = getAllProxiesFor(localSource, chainId, proxiedAccountId)
return proxied.proxies.size
}
override suspend fun getProxyDeposit(chainId: ChainId, proxiedAccountId: AccountId): BigInteger {
val proxied = getAllProxiesFor(localSource, chainId, proxiedAccountId)
return proxied.deposit
}
override suspend fun maxProxiesQuantity(chain: Chain): Int {
val runtime = chainRegistry.getRuntime(chain.id)
val constantQuery = runtime.metadata.proxy()
return constantQuery.numberConstant("MaxProxies", runtime).toInt()
}
override fun proxiesByTypeFlow(chain: Chain, accountId: AccountId, proxyType: ProxyType): Flow<List<ProxyPermission>> {
return localSource.subscribe(chain.id) {
runtime.metadata.module(Modules.PROXY)
.storage("Proxies")
.observe(
accountId,
binding = { bindProxyAccounts(it) }
)
}.map { proxied ->
proxied.proxies
.filter { it.proxyType.name == proxyType.name }
.map { ProxyPermission(accountId.intoKey(), it.proxy, it.proxyType) }
}
}
override fun proxiesQuantityByTypeFlow(chain: Chain, accountId: AccountId, proxyType: ProxyType): Flow<Int> {
return proxiesByTypeFlow(chain, accountId, proxyType)
.map { it.size }
}
private suspend fun getDelegatedProxyTypes(
storageDataSource: StorageDataSource,
chainId: ChainId,
proxiedAccountId: AccountId,
proxyAccountId: AccountId
): List<ProxyType> {
val proxied = getAllProxiesFor(storageDataSource, chainId, proxiedAccountId)
return proxied.proxies
.filter { it.proxy == proxyAccountId.intoKey() }
.map { it.proxyType }
}
private suspend fun getAllProxiesFor(storageDataSource: StorageDataSource, chainId: ChainId, accountId: AccountId): OnChainProxiedModel {
return storageDataSource.query(chainId) {
runtime.metadata.module(Modules.PROXY)
.storage("Proxies")
.query(
keyArguments = arrayOf(accountId),
binding = { result -> bindProxyAccounts(result) }
)
}
}
private suspend fun receiveAllProxiesInChain(chainId: ChainId): Map<AccountIdKey, OnChainProxiedModel> {
return remoteSource.query(chainId) {
runtime.metadata.module(Modules.PROXY)
.storage("Proxies")
.entries(
keyExtractor = { (accountId: AccountId) -> AccountIdKey(accountId) },
binding = { result, _ -> bindProxyAccounts(result) },
recover = { _, _ ->
// Do nothing if entry binding throws an exception
}
)
}
}
private fun bindProxyAccounts(dynamicInstance: Any?): OnChainProxiedModel {
if (dynamicInstance == null) return OnChainProxiedModel(emptyList(), BigInteger.ZERO)
val root = dynamicInstance.castToList()
val proxies = root[0].castToList()
return OnChainProxiedModel(
proxies = proxies.map {
val proxy = it.castToStruct()
val proxyAccountId: ByteArray = proxy.getTyped("delegate")
val proxyType = proxy.get<Any?>("proxyType").castToDictEnum()
val delay = proxy.getTyped<BigInteger>("delay")
OnChainProxyModel(
proxy = proxyAccountId.intoKey(),
proxyType = ProxyType.fromString(proxyType.name),
delay = delay
)
},
deposit = root[1].cast()
)
}
}
@@ -0,0 +1,23 @@
package io.novafoundation.nova.feature_proxy_impl.data.repository
import io.novafoundation.nova.common.utils.numberConstant
import io.novafoundation.nova.common.utils.proxy
import io.novafoundation.nova.feature_proxy_api.data.common.DepositBaseAndFactor
import io.novafoundation.nova.feature_proxy_api.data.repository.ProxyConstantsRepository
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
import io.novafoundation.nova.runtime.multiNetwork.getRuntime
class RealProxyConstantsRepository(
private val chainRegestry: ChainRegistry
) : ProxyConstantsRepository {
override suspend fun getDepositConstants(chainId: ChainId): DepositBaseAndFactor {
val runtime = chainRegestry.getRuntime(chainId)
val constantQuery = runtime.metadata.proxy()
return DepositBaseAndFactor(
baseAmount = constantQuery.numberConstant("ProxyDepositBase", runtime),
factorAmount = constantQuery.numberConstant("ProxyDepositFactor", runtime)
)
}
}
@@ -0,0 +1,35 @@
package io.novafoundation.nova.feature_proxy_impl.di
import dagger.Component
import io.novafoundation.nova.common.di.CommonApi
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.feature_proxy_api.di.ProxyFeatureApi
import io.novafoundation.nova.runtime.di.RuntimeApi
@Component(
dependencies = [
ProxyFeatureDependencies::class
],
modules = [
ProxyFeatureModule::class,
]
)
@FeatureScope
interface ProxyFeatureComponent : ProxyFeatureApi {
@Component.Factory
interface Factory {
fun create(
deps: ProxyFeatureDependencies
): ProxyFeatureComponent
}
@Component(
dependencies = [
CommonApi::class,
RuntimeApi::class
]
)
interface VoteFeatureDependenciesComponent : ProxyFeatureDependencies
}
@@ -0,0 +1,18 @@
package io.novafoundation.nova.feature_proxy_impl.di
import io.novafoundation.nova.runtime.di.LOCAL_STORAGE_SOURCE
import io.novafoundation.nova.runtime.di.REMOTE_STORAGE_SOURCE
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.storage.source.StorageDataSource
import javax.inject.Named
interface ProxyFeatureDependencies {
@Named(REMOTE_STORAGE_SOURCE)
fun remoteStorageSource(): StorageDataSource
@Named(LOCAL_STORAGE_SOURCE)
fun localStorageSource(): StorageDataSource
fun chainRegistry(): ChainRegistry
}
@@ -0,0 +1,22 @@
package io.novafoundation.nova.feature_proxy_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.runtime.di.RuntimeApi
import javax.inject.Inject
@ApplicationScope
class ProxyFeatureHolder @Inject constructor(
featureContainer: FeatureContainer
) : FeatureApiHolder(featureContainer) {
override fun initializeDependencies(): Any {
val dependencies = DaggerProxyFeatureComponent_VoteFeatureDependenciesComponent.builder()
.commonApi(commonApi())
.runtimeApi(getFeature(RuntimeApi::class.java))
.build()
return DaggerProxyFeatureComponent.factory()
.create(dependencies)
}
}
@@ -0,0 +1,48 @@
package io.novafoundation.nova.feature_proxy_impl.di
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.feature_proxy_api.data.common.ProxyDepositCalculator
import io.novafoundation.nova.feature_proxy_api.data.repository.GetProxyRepository
import io.novafoundation.nova.feature_proxy_api.data.repository.ProxyConstantsRepository
import io.novafoundation.nova.feature_proxy_impl.data.common.RealProxyDepositCalculator
import io.novafoundation.nova.feature_proxy_impl.data.repository.RealGetProxyRepository
import io.novafoundation.nova.feature_proxy_impl.data.repository.RealProxyConstantsRepository
import io.novafoundation.nova.runtime.di.LOCAL_STORAGE_SOURCE
import io.novafoundation.nova.runtime.di.REMOTE_STORAGE_SOURCE
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.storage.source.StorageDataSource
import javax.inject.Named
@Module
class ProxyFeatureModule {
@Provides
@FeatureScope
fun provideProxyRepository(
@Named(REMOTE_STORAGE_SOURCE) remoteSource: StorageDataSource,
@Named(LOCAL_STORAGE_SOURCE) localSource: StorageDataSource,
chainRegistry: ChainRegistry
): GetProxyRepository = RealGetProxyRepository(
remoteSource = remoteSource,
localSource = localSource,
chainRegistry
)
@Provides
@FeatureScope
fun provideProxyConstantsRepository(
chainRegistry: ChainRegistry
): ProxyConstantsRepository = RealProxyConstantsRepository(
chainRegistry
)
@Provides
@FeatureScope
fun provideProxyDepositCalculator(
proxyConstantsRepository: ProxyConstantsRepository
): ProxyDepositCalculator {
return RealProxyDepositCalculator(proxyConstantsRepository)
}
}