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,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />
@@ -0,0 +1,16 @@
package io.novafoundation.nova.feature_ledger_api.data.repository
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
object LedgerDerivationPath {
private const val LEDGER_DERIVATION_PATH_KEY = "LedgerChainAccount.derivationPath"
fun legacyDerivationPathSecretKey(chainId: ChainId): String {
return "$LEDGER_DERIVATION_PATH_KEY.$chainId"
}
fun genericDerivationPathSecretKey(): String {
return "$LEDGER_DERIVATION_PATH_KEY.Generic"
}
}
@@ -0,0 +1,13 @@
package io.novafoundation.nova.feature_ledger_api.data.repository
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
interface LedgerRepository {
suspend fun getChainAccountDerivationPath(
metaId: Long,
chainId: ChainId
): String
suspend fun getGenericDerivationPath(metaId: Long): String
}
@@ -0,0 +1,3 @@
package io.novafoundation.nova.feature_ledger_api.di
interface LedgerFeatureApi
@@ -0,0 +1,33 @@
package io.novafoundation.nova.feature_ledger_api.sdk.application.substrate
enum class LedgerApplicationResponse(val code: UShort) {
UNKNOWN(1u),
BAD_REQUEST(2u),
UNSUPPORTED(3u),
INELIGIBLE_DEVICE(4u),
TIMEOUT_U2F(5u),
TIMEOUT(14u),
NO_ERROR(0x9000u),
DEVICE_BUSY(0x9001u),
DERIVING_KEY_ERROR(0x6802u),
EXECUTION_ERROR(0x6400u),
WRONG_LENGTH(0x6700u),
EMPTY_BUFFER(0x6982u),
OUTPUT_BUFFER_TOO_SMALL(0x6983u),
INVALID_DATA(0x6984u),
CONDITIONS_NOT_SATISFIED(0x6985u),
TRANSACTION_REJECTED(0x6986u),
BAD_KEY(0x6A80u),
INVALID_P1P2(0x6B00u),
INSTRUCTION_NOT_SUPPORTED(0x6D00u),
WRONG_APP_OPEN(0x6E00u),
APP_NOT_OPEN(0x6E01u),
UNKNOWN_ERROR(0x6F00u),
SIGN_VERIFY_ERROR(0x6F01u);
companion object {
fun fromCode(code: UShort): LedgerApplicationResponse {
return values().firstOrNull { it.code == code } ?: UNKNOWN
}
}
}
@@ -0,0 +1,23 @@
package io.novafoundation.nova.feature_ledger_api.sdk.application.substrate
import io.novasama.substrate_sdk_android.encrypt.EncryptionType
import io.novasama.substrate_sdk_android.extensions.asEthereumAccountId
import io.novasama.substrate_sdk_android.extensions.toAddress
import io.novasama.substrate_sdk_android.runtime.AccountId
class LedgerSubstrateAccount(
val address: String,
val publicKey: ByteArray,
val encryptionType: EncryptionType,
val derivationPath: String,
)
// Ledger EVM shares derivation path with Ledger Substrate
class LedgerEvmAccount(
val accountId: AccountId,
val publicKey: ByteArray,
)
fun LedgerEvmAccount.address(): String {
return accountId.asEthereumAccountId().toAddress().value
}
@@ -0,0 +1,46 @@
package io.novafoundation.nova.feature_ledger_api.sdk.application.substrate
import io.novafoundation.nova.feature_ledger_api.BuildConfig
import io.novafoundation.nova.runtime.ext.Geneses
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
class SubstrateApplicationConfig(
val chainId: String,
val coin: Int,
val cla: UByte
) {
companion object {
private val ALL by lazy {
listOfNotNull(
SubstrateApplicationConfig(chainId = Chain.Geneses.POLKADOT, coin = 354, cla = 0x90u),
SubstrateApplicationConfig(chainId = Chain.Geneses.KUSAMA, coin = 434, cla = 0x99u),
SubstrateApplicationConfig(chainId = Chain.Geneses.STATEMINT, coin = 354, cla = 0x96u),
SubstrateApplicationConfig(chainId = Chain.Geneses.KUSAMA_ASSET_HUB, coin = 434, cla = 0x97u),
SubstrateApplicationConfig(chainId = Chain.Geneses.EDGEWARE, coin = 523, cla = 0x94u),
SubstrateApplicationConfig(chainId = Chain.Geneses.KARURA, coin = 686, cla = 0x9au),
SubstrateApplicationConfig(chainId = Chain.Geneses.ACALA, coin = 787, cla = 0x9bu),
SubstrateApplicationConfig(chainId = Chain.Geneses.NODLE_PARACHAIN, coin = 1003, cla = 0x98u),
SubstrateApplicationConfig(chainId = Chain.Geneses.POLYMESH, coin = 595, cla = 0x91u),
SubstrateApplicationConfig(chainId = Chain.Geneses.XX_NETWORK, coin = 1955, cla = 0xa3u),
SubstrateApplicationConfig(chainId = Chain.Geneses.ASTAR, coin = 810, cla = 0xa9u),
SubstrateApplicationConfig(chainId = Chain.Geneses.ALEPH_ZERO, coin = 643, cla = 0xa4u),
SubstrateApplicationConfig(chainId = Chain.Geneses.POLKADEX, coin = 799, cla = 0xa0u),
novasamaLedgerTestnetFakeApp()
)
}
fun all() = ALL
private fun novasamaLedgerTestnetFakeApp(): SubstrateApplicationConfig? {
return SubstrateApplicationConfig(chainId = "d67c91ca75c199ff1ee9555567dfad21b9033165c39977170ec8d3f6c1fa433c", coin = 434, cla = 0x90u)
.takeIf { BuildConfig.DEBUG }
}
}
}
fun SubstrateApplicationConfig.Companion.supports(chainId: String): Boolean {
return all().any { it.chainId == chainId }
}
@@ -0,0 +1,29 @@
package io.novafoundation.nova.feature_ledger_api.sdk.application.substrate
import io.novafoundation.nova.feature_ledger_api.sdk.device.LedgerDevice
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
import io.novasama.substrate_sdk_android.encrypt.SignatureWrapper
import io.novasama.substrate_sdk_android.runtime.extrinsic.v5.transactionExtension.InheritedImplication
interface SubstrateLedgerApplication {
suspend fun getSubstrateAccount(
device: LedgerDevice,
chainId: ChainId,
accountIndex: Int,
confirmAddress: Boolean
): LedgerSubstrateAccount
suspend fun getEvmAccount(
device: LedgerDevice,
accountIndex: Int,
confirmAddress: Boolean
): LedgerEvmAccount?
suspend fun getSignature(
device: LedgerDevice,
metaId: Long,
chainId: ChainId,
payload: InheritedImplication,
): SignatureWrapper
}
@@ -0,0 +1,11 @@
package io.novafoundation.nova.feature_ledger_api.sdk.application.substrate
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
sealed class SubstrateLedgerApplicationError(message: String) : Exception(message) {
class UnsupportedApp(val chainId: ChainId) : SubstrateLedgerApplicationError("Unsupported app for chainId: $chainId")
class Response(val response: LedgerApplicationResponse, val errorMessage: String?) :
SubstrateLedgerApplicationError("Application error: $response")
}
@@ -0,0 +1,35 @@
package io.novafoundation.nova.feature_ledger_api.sdk.connection
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
interface LedgerConnection {
enum class Type {
BLE,
USB
}
val channel: Short?
val type: Type
val isActive: Flow<Boolean>
suspend fun mtu(): Int
suspend fun send(chunks: List<ByteArray>)
suspend fun connect(): Result<Unit>
suspend fun resetReceiveChannel()
val receiveChannel: Channel<ByteArray>
}
suspend fun LedgerConnection.ensureConnected() {
if (!isConnected()) connect().getOrThrow()
}
suspend fun LedgerConnection.awaitConnected() = isActive.first { connected -> connected }
suspend fun LedgerConnection.isConnected() = isActive.first()
@@ -0,0 +1,15 @@
package io.novafoundation.nova.feature_ledger_api.sdk.device
import io.novafoundation.nova.feature_ledger_api.sdk.connection.LedgerConnection
class LedgerDevice(
val id: String,
val deviceType: LedgerDeviceType,
val name: String?,
val connection: LedgerConnection,
) {
override fun toString(): String {
return "${name ?: id} (${connection.type})"
}
}
@@ -0,0 +1,98 @@
package io.novafoundation.nova.feature_ledger_api.sdk.device
import io.novafoundation.nova.common.utils.toUuid
import java.util.UUID
private const val LEDGER_VENDOR_ID = 11415
// You can find this and new devices here: https://github.com/LedgerHQ/device-sdk-ts/blob/develop/packages/device-management-kit/src/api/device-model/data/StaticDeviceModelDataSource.ts
enum class LedgerDeviceType(
val bleDevice: BleDevice,
val usbOptions: UsbDeviceInfo
) {
STAX(
BleDevice.Supported(
serviceUuid = "13d63400-2c97-6004-0000-4c6564676572".toUuid(),
notifyUuid = "13d63400-2c97-6004-0001-4c6564676572".toUuid(),
writeUuid = "13d63400-2c97-6004-0002-4c6564676572".toUuid()
),
usbOptions = UsbDeviceInfo(vendorId = LEDGER_VENDOR_ID, productId = 24576)
),
FLEX(
BleDevice.Supported(
serviceUuid = "13d63400-2c97-3004-0000-4c6564676572".toUuid(),
notifyUuid = "13d63400-2c97-3004-0001-4c6564676572".toUuid(),
writeUuid = "13d63400-2c97-3004-0002-4c6564676572".toUuid()
),
usbOptions = UsbDeviceInfo(vendorId = LEDGER_VENDOR_ID, productId = 28672)
),
NANO_X(
BleDevice.Supported(
serviceUuid = "13d63400-2c97-0004-0000-4c6564676572".toUuid(),
notifyUuid = "13d63400-2c97-0004-0001-4c6564676572".toUuid(),
writeUuid = "13d63400-2c97-0004-0002-4c6564676572".toUuid()
),
usbOptions = UsbDeviceInfo(vendorId = LEDGER_VENDOR_ID, productId = 16401)
),
NANO_S_PLUS(
BleDevice.NotSupported,
usbOptions = UsbDeviceInfo(vendorId = LEDGER_VENDOR_ID, productId = 20480)
),
NANO_S(
BleDevice.NotSupported,
usbOptions = UsbDeviceInfo(vendorId = LEDGER_VENDOR_ID, productId = 4113)
),
NANO_GEN5(
BleDevice.Supported(
BleDevice.Supported.Spec(
serviceUuid = "13d63400-2c97-8004-0000-4c6564676572".toUuid(),
notifyUuid = "13d63400-2c97-8004-0001-4c6564676572".toUuid(),
writeUuid = "13d63400-2c97-8004-0002-4c6564676572".toUuid()
),
BleDevice.Supported.Spec(
serviceUuid = "13d63400-2c97-9004-0000-4c6564676572".toUuid(),
notifyUuid = "13d63400-2c97-9004-0001-4c6564676572".toUuid(),
writeUuid = "13d63400-2c97-9004-0002-4c6564676572".toUuid()
)
),
usbOptions = UsbDeviceInfo(vendorId = LEDGER_VENDOR_ID, productId = 32768)
)
}
sealed interface BleDevice {
object NotSupported : BleDevice
class Supported(
vararg val specs: Spec
) : BleDevice {
constructor(
serviceUuid: UUID,
writeUuid: UUID,
notifyUuid: UUID,
) : this(Spec(serviceUuid, writeUuid, notifyUuid))
class Spec(
val serviceUuid: UUID,
val writeUuid: UUID,
val notifyUuid: UUID,
)
}
}
fun LedgerDeviceType.supportedBleSpecs(): List<BleDevice.Supported.Spec> {
return (bleDevice as? BleDevice.Supported)
?.specs?.toList()
.orEmpty()
}
class UsbDeviceInfo(
val vendorId: Int,
val productId: Int
)
@@ -0,0 +1,56 @@
package io.novafoundation.nova.feature_ledger_api.sdk.discovery
import io.novafoundation.nova.common.utils.filterToSet
@JvmInline
value class DiscoveryMethods(val methods: List<Method>) {
constructor(vararg methods: Method) : this(methods.toList())
enum class Method {
BLE,
USB
}
companion object {
fun all() = DiscoveryMethods(Method.BLE, Method.USB)
}
}
enum class DiscoveryRequirement {
BLUETOOTH, LOCATION
}
fun DiscoveryMethods.filterBySatisfiedRequirements(
discoveryRequirementAvailability: DiscoveryRequirementAvailability
): Set<DiscoveryMethods.Method> {
return methods.filterToSet { method ->
val methodRequirements = method.discoveryRequirements()
val requirementsSatisfied = methodRequirements.all { it in discoveryRequirementAvailability.satisfiedRequirements }
val availableWithPermissions = methodRequirements.availableWithPermissions(discoveryRequirementAvailability.permissionsGranted)
requirementsSatisfied && availableWithPermissions
}
}
private fun List<DiscoveryRequirement>.availableWithPermissions(permissionsGranted: Boolean): Boolean {
return if (isEmpty()) {
true
} else {
permissionsGranted
}
}
fun DiscoveryMethods.discoveryRequirements() = methods.flatMap {
when (it) {
DiscoveryMethods.Method.BLE -> listOf(DiscoveryRequirement.BLUETOOTH, DiscoveryRequirement.LOCATION)
DiscoveryMethods.Method.USB -> emptyList()
}
}
fun DiscoveryMethods.Method.discoveryRequirements(): List<DiscoveryRequirement> {
return when (this) {
DiscoveryMethods.Method.BLE -> listOf(DiscoveryRequirement.BLUETOOTH, DiscoveryRequirement.LOCATION)
DiscoveryMethods.Method.USB -> emptyList()
}
}
@@ -0,0 +1,22 @@
package io.novafoundation.nova.feature_ledger_api.sdk.discovery
data class DiscoveryRequirementAvailability(
val satisfiedRequirements: Set<DiscoveryRequirement>,
val permissionsGranted: Boolean
)
fun DiscoveryRequirementAvailability.grantPermissions(): DiscoveryRequirementAvailability {
return copy(permissionsGranted = true)
}
fun DiscoveryRequirementAvailability.satisfyRequirement(requirement: DiscoveryRequirement): DiscoveryRequirementAvailability {
return copy(
satisfiedRequirements = satisfiedRequirements + requirement
)
}
fun DiscoveryRequirementAvailability.missRequirement(requirement: DiscoveryRequirement): DiscoveryRequirementAvailability {
return copy(
satisfiedRequirements = satisfiedRequirements - requirement
)
}
@@ -0,0 +1,28 @@
package io.novafoundation.nova.feature_ledger_api.sdk.discovery
import io.novafoundation.nova.feature_ledger_api.sdk.device.LedgerDevice
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
interface LedgerDeviceDiscoveryService {
val discoveredDevices: Flow<List<LedgerDevice>>
val errors: Flow<Throwable>
fun startDiscovery(methods: Set<DiscoveryMethods.Method>)
fun stopDiscovery(methods: Set<DiscoveryMethods.Method>)
fun stopDiscovery()
}
suspend fun LedgerDeviceDiscoveryService.findDevice(id: String): LedgerDevice? {
val devices = discoveredDevices.first()
return devices.find { it.id == id }
}
suspend fun LedgerDeviceDiscoveryService.findDeviceOrThrow(id: String): LedgerDevice {
return findDevice(id) ?: throw IllegalArgumentException("Device not found")
}
@@ -0,0 +1,34 @@
package io.novafoundation.nova.feature_ledger_api.sdk.transport
import io.novafoundation.nova.common.utils.bigEndianBytes
import io.novafoundation.nova.feature_ledger_api.sdk.device.LedgerDevice
interface LedgerTransport {
suspend fun exchange(data: ByteArray, device: LedgerDevice): ByteArray
}
@OptIn(ExperimentalUnsignedTypes::class)
suspend fun LedgerTransport.send(
cla: UByte,
ins: UByte,
p1: UByte,
p2: UByte,
data: ByteArray,
device: LedgerDevice
): ByteArray {
var message = ubyteArrayOf(cla, ins, p1, p2)
if (data.isNotEmpty()) {
if (data.size < 256) {
message += data.size.toUByte()
} else {
message += 0x00u
message += data.size.toShort().bigEndianBytes.toUByteArray()
}
message += data.toUByteArray()
}
return exchange(message.toByteArray(), device)
}
@@ -0,0 +1,8 @@
package io.novafoundation.nova.feature_ledger_api.sdk.transport
class LedgerTransportError(val reason: Reason) : Exception(reason.toString()) {
enum class Reason {
NO_HEADER_FOUND, UNSUPPORTED_RESPONSE, INCOMPLETE_RESPONSE, NO_MESSAGE_SIZE_FOUND
}
}