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
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest>
</manifest>
@@ -0,0 +1,29 @@
package io.novafoundation.nova.feature_crowdloan_api.data.network.blockhain.binding
import io.novafoundation.nova.common.data.network.runtime.binding.bindNumber
import io.novafoundation.nova.common.data.network.runtime.binding.bindString
import io.novafoundation.nova.common.data.network.runtime.binding.cast
import io.novafoundation.nova.common.data.network.runtime.binding.incompatible
import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.Contribution.Companion.DIRECT_SOURCE_ID
import io.novasama.substrate_sdk_android.runtime.RuntimeSnapshot
import io.novasama.substrate_sdk_android.runtime.definitions.types.fromHex
import java.math.BigInteger
class DirectContribution(
val amount: BigInteger,
val memo: String,
) {
val sourceId = DIRECT_SOURCE_ID
}
fun bindContribution(scale: String, runtime: RuntimeSnapshot): DirectContribution {
val type = runtime.typeRegistry["(Balance, Vec<u8>)"] ?: incompatible()
val dynamicInstance = type.fromHex(runtime, scale).cast<List<*>>()
return DirectContribution(
amount = bindNumber(dynamicInstance[0]),
memo = bindString(dynamicInstance[1])
)
}
@@ -0,0 +1,67 @@
package io.novafoundation.nova.feature_crowdloan_api.data.network.blockhain.binding
import io.novafoundation.nova.common.data.network.runtime.binding.ParaId
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.castToStruct
import io.novafoundation.nova.common.data.network.runtime.binding.fromHexOrIncompatible
import io.novafoundation.nova.common.data.network.runtime.binding.storageReturnType
import io.novafoundation.nova.common.utils.Modules
import io.novasama.substrate_sdk_android.runtime.AccountId
import io.novasama.substrate_sdk_android.runtime.RuntimeSnapshot
import io.novasama.substrate_sdk_android.runtime.definitions.types.primitives.u32
import io.novasama.substrate_sdk_android.runtime.definitions.types.toByteArray
import java.math.BigInteger
class FundInfo(
val depositor: AccountId,
val deposit: BigInteger,
val raised: BigInteger,
val lastSlot: BigInteger,
val firstSlot: BigInteger,
val end: BigInteger,
val cap: BigInteger,
val verifier: Any?,
val trieIndex: TrieIndex,
val paraId: ParaId,
val bidderAccountId: AccountId,
val pre9180BidderAccountId: AccountId,
)
fun bindFundInfo(dynamic: Any?, runtime: RuntimeSnapshot, paraId: ParaId): FundInfo {
val dynamicInstance = dynamic.castToStruct()
val fundIndex = bindTrieIndex(dynamicInstance["fundIndex"] ?: dynamicInstance["trieIndex"])
return FundInfo(
depositor = bindAccountId(dynamicInstance["depositor"]),
deposit = bindNumber(dynamicInstance["deposit"]),
raised = bindNumber(dynamicInstance["raised"]),
end = bindNumber(dynamicInstance["end"]),
cap = bindNumber(dynamicInstance["cap"]),
firstSlot = bindNumber(dynamicInstance["firstPeriod"] ?: dynamicInstance["firstSlot"]),
lastSlot = bindNumber(dynamicInstance["lastPeriod"] ?: dynamicInstance["lastSlot"]),
verifier = dynamicInstance["verifier"],
trieIndex = fundIndex,
bidderAccountId = createBidderAccountId(runtime, fundIndex),
pre9180BidderAccountId = createBidderAccountId(runtime, paraId),
paraId = paraId
)
}
fun bindFundInfo(scale: String, runtime: RuntimeSnapshot, paraId: ParaId): FundInfo {
val type = runtime.metadata.storageReturnType(Modules.CROWDLOAN, "Funds")
val dynamicInstance = type.fromHexOrIncompatible(scale, runtime)
return bindFundInfo(dynamicInstance, runtime, paraId)
}
private val ADDRESS_PADDING = ByteArray(32)
private val ADDRESS_PREFIX = "modlpy/cfund".encodeToByteArray()
private fun createBidderAccountId(runtime: RuntimeSnapshot, index: BigInteger): AccountId {
val fullKey = ADDRESS_PREFIX + u32.toByteArray(runtime, index) + ADDRESS_PADDING
return fullKey.copyOfRange(0, 32)
}
@@ -0,0 +1,26 @@
package io.novafoundation.nova.feature_crowdloan_api.data.network.blockhain.binding
import io.novafoundation.nova.common.data.network.runtime.binding.BalanceOf
import io.novafoundation.nova.common.data.network.runtime.binding.bindAccountId
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.novasama.substrate_sdk_android.runtime.AccountId
class LeaseEntry(
val accountId: AccountId,
val locked: BalanceOf
)
fun bindLeases(decoded: Any?): List<LeaseEntry?> {
return bindList(decoded) {
it?.let {
val (accountIdRaw, balanceRaw) = it.cast<List<*>>()
LeaseEntry(
accountId = bindAccountId(accountIdRaw),
locked = bindNumber(balanceRaw)
)
}
}
}
@@ -0,0 +1,10 @@
package io.novafoundation.nova.feature_crowdloan_api.data.network.blockhain.binding
import io.novafoundation.nova.common.data.network.runtime.binding.HelperBinding
import io.novafoundation.nova.common.data.network.runtime.binding.bindNumber
import java.math.BigInteger
typealias TrieIndex = BigInteger
@HelperBinding
fun bindTrieIndex(dynamicInstance: Any?) = bindNumber(dynamicInstance)
@@ -0,0 +1,14 @@
package io.novafoundation.nova.feature_crowdloan_api.data.network.updater
import io.novafoundation.nova.core.updater.UpdateScope
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
import io.novafoundation.nova.feature_crowdloan_api.data.network.updater.AssetBalanceScope.ScopeValue
import io.novafoundation.nova.feature_wallet_api.domain.model.Asset
import kotlinx.coroutines.flow.Flow
interface AssetBalanceScope : UpdateScope<ScopeValue> {
class ScopeValue(val metaAccount: MetaAccount, val asset: Asset)
override fun invalidationFlow(): Flow<ScopeValue>
}
@@ -0,0 +1,7 @@
package io.novafoundation.nova.feature_crowdloan_api.data.network.updater
import io.novafoundation.nova.core.updater.UpdateSystem
interface ContributionsUpdateSystemFactory {
fun create(): UpdateSystem
}
@@ -0,0 +1,8 @@
package io.novafoundation.nova.feature_crowdloan_api.data.network.updater
import io.novafoundation.nova.core.updater.Updater
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
interface ContributionsUpdaterFactory {
fun create(chain: Chain, assetBalanceScope: AssetBalanceScope): Updater<AssetBalanceScope.ScopeValue>
}
@@ -0,0 +1,23 @@
package io.novafoundation.nova.feature_crowdloan_api.data.repository
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.Contribution
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novafoundation.nova.runtime.multiNetwork.chain.model.FullChainAssetId
import kotlinx.coroutines.flow.Flow
interface ContributionsRepository {
fun loadContributionsGraduallyFlow(
chain: Chain,
accountId: ByteArray,
): Flow<Pair<String, Result<List<Contribution>>>>
fun observeContributions(metaAccount: MetaAccount): Flow<List<Contribution>>
fun observeContributions(metaAccount: MetaAccount, chain: Chain, asset: Chain.Asset): Flow<List<Contribution>>
suspend fun getDirectContributions(chain: Chain, asset: Chain.Asset, accountId: ByteArray): Result<List<Contribution>>
suspend fun deleteContributions(assetIds: List<FullChainAssetId>)
}
@@ -0,0 +1,39 @@
package io.novafoundation.nova.feature_crowdloan_api.data.repository
import io.novafoundation.nova.common.data.network.runtime.binding.ParaId
import io.novafoundation.nova.feature_crowdloan_api.data.network.blockhain.binding.FundInfo
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
import kotlinx.coroutines.flow.Flow
import java.math.BigDecimal
import java.math.BigInteger
interface CrowdloanRepository {
suspend fun allFundInfos(chainId: ChainId): Map<ParaId, FundInfo>
suspend fun getWinnerInfo(chainId: ChainId, funds: Map<ParaId, FundInfo>): Map<ParaId, Boolean>
suspend fun getParachainMetadata(chain: Chain): Map<ParaId, ParachainMetadata>
suspend fun leasePeriodToBlocksConverter(chainId: ChainId): LeasePeriodToBlocksConverter
fun fundInfoFlow(chainId: ChainId, parachainId: ParaId): Flow<FundInfo>
suspend fun minContribution(chainId: ChainId): BigInteger
}
class ParachainMetadata(
val paraId: ParaId,
val movedToParaId: ParaId?,
val iconLink: String,
val name: String,
val description: String,
val rewardRate: BigDecimal?,
val website: String,
val customFlow: String?,
val token: String,
val extras: Map<String, String>,
)
fun ParachainMetadata.getExtra(key: String) = extras[key] ?: throw IllegalArgumentException("No key $key found in parachain metadata extras for $name")
@@ -0,0 +1,10 @@
package io.novafoundation.nova.feature_crowdloan_api.data.repository
import io.novafoundation.nova.feature_crowdloan_api.data.network.blockhain.binding.FundInfo
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
suspend fun CrowdloanRepository.hasWonAuction(chainId: ChainId, fundInfo: FundInfo): Boolean {
val paraId = fundInfo.paraId
return getWinnerInfo(chainId, mapOf(paraId to fundInfo)).getValue(paraId)
}
@@ -0,0 +1,18 @@
package io.novafoundation.nova.feature_crowdloan_api.data.repository
import io.novafoundation.nova.common.data.network.runtime.binding.BlockNumber
import java.math.BigInteger
class LeasePeriodToBlocksConverter(
private val blocksPerLease: BigInteger,
private val blocksOffset: BigInteger
) {
fun startBlockFor(leasePeriod: BigInteger): BlockNumber {
return blocksPerLease * leasePeriod + blocksOffset
}
fun leaseIndexFromBlock(blockNumber: BlockNumber): BigInteger {
return (blockNumber - blocksOffset) / blocksPerLease
}
}
@@ -0,0 +1,30 @@
package io.novafoundation.nova.feature_crowdloan_api.data.source.contribution
import io.novafoundation.nova.common.data.network.runtime.binding.ParaId
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
import io.novasama.substrate_sdk_android.runtime.AccountId
import java.math.BigInteger
interface ExternalContributionSource {
class ExternalContribution(
val sourceId: String,
val amount: BigInteger,
val paraId: ParaId,
)
/**
* null in case every chain is supported
*/
val supportedChains: Set<ChainId>?
val sourceId: String
suspend fun getContributions(
chain: Chain,
accountId: AccountId,
): Result<List<ExternalContribution>>
}
fun ExternalContributionSource.supports(chain: Chain) = supportedChains == null || chain.id in supportedChains!!
@@ -0,0 +1,7 @@
package io.novafoundation.nova.feature_crowdloan_api.di
import javax.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class Crowdloan
@@ -0,0 +1,14 @@
package io.novafoundation.nova.feature_crowdloan_api.di
import io.novafoundation.nova.feature_crowdloan_api.data.repository.ContributionsRepository
import io.novafoundation.nova.feature_crowdloan_api.data.repository.CrowdloanRepository
import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.ContributionsInteractor
interface CrowdloanFeatureApi {
fun repository(): CrowdloanRepository
fun contributionsInteractor(): ContributionsInteractor
fun contributionsRepository(): ContributionsRepository
}
@@ -0,0 +1,99 @@
package io.novafoundation.nova.feature_crowdloan_api.domain.contributions
import io.novafoundation.nova.common.address.AccountIdKey
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.ParaId
import io.novafoundation.nova.common.utils.formatting.TimerValue
import io.novafoundation.nova.core_db.model.ContributionLocal
import io.novafoundation.nova.feature_crowdloan_api.data.repository.ParachainMetadata
import io.novafoundation.nova.runtime.ext.utilityAsset
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novafoundation.nova.runtime.util.BlockDurationEstimator
import io.novafoundation.nova.runtime.util.timerUntil
import java.math.BigInteger
class Contribution(
val chain: Chain,
val asset: Chain.Asset,
val amountInPlanks: BigInteger,
val paraId: ParaId,
val sourceId: String,
val unlockBlock: BlockNumber,
val leaseDepositor: AccountIdKey,
) {
companion object {
const val DIRECT_SOURCE_ID = "direct"
const val LIQUID_SOURCE_ID = "liquid"
const val PARALLEL_SOURCE_ID = "parallel"
}
}
class ContributionMetadata(
val claimStatus: ContributionClaimStatus,
val parachainMetadata: ParachainMetadata?,
)
sealed class ContributionClaimStatus {
object Claimable : ContributionClaimStatus()
class ReturnsIn(val timer: TimerValue) : ContributionClaimStatus()
}
class ContributionWithMetadata(
val contribution: Contribution,
val metadata: ContributionMetadata
)
fun BlockDurationEstimator.claimStatusOf(contribution: Contribution): ContributionClaimStatus {
return if (contribution.unlockBlock > currentBlock) {
ContributionClaimStatus.ReturnsIn(timerUntil(contribution.unlockBlock))
} else {
ContributionClaimStatus.Claimable
}
}
class ContributionsWithTotalAmount<T>(val totalContributed: BigInteger, val contributions: List<T>) {
companion object {
fun <T> empty(): ContributionsWithTotalAmount<T> {
return ContributionsWithTotalAmount(BigInteger.ZERO, emptyList())
}
fun fromContributions(contributions: List<Contribution>): ContributionsWithTotalAmount<Contribution> {
return ContributionsWithTotalAmount(
totalContributed = contributions.sumOf { it.amountInPlanks },
contributions = contributions
)
}
}
}
fun mapContributionToLocal(metaId: Long, contribution: Contribution): ContributionLocal {
return ContributionLocal(
metaId,
contribution.chain.id,
contribution.asset.id,
contribution.paraId,
contribution.amountInPlanks,
contribution.sourceId,
unlockBlock = contribution.unlockBlock,
leaseDepositor = contribution.leaseDepositor.value
)
}
fun mapContributionFromLocal(
contribution: ContributionLocal,
chain: Chain,
): Contribution {
return Contribution(
chain,
chain.utilityAsset,
contribution.amountInPlanks,
contribution.paraId,
contribution.sourceId,
unlockBlock = contribution.unlockBlock,
leaseDepositor = contribution.leaseDepositor.intoKey()
)
}
@@ -0,0 +1,20 @@
package io.novafoundation.nova.feature_crowdloan_api.domain.contributions
import io.novafoundation.nova.core.updater.Updater
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainAssetId
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
import kotlinx.coroutines.flow.Flow
interface ContributionsInteractor {
fun runUpdate(): Flow<Updater.SideEffect>
fun observeSelectedChainContributionsWithMetadata(): Flow<ContributionsWithTotalAmount<ContributionWithMetadata>>
fun observeChainContributions(
metaAccount: MetaAccount,
chainId: ChainId,
assetId: ChainAssetId
): Flow<ContributionsWithTotalAmount<Contribution>>
}