mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-04-26 16:47:59 +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,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)
|
||||
+3
@@ -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
|
||||
}
|
||||
+11
@@ -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
|
||||
}
|
||||
}
|
||||
+13
@@ -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
|
||||
}
|
||||
}
|
||||
+10
@@ -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()
|
||||
}
|
||||
}
|
||||
+13
@@ -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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user