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
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
@@ -0,0 +1,41 @@
package io.novafoundation.nova.caip.caip19
import io.novafoundation.nova.caip.caip19.matchers.Caip19Matcher
import io.novafoundation.nova.caip.caip19.matchers.asset.AssetMatcher
import io.novafoundation.nova.caip.caip19.matchers.asset.Erc20AssetMatcher
import io.novafoundation.nova.caip.caip19.matchers.asset.Slip44AssetMatcher
import io.novafoundation.nova.caip.caip19.matchers.asset.UnsupportedAssetMatcher
import io.novafoundation.nova.caip.caip2.Caip2MatcherFactory
import io.novafoundation.nova.caip.slip44.Slip44CoinRepository
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain.Asset.Type.EvmErc20
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain.Asset.Type.Unsupported
interface Caip19MatcherFactory {
suspend fun getCaip19Matcher(chain: Chain, chainAsset: Chain.Asset): Caip19Matcher
}
class RealCaip19MatcherFactory(
private val slip44CoinRepository: Slip44CoinRepository,
private val caip2MatcherFactory: Caip2MatcherFactory,
) : Caip19MatcherFactory {
override suspend fun getCaip19Matcher(chain: Chain, chainAsset: Chain.Asset): Caip19Matcher {
val caip2Matcher = caip2MatcherFactory.getCaip2Matcher(chain)
val assetNamespaceMatcher = getAssetNamespaceMatcher(chainAsset)
return Caip19Matcher(caip2Matcher, assetNamespaceMatcher)
}
private suspend fun getAssetNamespaceMatcher(chainAsset: Chain.Asset): AssetMatcher {
return when (val assetType = chainAsset.type) {
is EvmErc20 -> Erc20AssetMatcher(assetType.contractAddress)
Unsupported -> UnsupportedAssetMatcher()
else -> slip44CoinRepository.getCoinCode(chainAsset)?.let(::Slip44AssetMatcher)
?: UnsupportedAssetMatcher()
}
}
}
@@ -0,0 +1,40 @@
package io.novafoundation.nova.caip.caip19
import io.novafoundation.nova.caip.caip19.identifiers.AssetIdentifier
import io.novafoundation.nova.caip.caip19.identifiers.Caip19Identifier
import io.novafoundation.nova.caip.caip19.identifiers.ERC20
import io.novafoundation.nova.caip.caip19.identifiers.NotSupportedIdentifierException
import io.novafoundation.nova.caip.caip19.identifiers.SLIP44
import io.novafoundation.nova.caip.caip2.Caip2Parser
import io.novafoundation.nova.caip.common.toNamespaceAndReference
interface Caip19Parser {
fun parseCaip19(caip19Identifier: String): Result<Caip19Identifier>
}
internal class RealCaip19Parser(
private val caip2Parser: Caip2Parser,
) : Caip19Parser {
override fun parseCaip19(caip19Identifier: String): Result<Caip19Identifier> = runCatching {
val (chain, asset) = caip19Identifier.splitToNamespaces()
Caip19Identifier(caip2Parser.parseCaip2(chain).getOrThrow(), parseAsset(asset))
}
private fun parseAsset(asset: String): AssetIdentifier {
val (assetNamespace, assetIdentifier) = asset.toNamespaceAndReference()
return when (assetNamespace) {
SLIP44 -> AssetIdentifier.Slip44(assetIdentifier.toInt())
ERC20 -> AssetIdentifier.Erc20(assetIdentifier)
else -> throw NotSupportedIdentifierException()
}
}
private fun String.splitToNamespaces(): Pair<String, String> {
val (chainNamespace, tokenNamespace) = split("/")
return Pair(chainNamespace, tokenNamespace)
}
}
@@ -0,0 +1,10 @@
package io.novafoundation.nova.caip.caip19.identifiers
const val SLIP44 = "slip44"
const val ERC20 = "erc20"
sealed interface AssetIdentifier {
class Slip44(val slip44CoinCode: Int) : AssetIdentifier
class Erc20(val contractAddress: String) : AssetIdentifier
}
@@ -0,0 +1,5 @@
package io.novafoundation.nova.caip.caip19.identifiers
import io.novafoundation.nova.caip.caip2.identifier.Caip2Identifier
class Caip19Identifier(val caip2Identifier: Caip2Identifier, val assetIdentifier: AssetIdentifier)
@@ -0,0 +1,3 @@
package io.novafoundation.nova.caip.caip19.identifiers
class NotSupportedIdentifierException : Exception()
@@ -0,0 +1,21 @@
package io.novafoundation.nova.caip.caip19.matchers
import io.novafoundation.nova.caip.caip19.identifiers.Caip19Identifier
import io.novafoundation.nova.caip.caip19.matchers.asset.AssetMatcher
import io.novafoundation.nova.caip.caip19.matchers.asset.UnsupportedAssetMatcher
import io.novafoundation.nova.caip.caip2.matchers.Caip2Matcher
class Caip19Matcher(
private val caip2Matcher: Caip2Matcher,
private val assetMatcher: AssetMatcher
) {
fun match(caip19Identifier: Caip19Identifier): Boolean {
return caip2Matcher.match(caip19Identifier.caip2Identifier) &&
assetMatcher.match(caip19Identifier.assetIdentifier)
}
fun isUnsupported(): Boolean {
return assetMatcher is UnsupportedAssetMatcher
}
}
@@ -0,0 +1,8 @@
package io.novafoundation.nova.caip.caip19.matchers.asset
import io.novafoundation.nova.caip.caip19.identifiers.AssetIdentifier
interface AssetMatcher {
fun match(assetIdentifier: AssetIdentifier): Boolean
}
@@ -0,0 +1,11 @@
package io.novafoundation.nova.caip.caip19.matchers.asset
import io.novafoundation.nova.caip.caip19.identifiers.AssetIdentifier
import io.novafoundation.nova.caip.caip19.identifiers.AssetIdentifier.Erc20
class Erc20AssetMatcher(private val contractAddress: String) : AssetMatcher {
override fun match(assetIdentifier: AssetIdentifier): Boolean {
return assetIdentifier is Erc20 && assetIdentifier.contractAddress == contractAddress
}
}
@@ -0,0 +1,13 @@
package io.novafoundation.nova.caip.caip19.matchers.asset
import io.novafoundation.nova.caip.caip19.identifiers.AssetIdentifier
class Slip44AssetMatcher(
private val assetSlip44CoinCode: Int,
) : AssetMatcher {
override fun match(assetIdentifier: AssetIdentifier): Boolean {
return assetIdentifier is AssetIdentifier.Slip44 &&
assetSlip44CoinCode == assetIdentifier.slip44CoinCode
}
}
@@ -0,0 +1,10 @@
package io.novafoundation.nova.caip.caip19.matchers.asset
import io.novafoundation.nova.caip.caip19.identifiers.AssetIdentifier
class UnsupportedAssetMatcher : AssetMatcher {
override fun match(assetIdentifier: AssetIdentifier): Boolean {
return false
}
}
@@ -0,0 +1,26 @@
package io.novafoundation.nova.caip.caip2
import io.novafoundation.nova.caip.caip2.matchers.Caip2Matcher
import io.novafoundation.nova.caip.caip2.matchers.Caip2MatcherList
import io.novafoundation.nova.caip.caip2.matchers.Eip155Matcher
import io.novafoundation.nova.caip.caip2.matchers.SubstrateCaip2Matcher
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
interface Caip2MatcherFactory {
suspend fun getCaip2Matcher(chain: Chain): Caip2Matcher
}
internal class RealCaip2MatcherFactory : Caip2MatcherFactory {
override suspend fun getCaip2Matcher(chain: Chain): Caip2Matcher {
val matchers = buildList {
add(SubstrateCaip2Matcher(chain))
if (chain.isEthereumBased) {
add(Eip155Matcher(chain))
}
}
return Caip2MatcherList(matchers)
}
}
@@ -0,0 +1,28 @@
package io.novafoundation.nova.caip.caip2
import io.novafoundation.nova.caip.caip19.identifiers.NotSupportedIdentifierException
import io.novafoundation.nova.caip.caip2.identifier.Caip2Identifier
import io.novafoundation.nova.caip.caip2.identifier.Caip2Namespace
import io.novafoundation.nova.caip.common.toNamespaceAndReference
interface Caip2Parser {
fun parseCaip2(caip2Identifier: String): Result<Caip2Identifier>
}
fun Caip2Parser.isValidCaip2(caip2Identifier: String): Boolean = parseCaip2(caip2Identifier).isSuccess
fun Caip2Parser.parseCaip2OrThrow(caip2Identifier: String): Caip2Identifier = parseCaip2(caip2Identifier).getOrThrow()
internal class RealCaip2Parser : Caip2Parser {
override fun parseCaip2(caip2Identifier: String): Result<Caip2Identifier> = runCatching {
val (chainNamespace, chainIdentifier) = caip2Identifier.toNamespaceAndReference()
when (chainNamespace) {
Caip2Namespace.EIP155.namespaceName -> Caip2Identifier.Eip155(chainIdentifier.toBigInteger())
Caip2Namespace.POLKADOT.namespaceName -> Caip2Identifier.Polkadot(chainIdentifier)
else -> throw NotSupportedIdentifierException()
}
}
}
@@ -0,0 +1,50 @@
package io.novafoundation.nova.caip.caip2
import io.novafoundation.nova.caip.caip2.identifier.Caip2Identifier
import io.novafoundation.nova.caip.caip2.identifier.Caip2Namespace
import io.novafoundation.nova.common.utils.associateByMultiple
import io.novafoundation.nova.runtime.ext.genesisHash
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novafoundation.nova.runtime.multiNetwork.enabledChains
interface Caip2Resolver {
fun caip2Of(chain: Chain, preferredNamespace: Caip2Namespace): Caip2Identifier?
fun allCaip2Of(chain: Chain): List<Caip2Identifier>
suspend fun chainsByCaip2(): Map<String, Chain>
}
internal class RealCaip2Resolver(
private val chainRegistry: ChainRegistry,
) : Caip2Resolver {
override fun caip2Of(chain: Chain, preferredNamespace: Caip2Namespace): Caip2Identifier? {
val allCaips = allCaip2Of(chain)
return allCaips.find { it.namespace == preferredNamespace } ?: allCaips.firstOrNull()
}
override fun allCaip2Of(chain: Chain): List<Caip2Identifier> {
return buildList {
if (chain.hasSubstrateRuntime) add(polkadotChain(requireNotNull(chain.genesisHash)))
if (chain.isEthereumBased) add(eipChain(chain.addressPrefix))
}
}
override suspend fun chainsByCaip2(): Map<String, Chain> {
val allChains = chainRegistry.enabledChains()
return allChains.associateByMultiple { chain -> allCaip2Of(chain).map { it.namespaceWitId } }
}
private fun polkadotChain(genesisHash: String): Caip2Identifier {
return Caip2Identifier.Polkadot(genesisHash)
}
private fun eipChain(eipChainId: Int): Caip2Identifier {
return Caip2Identifier.Eip155(eipChainId.toBigInteger())
}
}
@@ -0,0 +1,44 @@
package io.novafoundation.nova.caip.caip2.identifier
import io.novafoundation.nova.common.utils.removeHexPrefix
import java.math.BigInteger
enum class Caip2Namespace(val namespaceName: String) {
EIP155("eip155"),
POLKADOT("polkadot");
companion object {
fun find(namespaceName: String): Caip2Namespace? {
return values().find { it.namespaceName == namespaceName }
}
}
}
sealed class Caip2Identifier {
abstract val namespaceWitId: String
abstract val namespace: Caip2Namespace
override operator fun equals(other: Any?): Boolean = other is Caip2Identifier && namespaceWitId == other.namespaceWitId
override fun hashCode(): Int = namespaceWitId.hashCode()
class Eip155(val chainId: BigInteger) : Caip2Identifier() {
override val namespace = Caip2Namespace.EIP155
override val namespaceWitId: String = formatCaip2(Caip2Namespace.EIP155, chainId)
}
class Polkadot(val genesisHash: String) : Caip2Identifier() {
override val namespace = Caip2Namespace.POLKADOT
override val namespaceWitId: String = formatCaip2(Caip2Namespace.POLKADOT, genesisHash.removeHexPrefix().take(32))
}
}
private fun formatCaip2(namespace: Caip2Namespace, reference: Any): String {
return "${namespace.namespaceName}:$reference"
}
@@ -0,0 +1,8 @@
package io.novafoundation.nova.caip.caip2.matchers
import io.novafoundation.nova.caip.caip2.identifier.Caip2Identifier
interface Caip2Matcher {
fun match(caip2Identifier: Caip2Identifier): Boolean
}
@@ -0,0 +1,10 @@
package io.novafoundation.nova.caip.caip2.matchers
import io.novafoundation.nova.caip.caip2.identifier.Caip2Identifier
class Caip2MatcherList(private val matchers: List<Caip2Matcher>) : Caip2Matcher {
override fun match(caip2Identifier: Caip2Identifier): Boolean {
return matchers.any { it.match(caip2Identifier) }
}
}
@@ -0,0 +1,12 @@
package io.novafoundation.nova.caip.caip2.matchers
import io.novafoundation.nova.caip.caip2.identifier.Caip2Identifier
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
class Eip155Matcher(private val chain: Chain) : Caip2Matcher {
override fun match(caip2Identifier: Caip2Identifier): Boolean {
return caip2Identifier is Caip2Identifier.Eip155 &&
caip2Identifier.chainId == chain.addressPrefix.toBigInteger()
}
}
@@ -0,0 +1,13 @@
package io.novafoundation.nova.caip.caip2.matchers
import io.novafoundation.nova.common.utils.removeHexPrefix
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novafoundation.nova.caip.caip2.identifier.Caip2Identifier
class SubstrateCaip2Matcher(private val chain: Chain) : Caip2Matcher {
override fun match(caip2Identifier: Caip2Identifier): Boolean {
return caip2Identifier is Caip2Identifier.Polkadot &&
chain.id.removeHexPrefix().startsWith(caip2Identifier.genesisHash.removeHexPrefix())
}
}
@@ -0,0 +1,6 @@
package io.novafoundation.nova.caip.common
internal fun String.toNamespaceAndReference(): Pair<String, String> {
val (namespaceName, namespaceReference) = split(":")
return Pair(namespaceName, namespaceReference)
}
@@ -0,0 +1,20 @@
package io.novafoundation.nova.caip.di
import io.novafoundation.nova.caip.caip19.Caip19MatcherFactory
import io.novafoundation.nova.caip.caip19.Caip19Parser
import io.novafoundation.nova.caip.caip2.Caip2MatcherFactory
import io.novafoundation.nova.caip.caip2.Caip2Parser
import io.novafoundation.nova.caip.caip2.Caip2Resolver
interface CaipApi {
val caip2Parser: Caip2Parser
val caip2Resolver: Caip2Resolver
val caip2MatcherFactory: Caip2MatcherFactory
val caip19Parser: Caip19Parser
val caip19MatcherFactory: Caip19MatcherFactory
}
@@ -0,0 +1,14 @@
package io.novafoundation.nova.caip.di
import com.google.gson.Gson
import io.novafoundation.nova.common.data.network.NetworkApiCreator
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
interface CaipDependencies {
val networkApiCreator: NetworkApiCreator
val gson: Gson
val chainRegistry: ChainRegistry
}
@@ -0,0 +1,26 @@
package io.novafoundation.nova.caip.di
import dagger.Component
import io.novafoundation.nova.common.di.CommonApi
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.runtime.di.RuntimeApi
@Component(
modules = [
CaipModule::class
],
dependencies = [
CaipDependencies::class
]
)
@FeatureScope
abstract class CaipFeatureComponent : CaipApi {
@Component(
dependencies = [
CommonApi::class,
RuntimeApi::class,
]
)
interface CaipDependenciesComponent : CaipDependencies
}
@@ -0,0 +1,24 @@
package io.novafoundation.nova.caip.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 CaipFeatureHolder @Inject constructor(
featureContainer: FeatureContainer
) : FeatureApiHolder(featureContainer) {
override fun initializeDependencies(): Any {
val dbDependencies = DaggerCaipFeatureComponent_CaipDependenciesComponent.builder()
.commonApi(commonApi())
.runtimeApi(getFeature(RuntimeApi::class.java))
.build()
return DaggerCaipFeatureComponent.builder()
.caipDependencies(dbDependencies)
.build()
}
}
@@ -0,0 +1,67 @@
package io.novafoundation.nova.caip.di
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.caip.caip19.Caip19MatcherFactory
import io.novafoundation.nova.caip.caip19.Caip19Parser
import io.novafoundation.nova.caip.caip19.RealCaip19MatcherFactory
import io.novafoundation.nova.caip.caip19.RealCaip19Parser
import io.novafoundation.nova.caip.caip2.Caip2MatcherFactory
import io.novafoundation.nova.caip.caip2.Caip2Parser
import io.novafoundation.nova.caip.caip2.Caip2Resolver
import io.novafoundation.nova.caip.caip2.RealCaip2MatcherFactory
import io.novafoundation.nova.caip.caip2.RealCaip2Parser
import io.novafoundation.nova.caip.caip2.RealCaip2Resolver
import io.novafoundation.nova.caip.slip44.RealSlip44CoinRepository
import io.novafoundation.nova.caip.slip44.Slip44CoinRepository
import io.novafoundation.nova.caip.slip44.endpoint.Slip44CoinApi
import io.novafoundation.nova.caip.BuildConfig
import io.novafoundation.nova.common.data.network.NetworkApiCreator
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
@Module
class CaipModule {
@Provides
@FeatureScope
fun provideSlip44CoinApi(
networkApiCreator: NetworkApiCreator
): Slip44CoinApi {
return networkApiCreator.create(Slip44CoinApi::class.java)
}
@Provides
@FeatureScope
fun provideSlip44CoinRepository(slip44Api: Slip44CoinApi): Slip44CoinRepository {
return RealSlip44CoinRepository(
slip44Api = slip44Api,
slip44CoinsUrl = BuildConfig.SLIP_44_COINS_BASE_URL
)
}
@Provides
@FeatureScope
fun provideCaip2MatcherFactory(): Caip2MatcherFactory = RealCaip2MatcherFactory()
@Provides
@FeatureScope
fun provideCaip2Parser(): Caip2Parser = RealCaip2Parser()
@Provides
@FeatureScope
fun provideCaip2Resolver(chainRegistry: ChainRegistry): Caip2Resolver = RealCaip2Resolver(chainRegistry)
@Provides
@FeatureScope
fun provideCaip19MatcherFactory(
slip44CoinRepository: Slip44CoinRepository,
caip2MatcherFactory: Caip2MatcherFactory,
): Caip19MatcherFactory {
return RealCaip19MatcherFactory(slip44CoinRepository, caip2MatcherFactory)
}
@Provides
@FeatureScope
fun provideCaip19Parser(caip2Parser: Caip2Parser): Caip19Parser = RealCaip19Parser(caip2Parser)
}
@@ -0,0 +1,33 @@
package io.novafoundation.nova.caip.slip44
import io.novafoundation.nova.caip.slip44.endpoint.Slip44CoinApi
import io.novafoundation.nova.caip.slip44.endpoint.Slip44CoinRemote
import io.novafoundation.nova.runtime.ext.normalizeSymbol
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
interface Slip44CoinRepository {
suspend fun getCoinCode(chainAsset: Chain.Asset): Int?
}
internal class RealSlip44CoinRepository(
private val slip44Api: Slip44CoinApi,
private val slip44CoinsUrl: String
) : Slip44CoinRepository {
private var slip44Coins: Map<String, Slip44CoinRemote> = emptyMap()
private val mutex = Mutex()
override suspend fun getCoinCode(chainAsset: Chain.Asset): Int? = mutex.withLock {
if (slip44Coins.isEmpty()) {
slip44Coins = slip44Api.getSlip44Coins(slip44CoinsUrl)
.associateBy { it.symbol }
}
return slip44Coins[chainAsset.normalizeSymbol()]
?.index
?.toIntOrNull()
}
}
@@ -0,0 +1,10 @@
package io.novafoundation.nova.caip.slip44.endpoint
import retrofit2.http.GET
import retrofit2.http.Url
interface Slip44CoinApi {
@GET
suspend fun getSlip44Coins(@Url url: String): List<Slip44CoinRemote>
}
@@ -0,0 +1,6 @@
package io.novafoundation.nova.caip.slip44.endpoint
class Slip44CoinRemote(
val index: String,
val symbol: String
)
@@ -0,0 +1,116 @@
package io.novafoundation.nova.caip
import io.novafoundation.nova.caip.caip19.RealCaip19MatcherFactory
import io.novafoundation.nova.caip.caip19.RealCaip19Parser
import io.novafoundation.nova.caip.caip19.identifiers.Caip19Identifier
import io.novafoundation.nova.caip.caip2.RealCaip2MatcherFactory
import io.novafoundation.nova.caip.caip2.RealCaip2Parser
import io.novafoundation.nova.caip.slip44.Slip44CoinRepository
import io.novafoundation.nova.common.utils.requireValue
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnitRunner
@RunWith(MockitoJUnitRunner::class)
class Caip19MatcherTest {
private val caip19MatcherFactory = RealCaip19MatcherFactory(getSlip44CoinRepository(), RealCaip2MatcherFactory())
private val parser = RealCaip19Parser(RealCaip2Parser())
private val cointType = 1
private val substrateChainId = "0x0"
private val ethereumChainId = "eip155:1"
private val eip155ChainId = 1
@Mock
private lateinit var chain: Chain
@Mock
private lateinit var chainAsset: Chain.Asset
@Test
fun `polkadot slip44 should match`() = runBlocking {
mockChain(chainId = substrateChainId, isEthereumBased = false)
mockAsset(type = Chain.Asset.Type.Native)
val identifier = getIdentifier("polkadot:$substrateChainId/slip44:$cointType")
val matcher = caip19MatcherFactory.getCaip19Matcher(chain, chainAsset)
assertTrue(matcher.match(identifier))
}
@Test
fun `polkadot slip44 should not match`() = runBlocking {
mockChain(chainId = "eip155:1", isEthereumBased = true)
mockAsset(type = Chain.Asset.Type.EvmErc20("0x0"))
val identifier = getIdentifier("polkadot:${substrateChainId}/slip44:$cointType")
val matcher = caip19MatcherFactory.getCaip19Matcher(chain, chainAsset)
assertFalse(matcher.match(identifier))
}
@Test
fun `eip155 erc20 should match`() = runBlocking {
mockChain(chainId = ethereumChainId, isEthereumBased = true, addressPrefix = eip155ChainId)
mockAsset(type = Chain.Asset.Type.EvmErc20("0x0"))
val identifier = getIdentifier("$ethereumChainId/erc20:0x0")
val matcher = caip19MatcherFactory.getCaip19Matcher(chain, chainAsset)
assertTrue(matcher.match(identifier))
}
@Test
fun `substrate-chainId ethereum-based erc20 should match`() = runBlocking {
mockChain(chainId = substrateChainId, isEthereumBased = true, addressPrefix = eip155ChainId)
mockAsset(type = Chain.Asset.Type.EvmErc20("0x0"))
val identifier = getIdentifier("$ethereumChainId/erc20:0x0")
val matcher = caip19MatcherFactory.getCaip19Matcher(chain, chainAsset)
assertTrue(matcher.match(identifier))
}
@Test
fun `eip155 erc20 wrong coinType`() = runBlocking {
mockChain(chainId = ethereumChainId, isEthereumBased = true)
mockAsset(type = Chain.Asset.Type.EvmErc20("0x0"))
val identifier = getIdentifier("eip155:3/erc20:0x0")
val matcher = caip19MatcherFactory.getCaip19Matcher(chain, chainAsset)
assertFalse(matcher.match(identifier))
}
@Test
fun `eip155 erc20 should not match`() = runBlocking {
mockChain(chainId = substrateChainId, isEthereumBased = false)
mockAsset(type = Chain.Asset.Type.Native)
val identifier = getIdentifier("eip155:$cointType/erc20:0x0")
val matcher = caip19MatcherFactory.getCaip19Matcher(chain, chainAsset)
assertFalse(matcher.match(identifier))
}
private fun getSlip44CoinRepository(): Slip44CoinRepository {
return object : Slip44CoinRepository {
override suspend fun getCoinCode(chainAsset: Chain.Asset): Int {
return cointType
}
}
}
private fun getIdentifier(raw: String): Caip19Identifier {
return parser.parseCaip19(raw).requireValue()
}
private fun mockChain(chainId: String, isEthereumBased: Boolean, addressPrefix: Int? = null) {
`when`(chain.id).thenReturn(chainId)
`when`(chain.isEthereumBased).thenReturn(isEthereumBased)
addressPrefix?.let {
`when`(chain.addressPrefix).thenReturn(addressPrefix)
}
}
private fun mockAsset(type: Chain.Asset.Type) {
`when`(chainAsset.type).thenReturn(type)
}
}
@@ -0,0 +1,48 @@
package io.novafoundation.nova.caip
import io.novafoundation.nova.caip.caip19.RealCaip19Parser
import io.novafoundation.nova.caip.caip19.identifiers.AssetIdentifier
import io.novafoundation.nova.caip.caip19.identifiers.Caip19Identifier
import io.novafoundation.nova.caip.caip2.RealCaip2Parser
import io.novafoundation.nova.caip.caip2.identifier.Caip2Identifier
import io.novafoundation.nova.common.utils.requireValue
import org.junit.Assert.assertTrue
import org.junit.Test
class Caip19ParserTest {
private val caip2Parser = RealCaip2Parser()
private val caip19Parser = RealCaip19Parser(caip2Parser)
@Test
fun `test substrate chain namespace`() {
val identifier = getIdentifier("polkadot:0x0/slip44:10")
assertInstance<Caip2Identifier.Polkadot>(identifier.caip2Identifier)
}
@Test
fun `test ethereum chain namespace`() {
val identifier = getIdentifier("eip155:1/slip44:10")
assertInstance<Caip2Identifier.Eip155>(identifier.caip2Identifier)
}
@Test
fun `test slip44 asset namespace`() {
val identifier = getIdentifier("polkadot:0x0/slip44:10")
assertInstance<AssetIdentifier.Slip44>(identifier.assetIdentifier)
}
@Test
fun `test erc20 asset namespace`() {
val identifier = getIdentifier("polkadot:0x0/erc20:10")
assertInstance<AssetIdentifier.Erc20>(identifier.assetIdentifier)
}
private inline fun <reified T> assertInstance(value: Any?) {
assertTrue(value is T)
}
private fun getIdentifier(raw: String): Caip19Identifier {
return caip19Parser.parseCaip19(raw).requireValue()
}
}