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,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
package io.novafoundation.nova.feature_xcm_impl.builder
|
||||
|
||||
import io.novafoundation.nova.common.address.AccountIdKey
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.BalanceOf
|
||||
import io.novafoundation.nova.feature_xcm_api.asset.MultiAsset
|
||||
import io.novafoundation.nova.feature_xcm_api.asset.MultiAssetFilter
|
||||
import io.novafoundation.nova.feature_xcm_api.asset.MultiAssetId
|
||||
import io.novafoundation.nova.feature_xcm_api.asset.MultiAssets
|
||||
import io.novafoundation.nova.feature_xcm_api.asset.withAmount
|
||||
import io.novafoundation.nova.feature_xcm_api.builder.XcmBuilder
|
||||
import io.novafoundation.nova.feature_xcm_api.builder.fees.MeasureXcmFees
|
||||
import io.novafoundation.nova.feature_xcm_api.builder.fees.PayFeesMode
|
||||
import io.novafoundation.nova.feature_xcm_api.message.VersionedXcmMessage
|
||||
import io.novafoundation.nova.feature_xcm_api.message.XcmInstruction
|
||||
import io.novafoundation.nova.feature_xcm_api.message.XcmMessage
|
||||
import io.novafoundation.nova.feature_xcm_api.message.asVersionedXcmMessage
|
||||
import io.novafoundation.nova.feature_xcm_api.message.asXcmMessage
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.AbsoluteMultiLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.AssetLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.ChainLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.multiAssetIdOn
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.toMultiLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.XcmVersion
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.versionedXcm
|
||||
import io.novafoundation.nova.feature_xcm_api.weight.WeightLimit
|
||||
|
||||
internal class RealXcmBuilder(
|
||||
initialLocation: ChainLocation,
|
||||
override val xcmVersion: XcmVersion,
|
||||
private val measureXcmFees: MeasureXcmFees,
|
||||
) : XcmBuilder {
|
||||
|
||||
override var currentLocation: ChainLocation = initialLocation
|
||||
|
||||
private val previousContexts: MutableList<PendingContextInstructions> = mutableListOf()
|
||||
private val currentLocationInstructions: MutableList<PendingInstruction> = mutableListOf()
|
||||
|
||||
override fun payFees(payFeesMode: PayFeesMode) {
|
||||
currentLocationInstructions.add(PendingInstruction.PayFees(payFeesMode))
|
||||
}
|
||||
|
||||
override fun withdrawAsset(assets: MultiAssets) {
|
||||
addRegularInstruction(XcmInstruction.WithdrawAsset(assets))
|
||||
}
|
||||
|
||||
override fun buyExecution(fees: MultiAsset, weightLimit: WeightLimit) {
|
||||
addRegularInstruction(XcmInstruction.BuyExecution(fees, weightLimit))
|
||||
}
|
||||
|
||||
override fun depositAsset(assets: MultiAssetFilter, beneficiary: AccountIdKey) {
|
||||
addRegularInstruction(XcmInstruction.DepositAsset(assets, beneficiary.toMultiLocation()))
|
||||
}
|
||||
|
||||
override fun transferReserveAsset(assets: MultiAssets, dest: ChainLocation) {
|
||||
performContextSwitch(dest) { forwardedMessage, forwardingFrom ->
|
||||
XcmInstruction.TransferReserveAsset(assets, dest.location.fromPointOfViewOf(forwardingFrom), forwardedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
override fun initiateReserveWithdraw(assets: MultiAssetFilter, reserve: ChainLocation) {
|
||||
performContextSwitch(reserve) { forwardedMessage, forwardingFrom ->
|
||||
XcmInstruction.InitiateReserveWithdraw(assets, reserve.location.fromPointOfViewOf(forwardingFrom), forwardedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
override fun depositReserveAsset(assets: MultiAssetFilter, dest: ChainLocation) {
|
||||
performContextSwitch(dest) { forwardedMessage, forwardingFrom ->
|
||||
XcmInstruction.DepositReserveAsset(assets, dest.location.fromPointOfViewOf(forwardingFrom), forwardedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
override fun initiateTeleport(assets: MultiAssetFilter, dest: ChainLocation) {
|
||||
performContextSwitch(dest) { forwardedMessage, forwardingFrom ->
|
||||
XcmInstruction.InitiateTeleport(assets, dest.location.fromPointOfViewOf(forwardingFrom), forwardedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun build(): VersionedXcmMessage {
|
||||
val lastMessage = createXcmMessage(currentLocationInstructions, currentLocation)
|
||||
|
||||
return previousContexts.foldRight(lastMessage) { context, forwardedMessage ->
|
||||
createXcmMessage(context, forwardedMessage)
|
||||
}.versionedXcm(xcmVersion)
|
||||
}
|
||||
|
||||
private fun addRegularInstruction(instruction: XcmInstruction) {
|
||||
currentLocationInstructions.add(PendingInstruction.Regular(instruction))
|
||||
}
|
||||
|
||||
private suspend fun createXcmMessage(
|
||||
pendingContextInstructions: PendingContextInstructions,
|
||||
forwardedMessage: XcmMessage
|
||||
): XcmMessage {
|
||||
val switchInstruction = pendingContextInstructions.contextSwitch(forwardedMessage, pendingContextInstructions.chainLocation.location)
|
||||
val allInstructions = pendingContextInstructions.instructions + PendingInstruction.Regular(switchInstruction)
|
||||
|
||||
return createXcmMessage(allInstructions, pendingContextInstructions.chainLocation)
|
||||
}
|
||||
|
||||
private suspend fun createXcmMessage(
|
||||
pendingInstructions: List<PendingInstruction>,
|
||||
chainLocation: ChainLocation,
|
||||
): XcmMessage {
|
||||
return pendingInstructions.map { pendingInstruction ->
|
||||
pendingInstruction.constructSubmissionInstruction(pendingInstructions, chainLocation)
|
||||
}.asXcmMessage()
|
||||
}
|
||||
|
||||
private suspend fun PendingInstruction.constructSubmissionInstruction(
|
||||
allInstructions: List<PendingInstruction>,
|
||||
chainLocation: ChainLocation,
|
||||
): XcmInstruction {
|
||||
return when (this) {
|
||||
is PendingInstruction.Regular -> instruction
|
||||
is PendingInstruction.PayFees -> constructSubmissionInstruction(allInstructions, chainLocation)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun PendingInstruction.PayFees.constructSubmissionInstruction(
|
||||
allInstructions: List<PendingInstruction>,
|
||||
chainLocation: ChainLocation,
|
||||
): XcmInstruction.PayFees {
|
||||
val fees = when (val mode = mode) {
|
||||
is PayFeesMode.Exact -> mode.fee
|
||||
is PayFeesMode.Measured -> measureFees(allInstructions, mode.feeAssetId, chainLocation)
|
||||
}
|
||||
|
||||
return XcmInstruction.PayFees(fees)
|
||||
}
|
||||
|
||||
private suspend fun measureFees(
|
||||
allInstructions: List<PendingInstruction>,
|
||||
feeAssetIdLocation: AssetLocation,
|
||||
chainLocation: ChainLocation,
|
||||
): MultiAsset {
|
||||
val feeAssetId = feeAssetIdLocation.multiAssetIdOn(chainLocation)
|
||||
|
||||
val messageForEstimation = allInstructions.map { pendingInstruction ->
|
||||
pendingInstruction.constructEstimationInstruction(feeAssetId)
|
||||
}.asVersionedXcmMessage(xcmVersion)
|
||||
|
||||
require(chainLocation.chainId == feeAssetIdLocation.assetId.chainId) {
|
||||
"""
|
||||
Supplied fee asset does not belong to the current chain.
|
||||
Expected: ${chainLocation.chainId}
|
||||
Got: ${feeAssetIdLocation.assetId.chainId} (Asset id: ${feeAssetIdLocation.assetId.assetId})
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
val measuredFees = measureXcmFees.measureFees(messageForEstimation, feeAssetIdLocation, chainLocation)
|
||||
|
||||
return feeAssetId.withAmount(measuredFees)
|
||||
}
|
||||
|
||||
private fun PendingInstruction.constructEstimationInstruction(feeAssetId: MultiAssetId): XcmInstruction {
|
||||
return when (this) {
|
||||
is PendingInstruction.Regular -> instruction
|
||||
is PendingInstruction.PayFees -> constructEstimationInstruction(feeAssetId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun PendingInstruction.PayFees.constructEstimationInstruction(feeAssetId: MultiAssetId): XcmInstruction.PayFees {
|
||||
val fees = when (val mode = mode) {
|
||||
is PayFeesMode.Exact -> mode.fee
|
||||
is PayFeesMode.Measured -> feeAssetId.withAmount(BalanceOf.ONE) // Use fake amount in pay fees instruction for fee estimation
|
||||
}
|
||||
|
||||
return XcmInstruction.PayFees(fees)
|
||||
}
|
||||
|
||||
private fun performContextSwitch(newLocation: ChainLocation, switch: PendingContextSwitch) {
|
||||
val instructionsInCurrentContext = currentLocationInstructions.toList()
|
||||
val pendingContextInstructions = PendingContextInstructions(instructionsInCurrentContext, currentLocation, switch)
|
||||
|
||||
previousContexts.add(pendingContextInstructions)
|
||||
currentLocationInstructions.clear()
|
||||
currentLocation = newLocation
|
||||
}
|
||||
|
||||
private class PendingContextInstructions(
|
||||
val instructions: List<PendingInstruction>,
|
||||
val chainLocation: ChainLocation,
|
||||
val contextSwitch: PendingContextSwitch
|
||||
)
|
||||
|
||||
private sealed class PendingInstruction {
|
||||
|
||||
class Regular(val instruction: XcmInstruction) : PendingInstruction()
|
||||
|
||||
class PayFees(val mode: PayFeesMode) : PendingInstruction()
|
||||
}
|
||||
}
|
||||
|
||||
private typealias PendingContextSwitch = (forwardedXcm: XcmMessage, forwardingFrom: AbsoluteMultiLocation) -> XcmInstruction
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package io.novafoundation.nova.feature_xcm_impl.builder
|
||||
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.feature_xcm_api.builder.XcmBuilder
|
||||
import io.novafoundation.nova.feature_xcm_api.builder.fees.MeasureXcmFees
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.ChainLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.XcmVersion
|
||||
import javax.inject.Inject
|
||||
|
||||
@FeatureScope
|
||||
internal class RealXcmBuilderFactory @Inject constructor() : XcmBuilder.Factory {
|
||||
|
||||
override fun create(
|
||||
initial: ChainLocation,
|
||||
xcmVersion: XcmVersion,
|
||||
measureXcmFees: MeasureXcmFees
|
||||
): XcmBuilder {
|
||||
return RealXcmBuilder(initial, xcmVersion, measureXcmFees)
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package io.novafoundation.nova.feature_xcm_impl.converter
|
||||
|
||||
import io.novafoundation.nova.common.utils.tryFindNonNull
|
||||
import io.novafoundation.nova.feature_xcm_api.converter.MultiLocationConverter
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.RelativeMultiLocation
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
|
||||
internal class CompoundMultiLocationConverter(
|
||||
private vararg val delegates: MultiLocationConverter
|
||||
) : MultiLocationConverter {
|
||||
|
||||
override suspend fun toMultiLocation(chainAsset: Chain.Asset): RelativeMultiLocation? {
|
||||
return delegates.tryFindNonNull { it.toMultiLocation(chainAsset) }
|
||||
}
|
||||
|
||||
override suspend fun toChainAsset(multiLocation: RelativeMultiLocation): Chain.Asset? {
|
||||
return delegates.tryFindNonNull { it.toChainAsset(multiLocation) }
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package io.novafoundation.nova.feature_xcm_impl.converter
|
||||
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.RelativeMultiLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.bindMultiLocation
|
||||
import io.novafoundation.nova.common.utils.lazyAsync
|
||||
import io.novafoundation.nova.common.utils.toHexUntypedOrNull
|
||||
import io.novafoundation.nova.feature_xcm_api.converter.MultiLocationConverter
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.XcmVersion
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.detector.XcmVersionDetector
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.orDefault
|
||||
import io.novafoundation.nova.runtime.ext.requireStatemine
|
||||
import io.novafoundation.nova.runtime.ext.statemineOrNull
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.StatemineAssetId
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.asScaleEncodedOrThrow
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.isScaleEncoded
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.prepareIdForEncoding
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.statemineAssetIdScaleType
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.RuntimeType
|
||||
|
||||
private typealias ScaleEncodedMultiLocation = String
|
||||
private typealias ForeignAssetsAssetId = ScaleEncodedMultiLocation
|
||||
private typealias ForeignAssetsMappingKey = ForeignAssetsAssetId // we only allow one pallet so no need to include pallet name into key
|
||||
private typealias ForeignAssetsMapping = Map<ForeignAssetsMappingKey, Chain.Asset>
|
||||
|
||||
private const val FOREIGN_ASSETS_PALLET_NAME = "ForeignAssets"
|
||||
|
||||
internal class ForeignAssetsLocationConverter(
|
||||
private val chain: Chain,
|
||||
private val runtime: RuntimeSource,
|
||||
private val xcmVersionDetector: XcmVersionDetector,
|
||||
) : MultiLocationConverter {
|
||||
|
||||
private val assetIdToAssetMapping by lazy { constructAssetIdToAssetMapping() }
|
||||
|
||||
private var assetIdEncodingContext = lazyAsync { constructAssetIdEncodingContext() }
|
||||
|
||||
override suspend fun toMultiLocation(chainAsset: Chain.Asset): RelativeMultiLocation? {
|
||||
if (chainAsset.chainId != chain.id) return null
|
||||
|
||||
return chainAsset.extractMultiLocation()
|
||||
}
|
||||
|
||||
override suspend fun toChainAsset(multiLocation: RelativeMultiLocation): Chain.Asset? {
|
||||
val (xcmVersion, assetIdType) = assetIdEncodingContext.get() ?: return null
|
||||
|
||||
val encodableInstance = multiLocation.toEncodableInstance(xcmVersion)
|
||||
val multiLocationHex = assetIdType.toHexUntypedOrNull(runtime.getRuntime(), encodableInstance) ?: return null
|
||||
|
||||
return assetIdToAssetMapping[multiLocationHex]
|
||||
}
|
||||
|
||||
private fun constructAssetIdToAssetMapping(): ForeignAssetsMapping {
|
||||
return chain.assets
|
||||
.filter {
|
||||
val type = it.type
|
||||
type is Chain.Asset.Type.Statemine &&
|
||||
type.palletName == FOREIGN_ASSETS_PALLET_NAME &&
|
||||
type.id is StatemineAssetId.ScaleEncoded
|
||||
}
|
||||
.associateBy { statemineAsset ->
|
||||
val assetsType = statemineAsset.requireStatemine()
|
||||
assetsType.id.asScaleEncodedOrThrow()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun Chain.Asset.extractMultiLocation(): RelativeMultiLocation? {
|
||||
val assetsType = statemineOrNull() ?: return null
|
||||
if (!assetsType.id.isScaleEncoded()) return null
|
||||
|
||||
return runCatching {
|
||||
val encodableMultiLocation = assetsType.prepareIdForEncoding(runtime.getRuntime())
|
||||
bindMultiLocation(encodableMultiLocation)
|
||||
}.getOrNull()
|
||||
}
|
||||
|
||||
private suspend fun constructAssetIdEncodingContext(): AssetIdEncodingContext? {
|
||||
val assetIdType = statemineAssetIdScaleType(
|
||||
runtime.getRuntime(),
|
||||
FOREIGN_ASSETS_PALLET_NAME
|
||||
) ?: return null
|
||||
val xcmVersion = xcmVersionDetector.detectMultiLocationVersion(chain.id, assetIdType).orDefault()
|
||||
|
||||
return AssetIdEncodingContext(xcmVersion, assetIdType)
|
||||
}
|
||||
|
||||
private data class AssetIdEncodingContext(
|
||||
val xcmVersion: XcmVersion,
|
||||
val assetIdType: RuntimeType<*, *>
|
||||
)
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
package io.novafoundation.nova.feature_xcm_impl.converter
|
||||
|
||||
import io.novafoundation.nova.common.utils.PalletName
|
||||
import io.novafoundation.nova.feature_xcm_api.converter.MultiLocationConverter
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.Junctions
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.MultiLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.RelativeMultiLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.junctions
|
||||
import io.novafoundation.nova.runtime.ext.palletNameOrDefault
|
||||
import io.novafoundation.nova.runtime.ext.requireStatemine
|
||||
import io.novafoundation.nova.runtime.ext.statemineOrNull
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.StatemineAssetId
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.asNumberOrNull
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.asNumberOrThrow
|
||||
import io.novasama.substrate_sdk_android.runtime.metadata.moduleOrNull
|
||||
import java.math.BigInteger
|
||||
|
||||
private typealias LocalAssetsAssetId = BigInteger
|
||||
private typealias LocalAssetsMappingKey = Pair<PalletName, LocalAssetsAssetId>
|
||||
private typealias LocalAssetsMapping = Map<LocalAssetsMappingKey, Chain.Asset>
|
||||
|
||||
class LocalAssetsLocationConverter(
|
||||
private val chain: Chain,
|
||||
private val runtimeSource: RuntimeSource
|
||||
) : MultiLocationConverter {
|
||||
|
||||
private val assetIdToAssetMapping by lazy { constructAssetIdToAssetMapping() }
|
||||
|
||||
override suspend fun toMultiLocation(chainAsset: Chain.Asset): RelativeMultiLocation? {
|
||||
if (chainAsset.chainId != chain.id) return null
|
||||
|
||||
val assetsType = chainAsset.statemineOrNull() ?: return null
|
||||
// LocalAssets converter only supports number ids to use as GeneralIndex
|
||||
val index = assetsType.id.asNumberOrNull() ?: return null
|
||||
val pallet = runtimeSource.getRuntime().metadata.moduleOrNull(assetsType.palletNameOrDefault()) ?: return null
|
||||
|
||||
return RelativeMultiLocation(
|
||||
parents = 0, // For Local Assets chain serves as a reserve
|
||||
interior = Junctions(
|
||||
MultiLocation.Junction.PalletInstance(pallet.index),
|
||||
MultiLocation.Junction.GeneralIndex(index)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun toChainAsset(multiLocation: RelativeMultiLocation): Chain.Asset? {
|
||||
// We only consider local reserves for LocalAssets
|
||||
if (multiLocation.parents > 0) return null
|
||||
|
||||
val junctions = multiLocation.junctions
|
||||
if (junctions.size != 2) return null
|
||||
|
||||
val (maybePalletInstance, maybeGeneralIndex) = junctions
|
||||
if (maybePalletInstance !is MultiLocation.Junction.PalletInstance || maybeGeneralIndex !is MultiLocation.Junction.GeneralIndex) return null
|
||||
|
||||
val pallet = runtimeSource.getRuntime().metadata.moduleOrNull(maybePalletInstance.index.toInt()) ?: return null
|
||||
val assetId = maybeGeneralIndex.index
|
||||
|
||||
return assetIdToAssetMapping[pallet.name to assetId]
|
||||
}
|
||||
|
||||
private fun constructAssetIdToAssetMapping(): LocalAssetsMapping {
|
||||
return chain.assets
|
||||
.filter {
|
||||
val type = it.type
|
||||
type is Chain.Asset.Type.Statemine && type.id is StatemineAssetId.Number
|
||||
}
|
||||
.associateBy { statemineAsset ->
|
||||
val assetsType = statemineAsset.requireStatemine()
|
||||
val palletName = assetsType.palletNameOrDefault()
|
||||
|
||||
palletName to assetsType.id.asNumberOrThrow()
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package io.novafoundation.nova.feature_xcm_impl.converter
|
||||
|
||||
import io.novafoundation.nova.feature_xcm_api.converter.MultiLocationConverter
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.MultiLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.RelativeMultiLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.isHere
|
||||
import io.novafoundation.nova.runtime.ext.isUtilityAsset
|
||||
import io.novafoundation.nova.runtime.ext.relaychainAsNative
|
||||
import io.novafoundation.nova.runtime.ext.utilityAsset
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
|
||||
internal class NativeAssetLocationConverter(
|
||||
private val chain: Chain,
|
||||
) : MultiLocationConverter {
|
||||
|
||||
override suspend fun toMultiLocation(chainAsset: Chain.Asset): RelativeMultiLocation? {
|
||||
return if (chainAsset.chainId == chain.id && chainAsset.isUtilityAsset) {
|
||||
RelativeMultiLocation(
|
||||
parents = chain.expectedParentsInNativeInterior(),
|
||||
interior = MultiLocation.Interior.Here
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun toChainAsset(multiLocation: RelativeMultiLocation): Chain.Asset? {
|
||||
return if (chain.isNativeMultiLocation(multiLocation)) {
|
||||
chain.utilityAsset
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun Chain.expectedParentsInNativeInterior(): Int {
|
||||
return if (additional.relaychainAsNative()) 1 else 0
|
||||
}
|
||||
|
||||
private fun Chain.isNativeMultiLocation(multiLocation: RelativeMultiLocation): Boolean {
|
||||
return multiLocation.interior.isHere() && multiLocation.parents == expectedParentsInNativeInterior()
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package io.novafoundation.nova.feature_xcm_impl.converter
|
||||
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.feature_xcm_api.converter.MultiLocationConverter
|
||||
import io.novafoundation.nova.feature_xcm_api.converter.MultiLocationConverterFactory
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.detector.XcmVersionDetector
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.getRuntime
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.async
|
||||
import javax.inject.Inject
|
||||
|
||||
@FeatureScope
|
||||
class RealMultiLocationConverterFactory @Inject constructor(
|
||||
private val chainRegistry: ChainRegistry,
|
||||
private val xcmVersionDetector: XcmVersionDetector,
|
||||
) : MultiLocationConverterFactory {
|
||||
|
||||
override fun defaultAsync(chain: Chain, coroutineScope: CoroutineScope): MultiLocationConverter {
|
||||
val runtimeAsync = coroutineScope.async { chainRegistry.getRuntime(chain.id) }
|
||||
val runtimeSource = RuntimeSource.Async(runtimeAsync)
|
||||
|
||||
return CompoundMultiLocationConverter(
|
||||
NativeAssetLocationConverter(chain),
|
||||
LocalAssetsLocationConverter(chain, runtimeSource),
|
||||
ForeignAssetsLocationConverter(chain, runtimeSource, xcmVersionDetector)
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun defaultSync(chain: Chain): MultiLocationConverter {
|
||||
val runtimeAsync = chainRegistry.getRuntime(chain.id)
|
||||
val runtimeSource = RuntimeSource.Sync(runtimeAsync)
|
||||
|
||||
return CompoundMultiLocationConverter(
|
||||
NativeAssetLocationConverter(chain),
|
||||
LocalAssetsLocationConverter(chain, runtimeSource),
|
||||
ForeignAssetsLocationConverter(chain, runtimeSource, xcmVersionDetector)
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun resolveLocalAssets(chain: Chain): MultiLocationConverter {
|
||||
val runtime = chainRegistry.getRuntime(chain.id)
|
||||
|
||||
return CompoundMultiLocationConverter(
|
||||
NativeAssetLocationConverter(chain),
|
||||
LocalAssetsLocationConverter(
|
||||
chain,
|
||||
RuntimeSource.Sync(runtime)
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package io.novafoundation.nova.feature_xcm_impl.converter
|
||||
|
||||
import io.novasama.substrate_sdk_android.runtime.RuntimeSnapshot
|
||||
import kotlinx.coroutines.Deferred
|
||||
|
||||
sealed interface RuntimeSource {
|
||||
|
||||
suspend fun getRuntime(): RuntimeSnapshot
|
||||
|
||||
class Sync(private val runtimeSnapshot: RuntimeSnapshot) : RuntimeSource {
|
||||
|
||||
override suspend fun getRuntime(): RuntimeSnapshot {
|
||||
return runtimeSnapshot
|
||||
}
|
||||
}
|
||||
|
||||
class Async(private val runtimeSnapshotAsync: Deferred<RuntimeSnapshot>) : RuntimeSource {
|
||||
|
||||
override suspend fun getRuntime(): RuntimeSnapshot {
|
||||
return runtimeSnapshotAsync.await()
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package io.novafoundation.nova.feature_xcm_impl.converter.chain
|
||||
|
||||
import io.novafoundation.nova.feature_xcm_api.converter.chain.ChainMultiLocationConverter
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.MultiLocation.Junction.ParachainId
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.RelativeMultiLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.junctions
|
||||
import io.novafoundation.nova.runtime.ext.Geneses
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import io.novafoundation.nova.runtime.multiNetwork.getChainOrNull
|
||||
import java.math.BigInteger
|
||||
|
||||
internal class ChildParachainLocationConverter(
|
||||
private val relayChain: Chain,
|
||||
private val chainRegistry: ChainRegistry
|
||||
) : ChainMultiLocationConverter {
|
||||
|
||||
private val parachainIdToChainIdByRelay = mapOf(
|
||||
Chain.Geneses.POLKADOT to mapOf(
|
||||
1000 to Chain.Geneses.POLKADOT_ASSET_HUB
|
||||
)
|
||||
)
|
||||
|
||||
override suspend fun toChain(multiLocation: RelativeMultiLocation): Chain? {
|
||||
// This is not a child parachain from relay point
|
||||
if (multiLocation.parents != 0) return null
|
||||
|
||||
val junctions = multiLocation.junctions
|
||||
// Child parachain has only 1 ParachainId junction
|
||||
if (junctions.size != 1) return null
|
||||
val parachainId = junctions.single() as? ParachainId ?: return null
|
||||
|
||||
val parachainChainId = getParachainChainId(parachainId.id) ?: return null
|
||||
|
||||
return chainRegistry.getChainOrNull(parachainChainId)
|
||||
}
|
||||
|
||||
private fun getParachainChainId(parachainId: BigInteger): ChainId? {
|
||||
return parachainIdToChainIdByRelay[relayChain.id]?.get(parachainId.toInt())
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package io.novafoundation.nova.feature_xcm_impl.converter.chain
|
||||
|
||||
import io.novafoundation.nova.common.utils.tryFindNonNull
|
||||
import io.novafoundation.nova.feature_xcm_api.converter.chain.ChainMultiLocationConverter
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.RelativeMultiLocation
|
||||
|
||||
internal class CompoundChainLocationConverter(
|
||||
private vararg val delegates: ChainMultiLocationConverter
|
||||
) : ChainMultiLocationConverter {
|
||||
|
||||
override suspend fun toChain(multiLocation: RelativeMultiLocation): Chain? {
|
||||
return delegates.tryFindNonNull { it.toChain(multiLocation) }
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package io.novafoundation.nova.feature_xcm_impl.converter.chain
|
||||
|
||||
import io.novafoundation.nova.feature_xcm_api.converter.chain.ChainMultiLocationConverter
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.RelativeMultiLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.isHere
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
|
||||
internal class LocalChainMultiLocationConverter(
|
||||
val chain: Chain
|
||||
) : ChainMultiLocationConverter {
|
||||
|
||||
override suspend fun toChain(multiLocation: RelativeMultiLocation): Chain? {
|
||||
return chain.takeIf { multiLocation.isHere() }
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package io.novafoundation.nova.feature_xcm_impl.converter.chain
|
||||
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.feature_xcm_api.converter.chain.ChainMultiLocationConverter
|
||||
import io.novafoundation.nova.feature_xcm_api.converter.chain.ChainMultiLocationConverterFactory
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import javax.inject.Inject
|
||||
|
||||
@FeatureScope
|
||||
class RealChainMultiLocationConverterFactory @Inject constructor(
|
||||
private val chainRegistry: ChainRegistry
|
||||
) : ChainMultiLocationConverterFactory {
|
||||
|
||||
override fun resolveSelfAndChildrenParachains(self: Chain): ChainMultiLocationConverter {
|
||||
return CompoundChainLocationConverter(
|
||||
LocalChainMultiLocationConverter(self),
|
||||
ChildParachainLocationConverter(self, chainRegistry)
|
||||
)
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package io.novafoundation.nova.feature_xcm_impl.di
|
||||
|
||||
import dagger.Component
|
||||
import io.novafoundation.nova.common.di.CommonApi
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.feature_xcm_api.di.XcmFeatureApi
|
||||
import io.novafoundation.nova.runtime.di.RuntimeApi
|
||||
|
||||
@Component(
|
||||
dependencies = [
|
||||
XcmFeatureDependencies::class,
|
||||
],
|
||||
modules = [
|
||||
XcmFeatureModule::class
|
||||
]
|
||||
)
|
||||
@FeatureScope
|
||||
interface XcmFeatureComponent : XcmFeatureApi {
|
||||
|
||||
@Component.Factory
|
||||
interface Factory {
|
||||
|
||||
fun create(
|
||||
deps: XcmFeatureDependencies
|
||||
): XcmFeatureComponent
|
||||
}
|
||||
|
||||
@Component(
|
||||
dependencies = [
|
||||
CommonApi::class,
|
||||
RuntimeApi::class,
|
||||
]
|
||||
)
|
||||
interface XcmFeatureDependenciesComponent : XcmFeatureDependencies
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package io.novafoundation.nova.feature_xcm_impl.di
|
||||
|
||||
import io.novafoundation.nova.runtime.call.MultiChainRuntimeCallsApi
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
|
||||
interface XcmFeatureDependencies {
|
||||
|
||||
val chainRegistry: ChainRegistry
|
||||
|
||||
val runtimeCallApi: MultiChainRuntimeCallsApi
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package io.novafoundation.nova.feature_xcm_impl.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 XcmFeatureHolder @Inject constructor(
|
||||
featureContainer: FeatureContainer,
|
||||
) : FeatureApiHolder(featureContainer) {
|
||||
|
||||
override fun initializeDependencies(): Any {
|
||||
val xcmFeatureDependencies = DaggerXcmFeatureComponent_XcmFeatureDependenciesComponent.builder()
|
||||
.commonApi(commonApi())
|
||||
.runtimeApi(getFeature(RuntimeApi::class.java))
|
||||
.build()
|
||||
|
||||
return DaggerXcmFeatureComponent.factory()
|
||||
.create(
|
||||
deps = xcmFeatureDependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package io.novafoundation.nova.feature_xcm_impl.di
|
||||
|
||||
import dagger.Module
|
||||
import io.novafoundation.nova.feature_xcm_impl.di.modules.BindsModule
|
||||
|
||||
@Module(
|
||||
includes = [
|
||||
BindsModule::class
|
||||
]
|
||||
)
|
||||
class XcmFeatureModule
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package io.novafoundation.nova.feature_xcm_impl.di.modules
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import io.novafoundation.nova.feature_xcm_api.builder.XcmBuilder
|
||||
import io.novafoundation.nova.feature_xcm_api.converter.MultiLocationConverterFactory
|
||||
import io.novafoundation.nova.feature_xcm_api.converter.chain.ChainMultiLocationConverterFactory
|
||||
import io.novafoundation.nova.feature_xcm_api.runtimeApi.dryRun.DryRunApi
|
||||
import io.novafoundation.nova.feature_xcm_api.runtimeApi.xcmPayment.XcmPaymentApi
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.detector.XcmVersionDetector
|
||||
import io.novafoundation.nova.feature_xcm_impl.builder.RealXcmBuilderFactory
|
||||
import io.novafoundation.nova.feature_xcm_impl.converter.RealMultiLocationConverterFactory
|
||||
import io.novafoundation.nova.feature_xcm_impl.converter.chain.RealChainMultiLocationConverterFactory
|
||||
import io.novafoundation.nova.feature_xcm_impl.runtimeApi.dryRun.RealDryRunApi
|
||||
import io.novafoundation.nova.feature_xcm_impl.runtimeApi.xcmPayment.RealXcmPaymentApi
|
||||
import io.novafoundation.nova.feature_xcm_impl.versions.detector.RealXcmVersionDetector
|
||||
|
||||
@Module
|
||||
internal interface BindsModule {
|
||||
|
||||
@Binds
|
||||
fun bindXcmVersionDetector(real: RealXcmVersionDetector): XcmVersionDetector
|
||||
|
||||
@Binds
|
||||
fun bindChainMultiLocationConverterFactory(real: RealChainMultiLocationConverterFactory): ChainMultiLocationConverterFactory
|
||||
|
||||
@Binds
|
||||
fun bindAssetMultiLocationConverterFactory(real: RealMultiLocationConverterFactory): MultiLocationConverterFactory
|
||||
|
||||
@Binds
|
||||
fun bindDryRunApi(real: RealDryRunApi): DryRunApi
|
||||
|
||||
@Binds
|
||||
fun bindXcmPaymentApi(real: RealXcmPaymentApi): XcmPaymentApi
|
||||
|
||||
@Binds
|
||||
fun bindXcmBuilderFactory(real: RealXcmBuilderFactory): XcmBuilder.Factory
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
package io.novafoundation.nova.feature_xcm_impl.runtimeApi.dryRun
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.ScaleResult
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.common.utils.provideContext
|
||||
import io.novafoundation.nova.feature_xcm_api.message.VersionedRawXcmMessage
|
||||
import io.novafoundation.nova.feature_xcm_api.runtimeApi.dryRun.DryRunApi
|
||||
import io.novafoundation.nova.feature_xcm_api.runtimeApi.dryRun.model.CallDryRunEffects
|
||||
import io.novafoundation.nova.feature_xcm_api.runtimeApi.dryRun.model.DryRunEffectsResultErr
|
||||
import io.novafoundation.nova.feature_xcm_api.runtimeApi.dryRun.model.OriginCaller
|
||||
import io.novafoundation.nova.feature_xcm_api.runtimeApi.dryRun.model.XcmDryRunEffects
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.VersionedXcmLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.XcmVersion
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.toEncodableInstance
|
||||
import io.novafoundation.nova.runtime.call.MultiChainRuntimeCallsApi
|
||||
import io.novafoundation.nova.runtime.call.RuntimeCallsApi
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericCall
|
||||
import javax.inject.Inject
|
||||
|
||||
@FeatureScope
|
||||
class RealDryRunApi @Inject constructor(
|
||||
private val multiChainRuntimeCallsApi: MultiChainRuntimeCallsApi
|
||||
) : DryRunApi {
|
||||
|
||||
override suspend fun dryRunXcm(
|
||||
originLocation: VersionedXcmLocation,
|
||||
xcm: VersionedRawXcmMessage,
|
||||
chainId: ChainId
|
||||
): Result<ScaleResult<XcmDryRunEffects, DryRunEffectsResultErr>> {
|
||||
return multiChainRuntimeCallsApi.forChain(chainId).dryRunXcm(xcm, originLocation)
|
||||
}
|
||||
|
||||
override suspend fun dryRunCall(
|
||||
originCaller: OriginCaller,
|
||||
call: GenericCall.Instance,
|
||||
xcmResultsVersion: XcmVersion,
|
||||
chainId: ChainId
|
||||
): Result<ScaleResult<CallDryRunEffects, DryRunEffectsResultErr>> {
|
||||
return multiChainRuntimeCallsApi.forChain(chainId).dryRunCall(originCaller, call, xcmResultsVersion)
|
||||
}
|
||||
|
||||
private suspend fun RuntimeCallsApi.dryRunXcm(
|
||||
xcm: VersionedRawXcmMessage,
|
||||
origin: VersionedXcmLocation,
|
||||
): Result<ScaleResult<XcmDryRunEffects, DryRunEffectsResultErr>> {
|
||||
return runCatching {
|
||||
call(
|
||||
section = "DryRunApi",
|
||||
method = "dry_run_xcm",
|
||||
arguments = mapOf(
|
||||
"origin_location" to origin.toEncodableInstance(),
|
||||
"xcm" to xcm.toEncodableInstance()
|
||||
),
|
||||
returnBinding = {
|
||||
runtime.provideContext {
|
||||
ScaleResult.bind(
|
||||
dynamicInstance = it,
|
||||
bindOk = { XcmDryRunEffects.bind(it) },
|
||||
bindError = DryRunEffectsResultErr::bind
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun RuntimeCallsApi.dryRunCall(
|
||||
originCaller: OriginCaller,
|
||||
call: GenericCall.Instance,
|
||||
xcmResultsVersion: XcmVersion,
|
||||
): Result<ScaleResult<CallDryRunEffects, DryRunEffectsResultErr>> {
|
||||
return runCatching {
|
||||
call(
|
||||
section = "DryRunApi",
|
||||
method = "dry_run_call",
|
||||
arguments = mapOf(
|
||||
"origin" to originCaller.toEncodableInstance(),
|
||||
"call" to call,
|
||||
"result_xcms_version" to xcmResultsVersion.version.toBigInteger()
|
||||
),
|
||||
returnBinding = {
|
||||
runtime.provideContext {
|
||||
ScaleResult.bind(
|
||||
dynamicInstance = it,
|
||||
bindOk = { CallDryRunEffects.bind(it) },
|
||||
bindError = DryRunEffectsResultErr::bind
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package io.novafoundation.nova.feature_xcm_impl.runtimeApi.xcmPayment
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.ScaleResult
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.WeightV2
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.bindWeightV2
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.feature_xcm_api.message.VersionedXcmMessage
|
||||
import io.novafoundation.nova.feature_xcm_api.runtimeApi.xcmPayment.XcmPaymentApi
|
||||
import io.novafoundation.nova.feature_xcm_api.runtimeApi.xcmPayment.model.QueryXcmWeightErr
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.toEncodableInstance
|
||||
import io.novafoundation.nova.runtime.call.MultiChainRuntimeCallsApi
|
||||
import io.novafoundation.nova.runtime.call.RuntimeCallsApi
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import javax.inject.Inject
|
||||
|
||||
@FeatureScope
|
||||
class RealXcmPaymentApi @Inject constructor(
|
||||
private val multiChainRuntimeCallsApi: MultiChainRuntimeCallsApi,
|
||||
) : XcmPaymentApi {
|
||||
|
||||
override suspend fun queryXcmWeight(
|
||||
chainId: ChainId,
|
||||
xcm: VersionedXcmMessage
|
||||
): Result<ScaleResult<WeightV2, QueryXcmWeightErr>> {
|
||||
return multiChainRuntimeCallsApi.forChain(chainId).queryXcmWeight(xcm)
|
||||
}
|
||||
|
||||
override suspend fun isSupported(chainId: ChainId): Boolean {
|
||||
return multiChainRuntimeCallsApi.isSupported(chainId, "XcmPaymentApi", "query_xcm_weight")
|
||||
}
|
||||
|
||||
private suspend fun RuntimeCallsApi.queryXcmWeight(
|
||||
xcm: VersionedXcmMessage,
|
||||
): Result<ScaleResult<WeightV2, QueryXcmWeightErr>> {
|
||||
return runCatching {
|
||||
call(
|
||||
section = "XcmPaymentApi",
|
||||
method = "query_xcm_weight",
|
||||
arguments = mapOf(
|
||||
"message" to xcm.toEncodableInstance()
|
||||
),
|
||||
returnBinding = {
|
||||
ScaleResult.bind(
|
||||
dynamicInstance = it,
|
||||
bindOk = ::bindWeightV2,
|
||||
bindError = QueryXcmWeightErr::bind
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
package io.novafoundation.nova.feature_xcm_impl.versions.detector
|
||||
|
||||
import android.util.Log
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.common.utils.enumValueOfOrNull
|
||||
import io.novafoundation.nova.common.utils.xcmPalletName
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.XcmVersion
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.detector.XcmVersionDetector
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import io.novafoundation.nova.runtime.multiNetwork.withRuntime
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.RuntimeType
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.DictEnum
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.skipAliases
|
||||
import io.novasama.substrate_sdk_android.runtime.metadata.RuntimeMetadata
|
||||
import io.novasama.substrate_sdk_android.runtime.metadata.callOrNull
|
||||
import io.novasama.substrate_sdk_android.runtime.metadata.module.MetadataFunction
|
||||
import io.novasama.substrate_sdk_android.runtime.metadata.moduleOrNull
|
||||
import javax.inject.Inject
|
||||
|
||||
@FeatureScope
|
||||
class RealXcmVersionDetector @Inject constructor(
|
||||
private val chainRegistry: ChainRegistry
|
||||
) : XcmVersionDetector {
|
||||
|
||||
override suspend fun lowestPresentMultiLocationVersion(chainId: ChainId): XcmVersion? {
|
||||
return lowestPresentXcmTypeVersionFromCallArgument(
|
||||
chainId = chainId,
|
||||
getCall = { it.moduleOrNull(it.xcmPalletName())?.callOrNull("reserve_transfer_assets") },
|
||||
argumentName = "dest"
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun lowestPresentMultiAssetsVersion(chainId: ChainId): XcmVersion? {
|
||||
return lowestPresentXcmTypeVersionFromCallArgument(
|
||||
chainId = chainId,
|
||||
getCall = { it.moduleOrNull(it.xcmPalletName())?.callOrNull("reserve_transfer_assets") },
|
||||
argumentName = "assets"
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun lowestPresentMultiAssetIdVersion(chainId: ChainId): XcmVersion? {
|
||||
return lowestPresentXcmTypeVersionFromCallArgument(
|
||||
chainId = chainId,
|
||||
getCall = { it.moduleOrNull(it.xcmPalletName())?.callOrNull("transfer_assets_using_type_and_then") },
|
||||
argumentName = "remote_fees_id"
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun lowestPresentMultiAssetVersion(chainId: ChainId): XcmVersion? {
|
||||
return lowestPresentMultiAssetsVersion(chainId)
|
||||
}
|
||||
|
||||
override suspend fun detectMultiLocationVersion(chainId: ChainId, multiLocationType: RuntimeType<*, *>?): XcmVersion? {
|
||||
val actualCheckedType = multiLocationType?.skipAliases() ?: return null
|
||||
val versionedType = getVersionedType(
|
||||
chainId = chainId,
|
||||
getCall = { moduleOrNull(xcmPalletName())?.callOrNull("reserve_transfer_assets") },
|
||||
argumentName = "dest"
|
||||
) ?: return null
|
||||
|
||||
val matchingEnumEntry = versionedType.elements.values.find { enumEntry -> enumEntry.value.skipAliases().value === actualCheckedType }
|
||||
?: run {
|
||||
Log.w("RealPalletXcmRepository", "Failed to find matching variant in versioned multiplication for type ${actualCheckedType.name}")
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
return enumValueOfOrNull<XcmVersion>(matchingEnumEntry.name)?.also {
|
||||
Log.d("RealPalletXcmRepository", "Identified xcm version for ${actualCheckedType.name} to be ${it.name}")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun lowestPresentXcmTypeVersionFromCallArgument(
|
||||
chainId: ChainId,
|
||||
getCall: (RuntimeMetadata) -> MetadataFunction?,
|
||||
argumentName: String,
|
||||
): XcmVersion? {
|
||||
val type = getVersionedType(chainId, getCall, argumentName) ?: return null
|
||||
|
||||
val allSupportedVersions = type.elements.values.map { it.name }
|
||||
val leastSupportedVersion = allSupportedVersions.min()
|
||||
|
||||
return enumValueOfOrNull<XcmVersion>(leastSupportedVersion)
|
||||
}
|
||||
|
||||
private suspend fun getVersionedType(
|
||||
chainId: ChainId,
|
||||
getCall: RuntimeMetadata.() -> MetadataFunction?,
|
||||
argumentName: String,
|
||||
): DictEnum? {
|
||||
return chainRegistry.withRuntime(chainId) {
|
||||
val call = getCall(runtime.metadata) ?: return@withRuntime null
|
||||
val argument = call.arguments.find { it.name == argumentName } ?: return@withRuntime null
|
||||
argument.type?.skipAliases() as? DictEnum
|
||||
}
|
||||
}
|
||||
}
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
package io.novafoundation.nova.feature_xcm_impl.builder
|
||||
|
||||
import io.novafoundation.nova.common.address.intoKey
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.BalanceOf
|
||||
import io.novafoundation.nova.feature_xcm_api.asset.MultiAssetFilter.Wild.All
|
||||
import io.novafoundation.nova.feature_xcm_api.asset.MultiAssetId
|
||||
import io.novafoundation.nova.feature_xcm_api.asset.intoMultiAssets
|
||||
import io.novafoundation.nova.feature_xcm_api.asset.withAmount
|
||||
import io.novafoundation.nova.feature_xcm_api.builder.XcmBuilder
|
||||
import io.novafoundation.nova.feature_xcm_api.builder.buyExecution
|
||||
import io.novafoundation.nova.feature_xcm_api.builder.depositAllAssetsTo
|
||||
import io.novafoundation.nova.feature_xcm_api.builder.fees.MeasureXcmFees
|
||||
import io.novafoundation.nova.feature_xcm_api.builder.payFees
|
||||
import io.novafoundation.nova.feature_xcm_api.builder.payFeesIn
|
||||
import io.novafoundation.nova.feature_xcm_api.builder.transferReserveAsset
|
||||
import io.novafoundation.nova.feature_xcm_api.builder.withdrawAsset
|
||||
import io.novafoundation.nova.feature_xcm_api.message.VersionedXcmMessage
|
||||
import io.novafoundation.nova.feature_xcm_api.message.XcmInstruction.BuyExecution
|
||||
import io.novafoundation.nova.feature_xcm_api.message.XcmInstruction.DepositAsset
|
||||
import io.novafoundation.nova.feature_xcm_api.message.XcmInstruction.DepositReserveAsset
|
||||
import io.novafoundation.nova.feature_xcm_api.message.XcmInstruction.InitiateReserveWithdraw
|
||||
import io.novafoundation.nova.feature_xcm_api.message.XcmInstruction.PayFees
|
||||
import io.novafoundation.nova.feature_xcm_api.message.XcmInstruction.TransferReserveAsset
|
||||
import io.novafoundation.nova.feature_xcm_api.message.XcmInstruction.WithdrawAsset
|
||||
import io.novafoundation.nova.feature_xcm_api.message.XcmMessage
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.AssetLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.ChainLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.MultiLocation.Interior.Here
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.MultiLocation.Junction.ParachainId
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.asLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.toMultiLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.XcmVersion
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.versionedXcm
|
||||
import io.novafoundation.nova.feature_xcm_api.weight.WeightLimit
|
||||
import io.novafoundation.nova.runtime.ext.Geneses
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.FullChainAssetId
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Test
|
||||
import java.math.BigInteger
|
||||
|
||||
|
||||
class RealXcmBuilderTest {
|
||||
|
||||
val amount = 100000000000000.toBigInteger()
|
||||
val buyExecutionFees = amount / 2.toBigInteger()
|
||||
|
||||
val testMeasuredFees = 10.toBigInteger()
|
||||
val testExactFees = 11.toBigInteger()
|
||||
|
||||
val recipient = ByteArray(32) { 1 }.intoKey()
|
||||
|
||||
val polkadot = ChainLocation(Chain.Geneses.POLKADOT, Here.asLocation())
|
||||
val pah = ChainLocation(Chain.Geneses.POLKADOT_ASSET_HUB, ParachainId(1000).asLocation())
|
||||
val hydration = ChainLocation(Chain.Geneses.HYDRA_DX, ParachainId(2034).asLocation())
|
||||
|
||||
val dot = Here.asLocation()
|
||||
val dotLocation = AssetLocation(FullChainAssetId(Chain.Geneses.POLKADOT, 0), dot)
|
||||
val dotOnPolkadot = MultiAssetId(dot.fromPointOfViewOf(polkadot.location))
|
||||
val dotOnPah = MultiAssetId(dot.fromPointOfViewOf(pah.location))
|
||||
val dotOnHydration = MultiAssetId(dot.fromPointOfViewOf(hydration.location))
|
||||
|
||||
|
||||
val xcmVersion = XcmVersion.V4
|
||||
|
||||
@Test
|
||||
fun `should build empty message`() = runBlocking {
|
||||
val expected = XcmMessage(emptyList())
|
||||
|
||||
val result = createBuilder(polkadot).build()
|
||||
|
||||
assertXcmMessageEquals(expected, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should build single chain message`() = runBlocking {
|
||||
val expected = XcmMessage(
|
||||
BuyExecution(dotOnPolkadot.withAmount(amount), WeightLimit.Unlimited),
|
||||
DepositAsset(All, recipient.toMultiLocation())
|
||||
)
|
||||
|
||||
val result = createBuilder(polkadot).apply {
|
||||
buyExecution(dot, amount, WeightLimit.Unlimited)
|
||||
depositAllAssetsTo(recipient)
|
||||
}.build()
|
||||
|
||||
assertXcmMessageEquals(expected, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should perform single context switch`() = runBlocking {
|
||||
val forwardedToHydration = XcmMessage(
|
||||
BuyExecution(dotOnHydration.withAmount(buyExecutionFees), WeightLimit.Unlimited),
|
||||
DepositAsset(All, recipient.toMultiLocation())
|
||||
)
|
||||
val expectedOnPolkadot = XcmMessage(
|
||||
TransferReserveAsset(
|
||||
assets = dotOnPolkadot.withAmount(amount).intoMultiAssets(),
|
||||
dest = hydration.location.fromPointOfViewOf(polkadot.location),
|
||||
xcm = forwardedToHydration
|
||||
)
|
||||
)
|
||||
|
||||
val result = createBuilder(polkadot).apply {
|
||||
// polkadot
|
||||
transferReserveAsset(dot, amount, hydration)
|
||||
|
||||
// hydration
|
||||
buyExecution(dot, buyExecutionFees, WeightLimit.Unlimited)
|
||||
depositAllAssetsTo(recipient)
|
||||
}.build()
|
||||
|
||||
assertXcmMessageEquals(expectedOnPolkadot, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should perform multiple context switches`() = runBlocking {
|
||||
val forwardedToHydration = XcmMessage(
|
||||
BuyExecution(dotOnHydration.withAmount(buyExecutionFees), WeightLimit.Unlimited),
|
||||
DepositAsset(All, recipient.toMultiLocation())
|
||||
)
|
||||
val forwardedToPolkadot = XcmMessage(
|
||||
BuyExecution(dotOnPolkadot.withAmount(buyExecutionFees), WeightLimit.Unlimited),
|
||||
DepositReserveAsset(
|
||||
assets = All,
|
||||
dest = hydration.location.fromPointOfViewOf(polkadot.location),
|
||||
xcm = forwardedToHydration
|
||||
)
|
||||
)
|
||||
val expectedOnPah = XcmMessage(
|
||||
WithdrawAsset(dotOnPah.withAmount(amount).intoMultiAssets()),
|
||||
InitiateReserveWithdraw(
|
||||
assets = All,
|
||||
reserve = polkadot.location.fromPointOfViewOf(pah.location),
|
||||
xcm = forwardedToPolkadot
|
||||
)
|
||||
)
|
||||
|
||||
val result = createBuilder(pah).apply {
|
||||
// on Pah
|
||||
withdrawAsset(dot, amount)
|
||||
initiateReserveWithdraw(All, reserve = polkadot)
|
||||
|
||||
// on Polkadot
|
||||
buyExecution(dot, buyExecutionFees, WeightLimit.Unlimited)
|
||||
depositReserveAsset(All, dest = hydration)
|
||||
|
||||
// on Hydration
|
||||
buyExecution(dot, buyExecutionFees, WeightLimit.Unlimited)
|
||||
depositAsset(All, recipient)
|
||||
}.build()
|
||||
|
||||
assertXcmMessageEquals(expectedOnPah, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should set PayFees in exact mode`() = runBlocking {
|
||||
val expected = XcmMessage(
|
||||
PayFees(dotOnPolkadot.withAmount(testExactFees)),
|
||||
DepositAsset(All, recipient.toMultiLocation())
|
||||
)
|
||||
|
||||
val result = createBuilder(polkadot).apply {
|
||||
payFees(dotOnPolkadot, testExactFees)
|
||||
depositAllAssetsTo(recipient)
|
||||
}.build()
|
||||
|
||||
assertXcmMessageEquals(expected, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should set PayFees in measured mode`() = runBlocking {
|
||||
val expected = XcmMessage(
|
||||
PayFees(dotOnPolkadot.withAmount(testMeasuredFees)),
|
||||
DepositAsset(All, recipient.toMultiLocation())
|
||||
)
|
||||
val expectedForMeasure = XcmMessage(
|
||||
PayFees(dotOnPolkadot.withAmount(BigInteger.ONE)),
|
||||
DepositAsset(All, recipient.toMultiLocation())
|
||||
)
|
||||
|
||||
val result = createBuilder(polkadot, validateMeasuringMessage = expectedForMeasure).apply {
|
||||
payFeesIn(dotLocation)
|
||||
depositAllAssetsTo(recipient)
|
||||
}.build()
|
||||
|
||||
assertXcmMessageEquals(expected, result)
|
||||
}
|
||||
|
||||
private fun assertXcmMessageEquals(expected: XcmMessage, actual: VersionedXcmMessage) {
|
||||
assertEquals(expected.versionedXcm(xcmVersion), actual)
|
||||
}
|
||||
|
||||
private fun createBuilder(
|
||||
origin: ChainLocation,
|
||||
validateMeasuringMessage: XcmMessage? = null
|
||||
): XcmBuilder {
|
||||
return RealXcmBuilder(origin, XcmVersion.V4, TestMeasureFees(validateMeasuringMessage))
|
||||
}
|
||||
|
||||
private inner class TestMeasureFees(
|
||||
private val validateMeasuringMessage: XcmMessage?
|
||||
) : MeasureXcmFees {
|
||||
|
||||
override suspend fun measureFees(
|
||||
message: VersionedXcmMessage,
|
||||
feeAsset: AssetLocation,
|
||||
chainLocation: ChainLocation
|
||||
): BalanceOf {
|
||||
validateMeasuringMessage?.let { assertXcmMessageEquals(validateMeasuringMessage, message) }
|
||||
return testMeasuredFees
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user