mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-04-23 03:47:56 +00:00
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:
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest />
|
||||
+16
@@ -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"
|
||||
}
|
||||
}
|
||||
+13
@@ -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
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package io.novafoundation.nova.feature_ledger_api.di
|
||||
|
||||
interface LedgerFeatureApi
|
||||
+33
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
@@ -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
|
||||
}
|
||||
+46
@@ -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 }
|
||||
}
|
||||
+29
@@ -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
|
||||
}
|
||||
+11
@@ -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")
|
||||
}
|
||||
+35
@@ -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()
|
||||
+15
@@ -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})"
|
||||
}
|
||||
}
|
||||
+98
@@ -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
|
||||
)
|
||||
+56
@@ -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()
|
||||
}
|
||||
}
|
||||
+22
@@ -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
|
||||
)
|
||||
}
|
||||
+28
@@ -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")
|
||||
}
|
||||
+34
@@ -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)
|
||||
}
|
||||
+8
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user