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,274 @@
package io.novafoundation.nova.feature_governance_api.domain.locks
import io.novafoundation.nova.common.data.network.runtime.binding.BlockNumber
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.AccountVote
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.AyeVote
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.PriorLock
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.Voting
import io.novafoundation.nova.feature_governance_api.domain.locks.ClaimSchedule.ClaimAction
import io.novafoundation.nova.feature_governance_api.domain.locks.ClaimSchedule.UnlockChunk
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.novasama.substrate_sdk_android.runtime.AccountId
interface ClaimScheduleTestBuilder {
fun given(builder: Given.() -> Unit)
interface Given {
fun currentBlock(block: Int)
fun track(trackId: Int, builder: Track.() -> Unit)
interface Track {
fun lock(lock: Int)
fun voting(builder: Voting.() -> Unit)
fun delegating(builder: Delegating.() -> Unit)
interface Voting {
fun prior(amount: Int, unlockAt: Int)
fun vote(amount: Int, referendumId: Int, unlockAt: Int)
}
interface Delegating {
fun prior(amount: Int, unlockAt: Int)
fun delegate(amount: Int)
}
}
}
fun expect(builder: Expect.() -> Unit)
interface Expect {
fun claimable(amount: Int, actions: ClaimableActions.() -> Unit)
fun nonClaimable(amount: Int, claimAt: Int)
fun nonClaimable(amount: Int)
interface ClaimableActions {
fun unlock(trackId: Int)
fun removeVote(trackId: Int, referendumId: Int)
}
}
}
fun ClaimScheduleTest(builder: ClaimScheduleTestBuilder.() -> Unit) {
val test = ClaimScheduleTest().apply(builder)
test.runTest()
}
private class ClaimScheduleTest : ClaimScheduleTestBuilder {
private var calculator: RealClaimScheduleCalculator? = null
private var expectedSchedule: ClaimSchedule? = null
override fun given(builder: ClaimScheduleTestBuilder.Given.() -> Unit) {
calculator = GivenBuilder().apply(builder).build()
}
override fun expect(builder: ClaimScheduleTestBuilder.Expect.() -> Unit) {
expectedSchedule = ExpectedBuilder().apply(builder).buildSchedule()
}
fun runTest() {
val actualSchedule = calculator!!.estimateClaimSchedule()
assert(actualSchedule == expectedSchedule!!) {
buildString {
append("Expected schedule: $expectedSchedule\n")
append("Actual schedule : $actualSchedule\n")
}
}
}
}
private class ExpectedBuilder : ClaimScheduleTestBuilder.Expect {
private var chunks = mutableListOf<UnlockChunk>()
override fun claimable(amount: Int, actionsBuilder: ClaimScheduleTestBuilder.Expect.ClaimableActions.() -> Unit) {
val actions = ClaimableActionsBuilder().apply(actionsBuilder).buildActions()
chunks.add(UnlockChunk.Claimable(amount.toBigInteger(), actions))
}
override fun nonClaimable(amount: Int, claimAt: Int) {
chunks.add(UnlockChunk.Pending(amount.toBigInteger(), ClaimTime.At(claimAt.toBigInteger())))
}
override fun nonClaimable(amount: Int) {
chunks.add(UnlockChunk.Pending(amount.toBigInteger(), ClaimTime.UntilAction))
}
fun buildSchedule(): ClaimSchedule {
return ClaimSchedule(chunks)
}
}
private class ClaimableActionsBuilder : ClaimScheduleTestBuilder.Expect.ClaimableActions {
private val actions = mutableListOf<ClaimAction>()
override fun unlock(trackId: Int) {
val action = ClaimAction.Unlock(TrackId(trackId.toBigInteger()))
actions.add(action)
}
override fun removeVote(trackId: Int, referendumId: Int) {
val trackIdTyped = TrackId(trackId.toBigInteger())
val referendumIdTyped = ReferendumId(referendumId.toBigInteger())
val action = ClaimAction.RemoveVote(trackIdTyped, referendumIdTyped)
actions.add(action)
}
fun buildActions(): List<ClaimAction> {
return actions
}
}
private class GivenBuilder : ClaimScheduleTestBuilder.Given {
private var voting: MutableMap<TrackId, Voting> = mutableMapOf()
private var currentBlockNumber: BlockNumber = BlockNumber.ZERO
private var referenda: MutableMap<ReferendumId, OnChainReferendum> = mutableMapOf()
private var trackLocks: MutableMap<TrackId, Balance> = mutableMapOf()
override fun currentBlock(block: Int) {
currentBlockNumber = block.toBigInteger()
}
override fun track(trackId: Int, builder: ClaimScheduleTestBuilder.Given.Track.() -> Unit) {
val trackIdTyped = TrackId(trackId.toBigInteger())
val builtTrack = TrackBuilder().apply(builder)
voting[trackIdTyped] = builtTrack.buildVoting()
val newReferenda = builtTrack.buildReferendaApprovedAt().mapValues { (referendaId, approvedAt) ->
OnChainReferendum(
id = referendaId,
status = OnChainReferendumStatus.Approved(since = approvedAt)
)
}
referenda += newReferenda
trackLocks[trackIdTyped] = builtTrack.buildTrackLock()
}
fun build(): RealClaimScheduleCalculator {
return RealClaimScheduleCalculator(
votingByTrack = voting,
currentBlockNumber = currentBlockNumber,
referenda = referenda,
trackLocks = trackLocks,
// those parameters are only used for ongoing referenda estimation
// we only use approved ones in this tests
tracks = emptyMap(),
undecidingTimeout = BlockNumber.ZERO,
// we do not use conviction in tests
voteLockingPeriod = BlockNumber.ZERO
)
}
}
private fun PriorLock(): PriorLock = PriorLock(BlockNumber.ZERO, Balance.ZERO)
private class VotingBuilder : ClaimScheduleTestBuilder.Given.Track.Voting {
private var prior: PriorLock = PriorLock()
private val votes = mutableMapOf<ReferendumId, AccountVote>()
private var referendumApprovedAt = mutableMapOf<ReferendumId, BlockNumber>()
override fun prior(amount: Int, unlockAt: Int) {
prior = PriorLock(unlockAt = unlockAt.toBigInteger(), amount = amount.toBigInteger())
}
override fun vote(amount: Int, referendumId: Int, unlockAt: Int) {
val referendumIdTyped = ReferendumId(referendumId.toBigInteger())
votes[referendumIdTyped] = AyeVote(amount.toBigInteger(), Conviction.None)
referendumApprovedAt[referendumIdTyped] = unlockAt.toBigInteger()
}
fun buildReferendaApprovedAt(): Map<ReferendumId, BlockNumber> {
return referendumApprovedAt
}
fun build(): Voting.Casting = Voting.Casting(votes, prior)
}
private class DelegatingBuilder : ClaimScheduleTestBuilder.Given.Track.Delegating {
private var prior: PriorLock = PriorLock()
private var delegation: Balance? = null
override fun prior(amount: Int, unlockAt: Int) {
prior = PriorLock(unlockAt = unlockAt.toBigInteger(), amount = amount.toBigInteger())
}
override fun delegate(amount: Int) {
delegation = amount.toBigInteger()
}
fun build(): Voting.Delegating {
return Voting.Delegating(
amount = requireNotNull(delegation),
target = AccountId(32),
conviction = Conviction.None, // we don't use conviction since it doesn't matter until undelegated
prior = prior,
)
}
}
private class TrackBuilder : ClaimScheduleTestBuilder.Given.Track {
private var trackLock: Balance = Balance.ZERO
private var voting: Voting = Voting.Casting(emptyMap(), PriorLock())
private var referendumApprovedAt = mapOf<ReferendumId, BlockNumber>()
override fun lock(lock: Int) {
trackLock = lock.toBigInteger()
}
override fun voting(builder: ClaimScheduleTestBuilder.Given.Track.Voting.() -> Unit) {
val votingBuilder = VotingBuilder().apply(builder)
voting = votingBuilder.build()
referendumApprovedAt = votingBuilder.buildReferendaApprovedAt()
}
override fun delegating(builder: ClaimScheduleTestBuilder.Given.Track.Delegating.() -> Unit) {
voting = DelegatingBuilder().apply(builder).build()
}
fun buildVoting(): Voting {
return voting
}
fun buildReferendaApprovedAt(): Map<ReferendumId, BlockNumber> {
return referendumApprovedAt
}
fun buildTrackLock(): Balance {
return trackLock
}
}
@@ -0,0 +1,480 @@
package io.novafoundation.nova.feature_governance_api.domain.locks
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.junit.MockitoJUnitRunner
@RunWith(MockitoJUnitRunner::class)
class RealClaimScheduleCalculatorTest {
@Test
fun `should handle empty case`() = ClaimScheduleTest {
given {
}
expect {
}
}
@Test
fun `should handle single claimable`() = ClaimScheduleTest {
given {
currentBlock(1000)
track(0) {
voting {
vote(amount = 1, referendumId = 0, unlockAt = 1000)
}
}
}
expect {
claimable(amount = 1) {
removeVote(trackId = 0, referendumId = 0)
unlock(trackId = 0)
}
}
}
@Test
fun `should handle both passed and not priors`() = ClaimScheduleTest {
given {
currentBlock(1000)
track(0) {
voting {
prior(amount = 2, unlockAt = 1000)
}
}
track(1) {
voting {
prior(amount = 1, unlockAt = 1100)
}
}
}
expect {
claimable(amount = 1) {
unlock(trackId = 0)
}
nonClaimable(amount = 1, claimAt = 1100)
}
}
@Test
fun `should extend votes by prior`() = ClaimScheduleTest {
given {
currentBlock(1000)
track(0) {
voting {
prior(amount = 1, unlockAt = 1100)
vote(amount = 2, unlockAt = 1000, referendumId = 1)
}
}
}
expect {
nonClaimable(amount = 2, claimAt = 1100)
}
}
@Test
fun `should take max between two locks with same time`() = ClaimScheduleTest {
given {
currentBlock(1000)
track(0) {
voting {
vote(amount = 8, referendumId = 0, unlockAt = 1000)
vote(amount = 2, referendumId = 1, unlockAt = 1000)
}
}
}
expect {
claimable(amount = 8) {
removeVote(trackId = 0, referendumId = 0)
removeVote(trackId = 0, referendumId = 1)
unlock(trackId = 0)
}
}
}
@Test
fun `should handle rejigged prior`() = ClaimScheduleTest {
given {
currentBlock(1200)
track(0) {
voting {
prior(amount = 1, unlockAt = 1100)
vote(amount = 2, unlockAt = 1000, referendumId = 1)
}
}
}
expect {
claimable(amount = 2) {
removeVote(trackId = 0, referendumId = 1)
unlock(trackId = 0)
}
}
}
@Test
fun `should fold several claimable to one`() = ClaimScheduleTest {
given {
currentBlock(1100)
track(0) {
lock(0)
voting {
vote(amount = 1, referendumId = 0, unlockAt = 1100)
}
}
track(1) {
lock(0)
voting {
vote(amount = 2, referendumId = 1, unlockAt = 1000)
}
}
}
expect {
claimable(amount = 2) {
removeVote(trackId = 1, referendumId = 1)
removeVote(trackId = 0, referendumId = 0)
unlock(trackId = 1)
unlock(trackId = 0)
}
}
}
@Test
fun `should include shadowed actions`() = ClaimScheduleTest {
given {
currentBlock(1200)
track(1) {
lock(0)
voting {
vote(amount = 1, referendumId = 1, unlockAt = 1000)
}
}
track(2) {
lock(0)
voting {
vote(amount = 2, referendumId = 2, unlockAt = 1100)
}
}
track(3) {
lock(0)
voting {
vote(1, referendumId = 3, unlockAt = 1200)
}
}
}
expect {
claimable(amount = 2) {
removeVote(trackId = 2, referendumId = 2)
removeVote(trackId = 1, referendumId = 1)
removeVote(trackId = 3, referendumId = 3)
unlock(2)
unlock(1)
unlock(3)
}
}
}
@Test
fun `should take gap into account`() = ClaimScheduleTest {
given {
currentBlock(1000)
track(0) {
lock(10)
voting {
vote(amount = 2, referendumId = 0, unlockAt = 1000)
}
}
}
expect {
claimable(amount = 10) {
removeVote(trackId = 0, referendumId = 0)
unlock(trackId = 0)
}
}
}
@Test
fun `gap should be limited with other locks`() = ClaimScheduleTest {
given {
currentBlock(1000)
track(0) {
lock(10)
voting {
vote(amount = 1, referendumId = 0, unlockAt = 1000)
}
}
track(1) {
voting {
prior(amount = 10, unlockAt = 1000)
}
}
track(2) {
voting {
prior(amount = 1, unlockAt = 1100)
}
}
}
expect {
claimable(amount = 9) {
removeVote(trackId = 0, referendumId = 0)
unlock(trackId = 0)
unlock(trackId = 1)
}
nonClaimable(amount = 1, claimAt = 1100)
}
}
@Test
fun `gap claim should be delayed`() = ClaimScheduleTest {
given {
currentBlock(1000)
track(0) {
lock(10)
}
track(1) {
voting {
prior(amount = 10, unlockAt = 1100)
}
}
}
expect {
nonClaimable(amount = 10, claimAt = 1100)
}
}
@Test
fun `should not dublicate unlock command with both prior and gap present`() = ClaimScheduleTest {
given {
currentBlock(1100)
track(0) {
lock(10)
voting {
prior(amount = 5, unlockAt = 1050)
}
}
}
expect {
claimable(amount = 10) {
unlock(trackId = 0)
}
}
}
@Test
fun `pending should be sorted by remaining time`() = ClaimScheduleTest {
given {
currentBlock(1000)
track(0) {
voting {
vote(amount = 3, unlockAt = 1100, referendumId = 0)
vote(amount = 2, unlockAt = 1200, referendumId = 2)
vote(amount = 1, unlockAt = 1300, referendumId = 1)
}
}
}
expect {
nonClaimable(amount = 1, claimAt = 1100)
nonClaimable(amount = 1, claimAt = 1200)
nonClaimable(amount = 1, claimAt = 1300)
}
}
@Test
fun `gap should not be covered by its track locks`() = ClaimScheduleTest {
given {
currentBlock(1000)
track(20) {
lock(1)
voting {
vote(amount = 1, unlockAt = 2000, referendumId = 13)
}
}
track(21) {
// gap is 101 - 10 = 91 - should not be delayed by its own track voting
lock(101)
voting {
vote(amount = 10, unlockAt = 1500, referendumId = 5)
}
}
}
expect {
claimable(amount = 91) {
unlock(21)
}
nonClaimable(amount = 9, claimAt = 1500)
nonClaimable(amount = 1, claimAt = 2000)
}
}
@Test
fun `should handle standalone delegation`() = ClaimScheduleTest{
given {
track(0) {
delegating {
delegate(1)
}
}
}
expect {
nonClaimable(amount = 1)
}
}
@Test
fun `should take delegation prior lock into account`() = ClaimScheduleTest{
given {
currentBlock(1000)
track(0) {
delegating {
prior(amount = 10, unlockAt = 1100)
delegate(1)
}
}
}
expect {
nonClaimable(amount = 9, claimAt = 1100) // prior is 10, but 1 is delayed because of delegation
nonClaimable(amount = 1)
}
}
@Test
fun `delegation plus gap case`() = ClaimScheduleTest{
given {
currentBlock(1000)
track(0) {
lock(10)
delegating {
delegate(1)
}
}
}
expect {
claimable(amount = 9) {
unlock(0)
}
nonClaimable(amount = 1)
}
}
@Test
fun `delegate plus voting case`() = ClaimScheduleTest{
given {
currentBlock(1000)
track(0) {
delegating {
delegate(1)
}
}
track(1) {
voting {
prior(10, unlockAt = 1000)
vote(amount = 5, unlockAt = 1100, referendumId = 0)
}
}
}
expect {
// 5 is claimable from track 1 priors
claimable(amount = 5) {
unlock(1)
}
// 4 is delayed until 1100 from track 1 votes
nonClaimable(amount = 4, claimAt = 1100)
// 1 is delayed indefinitely because of track 1 delegation
nonClaimable(amount = 1)
}
}
@Test
fun `should not dublicate unlcock when claiming multiple chunks`() = ClaimScheduleTest {
given {
currentBlock(1100)
track(1) {
lock(10)
voting {
vote(amount = 5, unlockAt = 1002, referendumId = 2)
vote(amount = 10, unlockAt = 1001, referendumId = 1)
}
}
}
expect {
claimable(amount = 10) {
removeVote(trackId = 1, referendumId = 1)
removeVote(trackId = 1, referendumId = 2)
unlock(trackId = 1)
}
}
}
}
@@ -0,0 +1,27 @@
package io.novafoundation.nova.feature_governance_impl.data.model.curve
import io.novafoundation.nova.common.data.network.runtime.binding.Perbill
import io.novafoundation.nova.common.utils.hasTheSaveValueAs
import io.novafoundation.nova.common.utils.percentageToFraction
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.VotingCurve
import org.junit.Assert
import java.math.BigDecimal
import java.math.BigInteger
import java.math.MathContext
val Int.percent
get() = this.toBigDecimal().percentageToFraction()
fun VotingCurve.runThresholdTests(tests: List<Pair<Perbill, Perbill>>) {
tests.forEach { (x, expectedY) ->
val y = threshold(x)
Assert.assertTrue("Expected: ${expectedY}, got: ${y}", expectedY hasTheSaveValueAs y)
}
}
fun VotingCurve.runDelayTests(tests: List<Pair<Perbill, Perbill>>) {
tests.forEach { (x, expectedY) ->
val y = delay(x)
Assert.assertTrue("Expected $expectedY for input $x but got: $y", expectedY hasTheSaveValueAs y)
}
}
@@ -0,0 +1,41 @@
package io.novafoundation.nova.feature_governance_impl.data.model.curve
import io.novafoundation.nova.feature_governance_api.data.thresold.gov2.curve.LinearDecreasingCurve
import org.junit.Test
class LinearDecreasingCurveTest {
val curve = LinearDecreasingCurve(
ceil = 90.percent,
floor = 10.percent,
length = 50.percent,
)
// x to y
private val THRESHOLD_TESTS = listOf(
0.percent to 90.percent,
25.percent to 50.percent,
50.percent to 10.percent,
100.percent to 10.percent
)
// y to x
private val DELAY_TESTS = listOf(
100.percent to 0.percent,
90.percent to 0.percent,
50.percent to 25.percent,
10.percent to 50.percent,
9.percent to 100.percent,
0.percent to 100.percent
)
@Test
fun threshold() {
curve.runThresholdTests(THRESHOLD_TESTS)
}
@Test
fun delay() {
curve.runDelayTests(DELAY_TESTS)
}
}
@@ -0,0 +1,40 @@
package io.novafoundation.nova.feature_governance_impl.data.model.curve
import io.novafoundation.nova.feature_governance_api.data.thresold.gov2.curve.ReciprocalCurve
import org.junit.Test
import java.math.BigDecimal
class ReciprocalCurveTest {
// 10/(x + 1) - 1
val curve = ReciprocalCurve(
factor = BigDecimal.TEN,
xOffset = BigDecimal.ONE,
yOffset = (-1).toBigDecimal()
)
// x to y
private val TESTS = listOf(
BigDecimal.ZERO to 9.toBigDecimal(),
0.25.toBigDecimal() to 7.toBigDecimal(),
BigDecimal.ONE to 4.toBigDecimal(),
3.toBigDecimal() to 1.5.toBigDecimal()
)
// y to x
private val DELAY_TESTS = listOf(
9.toBigDecimal() to BigDecimal.ZERO,
7.toBigDecimal() to 0.25.toBigDecimal(),
4.toBigDecimal() to BigDecimal.ONE
)
@Test
fun threshold() {
curve.runThresholdTests(TESTS)
}
@Test
fun delay() {
curve.runDelayTests(DELAY_TESTS)
}
}
@@ -0,0 +1,46 @@
package io.novafoundation.nova.feature_governance_impl.data.model.curve
import io.novafoundation.nova.common.utils.lessEpsilon
import io.novafoundation.nova.feature_governance_api.data.thresold.gov2.curve.LinearDecreasingCurve
import io.novafoundation.nova.feature_governance_api.data.thresold.gov2.curve.SteppedDecreasingCurve
import java.math.BigDecimal
import org.junit.Test
class SteppedDecreasingCurveTest {
val curve = SteppedDecreasingCurve(
begin = 80.percent,
end = 30.percent,
step = 10.percent,
period = 15.percent
)
// x to y
private val TESTS = listOf(
0.percent to 80.percent,
15.percent.lessEpsilon() to 80.percent,
15.percent to 70.percent,
30.percent.lessEpsilon() to 70.percent,
30.percent to 60.percent,
100.percent to 30.percent
)
// y to x
private val DELAY_TESTS = listOf(
80.percent to 0.percent,
70.percent to 15.percent,
60.percent to 30.percent,
30.percent to 75.percent,
10.percent to 100.percent
)
@Test
fun threshold() {
curve.runThresholdTests(TESTS)
}
@Test
fun delay() {
curve.runDelayTests(DELAY_TESTS)
}
}